Authored by 张丽霞

VIP等级,review by Redding

... ... @@ -20,8 +20,9 @@ import {Record, List, Map} from 'immutable';
import appInitialState from './reducers/app/appInitialState';
import personalInfoInitialState from './reducers/personalInfo/personalInfoInitialState';
import vipLevelInitialState from './reducers/vipLevel/vipLevelInitialState';
import PersonalInfoContainer from './containers/PersonalInfoContainer';
import VIPLevelContainer from './containers/VIPLevelContainer';
import {
setPlatform,
... ... @@ -29,10 +30,15 @@ import {
setServiceHost,
} from './reducers/app/appActions';
import {
setNickname,
} from './reducers/vipLevel/vipLevelActions';
function getInitialState() {
const _initState = {
app: (new appInitialState()),
personalInfo: (new personalInfoInitialState()),
vipLevel: (new vipLevelInitialState()),
};
return _initState;
}
... ... @@ -42,13 +48,22 @@ export default function native(platform) {
let YH_PersonalInfo = React.createClass({
render() {
const store = configureStore(getInitialState());
if (this.props.type == 'VIPLevel') {
let nickname = this.props.nickname;
store.dispatch(setNickname(nickname));
return(
<Provider store={store}>
<VIPLevelContainer />
</Provider>
);
} else {
return (
<Provider store={store}>
<PersonalInfoContainer />
</Provider>
);
}
}
});
AppRegistry.registerComponent('YH_PersonalInfo', () => YH_PersonalInfo);
... ...
'use strict'
import React, {Component} from 'react';
import {
StyleSheet,
Dimensions,
Platform,
View,
Text,
Image,
ListView,
TouchableOpacity,
} from 'react-native';
import {Map} from 'immutable';
import VIPPrivilegeCell from './VIPPrivilegeCell';
import VIPLevelHeader from './VIPLevelHeader';
export default class VIPLevel extends Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.renderFooter = this.renderFooter.bind(this);
this.renderHeader = this.renderHeader.bind(this);
this.renderSectionHeader = this.renderSectionHeader.bind(this);
this.renderSeparator = this.renderSeparator.bind(this);
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => !Immutable.is(r1, r2),
});
}
renderFooter() {
let {vipInfo} = this.props;
return(
<View style={styles.footer}>
{vipInfo.get('enjoy_preferential') && vipInfo.get('enjoy_preferential').size ?
<View style={{width: width,height: 15,backgroundColor: '#e5e5e5',}}/>
:null
}
<TouchableOpacity activeOpacity={1.0} onPress={() => {
this.props.onPressAllVIPPrivilegeCell && this.props.onPressAllVIPPrivilegeCell();
}}>
<View style={styles.allPrivilegeCell}>
<Text style={{marginLeft: 15, fontSize: 15}}>
查看全部VIP特权
</Text>
<Image style={{marginRight: 15}}
source={require('../image/cell_indicator.png')}
/>
</View>
</TouchableOpacity>
</View>
);
}
renderHeader() {
let {nickname, vipInfo, vipShowPageInfo} = this.props;
return(
<VIPLevelHeader
nickname={nickname}
vipInfo={vipInfo}
vipShowPageInfo={vipShowPageInfo}
/>
);
}
renderSectionHeader(sectionData, sectionID) {
if (!sectionData.length) {
return null;
}
return(
<View style={styles.sectionHeader}>
<Text style={{lineHeight:38,fontSize:17,color:'#444444',height:58}}>
当前可享受的特权
</Text>
<View style={{width: width,height: 0.5,backgroundColor: '#e5e5e5',}}/>
</View>
);
}
renderRow(rowData,sectionID,rowID) {
return(
<VIPPrivilegeCell
dataSource={rowData}
onPressPrivilegeCell={this.props.onPressPrivilegeCell}
/>
);
}
renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
return(
<View style={{width: width,height: 0.5,backgroundColor: '#e5e5e5',}}/>
);
}
render() {
let {nickname, isFetching, vipInfo} = this.props;
let dataSource = {};
if (vipInfo.get('enjoy_preferential') && vipInfo.get('enjoy_preferential').size) {
dataSource = {'privilegeSection':vipInfo.get('enjoy_preferential').toArray()};
}
return (
<View style={styles.container}>
<ListView
contentContainerStyle={styles.contentContainer}
enableEmptySections={true}
dataSource={this.dataSource.cloneWithRowsAndSections(dataSource)}
renderRow={this.renderRow}
renderFooter={this.renderFooter}
renderHeader={this.renderHeader}
renderSectionHeader={this.renderSectionHeader}
renderSeparator={this.renderSeparator}
/>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
const DEVICE_WIDTH_RATIO = width / 320;
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ebebeb',
},
allPrivilegeCell: {
height: 44 * DEVICE_WIDTH_RATIO,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: 'white',
},
sectionHeader: {
height: 59,
paddingLeft: 15,
backgroundColor:'white',
},
});
... ...
'use strict'
import React, {Component} from 'react';
import {
StyleSheet,
Dimensions,
Platform,
View,
Text,
Image,
TouchableOpacity,
} from 'react-native';
import {Map} from 'immutable';
export default class VIPLevelHeader extends Component {
constructor(props) {
super(props);
}
render() {
let {nickname, vipInfo, vipShowPageInfo} = this.props;
vipInfo = vipInfo.toJS();
let vipImageUrl = '';
let VIPLevel = vipInfo.current_vip_level;
switch (VIPLevel) {
case '1':
vipImageUrl = require('../image/mine_vip_1.png');
break;
case '2':
vipImageUrl = require('../image/mine_vip_2.png');
break;
case '3':
vipImageUrl = require('../image/mine_vip_3.png');
break;
default:
vipImageUrl = '';
}
let nextLevelTipMessage = '您已升至最高等级啦!'
if (VIPLevel != '3') {
}
return (
<TouchableOpacity activeOpacity={1.0} onPress={() => {
this.props.onPressPrivilegeCell && this.props.onPressPrivilegeCell(dataSource);
}}>
<View style={[styles.container, {height: 260}]}>
<View style={styles.nickname}>
<Text style={{marginLeft:17, color:'#444444'}}>
{nickname}
</Text>
{vipImageUrl !='' ?
<Image style={{marginLeft:10}}
source={vipImageUrl}
/>:null
}
</View>
{VIPLevel == '3'?
<Text style={{marginLeft:15,marginTop:5,fontSize:12,color:'#b0b0b0'}}>
您已升至最高等级啦!
</Text>
:<Text style={{marginLeft:15,marginTop:5,fontSize:12,color:'#b0b0b0'}}>
还差
<Text style={{marginLeft:15,color:'#444444'}}>
¥{vipShowPageInfo.get('upgradeNeedCost')}
</Text>
就可以升级为{vipInfo.next_vip_title}
</Text>
}
<Text style={{marginLeft:15,marginTop:5,fontSize:12,color:'#b0b0b0'}}>
(VIP金额累计需订单成功签收满15天且无退换货)
</Text>
<Text style={{marginLeft:15,marginTop:5,fontSize:12,color:'#444444'}}>
年度累计金额:
<Text style={{marginLeft:5,fontSize:17,color:'#d0021b'}}>
¥{vipInfo.current_year_cost}
</Text>
</Text>
<View style={styles.nextLevelNeedProcess}>
{VIPLevel == '3'?null:
<View style={styles.nextLevelCost}>
<Text style={{color:'#b0b0b0',fontSize:13,color:'#b0b0b0'}}>
¥0.00{vipInfo.next_need_cost}
</Text>
</View>
}
<View style={styles.nextLevelCompletedProcess}>
</View>
</View>
<View style={styles.curAndNextVIPTitle}>
<Text style={{marginLeft:15,fontSize:12,color:'#444444',fontStyle:'italic'}}>
{vipInfo.current_vip_title}
</Text>
{vipInfo.next_vip_title =='3' ?
<Text style={{marginRight:15,fontSize:12,color:'#444444',fontStyle: 'italic'}}>
{vipInfo.next_vip_title}
</Text>:null
}
</View>
<View style={{width: width,height: 15,backgroundColor: '#e5e5e5',}}/>
<View style={styles.totalCost}>
<Text style={{marginLeft:15,fontSize:17,color:'#444444'}}>
年度累计金额:
</Text>
<Text style={{marginRight:15,fontSize:14,color:'#444444'}}>
¥{vipInfo.current_year_cost}
</Text>
</View>
<View style={{height: 1.0,backgroundColor: '#e5e5e5',marginLeft:15}}/>
<View style={styles.totalCost}>
<Text style={{marginLeft:15,fontSize:17,color:'#444444'}}>
历史消费总金额:
</Text>
<Text style={{marginRight:15,fontSize:14,color:'#444444'}}>
¥{vipInfo.current_total_cost}
</Text>
</View>
<View style={{width: width,height: 15,backgroundColor: '#e5e5e5',}}/>
</View>
</TouchableOpacity>
);
}
}
let {width, height} = Dimensions.get('window');
const DEVICE_WIDTH_RATIO = width / 320;
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
nickname: {
flexDirection: 'row',
marginTop: 15,
},
nextLevelNeedProcess: {
position: 'relative',
marginTop: 10,
height: 10,
width: width - 30,
marginLeft: 15,
backgroundColor: '#b0b0b0',
borderRadius: 5,
},
nextLevelCompletedProcess: {
position: 'absolute',
bottom: 0,
left: 0,
height: 10,
width: width - 30,
backgroundColor: '#c40016',
borderRadius: 5,
},
nextLevelCost: {
position: 'absolute',
top: 0,
right: 0,
marginTop: -17,
backgroundColor: 'white',
},
curAndNextVIPTitle: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 5,
marginTop: 5,
},
totalCost: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
height: 44,
}
});
... ...
'use strict'
import React, {Component} from 'react';
import {
StyleSheet,
Dimensions,
Platform,
View,
Text,
Image,
TouchableOpacity,
} from 'react-native';
import {Map} from 'immutable';
import YH_Image from '../../common/components/YH_Image';
export default class VIPPrivilegeCell extends Component {
constructor(props) {
super(props);
}
render() {
let {dataSource} = this.props;
return (
<TouchableOpacity activeOpacity={1.0} onPress={() => {
this.props.onPressPrivilegeCell && this.props.onPressPrivilegeCell(dataSource);
}}>
<View style={styles.container}>
<YH_Image url={dataSource.get('pic')} style={styles.image} />
<View style={styles.descriptionContainer}>
<Text style={styles.title}>
{dataSource.get('title')}
</Text>
<Text style={styles.description}>
{dataSource.get('description')}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
let {width, height} = Dimensions.get('window');
const DEVICE_WIDTH_RATIO = width / 320;
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
flexDirection: 'row',
height: 69,
},
image: {
marginLeft: 15,
width: 35,
height: 35,
marginTop: 17,
},
descriptionContainer: {
height: 69,
width: width - 80,
justifyContent: 'center',
},
title: {
textAlign: 'left',
fontSize: 12,
marginBottom: 8,
},
description: {
textAlign: 'left',
fontSize: 12,
lineHeight: 15,
color: '#b0b0b0',
}
});
... ...
... ... @@ -6,6 +6,10 @@ export default keyMirror({
FETCH_PROFILE_REQUEST: null,
FETCH_PROFILE_SUCCESS: null,
FETCH_PROFILE_FAILURE: null,
UPDATE_PAGE_CELL_LIST: null,
SET_NICKNAME: null,
FETCH_VIP_INFO_REQUEST: null,
FETCH_VIP_INFO_SUCCESS: null,
FETCH_VIP_INFO_FAILURE: null,
});
... ...
'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 vipLevelActions from '../reducers/vipLevel/vipLevelActions';
import VIPLevel from '../components/VIPLevel';
const actions = [
vipLevelActions,
];
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 VIPLevelContainer extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.actions.fetchVIPInfo();
}
render() {
let {vipLevel} = this.props;
let {nickname, isFetching, vipInfo, vipShowPageInfo} = vipLevel;
return (
<View style={styles.container}>
<VIPLevel
nickname={nickname}
isFetching={isFetching}
vipInfo={vipInfo}
vipShowPageInfo={vipShowPageInfo}
/>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(VIPLevelContainer);
... ...
import {combineReducers} from 'redux';
import app from './app/appReducer';
import personalInfo from './personalInfo/personalInfoReducer';
import vipLevel from './vipLevel/vipLevelReducer';
const rootReducer = combineReducers({
app,
personalInfo,
vipLevel,
});
export default rootReducer;
... ...
'use strict';
import ReactNative from 'react-native';
import {Platform} from 'react-native';
import VIPLevelService from '../../services/VIPLevelService';
import {Record, List, Map} from 'immutable';
const {
SET_NICKNAME,
FETCH_VIP_INFO_REQUEST,
FETCH_VIP_INFO_SUCCESS,
FETCH_VIP_INFO_FAILURE,
} = require('../../constants/actionTypes').default;
export function fetchVIPInfoRequest() {
return{
type: FETCH_VIP_INFO_REQUEST,
}
}
export function fetchVIPInfoSuccess(object) {
return{
type: FETCH_VIP_INFO_SUCCESS,
payload: object,
}
}
export function fetchVIPInfoFailure(error) {
return{
type: FETCH_VIP_INFO_FAILURE,
payload: error,
}
}
export function setNickname(nickname) {
return{
type: SET_NICKNAME,
payload: nickname,
}
}
export function fetchVIPInfo() {
return(dispatch, getState) => {
let {app, vipLevel} = getState();
let fetchVIP = (uid) => {
dispatch(fetchVIPInfoRequest());
return new VIPLevelService(app.host).getVIPInfo(uid)
.then(json => {
let vipShowPageInfo = processVIPInfo(json);
dispatch(fetchVIPInfoSuccess({
vipInfo: json,
upgradeNeedCost: vipShowPageInfo.upgradeNeedCost,
vipNextLevelProgress: vipShowPageInfo.vipNextLevelProgress,
}));
})
.catch(error => {
dispatch(fetchVIPInfoFailure(error));
});
}
ReactNative.NativeModules.YH_CommonHelper.uid()
.then(uid => {
fetchVIP(uid);
})
.catch(error => {
fetchVIP(0);
});
}
}
function processVIPInfo(json) {
let upgradeNeedCost = 0.0;
let nextNeedCost = 0.0;
if (!json.upgrade_need_cost || json.upgrade_need_cost =='') {
upgradeNeedCost = 0.0;
} else {
upgradeNeedCost = parseFloat(json.upgrade_need_cost);
}
if (!json.next_need_cost || json.next_need_cost =='') {
nextNeedCost = 0.0;
} else {
nextNeedCost = parseFloat(json.next_need_cost);
}
let currentLevelCost = 0.0;
if (nextNeedCost - upgradeNeedCost >= 0) {
currentLevelCost = nextNeedCost - upgradeNeedCost;
}
let vipNextLevelProgress = 1.0;
if (nextNeedCost == 0.0) {
vipNextLevelProgress = 1.0;
}else{
vipNextLevelProgress = currentLevelCost/nextNeedCost;
}
return({upgradeNeedCost,vipNextLevelProgress})
}
... ...
'use strict';
import Immutable,{Record, List, Map} from 'immutable';
let InitialState = Record({
nickname: '',
isFetching: false,
vipInfo: Map(),
vipShowPageInfo: new(Record({
upgradeNeedCost: 0.0,
vipNextLevelProgress: 0.0,
}))
});
export default InitialState;
... ...
'use strict';
import InitialState from './vipLevelInitialState';
import Immutable, {Map} from 'immutable';
const {
SET_NICKNAME,
FETCH_VIP_INFO_REQUEST,
FETCH_VIP_INFO_SUCCESS,
FETCH_VIP_INFO_FAILURE,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function brandReducer(state=initialState, action) {
switch(action.type) {
case FETCH_VIP_INFO_REQUEST:
return state.set('isFetching', true);
case FETCH_VIP_INFO_FAILURE:
return state.set('isFetching', false);
case FETCH_VIP_INFO_SUCCESS:
return state.set('isFetching', false)
.set('vipInfo', Immutable.fromJS(action.payload.vipInfo))
.setIn(['vipShowPageInfo', 'upgradeNeedCost'], action.payload.upgradeNeedCost)
.setIn(['vipShowPageInfo', 'vipNextLevelProgress'], action.payload.vipNextLevelProgress);
case SET_NICKNAME:
return state.set('nickname', action.payload);
}
return state;
}
... ...
'use strict';
import Request from '../../common/services/NativeRequest';
export default class MineGuangService {
constructor (host) {
let baseURL = 'http://api.yoho.cn';
if(host){
baseURL = host;
}
this.api = new Request(baseURL);
}
async getVIPInfo(uid) {
return await this.api.get({
url: '',
body: {
method: 'app.passport.vip',
uid,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...