Authored by 于良

添加关注 review by shixiang

... ... @@ -6,17 +6,19 @@ import Plustar from './plustar/Plustar';
import Coupon from './coupon/Coupon';
import QRCode from './qrcode/QRCode';
import BrandStore from './brandStore/BrandStore';
import Classify from './classify/Classify';
// import Search from './search/Search';
export default function native(platform) {
Coupon(platform);
BrandStore(platform);
Classify(platform);
if (Platform.OS === 'ios') {
Community(platform);
Plustar(platform);
Coupon(platform);
QRCode(platform);
BrandStore(platform);
} else {
Coupon(platform);
BrandStore(platform);
}
}
... ...
'use strict';
import React from 'react';
import ReactNative, {
AppRegistry,
Platform,
StyleSheet,
Dimensions,
TouchableOpacity,
} from 'react-native';
import {
Provider,
connect
} from 'react-redux';
import configureStore from './store/configureStore';
import {Record, List, Map} from 'immutable';
import appInitialState from './reducers/app/appInitialState';
import classifyInitialState from './reducers/classify/classifyInitialState';
import ClassifyContainer from './containers/ClassifyContainer';
import {
setPlatform,
setHost,
} from './reducers/app/appActions';
import {
setType,
} from './reducers/classify/classifyActions';
function getInitialState() {
const _initState = {
app: (new appInitialState()),
classify: (new classifyInitialState()),
};
return _initState;
}
export default function native(platform) {
let YH_Classify = React.createClass({
render() {
const store = configureStore(getInitialState());
store.dispatch(setPlatform(platform));
store.dispatch(setHost(this.props.host));
store.dispatch(setType(this.props.type));
return (
<Provider store={store}>
<ClassifyContainer />
</Provider>
);
}
});
AppRegistry.registerComponent('YH_Classify', () => YH_Classify);
}
let styles = StyleSheet.create({
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
Image,
ListView,
StyleSheet,
Dimensions,
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 {
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}
</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'
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
Image,
ListView,
StyleSheet,
Dimensions,
TouchableOpacity,
} from 'react-native';
export default class Interest 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) {
return null;
}
render() {
return (
<View style={styles.container}>
<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'
},
});
... ...
import keyMirror from 'key-mirror';
export default keyMirror({
SET_PLATFORM: null,
SET_HOST: null,
SET_CHANNEL: null,
SET_TYPE: null,
INTEREST_LIST_REQUEST: null,
INTEREST_LIST_SUCCESS: null,
INTEREST_LIST_FAILURE: null,
GET_COUPON_REQUEST: null,
GET_COUPON_SUCCESS: null,
GET_COUPON_FAILURE: null,
JUMP_WITH_URL: null,
HIDE_SUCCESS_PROMPT: null,
HIDE_NET_ERROR_PROMPT: null,
});
... ...
'use strict'
import React, {Component} from 'react';
import {
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 classifyActions from '../reducers/classify/classifyActions';
import Interest from '../components/interest/Interest';
const actions = [
classifyActions,
];
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 ClassifyContainer extends Component {
constructor(props) {
super(props);
this._onPressBanner = this._onPressBanner.bind(this);
this._onPressImage = this._onPressImage.bind(this);
this._onPressCoupon = this._onPressCoupon.bind(this);
this._onGetCoupon = this._onGetCoupon.bind(this);
this._onPromptHidden = this._onPromptHidden.bind(this);
this._onNetPromptHidden = this._onNetPromptHidden.bind(this);
this._renderClassify = this._renderClassify.bind(this);
// this.subscription = NativeAppEventEmitter.addListener(
// 'UserDidLoginEvent',
// (reminder) => {
// this.props.actions.couponCenter(true);
// }
// );
}
componentDidMount() {
this.props.actions.interestList();
}
componentWillUnmount() {
// this.subscription && this.subscription.remove();
}
_onPressBanner(url) {
this.props.actions.jumpWithUrl(url);
}
_onPressImage(url) {
this.props.actions.jumpWithUrl(url);
}
_onPressCoupon(url) {
this.props.actions.jumpWithUrl(url);
}
_onGetCoupon(couponID) {
this.props.actions.getCoupon(couponID);
}
_onPromptHidden() {
this.props.actions.promptHidden();
}
_onNetPromptHidden() {
this.props.actions.netPromptHidden();
}
_renderClassify() {
let {type} = this.props.classify;
if (type == 0) {
return (
<Text>品类</Text>
);
} else if (type == 1) {
return (
<Text>品牌</Text>
);
} else if (type == 2) {
return (
<Interest />
);
}
return null;
}
render() {
return (
<View style={styles.container}>
{this._renderClassify()}
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ClassifyContainer);
... ...
'use strict';
import ReactNative from 'react-native';
const {
SET_PLATFORM,
SET_CHANNEL,
SET_HOST,
} = require('../../constants/actionTypes').default;
export function setPlatform(platform) {
return {
type: SET_PLATFORM,
payload: platform
};
}
export function setChannel(channel) {
return {
type: SET_CHANNEL,
payload: channel
};
}
export function setHost(host) {
return {
type: SET_HOST,
payload: host
};
}
... ...
'use strict';
import {Record, List, Map} from 'immutable';
let InitialState = Record({
platform: 'ios', // ios, android
channel: 1, // 1 - boy, 2 - girl, 3 - kid, 4 - lifestyle, 5 - yoho
host: 'http://api.yoho.cn',
});
export default InitialState;
... ...
'use strict';
import InitialState from './appInitialState';
const {
SET_PLATFORM,
SET_CHANNEL,
SET_HOST,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function appReducer(state = initialState, action) {
if (!(state instanceof InitialState)) return initialState.merge(state);
switch (action.type) {
case SET_PLATFORM:
return state.set('platform', action.payload);
case SET_CHANNEL:
return state.set('channel', action.payload);
case SET_HOST:
return state.set('host',action.payload);
}
return state;
}
... ...
'use strict';
import ReactNative from 'react-native';
import ClassifyService from '../../services/ClassifyService';
const Platform = require('Platform');
const {
SET_TYPE,
INTEREST_LIST_REQUEST,
INTEREST_LIST_SUCCESS,
INTEREST_LIST_FAILURE,
} = require('../../constants/actionTypes').default;
export function setType(type) {
return {
type: SET_TYPE,
payload: type
};
}
export function interestListRequest() {
return {
type: INTEREST_LIST_REQUEST,
};
}
export function interestListSuccess(json) {
return {
type: INTEREST_LIST_SUCCESS,
payload: json
};
}
export function interestListFailure(error) {
return {
type: INTEREST_LIST_FAILURE,
payload: error
};
}
/*
* reload bool 是否需要强制重新请求数据,
*/
export function interestList(reload=false) {
return (dispatch, getState) => {
let {app, classify} = getState();
let {interest} = classify;
if (reload) {
// 强制刷新数据
} else {
if (interest.isFetching || interest.endReached || interest.error) {
return;
}
}
let fetchList = (gender, uid, page, pageSize, sourcePage) => {
dispatch(interestListRequest());
return new ClassifyService(app.host).searchProductList(gender, uid, page, pageSize, sourcePage)
.then(json => {
let payload = parseInterestList(json);
payload.endReached = payload.currentPage == payload.pageCount;
if (payload.currentPage > 1) {
let oldList = productList.list.toJS();
let list = [...oldList, ...payload.list];
payload.list = list;
}
dispatch(interestListSuccess(payload));
})
.catch(error => {
dispatch(interestListFailure(error));
});
}
let gender = '1,3';
let uid = 0;
let sourcePage = '';
let page = interest.currentPage + 1;
let pageSize = interest.pageSize;
ReactNative.NativeModules.YH_CommonHelper.sourcePage('YH_CategoryConcernVC')
.then(data => {
sourcePage = data;
return ReactNative.NativeModules.YH_CommonHelper.uid();
})
.then(data => {
uid = data;
fetchList(gender, uid, page, pageSize, sourcePage);
})
.catch(error => {
fetchList(gender, uid, page, pageSize, sourcePage);
});
};
}
function parseProductList(json) {
let currentPage = json && json.page ? json.page : 1;
let pageCount = json && json.page_total ? json.page_total : 0;
let total = json && json.total ? json.total : 0;
let shopOrBrand = [];
if (currentPage == 1) {
shopOrBrand = json && json.shop ? json.shop : [];
if (!shopOrBrand || shopOrBrand.length == 0) {
let brand = json && json.brand;
if (brand) {
shopOrBrand.push(brand);
}
}
}
let list = json && json.product_list ? json.product_list : [];
return {
list,
currentPage,
pageCount,
total,
};
}
export function jumpWithUrl(url) {
if (!url) {
__DEV__ && console.log('Illegal url');
return {
type: JUMP_WITH_URL
}
}
ReactNative.NativeModules.YH_CommonHelper.jumpWithUrl(url);
return {
type: JUMP_WITH_URL,
payload: url
};
}
... ...
'use strict';
import {Record, List, Map} from 'immutable';
let InitialState = Record({
type: 0, // 0 - 品类,1 - 品牌,2 - 关注
category: null,
brand: null,
interest: new (Record({
isFetching: false,
error: null,
list: List(),
currentPage: 0,
pageCount: 0,
pageSize: 6, //60,
total: 0,
endReached: false,
sourceType: 0, // 0 - 默认,1 - 全球购,2 - 奥莱
})),
});
export default InitialState;
... ...
'use strict';
import InitialState from './classifyInitialState';
import Immutable, {Map} from 'immutable';
const {
SET_TYPE,
INTEREST_LIST_REQUEST,
INTEREST_LIST_SUCCESS,
INTEREST_LIST_FAILURE,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function classifyReducer(state=initialState, action) {
switch(action.type) {
case SET_TYPE: {
return state.set('type', action.payload);
}
case INTEREST_LIST_REQUEST: {
return state.setIn(['interest', 'isFetching'], true)
.setIn(['interest', 'error'], null);
}
case INTEREST_LIST_SUCCESS: {
let {
list,
currentPage,
pageCount,
total,
endReached,
} = action.payload;
let newState = state.setIn(['interest', 'isFetching'], false)
.setIn(['interest', 'error'], null)
.setIn(['interest', 'list'], Immutable.fromJS(list))
.setIn(['interest', 'currentPage'], currentPage)
.setIn(['interest', 'pageCount'], pageCount)
.setIn(['interest', 'total'], total)
.setIn(['interest', 'endReached'], endReached);
return newState;
}
case INTEREST_LIST_FAILURE: {
return state.setIn(['interest', 'isFetching'], false)
.setIn(['interest', 'error'], action.payload);
}
}
return state;
}
... ...
import {combineReducers} from 'redux';
import app from './app/appReducer';
import classify from './classify/classifyReducer';
const rootReducer = combineReducers({
app,
classify,
});
export default rootReducer;
... ...
'use strict';
import Request from '../../common/services/Request';
export default class ClassifyService {
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 getCoupon(couponId, uid) {
return await this.api.get({
url: '',
body: {
method: 'app.promotion.getCoupon',
couponId,
uid,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...
/**
* # configureStore.js
*
* A Redux boilerplate setup
*
*/
'use strict';
/**
* ## Imports
*
* redux functions
*/
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
/**
* ## Reducer
* The reducer contains the 4 reducers from
* device, global, auth, profile
*/
import reducer from '../reducers';
const logger = createLogger({
predicate: (getState, action) => process.env.NODE_ENV === `development`
});
/**
* ## creatStoreWithMiddleware
* Like the name...
*/
const createStoreWithMiddleware = applyMiddleware(
thunk,
logger
)(createStore);
/**
* ## configureStore
* @param {Object} the state with for keys:
* device, global, auth, profile
*
*/
export default function configureStore(initialState) {
return createStoreWithMiddleware(reducer, initialState);
};
... ...