Authored by 高小祥

每日秒杀框架

Showing 48 changed files with 2510 additions and 0 deletions
... ... @@ -8,6 +8,7 @@ import QRCode from './qrcode/QRCode';
import BrandStore from './brandStore/BrandStore';
//import Classify from './classify/Classify';
import Seckill from './seckill/Seckill';
import PanicBuying from './panicBuying/PanicBuying'
import Guang from './guang/Guang';
import Message from './message/Message';
import NewArrival from './newArrival/NewArrival';
... ... @@ -41,6 +42,7 @@ export default function native(platform) {
BrandStore(platform);
// Classify(platform);
Seckill(platform);
PanicBuying(platform);
Guang(platform);
Plustar(platform);
Message(platform)
... ...
'use strict';
import React from 'react';
import ReactNative, {
AppRegistry,
Platform,
StyleSheet,
Dimensions,
TouchableOpacity,
} from 'react-native';
import {
Provider,
connect
} from 'react-redux';
import createReactClass from 'create-react-class';
import configureStore from './store/configureStore';
import appInitialState from './reducers/app/appInitialState';
import panicbuyingInitialState from './reducers/panicbuying/panicbuyingInitialState';
import DiscountProductInitialState from "./reducers/discountProduct/DiscountProductInitialState";
import PanicBuyingContainer from './containers/PanicBuyingContainer';
import {
setPlatform,
setHost,
} from './reducers/app/appActions';
import {
setStartTime,
setContentCode,
} from './reducers/panicbuying/panicbuyingActions';
function getInitialState() {
const _initState = {
app: (new appInitialState()),
panicbuying: (new panicbuyingInitialState()),
discountProduct:(new DiscountProductInitialState())
};
return _initState;
}
export default function native(platform) {
let YH_PanicBuying = createReactClass({
render() {
const store = configureStore(getInitialState());
store.dispatch(setPlatform(platform));
let time = this.props.time;
if (time) {
store.dispatch(setStartTime(time));
}
if(this.props.contentCode){
store.dispatch(setContentCode(this.props.contentCode));
}
store.dispatch(setHost(this.props.host));
return (
<Provider store={store}>
<PanicBuyingContainer />
</Provider>
);
}
});
AppRegistry.registerComponent('YH_PanicBuying', () => YH_PanicBuying);
}
let styles = StyleSheet.create({
});
... ...
'use strict';
import React, {Component} from "react";
import {View, Text, Image, ListView, StyleSheet, Dimensions, TouchableOpacity} from "react-native";
import SlicedImage from "../../../common/components/SlicedImage";
import YH_Image from '../../../common/components/YH_Image';
export default class PBProductListView extends Component {
constructor(props) {
super(props);
this.tipMessage = '';
this.state = {
selectedIndex: 0,
};
}
render() {
let {
index,
rowData,
tipMessage,
} = this.props;
this.tipMessage = tipMessage;
let brandIconUrl = SlicedImage.getSlicedUrl(rowData.defaultImages, 152, 203, 2);
let tipState = '';
let btnBgColor = '#d30018';
let btnTextcolor = 'white';
let url = rowData.shopUrl;
if (rowData.over) {
tipState = '已抢光';
btnBgColor = '#b2b2b2';
} else if (rowData.wait) {
if (!rowData.remindFlag) {
tipState = '提醒我';
btnBgColor = '#444444';
} else {
tipState = '取消提醒';
btnBgColor = 'white';
btnTextcolor = '#000000';
}
} else {
tipState = '去抢购';
}
return (
<TouchableOpacity activeOpacity={1.0} yh_exposureData={rowData.yh_exposureData} onPress={() => {
this.props.onPressProductItem && this.props.onPressProductItem(rowData, index);
}}>
<View style={styles.rowContainer}>
<View style={styles.leftImage}>
<YH_Image style={{flex:1}} url={brandIconUrl} />
{tipState == '已抢光' ?
<View style={styles.soldOutContainer}>
<Image source={require('../../images/yqg.png')}/>
</View>
: null }
</View>
<View style={styles.rowRight}>
<Text style={styles.productTitle}
numberOfLines={2}>{rowData.productName}</Text>
<View style={{
flexDirection: 'row',
flex: 1,
alignItems: 'flex-end',
justifyContent: 'space-between'
}}>
<View style={{}}>
<Text
style={styles.seckillPrice}>¥{parseFloat(rowData.secKillPrice + "").toFixed(2)}</Text>
<Text style={styles.originPrice}>
{'¥' + parseFloat(rowData.marketPrice + '').toFixed(2)}</Text>
</View>
<View style={{justifyContent: 'center', alignItems: 'flex-end'}}>
{tipState == '已抢光' && url && url.length ?
<TouchableOpacity onPress={()=> {
this.props.onPressGuangShopWithURL && this.props.onPressGuangShopWithURL(url);
}} style={[styles.priceClickTipViewRight, {
backgroundColor: 'white',
borderColor: 'black',
borderWidth: 1
}]}>
<Text style={{color: 'black'}}>进店逛逛</Text>
</TouchableOpacity>
: null}
{tipState != '已抢光' ? <TouchableOpacity onPress={() => {
if (this.tipMessage == '') {
if (rowData.wait) {
this.props.onPressRemindBtn && this.props.onPressRemindBtn(rowData);
} else {
this.props.onPressProductItem && this.props.onPressProductItem(rowData);
}
}
}}>
{
(tipState == '取消提醒') ?
<View style={[styles.priceClickTipViewRight, {
backgroundColor: btnBgColor,
borderWidth: 1.0
}]}>
<Text style={{color: btnTextcolor}}>{tipState}</Text>
</View>
: <View
style={[styles.priceClickTipViewRight, {backgroundColor: btnBgColor}]}>
<Text style={{color: btnTextcolor}}>{tipState}</Text>
</View>
}
</TouchableOpacity> : null
}
{rowData.wait &&
<Text style={styles.seckillBeginTimeTip}>{rowData.readableTime + ' 开始'}</Text>}
</View>
</View>
</View>
</View>
</TouchableOpacity>
);
}
}
let {width, height} = Dimensions.get('window');
let backgroundWidth = width;
let backgroundHeight = 121;
let rowHeight = backgroundHeight;
let WIDTH_RATION = width / 375.0;
let imageWidth = 80;
let imageHeight = 106;
let adjustPx = (number) => Math.round(number * WIDTH_RATION)
let styles = StyleSheet.create({
rowContainer: {
flexDirection: 'row',
paddingLeft: adjustPx(15),
paddingRight: adjustPx(15),
paddingTop: adjustPx(10),
paddingBottom: adjustPx(10),
backgroundColor: 'white',
},
leftImage: {
width: adjustPx(imageWidth),
height: adjustPx(imageHeight),
},
soldOutContainer: {
backgroundColor: '#0000007F',
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
alignItems: 'center',
justifyContent: 'center',
},
rowRight: {
flexDirection: 'column',
marginLeft: 10,
justifyContent: 'space-between',
flex: 1,
},
priceClickTipView: {
flexDirection: 'row',
justifyContent: 'space-between',
width: width - imageWidth - 35,
height: 50,
backgroundColor: 'yellow',
},
priceClickTipViewLeft: {
top: 25,
},
priceClickTipViewRight: {
width: 80,
height: 30,
marginTop: 10,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 3,
backgroundColor: '#d0021b',
},
secKillMarketPriceContainer: {
flexDirection: 'row',
alignItems: 'center'
},
bottomToolBar: {
top: 200,
height: 44,
backgroundColor: '#000',
},
closeScan: {
left: 10,
width: 25,
height: 25,
top: 10,
},
productTitle: {
fontSize: 14,
color: '#444444',
lineHeight: 20,
marginRight: 20,
},
seckillPrice: {
fontSize: 18,
lineHeight: 21,
fontWeight: 'bold',
color: '#d0021b',
},
originPrice: {
textDecorationLine: 'line-through',
color: '#B0B0B0',
fontSize: 12,
lineHeight: 17
},
seckillBeginTimeTip: {
fontSize: 9,
marginTop: 6,
color: '#D0021B',
},
separator: {
position: 'absolute',
left: 15,
right: 0,
bottom: 0,
height: 0.5,
backgroundColor: '#e5e5e5',
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
StyleSheet,
Dimensions,
} from 'react-native';
import Immutable, {Map} from 'immutable';
import TimeForWait from './PBTimeForWait';
import TimeForNow from './PBTimeForNow';
import TimeForOver from './PBTimeForOver';
export default class PBTimeForFocus extends Component {
constructor(props) {
super(props);
}
render() {
let {
time,
now,
over,
wait,
tickTimeOut,
tickHour,
tickMinute,
tickSecond,
lastNowTime,
} = this.props;
let colorStyle = {color: '#3e3e3e'};
let TimeComponent = null;
if (now) {
TimeComponent = TimeForNow;
} else if (over) {
TimeComponent = TimeForOver;
} else if (wait) {
TimeComponent = TimeForWait;
}
return (
<View style={styles.rowContainerFocus}>
{TimeComponent ?
<TimeComponent
time={time}
tickTimeOut={tickTimeOut}
tickHour={tickHour}
tickMinute={tickMinute}
tickSecond={tickSecond}
lastNowTime={lastNowTime}
/>
: null}
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let backgroundHeight = 61;
let rowContainerFocusWidth = Math.ceil((294 * width) / 750);
let styles = StyleSheet.create({
rowContainerFocus: {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: rowContainerFocusWidth,
height: backgroundHeight,
backgroundColor:'white',
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
StyleSheet,
Dimensions,
} from 'react-native';
import Immutable, {Map} from 'immutable';
export default class PBTimeForLostFocus extends Component {
constructor(props) {
super(props);
}
render() {
let {
time,
now,
over,
wait,
specialState,
} = this.props;
let colorStyle = {color: '#3e3e3e'};
let text = '';
if (now) {
text = '抢购中';
} else if (over) {
text = '已结束';
} else if (wait && !specialState) {
text = '即将开抢';
} else if (specialState) {
if (specialState == 1) {
text = '明天开抢';
} else {
text = specialState + '天后开抢';
}
}
return (
<View style={styles.rowContainer}>
<Text style={[colorStyle, {fontSize: 13, marginTop:15, fontWeight:'bold'}]}>{time}</Text>
<Text style={[colorStyle, {fontSize: 10, marginTop:1}]}>{text}</Text>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let backgroundHeight = 61;
let rowContainerWidth = Math.ceil((223 * width) / 750);
let styles = StyleSheet.create({
rowContainer: {
flexDirection: 'column',
alignItems: 'center',
width: rowContainerWidth,
height: backgroundHeight,
backgroundColor:'white',
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
StyleSheet,
Dimensions,
} from 'react-native';
import Immutable, {Map} from 'immutable';
import TimeNumber from './PBTimeNumber';
export default class PBTimeForNow extends Component {
constructor(props) {
super(props);
}
render() {
let {
time,
tickTimeOut,
tickHour,
tickMinute,
tickSecond,
lastNowTime,
} = this.props;
let colorStyle = {color: '#d30018'};
if (lastNowTime) {
return(
<View style={[styles.rowContainerFocus,{justifyContent: 'center'}]}>
<Text style={[colorStyle,{fontSize: 13, fontWeight: 'bold'}]}>{time} 抢购中</Text>
</View>
);
}else {
return (
<View style={styles.rowContainerFocus}>
<Text style={[colorStyle,{fontSize: 13, marginTop:15, fontWeight:'bold'}]}>{time} 抢购中</Text>
{tickTimeOut ? null :
<View style={styles.tickTimeContainer}>
<Text style={[colorStyle,{fontSize: 10}]}>剩余:</Text>
<TimeNumber text={tickHour} />
<TimeNumber text={tickMinute} />
<TimeNumber text={tickSecond} />
</View>}
</View>
);
}
}
}
let {width, height} = Dimensions.get('window');
let backgroundHeight = 61;
let rowContainerFocusWidth = Math.ceil((294 * width) / 750);
let styles = StyleSheet.create({
rowContainerFocus: {
flexDirection: 'column',
alignItems: 'center',
width: rowContainerFocusWidth,
height: backgroundHeight,
backgroundColor:'white',
},
tickTimeContainer: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
StyleSheet,
Dimensions,
} from 'react-native';
import Immutable, {Map} from 'immutable';
export default class PBTimeForOver extends Component {
constructor(props) {
super(props);
}
render() {
let {
time,
} = this.props;
let colorStyle = {color: '#d30018'};
return (
<View style={styles.rowContainerFocus}>
<Text style={[colorStyle, {fontSize: 13, marginTop: 15, fontWeight: 'bold'}]}>{time} 已结束</Text>
<Text style={[colorStyle,{fontSize: 10, marginTop: 1}]}>数量有限 抢完为止</Text>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let backgroundHeight = 61;
let rowContainerFocusWidth = Math.ceil((294 * width) / 750);
let styles = StyleSheet.create({
rowContainerFocus: {
flexDirection: 'column',
alignItems: 'center',
width: rowContainerFocusWidth,
height: backgroundHeight,
backgroundColor:'white',
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
StyleSheet,
Dimensions,
} from 'react-native';
import Immutable, {Map} from 'immutable';
import TimeNumber from './PBTimeNumber';
export default class PBTimeForWait extends Component {
constructor(props) {
super(props);
}
render() {
let {
time,
tickTimeOut,
tickHour,
tickMinute,
tickSecond,
} = this.props;
let colorStyle = {color: '#d30018'};
return (
<View style={styles.rowContainerFocus}>
<Text style={[colorStyle,{fontSize: 13, marginTop:15, fontWeight:'bold'}]}>{time} 即将开抢</Text>
{tickTimeOut ? null :
<View style={styles.tickTimeContainer}>
<Text style={[colorStyle, {fontSize: 10}]}>距开抢:</Text>
<TimeNumber text={tickHour} />
<TimeNumber text={tickMinute} />
<TimeNumber text={tickSecond} />
</View>}
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let backgroundHeight = 61;
let rowContainerFocusWidth = Math.ceil((294 * width) / 750);
let styles = StyleSheet.create({
rowContainerFocus: {
flexDirection: 'column',
alignItems: 'center',
width: rowContainerFocusWidth,
height: backgroundHeight,
backgroundColor:'white',
},
tickTimeContainer: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
},
});
... ...
'use strict';
import React, {Component} from "react";
import ReactNative, {View, Text, Image, ListView, StyleSheet, Dimensions, TouchableOpacity, Platform} from "react-native";
import Immutable from "immutable";
import TimeForFocus from "./PBTimeForFocus";
import TimeForLostFocus from "./PBTimeForLostFocus";
let diff;
export default class PBTimeListView extends Component {
constructor(props) {
super(props);
this._renderRow = this._renderRow.bind(this);
this._renderSeparator = this._renderSeparator.bind(this);
this._scrollToFocusActivity = this._scrollToFocusActivity.bind(this);
this._caculateTimerState = this._caculateTimerState.bind(this);
this._searchRightNowStartActivity = this._searchRightNowStartActivity.bind(this);
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1.key != r2.key,
});
this.curFocusActivity = Immutable.fromJS({});
this.secKillProductVoList = Immutable.fromJS([]);
this.state = {
tickHour: '00',
tickMinute: '00',
tickSecond: '00',
tickTimeOut: true,
};
this.scrollX = 0;
}
_scrollToFocusActivity = () => {
//focus
if (this.secKillProductVoList.length >= 1) {
let listLength = this.secKillProductVoList.length;
let scrollX;
let currentScrollX = this.listView ? this.listView.scrollProperties.offset : 0;
scrollX = (this.curFocusActivity.get('index') - 1) * (rowContainerWidth + separatorWidth);
if (Platform.OS == 'ios') {
if (scrollX < 0) {
scrollX = 0
} else if (this.listView && (scrollX > this.listView.scrollProperties.contentLength - this.listView.scrollProperties.visibleLength)) {
scrollX = this.listView.scrollProperties.contentLength - this.listView.scrollProperties.visibleLength
}
}
if (this.scrollX == scrollX && ((Math.floor(scrollX) == Math.floor(currentScrollX)) || scrollX != 0)) {
return;
}
this.scrollX = scrollX;
this.listView && this.listView.scrollTo({x: scrollX, y: 0, animated: true});
}
}
_searchRightNowStartActivity() {
this.secKillProductVoList.forEach((activityItem, i) => {
if (activityItem.has('wait')) {
let nowTime = Date.parse(new Date()) / 1000 + diffTime;
let startTime = activityItem.get('startTime') / 1000;
let offsetTime = startTime - nowTime;
if (offsetTime < 0) {
activityItem.focus = true;
this.curFocusActivity = activityItem;
this._scrollToFocusActivity();
this.props.onFocusToCurStartedActivity && this.props.onFocusToCurStartedActivity(activityItem);
}
}
});
}
_caculateTimerState() {
let nextActivity = undefined;
if (this.curFocusActivity.get('index') < this.secKillProductVoList.length - 1) {
nextActivity = this.secKillProductVoList[this.curFocusActivity.get('index') + 1];
}
let nowTime = Date.parse(new Date()) / 1000 + diffTime;
let time = this.curFocusActivity.has('now') ? this.curFocusActivity.get('endTime') / 1000 : this.curFocusActivity.get('startTime') / 1000;
let offsetTime = time - nowTime;
let hour = parseInt(offsetTime / (60 * 60), 10);
let minute = parseInt(offsetTime % (60 * 60) / 60, 10);
let second = offsetTime % 60;
if (offsetTime < 0) {
if (!this.state.tickTimeOut) {
this.setState({
tickHour: '',
tickMinute: '',
tickSecond: '',
tickTimeOut: true,
});
if (this.curFocusActivity.has('now') && nextActivity != undefined) {
nextActivity = nextActivity.toJS();
nextActivity.focus = true;
this.curFocusActivity = Immutable.fromJS(nextActivity);
this._scrollToFocusActivity();
this.props.onFocusToCurStartedActivity && this.props.onFocusToCurStartedActivity(nextActivity);
} else {
this.props.onFocusToCurStartedActivity && this.props.onFocusToCurStartedActivity(this.curFocusActivity.toJS());
}
}
} else {
this.setState({
tickHour: hour < 0 ? '00' : (hour < 10 ? ('0' + hour) : (hour > 99 ? 99 : hour)),
tickMinute: minute < 0 ? '00' : (minute < 10 ? ('0' + minute) : minute),
tickSecond: second < 0 ? '00' : (second < 10 ? ('0' + second) : second),
tickTimeOut: false,
});
}
}
componentDidMount() {
this._scrollToFocusActivity();
this.timer = setInterval(function () {
if (!this.curFocusActivity && this.curFocusActivity.size > 0 && !this.curFocusActivity.has('over')) {
return;
}
this._caculateTimerState();
this._searchRightNowStartActivity();
}.bind(this), 1000);
}
componentWillUnmount() {
this.timer && clearInterval(this.timer);
}
componentWillReceiveProps(nextProps) {
this.curFocusActivity = nextProps.curActivity;
if (this.curFocusActivity) {
this._scrollToFocusActivity();
}
this._caculateTimerState();
}
_renderRow(rowData, sectionID, rowID) {
rowData = rowData.toJS();
return (
<TouchableOpacity activeOpacity={1.0} onPress={() => {
if (rowData.focus) {
return;
}
rowData.focus = true;
this.curFocusActivity = Immutable.fromJS(rowData);//rowData;
this._scrollToFocusActivity();
this.props.onPressTimeItem && this.props.onPressTimeItem(rowData);
//埋点
let params = {
TAB_ID: Number(rowID)+1,
TAB_PARAM: rowData.time,
};
ReactNative.NativeModules.YH_CommonHelper.logEvent('YB_SECKILL_TAB_C', params);
}}>
{rowData.focus ? <TimeForFocus
key={'row' + rowID}
time={rowData.time}
endTime={rowData.endTime}
now={rowData.now}
over={rowData.over}
wait={rowData.wait}
tickTimeOut={this.state.tickTimeOut}
tickHour={this.state.tickHour}
tickMinute={this.state.tickMinute}
tickSecond={this.state.tickSecond}
lastNowTime={false}
/> : <TimeForLostFocus
key={'row' + rowID}
time={rowData.time}
now={rowData.now}
over={rowData.over}
wait={rowData.wait}
specialState={
rowData.specialState
}
/>
}
</TouchableOpacity>
);
}
_renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
let listLength = this.secKillProductVoList.length;
if (listLength - 1 == rowID) {
return null;
}
return (
<View key={'sep' + rowID} style={styles.separator}>
</View>
);
}
render() {
let {
resource,
diff,
curActivity,
} = this.props;
diffTime = diff;
this.secKillProductVoList = resource;
this.curFocusActivity = curActivity;
backgroundWidth = Math.max((rowContainerWidth + separatorWidth) * (resource.length - 1) + rowContainerFocusWidth, width);
return (
<View style={[styles.container]}>
<View style={{width: width, height: 0.5, backgroundColor: '#e5e5e5',}}/>
<ListView
ref={(ref)=>this.listView = ref}
contentContainerStyle={[styles.contentContainer]}
enableEmptySections={true}
dataSource={this.dataSource.cloneWithRows(resource)}
renderSeparator={this._renderSeparator}
renderRow={this._renderRow}
showsHorizontalScrollIndicator={false}
scrollEnabled={true}
horizontal={true}
scrollsToTop={false}
/>
<View style={{width: width, height: 0.5, backgroundColor: '#e5e5e5',}}/>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let backgroundWidth = width;
let backgroundHeight = 61;
let diffTime;
let rowContainerWidth = Math.ceil((223 * width) / 750);
let rowContainerFocusWidth = Math.ceil((294 * width) / 750);
let separatorWidth = 1;
let styles = StyleSheet.create({
container: {
marginLeft: -1,
width: backgroundWidth + 2,
height: backgroundHeight,
backgroundColor: 'white',
},
contentContainer: {
flexDirection: 'row',
backgroundColor: 'white',
},
bottomToolBar: {
top: 200,
height: 44,
backgroundColor: '#000',
},
closeScan: {
left: 10,
width: 25,
height: 25,
top: 10,
},
separator: {
width: separatorWidth,
top: 11,
height: backgroundHeight - 22,
backgroundColor: '#dfe3e2',
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
StyleSheet,
} from 'react-native';
export default class PBTimeNumber extends Component {
constructor(props) {
super(props);
}
render() {
let {
text,
} = this.props;
return (
<View style={styles.tickTime}>
<Text style={styles.tickTimeText}>{text}</Text>
</View>
);
}
}
let styles = StyleSheet.create({
tickTime: {
backgroundColor:'#d30018',
alignItems: 'center',
justifyContent: 'center',
left:0,
marginLeft:5,
width:15,
height:15,
borderRadius:3,
},
tickTimeText: {
color: 'white',
fontSize: 9,
},
});
... ...
'use strict'
import React, {Component} from "react";
import {
StyleSheet,
Dimensions,
Platform,
View,
Text,
Image,
FlatList,
TouchableOpacity,
RefreshControl,
InteractionManager,
SectionList
} from "react-native";
import TimerMixin from "react-timer-mixin";
import Immutable from "immutable";
import ProductListView from "./PBProductListView";
import PBTimeListView from "./PBTimeListView";
import Prompt from "../../../coupon/components/coupon/Prompt";
import ProductListCell from "../../../common/components/ListCell/ProductListCell";
import LoadingIndicator from "../../../common/components/LoadingIndicator";
const SECTION_TYPE_SECKILL = 1;
const SECTION_TYPE_DISCOUNT = 2;
export default class PanicBuying extends Component {
constructor(props) {
super(props);
this.renderItem = this.renderItem.bind(this);
this.renderListHeaderComponent = this.renderListHeaderComponent.bind(this);
this.renderDiscountSection = this.renderDiscountSection.bind(this);
this._onPressTimeItem = this._onPressTimeItem.bind(this);
this.isFetching = false;
this.tipMessage = '';
this.queryActivityInfo = Immutable.fromJS({});
this.firstLaunch = true;
this.renderDiscountProduct = this.renderDiscountProduct.bind(this)
}
componentDidMount() {
if (Platform.OS === 'ios') {
// this.timer = TimerMixin.setTimeout(() => {
// this.listView && this.listView.getScrollResponder().startPullToRefresh();
// }, 0);
this.props.onRefresh && this.props.onRefresh();
} else {
this.props.onRefresh && this.props.onRefresh();
}
}
componentWillUnmount() {
this.timer && TimerMixin.clearTimeout(this.timer);
}
componentWillReceiveProps(nextProps) {
this.firstLaunch = false;
}
_onPressTimeItem(activity) {
this.props.onPressTimeItem(activity);
this.props.onRefresh && this.props.onRefresh();
}
renderHeader() {
if (this.queryActivityInfo && this.queryActivityInfo.secKillProductVoList && this.queryActivityInfo.secKillProductVoList.size > 0) {
return (
<PBTimeListView
resource={this.queryActivityInfo.secKillProductVoList.toArray()}
onPressTimeItem={this._onPressTimeItem}
onFocusToCurStartedActivity={this.props.onFocusToCurStartedActivity}
curActivity={this.props.curActivity}
diff={diff}
/>
);
}
return null
}
renderListHeaderComponent() {
if (this.queryActivityInfo.secKillProductVoList.size == 0 && !this.firstLaunch) {
return (
<View style={styles.seckillEndView}>
<Text style={{fontSize: 16, color: 'black'}}>来晚啦~秒杀已结束</Text>
</View>
);
} else {
return null;
}
}
renderSectionHeader({section: {type, title}}) {
if (type == SECTION_TYPE_DISCOUNT) {
return <View style={styles.sectionHeaderContainer}>
<View style={styles.sectionLine}/>
<Text style={styles.sectionHeaderText}>{title}</Text>
<View style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: 0.5,
backgroundColor: '#E0E0E0'
}}/>
</View>
}
return null
}
renderSectionFooter({section: {type}}) {
if (type == SECTION_TYPE_SECKILL) {
return <View style={styles.descriptionContainer}>
<Text style={[styles.descriptionText, {fontWeight: 'bold'}]}>{'秒杀活动说明\n'}</Text>
<Text
numberOfLines={0}
style={styles.descriptionText}>
{"1. 秒杀商品限活动开始后,在详情页抢购,提前加入购物车无效;\n2. 秒杀数量有限,抢完即止;\n3. 秒杀商品限购1件, 不能使用优惠券或优惠码;\n4. 活动最终解释权归YOHO!BUY有货所有。"}
</Text>
</View>
}
return null
}
renderItem({item, index, section}) {
let {
curActivity,
} = this.props;
curActivity = curActivity.toJS();
let P_NAME = 'aFP_Seckill';
if (Platform.OS === 'ios') {
P_NAME = 'iFP_Seckill';
}
item.yh_exposureData = {
P_NAME: P_NAME,
PRD_NUM: Number(index)+1,
PRD_SKN: item.productSkn,
TAB_ID: Number(curActivity.index) + 1,
TAB_NAME : curActivity.time,
exposureEnd: 1,
}
if (this.queryActivityInfo.secKillProductVoList.size && item.secKillPrice) {
return (
<ProductListView
index={index}
rowData={item}
onPressGuangShopWithURL={this.props.onPressGuangShopWithURL}
onPressProductItem={this.props.onPressProductItem}
onPressRemindBtn={this.props.onPressRemindBtn}
tipMessage={this.tipMessage}
/>
);
}
return null;
}
renderDiscountProduct({item, index}) {
let paddingLeft = index % 2 === 1 ? rowMarginHorizontal / 2 : rowMarginHorizontal;
let customStyle = index === 0 || index === 1 ? {
paddingLeft
} : {
paddingLeft
};
return <ProductListCell
style={
[styles.listContainer, customStyle]
}
key={'row' + index}
rowID={index}
data={item}
onPressProduct={this.props.onPressProduct}
/>
}
renderDiscountSection({item}) {
return <FlatList
data={item}
style={{backgroundColor: 'white'}}
numColumns={2}
renderItem={this.renderDiscountProduct}
/>
}
_renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
return (
<View key={'separator' + sectionID + rowID} style={styles.separator}/>
);
}
render() {
let {
isFetching,
error,
queryActivityInfo,
queryProductList,
tipMessage,
ptr,
localServerTimeDiff,
curActivity,
discountProduct,
} = this.props;
this.isFetching = isFetching;
this.queryActivityInfo = queryActivityInfo;
this.tipMessage = tipMessage;
diff = localServerTimeDiff;
let isPullToRefresh = ptr && isFetching;
let sections = [
{title: null, type: SECTION_TYPE_SECKILL, data: queryProductList ? queryProductList : []},
];
return (
<View style={styles.container}>
{this.renderHeader()}
<SectionList
renderItem={this.renderItem}
contentContainerStyle={styles.contentContainer}
ListHeaderComponent={this.renderListHeaderComponent}
renderSectionFooter={this.renderSectionFooter}
renderSectionHeader={this.renderSectionHeader}
ItemSeparatorComponent={this._renderSeparator}
stickySectionHeadersEnabled={false}
onRefreshData={() => this.props.onRefresh && this.props.onRefresh()}
sections={sections}
yh_viewVisible={true}
/>
<LoadingIndicator isVisible={isPullToRefresh}/>
{tipMessage != '' ?
<Prompt
text={tipMessage}
duration={800}
onPromptHidden={this.props.onClearTipMessage}
/>
: null
}
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let diff = 0;
let rowWidth = Math.ceil(137.5 * width / 320);
let rowMarginHorizontal = (width - rowWidth * 2) / 3;
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f0f0f0',
},
contentContainer: {
// flexDirection: 'row',
// flexWrap: 'wrap',
},
seckillEndView: {
height: 200,
width: width,
alignItems: 'center',
justifyContent: 'center',
},
listContainer: {
width: width / 2,
backgroundColor: 'white'
},
brandFilterContainer: {
marginLeft: -1,
width: width + 2,
height: 37,
borderTopColor: '#e5e5e5',
borderBottomColor: '#e5e5e5',
borderWidth: 0.5,
},
descriptionContainer: {
paddingTop: 20,
paddingBottom: 10,
paddingLeft: 15,
paddingRight: 15,
},
descriptionText: {
color: '#b0b0b0',
fontSize: 12,
lineHeight:17,
},
sectionHeaderContainer: {
width: width,
paddingTop: 10,
paddingBottom: 10,
paddingLeft: 15,
backgroundColor: 'white',
alignItems: 'center',
flexDirection: 'row'
},
sectionHeaderText: {
fontSize: 14,
marginLeft: 10,
color: '#444444',
fontFamily: 'PingFang-SC-Medium'
},
separator: {
position: 'absolute',
left: 15,
right: 0,
bottom: 0,
height: 0.5,
backgroundColor: '#e5e5e5',
},
sectionLine: {width: 2, height: 15, backgroundColor: '#444444', marginLeft: 2}
});
... ...
import keyMirror from 'key-mirror';
export default keyMirror({
SET_PLATFORM: null,
SET_HOST: null,
SET_CHANNEL: null,
SET_TYPE: null,
SET_START_TIME: null,
QUERY_ACTIVITY_REQUEST: null,
QUERY_ACTIVITY_SUCCESS: null,
QUERY_ACTIVITY_FAILURE: null,
QUERY_PRODUCT_LIST_REQUEST: null,
QUERY_PRODUCT_LIST_SUCCESS: null,
QUERY_PRODUCT_LIST_FAILURE: null,
QUERY_DISCOUNT_PRODUCT_LIST_REQUEST:null,
QUERY_DISCOUNT_PRODUCT_LIST_SUCCESS:null,
QUERY_DISCOUNT_PRODUCT_LIST_FAILURE:null,
QUERY_PRODUCT_AND_REMIND_LIST_REQUEST: null,
QUERY_PRODUCT_AND_REMIND_LIST_SUCCESS: null,
QUERY_PRODUCT_AND_REMIND_LIST_FAILURE: null,
SHOW_TIP_MESSAGE: null,
CLEAR_TIP_MESSAGE: null,
UPDATE_SECKILL_PRODUCT_VO_LIST: null,
UPDATE_SECKILL_PRODUCT_LIST: null,
});
... ...
'use strict'
import React, {Component} from "react";
import ReactNative, {StyleSheet, Dimensions, Platform, View, NativeModules} from "react-native";
import {bindActionCreators} from "redux";
import {connect} from "react-redux";
import {Map} from "immutable";
import * as panicbuyingActions from "../reducers/panicbuying/panicbuyingActions";
import * as discountProductActions from "../reducers/discountProduct/DiscountProductActions";
import PanicBuying from "../components/panicbuying/PanicBuying";
const actions = [
panicbuyingActions,
discountProductActions
];
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 PanicBuyingContainer extends Component {
constructor(props) {
super(props);
this._onPressTimeItem = this._onPressTimeItem.bind(this);
this._onPressProductItem = this._onPressProductItem.bind(this);
this._onPressRemindBtn = this._onPressRemindBtn.bind(this);
this._onPressGuangShopWithURL = this._onPressGuangShopWithURL.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this._onClearTipMessage = this._onClearTipMessage.bind(this);
this._onFocusToCurStartedActivity = this._onFocusToCurStartedActivity.bind(this);
this._onFetchDiscountProductList = this._onFetchDiscountProductList.bind(this);
this._onEndReached = this._onEndReached.bind(this);
this._onPressProduct = this._onPressProduct.bind(this);
}
_onPressProduct(product) {
this.props.actions.clickProductItem(product);
let productSkn = product && product.get('product_skn', 0);
if (!productSkn) {
return;
}
let pageName = 'iFP_Alliance';
if (Platform.OS === 'android') {
pageName = 'aFP_Alliance';
}
let url = `http://m.yohobuy.com?openby:yohobuy={"action":"go.productDetail","params":{"product_skn":"${productSkn}", "from_page_name":"${pageName}"}}`;
NativeModules.YH_CommonHelper.jumpWithUrl(url);
}
_onPressTimeItem(activity) {
this.props.actions.clickActivityTimeItem(activity);
}
_onPressProductItem(product, index) {
this.props.actions.clickProductItem(product);
let {
curActivity,
} = this.props.panicbuying;
curActivity = curActivity.toJS();
//埋点
let params = {
TAB_ID: Number(curActivity.index) + 1,
TAB_NAME : curActivity.time,
PRD_NUM: Number(index) + 1,
PRD_SKN: product.productSkn
};
ReactNative.NativeModules.YH_CommonHelper.logEvent('YB_SECKILL_TAB_LIST_C', params);
}
_onPressGuangShopWithURL(url) {
if (url && url.length) {
NativeModules.YH_CommonHelper.jumpWithUrl(url);
}
}
_onPressRemindBtn(product) {
this.props.actions.clickRemindBtn(product);
}
_onRefresh(activityId) {
this.props.actions.refreshList(activityId);
}
_onClearTipMessage() {
this.props.actions.clearTipMessage();
}
_onFocusToCurStartedActivity(activity) {
if (this.props.panicbuying.ptr) {
return;
}
this.props.actions.clickActivityTimeItem(activity);
// this.props.actions.getSeckillQueryActivity(activity.startTime / 1000);
}
_onFetchDiscountProductList() {
// this.props.actions.getDiscountProductList()
}
_onEndReached() {
// this.props.actions.getDiscountProductList()
}
render() {
let {
isFetching,
error,
queryActivityInfo,
queryProductList,
queryRemindList,
tipMessage,
ptr,
curActivity,
localServerTimeDiff,
} = this.props.panicbuying;
if (queryProductList) {
queryProductList = queryProductList.toJS();
}
let productList = queryProductList[curActivity.get('activityId')];
let discountProduct = this.props.discountProduct;
return (
<PanicBuying
isFetching={isFetching}
error={error}
ptr={ptr}
queryActivityInfo={queryActivityInfo}
queryProductList={productList}
queryRemindList={queryRemindList}
tipMessage={tipMessage}
curActivity={curActivity}
discountProduct={discountProduct}
localServerTimeDiff={localServerTimeDiff}
onPressTimeItem={this._onPressTimeItem}
onPressProductItem={this._onPressProductItem}
onPressRemindBtn={this._onPressRemindBtn}
onPressGuangShopWithURL={this._onPressGuangShopWithURL}
onRefresh={this._onRefresh}
onEndReached={this._onEndReached}
onFetchDiscountProductList={this._onFetchDiscountProductList}
onClearTipMessage={this._onClearTipMessage}
onFocusToCurStartedActivity={this._onFocusToCurStartedActivity}
onPressProduct={this._onPressProduct}
/>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(PanicBuyingContainer);
... ...
'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 DiscountProductService from "../../services/DiscountProductService";
import Immutable from "immutable";
const {
QUERY_DISCOUNT_PRODUCT_LIST_REQUEST,
QUERY_DISCOUNT_PRODUCT_LIST_SUCCESS,
QUERY_DISCOUNT_PRODUCT_LIST_FAILURE,
} = require('../../constants/actionTypes').default;
export function queryDiscountProductListRequest() {
return {
type: QUERY_DISCOUNT_PRODUCT_LIST_REQUEST,
}
}
export function queryDiscountProductListSuccess(json) {
return {
type: QUERY_DISCOUNT_PRODUCT_LIST_SUCCESS,
payload: json
}
}
export function queryDiscountProductListFailure(error) {
return {
type: QUERY_DISCOUNT_PRODUCT_LIST_FAILURE,
payload: error
}
}
/*
* 限时热促商品列表
*/
export function getDiscountProductList() {
return (dispatch, getState) => {
let {app, discountProduct} = getState();
let {isFetching, endReached, currentPage, pageSize, type} = discountProduct
if (isFetching || endReached) {
return;
}
let channel = app.channel;
let page = currentPage + 1;
dispatch(queryDiscountProductListRequest());
return new DiscountProductService(app.host).fetchDiscountProductList(type, channel, pageSize, page)
.then(json => {
let payload = {}
if (json.page > 1) {
let oldList = discountProduct.get('list');
let newList = [...oldList, ...(json.product_list != null ? json.product_list.map(item=>Immutable.fromJS(item)) : [])];
payload.list = newList;
} else {
payload.list = json.product_list != null ? json.product_list.map(item=>Immutable.fromJS(item)) : [];
}
payload.endReached = (payload.list.length == 0) || (json.page == json.page_total);
payload.currentPage = json.page;
dispatch(queryDiscountProductListSuccess(payload));
})
.catch(error => {
dispatch(queryDiscountProductListFailure(error));
});
};
}
\ No newline at end of file
... ...
'use strict';
import {Record} from "immutable";
let InitialState = Record({
contentCode: 'b78b32ed81b18dde8ac84fd33602b88b',
isFetching: false,
list: [],
currentPage: 0,
endReached: false,
similarIndex: -1,
total: 0,
type: 'discount',
pageSize: 60,
pageCount: 0,
});
export default InitialState;
... ...
'use strict';
import InitialState from "./DiscountProductInitialState";
import Immutable from "immutable";
const {
QUERY_DISCOUNT_PRODUCT_LIST_REQUEST,
QUERY_DISCOUNT_PRODUCT_LIST_SUCCESS,
QUERY_DISCOUNT_PRODUCT_LIST_FAILURE,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function DiscountProductReducer(state = initialState, action) {
switch (action.type) {
case QUERY_DISCOUNT_PRODUCT_LIST_FAILURE: {
return state.set('isFetching', false);
}
case QUERY_DISCOUNT_PRODUCT_LIST_SUCCESS: {
let json = action.payload;
return state.set('isFetching', false)
.set('list', json.list)
.set('currentPage', json.currentPage)
.set('endReached', json.endReached);
}
case QUERY_DISCOUNT_PRODUCT_LIST_REQUEST: {
return state.set('isFetching', true);
}
}
return state;
}
... ...
import {combineReducers} from "redux";
import app from "./app/appReducer";
import panicbuying from "./panicbuying/panicbuyingReducer";
import discountProduct from "./discountProduct/DiscountProductReducer";
const rootReducer = combineReducers({
app,
panicbuying,
discountProduct,
});
export default rootReducer;
... ...
'use strict';
import ReactNative from 'react-native';
import SeckillService from '../../services/PanicbuyingService';
import Immutable, {Map} from 'immutable';
import PanicbuyingService from '../../services/PanicbuyingService';
const Platform = require('Platform');
const moment = require('moment');
const {
SET_START_TIME,
QUERY_ACTIVITY_REQUEST,
QUERY_ACTIVITY_SUCCESS,
QUERY_ACTIVITY_FAILURE,
QUERY_PRODUCT_LIST_REQUEST,
QUERY_PRODUCT_LIST_SUCCESS,
QUERY_PRODUCT_LIST_FAILURE,
QUERY_PRODUCT_AND_REMIND_LIST_REQUEST,
QUERY_PRODUCT_AND_REMIND_LIST_SUCCESS,
QUERY_PRODUCT_AND_REMIND_LIST_FAILURE,
SHOW_TIP_MESSAGE,
CLEAR_TIP_MESSAGE,
UPDATE_SECKILL_PRODUCT_VO_LIST,
UPDATE_SECKILL_PRODUCT_LIST,
} = require('../../constants/actionTypes').default;
/**
* [时间缺0补0]
*/
const _timeFormat = (tick) => {
return tick < 10 ? `0${tick}` : tick;
};
export function queryActivityRequest() {
return {
type: QUERY_ACTIVITY_REQUEST,
};
}
export function queryActivitySuccess(json) {
return {
type: QUERY_ACTIVITY_SUCCESS,
payload: json
};
}
export function queryActivityFailure(error) {
return {
type: QUERY_ACTIVITY_FAILURE,
payload: error
};
}
export function queryProductListRequest(activity) {
return {
type: QUERY_PRODUCT_LIST_REQUEST,
payload: activity
}
}
export function queryProductListSuccess(json) {
return {
type: QUERY_PRODUCT_LIST_SUCCESS,
payload: json
}
}
export function queryProductListFailure(error) {
return {
type: QUERY_PRODUCT_LIST_FAILURE,
payload: error
}
}
export function queryProductAndRemindListRequest(activity) {
return {
type: QUERY_PRODUCT_AND_REMIND_LIST_REQUEST,
payload: activity
}
}
export function queryProductAndRemindListSuccess(json) {
return {
type: QUERY_PRODUCT_AND_REMIND_LIST_SUCCESS,
payload: json
}
}
export function queryProductAndRemindListFailure(error) {
return {
type: QUERY_PRODUCT_AND_REMIND_LIST_FAILURE,
payload: error
}
}
export function showTipMessage(message) {
return {
type: SHOW_TIP_MESSAGE,
payload: message
}
}
export function clearTipMessage() {
return {
type:CLEAR_TIP_MESSAGE,
}
}
export function updateSecKillProductVoList(json) {
return {
type: UPDATE_SECKILL_PRODUCT_VO_LIST,
payload: json
}
}
export function updateSecKillProductList(json) {
return {
type: UPDATE_SECKILL_PRODUCT_LIST,
payload: json
}
}
export function setStartTime(startTime) {
startTime = moment(startTime, 'YYYYMMDDHHmmss').unix();
return {
type: SET_START_TIME,
payload: startTime,
};
}
/*
* 秒杀时间栏
*/
export function getSeckillQueryActivity(inStartTime = 0) {
return (dispatch, getState) => {
let {app, panicbuying} = getState();
let {startTime} = panicbuying;
if (inStartTime) {
startTime = inStartTime;
}
dispatch(queryActivityRequest());
return new PanicbuyingService(app.host).fetchQueryActivity()
.then(json => {
if (json.secKillProductVoList && json.secKillProductVoList.length) {
let processedTimeInfo = parseActivityTimeLsit(json, startTime);
let queryActivityInfo = processedTimeInfo.newQueryActivityInfo;
let focusActivity = processedTimeInfo.focusActivity;
dispatch(queryActivitySuccess({queryActivityInfo, focusActivity}));
if (Date.now() < focusActivity.startTime) {
dispatch(getSeckillProductRemindList(focusActivity));
} else {
dispatch(getSeckillProductList(focusActivity));
}
} else {
dispatch(queryActivityFailure({error:'来晚啦~秒杀已结束'}));
}
})
.catch(error => {
dispatch(queryActivityFailure(error));
dispatch(showTipMessage(error.message));
});
};
}
/*
* 秒杀商品列表
*/
export function getSeckillProductList(activity) {
return (dispatch, getState) => {
let {app, panicbuying} = getState();
let {queryRemindList, queryProductList, curActivity} = panicbuying;
if (!activity) {
activity = curActivity.toJS();
}
dispatch(queryProductListRequest(activity));
queryRemindList = queryRemindList.toJS();
return new PanicbuyingService(app.host).fetchQueryProductList(activity.activityId)
.then(json => {
let productList = [];
if (queryRemindList && queryRemindList.length > 0) {
productList = productAddFlag(json, queryRemindList);
} else {
productList = productAddFlag(json);
}
queryProductList = queryProductList.toJS();
queryProductList[activity.activityId] = productList;
dispatch(queryProductListSuccess(queryProductList));
})
.catch(error => {
dispatch(queryProductListFailure(error));
});
};
}
/*
* 秒杀商品列表
*/
export function getSeckillProductRemindList(activity) {
return (dispatch, getState) => {
let queryProductRemindList = (activity, uid) => {
dispatch(queryProductAndRemindListRequest(activity));
let {app, panicbuying} = getState();
let {queryProductList} = panicbuying;
Promise.all([
new PanicbuyingService(app.host).fetchQueryProductList(activity.activityId),
new PanicbuyingService(app.host).fetchQueryRemindList(activity.activityId, uid),
]).then(result => {
let productList = result[0];
let remindList = result[1];
productList = productAddFlag(productList, remindList);
queryProductList = queryProductList.toJS();
queryProductList[activity.activityId] = productList;
dispatch(queryProductAndRemindListSuccess({queryProductList,remindList}));
})
.catch(error => {
dispatch(queryProductAndRemindListFailure(error));
});
}
ReactNative.NativeModules.YH_CommonHelper.uid()
.then(uid => {
queryProductRemindList(activity, uid);
})
.catch(error => {
dispatch(getSeckillProductList(activity));
});
};
}
export function addCancelUserReminder(method='app.seckill.addUserReminder', activityId=0, productSkn=0, uid=0, secKillId=0, okTip='', failTip='') {
return (dispatch, getState) => {
let {app, panicbuying} = getState();
let {queryProductList, curActivity} = panicbuying;
dispatch(clearTipMessage());
return new PanicbuyingService(app.host).addCancelUserReminder(method,activityId,productSkn,uid,secKillId)
.then(json => {
//更新提醒状态
let remindFlag;
if (method === 'app.seckill.addUserReminder') {
remindFlag = true;
} else {
remindFlag = false;
}
queryProductList = queryProductList.toJS();
let curActivity = {};
queryProductList[activityId].forEach((productItem, i) => {
if (productItem.id === secKillId) {
productItem.remindFlag = remindFlag;
}
});
dispatch(showTipMessage(okTip));
dispatch(updateSecKillProductList(queryProductList));
})
.catch(error => {
dispatch(showTipMessage(failTip));
});
};
}
export function clickActivityTimeItem(activity) {
return (dispatch, getState) => {
let {app, panicbuying} = getState();
let {queryActivityInfo} = panicbuying;
queryActivityInfo = queryActivityInfo.toJS();
let curActivity = activity;
queryActivityInfo.secKillProductVoList.forEach((activityTimeItem, i) => {
if (activityTimeItem.activityId === activity.activityId) {
curActivity = activityTimeItem;
activityTimeItem.focus = true;
} else {
activityTimeItem.focus = false;
}
});
let secKillProductVoList = queryActivityInfo.secKillProductVoList;
dispatch(updateSecKillProductVoList({secKillProductVoList, curActivity}));
};
}
export function clickProductItem(product) {
return (dispatch, getState) => {
if (!product.secKillSku || product.secKillSku.length == 0) {
return;
}
let skn = product.secKillSku[0].productSkn;
//
let url = `http://m.yohobuy.com?openby:yohobuy={"action":"go.productDetail","params":{"product_skn":"${skn}", "is_seckill": "Y","tags_filter":"","from_page_name":"${Platform.OS === 'ios'?'iFP_Seckill':'aFP_Seckill'}"}}`;
ReactNative.NativeModules.YH_CommonHelper.jumpWithUrl(url);
}
}
export function clickRemindBtn(product) {
return (dispatch, getState) => {
ReactNative.NativeModules.YH_CommonHelper.uid()
.then(uid => {
queryRemindList(uid);
})
.catch(error => {
ReactNative.NativeModules.YH_CommonHelper.login()
.then(uid => {
dispatch(clickActivityTimeItem(product));
queryRemindList(uid);
})
.catch(error => {
});
});
let queryRemindList = (uid) => {
let {app, panicbuying} = getState();
let {queryProductList} = panicbuying;
if (!product.secKillSku || product.secKillSku.length == 0) {
return;
}
let skn = product.secKillSku[0].productSkn;
let actionName,
action,
params,
on_off,
method;
let okTip,
failTip,
result;
// default
on_off = true;
action = 'go.addSecKill';
method = 'app.seckill.addUserReminder';
params = {
skn: skn,
startTime: product.startTime,
productName: product.productName,
};
okTip = '设置提醒成功,将在开抢3分钟前提醒';
failTip = '设置提醒失败';
if (product.remindFlag) {
on_off = false;
action = 'go.delSecKill';
okTip = '取消提醒成功';
failTip = '取消提醒失败';
method = 'app.seckill.cancelUserReminder';
}
ReactNative.NativeModules.YH_SeckillHelper.remindTip({
method: action,
arguments: params,
})
.then(flag => { //成功
dispatch(addCancelUserReminder(method, product.activityId, skn,uid, product.id, okTip, failTip));
})
.catch(error => {
dispatch(showTipMessage('请到设置中开启日历授权'));
});
};
}
}
export function refreshList() {
return (dispatch, getState) => {
let {app, panicbuying} = getState();
let {queryActivityInfo,queryProductList,curActivity} = panicbuying;
if (curActivity.get('activityId') !== 0 && queryActivityInfo.secKillProductVoList && queryActivityInfo.secKillProductVoList.size > 0) {
queryActivityInfo.secKillProductVoList.forEach((activityInfo, i) => {
if (activityInfo.get('activityId') === curActivity.get('activityId')) {
dispatch(getSeckillQueryActivity(activityInfo.get('startTime')/1000));
}
});
} else {
dispatch(getSeckillQueryActivity());
}
};
}
/**
* 秒杀商品 添加 自定义属性
* ----------------------------------------
* 已开抢
* product.over [APP,H5: 1.已抢光 2.过了endTime]
* product.isSeckill [APP,H5 秒杀中, 与over互斥]
* 抢购中:
* product.over
* product.isSeckill
* 即将开抢:
* product.wait = true
* product.remindFlag = true [APP: 已添加提醒]
* product.remindFalg = false [APP: 默认提醒状态]
* Product.hideRemind = true [APP: queryRemindList接口失败的情况下]
* Product.specialState = 0 1,明天开抢 2.2天后开抢
*/
function productAddFlag(productList, remindList) {
let now = Date.now();
productList
.sort((a, b) => b.orderBy - a.orderBy)
.forEach(product => {
let startTime = product.startTime * 1000; // s to ms
let endTime = product.endTime * 1000;
product.remindFlag = false;
product.readableTime = moment.unix(product.startTime).format('MM月DD日 HH:mm');
if (now < startTime) { // 未开抢
product.wait = true;
product.isSeckill = true;
if (!remindList) {
product.hideRemind = true;
} else if (remindList.length) {
for (let i = 0, j = remindList.length; i < j; i++) {
if (remindList[i].productSkn === product.productSkn) { // remind on;
product.remindFlag = true;
remindList.splice(i, 1);
break;
}
}
}
} else if (product.secKillStatus === 0 || (now > endTime)) { // 确实已抢光, 过了秒杀时间
product.over = true;
}
});
return productList;
}
function parseActivityTimeLsit(json, focusTime=0) {
let currentTime = json && json.currentTime ? json.currentTime * 1000 : Date.new();
let secKillProductVoList = json && json.secKillProductVoList ? json.secKillProductVoList : [];
let newActivityTimeList = [];
let focusIndex = false;
newActivityTimeList = secKillProductVoList.sort((a,b) => a.startTime - b.startTime);// orderBy startTime ASC
if (focusTime) {
focusIndex = newActivityTimeList.findIndex(activityTimeItem=> activityTimeItem.startTime === focusTime);
if (focusIndex !== -1) {
newActivityTimeList[focusIndex].focus = true;
focusIndex = true;
} else {
focusIndex = false;
}
}
let now = new Date();
newActivityTimeList.forEach((activityTimeItem, i) => {
let date,
hour = 0,
minute = 0;
activityTimeItem.startTime *= 1000;
activityTimeItem.endTime *= 1000;
activityTimeItem.index = i;
date = new Date(activityTimeItem.startTime);
hour = date.getHours();
minute = date.getMinutes();
activityTimeItem.time = `${_timeFormat(hour)}:${_timeFormat(minute)}`;
activityTimeItem.specialState = 0;
let offsetDate = getDiffDays(date, now);
activityTimeItem.specialState = offsetDate < 0 ? 0 : offsetDate;
if (currentTime > activityTimeItem.startTime) {
if(currentTime > activityTimeItem.endTime){
activityTimeItem.over = true;
}else{
activityTimeItem.now = true;
focusIndex || (activityTimeItem.focus = focusIndex = true);
}
} else {
activityTimeItem.wait = true;
}
});
if (newActivityTimeList.length && newActivityTimeList.findIndex(activityTimeItem => activityTimeItem.focus) < 0) {
let i = newActivityTimeList.length;
// 没有focus的活动,则focus 最后一个 over 的活动
while (i) {
let activityTimeItem = newActivityTimeList[i - 1];
if (activityTimeItem.over) {
activityTimeItem.focus = true;
break;
}
i = i - 1;
}
// 没有over,全部都是未开抢,则focus第一个
if (!i) {
newActivityTimeList[0].focus = true;
}
}
let focusActivity = newActivityTimeList.find(activityTimeItem => activityTimeItem.focus);
let newQueryActivityInfo = {
currentTime:currentTime,
secKillProductVoList: newActivityTimeList,
}
return {
newQueryActivityInfo,
focusActivity,
};
}
//date1 减去 date2 相差的天数
function getDiffDays(date1, date2){
let nDate1 = new Date();
nDate1 = new Date(date1.getFullYear(),date1.getMonth(),date1.getDate());
let newDate2 = new Date(date2);
newDate2 =new Date(date2.getFullYear(),date2.getMonth(),date2.getDate());
let diffDays = (nDate1 - newDate2) / 1000 / 60 / 60 / 24;
return diffDays;
}
... ...
'use strict';
import {Record, List, Map} from 'immutable';
let InitialState = Record({
ptr: false, // 是否下拉刷新
contentCode: 'b78b32ed81b18dde8ac84fd33602b88b',
isFetching: false,
error: null,
startTime: 0,
localServerTimeDiff: 0,
queryActivityInfo: new (Record({
currentTime: 0,
secKillProductVoList: List(),
})),
curActivity: Map(),
queryProductList: Map(),
queryRemindList: List(),
tipMessage: '',
});
export default InitialState;
... ...
'use strict';
import InitialState from './panicbuyingInitialState';
import Immutable, {Map} from 'immutable';
const {
SET_START_TIME,
QUERY_ACTIVITY_REQUEST,
QUERY_ACTIVITY_SUCCESS,
QUERY_ACTIVITY_FAILURE,
QUERY_PRODUCT_LIST_REQUEST,
QUERY_PRODUCT_LIST_SUCCESS,
QUERY_PRODUCT_LIST_FAILURE,
QUERY_PRODUCT_AND_REMIND_LIST_REQUEST,
QUERY_PRODUCT_AND_REMIND_LIST_SUCCESS,
QUERY_PRODUCT_AND_REMIND_LIST_FAILURE,
SHOW_TIP_MESSAGE,
CLEAR_TIP_MESSAGE,
UPDATE_SECKILL_PRODUCT_VO_LIST,
UPDATE_SECKILL_PRODUCT_LIST,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function seckillReducer(state=initialState, action) {
switch(action.type) {
case SET_START_TIME: {
return state.set('startTime',action.payload)
}
case QUERY_ACTIVITY_REQUEST: {
return state.set('isFetching', true)
.set('ptr', true)
.set('error', null);
}
case QUERY_ACTIVITY_SUCCESS: {
let timeListInfo = action.payload.queryActivityInfo;
let focusActivity = action.payload.focusActivity;
let diff = Math.round((timeListInfo.currentTime - Date.now()) / 1000)
let newQueryActivity = state.queryActivityInfo.set('currentTime', timeListInfo.currentTime)
.set('secKillProductVoList', Immutable.fromJS(timeListInfo.secKillProductVoList));
return state.set('queryActivityInfo', newQueryActivity)
.set('localServerTimeDiff', diff)
.set('curActivity', Immutable.fromJS(focusActivity))
.set('error', null);
}
case QUERY_ACTIVITY_FAILURE: {
return state.set('isFetching', false)
.set('ptr', false)
.set('error', action.payload);
}
case QUERY_PRODUCT_LIST_REQUEST: {
return state.set('isFetching', true)
.set('ptr', true)
.set('curActivity', Immutable.fromJS(action.payload))
.set('error', null);
}
case QUERY_PRODUCT_LIST_SUCCESS: {
console.log(action.payload);
return state.set('isFetching', false)
.set('ptr', false)
.set('queryProductList', Immutable.fromJS(action.payload))
.set('error', null);
}
case QUERY_PRODUCT_LIST_FAILURE: {
return state.set('isFetching', false)
.set('ptr', false)
.set('error', action.payload);
}
case QUERY_PRODUCT_AND_REMIND_LIST_REQUEST: {
return state.set('isFetching', true)
.set('ptr', true)
.set('curActivity', Immutable.fromJS(action.payload))
.set('error', null);
}
case QUERY_PRODUCT_AND_REMIND_LIST_SUCCESS: {
return state.set('isFetching', false)
.set('ptr', false)
.set('queryProductList', Immutable.fromJS(action.payload.queryProductList))
.set('queryRemindList', Immutable.fromJS(action.payload.remindList))
.set('error', null);
}
case QUERY_PRODUCT_AND_REMIND_LIST_FAILURE: {
return state.set('isFetching', false)
.set('ptr', false)
.set('error', action.payload);
}
case CLEAR_TIP_MESSAGE: {
return state.set('tipMessage', '');
}
case SHOW_TIP_MESSAGE: {
return state.set('tipMessage', Immutable.fromJS(action.payload));
}
case UPDATE_SECKILL_PRODUCT_VO_LIST: {
return state.setIn(['queryActivityInfo', 'secKillProductVoList'], Immutable.fromJS(action.payload.secKillProductVoList))
.set('ptr', true)
.set('isFetching', false)
.set('curActivity', Immutable.fromJS(action.payload.curActivity))
.set('error', null);
}
case UPDATE_SECKILL_PRODUCT_LIST: {
return state.set('queryProductList', Immutable.fromJS(action.payload))
.set('error', null);
}
}
return state;
}
... ...
'use strict';
import Request from "../../common/services/NativeRequest";
export default class DiscountProductService {
constructor(host) {
let baseURL = 'http://api.yoho.cn';
if (host) {
baseURL = host;
}
this.api = new Request(baseURL);
}
async fetchDiscountProductList(type, channel, pageSize, page) {
return await this.api.get({
url: '',
body: {
method: 'app.search.sales',
page,
limit: pageSize,
yh_channel: channel,
type: type,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...
'use strict';
import Request from '../../common/services/NativeRequest';
// export default class SeckillService {
export default class PanicbuyingService {
constructor (host) {
console.log(host);
let baseURL = 'http://api.yoho.cn';
if(host){
baseURL = host;
}
this.api = new Request(baseURL);
}
async fetchQueryActivity() {
return await this.api.get({
url: '',
body: {
method: 'app.seckill.queryActivity',
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async fetchQueryProductList(activityId) {
return await this.api.get({
url: '',
body: {
method: 'app.seckill.queryProductList',
activityId,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async fetchQueryRemindList(activityId ,uid) {
return await this.api.get({
url: '',
body: {
method: 'app.seckill.queryRemindList',
activityId,
uid,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
async addCancelUserReminder(method, activity_id, product_skn, uid, sec_kill_id) {
return await this.api.get({
url: '',
body: {
method: method,
activity_id,
product_skn,
uid,
sec_kill_id,
}
})
.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);
};
... ...