Explorar el Código

feats: change time ticker component

张延森 hace 5 años
padre
commit
a87b6e6df0

+ 3
- 0
.vscode/settings.json Ver fichero

@@ -0,0 +1,3 @@
1
+{
2
+  "editor.fontFamily": "'Cascadia Code', Consolas, 'Courier New', monospace"
3
+}

+ 106
- 44
src/components/TimeTicker/index.jsx Ver fichero

@@ -1,16 +1,18 @@
1 1
 import Taro from '@tarojs/taro'
2 2
 import dayjs from 'dayjs'
3
-
4
-// 未开始
5
-const STATUS_READY = -1
6
-
7
-// 进行中
8
-const STATUS_PROCESSING = 0
9
-
10
-// 已结束
11
-const STATUS_OVER = 1
3
+import {
4
+  STATUS_READY,
5
+  STATUS_PROCESSING,
6
+  STATUS_OVER,
7
+  getPreText,
8
+  getTkText,
9
+  isFunction,
10
+} from './utils'
12 11
 
13 12
 export default class TimeTicker extends Taro.Component {
13
+  static options = {
14
+    addGlobalClass: true
15
+  }
14 16
 
15 17
   state = {
16 18
     status: STATUS_READY,
@@ -23,8 +25,17 @@ export default class TimeTicker extends Taro.Component {
23 25
   timer = null
24 26
 
25 27
   componentWillMount() {
28
+    this.computeProps(this.props)
29
+  }
26 30
 
31
+  componentWillReceiveProps(nextProps) {
32
+    this.computeProps(nextProps)
33
+  }
27 34
 
35
+  componentWillUnmount() {
36
+    if (this.timer) {
37
+      clearInterval(this.timer)
38
+    }
28 39
   }
29 40
 
30 41
   computeProps(props) {
@@ -44,62 +55,113 @@ export default class TimeTicker extends Taro.Component {
44 55
 
45 56
     if (!t1 || !t2) {
46 57
       // 只设置了一个时间
47
-      targetTime = t1 ? dayjs(t1).valueOf() : dayjs(t2).valueOf()
48
-
49
-      if (now > targetTime) {
50
-        status = STATUS_OVER
51
-      } else if (now === targetTime) {
52
-        status = STATUS_PROCESSING
53
-      } else {
54
-        status = STATUS_READY
55
-      }
58
+      tmEnd = tmStart = t1 ? dayjs(t1).valueOf() : dayjs(t2).valueOf()
56 59
     } else {
57 60
       // 设置了时间区间
58
-      const dtStart = dayjs(t1).valueOf()
59
-      const dtEnd = dayjs(t2).valueOf()
60
-
61
-      if (dtStart > now) {
62
-        status = STATUS_READY
63
-        targetTime = dtStart
64
-      } else if (dtStart <= now && now < dtEnd) {
65
-        status = STATUS_PROCESSING
66
-        targetTime = dtEnd
67
-      } else {
68
-        status = STATUS_OVER
69
-        targetTime = dtEnd
70
-      }
61
+      tmStart = dayjs(t1).valueOf()
62
+      tmEnd = dayjs(t2).valueOf()
71 63
     }
72 64
 
73
-    this.setState({ status, targetTime }, this.startTimer.bind(this))
65
+    if (tmStart > now) {
66
+      status = STATUS_READY
67
+    } else if (tmStart <= now && now < tmEnd) {
68
+      status = STATUS_PROCESSING
69
+    } else {
70
+      status = STATUS_OVER
71
+    }
72
+
73
+    this.setState({ status, tmStart, tmEnd }, () => this.startTimer(props))
74 74
   }
75 75
 
76
-  startTimer = (props) => {
76
+  computeStatus(props) {
77 77
     const {
78
-      status,
79
-      targetTime,
80
-      interval = 1000,
81 78
       onStart,
82 79
       onProcess,
83 80
       onEnd,
84 81
     } = props || {}
85 82
 
86
-    if (this.timer) {
87
-      clearInterval(this.timer)
83
+    let {
84
+      status,
85
+      tmStart,
86
+      tmEnd,
87
+      preText,
88
+      tkText,
89
+    } = this.state
90
+
91
+    const now = dayjs().valueOf()
92
+    const diff = status === STATUS_READY ? tmStart - now : tmEnd - now
93
+
94
+    if (diff <= 0) {
95
+      if (status === STATUS_READY) {
96
+        status = tmStart === tmEnd ? STATUS_OVER : STATUS_PROCESSING
97
+        
98
+        if (isFunction(onStart)) {
99
+          [preText, tkText] = onStart()
100
+        }
101
+      } else {
102
+        status = STATUS_OVER
103
+        if (this.timer) {
104
+          clearInterval(this.timer)
105
+        }
106
+
107
+        if (isFunction(onEnd)) {
108
+          [preText, tkText] = onEnd()
109
+        }
110
+      }
111
+    } else {
112
+      if (isFunction(onProcess)) {
113
+        [preText, tkText] = onProcess(diff, status)
114
+      }
88 115
     }
89 116
 
90
-    this.timer = setInterval(() => {
91
-      const now = dayjs().valueOf()
92
-      const diff = targetTime - now
117
+    if (preText === undefined) {
118
+      preText = getPreText(diff, status)
119
+    }
120
+    
121
+    if (tkText === undefined) {
122
+      tkText = getTkText(diff, status)
123
+    }
93 124
 
125
+    this.setState({ status, preText, tkText })
126
+  }
94 127
 
128
+  startTimer = (props) => {
129
+    const { interval = 1000 } = props || {}
95 130
 
96
-    }, interval)
131
+    if (this.timer) {
132
+      clearInterval(this.timer)
133
+    }
97 134
 
135
+    // 立即执行一次
136
+    this.computeStatus(props);
137
+    // 再循环执行
138
+    this.timer = setInterval(() => this.computeStatus(props), interval)
98 139
   }
99 140
 
100
-  render() {
101
-    
102 141
 
103 142
 
143
+  classNames = (...args) => {
144
+    return (args || []).reduce((cls, item) => {
145
+      return [].concat(cls).concat(item)
146
+    }, []).filter(x => x).join(' ')
147
+  }
148
+
149
+  render() {
150
+    const wrapperCls = this.classNames('tk-box', this.props.className)
151
+    const headerCls = this.classNames('tk-box-header', this.props.headerClass)
152
+    const bodyCls = this.classNames('tk-box-body', this.props.bodyClass)
153
+
154
+    return (
155
+      <View className={wrapperCls}>
156
+        {
157
+          this.state.preText &&
158
+          (<Text className={headerCls}>{this.state.preText}</Text>)
159
+        }        
160
+        {
161
+          this.state.tkText &&
162
+          (<Text className={bodyCls}>{this.state.tkText}</Text>)
163
+        }        
164
+      </View>
165
+    );
104 166
   }
105 167
 }

+ 23
- 0
src/components/TimeTicker/utils.js Ver fichero

@@ -0,0 +1,23 @@
1
+
2
+import { formateLeftTime } from '@utils/tools'
3
+
4
+// 未开始
5
+export const STATUS_READY = -1
6
+
7
+// 进行中
8
+export const STATUS_PROCESSING = 0
9
+
10
+// 已结束
11
+export const STATUS_OVER = 1
12
+
13
+export function getPreText(tk, status) {
14
+  return status === STATUS_OVER ? '已结束' : `距离 ${status === STATUS_READY ? '开始' : '结束'} 还有: `
15
+}
16
+
17
+export function getTkText(tk, status) {
18
+  return status === STATUS_OVER ? '' : formateLeftTime(tk)
19
+}
20
+
21
+export function isFunction(fn) {
22
+  return typeof fn === 'function'
23
+}

+ 30
- 75
src/pages/activity/detail/assemble.js Ver fichero

@@ -6,6 +6,7 @@ import AchievePhone from '@components/achievePhone'
6 6
 import AchieveAvatar from '@components/achieveAvatar'
7 7
 import Poster from '@components/Poster'
8 8
 import FormIdCollector from '@components/formIdCollector'
9
+import TimeTicker from '@components/TimeTicker'
9 10
 import dayjs from 'dayjs'
10 11
 // import WxParse from '@components/wxParse/wxParse'
11 12
 // import getUserPhone from '@utils/getUserPhone'
@@ -56,8 +57,6 @@ export default class Detail extends Component {
56 57
     shares: [], // 分享设置
57 58
     posters: [],  // 海报设置
58 59
     posterTpls: [], // 海报模板
59
-    leftTime: 0,  // 剩余时间
60
-    ltTicker: undefined,  // 剩余时间计时器
61 60
     actState: ActBeforeStart,  // 活动本身状态
62 61
     groupState: undefined, // 发起拼团活动的状态
63 62
     isStarter: true, // 是否发起人
@@ -80,18 +79,9 @@ export default class Detail extends Component {
80 79
   }
81 80
 
82 81
   componentWillUnmount() {
83
-    this.stopTicker()
84 82
     this.state.pointRecordId && updatePoint(this.state.pointRecordId)
85 83
   }
86 84
 
87
-  componentDidShow() {
88
-    this.startTicker()
89
-  }
90
-
91
-  componentDidHide() {
92
-    this.stopTicker()
93
-  }
94
-
95 85
   // 初始化页面数据
96 86
   initPageData = () => {
97 87
     if (!this.state.detail.groupActivityId) {
@@ -110,20 +100,6 @@ export default class Detail extends Component {
110 100
     }
111 101
   }
112 102
 
113
-  // 启动 ticker
114
-  startTicker() {
115
-    if (this.state.ltTicker && !this.state.ltTicker.processing) {
116
-      this.state.ltTicker.start()
117
-    }
118
-  }
119
-
120
-  // 清除 ticker
121
-  stopTicker() {
122
-    if (this.state.ltTicker) {
123
-      this.state.ltTicker.stop()
124
-    }
125
-  }
126
-
127 103
 
128 104
    // 调起授权电话
129 105
    toggleGrantPhone = () => {
@@ -156,10 +132,6 @@ export default class Detail extends Component {
156 132
   toggleActionVisible = () => {
157 133
     const { actionSheetVisible } = this.state
158 134
 
159
-    if (this.state.ltTicker) {
160
-      actionSheetVisible ? this.startTicker() : this.stopTicker()
161
-    }
162
-
163 135
     this.setState({
164 136
       actionSheetVisible: !actionSheetVisible
165 137
     })
@@ -203,44 +175,13 @@ export default class Detail extends Component {
203 175
     }
204 176
   }
205 177
 
206
-  // 计时器更新剩余时间
207
-  getLeftTimeTicker(startDate, endDate) {
208
-    let processing = false
209
-    let ticker = undefined
210
-
211
-    const stop = () => {
212
-      ticker && clearInterval(ticker)
213
-      processing = false
214
-    }
215
-
216
-    const fn = () => {
217
-      const [actState, leftTime] = this.compActState(startDate, endDate)
218
-      this.setState({ actState, leftTime })
219
-
220
-      if (actState === ActFinished) {
221
-        stop()
222
-      }
223
-    }
224
-
225
-    const start = () => {
226
-      ticker = setInterval(fn, 1000)
227
-      processing = true
228
-    }
229
-
230
-    return {
231
-      start,
232
-      stop,
233
-      processing,
234
-    }
235
-  }
236
-
178
+  // 请求详情
237 179
   loadDetail() {
238 180
     let { id, recordId, ltTicker } = this.state
239 181
     const { userInfo } = this.props
240 182
 
241 183
     Taro.showLoading()
242 184
     getGroupDetail(id, recordId).then(res => {
243
-      this.stopTicker()
244 185
 
245 186
       const recordDetail = res.taShareRecord || {}
246 187
       let [actState, leftTime] = this.compActState(res.taShareActivity.startTime, res.taShareActivity.endTime)
@@ -249,10 +190,6 @@ export default class Detail extends Component {
249 190
         actState = ActFinished
250 191
       }
251 192
 
252
-      if (actState != ActFinished) {
253
-        ltTicker = this.getLeftTimeTicker(res.taShareActivity.startTime, res.taShareActivity.endTime)
254
-      }
255
-
256 193
       Taro.hideLoading()
257 194
       this.setState({
258 195
         detail: res.taShareActivity,
@@ -266,9 +203,7 @@ export default class Detail extends Component {
266 203
         loaded: true,
267 204
         isStarter: userInfo.person.personId === recordDetail.personId,
268 205
         actState,
269
-        leftTime,
270
-        ltTicker
271
-      }, () => this.startTicker())
206
+      })
272 207
 
273 208
       // Taro.setNavigationBarTitle({ title: res.taShareActivity.activityName })
274 209
 
@@ -473,6 +408,22 @@ export default class Detail extends Component {
473 408
     })
474 409
   }
475 410
 
411
+  handleTickerProcess = (status) => (tk, st) => {
412
+    if (this.state.actState === ActFinished) {
413
+      return ['活动已结束', null]
414
+    }
415
+
416
+    switch (status) {
417
+      case 'start':
418
+        this.setState({ actState: ActInProcess })
419
+        return ['活动马上开始', null];
420
+      case 'process':
421
+        return [(st === -1 ? '距活动开始' : '活动剩余时间')];
422
+      case 'end':
423
+        this.setState({ actState: ActFinished })
424
+        return ['活动已结束', null]
425
+    }
426
+  }
476 427
 
477 428
   render() {
478 429
     const {
@@ -491,8 +442,6 @@ export default class Detail extends Component {
491 442
       posterTpls,
492 443
       posters,
493 444
       qrCode,
494
-      leftTime,
495
-      ltTicker,
496 445
     } = this.state
497 446
 
498 447
     const { userInfo } = this.props
@@ -523,7 +472,7 @@ export default class Detail extends Component {
523 472
     return (
524 473
       <Block>
525 474
         {/* 生成海报 */}
526
-        {posterVisible && !ltTicker.processing &&
475
+        {posterVisible &&
527 476
           (
528 477
             <Poster configs={posterConfigs} onCancel={this.togglePosterVisible} onFinish={this.togglePosterVisible}></Poster>
529 478
           )
@@ -545,10 +494,16 @@ export default class Detail extends Component {
545 494
               <View className="detail-banner">
546 495
                 <Image mode="widthFix" src={transferImage(detail.mainImg)} className="detail-banner__img"></Image>
547 496
 
548
-                <View className="rest-time">
549
-                  <Text className="row-label">{actState === ActBeforeStart ? '距活动开始 :' : (actState === ActInProcess ? '活动剩余时间 :' : '')} </Text>
550
-                  <Text className="row-text">{actState != ActFinished ? formateLeftTime(leftTime) : '活动已结束'}</Text>
551
-                </View>
497
+                <TimeTicker
498
+                  className="rest-time"
499
+                  headerClass="row-label"
500
+                  bodyClass="row-text"
501
+                  timeRange={[detail.startTime, detail.endTime]}
502
+                  onStart={this.handleTickerProcess('start')}
503
+                  onEnd={this.handleTickerProcess('end')}
504
+                  onProcess={this.handleTickerProcess('process')}
505
+                />
506
+
552 507
                 {detail.successNum > 0 &&
553 508
                   <View className="success-num">
554 509
                     <View className="triangle"></View>

+ 33
- 80
src/pages/activity/detail/assistance.js Ver fichero

@@ -6,6 +6,7 @@ import AchievePhone from '@components/achievePhone'
6 6
 import AchieveAvatar from '@components/achieveAvatar'
7 7
 import Poster from '@components/Poster'
8 8
 import FormIdCollector from '@components/formIdCollector'
9
+import TimeTicker from '@components/TimeTicker'
9 10
 import dayjs from 'dayjs'
10 11
 // import WxParse from '@components/wxParse/wxParse'
11 12
 // import getUserPhone from '@utils/getUserPhone'
@@ -54,8 +55,6 @@ export default class Detail extends Component {
54 55
     shares: [], // 分享设置
55 56
     posters: [],  // 海报设置
56 57
     posterTpls: [], // 海报模板
57
-    leftTime: 0,  // 剩余时间
58
-    ltTicker: undefined,  // 剩余时间计时器
59 58
     actState: ActBeforeStart,  // 活动本身状态
60 59
     helpState: HelpInProcess, // 发起助力活动的状态
61 60
     isStarter: true, // 是否发起人
@@ -80,18 +79,9 @@ export default class Detail extends Component {
80 79
   }
81 80
 
82 81
   componentWillUnmount() {
83
-    this.stopTicker()
84 82
     this.state.pointRecordId && updatePoint(this.state.pointRecordId)
85 83
   }
86 84
 
87
-  componentDidShow() {
88
-    this.startTicker()
89
-  }
90
-
91
-  componentDidHide() {
92
-    this.stopTicker()
93
-  }
94
-
95 85
   // 初始化页面数据
96 86
   initPageData = () => {
97 87
     if (!this.state.detail.helpActivityId) {
@@ -110,20 +100,6 @@ export default class Detail extends Component {
110 100
     }
111 101
   }
112 102
 
113
-  // 启动 ticker
114
-  startTicker() {
115
-    if (this.state.ltTicker && !this.state.ltTicker.processing) {
116
-      this.state.ltTicker.start()
117
-    }
118
-  }
119
-
120
-  // 清除 ticker
121
-  stopTicker() {
122
-    if (this.state.ltTicker) {
123
-      this.state.ltTicker.stop()
124
-    }
125
-  }
126
-
127 103
   // 调起授权电话
128 104
   toggleGrantPhone = () => {
129 105
     const { userInfo: { person: { phone, tel } } } = this.props
@@ -155,10 +131,6 @@ export default class Detail extends Component {
155 131
   toggleActionVisible = () => {
156 132
     const { actionSheetVisible } = this.state
157 133
 
158
-    if (this.state.ltTicker) {
159
-      actionSheetVisible ? this.startTicker() : this.stopTicker()
160
-    }
161
-
162 134
     this.setState({
163 135
       actionSheetVisible: !actionSheetVisible
164 136
     })
@@ -202,46 +174,13 @@ export default class Detail extends Component {
202 174
     }
203 175
   }
204 176
 
205
-  // 计时器更新剩余时间
206
-  getLeftTimeTicker(startDate, endDate) {
207
-    let processing = false
208
-    let ticker = undefined
209
-
210
-    const stop = () => {
211
-      ticker && clearInterval(ticker)
212
-      processing = false
213
-    }
214
-
215
-    const fn = () => {
216
-      const [actState, leftTime] = this.compActState(startDate, endDate)
217
-      this.setState({ actState, leftTime })
218
-
219
-      if (actState === ActFinished) {
220
-        stop()
221
-      }
222
-    }
223
-
224
-    const start = () => {
225
-      ticker = setInterval(fn, 1000)
226
-      processing = true
227
-    }
228
-
229
-    return {
230
-      start,
231
-      stop,
232
-      processing,
233
-    }
234
-  }
235
-
236 177
   // 请求详情
237 178
   loadDetail() {
238
-    let { id, initiateId, ltTicker } = this.state
179
+    let { id, initiateId } = this.state
239 180
     const { userInfo } = this.props
240 181
 
241 182
     Taro.showLoading()
242 183
     getHelpDetail(id, initiateId).then(res => {
243
-      this.stopTicker()
244
-
245 184
       const initiateDetail = res.helpInitiateRecord || {}
246 185
       let [actState, leftTime] = this.compActState(res.helpActivity.startDate, res.helpActivity.endDate)
247 186
 
@@ -249,10 +188,6 @@ export default class Detail extends Component {
249 188
         actState = ActFinished
250 189
       }
251 190
 
252
-      if (actState != ActFinished) {
253
-        ltTicker = this.getLeftTimeTicker(res.helpActivity.startDate, res.helpActivity.endDate)
254
-      }
255
-
256 191
       Taro.hideLoading()
257 192
       this.setState({
258 193
         detail: res.helpActivity,
@@ -266,13 +201,10 @@ export default class Detail extends Component {
266 201
         loaded: true,
267 202
         isStarter: !initiateDetail.personId || userInfo.person.personId === initiateDetail.personId,
268 203
         actState,
269
-        leftTime,
204
+        // leftTime,
270 205
         isJoin: res.isJoin,
271
-        ltTicker
272
-      }, () => this.startTicker())
273
-
274
-      // Taro.setNavigationBarTitle({ title: res.helpActivity.title })
275
-
206
+      })
207
+      
276 208
       // WxParse.wxParse('article', 'html', res.desc, this.$scope, 0)
277 209
 
278 210
       savePoint({
@@ -465,6 +397,23 @@ export default class Detail extends Component {
465 397
     })
466 398
   }
467 399
 
400
+  handleTickerProcess = (status) => (tk, st) => {
401
+    if (this.state.actState === ActFinished) {
402
+      return ['活动已结束', null]
403
+    }
404
+
405
+    switch (status) {
406
+      case 'start':
407
+        this.setState({ actState: ActInProcess })
408
+        return ['活动马上开始', null];
409
+      case 'process':
410
+        return [(st === -1 ? '距活动开始' : '活动剩余时间')];
411
+      case 'end':
412
+        this.setState({ actState: ActFinished })
413
+        return ['活动已结束', null]
414
+    }
415
+  }
416
+
468 417
   render() {
469 418
     const {
470 419
       initiateId,
@@ -482,8 +431,6 @@ export default class Detail extends Component {
482 431
       posterTpls,
483 432
       posters,
484 433
       qrCode,
485
-      leftTime,
486
-      ltTicker,
487 434
     } = this.state
488 435
 
489 436
     const { userInfo } = this.props
@@ -509,7 +456,7 @@ export default class Detail extends Component {
509 456
     return (
510 457
       <View>
511 458
         {/* 生成海报 */}
512
-        {posterVisible && !ltTicker.processing &&
459
+        {posterVisible &&
513 460
           (
514 461
             <Poster configs={posterConfigs} onCancel={this.togglePosterVisible} onFinish={this.togglePosterVisible}></Poster>
515 462
           )
@@ -530,10 +477,16 @@ export default class Detail extends Component {
530 477
               <View className="detail-banner">
531 478
                 <Image mode="aspectFill" src={transferImage(detail.img)} className="detail-banner__img"></Image>
532 479
 
533
-                <View className="rest-time">
534
-                  <Text className="row-label">{actState === ActBeforeStart ? '距活动开始 :' : (actState === ActInProcess ? '活动剩余时间 :' : '')} </Text>
535
-                  <Text className="row-text">{actState != ActFinished ? formateLeftTime(leftTime) : '活动已结束'}</Text>
536
-                </View>
480
+                <TimeTicker
481
+                  className="rest-time"
482
+                  headerClass="row-label"
483
+                  bodyClass="row-text"
484
+                  timeRange={[detail.startDate, detail.endDate]}
485
+                  onStart={this.handleTickerProcess('start')}
486
+                  onEnd={this.handleTickerProcess('end')}
487
+                  onProcess={this.handleTickerProcess('process')}
488
+                />
489
+
537 490
                 {detail.successNum > 0 &&
538 491
 
539 492
                   <View className="success-num">