Authored by Zhu-Arthur

优惠券相关

1 import React, { Component } from 'react'; 1 import React, { Component } from 'react';
2 -import { 2 +import ReactNative, {
  3 + Dimensions,
  4 + Image,
3 SectionList, 5 SectionList,
4 StyleSheet, 6 StyleSheet,
5 Text, 7 Text,
@@ -10,30 +12,70 @@ import { @@ -10,30 +12,70 @@ import {
10 12
11 import CouponListCell from './CouponListCell'; 13 import CouponListCell from './CouponListCell';
12 14
  15 +let {width, height} = Dimensions.get('window');
  16 +const DEVICE_WIDTH_RATIO = width / 375;
  17 +
13 export default class CouponList extends Component { 18 export default class CouponList extends Component {
14 state = { } 19 state = { }
15 20
16 render() { 21 render() {
17 let data = [{title: '', data: this.props.data || []}]; 22 let data = [{title: '', data: this.props.data || []}];
18 - return (  
19 - <SectionList  
20 - style={styles.container}  
21 - renderItem={this.renderItem}  
22 - sections={data}  
23 - />  
24 - ); 23 + if (!this.props.isFetching && data[0].data.length == 0) {
  24 + return (
  25 + <View style={styles.emptyContainer}>
  26 + <Image source={require('../../images/coupon.png')} style={styles.emptyImage} />
  27 + <Text style={styles.couponErrorPageText}>暂无优惠券</Text>
  28 + </View>
  29 + )
  30 + } else {
  31 + return (
  32 + <SectionList
  33 + style={styles.container}
  34 + contentContainerStyle={styles.contentContainerStyle}
  35 + renderItem={this.renderItem}
  36 + sections={data}
  37 + onEndReached={this.props.onEndReached}
  38 + keyExtractor={(item, index) => '' + index}
  39 + />
  40 + );
  41 + }
25 } 42 }
26 43
27 renderItem = ({item, index}) => { 44 renderItem = ({item, index}) => {
28 return ( 45 return (
29 - <CouponListCell /> 46 + <CouponListCell data={item} type={this.props.type} goCouponProductList={() => this.goCouponProductList(item)}/>
30 ) 47 )
31 } 48 }
  49 +
  50 + goCouponProductList(item) {
  51 + let url = `http://m.yohobuy.com?openby:yohobuy={"action":"go.couponProductList",
  52 + "params":{"coupon_id":"${item.coupon_id}","coupon_code":"${item.coupon_code}"}, "coupon_title":"${item.coupon_name}"}`;
  53 + ReactNative.NativeModules.YH_CommonHelper.jumpWithUrl(url);
  54 + }
32 } 55 }
33 56
34 const styles = StyleSheet.create({ 57 const styles = StyleSheet.create({
35 container: { 58 container: {
36 flex: 1, 59 flex: 1,
37 backgroundColor: '#f0f0f0', 60 backgroundColor: '#f0f0f0',
  61 + },
  62 + emptyContainer: {
  63 + flex: 1,
  64 + backgroundColor: '#f0f0f0',
  65 + justifyContent: 'center',
  66 + alignItems: 'center',
  67 + },
  68 + contentContainerStyle: {
  69 + paddingVertical: 5 * DEVICE_WIDTH_RATIO
  70 + },
  71 + emptyImage: {
  72 + width: 104 * DEVICE_WIDTH_RATIO,
  73 + height: 65 * DEVICE_WIDTH_RATIO,
  74 + // marginTop: 131 * DEVICE_WIDTH_RATIO,
  75 + marginBottom: 15 * DEVICE_WIDTH_RATIO,
  76 + },
  77 + couponErrorPageText: {
  78 + fontSize: 14,
  79 + color: '#b0b0b0',
38 } 80 }
39 }) 81 })
1 import React, { Component } from 'react'; 1 import React, { Component } from 'react';
2 import { 2 import {
  3 + Dimensions,
  4 + Image,
3 SectionList, 5 SectionList,
4 StyleSheet, 6 StyleSheet,
5 Text, 7 Text,
@@ -8,18 +10,240 @@ import { @@ -8,18 +10,240 @@ import {
8 View, 10 View,
9 } from 'react-native'; 11 } from 'react-native';
10 12
  13 +let {width, height} = Dimensions.get('window');
  14 +const DEVICE_WIDTH_RATIO = width / 375;
  15 +
11 export default class CouponListCell extends Component { 16 export default class CouponListCell extends Component {
12 state = { } 17 state = { }
13 render() { 18 render() {
  19 + let { type } = this.props;
  20 + let data = this.props.data || {};
  21 + let coupon_value = data && data.coupon_value_str ? data.coupon_value_str : 0;
  22 + let coupon_name = data && data.coupon_name ? data.coupon_name : '';
  23 + let coupon_validity = data && data.coupon_validity ? data.coupon_validity : '';
  24 + let use_rule = data && data.use_rule ? data.use_rule : '';
  25 + let notes = data && data.notes ? data.notes : [];
  26 + let { catalog, catalog_name, is_online_avail, is_overdue_soon } = data;
  27 + let image, color;
  28 + if (catalog == '100') {
  29 + image = require('../../images/bgyellow.png');
  30 + color = '#ffa72e';
  31 + } else if (catalog == '200') {
  32 + image = require('../../images/bgred.png');
  33 + color = '#fc5960';
  34 + } else {
  35 + image = require('../../images/bgblack.png');
  36 + color = '#000000';
  37 + }
  38 + let arrowImage = this.state.showDetail ? require('../../images/up.png') : require('../../images/down.png');
  39 + let grayImage = type == 'use' ? require('../../images/yishiyong.png') : type == 'overtime' ? require('../../images/guoqi.png') : require('../../images/yishiyong.png');
  40 + if (type != 'notuse') {
  41 + color = '#b0b0b0';
  42 + image = require('../../images/bggrey.png');
  43 + }
14 return ( 44 return (
15 - <View /> 45 + <View style={styles.container}>
  46 + <View style={[styles.row, this.state.showDetail && styles.shadow]}>
  47 + <View>
  48 + <Image source={image} resizeMode="stretch" style={styles.bgImg}/>
  49 + <View style={styles.leftAb}>
  50 + <Text style={[styles.price, {color}, (data.coupon_value != data.coupon_value_str) && {fontSize: 24 * DEVICE_WIDTH_RATIO}]} numberOfLines={1}>
  51 + {coupon_value}
  52 + </Text>
  53 + {use_rule ? <Text style={[styles.priceDetail, {color}]}>
  54 + {use_rule}
  55 + </Text> : null}
  56 + </View>
  57 + </View>
  58 + <View style={styles.right}>
  59 + <Text style={[styles.titleTip, {color}]} numberOfLines={2} >
  60 + [{catalog_name}]
  61 + <Text style={[styles.title, type != 'notuse' && {color}]} >
  62 + {' '} {coupon_name}
  63 + </Text>
  64 + </Text>
  65 + <Text style={styles.time}>
  66 + {coupon_validity}
  67 + </Text>
  68 + <TouchableOpacity
  69 + activeOpacity={1.0}
  70 + onPress={() => {
  71 + this.setState({showDetail: !this.state.showDetail})
  72 + }}>
  73 + <View style={styles.bottom}>
  74 + <Text style={styles.bottomText}>
  75 + 使用说明
  76 + </Text>
  77 + <Image source={arrowImage} style={styles.bottomIcon} resizeMode="contain"/>
  78 + </View>
  79 + </TouchableOpacity>
  80 + {(type == 'notuse' && is_online_avail) ? <TouchableOpacity onPress={this.props.goCouponProductList} style={styles.immediatelyUse}>
  81 + <Text style={styles.immediatelyUseText}>立即使用</Text>
  82 + </TouchableOpacity> : null}
  83 + {type != 'notuse' ? <Image source={grayImage} style={styles.grayImage} /> : null}
  84 + {is_overdue_soon ? <Image source={require('../../images/tip.png')} style={styles.overDueSoon} resizeMode="stretch" /> : null}
  85 + </View>
  86 + </View>
  87 + {this.state.showDetail ?
  88 + <View style={styles.detail}>
  89 + {notes.map((item, i) => {
  90 + return (
  91 + <View key={i} style={styles.detailTextView}>
  92 + <Text style={styles.detailText} numberOfLines={2}>
  93 + {item}
  94 + </Text>
  95 + </View>
  96 + )
  97 + })}
  98 + </View>
  99 + : null
  100 + }
  101 + </View>
16 ); 102 );
17 } 103 }
18 } 104 }
19 105
20 const styles = StyleSheet.create({ 106 const styles = StyleSheet.create({
21 container: { 107 container: {
22 - flex: 1,  
23 - backgroundColor: '#f0f0f0', 108 + marginVertical: 5 * DEVICE_WIDTH_RATIO,
  109 + marginHorizontal: 10 * DEVICE_WIDTH_RATIO,
  110 + },
  111 + row: {
  112 + flexDirection: 'row',
  113 + zIndex: 1,
  114 + },
  115 + bgImg: {
  116 + width: 110 * DEVICE_WIDTH_RATIO,
  117 + height: 100 * DEVICE_WIDTH_RATIO,
  118 + },
  119 + right: {
  120 + width: 245 * DEVICE_WIDTH_RATIO,
  121 + height: 100 * DEVICE_WIDTH_RATIO,
  122 + backgroundColor: '#ffffff',
  123 + borderTopRightRadius: 4 * DEVICE_WIDTH_RATIO,
  124 + borderBottomRightRadius: 4 * DEVICE_WIDTH_RATIO,
  125 + },
  126 + price: {
  127 + fontSize: 30 * DEVICE_WIDTH_RATIO,
  128 + color: '#002B47',
  129 + letterSpacing: 0,
  130 + textAlign: 'center',
  131 + fontWeight: 'bold',
  132 + },
  133 + priceDetail: {
  134 + fontFamily: 'PingFang-SC-Regular',
  135 + fontSize: 12*DEVICE_WIDTH_RATIO,
  136 + color: '#002B47',
  137 + letterSpacing: 0,
  138 + textAlign: 'center',
  139 + },
  140 + leftAb: {
  141 + position: 'absolute',
  142 + top: 0,
  143 + left: 8*DEVICE_WIDTH_RATIO,
  144 + right: 5*DEVICE_WIDTH_RATIO,
  145 + bottom: 0,
  146 + justifyContent: 'center',
  147 + alignItems: 'center',
  148 + },
  149 + titleTip: {
  150 + fontFamily: 'PingFang-SC-Medium',
  151 + fontSize: 12*DEVICE_WIDTH_RATIO,
  152 + color: '#002B47',
  153 + letterSpacing: 0,
  154 + marginLeft: 10*DEVICE_WIDTH_RATIO,
  155 + marginTop: 10*DEVICE_WIDTH_RATIO,
  156 + height: 35*DEVICE_WIDTH_RATIO,
  157 + maxWidth: 185*DEVICE_WIDTH_RATIO,
  158 + fontWeight: 'bold'
  159 + },
  160 + title: {
  161 + fontFamily: 'PingFang-SC-Regular',
  162 + fontSize: 12*DEVICE_WIDTH_RATIO,
  163 + color: '#444444',
  164 + fontWeight: 'normal',
  165 + letterSpacing: 0,
  166 + lineHeight: 15*DEVICE_WIDTH_RATIO,
  167 + },
  168 + time: {
  169 + marginLeft: 10*DEVICE_WIDTH_RATIO,
  170 + fontFamily: 'PingFang-SC-Regular',
  171 + fontSize: 11*DEVICE_WIDTH_RATIO,
  172 + color: '#b0b0b0',
  173 + letterSpacing: 0,
  174 +
  175 + },
  176 + bottom: {
  177 + marginTop: 5*DEVICE_WIDTH_RATIO,
  178 + height: 35*DEVICE_WIDTH_RATIO,
  179 + flexDirection: 'row',
  180 + marginLeft: 10*DEVICE_WIDTH_RATIO,
  181 + alignItems: 'center',
  182 + },
  183 + bottomText: {
  184 + fontFamily: 'PingFang-SC-Regular',
  185 + fontSize: 11*DEVICE_WIDTH_RATIO,
  186 + color: '#B0B0B0',
  187 + letterSpacing: 0,
  188 + },
  189 + bottomIcon: {
  190 + width: 10*DEVICE_WIDTH_RATIO,
  191 + height: 10*DEVICE_WIDTH_RATIO,
  192 + marginLeft: 5*DEVICE_WIDTH_RATIO,
  193 + },
  194 + immediatelyUse: {
  195 + position: 'absolute',
  196 + right: 10*DEVICE_WIDTH_RATIO,
  197 + bottom: 10*DEVICE_WIDTH_RATIO,
  198 + width: 65*DEVICE_WIDTH_RATIO,
  199 + height: 25*DEVICE_WIDTH_RATIO,
  200 + borderWidth: 1,
  201 + borderColor: '#444444',
  202 + borderRadius: 12.5*DEVICE_WIDTH_RATIO,
  203 + justifyContent: 'center',
  204 + alignItems: 'center',
  205 + },
  206 + immediatelyUseText: {
  207 + fontSize: 10*DEVICE_WIDTH_RATIO,
  208 + color: '#444444',
  209 + },
  210 + grayImage: {
  211 + position: 'absolute',
  212 + top: 21.5*DEVICE_WIDTH_RATIO,
  213 + right: 10*DEVICE_WIDTH_RATIO,
  214 + width: 63*DEVICE_WIDTH_RATIO,
  215 + height: 57*DEVICE_WIDTH_RATIO,
  216 + },
  217 + detail: {
  218 + width: 355*DEVICE_WIDTH_RATIO,
  219 + marginTop: -6*DEVICE_WIDTH_RATIO,
  220 + backgroundColor: 'rgba(255,255,255,0.7)',
  221 + paddingTop: 18*DEVICE_WIDTH_RATIO,
  222 + paddingBottom: 11*DEVICE_WIDTH_RATIO,
  223 + paddingHorizontal: 11*DEVICE_WIDTH_RATIO,
  224 + zIndex: 0,
  225 + },
  226 + detailTextView: {
  227 + flexDirection: 'row',
  228 + },
  229 + detailText: {
  230 + fontFamily: 'PingFang-SC-Regular',
  231 + fontSize: 11*DEVICE_WIDTH_RATIO,
  232 + color: '#444444',
  233 + letterSpacing: 0,
  234 + },
  235 + shadow: {
  236 + shadowColor: 'rgba(0,0,0,0.1)',
  237 + shadowOffset: {width: 0, height: 3},
  238 + shadowOpacity: 0.5,
  239 + shadowRadius: 5,
  240 + elevation: 1,
  241 + },
  242 + overDueSoon: {
  243 + position: 'absolute',
  244 + top: 0,
  245 + right: 0,
  246 + width: 42*DEVICE_WIDTH_RATIO,
  247 + height: 42*DEVICE_WIDTH_RATIO,
24 } 248 }
25 }) 249 })
@@ -18,13 +18,15 @@ export default class CouponTabs extends Component { @@ -18,13 +18,15 @@ export default class CouponTabs extends Component {
18 18
19 render() { 19 render() {
20 const { activeTab } = this.props; 20 const { activeTab } = this.props;
  21 + let image = this.props.showFilter ? require('../../images/blackupa.png') : require('../../images/blackdwona.png');
21 return ( 22 return (
22 <View style={styles.tabs}> 23 <View style={styles.tabs}>
23 {this.props.tabs.map((tab, i) => { 24 {this.props.tabs.map((tab, i) => {
24 return ( 25 return (
25 - <TouchableOpacity key={i} activeOpacity={0.8} style={styles.tab} onPress={() => this.props.goToPage(i)}> 26 + <TouchableOpacity key={i} activeOpacity={0.8} style={styles.tab} onPress={() => this._goToPage(i)}>
26 <View style={[styles.tabRow, i > 0 && styles.tabBorder]}> 27 <View style={[styles.tabRow, i > 0 && styles.tabBorder]}>
27 <Text style={[styles.tabText, activeTab == i && styles.activeTabText]}>{tab}{this.renderNums(i)}</Text> 28 <Text style={[styles.tabText, activeTab == i && styles.activeTabText]}>{tab}{this.renderNums(i)}</Text>
  29 + {i == 0 && <Image source={activeTab == i ? image : require('../../images/down.png')} style={styles.image} resizeMode="stretch" />}
28 </View> 30 </View>
29 </TouchableOpacity> 31 </TouchableOpacity>
30 ) 32 )
@@ -58,6 +60,13 @@ export default class CouponTabs extends Component { @@ -58,6 +60,13 @@ export default class CouponTabs extends Component {
58 return ` (${nums})`; 60 return ` (${nums})`;
59 } 61 }
60 } 62 }
  63 +
  64 + _goToPage(i) {
  65 + let shouldGo = this.props.selectTab(i);
  66 + if (shouldGo) {
  67 + this.props.goToPage(i);
  68 + }
  69 + }
61 } 70 }
62 71
63 const styles = StyleSheet.create({ 72 const styles = StyleSheet.create({
@@ -91,7 +100,6 @@ const styles = StyleSheet.create({ @@ -91,7 +100,6 @@ const styles = StyleSheet.create({
91 }, 100 },
92 tabText: { 101 tabText: {
93 fontSize: 14, 102 fontSize: 14,
94 - lineHeight: 30,  
95 color: '#b0b0b0', 103 color: '#b0b0b0',
96 }, 104 },
97 activeTabText: { 105 activeTabText: {
@@ -100,5 +108,10 @@ const styles = StyleSheet.create({ @@ -100,5 +108,10 @@ const styles = StyleSheet.create({
100 icon: { 108 icon: {
101 width: 24, 109 width: 24,
102 height: 38, 110 height: 38,
  111 + },
  112 + image: {
  113 + width: 9,
  114 + height: 6,
  115 + marginLeft: 5,
103 } 116 }
104 }); 117 });
@@ -21,5 +21,8 @@ export default keyMirror({ @@ -21,5 +21,8 @@ export default keyMirror({
21 21
22 HIDE_SUCCESS_PROMPT: null, 22 HIDE_SUCCESS_PROMPT: null,
23 HIDE_NET_ERROR_PROMPT: null, 23 HIDE_NET_ERROR_PROMPT: null,
  24 + SHOW_PROMPT_TIP: null,
  25 + GET_COUPONLIST_REQUEST: null,
  26 + GET_COUPONLIST_FAILURE: null,
24 27
25 }); 28 });
@@ -13,6 +13,7 @@ import {Map} from 'immutable'; @@ -13,6 +13,7 @@ import {Map} from 'immutable';
13 import * as couponActions from '../reducers/coupon/couponActions'; 13 import * as couponActions from '../reducers/coupon/couponActions';
14 import CouponList from '../components/coupon/CouponList'; 14 import CouponList from '../components/coupon/CouponList';
15 import CouponTabs from '../components/coupon/CouponTabs'; 15 import CouponTabs from '../components/coupon/CouponTabs';
  16 +import Prompt from '../components/coupon/Prompt';
16 17
17 const actions = [ 18 const actions = [
18 couponActions, 19 couponActions,
@@ -46,36 +47,103 @@ class CouponListContainer extends Component { @@ -46,36 +47,103 @@ class CouponListContainer extends Component {
46 props.actions.getCouponList('overtime', true); 47 props.actions.getCouponList('overtime', true);
47 } 48 }
48 49
49 - state = {couponCode: ''} 50 + state = {couponCode: '', selectedTab: 0, showFilter: false, selectedFilter: 0}
50 51
51 render() { 52 render() {
52 - const { couponNums } = this.props.coupon; 53 + const { showFilter, selectedFilter } = this.state;
  54 + const { couponNums, notuse, use, overtime, showSuccessTip } = this.props.coupon;
53 return ( 55 return (
54 - <ScrollableTabView  
55 - renderTabBar={() => <CouponTabs couponNums={couponNums} />}  
56 - >  
57 - <View style={styles.container} tabLabel="未使用">  
58 - <View style={styles.bindCouponContainer}>  
59 - <TextInput  
60 - style={styles.textInput}  
61 - onChangeText={text => this.setState({couponCode: text})}  
62 - placeholder="请输入优惠券码"  
63 - underlineColorAndroid="transparent"  
64 - />  
65 - <TouchableOpacity  
66 - onPress={() => null}  
67 - style={[styles.bindCouponBtn, this.state.couponCode.length > 0 && styles.blackBack]}  
68 - >  
69 - <Text style={styles.bindCouponText}>兑换</Text>  
70 - </TouchableOpacity> 56 + <View style={styles.container}>
  57 + <ScrollableTabView
  58 + onChangeTab={this.onChangeTab}
  59 + renderTabBar={() => <CouponTabs couponNums={couponNums} selectTab={this.selectTab} showFilter={this.state.showFilter}/>}
  60 + >
  61 + <View style={styles.container} tabLabel="未使用">
  62 + <View style={styles.bindCouponContainer}>
  63 + <TextInput
  64 + style={styles.textInput}
  65 + onChangeText={text => this.setState({couponCode: text})}
  66 + placeholder="请输入优惠券码"
  67 + underlineColorAndroid="transparent"
  68 + />
  69 + <TouchableOpacity
  70 + onPress={this.bindCoupon}
  71 + style={[styles.bindCouponBtn, this.state.couponCode.length > 0 && styles.blackBack]}
  72 + >
  73 + <Text style={styles.bindCouponText}>兑换</Text>
  74 + </TouchableOpacity>
  75 + </View>
  76 + <CouponList isFetching={notuse.isFetching} data={notuse.list} onEndReached={() => this.props.actions.getCouponList('notuse')} type="notuse"/>
71 </View> 77 </View>
72 - <CouponList />  
73 - </View>  
74 - <View style={{flex: 1, backgroundColor: 'yellow'}} tabLabel="已使用"/>  
75 - <View style={{flex: 1, backgroundColor: 'green'}} tabLabel="已失效"/>  
76 - </ScrollableTabView> 78 + <View style={styles.container} tabLabel="已使用">
  79 + <CouponList isFetching={use.isFetching} data={use.list} onEndReached={() => this.props.actions.getCouponList('use')} type="use"/>
  80 + </View>
  81 + <View style={styles.container} tabLabel="已失效">
  82 + <CouponList isFetching={overtime.isFetching} data={overtime.list} onEndReached={() => this.props.actions.getCouponList('overtime')} type="overtime"/>
  83 + </View>
  84 + </ScrollableTabView>
  85 + {showSuccessTip ? <Prompt
  86 + text={showSuccessTip}
  87 + duration={800}
  88 + onPromptHidden={this._onPromptHidden}
  89 + /> : null}
  90 + {showFilter ? <View style={styles.filterContianer}>
  91 + <View style={styles.filterTabs}>
  92 + {notuse.filters && notuse.filters.map((item, index) => {
  93 + return (
  94 + <TouchableOpacity
  95 + activeOpacity={0.9}
  96 + key={index}
  97 + style={[styles.filterTab, (selectedFilter == item.filter_id) && styles.selectedFilterTab]}
  98 + onPress={() => this.pressFilter(item.filter_id)}>
  99 + <Text style={[styles.filterText, (selectedFilter == item.filter_id) && styles.selectedFilterText]}>{item.filter_name}</Text>
  100 + </TouchableOpacity>
  101 + )
  102 + })}
  103 + </View>
  104 + <TouchableOpacity style={styles.container} onPress={() => this.setState({showFilter: false})} />
  105 + </View> : null}
  106 + </View>
77 ) 107 )
78 } 108 }
  109 +
  110 + selectTab = i => {
  111 + if (i == 0 && this.state.selectedTab == 0) {
  112 + this.setState({showFilter: !this.state.showFilter});
  113 + return false;
  114 + } else {
  115 + return true;
  116 + }
  117 + }
  118 +
  119 + onChangeTab = tab => {
  120 + const { i } = tab;
  121 + let { showFilter } = this.state;
  122 + if (i != 0) {
  123 + showFilter = false;
  124 + }
  125 + this.setState({selectedTab: i, showFilter});
  126 + }
  127 +
  128 + bindCoupon = () => {
  129 + this.props.actions.bindCoupon(this.state.couponCode);
  130 + }
  131 +
  132 + pressFilter(filter_id) {
  133 + if (this.state.selectedFilter == filter_id) {
  134 + return
  135 + }
  136 + this.props.actions.getCouponList('notuse', true, filter_id);
  137 + this.setState({selectedFilter: filter_id, showFilter: false});
  138 + }
  139 +
  140 + _onPromptHidden = () => {
  141 + this.props.actions.promptHidden();
  142 + }
  143 +
  144 + _onNetPromptHidden() {
  145 + this.props.actions.netPromptHidden();
  146 + }
79 } 147 }
80 148
81 const styles = StyleSheet.create({ 149 const styles = StyleSheet.create({
@@ -112,6 +180,40 @@ const styles = StyleSheet.create({ @@ -112,6 +180,40 @@ const styles = StyleSheet.create({
112 bindCouponText: { 180 bindCouponText: {
113 fontSize: 14, 181 fontSize: 14,
114 color: '#ffffff', 182 color: '#ffffff',
  183 + },
  184 + filterContianer: {
  185 + position: 'absolute',
  186 + top: 45,
  187 + left: 0,
  188 + right: 0,
  189 + bottom: 0,
  190 + backgroundColor: 'rgba(0, 0, 0, 0.4)'
  191 + },
  192 + filterTabs: {
  193 + height: 65,
  194 + flexDirection: 'row',
  195 + justifyContent: 'space-around',
  196 + alignItems: 'center',
  197 + backgroundColor: '#ffffff'
  198 + },
  199 + filterTab: {
  200 + width: 73,
  201 + height: 33,
  202 + borderWidth: 1,
  203 + borderColor: '#e0e0e0',
  204 + borderRadius: 2,
  205 + justifyContent: 'center',
  206 + alignItems: 'center',
  207 + },
  208 + selectedFilterTab: {
  209 + backgroundColor: '#444444'
  210 + },
  211 + filterText: {
  212 + fontSize: 14,
  213 + color: '#444444',
  214 + },
  215 + selectedFilterText: {
  216 + color: '#ffffff',
115 } 217 }
116 }) 218 })
117 219
@@ -18,6 +18,9 @@ const { @@ -18,6 +18,9 @@ const {
18 HIDE_NET_ERROR_PROMPT, 18 HIDE_NET_ERROR_PROMPT,
19 GET_COUPONLIST_SUCCESS, 19 GET_COUPONLIST_SUCCESS,
20 GET_COUPONNUMS_SUCCESS, 20 GET_COUPONNUMS_SUCCESS,
  21 + SHOW_PROMPT_TIP,
  22 + GET_COUPONLIST_REQUEST,
  23 + GET_COUPONLIST_FAILURE,
21 } = require('../../constants/actionTypes').default; 24 } = require('../../constants/actionTypes').default;
22 25
23 export function setContentCode(code) { 26 export function setContentCode(code) {
@@ -377,26 +380,52 @@ export function getCouponListSuccess(payload) { @@ -377,26 +380,52 @@ export function getCouponListSuccess(payload) {
377 } 380 }
378 } 381 }
379 382
380 -export function getCouponList(type, init) { 383 +export function getCouponListRequest(payload) {
  384 + return {
  385 + type: GET_COUPONLIST_REQUEST,
  386 + payload,
  387 + }
  388 +}
  389 +
  390 +export function getCouponListFailure(payload) {
  391 + return {
  392 + type: GET_COUPONLIST_FAILURE,
  393 + payload,
  394 + }
  395 +}
  396 +
  397 +export function getCouponList(type, init, filter) {
381 return (dispatch, getState) => { 398 return (dispatch, getState) => {
382 let {app, coupon} = getState(); 399 let {app, coupon} = getState();
383 let couponData = (coupon.toJS())[type] || {}; 400 let couponData = (coupon.toJS())[type] || {};
  401 + if (!init && (couponData.isFetching || couponData.reachedEnd)) return
  402 + dispatch(getCouponListRequest(type))
384 if (init) { 403 if (init) {
385 couponData.page = 1; 404 couponData.page = 1;
386 } else { 405 } else {
387 couponData.page += 1; 406 couponData.page += 1;
388 } 407 }
  408 + if (filter != null && filter != undefined) {
  409 + couponData.filter = filter;
  410 + }
389 return new CouponService(app.host).getCouponList(couponData) 411 return new CouponService(app.host).getCouponList(couponData)
390 .then(data => { 412 .then(data => {
  413 + let length = couponData.list.length;
391 if (init) { 414 if (init) {
392 couponData.list = data.couponList; 415 couponData.list = data.couponList;
393 } else { 416 } else {
394 couponData.list = [...couponData.list, ...data.couponList]; 417 couponData.list = [...couponData.list, ...data.couponList];
395 } 418 }
  419 + if (couponData.list.length == length) {
  420 + couponData.reachedEnd = true;
  421 + couponData.page -= 1;
  422 + }
  423 + couponData.filters = data.filters;
  424 + couponData.isFetching = false;
396 dispatch(getCouponListSuccess(couponData)); 425 dispatch(getCouponListSuccess(couponData));
397 }) 426 })
398 .catch(e => { 427 .catch(e => {
399 - dispatch(); 428 + dispatch(getCouponListFailure(e));
400 }) 429 })
401 } 430 }
402 } 431 }
@@ -416,7 +445,28 @@ export function getCouponNums() { @@ -416,7 +445,28 @@ export function getCouponNums() {
416 dispatch(getCouponNumsSuccess(data)); 445 dispatch(getCouponNumsSuccess(data));
417 }) 446 })
418 .catch(e => { 447 .catch(e => {
  448 + dispatch(getCouponListFailure(e));
  449 + })
  450 + }
  451 +}
419 452
  453 +export function bindCoupon(coupon_code) {
  454 + return (dispatch, getState) => {
  455 + let {app} = getState();
  456 + return new CouponService(app.host).bindCoupon({coupon_code})
  457 + .then(data => {
  458 + dispatch(showPrompt(data.message || '兑换成功'));
  459 + dispatch(getCouponList('notuse', true));
  460 + })
  461 + .catch(e => {
  462 + dispatch(showPrompt(e.message || '兑换失败'));
420 }) 463 })
421 } 464 }
  465 +}
  466 +
  467 +export function showPrompt(payload) {
  468 + return {
  469 + type: SHOW_PROMPT_TIP,
  470 + payload,
  471 + }
422 } 472 }
@@ -10,27 +10,33 @@ let InitialState = Record({ @@ -10,27 +10,33 @@ let InitialState = Record({
10 floors: List(), 10 floors: List(),
11 showSuccessTip: false, 11 showSuccessTip: false,
12 showNetErrorTip: false, 12 showNetErrorTip: false,
13 - notuse: { 13 + notuse: new (Record({
14 type: 'notuse', 14 type: 'notuse',
15 - list: List(), 15 + isFetching: false,
  16 + reachedEnd: false,
  17 + list: [],
16 filter: 0, 18 filter: 0,
17 page: 0, 19 page: 0,
18 limit: 10, 20 limit: 10,
19 - },  
20 - use: { 21 + })),
  22 + use: new (Record({
21 type: 'use', 23 type: 'use',
22 - list: List(), 24 + isFetching: false,
  25 + reachedEnd: false,
  26 + list: [],
23 filter: 0, 27 filter: 0,
24 page: 0, 28 page: 0,
25 limit: 10, 29 limit: 10,
26 - },  
27 - overtime: { 30 + })),
  31 + overtime: new (Record({
28 type: 'overtime', 32 type: 'overtime',
29 - list: List(), 33 + isFetching: false,
  34 + reachedEnd: false,
  35 + list: [],
30 filter: 0, 36 filter: 0,
31 page: 0, 37 page: 0,
32 limit: 10, 38 limit: 10,
33 - }, 39 + })),
34 couponNums: null, 40 couponNums: null,
35 }); 41 });
36 42
@@ -17,6 +17,9 @@ const { @@ -17,6 +17,9 @@ const {
17 HIDE_NET_ERROR_PROMPT, 17 HIDE_NET_ERROR_PROMPT,
18 GET_COUPONLIST_SUCCESS, 18 GET_COUPONLIST_SUCCESS,
19 GET_COUPONNUMS_SUCCESS, 19 GET_COUPONNUMS_SUCCESS,
  20 + SHOW_PROMPT_TIP,
  21 + GET_COUPONLIST_REQUEST,
  22 + GET_COUPONLIST_FAILURE,
20 } = require('../../constants/actionTypes').default; 23 } = require('../../constants/actionTypes').default;
21 24
22 const initialState = new InitialState; 25 const initialState = new InitialState;
@@ -78,12 +81,22 @@ export default function couponReducer(state=initialState, action) { @@ -78,12 +81,22 @@ export default function couponReducer(state=initialState, action) {
78 81
79 case GET_COUPONLIST_SUCCESS: { 82 case GET_COUPONLIST_SUCCESS: {
80 let data = action.payload || {}; 83 let data = action.payload || {};
81 - return state.set(data.type, data); 84 + return state.set(data.type, new (Immutable.Record(data)));
82 } 85 }
83 86
84 case GET_COUPONNUMS_SUCCESS: { 87 case GET_COUPONNUMS_SUCCESS: {
85 return state.set('couponNums', action.payload); 88 return state.set('couponNums', action.payload);
86 } 89 }
  90 +
  91 + case SHOW_PROMPT_TIP: {
  92 + return state.set('showSuccessTip', action.payload);
  93 + }
  94 + case GET_COUPONLIST_REQUEST: {
  95 + return state.setIn([action.payload, 'isFetching'], true);
  96 + }
  97 + case GET_COUPONLIST_FAILURE: {
  98 + return state.set('error', action.payload);
  99 + }
87 } 100 }
88 101
89 return state; 102 return state;
@@ -67,6 +67,7 @@ export default class CouponService { @@ -67,6 +67,7 @@ export default class CouponService {
67 async getCouponList(data) { 67 async getCouponList(data) {
68 let params = {...data}; 68 let params = {...data};
69 delete params.list; 69 delete params.list;
  70 + delete params.filters;
70 return await this.api.get({ 71 return await this.api.get({
71 url: '', 72 url: '',
72 body: { 73 body: {