3 Revize 504e6931b1 ... 6d04ab8bb7

Autor SHA1 Zpráva Datum
  谢创宏 6d04ab8bb7 ✨ 对接反馈记录/首页/订单详情 před 3 roky
  谢创宏 71268f3546 ✨ 界面/对接 před 3 roky
  谢创宏 e41ebf68ee ✨ 发布记录反馈 před 3 roky
100 změnil soubory, kde provedl 16089 přidání a 1 odebrání
  1. binární
      .DS_Store
  2. 12 0
      app.js
  3. 15 1
      app.json
  4. binární
      components/.DS_Store
  5. binární
      components/dist/.DS_Store
  6. 382 0
      components/dist/animation-group/index.js
  7. 3 0
      components/dist/animation-group/index.json
  8. 3 0
      components/dist/animation-group/index.wxml
  9. 206 0
      components/dist/animation-group/index.wxss
  10. 62 0
      components/dist/backdrop/index.js
  11. 6 0
      components/dist/backdrop/index.json
  12. 1 0
      components/dist/backdrop/index.wxml
  13. 15 0
      components/dist/backdrop/index.wxss
  14. 755 0
      components/dist/calendar/index.js
  15. 6 0
      components/dist/calendar/index.json
  16. 67 0
      components/dist/calendar/index.wxml
  17. 215 0
      components/dist/calendar/index.wxss
  18. 176 0
      components/dist/countdown/index.js
  19. 220 0
      components/dist/countup/index.js
  20. 24 0
      components/dist/helpers/arrayTreeFilter.js
  21. 77 0
      components/dist/helpers/baseComponent.js
  22. 29 0
      components/dist/helpers/checkIPhoneX.js
  23. 39 0
      components/dist/helpers/classNames.js
  24. 27 0
      components/dist/helpers/colors.js
  25. 27 0
      components/dist/helpers/compareVersion.js
  26. 48 0
      components/dist/helpers/computedBehavior.js
  27. 75 0
      components/dist/helpers/createFieldsStore.js
  28. 56 0
      components/dist/helpers/debounce.js
  29. 53 0
      components/dist/helpers/eventsMixin.js
  30. 97 0
      components/dist/helpers/funcBehavior.js
  31. 50 0
      components/dist/helpers/gestures.js
  32. 19 0
      components/dist/helpers/isEmpty.js
  33. 17 0
      components/dist/helpers/mergeOptionsToData.js
  34. 314 0
      components/dist/helpers/popupMixin.js
  35. 67 0
      components/dist/helpers/relationsBehavior.js
  36. 46 0
      components/dist/helpers/safeAreaBehavior.js
  37. 57 0
      components/dist/helpers/safeSetDataBehavior.js
  38. 65 0
      components/dist/helpers/shallowEqual.js
  39. 139 0
      components/dist/helpers/styleToCssString.js
  40. 47 0
      components/dist/icon/index.js
  41. 3 0
      components/dist/icon/index.json
  42. 1 0
      components/dist/icon/index.wxml
  43. 2820 0
      components/dist/icon/index.wxss
  44. 67 0
      components/dist/index.js
  45. 66 0
      components/dist/landscape/index.js
  46. 6 0
      components/dist/landscape/index.json
  47. 22 0
      components/dist/landscape/index.wxml
  48. 36 0
      components/dist/landscape/index.wxss
  49. 194 0
      components/dist/popup/index.js
  50. 7 0
      components/dist/popup/index.json
  51. 26 0
      components/dist/popup/index.wxml
  52. 121 0
      components/dist/popup/index.wxss
  53. 202 0
      components/dist/rater/index.js
  54. 6 0
      components/dist/rater/index.json
  55. 16 0
      components/dist/rater/index.wxml
  56. 52 0
      components/dist/rater/index.wxss
  57. 33 0
      components/local/calendar/func/config.js
  58. 1036 0
      components/local/calendar/func/convertSolarLunar.js
  59. 543 0
      components/local/calendar/func/day.js
  60. 375 0
      components/local/calendar/func/render.js
  61. 182 0
      components/local/calendar/func/todo.js
  62. 367 0
      components/local/calendar/func/utils.js
  63. 601 0
      components/local/calendar/func/week.js
  64. 26 0
      components/local/calendar/func/wxData.js
  65. 254 0
      components/local/calendar/index.js
  66. 3 0
      components/local/calendar/index.json
  67. 87 0
      components/local/calendar/index.wxml
  68. 214 0
      components/local/calendar/index.wxss
  69. 877 0
      components/local/calendar/main.js
  70. 29 0
      components/local/calendar/theme/iconfont.wxss
  71. 52 0
      components/local/calendar/theme/theme-default.wxss
  72. 49 0
      components/local/calendar/theme/theme-elegant.wxss
  73. 144 0
      components/local/v2/core.js
  74. 12 0
      components/local/v2/helper.js
  75. 257 0
      components/local/v2/index.js
  76. 3 0
      components/local/v2/index.json
  77. 60 0
      components/local/v2/index.wxml
  78. 214 0
      components/local/v2/index.wxss
  79. 212 0
      components/local/v2/plugins/holidays/holidays-map.js
  80. 201 0
      components/local/v2/plugins/holidays/index.js
  81. 18 0
      components/local/v2/plugins/index.js
  82. 277 0
      components/local/v2/plugins/preset/base.js
  83. 69 0
      components/local/v2/plugins/preset/get-calendar-data.js
  84. 9 0
      components/local/v2/plugins/preset/index.js
  85. 219 0
      components/local/v2/plugins/selectable.js
  86. 1036 0
      components/local/v2/plugins/solarLunar/convertSolarLunar.js
  87. 59 0
      components/local/v2/plugins/solarLunar/index.js
  88. 305 0
      components/local/v2/plugins/time-range.js
  89. 135 0
      components/local/v2/plugins/todo.js
  90. 432 0
      components/local/v2/plugins/week.js
  91. 51 0
      components/local/v2/render.js
  92. 29 0
      components/local/v2/theme/iconfont.wxss
  93. 61 0
      components/local/v2/theme/theme-default.wxss
  94. 58 0
      components/local/v2/theme/theme-elegant.wxss
  95. 285 0
      components/local/v2/utils/index.js
  96. 23 0
      components/local/v2/utils/logger.js
  97. 30 0
      components/local/v2/utils/wxData.js
  98. binární
      images/.DS_Store
  99. binární
      images/gzh.png
  100. 0 0
      images/header.png

binární
.DS_Store


+ 12 - 0
app.js

@@ -13,6 +13,18 @@ App({
13 13
       }
14 14
     })
15 15
   },
16
+  onShow(opts) {
17
+    this.globalData.sceneData = {
18
+      path: opts.path,
19
+      query: opts.query
20
+    }
21
+    if (!wx.getStorageSync('token') || !wx.getStorageSync('userInfo')) {
22
+      wx.reLaunch({
23
+        url: '/pages/login/login',
24
+      })
25
+      // login(opts)
26
+    }
27
+  }, 
16 28
   globalData: {
17 29
     userInfo: null
18 30
   }

+ 15 - 1
app.json

@@ -4,7 +4,21 @@
4 4
     "pages/index/index",
5 5
     "pages/logs/logs",
6 6
     "pages/member/member",
7
-    "pages/orderDetail/orderDetail"
7
+    "pages/orderDetail/orderDetail",
8
+    "pages/feedback/feedback",
9
+    "pages/clientInfo/clientInfo",
10
+    "pages/clientInfoEdit/clientInfoEdit",
11
+    "pages/tagsEdit/tagsEdit",
12
+    "pages/tagsAdd/tagsAdd",
13
+    "pages/tagsCustom/tagsCustom",
14
+    "pages/recordSheet/recordSheet",
15
+    "pages/contact/contact",
16
+    "pages/dossier/dossier",
17
+    "pages/name/name",
18
+    "pages/role/role",
19
+    "pages/recordList/recordList",
20
+    "pages/test/test",
21
+    "pages/login/login"
8 22
   ],
9 23
   "tabBar": {
10 24
     "color": "#666666",

binární
components/.DS_Store


binární
components/dist/.DS_Store


+ 382 - 0
components/dist/animation-group/index.js

@@ -0,0 +1,382 @@
1
+import baseComponent from '../helpers/baseComponent'
2
+import styleToCssString from '../helpers/styleToCssString'
3
+
4
+const ENTER = 'enter'
5
+const ENTERING = 'entering'
6
+const ENTERED = 'entered'
7
+const EXIT = 'exit'
8
+const EXITING = 'exiting'
9
+const EXITED = 'exited'
10
+const UNMOUNTED = 'unmounted'
11
+
12
+const TRANSITION = 'transition'
13
+const ANIMATION = 'animation'
14
+
15
+const TIMEOUT = 1000 / 60
16
+
17
+const defaultClassNames = {
18
+    enter: '', // 进入过渡的开始状态,在过渡过程完成之后移除
19
+    enterActive: '', // 进入过渡的结束状态,在过渡过程完成之后移除
20
+    enterDone: '', // 进入过渡的完成状态
21
+    exit: '', // 离开过渡的开始状态,在过渡过程完成之后移除
22
+    exitActive: '', // 离开过渡的结束状态,在过渡过程完成之后移除
23
+    exitDone: '', // 离开过渡的完成状态
24
+}
25
+
26
+baseComponent({
27
+    properties: {
28
+        // 触发组件进入或离开过渡的状态
29
+        in: {
30
+            type: Boolean,
31
+            value: false,
32
+            observer(newVal) {
33
+                if (this.data.isMounting) {
34
+                    this.updated(newVal)
35
+                }
36
+            },
37
+        },
38
+        // 过渡的类名
39
+        classNames: {
40
+            type: null,
41
+            value: defaultClassNames,
42
+        },
43
+        // 过渡持续时间
44
+        duration: {
45
+            type: null,
46
+            value: null,
47
+        },
48
+        // 过渡动效的类型
49
+        type: {
50
+            type: String,
51
+            value: TRANSITION,
52
+        },
53
+        // 首次挂载时是否触发进入过渡
54
+        appear: {
55
+            type: Boolean,
56
+            value: false,
57
+        },
58
+        // 是否启用进入过渡
59
+        enter: {
60
+            type: Boolean,
61
+            value: true,
62
+        },
63
+        // 是否启用离开过渡
64
+        exit: {
65
+            type: Boolean,
66
+            value: true,
67
+        },
68
+        // 首次进入过渡时是否懒挂载组件
69
+        mountOnEnter: {
70
+            type: Boolean,
71
+            value: true,
72
+        },
73
+        // 离开过渡完成时是否卸载组件
74
+        unmountOnExit: {
75
+            type: Boolean,
76
+            value: true,
77
+        },
78
+        // 自定义类名
79
+        wrapCls: {
80
+            type: String,
81
+            value: '',
82
+        },
83
+        // 自定义样式
84
+        wrapStyle: {
85
+            type: [String, Object],
86
+            value: '',
87
+            observer(newVal) {
88
+                this.setData({
89
+                    extStyle: styleToCssString(newVal),
90
+                })
91
+            },
92
+        },
93
+        disableScroll: {
94
+            type: Boolean,
95
+            value: false,
96
+        },
97
+    },
98
+    data: {
99
+        animateCss: '', // 动画样式
100
+        animateStatus: EXITED, // 动画状态,可选值 entering、entered、exiting、exited
101
+        isMounting: false, // 是否首次挂载
102
+        extStyle: '', // 组件样式
103
+    },
104
+    methods: {
105
+        /**
106
+         * 监听过渡或动画的回调函数
107
+         */
108
+        addEventListener() {
109
+            const { animateStatus } = this.data
110
+            const { enter, exit } = this.getTimeouts()
111
+
112
+            if (animateStatus === ENTERING && !enter && this.data.enter) {
113
+                this.performEntered()
114
+            }
115
+
116
+            if (animateStatus === EXITING && !exit && this.data.exit) {
117
+                this.performExited()
118
+            }
119
+        },
120
+        /**
121
+         * 会在 WXSS transition 或 wx.createAnimation 动画结束后触发
122
+         */
123
+        onTransitionEnd() {
124
+            if (this.data.type === TRANSITION) {
125
+                this.addEventListener()
126
+            }
127
+        },
128
+        /**
129
+         * 会在一个 WXSS animation 动画完成时触发
130
+         */
131
+        onAnimationEnd() {
132
+            if (this.data.type === ANIMATION) {
133
+                this.addEventListener()
134
+            }
135
+        },
136
+        /**
137
+         * 更新组件状态
138
+         * @param {String} nextStatus 下一状态,ENTERING 或 EXITING
139
+         * @param {Boolean} mounting 是否首次挂载
140
+         */
141
+        updateStatus(nextStatus, mounting = false) {
142
+            if (nextStatus !== null) {
143
+                this.cancelNextCallback()
144
+                this.isAppearing = mounting
145
+
146
+                if (nextStatus === ENTERING) {
147
+                    this.performEnter()
148
+                } else {
149
+                    this.performExit()
150
+                }
151
+            }
152
+        },
153
+        /**
154
+         * 进入过渡
155
+         */
156
+        performEnter() {
157
+            const { className, activeClassName } = this.getClassNames(ENTER)
158
+            const { enter } = this.getTimeouts()
159
+            const enterParams = {
160
+                animateStatus: ENTER,
161
+                animateCss: className,
162
+            }
163
+            const enteringParams = {
164
+                animateStatus: ENTERING,
165
+                animateCss: `${className} ${activeClassName}`,
166
+            }
167
+
168
+            // 若已禁用进入过渡,则更新状态至 ENTERED
169
+            if (!this.isAppearing && !this.data.enter) {
170
+                return this.performEntered()
171
+            }
172
+
173
+            // 第一阶段:设置进入过渡的开始状态,并触发 ENTER 事件
174
+            // 第二阶段:延迟一帧后,设置进入过渡的结束状态,并触发 ENTERING 事件
175
+            // 第三阶段:若已设置过渡的持续时间,则延迟指定时间后触发进入过渡完成 performEntered,否则等待触发 onTransitionEnd 或 onAnimationEnd
176
+            this.safeSetData(enterParams, () => {
177
+                this.triggerEvent('change', { animateStatus: ENTER })
178
+                this.triggerEvent(ENTER, { isAppearing: this.isAppearing })
179
+
180
+                // 由于有些时候不能正确的触发动画完成的回调,具体原因未知
181
+                // 所以采用延迟一帧的方式来确保可以触发回调
182
+                this.delayHandler(TIMEOUT, () => {
183
+                    this.safeSetData(enteringParams, () => {
184
+                        this.triggerEvent('change', { animateStatus: ENTERING })
185
+                        this.triggerEvent(ENTERING, { isAppearing: this.isAppearing })
186
+
187
+                        if (enter) {
188
+                            this.delayHandler(enter, this.performEntered)
189
+                        }
190
+                    })
191
+                })
192
+            })
193
+        },
194
+        /**
195
+         * 进入过渡完成
196
+         */
197
+        performEntered() {
198
+            const { doneClassName } = this.getClassNames(ENTER)
199
+            const enteredParams = {
200
+                animateStatus: ENTERED,
201
+                animateCss: doneClassName,
202
+            }
203
+
204
+            // 第三阶段:设置进入过渡的完成状态,并触发 ENTERED 事件
205
+            this.safeSetData(enteredParams, () => {
206
+                this.triggerEvent('change', { animateStatus: ENTERED })
207
+                this.triggerEvent(ENTERED, { isAppearing: this.isAppearing })
208
+            })
209
+        },
210
+        /**
211
+         * 离开过渡
212
+         */
213
+        performExit() {
214
+            const { className, activeClassName } = this.getClassNames(EXIT)
215
+            const { exit } = this.getTimeouts()
216
+            const exitParams = {
217
+                animateStatus: EXIT,
218
+                animateCss: className,
219
+            }
220
+            const exitingParams = {
221
+                animateStatus: EXITING,
222
+                animateCss: `${className} ${activeClassName}`,
223
+            }
224
+
225
+            // 若已禁用离开过渡,则更新状态至 EXITED
226
+            if (!this.data.exit) {
227
+                return this.performExited()
228
+            }
229
+
230
+            // 第一阶段:设置离开过渡的开始状态,并触发 EXIT 事件
231
+            // 第二阶段:延迟一帧后,设置离开过渡的结束状态,并触发 EXITING 事件
232
+            // 第三阶段:若已设置过渡的持续时间,则延迟指定时间后触发离开过渡完成 performExited,否则等待触发 onTransitionEnd 或 onAnimationEnd
233
+            this.safeSetData(exitParams, () => {
234
+                this.triggerEvent('change', { animateStatus: EXIT })
235
+                this.triggerEvent(EXIT)
236
+
237
+                this.delayHandler(TIMEOUT, () => {
238
+                    this.safeSetData(exitingParams, () => {
239
+                        this.triggerEvent('change', { animateStatus: EXITING })
240
+                        this.triggerEvent(EXITING)
241
+
242
+                        if (exit) {
243
+                            this.delayHandler(exit, this.performExited)
244
+                        }
245
+                    })
246
+                })
247
+            })
248
+        },
249
+        /**
250
+         * 离开过渡完成
251
+         */
252
+        performExited() {
253
+            const { doneClassName } = this.getClassNames(EXIT)
254
+            const exitedParams = {
255
+                animateStatus: EXITED,
256
+                animateCss: doneClassName,
257
+            }
258
+
259
+            // 第三阶段:设置离开过渡的完成状态,并触发 EXITED 事件
260
+            this.safeSetData(exitedParams, () => {
261
+                this.triggerEvent('change', { animateStatus: EXITED })
262
+                this.triggerEvent(EXITED)
263
+
264
+                // 判断离开过渡完成时是否卸载组件
265
+                if (this.data.unmountOnExit) {
266
+                    this.setData({ animateStatus: UNMOUNTED }, () => {
267
+                        this.triggerEvent('change', { animateStatus: UNMOUNTED })
268
+                    })
269
+                }
270
+            })
271
+        },
272
+        /**
273
+         * 获取指定状态下的类名
274
+         * @param {String} type 过渡类型,enter 或 exit
275
+         */
276
+        getClassNames(type) {
277
+            const { classNames } = this.data
278
+            const className = typeof classNames !== 'string' ? classNames[type] : `${classNames}-${type}`
279
+            const activeClassName = typeof classNames !== 'string' ? classNames[`${type}Active`] : `${classNames}-${type}-active`
280
+            const doneClassName = typeof classNames !== 'string' ? classNames[`${type}Done`] : `${classNames}-${type}-done`
281
+
282
+            return {
283
+                className,
284
+                activeClassName,
285
+                doneClassName,
286
+            }
287
+        },
288
+        /**
289
+         * 获取过渡持续时间
290
+         */
291
+        getTimeouts() {
292
+            const { duration } = this.data
293
+
294
+            if (duration !== null && typeof duration === 'object') {
295
+                return {
296
+                    enter: duration.enter,
297
+                    exit: duration.exit,
298
+                }
299
+            } else if (typeof duration === 'number') {
300
+                return {
301
+                    enter: duration,
302
+                    exit: duration,
303
+                }
304
+            }
305
+
306
+            return {}
307
+        },
308
+        /**
309
+         * 属性值 in 被更改时的响应函数
310
+         * @param {Boolean} newVal 触发组件进入或离开过渡的状态
311
+         */
312
+        updated(newVal) {
313
+            let { animateStatus } = this.pendingData || this.data
314
+            let nextStatus = null
315
+
316
+            if (newVal) {
317
+                if (animateStatus === UNMOUNTED) {
318
+                    animateStatus = EXITED
319
+                    this.setData({ animateStatus: EXITED }, () => {
320
+                        this.triggerEvent('change', { animateStatus: EXITED })
321
+                    })
322
+                }
323
+                if (animateStatus !== ENTER && animateStatus !== ENTERING && animateStatus !== ENTERED) {
324
+                    nextStatus = ENTERING
325
+                }
326
+            } else {
327
+                if (animateStatus === ENTER || animateStatus === ENTERING || animateStatus === ENTERED) {
328
+                    nextStatus = EXITING
329
+                }
330
+            }
331
+
332
+            this.updateStatus(nextStatus)
333
+        },
334
+        /**
335
+         * 延迟一段时间触发回调
336
+         * @param {Number} timeout 延迟时间
337
+         * @param {Function} handler 回调函数
338
+         */
339
+        delayHandler(timeout, handler) {
340
+            if (timeout) {
341
+                this.setNextCallback(handler)
342
+                setTimeout(this.nextCallback, timeout)
343
+            }
344
+        },
345
+        /**
346
+         * 点击事件
347
+         */
348
+        onTap() {
349
+            this.triggerEvent('click')
350
+        },
351
+        /**
352
+         * 阻止移动触摸
353
+         */
354
+        noop() {},
355
+    },
356
+    attached() {
357
+        let animateStatus = null
358
+        let appearStatus = null
359
+
360
+        if (this.data.in) {
361
+            if (this.data.appear) {
362
+                animateStatus = EXITED
363
+                appearStatus = ENTERING
364
+            } else {
365
+                animateStatus = ENTERED
366
+            }
367
+        } else {
368
+            if (this.data.unmountOnExit || this.data.mountOnEnter) {
369
+                animateStatus = UNMOUNTED
370
+            } else {
371
+                animateStatus = EXITED
372
+            }
373
+        }
374
+
375
+        // 由于小程序组件首次挂载时 observer 事件总是优先于 attached 事件
376
+        // 所以使用 isMounting 来强制优先触发 attached 事件
377
+        this.safeSetData({ animateStatus, isMounting: true }, () => {
378
+            this.triggerEvent('change', { animateStatus })
379
+            this.updateStatus(appearStatus, true)
380
+        })
381
+    },
382
+})

+ 3 - 0
components/dist/animation-group/index.json

@@ -0,0 +1,3 @@
1
+{
2
+    "component": true
3
+}

+ 3 - 0
components/dist/animation-group/index.wxml

@@ -0,0 +1,3 @@
1
+<view class="wux-class {{ wrapCls }} {{ animateCss }}" bindtap="onTap" catchtouchmove="{{ disableScroll ? 'noop' : '' }}" bindtransitionend="onTransitionEnd" bindanimationend="onAnimationEnd" wx:if="{{ animateStatus !== 'unmounted' }}" style="{{ extStyle }}">
2
+    <slot></slot>
3
+</view>

+ 206 - 0
components/dist/animation-group/index.wxss

@@ -0,0 +1,206 @@
1
+.wux-animate--fadeIn-enter {
2
+  transition: opacity .3s;
3
+  opacity: 0
4
+}
5
+.wux-animate--fadeIn-enter-active,
6
+.wux-animate--fadeIn-enter-done {
7
+  opacity: 1
8
+}
9
+.wux-animate--fadeIn-exit {
10
+  transition: opacity .3s;
11
+  opacity: 1
12
+}
13
+.wux-animate--fadeIn-exit-active,
14
+.wux-animate--fadeIn-exit-done {
15
+  opacity: 0
16
+}
17
+.wux-animate--fadeInDown-enter {
18
+  transition: opacity .3s,transform .3s;
19
+  opacity: 0;
20
+  transform: translate3d(0,-100%,0)
21
+}
22
+.wux-animate--fadeInDown-enter-active,
23
+.wux-animate--fadeInDown-enter-done {
24
+  opacity: 1;
25
+  transform: none
26
+}
27
+.wux-animate--fadeInDown-exit {
28
+  transition: opacity .3s,transform .3s;
29
+  opacity: 1;
30
+  transform: none
31
+}
32
+.wux-animate--fadeInDown-exit-active,
33
+.wux-animate--fadeInDown-exit-done {
34
+  opacity: 0;
35
+  transform: translate3d(0,-100%,0)
36
+}
37
+.wux-animate--fadeInLeft-enter {
38
+  transition: opacity .3s,transform .3s;
39
+  opacity: 0;
40
+  transform: translate3d(-100%,0,0)
41
+}
42
+.wux-animate--fadeInLeft-enter-active,
43
+.wux-animate--fadeInLeft-enter-done {
44
+  opacity: 1;
45
+  transform: none
46
+}
47
+.wux-animate--fadeInLeft-exit {
48
+  transition: opacity .3s,transform .3s;
49
+  opacity: 1;
50
+  transform: none
51
+}
52
+.wux-animate--fadeInLeft-exit-active,
53
+.wux-animate--fadeInLeft-exit-done {
54
+  opacity: 0;
55
+  transform: translate3d(-100%,0,0)
56
+}
57
+.wux-animate--fadeInRight-enter {
58
+  transition: opacity .3s,transform .3s;
59
+  opacity: 0;
60
+  transform: translate3d(100%,0,0)
61
+}
62
+.wux-animate--fadeInRight-enter-active,
63
+.wux-animate--fadeInRight-enter-done {
64
+  opacity: 1;
65
+  transform: none
66
+}
67
+.wux-animate--fadeInRight-exit {
68
+  transition: opacity .3s,transform .3s;
69
+  opacity: 1;
70
+  transform: none
71
+}
72
+.wux-animate--fadeInRight-exit-active,
73
+.wux-animate--fadeInRight-exit-done {
74
+  opacity: 0;
75
+  transform: translate3d(100%,0,0)
76
+}
77
+.wux-animate--fadeInUp-enter {
78
+  transition: opacity .3s,transform .3s;
79
+  opacity: 0;
80
+  transform: translate3d(0,100%,0)
81
+}
82
+.wux-animate--fadeInUp-enter-active,
83
+.wux-animate--fadeInUp-enter-done {
84
+  opacity: 1;
85
+  transform: none
86
+}
87
+.wux-animate--fadeInUp-exit {
88
+  transition: opacity .3s,transform .3s;
89
+  opacity: 1;
90
+  transform: none
91
+}
92
+.wux-animate--fadeInUp-exit-active,
93
+.wux-animate--fadeInUp-exit-done {
94
+  opacity: 0;
95
+  transform: translate3d(0,100%,0)
96
+}
97
+.wux-animate--slideInUp-enter {
98
+  transition: transform .3s;
99
+  transform: translate3d(0,100%,0);
100
+  visibility: visible
101
+}
102
+.wux-animate--slideInUp-enter-active,
103
+.wux-animate--slideInUp-enter-done {
104
+  transform: translateZ(0)
105
+}
106
+.wux-animate--slideInUp-exit {
107
+  transition: transform .3s;
108
+  transform: translateZ(0)
109
+}
110
+.wux-animate--slideInUp-exit-active,
111
+.wux-animate--slideInUp-exit-done {
112
+  transform: translate3d(0,100%,0);
113
+  visibility: visible
114
+}
115
+.wux-animate--slideInDown-enter {
116
+  transition: transform .3s;
117
+  transform: translate3d(0,-100%,0);
118
+  visibility: visible
119
+}
120
+.wux-animate--slideInDown-enter-active,
121
+.wux-animate--slideInDown-enter-done {
122
+  transform: translateZ(0)
123
+}
124
+.wux-animate--slideInDown-exit {
125
+  transition: transform .3s;
126
+  transform: translateZ(0)
127
+}
128
+.wux-animate--slideInDown-exit-active,
129
+.wux-animate--slideInDown-exit-done {
130
+  transform: translate3d(0,-100%,0);
131
+  visibility: visible
132
+}
133
+.wux-animate--slideInLeft-enter {
134
+  transition: transform .3s;
135
+  transform: translate3d(-100%,0,0);
136
+  visibility: visible
137
+}
138
+.wux-animate--slideInLeft-enter-active,
139
+.wux-animate--slideInLeft-enter-done {
140
+  transform: translateZ(0)
141
+}
142
+.wux-animate--slideInLeft-exit {
143
+  transition: transform .3s;
144
+  transform: translateZ(0)
145
+}
146
+.wux-animate--slideInLeft-exit-active,
147
+.wux-animate--slideInLeft-exit-done {
148
+  transform: translate3d(-100%,0,0);
149
+  visibility: visible
150
+}
151
+.wux-animate--slideInRight-enter {
152
+  transition: transform .3s;
153
+  transform: translate3d(100%,0,0);
154
+  visibility: visible
155
+}
156
+.wux-animate--slideInRight-enter-active,
157
+.wux-animate--slideInRight-enter-done {
158
+  transform: none
159
+}
160
+.wux-animate--slideInRight-exit {
161
+  transition: transform .3s;
162
+  transform: none
163
+}
164
+.wux-animate--slideInRight-exit-active,
165
+.wux-animate--slideInRight-exit-done {
166
+  transform: translate3d(100%,0,0);
167
+  visibility: visible
168
+}
169
+.wux-animate--zoom-enter {
170
+  transition: all .3s cubic-bezier(.215,.61,.355,1);
171
+  opacity: .01;
172
+  transform: scale(.75)
173
+}
174
+.wux-animate--zoom-enter-active,
175
+.wux-animate--zoom-enter-done {
176
+  opacity: 1;
177
+  transform: none
178
+}
179
+.wux-animate--zoom-exit {
180
+  transition: all .25s linear;
181
+  transform: none
182
+}
183
+.wux-animate--zoom-exit-active,
184
+.wux-animate--zoom-exit-done {
185
+  opacity: .01;
186
+  transform: scale(.75)
187
+}
188
+.wux-animate--punch-enter {
189
+  transition: all .3s cubic-bezier(.215,.61,.355,1);
190
+  opacity: .01;
191
+  transform: scale(1.35)
192
+}
193
+.wux-animate--punch-enter-active,
194
+.wux-animate--punch-enter-done {
195
+  opacity: 1;
196
+  transform: none
197
+}
198
+.wux-animate--punch-exit {
199
+  transition: all .25s linear;
200
+  transform: none
201
+}
202
+.wux-animate--punch-exit-active,
203
+.wux-animate--punch-exit-done {
204
+  opacity: .01;
205
+  transform: scale(1.35)
206
+}

+ 62 - 0
components/dist/backdrop/index.js

@@ -0,0 +1,62 @@
1
+import baseComponent from '../helpers/baseComponent'
2
+
3
+baseComponent({
4
+    properties: {
5
+        prefixCls: {
6
+            type: String,
7
+            value: 'wux-backdrop',
8
+        },
9
+        transparent: {
10
+            type: Boolean,
11
+            value: false,
12
+        },
13
+        zIndex: {
14
+            type: Number,
15
+            value: 1000,
16
+        },
17
+        classNames: {
18
+            type: null,
19
+            value: 'wux-animate--fadeIn',
20
+        },
21
+    },
22
+    computed: {
23
+        classes: ['prefixCls, transparent', function(prefixCls, transparent) {
24
+            const wrap = transparent ? `${prefixCls}--transparent` : prefixCls
25
+
26
+            return {
27
+                wrap,
28
+            }
29
+        }],
30
+    },
31
+    methods: {
32
+        /**
33
+         * 保持锁定
34
+         */
35
+        retain() {
36
+            if (typeof this.backdropHolds !== 'number' || !this.backdropHolds) {
37
+                this.backdropHolds = 0
38
+            }
39
+
40
+            this.backdropHolds = this.backdropHolds + 1
41
+
42
+            if (this.backdropHolds === 1) {
43
+                this.setData({ in: true })
44
+            }
45
+        },
46
+        /**
47
+         * 释放锁定
48
+         */
49
+        release() {
50
+            if (this.backdropHolds === 1) {
51
+                this.setData({ in: false })
52
+            }
53
+            this.backdropHolds = Math.max(0, this.backdropHolds - 1)
54
+        },
55
+        /**
56
+         * 点击事件
57
+         */
58
+        onClick() {
59
+            this.triggerEvent('click')
60
+        },
61
+    },
62
+})

+ 6 - 0
components/dist/backdrop/index.json

@@ -0,0 +1,6 @@
1
+{
2
+    "component": true,
3
+    "usingComponents": {
4
+        "wux-animation-group": "../animation-group/index"
5
+    }
6
+}

+ 1 - 0
components/dist/backdrop/index.wxml

@@ -0,0 +1 @@
1
+<wux-animation-group wux-class="{{ classes.wrap }}" in="{{ in }}" classNames="{{ classNames }}" bind:click="onClick" wrapStyle="{{ { zIndex } }}" disableScroll />

+ 15 - 0
components/dist/backdrop/index.wxss

@@ -0,0 +1,15 @@
1
+.wux-backdrop {
2
+  background: rgba(0,0,0,.4)
3
+}
4
+.wux-backdrop,
5
+.wux-backdrop--transparent {
6
+  position: fixed;
7
+  z-index: 1000;
8
+  top: 0;
9
+  right: 0;
10
+  left: 0;
11
+  bottom: 0
12
+}
13
+.wux-backdrop--transparent {
14
+  background: 0 0
15
+}

+ 755 - 0
components/dist/calendar/index.js

@@ -0,0 +1,755 @@
1
+import baseComponent from '../helpers/baseComponent'
2
+import classNames from '../helpers/classNames'
3
+
4
+const defaults = {
5
+    prefixCls: 'wux-calendar',
6
+    monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
7
+    monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
8
+    dayNames: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
9
+    dayNamesShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
10
+    firstDay: 1, // First day of the week, Monday
11
+    weekendDays: [0, 6], // Sunday and Saturday
12
+    multiple: false,
13
+    dateFormat: 'yyyy-mm-dd',
14
+    direction: 'horizontal', // or 'vertical'
15
+    minDate: null,
16
+    maxDate: null,
17
+    touchMove: true,
18
+    animate: true,
19
+    closeOnSelect: true,
20
+    weekHeader: true,
21
+    toolbar: true,
22
+    value: [],
23
+    onMonthAdd() {},
24
+    onChange() {},
25
+    onOpen() {},
26
+    onClose() {},
27
+    onDayClick() {},
28
+    onMonthYearChangeStart() {},
29
+    onMonthYearChangeEnd() {},
30
+}
31
+
32
+// 获取手指触摸点坐标
33
+const getTouchPosition = (e) => {
34
+    const touches = e.touches[0] || e.changedTouches[0]
35
+    return {
36
+        x: touches.pageX,
37
+        y: touches.pageY,
38
+    }
39
+}
40
+
41
+// 获取元素旋转属性
42
+const getTransform = (translate, isH) => `transform: translate3d(${isH ? translate : 0}%, ${isH ? 0 : translate}%, 0)`
43
+
44
+// 判断两个日期是否在同一天
45
+const isSameDate = (a, b) => {
46
+    const prev = new Date(a)
47
+    const next = new Date(b)
48
+    return prev.getFullYear() === next.getFullYear() && prev.getMonth() === next.getMonth() && prev.getDate() === next.getDate()
49
+}
50
+
51
+baseComponent({
52
+    useFunc: true,
53
+    data: defaults,
54
+    computed: {
55
+        classes: ['prefixCls, direction', function(prefixCls, direction) {
56
+            const wrap = classNames(prefixCls, {
57
+                [`${prefixCls}--${direction}`]: direction,
58
+            })
59
+            const content = `${prefixCls}__content`
60
+            const hd = `${prefixCls}__hd`
61
+            const toolbar = `${prefixCls}__toolbar`
62
+            const picker = `${prefixCls}__picker`
63
+            const link = `${prefixCls}__link`
64
+            const prev = classNames(`${prefixCls}__icon`, {
65
+                [`${prefixCls}__icon--prev`]: true,
66
+            })
67
+            const next = classNames(`${prefixCls}__icon`, {
68
+                [`${prefixCls}__icon--next`]: true,
69
+            })
70
+            const value = `${prefixCls}__value`
71
+            const bd = `${prefixCls}__bd`
72
+            const weekdays = `${prefixCls}__weekdays`
73
+            const weekday = `${prefixCls}__weekday`
74
+            const months = `${prefixCls}__months`
75
+            const monthsContent = `${prefixCls}__months-content`
76
+            const month = `${prefixCls}__month`
77
+            const days = `${prefixCls}__days`
78
+            const day = `${prefixCls}__day`
79
+            const text = `${prefixCls}__text`
80
+
81
+            return {
82
+                wrap,
83
+                content,
84
+                hd,
85
+                toolbar,
86
+                picker,
87
+                link,
88
+                prev,
89
+                next,
90
+                value,
91
+                bd,
92
+                weekdays,
93
+                weekday,
94
+                months,
95
+                monthsContent,
96
+                month,
97
+                days,
98
+                day,
99
+                text,
100
+            }
101
+        }],
102
+    },
103
+    methods: {
104
+        /**
105
+         * 打开日历
106
+         * @param {Object} opts
107
+         */
108
+        open(opts = {}) {
109
+            const options = this.$$mergeOptionsAndBindMethods(Object.assign({}, defaults, opts))
110
+
111
+            this.monthsTranslate = 0
112
+            this.isH = options.direction === 'horizontal'
113
+
114
+            this.$$setData({ in: true, ...options }).then(() => this.init())
115
+            this.setValue(options.value)
116
+
117
+            if (typeof this.fns.onOpen === 'function') {
118
+                this.fns.onOpen.call(this)
119
+            }
120
+        },
121
+        /**
122
+         * 关闭日历
123
+         */
124
+        close() {
125
+            this.$$setData({ in: false })
126
+
127
+            if (typeof this.fns.onClose === 'function') {
128
+                this.fns.onClose.call(this)
129
+            }
130
+        },
131
+        /**
132
+         * 初始化
133
+         */
134
+        init() {
135
+            const weeks = this.setWeekHeader()
136
+            const months = this.setMonthsHTML()
137
+            const monthsTranslate = this.setMonthsTranslate()
138
+
139
+            if (typeof this.fns.onMonthAdd === 'function') {
140
+                months.forEach((month) => this.fns.onMonthAdd.call(this, month))
141
+            }
142
+
143
+            return this.$$setData({ weeks, months, monthsTranslate, wrapperTranslate: '' }).then(() => this.$$setData({...this.updateCurrentMonthYear() }))
144
+        },
145
+        /**
146
+         * 设置月份的位置信息
147
+         * @param {Number} translate
148
+         */
149
+        setMonthsTranslate(translate = this.monthsTranslate) {
150
+            const prevMonthTranslate = -(translate + 1) * 100
151
+            const currentMonthTranslate = -translate * 100
152
+            const nextMonthTranslate = -(translate - 1) * 100
153
+
154
+            return [
155
+                getTransform(prevMonthTranslate, this.isH),
156
+                getTransform(currentMonthTranslate, this.isH),
157
+                getTransform(nextMonthTranslate, this.isH),
158
+            ]
159
+        },
160
+        /**
161
+         * 更新当前年月
162
+         * @param {String} dir 方向
163
+         */
164
+        updateCurrentMonthYear(dir) {
165
+            const { months, monthNames } = this.data
166
+
167
+            if (typeof dir === 'undefined') {
168
+                const currentMonth = parseInt(months[1].month, 10)
169
+                const currentYear = parseInt(months[1].year, 10)
170
+                const currentMonthName = monthNames[currentMonth]
171
+
172
+                return {
173
+                    currentMonth,
174
+                    currentYear,
175
+                    currentMonthName,
176
+                }
177
+            }
178
+
179
+            const currentMonth = parseInt(months[dir === 'next' ? (months.length - 1) : 0].month, 10)
180
+            const currentYear = parseInt(months[dir === 'next' ? (months.length - 1) : 0].year, 10)
181
+            const currentMonthName = monthNames[currentMonth]
182
+
183
+            return {
184
+                currentMonth,
185
+                currentYear,
186
+                currentMonthName,
187
+            }
188
+        },
189
+        /**
190
+         * 手指触摸动作开始
191
+         * @param {Object} e 事件对象
192
+         */
193
+        onTouchStart(e) {
194
+            if (!this.data.touchMove || this.isMoved || this.isRendered) return
195
+
196
+            this.start = getTouchPosition(e)
197
+            this.move = {}
198
+            this.touchesDiff = 0
199
+            this.allowItemClick = true
200
+            this.isMoved = false
201
+        },
202
+        /**
203
+         * 手指触摸后移动
204
+         * @param {Object} e 事件对象
205
+         */
206
+        onTouchMove(e) {
207
+            if (!this.data.touchMove || this.isRendered) return
208
+
209
+            this.allowItemClick = false
210
+
211
+            if (!this.isMoved) {
212
+                this.isMoved = true
213
+            }
214
+
215
+            this.$$setData({ swiping: true })
216
+
217
+            const { prefixCls } = this.data
218
+            const query = wx.createSelectorQuery().in(this)
219
+            query.select(`.${prefixCls}__months-content`).boundingClientRect((rect) => {
220
+
221
+                // 由于 boundingClientRect 为异步方法,某些情况下其回调函数在 onTouchEnd 之后触发,导致 wrapperTranslate 计算错误
222
+                // 所以判断 this.isMoved = false 时阻止回调函数的执行
223
+                if (!rect || !this.isMoved) return
224
+
225
+                this.move = getTouchPosition(e)
226
+                this.touchesDiff = this.isH ? this.move.x - this.start.x : this.move.y - this.start.y
227
+
228
+                const { width, height } = rect
229
+                const percentage = this.touchesDiff / (this.isH ? width : height)
230
+                const currentTranslate = (this.monthsTranslate + percentage) * 100
231
+                const transform = getTransform(currentTranslate, this.isH)
232
+
233
+                this.$$setData({
234
+                    wrapperTranslate: `transition-duration: 0s; ${transform}`,
235
+                })
236
+            })
237
+            query.exec()
238
+        },
239
+        /**
240
+         * 手指触摸动作结束
241
+         */
242
+        onTouchEnd() {
243
+            if (!this.data.touchMove || !this.isMoved || this.isRendered) return
244
+
245
+            this.isMoved = false
246
+            this.$$setData({ swiping: false })
247
+
248
+            if (Math.abs(this.touchesDiff) < 30) {
249
+                this.resetMonth()
250
+            } else if (this.touchesDiff >= 30) {
251
+                this.prevMonth()
252
+            } else {
253
+                this.nextMonth()
254
+            }
255
+
256
+            // Allow click
257
+            setTimeout(() => (this.allowItemClick = true), 100)
258
+        },
259
+        /**
260
+         * 日期的点击事件
261
+         * @param {Object} e 事件对象
262
+         */
263
+        onDayClick(e) {
264
+            if (this.allowItemClick) {
265
+                const dataset = e.currentTarget.dataset
266
+                const dateYear = dataset.year
267
+                const dateMonth = dataset.month
268
+                const dateDay = dataset.day
269
+                const dateType = dataset.type
270
+
271
+                if (dateType.selected && !this.data.multiple) return
272
+                if (dateType.disabled) return
273
+                if (dateType.next) this.nextMonth()
274
+                if (dateType.prev) this.prevMonth()
275
+
276
+                if (typeof this.fns.onDayClick === 'function') {
277
+                    this.fns.onDayClick.call(this, dateYear, dateMonth, dateDay)
278
+                }
279
+
280
+                this.addValue(new Date(dateYear, dateMonth, dateDay).getTime())
281
+
282
+                if (this.data.closeOnSelect && !this.data.multiple) {
283
+                    this.close()
284
+                }
285
+            }
286
+        },
287
+        /**
288
+         * 重置月份的位置信息
289
+         */
290
+        resetMonth() {
291
+            const translate = this.monthsTranslate * 100
292
+            const transform = getTransform(translate, this.isH)
293
+
294
+            this.$$setData({
295
+                wrapperTranslate: `transition-duration: 0s; ${transform}`,
296
+            })
297
+        },
298
+        /**
299
+         * 设置年月
300
+         * @param {String} year 年份
301
+         * @param {String} month 月份
302
+         */
303
+        setYearMonth(year = this.data.currentYear, month = this.data.currentMonth) {
304
+            const { months, monthsTranslate, maxDate, minDate, currentYear, currentMonth } = this.data
305
+            const targetDate = year < currentYear ? new Date(year, month + 1, -1).getTime() : new Date(year, month).getTime()
306
+
307
+            // 判断是否存在最大日期
308
+            if (maxDate && targetDate > new Date(maxDate).getTime()) return
309
+
310
+            // 判断是否存在最小日期
311
+            if (minDate && targetDate < new Date(minDate).getTime()) return
312
+
313
+            const currentDate = new Date(currentYear, currentMonth).getTime()
314
+            const dir = targetDate > currentDate ? 'next' : 'prev'
315
+            const newMonthHTML = this.monthHTML(new Date(year, month))
316
+
317
+            const prevTranslate = this.monthsTranslate = this.monthsTranslate || 0
318
+
319
+            if (targetDate > currentDate) {
320
+                this.monthsTranslate = this.monthsTranslate - 1
321
+
322
+                const translate = -(prevTranslate - 1) * 100
323
+                const nextMonthTranslate = getTransform(translate, this.isH)
324
+
325
+                this.$$setData({
326
+                    months: [months[1], months[2], newMonthHTML],
327
+                    monthsTranslate: [monthsTranslate[1], monthsTranslate[2], nextMonthTranslate],
328
+                })
329
+            } else {
330
+                this.monthsTranslate = this.monthsTranslate + 1
331
+
332
+                const translate = -(prevTranslate + 1) * 100
333
+                const prevMonthTranslate = getTransform(translate, this.isH)
334
+
335
+                this.$$setData({
336
+                    months: [newMonthHTML, months[0], months[1]],
337
+                    monthsTranslate: [prevMonthTranslate, monthsTranslate[0], monthsTranslate[1]],
338
+                })
339
+            }
340
+
341
+            this.onMonthChangeStart(dir)
342
+
343
+            const transform = getTransform(this.monthsTranslate * 100, this.isH)
344
+            const duration = this.data.animate ? .3 : 0
345
+            const wrapperTranslate = `transition-duration: ${duration}s; ${transform}`
346
+
347
+            this.$$setData({
348
+                wrapperTranslate,
349
+            })
350
+
351
+            setTimeout(() => this.onMonthChangeEnd(dir, true), duration)
352
+        },
353
+        /**
354
+         * 下一年
355
+         */
356
+        nextYear() {
357
+            this.setYearMonth(this.data.currentYear + 1)
358
+        },
359
+        /**
360
+         * 上一年
361
+         */
362
+        prevYear() {
363
+            this.setYearMonth(this.data.currentYear - 1)
364
+        },
365
+        /**
366
+         * 下一月
367
+         */
368
+        nextMonth() {
369
+            const { months, monthsTranslate, maxDate, currentMonth } = this.data
370
+            const nextMonth = parseInt(months[months.length - 1].month, 10)
371
+            const nextYear = parseInt(months[months.length - 1].year, 10)
372
+            const nextDate = new Date(nextYear, nextMonth)
373
+            const nextDateTime = nextDate.getTime()
374
+
375
+            // 判断是否存在最大日期
376
+            if (maxDate && nextDateTime > new Date(maxDate).getTime()) {
377
+                return this.resetMonth()
378
+            }
379
+
380
+            this.monthsTranslate = this.monthsTranslate - 1
381
+
382
+            if (nextMonth === currentMonth) {
383
+                const translate = -(this.monthsTranslate) * 100
384
+                const nextMonthHTML = this.monthHTML(nextDateTime, 'next')
385
+                const nextMonthTranslate = getTransform(translate, this.isH)
386
+                const months = [this.data.months[1], this.data.months[2], nextMonthHTML]
387
+
388
+                this.$$setData({
389
+                    months,
390
+                    monthsTranslate: [monthsTranslate[1], monthsTranslate[2], nextMonthTranslate],
391
+                })
392
+
393
+                if (typeof this.fns.onMonthAdd === 'function') {
394
+                    this.fns.onMonthAdd.call(this, months[months.length - 1])
395
+                }
396
+            }
397
+
398
+            this.onMonthChangeStart('next')
399
+
400
+            const transform = getTransform(this.monthsTranslate * 100, this.isH)
401
+            const duration = this.data.animate ? .3 : 0
402
+            const wrapperTranslate = `transition-duration: ${duration}s; ${transform}`
403
+
404
+            this.$$setData({
405
+                wrapperTranslate,
406
+            })
407
+
408
+            setTimeout(() => this.onMonthChangeEnd('next'), duration)
409
+        },
410
+        /**
411
+         * 上一月
412
+         */
413
+        prevMonth() {
414
+            const { months, monthsTranslate, minDate, currentMonth } = this.data
415
+            const prevMonth = parseInt(months[0].month, 10)
416
+            const prevYear = parseInt(months[0].year, 10)
417
+            const prevDate = new Date(prevYear, prevMonth + 1, -1)
418
+            const prevDateTime = prevDate.getTime()
419
+
420
+            // 判断是否存在最小日期
421
+            if (minDate && prevDateTime < new Date(minDate).getTime()) {
422
+                return this.resetMonth()
423
+            }
424
+
425
+            this.monthsTranslate = this.monthsTranslate + 1
426
+
427
+            if (prevMonth === currentMonth) {
428
+                const translate = -(this.monthsTranslate) * 100
429
+                const prevMonthHTML = this.monthHTML(prevDateTime, 'prev')
430
+                const prevMonthTranslate = getTransform(translate, this.isH)
431
+                const months = [prevMonthHTML, this.data.months[0], this.data.months[1]]
432
+
433
+                this.$$setData({
434
+                    months,
435
+                    monthsTranslate: [prevMonthTranslate, monthsTranslate[0], monthsTranslate[1]],
436
+                })
437
+
438
+                if (typeof this.fns.onMonthAdd === 'function') {
439
+                    this.fns.onMonthAdd.call(this, months[0])
440
+                }
441
+            }
442
+
443
+            this.onMonthChangeStart('prev')
444
+
445
+            const transform = getTransform(this.monthsTranslate * 100, this.isH)
446
+            const duration = this.data.animate ? .3 : 0
447
+            const wrapperTranslate = `transition-duration: ${duration}s; ${transform}`
448
+
449
+            this.$$setData({
450
+                wrapperTranslate,
451
+            })
452
+
453
+            setTimeout(() => this.onMonthChangeEnd('prev'), duration)
454
+        },
455
+        /**
456
+         * 月份变化开始时的回调函数
457
+         * @param {String} dir 方向
458
+         */
459
+        onMonthChangeStart(dir) {
460
+            const params = this.updateCurrentMonthYear(dir)
461
+
462
+            this.$$setData(params)
463
+
464
+            if (typeof this.fns.onMonthYearChangeStart === 'function') {
465
+                this.fns.onMonthYearChangeStart.call(this, params.currentYear, params.currentMonth)
466
+            }
467
+        },
468
+        /**
469
+         * 月份变化完成时的回调函数
470
+         * @param {String} dir 方向
471
+         * @param {Boolean} rebuildBoth 重置
472
+         */
473
+        onMonthChangeEnd(dir = 'next', rebuildBoth = false) {
474
+            const { currentYear, currentMonth } = this.data
475
+            let nextMonthHTML, prevMonthHTML, newMonthHTML, months = [...this.data.months]
476
+
477
+            if (!rebuildBoth) {
478
+                newMonthHTML = this.monthHTML(new Date(currentYear, currentMonth), dir)
479
+                if (dir === 'next') {
480
+                    months = [months[1], months[2], newMonthHTML]
481
+                } else if (dir === 'prev') {
482
+                    months = [newMonthHTML, months[0], months[1]]
483
+                }
484
+            } else {
485
+                prevMonthHTML = this.monthHTML(new Date(currentYear, currentMonth), 'prev')
486
+                nextMonthHTML = this.monthHTML(new Date(currentYear, currentMonth), 'next')
487
+                months = [prevMonthHTML, months[dir === 'next' ? months.length - 1 : 0], nextMonthHTML]
488
+            }
489
+
490
+            const monthsTranslate = this.setMonthsTranslate(this.monthsTranslate)
491
+
492
+            this.isRendered = true
493
+            this.$$setData({ months, monthsTranslate }).then(() => (this.isRendered = false))
494
+
495
+            if (typeof this.fns.onMonthAdd === 'function') {
496
+                this.fns.onMonthAdd.call(this, dir === 'next' ? months[months.length - 1] : months[0])
497
+            }
498
+
499
+            if (typeof this.fns.onMonthYearChangeEnd === 'function') {
500
+                this.fns.onMonthYearChangeEnd.call(this, currentYear, currentMonth)
501
+            }
502
+        },
503
+        /**
504
+         * 设置星期
505
+         */
506
+        setWeekHeader() {
507
+            const { weekHeader, firstDay, dayNamesShort, weekendDays } = this.data
508
+            const weeks = []
509
+
510
+            if (weekHeader) {
511
+                for (let i = 0; i < 7; i++) {
512
+                    const weekDayIndex = (i + firstDay > 6) ? (i - 7 + firstDay) : (i + firstDay)
513
+                    const dayName = dayNamesShort[weekDayIndex]
514
+                    const weekend = weekendDays.indexOf(weekDayIndex) >= 0
515
+
516
+                    weeks.push({
517
+                        weekend,
518
+                        dayName,
519
+                    })
520
+                }
521
+            }
522
+
523
+            return weeks
524
+        },
525
+        /**
526
+         * 判断日期是否存在
527
+         */
528
+        daysInMonth(date) {
529
+            const d = new Date(date)
530
+            return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate()
531
+        },
532
+        /**
533
+         * 设置月份数据
534
+         */
535
+        monthHTML(date, offset) {
536
+            date = new Date(date)
537
+            let year = date.getFullYear(),
538
+                month = date.getMonth(),
539
+                time = date.getTime()
540
+
541
+            const monthHTML = {
542
+                year,
543
+                month,
544
+                time,
545
+                items: [],
546
+            }
547
+
548
+            if (offset === 'next') {
549
+                if (month === 11) date = new Date(year + 1, 0)
550
+                else date = new Date(year, month + 1, 1)
551
+            }
552
+
553
+            if (offset === 'prev') {
554
+                if (month === 0) date = new Date(year - 1, 11)
555
+                else date = new Date(year, month - 1, 1)
556
+            }
557
+
558
+            if (offset === 'next' || offset === 'prev') {
559
+                month = date.getMonth()
560
+                year = date.getFullYear()
561
+                time = date.getTime()
562
+            }
563
+
564
+            let daysInPrevMonth = this.daysInMonth(new Date(date.getFullYear(), date.getMonth()).getTime() - 10 * 24 * 60 * 60 * 1000),
565
+                daysInMonth = this.daysInMonth(date),
566
+                firstDayOfMonthIndex = new Date(date.getFullYear(), date.getMonth()).getDay()
567
+            if (firstDayOfMonthIndex === 0) firstDayOfMonthIndex = 7
568
+
569
+            let dayDate, currentValues = [],
570
+                i, j,
571
+                rows = 6,
572
+                cols = 7,
573
+                dayIndex = 0 + (this.data.firstDay - 1),
574
+                today = new Date().setHours(0, 0, 0, 0),
575
+                minDate = this.data.minDate ? new Date(this.data.minDate).getTime() : null,
576
+                maxDate = this.data.maxDate ? new Date(this.data.maxDate).getTime() : null
577
+
578
+            if (this.data.value && this.data.value.length) {
579
+                for (let i = 0; i < this.data.value.length; i++) {
580
+                    currentValues.push(new Date(this.data.value[i]).setHours(0, 0, 0, 0))
581
+                }
582
+            }
583
+
584
+            for (let i = 1; i <= rows; i++) {
585
+                let rowHTML = []
586
+                let row = i
587
+
588
+                for (let j = 1; j <= cols; j++) {
589
+                    let col = j
590
+                    dayIndex++
591
+                    let dayNumber = dayIndex - firstDayOfMonthIndex
592
+                    let type = {}
593
+
594
+                    if (dayNumber < 0) {
595
+                        dayNumber = daysInPrevMonth + dayNumber + 1
596
+                        type.prev = true
597
+                        dayDate = new Date(month - 1 < 0 ? year - 1 : year, month - 1 < 0 ? 11 : month - 1, dayNumber).getTime()
598
+                    } else {
599
+                        dayNumber = dayNumber + 1
600
+                        if (dayNumber > daysInMonth) {
601
+                            dayNumber = dayNumber - daysInMonth
602
+                            type.next = true
603
+                            dayDate = new Date(month + 1 > 11 ? year + 1 : year, month + 1 > 11 ? 0 : month + 1, dayNumber).getTime()
604
+                        } else {
605
+                            dayDate = new Date(year, month, dayNumber).getTime()
606
+                        }
607
+                    }
608
+
609
+                    // Today
610
+                    if (dayDate === today) type.today = true
611
+
612
+                    // Selected
613
+                    if (currentValues.indexOf(dayDate) >= 0) type.selected = true
614
+
615
+                    // Weekend
616
+                    if (this.data.weekendDays.indexOf(col - 1) >= 0) {
617
+                        type.weekend = true
618
+                    }
619
+
620
+                    // Disabled
621
+                    if ((minDate && dayDate < minDate) || (maxDate && dayDate > maxDate)) {
622
+                        type.disabled = true
623
+                    }
624
+
625
+                    dayDate = new Date(dayDate)
626
+                    const dayYear = dayDate.getFullYear()
627
+                    const dayMonth = dayDate.getMonth()
628
+
629
+                    rowHTML.push({
630
+                        type,
631
+                        year: dayYear,
632
+                        month: dayMonth,
633
+                        day: dayNumber,
634
+                        date: `${dayYear}-${dayMonth + 1}-${dayNumber}`,
635
+                    })
636
+                }
637
+
638
+                monthHTML.year = year
639
+                monthHTML.month = month
640
+                monthHTML.time = time
641
+
642
+                monthHTML.items.push(rowHTML)
643
+            }
644
+
645
+            return monthHTML
646
+        },
647
+        /**
648
+         * 设置月份
649
+         */
650
+        setMonthsHTML() {
651
+            const layoutDate = this.data.value && this.data.value.length ? this.data.value[0] : new Date().setHours(0, 0, 0, 0)
652
+            const prevMonthHTML = this.monthHTML(layoutDate, 'prev')
653
+            const currentMonthHTML = this.monthHTML(layoutDate)
654
+            const nextMonthHTML = this.monthHTML(layoutDate, 'next')
655
+
656
+            return [prevMonthHTML, currentMonthHTML, nextMonthHTML]
657
+        },
658
+        /**
659
+         * 格式化日期
660
+         */
661
+        formatDate(date) {
662
+            date = new Date(date)
663
+            const year = date.getFullYear()
664
+            const month = date.getMonth()
665
+            const month1 = month + 1
666
+            const day = date.getDate()
667
+            const weekDay = date.getDay()
668
+
669
+            return this.data.dateFormat
670
+                .replace(/yyyy/g, year)
671
+                .replace(/yy/g, (year + '').substring(2))
672
+                .replace(/mm/g, month1 < 10 ? '0' + month1 : month1)
673
+                .replace(/m/g, month1)
674
+                .replace(/MM/g, this.data.monthNames[month])
675
+                .replace(/M/g, this.data.monthNamesShort[month])
676
+                .replace(/dd/g, day < 10 ? '0' + day : day)
677
+                .replace(/d/g, day)
678
+                .replace(/DD/g, this.data.dayNames[weekDay])
679
+                .replace(/D/g, this.data.dayNamesShort[weekDay])
680
+        },
681
+        /**
682
+         * 添加选中值
683
+         */
684
+        addValue(value) {
685
+            if (this.data.multiple) {
686
+                let arrValues = this.data.value || []
687
+                let inValuesIndex = -1
688
+
689
+                for (let i = 0; i < arrValues.length; i++) {
690
+                    if (isSameDate(value, arrValues[i])) {
691
+                        inValuesIndex = i
692
+                    }
693
+                }
694
+
695
+                if (inValuesIndex === -1) {
696
+                    arrValues.push(value)
697
+                } else {
698
+                    arrValues.splice(inValuesIndex, 1)
699
+                }
700
+
701
+                this.setValue(arrValues)
702
+            } else {
703
+                this.setValue([value])
704
+            }
705
+        },
706
+        /**
707
+         * 设置选择值
708
+         */
709
+        setValue(value) {
710
+            this.$$setData({ value }).then(() => this.updateValue())
711
+        },
712
+        /**
713
+         * 更新日历
714
+         */
715
+        updateValue() {
716
+            const changedPath = {}
717
+
718
+            this.data.months.forEach((n, i) => {
719
+                n.items.forEach((v, k) => {
720
+                    v.forEach((p, j) => {
721
+                        if (p.type.selected) {
722
+                            changedPath[`months[${i}].items[${k}][${j}].type.selected`] = false
723
+                        }
724
+                    })
725
+                })
726
+            })
727
+
728
+            for (let ii = 0; ii < this.data.value.length; ii++) {
729
+                const valueDate = new Date(this.data.value[ii])
730
+                const valueYear = valueDate.getFullYear()
731
+                const valueMonth = valueDate.getMonth()
732
+                const valueDay = valueDate.getDate()
733
+
734
+                this.data.months.forEach((n, i) => {
735
+                    if (n.year === valueYear && n.month === valueMonth) {
736
+                        n.items.forEach((v, k) => {
737
+                            v.forEach((p, j) => {
738
+                                if (p.year === valueYear && p.month === valueMonth && p.day === valueDay) {
739
+                                    changedPath[`months[${i}].items[${k}][${j}].type.selected`] = true
740
+                                }
741
+                            })
742
+                        })
743
+                    }
744
+                })
745
+            }
746
+
747
+            this.$$setData(changedPath)
748
+
749
+            if (typeof this.fns.onChange === 'function') {
750
+                this.fns.onChange.call(this, this.data.value, this.data.value.map((n) => this.formatDate(n)))
751
+            }
752
+        },
753
+        noop() {},
754
+    },
755
+})

+ 6 - 0
components/dist/calendar/index.json

@@ -0,0 +1,6 @@
1
+{
2
+    "component": true,
3
+    "usingComponents": {
4
+        "wux-popup": "../popup/index"
5
+    }
6
+}

+ 67 - 0
components/dist/calendar/index.wxml

@@ -0,0 +1,67 @@
1
+<wux-popup position="bottom" visible="{{ in }}" zIndex="1010" safeArea="bottom" bind:close="close">
2
+    <view class="wux-class {{ classes.wrap }}">
3
+        <view class="{{ classes.content }}">
4
+            <view class="{{ classes.hd }}" wx:if="{{ toolbar }}">
5
+                <view class="{{ classes.toolbar }}">
6
+                    <view class="{{ classes.picker }}">
7
+                        <view class="{{ classes.link }}" bindtap="prevMonth">
8
+                            <view class="{{ classes.prev }}"></view>
9
+                        </view>
10
+                        <view class="{{ classes.value }}">{{ currentMonthName }}</view>
11
+                        <view class="{{ classes.link }}" bindtap="nextMonth">
12
+                            <view class="{{ classes.next }}"></view>
13
+                        </view>
14
+                    </view>
15
+                    <view class="{{ classes.picker }}">
16
+                        <view class="{{ classes.link }}" bindtap="prevYear">
17
+                            <view class="{{ classes.prev }}"></view>
18
+                        </view>
19
+                        <text class="{{ classes.value }}">{{ currentYear }}</text>
20
+                        <view class="{{ classes.link }}" bindtap="nextYear">
21
+                            <view class="{{ classes.next }}"></view>
22
+                        </view>
23
+                    </view>
24
+                </view>
25
+            </view>
26
+            <view class="{{ classes.bd }}">
27
+                <view class="{{ classes.weekdays }}" wx:if="{{ weekHeader }}">
28
+                    <block wx:for="{{ weeks }}" wx:for-item="week" wx:for-index="weekIndex" wx:key="weekIndex">
29
+                        <view class="{{ classes.weekday }} {{ week.weekend ? prefixCls + '__weekday--weekend' : '' }}">
30
+                            {{ week.dayName }}
31
+                        </view>
32
+                    </block>
33
+                </view>
34
+                <view class="{{ classes.months }}">
35
+                    <view class="{{ classes.monthsContent }}" bindtouchstart="onTouchStart" catchtouchmove="{{ swiping ? 'noop' : '' }}" capture-bind:touchmove="onTouchMove" bindtouchend="onTouchEnd" style="{{ wrapperTranslate }}">
36
+                        <block wx:for="{{ months }}" wx:for-item="month" wx:for-index="monthIndex" wx:key="monthIndex">
37
+                            <view
38
+                                data-year="{{ month.year }}"
39
+                                data-month="{{ month.month }}"
40
+                                class="{{ classes.month }} {{ monthIndex === 0 ? (prefixCls + '__month--prev') : monthIndex === 1 ? (prefixCls + '__month--current') : (prefixCls + '__month--next') }}"
41
+                                style="{{ monthsTranslate[monthIndex] }}"
42
+                            >
43
+                                <block wx:for="{{ month.items }}" wx:for-item="row" wx:for-index="rowIndex" wx:key="rowIndex">
44
+                                    <view class="{{ classes.days }}">
45
+                                        <block wx:for="{{ row }}" wx:for-item="col" wx:for-index="colIndex" wx:key="colIndex">
46
+                                            <view
47
+                                            data-year="{{ col.year }}"
48
+                                            data-month="{{ col.month }}"
49
+                                            data-day="{{ col.day }}"
50
+                                            data-date="{{ col.date }}"
51
+                                            data-type="{{ col.type }}"
52
+                                            class="{{ classes.day }} {{ col.type.prev ? prefixCls + '__day--prev' : '' }} {{ col.type.next ? prefixCls + '__day--next' : '' }} {{ col.type.today ? prefixCls + '__day--today' : '' }} {{ col.type.selected ? prefixCls + '__day--selected' : '' }} {{ col.type.weekend ? prefixCls + '__day--weekend' : '' }} {{ col.type.disabled ? prefixCls + '__day--disabled' : '' }}"
53
+                                            bindtap="onDayClick"
54
+                                            >
55
+                                                <text class="{{ classes.text }}">{{ col.day }}</text>
56
+                                            </view>
57
+                                        </block>
58
+                                    </view>
59
+                                </block>
60
+                            </view>
61
+                        </block>
62
+                    </view>
63
+                </view>
64
+            </view>
65
+        </view>
66
+    </view>
67
+</wux-popup>

+ 215 - 0
components/dist/calendar/index.wxss

@@ -0,0 +1,215 @@
1
+.wux-calendar {
2
+  position: relative;
3
+  background: #fff;
4
+  height: 600rpx;
5
+  width: 100%;
6
+  overflow: hidden
7
+}
8
+.wux-calendar__content {
9
+  position: relative;
10
+  width: 100%;
11
+  height: 100%;
12
+  transition: transform .3s
13
+}
14
+.wux-calendar__bd {
15
+  height: 100%;
16
+  position: relative;
17
+  overflow: hidden
18
+}
19
+.wux-calendar__hd {
20
+  position: relative;
21
+  width: 100%
22
+}
23
+.wux-calendar__hd::before {
24
+  content: " ";
25
+  position: absolute;
26
+  left: 0;
27
+  top: 0;
28
+  right: 0;
29
+  height: 1PX;
30
+  border-top: 1PX solid #d9d9d9;
31
+  color: #d9d9d9;
32
+  transform-origin: 0 0;
33
+  transform: scaleY(.5)
34
+}
35
+.wux-calendar__hd + .wux-calendar__bd {
36
+  height: calc(97.8%)
37
+}
38
+.wux-calendar__toolbar {
39
+  height: 2.2rem;
40
+  display: -ms-flexbox;
41
+  display: flex;
42
+  text-align: center
43
+}
44
+.wux-calendar__picker {
45
+  display: -ms-flexbox;
46
+  display: flex;
47
+  -ms-flex-align: center;
48
+  align-items: center;
49
+  -ms-flex-pack: justify;
50
+  justify-content: space-between;
51
+  width: 50%;
52
+  max-width: 400rpx;
53
+  -ms-flex-negative: 10;
54
+  flex-shrink: 10;
55
+  display: block;
56
+  line-height: 2.2rem
57
+}
58
+.wux-calendar__link {
59
+  float: left;
60
+  width: 25%;
61
+  height: 2.2rem;
62
+  line-height: 2rem;
63
+  min-width: 72rpx
64
+}
65
+.wux-calendar__icon {
66
+  display: inline-block;
67
+  vertical-align: middle;
68
+  background-size: 100% auto;
69
+  background-position: center
70
+}
71
+.wux-calendar__icon--next,
72
+.wux-calendar__icon--prev {
73
+  width: .75rem;
74
+  height: .75rem
75
+}
76
+.wux-calendar__icon--next {
77
+  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2015%2015'%3E%3Cg%3E%3Cpath%20fill%3D'%23007aff'%20d%3D'M1%2C1.6l11.8%2C5.8L1%2C13.4V1.6%20M0%2C0v15l15-7.6L0%2C0L0%2C0z'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
78
+}
79
+.wux-calendar__icon--prev {
80
+  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2015%2015'%3E%3Cg%3E%3Cpath%20fill%3D'%23007aff'%20d%3D'M14%2C1.6v11.8L2.2%2C7.6L14%2C1.6%20M15%2C0L0%2C7.6L15%2C15V0L15%2C0z'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
81
+}
82
+.wux-calendar__value {
83
+  -ms-flex-negative: 1;
84
+  flex-shrink: 1;
85
+  position: relative;
86
+  overflow: hidden;
87
+  text-overflow: ellipsis;
88
+  float: left;
89
+  width: 50%;
90
+  height: 2.2rem
91
+}
92
+.wux-calendar__weekdays {
93
+  height: 36rpx;
94
+  background: #f7f7f8;
95
+  display: -ms-flexbox;
96
+  display: flex;
97
+  font-size: 22rpx;
98
+  box-sizing: border-box;
99
+  position: relative
100
+}
101
+.wux-calendar__weekdays::after {
102
+  content: " ";
103
+  position: absolute;
104
+  left: 0;
105
+  bottom: 0;
106
+  right: 0;
107
+  height: 1PX;
108
+  border-bottom: 1PX solid #d9d9d9;
109
+  color: #d9d9d9;
110
+  transform-origin: 0 100%;
111
+  transform: scaleY(.5)
112
+}
113
+.wux-calendar__weekdays + .wux-calendar__months {
114
+  height: calc(82%)
115
+}
116
+.wux-calendar__weekday {
117
+  -ms-flex-negative: 1;
118
+  flex-shrink: 1;
119
+  width: 14.28571429%;
120
+  width: calc(14.28571429%);
121
+  line-height: 34rpx;
122
+  text-align: center
123
+}
124
+.wux-calendar__months {
125
+  width: 100%;
126
+  height: 100%;
127
+  overflow: hidden;
128
+  position: relative
129
+}
130
+.wux-calendar__months-content {
131
+  width: 100%;
132
+  height: 100%;
133
+  display: -ms-flexbox;
134
+  display: flex;
135
+  position: relative;
136
+  -webkit-backface-visibility: hidden;
137
+  transform: translate3d(0,0,0)
138
+}
139
+.wux-calendar__month {
140
+  display: -ms-flexbox;
141
+  display: flex;
142
+  -ms-flex-direction: column;
143
+  flex-direction: column;
144
+  width: 100%;
145
+  height: 100%;
146
+  position: absolute;
147
+  left: 0;
148
+  top: 0
149
+}
150
+.wux-calendar__days {
151
+  height: 16.66666667%;
152
+  height: calc(16.66666667%);
153
+  display: -ms-flexbox;
154
+  display: flex;
155
+  -ms-flex-negative: 1;
156
+  flex-shrink: 1;
157
+  width: 100%;
158
+  position: relative
159
+}
160
+.wux-calendar__days::after {
161
+  content: " ";
162
+  position: absolute;
163
+  left: 0;
164
+  bottom: 0;
165
+  right: 0;
166
+  height: 1PX;
167
+  border-bottom: 1PX solid #d9d9d9;
168
+  color: #d9d9d9;
169
+  transform-origin: 0 100%;
170
+  transform: scaleY(.5)
171
+}
172
+.wux-calendar__days:last-child::after {
173
+  display: none
174
+}
175
+.wux-calendar__day {
176
+  -ms-flex-negative: 1;
177
+  flex-shrink: 1;
178
+  display: -ms-flexbox;
179
+  display: flex;
180
+  -ms-flex-pack: center;
181
+  justify-content: center;
182
+  -ms-flex-align: center;
183
+  align-items: center;
184
+  box-sizing: border-box;
185
+  width: 14.28571429%;
186
+  width: calc(14.28571429%);
187
+  text-align: center;
188
+  color: #3d4145;
189
+  font-size: 30rpx;
190
+  cursor: pointer
191
+}
192
+.wux-calendar__day--prev {
193
+  color: #ccc
194
+}
195
+.wux-calendar__day--next {
196
+  color: #ccc
197
+}
198
+.wux-calendar__day--disabled {
199
+  color: #d4d4d4;
200
+  cursor: auto
201
+}
202
+.wux-calendar__day--today .wux-calendar__text {
203
+  background: #e3e3e3
204
+}
205
+.wux-calendar__day--selected .wux-calendar__text {
206
+  background: #0894ec;
207
+  color: #fff
208
+}
209
+.wux-calendar__text {
210
+  display: inline-block;
211
+  border-radius: 100%;
212
+  width: 60rpx;
213
+  height: 60rpx;
214
+  line-height: 60rpx
215
+}

+ 176 - 0
components/dist/countdown/index.js

@@ -0,0 +1,176 @@
1
+class Countdown {
2
+    constructor(options = {}, page = getCurrentPages()[getCurrentPages().length - 1]) {
3
+        Object.assign(this, {
4
+            page,
5
+            options,
6
+        })
7
+        this.__init()
8
+    }
9
+
10
+    /**
11
+     * 初始化
12
+     */
13
+    __init() {
14
+        this.setData = this.page.setData.bind(this.page)
15
+        this.restart(this.options)
16
+    }
17
+
18
+    /**
19
+     * 默认参数
20
+     */
21
+    setDefaults() {
22
+        return {
23
+            date: 'June 7, 2087 15:03:25',
24
+            refresh: 1000,
25
+            offset: 0,
26
+            onEnd() {},
27
+            render(date) {},
28
+        }
29
+    }
30
+
31
+    /**
32
+     * 合并参数
33
+     */
34
+    mergeOptions(options) {
35
+        const defaultOptions = this.setDefaults()
36
+
37
+        for (let i in defaultOptions) {
38
+            if (defaultOptions.hasOwnProperty(i)) {
39
+                this.options[i] = typeof options[i] !== 'undefined' ? options[i] : defaultOptions[i]
40
+
41
+                if (i === 'date' && typeof this.options.date !== 'object') {
42
+                    this.options.date = new Date(this.options.date)
43
+                }
44
+
45
+                if (typeof this.options[i] === 'function') {
46
+                    this.options[i] = this.options[i].bind(this)
47
+                }
48
+            }
49
+        }
50
+
51
+        if (typeof this.options.date !== 'object') {
52
+            this.options.date = new Date(this.options.date)
53
+        }
54
+    }
55
+
56
+    /**
57
+     * 计算日期差
58
+     */
59
+    getDiffDate() {
60
+        let diff = (this.options.date.getTime() - Date.now() + this.options.offset) / 1000
61
+
62
+        let dateData = {
63
+            years: 0,
64
+            days: 0,
65
+            hours: 0,
66
+            min: 0,
67
+            sec: 0,
68
+            millisec: 0,
69
+        }
70
+
71
+        if (diff <= 0) {
72
+            if (this.interval) {
73
+                this.stop()
74
+                this.options.onEnd()
75
+            }
76
+            return dateData
77
+        }
78
+
79
+        if (diff >= (365.25 * 86400)) {
80
+            dateData.years = Math.floor(diff / (365.25 * 86400))
81
+            diff -= dateData.years * 365.25 * 86400
82
+        }
83
+
84
+        if (diff >= 86400) {
85
+            dateData.days = Math.floor(diff / 86400)
86
+            diff -= dateData.days * 86400
87
+        }
88
+
89
+        if (diff >= 3600) {
90
+            dateData.hours = Math.floor(diff / 3600)
91
+            diff -= dateData.hours * 3600
92
+        }
93
+
94
+        if (diff >= 60) {
95
+            dateData.min = Math.floor(diff / 60)
96
+            diff -= dateData.min * 60
97
+        }
98
+
99
+        dateData.sec = Math.round(diff)
100
+
101
+        dateData.millisec = diff % 1 * 1000
102
+
103
+        return dateData
104
+    }
105
+
106
+    /**
107
+     * 补零
108
+     */
109
+    leadingZeros(num, length = 2) {
110
+        num = String(num)
111
+        if (num.length > length) return num
112
+        return (Array(length + 1).join('0') + num).substr(-length)
113
+    }
114
+
115
+    /**
116
+     * 更新组件
117
+     */
118
+    update(newDate) {
119
+        this.options.date = typeof newDate !== 'object' ? new Date(newDate) : newDate
120
+        this.render()
121
+        return this
122
+    }
123
+
124
+    /**
125
+     * 停止倒计时
126
+     */
127
+    stop() {
128
+        if (this.interval) {
129
+            clearInterval(this.interval)
130
+            this.interval = !1
131
+        }
132
+        return this
133
+    }
134
+
135
+    /**
136
+     * 渲染组件
137
+     */
138
+    render() {
139
+        this.options.render(this.getDiffDate())
140
+        return this
141
+    }
142
+
143
+    /**
144
+     * 启动倒计时
145
+     */
146
+    start() {
147
+        if (this.interval) return !1
148
+        this.render()
149
+        if (this.options.refresh) {
150
+            this.interval = setInterval(() => {
151
+                this.render()
152
+            }, this.options.refresh)
153
+        }
154
+        return this
155
+    }
156
+
157
+    /**
158
+     * 更新offset
159
+     */
160
+    updateOffset(offset) {
161
+        this.options.offset = offset
162
+        return this
163
+    }
164
+
165
+    /**
166
+     * 重启倒计时
167
+     */
168
+    restart(options = {}) {
169
+        this.mergeOptions(options)
170
+        this.interval = !1
171
+        this.start()
172
+        return this
173
+    }
174
+}
175
+
176
+export default Countdown

+ 220 - 0
components/dist/countup/index.js

@@ -0,0 +1,220 @@
1
+class CountUp {
2
+    constructor(startVal, endVal, decimals, duration, options = {}, page = getCurrentPages()[getCurrentPages().length - 1]) {
3
+        Object.assign(this, {
4
+            page,
5
+            startVal,
6
+            endVal,
7
+            decimals,
8
+            duration,
9
+            options,
10
+        })
11
+        this.__init()
12
+    }
13
+
14
+    /**
15
+     * 初始化
16
+     */
17
+    __init() {
18
+        this.setData = this.page.setData.bind(this.page)
19
+
20
+        this.lastTime = 0
21
+
22
+        // merge options
23
+        this.mergeOptions(this.options)
24
+
25
+        this.startVal = Number(this.startVal)
26
+        this.cacheVal = this.startVal
27
+        this.endVal = Number(this.endVal)
28
+        this.countDown = (this.startVal > this.endVal)
29
+        this.frameVal = this.startVal
30
+        this.decimals = Math.max(0, this.decimals || 0)
31
+        this.dec = Math.pow(10, this.decimals)
32
+        this.duration = Number(this.duration) * 1000 || 2000
33
+
34
+        // format startVal on initialization
35
+        this.printValue(this.formattingFn(this.startVal))
36
+    }
37
+
38
+    /**
39
+     * 默认参数
40
+     */
41
+    setDefaultOptions() {
42
+        return {
43
+            useEasing: true, // toggle easing
44
+            useGrouping: true, // 1,000,000 vs 1000000
45
+            separator: ',', // character to use as a separator
46
+            decimal: '.', // character to use as a decimal
47
+            easingFn: null, // optional custom easing closure function, default is Robert Penner's easeOutExpo
48
+            formattingFn: null, // optional custom formatting function, default is this.formatNumber below
49
+            printValue(value) {}, // printValue
50
+        }
51
+    }
52
+
53
+    /**
54
+     * 合并参数
55
+     */
56
+    mergeOptions(options) {
57
+        const defaultOptions = this.setDefaultOptions()
58
+
59
+        // extend default options with passed options object
60
+        for (let key in defaultOptions) {
61
+            if (defaultOptions.hasOwnProperty(key)) {
62
+                this.options[key] = typeof options[key] !== 'undefined' ? options[key] : defaultOptions[key]
63
+                if (typeof this.options[key] === 'function') {
64
+                    this.options[key] = this.options[key].bind(this)
65
+                }
66
+            }
67
+        }
68
+
69
+        if (this.options.separator === '') { this.options.useGrouping = !1 }
70
+        if (!this.options.prefix) this.options.prefix = ''
71
+        if (!this.options.suffix) this.options.suffix = ''
72
+
73
+        this.easingFn = this.options.easingFn ? this.options.easingFn : this.easeOutExpo
74
+        this.formattingFn = this.options.formattingFn ? this.options.formattingFn : this.formatNumber
75
+        this.printValue = this.options.printValue ? this.options.printValue : function() {}
76
+    }
77
+
78
+    /**
79
+     * 创建定时器
80
+     */
81
+    requestAnimationFrame(callback) {
82
+        let currTime = new Date().getTime()
83
+        let timeToCall = Math.max(0, 16 - (currTime - this.lastTime))
84
+        let timeout = setTimeout(() => {
85
+            callback.bind(this)(currTime + timeToCall)
86
+        }, timeToCall)
87
+        this.lastTime = currTime + timeToCall
88
+        return timeout
89
+    }
90
+
91
+    /**
92
+     * 清空定时器
93
+     */
94
+    cancelAnimationFrame(timeout) {
95
+        clearTimeout(timeout)
96
+    }
97
+
98
+    /**
99
+     * 格式化数字
100
+     */
101
+    formatNumber(nStr) {
102
+        nStr = nStr.toFixed(this.decimals)
103
+        nStr += ''
104
+        let x, x1, x2, rgx
105
+        x = nStr.split('.')
106
+        x1 = x[0]
107
+        x2 = x.length > 1 ? this.options.decimal + x[1] : ''
108
+        rgx = /(\d+)(\d{3})/
109
+        if (this.options.useGrouping) {
110
+            while (rgx.test(x1)) {
111
+                x1 = x1.replace(rgx, '$1' + this.options.separator + '$2')
112
+            }
113
+        }
114
+        return this.options.prefix + x1 + x2 + this.options.suffix
115
+    }
116
+
117
+    /**
118
+     * 过渡效果
119
+     */
120
+    easeOutExpo(t, b, c, d) {
121
+        return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b
122
+    }
123
+
124
+    /**
125
+     * 计数函数
126
+     */
127
+    count(timestamp) {
128
+        if (!this.startTime) { this.startTime = timestamp }
129
+
130
+        this.timestamp = timestamp
131
+        const progress = timestamp - this.startTime
132
+        this.remaining = this.duration - progress
133
+
134
+        // to ease or not to ease
135
+        if (this.options.useEasing) {
136
+            if (this.countDown) {
137
+                this.frameVal = this.startVal - this.easingFn(progress, 0, this.startVal - this.endVal, this.duration)
138
+            } else {
139
+                this.frameVal = this.easingFn(progress, this.startVal, this.endVal - this.startVal, this.duration)
140
+            }
141
+        } else {
142
+            if (this.countDown) {
143
+                this.frameVal = this.startVal - ((this.startVal - this.endVal) * (progress / this.duration))
144
+            } else {
145
+                this.frameVal = this.startVal + (this.endVal - this.startVal) * (progress / this.duration)
146
+            }
147
+        }
148
+
149
+        // don't go past endVal since progress can exceed duration in the last frame
150
+        if (this.countDown) {
151
+            this.frameVal = (this.frameVal < this.endVal) ? this.endVal : this.frameVal
152
+        } else {
153
+            this.frameVal = (this.frameVal > this.endVal) ? this.endVal : this.frameVal
154
+        }
155
+
156
+        // decimal
157
+        this.frameVal = Math.round(this.frameVal * this.dec) / this.dec
158
+
159
+        // format and print value
160
+        this.printValue(this.formattingFn(this.frameVal))
161
+
162
+        // whether to continue
163
+        if (progress < this.duration) {
164
+            this.rAF = this.requestAnimationFrame(this.count)
165
+        } else {
166
+            if (this.callback) { this.callback() }
167
+        }
168
+    }
169
+
170
+    /**
171
+     * 启动计数器
172
+     */
173
+    start(callback) {
174
+        this.callback = callback
175
+        this.rAF = this.requestAnimationFrame(this.count)
176
+        return !1
177
+    }
178
+
179
+    /**
180
+     * 停止计数器
181
+     */
182
+    pauseResume() {
183
+        if (!this.paused) {
184
+            this.paused = !0
185
+            this.cancelAnimationFrame(this.rAF)
186
+        } else {
187
+            this.paused = !1
188
+            delete this.startTime
189
+            this.duration = this.remaining
190
+            this.startVal = this.frameVal
191
+            this.requestAnimationFrame(this.count)
192
+        }
193
+    }
194
+
195
+    /**
196
+     * 重置计数器
197
+     */
198
+    reset() {
199
+        this.paused = !1
200
+        delete this.startTime
201
+        this.startVal = this.cacheVal
202
+        this.cancelAnimationFrame(this.rAF)
203
+        this.printValue(this.formattingFn(this.startVal))
204
+    }
205
+
206
+    /**
207
+     * 更新计数器
208
+     */
209
+    update(newEndVal) {
210
+        this.cancelAnimationFrame(this.rAF)
211
+        this.paused = !1
212
+        delete this.startTime
213
+        this.startVal = this.frameVal
214
+        this.endVal = Number(newEndVal)
215
+        this.countDown = (this.startVal > this.endVal)
216
+        this.rAF = this.requestAnimationFrame(this.count)
217
+    }
218
+}
219
+
220
+export default CountUp

+ 24 - 0
components/dist/helpers/arrayTreeFilter.js

@@ -0,0 +1,24 @@
1
+/**
2
+ * https://github.com/afc163/array-tree-filter
3
+ */
4
+function arrayTreeFilter(data, filterFn, options) {
5
+    options = options || {}
6
+    options.childrenKeyName = options.childrenKeyName || 'children'
7
+    let children = data || []
8
+    const result = []
9
+    let level = 0
10
+    do {
11
+        const foundItem = children.filter(function(item) {
12
+            return filterFn(item, level)
13
+        })[0]
14
+        if (!foundItem) {
15
+            break
16
+        }
17
+        result.push(foundItem)
18
+        children = foundItem[options.childrenKeyName] || []
19
+        level += 1
20
+    } while (children.length > 0)
21
+    return result
22
+}
23
+
24
+export default arrayTreeFilter

+ 77 - 0
components/dist/helpers/baseComponent.js

@@ -0,0 +1,77 @@
1
+import computedBehavior from './computedBehavior'
2
+import relationsBehavior from './relationsBehavior'
3
+import safeAreaBehavior from './safeAreaBehavior'
4
+import safeSetDataBehavior from './safeSetDataBehavior'
5
+import funcBehavior from './funcBehavior'
6
+import compareVersion from './compareVersion'
7
+
8
+const { platform, SDKVersion } = wx.getSystemInfoSync()
9
+const libVersion = '2.6.6'
10
+
11
+// check SDKVersion
12
+if (platform === 'devtools' && compareVersion(SDKVersion, libVersion) < 0) {
13
+    if (wx && wx.showModal) {
14
+        wx.showModal({
15
+            title: '提示',
16
+            content: `当前基础库版本(${SDKVersion})过低,无法使用 Wux Weapp 组件库,请更新基础库版本 >=${libVersion} 后重试。`,
17
+        })
18
+    }
19
+}
20
+
21
+const baseComponent = (options = {}) => {
22
+    // add default externalClasses
23
+    options.externalClasses = [
24
+        'wux-class',
25
+        'wux-hover-class',
26
+        ...(options.externalClasses = options.externalClasses || []),
27
+    ]
28
+
29
+    // add default behaviors
30
+    options.behaviors = [
31
+        relationsBehavior,
32
+        safeSetDataBehavior,
33
+        ...(options.behaviors = options.behaviors || []),
34
+        computedBehavior, // make sure it's triggered
35
+    ]
36
+
37
+    // use safeArea
38
+    if (options.useSafeArea) {
39
+        options.behaviors = [...options.behaviors, safeAreaBehavior]
40
+        delete options.useSafeArea
41
+    }
42
+
43
+    // use func
44
+    if (options.useFunc) {
45
+        options.behaviors = [...options.behaviors, funcBehavior]
46
+        delete options.useFunc
47
+    }
48
+
49
+    // use field
50
+    if (options.useField) {
51
+        options.behaviors = [...options.behaviors, 'wx://form-field']
52
+        delete options.useField
53
+    }
54
+
55
+    // use export
56
+    if (options.useExport) {
57
+        options.behaviors = [...options.behaviors, 'wx://component-export']
58
+        options.methods = {
59
+            export () {
60
+                return this
61
+            },
62
+            ...options.methods,
63
+        }
64
+        delete options.useExport
65
+    }
66
+
67
+    // add default options
68
+    options.options = {
69
+        multipleSlots: true,
70
+        addGlobalClass: true,
71
+        ...options.options,
72
+    }
73
+
74
+    return Component(options)
75
+}
76
+
77
+export default baseComponent

+ 29 - 0
components/dist/helpers/checkIPhoneX.js

@@ -0,0 +1,29 @@
1
+/**
2
+ * 获取系统信息
3
+ */
4
+
5
+let systemInfo = null
6
+
7
+export const getSystemInfo = (isForce) => {
8
+    if (!systemInfo || isForce) {
9
+        try {
10
+            systemInfo = wx.getSystemInfoSync()
11
+        } catch(e) { /* Ignore */ }
12
+    }
13
+
14
+    return systemInfo
15
+}
16
+
17
+// iPhoneX 竖屏安全区域
18
+export const safeAreaInset = {
19
+    top: 88, // StatusBar & NavBar
20
+    left: 0,
21
+    right: 0,
22
+    bottom: 34, // Home Indicator
23
+}
24
+
25
+const isIPhoneX = ({ model, platform }) => {
26
+    return /iPhone X/.test(model) && platform === 'ios'
27
+}
28
+
29
+export const checkIPhoneX = (isForce) => isIPhoneX(getSystemInfo(isForce))

+ 39 - 0
components/dist/helpers/classNames.js

@@ -0,0 +1,39 @@
1
+/*!
2
+  Copyright (c) 2018 Jed Watson.
3
+  Licensed under the MIT License (MIT), see
4
+  http://jedwatson.github.io/classnames
5
+*/
6
+/* global define */
7
+'use strict';
8
+
9
+var hasOwn = {}.hasOwnProperty;
10
+
11
+function classNames() {
12
+    var classes = [];
13
+
14
+    for (var i = 0; i < arguments.length; i++) {
15
+        var arg = arguments[i];
16
+        if (!arg) continue;
17
+
18
+        var argType = typeof arg;
19
+
20
+        if (argType === 'string' || argType === 'number') {
21
+            classes.push(arg);
22
+        } else if (Array.isArray(arg) && arg.length) {
23
+            var inner = classNames.apply(null, arg);
24
+            if (inner) {
25
+                classes.push(inner);
26
+            }
27
+        } else if (argType === 'object') {
28
+            for (var key in arg) {
29
+                if (hasOwn.call(arg, key) && arg[key]) {
30
+                    classes.push(key);
31
+                }
32
+            }
33
+        }
34
+    }
35
+
36
+    return classes.join(' ');
37
+}
38
+
39
+export default classNames

+ 27 - 0
components/dist/helpers/colors.js

@@ -0,0 +1,27 @@
1
+/**
2
+ * Don't modify this file!
3
+ * Colors generated by themes!
4
+ */
5
+
6
+/* eslint-disable */
7
+
8
+export const colors = {
9
+    'light': '#ddd',
10
+    'stable': '#b2b2b2',
11
+    'positive': '#387ef5',
12
+    'calm': '#11c1f3',
13
+    'balanced': '#33cd5f',
14
+    'energized': '#ffc900',
15
+    'assertive': '#ef473a',
16
+    'royal': '#886aea',
17
+    'dark': '#444',
18
+}
19
+
20
+export const isPresetColor = (color) => {
21
+    if (!color) {
22
+        return false
23
+    }
24
+    return colors[color] ? colors[color] : color
25
+}
26
+
27
+/* eslint-enable */

+ 27 - 0
components/dist/helpers/compareVersion.js

@@ -0,0 +1,27 @@
1
+function compareVersion(v1, v2) {
2
+    const $v1 = v1.split('.')
3
+    const $v2 = v2.split('.')
4
+    const len = Math.max($v1.length, $v2.length)
5
+
6
+    while ($v1.length < len) {
7
+        $v1.push('0')
8
+    }
9
+    while ($v2.length < len) {
10
+        $v2.push('0')
11
+    }
12
+
13
+    for (let i = 0; i < len; i++) {
14
+        const num1 = parseInt($v1[i])
15
+        const num2 = parseInt($v2[i])
16
+
17
+        if (num1 > num2) {
18
+            return 1
19
+        } else if (num1 < num2) {
20
+            return -1
21
+        }
22
+    }
23
+
24
+    return 0
25
+}
26
+
27
+export default compareVersion

+ 48 - 0
components/dist/helpers/computedBehavior.js

@@ -0,0 +1,48 @@
1
+import isEmpty from './isEmpty'
2
+import shallowEqual from './shallowEqual'
3
+
4
+const ALL_DATA_KEY = '**'
5
+
6
+const trim = (str = '') => str.replace(/\s/g, '')
7
+
8
+export default Behavior({
9
+    lifetimes: {
10
+        attached() {
11
+            this.initComputed()
12
+        },
13
+    },
14
+    definitionFilter(defFields) {
15
+        const { computed = {} } = defFields
16
+        const observers = Object.keys(computed).reduce((acc, name) => {
17
+            const [field, getter] = Array.isArray(computed[name]) ? computed[name] : [ALL_DATA_KEY, computed[name]]
18
+            return {
19
+                ...acc,
20
+                [field]: function(...args) {
21
+                    if (typeof getter === 'function') {
22
+                        const newValue = getter.apply(this, args)
23
+                        const oldValue = this.data[name]
24
+                        if (!isEmpty(newValue) && !shallowEqual(newValue, oldValue)) {
25
+                            this.setData({ [name]: newValue })
26
+                        }
27
+                    }
28
+                },
29
+            }
30
+        }, {})
31
+
32
+        Object.assign(defFields.observers = (defFields.observers || {}), observers)
33
+        Object.assign(defFields.methods = (defFields.methods || {}), {
34
+            initComputed: function(data = {}, isForce = false) {
35
+                if (!this.runInitComputed || isForce) {
36
+                    this.runInitComputed = false
37
+                    const context = this
38
+                    const result = { ...this.data, ...data }
39
+                    Object.keys(observers).forEach((key) => {
40
+                        const values = trim(key).split(',').reduce((acc, name) => ([...acc, result[name]]), [])
41
+                        observers[key].apply(context, values)
42
+                    })
43
+                    this.runInitComputed = true
44
+                }
45
+            },
46
+        })
47
+    },
48
+})

+ 75 - 0
components/dist/helpers/createFieldsStore.js

@@ -0,0 +1,75 @@
1
+class FieldsStore {
2
+    constructor(fields = {}) {
3
+        this.fields = fields
4
+    }
5
+
6
+    setFields(fields) {
7
+        Object.assign(this.fields, fields)
8
+    }
9
+
10
+    updateFields(fields) {
11
+        this.fields = fields
12
+    }
13
+
14
+    clearField(name) {
15
+        delete this.fields[name]
16
+    }
17
+
18
+    getValueFromFields(name, fields) {
19
+        const field = fields[name]
20
+        if (field && 'value' in field) {
21
+            return field.value
22
+        }
23
+        return field.initialValue
24
+    }
25
+
26
+    getAllFieldsName() {
27
+        const { fields } = this
28
+        return fields ? Object.keys(fields) : []
29
+    }
30
+
31
+    getField(name) {
32
+        return {
33
+            ...this.fields[name],
34
+            name,
35
+        }
36
+    }
37
+
38
+    getFieldValuePropValue(fieldOption) {
39
+        const { name, valuePropName } = fieldOption
40
+        const field = this.getField(name)
41
+        const fieldValue = 'value' in field ? field.value : field.initialValue
42
+
43
+        return {
44
+            [valuePropName]: fieldValue,
45
+        }
46
+    }
47
+
48
+    getFieldValue(name) {
49
+        return this.getValueFromFields(name, this.fields)
50
+    }
51
+
52
+    getFieldsValue(names) {
53
+        const fields = names || this.getAllFieldsName()
54
+        return fields.reduce((acc, name) => {
55
+            acc[name] = this.getFieldValue(name)
56
+            return acc
57
+        }, {})
58
+    }
59
+
60
+    resetFields(ns) {
61
+        const { fields } = this
62
+        const names = ns || this.getAllFieldsName()
63
+        return names.reduce((acc, name) => {
64
+            const field = fields[name]
65
+            if (field) {
66
+                acc[name] = field.initialValue
67
+            }
68
+            return acc
69
+        }, {})
70
+    }
71
+}
72
+
73
+export default function createFieldsStore(fields) {
74
+    return new FieldsStore(fields)
75
+}

+ 56 - 0
components/dist/helpers/debounce.js

@@ -0,0 +1,56 @@
1
+export default function debounce(func, wait, immediate) {
2
+    let timeout,
3
+        args,
4
+        context,
5
+        timestamp,
6
+        result
7
+
8
+    function later() {
9
+        const last = +(new Date()) - timestamp
10
+        if (last < wait && last >= 0) {
11
+            timeout = setTimeout(later, wait - last)
12
+        } else {
13
+            timeout = undefined
14
+            if (!immediate) {
15
+                result = func.apply(context, args)
16
+                if (!timeout) {
17
+                    context = undefined
18
+                    args = undefined
19
+                }
20
+            }
21
+        }
22
+    }
23
+
24
+    function debounced() {
25
+        context = this
26
+        args = arguments
27
+        timestamp = +(new Date())
28
+
29
+        const callNow = immediate && !timeout
30
+        if (!timeout) {
31
+            timeout = setTimeout(later, wait)
32
+        }
33
+
34
+        if (callNow) {
35
+            result = func.apply(context, args)
36
+            context = undefined
37
+            args = undefined
38
+        }
39
+
40
+        return result
41
+    }
42
+
43
+    function cancel() {
44
+        if (timeout !== undefined) {
45
+            clearTimeout(timeout)
46
+            timeout = undefined
47
+        }
48
+
49
+        context = undefined
50
+        args = undefined
51
+    }
52
+
53
+    debounced.cancel = cancel
54
+
55
+    return debounced
56
+}

+ 53 - 0
components/dist/helpers/eventsMixin.js

@@ -0,0 +1,53 @@
1
+const defaultEvents = {
2
+    onChange() {},
3
+}
4
+
5
+export default function eventsMixin(params = { defaultEvents }) {
6
+    return Behavior({
7
+        lifetimes: {
8
+            created () {
9
+                this._oriTriggerEvent = this.triggerEvent
10
+                this.triggerEvent = this._triggerEvent
11
+            },
12
+        },
13
+        properties: {
14
+            events: {
15
+                type: Object,
16
+                value: defaultEvents,
17
+            },
18
+        },
19
+        data: {
20
+            inputEvents: defaultEvents,
21
+        },
22
+        definitionFilter(defFields) {
23
+            // set default data
24
+            Object.assign(defFields.data = (defFields.data || {}), {
25
+                inputEvents: Object.assign({}, defaultEvents, defFields.inputEvents),
26
+            })
27
+
28
+            // set default methods
29
+            Object.assign(defFields.methods = (defFields.methods || {}), {
30
+                _triggerEvent(name, params, runCallbacks = true, option) {
31
+                    const { inputEvents } = this.data
32
+                    const method = `on${name[0].toUpperCase()}${name.slice(1)}`
33
+                    const func = inputEvents[method]
34
+
35
+                    if (runCallbacks && typeof func === 'function') {
36
+                        func.call(this, params)
37
+                    }
38
+
39
+                    this._oriTriggerEvent(name, params, option)
40
+                },
41
+            })
42
+
43
+            // set default observers
44
+            Object.assign(defFields.observers = (defFields.observers || {}), {
45
+                events(newVal) {
46
+                    this.setData({
47
+                        inputEvents: Object.assign({}, defaultEvents, this.data.inputEvents, newVal),
48
+                    })
49
+                },
50
+            })
51
+        },
52
+    })
53
+}

+ 97 - 0
components/dist/helpers/funcBehavior.js

@@ -0,0 +1,97 @@
1
+/**
2
+ * 过滤对象的函数属性
3
+ * @param {Object} opts
4
+ */
5
+const mergeOptionsToData = (opts = {}) => {
6
+    const options = Object.assign({}, opts)
7
+
8
+    for (const key in options) {
9
+        if (options.hasOwnProperty(key) && typeof options[key] === 'function') {
10
+            delete options[key]
11
+        }
12
+    }
13
+
14
+    return options
15
+}
16
+
17
+/**
18
+ * Simple bind, faster than native
19
+ *
20
+ * @param {Function} fn
21
+ * @param {Object} ctx
22
+ * @return {Function}
23
+ */
24
+const bind = (fn, ctx) => {
25
+    return (...args) => {
26
+        return args.length ? fn.apply(ctx, args) : fn.call(ctx)
27
+    }
28
+}
29
+
30
+/**
31
+ * Object assign
32
+ */
33
+const assign = (...args) => Object.assign({}, ...args)
34
+
35
+export default Behavior({
36
+    definitionFilter(defFields) {
37
+        defFields.data = mergeOptionsToData(defFields.data)
38
+        defFields.data.in = false
39
+        defFields.data.visible = false
40
+    },
41
+    methods: {
42
+        /**
43
+         * 过滤对象的函数属性
44
+         * @param {Object} opts
45
+         */
46
+        $$mergeOptionsToData: mergeOptionsToData,
47
+        /**
48
+         * 合并参数并绑定方法
49
+         *
50
+         * @param {Object} opts 参数对象
51
+         * @param {Object} fns 方法挂载的属性
52
+         */
53
+        $$mergeOptionsAndBindMethods (opts = {}, fns = this.fns) {
54
+            const options = Object.assign({}, opts)
55
+
56
+            for (const key in options) {
57
+                if (options.hasOwnProperty(key) && typeof options[key] === 'function') {
58
+                    fns[key] = bind(options[key], this)
59
+                    delete options[key]
60
+                }
61
+            }
62
+
63
+            return options
64
+        },
65
+        /**
66
+         * Promise setData
67
+         * @param {Array} args 参数对象
68
+         */
69
+        $$setData (...args) {
70
+            const params = assign({}, ...args)
71
+
72
+            return new Promise((resolve) => {
73
+                this.setData(params, resolve)
74
+            })
75
+        },
76
+        /**
77
+         * 延迟指定时间执行回调函数
78
+         * @param {Function} callback 回调函数
79
+         * @param {Number} timeout 延迟时间
80
+         */
81
+        $$requestAnimationFrame (callback = () => {}, timeout = 1000 / 60) {
82
+            return new Promise((resolve) => setTimeout(resolve, timeout)).then(callback)
83
+        },
84
+    },
85
+    /**
86
+     * 组件生命周期函数,在组件实例进入页面节点树时执行
87
+     */
88
+    created () {
89
+        this.fns = {}
90
+    },
91
+    /**
92
+     * 组件生命周期函数,在组件实例被从页面节点树移除时执行
93
+     */
94
+    detached () {
95
+        this.fns = {}
96
+    },
97
+})

+ 50 - 0
components/dist/helpers/gestures.js

@@ -0,0 +1,50 @@
1
+/**
2
+ * 获取触摸点位置信息
3
+ */
4
+export const getTouchPoints = (nativeEvent, index = 0) => {
5
+    const touches = nativeEvent.touches
6
+    const changedTouches = nativeEvent.changedTouches
7
+    const hasTouches = touches && touches.length > 0
8
+    const hasChangedTouches = changedTouches && changedTouches.length > 0
9
+    const points = !hasTouches && hasChangedTouches ? changedTouches[index] : hasTouches ? touches[index] : nativeEvent
10
+
11
+    return {
12
+        x: points.pageX,
13
+        y: points.pageY,
14
+    }
15
+}
16
+
17
+/**
18
+ * 获取触摸点个数
19
+ */
20
+export const getPointsNumber = (e) => e.touches && e.touches.length || e.changedTouches && e.changedTouches.length
21
+
22
+/**
23
+ * 判断是否为同一点
24
+ */
25
+export const isEqualPoints = (p1, p2) => p1.x === p2.x && p1.y === p2.y
26
+
27
+/**
28
+ * 判断是否为相近的两点
29
+ */
30
+export const isNearbyPoints = (p1, p2, DOUBLE_TAP_RADIUS = 25) => {
31
+    const xMove = Math.abs(p1.x - p2.x)
32
+    const yMove = Math.abs(p1.y - p2.y)
33
+    return xMove < DOUBLE_TAP_RADIUS & yMove < DOUBLE_TAP_RADIUS
34
+}
35
+
36
+/**
37
+ * 获取两点之间的距离
38
+ */
39
+export const getPointsDistance = (p1, p2) => {
40
+    const xMove = Math.abs(p1.x - p2.x)
41
+    const yMove = Math.abs(p1.y - p2.y)
42
+    return Math.sqrt(xMove * xMove + yMove * yMove)
43
+}
44
+
45
+/**
46
+ * 获取触摸移动方向
47
+ */
48
+export const getSwipeDirection = (x1, x2, y1, y2) => {
49
+    return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
50
+}

+ 19 - 0
components/dist/helpers/isEmpty.js

@@ -0,0 +1,19 @@
1
+/**
2
+ * Checks if a value is empty.
3
+ */
4
+function isEmpty(value) {
5
+    if (Array.isArray(value)) {
6
+        return value.length === 0
7
+    } else if (typeof value === 'object') {
8
+        if (value) {
9
+            for (const _ in value) {
10
+                return false
11
+            }
12
+        }
13
+        return true
14
+    } else {
15
+        return !value
16
+    }
17
+}
18
+
19
+export default isEmpty

+ 17 - 0
components/dist/helpers/mergeOptionsToData.js

@@ -0,0 +1,17 @@
1
+/**
2
+ * 过滤对象的函数属性
3
+ * @param {Object} opts
4
+ */
5
+const mergeOptionsToData = (opts = {}) => {
6
+    const options = Object.assign({}, opts)
7
+
8
+    for (const key in options) {
9
+        if (options.hasOwnProperty(key) && typeof options[key] === 'function') {
10
+            delete options[key]
11
+        }
12
+    }
13
+
14
+    return options
15
+}
16
+
17
+export default mergeOptionsToData

+ 314 - 0
components/dist/helpers/popupMixin.js

@@ -0,0 +1,314 @@
1
+import classNames from './classNames'
2
+import eventsMixin from './eventsMixin'
3
+
4
+const DEFAULT_TRIGGER = 'onClick'
5
+const CELL_NAME = '../cell/index'
6
+const FIELD_NAME = '../field/index'
7
+
8
+const defaultToolbar = {
9
+    title: '请选择',
10
+    cancelText: '取消',
11
+    confirmText: '确定',
12
+}
13
+
14
+const defaultEvents = {
15
+    onChange() {},
16
+    onConfirm() {},
17
+    onCancel() {},
18
+    onVisibleChange() {},
19
+    onValueChange() {},
20
+}
21
+
22
+const defaultPlatformProps = {
23
+    labelPropName: 'label',
24
+    format(values, props) {
25
+        return Array.isArray(values.displayValue) ? values.displayValue.join(',') : values.displayValue
26
+    },
27
+}
28
+
29
+const defaultFieldNames = {
30
+    label: 'label',
31
+    value: 'value',
32
+    children: 'children',
33
+}
34
+
35
+export default function popupMixin(selector = '#wux-picker', platformProps = defaultPlatformProps) {
36
+    return Behavior({
37
+        behaviors: [eventsMixin({ defaultEvents })],
38
+        properties: {
39
+            toolbar: {
40
+                type: Object,
41
+                value: defaultToolbar,
42
+            },
43
+            trigger: {
44
+                type: String,
45
+                value: DEFAULT_TRIGGER,
46
+            },
47
+            defaultVisible: {
48
+                type: Boolean,
49
+                value: false,
50
+            },
51
+            visible: {
52
+                type: Boolean,
53
+                value: false,
54
+            },
55
+            controlled: {
56
+                type: Boolean,
57
+                value: false,
58
+            },
59
+            disabled: {
60
+                type: Boolean,
61
+                value: false,
62
+            },
63
+        },
64
+        data: {
65
+            mounted: false,
66
+            popupVisible: false,
67
+            inputValue: [],
68
+        },
69
+        methods: {
70
+            /**
71
+             * 设置组件显示或隐藏
72
+             */
73
+            setVisibleState(popupVisible, callback = () => {}) {
74
+                if (this.data.popupVisible !== popupVisible) {
75
+                    const params = {
76
+                        mounted: true,
77
+                        inputValue: this.data.value, // forceUpdate
78
+                        popupVisible,
79
+                    }
80
+                    this.setData(popupVisible ? params : { popupVisible }, () => {
81
+                        // collect field component & forceUpdate
82
+                        if (popupVisible && this.hasFieldDecorator) {
83
+                            const field = this.getFieldElem()
84
+                            if (field) {
85
+                                field.changeValue(field.data.value)
86
+                            }
87
+                        }
88
+                        callback()
89
+                    })
90
+                }
91
+            },
92
+            /**
93
+             * 触发 visibleChange 事件
94
+             */
95
+            fireVisibleChange(popupVisible) {
96
+                if (this.data.popupVisible !== popupVisible) {
97
+                    if (!this.data.controlled) {
98
+                        this.setVisibleState(popupVisible)
99
+                    }
100
+                    this.setScrollValue(undefined)
101
+                    this.triggerEvent('visibleChange', { visible: popupVisible })
102
+                }
103
+            },
104
+            /**
105
+             * 打开
106
+             */
107
+            open() {
108
+                this.fireVisibleChange(true)
109
+            },
110
+            /**
111
+             * 关闭
112
+             */
113
+            close(callback) {
114
+                if (typeof callback === 'function') {
115
+                    const values = this.getPickerValue(this.scrollValue || this.data.inputValue)
116
+                    callback.call(this, this.formatPickerValue(values))
117
+                }
118
+                this.fireVisibleChange(false)
119
+            },
120
+            /**
121
+             * 组件关闭时重置其内部数据
122
+             */
123
+            onClosed() {
124
+                this.picker = null
125
+                this.setData({ mounted: false, inputValue: null })
126
+            },
127
+            /**
128
+             * 点击确定按钮时的回调函数
129
+             */
130
+            onConfirm() {
131
+                this.close((values) => {
132
+                    this.triggerEvent('change', values) // collect field component
133
+                    this.triggerEvent('confirm', values)
134
+                })
135
+            },
136
+            /**
137
+             * 点击取消按钮时的回调函数
138
+             */
139
+            onCancel() {
140
+                this.close((values) => this.triggerEvent('cancel', values))
141
+            },
142
+            /**
143
+             * 每列数据选择变化后的回调函数
144
+             */
145
+            onValueChange(e) {
146
+                if (!this.data.mounted) return
147
+                const { value } = e.detail
148
+                if (this.data.cascade) {
149
+                    this.setCasecadeScrollValue(value)
150
+                } else {
151
+                    this.setScrollValue(value)
152
+                }
153
+
154
+                this.updated(value, true)
155
+                this.triggerEvent('valueChange', this.formatPickerValue(e.detail))
156
+            },
157
+            /**
158
+             * 获取当前 picker 的值
159
+             */
160
+            getPickerValue(value = this.data.inputValue) {
161
+                this.picker = this.picker || this.selectComponent(selector)
162
+                return this.picker && this.picker.getValue(value)
163
+            },
164
+            /**
165
+             * 格式化 picker 返回值
166
+             */
167
+            formatPickerValue(values) {
168
+                return {
169
+                    ...values,
170
+                    [platformProps.labelPropName]: platformProps.format(values, this.data),
171
+                }
172
+            },
173
+            /**
174
+             * 获取 field 父元素
175
+             */
176
+            getFieldElem() {
177
+                return this.field = (this.field || this.getRelationNodes(FIELD_NAME)[0])
178
+            },
179
+            /**
180
+             * 设置子元素 props
181
+             */
182
+            setChildProps() {
183
+                if (this.data.disabled) return
184
+                const elements = this.getRelationNodes(CELL_NAME)
185
+                const { trigger = DEFAULT_TRIGGER } = this.data
186
+                if (elements.length > 0) {
187
+                    elements.forEach((inputElem) => {
188
+                        const { inputEvents } = inputElem.data
189
+                        const oriInputEvents = inputElem.data.oriInputEvents || { ...inputEvents }
190
+                        inputEvents[trigger] = (...args) => {
191
+                            if (oriInputEvents && oriInputEvents[trigger]) {
192
+                                oriInputEvents[trigger](...args)
193
+                            }
194
+                            this.onTriggerClick()
195
+                        }
196
+                        inputElem.setData({ oriInputEvents, inputEvents })
197
+                    })
198
+                }
199
+            },
200
+            /**
201
+             * 触发事件
202
+             */
203
+            onTriggerClick() {
204
+                this.fireVisibleChange(!this.data.popupVisible)
205
+            },
206
+            /**
207
+             * 阻止移动触摸
208
+             */
209
+            noop() {},
210
+            /**
211
+             * 更新值
212
+             */
213
+            updated(inputValue, isForce) {
214
+                if (!this.hasFieldDecorator || isForce) {
215
+                    if (this.data.inputValue !== inputValue) {
216
+                        this.setData({ inputValue })
217
+                    }
218
+                }
219
+            },
220
+            /**
221
+             * 记录每列数据的变化值
222
+             */
223
+            setScrollValue(value) {
224
+                this.scrollValue = value
225
+            },
226
+            /**
227
+             * 记录每列数据的变化值 - 针对于级联
228
+             */
229
+            setCasecadeScrollValue(value) {
230
+                if (value && this.scrollValue) {
231
+                    const length = this.scrollValue.length
232
+                    if (length === value.length && this.scrollValue[length - 1] === value[length - 1]) {
233
+                        return
234
+                    }
235
+                }
236
+                this.setScrollValue(value)
237
+            },
238
+        },
239
+        lifetimes: {
240
+            ready() {
241
+                const { defaultVisible, visible, controlled, value } = this.data
242
+                const popupVisible = controlled ? visible : defaultVisible
243
+
244
+                // 若 defaultFieldNames 存在,则缓存之,并传递给子组件,只生效一次
245
+                if ('defaultFieldNames' in this.data) {
246
+                    this.setData({
247
+                        fieldNames: Object.assign({}, defaultFieldNames, this.data.defaultFieldNames),
248
+                    })
249
+                }
250
+
251
+                this.mounted = true
252
+                this.scrollValue = undefined
253
+                this.setVisibleState(popupVisible)
254
+                this.setChildProps()
255
+            },
256
+            detached() {
257
+                this.mounted = false
258
+            },
259
+        },
260
+        definitionFilter(defFields) {
261
+            // set default child
262
+            Object.assign(defFields.relations = (defFields.relations || {}), {
263
+                [CELL_NAME]: {
264
+                    type: 'child',
265
+                    observer() {
266
+                        this.setChildProps()
267
+                    },
268
+                },
269
+                [FIELD_NAME]: {
270
+                    type: 'ancestor',
271
+                },
272
+            })
273
+
274
+            // set default classes
275
+            Object.assign(defFields.computed = (defFields.computed || {}), {
276
+                classes: ['prefixCls', function(prefixCls) {
277
+                    const wrap = classNames(prefixCls)
278
+                    const toolbar = `${prefixCls}__toolbar`
279
+                    const inner = `${prefixCls}__inner`
280
+                    const cancel = classNames(`${prefixCls}__button`, {
281
+                        [`${prefixCls}__button--cancel`]: true,
282
+                    })
283
+                    const confirm = classNames(`${prefixCls}__button`, {
284
+                        [`${prefixCls}__button--confirm`]: true,
285
+                    })
286
+                    const hover = `${prefixCls}__button--hover`
287
+                    const title = `${prefixCls}__title`
288
+
289
+                    return {
290
+                        wrap,
291
+                        toolbar,
292
+                        inner,
293
+                        cancel,
294
+                        confirm,
295
+                        hover,
296
+                        title,
297
+                    }
298
+                }],
299
+            })
300
+
301
+            // set default observers
302
+            Object.assign(defFields.observers = (defFields.observers || {}), {
303
+                visible(popupVisible) {
304
+                    if (this.data.controlled) {
305
+                        this.setVisibleState(popupVisible)
306
+                    }
307
+                },
308
+                value(value) {
309
+                    this.updated(value)
310
+                },
311
+            })
312
+        },
313
+    })
314
+}

+ 67 - 0
components/dist/helpers/relationsBehavior.js

@@ -0,0 +1,67 @@
1
+import isEmpty from './isEmpty'
2
+import debounce from './debounce'
3
+
4
+/**
5
+ * bind func to obj
6
+ */
7
+function bindFunc(obj, method, observer) {
8
+    const oldFn = obj[method]
9
+    obj[method] = function(target) {
10
+        if (observer) {
11
+            observer.call(this, target, {
12
+                [method]: true,
13
+            })
14
+        }
15
+        if (oldFn) {
16
+            oldFn.call(this, target)
17
+        }
18
+    }
19
+}
20
+
21
+// default methods
22
+const methods = ['linked', 'linkChanged', 'unlinked']
23
+
24
+// extra props
25
+const extProps = ['observer']
26
+
27
+export default Behavior({
28
+    lifetimes: {
29
+        created() {
30
+            this._debounce = null
31
+        },
32
+        detached() {
33
+            if (this._debounce && this._debounce.cancel) {
34
+                this._debounce.cancel()
35
+            }
36
+        },
37
+    },
38
+    definitionFilter(defFields) {
39
+        const { relations } = defFields
40
+
41
+        if (!isEmpty(relations)) {
42
+            for (const key in relations) {
43
+                const relation = relations[key]
44
+
45
+                // bind func
46
+                methods.forEach((method) => bindFunc(relation, method, relation.observer))
47
+
48
+                // delete extProps
49
+                extProps.forEach((prop) => delete relation[prop])
50
+            }
51
+        }
52
+
53
+        Object.assign(defFields.methods = (defFields.methods || {}), {
54
+            getRelationsName: function(types = ['parent', 'child', 'ancestor', 'descendant']) {
55
+                return Object.keys(relations || {}).map((key) => {
56
+                    if (relations[key] && types.includes(relations[key].type)) {
57
+                        return key
58
+                    }
59
+                    return null
60
+                }).filter((v) => !!v)
61
+            },
62
+            debounce: function(func, wait = 0, immediate = false) {
63
+                return (this._debounce = this._debounce || debounce(func.bind(this), wait, immediate)).call(this)
64
+            },
65
+        })
66
+    },
67
+})

+ 46 - 0
components/dist/helpers/safeAreaBehavior.js

@@ -0,0 +1,46 @@
1
+import { getSystemInfo, checkIPhoneX } from './checkIPhoneX'
2
+
3
+const defaultSafeArea = {
4
+    top: false,
5
+    bottom: false,
6
+}
7
+
8
+const setSafeArea = (params) => {
9
+    if (typeof params === 'boolean') {
10
+        return Object.assign({}, defaultSafeArea, {
11
+            top: params,
12
+            bottom: params,
13
+        })
14
+    } else if (params !== null && typeof params === 'object') {
15
+        return Object.assign({}, defaultSafeArea)
16
+    } else if (typeof params === 'string') {
17
+        return Object.assign({}, defaultSafeArea, {
18
+            [params]: true,
19
+        })
20
+    }
21
+    return defaultSafeArea
22
+}
23
+
24
+export default Behavior({
25
+    properties: {
26
+        safeArea: {
27
+            type: [Boolean, String, Object],
28
+            value: false,
29
+        },
30
+    },
31
+    observers: {
32
+        safeArea(newVal) {
33
+            this.setData({ safeAreaConfig: setSafeArea(newVal) })
34
+        },
35
+    },
36
+    definitionFilter(defFields) {
37
+        const { statusBarHeight } = getSystemInfo() || {}
38
+        const isIPhoneX = checkIPhoneX()
39
+
40
+        Object.assign(defFields.data = (defFields.data || {}), {
41
+            safeAreaConfig: defaultSafeArea,
42
+            statusBarHeight,
43
+            isIPhoneX,
44
+        })
45
+    },
46
+})

+ 57 - 0
components/dist/helpers/safeSetDataBehavior.js

@@ -0,0 +1,57 @@
1
+export default Behavior({
2
+    lifetimes: {
3
+        created() {
4
+            this.nextCallback = null
5
+        },
6
+        detached() {
7
+            this.cancelNextCallback()
8
+        },
9
+    },
10
+    methods: {
11
+        /**
12
+         * safeSetData
13
+         * @param {Object} nextData 数据对象
14
+         * @param {Function} callback 回调函数
15
+         */
16
+        safeSetData(nextData, callback) {
17
+            this.pendingData = Object.assign({}, this.data, nextData)
18
+            callback = this.setNextCallback(callback)
19
+
20
+            this.setData(nextData, () => {
21
+                this.pendingData = null
22
+                callback()
23
+            })
24
+        },
25
+        /**
26
+         * 设置下一回调函数
27
+         * @param {Function} callback 回调函数
28
+         */
29
+        setNextCallback(callback) {
30
+            let active = true
31
+
32
+            this.nextCallback = (event) => {
33
+                if (active) {
34
+                    active = false
35
+                    this.nextCallback = null
36
+
37
+                    callback.call(this, event)
38
+                }
39
+            }
40
+
41
+            this.nextCallback.cancel = () => {
42
+                active = false
43
+            }
44
+
45
+            return this.nextCallback
46
+        },
47
+        /**
48
+         * 取消下一回调函数
49
+         */
50
+        cancelNextCallback() {
51
+            if (this.nextCallback !== null) {
52
+                this.nextCallback.cancel()
53
+                this.nextCallback = null
54
+            }
55
+        },
56
+    },
57
+})

+ 65 - 0
components/dist/helpers/shallowEqual.js

@@ -0,0 +1,65 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @typechecks
8
+ * 
9
+ */
10
+
11
+/*eslint-disable no-self-compare */
12
+
13
+'use strict';
14
+
15
+var hasOwnProperty = Object.prototype.hasOwnProperty;
16
+
17
+/**
18
+ * inlined Object.is polyfill to avoid requiring consumers ship their own
19
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
20
+ */
21
+function is(x, y) {
22
+    // SameValue algorithm
23
+    if (x === y) {
24
+        // Steps 1-5, 7-10
25
+        // Steps 6.b-6.e: +0 != -0
26
+        // Added the nonzero y check to make Flow happy, but it is redundant
27
+        return x !== 0 || y !== 0 || 1 / x === 1 / y;
28
+    } else {
29
+        // Step 6.a: NaN == NaN
30
+        return x !== x && y !== y;
31
+    }
32
+}
33
+
34
+/**
35
+ * Performs equality by iterating through keys on an object and returning false
36
+ * when any key has values which are not strictly equal between the arguments.
37
+ * Returns true when the values of all keys are strictly equal.
38
+ */
39
+function shallowEqual(objA, objB) {
40
+    if (is(objA, objB)) {
41
+        return true;
42
+    }
43
+
44
+    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
45
+        return false;
46
+    }
47
+
48
+    var keysA = Object.keys(objA);
49
+    var keysB = Object.keys(objB);
50
+
51
+    if (keysA.length !== keysB.length) {
52
+        return false;
53
+    }
54
+
55
+    // Test for A's keys different from B.
56
+    for (var i = 0; i < keysA.length; i++) {
57
+        if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
58
+            return false;
59
+        }
60
+    }
61
+
62
+    return true;
63
+}
64
+
65
+export default shallowEqual

+ 139 - 0
components/dist/helpers/styleToCssString.js

@@ -0,0 +1,139 @@
1
+'use strict';
2
+
3
+/**
4
+ * CSS properties which accept numbers but are not in units of "px".
5
+ */
6
+var isUnitlessNumber = {
7
+    boxFlex: true,
8
+    boxFlexGroup: true,
9
+    columnCount: true,
10
+    flex: true,
11
+    flexGrow: true,
12
+    flexPositive: true,
13
+    flexShrink: true,
14
+    flexNegative: true,
15
+    fontWeight: true,
16
+    lineClamp: true,
17
+    lineHeight: true,
18
+    opacity: true,
19
+    order: true,
20
+    orphans: true,
21
+    widows: true,
22
+    zIndex: true,
23
+    zoom: true,
24
+
25
+    // SVG-related properties
26
+    fillOpacity: true,
27
+    strokeDashoffset: true,
28
+    strokeOpacity: true,
29
+    strokeWidth: true
30
+};
31
+
32
+/**
33
+ * @param {string} prefix vendor-specific prefix, eg: Webkit
34
+ * @param {string} key style name, eg: transitionDuration
35
+ * @return {string} style name prefixed with `prefix`, properly camelCased, eg:
36
+ * WebkitTransitionDuration
37
+ */
38
+function prefixKey(prefix, key) {
39
+    return prefix + key.charAt(0).toUpperCase() + key.substring(1);
40
+}
41
+
42
+/**
43
+ * Support style names that may come passed in prefixed by adding permutations
44
+ * of vendor prefixes.
45
+ */
46
+var prefixes = ['Webkit', 'ms', 'Moz', 'O'];
47
+
48
+// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
49
+// infinite loop, because it iterates over the newly added props too.
50
+Object.keys(isUnitlessNumber).forEach(function(prop) {
51
+    prefixes.forEach(function(prefix) {
52
+        isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
53
+    });
54
+});
55
+
56
+var msPattern = /^ms-/;
57
+
58
+var _uppercasePattern = /([A-Z])/g;
59
+
60
+/**
61
+ * Hyphenates a camelcased string, for example:
62
+ *
63
+ *   > hyphenate('backgroundColor')
64
+ *   < "background-color"
65
+ *
66
+ * For CSS style names, use `hyphenateStyleName` instead which works properly
67
+ * with all vendor prefixes, including `ms`.
68
+ *
69
+ * @param {string} string
70
+ * @return {string}
71
+ */
72
+function hyphenate(string) {
73
+    return string.replace(_uppercasePattern, '-$1').toLowerCase();
74
+}
75
+
76
+/**
77
+ * Hyphenates a camelcased CSS property name, for example:
78
+ *
79
+ *   > hyphenateStyleName('backgroundColor')
80
+ *   < "background-color"
81
+ *   > hyphenateStyleName('MozTransition')
82
+ *   < "-moz-transition"
83
+ *   > hyphenateStyleName('msTransition')
84
+ *   < "-ms-transition"
85
+ *
86
+ * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
87
+ * is converted to `-ms-`.
88
+ *
89
+ * @param {string} string
90
+ * @return {string}
91
+ */
92
+function hyphenateStyleName(string) {
93
+    return hyphenate(string).replace(msPattern, '-ms-');
94
+}
95
+
96
+var isArray = Array.isArray;
97
+var keys = Object.keys;
98
+
99
+var counter = 1;
100
+// Follows syntax at https://developer.mozilla.org/en-US/docs/Web/CSS/content,
101
+// including multiple space separated values.
102
+var unquotedContentValueRegex = /^(normal|none|(\b(url\([^)]*\)|chapter_counter|attr\([^)]*\)|(no-)?(open|close)-quote|inherit)((\b\s*)|$|\s+))+)$/;
103
+
104
+function buildRule(key, value) {
105
+    if (!isUnitlessNumber[key] && typeof value === 'number') {
106
+        value = '' + value + 'px';
107
+    } else if (key === 'content' && !unquotedContentValueRegex.test(value)) {
108
+        value = "'" + value.replace(/'/g, "\\'") + "'";
109
+    }
110
+
111
+    return hyphenateStyleName(key) + ': ' + value + ';  ';
112
+}
113
+
114
+function styleToCssString(rules) {
115
+    var result = ''
116
+    if (typeof rules === 'string') {
117
+        rules = rules.trim()
118
+        return rules.slice(-1) === ";" ? `${rules} ` : `${rules}; `
119
+    }
120
+    if (!rules || keys(rules).length === 0) {
121
+        return result;
122
+    }
123
+    var styleKeys = keys(rules);
124
+    for (var j = 0, l = styleKeys.length; j < l; j++) {
125
+        var styleKey = styleKeys[j];
126
+        var value = rules[styleKey];
127
+
128
+        if (isArray(value)) {
129
+            for (var i = 0, len = value.length; i < len; i++) {
130
+                result += buildRule(styleKey, value[i]);
131
+            }
132
+        } else {
133
+            result += buildRule(styleKey, value);
134
+        }
135
+    }
136
+    return result;
137
+}
138
+
139
+export default styleToCssString

+ 47 - 0
components/dist/icon/index.js

@@ -0,0 +1,47 @@
1
+Component({
2
+    externalClasses: ['wux-class'],
3
+    properties: {
4
+        type: {
5
+            type: String,
6
+            value: '',
7
+        },
8
+        size: {
9
+            type: [String, Number],
10
+            value: 32,
11
+            observer: 'updated',
12
+        },
13
+        color: {
14
+            type: String,
15
+            value: '',
16
+        },
17
+        hidden: {
18
+            type: Boolean,
19
+            value: false,
20
+        },
21
+    },
22
+    data: {
23
+        fontSize: '',
24
+    },
25
+    methods: {
26
+        updated(size = this.data.size) {
27
+            let fontSize = size
28
+
29
+            if (typeof size === 'number') {
30
+                fontSize = `${size}px`
31
+            } else if (typeof size === 'string') {
32
+                if (!isNaN(Number(size))) {
33
+                    fontSize = `${size}px`
34
+                }
35
+            }
36
+
37
+            if (this.data.fontSize !== fontSize) {
38
+                this.setData({
39
+                    fontSize,
40
+                })
41
+            }
42
+        },
43
+    },
44
+    attached() {
45
+        this.updated()
46
+    },
47
+})

+ 3 - 0
components/dist/icon/index.json

@@ -0,0 +1,3 @@
1
+{
2
+    "component": true
3
+}

+ 1 - 0
components/dist/icon/index.wxml

@@ -0,0 +1 @@
1
+<view class="wux-class ion {{ type ? 'ion-' + type : '' }}" style="font-size: {{ fontSize }}; {{ color ? 'color: ' + color : '' }}" hidden="{{ hidden }}"></view>

Diff nebyl zobrazen, protože je příliš veliký
+ 2820 - 0
components/dist/icon/index.wxss


+ 67 - 0
components/dist/index.js

@@ -0,0 +1,67 @@
1
+/**
2
+ * Wux Weapp 3.8.7
3
+ * 一套组件化、可复用、易扩展的微信小程序 UI 组件库
4
+ * https://github.com/wux-weapp/wux-weapp#readme
5
+ *
6
+ * Copyright 2017-2021 skyvow
7
+ *
8
+ * Released under the MIT License
9
+ *
10
+ * Released on: 2021-4-12
11
+ */
12
+
13
+import $wuxCountDown from './countdown/index'
14
+import $wuxCountUp from './countup/index'
15
+
16
+/**
17
+ * 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象
18
+ * @param {String} selector 节点选择器
19
+ * @param {Object} ctx 页面栈或组件的实例,默认为当前页面栈实例
20
+ */
21
+export const getCtx = (selector, ctx = getCurrentPages()[getCurrentPages().length - 1]) => {
22
+    const componentCtx = ctx.selectComponent(selector)
23
+
24
+    if (!componentCtx) {
25
+        throw new Error('无法找到对应的组件,请按文档说明使用组件')
26
+    }
27
+
28
+    return componentCtx
29
+}
30
+
31
+const version = '3.8.7'
32
+const $wuxActionSheet = (selector = '#wux-actionsheet', ctx) => getCtx(selector, ctx)
33
+const $wuxBackdrop = (selector = '#wux-backdrop', ctx) => getCtx(selector, ctx)
34
+const $wuxCalendar = (selector = '#wux-calendar', ctx) => getCtx(selector, ctx)
35
+const $wuxDialog = (selector = '#wux-dialog', ctx) => getCtx(selector, ctx)
36
+const $wuxForm = (selector = '#wux-form', ctx) => getCtx(selector, ctx)
37
+const $wuxGallery = (selector = '#wux-gallery', ctx) => getCtx(selector, ctx)
38
+const $wuxKeyBoard = (selector = '#wux-keyboard', ctx) => getCtx(selector, ctx)
39
+const $wuxLoading = (selector = '#wux-loading', ctx) => getCtx(selector, ctx)
40
+const $wuxNotification = (selector = '#wux-notification', ctx) => getCtx(selector, ctx)
41
+const $startWuxRefresher = (selector = '#wux-refresher', ctx) => getCtx(selector, ctx).triggerRefresh()
42
+const $stopWuxRefresher = (selector = '#wux-refresher', ctx) => getCtx(selector, ctx).finishPullToRefresh()
43
+const $stopWuxLoader = (selector = '#wux-refresher', ctx, isEnd) => getCtx(selector, ctx).finishLoadmore(isEnd)
44
+const $wuxSelect = (selector = '#wux-select', ctx) => getCtx(selector, ctx)
45
+const $wuxToast = (selector = '#wux-toast', ctx) => getCtx(selector, ctx)
46
+const $wuxToptips = (selector = '#wux-toptips', ctx) => getCtx(selector, ctx)
47
+
48
+export {
49
+    version,
50
+	$wuxCountDown,
51
+	$wuxCountUp,
52
+	$wuxActionSheet,
53
+	$wuxBackdrop,
54
+	$wuxCalendar,
55
+	$wuxDialog,
56
+	$wuxForm,
57
+	$wuxGallery,
58
+	$wuxKeyBoard,
59
+	$wuxLoading,
60
+	$wuxNotification,
61
+	$startWuxRefresher,
62
+	$stopWuxRefresher,
63
+	$stopWuxLoader,
64
+	$wuxSelect,
65
+	$wuxToast,
66
+	$wuxToptips
67
+}

+ 66 - 0
components/dist/landscape/index.js

@@ -0,0 +1,66 @@
1
+import baseComponent from '../helpers/baseComponent'
2
+import classNames from '../helpers/classNames'
3
+
4
+baseComponent({
5
+    properties: {
6
+        prefixCls: {
7
+            type: String,
8
+            value: 'wux-landscape',
9
+        },
10
+        newPrefixCls: {
11
+            type: String,
12
+            value: '',
13
+        },
14
+        visible: {
15
+            type: Boolean,
16
+            value: false,
17
+        },
18
+        mask: {
19
+            type: Boolean,
20
+            value: true,
21
+            observer(newVal) {
22
+                this.setData({ showMask: newVal })
23
+            },
24
+        },
25
+        maskClosable: {
26
+            type: Boolean,
27
+            value: false,
28
+        },
29
+        closable: {
30
+            type: Boolean,
31
+            value: true,
32
+        },
33
+    },
34
+    data: {
35
+        showMask: true,
36
+    },
37
+    computed: {
38
+        classes: ['prefixCls, showMask', function(prefixCls, showMask) {
39
+            const wrap = classNames(prefixCls, {
40
+                [`${prefixCls}--has-mask`]: showMask,
41
+            })
42
+            const popup = `${prefixCls}__popup`
43
+            const popupBody = `${prefixCls}__popup-body`
44
+            const popupClose = `${prefixCls}__popup-close`
45
+            const inner = `${prefixCls}__inner`
46
+            const close = `${prefixCls}__close`
47
+            const x = `${prefixCls}__close-x`
48
+
49
+            return {
50
+                wrap,
51
+                popup,
52
+                popupBody,
53
+                popupClose,
54
+                inner,
55
+                close,
56
+                x,
57
+            }
58
+        }],
59
+    },
60
+    methods: {
61
+        onClose() {
62
+            this.triggerEvent('close', { visible: !this.data.visible })
63
+        },
64
+    },
65
+    attached() {},
66
+})

+ 6 - 0
components/dist/landscape/index.json

@@ -0,0 +1,6 @@
1
+{
2
+    "component": true,
3
+    "usingComponents": {
4
+        "wux-popup": "../popup/index"
5
+    }
6
+}

+ 22 - 0
components/dist/landscape/index.wxml

@@ -0,0 +1,22 @@
1
+<wux-popup
2
+    wux-content-class="{{ classes.popup }}"
3
+    wux-body-class="{{ classes.popupBody }}"
4
+    wux-close-class="{{ classes.popupClose }}"
5
+    visible="{{ visible }}"
6
+    hasHeader="{{ false }}"
7
+    hasFooter="{{ false }}"
8
+    mask="{{ showMask }}"
9
+    maskClosable="{{ maskClosable }}"
10
+    bind:close="onClose"
11
+    newPrefixCls="{{ newPrefixCls }}"
12
+>
13
+	<view class="wux-class {{ classes.wrap }}">
14
+	    <view class="{{ classes.inner }}">
15
+	    	<slot></slot>
16
+	    </view>
17
+        <view class="{{ classes.close }}" wx:if="{{ closable }}" bindtap="onClose">
18
+            <!-- <text class="{{ classes.x }}"></text> -->
19
+            <image class="close" src="/images/home_popup_close.png"></image>
20
+        </view>
21
+	</view>
22
+</wux-popup>

+ 36 - 0
components/dist/landscape/index.wxss

@@ -0,0 +1,36 @@
1
+.wux-landscape__popup {
2
+  background-color: transparent!important
3
+}
4
+.wux-landscape__popup-body {
5
+  padding: 0!important
6
+}
7
+.wux-landscape__inner {
8
+  padding: 30rpx;
9
+  font-size: 30rpx;
10
+  line-height: 1.5;
11
+  color: rgba(0,0,0,.65)
12
+}
13
+.wux-landscape__inner > image {
14
+  width: 100%;
15
+  max-width: 100%
16
+}
17
+.wux-landscape__close {
18
+  position: relative;
19
+  display: inline-block;
20
+  margin-top: 10rpx
21
+}
22
+.wux-landscape__close image {
23
+  width: 96rpx;
24
+  height: 96rpx;
25
+}
26
+.wux-landscape__close-x {
27
+  display: inline-block;
28
+  width: 48rpx;
29
+  height: 48rpx;
30
+  background-repeat: no-repeat;
31
+  background-size: cover;
32
+  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20width%3D%2230%22%20height%3D%2230%22%20viewBox%3D%220%200%2030%2030%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22%23888%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M1.414%200l28.284%2028.284-1.414%201.414L0%201.414z%22%2F%3E%3Cpath%20d%3D%22M28.284%200L0%2028.284l1.414%201.414L29.698%201.414z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
33
+}
34
+.wux-landscape--has-mask .wux-landscape__close-x {
35
+  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20width%3D%2230%22%20height%3D%2230%22%20viewBox%3D%220%200%2030%2030%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22%23fff%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M1.414%200l28.284%2028.284-1.414%201.414L0%201.414z%22%2F%3E%3Cpath%20d%3D%22M28.284%200L0%2028.284l1.414%201.414L29.698%201.414z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
36
+}

+ 194 - 0
components/dist/popup/index.js

@@ -0,0 +1,194 @@
1
+import baseComponent from '../helpers/baseComponent'
2
+import classNames from '../helpers/classNames'
3
+import styleToCssString from '../helpers/styleToCssString'
4
+import { $wuxBackdrop } from '../index'
5
+
6
+baseComponent({
7
+    useSafeArea: true,
8
+    externalClasses: ['wux-content-class', 'wux-header-class', 'wux-body-class', 'wux-footer-class', 'wux-close-class'],
9
+    properties: {
10
+        prefixCls: {
11
+            type: String,
12
+            value: 'wux-popup',
13
+        },
14
+        newPrefixCls: {
15
+            type: String,
16
+            value: '',
17
+        },
18
+        animationPrefixCls: {
19
+            type: String,
20
+            value: 'wux-animate',
21
+        },
22
+        title: {
23
+            type: String,
24
+            value: '',
25
+        },
26
+        content: {
27
+            type: String,
28
+            value: '',
29
+        },
30
+        extra: {
31
+            type: String,
32
+            value: '',
33
+        },
34
+        position: {
35
+            type: String,
36
+            value: 'center',
37
+            observer: 'getTransitionName',
38
+        },
39
+        wrapStyle: {
40
+            type: [String, Object],
41
+            value: '',
42
+            observer(newVal) {
43
+                this.setData({
44
+                    extStyle: styleToCssString(newVal),
45
+                })
46
+            },
47
+        },
48
+        closable: {
49
+            type: Boolean,
50
+            value: false,
51
+        },
52
+        mask: {
53
+            type: Boolean,
54
+            value: true,
55
+        },
56
+        maskClosable: {
57
+            type: Boolean,
58
+            value: true,
59
+        },
60
+        visible: {
61
+            type: Boolean,
62
+            value: false,
63
+            observer: 'setPopupVisible',
64
+        },
65
+        zIndex: {
66
+            type: Number,
67
+            value: 1000,
68
+        },
69
+        hasHeader: {
70
+            type: Boolean,
71
+            value: true,
72
+        },
73
+        hasFooter: {
74
+            type: Boolean,
75
+            value: true,
76
+        },
77
+        mountOnEnter: {
78
+            type: Boolean,
79
+            value: true,
80
+        },
81
+        unmountOnExit: {
82
+            type: Boolean,
83
+            value: true,
84
+        },
85
+    },
86
+    data: {
87
+        transitionName: '',
88
+        popupVisible: false,
89
+        extStyle: '',
90
+    },
91
+    computed: {
92
+        classes: ['prefixCls, position, safeAreaConfig, isIPhoneX', function(prefixCls, position, safeAreaConfig, isIPhoneX) {
93
+            const wrap = classNames(`${prefixCls}-position`, {
94
+                [`${prefixCls}-position--${position}`]: position,
95
+                [`${prefixCls}-position--is-iphonex`]: safeAreaConfig.bottom && isIPhoneX,
96
+            })
97
+            const content = `${prefixCls}__content`
98
+            const hd = `${prefixCls}__hd`
99
+            const title = `${prefixCls}__title`
100
+            const bd = `${prefixCls}__bd`
101
+            const ft = `${prefixCls}__ft`
102
+            const extra = `${prefixCls}__extra`
103
+            const close = `${prefixCls}__close`
104
+            const x = `${prefixCls}__close-x`
105
+
106
+            return {
107
+                wrap,
108
+                content,
109
+                hd,
110
+                title,
111
+                bd,
112
+                ft,
113
+                extra,
114
+                close,
115
+                x,
116
+            }
117
+        }],
118
+    },
119
+    methods: {
120
+        /**
121
+         * 点击关闭按钮事件
122
+         */
123
+        close() {
124
+            this.triggerEvent('close')
125
+        },
126
+        /**
127
+         * 点击蒙层事件
128
+         */
129
+        onMaskClick() {
130
+            if (this.data.maskClosable) {
131
+                this.close()
132
+            }
133
+        },
134
+        /**
135
+         * 组件关闭后的回调函数
136
+         */
137
+        onExited() {
138
+            this.triggerEvent('closed')
139
+        },
140
+        /**
141
+         * 获取过渡的类名
142
+         */
143
+        getTransitionName(value = this.data.position) {
144
+            const { animationPrefixCls } = this.data
145
+            let transitionName = ''
146
+
147
+            switch (value) {
148
+                    case 'top':
149
+                        transitionName = `${animationPrefixCls}--slideInDown`
150
+                        break
151
+                    case 'right':
152
+                        transitionName = `${animationPrefixCls}--slideInRight`
153
+                        break
154
+                    case 'bottom':
155
+                        transitionName = `${animationPrefixCls}--slideInUp`
156
+                        break
157
+                    case 'left':
158
+                        transitionName = `${animationPrefixCls}--slideInLeft`
159
+                        break
160
+                    default:
161
+                        transitionName = `${animationPrefixCls}--fadeIn`
162
+                        break
163
+            }
164
+
165
+            this.setData({ transitionName })
166
+        },
167
+        /**
168
+         * 设置 popup 组件的显示隐藏
169
+         */
170
+        setPopupVisible(popupVisible) {
171
+            if (this.data.popupVisible !== popupVisible) {
172
+                this.setData({ popupVisible })
173
+                this.setBackdropVisible(popupVisible)
174
+            }
175
+        },
176
+        /**
177
+         * 设置 backdrop 组件的显示隐藏
178
+         */
179
+        setBackdropVisible(visible) {
180
+            if (this.data.mask && this.$wuxBackdrop) {
181
+                this.$wuxBackdrop[visible ? 'retain' : 'release']()
182
+            }
183
+        },
184
+    },
185
+    created() {
186
+        if (this.data.mask) {
187
+            this.$wuxBackdrop = $wuxBackdrop('#wux-backdrop', this)
188
+        }
189
+    },
190
+    attached() {
191
+        this.setPopupVisible(this.data.visible)
192
+        this.getTransitionName()
193
+    },
194
+})

+ 7 - 0
components/dist/popup/index.json

@@ -0,0 +1,7 @@
1
+{
2
+    "component": true,
3
+    "usingComponents": {
4
+        "wux-animation-group": "../animation-group/index",
5
+        "wux-backdrop": "../backdrop/index"
6
+    }
7
+}

+ 26 - 0
components/dist/popup/index.wxml

@@ -0,0 +1,26 @@
1
+<wux-backdrop id="wux-backdrop" wx:if="{{ mask }}" bind:click="onMaskClick" zIndex="{{ zIndex }}" />
2
+<view class="wux-class {{ classes.wrap }}" style="{{ extStyle }}">
3
+    <wux-animation-group wux-class="{{ prefixCls }} {{ newPrefixCls }}" in="{{ popupVisible }}" classNames="{{ transitionName }}" bind:exited="onExited" wrapStyle="{{ { zIndex } }}" mountOnEnter="{{ mountOnEnter }}" unmountOnExit="{{ unmountOnExit }}">
4
+        <view class="wux-content-class {{ classes.content }}">
5
+            <view class="wux-header-class {{ classes.hd }}" wx:if="{{ hasHeader }}">
6
+                <view class="{{ classes.title }}" wx:if="{{ title }}">{{ title }}</view>
7
+                <block wx:else>
8
+                    <slot name="header"></slot>
9
+                </block>
10
+            </view>
11
+            <view class="wux-body-class {{ classes.bd }}">
12
+                <view wx:if="{{ content }}">{{ content }}</view>
13
+                <slot></slot>
14
+            </view>
15
+            <view class="wux-footer-class {{ classes.ft }}" wx:if="{{ hasFooter }}">
16
+                <view class="{{ classes.extra }}" wx:if="{{ extra }}">{{ extra }}</view>
17
+                <block wx:else>
18
+                    <slot name="footer"></slot>
19
+                </block>
20
+            </view>
21
+            <view class="wux-close-class {{ classes.close }}" wx:if="{{ closable }}" bindtap="close">
22
+                <text class="{{ classes.x }}"></text>
23
+            </view>
24
+        </view>
25
+    </wux-animation-group>
26
+</view>

+ 121 - 0
components/dist/popup/index.wxss

@@ -0,0 +1,121 @@
1
+.wux-popup {
2
+  position: fixed;
3
+  z-index: 1000;
4
+  width: 80%;
5
+  max-width: 600rpx
6
+}
7
+
8
+.wux-popup-widtn {
9
+  width: 100%;
10
+  max-width: 100%
11
+}
12
+
13
+.wux-popup-position.wux-popup-position--center .wux-popup {
14
+  top: 50%;
15
+  left: 50%;
16
+  transform: translate(-50%,-50%)
17
+}
18
+.wux-popup-position.wux-popup-position--center .wux-popup__content {
19
+  border-radius: 8rpx
20
+}
21
+.wux-popup-position.wux-popup-position--center .wux-popup__hd {
22
+  padding: 1.3em 1.6em .5em
23
+}
24
+.wux-popup-position.wux-popup-position--center .wux-popup__bd {
25
+  padding: 0 1.6em .8em
26
+}
27
+.wux-popup-position.wux-popup-position--center .wux-popup__ft::after {
28
+  content: " ";
29
+  position: absolute;
30
+  left: 0;
31
+  top: 0;
32
+  right: 0;
33
+  height: 1PX;
34
+  border-top: 1PX solid #d9d9d9;
35
+  color: #d9d9d9;
36
+  transform-origin: 0 0;
37
+  transform: scaleY(.5)
38
+}
39
+.wux-popup-position.wux-popup-position--top .wux-popup {
40
+  position: fixed;
41
+  left: 0;
42
+  top: 0;
43
+  width: 100%;
44
+  max-width: 100%
45
+}
46
+.wux-popup-position.wux-popup-position--right .wux-popup {
47
+  position: fixed;
48
+  top: 0;
49
+  right: 0;
50
+  width: 80%;
51
+  max-width: 100%;
52
+  height: 100%;
53
+  max-height: 100%
54
+}
55
+.wux-popup-position.wux-popup-position--bottom .wux-popup {
56
+  position: fixed;
57
+  left: 0;
58
+  bottom: 0;
59
+  width: 100%;
60
+  max-width: 100%
61
+}
62
+.wux-popup-position.wux-popup-position--left .wux-popup {
63
+  position: fixed;
64
+  left: 0;
65
+  top: 0;
66
+  width: 80%;
67
+  max-width: 100%;
68
+  height: 100%;
69
+  max-height: 100%
70
+}
71
+.wux-popup-position.wux-popup-position--is-iphonex .wux-popup__content {
72
+  padding-bottom: 68rpx
73
+}
74
+.wux-popup__content {
75
+  position: relative;
76
+  background-color: #fff;
77
+  border: 0;
78
+  background-clip: padding-box;
79
+  height: 100%;
80
+  text-align: center;
81
+  overflow: hidden
82
+}
83
+.wux-popup__title {
84
+  font-weight: 400;
85
+  font-size: 36rpx;
86
+  color: rgba(0,0,0,.85)
87
+}
88
+.wux-popup__bd {
89
+  min-height: 80rpx;
90
+  font-size: 30rpx;
91
+  line-height: 1.3;
92
+  word-wrap: break-word;
93
+  word-break: break-all;
94
+  color: rgba(0,0,0,.45)
95
+}
96
+.wux-popup__ft {
97
+  position: relative;
98
+  line-height: 96rpx;
99
+  font-size: 36rpx;
100
+  display: -ms-flexbox;
101
+  display: flex
102
+}
103
+.wux-popup__close {
104
+  border: 0;
105
+  padding: 6rpx;
106
+  background-color: transparent;
107
+  outline: 0;
108
+  position: absolute;
109
+  top: 12rpx;
110
+  right: 12rpx;
111
+  height: 42rpx;
112
+  width: 42rpx
113
+}
114
+.wux-popup__close-x {
115
+  display: inline-block;
116
+  width: 30rpx;
117
+  height: 30rpx;
118
+  background-repeat: no-repeat;
119
+  background-size: cover;
120
+  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20width%3D%2230%22%20height%3D%2230%22%20viewBox%3D%220%200%2030%2030%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22%23888%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M1.414%200l28.284%2028.284-1.414%201.414L0%201.414z%22%2F%3E%3Cpath%20d%3D%22M28.284%200L0%2028.284l1.414%201.414L29.698%201.414z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
121
+}

+ 202 - 0
components/dist/rater/index.js

@@ -0,0 +1,202 @@
1
+import baseComponent from '../helpers/baseComponent'
2
+import classNames from '../helpers/classNames'
3
+import eventsMixin from '../helpers/eventsMixin'
4
+
5
+baseComponent({
6
+    behaviors: [eventsMixin()],
7
+    relations: {
8
+        '../field/index': {
9
+            type: 'ancestor',
10
+        },
11
+    },
12
+    properties: {
13
+        prefixCls: {
14
+            type: String,
15
+            value: 'wux-rater',
16
+        },
17
+        max: {
18
+            type: Number,
19
+            value: 5,
20
+            observer() {
21
+                this.setValue(this.data.inputValue)
22
+            },
23
+        },
24
+        icon: {
25
+            type: String,
26
+            value: '',
27
+        },
28
+        star: {
29
+            type: String,
30
+            value: '★',
31
+        },
32
+        defaultValue: {
33
+            type: Number,
34
+            value: 0,
35
+        },
36
+        value: {
37
+            type: Number,
38
+            value: 0,
39
+            observer(newVal) {
40
+                if (this.data.controlled) {
41
+                    this.setValue(newVal)
42
+                }
43
+            },
44
+        },
45
+        activeColor: {
46
+            type: String,
47
+            value: '#ffc900',
48
+        },
49
+        margin: {
50
+            type: Number,
51
+            value: 2,
52
+        },
53
+        fontSize: {
54
+            type: Number,
55
+            value: 25,
56
+        },
57
+        disabled: {
58
+            type: Boolean,
59
+            value: false,
60
+        },
61
+        allowHalf: {
62
+            type: Boolean,
63
+            value: false,
64
+        },
65
+        allowClear: {
66
+            type: Boolean,
67
+            value: false,
68
+        },
69
+        allowTouchMove: {
70
+            type: Boolean,
71
+            value: false,
72
+        },
73
+        controlled: {
74
+            type: Boolean,
75
+            value: false,
76
+        },
77
+    },
78
+    data: {
79
+        inputValue: 0,
80
+    },
81
+    computed: {
82
+        classes: ['prefixCls, disabled', function(prefixCls, disabled) {
83
+            const wrap = classNames(prefixCls, {
84
+                [`${prefixCls}--disabled`]: disabled,
85
+            })
86
+            const star = `${prefixCls}__star`
87
+            const box = `${prefixCls}__box`
88
+            const inner = `${prefixCls}__inner`
89
+            const outer = `${prefixCls}__outer`
90
+            const icon = `${prefixCls}__icon`
91
+
92
+            return {
93
+                wrap,
94
+                star,
95
+                box,
96
+                inner,
97
+                outer,
98
+                icon,
99
+            }
100
+        }],
101
+    },
102
+    observers: {
103
+        ['inputValue, max, activeColor'](inputValue, max, activeColor) {
104
+            const stars = [...new Array(max)].map((_, i) => i)
105
+            const colors = stars.reduce((a, _, i) => ([...a, i <= inputValue - 1 ? activeColor : '#ccc']), [])
106
+            const _val = inputValue.toString().split('.')
107
+            const sliceValue = _val.length === 1 ? [_val[0], 0] : _val
108
+
109
+            this.setData({
110
+                stars,
111
+                colors,
112
+                cutIndex: sliceValue[0] * 1,
113
+                cutPercent: sliceValue[1] * 10,
114
+            })
115
+        },
116
+    },
117
+    methods: {
118
+        updated(inputValue) {
119
+            if (this.hasFieldDecorator) return
120
+            if (this.data.inputValue !== inputValue) {
121
+                this.setData({ inputValue })
122
+            }
123
+        },
124
+        setValue(value) {
125
+            const { max } = this.data
126
+            const inputValue = value <= 0 ? 0 : value > max ? max : value
127
+
128
+            this.updated(inputValue)
129
+        },
130
+        updateHalfStarValue(index, x, cb) {
131
+            const { prefixCls } = this.data
132
+            const query = wx.createSelectorQuery().in(this)
133
+            query.selectAll(`.${prefixCls}__star`).boundingClientRect((rects) => {
134
+                if (rects.filter((n) => !n).length) return
135
+                const { left, width } = rects[index]
136
+                const has = (x - left) < width / 2
137
+                const value = has ? index + .5 : index + 1
138
+                cb.call(this, value, index)
139
+            }).exec()
140
+        },
141
+        onTap(e) {
142
+            const { index } = e.currentTarget.dataset
143
+            const { inputValue, disabled, allowHalf, allowClear } = this.data
144
+
145
+            // 判断是否禁用
146
+            if (!disabled) {
147
+                // 判断是否支持选中半星
148
+                if (!allowHalf) {
149
+                    const value = index + 1
150
+                    const isReset = allowClear && value === inputValue
151
+
152
+                    this.onChange(isReset ? 0 : value, index)
153
+                } else {
154
+                    this.updateHalfStarValue(index, e.detail.x, (value, index) => {
155
+                        const isReset = allowClear && value === inputValue
156
+
157
+                        this.onChange(isReset ? 0 : value, index)
158
+                    })
159
+                }
160
+            }
161
+        },
162
+        onChange(value, index) {
163
+            if (!this.data.controlled) {
164
+                this.setValue(value)
165
+            }
166
+
167
+            this.triggerEvent('change', { value, index })
168
+        },
169
+        onTouchMove(e) {
170
+            const { disabled, allowHalf, allowTouchMove } = this.data
171
+            if (!disabled && allowTouchMove) {
172
+                const x = e.changedTouches[0].pageX
173
+                const { prefixCls } = this.data
174
+                const query = wx.createSelectorQuery().in(this)
175
+                query.selectAll(`.${prefixCls}__star`).boundingClientRect((rects) => {
176
+                    if (rects.filter((n) => !n).length) return
177
+                    const { left, width } = rects[0]
178
+                    const maxWidth = rects.map((n) => n.width).reduce((a, b) => a + b)
179
+                    const diff = x - left
180
+                    let value = Math.ceil(diff / width)
181
+
182
+                    // 判断是否在组件宽度范围内
183
+                    if (diff > 0 && diff < maxWidth) {
184
+                        const index = value - 1
185
+                        if (allowHalf) {
186
+                            const star = rects[index]
187
+                            const has = (x - star.left) < star.width / 2
188
+                            value = has ? value - .5 : value
189
+                        }
190
+                        this.onChange(value, index)
191
+                    }
192
+                }).exec()
193
+            }
194
+        },
195
+    },
196
+    attached() {
197
+        const { defaultValue, value, controlled } = this.data
198
+        const inputValue = controlled ? value : defaultValue
199
+
200
+        this.setValue(inputValue)
201
+    },
202
+})

+ 6 - 0
components/dist/rater/index.json

@@ -0,0 +1,6 @@
1
+{
2
+    "component": true,
3
+    "usingComponents": {
4
+        "wux-icon": "../icon/index"
5
+    }
6
+}

+ 16 - 0
components/dist/rater/index.wxml

@@ -0,0 +1,16 @@
1
+<view class="wux-class {{ classes.wrap }}" bindtouchmove="onTouchMove">
2
+    <block wx:for="{{ stars }}" wx:key="index">
3
+        <view class="{{ classes.star }}" bindtap="onTap" data-index="{{ index }}">
4
+            <view class="{{ classes.box }} {{ inputValue > index ? prefixCls + '__box--active' : '' }}" style="color: {{ colors && colors[index] ? colors[index]: '#ccc' }}; margin-right: {{ margin + 'px' }}; font-size: {{ fontSize + 'px' }}; width: {{ fontSize + 'px' }}; height: {{ fontSize + 'px' }}; line-height: {{ fontSize + 'px' }}">
5
+                <view class="{{ classes.inner }}">
6
+                    <wux-icon wx:if="{{ icon }}" wux-class="{{ classes.icon }}" type="{{ icon }}" size="{{ fontSize }}" color="{{ colors && colors[index] ? colors[index]: '#ccc' }}" />
7
+                    <block wx:else>{{ star }}</block>
8
+                    <view class="{{ classes.outer }}" style="color: {{ activeColor }}; width: {{ cutPercent + '%' }}" wx:if="{{ cutPercent > 0 && cutIndex === index }}">
9
+                        <wux-icon wx:if="{{ icon }}" wux-class="{{ classes.icon }}" type="{{ icon }}" size="{{ fontSize }}" color="{{ activeColor }}" />
10
+                        <block wx:else>{{ star }}</block>
11
+                    </view>
12
+                </view>
13
+            </view>
14
+        </view>
15
+    </block>
16
+</view>

+ 52 - 0
components/dist/rater/index.wxss

@@ -0,0 +1,52 @@
1
+.wux-rater {
2
+  display: inline-block;
3
+  margin: 0;
4
+  padding: 0;
5
+  line-height: normal;
6
+  vertical-align: middle;
7
+  font-weight: 400;
8
+  font-style: normal;
9
+  text-align: left
10
+}
11
+.wux-rater::after,
12
+.wux-rater::before {
13
+  display: table;
14
+  content: " "
15
+}
16
+.wux-rater::after {
17
+  clear: both
18
+}
19
+.wux-rater__star {
20
+  position: relative;
21
+  display: inline-block
22
+}
23
+.wux-rater__star:last-child .wux-rater__box {
24
+  padding-right: 4rpx!important;
25
+  margin-right: 0!important
26
+}
27
+.wux-rater__box {
28
+  position: relative;
29
+  display: inline-block;
30
+  text-align: center;
31
+  cursor: pointer;
32
+  color: #ccc;
33
+  transition: color .3s ease
34
+}
35
+.wux-rater__box--disabled {
36
+  color: #ccc!important;
37
+  cursor: not-allowed
38
+}
39
+.wux-rater__icon {
40
+  transition: color .3s ease
41
+}
42
+.wux-rater__inner {
43
+  position: relative;
44
+  display: inline-block
45
+}
46
+.wux-rater__outer {
47
+  position: absolute;
48
+  left: 0;
49
+  top: 0;
50
+  display: inline-block;
51
+  overflow: hidden
52
+}

+ 33 - 0
components/local/calendar/func/config.js

@@ -0,0 +1,33 @@
1
+import WxData from './wxData'
2
+
3
+class Config extends WxData {
4
+  constructor(component) {
5
+    super(component)
6
+    this.Component = component
7
+  }
8
+  getCalendarConfig() {
9
+    if (!this.Component || !this.Component.config) return {}
10
+    return this.Component.config
11
+  }
12
+  setCalendarConfig(config) {
13
+    return new Promise((resolve, reject) => {
14
+      if (!this.Component || !this.Component.config) {
15
+        reject('异常:未找到组件配置信息')
16
+        return
17
+      }
18
+      let conf = { ...this.Component.config, ...config }
19
+
20
+      this.Component.config = conf
21
+      this.setData(
22
+        {
23
+          calendarConfig: conf
24
+        },
25
+        () => {
26
+          resolve(conf)
27
+        }
28
+      )
29
+    })
30
+  }
31
+}
32
+
33
+export default component => new Config(component)

Diff nebyl zobrazen, protože je příliš veliký
+ 1036 - 0
components/local/calendar/func/convertSolarLunar.js


+ 543 - 0
components/local/calendar/func/day.js

@@ -0,0 +1,543 @@
1
+import WxData from './wxData'
2
+import CalendarConfig from './config'
3
+import convertSolarLunar from './convertSolarLunar'
4
+import {
5
+  Logger,
6
+  GetDate,
7
+  getDateTimeStamp,
8
+  uniqueArrayByDate,
9
+  delRepeatedEnableDay,
10
+  convertEnableAreaToTimestamp,
11
+  converEnableDaysToTimestamp
12
+} from './utils'
13
+
14
+const logger = new Logger()
15
+const getDate = new GetDate()
16
+const toString = Object.prototype.toString
17
+
18
+class Day extends WxData {
19
+  constructor(component) {
20
+    super(component)
21
+    this.Component = component
22
+  }
23
+  getCalendarConfig() {
24
+    return this.Component.config
25
+  }
26
+  /**
27
+   *
28
+   * @param {number} year
29
+   * @param {number} month
30
+   */
31
+  buildDate(year, month) {
32
+    const today = getDate.todayDate()
33
+    const thisMonthDays = getDate.thisMonthDays(year, month)
34
+    const dates = []
35
+    for (let i = 1; i <= thisMonthDays; i++) {
36
+      const isToday =
37
+        +today.year === +year && +today.month === +month && i === +today.date
38
+      const config = this.getCalendarConfig()
39
+      const date = {
40
+        year,
41
+        month,
42
+        day: i,
43
+        choosed: false,
44
+        week: getDate.dayOfWeek(year, month, i),
45
+        isToday: isToday && config.highlightToday,
46
+        lunar: convertSolarLunar.solar2lunar(+year, +month, +i)
47
+      }
48
+      dates.push(date)
49
+    }
50
+    return dates
51
+  }
52
+  /**
53
+   * 指定可选日期范围
54
+   * @param {array} area 日期访问数组
55
+   */
56
+  enableArea(dateArea = []) {
57
+    if (dateArea.length === 2) {
58
+      const isRight = this.__judgeParam(dateArea)
59
+      if (isRight) {
60
+        let { days = [], selectedDay = [] } = this.getData('calendar')
61
+        const { startTimestamp, endTimestamp } = convertEnableAreaToTimestamp(
62
+          dateArea
63
+        )
64
+        const dataAfterHandle = this.__handleEnableArea(
65
+          {
66
+            dateArea,
67
+            days,
68
+            startTimestamp,
69
+            endTimestamp
70
+          },
71
+          selectedDay
72
+        )
73
+        this.setData({
74
+          'calendar.enableArea': dateArea,
75
+          'calendar.days': dataAfterHandle.dates,
76
+          'calendar.selectedDay': dataAfterHandle.selectedDay,
77
+          'calendar.enableAreaTimestamp': [startTimestamp, endTimestamp]
78
+        })
79
+      }
80
+    } else {
81
+      logger.warn(
82
+        'enableArea()参数需为时间范围数组,形如:["2018-8-4" , "2018-8-24"]'
83
+      )
84
+    }
85
+  }
86
+  /**
87
+   * 指定特定日期可选
88
+   * @param {array} days 指定日期数组
89
+   */
90
+  enableDays(dates = []) {
91
+    const { enableArea = [] } = this.getData('calendar')
92
+    let expectEnableDaysTimestamp = []
93
+    if (enableArea.length) {
94
+      expectEnableDaysTimestamp = delRepeatedEnableDay(dates, enableArea)
95
+    } else {
96
+      expectEnableDaysTimestamp = converEnableDaysToTimestamp(dates)
97
+    }
98
+    let { days = [], selectedDay = [] } = this.getData('calendar')
99
+    const dataAfterHandle = this.__handleEnableDays(
100
+      {
101
+        days,
102
+        expectEnableDaysTimestamp
103
+      },
104
+      selectedDay
105
+    )
106
+    this.setData({
107
+      'calendar.days': dataAfterHandle.dates,
108
+      'calendar.selectedDay': dataAfterHandle.selectedDay,
109
+      'calendar.enableDays': dates,
110
+      'calendar.enableDaysTimestamp': expectEnableDaysTimestamp
111
+    })
112
+  }
113
+  /**
114
+   * 设置多个日期选中
115
+   * @param {array} selected 需选中日期
116
+   */
117
+  setSelectedDays(selected) {
118
+    const config = CalendarConfig(this.Component).getCalendarConfig()
119
+    if (!config.multi) {
120
+      return logger.warn('单选模式下不能设置多日期选中,请配置 multi')
121
+    }
122
+    let { days } = this.getData('calendar')
123
+    let newSelectedDay = []
124
+    if (!selected) {
125
+      days.map(item => {
126
+        item.choosed = true
127
+        item.showTodoLabel = false
128
+      })
129
+      newSelectedDay = days
130
+    } else if (selected && selected.length) {
131
+      const { dates, selectedDates } = this.__handleSelectedDays(
132
+        days,
133
+        newSelectedDay,
134
+        selected
135
+      )
136
+      days = dates
137
+      newSelectedDay = selectedDates
138
+    }
139
+    CalendarConfig(this.Component).setCalendarConfig('multi', true)
140
+    this.setData({
141
+      'calendar.days': days,
142
+      'calendar.selectedDay': newSelectedDay
143
+    })
144
+  }
145
+  /**
146
+   * 禁用指定日期
147
+   * @param {array} dates  禁用
148
+   */
149
+  disableDays(dates) {
150
+    const { disableDays = [], days } = this.getData('calendar')
151
+    if (Object.prototype.toString.call(dates) !== '[object Array]') {
152
+      return logger.warn('disableDays 参数为数组')
153
+    }
154
+    let _disableDays = []
155
+    if (dates.length) {
156
+      _disableDays = uniqueArrayByDate(dates.concat(disableDays))
157
+      const disableDaysCol = _disableDays.map(d => getDate.toTimeStr(d))
158
+      days.forEach(item => {
159
+        const cur = getDate.toTimeStr(item)
160
+        if (disableDaysCol.includes(cur)) item.disable = true
161
+      })
162
+    } else {
163
+      days.forEach(item => {
164
+        item.disable = false
165
+      })
166
+    }
167
+    this.setData({
168
+      'calendar.days': days,
169
+      'calendar.disableDays': _disableDays
170
+    })
171
+  }
172
+  /**
173
+   * 设置连续日期选择区域
174
+   * @param {array} dateArea 区域开始结束日期数组
175
+   */
176
+  chooseArea(dateArea = []) {
177
+    return new Promise((resolve, reject) => {
178
+      if (dateArea.length === 1) {
179
+        dateArea = dateArea.concat(dateArea)
180
+      }
181
+      if (dateArea.length === 2) {
182
+        const isRight = this.__judgeParam(dateArea)
183
+        if (isRight) {
184
+          const config = CalendarConfig(this.Component).getCalendarConfig()
185
+          const { startTimestamp, endTimestamp } = convertEnableAreaToTimestamp(
186
+            dateArea
187
+          )
188
+          this.setData(
189
+            {
190
+              calendarConfig: {
191
+                ...config,
192
+                chooseAreaMode: true,
193
+                mulit: true
194
+              },
195
+              'calendar.chooseAreaTimestamp': [startTimestamp, endTimestamp]
196
+            },
197
+            () => {
198
+              this.__chooseContinuousDates(startTimestamp, endTimestamp)
199
+                .then(resolve)
200
+                .catch(reject)
201
+            }
202
+          )
203
+        }
204
+      }
205
+    })
206
+  }
207
+  __pusheNextMonthDateArea(item, startTimestamp, endTimestamp, selectedDates) {
208
+    const days = this.buildDate(item.year, item.month)
209
+    let daysLen = days.length
210
+    for (let i = 0; i < daysLen; i++) {
211
+      const item = days[i]
212
+      const timeStamp = getDateTimeStamp(item)
213
+      if (timeStamp <= endTimestamp && timeStamp >= startTimestamp) {
214
+        selectedDates.push({
215
+          ...item,
216
+          choosed: true
217
+        })
218
+      }
219
+      if (i === daysLen - 1 && timeStamp < endTimestamp) {
220
+        this.__pusheNextMonthDateArea(
221
+          getDate.nextMonth(item),
222
+          startTimestamp,
223
+          endTimestamp,
224
+          selectedDates
225
+        )
226
+      }
227
+    }
228
+  }
229
+  __pushPrevMonthDateArea(item, startTimestamp, endTimestamp, selectedDates) {
230
+    const days = getDate.sortDates(
231
+      this.buildDate(item.year, item.month),
232
+      'desc'
233
+    )
234
+    let daysLen = days.length
235
+    let firstDate = getDateTimeStamp(days[0])
236
+    for (let i = 0; i < daysLen; i++) {
237
+      const item = days[i]
238
+      const timeStamp = getDateTimeStamp(item)
239
+      if (timeStamp >= startTimestamp && timeStamp <= endTimestamp) {
240
+        selectedDates.push({
241
+          ...item,
242
+          choosed: true
243
+        })
244
+      }
245
+      if (i === daysLen - 1 && firstDate > startTimestamp) {
246
+        this.__pushPrevMonthDateArea(
247
+          getDate.prevMonth(item),
248
+          startTimestamp,
249
+          endTimestamp,
250
+          selectedDates
251
+        )
252
+      }
253
+    }
254
+  }
255
+  /**
256
+   * 当设置日期区域非当前时保存其他月份的日期至已选日期数组
257
+   * @param {object} info
258
+   */
259
+  __calcDateWhenNotInOneMonth(info) {
260
+    const {
261
+      firstDate,
262
+      lastDate,
263
+      startTimestamp,
264
+      endTimestamp,
265
+      filterSelectedDate
266
+    } = info
267
+    if (getDateTimeStamp(firstDate) > startTimestamp) {
268
+      this.__pushPrevMonthDateArea(
269
+        getDate.prevMonth(firstDate),
270
+        startTimestamp,
271
+        endTimestamp,
272
+        filterSelectedDate
273
+      )
274
+    }
275
+    if (getDateTimeStamp(lastDate) < endTimestamp) {
276
+      this.__pusheNextMonthDateArea(
277
+        getDate.nextMonth(lastDate),
278
+        startTimestamp,
279
+        endTimestamp,
280
+        filterSelectedDate
281
+      )
282
+    }
283
+    const newSelectedDates = [...getDate.sortDates(filterSelectedDate)]
284
+    return newSelectedDates
285
+  }
286
+  /**
287
+   * 设置连续日期段
288
+   * @param {number} startTimestamp 连续日期段开始日期时间戳
289
+   * @param {number} endTimestamp 连续日期段结束日期时间戳
290
+   */
291
+  __chooseContinuousDates(startTimestamp, endTimestamp) {
292
+    return new Promise((resolve, reject) => {
293
+      const { days, selectedDay = [] } = this.getData('calendar')
294
+      const selectedDateStr = []
295
+      let filterSelectedDate = []
296
+      selectedDay.forEach(item => {
297
+        const timeStamp = getDateTimeStamp(item)
298
+        if (timeStamp >= startTimestamp && timeStamp <= endTimestamp) {
299
+          filterSelectedDate.push(item)
300
+          selectedDateStr.push(getDate.toTimeStr(item))
301
+        }
302
+      })
303
+      days.forEach(item => {
304
+        const timeStamp = getDateTimeStamp(item)
305
+        const dateInSelecedArray = selectedDateStr.includes(
306
+          getDate.toTimeStr(item)
307
+        )
308
+        if (timeStamp >= startTimestamp && timeStamp <= endTimestamp) {
309
+          if (dateInSelecedArray) {
310
+            return
311
+          }
312
+          item.choosed = true
313
+          filterSelectedDate.push(item)
314
+        } else {
315
+          item.choosed = false
316
+          if (dateInSelecedArray) {
317
+            const idx = filterSelectedDate.findIndex(
318
+              selectedDate =>
319
+                getDate.toTimeStr(selectedDate) === getDate.toTimeStr(item)
320
+            )
321
+            if (idx > -1) {
322
+              filterSelectedDate.splice(idx, 1)
323
+            }
324
+          }
325
+        }
326
+      })
327
+      const firstDate = days[0]
328
+      const lastDate = days[days.length - 1]
329
+      const newSelectedDates = this.__calcDateWhenNotInOneMonth({
330
+        firstDate,
331
+        lastDate,
332
+        startTimestamp,
333
+        endTimestamp,
334
+        filterSelectedDate
335
+      })
336
+      try {
337
+        this.setData(
338
+          {
339
+            'calendar.days': [...days],
340
+            'calendar.selectedDay': newSelectedDates
341
+          },
342
+          () => {
343
+            resolve(newSelectedDates)
344
+          }
345
+        )
346
+      } catch (err) {
347
+        reject(err)
348
+      }
349
+    })
350
+  }
351
+  /**
352
+   * 设置指定日期样式
353
+   * @param {array} dates 待设置特殊样式的日期
354
+   */
355
+  setDateStyle(dates) {
356
+    if (toString.call(dates) !== '[object Array]') return
357
+    const { days, specialStyleDates } = this.getData('calendar')
358
+    if (toString.call(specialStyleDates) === '[object Array]') {
359
+      dates = uniqueArrayByDate([...specialStyleDates, ...dates])
360
+    }
361
+    const _specialStyleDates = dates.map(
362
+      item => `${item.year}_${item.month}_${item.day}`
363
+    )
364
+    const _days = days.map(item => {
365
+      const idx = _specialStyleDates.indexOf(
366
+        `${item.year}_${item.month}_${item.day}`
367
+      )
368
+      if (idx > -1) {
369
+        return {
370
+          ...item,
371
+          class: dates[idx].class
372
+        }
373
+      } else {
374
+        return { ...item }
375
+      }
376
+    })
377
+    this.setData({
378
+      'calendar.days': _days,
379
+      'calendar.specialStyleDates': dates
380
+    })
381
+  }
382
+  __judgeParam(dateArea) {
383
+    const {
384
+      start,
385
+      end,
386
+      startTimestamp,
387
+      endTimestamp
388
+    } = convertEnableAreaToTimestamp(dateArea)
389
+    if (!start || !end) return
390
+    const startMonthDays = getDate.thisMonthDays(start[0], start[1])
391
+    const endMonthDays = getDate.thisMonthDays(end[0], end[1])
392
+    if (start[2] > startMonthDays || start[2] < 1) {
393
+      logger.warn('enableArea() 开始日期错误,指定日期不在当前月份天数范围内')
394
+      return false
395
+    } else if (start[1] > 12 || start[1] < 1) {
396
+      logger.warn('enableArea() 开始日期错误,月份超出1-12月份')
397
+      return false
398
+    } else if (end[2] > endMonthDays || end[2] < 1) {
399
+      logger.warn('enableArea() 截止日期错误,指定日期不在当前月份天数范围内')
400
+      return false
401
+    } else if (end[1] > 12 || end[1] < 1) {
402
+      logger.warn('enableArea() 截止日期错误,月份超出1-12月份')
403
+      return false
404
+    } else if (startTimestamp > endTimestamp) {
405
+      logger.warn('enableArea()参数最小日期大于了最大日期')
406
+      return false
407
+    } else {
408
+      return true
409
+    }
410
+  }
411
+  __getDisableDateTimestamp() {
412
+    let disableDateTimestamp
413
+    const { date, type } = this.getCalendarConfig().disableMode || {}
414
+    if (date) {
415
+      const t = date.split('-')
416
+      if (t.length < 3) {
417
+        logger.warn('配置 disableMode.date 格式错误')
418
+        return {}
419
+      }
420
+      disableDateTimestamp = getDateTimeStamp({
421
+        year: +t[0],
422
+        month: +t[1],
423
+        day: +t[2]
424
+      })
425
+    }
426
+    return {
427
+      disableDateTimestamp,
428
+      disableType: type
429
+    }
430
+  }
431
+  __handleEnableArea(data = {}, selectedDay = []) {
432
+    const { area, days, startTimestamp, endTimestamp } = data
433
+    const enableDays = this.getData('calendar.enableDays') || []
434
+    let expectEnableDaysTimestamp = []
435
+    if (enableDays.length) {
436
+      expectEnableDaysTimestamp = delRepeatedEnableDay(enableDays, area)
437
+    }
438
+    const {
439
+      disableDateTimestamp,
440
+      disableType
441
+    } = this.__getDisableDateTimestamp()
442
+    const dates = [...days]
443
+    dates.forEach(item => {
444
+      const timestamp = +getDate
445
+        .newDate(item.year, item.month, item.day)
446
+        .getTime()
447
+      const ifOutofArea =
448
+        (+startTimestamp > timestamp || timestamp > +endTimestamp) &&
449
+        !expectEnableDaysTimestamp.includes(timestamp)
450
+      if (
451
+        ifOutofArea ||
452
+        (disableType === 'before' &&
453
+          disableDateTimestamp &&
454
+          timestamp < disableDateTimestamp) ||
455
+        (disableType === 'after' &&
456
+          disableDateTimestamp &&
457
+          timestamp > disableDateTimestamp)
458
+      ) {
459
+        item.disable = true
460
+        if (item.choosed) {
461
+          item.choosed = false
462
+          selectedDay = selectedDay.filter(
463
+            d => getDate.toTimeStr(item) !== getDate.toTimeStr(d)
464
+          )
465
+        }
466
+      } else if (item.disable) {
467
+        item.disable = false
468
+      }
469
+    })
470
+    return {
471
+      dates,
472
+      selectedDay
473
+    }
474
+  }
475
+  __handleEnableDays(data = {}, selectedDay = []) {
476
+    const { days, expectEnableDaysTimestamp } = data
477
+    const { enableAreaTimestamp = [] } = this.getData('calendar')
478
+    const dates = [...days]
479
+    dates.forEach(item => {
480
+      const timestamp = getDate
481
+        .newDate(item.year, item.month, item.day)
482
+        .getTime()
483
+      let setDisable = false
484
+      if (enableAreaTimestamp.length) {
485
+        if (
486
+          (+enableAreaTimestamp[0] > +timestamp ||
487
+            +timestamp > +enableAreaTimestamp[1]) &&
488
+          !expectEnableDaysTimestamp.includes(+timestamp)
489
+        ) {
490
+          setDisable = true
491
+        }
492
+      } else if (!expectEnableDaysTimestamp.includes(+timestamp)) {
493
+        setDisable = true
494
+      }
495
+      if (setDisable) {
496
+        item.disable = true
497
+        if (item.choosed) {
498
+          item.choosed = false
499
+          selectedDay = selectedDay.filter(
500
+            d => getDate.toTimeStr(item) !== getDate.toTimeStr(d)
501
+          )
502
+        }
503
+      } else {
504
+        item.disable = false
505
+      }
506
+    })
507
+    return {
508
+      dates,
509
+      selectedDay
510
+    }
511
+  }
512
+  __handleSelectedDays(days = [], newSelectedDay = [], selected) {
513
+    const { selectedDay, showLabelAlways } = this.getData('calendar')
514
+    if (selectedDay && selectedDay.length) {
515
+      newSelectedDay = uniqueArrayByDate(selectedDay.concat(selected))
516
+    } else {
517
+      newSelectedDay = selected
518
+    }
519
+    const { year: curYear, month: curMonth } = days[0]
520
+    const currentSelectedDays = []
521
+    newSelectedDay.forEach(item => {
522
+      if (+item.year === +curYear && +item.month === +curMonth) {
523
+        currentSelectedDays.push(getDate.toTimeStr(item))
524
+      }
525
+    })
526
+    ;[...days].map(item => {
527
+      if (currentSelectedDays.includes(getDate.toTimeStr(item))) {
528
+        item.choosed = true
529
+        if (showLabelAlways && item.showTodoLabel) {
530
+          item.showTodoLabel = true
531
+        } else {
532
+          item.showTodoLabel = false
533
+        }
534
+      }
535
+    })
536
+    return {
537
+      dates: days,
538
+      selectedDates: newSelectedDay
539
+    }
540
+  }
541
+}
542
+
543
+export default component => new Day(component)

+ 375 - 0
components/local/calendar/func/render.js

@@ -0,0 +1,375 @@
1
+import Day from './day'
2
+import Todo from './todo'
3
+import WxData from './wxData'
4
+import convertSolarLunar from './convertSolarLunar'
5
+import {
6
+  Logger,
7
+  GetDate,
8
+  delRepeatedEnableDay,
9
+  getDateTimeStamp,
10
+  converEnableDaysToTimestamp
11
+} from './utils'
12
+
13
+const getDate = new GetDate()
14
+const logger = new Logger()
15
+
16
+class Calendar extends WxData {
17
+  constructor(component) {
18
+    super(component)
19
+    this.Component = component
20
+  }
21
+  getCalendarConfig() {
22
+    return this.Component.config
23
+  }
24
+  /**
25
+   * 渲染日历
26
+   * @param {number} curYear 年份
27
+   * @param {number} curMonth  月份
28
+   * @param {number} curDate  日期
29
+   * @param {boolean} disableSelect 是否禁用选中
30
+   */
31
+  renderCalendar(curYear, curMonth, curDate, disableSelect) {
32
+    return new Promise(resolve => {
33
+      const config = this.getCalendarConfig()
34
+      this.calculateEmptyGrids(curYear, curMonth)
35
+      this.calculateDays(curYear, curMonth, curDate, disableSelect).then(() => {
36
+        const { todoLabels, specialStyleDates, enableDays, selectedDay } =
37
+          this.getData('calendar') || {}
38
+        if (
39
+          todoLabels &&
40
+          todoLabels.find(
41
+            item => +item.month === +curMonth && +item.year === +curYear
42
+          )
43
+        ) {
44
+          Todo(this.Component).setTodoLabels()
45
+        }
46
+        if (
47
+          specialStyleDates &&
48
+          specialStyleDates.length &&
49
+          specialStyleDates.find(
50
+            item => +item.month === +curMonth && +item.year === +curYear
51
+          )
52
+        ) {
53
+          Day(this.Component).setDateStyle(specialStyleDates)
54
+        }
55
+
56
+        if (
57
+          enableDays &&
58
+          enableDays.length &&
59
+          enableDays.find(item => {
60
+            let ymd = item.split('-')
61
+            return +ymd[1] === +curMonth && +ymd[0] === +curYear
62
+          })
63
+        ) {
64
+          Day(this.Component).enableDays(enableDays)
65
+        }
66
+
67
+        if (
68
+          selectedDay &&
69
+          selectedDay.length &&
70
+          selectedDay.find(
71
+            item => +item.month === +curMonth && +item.year === +curYear
72
+          ) &&
73
+          config.mulit
74
+        ) {
75
+          Day(this.Component).setSelectedDays(selectedDay)
76
+        }
77
+
78
+        if (!this.Component.firstRender) {
79
+          resolve({
80
+            firstRender: true
81
+          })
82
+        } else {
83
+          resolve({
84
+            firstRender: false
85
+          })
86
+        }
87
+      })
88
+    })
89
+  }
90
+  /**
91
+   * 计算当前月份前后两月应占的格子
92
+   * @param {number} year 年份
93
+   * @param {number} month 月份
94
+   */
95
+  calculateEmptyGrids(year, month) {
96
+    this.calculatePrevMonthGrids(year, month)
97
+    this.calculateNextMonthGrids(year, month)
98
+  }
99
+  /**
100
+   * 计算上月应占的格子
101
+   * @param {number} year 年份
102
+   * @param {number} month 月份
103
+   */
104
+  calculatePrevMonthGrids(year, month) {
105
+    let empytGrids = []
106
+    const prevMonthDays = getDate.thisMonthDays(year, month - 1)
107
+    let firstDayOfWeek = getDate.firstDayOfWeek(year, month)
108
+    const config = this.getCalendarConfig() || {}
109
+    if (config.firstDayOfWeek === 'Mon') {
110
+      if (firstDayOfWeek === 0) {
111
+        firstDayOfWeek = 6
112
+      } else {
113
+        firstDayOfWeek -= 1
114
+      }
115
+    }
116
+    if (firstDayOfWeek > 0) {
117
+      const len = prevMonthDays - firstDayOfWeek
118
+      const { onlyShowCurrentMonth } = config
119
+      const { showLunar } = this.getCalendarConfig()
120
+      for (let i = prevMonthDays; i > len; i--) {
121
+        if (onlyShowCurrentMonth) {
122
+          empytGrids.push('')
123
+        } else {
124
+          empytGrids.push({
125
+            day: i,
126
+            lunar: showLunar
127
+              ? convertSolarLunar.solar2lunar(year, month - 1, i)
128
+              : null
129
+          })
130
+        }
131
+      }
132
+      this.setData({
133
+        'calendar.empytGrids': empytGrids.reverse()
134
+      })
135
+    } else {
136
+      this.setData({
137
+        'calendar.empytGrids': null
138
+      })
139
+    }
140
+  }
141
+  /**
142
+   * 计算下一月日期是否需要多展示的日期
143
+   * 某些月份日期为5排,某些月份6排,统一为6排
144
+   * @param {number} year
145
+   * @param {number} month
146
+   * @param {object} config
147
+   */
148
+  calculateExtraEmptyDate(year, month, config) {
149
+    let extDate = 0
150
+    if (+month === 2) {
151
+      extDate += 7
152
+      let firstDayofMonth = getDate.dayOfWeek(year, month, 1)
153
+      if (config.firstDayOfWeek === 'Mon') {
154
+        if (+firstDayofMonth === 1) extDate += 7
155
+      } else {
156
+        if (+firstDayofMonth === 0) extDate += 7
157
+      }
158
+    } else {
159
+      let firstDayofMonth = getDate.dayOfWeek(year, month, 1)
160
+      if (config.firstDayOfWeek === 'Mon') {
161
+        if (firstDayofMonth !== 0 && firstDayofMonth < 6) {
162
+          extDate += 7
163
+        }
164
+      } else {
165
+        if (firstDayofMonth <= 5) {
166
+          extDate += 7
167
+        }
168
+      }
169
+    }
170
+    return extDate
171
+  }
172
+  /**
173
+   * 计算下月应占的格子
174
+   * @param {number} year 年份
175
+   * @param {number} month  月份
176
+   */
177
+  calculateNextMonthGrids(year, month) {
178
+    let lastEmptyGrids = []
179
+    const thisMonthDays = getDate.thisMonthDays(year, month)
180
+    let lastDayWeek = getDate.dayOfWeek(year, month, thisMonthDays)
181
+    const config = this.getCalendarConfig() || {}
182
+    if (config.firstDayOfWeek === 'Mon') {
183
+      if (lastDayWeek === 0) {
184
+        lastDayWeek = 6
185
+      } else {
186
+        lastDayWeek -= 1
187
+      }
188
+    }
189
+    let len = 7 - (lastDayWeek + 1)
190
+    const { onlyShowCurrentMonth, showLunar } = config
191
+    if (!onlyShowCurrentMonth) {
192
+      len = len + this.calculateExtraEmptyDate(year, month, config)
193
+    }
194
+    for (let i = 1; i <= len; i++) {
195
+      if (onlyShowCurrentMonth) {
196
+        lastEmptyGrids.push('')
197
+      } else {
198
+        lastEmptyGrids.push({
199
+          day: i,
200
+          lunar: showLunar
201
+            ? convertSolarLunar.solar2lunar(year, month + 1, i)
202
+            : null
203
+        })
204
+      }
205
+    }
206
+    this.setData({
207
+      'calendar.lastEmptyGrids': lastEmptyGrids
208
+    })
209
+  }
210
+  /**
211
+   * 日历初始化将默认值写入 selectDay
212
+   * @param {number} year
213
+   * @param {number} month
214
+   * @param {number} curDate
215
+   */
216
+  setSelectedDay(year, month, curDate) {
217
+    let selectedDay = []
218
+    const config = this.getCalendarConfig()
219
+    if (config.noDefault) {
220
+      selectedDay = []
221
+      config.noDefault = false
222
+    } else {
223
+      const data = this.getData('calendar') || {}
224
+      const { showLunar } = this.getCalendarConfig()
225
+      selectedDay = curDate
226
+        ? [
227
+            {
228
+              year,
229
+              month,
230
+              day: curDate,
231
+              choosed: true,
232
+              week: getDate.dayOfWeek(year, month, curDate),
233
+              lunar: showLunar
234
+                ? convertSolarLunar.solar2lunar(year, month, curDate)
235
+                : null
236
+            }
237
+          ]
238
+        : data.selectedDay
239
+    }
240
+    return selectedDay
241
+  }
242
+  __getDisableDateTimestamp() {
243
+    let disableDateTimestamp
244
+    const { date, type } = this.getCalendarConfig().disableMode || {}
245
+    if (date) {
246
+      const t = date.split('-')
247
+      if (t.length < 3) {
248
+        logger.warn('配置 disableMode.date 格式错误')
249
+        return {}
250
+      }
251
+      disableDateTimestamp = getDateTimeStamp({
252
+        year: +t[0],
253
+        month: +t[1],
254
+        day: +t[2]
255
+      })
256
+    }
257
+    return {
258
+      disableDateTimestamp,
259
+      disableType: type
260
+    }
261
+  }
262
+  resetDates() {
263
+    this.setData({
264
+      'calendar.days': []
265
+    })
266
+  }
267
+  /**
268
+   * 设置日历面板数据
269
+   * @param {number} year 年份
270
+   * @param {number} month  月份
271
+   * @param {number} curDate  日期
272
+   * @param {boolean} disableSelect 是否禁用选中
273
+   */
274
+  calculateDays(year, month, curDate, disableSelect) {
275
+    return new Promise(resolve => {
276
+      // 避免切换日期时样式残影
277
+      this.resetDates()
278
+      let days = []
279
+      const {
280
+        disableDays = [],
281
+        chooseAreaTimestamp = [],
282
+        selectedDay: selectedDates = []
283
+      } = this.getData('calendar')
284
+      days = Day(this.Component).buildDate(year, month)
285
+      let selectedDay = selectedDates
286
+      if (!disableSelect) {
287
+        selectedDay = this.setSelectedDay(year, month, curDate)
288
+      }
289
+      const selectedDayStr = selectedDay.map(d => getDate.toTimeStr(d))
290
+      const disableDaysStr = disableDays.map(d => getDate.toTimeStr(d))
291
+      const [areaStart, areaEnd] = chooseAreaTimestamp
292
+      days.forEach(item => {
293
+        const cur = getDate.toTimeStr(item)
294
+        const timestamp = getDateTimeStamp(item)
295
+        if (selectedDayStr.includes(cur) && !disableSelect) {
296
+          item.choosed = true
297
+          if (timestamp > areaEnd || timestamp < areaStart) {
298
+            const idx = selectedDay.findIndex(
299
+              selectedDate =>
300
+                getDate.toTimeStr(selectedDate) === getDate.toTimeStr(item)
301
+            )
302
+            selectedDay.splice(idx, 1)
303
+          }
304
+        } else if (
305
+          areaStart &&
306
+          areaEnd &&
307
+          timestamp >= areaStart &&
308
+          timestamp <= areaEnd &&
309
+          !disableSelect
310
+        ) {
311
+          item.choosed = true
312
+          selectedDay.push(item)
313
+        }
314
+        if (disableDaysStr.includes(cur)) item.disable = true
315
+
316
+        const {
317
+          disableDateTimestamp,
318
+          disableType
319
+        } = this.__getDisableDateTimestamp()
320
+        let disabelByConfig = false
321
+        if (disableDateTimestamp) {
322
+          if (
323
+            (disableType === 'before' && timestamp < disableDateTimestamp) ||
324
+            (disableType === 'after' && timestamp > disableDateTimestamp)
325
+          ) {
326
+            disabelByConfig = true
327
+          }
328
+        }
329
+        const isDisable = disabelByConfig || this.__isDisable(timestamp)
330
+        if (isDisable) {
331
+          item.disable = true
332
+          item.choosed = false
333
+        }
334
+      })
335
+      this.setData(
336
+        {
337
+          'calendar.days': days,
338
+          'calendar.selectedDay': [...selectedDay] || []
339
+        },
340
+        () => {
341
+          resolve()
342
+        }
343
+      )
344
+    })
345
+  }
346
+  __isDisable(timestamp) {
347
+    const {
348
+      enableArea = [],
349
+      enableDays = [],
350
+      enableAreaTimestamp = []
351
+    } = this.getData('calendar')
352
+    let setDisable = false
353
+    let expectEnableDaysTimestamp = converEnableDaysToTimestamp(enableDays)
354
+    if (enableArea.length) {
355
+      expectEnableDaysTimestamp = delRepeatedEnableDay(enableDays, enableArea)
356
+    }
357
+    if (enableAreaTimestamp.length) {
358
+      if (
359
+        (+enableAreaTimestamp[0] > +timestamp ||
360
+          +timestamp > +enableAreaTimestamp[1]) &&
361
+        !expectEnableDaysTimestamp.includes(+timestamp)
362
+      ) {
363
+        setDisable = true
364
+      }
365
+    } else if (
366
+      expectEnableDaysTimestamp.length &&
367
+      !expectEnableDaysTimestamp.includes(+timestamp)
368
+    ) {
369
+      setDisable = true
370
+    }
371
+    return setDisable
372
+  }
373
+}
374
+
375
+export default component => new Calendar(component)

+ 182 - 0
components/local/calendar/func/todo.js

@@ -0,0 +1,182 @@
1
+import WxData from './wxData'
2
+import { Logger, uniqueArrayByDate, GetDate } from './utils'
3
+
4
+const logger = new Logger()
5
+const getDate = new GetDate()
6
+
7
+class Todo extends WxData {
8
+  constructor(component) {
9
+    super(component)
10
+    this.Component = component
11
+  }
12
+  /**
13
+   * 设置待办事项标志
14
+   * @param {object} options 待办事项配置
15
+   */
16
+  setTodoLabels(options) {
17
+    if (options) this.Component.todoConfig = options
18
+    const calendar = this.getData('calendar')
19
+    if (!calendar || !calendar.days) {
20
+      return logger.warn('请等待日历初始化完成后再调用该方法')
21
+    }
22
+    const dates = [...calendar.days]
23
+    const { curYear, curMonth } = calendar
24
+    const {
25
+      circle,
26
+      dotColor = '',
27
+      pos = 'bottom',
28
+      showLabelAlways,
29
+      days: todoDays = []
30
+    } = options || this.Component.todoConfig || {}
31
+    const { todoLabels = [] } = calendar
32
+    const currentMonthTodoLabels = this.getTodoLabels({
33
+      year: curYear,
34
+      month: curMonth
35
+    })
36
+    let newTodoLabels = todoDays.filter(
37
+      item => +item.year === +curYear && +item.month === +curMonth
38
+    )
39
+    if (this.Component.weekMode) {
40
+      newTodoLabels = todoDays
41
+    }
42
+    const allTodos = currentMonthTodoLabels.concat(newTodoLabels)
43
+    for (let todo of allTodos) {
44
+      let target
45
+      if (this.Component.weekMode) {
46
+        target = dates.find(
47
+          date =>
48
+            +todo.year === +date.year &&
49
+            +todo.month === +date.month &&
50
+            +todo.day === +date.day
51
+        )
52
+      } else {
53
+        target = dates[todo.day - 1]
54
+      }
55
+      if (!target) continue
56
+      if (showLabelAlways) {
57
+        target.showTodoLabel = true
58
+      } else {
59
+        target.showTodoLabel = !target.choosed
60
+      }
61
+      if (target.showTodoLabel) {
62
+        target.todoText = todo.todoText
63
+      }
64
+      target.color = todo.color
65
+    }
66
+    const o = {
67
+      'calendar.days': dates,
68
+      'calendar.todoLabels': uniqueArrayByDate(todoLabels.concat(todoDays))
69
+    }
70
+    if (!circle) {
71
+      o['calendar.todoLabelPos'] = pos
72
+      o['calendar.todoLabelColor'] = dotColor
73
+    }
74
+    o['calendar.todoLabelCircle'] = circle || false
75
+    o['calendar.showLabelAlways'] = showLabelAlways || false
76
+    this.setData(o)
77
+  }
78
+  /**
79
+   *  删除指定日期的待办事项
80
+   * @param {array} todos 需要删除待办事项的日期
81
+   */
82
+  deleteTodoLabels(todos) {
83
+    if (!(todos instanceof Array) || !todos.length) return
84
+    const todoLabels = this.filterTodos(todos)
85
+    const { days: dates, curYear, curMonth } = this.getData('calendar')
86
+    const currentMonthTodoLabels = todoLabels.filter(
87
+      item => curYear === +item.year && curMonth === +item.month
88
+    )
89
+    dates.forEach(item => {
90
+      item.showTodoLabel = false
91
+    })
92
+    currentMonthTodoLabels.forEach(item => {
93
+      dates[item.day - 1].showTodoLabel = !dates[item.day - 1].choosed
94
+    })
95
+    this.setData({
96
+      'calendar.days': dates,
97
+      'calendar.todoLabels': todoLabels
98
+    })
99
+  }
100
+  /**
101
+   * 清空所有待办事项
102
+   */
103
+  clearTodoLabels() {
104
+    const { days = [] } = this.getData('calendar')
105
+    const dates = [].concat(days)
106
+    dates.forEach(item => {
107
+      item.showTodoLabel = false
108
+    })
109
+    this.setData({
110
+      'calendar.days': dates,
111
+      'calendar.todoLabels': []
112
+    })
113
+  }
114
+  /**
115
+   * 获取所有待办事项
116
+   * @param {object} target 指定年月
117
+   * @param {number} [target.year] 年
118
+   * @param {number} [target.month] 月
119
+   */
120
+  getTodoLabels(target) {
121
+    const { todoLabels = [] } = this.getData('calendar')
122
+    if (target) {
123
+      const { year, month } = target
124
+      const _todoLabels = todoLabels.filter(
125
+        item => +item.year === +year && +item.month === +month
126
+      )
127
+      return _todoLabels
128
+    }
129
+    return todoLabels
130
+  }
131
+  /**
132
+   * 过滤将删除的待办事项
133
+   * @param {array} todos 需要删除待办事项
134
+   */
135
+  filterTodos(todos) {
136
+    const todoLabels = this.getData('calendar.todoLabels') || []
137
+    const deleteTodo = todos.map(item => getDate.toTimeStr(item))
138
+    return todoLabels.filter(
139
+      item => !deleteTodo.includes(getDate.toTimeStr(item))
140
+    )
141
+  }
142
+  /**
143
+   * 单选时显示待办事项
144
+   * @param {array} todoDays
145
+   * @param {array} days
146
+   * @param {array} selectedDays
147
+   */
148
+  showTodoLabels(todoDays, days, selectedDays) {
149
+    todoDays.forEach(item => {
150
+      if (this.Component.weekMode) {
151
+        days.forEach((_item, idx) => {
152
+          if (+_item.day === +item.day) {
153
+            const day = days[idx]
154
+            day.hasTodo = true
155
+            day.todoText = item.todoText
156
+            if (
157
+              selectedDays &&
158
+              selectedDays.length &&
159
+              +selectedDays[0].day === +item.day
160
+            ) {
161
+              day.showTodoLabel = true
162
+            }
163
+          }
164
+        })
165
+      } else {
166
+        const day = days[item.day - 1]
167
+        if (!day) return
168
+        day.hasTodo = true
169
+        day.todoText = item.todoText
170
+        if (
171
+          selectedDays &&
172
+          selectedDays.length &&
173
+          +selectedDays[0].day === +item.day
174
+        ) {
175
+          days[selectedDays[0].day - 1].showTodoLabel = true
176
+        }
177
+      }
178
+    })
179
+  }
180
+}
181
+
182
+export default component => new Todo(component)

+ 367 - 0
components/local/calendar/func/utils.js

@@ -0,0 +1,367 @@
1
+import convertSolarLunar from './convertSolarLunar'
2
+
3
+let systemInfo
4
+export function getSystemInfo() {
5
+  if (systemInfo) return systemInfo
6
+  systemInfo = wx.getSystemInfoSync()
7
+  return systemInfo
8
+}
9
+
10
+export function isComponent(target) {
11
+  return (
12
+    target &&
13
+    target.__wxExparserNodeId__ !== void 0 &&
14
+    typeof target.setData === 'function'
15
+  )
16
+}
17
+
18
+export class Logger {
19
+  info(msg) {
20
+    console.log(
21
+      '%cInfo: %c' + msg,
22
+      'color:#FF0080;font-weight:bold',
23
+      'color: #FF509B'
24
+    )
25
+  }
26
+  warn(msg) {
27
+    console.log(
28
+      '%cWarn: %c' + msg,
29
+      'color:#FF6600;font-weight:bold',
30
+      'color: #FF9933'
31
+    )
32
+  }
33
+  tips(msg) {
34
+    console.log(
35
+      '%cTips: %c' + msg,
36
+      'color:#00B200;font-weight:bold',
37
+      'color: #00CC33'
38
+    )
39
+  }
40
+}
41
+
42
+export class Slide {
43
+  /**
44
+   * 上滑
45
+   * @param {object} e 事件对象
46
+   * @returns {boolean} 布尔值
47
+   */
48
+  isUp(gesture = {}, touche = {}) {
49
+    const { startX, startY } = gesture
50
+    const deltaX = touche.clientX - startX
51
+    const deltaY = touche.clientY - startY
52
+    if (deltaY < -60 && deltaX < 20 && deltaX > -20) {
53
+      this.slideLock = false
54
+      return true
55
+    } else {
56
+      return false
57
+    }
58
+  }
59
+  /**
60
+   * 下滑
61
+   * @param {object} e 事件对象
62
+   * @returns {boolean} 布尔值
63
+   */
64
+  isDown(gesture = {}, touche = {}) {
65
+    const { startX, startY } = gesture
66
+    const deltaX = touche.clientX - startX
67
+    const deltaY = touche.clientY - startY
68
+    if (deltaY > 60 && deltaX < 20 && deltaX > -20) {
69
+      return true
70
+    } else {
71
+      return false
72
+    }
73
+  }
74
+  /**
75
+   * 左滑
76
+   * @param {object} e 事件对象
77
+   * @returns {boolean} 布尔值
78
+   */
79
+  isLeft(gesture = {}, touche = {}) {
80
+    const { startX, startY } = gesture
81
+    const deltaX = touche.clientX - startX
82
+    const deltaY = touche.clientY - startY
83
+    if (deltaX < -60 && deltaY < 20 && deltaY > -20) {
84
+      return true
85
+    } else {
86
+      return false
87
+    }
88
+  }
89
+  /**
90
+   * 右滑
91
+   * @param {object} e 事件对象
92
+   * @returns {boolean} 布尔值
93
+   */
94
+  isRight(gesture = {}, touche = {}) {
95
+    const { startX, startY } = gesture
96
+    const deltaX = touche.clientX - startX
97
+    const deltaY = touche.clientY - startY
98
+
99
+    if (deltaX > 60 && deltaY < 20 && deltaY > -20) {
100
+      return true
101
+    } else {
102
+      return false
103
+    }
104
+  }
105
+}
106
+
107
+export class GetDate {
108
+  /**
109
+   * new Date 区分平台
110
+   * @param {number} year
111
+   * @param {number} month
112
+   * @param {number} day
113
+   */
114
+  newDate(year, month, day) {
115
+    let cur = `${+year}-${+month}-${+day}`
116
+    if (isIos()) {
117
+      cur = `${+year}/${+month}/${+day}`
118
+    }
119
+    return new Date(cur)
120
+  }
121
+  /**
122
+   * 计算指定月份共多少天
123
+   * @param {number} year 年份
124
+   * @param {number} month  月份
125
+   */
126
+  thisMonthDays(year, month) {
127
+    return new Date(Date.UTC(year, month, 0)).getUTCDate()
128
+  }
129
+  /**
130
+   * 计算指定月份第一天星期几
131
+   * @param {number} year 年份
132
+   * @param {number} month  月份
133
+   */
134
+  firstDayOfWeek(year, month) {
135
+    return new Date(Date.UTC(year, month - 1, 1)).getUTCDay()
136
+  }
137
+  /**
138
+   * 计算指定日期星期几
139
+   * @param {number} year 年份
140
+   * @param {number} month  月份
141
+   * @param {number} date 日期
142
+   */
143
+  dayOfWeek(year, month, date) {
144
+    return new Date(Date.UTC(year, month - 1, date)).getUTCDay()
145
+  }
146
+  todayDate() {
147
+    const _date = new Date()
148
+    const year = _date.getFullYear()
149
+    const month = _date.getMonth() + 1
150
+    const date = _date.getDate()
151
+    return {
152
+      year,
153
+      month,
154
+      date
155
+    }
156
+  }
157
+  todayTimestamp() {
158
+    const { year, month, date } = this.todayDate()
159
+    const timestamp = this.newDate(year, month, date).getTime()
160
+    return timestamp
161
+  }
162
+  toTimeStr(dateInfo) {
163
+    if (dateInfo.day) {
164
+      dateInfo.date = dateInfo.day
165
+    }
166
+    return `${+dateInfo.year}-${+dateInfo.month}-${+dateInfo.date}`
167
+  }
168
+  sortDates(dates, sortType) {
169
+    return dates.sort(function(a, b) {
170
+      const at = getDateTimeStamp(a)
171
+      const bt = getDateTimeStamp(b)
172
+      if (at < bt && sortType !== 'desc') {
173
+        return -1
174
+      } else {
175
+        return 1
176
+      }
177
+    })
178
+  }
179
+  prevMonth(dataInfo) {
180
+    const prevMonthInfo =
181
+      +dataInfo.month > 1
182
+        ? {
183
+            year: dataInfo.year,
184
+            month: dataInfo.month - 1
185
+          }
186
+        : {
187
+            year: dataInfo.year - 1,
188
+            month: 12
189
+          }
190
+    return prevMonthInfo
191
+  }
192
+  nextMonth(dataInfo) {
193
+    const nextMonthInfo =
194
+      +dataInfo.month < 12
195
+        ? {
196
+            year: dataInfo.year,
197
+            month: dataInfo.month + 1
198
+          }
199
+        : {
200
+            year: dataInfo.year + 1,
201
+            month: 1
202
+          }
203
+    return nextMonthInfo
204
+  }
205
+  convertLunar(dates = []) {
206
+    const datesWithLunar = dates.map(date => {
207
+      if (date) {
208
+        date.lunar = convertSolarLunar.solar2lunar(
209
+          +date.year,
210
+          +date.month,
211
+          +date.day
212
+        )
213
+      }
214
+      return date
215
+    })
216
+    return datesWithLunar
217
+  }
218
+}
219
+
220
+export function isIos() {
221
+  const sys = getSystemInfo()
222
+  return /iphone|ios/i.test(sys.platform)
223
+}
224
+
225
+/**
226
+ * 浅比较对象是否相等
227
+ * @param {Object} origin 对比源
228
+ * @param {Object} target 对比目标
229
+ * @return {Boolean} true 为相等,false 为不等
230
+ */
231
+export function shallowEqual(origin, target) {
232
+  if (origin === target) {
233
+    return true
234
+  } else if (
235
+    typeof origin === 'object' &&
236
+    origin != null &&
237
+    typeof target === 'object' &&
238
+    target != null
239
+  ) {
240
+    if (Object.keys(origin).length !== Object.keys(target).length) return false
241
+    for (var prop in origin) {
242
+      if (target.hasOwnProperty(prop)) {
243
+        if (!shallowEqual(origin[prop], target[prop])) return false
244
+      } else return false
245
+    }
246
+    return true
247
+  } else return false
248
+}
249
+
250
+/**
251
+ * 获取当前页面实例
252
+ */
253
+export function getCurrentPage() {
254
+  const pages = getCurrentPages()
255
+  const last = pages.length - 1
256
+  return pages[last]
257
+}
258
+
259
+export function getComponent(componentId) {
260
+  const logger = new Logger()
261
+  let page = getCurrentPage() || {}
262
+  if (page.selectComponent && typeof page.selectComponent === 'function') {
263
+    if (componentId) {
264
+      return page.selectComponent(componentId)
265
+    } else {
266
+      logger.warn('请传入组件ID')
267
+    }
268
+  } else {
269
+    logger.warn('该基础库暂不支持多个小程序日历组件')
270
+  }
271
+}
272
+
273
+/**
274
+ * 日期数组根据日期去重
275
+ * @param {array} array 数组
276
+ */
277
+export function uniqueArrayByDate(array = []) {
278
+  let uniqueObject = {}
279
+  let uniqueArray = []
280
+  array.forEach(item => {
281
+    uniqueObject[`${item.year}-${item.month}-${item.day}`] = item
282
+  })
283
+  for (let i in uniqueObject) {
284
+    uniqueArray.push(uniqueObject[i])
285
+  }
286
+  return uniqueArray
287
+}
288
+
289
+/**
290
+ * 指定可选日期及可选日期数组去重
291
+ * @param {array} enableDays 特定可选日期数组
292
+ * @param {array} enableArea 可选日期区域数组
293
+ */
294
+export function delRepeatedEnableDay(enableDays = [], enableArea = []) {
295
+  let _startTimestamp
296
+  let _endTimestamp
297
+  if (enableArea.length === 2) {
298
+    const { startTimestamp, endTimestamp } = convertEnableAreaToTimestamp(
299
+      enableArea
300
+    )
301
+    _startTimestamp = startTimestamp
302
+    _endTimestamp = endTimestamp
303
+  }
304
+  const enableDaysTimestamp = converEnableDaysToTimestamp(enableDays)
305
+  const tmp = enableDaysTimestamp.filter(
306
+    item => item < _startTimestamp || item > _endTimestamp
307
+  )
308
+  return tmp
309
+}
310
+
311
+/**
312
+ *  指定日期区域转时间戳
313
+ * @param {array} timearea 时间区域
314
+ */
315
+export function convertEnableAreaToTimestamp(timearea = []) {
316
+  const getDate = new GetDate()
317
+  const start = timearea[0].split('-')
318
+  const end = timearea[1].split('-')
319
+  const logger = new Logger()
320
+  if (start.length !== 3 || end.length !== 3) {
321
+    logger.warn('enableArea() 参数格式为: ["2018-2-1", "2018-3-1"]')
322
+    return {}
323
+  }
324
+  const startTimestamp = getDate.newDate(start[0], start[1], start[2]).getTime()
325
+  const endTimestamp = getDate.newDate(end[0], end[1], end[2]).getTime()
326
+  return {
327
+    start,
328
+    end,
329
+    startTimestamp,
330
+    endTimestamp
331
+  }
332
+}
333
+
334
+/**
335
+ * 计算指定日期时间戳
336
+ * @param {object} dateInfo
337
+ */
338
+export function getDateTimeStamp(dateInfo) {
339
+  if (Object.prototype.toString.call(dateInfo) !== '[object Object]') return
340
+  const getDate = new GetDate()
341
+  return getDate.newDate(dateInfo.year, dateInfo.month, dateInfo.day).getTime()
342
+}
343
+
344
+/**
345
+ *  指定特定日期数组转时间戳
346
+ * @param {array} enableDays 指定时间数组
347
+ */
348
+export function converEnableDaysToTimestamp(enableDays = []) {
349
+  const logger = new Logger()
350
+  const getDate = new GetDate()
351
+  const enableDaysTimestamp = []
352
+  enableDays.forEach(item => {
353
+    if (typeof item !== 'string')
354
+      return logger.warn('enableDays()入参日期格式错误')
355
+    const tmp = item.split('-')
356
+    if (tmp.length !== 3) return logger.warn('enableDays()入参日期格式错误')
357
+    const timestamp = getDate.newDate(tmp[0], tmp[1], tmp[2]).getTime()
358
+    enableDaysTimestamp.push(timestamp)
359
+  })
360
+  return enableDaysTimestamp
361
+}
362
+
363
+// 同一页面多个日历组件按先后顺序渲染
364
+export const initialTasks = {
365
+  flag: 'finished', // process 处理中,finished 处理完成
366
+  tasks: []
367
+}

+ 601 - 0
components/local/calendar/func/week.js

@@ -0,0 +1,601 @@
1
+import Day from './day'
2
+import WxData from './wxData'
3
+import Render from './render'
4
+import CalendarConfig from './config'
5
+import convertSolarLunar from './convertSolarLunar'
6
+import { GetDate, Logger, getDateTimeStamp } from './utils'
7
+
8
+const getDate = new GetDate()
9
+const logger = new Logger()
10
+
11
+class WeekMode extends WxData {
12
+  constructor(component) {
13
+    super(component)
14
+    this.Component = component
15
+    this.getCalendarConfig = CalendarConfig(this.Component).getCalendarConfig
16
+  }
17
+  /**
18
+   * 周、月视图切换
19
+   * @param {string} view  视图 [week, month]
20
+   * @param {object} date  {year: 2017, month: 11, day: 1}
21
+   */
22
+  switchWeek(view, date) {
23
+    return new Promise((resolve, reject) => {
24
+      const config = CalendarConfig(this.Component).getCalendarConfig()
25
+      if (config.multi) return logger.warn('多选模式不能切换周月视图')
26
+      const { selectedDay = [], curYear, curMonth } = this.getData('calendar')
27
+      let currentDate = []
28
+      let disableSelected = false
29
+      if (!selectedDay.length) {
30
+        currentDate = getDate.todayDate()
31
+        currentDate.day = currentDate.date
32
+        disableSelected = true
33
+        // return this.__tipsWhenCanNotSwtich();
34
+      } else {
35
+        currentDate = selectedDay[0]
36
+      }
37
+      let selectedDate = date || currentDate
38
+      const { year, month } = selectedDate
39
+      const notInCurrentMonth = curYear !== year || curMonth !== month
40
+      if (view === 'week') {
41
+        if (this.Component.weekMode) return
42
+        if ((selectedDay.length && notInCurrentMonth) || !selectedDay.length) {
43
+          // return this.__tipsWhenCanNotSwtich();
44
+          disableSelected = true
45
+          selectedDate = {
46
+            year: curYear,
47
+            month: curMonth,
48
+            day: selectedDate.day
49
+          }
50
+        }
51
+        this.Component.weekMode = true
52
+        this.setData({
53
+          'calendarConfig.weekMode': true
54
+        })
55
+        this.jump(selectedDate, disableSelected)
56
+          .then(resolve)
57
+          .catch(reject)
58
+      } else {
59
+        this.Component.weekMode = false
60
+        this.setData({
61
+          'calendarConfig.weekMode': false
62
+        })
63
+        const disableSelected =
64
+          (selectedDay.length && notInCurrentMonth) || !selectedDay.length
65
+        Render(this.Component)
66
+          .renderCalendar(curYear, curMonth, selectedDate.day, disableSelected)
67
+          .then(resolve)
68
+          .catch(reject)
69
+      }
70
+    })
71
+  }
72
+  /**
73
+   * 更新当前年月
74
+   */
75
+  updateCurrYearAndMonth(type) {
76
+    let { days, curYear, curMonth } = this.getData('calendar')
77
+    const { month: firstMonth } = days[0]
78
+    const { month: lastMonth } = days[days.length - 1]
79
+    const lastDayOfThisMonth = getDate.thisMonthDays(curYear, curMonth)
80
+    const lastDayOfThisWeek = days[days.length - 1]
81
+    const firstDayOfThisWeek = days[0]
82
+    if (
83
+      (lastDayOfThisWeek.day + 7 > lastDayOfThisMonth ||
84
+        (curMonth === firstMonth && firstMonth !== lastMonth)) &&
85
+      type === 'next'
86
+    ) {
87
+      curMonth = curMonth + 1
88
+      if (curMonth > 12) {
89
+        curYear = curYear + 1
90
+        curMonth = 1
91
+      }
92
+    } else if (
93
+      (+firstDayOfThisWeek.day <= 7 ||
94
+        (curMonth === lastMonth && firstMonth !== lastMonth)) &&
95
+      type === 'prev'
96
+    ) {
97
+      curMonth = curMonth - 1
98
+      if (curMonth <= 0) {
99
+        curYear = curYear - 1
100
+        curMonth = 12
101
+      }
102
+    }
103
+    return {
104
+      Uyear: curYear,
105
+      Umonth: curMonth
106
+    }
107
+  }
108
+  /**
109
+   * 计算周视图下当前这一周和当月的最后一天
110
+   */
111
+  calculateLastDay() {
112
+    const { days = [], curYear, curMonth } = this.getData('calendar')
113
+    const lastDayInThisWeek = days[days.length - 1].day
114
+    const lastDayInThisMonth = getDate.thisMonthDays(curYear, curMonth)
115
+    return { lastDayInThisWeek, lastDayInThisMonth }
116
+  }
117
+  /**
118
+   * 计算周视图下当前这一周第一天
119
+   */
120
+  calculateFirstDay() {
121
+    const { days } = this.getData('calendar')
122
+    const firstDayInThisWeek = days[0].day
123
+    return { firstDayInThisWeek }
124
+  }
125
+  /**
126
+   * 当月第一周所有日期范围
127
+   * @param {number} year
128
+   * @param {number} month
129
+   * @param {boolean} firstDayOfWeekIsMon 每周是否配置为以周一开始
130
+   */
131
+  firstWeekInMonth(year, month, firstDayOfWeekIsMon) {
132
+    let firstDay = getDate.dayOfWeek(year, month, 1)
133
+    if (firstDayOfWeekIsMon && firstDay === 0) {
134
+      firstDay = 7
135
+    }
136
+    const [, end] = [0, 7 - firstDay]
137
+    let days = this.getData('calendar.days') || []
138
+    if (this.Component.weekMode) {
139
+      days = Day(this.Component).buildDate(year, month)
140
+    }
141
+    const daysCut = days.slice(0, firstDayOfWeekIsMon ? end + 1 : end)
142
+    return daysCut
143
+  }
144
+  /**
145
+   * 当月最后一周所有日期范围
146
+   * @param {number} year
147
+   * @param {number} month
148
+   * @param {boolean} firstDayOfWeekIsMon 每周是否配置为以周一开始
149
+   */
150
+  lastWeekInMonth(year, month, firstDayOfWeekIsMon) {
151
+    const lastDay = getDate.thisMonthDays(year, month)
152
+    const lastDayWeek = getDate.dayOfWeek(year, month, lastDay)
153
+    const [start, end] = [lastDay - lastDayWeek, lastDay]
154
+    let days = this.getData('calendar.days') || []
155
+    if (this.Component.weekMode) {
156
+      days = Day(this.Component).buildDate(year, month)
157
+    }
158
+    const daysCut = days.slice(firstDayOfWeekIsMon ? start : start - 1, end)
159
+    return daysCut
160
+  }
161
+  __getDisableDateTimestamp(config) {
162
+    const { date, type } = config.disableMode || {}
163
+    let disableDateTimestamp
164
+    if (date) {
165
+      const t = date.split('-')
166
+      if (t.length < 3) {
167
+        logger.warn('配置 disableMode.date 格式错误')
168
+        return {}
169
+      }
170
+      disableDateTimestamp = getDateTimeStamp({
171
+        year: +t[0],
172
+        month: +t[1],
173
+        day: +t[2]
174
+      })
175
+    }
176
+    return {
177
+      disableDateTimestamp,
178
+      disableType: type
179
+    }
180
+  }
181
+  /**
182
+   * 渲染日期之前初始化已选日期
183
+   * @param {array} dates 当前日期数组
184
+   */
185
+  initSelectedDay(dates) {
186
+    let datesCopy = [...dates]
187
+    const { selectedDay = [] } = this.getData('calendar')
188
+    const selectedDayStr = selectedDay.map(
189
+      item => `${+item.year}-${+item.month}-${+item.day}`
190
+    )
191
+    const config = this.getCalendarConfig()
192
+    const {
193
+      disableDateTimestamp,
194
+      disableType
195
+    } = this.__getDisableDateTimestamp(config)
196
+    datesCopy = datesCopy.map(item => {
197
+      if (!item) return {}
198
+      const dateTimestamp = getDateTimeStamp(item)
199
+      let date = { ...item }
200
+      if (
201
+        selectedDayStr.includes(`${+date.year}-${+date.month}-${+date.day}`)
202
+      ) {
203
+        date.choosed = true
204
+      } else {
205
+        date.choosed = false
206
+      }
207
+      if (
208
+        (disableType === 'after' && dateTimestamp > disableDateTimestamp) ||
209
+        (disableType === 'before' && dateTimestamp < disableDateTimestamp)
210
+      ) {
211
+        date.disable = true
212
+      }
213
+      date = this.__setTodoWhenJump(date, config)
214
+      if (config.showLunar) {
215
+        date = this.__setSolarLunar(date)
216
+      }
217
+      if (config.highlightToday) {
218
+        date = this.__highlightToday(date)
219
+      }
220
+      return date
221
+    })
222
+    return datesCopy
223
+  }
224
+  /**
225
+   * 周视图下设置可选日期范围
226
+   * @param {object} days 当前展示的日期
227
+   */
228
+  setEnableAreaOnWeekMode(dates = []) {
229
+    let { enableAreaTimestamp = [], enableDaysTimestamp = [] } = this.getData(
230
+      'calendar'
231
+    )
232
+    dates.forEach(item => {
233
+      const timestamp = getDate
234
+        .newDate(item.year, item.month, item.day)
235
+        .getTime()
236
+
237
+      let setDisable = false
238
+      if (enableAreaTimestamp.length) {
239
+        if (
240
+          (+enableAreaTimestamp[0] > +timestamp ||
241
+            +timestamp > +enableAreaTimestamp[1]) &&
242
+          !enableDaysTimestamp.includes(+timestamp)
243
+        ) {
244
+          setDisable = true
245
+        }
246
+      } else if (
247
+        enableDaysTimestamp.length &&
248
+        !enableDaysTimestamp.includes(+timestamp)
249
+      ) {
250
+        setDisable = true
251
+      }
252
+      if (setDisable) {
253
+        item.disable = true
254
+        item.choosed = false
255
+      }
256
+      const config = CalendarConfig(this.Component).getCalendarConfig()
257
+      const {
258
+        disableDateTimestamp,
259
+        disableType
260
+      } = this.__getDisableDateTimestamp(config)
261
+      if (
262
+        (disableType === 'before' && timestamp < disableDateTimestamp) ||
263
+        (disableType === 'after' && timestamp > disableDateTimestamp)
264
+      ) {
265
+        item.disable = true
266
+      }
267
+    })
268
+  }
269
+  updateYMWhenSwipeCalendarHasSelected(dates) {
270
+    const hasSelectedDate = dates.filter(date => date.choosed)
271
+    if (hasSelectedDate && hasSelectedDate.length) {
272
+      const { year, month } = hasSelectedDate[0]
273
+      return {
274
+        year,
275
+        month
276
+      }
277
+    }
278
+    return {}
279
+  }
280
+  /**
281
+   * 计算下一周的日期
282
+   */
283
+  calculateNextWeekDays() {
284
+    let { lastDayInThisWeek, lastDayInThisMonth } = this.calculateLastDay()
285
+    let { curYear, curMonth } = this.getData('calendar')
286
+    let days = []
287
+    if (lastDayInThisMonth - lastDayInThisWeek >= 7) {
288
+      const { Uyear, Umonth } = this.updateCurrYearAndMonth('next')
289
+      curYear = Uyear
290
+      curMonth = Umonth
291
+      for (let i = lastDayInThisWeek + 1; i <= lastDayInThisWeek + 7; i++) {
292
+        days.push({
293
+          year: curYear,
294
+          month: curMonth,
295
+          day: i,
296
+          week: getDate.dayOfWeek(curYear, curMonth, i)
297
+        })
298
+      }
299
+    } else {
300
+      for (let i = lastDayInThisWeek + 1; i <= lastDayInThisMonth; i++) {
301
+        days.push({
302
+          year: curYear,
303
+          month: curMonth,
304
+          day: i,
305
+          week: getDate.dayOfWeek(curYear, curMonth, i)
306
+        })
307
+      }
308
+      const { Uyear, Umonth } = this.updateCurrYearAndMonth('next')
309
+      curYear = Uyear
310
+      curMonth = Umonth
311
+      for (let i = 1; i <= 7 - (lastDayInThisMonth - lastDayInThisWeek); i++) {
312
+        days.push({
313
+          year: curYear,
314
+          month: curMonth,
315
+          day: i,
316
+          week: getDate.dayOfWeek(curYear, curMonth, i)
317
+        })
318
+      }
319
+    }
320
+    days = this.initSelectedDay(days)
321
+    const {
322
+      year: updateYear,
323
+      month: updateMonth
324
+    } = this.updateYMWhenSwipeCalendarHasSelected(days)
325
+    if (updateYear && updateMonth) {
326
+      curYear = updateYear
327
+      curMonth = updateMonth
328
+    }
329
+    this.setEnableAreaOnWeekMode(days)
330
+    this.setData(
331
+      {
332
+        'calendar.curYear': curYear,
333
+        'calendar.curMonth': curMonth,
334
+        'calendar.days': days
335
+      },
336
+      () => {
337
+        Day(this.Component).setDateStyle()
338
+      }
339
+    )
340
+  }
341
+  /**
342
+   * 计算上一周的日期
343
+   */
344
+  calculatePrevWeekDays() {
345
+    let { firstDayInThisWeek } = this.calculateFirstDay()
346
+    let { curYear, curMonth } = this.getData('calendar')
347
+    let days = []
348
+
349
+    if (firstDayInThisWeek - 7 > 0) {
350
+      const { Uyear, Umonth } = this.updateCurrYearAndMonth('prev')
351
+      curYear = Uyear
352
+      curMonth = Umonth
353
+      for (let i = firstDayInThisWeek - 7; i < firstDayInThisWeek; i++) {
354
+        days.push({
355
+          year: curYear,
356
+          month: curMonth,
357
+          day: i,
358
+          week: getDate.dayOfWeek(curYear, curMonth, i)
359
+        })
360
+      }
361
+    } else {
362
+      let temp = []
363
+      for (let i = 1; i < firstDayInThisWeek; i++) {
364
+        temp.push({
365
+          year: curYear,
366
+          month: curMonth,
367
+          day: i,
368
+          week: getDate.dayOfWeek(curYear, curMonth, i)
369
+        })
370
+      }
371
+      const { Uyear, Umonth } = this.updateCurrYearAndMonth('prev')
372
+      curYear = Uyear
373
+      curMonth = Umonth
374
+      const prevMonthDays = getDate.thisMonthDays(curYear, curMonth)
375
+      for (
376
+        let i = prevMonthDays - Math.abs(firstDayInThisWeek - 7);
377
+        i <= prevMonthDays;
378
+        i++
379
+      ) {
380
+        days.push({
381
+          year: curYear,
382
+          month: curMonth,
383
+          day: i,
384
+          week: getDate.dayOfWeek(curYear, curMonth, i)
385
+        })
386
+      }
387
+      days = days.concat(temp)
388
+    }
389
+    days = this.initSelectedDay(days)
390
+    const {
391
+      year: updateYear,
392
+      month: updateMonth
393
+    } = this.updateYMWhenSwipeCalendarHasSelected(days)
394
+    if (updateYear && updateMonth) {
395
+      curYear = updateYear
396
+      curMonth = updateMonth
397
+    }
398
+    this.setEnableAreaOnWeekMode(days)
399
+    this.setData(
400
+      {
401
+        'calendar.curYear': curYear,
402
+        'calendar.curMonth': curMonth,
403
+        'calendar.days': days
404
+      },
405
+      () => {
406
+        Day(this.Component).setDateStyle()
407
+      }
408
+    )
409
+  }
410
+  calculateDatesWhenJump(
411
+    { year, month, day },
412
+    { firstWeekDays, lastWeekDays },
413
+    firstDayOfWeekIsMon
414
+  ) {
415
+    const inFirstWeek = this.__dateIsInWeek({ year, month, day }, firstWeekDays)
416
+    const inLastWeek = this.__dateIsInWeek({ year, month, day }, lastWeekDays)
417
+    let dates = []
418
+    if (inFirstWeek) {
419
+      dates = this.__calculateDatesWhenInFirstWeek(
420
+        firstWeekDays,
421
+        firstDayOfWeekIsMon
422
+      )
423
+    } else if (inLastWeek) {
424
+      dates = this.__calculateDatesWhenInLastWeek(
425
+        lastWeekDays,
426
+        firstDayOfWeekIsMon
427
+      )
428
+    } else {
429
+      dates = this.__calculateDates({ year, month, day }, firstDayOfWeekIsMon)
430
+    }
431
+    return dates
432
+  }
433
+  jump({ year, month, day }, disableSelected) {
434
+    return new Promise(resolve => {
435
+      if (!day) return
436
+      const config = this.getCalendarConfig()
437
+      const firstDayOfWeekIsMon = config.firstDayOfWeek === 'Mon'
438
+      const firstWeekDays = this.firstWeekInMonth(
439
+        year,
440
+        month,
441
+        firstDayOfWeekIsMon
442
+      )
443
+      let lastWeekDays = this.lastWeekInMonth(year, month, firstDayOfWeekIsMon)
444
+      let dates = this.calculateDatesWhenJump(
445
+        { year, month, day },
446
+        {
447
+          firstWeekDays,
448
+          lastWeekDays
449
+        },
450
+        firstDayOfWeekIsMon
451
+      )
452
+      dates = dates.map(d => {
453
+        let date = { ...d }
454
+        if (
455
+          +date.year === +year &&
456
+          +date.month === +month &&
457
+          +date.day === +day &&
458
+          !disableSelected
459
+        ) {
460
+          date.choosed = true
461
+        }
462
+        date = this.__setTodoWhenJump(date, config)
463
+        if (config.showLunar) {
464
+          date = this.__setSolarLunar(date)
465
+        }
466
+        if (config.highlightToday) {
467
+          date = this.__highlightToday(date)
468
+        }
469
+        return date
470
+      })
471
+      this.setEnableAreaOnWeekMode(dates)
472
+      const tmpData = {
473
+        'calendar.days': dates,
474
+        'calendar.curYear': year,
475
+        'calendar.curMonth': month,
476
+        'calendar.empytGrids': [],
477
+        'calendar.lastEmptyGrids': []
478
+      }
479
+      if (!disableSelected) {
480
+        tmpData['calendar.selectedDay'] = dates.filter(item => item.choosed)
481
+      }
482
+      this.setData(tmpData, () => {
483
+        Day(this.Component).setDateStyle()
484
+        resolve({ year, month, date: day })
485
+      })
486
+    })
487
+  }
488
+  __setTodoWhenJump(dateInfo) {
489
+    const date = { ...dateInfo }
490
+    const { todoLabels = [], showLabelAlways } = this.getData('calendar')
491
+    const todosStr = todoLabels.map(d => `${+d.year}-${+d.month}-${+d.day}`)
492
+    const idx = todosStr.indexOf(`${+date.year}-${+date.month}-${+date.day}`)
493
+    if (idx !== -1) {
494
+      if (showLabelAlways) {
495
+        date.showTodoLabel = true
496
+      } else {
497
+        date.showTodoLabel = !date.choosed
498
+      }
499
+      const todo = todoLabels[idx] || {}
500
+      if (date.showTodoLabel && todo.todoText) date.todoText = todo.todoText
501
+      if (todo.color) date.color = todo.color
502
+    }
503
+    return date
504
+  }
505
+  __setSolarLunar(dateInfo) {
506
+    const date = { ...dateInfo }
507
+    date.lunar = convertSolarLunar.solar2lunar(
508
+      +date.year,
509
+      +date.month,
510
+      +date.day
511
+    )
512
+    return date
513
+  }
514
+  __highlightToday(dateInfo) {
515
+    const date = { ...dateInfo }
516
+    const today = getDate.todayDate()
517
+    const isToday =
518
+      +today.year === +date.year &&
519
+      +today.month === +date.month &&
520
+      +date.day === +today.date
521
+    date.isToday = isToday
522
+    return date
523
+  }
524
+  __calculateDatesWhenInFirstWeek(firstWeekDays) {
525
+    const dates = [...firstWeekDays]
526
+    if (dates.length < 7) {
527
+      let { year, month } = dates[0]
528
+      let len = 7 - dates.length
529
+      let lastDate
530
+      if (month > 1) {
531
+        month -= 1
532
+        lastDate = getDate.thisMonthDays(year, month)
533
+      } else {
534
+        month = 12
535
+        year -= 1
536
+        lastDate = getDate.thisMonthDays(year, month)
537
+      }
538
+      while (len) {
539
+        dates.unshift({
540
+          year,
541
+          month,
542
+          day: lastDate,
543
+          week: getDate.dayOfWeek(year, month, lastDate)
544
+        })
545
+        lastDate -= 1
546
+        len -= 1
547
+      }
548
+    }
549
+    return dates
550
+  }
551
+  __calculateDatesWhenInLastWeek(lastWeekDays) {
552
+    const dates = [...lastWeekDays]
553
+    if (dates.length < 7) {
554
+      let { year, month } = dates[0]
555
+      let len = 7 - dates.length
556
+      let firstDate = 1
557
+      if (month > 11) {
558
+        month = 1
559
+        year += 1
560
+      } else {
561
+        month += 1
562
+      }
563
+      while (len) {
564
+        dates.push({
565
+          year,
566
+          month,
567
+          day: firstDate,
568
+          week: getDate.dayOfWeek(year, month, firstDate)
569
+        })
570
+        firstDate += 1
571
+        len -= 1
572
+      }
573
+    }
574
+    return dates
575
+  }
576
+  __calculateDates({ year, month, day }, firstDayOfWeekIsMon) {
577
+    const week = getDate.dayOfWeek(year, month, day)
578
+    let range = [day - week, day + (6 - week)]
579
+    if (firstDayOfWeekIsMon) {
580
+      range = [day + 1 - week, day + (7 - week)]
581
+    }
582
+    const dates = Day(this.Component).buildDate(year, month)
583
+    const weekDates = dates.slice(range[0] - 1, range[1])
584
+    return weekDates
585
+  }
586
+  __dateIsInWeek(date, week) {
587
+    return week.find(
588
+      item =>
589
+        +item.year === +date.year &&
590
+        +item.month === +date.month &&
591
+        +item.day === +date.day
592
+    )
593
+  }
594
+  __tipsWhenCanNotSwtich() {
595
+    logger.info(
596
+      '当前月份未选中日期下切换为周视图,不能明确该展示哪一周的日期,故此情况不允许切换'
597
+    )
598
+  }
599
+}
600
+
601
+export default component => new WeekMode(component)

+ 26 - 0
components/local/calendar/func/wxData.js

@@ -0,0 +1,26 @@
1
+class WxData {
2
+  constructor(component) {
3
+    this.Component = component
4
+  }
5
+  getData(key) {
6
+    const data = this.Component.data
7
+    if (!key) return data
8
+    if (key.includes('.')) {
9
+      let keys = key.split('.')
10
+      const tmp = keys.reduce((prev, next) => {
11
+        return prev[next]
12
+      }, data)
13
+      return tmp
14
+    } else {
15
+      return this.Component.data[key]
16
+    }
17
+  }
18
+  setData(data, cb = () => {}) {
19
+    if (!data) return
20
+    if (typeof data === 'object') {
21
+      this.Component.setData(data, cb)
22
+    }
23
+  }
24
+}
25
+
26
+export default WxData

+ 254 - 0
components/local/calendar/index.js

@@ -0,0 +1,254 @@
1
+import Week from './func/week'
2
+import { Logger, Slide, GetDate, initialTasks } from './func/utils'
3
+import initCalendar, {
4
+  jump,
5
+  getCurrentYM,
6
+  whenChangeDate,
7
+  renderCalendar,
8
+  whenMulitSelect,
9
+  whenSingleSelect,
10
+  whenChooseArea,
11
+  getCalendarDates
12
+} from './main.js'
13
+
14
+const slide = new Slide()
15
+const logger = new Logger()
16
+const getDate = new GetDate()
17
+
18
+Component({
19
+  options: {
20
+    styleIsolation: 'apply-shared',
21
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
22
+  },
23
+  properties: {
24
+    calendarConfig: {
25
+      type: Object,
26
+      value: {}
27
+    }
28
+  },
29
+  data: {
30
+    handleMap: {
31
+      prev_year: 'chooseYear',
32
+      prev_month: 'chooseMonth',
33
+      next_month: 'chooseMonth',
34
+      next_year: 'chooseYear'
35
+    }
36
+  },
37
+  lifetimes: {
38
+    attached: function() {
39
+      this.initComp()
40
+    },
41
+    detached: function() {
42
+      initialTasks.flag = 'finished'
43
+      initialTasks.tasks.length = 0
44
+    }
45
+  },
46
+  methods: {
47
+    initComp() {
48
+      const calendarConfig = this.setDefaultDisableDate()
49
+      this.setConfig(calendarConfig)
50
+    },
51
+    setDefaultDisableDate() {
52
+      const calendarConfig = this.properties.calendarConfig || {}
53
+      if (calendarConfig.disableMode && !calendarConfig.disableMode.date) {
54
+        calendarConfig.disableMode.date = getDate.toTimeStr(getDate.todayDate())
55
+      }
56
+      return calendarConfig
57
+    },
58
+    setConfig(config) {
59
+      if (config.markToday && typeof config.markToday === 'string') {
60
+        config.highlightToday = true
61
+      }
62
+      config.theme = config.theme || 'default'
63
+      this.weekMode = config.weekMode
64
+      this.setData(
65
+        {
66
+          calendarConfig: config
67
+        },
68
+        () => {
69
+          initCalendar(this, config)
70
+        }
71
+      )
72
+    },
73
+    chooseDate(e) {
74
+      const { type } = e.currentTarget.dataset
75
+      if (!type) return
76
+      const methodName = this.data.handleMap[type]
77
+      this[methodName](type)
78
+    },
79
+    chooseYear(type) {
80
+      const { curYear, curMonth } = this.data.calendar
81
+      if (!curYear || !curMonth) return logger.warn('异常:未获取到当前年月')
82
+      if (this.weekMode) {
83
+        return console.warn('周视图下不支持点击切换年月')
84
+      }
85
+      let newYear = +curYear
86
+      let newMonth = +curMonth
87
+      if (type === 'prev_year') {
88
+        newYear -= 1
89
+      } else if (type === 'next_year') {
90
+        newYear += 1
91
+      }
92
+      this.render(curYear, curMonth, newYear, newMonth)
93
+    },
94
+    chooseMonth(type) {
95
+      const { curYear, curMonth } = this.data.calendar
96
+      if (!curYear || !curMonth) return logger.warn('异常:未获取到当前年月')
97
+      if (this.weekMode) return console.warn('周视图下不支持点击切换年月')
98
+      let newYear = +curYear
99
+      let newMonth = +curMonth
100
+      if (type === 'prev_month') {
101
+        newMonth = newMonth - 1
102
+        if (newMonth < 1) {
103
+          newYear -= 1
104
+          newMonth = 12
105
+        }
106
+      } else if (type === 'next_month') {
107
+        newMonth += 1
108
+        if (newMonth > 12) {
109
+          newYear += 1
110
+          newMonth = 1
111
+        }
112
+      }
113
+      this.render(curYear, curMonth, newYear, newMonth)
114
+    },
115
+    render(curYear, curMonth, newYear, newMonth) {
116
+      whenChangeDate.call(this, {
117
+        curYear,
118
+        curMonth,
119
+        newYear,
120
+        newMonth
121
+      })
122
+      this.setData({
123
+        'calendar.curYear': newYear,
124
+        'calendar.curMonth': newMonth
125
+      })
126
+      renderCalendar.call(this, newYear, newMonth)
127
+    },
128
+    /**
129
+     * 日期点击事件
130
+     * @param {!object} e 事件对象
131
+     */
132
+    tapDayItem(e) {
133
+      const { idx, date = {} } = e.currentTarget.dataset
134
+      const { day, disable } = date
135
+      if (disable || !day) return
136
+      const config = this.data.calendarConfig || this.config || {}
137
+      const { multi, chooseAreaMode } = config
138
+      if (multi) {
139
+        whenMulitSelect.call(this, idx)
140
+      } else if (chooseAreaMode) {
141
+        whenChooseArea.call(this, idx)
142
+      } else {
143
+        whenSingleSelect.call(this, idx)
144
+      }
145
+      this.setData({
146
+        'calendar.noDefault': false
147
+      })
148
+    },
149
+    doubleClickToToday() {
150
+      if (this.config.multi || this.weekMode) return
151
+      if (this.count === undefined) {
152
+        this.count = 1
153
+      } else {
154
+        this.count += 1
155
+      }
156
+      if (this.lastClick) {
157
+        const difference = new Date().getTime() - this.lastClick
158
+        if (difference < 500 && this.count >= 2) {
159
+          jump.call(this)
160
+        }
161
+        this.count = undefined
162
+        this.lastClick = undefined
163
+      } else {
164
+        this.lastClick = new Date().getTime()
165
+      }
166
+    },
167
+    /**
168
+     * 日历滑动开始
169
+     * @param {object} e
170
+     */
171
+    calendarTouchstart(e) {
172
+      const t = e.touches[0]
173
+      const startX = t.clientX
174
+      const startY = t.clientY
175
+      this.slideLock = true // 滑动事件加锁
176
+      this.setData({
177
+        'gesture.startX': startX,
178
+        'gesture.startY': startY
179
+      })
180
+    },
181
+    /**
182
+     * 日历滑动中
183
+     * @param {object} e
184
+     */
185
+    calendarTouchmove(e) {
186
+      const { gesture } = this.data
187
+      const { preventSwipe } = this.properties.calendarConfig
188
+      if (!this.slideLock || preventSwipe) return
189
+      if (slide.isLeft(gesture, e.touches[0])) {
190
+        this.handleSwipe('left')
191
+        this.slideLock = false
192
+      }
193
+      if (slide.isRight(gesture, e.touches[0])) {
194
+        this.handleSwipe('right')
195
+        this.slideLock = false
196
+      }
197
+    },
198
+    calendarTouchend(e) {
199
+      this.setData({
200
+        'calendar.leftSwipe': 0,
201
+        'calendar.rightSwipe': 0
202
+      })
203
+    },
204
+    handleSwipe(direction) {
205
+      let swipeKey = 'calendar.leftSwipe'
206
+      let swipeCalendarType = 'next_month'
207
+      let weekChangeType = 'next_week'
208
+      if (direction === 'right') {
209
+        swipeKey = 'calendar.rightSwipe'
210
+        swipeCalendarType = 'prev_month'
211
+        weekChangeType = 'prev_week'
212
+      }
213
+      this.setData({
214
+        [swipeKey]: 1
215
+      })
216
+      this.currentYM = getCurrentYM()
217
+      if (this.weekMode) {
218
+        this.slideLock = false
219
+        this.currentDates = getCalendarDates()
220
+        if (weekChangeType === 'prev_week') {
221
+          Week(this).calculatePrevWeekDays()
222
+        } else if (weekChangeType === 'next_week') {
223
+          Week(this).calculateNextWeekDays()
224
+        }
225
+        this.onSwipeCalendar(weekChangeType)
226
+        this.onWeekChange(weekChangeType)
227
+        return
228
+      }
229
+      this.chooseMonth(swipeCalendarType)
230
+      this.onSwipeCalendar(swipeCalendarType)
231
+    },
232
+    onSwipeCalendar(direction) {
233
+      this.triggerEvent('onSwipe', {
234
+        directionType: direction,
235
+        currentYM: this.currentYM
236
+      })
237
+    },
238
+    onWeekChange(direction) {
239
+      this.triggerEvent('whenChangeWeek', {
240
+        current: {
241
+          currentYM: this.currentYM,
242
+          dates: [...this.currentDates]
243
+        },
244
+        next: {
245
+          currentYM: getCurrentYM(),
246
+          dates: getCalendarDates()
247
+        },
248
+        directionType: direction
249
+      })
250
+      this.currentDates = null
251
+      this.currentYM = null
252
+    }
253
+  }
254
+})

+ 3 - 0
components/local/calendar/index.json

@@ -0,0 +1,3 @@
1
+{
2
+  "component": true
3
+}

Diff nebyl zobrazen, protože je příliš veliký
+ 87 - 0
components/local/calendar/index.wxml


+ 214 - 0
components/local/calendar/index.wxss

@@ -0,0 +1,214 @@
1
+@import './theme/iconfont.wxss';
2
+@import './theme/theme-default.wxss';
3
+@import './theme/theme-elegant.wxss';
4
+
5
+.b {
6
+    display: flex;
7
+}
8
+
9
+.lr {
10
+    flex-direction: row;
11
+}
12
+
13
+.tb {
14
+    flex-direction: column;
15
+}
16
+
17
+.pc {
18
+    justify-content: center;
19
+}
20
+
21
+.ac {
22
+    align-items: center;
23
+}
24
+
25
+.cc {
26
+    align-items: center;
27
+    justify-content: center;
28
+}
29
+
30
+.wrap {
31
+    flex-wrap: wrap;
32
+}
33
+
34
+.flex {
35
+    flex-grow: 1;
36
+}
37
+
38
+.bg {
39
+    background-image: linear-gradient(to bottom, #faefe7, #ffcbd7);
40
+    overflow: hidden;
41
+}
42
+
43
+.white-color {
44
+    color: #fff;
45
+}
46
+
47
+.fs24 {
48
+    font-size: 24rpx;
49
+}
50
+
51
+.fs28 {
52
+    font-size: 28rpx;
53
+}
54
+
55
+.fs32 {
56
+    font-size: 32rpx;
57
+}
58
+
59
+.fs36 {
60
+    font-size: 36rpx;
61
+}
62
+
63
+.calendar {
64
+    width: 100%;
65
+    box-sizing: border-box;
66
+}
67
+
68
+/* 日历操作栏 */
69
+
70
+.handle {
71
+    height: 80rpx;
72
+}
73
+
74
+.prev-handle,
75
+.next-handle {
76
+    padding: 20rpx;
77
+}
78
+
79
+.date-in-handle {
80
+    height: 80rpx;
81
+}
82
+
83
+/* 星期栏 */
84
+
85
+.weeks {
86
+    height: 50rpx;
87
+    line-height: 50rpx;
88
+    opacity: 0.5;
89
+}
90
+
91
+.week {
92
+    text-align: center;
93
+}
94
+
95
+.grid,
96
+.week {
97
+    width: 14.286014285714286%;
98
+}
99
+
100
+.date-wrap {
101
+    width: 100%;
102
+    height: 80rpx;
103
+    position: relative;
104
+    left: 0;
105
+    top: 0;
106
+}
107
+
108
+.date {
109
+    position: relative;
110
+    left: 0;
111
+    top: 0;
112
+    width: 55rpx;
113
+    height: 55rpx;
114
+    text-align: center;
115
+    line-height: 55rpx;
116
+    font-size: 26rpx;
117
+    font-weight: 200;
118
+    border-radius: 50%;
119
+    transition: all 0.3s;
120
+    animation-name: choosed;
121
+    animation-duration: 0.5s;
122
+    animation-timing-function: linear;
123
+    animation-iteration-count: 1;
124
+}
125
+
126
+.date-area-mode {
127
+    width: 100%;
128
+    border-radius: 0;
129
+}
130
+
131
+.date-desc {
132
+    width: 150%;
133
+    height: 32rpx;
134
+    font-size: 20rpx;
135
+    line-height: 32rpx;
136
+    position: absolute;
137
+    left: 50%;
138
+    transform: translateX(-50%);
139
+    overflow: hidden;
140
+    word-break: break-all;
141
+    text-overflow: ellipsis;
142
+    white-space: nowrap;
143
+    -webkit-line-clamp: 1;
144
+    text-align: center;
145
+}
146
+
147
+@keyframes choosed {
148
+    from {
149
+        transform: scale(1);
150
+    }
151
+
152
+    50% {
153
+        transform: scale(0.9);
154
+    }
155
+
156
+    to {
157
+        transform: scale(1);
158
+    }
159
+}
160
+
161
+/* 日期圆圈标记 */
162
+.todo-circle {
163
+    border-width: 1rpx;
164
+    border-style: solid;
165
+    box-sizing: border-box;
166
+}
167
+
168
+/* 待办点标记相关样式 */
169
+.todo-dot {
170
+    width: 10rpx;
171
+    height: 10rpx;
172
+    border-radius: 50%;
173
+    position: absolute;
174
+    left: 50%;
175
+    transform: translateX(-50%);
176
+}
177
+
178
+.todo-dot-top {
179
+    top: 3rpx;
180
+}
181
+
182
+.todo-dot.todo-dot-top-always {
183
+    top: -8rpx;
184
+}
185
+
186
+.todo-dot.todo-dot-bottom {
187
+    bottom: 0;
188
+}
189
+
190
+.todo-dot.todo-dot-bottom-always {
191
+    bottom: -10rpx;
192
+}
193
+
194
+/* 日期描述文字(待办文字/农历)相关样式 */
195
+
196
+.date-desc.date-desc-top {
197
+    top: -6rpx;
198
+}
199
+
200
+.date-desc.date-desc-top-always {
201
+    top: -20rpx;
202
+}
203
+
204
+.date-desc.date-desc-bottom {
205
+    bottom: -14rpx;
206
+}
207
+
208
+.todo-circle .date-desc.date-desc-bottom {
209
+    bottom: -30rpx;
210
+}
211
+
212
+.date-desc.date-desc-bottom-always {
213
+    bottom: -28rpx;
214
+}

+ 877 - 0
components/local/calendar/main.js

@@ -0,0 +1,877 @@
1
+import Day from './func/day'
2
+import Week from './func/week'
3
+import Todo from './func/todo'
4
+import WxData from './func/wxData'
5
+import Calendar from './func/render'
6
+import CalendarConfig from './func/config'
7
+import convertSolarLunar from './func/convertSolarLunar'
8
+import {
9
+  Logger,
10
+  GetDate,
11
+  isComponent,
12
+  initialTasks,
13
+  getCurrentPage,
14
+  getComponent,
15
+  getDateTimeStamp
16
+} from './func/utils'
17
+
18
+let Component = {}
19
+let logger = new Logger()
20
+let getDate = new GetDate()
21
+let dataInstance = null
22
+
23
+/**
24
+ * 全局赋值正在操作的组件实例,方便读/写各自的 data
25
+ * @param {string} componentId 要操作的日历组件ID
26
+ */
27
+function bindCurrentComponent(componentId) {
28
+  if (componentId) {
29
+    Component = getComponent(componentId)
30
+  }
31
+  return Component
32
+}
33
+/**
34
+ * 获取日历内部数据
35
+ * @param {string} key 获取值的键名
36
+ * @param {string} componentId 要操作的日历组件ID
37
+ */
38
+function getData(key, componentId) {
39
+  bindCurrentComponent(componentId)
40
+  dataInstance = new WxData(Component)
41
+  return dataInstance.getData(key)
42
+}
43
+/**
44
+ * 设置日历内部数据
45
+ * @param {object}} data 待设置的数据
46
+ * @param {function} callback 设置成功回调函数
47
+ */
48
+function setData(data, callback = () => {}) {
49
+  const dataInstance = new WxData(Component)
50
+  return dataInstance.setData(data, callback)
51
+}
52
+
53
+const conf = {
54
+  /**
55
+   * 渲染日历
56
+   * @param {number} curYear
57
+   * @param {number} curMonth
58
+   * @param {number} curDate
59
+   */
60
+  renderCalendar(curYear, curMonth, curDate) {
61
+    if (isComponent(this)) Component = this
62
+    return new Promise((resolve, reject) => {
63
+      Calendar(Component)
64
+        .renderCalendar(curYear, curMonth, curDate)
65
+        .then((info = {}) => {
66
+          if (!info.firstRender) {
67
+            return resolve({
68
+              year: curYear,
69
+              month: curMonth,
70
+              date: curDate
71
+            })
72
+          }
73
+          mountEventsOnPage(getCurrentPage())
74
+          Component.triggerEvent('afterCalendarRender', Component)
75
+          Component.firstRender = true
76
+          initialTasks.flag = 'finished'
77
+          if (initialTasks.tasks.length) {
78
+            initialTasks.tasks.shift()()
79
+          }
80
+          resolve({
81
+            year: curYear,
82
+            month: curMonth,
83
+            date: curDate
84
+          })
85
+        })
86
+        .catch(err => {
87
+          reject(err)
88
+        })
89
+    })
90
+  },
91
+  /**
92
+   * 当改变月份时触发
93
+   * @param {object} param
94
+   */
95
+  whenChangeDate({ curYear, curMonth, newYear, newMonth }) {
96
+    Component.triggerEvent('whenChangeMonth', {
97
+      current: {
98
+        year: curYear,
99
+        month: curMonth
100
+      },
101
+      next: {
102
+        year: newYear,
103
+        month: newMonth
104
+      }
105
+    })
106
+  },
107
+  /**
108
+   * 多选
109
+   * @param {number} dateIdx 当前选中日期索引值
110
+   */
111
+  whenMulitSelect(dateIdx) {
112
+    if (isComponent(this)) Component = this
113
+    const { calendar = {} } = getData()
114
+    const { days, todoLabels } = calendar
115
+    const config = CalendarConfig(Component).getCalendarConfig()
116
+    let { selectedDay: selectedDays = [] } = calendar
117
+    const currentDay = days[dateIdx]
118
+    if (!currentDay) return
119
+    currentDay.choosed = !currentDay.choosed
120
+    if (!currentDay.choosed) {
121
+      currentDay.cancel = true // 该次点击是否为取消日期操作
122
+      const currentDayStr = getDate.toTimeStr(currentDay)
123
+      selectedDays = selectedDays.filter(
124
+        item => currentDayStr !== getDate.toTimeStr(item)
125
+      )
126
+      if (todoLabels) {
127
+        todoLabels.forEach(item => {
128
+          if (currentDayStr === getDate.toTimeStr(item)) {
129
+            currentDay.showTodoLabel = true
130
+          }
131
+        })
132
+      }
133
+    } else {
134
+      currentDay.cancel = false
135
+      const { showLabelAlways } = getData('calendar')
136
+      if (showLabelAlways && currentDay.showTodoLabel) {
137
+        currentDay.showTodoLabel = true
138
+      } else {
139
+        currentDay.showTodoLabel = false
140
+      }
141
+      if (!config.takeoverTap) {
142
+        selectedDays.push(currentDay)
143
+      }
144
+    }
145
+    if (config.takeoverTap) {
146
+      return Component.triggerEvent('onTapDay', currentDay)
147
+    }
148
+    setData({
149
+      'calendar.days': days,
150
+      'calendar.selectedDay': selectedDays
151
+    })
152
+    conf.afterTapDay(currentDay, selectedDays)
153
+  },
154
+  /**
155
+   * 单选
156
+   * @param {number} dateIdx 当前选中日期索引值
157
+   */
158
+  whenSingleSelect(dateIdx) {
159
+    if (isComponent(this)) Component = this
160
+    const { calendar = {} } = getData()
161
+    const { days, selectedDay: selectedDays = [], todoLabels } = calendar
162
+    let shouldMarkerTodoDay = []
163
+    const currentDay = days[dateIdx]
164
+    if (!currentDay) return
165
+    const preSelectedDate = [...selectedDays].pop() || {}
166
+    const { month: dMonth, year: dYear } = days[0] || {}
167
+    const config = CalendarConfig(Component).getCalendarConfig()
168
+    if (config.takeoverTap) {
169
+      return Component.triggerEvent('onTapDay', currentDay)
170
+    }
171
+    conf.afterTapDay(currentDay)
172
+    if (!config.inverse && preSelectedDate.day === currentDay.day) return
173
+    days.forEach((item, idx) => {
174
+      if (+item.day === +preSelectedDate.day) days[idx].choosed = false
175
+    })
176
+    if (todoLabels) {
177
+      // 筛选当月待办事项的日期
178
+      shouldMarkerTodoDay = todoLabels.filter(
179
+        item => +item.year === dYear && +item.month === dMonth
180
+      )
181
+    }
182
+    Todo(Component).showTodoLabels(shouldMarkerTodoDay, days, selectedDays)
183
+    const tmp = {
184
+      'calendar.days': days
185
+    }
186
+    if (preSelectedDate.day !== currentDay.day) {
187
+      preSelectedDate.choosed = false
188
+      currentDay.choosed = true
189
+      if (!calendar.showLabelAlways || !currentDay.showTodoLabel) {
190
+        currentDay.showTodoLabel = false
191
+      }
192
+      tmp['calendar.selectedDay'] = [currentDay]
193
+    } else if (config.inverse) {
194
+      if (currentDay.choosed) {
195
+        if (currentDay.showTodoLabel && calendar.showLabelAlways) {
196
+          currentDay.showTodoLabel = true
197
+        } else {
198
+          currentDay.showTodoLabel = false
199
+        }
200
+      }
201
+      tmp['calendar.selectedDay'] = []
202
+    }
203
+    if (config.weekMode) {
204
+      tmp['calendar.curYear'] = currentDay.year
205
+      tmp['calendar.curMonth'] = currentDay.month
206
+    }
207
+    setData(tmp)
208
+  },
209
+  gotoSetContinuousDates(start, end) {
210
+    return chooseDateArea([
211
+      `${getDate.toTimeStr(start)}`,
212
+      `${getDate.toTimeStr(end)}`
213
+    ])
214
+  },
215
+  timeRangeHelper(currentDate, selectedDay) {
216
+    const currentDateTimestamp = getDateTimeStamp(currentDate)
217
+    const startDate = selectedDay[0]
218
+    let endDate
219
+    let endDateTimestamp
220
+    let selectedLen = selectedDay.length
221
+    if (selectedLen > 1) {
222
+      endDate = selectedDay[selectedLen - 1]
223
+      endDateTimestamp = getDateTimeStamp(endDate)
224
+    }
225
+    const startTimestamp = getDateTimeStamp(startDate)
226
+    return {
227
+      endDate,
228
+      startDate,
229
+      currentDateTimestamp,
230
+      endDateTimestamp,
231
+      startTimestamp
232
+    }
233
+  },
234
+  /**
235
+   * 计算连续日期选择的开始及结束日期
236
+   * @param {object} currentDate 当前选择日期
237
+   * @param {array} selectedDay 已选择的的日期
238
+   */
239
+  calculateDateRange(currentDate, selectedDay) {
240
+    const {
241
+      endDate,
242
+      startDate,
243
+      currentDateTimestamp,
244
+      endDateTimestamp,
245
+      startTimestamp
246
+    } = this.timeRangeHelper(currentDate, selectedDay)
247
+    let range = []
248
+    let selectedLen = selectedDay.length
249
+    const isWantToChooseOneDate = selectedDay.filter(
250
+      item => getDate.toTimeStr(item) === getDate.toTimeStr(currentDate)
251
+    )
252
+    if (selectedLen === 2 && isWantToChooseOneDate.length) {
253
+      range = [currentDate, currentDate]
254
+      return range
255
+    }
256
+    if (
257
+      currentDateTimestamp >= startTimestamp &&
258
+      endDateTimestamp &&
259
+      currentDateTimestamp <= endDateTimestamp
260
+    ) {
261
+      const currentDateIdxInChoosedDateArea = selectedDay.findIndex(
262
+        item => getDate.toTimeStr(item) === getDate.toTimeStr(currentDate)
263
+      )
264
+      if (selectedLen / 2 > currentDateIdxInChoosedDateArea) {
265
+        range = [currentDate, endDate]
266
+      } else {
267
+        range = [startDate, currentDate]
268
+      }
269
+    } else if (currentDateTimestamp < startTimestamp) {
270
+      range = [currentDate, endDate]
271
+    } else if (currentDateTimestamp > startTimestamp) {
272
+      range = [startDate, currentDate]
273
+    }
274
+    return range
275
+  },
276
+  chooseAreaWhenExistArea(currentDate, selectedDay) {
277
+    return new Promise((resolve, reject) => {
278
+      const range = conf.calculateDateRange(
279
+        currentDate,
280
+        getDate.sortDates(selectedDay)
281
+      )
282
+      conf
283
+        .gotoSetContinuousDates(...range)
284
+        .then(data => {
285
+          resolve(data)
286
+          conf.afterTapDay(currentDate)
287
+        })
288
+        .catch(err => {
289
+          reject(err)
290
+          conf.afterTapDay(currentDate)
291
+        })
292
+    })
293
+  },
294
+  chooseAreaWhenHasOneDate(currentDate, selectedDay, lastChoosedDate) {
295
+    return new Promise((resolve, reject) => {
296
+      const startDate = lastChoosedDate || selectedDay[0]
297
+      let range = [startDate, currentDate]
298
+      const currentDateTimestamp = getDateTimeStamp(currentDate)
299
+      const lastChoosedDateTimestamp = getDateTimeStamp(startDate)
300
+      if (lastChoosedDateTimestamp > currentDateTimestamp) {
301
+        range = [currentDate, startDate]
302
+      }
303
+      conf
304
+        .gotoSetContinuousDates(...range)
305
+        .then(data => {
306
+          resolve(data)
307
+          conf.afterTapDay(currentDate)
308
+        })
309
+        .catch(err => {
310
+          reject(err)
311
+          conf.afterTapDay(currentDate)
312
+        })
313
+    })
314
+  },
315
+  /**
316
+   * 日期范围选择模式
317
+   * @param {number} dateIdx 当前选中日期索引值
318
+   */
319
+  whenChooseArea(dateIdx) {
320
+    return new Promise((resolve, reject) => {
321
+      if (isComponent(this)) Component = this
322
+      if (Component.weekMode) return
323
+      const { days = [], selectedDay, lastChoosedDate } = getData('calendar')
324
+      const currentDate = days[dateIdx]
325
+      if (currentDate.disable) return
326
+      const config = CalendarConfig(Component).getCalendarConfig()
327
+      if (config.takeoverTap) {
328
+        return Component.triggerEvent('onTapDay', currentDate)
329
+      }
330
+      if (selectedDay && selectedDay.length > 1) {
331
+        conf
332
+          .chooseAreaWhenExistArea(currentDate, selectedDay)
333
+          .then(dates => {
334
+            resolve(dates)
335
+          })
336
+          .catch(err => {
337
+            reject(err)
338
+          })
339
+      } else if (lastChoosedDate || (selectedDay && selectedDay.length === 1)) {
340
+        conf
341
+          .chooseAreaWhenHasOneDate(currentDate, selectedDay, lastChoosedDate)
342
+          .then(dates => {
343
+            resolve(dates)
344
+          })
345
+          .catch(err => {
346
+            reject(err)
347
+          })
348
+      } else {
349
+        days.forEach(date => {
350
+          if (+date.day === +currentDate.day) {
351
+            date.choosed = true
352
+          } else {
353
+            date.choosed = false
354
+          }
355
+        })
356
+
357
+        const dataInstance = new WxData(Component)
358
+        dataInstance.setData({
359
+          'calendar.days': [...days],
360
+          'calendar.lastChoosedDate': currentDate
361
+        })
362
+      }
363
+    })
364
+  },
365
+  /**
366
+   * 点击日期后触发事件
367
+   * @param {object} currentSelected 当前选择的日期
368
+   * @param {array} selectedDates  多选状态下选中的日期
369
+   */
370
+  afterTapDay(currentSelected, selectedDates) {
371
+    const config = CalendarConfig(Component).getCalendarConfig()
372
+    const { multi } = config
373
+    if (!multi) {
374
+      Component.triggerEvent('afterTapDay', currentSelected)
375
+    } else {
376
+      Component.triggerEvent('afterTapDay', {
377
+        currentSelected,
378
+        selectedDates
379
+      })
380
+    }
381
+  },
382
+  /**
383
+   * 跳转至今天
384
+   */
385
+  jumpToToday() {
386
+    return new Promise((resolve, reject) => {
387
+      const { year, month, date } = getDate.todayDate()
388
+      const timestamp = getDate.todayTimestamp()
389
+      const config = CalendarConfig(Component).getCalendarConfig()
390
+      setData({
391
+        'calendar.curYear': year,
392
+        'calendar.curMonth': month,
393
+        'calendar.selectedDay': [
394
+          {
395
+            year: year,
396
+            day: date,
397
+            month: month,
398
+            choosed: true,
399
+            lunar: config.showLunar
400
+              ? convertSolarLunar.solar2lunar(year, month, date)
401
+              : null
402
+          }
403
+        ],
404
+        'calendar.todayTimestamp': timestamp
405
+      })
406
+      conf
407
+        .renderCalendar(year, month, date)
408
+        .then(() => {
409
+          resolve({ year, month, date })
410
+        })
411
+        .catch(() => {
412
+          reject('jump failed')
413
+        })
414
+    })
415
+  }
416
+}
417
+
418
+export const whenChangeDate = conf.whenChangeDate
419
+export const renderCalendar = conf.renderCalendar
420
+export const whenSingleSelect = conf.whenSingleSelect
421
+export const whenChooseArea = conf.whenChooseArea
422
+export const whenMulitSelect = conf.whenMulitSelect
423
+export const calculatePrevWeekDays = conf.calculatePrevWeekDays
424
+export const calculateNextWeekDays = conf.calculateNextWeekDays
425
+
426
+/**
427
+ * 获取当前年月
428
+ * @param {string} componentId 要操作的日历组件ID
429
+ */
430
+export function getCurrentYM(componentId) {
431
+  bindCurrentComponent(componentId)
432
+  return {
433
+    year: getData('calendar.curYear'),
434
+    month: getData('calendar.curMonth')
435
+  }
436
+}
437
+
438
+/**
439
+ * 获取已选择的日期
440
+ * @param {object } options 日期配置选项 {lunar} 是否返回农历信息
441
+ * @param {string} componentId 要操作的日历组件ID
442
+ */
443
+export function getSelectedDay(options = {}, componentId) {
444
+  bindCurrentComponent(componentId)
445
+  const config = getCalendarConfig()
446
+  const dates = getData('calendar.selectedDay') || []
447
+  if (options.lunar && !config.showLunar) {
448
+    const datesWithLunar = getDate.convertLunar(dates)
449
+    return datesWithLunar
450
+  } else {
451
+    return dates
452
+  }
453
+}
454
+
455
+/**
456
+ * 取消选中日期
457
+ * @param {array} dates 需要取消的日期,不传则取消所有已选择的日期
458
+ * @param {string} componentId 要操作的日历组件ID
459
+ */
460
+export function cancelSelectedDates(dates, componentId) {
461
+  bindCurrentComponent(componentId)
462
+  const { days = [], selectedDay = [] } = getData('calendar') || {}
463
+  if (!dates || !dates.length) {
464
+    days.forEach(item => {
465
+      item.choosed = false
466
+    })
467
+    setData({
468
+      'calendar.days': days,
469
+      'calendar.selectedDay': []
470
+    })
471
+  } else {
472
+    const cancelDatesStr = dates.map(
473
+      date => `${+date.year}-${+date.month}-${+date.day}`
474
+    )
475
+    const filterSelectedDates = selectedDay.filter(
476
+      date =>
477
+        !cancelDatesStr.includes(`${+date.year}-${+date.month}-${+date.day}`)
478
+    )
479
+    days.forEach(date => {
480
+      if (
481
+        cancelDatesStr.includes(`${+date.year}-${+date.month}-${+date.day}`)
482
+      ) {
483
+        date.choosed = false
484
+      }
485
+    })
486
+    setData({
487
+      'calendar.days': days,
488
+      'calendar.selectedDay': filterSelectedDates
489
+    })
490
+  }
491
+}
492
+/**
493
+ * 周视图跳转
494
+ * @param {object} date info
495
+ * @param {boolean} disableSelected 跳转时是否需要选中,周视图切换调用该方法,如未选择日期时不选中日期
496
+ */
497
+function jumpWhenWeekMode({ year, month, day }, disableSelected) {
498
+  return new Promise((resolve, reject) => {
499
+    Week(Component)
500
+      .jump(
501
+        {
502
+          year: +year,
503
+          month: +month,
504
+          day: +day
505
+        },
506
+        disableSelected
507
+      )
508
+      .then(date => {
509
+        resolve(date)
510
+        Component.triggerEvent('afterCalendarRender', Component)
511
+      })
512
+      .catch(err => {
513
+        reject(err)
514
+        Component.triggerEvent('afterCalendarRender', Component)
515
+      })
516
+  })
517
+}
518
+
519
+/**
520
+ * 月视图跳转
521
+ * @param {object} date info
522
+ */
523
+function jumpWhenNormalMode({ year, month, day }) {
524
+  return new Promise((resolve, reject) => {
525
+    if (typeof +year !== 'number' || typeof +month !== 'number') {
526
+      return logger.warn('jump 函数年月日参数必须为数字')
527
+    }
528
+    const timestamp = getDate.todayTimestamp()
529
+    let tmp = {
530
+      'calendar.curYear': +year,
531
+      'calendar.curMonth': +month,
532
+      'calendar.todayTimestamp': timestamp
533
+    }
534
+    setData(tmp, () => {
535
+      conf
536
+        .renderCalendar(+year, +month, +day)
537
+        .then(date => {
538
+          resolve(date)
539
+        })
540
+        .catch(err => {
541
+          reject(err)
542
+        })
543
+    })
544
+  })
545
+}
546
+
547
+/**
548
+ * 跳转至指定日期
549
+ * @param {number} year
550
+ * @param {number} month
551
+ * @param {number} day
552
+ * @param {string} componentId 要操作的日历组件ID
553
+ */
554
+export function jump(year, month, day, componentId) {
555
+  return new Promise((resolve, reject) => {
556
+    bindCurrentComponent(componentId)
557
+    const { selectedDay = [] } = getData('calendar') || {}
558
+    const { weekMode } = getData('calendarConfig') || {}
559
+    const { year: y, month: m, day: d } = selectedDay[0] || {}
560
+    if (+y === +year && +m === +month && +d === +day) {
561
+      return
562
+    }
563
+    if (weekMode) {
564
+      let disableSelected = false
565
+      if (!year || !month || !day) {
566
+        const today = getDate.todayDate()
567
+        year = today.year
568
+        month = today.month
569
+        day = today.date
570
+        disableSelected = true
571
+      }
572
+      jumpWhenWeekMode({ year, month, day }, disableSelected)
573
+        .then(date => {
574
+          resolve(date)
575
+        })
576
+        .catch(err => {
577
+          reject(err)
578
+        })
579
+      mountEventsOnPage(getCurrentPage())
580
+      return
581
+    }
582
+    if (year && month) {
583
+      jumpWhenNormalMode({ year, month, day })
584
+        .then(date => {
585
+          resolve(date)
586
+        })
587
+        .catch(err => {
588
+          reject(err)
589
+        })
590
+    } else {
591
+      conf
592
+        .jumpToToday()
593
+        .then(date => {
594
+          resolve(date)
595
+        })
596
+        .catch(err => {
597
+          reject(err)
598
+        })
599
+    }
600
+  })
601
+}
602
+
603
+/**
604
+ * 设置待办事项日期标记
605
+ * @param {object} todos  待办事项配置
606
+ * @param {string} [todos.pos] 标记显示位置,默认值'bottom' ['bottom', 'top']
607
+ * @param {string} [todos.dotColor] 标记点颜色,backgroundColor 支持的值都行
608
+ * @param {object[]} [todos.days] 需要标记的所有日期,如:[{year: 2015, month: 5, day: 12}],其中年月日字段必填
609
+ * @param {string} componentId 要操作的日历组件ID
610
+ */
611
+export function setTodoLabels(todos, componentId) {
612
+  bindCurrentComponent(componentId)
613
+  Todo(Component).setTodoLabels(todos)
614
+}
615
+
616
+/**
617
+ * 删除指定日期待办事项
618
+ * @param {array} todos 需要删除的待办日期数组
619
+ * @param {string} componentId 要操作的日历组件ID
620
+ */
621
+export function deleteTodoLabels(todos, componentId) {
622
+  bindCurrentComponent(componentId)
623
+  Todo(Component).deleteTodoLabels(todos)
624
+}
625
+
626
+/**
627
+ * 清空所有待办事项
628
+ * @param {string} componentId 要操作的日历组件ID
629
+ */
630
+export function clearTodoLabels(componentId) {
631
+  bindCurrentComponent(componentId)
632
+  Todo(Component).clearTodoLabels()
633
+}
634
+
635
+/**
636
+ * 获取所有待办事项
637
+ * @param {object } options 日期配置选项 {lunar} 是否返回农历信息
638
+ * @param {string} componentId 要操作的日历组件ID
639
+ */
640
+export function getTodoLabels(options = {}, componentId) {
641
+  bindCurrentComponent(componentId)
642
+  const config = getCalendarConfig()
643
+  const todoDates = Todo(Component).getTodoLabels() || []
644
+  if (options.lunar && !config.showLunar) {
645
+    const todoDatesWithLunar = getDate.convertLunar(todoDates)
646
+    return todoDatesWithLunar
647
+  } else {
648
+    return todoDates
649
+  }
650
+}
651
+
652
+/**
653
+ * 禁用指定日期
654
+ * @param {array} days 日期
655
+ * @param {number} [days.year]
656
+ * @param {number} [days.month]
657
+ * @param {number} [days.day]
658
+ * @param {string} componentId 要操作的日历组件ID
659
+ */
660
+export function disableDay(days = [], componentId) {
661
+  bindCurrentComponent(componentId)
662
+  Day(Component).disableDays(days)
663
+}
664
+
665
+/**
666
+ * 指定可选日期范围
667
+ * @param {array} area 日期访问数组
668
+ * @param {string} componentId 要操作的日历组件ID
669
+ */
670
+export function enableArea(area = [], componentId) {
671
+  bindCurrentComponent(componentId)
672
+  Day(Component).enableArea(area)
673
+}
674
+
675
+/**
676
+ * 指定特定日期可选
677
+ * @param {array} days 指定日期数组
678
+ * @param {string} componentId 要操作的日历组件ID
679
+ */
680
+export function enableDays(days = [], componentId) {
681
+  bindCurrentComponent(componentId)
682
+  Day(Component).enableDays(days)
683
+}
684
+
685
+/**
686
+ * 设置选中日期(多选模式下)
687
+ * @param {array} selected 需选中日期
688
+ * @param {string} componentId 要操作的日历组件ID
689
+ */
690
+export function setSelectedDays(selected, componentId) {
691
+  bindCurrentComponent(componentId)
692
+  Day(Component).setSelectedDays(selected)
693
+}
694
+
695
+/**
696
+ * 获取当前日历配置
697
+ * @param {string} componentId 要操作的日历组件ID
698
+ */
699
+export function getCalendarConfig(componentId) {
700
+  bindCurrentComponent(componentId)
701
+  return CalendarConfig(Component).getCalendarConfig()
702
+}
703
+
704
+/**
705
+ * 设置日历配置
706
+ * @param {object} config
707
+ * @param {string} componentId 要操作的日历组件ID
708
+ */
709
+export function setCalendarConfig(config, componentId) {
710
+  bindCurrentComponent(componentId)
711
+  if (!config || Object.keys(config).length === 0) {
712
+    return logger.warn('setCalendarConfig 参数必须为非空对象')
713
+  }
714
+  const existConfig = getCalendarConfig()
715
+  return new Promise((resolve, reject) => {
716
+    CalendarConfig(Component)
717
+      .setCalendarConfig(config)
718
+      .then(conf => {
719
+        resolve(conf)
720
+        const { date, type } = existConfig.disableMode || {}
721
+        const { _date, _type } = config.disableMode || {}
722
+        if (type !== _type || date !== _date) {
723
+          const { year, month } = getCurrentYM()
724
+          jump(year, month)
725
+        }
726
+      })
727
+      .catch(err => {
728
+        reject(err)
729
+      })
730
+  })
731
+}
732
+
733
+/**
734
+ * 获取当前日历面板日期
735
+ * @param {object } options 日期配置选项 {lunar} 是否返回农历信息
736
+ * @param {string} componentId 要操作的日历组件ID
737
+ */
738
+export function getCalendarDates(options = {}, componentId) {
739
+  bindCurrentComponent(componentId)
740
+  const config = getCalendarConfig()
741
+  const dates = getData('calendar.days', componentId) || []
742
+  if (options.lunar && !config.showLunar) {
743
+    const datesWithLunar = getDate.convertLunar(dates)
744
+    return datesWithLunar
745
+  } else {
746
+    return dates
747
+  }
748
+}
749
+
750
+/**
751
+ * 选择连续日期范围
752
+ * @param {string} componentId 要操作的日历组件ID
753
+ */
754
+export function chooseDateArea(dateArea, componentId) {
755
+  bindCurrentComponent(componentId)
756
+  return Day(Component).chooseArea(dateArea)
757
+}
758
+
759
+/**
760
+ * 设置指定日期样式
761
+ * @param {array} dates 待设置特殊样式的日期
762
+ * @param {string} componentId 要操作的日历组件ID
763
+ */
764
+export function setDateStyle(dates, componentId) {
765
+  if (!dates) return
766
+  bindCurrentComponent(componentId)
767
+  Day(Component).setDateStyle(dates)
768
+}
769
+
770
+/**
771
+ * 切换周月视图
772
+ * 切换视图时可传入指定日期,如: {year: 2019, month: 1, day: 3}
773
+ * args[0] view 视图模式[week, month]
774
+ * args[1]|args[2]为day object或者 componentId
775
+ */
776
+export function switchView(...args) {
777
+  return new Promise((resolve, reject) => {
778
+    const view = args[0]
779
+    if (!args[1]) {
780
+      return Week(Component)
781
+        .switchWeek(view)
782
+        .then(resolve)
783
+        .catch(reject)
784
+    }
785
+    if (typeof args[1] === 'string') {
786
+      bindCurrentComponent(args[1], this)
787
+      Week(Component)
788
+        .switchWeek(view, args[2])
789
+        .then(resolve)
790
+        .catch(reject)
791
+    } else if (typeof args[1] === 'object') {
792
+      if (typeof args[2] === 'string') {
793
+        bindCurrentComponent(args[1], this)
794
+      }
795
+      Week(Component)
796
+        .switchWeek(view, args[1])
797
+        .then(resolve)
798
+        .catch(reject)
799
+    }
800
+  })
801
+}
802
+
803
+/**
804
+ * 绑定日历事件至当前页面实例
805
+ * @param {object} page 当前页面实例
806
+ */
807
+function mountEventsOnPage(page) {
808
+  page.calendar = {
809
+    jump,
810
+    switchView,
811
+    disableDay,
812
+    enableArea,
813
+    enableDays,
814
+    chooseDateArea,
815
+    getCurrentYM,
816
+    getSelectedDay,
817
+    cancelSelectedDates,
818
+    setDateStyle,
819
+    setTodoLabels,
820
+    getTodoLabels,
821
+    deleteTodoLabels,
822
+    clearTodoLabels,
823
+    setSelectedDays,
824
+    getCalendarConfig,
825
+    setCalendarConfig,
826
+    getCalendarDates
827
+  }
828
+}
829
+
830
+function setWeekHeader(firstDayOfWeek) {
831
+  let weeksCh = ['日', '一', '二', '三', '四', '五', '六']
832
+  if (firstDayOfWeek === 'Mon') {
833
+    weeksCh = ['一', '二', '三', '四', '五', '六', '日']
834
+  }
835
+  setData({
836
+    'calendar.weeksCh': weeksCh
837
+  })
838
+}
839
+
840
+function autoSelectDay(defaultDay) {
841
+  Component.firstRenderWeekMode = true
842
+  if (defaultDay && typeof defaultDay === 'string') {
843
+    const day = defaultDay.split('-')
844
+    if (day.length < 3) {
845
+      return logger.warn('配置 jumpTo 格式应为: 2018-4-2 或 2018-04-02')
846
+    }
847
+    jump(+day[0], +day[1], +day[2])
848
+  } else {
849
+    if (!defaultDay) {
850
+      Component.config.noDefault = true
851
+      setData({
852
+        'config.noDefault': true
853
+      })
854
+    }
855
+    jump()
856
+  }
857
+}
858
+
859
+function init(component, config) {
860
+  initialTasks.flag = 'process'
861
+  Component = component
862
+  Component.config = config
863
+  setWeekHeader(config.firstDayOfWeek)
864
+  autoSelectDay(config.defaultDay)
865
+  logger.tips(
866
+    '使用中若遇问题请反馈至 https://github.com/treadpit/wx_calendar/issues ✍️'
867
+  )
868
+}
869
+
870
+export default (component, config = {}) => {
871
+  if (initialTasks.flag === 'process') {
872
+    return initialTasks.tasks.push(function() {
873
+      init(component, config)
874
+    })
875
+  }
876
+  init(component, config)
877
+}

Diff nebyl zobrazen, protože je příliš veliký
+ 29 - 0
components/local/calendar/theme/iconfont.wxss


+ 52 - 0
components/local/calendar/theme/theme-default.wxss

@@ -0,0 +1,52 @@
1
+
2
+/* 日历主要颜色相关样式 */
3
+
4
+.default_color,
5
+.default_weekend-color,
6
+.default_handle-color,
7
+.default_week-color {
8
+    color: #ff629a;
9
+}
10
+
11
+.default_today {
12
+    color: #fff;
13
+    background-color: #874fb4;
14
+}
15
+
16
+.default_choosed {
17
+    color: #fff;
18
+    background-color: #ff629a;
19
+}
20
+
21
+.default_date-disable {
22
+    color: #c7c7c7;
23
+}
24
+
25
+.default_prev-month-date,
26
+.default_next-month-date {
27
+    color: #e2e2e2;
28
+}
29
+
30
+.default_normal-date {
31
+    color: #88d2ac;
32
+}
33
+
34
+.default_todo-circle {
35
+    border-color: #88d2ac;
36
+}
37
+
38
+.default_todo-dot {
39
+    background-color: #e54d42;
40
+}
41
+
42
+.default_date-desc {
43
+    color: #c2c2c2;
44
+}
45
+
46
+.default_date-desc-lunar {
47
+    color: #e54d42;
48
+}
49
+
50
+.default_date-desc-disable {
51
+    color: #e2e2e2;
52
+}

+ 49 - 0
components/local/calendar/theme/theme-elegant.wxss

@@ -0,0 +1,49 @@
1
+.elegant_color,
2
+.elegant_weekend-color,
3
+.elegant_handle-color,
4
+.elegant_week-color {
5
+    color: #333;
6
+}
7
+
8
+.elegant_today {
9
+    color: #000;
10
+    background-color: #e1e7f5;
11
+}
12
+
13
+.elegant_choosed {
14
+    color: #000;
15
+    background-color: #e2e2e2;
16
+}
17
+
18
+.elegant_date-disable {
19
+    color: #c7c7c7;
20
+}
21
+
22
+.elegant_prev-month-date,
23
+.elegant_next-month-date {
24
+    color: #e2e2e2;
25
+}
26
+
27
+.elegant_normal-date {
28
+    color: #333;
29
+}
30
+
31
+.elegant_todo-circle {
32
+    border-color: #161035;
33
+}
34
+
35
+.elegant_todo-dot {
36
+    background-color: #161035;
37
+}
38
+
39
+.elegant_date-desc {
40
+    color: #c2c2c2;
41
+}
42
+
43
+.elegant_date-desc-lunar {
44
+    color: #161035;
45
+}
46
+
47
+.elegant_date-desc-disable {
48
+    color: #e2e2e2;
49
+}

+ 144 - 0
components/local/v2/core.js

@@ -0,0 +1,144 @@
1
+import { dateUtil, getCalendarConfig } from './utils/index'
2
+
3
+/**
4
+ * 计算当前月份前后两月应占的格子
5
+ * @param {number} year 年份
6
+ * @param {number} month 月份
7
+ */
8
+function calculateEmptyGrids(year, month, config) {
9
+  const prevMonthGrids = calculatePrevMonthGrids(year, month, config)
10
+  const nextMonthGrids = calculateNextMonthGrids(year, month, config)
11
+  return {
12
+    prevMonthGrids,
13
+    nextMonthGrids
14
+  }
15
+}
16
+
17
+/**
18
+ * 计算上月应占的格子
19
+ * @param {number} year 年份
20
+ * @param {number} month 月份
21
+ */
22
+function calculatePrevMonthGrids(year, month, config) {
23
+  let emptyGrids = []
24
+  const prevMonthDays = dateUtil.getDatesCountOfMonth(year, month - 1)
25
+  let firstDayOfWeek = dateUtil.firstDayOfWeek(year, month)
26
+  if (config.firstDayOfWeek === 'Mon') {
27
+    if (firstDayOfWeek === 0) {
28
+      firstDayOfWeek = 6
29
+    } else {
30
+      firstDayOfWeek -= 1
31
+    }
32
+  }
33
+  if (firstDayOfWeek > 0) {
34
+    const len = prevMonthDays - firstDayOfWeek
35
+    const { onlyShowCurrentMonth } = config
36
+    const YMInfo = dateUtil.getPrevMonthInfo({ year, month })
37
+    for (let i = prevMonthDays; i > len; i--) {
38
+      if (onlyShowCurrentMonth) {
39
+        emptyGrids.push('')
40
+      } else {
41
+        const week = dateUtil.getDayOfWeek(+year, +month, i)
42
+        emptyGrids.push({
43
+          ...YMInfo,
44
+          date: i,
45
+          week
46
+        })
47
+      }
48
+    }
49
+    emptyGrids.reverse()
50
+  }
51
+  return emptyGrids
52
+}
53
+/**
54
+ * 计算下一月日期是否需要多展示的日期
55
+ * 某些月份日期为5排,某些月份6排,统一为6排
56
+ * @param {number} year
57
+ * @param {number} month
58
+ * @param {object} config
59
+ */
60
+function calculateExtraEmptyDate(year, month, config) {
61
+  let extDate = 0
62
+  if (+month === 2) {
63
+    extDate += 7
64
+    let firstDayofMonth = dateUtil.getDayOfWeek(year, month, 1)
65
+    if (config.firstDayOfWeek === 'Mon') {
66
+      if (+firstDayofMonth === 1) extDate += 7
67
+    } else {
68
+      if (+firstDayofMonth === 0) extDate += 7
69
+    }
70
+  } else {
71
+    let firstDayofMonth = dateUtil.getDayOfWeek(year, month, 1)
72
+    if (config.firstDayOfWeek === 'Mon') {
73
+      if (firstDayofMonth !== 0 && firstDayofMonth < 6) {
74
+        extDate += 7
75
+      }
76
+    } else {
77
+      if (firstDayofMonth <= 5) {
78
+        extDate += 7
79
+      }
80
+    }
81
+  }
82
+  return extDate
83
+}
84
+/**
85
+ * 计算下月应占的格子
86
+ * @param {number} year 年份
87
+ * @param {number} month  月份
88
+ */
89
+function calculateNextMonthGrids(year, month, config) {
90
+  let emptyGrids = []
91
+  const datesCount = dateUtil.getDatesCountOfMonth(year, month)
92
+  let lastDayWeek = dateUtil.getDayOfWeek(year, month, datesCount)
93
+  if (config.firstDayOfWeek === 'Mon') {
94
+    if (lastDayWeek === 0) {
95
+      lastDayWeek = 6
96
+    } else {
97
+      lastDayWeek -= 1
98
+    }
99
+  }
100
+  let len = 7 - (lastDayWeek + 1)
101
+  const { onlyShowCurrentMonth } = config
102
+  if (!onlyShowCurrentMonth) {
103
+    len = len + calculateExtraEmptyDate(year, month, config)
104
+  }
105
+  const YMInfo = dateUtil.getNextMonthInfo({ year, month })
106
+  for (let i = 1; i <= len; i++) {
107
+    const week = dateUtil.getDayOfWeek(+year, +month, i)
108
+    if (onlyShowCurrentMonth) {
109
+      emptyGrids.push('')
110
+    } else {
111
+      emptyGrids.push({
112
+        id: i - 1,
113
+        ...YMInfo,
114
+        date: i,
115
+        week: week || 7
116
+      })
117
+    }
118
+  }
119
+  return emptyGrids
120
+}
121
+/**
122
+ * 设置日历面板数据
123
+ * @param {number} year 年份
124
+ * @param {number} month  月份
125
+ * @param {number} curDate  日期
126
+ */
127
+function calculateCurrentMonthDates(year, month) {
128
+  return dateUtil.calcDates(year, month)
129
+}
130
+
131
+export function calcJumpData({ dateInfo, config, component }) {
132
+  dateInfo = dateInfo || dateUtil.todayFMD()
133
+  const { year, month, date } = dateInfo
134
+  const calendarConfig = config || getCalendarConfig(component)
135
+  const emptyGrids = calculateEmptyGrids(year, month, calendarConfig)
136
+  const calendar = {
137
+    curYear: year,
138
+    curMonth: month,
139
+    curDate: date,
140
+    dates: calculateCurrentMonthDates(year, month),
141
+    ...emptyGrids
142
+  }
143
+  return calendar
144
+}

+ 12 - 0
components/local/v2/helper.js

@@ -0,0 +1,12 @@
1
+import { dateUtil } from './utils/index'
2
+
3
+export function calcTargetYMInfo() {
4
+  return {
5
+    right: dateUtil.getPrevMonthInfo,
6
+    left: dateUtil.getNextMonthInfo,
7
+    prev_month: dateUtil.getPrevMonthInfo,
8
+    next_month: dateUtil.getNextMonthInfo,
9
+    prev_year: dateUtil.getPrevYearInfo,
10
+    next_year: dateUtil.getNextYearInfo
11
+  }
12
+}

+ 257 - 0
components/local/v2/index.js

@@ -0,0 +1,257 @@
1
+import plugins from './plugins/index'
2
+import { calcJumpData } from './core'
3
+import { renderCalendar } from './render'
4
+import { calcTargetYMInfo } from './helper'
5
+import { dateUtil, calendarGesture, logger } from './utils/index'
6
+
7
+Component({
8
+  options: {
9
+    styleIsolation: 'apply-shared',
10
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
11
+  },
12
+  properties: {
13
+    config: {
14
+      type: Object,
15
+      value: {}
16
+    }
17
+  },
18
+  lifetimes: {
19
+    attached: function() {
20
+      this.initComp()
21
+    }
22
+  },
23
+  methods: {
24
+    initComp() {
25
+      const calendarConfig = this.setDefaultDisableDate()
26
+      this.setConfig(calendarConfig)
27
+    },
28
+    // 禁用某天日期配置默认为今天
29
+    setDefaultDisableDate() {
30
+      const calendarConfig = this.properties.config || {}
31
+      if (calendarConfig.disableMode && !calendarConfig.disableMode.date) {
32
+        calendarConfig.disableMode.date = dateUtil.toTimeStr(
33
+          dateUtil.todayFMD()
34
+        )
35
+      }
36
+      return calendarConfig
37
+    },
38
+    initCalendar(config) {
39
+      const { defaultDate } = config
40
+      let date = dateUtil.todayFMD()
41
+      if (defaultDate && typeof defaultDate === 'string') {
42
+        const dateInfo = defaultDate.split('-')
43
+        if (dateInfo.length < 3) {
44
+          return logger.warn('defaultDate配置格式应为: 2018-4-2 或 2018-04-02')
45
+        } else {
46
+          date = {
47
+            year: +dateInfo[0],
48
+            month: +dateInfo[1],
49
+            date: +dateInfo[2]
50
+          }
51
+        }
52
+      }
53
+      const waitRenderData = calcJumpData({
54
+        dateInfo: date,
55
+        config
56
+      })
57
+      const timestamp = dateUtil.todayTimestamp()
58
+      if (config.autoChoosedWhenJump) {
59
+        const target = waitRenderData.dates.filter(
60
+          item => dateUtil.toTimeStr(item) === dateUtil.toTimeStr(date)
61
+        )
62
+        if (target && target.length) {
63
+          if (!waitRenderData.selectedDates) {
64
+            waitRenderData.selectedDates = target
65
+          } else {
66
+            waitRenderData.selectedDates.push(target[0])
67
+          }
68
+        }
69
+      }
70
+      return {
71
+        ...waitRenderData,
72
+        todayTimestamp: timestamp,
73
+        weeksCh: dateUtil.getWeekHeader(config.firstDayOfWeek)
74
+      }
75
+    },
76
+    setConfig(config) {
77
+      if (config.markToday && typeof config.markToday === 'string') {
78
+        config.highlightToday = true
79
+      }
80
+      config.theme = config.theme || 'default'
81
+      this.setData(
82
+        {
83
+          config
84
+        },
85
+        () => {
86
+          for (let plugin of plugins.installed) {
87
+            const [, p] = plugin
88
+            if (typeof p.install === 'function') {
89
+              p.install(this)
90
+            }
91
+            if (typeof p.methods === 'function') {
92
+              const methods = p.methods(this)
93
+              for (let fnName in methods) {
94
+                if (fnName.startsWith('__')) continue
95
+                const fn = methods[fnName]
96
+                if (typeof fn === 'function') {
97
+                  if (!this.calendar) this.calendar = {}
98
+                  this.calendar[fnName] = fn
99
+                }
100
+              }
101
+            }
102
+          }
103
+          const initData = this.initCalendar(config)
104
+          renderCalendar.call(this, initData, config)
105
+        }
106
+      )
107
+    },
108
+    tapDate(e) {
109
+      const { info } = e.currentTarget.dataset
110
+      const { date, disable } = info || {}
111
+      if (disable || !date) return
112
+      const { calendar, config } = this.data
113
+      let calendarData = calendar
114
+      let calendarConfig = config
115
+      if (config.takeoverTap) {
116
+        return this.triggerEvent('takeoverTap', info)
117
+      }
118
+      for (let plugin of plugins.installed) {
119
+        const [, p] = plugin
120
+        if (typeof p.onTapDate === 'function') {
121
+          const {
122
+            calendarData: __calendarData,
123
+            calendarConfig: __calendarConfig
124
+          } = p.onTapDate(info, calendarData, calendarConfig)
125
+          calendarData = __calendarData
126
+          calendarConfig = __calendarConfig
127
+        }
128
+      }
129
+      renderCalendar.call(this, calendarData, calendarConfig).then(() => {
130
+        this.triggerEvent('afterTapDate', info)
131
+      })
132
+    },
133
+    /**
134
+     * 日历滑动开始
135
+     * @param {object} e
136
+     */
137
+    calendarTouchstart(e) {
138
+      const t = e.touches[0]
139
+      const startX = t.clientX
140
+      const startY = t.clientY
141
+      this.swipeLock = true
142
+      this.setData({
143
+        'gesture.startX': startX,
144
+        'gesture.startY': startY
145
+      })
146
+    },
147
+    /**
148
+     * 日历滑动中
149
+     * @param {object} e
150
+     */
151
+    calendarTouchmove(e) {
152
+      const { gesture } = this.data
153
+      const { preventSwipe } = this.properties.config
154
+      if (!this.swipeLock || preventSwipe) return
155
+      if (calendarGesture.isLeft(gesture, e.touches[0])) {
156
+        this.handleSwipe('left')
157
+        this.swipeLock = false
158
+      }
159
+      if (calendarGesture.isRight(gesture, e.touches[0])) {
160
+        this.handleSwipe('right')
161
+        this.swipeLock = false
162
+      }
163
+    },
164
+    calendarTouchend(e) {
165
+      this.setData({
166
+        'calendar.leftSwipe': 0,
167
+        'calendar.rightSwipe': 0
168
+      })
169
+    },
170
+    handleSwipe(direction) {
171
+      let swipeKey = 'calendar.leftSwipe'
172
+      if (direction === 'right') {
173
+        swipeKey = 'calendar.rightSwipe'
174
+      }
175
+      this.setData({
176
+        [swipeKey]: 1
177
+      })
178
+      const { calendar } = this.data
179
+      let calendarData = calendar
180
+      const { curYear, curMonth } = calendarData
181
+      const getMonthInfo = calcTargetYMInfo()[direction]
182
+      const target = getMonthInfo({
183
+        year: +curYear,
184
+        month: +curMonth
185
+      })
186
+      target.direction = direction
187
+      this.renderCalendar(target)
188
+    },
189
+    changeDate(e) {
190
+      const { type } = e.currentTarget.dataset
191
+      const { calendar: calendarData } = this.data
192
+      const { curYear, curMonth } = calendarData
193
+      const getMonthInfo = calcTargetYMInfo()[type]
194
+      const target = getMonthInfo({
195
+        year: +curYear,
196
+        month: +curMonth
197
+      })
198
+      target.direction = type
199
+      this.renderCalendar(target)
200
+    },
201
+    renderCalendar(target) {
202
+      let { calendar: calendarData, config } = this.data
203
+      const { curYear, curMonth } = calendarData || {}
204
+      for (let plugin of plugins.installed) {
205
+        const [, p] = plugin
206
+        if (typeof p.onSwitchCalendar === 'function') {
207
+          calendarData = p.onSwitchCalendar(target, calendarData, this)
208
+        }
209
+      }
210
+      return renderCalendar.call(this, calendarData, config).then(() => {
211
+        let triggerEventName = 'whenChangeMonth'
212
+        if (config.weekMode) {
213
+          triggerEventName = 'whenChangeWeek'
214
+        }
215
+        this.triggerEvent(triggerEventName, {
216
+          current: {
217
+            year: +curYear,
218
+            month: +curMonth
219
+          },
220
+          next: target
221
+        })
222
+        this.triggerEvent('onSwipe', {
223
+          current: {
224
+            year: +curYear,
225
+            month: +curMonth
226
+          },
227
+          next: target,
228
+          type: triggerEventName
229
+        })
230
+      })
231
+    },
232
+    doubleClickJumpToToday() {
233
+      const { multi, weekMode } = this.calendar.getCalendarConfig() || {}
234
+      if (multi || weekMode) return
235
+      if (this.count === undefined) {
236
+        this.count = 1
237
+      } else {
238
+        this.count += 1
239
+      }
240
+      if (this.lastClick) {
241
+        const difference = new Date().getTime() - this.lastClick
242
+        if (
243
+          difference < 500 &&
244
+          this.count >= 2 &&
245
+          typeof this.calendar.jump === 'function'
246
+        ) {
247
+          const today = dateUtil.todayFMD()
248
+          this.calendar.jump(today)
249
+        }
250
+        this.count = undefined
251
+        this.lastClick = undefined
252
+      } else {
253
+        this.lastClick = new Date().getTime()
254
+      }
255
+    }
256
+  }
257
+})

+ 3 - 0
components/local/v2/index.json

@@ -0,0 +1,3 @@
1
+{
2
+  "component": true
3
+}

Diff nebyl zobrazen, protože je příliš veliký
+ 60 - 0
components/local/v2/index.wxml


+ 214 - 0
components/local/v2/index.wxss

@@ -0,0 +1,214 @@
1
+@import './theme/iconfont.wxss';
2
+@import './theme/theme-default.wxss';
3
+@import './theme/theme-elegant.wxss';
4
+
5
+.b {
6
+    display: flex;
7
+}
8
+
9
+.lr {
10
+    flex-direction: row;
11
+}
12
+
13
+.tb {
14
+    flex-direction: column;
15
+}
16
+
17
+.pc {
18
+    justify-content: center;
19
+}
20
+
21
+.ac {
22
+    align-items: center;
23
+}
24
+
25
+.cc {
26
+    align-items: center;
27
+    justify-content: center;
28
+}
29
+
30
+.wrap {
31
+    flex-wrap: wrap;
32
+}
33
+
34
+.flex {
35
+    flex-grow: 1;
36
+}
37
+
38
+.bg {
39
+    background-image: linear-gradient(to bottom, #faefe7, #ffcbd7);
40
+    overflow: hidden;
41
+}
42
+
43
+.white-color {
44
+    color: #fff;
45
+}
46
+
47
+.fs24 {
48
+    font-size: 24rpx;
49
+}
50
+
51
+.fs28 {
52
+    font-size: 28rpx;
53
+}
54
+
55
+.fs32 {
56
+    font-size: 32rpx;
57
+}
58
+
59
+.fs36 {
60
+    font-size: 36rpx;
61
+}
62
+
63
+.calendar {
64
+    width: 100%;
65
+    box-sizing: border-box;
66
+}
67
+
68
+/* 日历操作栏 */
69
+
70
+.handle {
71
+    height: 80rpx;
72
+}
73
+
74
+.prev-handle,
75
+.next-handle {
76
+    padding: 20rpx;
77
+}
78
+
79
+.date-in-handle {
80
+    height: 80rpx;
81
+}
82
+
83
+/* 星期栏 */
84
+
85
+.weeks {
86
+    height: 50rpx;
87
+    line-height: 50rpx;
88
+    opacity: 0.5;
89
+}
90
+
91
+.week {
92
+    text-align: center;
93
+}
94
+
95
+.grid,
96
+.week {
97
+    width: 14.286014285714286%;
98
+}
99
+
100
+.date-wrap {
101
+    width: 100%;
102
+    height: 80rpx;
103
+    position: relative;
104
+    left: 0;
105
+    top: 0;
106
+}
107
+
108
+.date {
109
+    position: relative;
110
+    left: 0;
111
+    top: 0;
112
+    width: 55rpx;
113
+    height: 55rpx;
114
+    text-align: center;
115
+    line-height: 55rpx;
116
+    font-size: 26rpx;
117
+    font-weight: 200;
118
+    border-radius: 50%;
119
+    transition: all 0.3s;
120
+    animation-name: choosed;
121
+    animation-duration: 0.5s;
122
+    animation-timing-function: linear;
123
+    animation-iteration-count: 1;
124
+}
125
+
126
+.date-area-mode {
127
+    width: 100%;
128
+    border-radius: 0;
129
+}
130
+
131
+.date-desc {
132
+    width: 150%;
133
+    height: 32rpx;
134
+    font-size: 20rpx;
135
+    line-height: 32rpx;
136
+    position: absolute;
137
+    left: 50%;
138
+    transform: translateX(-50%);
139
+    overflow: hidden;
140
+    word-break: break-all;
141
+    text-overflow: ellipsis;
142
+    white-space: nowrap;
143
+    -webkit-line-clamp: 1;
144
+    text-align: center;
145
+}
146
+
147
+@keyframes choosed {
148
+    from {
149
+        transform: scale(1);
150
+    }
151
+
152
+    50% {
153
+        transform: scale(0.9);
154
+    }
155
+
156
+    to {
157
+        transform: scale(1);
158
+    }
159
+}
160
+
161
+/* 日期圆圈标记 */
162
+.todo-circle {
163
+    border-width: 1rpx;
164
+    border-style: solid;
165
+    box-sizing: border-box;
166
+}
167
+
168
+/* 待办点标记相关样式 */
169
+.todo-dot {
170
+    width: 10rpx;
171
+    height: 10rpx;
172
+    border-radius: 50%;
173
+    position: absolute;
174
+    left: 50%;
175
+    transform: translateX(-50%);
176
+}
177
+
178
+.todo-dot-top {
179
+    top: 3rpx;
180
+}
181
+
182
+.todo-dot.todo-dot-top-always {
183
+    top: -8rpx;
184
+}
185
+
186
+.todo-dot.todo-dot-bottom {
187
+    bottom: 0;
188
+}
189
+
190
+.todo-dot.todo-dot-bottom-always {
191
+    bottom: -10rpx;
192
+}
193
+
194
+/* 日期描述文字(待办文字/农历)相关样式 */
195
+
196
+.date-desc.date-desc-top {
197
+    top: -6rpx;
198
+}
199
+
200
+.date-desc.date-desc-top-always {
201
+    top: -20rpx;
202
+}
203
+
204
+.date-desc.date-desc-bottom {
205
+    bottom: -14rpx;
206
+}
207
+
208
+.todo-circle .date-desc.date-desc-bottom {
209
+    bottom: -30rpx;
210
+}
211
+
212
+.date-desc.date-desc-bottom-always {
213
+    bottom: -28rpx;
214
+}

+ 212 - 0
components/local/v2/plugins/holidays/holidays-map.js

@@ -0,0 +1,212 @@
1
+/* *
2
+  @Author: drfu*
3
+  @Description: 数据来源于国务院办公厅关于2020年部分节假日安排的通知(国办发明电〔2019〕16号)_政府信息公开专栏,http://www.gov.cn/zhengce/content/2019-11/21/content_5454164.htm
4
+  @Date: 2020-10-12 14:29:45*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-16 17:38:08
7
+*/
8
+
9
+// 节日列表
10
+export const festival = {
11
+  // 农历固定日期节日
12
+  lunar: {
13
+    1: {
14
+      1: {
15
+        type: 'festival',
16
+        name: '春节',
17
+        label: '春节'
18
+      },
19
+      8: {
20
+        type: 'festival',
21
+        name: '腊八节',
22
+        label: '腊八'
23
+      },
24
+      15: {
25
+        type: 'festival',
26
+        name: '元宵节',
27
+        label: '元宵'
28
+      }
29
+    },
30
+    7: {
31
+      7: {
32
+        type: 'festival',
33
+        name: '七夕节',
34
+        label: '七夕'
35
+      },
36
+      15: {
37
+        type: 'festival',
38
+        name: '中元节',
39
+        label: '中元节'
40
+      }
41
+    },
42
+    9: {
43
+      9: {
44
+        type: 'festival',
45
+        name: '重阳节',
46
+        label: '重阳节'
47
+      }
48
+    }
49
+  },
50
+  // 阳历固定日期节日
51
+  solar: {
52
+    2: {
53
+      14: {
54
+        type: 'festival',
55
+        name: '情人节',
56
+        label: '情人节'
57
+      }
58
+    },
59
+    3: {
60
+      12: {
61
+        type: 'festival',
62
+        name: '植树节',
63
+        label: '植树节'
64
+      }
65
+    },
66
+    4: {
67
+      1: {
68
+        type: 'festival',
69
+        name: '愚人节',
70
+        label: '愚人节'
71
+      },
72
+      5: {
73
+        type: 'festival',
74
+        name: '清明节',
75
+        label: '清明节'
76
+      }
77
+    },
78
+    5: {
79
+      1: {
80
+        type: 'festival',
81
+        name: '劳动节',
82
+        label: '劳动节'
83
+      }
84
+    },
85
+    6: {
86
+      1: {
87
+        type: 'festival',
88
+        name: '儿童节',
89
+        label: '儿童节'
90
+      }
91
+    },
92
+    7: {
93
+      1: {
94
+        type: 'festival',
95
+        name: '建党节',
96
+        label: '建党节'
97
+      }
98
+    },
99
+    8: {
100
+      1: {
101
+        type: 'festival',
102
+        name: '建军节',
103
+        label: '建军节'
104
+      }
105
+    },
106
+    9: {
107
+      10: {
108
+        type: 'festival',
109
+        name: '教师节',
110
+        label: '教师节'
111
+      }
112
+    },
113
+    10: {
114
+      1: {
115
+        type: 'festival',
116
+        name: '国庆节',
117
+        label: '国庆节'
118
+      }
119
+    },
120
+    12: {
121
+      25: {
122
+        type: 'festival',
123
+        name: '圣诞节',
124
+        label: '圣诞节'
125
+      }
126
+    }
127
+  }
128
+}
129
+
130
+export const holidays = {
131
+  2020: {
132
+    1: {
133
+      1: {
134
+        type: 'holiday',
135
+        name: '元旦',
136
+        label: '休'
137
+      },
138
+      19: {
139
+        type: 'work',
140
+        name: '调班',
141
+        label: '班'
142
+      },
143
+      '24-30': {
144
+        type: 'holiday',
145
+        name: '春节',
146
+        label: '休'
147
+      }
148
+    },
149
+    2: {
150
+      1: {
151
+        type: 'work',
152
+        name: '调班',
153
+        label: '班'
154
+      }
155
+    },
156
+    4: {
157
+      '4-6': {
158
+        type: 'holiday',
159
+        name: '清明节',
160
+        label: '休'
161
+      },
162
+      26: {
163
+        type: 'work',
164
+        name: '调班',
165
+        label: '班'
166
+      }
167
+    },
168
+    5: {
169
+      '1-5': {
170
+        type: 'holiday',
171
+        name: '劳动节',
172
+        label: '休'
173
+      },
174
+      9: {
175
+        type: 'work',
176
+        name: '调班',
177
+        label: '班'
178
+      }
179
+    },
180
+    6: {
181
+      '25-27': {
182
+        type: 'holiday',
183
+        name: '端午节',
184
+        label: '休'
185
+      },
186
+      28: {
187
+        type: 'work',
188
+        name: '调班',
189
+        label: '班'
190
+      }
191
+    },
192
+    9: {
193
+      27: {
194
+        type: 'work',
195
+        name: '调班',
196
+        label: '班'
197
+      }
198
+    },
199
+    10: {
200
+      '1-8': {
201
+        type: 'holiday',
202
+        name: '国庆节/中秋节',
203
+        label: '休'
204
+      },
205
+      10: {
206
+        type: 'work',
207
+        name: '调班',
208
+        label: '班'
209
+      }
210
+    }
211
+  }
212
+}

+ 201 - 0
components/local/v2/plugins/holidays/index.js

@@ -0,0 +1,201 @@
1
+/* *
2
+  @Author: drfu*
3
+  @Description: 显示法定节假日班/休情况
4
+  @Date: 2020-10-12 14:29:45*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-16 17:34:13
7
+*/
8
+
9
+import { holidays, festival } from './holidays-map'
10
+import { dateUtil, getCalendarData, logger } from '../../utils/index'
11
+
12
+/**
13
+ * 当前是否在休假期内
14
+ * @param {object} { year, month }
15
+ * @param {object} { start, end, current }
16
+ * @returns
17
+ */
18
+function inHolidays({ year, month }, { start, end, current }) {
19
+  const getTimeStamp = dateUtil.getTimeStamp
20
+  const startTimestamp = getTimeStamp({
21
+    year,
22
+    month,
23
+    date: start
24
+  })
25
+  const endTimestamp = getTimeStamp({
26
+    year,
27
+    month,
28
+    date: end
29
+  })
30
+  const currentDateTimestamp = getTimeStamp({
31
+    year,
32
+    month,
33
+    date: current
34
+  })
35
+  if (
36
+    currentDateTimestamp >= startTimestamp &&
37
+    currentDateTimestamp <= endTimestamp
38
+  ) {
39
+    return true
40
+  }
41
+  return false
42
+}
43
+
44
+function addSpecialFestival(date, component) {
45
+  const { convertlLunar2Solar, convertSolarLunar } = component.calendar || {}
46
+  const lunarDateInfo = convertSolarLunar(date)
47
+  const { lYear, lMonth } = lunarDateInfo || {}
48
+  // 春节
49
+  const info = {
50
+    type: 'festival',
51
+    name: '除夕',
52
+    label: '除夕'
53
+  }
54
+  if (lMonth === 12) {
55
+    if (!festival.lunar['12']) festival.lunar['12'] = {}
56
+    if (convertlLunar2Solar(`${lYear}-12-30`) === -1) {
57
+      festival.lunar['12']['29'] = info
58
+    } else {
59
+      festival.lunar['12']['30'] = info
60
+    }
61
+  }
62
+}
63
+
64
+/**
65
+ * 是否匹配到节日
66
+ * @param {object} [dateInfo={}]
67
+ * @param {object} [component={}]
68
+ * @returns {object|boolean} 匹配到的节日数据或者false
69
+ */
70
+function hasFestivalDate(dateInfo = {}, component = {}) {
71
+  const { month, date } = dateInfo
72
+  let festivalDate = festival.solar[month] && festival.solar[month][date]
73
+  if (!festivalDate) {
74
+    const { convertSolarLunar } = component.calendar || {}
75
+    const lunarDateInfo = convertSolarLunar(dateInfo)
76
+    const { lMonth, lDay } = lunarDateInfo
77
+    festivalDate = festival.lunar[lMonth] && festival.lunar[lMonth][lDay]
78
+    if (!festivalDate) {
79
+      const festivalOfMonth = festival.lunar[lMonth] || {}
80
+      const festivalDateKey = Object.keys(festivalOfMonth).find(item =>
81
+        item.match(new RegExp(`\\b${lDay}\\b`))
82
+      )
83
+      if (!festivalDateKey) {
84
+        festivalDate = false
85
+      } else {
86
+        const festivalInfo = festival.lunar[lMonth][festivalDateKey]
87
+        if (!festivalInfo) {
88
+          festivalDate = false
89
+        } else {
90
+          const { condition } = festivalInfo
91
+          if (typeof condition === 'function') {
92
+            festivalDate = condition(lunarDateInfo)
93
+          } else {
94
+            festivalDate = false
95
+          }
96
+        }
97
+      }
98
+    }
99
+  }
100
+  return festivalDate
101
+}
102
+
103
+export default () => {
104
+  return {
105
+    name: 'holidays',
106
+    beforeRender(calendarData = {}, calendarConfig = {}, component) {
107
+      let { dates = [] } = calendarData
108
+      if (calendarConfig.showHolidays || calendarConfig.showFestival) {
109
+        dates = dates.map(d => {
110
+          let item = { ...d }
111
+          const { year, month, date } = item
112
+          const holidaysOfMonth =
113
+            (holidays[year] && holidays[year][month]) || {}
114
+          const holidayDate = holidaysOfMonth[date]
115
+          if (holidayDate) {
116
+            item = {
117
+              ...item,
118
+              ...holidayDate
119
+            }
120
+          } else {
121
+            const holidayKeys = Object.keys(holidaysOfMonth).filter(item =>
122
+              item.includes('-')
123
+            )
124
+            let target = ''
125
+            for (let v of holidayKeys) {
126
+              const [start, end] = v.split('-')
127
+              if (+d.date >= +start && +d.date <= +end) {
128
+                target = v
129
+                break
130
+              }
131
+            }
132
+            const [start, end] = target.split('-')
133
+            const isInHolidays = inHolidays(
134
+              {
135
+                year,
136
+                month
137
+              },
138
+              {
139
+                start,
140
+                end,
141
+                current: date
142
+              }
143
+            )
144
+            if (isInHolidays) {
145
+              item = {
146
+                ...item,
147
+                ...holidaysOfMonth[target]
148
+              }
149
+            } else if (calendarConfig.showFestival) {
150
+              const { convertSolarLunar, convertlLunar2Solar } =
151
+                component.calendar || {}
152
+              if (
153
+                typeof convertSolarLunar !== 'function' ||
154
+                typeof convertlLunar2Solar !== 'function'
155
+              ) {
156
+                return logger.warn(
157
+                  '农历节日显示需要引入农历插件(/component/v2/plugins/solarLunar)'
158
+                )
159
+              }
160
+              addSpecialFestival(item, component)
161
+              const festivalDate = hasFestivalDate(item, component)
162
+              if (festivalDate) {
163
+                item = {
164
+                  ...item,
165
+                  ...festivalDate
166
+                }
167
+              }
168
+            }
169
+          }
170
+          return item
171
+        })
172
+      }
173
+      return {
174
+        calendarData: {
175
+          ...calendarData,
176
+          dates: dates
177
+        },
178
+        calendarConfig
179
+      }
180
+    },
181
+    methods(component) {
182
+      return {
183
+        getHolidaysOfCurrentYear() {
184
+          const calendar = getCalendarData('calendar', component)
185
+          const { curYear } = calendar
186
+          return this.methods(component).getHolidaysOfYear(curYear)
187
+        },
188
+        getHolidaysOfYear(year) {
189
+          if (!year) return logger.warn('getHolidaysOfCurrentYear() 入参错误')
190
+          if (!holidays[year]) {
191
+            logger.warn('未匹配到当前年份节假日信息,请自行补充')
192
+            return {
193
+              err: 'not match'
194
+            }
195
+          }
196
+          return holidays[year]
197
+        }
198
+      }
199
+    }
200
+  }
201
+}

+ 18 - 0
components/local/v2/plugins/index.js

@@ -0,0 +1,18 @@
1
+import preset from './preset/index'
2
+
3
+export default {
4
+  installed: [...preset],
5
+  use(plugin) {
6
+    if (typeof plugin !== 'function') return
7
+    const info = plugin() || {}
8
+    const { name } = info
9
+    if (
10
+      name &&
11
+      name !== 'methods' &&
12
+      !this.installed.some(p => p[0] === name)
13
+    ) {
14
+      this.installed.unshift([name, info])
15
+    }
16
+    return this
17
+  }
18
+}

+ 277 - 0
components/local/v2/plugins/preset/base.js

@@ -0,0 +1,277 @@
1
+/**
2
+ * @Author: drfu*
3
+ * @Description: 基础功能
4
+ * @Date: 2020-10-08 21:22:09*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-11 13:28:52
7
+ * */
8
+
9
+import { calcJumpData } from '../../core'
10
+import { renderCalendar } from '../../render'
11
+import {
12
+  dateUtil,
13
+  getCalendarData,
14
+  setCalendarData,
15
+  getCalendarConfig
16
+} from '../../utils/index'
17
+
18
+export default () => {
19
+  return {
20
+    name: 'base',
21
+    beforeRender(calendarData = {}, calendarConfig) {
22
+      const calendar = calendarData
23
+      const { selectedDates = [], dates } = calendar
24
+      let _dates = [...dates]
25
+      if (selectedDates.length) {
26
+        const selectedDatesStr = selectedDates.map(date =>
27
+          dateUtil.toTimeStr(date)
28
+        )
29
+        _dates.forEach(date => {
30
+          const dateStr = dateUtil.toTimeStr(date)
31
+          if (selectedDatesStr.includes(dateStr)) {
32
+            date.choosed = true
33
+          }
34
+        })
35
+      }
36
+      return {
37
+        calendarData: {
38
+          ...calendarData,
39
+          dates: _dates
40
+        },
41
+        calendarConfig
42
+      }
43
+    },
44
+    onTapDate(tapedDate, calendarData = {}, calendarConfig = {}) {
45
+      const calendar = {
46
+        ...calendarData
47
+      }
48
+      const dateIndex = dateUtil.findDateIndexInArray(
49
+        tapedDate,
50
+        calendarData.dates
51
+      )
52
+      const { multi, inverse } = calendarConfig
53
+      let dates = [...calendar.dates]
54
+      const { selectedDates = [] } = calendar
55
+      if (!multi) {
56
+        let preSelectedDate = {}
57
+        if (selectedDates.length) {
58
+          preSelectedDate = [...selectedDates].pop() || {}
59
+        }
60
+        const timeStr = dateUtil.toTimeStr
61
+        if (!inverse && timeStr(preSelectedDate) === timeStr(tapedDate)) {
62
+          return calendar
63
+        }
64
+        let _tapedDate = { ...tapedDate, choosed: !tapedDate.choosed }
65
+
66
+        dates[dateIndex] = _tapedDate
67
+        if (preSelectedDate.date) {
68
+          const idx = dateUtil.findDateIndexInArray(preSelectedDate, dates)
69
+          const date = dates[idx]
70
+          if (date) {
71
+            date.choosed = false
72
+          }
73
+        }
74
+        if (dates[dateIndex].choosed) {
75
+          calendar.selectedDates = [dates[dateIndex]]
76
+        } else {
77
+          calendar.selectedDates = []
78
+        }
79
+      } else {
80
+        dates[dateIndex] = {
81
+          ...dates[dateIndex],
82
+          choosed: !dates[dateIndex].choosed
83
+        }
84
+        if (!calendar.selectedDates) {
85
+          calendar.selectedDates = []
86
+        }
87
+        if (dates[dateIndex].choosed) {
88
+          calendar.selectedDates.push(dates[dateIndex])
89
+        } else {
90
+          calendar.selectedDates = calendar.selectedDates.filter(
91
+            date =>
92
+              dateUtil.toTimeStr(date) !== dateUtil.toTimeStr(dates[dateIndex])
93
+          )
94
+        }
95
+      }
96
+      return {
97
+        calendarData: {
98
+          ...calendar,
99
+          dates
100
+        },
101
+        calendarConfig
102
+      }
103
+    },
104
+    onSwitchCalendar(date, calendarData = {}, component) {
105
+      const calendarConfig = getCalendarConfig(component)
106
+      if (calendarConfig.weekMode) {
107
+        return calendarData
108
+      }
109
+      const updatedRenderData = calcJumpData({
110
+        dateInfo: date,
111
+        config: calendarConfig
112
+      })
113
+      return {
114
+        ...calendarData,
115
+        ...updatedRenderData
116
+      }
117
+    },
118
+    methods(component) {
119
+      return {
120
+        jump: dateInfo => {
121
+          if (Object.prototype.toString.call(dateInfo) !== '[object Object]')
122
+            return
123
+          const updatedRenderData = calcJumpData({
124
+            dateInfo,
125
+            component
126
+          })
127
+          const existCalendarData = getCalendarData('calendar', component)
128
+          const config = getCalendarConfig(component)
129
+          if (config.autoChoosedWhenJump) {
130
+            const target = updatedRenderData.dates[dateInfo.date - 1]
131
+            if (!updatedRenderData.selectedDates) {
132
+              updatedRenderData.selectedDates = [target]
133
+            } else {
134
+              updatedRenderData.selectedDates.push(target)
135
+            }
136
+          }
137
+          return renderCalendar.call(component, {
138
+            ...existCalendarData,
139
+            ...updatedRenderData
140
+          })
141
+        },
142
+        getCalendarConfig() {
143
+          return getCalendarConfig(component)
144
+        },
145
+        setCalendarConfig(config) {
146
+          return new Promise((resolve, reject) => {
147
+            if (!component || !component.data.config) {
148
+              reject('异常:未找到组件配置信息')
149
+              return
150
+            }
151
+            let conf = { ...component.config, ...config }
152
+            component.config = conf
153
+            setCalendarData({ config: conf }, component)
154
+              .then(resolve)
155
+              .catch(reject)
156
+          })
157
+        },
158
+        cancelSelectedDates(cancelDates = []) {
159
+          const existCalendarData = getCalendarData('calendar', component) || {}
160
+          const { dates = [], selectedDates = [] } = existCalendarData
161
+          let updatedRenderData = {}
162
+          const config = getCalendarConfig(component)
163
+          let chooseAreaData = {}
164
+          if (config.chooseAreaMode) {
165
+            chooseAreaData = {
166
+              chooseAreaTimestamp: [],
167
+              tempChooseAreaTimestamp: []
168
+            }
169
+          }
170
+          if (!cancelDates.length) {
171
+            dates.forEach(item => {
172
+              item.choosed = false
173
+            })
174
+            updatedRenderData = {
175
+              dates,
176
+              selectedDates: []
177
+            }
178
+          } else {
179
+            const cancelDatesStr = cancelDates.map(date =>
180
+              dateUtil.toTimeStr(date)
181
+            )
182
+            const filterSelectedDates = selectedDates.filter(
183
+              date => !cancelDatesStr.includes(dateUtil.toTimeStr(date))
184
+            )
185
+            dates.forEach(date => {
186
+              if (cancelDatesStr.includes(dateUtil.toTimeStr(date))) {
187
+                date.choosed = false
188
+              }
189
+            })
190
+            updatedRenderData = {
191
+              dates,
192
+              selectedDates: filterSelectedDates
193
+            }
194
+          }
195
+
196
+          return renderCalendar.call(component, {
197
+            ...existCalendarData,
198
+            ...updatedRenderData,
199
+            ...chooseAreaData
200
+          })
201
+        },
202
+        setSelectedDates: targetDates => {
203
+          const existCalendarData = getCalendarData('calendar', component)
204
+          let { dates, selectedDates = [] } = existCalendarData || {}
205
+          let __selectedDates = []
206
+          let __dates = dates
207
+          if (!targetDates) {
208
+            __dates = dates.map(item => {
209
+              const date = { ...item }
210
+              date.choosed = true
211
+              if (existCalendarData.showLabelAlways && date.showTodoLabel) {
212
+                date.showTodoLabel = true
213
+              } else {
214
+                date.showTodoLabel = false
215
+              }
216
+              return date
217
+            })
218
+            __selectedDates = dates
219
+          } else if (targetDates && targetDates.length) {
220
+            const allSelected = dateUtil.uniqueArrayByDate(
221
+              selectedDates.concat(targetDates)
222
+            )
223
+            const allSelectedDateStr = allSelected.map(d =>
224
+              dateUtil.toTimeStr(d)
225
+            )
226
+            __dates = dates.map(item => {
227
+              const date = { ...item }
228
+              if (allSelectedDateStr.includes(dateUtil.toTimeStr(date))) {
229
+                date.choosed = true
230
+                __selectedDates.push(date)
231
+              }
232
+              if (existCalendarData.showLabelAlways && date.showTodoLabel) {
233
+                date.showTodoLabel = true
234
+              } else {
235
+                date.showTodoLabel = false
236
+              }
237
+              return date
238
+            })
239
+          }
240
+          return renderCalendar.call(component, {
241
+            ...existCalendarData,
242
+            dates: __dates,
243
+            selectedDates: __selectedDates
244
+          })
245
+        },
246
+        setDateStyle: toSetDates => {
247
+          if (!Array.isArray(toSetDates)) return Promise.reject()
248
+          const existCalendarData = getCalendarData('calendar', component)
249
+          const { dates = [], specialStyleDates } = existCalendarData || {}
250
+          if (Array.isArray(specialStyleDates)) {
251
+            toSetDates = dateUtil.uniqueArrayByDate([
252
+              ...specialStyleDates,
253
+              ...toSetDates
254
+            ])
255
+          }
256
+          const toSetDatesStr = toSetDates.map(item => dateUtil.toTimeStr(item))
257
+          const _dates = dates.map(item => {
258
+            const idx = toSetDatesStr.indexOf(dateUtil.toTimeStr(item))
259
+            if (idx > -1) {
260
+              return {
261
+                ...item,
262
+                class: toSetDates[idx].class
263
+              }
264
+            } else {
265
+              return item
266
+            }
267
+          })
268
+          return renderCalendar.call(component, {
269
+            ...existCalendarData,
270
+            dates: _dates,
271
+            specialStyleDates: toSetDates
272
+          })
273
+        }
274
+      }
275
+    }
276
+  }
277
+}

+ 69 - 0
components/local/v2/plugins/preset/get-calendar-data.js

@@ -0,0 +1,69 @@
1
+/**
2
+ * @Author: drfu*
3
+ * @Description: 获取日历数据
4
+ * @Date: 2020-10-08 21:22:09*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-11 13:42:37
7
+ * */
8
+
9
+import { getCalendarData, logger, getCalendarConfig } from '../../utils/index'
10
+
11
+function wrapDateWithLunar(dates = [], convertFn) {
12
+  const datesWithLunar = JSON.parse(JSON.stringify(dates)).map(date => ({
13
+    ...date,
14
+    lunar: convertFn(date)
15
+  }))
16
+  return datesWithLunar
17
+}
18
+
19
+export default () => {
20
+  return {
21
+    name: 'getData',
22
+    methods(component) {
23
+      return {
24
+        getCurrentYM: () => {
25
+          const { curYear, curMonth } = getCalendarData('calendar', component)
26
+          return {
27
+            year: curYear,
28
+            month: curMonth
29
+          }
30
+        },
31
+        getSelectedDates: (options = {}) => {
32
+          const dates =
33
+            getCalendarData('calendar.selectedDates', component) || []
34
+          const config = getCalendarConfig(component) || {}
35
+          if (options.lunar && !config.showLunar) {
36
+            const injectedFns = component.calendar || {}
37
+            if (typeof injectedFns.convertSolarLunar === 'function') {
38
+              return wrapDateWithLunar(dates, injectedFns.convertSolarLunar)
39
+            } else {
40
+              logger.warn('获取农历信息需引入农历插件')
41
+            }
42
+          } else {
43
+            return dates
44
+          }
45
+        },
46
+        getCalendarDates: (options = {}) => {
47
+          const config = getCalendarConfig(component) || {}
48
+          const dates = getCalendarData('calendar.dates', component)
49
+          if (options.lunar && !config.showLunar) {
50
+            const injectedFns = component.calendar || {}
51
+            if (typeof injectedFns.convertSolarLunar === 'function') {
52
+              return wrapDateWithLunar(dates, injectedFns.convertSolarLunar)
53
+            } else {
54
+              logger.warn('获取农历信息需引入农历插件')
55
+            }
56
+          } else {
57
+            return dates
58
+          }
59
+        },
60
+        getCalendarAllData: () => {
61
+          return {
62
+            data: getCalendarData('calendar', component) || {},
63
+            config: getCalendarConfig(component) || {}
64
+          }
65
+        }
66
+      }
67
+    }
68
+  }
69
+}

+ 9 - 0
components/local/v2/plugins/preset/index.js

@@ -0,0 +1,9 @@
1
+import base from './base'
2
+import getCalendarData from './get-calendar-data'
3
+
4
+const preset = [
5
+  ['base', base()],
6
+  ['get-calendar-data', getCalendarData()]
7
+]
8
+
9
+export default preset

+ 219 - 0
components/local/v2/plugins/selectable.js

@@ -0,0 +1,219 @@
1
+/**
2
+ * @Author: drfu*
3
+ * @Description: 禁用、启用日期选择
4
+ * @Date: 2020-10-08 21:22:09*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-08 21:25:00
7
+ * */
8
+
9
+import { getCalendarData, dateUtil, logger } from '../utils/index'
10
+import { renderCalendar } from '../render'
11
+
12
+function convertEnableAreaToTimestamp(timearea = []) {
13
+  const start = timearea[0].split('-')
14
+  const end = timearea[1].split('-')
15
+  if (start.length !== 3 || end.length !== 3) {
16
+    logger.warn('enableArea() 参数格式为: ["2018-2-1", "2018-3-1"]')
17
+    return {}
18
+  }
19
+  const startTimestamp = dateUtil
20
+    .newDate(start[0], start[1], start[2])
21
+    .getTime()
22
+  const endTimestamp = dateUtil.newDate(end[0], end[1], end[2]).getTime()
23
+  return {
24
+    start,
25
+    end,
26
+    startTimestamp,
27
+    endTimestamp
28
+  }
29
+}
30
+
31
+function isValiditeOfDateArea(dateArea) {
32
+  const {
33
+    start,
34
+    end,
35
+    startTimestamp,
36
+    endTimestamp
37
+  } = convertEnableAreaToTimestamp(dateArea)
38
+  if (!start || !end) return
39
+  const datesCountOfStart = dateUtil.getDatesCountOfMonth(start[0], start[1])
40
+  const datesCountOfEnd = dateUtil.getDatesCountOfMonth(end[0], end[1])
41
+  if (start[2] > datesCountOfStart || start[2] < 1) {
42
+    logger.warn('enableArea() 开始日期错误,指定日期不在指定月份天数范围内')
43
+    return false
44
+  } else if (start[1] > 12 || start[1] < 1) {
45
+    logger.warn('enableArea() 开始日期错误,月份超出1-12月份')
46
+    return false
47
+  } else if (end[2] > datesCountOfEnd || end[2] < 1) {
48
+    logger.warn('enableArea() 截止日期错误,指定日期不在指定月份天数范围内')
49
+    return false
50
+  } else if (end[1] > 12 || end[1] < 1) {
51
+    logger.warn('enableArea() 截止日期错误,月份超出1-12月份')
52
+    return false
53
+  } else if (startTimestamp > endTimestamp) {
54
+    logger.warn('enableArea()参数最小日期大于了最大日期')
55
+    return false
56
+  } else {
57
+    return true
58
+  }
59
+}
60
+
61
+function handleDisableMode(calendarConfig) {
62
+  const { disableMode } = calendarConfig
63
+  if (!disableMode) return {}
64
+  const disableBound =
65
+    dateUtil.getTimeStamp(disableMode.date) || dateUtil.todayTimestamp()
66
+  return {
67
+    disableBound,
68
+    disableType: disableMode.type
69
+  }
70
+}
71
+
72
+function disabledByConfig(dateInfo, currentDate, calendarConfig) {
73
+  const date = { ...dateInfo }
74
+  const { disableType, disableBound } = handleDisableMode(calendarConfig)
75
+  if (
76
+    (disableType === 'before' && disableBound && currentDate < disableBound) ||
77
+    (disableType === 'after' && disableBound && currentDate > disableBound)
78
+  ) {
79
+    date.disable = true
80
+  } else {
81
+    date.disable = false
82
+  }
83
+  return date
84
+}
85
+
86
+export default () => {
87
+  return {
88
+    name: 'enable',
89
+    beforeRender(calendarData = {}, calendarConfig = {}) {
90
+      const {
91
+        dates,
92
+        enableArea,
93
+        enableDates,
94
+        disableDates,
95
+        renderCausedBy
96
+      } = calendarData
97
+      const _dates = [...dates].map(date => {
98
+        let item = { ...date }
99
+        const timeStr = dateUtil.toTimeStr(date)
100
+        const timestamp = +dateUtil.getTimeStamp(item)
101
+        if (renderCausedBy === 'enableDates') {
102
+          if (enableDates && enableDates.length) {
103
+            if (enableDates.includes(timeStr)) {
104
+              item.disable = false
105
+            } else {
106
+              item.disable = true
107
+            }
108
+            return item
109
+          }
110
+        } else if (renderCausedBy === 'enableArea') {
111
+          if (enableArea && enableArea.length) {
112
+            const [startTimestamp, endTimestamp] = enableArea || []
113
+            const ifOutofArea =
114
+              +startTimestamp > timestamp || timestamp > +endTimestamp
115
+            item.disable = ifOutofArea
116
+            return item
117
+          }
118
+        } else if (renderCausedBy === 'disableDates') {
119
+          if (disableDates && disableDates.length) {
120
+            if (disableDates && disableDates.includes(timeStr)) {
121
+              item.disable = true
122
+            }
123
+            return item
124
+          }
125
+        }
126
+        return disabledByConfig(item, timestamp, calendarConfig)
127
+      })
128
+
129
+      return {
130
+        calendarData: {
131
+          ...calendarData,
132
+          dates: _dates
133
+        },
134
+        calendarConfig
135
+      }
136
+    },
137
+    methods(component) {
138
+      return {
139
+        enableArea: (dateArea = []) => {
140
+          if (dateArea.length === 2) {
141
+            const validate = isValiditeOfDateArea(dateArea)
142
+            if (validate) {
143
+              const existCalendarData = getCalendarData('calendar', component)
144
+              const {
145
+                startTimestamp,
146
+                endTimestamp
147
+              } = convertEnableAreaToTimestamp(dateArea)
148
+
149
+              return renderCalendar.call(component, {
150
+                ...existCalendarData,
151
+                renderCausedBy: 'enableArea',
152
+                enableArea: [startTimestamp, endTimestamp]
153
+              })
154
+            }
155
+          } else {
156
+            return Promise.inject(
157
+              'enableArea()参数需为时间范围数组,形如:["2018-8-4" , "2018-8-24"]'
158
+            )
159
+          }
160
+        },
161
+        enableDates: (toSet = []) => {
162
+          if (!toSet.length) return
163
+          const existCalendarData = getCalendarData('calendar', component)
164
+          const { enableDates = [] } = existCalendarData || {}
165
+          let toSetDates = toSet.map(item => {
166
+            if (typeof item === 'string') {
167
+              return dateUtil.transformDateRow2Dict(item)
168
+            }
169
+            return item
170
+          })
171
+          if (enableDates.length) {
172
+            toSetDates = dateUtil.uniqueArrayByDate([
173
+              ...toSetDates,
174
+              ...enableDates.map(d => dateUtil.transformDateRow2Dict(d))
175
+            ])
176
+          }
177
+          return renderCalendar.call(component, {
178
+            ...existCalendarData,
179
+            renderCausedBy: 'enableDates',
180
+            enableDates: toSetDates.map(date => {
181
+              if (typeof date !== 'string') {
182
+                return dateUtil.toTimeStr(date)
183
+              }
184
+              return date
185
+            })
186
+          })
187
+        },
188
+        disableDates: toSet => {
189
+          const existCalendarData = getCalendarData('calendar', component)
190
+          const { disableDates = [], dates = [] } = existCalendarData || {}
191
+          let toSetDates = toSet.map(item => {
192
+            let date = { ...item }
193
+            if (typeof date === 'string') {
194
+              return dateUtil.transformDateRow2Dict(item)
195
+            }
196
+            return item
197
+          })
198
+          if (disableDates && disableDates.length) {
199
+            toSetDates = dateUtil.uniqueArrayByDate([
200
+              ...toSetDates,
201
+              ...disableDates.map(d => dateUtil.transformDateRow2Dict(d))
202
+            ])
203
+          }
204
+          return renderCalendar.call(component, {
205
+            ...existCalendarData,
206
+            renderCausedBy: 'disableDates',
207
+            dates,
208
+            disableDates: toSetDates.map(date => {
209
+              if (typeof date !== 'string') {
210
+                return dateUtil.toTimeStr(date)
211
+              }
212
+              return date
213
+            })
214
+          })
215
+        }
216
+      }
217
+    }
218
+  }
219
+}

Diff nebyl zobrazen, protože je příliš veliký
+ 1036 - 0
components/local/v2/plugins/solarLunar/convertSolarLunar.js


+ 59 - 0
components/local/v2/plugins/solarLunar/index.js

@@ -0,0 +1,59 @@
1
+import { dateUtil } from '../../utils/index'
2
+import convertSolarLunar from './convertSolarLunar'
3
+
4
+function getDateRow2Dict(dateInfo) {
5
+  if (!dateInfo) return dateInfo
6
+  if (typeof dateInfo === 'string' && dateInfo.includes('-')) {
7
+    dateInfo = dateUtil.transformDateRow2Dict(dateInfo)
8
+  }
9
+  return dateInfo
10
+}
11
+
12
+export default () => {
13
+  return {
14
+    name: 'convertSolarLunar',
15
+    beforeRender(calendarData = {}, calendarConfig = {}) {
16
+      let { dates = [], selectedDates = [] } = calendarData
17
+      if (calendarConfig.showLunar) {
18
+        dates = dates.map(dataInfo => {
19
+          const { year, month, date } = dataInfo
20
+          return {
21
+            ...dataInfo,
22
+            lunar: convertSolarLunar.solar2lunar(year, month, date)
23
+          }
24
+        })
25
+        selectedDates = selectedDates.map(dataInfo => {
26
+          const { year, month, date } = dataInfo
27
+          return {
28
+            ...dataInfo,
29
+            lunar: convertSolarLunar.solar2lunar(year, month, date)
30
+          }
31
+        })
32
+      }
33
+      return {
34
+        calendarData: {
35
+          ...calendarData,
36
+          dates: dates,
37
+          selectedDates: selectedDates
38
+        },
39
+        calendarConfig
40
+      }
41
+    },
42
+    methods() {
43
+      return {
44
+        convertSolarLunar: dateInfo => {
45
+          dateInfo = getDateRow2Dict(dateInfo)
46
+          if (!dateInfo) return dateInfo
47
+          const { year, month, date } = dateInfo
48
+          return convertSolarLunar.solar2lunar(year, month, date)
49
+        },
50
+        convertlLunar2Solar: (dateInfo, isLeapMonth) => {
51
+          dateInfo = getDateRow2Dict(dateInfo)
52
+          if (!dateInfo) return dateInfo
53
+          const { year, month, date } = dateInfo
54
+          return convertSolarLunar.lunar2solar(year, month, date, isLeapMonth)
55
+        }
56
+      }
57
+    }
58
+  }
59
+}

+ 305 - 0
components/local/v2/plugins/time-range.js

@@ -0,0 +1,305 @@
1
+/**
2
+ * @Author: drfu*
3
+ * @Description: 时间区域选择
4
+ * @Date: 2020-10-08 21:22:09*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-11 13:56:32
7
+ * */
8
+
9
+import { renderCalendar } from '../render'
10
+import {
11
+  logger,
12
+  dateUtil,
13
+  getCalendarConfig,
14
+  getCalendarData
15
+} from '../utils/index'
16
+
17
+function pusheNextMonthDateArea(
18
+  dateInfo = {},
19
+  startTimestamp,
20
+  endTimestamp,
21
+  selectedDates = []
22
+) {
23
+  let tempOfSelectedDate = [...selectedDates]
24
+  const dates = dateUtil.calcDates(dateInfo.year, dateInfo.month)
25
+  let datesLen = dates.length
26
+  for (let i = 0; i < datesLen; i++) {
27
+    const date = dates[i]
28
+    const timeStamp = dateUtil.getTimeStamp(date)
29
+    if (timeStamp <= endTimestamp && timeStamp >= startTimestamp) {
30
+      tempOfSelectedDate.push({
31
+        ...date,
32
+        choosed: true
33
+      })
34
+    }
35
+    if (i === datesLen - 1 && timeStamp < endTimestamp) {
36
+      pusheNextMonthDateArea(
37
+        dateUtil.getNextMonthInfo(date),
38
+        startTimestamp,
39
+        endTimestamp,
40
+        tempOfSelectedDate
41
+      )
42
+    }
43
+  }
44
+  return tempOfSelectedDate
45
+}
46
+function pushPrevMonthDateArea(
47
+  dateInfo = {},
48
+  startTimestamp,
49
+  endTimestamp,
50
+  selectedDates = []
51
+) {
52
+  let tempOfSelectedDate = [...selectedDates]
53
+  const dates = dateUtil.sortDatesByTime(
54
+    dateUtil.calcDates(dateInfo.year, dateInfo.month),
55
+    'desc'
56
+  )
57
+  let datesLen = dates.length
58
+  let firstDate = dateUtil.getTimeStamp(dates[0])
59
+  for (let i = 0; i < datesLen; i++) {
60
+    const date = dates[i]
61
+    const timeStamp = dateUtil.getTimeStamp(date)
62
+    if (timeStamp >= startTimestamp && timeStamp <= endTimestamp) {
63
+      tempOfSelectedDate.push({
64
+        ...date,
65
+        choosed: true
66
+      })
67
+    }
68
+    if (i === datesLen - 1 && firstDate > startTimestamp) {
69
+      pushPrevMonthDateArea(
70
+        dateUtil.getPrevMonthInfo(date),
71
+        startTimestamp,
72
+        endTimestamp,
73
+        tempOfSelectedDate
74
+      )
75
+    }
76
+  }
77
+  return tempOfSelectedDate
78
+}
79
+/**
80
+ * 当设置日期区域非当前时保存其他月份的日期至已选日期数组
81
+ * @param {object} info
82
+ */
83
+function calcDateWhenNotInOneMonth(info) {
84
+  const { firstDate, lastDate, startTimestamp, endTimestamp } = info
85
+  let { selectedDate } = info
86
+  if (dateUtil.getTimeStamp(firstDate) > startTimestamp) {
87
+    selectedDate = pushPrevMonthDateArea(
88
+      dateUtil.getPrevMonthInfo(firstDate),
89
+      startTimestamp,
90
+      endTimestamp,
91
+      selectedDate
92
+    )
93
+  }
94
+  if (dateUtil.getTimeStamp(lastDate) < endTimestamp) {
95
+    selectedDate = pusheNextMonthDateArea(
96
+      dateUtil.getNextMonthInfo(lastDate),
97
+      startTimestamp,
98
+      endTimestamp,
99
+      selectedDate
100
+    )
101
+  }
102
+  return [...selectedDate]
103
+}
104
+
105
+/**
106
+ *  指定日期区域转时间戳
107
+ * @param {array} timearea 时间区域
108
+ */
109
+export function convertTimeRangeToTimestamp(timearea = []) {
110
+  const start = timearea[0].split('-')
111
+  const end = timearea[1].split('-')
112
+  if (start.length !== 3 || end.length !== 3) {
113
+    logger.warn('enableArea() 参数格式为: ["2018-2-1", "2018-3-1"]')
114
+    return {}
115
+  }
116
+  const startTimestamp = dateUtil
117
+    .newDate(start[0], start[1], start[2])
118
+    .getTime()
119
+  const endTimestamp = dateUtil.newDate(end[0], end[1], end[2]).getTime()
120
+  return {
121
+    start,
122
+    end,
123
+    startTimestamp,
124
+    endTimestamp
125
+  }
126
+}
127
+
128
+/**
129
+ * 校验时间区域是否合法
130
+ * @param {array} dateArea 时间区域
131
+ */
132
+function validateTimeRange(dateArea) {
133
+  const {
134
+    start,
135
+    end,
136
+    startTimestamp,
137
+    endTimestamp
138
+  } = convertTimeRangeToTimestamp(dateArea)
139
+  if (!start || !end) return
140
+  const startMonthDays = dateUtil.getDatesCountOfMonth(start[0], start[1])
141
+  const endMonthDays = dateUtil.getDatesCountOfMonth(end[0], end[1])
142
+  if (start[2] > startMonthDays || start[2] < 1) {
143
+    logger.warn('enableArea() 开始日期错误,指定日期不在当前月份天数范围内')
144
+    return false
145
+  } else if (start[1] > 12 || start[1] < 1) {
146
+    logger.warn('enableArea() 开始日期错误,月份超出1-12月份')
147
+    return false
148
+  } else if (end[2] > endMonthDays || end[2] < 1) {
149
+    logger.warn('enableArea() 截止日期错误,指定日期不在当前月份天数范围内')
150
+    return false
151
+  } else if (end[1] > 12 || end[1] < 1) {
152
+    logger.warn('enableArea() 截止日期错误,月份超出1-12月份')
153
+    return false
154
+  } else if (startTimestamp > endTimestamp) {
155
+    logger.warn('enableArea()参数最小日期大于了最大日期')
156
+    return false
157
+  } else {
158
+    return true
159
+  }
160
+}
161
+
162
+export default () => {
163
+  return {
164
+    name: 'timeRange',
165
+    beforeRender(calendarData = {}, calendarConfig = {}) {
166
+      const {
167
+        chooseAreaTimestamp = [],
168
+        dates = [],
169
+        selectedDates = []
170
+      } = calendarData
171
+      let __dates = dates
172
+      let __selectedDates = selectedDates
173
+      const [startDateTimestamp, endDateTimestamp] = chooseAreaTimestamp
174
+      if (chooseAreaTimestamp.length === 2) {
175
+        __selectedDates = []
176
+        __dates = dates.map(d => {
177
+          const date = { ...d }
178
+          const dateTimeStamp = dateUtil.getTimeStamp(date)
179
+          if (
180
+            dateTimeStamp >= startDateTimestamp &&
181
+            endDateTimestamp >= dateTimeStamp
182
+          ) {
183
+            date.choosed = true
184
+            __selectedDates.push(date)
185
+          } else {
186
+            date.choosed = false
187
+            __selectedDates = __selectedDates.filter(
188
+              item => dateUtil.getTimeStamp(item) !== dateTimeStamp
189
+            )
190
+          }
191
+          return date
192
+        })
193
+        const monthOfStartDate = new Date(startDateTimestamp).getMonth()
194
+        const monthOfEndDate = new Date(endDateTimestamp).getMonth()
195
+        if (monthOfStartDate !== monthOfEndDate) {
196
+          __selectedDates = calcDateWhenNotInOneMonth({
197
+            firstDate: __dates[0],
198
+            lastDate: __dates[__dates.length - 1],
199
+            startTimestamp: startDateTimestamp,
200
+            endTimestamp: endDateTimestamp,
201
+            selectedDate: __selectedDates
202
+          })
203
+        }
204
+      }
205
+      return {
206
+        calendarData: {
207
+          ...calendarData,
208
+          dates: __dates,
209
+          selectedDates: dateUtil.sortDatesByTime(
210
+            dateUtil.uniqueArrayByDate(__selectedDates)
211
+          )
212
+        },
213
+        calendarConfig
214
+      }
215
+    },
216
+    onTapDate(tapedDate, calendarData = {}, calendarConfig = {}) {
217
+      if (!calendarConfig.chooseAreaMode) {
218
+        return {
219
+          calendarData,
220
+          calendarConfig
221
+        }
222
+      }
223
+      let {
224
+        tempChooseAreaTimestamp = [],
225
+        chooseAreaTimestamp: existChooseAreaTimestamp = [],
226
+        selectedDates = [],
227
+        dates = []
228
+      } = calendarData
229
+      const timestamp = dateUtil.getTimeStamp(tapedDate)
230
+      let __dates = [...dates]
231
+      let __selectedDates = [...selectedDates]
232
+      if (
233
+        tempChooseAreaTimestamp.length === 2 ||
234
+        existChooseAreaTimestamp.length === 2
235
+      ) {
236
+        tempChooseAreaTimestamp = [tapedDate]
237
+        __selectedDates = []
238
+        __dates.forEach(d => (d.choosed = false))
239
+      } else if (tempChooseAreaTimestamp.length === 1) {
240
+        const preChoosedDate = tempChooseAreaTimestamp[0]
241
+        const preTimestamp = dateUtil.getTimeStamp(preChoosedDate)
242
+        if (preTimestamp <= timestamp) {
243
+          tempChooseAreaTimestamp.push(tapedDate)
244
+        } else if (preTimestamp > timestamp) {
245
+          tempChooseAreaTimestamp.unshift(tapedDate)
246
+        }
247
+      } else {
248
+        tempChooseAreaTimestamp = [tapedDate]
249
+      }
250
+      let chooseAreaTimestamp = []
251
+      if (tempChooseAreaTimestamp.length === 2) {
252
+        const [startDate, endDate] = tempChooseAreaTimestamp
253
+        const startDateTimestamp = dateUtil.getTimeStamp(startDate)
254
+        const endDateTimestamp = dateUtil.getTimeStamp(endDate)
255
+        chooseAreaTimestamp = [startDateTimestamp, endDateTimestamp]
256
+      }
257
+      return {
258
+        calendarData: {
259
+          ...calendarData,
260
+          chooseAreaTimestamp,
261
+          tempChooseAreaTimestamp,
262
+          dates: __dates,
263
+          selectedDates: __selectedDates
264
+        },
265
+        calendarConfig: {
266
+          ...calendarConfig,
267
+          multi: true
268
+        }
269
+      }
270
+    },
271
+    methods(component) {
272
+      return {
273
+        /**
274
+         * 设置连续日期选择区域
275
+         * @param {array} dateArea 区域开始结束日期数组
276
+         */
277
+        chooseDateArea: (dateArea = []) => {
278
+          if (dateArea.length === 1) {
279
+            dateArea = dateArea.concat(dateArea)
280
+          }
281
+          if (dateArea.length !== 2) return
282
+          const isRight = validateTimeRange(dateArea)
283
+          if (!isRight) return
284
+          const config = getCalendarConfig(component) || {}
285
+          const { startTimestamp, endTimestamp } = convertTimeRangeToTimestamp(
286
+            dateArea
287
+          )
288
+          const existCalendarData = getCalendarData('calendar', component)
289
+          return renderCalendar.call(
290
+            component,
291
+            {
292
+              ...existCalendarData,
293
+              chooseAreaTimestamp: [startTimestamp, endTimestamp]
294
+            },
295
+            {
296
+              ...config,
297
+              multi: true,
298
+              chooseAreaMode: true
299
+            }
300
+          )
301
+        }
302
+      }
303
+    }
304
+  }
305
+}

+ 135 - 0
components/local/v2/plugins/todo.js

@@ -0,0 +1,135 @@
1
+/**
2
+ * @Author: drfu*
3
+ * @Description: 代办事项
4
+ * @Date: 2020-10-08 21:22:09*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-11 14:23:02
7
+ * */
8
+
9
+import { getCalendarData, dateUtil } from '../utils/index'
10
+import { renderCalendar } from '../render'
11
+
12
+function updateDatePropertyOfTodoLabel(todos, dates, showLabelAlways) {
13
+  const datesInfo = [...dates]
14
+  for (let todo of todos) {
15
+    let targetIdx = datesInfo.findIndex(
16
+      item => dateUtil.toTimeStr(item) === dateUtil.toTimeStr(todo)
17
+    )
18
+    let target = datesInfo[targetIdx]
19
+    if (!target) continue
20
+    if (showLabelAlways) {
21
+      target.showTodoLabel = true
22
+    } else {
23
+      target.showTodoLabel = !target.choosed
24
+    }
25
+    if (target.showTodoLabel) {
26
+      target.todoText = todo.todoText
27
+    }
28
+    target.color = todo.color
29
+  }
30
+  return datesInfo
31
+}
32
+
33
+export default () => {
34
+  return {
35
+    name: 'todo',
36
+    beforeRender(calendarData = {}, calendarConfig = {}, component) {
37
+      const { todos = [], dates = [], showLabelAlways } = calendarData
38
+      const dateWithTodoInfo = updateDatePropertyOfTodoLabel(
39
+        todos,
40
+        dates,
41
+        showLabelAlways
42
+      )
43
+      return {
44
+        calendarData: {
45
+          ...calendarData,
46
+          dates: dateWithTodoInfo
47
+        },
48
+        calendarConfig
49
+      }
50
+    },
51
+    methods(component) {
52
+      return {
53
+        setTodos: (options = {}) => {
54
+          const calendar = getCalendarData('calendar', component)
55
+          if (!calendar || !calendar.dates) {
56
+            return Promise.reject('请等待日历初始化完成后再调用该方法')
57
+          }
58
+          const {
59
+            circle,
60
+            dotColor = '',
61
+            pos = 'bottom',
62
+            showLabelAlways,
63
+            dates: todoDates = []
64
+          } = options
65
+          const { todos = [] } = calendar
66
+          const tranformStr2NumOfTodo = todoDates.map(date =>
67
+            dateUtil.tranformStr2NumOfDate(date)
68
+          )
69
+          const calendarData = {
70
+            dates: calendar.dates,
71
+            todos: dateUtil.uniqueArrayByDate(
72
+              todos.concat(tranformStr2NumOfTodo)
73
+            )
74
+          }
75
+          if (!circle) {
76
+            calendarData.todoLabelPos = pos
77
+            calendarData.todoLabelColor = dotColor
78
+          }
79
+          calendarData.todoLabelCircle = circle || false
80
+          calendarData.showLabelAlways = showLabelAlways || false
81
+          const existCalendarData = getCalendarData('calendar', component)
82
+          return renderCalendar.call(component, {
83
+            ...existCalendarData,
84
+            ...calendarData
85
+          })
86
+        },
87
+        deleteTodos(todos = []) {
88
+          if (!(todos instanceof Array) || !todos.length)
89
+            return Promise.reject('deleteTodos()应为入参为非空数组')
90
+          const existCalendarData = getCalendarData('calendar', component)
91
+          const allTodos = existCalendarData.todos || []
92
+          const toDeleteTodos = todos.map(item => dateUtil.toTimeStr(item))
93
+          const remainTodos = allTodos.filter(
94
+            item => !toDeleteTodos.includes(dateUtil.toTimeStr(item))
95
+          )
96
+          const { dates, curYear, curMonth } = existCalendarData
97
+          const _dates = [...dates]
98
+          const currentMonthTodos = dateUtil.filterDatesByYM(
99
+            {
100
+              year: curYear,
101
+              month: curMonth
102
+            },
103
+            remainTodos
104
+          )
105
+          _dates.forEach(item => {
106
+            item.showTodoLabel = false
107
+          })
108
+          currentMonthTodos.forEach(item => {
109
+            _dates[item.date - 1].showTodoLabel = !_dates[item.date - 1].choosed
110
+          })
111
+          return renderCalendar.call(component, {
112
+            ...existCalendarData,
113
+            dates: _dates,
114
+            todos: remainTodos
115
+          })
116
+        },
117
+        clearTodos() {
118
+          const existCalendarData = getCalendarData('calendar', component)
119
+          const _dates = [...existCalendarData.dates]
120
+          _dates.forEach(item => {
121
+            item.showTodoLabel = false
122
+          })
123
+          return renderCalendar.call(component, {
124
+            ...existCalendarData,
125
+            dates: _dates,
126
+            todos: []
127
+          })
128
+        },
129
+        getTodos() {
130
+          return getCalendarData('calendar.todos', component) || []
131
+        }
132
+      }
133
+    }
134
+  }
135
+}

+ 432 - 0
components/local/v2/plugins/week.js

@@ -0,0 +1,432 @@
1
+/**
2
+ * @Author: drfu*
3
+ * @Description: 周视图
4
+ * @Date: 2020-10-08 21:22:09*
5
+ * @Last Modified by: drfu
6
+ * @Last Modified time: 2020-10-12 14:39:45
7
+ * */
8
+
9
+import { renderCalendar } from '../render'
10
+import {
11
+  getCalendarConfig,
12
+  getCalendarData,
13
+  logger,
14
+  dateUtil
15
+} from '../utils/index'
16
+import { calcJumpData } from '../core'
17
+
18
+/**
19
+ * 当月第一周所有日期
20
+ */
21
+function firstWeekInMonth(
22
+  target = {},
23
+  calendarDates = [],
24
+  calendarConfig = {}
25
+) {
26
+  const { firstDayOfWeek } = calendarConfig
27
+  const firstDayOfWeekIsMon = firstDayOfWeek === 'Mon'
28
+  const { year, month } = target
29
+  let firstDay = dateUtil.getDayOfWeek(year, month, 1)
30
+  if (firstDayOfWeekIsMon && firstDay === 0) {
31
+    firstDay = 7
32
+  }
33
+  const [, end] = [0, 7 - firstDay]
34
+  return calendarDates.slice(0, firstDayOfWeekIsMon ? end + 1 : end)
35
+}
36
+
37
+/**
38
+ * 当月最后一周所有日期
39
+ */
40
+function lastWeekInMonth(target = {}, calendarDates = [], calendarConfig = {}) {
41
+  const { firstDayOfWeek } = calendarConfig
42
+  const firstDayOfWeekIsMon = firstDayOfWeek === 'Mon'
43
+  const { year, month } = target
44
+  const lastDay = dateUtil.getDatesCountOfMonth(year, month)
45
+  let lastDayWeek = dateUtil.getDayOfWeek(year, month, lastDay)
46
+  if (firstDayOfWeekIsMon && lastDayWeek === 0) {
47
+    lastDayWeek = 7
48
+  }
49
+  const [start, end] = [lastDay - lastDayWeek, lastDay]
50
+  return calendarDates.slice(firstDayOfWeekIsMon ? start : start - 1, end)
51
+}
52
+
53
+/**
54
+ * 判断目标日期是否在某些指定日历内
55
+ */
56
+function dateIsInDatesRange(target, dates) {
57
+  if (!target || !dates || !dates.length) return false
58
+  const targetDateStr = dateUtil.toTimeStr(target)
59
+  let rst = false
60
+  for (let date of dates) {
61
+    const dateStr = dateUtil.toTimeStr(date)
62
+    if (dateStr === targetDateStr) {
63
+      rst = true
64
+      return rst
65
+    }
66
+    rst = false
67
+  }
68
+  return rst
69
+}
70
+
71
+function getDatesWhenTargetInFirstWeek(target, firstWeekDates) {
72
+  const { year, month } = target
73
+  const prevMonthInfo = dateUtil.getPrevMonthInfo({ year, month })
74
+  let lastMonthDatesCount = dateUtil.getDatesCountOfMonth(
75
+    prevMonthInfo.year,
76
+    prevMonthInfo.month
77
+  )
78
+  let dates = firstWeekDates
79
+  let firstWeekCount = firstWeekDates.length
80
+  for (let i = 0; i < 7 - firstWeekCount; i++) {
81
+    const week = dateUtil.getDayOfWeek(+year, +month, lastMonthDatesCount)
82
+    dates.unshift({
83
+      year: prevMonthInfo.year,
84
+      month: prevMonthInfo.month,
85
+      date: lastMonthDatesCount,
86
+      week
87
+    })
88
+    lastMonthDatesCount -= 1
89
+  }
90
+  return dates
91
+}
92
+
93
+function getDatesWhenTargetInLastWeek(target, lastWeekDates) {
94
+  const { year, month } = target
95
+  const prevMonthInfo = dateUtil.getNextMonthInfo({ year, month })
96
+  let dates = lastWeekDates
97
+  let lastWeekCount = lastWeekDates.length
98
+  for (let i = 0; i < 7 - lastWeekCount; i++) {
99
+    const week = dateUtil.getDayOfWeek(+year, +month, i + 1)
100
+    dates.push({
101
+      year: prevMonthInfo.year,
102
+      month: prevMonthInfo.month,
103
+      date: i + 1,
104
+      week
105
+    })
106
+  }
107
+  return dates
108
+}
109
+
110
+function getDates(target, calendarDates = [], calendarConfig = {}) {
111
+  const { year, month, date } = target
112
+  const targetDay = dateUtil.getDayOfWeek(year, month, date)
113
+  const { firstDayOfWeek } = calendarConfig
114
+  const firstDayOfWeekIsMon = firstDayOfWeek === 'Mon'
115
+  if (firstDayOfWeekIsMon) {
116
+    const startIdx = date - (targetDay || 7)
117
+    return calendarDates.splice(startIdx, 7)
118
+  } else {
119
+    const startIdx = date - targetDay - 1
120
+    return calendarDates.splice(startIdx, 7)
121
+  }
122
+}
123
+
124
+function getTargetWeekDates(target, calendarConfig) {
125
+  if (!target) return
126
+  const { year, month } = target
127
+  const calendarDates = dateUtil.calcDates(year, month)
128
+  const firstWeekDates = firstWeekInMonth(target, calendarDates, calendarConfig)
129
+  const lastWeekDates = lastWeekInMonth(target, calendarDates, calendarConfig)
130
+  if (dateIsInDatesRange(target, firstWeekDates)) {
131
+    return getDatesWhenTargetInFirstWeek(target, firstWeekDates)
132
+  } else if (dateIsInDatesRange(target, lastWeekDates)) {
133
+    return getDatesWhenTargetInLastWeek(target, lastWeekDates)
134
+  } else {
135
+    return getDates(target, calendarDates, calendarConfig)
136
+  }
137
+}
138
+
139
+/**
140
+ * 计算周视图下当前这一周最后一天
141
+ */
142
+function calculateLastDateOfCurrentWeek(calendarData = {}) {
143
+  const { dates = [] } = calendarData
144
+  return dates[dates.length - 1]
145
+}
146
+/**
147
+ * 计算周视图下当前这一周第一天
148
+ */
149
+function calculateFirstDateOfCurrentWeek(calendarData = {}) {
150
+  const { dates } = calendarData
151
+  return dates[0]
152
+}
153
+
154
+/**
155
+ * 计算下一周的日期
156
+ */
157
+function calculateNextWeekDates(calendarData = {}) {
158
+  let { curYear, curMonth } = calendarData
159
+  let calendarDates = []
160
+  let lastDateInThisWeek = calculateLastDateOfCurrentWeek(calendarData)
161
+  const { year: LYear, month: LMonth } = lastDateInThisWeek
162
+  if (curYear !== LYear || curMonth !== LMonth) {
163
+    calendarDates = dateUtil.calcDates(LYear, LMonth)
164
+    curYear = LYear
165
+    curMonth = LMonth
166
+  } else {
167
+    calendarDates = dateUtil.calcDates(curYear, curMonth)
168
+  }
169
+  const lastDateInThisMonth = dateUtil.getDatesCountOfMonth(curYear, curMonth)
170
+  const count = lastDateInThisMonth - lastDateInThisWeek.date
171
+  const lastDateIdx = calendarDates.findIndex(
172
+    date => dateUtil.toTimeStr(date) === dateUtil.toTimeStr(lastDateInThisWeek)
173
+  )
174
+  const startIdx = lastDateIdx + 1
175
+  if (count >= 7) {
176
+    return {
177
+      dates: calendarDates.splice(startIdx, 7),
178
+      year: curYear,
179
+      month: curMonth
180
+    }
181
+  } else {
182
+    const nextMonth = dateUtil.getNextMonthInfo({
183
+      year: curYear,
184
+      month: curMonth
185
+    })
186
+    const { year, month } = nextMonth || {}
187
+    const calendarDatesOfNextMonth = dateUtil.calcDates(year, month)
188
+    const remainDatesOfThisMonth = calendarDates.splice(startIdx)
189
+    const patchDatesOfNextMonth = calendarDatesOfNextMonth.splice(
190
+      0,
191
+      7 - remainDatesOfThisMonth.length
192
+    )
193
+    return {
194
+      dates: [...remainDatesOfThisMonth, ...patchDatesOfNextMonth],
195
+      ...nextMonth
196
+    }
197
+  }
198
+}
199
+
200
+/**
201
+ * 计算上一周的日期
202
+ */
203
+function calculatePrevWeekDates(calendarData = {}) {
204
+  let { curYear, curMonth } = calendarData
205
+  let firstDateInThisWeek = calculateFirstDateOfCurrentWeek(calendarData)
206
+  let calendarDates = []
207
+  const { year: FYear, month: FMonth } = firstDateInThisWeek
208
+  if (curYear !== FYear || curMonth !== FMonth) {
209
+    calendarDates = dateUtil.calcDates(FYear, FMonth)
210
+    curYear = FYear
211
+    curMonth = FMonth
212
+  } else {
213
+    calendarDates = dateUtil.calcDates(curYear, curMonth)
214
+  }
215
+  const firstDateIdx = calendarDates.findIndex(
216
+    date => dateUtil.toTimeStr(date) === dateUtil.toTimeStr(firstDateInThisWeek)
217
+  )
218
+  if (firstDateIdx - 7 >= 0) {
219
+    const startIdx = firstDateIdx - 7
220
+    return {
221
+      dates: calendarDates.splice(startIdx, 7),
222
+      year: curYear,
223
+      month: curMonth
224
+    }
225
+  } else {
226
+    const prevMonth = dateUtil.getPrevMonthInfo({
227
+      year: curYear,
228
+      month: curMonth
229
+    })
230
+    const { year, month } = prevMonth || {}
231
+    const calendarDatesOfPrevMonth = dateUtil.calcDates(year, month)
232
+    const remainDatesOfThisMonth = calendarDates.splice(
233
+      0,
234
+      firstDateInThisWeek.date - 1
235
+    )
236
+    const patchDatesOfPrevMonth = calendarDatesOfPrevMonth.splice(
237
+      -(7 - remainDatesOfThisMonth.length)
238
+    )
239
+    return {
240
+      dates: [...patchDatesOfPrevMonth, ...remainDatesOfThisMonth],
241
+      ...prevMonth
242
+    }
243
+  }
244
+}
245
+
246
+export default () => {
247
+  return {
248
+    name: 'week',
249
+    beforeRender(calendarData = {}, calendarConfig = {}, component) {
250
+      const { initializedWeekMode, selectedDates } = calendarData
251
+      if (calendarConfig.weekMode && !initializedWeekMode) {
252
+        const { defaultDate } = calendarConfig
253
+        const target =
254
+          (selectedDates && selectedDates[0]) ||
255
+          (defaultDate && dateUtil.transformDateRow2Dict(defaultDate)) ||
256
+          dateUtil.todayFMD()
257
+        const waitRenderData = this.methods(
258
+          component
259
+        ).__calcDatesWhenSwitchView('week', target)
260
+        const { data, config } = waitRenderData || {}
261
+        const setSelectDates = this.methods(
262
+          component
263
+        ).__selectTargetDateWhenJump(target, data.dates, config)
264
+        return {
265
+          calendarData: {
266
+            ...data,
267
+            ...setSelectDates,
268
+            weeksCh: dateUtil.getWeekHeader(calendarConfig.firstDayOfWeek),
269
+            initializedWeekMode: true
270
+          },
271
+          calendarConfig
272
+        }
273
+      }
274
+      return {
275
+        calendarData,
276
+        calendarConfig
277
+      }
278
+    },
279
+    onSwitchCalendar(target = {}, calendarData = {}, component) {
280
+      const { direction } = target
281
+      const { curYear, curMonth } = calendarData
282
+      const calendarConfig = getCalendarConfig(component)
283
+      let waitRenderData = {}
284
+      if (calendarConfig.weekMode) {
285
+        if (direction === 'left') {
286
+          waitRenderData = calculateNextWeekDates(calendarData)
287
+        } else {
288
+          waitRenderData = calculatePrevWeekDates(calendarData)
289
+        }
290
+        const { dates, year, month } = waitRenderData
291
+        return {
292
+          ...calendarData,
293
+          dates,
294
+          curYear: year || curYear,
295
+          curMonth: month || curMonth
296
+        }
297
+      }
298
+      return calendarData
299
+    },
300
+    methods(component) {
301
+      return {
302
+        __selectTargetDateWhenJump: (target = {}, dates = [], config = {}) => {
303
+          let selectedDate = target
304
+          const weekDates = dates.map((date, idx) => {
305
+            const tmp = { ...date }
306
+            tmp.id = idx
307
+            const isTarget =
308
+              dateUtil.toTimeStr(target) === dateUtil.toTimeStr(tmp)
309
+            if (isTarget && !target.choosed && config.autoChoosedWhenJump) {
310
+              tmp.choosed = true
311
+              selectedDate = tmp
312
+            }
313
+            return tmp
314
+          })
315
+          return {
316
+            dates: weekDates,
317
+            selectedDates: [selectedDate]
318
+          }
319
+        },
320
+        __calcDatesForWeekMode(target, config = {}, calendarData = {}) {
321
+          const { year, month } = target || {}
322
+          const weekDates = getTargetWeekDates(target, config)
323
+          weekDates.forEach((date, idx) => (date.id = idx))
324
+          return {
325
+            data: {
326
+              ...calendarData,
327
+              prevMonthGrids: null,
328
+              nextMonthGrids: null,
329
+              dates: weekDates,
330
+              curYear: year,
331
+              curMonth: month
332
+            },
333
+            config: {
334
+              ...config,
335
+              weekMode: true
336
+            }
337
+          }
338
+        },
339
+        __calcDatesForMonthMode(target, config = {}, calendarData = {}) {
340
+          const { year, month } = target || {}
341
+          const waitRenderData = calcJumpData({
342
+            dateInfo: target,
343
+            config
344
+          })
345
+          return {
346
+            data: {
347
+              ...calendarData,
348
+              ...waitRenderData,
349
+              curYear: year,
350
+              curMonth: month
351
+            },
352
+            config: {
353
+              ...config,
354
+              weekMode: false
355
+            }
356
+          }
357
+        },
358
+        /**
359
+         * 周、月视图切换
360
+         * @param {string} view  视图 [week, month]
361
+         * @param {object} target
362
+         */
363
+        __calcDatesWhenSwitchView: (view, target) => {
364
+          const calendarConfig = getCalendarConfig(component)
365
+          if (calendarConfig.multi)
366
+            return logger.warn('多选模式不能切换周月视图')
367
+          const existCalendarData = getCalendarData('calendar', component) || {}
368
+          const {
369
+            selectedDates = [],
370
+            dates = [],
371
+            curYear,
372
+            curMonth
373
+          } = existCalendarData
374
+          const currentMonthSelected = selectedDates.filter(
375
+            item => curYear === +item.year || curMonth === +item.month
376
+          )
377
+          let jumpTarget = {}
378
+          if (target) {
379
+            jumpTarget = target
380
+          } else {
381
+            if (currentMonthSelected.length) {
382
+              jumpTarget = currentMonthSelected.pop()
383
+            } else {
384
+              jumpTarget = dates[0]
385
+            }
386
+          }
387
+          if (view === 'week') {
388
+            return this.methods(component).__calcDatesForWeekMode(
389
+              jumpTarget,
390
+              calendarConfig,
391
+              existCalendarData
392
+            )
393
+          } else {
394
+            return this.methods(component).__calcDatesForMonthMode(
395
+              jumpTarget,
396
+              calendarConfig,
397
+              existCalendarData
398
+            )
399
+          }
400
+        },
401
+        weekModeJump: dateInfo => {
402
+          const target = dateInfo || dateUtil.todayFMD()
403
+          const existCalendarData = getCalendarData('calendar', component) || {}
404
+          const waitRenderData = this.methods(
405
+            component
406
+          ).__calcDatesWhenSwitchView('week', target)
407
+          const { data, config } = waitRenderData || {}
408
+          const setSelectDates = this.methods(
409
+            component
410
+          ).__selectTargetDateWhenJump(target, data.dates, config)
411
+          return renderCalendar.call(
412
+            component,
413
+            {
414
+              ...existCalendarData,
415
+              ...data,
416
+              ...setSelectDates
417
+            },
418
+            config
419
+          )
420
+        },
421
+        switchView: (view, target) => {
422
+          const waitRenderData = this.methods(
423
+            component
424
+          ).__calcDatesWhenSwitchView(view, target)
425
+          const { data, config } = waitRenderData || {}
426
+          if (!data) return logger.warn('当前状态不能切换为周视图')
427
+          return renderCalendar.call(component, data, config)
428
+        }
429
+      }
430
+    }
431
+  }
432
+}

+ 51 - 0
components/local/v2/render.js

@@ -0,0 +1,51 @@
1
+import plugins from './plugins/index'
2
+import { getCalendarConfig } from './utils/index'
3
+
4
+/**
5
+ * 渲染日历
6
+ */
7
+export function renderCalendar(calendarData, calendarConfig) {
8
+  return new Promise(resolve => {
9
+    const Component = this
10
+    if (Component.firstRender === void 0) {
11
+      Component.firstRender = true
12
+    } else {
13
+      Component.firstRender = false
14
+    }
15
+    const exitData = Component.data.calendar || {}
16
+    for (let plugin of plugins.installed) {
17
+      const [, p] = plugin
18
+      if (typeof p.beforeRender === 'function') {
19
+        const {
20
+          calendarData: newData,
21
+          calendarConfig: newConfig
22
+        } = p.beforeRender(
23
+          { ...exitData, ...calendarData },
24
+          calendarConfig || getCalendarConfig(Component),
25
+          Component
26
+        )
27
+        calendarData = newData
28
+        calendarConfig = newConfig
29
+      }
30
+    }
31
+
32
+    Component.setData(
33
+      {
34
+        config: calendarConfig,
35
+        calendar: calendarData
36
+      },
37
+      () => {
38
+        const rst = {
39
+          calendar: calendarData,
40
+          config: calendarConfig,
41
+          firstRender: Component.firstRender
42
+        }
43
+        resolve(rst)
44
+        if (Component.firstRender) {
45
+          Component.triggerEvent('afterCalendarRender', rst)
46
+          Component.firstRender = false
47
+        }
48
+      }
49
+    )
50
+  })
51
+}

Diff nebyl zobrazen, protože je příliš veliký
+ 29 - 0
components/local/v2/theme/iconfont.wxss


+ 61 - 0
components/local/v2/theme/theme-default.wxss

@@ -0,0 +1,61 @@
1
+
2
+/* 日历主要颜色相关样式 */
3
+
4
+.default_color,
5
+.default_weekend-color,
6
+.default_handle-color,
7
+.default_week-color {
8
+    color: #ff629a;
9
+}
10
+
11
+.default_today {
12
+    color: #fff;
13
+    background-color: #874fb4;
14
+}
15
+
16
+.default_choosed {
17
+    color: #fff;
18
+    background-color: #ff629a;
19
+}
20
+
21
+.default_date-disable {
22
+    color: #c7c7c7;
23
+}
24
+
25
+.default_choosed.default_date-disable {
26
+    color: #e2e2e2;
27
+    background-color: #c2afb6;
28
+}
29
+
30
+.default_prev-month-date,
31
+.default_next-month-date {
32
+    color: #e2e2e2;
33
+}
34
+
35
+.default_normal-date {
36
+    color: #88d2ac;
37
+}
38
+
39
+.default_todo-circle {
40
+    border-color: #88d2ac;
41
+}
42
+
43
+.default_todo-dot {
44
+    background-color: #e54d42;
45
+}
46
+
47
+.default_date-desc {
48
+    color: #c2c2c2;
49
+}
50
+
51
+.default_date-desc-lunar {
52
+    color: #e54d42;
53
+}
54
+
55
+.default_date-desc-disable {
56
+    color: #e2e2e2;
57
+}
58
+
59
+.default_festival {
60
+    color: #c2c2c2;
61
+}

+ 58 - 0
components/local/v2/theme/theme-elegant.wxss

@@ -0,0 +1,58 @@
1
+.elegant_color,
2
+.elegant_weekend-color,
3
+.elegant_handle-color,
4
+.elegant_week-color {
5
+    color: #333;
6
+}
7
+
8
+.elegant_today {
9
+    color: #000;
10
+    background-color: #e1e7f5;
11
+}
12
+
13
+.elegant_choosed {
14
+    color: #000;
15
+    background-color: #e2e2e2;
16
+}
17
+
18
+.elegant_date-disable {
19
+    color: #c7c7c7;
20
+}
21
+
22
+.elegant_choosed.elegant_date-disable {
23
+    color: #999;
24
+    background-color: #ebebeb;
25
+}
26
+
27
+.elegant_prev-month-date,
28
+.elegant_next-month-date {
29
+    color: #e2e2e2;
30
+}
31
+
32
+.elegant_normal-date {
33
+    color: #777;
34
+}
35
+
36
+.elegant_todo-circle {
37
+    border-color: #161035;
38
+}
39
+
40
+.elegant_todo-dot {
41
+    background-color: #161035;
42
+}
43
+
44
+.elegant_date-desc {
45
+    color: #c2c2c2;
46
+}
47
+
48
+.elegant_date-desc-lunar {
49
+    color: #161035;
50
+}
51
+
52
+.elegant_date-desc-disable {
53
+    color: #e2e2e2;
54
+}
55
+
56
+.elegant_festival {
57
+    color: #c2c2c2;
58
+}

+ 285 - 0
components/local/v2/utils/index.js

@@ -0,0 +1,285 @@
1
+import Logger from './logger'
2
+import WxData from './wxData'
3
+
4
+let systemInfo
5
+export function getSystemInfo() {
6
+  if (systemInfo) return systemInfo
7
+  systemInfo = wx.getSystemInfoSync()
8
+  return systemInfo
9
+}
10
+
11
+export function isIos() {
12
+  const sys = getSystemInfo()
13
+  return /iphone|ios/i.test(sys.platform)
14
+}
15
+
16
+class Gesture {
17
+  /**
18
+   * 左滑
19
+   * @param {object} e 事件对象
20
+   * @returns {boolean} 布尔值
21
+   */
22
+  isLeft(gesture = {}, touche = {}) {
23
+    const { startX, startY } = gesture
24
+    const deltaX = touche.clientX - startX
25
+    const deltaY = touche.clientY - startY
26
+    if (deltaX < -60 && deltaY < 20 && deltaY > -20) {
27
+      return true
28
+    } else {
29
+      return false
30
+    }
31
+  }
32
+  /**
33
+   * 右滑
34
+   * @param {object} e 事件对象
35
+   * @returns {boolean} 布尔值
36
+   */
37
+  isRight(gesture = {}, touche = {}) {
38
+    const { startX, startY } = gesture
39
+    const deltaX = touche.clientX - startX
40
+    const deltaY = touche.clientY - startY
41
+
42
+    if (deltaX > 60 && deltaY < 20 && deltaY > -20) {
43
+      return true
44
+    } else {
45
+      return false
46
+    }
47
+  }
48
+}
49
+
50
+class DateUtil {
51
+  newDate(year, month, date) {
52
+    let cur = `${+year}-${+month}-${+date}`
53
+    if (isIos()) {
54
+      cur = `${+year}/${+month}/${+date}`
55
+    }
56
+    return new Date(cur)
57
+  }
58
+  /**
59
+   * 计算指定日期时间戳
60
+   * @param {object} date
61
+   */
62
+  getTimeStamp(dateInfo) {
63
+    if (typeof dateInfo === 'string') {
64
+      dateInfo = this.transformDateRow2Dict(dateInfo)
65
+    }
66
+    if (Object.prototype.toString.call(dateInfo) !== '[object Object]') return
67
+    const dateUtil = new DateUtil()
68
+    return dateUtil
69
+      .newDate(dateInfo.year, dateInfo.month, dateInfo.date)
70
+      .getTime()
71
+  }
72
+  /**
73
+   * 计算指定月份共多少天
74
+   * @param {number} year 年份
75
+   * @param {number} month  月份
76
+   */
77
+  getDatesCountOfMonth(year, month) {
78
+    return new Date(Date.UTC(year, month, 0)).getUTCDate()
79
+  }
80
+  /**
81
+   * 计算指定月份第一天星期几
82
+   * @param {number} year 年份
83
+   * @param {number} month  月份
84
+   */
85
+  firstDayOfWeek(year, month) {
86
+    return new Date(Date.UTC(year, month - 1, 1)).getUTCDay()
87
+  }
88
+  /**
89
+   * 计算指定日期星期几
90
+   * @param {number} year 年份
91
+   * @param {number} month  月份
92
+   * @param {number} date 日期
93
+   */
94
+  getDayOfWeek(year, month, date) {
95
+    return new Date(Date.UTC(year, month - 1, date)).getUTCDay()
96
+  }
97
+  todayFMD() {
98
+    const _date = new Date()
99
+    const year = _date.getFullYear()
100
+    const month = _date.getMonth() + 1
101
+    const date = _date.getDate()
102
+    return {
103
+      year: +year,
104
+      month: +month,
105
+      date: +date
106
+    }
107
+  }
108
+  todayTimestamp() {
109
+    const { year, month, date } = this.todayFMD()
110
+    const timestamp = this.newDate(year, month, date).getTime()
111
+    return timestamp
112
+  }
113
+  toTimeStr(dateInfo = {}) {
114
+    return `${+dateInfo.year}-${+dateInfo.month}-${+dateInfo.date}`
115
+  }
116
+  transformDateRow2Dict(dateStr) {
117
+    if (typeof dateStr === 'string' && dateStr.includes('-')) {
118
+      const [year, month, date] = dateStr.split('-')
119
+      return this.tranformStr2NumOfDate({
120
+        year,
121
+        month,
122
+        date
123
+      })
124
+    }
125
+    return {}
126
+  }
127
+  tranformStr2NumOfDate(date = {}) {
128
+    const target = { ...date }
129
+    // 可能传入字符串
130
+    target.year = +target.year
131
+    target.month = +target.month
132
+    target.date = +target.date
133
+    return target
134
+  }
135
+  sortDatesByTime(dates = [], sortType) {
136
+    return dates.sort((a, b) => {
137
+      const at = this.getTimeStamp(a)
138
+      const bt = this.getTimeStamp(b)
139
+      if (at < bt && sortType !== 'desc') {
140
+        return -1
141
+      } else {
142
+        return 1
143
+      }
144
+    })
145
+  }
146
+  getPrevMonthInfo(date = {}) {
147
+    const prevMonthInfo =
148
+      Number(date.month) > 1
149
+        ? {
150
+            year: +date.year,
151
+            month: Number(date.month) - 1
152
+          }
153
+        : {
154
+            year: Number(date.year) - 1,
155
+            month: 12
156
+          }
157
+    return prevMonthInfo
158
+  }
159
+  getNextMonthInfo(date = {}) {
160
+    const nextMonthInfo =
161
+      Number(date.month) < 12
162
+        ? {
163
+            year: +date.year,
164
+            month: Number(date.month) + 1
165
+          }
166
+        : {
167
+            year: Number(date.year) + 1,
168
+            month: 1
169
+          }
170
+    return nextMonthInfo
171
+  }
172
+  getPrevYearInfo(date = {}) {
173
+    return {
174
+      year: Number(date.year) - 1,
175
+      month: +date.month
176
+    }
177
+  }
178
+  getNextYearInfo(date = {}) {
179
+    return {
180
+      year: Number(date.year) + 1,
181
+      month: +date.month
182
+    }
183
+  }
184
+  findDateIndexInArray(target, dates) {
185
+    return dates.findIndex(
186
+      item => dateUtil.toTimeStr(item) === dateUtil.toTimeStr(target)
187
+    )
188
+  }
189
+  calcDates(year, month) {
190
+    const datesCount = this.getDatesCountOfMonth(year, month)
191
+    const dates = []
192
+    const today = dateUtil.todayFMD()
193
+    for (let i = 1; i <= datesCount; i++) {
194
+      const week = dateUtil.getDayOfWeek(+year, +month, i)
195
+      const date = {
196
+        year: +year,
197
+        id: i - 1,
198
+        month: +month,
199
+        date: i,
200
+        week,
201
+        isToday:
202
+          +today.year === +year && +today.month === +month && i === +today.date
203
+      }
204
+      dates.push(date)
205
+    }
206
+    return dates
207
+  }
208
+  /**
209
+   * 日期数组根据日期去重
210
+   * @param {array} array 数组
211
+   */
212
+  uniqueArrayByDate(array = []) {
213
+    let uniqueObject = {}
214
+    let uniqueArray = []
215
+    array.forEach(item => {
216
+      uniqueObject[dateUtil.toTimeStr(item)] = item
217
+    })
218
+    for (let i in uniqueObject) {
219
+      uniqueArray.push(uniqueObject[i])
220
+    }
221
+    return uniqueArray
222
+  }
223
+  /**
224
+   * 筛选指定年月日期
225
+   * @param {object} target 指定年月
226
+   * @param {array} dates 待筛选日期
227
+   */
228
+  filterDatesByYM(target, dates) {
229
+    if (target) {
230
+      const { year, month } = target
231
+      const _dates = dates.filter(
232
+        item => +item.year === +year && +item.month === +month
233
+      )
234
+      return _dates
235
+    }
236
+    return dates
237
+  }
238
+  getWeekHeader(firstDayOfWeek) {
239
+    let weeksCh = ['日', '一', '二', '三', '四', '五', '六']
240
+    if (firstDayOfWeek === 'Mon') {
241
+      weeksCh = ['一', '二', '三', '四', '五', '六', '日']
242
+    }
243
+    return weeksCh
244
+  }
245
+}
246
+
247
+/**
248
+ * 获取当前页面实例
249
+ */
250
+export function getCurrentPage() {
251
+  const pages = getCurrentPages() || []
252
+  const last = pages.length - 1
253
+  return pages[last] || {}
254
+}
255
+
256
+export function getComponentById(componentId) {
257
+  const logger = new Logger()
258
+  let page = getCurrentPage() || {}
259
+  if (page.selectComponent && typeof page.selectComponent === 'function') {
260
+    if (componentId) {
261
+      return page.selectComponent(componentId)
262
+    } else {
263
+      logger.warn('请传入组件ID')
264
+    }
265
+  } else {
266
+    logger.warn('该基础库暂不支持多个小程序日历组件')
267
+  }
268
+}
269
+
270
+export const logger = new Logger()
271
+export const calendarGesture = new Gesture()
272
+export const dateUtil = new DateUtil()
273
+export const getCalendarData = (key, component) =>
274
+  new WxData(component).getData(key)
275
+export const setCalendarData = (data, component) =>
276
+  new WxData(component).setData(data)
277
+export const getCalendarConfig = component =>
278
+  getCalendarData('config', component)
279
+export const setCalendarConfig = (config, component) =>
280
+  setCalendarData(
281
+    {
282
+      config
283
+    },
284
+    component
285
+  )

+ 23 - 0
components/local/v2/utils/logger.js

@@ -0,0 +1,23 @@
1
+export default class Logger {
2
+  info(msg) {
3
+    console.log(
4
+      '%cInfo: %c' + msg,
5
+      'color:#FF0080;font-weight:bold',
6
+      'color: #FF509B'
7
+    )
8
+  }
9
+  warn(msg) {
10
+    console.log(
11
+      '%cWarn: %c' + msg,
12
+      'color:#FF6600;font-weight:bold',
13
+      'color: #FF9933'
14
+    )
15
+  }
16
+  tips(msg) {
17
+    console.log(
18
+      '%cTips: %c' + msg,
19
+      'color:#00B200;font-weight:bold',
20
+      'color: #00CC33'
21
+    )
22
+  }
23
+}

+ 30 - 0
components/local/v2/utils/wxData.js

@@ -0,0 +1,30 @@
1
+class WxData {
2
+  constructor(component) {
3
+    this.Component = component
4
+  }
5
+  getData(key) {
6
+    const data = this.Component.data
7
+    if (!key) return data
8
+    if (key.includes('.')) {
9
+      let keys = key.split('.')
10
+      const tmp = keys.reduce((prev, next) => {
11
+        return prev[next]
12
+      }, data)
13
+      return tmp
14
+    } else {
15
+      return this.Component.data[key]
16
+    }
17
+  }
18
+  setData(data) {
19
+    return new Promise((resolve, reject) => {
20
+      if (!data) return reject('no data to set')
21
+      if (typeof data === 'object') {
22
+        this.Component.setData(data, () => {
23
+          resolve(data)
24
+        })
25
+      }
26
+    })
27
+  }
28
+}
29
+
30
+export default WxData

binární
images/.DS_Store


binární
images/gzh.png


+ 0 - 0
images/header.png


Některé soubory nejsou zobrazny, neboť je v této revizi změněno mnoho souborů