Selaa lähdekoodia

✨ 发布记录反馈

谢创宏 3 vuotta sitten
vanhempi
commit
e41ebf68ee
49 muutettua tiedostoa jossa 3194 lisäystä ja 17 poistoa
  1. BIN
      .DS_Store
  2. 2 1
      app.json
  3. BIN
      components/.DS_Store
  4. BIN
      components/dist/.DS_Store
  5. 382 0
      components/dist/animation-group/index.js
  6. 3 0
      components/dist/animation-group/index.json
  7. 3 0
      components/dist/animation-group/index.wxml
  8. 206 0
      components/dist/animation-group/index.wxss
  9. 62 0
      components/dist/backdrop/index.js
  10. 6 0
      components/dist/backdrop/index.json
  11. 1 0
      components/dist/backdrop/index.wxml
  12. 15 0
      components/dist/backdrop/index.wxss
  13. 176 0
      components/dist/countdown/index.js
  14. 220 0
      components/dist/countup/index.js
  15. 24 0
      components/dist/helpers/arrayTreeFilter.js
  16. 77 0
      components/dist/helpers/baseComponent.js
  17. 29 0
      components/dist/helpers/checkIPhoneX.js
  18. 39 0
      components/dist/helpers/classNames.js
  19. 27 0
      components/dist/helpers/colors.js
  20. 27 0
      components/dist/helpers/compareVersion.js
  21. 48 0
      components/dist/helpers/computedBehavior.js
  22. 75 0
      components/dist/helpers/createFieldsStore.js
  23. 56 0
      components/dist/helpers/debounce.js
  24. 53 0
      components/dist/helpers/eventsMixin.js
  25. 97 0
      components/dist/helpers/funcBehavior.js
  26. 50 0
      components/dist/helpers/gestures.js
  27. 19 0
      components/dist/helpers/isEmpty.js
  28. 17 0
      components/dist/helpers/mergeOptionsToData.js
  29. 314 0
      components/dist/helpers/popupMixin.js
  30. 67 0
      components/dist/helpers/relationsBehavior.js
  31. 46 0
      components/dist/helpers/safeAreaBehavior.js
  32. 57 0
      components/dist/helpers/safeSetDataBehavior.js
  33. 65 0
      components/dist/helpers/shallowEqual.js
  34. 139 0
      components/dist/helpers/styleToCssString.js
  35. 67 0
      components/dist/index.js
  36. 194 0
      components/dist/popup/index.js
  37. 7 0
      components/dist/popup/index.json
  38. 26 0
      components/dist/popup/index.wxml
  39. 121 0
      components/dist/popup/index.wxss
  40. BIN
      images/.DS_Store
  41. 156 0
      pages/feedback/feedback.js
  42. 4 0
      pages/feedback/feedback.json
  43. 42 0
      pages/feedback/feedback.wxml
  44. 104 0
      pages/feedback/feedback.wxss
  45. 43 2
      pages/orderDetail/orderDetail.js
  46. 3 1
      pages/orderDetail/orderDetail.json
  47. 11 9
      pages/orderDetail/orderDetail.wxml
  48. 7 4
      pages/orderDetail/orderDetail.wxss
  49. 7 0
      project.private.config.json

BIN
.DS_Store


+ 2 - 1
app.json

@@ -4,7 +4,8 @@
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"
8 9
   ],
9 10
   "tabBar": {
10 11
     "color": "#666666",

BIN
components/.DS_Store


BIN
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
+}

+ 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

+ 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
+}

+ 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
+}

BIN
images/.DS_Store


+ 156 - 0
pages/feedback/feedback.js

@@ -0,0 +1,156 @@
1
+// pages/feedback/feedback.js
2
+Page({
3
+
4
+  /**
5
+   * 页面的初始数据
6
+   */
7
+  data: {
8
+    maxlength: 500,
9
+    number: 0,
10
+    value: "",
11
+    imgs: [
12
+      {
13
+        type: "video",
14
+        url: "http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400&bizid=1023&hy=SH&fileparam=302c020101042530230204136ffd93020457e3c4ff02024ef202031e8d7f02030f42400204045a320a0201000400"
15
+      },
16
+      {
17
+        type: "image",
18
+        url: "https://img1.baidu.com/it/u=202543353,3627416815&fm=26&fmt=auto"
19
+      },
20
+      {
21
+        type: "image",
22
+        url: "https://img0.baidu.com/it/u=745609344,230882238&fm=26&fmt=auto"
23
+      },
24
+      {
25
+        type: "image",
26
+        url: "https://img0.baidu.com/it/u=286636366,3227707112&fm=26&fmt=auto"
27
+      },
28
+      {
29
+        type: "image",
30
+        url: "https://img1.baidu.com/it/u=2450865760,444795162&fm=26&fmt=auto"
31
+      },
32
+      {
33
+        type: "image",
34
+        url: "https://img0.baidu.com/it/u=4226275504,4103997964&fm=26&fmt=auto"
35
+      },
36
+      {
37
+        type: "image",
38
+        url: "https://img0.baidu.com/it/u=2247422843,411257408&fm=26&fmt=auto"
39
+      },
40
+    ]
41
+  },
42
+
43
+  /**
44
+   * 生命周期函数--监听页面加载
45
+   */
46
+  onLoad: function (options) {
47
+
48
+  },
49
+
50
+  /**
51
+   * 生命周期函数--监听页面初次渲染完成
52
+   */
53
+  onReady: function () {
54
+
55
+  },
56
+
57
+  /**
58
+   * 生命周期函数--监听页面显示
59
+   */
60
+  onShow: function () {
61
+
62
+  },
63
+
64
+  /**
65
+   * 生命周期函数--监听页面隐藏
66
+   */
67
+  onHide: function () {
68
+
69
+  },
70
+
71
+  /**
72
+   * 生命周期函数--监听页面卸载
73
+   */
74
+  onUnload: function () {
75
+
76
+  },
77
+
78
+  /**
79
+   * 页面相关事件处理函数--监听用户下拉动作
80
+   */
81
+  onPullDownRefresh: function () {
82
+
83
+  },
84
+
85
+  /**
86
+   * 页面上拉触底事件的处理函数
87
+   */
88
+  onReachBottom: function () {
89
+
90
+  },
91
+
92
+  /**
93
+   * 用户点击右上角分享
94
+   */
95
+  onShareAppMessage: function () {
96
+
97
+  },
98
+
99
+  /**
100
+   * 监听文本域
101
+  */
102
+  bindTextAreaInput(e) {
103
+    this.setData({
104
+      number: e.detail.cursor,
105
+      value: e.detail.value
106
+    })
107
+  },
108
+
109
+  /**
110
+   * 预览图片和视频
111
+  */
112
+  previewMedia(e) {
113
+    let arr = this.data.imgs
114
+    let current = e.currentTarget.dataset.index
115
+    wx.previewMedia({
116
+      sources: arr,
117
+      current
118
+    })
119
+  },
120
+
121
+  /**
122
+   * 删除列表项
123
+  */
124
+  onDeleteItem(e) {
125
+    let imgs = this.data.imgs;
126
+    imgs.splice(e.currentTarget.dataset.index, 1);
127
+    this.setData({ imgs })
128
+    console.log(imgs)
129
+  },
130
+
131
+  /**
132
+   * 选择图片
133
+  */
134
+  chooseImage() {
135
+    let that = this;
136
+    let imgs = this.data.imgs;
137
+    let count = 9 - this.data.imgs.length
138
+    wx.chooseImage({
139
+      count,
140
+      sizeType: ['original', 'compressed'],
141
+      sourceType: ['album', 'camera'],
142
+      success (res) {
143
+        const tempFilePaths = res.tempFilePaths
144
+        tempFilePaths.forEach((item) => {
145
+          imgs.push({
146
+            type: 'image',
147
+            url: item
148
+          })
149
+        })
150
+        that.setData({
151
+          imgs
152
+        })
153
+      }
154
+    })
155
+  }
156
+})

+ 4 - 0
pages/feedback/feedback.json

@@ -0,0 +1,4 @@
1
+{
2
+  "navigationBarTitleText": "发布记录反馈",
3
+  "usingComponents": {}
4
+}

+ 42 - 0
pages/feedback/feedback.wxml

@@ -0,0 +1,42 @@
1
+<view class="content">
2
+  <view class="detail border-radius">
3
+    <view class="top flex-align-center">
4
+      <image src=""></image>
5
+      <text style="font-family: PingFangSC-Medium, PingFang SC;font-weight: 500;">水氧清洁水光提升综合管理</text>
6
+    </view>
7
+    <view class="box-textarea">
8
+      <textarea bindinput="bindTextAreaInput" value="{{value}}" maxlength="{{maxlength}}" placeholder="请输入记录反馈" />
9
+      <view class="box-text">{{number}}/{{maxlength}}</view>
10
+    </view>
11
+    <view class="change-img">
12
+      <view class="text">
13
+        <text>添加视频/图片</text>
14
+        <text style="font-size:28rpx;color:#999;">({{imgs.length}}/9)</text>
15
+      </view>
16
+      <view class="img-box">
17
+        <view class="position-relative" style="position: relative;" wx:for="{{ imgs }}" wx:key="index">
18
+          <view class="item">
19
+            <block wx:if="{{ item.type == 'video' }}">
20
+              <!-- <video src="{{ item.url }}"></video> -->
21
+              <!-- <text>视频播放</text> -->
22
+              <view class="flex-center" style="font-size: 28rpx;width: 100%;height: 100%;background-color: #ccc;" data-index="{{index}}" bindtap="previewMedia">这个是视频</view>
23
+            </block>
24
+            <block wx:if="{{ item.type == 'image' }}">
25
+              <image class="img-content" data-index="{{index}}" src="{{ item.url }}" alt="" bindtap="previewMedia"></image>
26
+            </block>
27
+          </view>
28
+          <image src="" class="delete-img" data-index="{{index}}" bindtap="onDeleteItem"></image>
29
+        </view>
30
+        
31
+        <view class="item upload flex-column flex-center" wx:if="{{ imgs.length>0 && imgs.length < 9 &&imgs[0].type != 'video' }}">
32
+          <image src=""></image>
33
+          <text style="font-size: 28rpx;">添加视频</text>
34
+        </view>
35
+        <view class="item upload flex-column flex-center" wx:if="{{ imgs.length > 0 && imgs.length < 9}}" bindtap="chooseImage">
36
+          <image src=""></image>
37
+          <text style="font-size: 28rpx;">添加图片</text>
38
+        </view>
39
+      </view>
40
+    </view>
41
+  </view>
42
+</view>

+ 104 - 0
pages/feedback/feedback.wxss

@@ -0,0 +1,104 @@
1
+.border-radius {
2
+  background: #FFFFFF;
3
+  box-shadow: 0rpx 4rpx 14rpx 0rpx rgba(0, 0, 0, 0.05);
4
+  border-radius: 24rpx;
5
+  margin: 20rpx 0;
6
+}
7
+
8
+.content {
9
+  padding: 10rpx 30rpx;
10
+}
11
+
12
+.detail {
13
+  padding: 30rpx 28rpx;
14
+}
15
+
16
+.detail .top {
17
+  padding-bottom: 30rpx;
18
+  border-bottom: 2rpx solid #F2F2F2;
19
+}
20
+
21
+.detail .top image {
22
+  width: 112rpx;
23
+  height: 112rpx;
24
+  background: #ECECEC;
25
+  border-radius: 12rpx;
26
+  margin-right: 20rpx;
27
+}
28
+
29
+.detail .box-textarea {
30
+  position: relative;
31
+  padding-bottom: 30rpx;
32
+  height: 280rpx;
33
+  box-sizing: border-box;
34
+}
35
+
36
+.detail textarea {
37
+  width: 100%;
38
+  height: 100%;
39
+  box-sizing: border-box;
40
+  /* height: 280rpx;
41
+  box-sizing: border-box; */
42
+  padding: 30rpx 0 50rpx;
43
+}
44
+
45
+.detail .box-textarea .box-text {
46
+  position: absolute;
47
+  right: 0;
48
+  bottom: 30rpx;
49
+  font-size: 28rpx;
50
+  color: #999999;
51
+}
52
+
53
+.detail .change-img {
54
+  padding-top: 30rpx;
55
+  border-top: 2rpx solid #F2F2F2;
56
+}
57
+
58
+.change-img .img-box {
59
+  margin-top: 30rpx;
60
+  display: grid;
61
+  grid-gap: 20rpx 20rpx;
62
+  grid-template-columns: repeat(3, 1fr);
63
+}
64
+
65
+.change-img .img-box .item {
66
+  width: 200rpx;
67
+  height: 200rpx;
68
+  border-radius: 16px;
69
+  box-sizing: border-box;
70
+  overflow: hidden;
71
+  position: relative;
72
+}
73
+
74
+.change-img .delete-img {
75
+  position: absolute;
76
+  top: -24rpx;
77
+  right: -16rpx;
78
+  width: 48rpx;
79
+  height: 48rpx;
80
+  border-radius: 50%;
81
+  background-color: black;
82
+}
83
+
84
+.change-img .img-box .item.upload {
85
+  border: dashed 2rpx #c6c6c6;
86
+  background: #F4F4F4;
87
+}
88
+
89
+.change-img .upload image {
90
+  width: 48rpx;
91
+  height: 48rpx;
92
+  background-color: #ccc;
93
+  margin-bottom: 6rpx;
94
+}
95
+
96
+.change-img .img-box video {
97
+  width: 100%;
98
+  height: 100%;
99
+}
100
+
101
+.change-img .img-box .img-content {
102
+  width: 100%;
103
+  height: 100%;
104
+}

+ 43 - 2
pages/orderDetail/orderDetail.js

@@ -5,7 +5,20 @@ Page({
5 5
    * 页面的初始数据
6 6
    */
7 7
   data: {
8
-
8
+    urls: [
9
+      "https://img1.baidu.com/it/u=202543353,3627416815&fm=26&fmt=auto",
10
+      "https://img0.baidu.com/it/u=745609344,230882238&fm=26&fmt=auto",
11
+      "https://img0.baidu.com/it/u=286636366,3227707112&fm=26&fmt=auto",
12
+      "https://img1.baidu.com/it/u=2450865760,444795162&fm=26&fmt=auto",
13
+      "https://img0.baidu.com/it/u=4226275504,4103997964&fm=26&fmt=auto",
14
+      "https://img0.baidu.com/it/u=2247422843,411257408&fm=26&fmt=auto",
15
+      "https://img0.baidu.com/it/u=3098615520,360170704&fm=26&fmt=auto",
16
+      "https://img1.baidu.com/it/u=510862345,2249984174&fm=26&fmt=auto",
17
+      "https://img2.baidu.com/it/u=2222750380,2392750381&fm=26&fmt=auto",
18
+    ],
19
+    index: 0,
20
+    visible: false,
21
+    array: ['美国', '中国', '巴西', '日本'],
9 22
   },
10 23
 
11 24
   /**
@@ -63,7 +76,7 @@ Page({
63 76
   onShareAppMessage: function () {
64 77
 
65 78
   },
66
-  
79
+
67 80
   /**
68 81
    * 复制订单号
69 82
    */
@@ -75,4 +88,32 @@ Page({
75 88
       }
76 89
     })
77 90
   },
91
+
92
+  /**
93
+   * 打开弹框
94
+   */
95
+  onEditItem() {
96
+    this.setData({
97
+      visible: true
98
+    })
99
+  },
100
+
101
+  /**
102
+   * 关闭弹框
103
+  */
104
+  onPopupState() {
105
+    this.setData({
106
+      visible: false
107
+    })
108
+  },
109
+
110
+  /**
111
+   * 图片预览
112
+   */
113
+  previewImage(e) {
114
+    wx.previewImage({
115
+      current: e.currentTarget.dataset.index,
116
+      urls: this.data.urls
117
+    })
118
+  }
78 119
 })

+ 3 - 1
pages/orderDetail/orderDetail.json

@@ -1,4 +1,6 @@
1 1
 {
2 2
   "navigationBarTitleText": "订单详情",
3
-  "usingComponents": {}
3
+  "usingComponents": {
4
+    "wux-popup": "../../components/dist/popup/index"
5
+  }
4 6
 }

+ 11 - 9
pages/orderDetail/orderDetail.wxml

@@ -61,14 +61,13 @@
61 61
       <view class="btn flex-center">新增反馈</view>
62 62
     </view>
63 63
     <view class="item flex-column" wx:for="{{4}}" wx:key="index">
64
-      
65 64
       <view class="user-info flex-align-center">
66 65
         <image></image>
67 66
         <view class="info flex-column flex1">
68 67
           <text style="font-size: 28rpx;margin-bottom: 6rpx;">查理兹·塞隆…</text>
69 68
           <text style="font-size: 20rpx;color: #999;">下午  3:45</text>
70 69
         </view>
71
-        <view class="more flex-align-center flex-justify-space-between">
70
+        <view class="more flex-align-center flex-justify-space-between" bindtap="onEditItem">
72 71
           <text></text>
73 72
           <text></text>
74 73
           <text></text>
@@ -77,14 +76,17 @@
77 76
       </view>
78 77
       <text style="font-size: 28rpx;color: #666;">当前皮肤毛孔出现堵塞,抓哟集中在T区,主要问题在于自身油脂分泌过多。</text>
79 78
       <view class="box">
80
-        <image></image>
81
-        <image></image>
82
-        <image></image>
83
-        <image></image>
84
-        <image></image>
85
-        <image></image>
79
+        <image src="{{item}}" data-index="{{item}}" bindtap="previewImage" wx:for="{{urls}}" wx:key="index"></image>
86 80
       </view>
87 81
     </view>
88 82
   </view>
89 83
   <view class="no-more-data">已经到底啦~~</view>
90
-</view>
84
+</view>
85
+
86
+<wux-popup class="popup-radius" position="bottom" catchtouchmove="isShow" visible="{{ visible }}" bind:close="onPopupState">
87
+  <view class="btn-item flex-center" style="color: #FF77B0;">删除</view>
88
+  <navigator url="/pages/feedback/feedback" hover-class="none">
89
+    <view class="btn-item flex-center">编辑</view>
90
+  </navigator>
91
+  <view class="btn-item flex-center" style="color: #999999;" bindtap="onPopupState">取消</view>
92
+</wux-popup>

+ 7 - 4
pages/orderDetail/orderDetail.wxss

@@ -129,8 +129,11 @@
129 129
   background-color: red;
130 130
 }
131 131
 
132
-/* .list .user-info .more {
132
+.btn-item {
133
+  height: 112rpx;
134
+  border-bottom: 2rpx solid #eee;
135
+  box-sizing: border-box;
133 136
   font-size: 36rpx;
134
-  font-family: PingFangSC-Medium, PingFang SC;
135
-  font-weight: 500;
136
-}  */
137
+  font-family: PingFangSC-Regular, PingFang SC;
138
+  font-weight: 400;
139
+}

+ 7 - 0
project.private.config.json

@@ -1,4 +1,5 @@
1 1
 {
2
+  "setting": {},
2 3
   "condition": {
3 4
     "plugin": {
4 5
       "list": []
@@ -16,6 +17,12 @@
16 17
           "pathName": "pages/orderDetail/orderDetail",
17 18
           "query": "",
18 19
           "scene": null
20
+        },
21
+        {
22
+          "name": "发布记录反馈",
23
+          "pathName": "pages/feedback/feedback",
24
+          "query": "",
25
+          "scene": null
19 26
         }
20 27
       ]
21 28
     }