Authored by shangjf

品类商品列表相关代码 review by 孙凯

... ... @@ -24,6 +24,7 @@ import productListForShopInitialState from './reducers/productListForShop/produc
import ProductListForShopContainer from './containers/ProductListForShopContainer';
import ProductListPoolContainer from './containers/ProductListPoolContainer';
import ProductListPoolInitialState from './reducers/productListPool/productListPoolInitialState';
import CategoryProListInitialState from './reducers/categoryProList/categoryProListInitialState';
import screenInitialState from './reducers/screen/screenInitialState';
import screenCategoryInitialState from './reducers/screenCategory/screenCategoryInitialState';
... ... @@ -33,6 +34,8 @@ import ScreenContainer from './containers/ScreenContainer';
import ScreenCategoryContainer from './containers/ScreenCategoryContainer';
import ScreenSubContainer from './containers/ScreenSubContainer';
import CategoryProductListContainer from './containers/CategoryProListContainer'
import {
setPlatform,
... ... @@ -56,6 +59,7 @@ function getInitialState() {
screenSub: (new screenSubInitialState()),
productListForShop: (new productListForShopInitialState()),
productListPool: (new ProductListPoolInitialState()),
categoryProList: (new CategoryProListInitialState()),
};
return _initState;
}
... ... @@ -68,6 +72,7 @@ export default function native(platform) {
const store = configureStore(getInitialState());
store.dispatch(setPlatform(platform));
let {host,serviceHost, channel,type,brand_id,shop_id,dictParams,saleType,activityId} = this.props;
store.dispatch(setHost(host));
store.dispatch(setServiceHost(serviceHost));
store.dispatch(setChannel(channel));
... ... @@ -114,7 +119,14 @@ export default function native(platform) {
<ProductListPoolContainer />
</Provider>
);
} else if (type == 'YH_CategoryProList') {
return (
<Provider store={store}>
<CategoryProductListContainer />
</Provider>
);
}
return null;
}
});
... ...
'use strict'
import React, {Component} from 'react';
import ReactNative, {
StyleSheet,
Dimensions,
NativeAppEventEmitter,
View,
} from 'react-native'
import YH_CategoryListHeader from './YH_CategoryListHeader'
export default class CategoryListHeader extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.clearClick = this.clearClick.bind(this);
}
onClick(event: Event) {
let type = event.nativeEvent.type;
let data = event.nativeEvent.data;
if (type == 'clear') {
this.props.clearClick&&this.props.clearClick(data);
}else if (type == 'click') {
this.props.onClick&&this.props.onClick(data);
}
}
clearClick(event: Event) {
let item = event.nativeEvent;
console.log(item);
}
render() {
let {dataSource} = this.props;
return (
<View style={styles.container}>
<YH_CategoryListHeader
style={styles.header}
dataSource={dataSource.toJS()}
onClick={this.onClick}
/>
</View>
)
}
}
let {width} = Dimensions.get('window');
const DEVICE_WIDTH_RATIO = width / 375;
let styles = StyleSheet.create({
container: {
width,
height: 45,
backgroundColor: 'white',
},
header: {
width: width,
height: 45,
},
});
... ...
'use strict'
import React, {Component} from 'react';
import ReactNative, {
StyleSheet,
Dimensions,
InteractionManager,
NativeAppEventEmitter,
View,
ListView,
} from 'react-native'
import LoadingIndicator from '../../../common/components/LoadingIndicator';
import LoadMoreIndicator from '../../../common/components/LoadMoreIndicator';
import BrandProductFilter from '../cell/BrandProductFilter';
import ProductListCell from '../../../common/components/ListCell/ProductListCell';
import CategoryListHeader from './CategoryListHeader'
export default class CategoryProList extends Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => !Immutable.is(r1, r2),
sectionHeaderHasChanged: (s1, s2) => !Immutable.is(s1, s2),
});
}
renderRow(rowData, sectionID, rowID, highlightRow) {
switch(sectionID) {
case 'productListHeader': {
let noFilterValue = true;
this.props.filterFactors.map((value, key) => {
if (value) {
noFilterValue = false;
}
});
let productListIsEmpty = !this.props.productList || !this.props.productList.list || this.props.productList.list.size == 0;
if (productListIsEmpty && noFilterValue) {
return null;
}
return (
<View style={styles.brandFilterContainer} onLayout={(evt) => {yPosition = evt.nativeEvent.layout.y;}}>
<BrandProductFilter
onPressFilter={this.props.onPressProductFilter}
selectOrder={this.props.productList.order}
/>
</View>
)
}
case 'categoryListHeader': {
let {
standardFilterList,
} = this.props;
return (
<CategoryListHeader
dataSource={standardFilterList}
onClick={this.props.onClickStandardFilter}
clearClick={this.props.clearClickStandardFilter}
/>
)
}
case 'productList': {
let similarIndex = this.props.similarIndex;
let paddingLeft = rowID % 2 == 1 ? rowMarginHorizontal / 2 : rowMarginHorizontal;
let customStyle = rowID == 0 || rowID == 1 ? {paddingLeft} : {paddingLeft};
return (
<ProductListCell
style={[styles.listContainer, customStyle]}
key={'row' + rowID}
rowID={rowID}
data={rowData}
similarIndex={similarIndex}
onPressProduct={this.props.onPressProductCell}
onLongPressProduct={this.props.onLongPressProduct}
onPressFindSimilar={this.props.onPressFindSimilar}
onPressDislike={this.props.onPressDislike}/>
)
}
default:
return null;
}
return null;
}
render() {
let {
productList,
standardFilterList,
} = this.props;
let isFetching = (productList.isFetching && productList.currentPage == 0);
let dataSource = {};
if (productList.list.toArray().length > 0) {
if (standardFilterList&&standardFilterList.size > 0) {
dataSource = {
productListHeader: ['1'],
categoryListHeader: ['1'],
productList: productList.list.toArray(),
};
}else {
dataSource = {
productListHeader: ['1'],
productList: productList.list.toArray(),
};
}
}
let isLoadingMore = productList.isFetching && productList.currentPage > 0;
let endReached = productList.endReached;
return (
<View style={styles.container}>
<ListView
ref='CategoryProList'
contentContainerStyle={styles.contentContainer}
enableEmptySections={true}
dataSource={this.dataSource.cloneWithRowsAndSections(dataSource)}
renderRow={this.renderRow}
renderFooter={()=>{
if (endReached) {
return <View style={styles.placeholder} />;
} else {
return <LoadMoreIndicator isVisible={isLoadingMore} animating={true}/>;
}
}}
onEndReached={() => {
if (productList && productList.list && productList.list.size > 0) {
this.props.onEndReached && this.props.onEndReached();
}
}}
/>
<LoadingIndicator
isVisible={isFetching}
/>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let rowWidth = Math.ceil(137.5 * width / 320);
let rowHeight = Math.ceil(254 * width / 320);
let rowMarginTop = Math.ceil(10 * width / 320);
let rowMarginHorizontal = (width - rowWidth * 2) / 3;
let yPosition = 0;
let styles = StyleSheet.create({
container: {
flex: 1,
},
contentContainer:{
flexDirection: 'row',
flexWrap: 'wrap',
},
placeholder: {
width,
height: 15,
},
listContainer: {
width: width / 2,
},
brandFilterContainer: {
marginLeft: -1,
width: width + 2,
height: 40,
},
categoryListContainer: {
width: width,
height: 45,
}
});
... ...
import React from 'react';
import ReactNative from 'react-native';
import ImmutablePropTypes from 'react-immutable-proptypes';
let {
requireNativeComponent,
View
} = ReactNative;
let YH_CategoryListHeader = requireNativeComponent('YH_CategoryListHeader', null);
export default class CategoryListHeader extends React.Component {
static propTypes = {
...View.propTypes // 包含默认的View的属性
};
constructor(props) {
super(props);
}
render() {
return <YH_CategoryListHeader {...this.props} data={this.props.data} />;
}
}
... ...
... ... @@ -59,4 +59,6 @@ export default keyMirror({
GET_ACTIVITY_REQUEST: null,
GET_ACTIVITY_SUCCESS: null,
GET_ACTIVITY_FAILURE: null,
SELECT_STANDARDFILTER_ITEM: null,
});
... ...
'use strict'
import React, {Component} from 'react';
import ReactNative, {
StyleSheet,
Platform,
InteractionManager,
NativeAppEventEmitter,
} from 'react-native'
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {Map} from 'immutable';
import * as categoryProListActions from '../reducers/categoryProList/categoryProListActions';
import CategoryProList from '../components/category/CategoryProList';
const actions = [
categoryProListActions,
];
function mapStateToProps(state) {
return {
...state
};
}
function mapDispatchToProps(dispatch) {
const creators = Map()
.merge(...actions)
.filter(value => typeof value === 'function')
.toObject();
return {
actions: bindActionCreators(creators, dispatch),
dispatch
};
}
class CategoryProductListContainer extends Component {
constructor(props) {
super(props);
this._onEndReached = this._onEndReached.bind(this);
this._onLongPressProduct = this._onLongPressProduct.bind(this);
this._onPressFindSimilar = this._onPressFindSimilar.bind(this);
this._onPressProductCell = this._onPressProductCell.bind(this);
this._onPressProductFilter = this._onPressProductFilter.bind(this);
this._onClickStandardFilter = this._onClickStandardFilter.bind(this);
this._clearClickStandardFilter = this._clearClickStandardFilter.bind(this);
this.subscription = NativeAppEventEmitter.addListener(
'updateProductByFilter',
(array) => {
if (array.length > 0) {
this.props.actions.setProductFilterFactors(array);
this.props.actions.resetListPageInfo();
this.props.actions.getProductList(true);
this.props.actions.setSimilarIndex(-1);
}
}
);
}
componentDidMount() {
this.props.actions.getProductList();
}
componentWillUnmount() {
this.subscription && this.subscription.remove();
}
_onPressProductFilter(value) {
this.props.actions.resetListPageInfo();
this.props.actions.setProductListFilter(value);
this.props.actions.getProductList(true);
this.props.actions.setSimilarIndex(-1);
}
_onEndReached() {
this.props.actions.getProductList();
}
_onPressProductCell(product, rowId=0) {
let productSkn = product && product.get('product_skn', 0);
if (!productSkn) {
return;
}
let url = `http://m.yohobuy.com?openby:yohobuy={"action":"go.productDetail","params":{"product_skn":"${productSkn}"}}`;
ReactNative.NativeModules.YH_CommonHelper.jumpWithUrl(url);
}
_onLongPressProduct(rowID) {
if (rowID) {
this.props.actions.setSimilarIndex(rowID);
}
}
_onPressFindSimilar(product) {
if (!product) {
return;
}
ReactNative.NativeModules.YH_CommonHelper.jumpFindSimilar(product.toJS());
}
_clearClickStandardFilter(standarFilterItem) {
this.props.actions.clearStandardFilter(standarFilterItem);
this.props.actions.resetListPageInfo();
this.props.actions.getProductList(true);
this.props.actions.setSimilarIndex(-1);
}
_onClickStandardFilter(standarFilterItem) {
this.props.actions.selectStandardFilter(standarFilterItem);
this.props.actions.resetListPageInfo();
this.props.actions.getProductList(true);
this.props.actions.setSimilarIndex(-1);
}
render() {
let {
productList,
similarIndex,
filterFactors,
filterNameFactors,
standardFilterList
} = this.props.categoryProList;
return (
<CategoryProList
similarIndex={similarIndex}
productList={productList}
filterFactors={filterFactors}
filterNameFactors={filterNameFactors}
standardFilterList={standardFilterList}
onClickStandardFilter={this._onClickStandardFilter}
clearClickStandardFilter={this._clearClickStandardFilter}
onEndReached={this._onEndReached}
onPressProductCell={this._onPressProductCell}
onLongPressProduct={this._onLongPressProduct}
onPressFindSimilar={this._onPressFindSimilar}
onPressProductFilter={this._onPressProductFilter}
/>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CategoryProductListContainer);
... ...
'use strict';
import ReactNative from 'react-native';
import CategoryProListService from '../../services/CategoryProListService';
import * as _ from 'lodash';
import Utils from '../../utils/Utils'
const {
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAILURE,
PRODUCT_FILTER_ACTION,
RESET_LIST_PAGE_INFO,
SET_PRODUCT_LIST_FILTER,
SET_SIMILAR_PRODUCT_INDEX,
SELECT_STANDARDFILTER_ITEM
} = require('../../constants/actionTypes').default;
export function productListRequest() {
return {
type: PRODUCT_LIST_REQUEST,
};
}
export function productListSuccess(json) {
return {
type: PRODUCT_LIST_SUCCESS,
payload: json
}
}
export function productListFailure(error) {
return {
type: PRODUCT_LIST_FAILURE,
payload: error
}
}
export function getProductList(reload=false) {
return (dispatch, getState) => {
let {app, categoryProList} = getState();
let {productList, filterFactors, standardFilterList} = categoryProList;
let {originParams} = app;
if (reload) {
} else {
if (productList.isFetching || productList.endReached || productList.error) {
return;
}
}
let page = productList.currentPage + 1;
let pageSize = productList.pageSize;
let order = productList.order;
let bSelectedFilterFactor,allFilterFactors;
allFilterFactors = filterFactors.toJS();
for (let prop in allFilterFactors) {
if (allFilterFactors.hasOwnProperty(prop)) {
if (allFilterFactors[prop] === '' || !allFilterFactors[prop]) {
delete allFilterFactors[prop];
}
if (prop == 'sizeKey' && allFilterFactors[prop]) {
allFilterFactors['size'] = allFilterFactors[prop];
delete allFilterFactors[prop];
}
}
}
let standard = '';
let standardList = standardFilterList.toJS();
for (var i = 0; i < standardList.length; i++) {
let filterItem = standardList[i]
if (filterItem.isSelect) {
let selectFilter = filterItem.selectFilter;
if (standard.length == 0) {
standard = filterItem.standard_id + '_' + selectFilter.standard_id;
} else {
standard = standard + ',' + filterItem.standard_id + '_' + selectFilter.standard_id;
}
}
}
allFilterFactors['standard'] = standard;
console.log(allFilterFactors);
dispatch(productListRequest());
return new CategoryProListService(app.host).productList(page, pageSize, originParams, allFilterFactors, order, '')
.then(json => {
let payload = Utils.parseProductList(json);
payload.endReached = payload.currentPage == payload.pageCount;
if (payload.currentPage > 1) {
let oldList = productList.list.toJS();
let list = [...oldList, ...payload.list];
payload.list = list;
}
let categoryFilterList = payload.categoryFilterList;
let filterCategoryDetailFilterList = payload.filterCategoryDetailFilterList;
let filters = Utils.parsecategoryFilter({categoryFilterList,filterCategoryDetailFilterList});
ReactNative.NativeModules.YH_ProductListRNViewHelper.setFilterData(filters);
ReactNative.NativeModules.YH_ScreenCategoryViewHelper.enableBrandFilter();
dispatch(productListSuccess(payload));
})
.catch(error => {
dispatch(productListFailure(error));
});
};
}
export function resetListPageInfo() {
return {
type: RESET_LIST_PAGE_INFO,
}
}
export function setProductListFilter(value) {
return {
type: SET_PRODUCT_LIST_FILTER,
payload: value
}
}
export function setSimilarIndex(rowID) {
return {
type: SET_SIMILAR_PRODUCT_INDEX,
payload: rowID
}
}
export function setProductFilterFactors(array) {
return (dispatch, getState) => {
let {app, categoryProList} = getState();
let {filterFactors} = categoryProList;
filterFactors = filterFactors.toJS();
for (var i = 0; i < array.length; i++) {
let item = array[i];
filterFactors[item._key] = item._value;
}
dispatch({
type: PRODUCT_FILTER_ACTION,
payload: filterFactors
});
};
}
export function selectStandardFilter(item) {
return (dispatch, getState) => {
let {categoryProList} = getState();
let {standardFilterList} = categoryProList;
standardFilterList = standardFilterList ? standardFilterList.toJS() : [];
for (var i = 0; i < standardFilterList.length; i++) {
let filterItem = standardFilterList[i];
if (filterItem.standard_id == item.standard_id) {
filterItem.isSelect = true;
filterItem.selectFilter = item.selectFilter;
break
}
}
dispatch({
type: SELECT_STANDARDFILTER_ITEM,
payload: standardFilterList
})
}
}
export function clearStandardFilter(item) {
return (dispatch, getState) => {
let {categoryProList} = getState();
let {standardFilterList} = categoryProList;
standardFilterList = standardFilterList ? standardFilterList.toJS() : [];
for (var i = 0; i < standardFilterList.length; i++) {
let filterItem = standardFilterList[i];
if (filterItem.standard_id == item.standard_id) {
filterItem.isSelect = false;
filterItem.selectFilter = {
standard_id: '',
standard_name: '',
};
break
}
}
dispatch({
type: SELECT_STANDARDFILTER_ITEM,
payload: standardFilterList
})
}
}
... ...
'use strict';
import {Record, List, Map} from 'immutable';
let InitialState = Record({
similarIndex : -1,
productList: new (Record({
isFetching: false,
error: null,
list: List(),
order: '',
currentPage: 0,
pageCount: 0,
pageSize: 60,//60,
total: 0,
endReached: false,
sourceType: 0, // 0 - 默认,1 - 购,全球2 - 奥莱
})),
categoryFilterList: List(),
filterCategoryDetailFilterList: List(),
standardFilterList: List(),
filterFactors: new (Record({
gender: '', //性别
color: '', //颜色
price: '', //价格
sizeKey: '', //尺码
p_d: '', //折扣
sort: '', //品类
brand: '', //品牌
})),
filterNameFactors: new (Record({
gender: '所有性别', //性别
color: '所有颜色', //颜色
price: '所有价格', //价格
sizeKey: '所有尺码', //尺码
p_d: '所有折扣', //折扣
sort: '所有品类', //品类
brand: '所有品牌', //品牌
})),
});
export default InitialState;
... ...
'use strict';
import InitialState from './categoryProListInitialState';
import Immutable, {Map} from 'immutable';
const {
RESET_LIST_PAGE_INFO,
SET_PRODUCT_LIST_FILTER,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAILURE,
PRODUCT_FILTER_ACTION,
SET_SIMILAR_PRODUCT_INDEX,
SELECT_STANDARDFILTER_ITEM
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function categoryProListReducer(state=initialState, action) {
switch(action.type) {
case RESET_LIST_PAGE_INFO: {
return state.setIn(['productList', 'currentPage'], 0)
.setIn(['productList', 'pageCount'], 0)
.setIn(['productList', 'total'], 0)
.setIn(['productList', 'endReached'], false);
}
case SET_PRODUCT_LIST_FILTER: {
return state.setIn(['productList', 'order'], action.payload);
}
case PRODUCT_LIST_REQUEST: {
return state.setIn(['productList', 'isFetching'], true)
.setIn(['productList', 'error'], null);
}
case PRODUCT_LIST_SUCCESS: {
let {
list,
categoryFilterList,
filterCategoryDetailFilterList,
standard,
currentPage,
pageCount,
total,
recId,
endReached,
} = action.payload;
let newState = state.setIn(['productList', 'isFetching'], false)
.setIn(['productList', 'error'], null)
.setIn(['productList', 'list'], Immutable.fromJS(list))
.setIn(['productList', 'currentPage'], currentPage)
.setIn(['productList', 'pageCount'], pageCount)
.setIn(['productList', 'total'], total)
.setIn(['productList', 'endReached'], endReached);
if (currentPage == 1 && state.categoryFilterList.size == 0 && state.filterCategoryDetailFilterList.size == 0) {
newState = newState.set('categoryFilterList', Immutable.fromJS(categoryFilterList))
.set('filterCategoryDetailFilterList', Immutable.fromJS(filterCategoryDetailFilterList));
}
if (currentPage == 1 && state.standardFilterList.size == 0) {
newState = newState.set('standardFilterList', Immutable.fromJS(standard));
}
return newState;
}
case PRODUCT_LIST_FAILURE: {
return state.setIn(['productList', 'isFetching'], false)
.setIn(['productList', 'error'], action.payload);
}
case PRODUCT_FILTER_ACTION: {
return state.set('filterFactors', Immutable.fromJS(action.payload));
}
case SET_SIMILAR_PRODUCT_INDEX: {
return state.set('similarIndex', action.payload);
}
case SELECT_STANDARDFILTER_ITEM: {
return state.set('standardFilterList', Immutable.fromJS(action.payload));
}
}
return state;
}
... ...
... ... @@ -8,6 +8,8 @@ import productForBrand from './productListForBrand/productListForBrandReducer';
import productListForShop from './productListForShop/productListForShopReducer';
import productListPool from './productListPool/productListPoolReducer';
import categoryProList from './categoryProList/categoryProListReducer';
const rootReducer = combineReducers({
app,
productForBrand,
... ... @@ -16,6 +18,7 @@ const rootReducer = combineReducers({
screenSub,
productListForShop,
productListPool,
categoryProList,
});
export default rootReducer;
... ...
'use strict';
import Request from '../../common/services/Request';
export default class CategoryProListService {
constructor(host) {
let baseURL = 'http://api.yoho.cn';
if(host){
baseURL = host;
}
this.api = new Request(baseURL);
}
async productList(page=1, limit=60, originParams={}, filterFactors={}, order, firstProductSkn='') {
return await this.api.get({
url: '',
body: {
method: 'app.search.category',
page,
limit,
...originParams,
...filterFactors,
fromPage: 'iFP_CategoryProList',
order,
firstProductSkn
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...
... ... @@ -262,10 +262,23 @@ function parseProductList(json) {
}
let recId = randomString(40);
let standard = json && json.standard ? json.standard : [];
for (var i = 0; i < standard.length; i++) {
let standardItem = standard[i]
standardItem.isSelect = false;
standardItem.selectFilter = {
standard_id: '',
standard_name: '',
}
}
return {
list,
categoryFilterList,
filterCategoryDetailFilterList,
standard,
currentPage,
pageCount,
total,
... ...