@didaoktv/didaoui-uniapp 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +16 -7
  2. package/components/dd-action-sheet/dd-action-sheet.vue +3 -3
  3. package/components/dd-album/album.js +19 -0
  4. package/components/dd-album/dd-album.vue +345 -0
  5. package/components/dd-album/props.js +89 -0
  6. package/components/dd-avatar/avatar.js +19 -0
  7. package/components/dd-avatar/dd-avatar.vue +172 -160
  8. package/components/dd-avatar/props.js +84 -0
  9. package/components/dd-avatar-group/avatarGroup.js +14 -0
  10. package/components/dd-avatar-group/dd-avatar-group.vue +110 -0
  11. package/components/dd-avatar-group/props.js +57 -0
  12. package/components/dd-badge/dd-badge.vue +2 -2
  13. package/components/dd-barcode/dd-barcode.vue +1030 -0
  14. package/components/dd-bill-detail/dd-bill-detail.vue +265 -0
  15. package/components/dd-button/dd-button.vue +17 -17
  16. package/components/dd-calendar/dd-calendar.vue +357 -0
  17. package/components/dd-canvas/dd-canvas.vue +1088 -0
  18. package/components/dd-capsule-button/dd-capsule-button.vue +6 -6
  19. package/components/dd-car-keyboard/carKeyboard.js +6 -0
  20. package/components/dd-car-keyboard/dd-car-keyboard.vue +315 -0
  21. package/components/dd-car-keyboard/props.js +19 -0
  22. package/components/dd-cascader/dd-cascader.vue +405 -0
  23. package/components/dd-cell/dd-cell.vue +50 -23
  24. package/components/dd-cell-group/dd-cell-group.vue +32 -25
  25. package/components/dd-champion-card/dd-champion-card.vue +4 -4
  26. package/components/dd-checkbox/dd-checkbox.vue +30 -39
  27. package/components/dd-checkbox-group/dd-checkbox-group.vue +67 -0
  28. package/components/dd-copy/dd-copy.vue +74 -0
  29. package/components/dd-coupon/dd-coupon.vue +368 -0
  30. package/components/dd-cropper/dd-cropper.vue +1297 -0
  31. package/components/dd-dialog/dd-dialog.vue +1 -1
  32. package/components/dd-divider/dd-divider.vue +3 -3
  33. package/components/dd-dropdown-item/dd-dropdown-item.vue +5 -5
  34. package/components/dd-empty-state/dd-empty-state.vue +1 -1
  35. package/components/dd-field/dd-field.vue +531 -106
  36. package/components/dd-form/dd-form.vue +116 -0
  37. package/components/dd-form/rules.ts +84 -0
  38. package/components/dd-goods-sku/dd-goods-sku.vue +434 -0
  39. package/components/dd-icon/dd-icon.vue +2 -2
  40. package/components/dd-image/dd-image.vue +4 -4
  41. package/components/dd-input/dd-input.vue +5 -5
  42. package/components/dd-keyboard/dd-keyboard.vue +178 -0
  43. package/components/dd-keyboard/keyboard.js +22 -0
  44. package/components/dd-keyboard/props.js +89 -0
  45. package/components/dd-link/dd-link.vue +87 -0
  46. package/components/dd-link/link.js +18 -0
  47. package/components/dd-link/props.js +44 -0
  48. package/components/dd-list-cell/dd-list-cell.vue +4 -4
  49. package/components/dd-loading/dd-loading.vue +1 -1
  50. package/components/dd-loadmore/dd-loadmore.vue +2 -2
  51. package/components/dd-markdown/dd-markdown.vue +344 -0
  52. package/components/dd-markdown/marked.esm.mjs +7 -0
  53. package/components/dd-mini-program-navbar/dd-mini-program-navbar.vue +9 -9
  54. package/components/dd-navigation/dd-navigation.vue +5 -5
  55. package/components/dd-number-keyboard/dd-number-keyboard.vue +200 -0
  56. package/components/dd-number-keyboard/numberKeyboard.js +8 -0
  57. package/components/dd-number-keyboard/props.js +24 -0
  58. package/components/dd-parse/dd-parse.vue +534 -0
  59. package/components/dd-parse/node/handler.wxs +26 -0
  60. package/components/dd-parse/node/node.vue +603 -0
  61. package/components/dd-parse/parse.js +13 -0
  62. package/components/dd-parse/parser.js +1400 -0
  63. package/components/dd-parse/props.js +57 -0
  64. package/components/dd-popover/dd-popover.vue +10 -10
  65. package/components/dd-popover-item/dd-popover-item.vue +3 -3
  66. package/components/dd-popup/dd-popup.vue +5 -5
  67. package/components/dd-poster/dd-poster.vue +695 -0
  68. package/components/dd-progress/dd-progress.vue +3 -3
  69. package/components/dd-pull-refresh/dd-pull-refresh.vue +3 -3
  70. package/components/dd-qrcode/dd-qrcode.vue +509 -0
  71. package/components/dd-qrcode/qrcode.js +1283 -0
  72. package/components/dd-radio/dd-radio.vue +19 -20
  73. package/components/dd-radio-group/dd-radio-group.vue +53 -0
  74. package/components/dd-rate/dd-rate.vue +1 -1
  75. package/components/dd-room-card/dd-room-card.vue +3 -3
  76. package/components/dd-search-bar/dd-search-bar.vue +8 -8
  77. package/components/dd-segmented-tab/dd-segmented-tab.vue +6 -6
  78. package/components/dd-signature/dd-signature.vue +513 -0
  79. package/components/dd-skeleton/dd-skeleton.vue +1 -1
  80. package/components/dd-slider/dd-slider.vue +4 -4
  81. package/components/dd-stat-card/dd-stat-card.vue +5 -5
  82. package/components/dd-step/dd-step.vue +11 -11
  83. package/components/dd-stepper/dd-stepper.vue +6 -6
  84. package/components/dd-swipe-action/dd-swipe-action.vue +1 -1
  85. package/components/dd-swipeable-tab/dd-swipeable-tab.vue +1 -1
  86. package/components/dd-tabbar-item/dd-tabbar-item.vue +8 -8
  87. package/components/dd-tag/dd-tag.vue +17 -1
  88. package/components/dd-text/dd-text.vue +250 -0
  89. package/components/dd-text/props.js +120 -0
  90. package/components/dd-text/text.js +30 -0
  91. package/components/dd-text/value.js +87 -0
  92. package/components/dd-toast/dd-toast.vue +2 -2
  93. package/components/dd-upload/dd-upload.vue +4 -4
  94. package/components/dd-workorder-card/dd-workorder-card.vue +237 -0
  95. package/index.ts +50 -9
  96. package/libs/config/color.js +16 -0
  97. package/libs/config/config.js +25 -0
  98. package/libs/config/props.js +185 -0
  99. package/libs/config/zIndex.js +20 -0
  100. package/libs/function/digit.js +167 -0
  101. package/libs/function/index.js +932 -0
  102. package/libs/function/test.js +327 -0
  103. package/libs/i18n/index.d.ts +2 -0
  104. package/libs/i18n/index.js +89 -0
  105. package/libs/i18n/locales/zh-Hans.js +86 -0
  106. package/libs/mixin/button.js +18 -0
  107. package/libs/mixin/mixin.js +44 -0
  108. package/libs/mixin/mpMixin.js +13 -0
  109. package/libs/mixin/openType.js +27 -0
  110. package/libs/utils.ts +462 -0
  111. package/libs/vue.js +3 -0
  112. package/package.json +1 -1
  113. package/scss/_dd-shared.scss +40 -0
  114. package/scss/_variables.scss +4 -1
@@ -0,0 +1,932 @@
1
+ import {
2
+ number as testNumber,
3
+ array as testArray,
4
+ empty as testEmpty
5
+ } from './test.js'
6
+ import { round } from './digit.js'
7
+ import config from '../config/config.js'
8
+
9
+ function emptyNodeInfo() {
10
+ return {
11
+ width: 0,
12
+ height: 0,
13
+ left: 0,
14
+ right: 0,
15
+ top: 0,
16
+ bottom: 0
17
+ }
18
+ }
19
+
20
+ // #ifdef APP-NVUE
21
+ const dom = (typeof uni !== 'undefined' && uni && typeof uni.requireNativePlugin === 'function')
22
+ ? uni.requireNativePlugin('dom')
23
+ : null
24
+
25
+ function getNvueRect(ref) {
26
+ return new Promise((resolve) => {
27
+ if (!dom || !ref) {
28
+ resolve(emptyNodeInfo())
29
+ return
30
+ }
31
+ dom.getComponentRect(ref, (res) => {
32
+ resolve(res && res.size ? res.size : emptyNodeInfo())
33
+ })
34
+ })
35
+ }
36
+ // #endif
37
+
38
+ // 查询节点信息
39
+ export function getRect(selector, all = false, comp = null) {
40
+ return new Promise((resolve) => {
41
+ // #ifndef APP-NVUE
42
+ const query = uni.createSelectorQuery()
43
+ const scopedQuery = comp ? query.in(comp) : query
44
+ scopedQuery[all ? 'selectAll' : 'select'](selector)
45
+ .boundingClientRect((rect) => {
46
+ if (all) {
47
+ resolve(Array.isArray(rect) ? rect : [])
48
+ return
49
+ }
50
+ resolve(rect || emptyNodeInfo())
51
+ })
52
+ .exec()
53
+ // #endif
54
+
55
+ // #ifdef APP-NVUE
56
+ sleep(30).then(async () => {
57
+ const refs = comp?.$refs || comp?.proxy?.$refs || comp?.refs || {}
58
+ const selectorRef = refs[selector.substring(1)]
59
+ if (all) {
60
+ const selectorRefs = Array.isArray(selectorRef)
61
+ ? selectorRef
62
+ : selectorRef ? [selectorRef] : []
63
+ resolve(await Promise.all(selectorRefs.map(ref => getNvueRect(ref))))
64
+ return
65
+ }
66
+ const ref = Array.isArray(selectorRef) ? selectorRef[0] : selectorRef
67
+ resolve(await getNvueRect(ref))
68
+ })
69
+ // #endif
70
+ })
71
+ }
72
+
73
+ /**
74
+ * @description 如果value小于min,取min;如果value大于max,取max
75
+ * @param {number} min
76
+ * @param {number} max
77
+ * @param {number} value
78
+ */
79
+ export function range(min = 0, max = 0, value = 0) {
80
+ return Math.max(min, Math.min(max, Number(value)))
81
+ }
82
+
83
+ /**
84
+ * @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.rpx2px进行转换
85
+ * @param {number|string} value 用户传递值的px值
86
+ * @param {boolean} unit
87
+ * @returns {number|string}
88
+ */
89
+ export function getPx(value, unit = false) {
90
+ if (testNumber(value)) {
91
+ return unit ? `${value}px` : Number(value)
92
+ }
93
+ // 如果带有rpx,先取出其数值部分,再转为px值
94
+ if (/(rpx|upx)$/.test(value)) {
95
+ return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
96
+ }
97
+ return unit ? `${parseInt(value)}px` : parseInt(value)
98
+ }
99
+
100
+ /**
101
+ * @description 用于统一rpx2px方法,因uni-app现有API未统一。
102
+ * @param {number} value 用户传递值的rpx值
103
+ * @returns {number}
104
+ */
105
+ export function rpx2px(value) {
106
+ // #ifdef APP
107
+ return uni.upx2px(value)
108
+ // #endif
109
+ // #ifndef APP
110
+ return uni.rpx2px(value)
111
+ // #endif
112
+ }
113
+
114
+ /**
115
+ * @description 进行延时,以达到可以简写代码的目的 比如: await sleep(20)将会阻塞20ms
116
+ * @param {number} value 堵塞时间 单位ms 毫秒
117
+ * @returns {Promise} 返回promise
118
+ */
119
+ export function sleep(value = 30) {
120
+ return new Promise((resolve) => {
121
+ setTimeout(() => {
122
+ resolve()
123
+ }, value)
124
+ })
125
+ }
126
+ /**
127
+ * @description 运行期判断平台
128
+ * @returns {string} 返回所在平台(小写)
129
+ * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
130
+ */
131
+ export function os() {
132
+ // #ifdef APP || H5 || MP-WEIXIN
133
+ return uni.getDeviceInfo().platform.toLowerCase()
134
+ // #endif
135
+ // #ifndef APP || H5 || MP-WEIXIN
136
+ return uni.getSystemInfoSync().platform.toLowerCase()
137
+ // #endif
138
+ }
139
+ /**
140
+ * @description 获取系统信息同步接口
141
+ * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
142
+ */
143
+ export function sys() {
144
+ return uni.getSystemInfoSync()
145
+ }
146
+ export function getWindowInfo() {
147
+ let ret = {}
148
+ // #ifdef APP || H5 || MP-WEIXIN
149
+ ret = uni.getWindowInfo()
150
+ // #endif
151
+ // #ifndef APP || H5 || MP-WEIXIN
152
+ ret = sys()
153
+ // #endif
154
+ return ret
155
+ }
156
+ export function getDeviceInfo() {
157
+ let ret = {}
158
+ // #ifdef APP || H5 || MP-WEIXIN
159
+ ret = uni.getDeviceInfo()
160
+ // #endif
161
+ // #ifndef APP || H5 || MP-WEIXIN
162
+ ret = sys()
163
+ // #endif
164
+ return ret
165
+ }
166
+
167
+ /**
168
+ * @description 取一个区间数
169
+ * @param {Number} min 最小值
170
+ * @param {Number} max 最大值
171
+ */
172
+ export function random(min, max) {
173
+ if (min >= 0 && max > 0 && max >= min) {
174
+ const gab = max - min + 1
175
+ return Math.floor(Math.random() * gab + min)
176
+ }
177
+ return 0
178
+ }
179
+
180
+ /**
181
+ * @param {Number} len uuid的长度
182
+ * @param {Boolean} firstU 将返回的首字母置为"u"
183
+ * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
184
+ */
185
+ export function guid(len = 32, firstU = true, radix = null) {
186
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
187
+ const uuid = []
188
+ radix = radix || chars.length
189
+
190
+ if (len) {
191
+ // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
192
+ for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
193
+ } else {
194
+ let r
195
+ // rfc4122标准要求返回的uuid中,某些位为固定的字符
196
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
197
+ uuid[14] = '4'
198
+
199
+ for (let i = 0; i < 36; i++) {
200
+ if (!uuid[i]) {
201
+ r = 0 | Math.random() * 16
202
+ uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
203
+ }
204
+ }
205
+ }
206
+ // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
207
+ if (firstU) {
208
+ uuid.shift()
209
+ return `u${uuid.join('')}`
210
+ }
211
+ return uuid.join('')
212
+ }
213
+
214
+ /**
215
+ * @description 获取父组件实例(H5 需逐层上溯,this 需为组件实例)
216
+ * @param {string|undefined} name 父组件的 name,不传 = 最顶层 $parent
217
+ */
218
+ export function $parent(name = undefined) {
219
+ let parent = this.$parent
220
+ while (parent) {
221
+ if (parent.$options && parent.$options.name !== name) {
222
+ parent = parent.$parent
223
+ } else {
224
+ return parent
225
+ }
226
+ }
227
+ return false
228
+ }
229
+
230
+ /**
231
+ * @description 样式转换
232
+ * 对象转字符串,或者字符串转对象
233
+ * @param {object | string} customStyle 需要转换的目标
234
+ * @param {String} target 转换的目的,object-转为对象,string-转为字符串
235
+ * @returns {object|string}
236
+ */
237
+ export function addStyle(customStyle, target = 'object') {
238
+ // 字符串转字符串,对象转对象情形,直接返回
239
+ if (testEmpty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
240
+ typeof(customStyle) === 'string') {
241
+ return customStyle
242
+ }
243
+ // 字符串转对象
244
+ if (target === 'object') {
245
+ // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
246
+ customStyle = trim(customStyle)
247
+ // 根据";"将字符串转为数组形式
248
+ const styleArray = customStyle.split(';')
249
+ const style = {}
250
+ // 历遍数组,拼接成对象
251
+ for (let i = 0; i < styleArray.length; i++) {
252
+ // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
253
+ if (styleArray[i]) {
254
+ const item = styleArray[i].split(':')
255
+ style[trim(item[0])] = trim(item[1])
256
+ }
257
+ }
258
+ return style
259
+ }
260
+ // 这里为对象转字符串形式
261
+ let string = ''
262
+ if (typeof customStyle === 'object') {
263
+ customStyle.forEach((val, i) => {
264
+ // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
265
+ const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
266
+ string += `${key}:${val};`
267
+ })
268
+ }
269
+ // 去除两端空格
270
+ return trim(string)
271
+ }
272
+
273
+ /**
274
+ * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
275
+ * @param {string|number} value 需要添加单位的值
276
+ * @param {string} unit 添加的单位名 比如px
277
+ */
278
+ export function addUnit(value = 'auto', unit = '') {
279
+ if (!unit) {
280
+ unit = config.unit || 'px'
281
+ }
282
+ if (unit == 'rpx' && testNumber(String(value))) {
283
+ value = value * 2
284
+ }
285
+ value = String(value)
286
+ // 用内置验证规则中的number判断是否为数值
287
+ return testNumber(value) ? `${value}${unit}` : value
288
+ }
289
+
290
+ /**
291
+ * @description 深度克隆
292
+ * @param {object} obj 需要深度克隆的对象
293
+ * @returns {*} 克隆后的对象或者原值(不是对象)
294
+ */
295
+ export function deepClone(obj) {
296
+ // 对常见的“非”值,直接返回原来值
297
+ if ([null, undefined, NaN, false].includes(obj)) return obj
298
+ if (typeof obj !== 'object' && typeof obj !== 'function') {
299
+ // 原始类型直接返回
300
+ return obj
301
+ }
302
+ const o = testArray(obj) ? [] : {}
303
+ for (const i in obj) {
304
+ if (obj.hasOwnProperty(i)) {
305
+ o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
306
+ }
307
+ }
308
+ return o
309
+ }
310
+
311
+ /**
312
+ * @description JS对象深度合并
313
+ * @param {object} target 需要拷贝的对象
314
+ * @param {object} source 拷贝的来源对象
315
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
316
+ */
317
+ export function deepMerge(targetOrigin = {}, source = {}) {
318
+ let target = deepClone(targetOrigin)
319
+ if (typeof target !== 'object' || typeof source !== 'object') return false
320
+ for (const prop in source) {
321
+ if (!source.hasOwnProperty(prop)) continue
322
+ if (prop in target) {
323
+ if (source[prop] == null) {
324
+ target[prop] = source[prop]
325
+ }else if (typeof target[prop] !== 'object') {
326
+ target[prop] = source[prop]
327
+ } else if (typeof source[prop] !== 'object') {
328
+ target[prop] = source[prop]
329
+ } else if (target[prop].concat && source[prop].concat) {
330
+ target[prop] = target[prop].concat(source[prop])
331
+ } else {
332
+ target[prop] = deepMerge(target[prop], source[prop])
333
+ }
334
+ } else {
335
+ target[prop] = source[prop]
336
+ }
337
+ }
338
+ return target
339
+ }
340
+ /**
341
+ * @description JS对象深度合并
342
+ * @param {object} target 需要拷贝的对象
343
+ * @param {object} source 拷贝的来源对象
344
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
345
+ */
346
+ export function shallowMerge(target, source = {}) {
347
+ if (typeof target !== 'object' || typeof source !== 'object') return false
348
+ for (const prop in source) {
349
+ if (!source.hasOwnProperty(prop)) continue
350
+ if (prop in target) {
351
+ if (source[prop] == null) {
352
+ target[prop] = source[prop]
353
+ }else if (typeof target[prop] !== 'object') {
354
+ target[prop] = source[prop]
355
+ } else if (typeof source[prop] !== 'object') {
356
+ target[prop] = source[prop]
357
+ } else if (target[prop].concat && source[prop].concat) {
358
+ target[prop] = target[prop].concat(source[prop])
359
+ } else {
360
+ target[prop] = shallowMerge(target[prop], source[prop])
361
+ }
362
+ } else {
363
+ target[prop] = source[prop]
364
+ }
365
+ }
366
+ return target
367
+ }
368
+
369
+ /**
370
+ * @description error提示
371
+ * @param {*} err 错误内容
372
+ */
373
+ export function error(err) {
374
+ // 开发环境才提示,生产环境不会提示
375
+ if (process.env.NODE_ENV === 'development') {
376
+ console.error(`DidaoUI提示:${err}`)
377
+ }
378
+ }
379
+
380
+ /**
381
+ * @description 打乱数组
382
+ * @param {array} array 需要打乱的数组
383
+ * @returns {array} 打乱后的数组
384
+ */
385
+ export function randomArray(array = []) {
386
+ // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
387
+ return array.sort(() => Math.random() - 0.5)
388
+ }
389
+
390
+ // padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
391
+ // 所以这里做一个兼容polyfill的兼容处理
392
+ if (!String.prototype.padStart) {
393
+ // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
394
+ String.prototype.padStart = function(maxLength, fillString = ' ') {
395
+ if (Object.prototype.toString.call(fillString) !== '[object String]') {
396
+ throw new TypeError(
397
+ 'fillString must be String'
398
+ )
399
+ }
400
+ const str = this
401
+ // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
402
+ if (str.length >= maxLength) return String(str)
403
+
404
+ const fillLength = maxLength - str.length
405
+ let times = Math.ceil(fillLength / fillString.length)
406
+ while (times >>= 1) {
407
+ fillString += fillString
408
+ if (times === 1) {
409
+ fillString += fillString
410
+ }
411
+ }
412
+ return fillString.slice(0, fillLength) + str
413
+ }
414
+ }
415
+
416
+ /**
417
+ * @description 格式化时间
418
+ * @param {String|Number} dateTime 需要格式化的时间戳
419
+ * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
420
+ * @returns {string} 返回格式化后的字符串
421
+ */
422
+ export function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
423
+ let date
424
+ // 若传入时间为假值,则取当前时间
425
+ if (!dateTime) {
426
+ date = new Date()
427
+ }
428
+ // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
429
+ else if (/^\d{10}$/.test(dateTime.toString().trim())) {
430
+ date = new Date(dateTime * 1000)
431
+ }
432
+ // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
433
+ else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
434
+ date = new Date(Number(dateTime))
435
+ }
436
+ // 检查是否为UTC格式的时间字符串 (2024-12-18T02:25:31.432Z)
437
+ else if (typeof dateTime === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})?$/.test(dateTime)) {
438
+ date = new Date(dateTime)
439
+ }
440
+ // 其他都认为符合 RFC 2822 规范
441
+ else {
442
+ // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
443
+ date = new Date(
444
+ typeof dateTime === 'string'
445
+ ? dateTime.replace(/-/g, '/')
446
+ : dateTime
447
+ )
448
+ }
449
+
450
+ const timeSource = {
451
+ 'y': date.getFullYear().toString(), // 年
452
+ 'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
453
+ 'd': date.getDate().toString().padStart(2, '0'), // 日
454
+ 'h': date.getHours().toString().padStart(2, '0'), // 时
455
+ 'M': date.getMinutes().toString().padStart(2, '0'), // 分
456
+ 's': date.getSeconds().toString().padStart(2, '0') // 秒
457
+ // 有其他格式化字符需求可以继续添加,必须转化成字符串
458
+ }
459
+
460
+ for (const key in timeSource) {
461
+ const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
462
+ if (ret) {
463
+ // 年可能只需展示两位
464
+ const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
465
+ formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
466
+ }
467
+ }
468
+
469
+ return formatStr
470
+ }
471
+
472
+ /**
473
+ * @description 时间戳转为多久之前
474
+ * @param {String|Number} timestamp 时间戳
475
+ * @param {String|Boolean} format
476
+ * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
477
+ * 如果为布尔值false,无论什么时间,都返回多久以前的格式
478
+ * @returns {string} 转化后的内容
479
+ */
480
+ export function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
481
+ if (timestamp == null) timestamp = Number(new Date())
482
+ timestamp = parseInt(timestamp)
483
+ // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
484
+ if (timestamp.toString().length == 10) timestamp *= 1000
485
+ let timer = (new Date()).getTime() - timestamp
486
+ timer = parseInt(timer / 1000)
487
+ // 如果小于5分钟,则返回"刚刚",其他以此类推
488
+ let tips = ''
489
+ switch (true) {
490
+ case timer < 300:
491
+ tips = '刚刚'
492
+ break
493
+ case timer >= 300 && timer < 3600:
494
+ tips = `${parseInt(timer / 60)}分钟前`
495
+ break
496
+ case timer >= 3600 && timer < 86400:
497
+ tips = `${parseInt(timer / 3600)}小时前`
498
+ break
499
+ case timer >= 86400 && timer < 2592000:
500
+ tips = `${parseInt(timer / 86400)}天前`
501
+ break
502
+ default:
503
+ // 如果format为false,则无论什么时间戳,都显示xx之前
504
+ if (format === false) {
505
+ if (timer >= 2592000 && timer < 365 * 86400) {
506
+ tips = `${parseInt(timer / (86400 * 30))}个月前`
507
+ } else {
508
+ tips = `${parseInt(timer / (86400 * 365))}年前`
509
+ }
510
+ } else {
511
+ tips = timeFormat(timestamp, format)
512
+ }
513
+ }
514
+ return tips
515
+ }
516
+
517
+ /**
518
+ * @description 去除空格
519
+ * @param String str 需要去除空格的字符串
520
+ * @param String pos both(左右)|left|right|all 默认both
521
+ */
522
+ export function trim(str, pos = 'both') {
523
+ str = String(str)
524
+ if (pos == 'both') {
525
+ return str.replace(/^\s+|\s+$/g, '')
526
+ }
527
+ if (pos == 'left') {
528
+ return str.replace(/^\s*/, '')
529
+ }
530
+ if (pos == 'right') {
531
+ return str.replace(/(\s*$)/g, '')
532
+ }
533
+ if (pos == 'all') {
534
+ return str.replace(/\s+/g, '')
535
+ }
536
+ return str
537
+ }
538
+
539
+ /**
540
+ * @description 对象转url参数
541
+ * @param {object} data,对象
542
+ * @param {Boolean} isPrefix,是否自动加上"?"
543
+ * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
544
+ */
545
+ export function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
546
+ const prefix = isPrefix ? '?' : ''
547
+ const _result = []
548
+ if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
549
+ for (const key in data) {
550
+ const value = data[key]
551
+ // 去掉为空的参数
552
+ if (['', undefined, null].indexOf(value) >= 0) {
553
+ continue
554
+ }
555
+ // 如果值为数组,另行处理
556
+ if (value.constructor === Array) {
557
+ // e.g. {ids: [1, 2, 3]}
558
+ switch (arrayFormat) {
559
+ case 'indices':
560
+ // 结果: ids[0]=1&ids[1]=2&ids[2]=3
561
+ for (let i = 0; i < value.length; i++) {
562
+ _result.push(`${key}[${i}]=${value[i]}`)
563
+ }
564
+ break
565
+ case 'brackets':
566
+ // 结果: ids[]=1&ids[]=2&ids[]=3
567
+ value.forEach((_value) => {
568
+ _result.push(`${key}[]=${_value}`)
569
+ })
570
+ break
571
+ case 'repeat':
572
+ // 结果: ids=1&ids=2&ids=3
573
+ value.forEach((_value) => {
574
+ _result.push(`${key}=${_value}`)
575
+ })
576
+ break
577
+ case 'comma':
578
+ // 结果: ids=1,2,3
579
+ let commaStr = ''
580
+ value.forEach((_value) => {
581
+ commaStr += (commaStr ? ',' : '') + _value
582
+ })
583
+ _result.push(`${key}=${commaStr}`)
584
+ break
585
+ default:
586
+ value.forEach((_value) => {
587
+ _result.push(`${key}[]=${_value}`)
588
+ })
589
+ }
590
+ } else {
591
+ _result.push(`${key}=${value}`)
592
+ }
593
+ }
594
+ return _result.length ? prefix + _result.join('&') : ''
595
+ }
596
+
597
+ /**
598
+ * 显示消息提示框
599
+ * @param {String} title 提示的内容,长度与 icon 取值有关。
600
+ * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000
601
+ */
602
+ export function toast(title, duration = 2000) {
603
+ uni.showToast({
604
+ title: String(title),
605
+ icon: 'none',
606
+ duration
607
+ })
608
+ }
609
+
610
+ /**
611
+ * @description 根据主题type值,获取对应的图标
612
+ * @param {String} type 主题名称,primary|info|error|warning|success
613
+ * @param {boolean} fill 是否使用fill填充实体的图标
614
+ */
615
+ export function type2icon(type = 'success', fill = false) {
616
+ // 如果非预置值,默认为success
617
+ if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
618
+ let iconName = ''
619
+ // 目前(2019-12-12),info和primary使用同一个图标
620
+ switch (type) {
621
+ case 'primary':
622
+ iconName = 'info-circle'
623
+ break
624
+ case 'info':
625
+ iconName = 'info-circle'
626
+ break
627
+ case 'error':
628
+ iconName = 'close-circle'
629
+ break
630
+ case 'warning':
631
+ iconName = 'error-circle'
632
+ break
633
+ case 'success':
634
+ iconName = 'checkmark-circle'
635
+ break
636
+ default:
637
+ iconName = 'checkmark-circle'
638
+ }
639
+ // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
640
+ if (fill) iconName += '-fill'
641
+ return iconName
642
+ }
643
+
644
+ /**
645
+ * @description 数字格式化
646
+ * @param {number|string} number 要格式化的数字
647
+ * @param {number} decimals 保留几位小数
648
+ * @param {string} decimalPoint 小数点符号
649
+ * @param {string} thousandsSeparator 千分位符号
650
+ * @returns {string} 格式化后的数字
651
+ */
652
+ export function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
653
+ number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
654
+ const n = !isFinite(+number) ? 0 : +number
655
+ const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
656
+ const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
657
+ const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
658
+ let s = ''
659
+
660
+ s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
661
+ const re = /(-?\d+)(\d{3})/
662
+ while (re.test(s[0])) {
663
+ s[0] = s[0].replace(re, `$1${sep}$2`)
664
+ }
665
+
666
+ if ((s[1] || '').length < prec) {
667
+ s[1] = s[1] || ''
668
+ s[1] += new Array(prec - s[1].length + 1).join('0')
669
+ }
670
+ return s.join(dec)
671
+ }
672
+
673
+ /**
674
+ * @description 获取duration值
675
+ * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
676
+ * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
677
+ * @param {String|number} value 比如: "1s"|"100ms"|1|100
678
+ * @param {boolean} unit 提示: 如果是false 默认返回number
679
+ * @return {string|number}
680
+ */
681
+ export function getDuration(value, unit = true) {
682
+ const valueNum = parseInt(value)
683
+ if (unit) {
684
+ if (/s$/.test(value)) return value
685
+ return value > 30 ? `${value}ms` : `${value}s`
686
+ }
687
+ if (/ms$/.test(value)) return valueNum
688
+ if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
689
+ return valueNum
690
+ }
691
+
692
+ /**
693
+ * @description 日期的月或日补零操作
694
+ * @param {String} value 需要补零的值
695
+ */
696
+ export function padZero(value) {
697
+ return `00${value}`.slice(-2)
698
+ }
699
+
700
+ /**
701
+ * @description 在u-form的子组件内容发生变化,或者失去焦点时,尝试通知u-form执行校验方法
702
+ * @param {*} instance
703
+ * @param {*} event
704
+ */
705
+ export function formValidate(instance, event) {
706
+ const formItem = $parent.call(instance, 'up-form-item')
707
+ const form = $parent.call(instance, 'up-form')
708
+ // 如果发生变化的input或者textarea等,其父组件中有u-form-item或者u-form等,就执行form的validate方法
709
+ // 同时将form-item的pros传递给form,让其进行精确对象验证
710
+ if (formItem && form) {
711
+ form.validateField(formItem.prop, () => {}, event)
712
+ }
713
+ }
714
+
715
+ /**
716
+ * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
717
+ * @param {object} obj 对象
718
+ * @param {string} key 需要获取的属性字段
719
+ * @returns {*}
720
+ */
721
+ export function getProperty(obj, key) {
722
+ if (typeof obj !== 'object' || null == obj) {
723
+ return ''
724
+ }
725
+ if (typeof key !== 'string' || key === '') {
726
+ return ''
727
+ }
728
+ if (key.indexOf('.') !== -1) {
729
+ const keys = key.split('.')
730
+ let firstObj = obj[keys[0]] || {}
731
+
732
+ for (let i = 1; i < keys.length; i++) {
733
+ if (firstObj) {
734
+ firstObj = firstObj[keys[i]]
735
+ }
736
+ }
737
+ return firstObj
738
+ }
739
+ return obj[key]
740
+ }
741
+
742
+ /**
743
+ * @description 设置对象的属性值,如果'a.b.c'的形式进行设置
744
+ * @param {object} obj 对象
745
+ * @param {string} key 需要设置的属性
746
+ * @param {string} value 设置的值
747
+ */
748
+ export function setProperty(obj, key, value) {
749
+ if (typeof obj !== 'object' || null == obj) {
750
+ return
751
+ }
752
+ // 递归赋值
753
+ const inFn = function(_obj, keys, v) {
754
+ // 最后一个属性key
755
+ if (keys.length === 1) {
756
+ _obj[keys[0]] = v
757
+ return
758
+ }
759
+ // 0~length-1个key
760
+ while (keys.length > 1) {
761
+ const k = keys[0]
762
+ if (!_obj[k] || (typeof _obj[k] !== 'object')) {
763
+ _obj[k] = {}
764
+ }
765
+ const key = keys.shift()
766
+ // 自调用判断是否存在属性,不存在则自动创建对象
767
+ inFn(_obj[k], keys, v)
768
+ }
769
+ }
770
+
771
+ if (typeof key !== 'string' || key === '') {
772
+
773
+ } else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
774
+ const keys = key.split('.')
775
+ inFn(obj, keys, value)
776
+ } else {
777
+ obj[key] = value
778
+ }
779
+ }
780
+
781
+ /**
782
+ * @description 获取当前页面路径
783
+ */
784
+ export function page() {
785
+ const pages = getCurrentPages()
786
+ // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
787
+ return `/${pages[pages.length - 1].route || ''}`
788
+ }
789
+
790
+ /**
791
+ * @description 获取当前路由栈实例数组
792
+ */
793
+ export function pages() {
794
+ const pages = getCurrentPages()
795
+ return pages
796
+ }
797
+
798
+ export function getValueByPath(obj, path) {
799
+ // 将路径字符串按 '.' 分割成数组
800
+ const pathArr = path.split('.');
801
+ // 使用 reduce 方法从 obj 开始,逐级访问嵌套属性
802
+ return pathArr.reduce((acc, curr) => {
803
+ // 如果当前累加器(acc)是对象且包含当前键(curr),则返回该键对应的值
804
+ // 否则返回 undefined(表示路径不存在)
805
+ return acc && acc[curr] !== undefined ? acc[curr] : undefined;
806
+ }, obj);
807
+ }
808
+
809
+ /**
810
+ * 生成同色系浅色背景色
811
+ * @param {string} textColor - 支持 #RGB、#RRGGBB、rgb()、rgba() 格式
812
+ * @param {number} [lightness=85] - 目标亮度百分比(默认85%)
813
+ * @returns {string} 十六进制颜色值
814
+ */
815
+ export function genLightColor(textColor, lightness = 95) {
816
+ // 手动解析颜色值(避免使用document)
817
+ const rgb = parseColorWithoutDOM(textColor);
818
+
819
+ // RGB转HSL色域
820
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
821
+
822
+ // 生成浅色背景
823
+ const bgHsl = {
824
+ h: hsl.h,
825
+ s: hsl.s,
826
+ l: Math.min(lightness, 95)
827
+ };
828
+
829
+ return hslToHex(bgHsl.h, bgHsl.s, bgHsl.l);
830
+ }
831
+
832
+ /* 手动解析颜色字符串(兼容uni-app环境) */
833
+ function parseColorWithoutDOM(colorStr) {
834
+ // 统一转小写处理
835
+ const str = colorStr.toLowerCase().trim();
836
+
837
+ // 处理十六进制格式
838
+ if (str.startsWith('#')) {
839
+ const hex = str.replace('#', '');
840
+ const fullHex = hex.length === 3 ?
841
+ hex.split('').map(c => c + c).join('') : hex;
842
+
843
+ return {
844
+ r: parseInt(fullHex.substring(0,2), 16),
845
+ g: parseInt(fullHex.substring(2,4), 16),
846
+ b: parseInt(fullHex.substring(4,6), 16)
847
+ };
848
+ }
849
+
850
+ // 处理rgb/rgba格式
851
+ const rgbMatch = str.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
852
+ if (rgbMatch) {
853
+ return {
854
+ r: +rgbMatch[1],
855
+ g: +rgbMatch[2],
856
+ b: +rgbMatch[3]
857
+ };
858
+ }
859
+
860
+ throw new Error('Invalid color format');
861
+ }
862
+
863
+ // 辅助函数:RGB 转 HSL(色相、饱和度、亮度)
864
+ function rgbToHsl(r, g, b) {
865
+ r /= 255, g /= 255, b /= 255;
866
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
867
+ let h, s, l = (max + min) / 2;
868
+
869
+ if (max === min) {
870
+ h = s = 0; // achromatic
871
+ } else {
872
+ const d = max - min;
873
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
874
+ switch (max) {
875
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
876
+ case g: h = (b - r) / d + 2; break;
877
+ case b: h = (r - g) / d + 4; break;
878
+ }
879
+ h = (h * 60).toFixed(1);
880
+ }
881
+ return { h: +h, s: +(s * 100).toFixed(1), l: +(l * 100).toFixed(1) };
882
+ }
883
+
884
+ // 辅助函数:HSL 转十六进制
885
+ function hslToHex(h, s, l) {
886
+ l /= 100;
887
+ const a = s * Math.min(l, 1 - l) / 100;
888
+ const f = n => {
889
+ const k = (n + h / 30) % 12;
890
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
891
+ return Math.round(255 * color).toString(16).padStart(2, '0');
892
+ };
893
+ return `#${f(0)}${f(8)}${f(4)}`;
894
+ }
895
+
896
+ export default {
897
+ getRect,
898
+ range,
899
+ getPx,
900
+ sleep,
901
+ os,
902
+ sys,
903
+ getWindowInfo,
904
+ random,
905
+ guid,
906
+ $parent,
907
+ addStyle,
908
+ addUnit,
909
+ deepClone,
910
+ deepMerge,
911
+ shallowMerge,
912
+ error,
913
+ randomArray,
914
+ timeFormat,
915
+ timeFrom,
916
+ trim,
917
+ queryParams,
918
+ toast,
919
+ type2icon,
920
+ priceFormat,
921
+ getDuration,
922
+ padZero,
923
+ formValidate,
924
+ getProperty,
925
+ setProperty,
926
+ page,
927
+ pages,
928
+ getValueByPath,
929
+ genLightColor,
930
+ rpx2px
931
+ }
932
+