Authored by 于良

结构调整 review by 孙凯

... ... @@ -18,9 +18,13 @@ import configureStore from './store/configureStore';
import {Record, List, Map} from 'immutable';
import appInitialState from './reducers/app/appInitialState';
import classifyInitialState from './reducers/classify/classifyInitialState';
import brandInitialState from './reducers/brand/brandInitialState';
import categoryInitialState from './reducers/category/categoryInitialState';
import interestInitialState from './reducers/interest/interestInitialState';
import ClassifyContainer from './containers/ClassifyContainer';
import CategoryContainer from './containers/CategoryContainer';
import BrandContainer from './containers/BrandContainer';
import InterestContainer from './containers/InterestContainer';
import {
setPlatform,
... ... @@ -28,14 +32,13 @@ import {
setServiceHost,
} from './reducers/app/appActions';
import {
setType,
} from './reducers/classify/classifyActions';
function getInitialState() {
const _initState = {
app: (new appInitialState()),
classify: (new classifyInitialState()),
brand: (new brandInitialState()),
category: (new categoryInitialState()),
interest: (new interestInitialState()),
};
return _initState;
}
... ... @@ -44,16 +47,27 @@ export default function native(platform) {
let YH_Classify = React.createClass({
_renderContainer() {
if (this.props.type == 0) {
return <CategoryContainer />;
} else if (this.props.type == 1) {
return <BrandContainer />;
} else if (this.props.type == 2) {
return <InterestContainer />;
}
return null;
},
render() {
const store = configureStore(getInitialState());
store.dispatch(setPlatform(platform));
store.dispatch(setHost(this.props.host));
store.dispatch(setServiceHost(this.props.serviceHost));
store.dispatch(setType(this.props.type));
return (
<Provider store={store}>
<ClassifyContainer />
{this._renderContainer()}
</Provider>
);
}
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
Image,
ListView,
StyleSheet,
Dimensions,
TouchableOpacity,
} from 'react-native';
export default class Brand extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<Text>{'品牌'}</Text>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let styles = StyleSheet.create({
});
... ...
... ... @@ -11,188 +11,29 @@ import ReactNative, {
TouchableOpacity,
} from 'react-native';
import Banner from '../../../common/components/Banner';
import SlicedImage from '../../../common/components/SlicedImage';
import AutoSizeImage from './AutoSizeImage';
import LoadingIndicator from '../../../common/components/LoadingIndicator';
import Prompt from './Prompt';
export default class CouponCenter extends Component {
export default class Category extends Component {
constructor(props) {
super(props);
this._renderRow = this._renderRow.bind(this);
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => !Immutable.is(r1, r2),
});
}
_renderRow(rowData: object, sectionID: number, rowID: number) {
let data = rowData.get('data');
switch (rowData.get('templateName')) {
case 'carousel_banner':
case 'focus':
return (
<Banner
data={data}
width={width}
height={bannerHeight}
onPress={this.props.onPressBanner}
/>
);
case 'text':
return (
<Text style={styles.text}>{data}</Text>
);
case 'single_image':
let src = SlicedImage.getSlicedUrl(data.get('src'), 0, 0, 2);
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
this.props.onPressImage && this.props.onPressImage(data.get('url'));
}}
>
<AutoSizeImage src={src}/>
</TouchableOpacity>
);
case 'getCoupon':
if(!data.get('couponID')){
return null;
}
let bg = SlicedImage.getSlicedUrl(data.get('image').get('src'), 0, 0, 2);
let optImage;
let resizeMode = 'contain';
let imageStyle = {};
if (data.get('status') == 1) {
optImage = require('../../images/click-txt.png');
resizeMode = 'contain';
let marginTop = (rightHeight - 27) / 2;
let marginLeft = (rightWidth - 26) / 2
imageStyle = {width: 26, height: 27, marginTop, marginLeft};
} else if (data.get('status') == 2) {
optImage = require('../../images/zero.png');
resizeMode = 'contain';
imageStyle = {marginTop: -9};
} else if (data.get('status') == 3) {
optImage = require('../../images/received.png');
resizeMode = 'contain';
imageStyle = {marginTop: -9};
}
return (
<Image source={{uri: bg}} style={styles.couponContainer}>
<TouchableOpacity
style={styles.couponLeft}
activeOpacity={0.8}
onPress={() => {
this.props.onPressCoupon && this.props.onPressCoupon(data.get('image').get('url'));
}}
>
</TouchableOpacity>
<TouchableOpacity
style={styles.couponRight}
activeOpacity={0.8}
onPress={() => {
if (data.get('status') == 1) {
this.props.onGetCoupon && this.props.onGetCoupon(data.get('couponID'));
} else {
this.props.onPressCoupon && this.props.onPressCoupon(data.get('image').get('url'));
}
}}
>
<Image source={optImage} resizeMode={resizeMode} style={[styles.rightImage, imageStyle]}/>
</TouchableOpacity>
</Image>
);
default:
return null;
}
}
render() {
let data = this.props.floors.toArray();
let errorMsg = this.props.error && this.props.error.message;
let errorCode = this.props.error && this.props.error.code;
let desc = errorMsg;
if (errorCode && errorCode == 401) {
} else {
let netError = errorMsg && (errorMsg.indexOf('Network request failed') != -1 || errorMsg.indexOf('Request timeout') != -1);
if (netError) {
desc = '网络异常!';
}
}
return (
<View style={styles.container}>
<ListView
enableEmptySections={true}
dataSource={this.dataSource.cloneWithRows(data)}
renderRow={this._renderRow}
/>
<LoadingIndicator
isVisible={this.props.isFetching}
/>
{this.props.showSuccessTip ? <Prompt
icon={require('../../images/hud_success.png')}
text={'领取成功'}
duration={800}
onPromptHidden={this.props.onPromptHidden}
/> : null}
{this.props.showNetErrorTip ? <Prompt
text={desc}
duration={800}
onPromptHidden={this.props.onNetPromptHidden}
/> : null}
<Text>{'品类'}</Text>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let bannerHeight = Math.ceil((310 / 640) * width);
let couponHeight = Math.ceil((180 / 640) * width);
let rightWidth = Math.ceil((110 / 640) * width);
let rightHeight = Math.ceil((180 / 110) * rightWidth);
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
textContainer: {
},
text: {
fontFamily: 'helvetica',
fontSize: 18,
lineHeight: 48,
textAlign: 'center',
paddingBottom: (48 - 18) / 2,
},
couponContainer: {
width,
height: couponHeight,
flexDirection: 'row',
},
couponLeft: {
flex: 0.78,
},
couponRight: {
flex: 0.22,
// justifyContent: 'center',
// alignItems: 'center'
},
rightImage: {
width: rightWidth,
height: rightHeight,
// backgroundColor: 'red'
},
let styles = StyleSheet.create({
});
... ...
... ... @@ -70,6 +70,8 @@ export default class Interest extends Component {
return (
<View style={styles.container}>
{showLoginTip ? <LoginTip onInterestLogin={this.props.onInterestLogin} /> : null}
<ListView
ref={(c) => {
this.listView = c;
... ... @@ -91,14 +93,6 @@ export default class Interest extends Component {
this.props.onEndReached && this.props.onEndReached();
}
}}
renderHeader={() => {
return <LoginTip />
if (showLoginTip) {
return <LoginTip />
} else {
return null;
}
}}
renderFooter={() => {
if (endReached) {
return <LoadMoreIndicator
... ...
... ... @@ -64,7 +64,6 @@ let styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
// alignSelf: 'center',
justifyContent: 'space-between',
width,
height: containerHeight,
... ... @@ -115,7 +114,7 @@ let styles = StyleSheet.create({
bottomView: {
position: 'absolute',
left: 0,
bottom: 2,
bottom: 0,
width,
height: 2,
backgroundColor: '#f0f0f0',
... ...
'use strict'
import React, {Component} from 'react';
import ReactNative, {
StyleSheet,
Dimensions,
Platform,
View,
Text,
NativeModules,
InteractionManager,
NativeAppEventEmitter,
} from 'react-native'
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {Map} from 'immutable';
import * as brandActions from '../reducers/brand/brandActions';
import Brand from '../components/brand/Brand';
const actions = [
brandActions,
];
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 BrandContainer extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
let {brand} = this.props;
return (
<View style={styles.container}>
<Brand
/>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(BrandContainer);
... ...
'use strict'
import React, {Component} from 'react';
import ReactNative, {
StyleSheet,
Dimensions,
Platform,
View,
Text,
NativeModules,
InteractionManager,
NativeAppEventEmitter,
} from 'react-native'
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {Map} from 'immutable';
import * as categoryActions from '../reducers/category/categoryActions';
import Category from '../components/category/Category';
const actions = [
categoryActions,
];
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 CategoryContainer extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
let {category} = this.props;
return (
<View style={styles.container}>
<Category
/>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CategoryContainer);
... ...
... ... @@ -15,13 +15,13 @@ import ReactNative, {
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {Map} from 'immutable';
import * as classifyActions from '../reducers/classify/classifyActions';
import * as interestActions from '../reducers/interest/interestActions';
import Interest from '../components/interest/Interest';
const actions = [
classifyActions,
interestActions,
];
function mapStateToProps(state) {
... ... @@ -43,17 +43,17 @@ function mapDispatchToProps(dispatch) {
};
}
class ClassifyContainer extends Component {
class InterestContainer extends Component {
constructor(props) {
super(props);
this._onInterestLike = this._onInterestLike.bind(this);
this._onInterestRefresh = this._onInterestRefresh.bind(this);
this._onInterestEndReached = this._onInterestEndReached.bind(this);
this._renderClassify = this._renderClassify.bind(this);
this._onPressInterestBrand = this._onPressInterestBrand.bind(this);
this._onPressInterestActivity = this._onPressInterestActivity.bind(this);
this._onPressInterestProduct = this._onPressInterestProduct.bind(this);
this._onInterestLogin = this._onInterestLogin.bind(this);
this.subscription = NativeAppEventEmitter.addListener(
'ChannelDidChangeEvent',
... ... @@ -143,19 +143,20 @@ class ClassifyContainer extends Component {
NativeModules.YH_CommonHelper.jumpWithUrl(url);
}
_renderClassify() {
let {type, interest} = this.props.classify;
if (type == 0) {
return (
<Text>品类</Text>
);
} else if (type == 1) {
return (
<Text>品牌</Text>
);
} else if (type == 2) {
return (
_onInterestLogin() {
ReactNative.NativeModules.YH_CommonHelper.login()
.then(data => {
})
.catch(error => {
});
}
render() {
let {interest} = this.props;
return (
<View style={styles.container}>
<Interest
data={interest}
onInterestLike={this._onInterestLike}
... ... @@ -164,18 +165,8 @@ class ClassifyContainer extends Component {
onPressBrand={this._onPressInterestBrand}
onPressProduct={this._onPressInterestProduct}
onPressActivity={this._onPressInterestActivity}
onInterestLogin={this._onInterestLogin}
/>
);
}
return null;
}
render() {
return (
<View style={styles.container}>
{this._renderClassify()}
</View>
);
}
... ... @@ -188,4 +179,4 @@ let styles = StyleSheet.create({
});
export default connect(mapStateToProps, mapDispatchToProps)(ClassifyContainer);
export default connect(mapStateToProps, mapDispatchToProps)(InterestContainer);
... ...
'use strict';
import ReactNative from 'react-native';
import BrandService from '../../services/BrandService';
const {
} = require('../../constants/actionTypes').default;
... ...
'use strict';
import {Record, List, Map} from 'immutable';
let InitialState = Record({
});
export default InitialState;
... ...
'use strict';
import InitialState from './brandInitialState';
import Immutable, {Map} from 'immutable';
const {
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function brandReducer(state=initialState, action) {
switch(action.type) {
}
return state;
}
... ...
'use strict';
import ReactNative from 'react-native';
import CategoryService from '../../services/CategoryService';
const {
} = require('../../constants/actionTypes').default;
... ...
'use strict';
import {Record, List, Map} from 'immutable';
let InitialState = Record({
});
export default InitialState;
... ...
'use strict';
import InitialState from './categoryInitialState';
import Immutable, {Map} from 'immutable';
const {
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function categoryReducer(state=initialState, action) {
switch(action.type) {
}
return state;
}
... ...
import {combineReducers} from 'redux';
import app from './app/appReducer';
import classify from './classify/classifyReducer';
import category from './category/categoryReducer';
import brand from './brand/brandReducer';
import interest from './interest/interestReducer';
const rootReducer = combineReducers({
app,
classify,
category,
brand,
interest,
});
export default rootReducer;
... ...
'use strict';
import ReactNative from 'react-native';
import ClassifyService from '../../services/ClassifyService';
const Platform = require('Platform');
import InterestService from '../../services/InterestService';
const {
SET_TYPE,
... ... @@ -68,8 +67,7 @@ export function interestListFailure(error) {
*/
export function interestList(reload=false) {
return (dispatch, getState) => {
let {app, classify} = getState();
let {interest} = classify;
let {app, interest} = getState();
if (reload) {
// 强制刷新数据
... ... @@ -82,7 +80,7 @@ export function interestList(reload=false) {
let fetchList = (gender, uid, page, pageSize, sourcePage) => {
dispatch(interestListRequest(reload));
return new ClassifyService(app.serviceHost).fetchInterestList(gender, uid, page, pageSize, sourcePage)
return new InterestService(app.serviceHost).fetchInterestList(gender, uid, page, pageSize, sourcePage)
.then(json => {
let payload = parseInterestList(json);
payload.endReached = payload.currentPage == payload.pageCount;
... ... @@ -168,13 +166,12 @@ export function interestLikeFailure(error) {
export function addFavorite(item, index) {
return (dispatch, getState) => {
let {app, classify} = getState();
// let {interest} = classify;
let {app, interest} = getState();
let likeOpt = (id, type='brand', uid=0, fromPage='') => {
dispatch(interestLikeStateChange('Y', index));
dispatch(interestLikeRequest());
return new ClassifyService(app.host).addFavorite(id, type, uid, fromPage)
return new InterestService(app.host).addFavorite(id, type, uid, fromPage)
.then(json => {
dispatch(interestLikeSuccess(json));
})
... ...
... ... @@ -3,22 +3,17 @@
import {Record, List, Map} from 'immutable';
let InitialState = Record({
type: 0, // 0 - 品类,1 - 品牌,2 - 关注
category: null,
brand: null,
interest: new (Record({
ptr: false, // 是否下拉刷新
isFetching: false,
error: null,
list: List(),
currentPage: 0,
pageCount: 0,
pageSize: 10,
total: 0,
endReached: false,
sourceType: 0, // 0 - 默认,1 - 全球购,2 - 奥莱
showLoginTip: false,
})),
ptr: false, // 是否下拉刷新
isFetching: false,
error: null,
list: List(),
currentPage: 0,
pageCount: 0,
pageSize: 10,
total: 0,
endReached: false,
sourceType: 0, // 0 - 默认,1 - 全球购,2 - 奥莱
showLoginTip: false,
});
export default InitialState;
... ...
'use strict';
import InitialState from './classifyInitialState';
import InitialState from './interestInitialState';
import Immutable, {Map} from 'immutable';
const {
... ... @@ -22,7 +22,7 @@ const {
const initialState = new InitialState;
export default function classifyReducer(state=initialState, action) {
export default function interestReducer(state=initialState, action) {
switch(action.type) {
case SET_TYPE: {
... ... @@ -30,20 +30,20 @@ export default function classifyReducer(state=initialState, action) {
}
case RESET_LIST_PAGE_INFO: {
return state.setIn(['interest', 'currentPage'], 0)
.setIn(['interest', 'pageCount'], 0)
.setIn(['interest', 'total'], 0)
.setIn(['interest', 'endReached'], false);
return state.set('currentPage', 0)
.set('pageCount', 0)
.set('total', 0)
.set('endReached', false);
}
case SHOW_LOGIN_TIP: {
return state.setIn(['interest', 'showLoginTip'], action.payload);
return state.set('showLoginTip', action.payload);
}
case INTEREST_LIST_REQUEST: {
return state.setIn(['interest', 'isFetching'], true)
.setIn(['interest', 'error'], null)
.setIn(['interest', 'ptr'], action.payload);
return state.set('isFetching', true)
.set('error', null)
.set('ptr', action.payload);
}
case INTEREST_LIST_SUCCESS: {
... ... @@ -55,27 +55,27 @@ export default function classifyReducer(state=initialState, action) {
endReached,
} = action.payload;
let newState = state.setIn(['interest', 'isFetching'], false)
.setIn(['interest', 'error'], null)
.setIn(['interest', 'ptr'], false)
.setIn(['interest', 'list'], Immutable.fromJS(list))
.setIn(['interest', 'currentPage'], currentPage)
.setIn(['interest', 'pageCount'], pageCount)
.setIn(['interest', 'total'], total)
.setIn(['interest', 'endReached'], endReached);
let newState = state.set('isFetching', false)
.set('error', null)
.set('ptr', false)
.set('list', Immutable.fromJS(list))
.set('currentPage', currentPage)
.set('pageCount', pageCount)
.set('total', total)
.set('endReached', endReached);
return newState;
}
case INTEREST_LIST_FAILURE: {
return state.setIn(['interest', 'isFetching'], false)
.setIn(['interest', 'error'], action.payload)
.setIn(['interest', 'ptr'], false);
return state.set('isFetching', false)
.set('error', action.payload)
.set('ptr', false);
}
case INTEREST_LIKE_STATE_CHANGE: {
let {like, index} = action.payload;
return state.setIn(['interest', 'list', index, 'is_fav'], like);
return state.setIn(['list', index, 'is_fav'], like);
}
case INTEREST_LIKE_REQUEST:
... ...
... ... @@ -2,7 +2,7 @@
import Request from '../../common/services/Request';
export default class ClassifyService {
export default class BrandService {
constructor (host) {
let baseURL = 'http://api.yoho.cn';
... ...
'use strict';
import Request from '../../common/services/Request';
export default class CategoryService {
constructor (host) {
let baseURL = 'http://api.yoho.cn';
if(host){
baseURL = host;
}
this.api = new Request(baseURL);
}
async fetchInterestList(gender='', uid=0, page=1, limit=60, fromPage='') {
return await this.api.get({
url: '/guang/api/v1/attention/getlist',
body: {
uid,
gender,
page,
limit,
fromPage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async addFavorite(id, type='brand', uid=0, fromPage='') {
return await this.api.post({
url: '',
body: {
method: 'app.favorite.add',
id,
type,
uid,
fromPage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async cancelFavorite(fav_id, type='brand', uid=0, fromPage='') {
return await this.api.post({
url: '',
body: {
method: 'app.favorite.cancel',
fav_id,
type,
uid,
fromPage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...
'use strict';
import Request from '../../common/services/Request';
export default class InterestService {
constructor (host) {
let baseURL = 'http://api.yoho.cn';
if(host){
baseURL = host;
}
this.api = new Request(baseURL);
}
async fetchInterestList(gender='', uid=0, page=1, limit=60, fromPage='') {
return await this.api.get({
url: '/guang/api/v1/attention/getlist',
body: {
uid,
gender,
page,
limit,
fromPage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async addFavorite(id, type='brand', uid=0, fromPage='') {
return await this.api.post({
url: '',
body: {
method: 'app.favorite.add',
id,
type,
uid,
fromPage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async cancelFavorite(fav_id, type='brand', uid=0, fromPage='') {
return await this.api.post({
url: '',
body: {
method: 'app.favorite.cancel',
fav_id,
type,
uid,
fromPage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...