sculp-js 1.10.3 → 1.10.4

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 (63) hide show
  1. package/lib/cjs/array.js +1 -1
  2. package/lib/cjs/async.js +1 -1
  3. package/lib/cjs/base64.js +1 -1
  4. package/lib/cjs/clipboard.js +1 -1
  5. package/lib/cjs/cloneDeep.js +1 -1
  6. package/lib/cjs/cookie.js +1 -1
  7. package/lib/cjs/date.js +1 -1
  8. package/lib/cjs/dom.js +1 -1
  9. package/lib/cjs/download.js +1 -1
  10. package/lib/cjs/easing.js +1 -1
  11. package/lib/cjs/file.js +10 -9
  12. package/lib/cjs/func.js +1 -1
  13. package/lib/cjs/index.js +1 -1
  14. package/lib/cjs/isEqual.js +1 -1
  15. package/lib/cjs/math.js +1 -1
  16. package/lib/cjs/number.js +1 -1
  17. package/lib/cjs/object.js +1 -1
  18. package/lib/cjs/path.js +1 -1
  19. package/lib/cjs/qs.js +1 -1
  20. package/lib/cjs/random.js +1 -1
  21. package/lib/cjs/string.js +1 -1
  22. package/lib/cjs/tooltip.js +1 -1
  23. package/lib/cjs/tree.js +1 -1
  24. package/lib/cjs/type.js +1 -1
  25. package/lib/cjs/unique.js +1 -1
  26. package/lib/cjs/url.js +1 -1
  27. package/lib/cjs/validator.js +1 -1
  28. package/lib/cjs/variable.js +1 -1
  29. package/lib/cjs/watermark.js +1 -1
  30. package/lib/cjs/we-decode.js +1 -1
  31. package/lib/es/array.js +1 -1
  32. package/lib/es/async.js +1 -1
  33. package/lib/es/base64.js +1 -1
  34. package/lib/es/clipboard.js +1 -1
  35. package/lib/es/cloneDeep.js +1 -1
  36. package/lib/es/cookie.js +1 -1
  37. package/lib/es/date.js +1 -1
  38. package/lib/es/dom.js +1 -1
  39. package/lib/es/download.js +1 -1
  40. package/lib/es/easing.js +1 -1
  41. package/lib/es/file.js +10 -9
  42. package/lib/es/func.js +1 -1
  43. package/lib/es/index.js +1 -1
  44. package/lib/es/isEqual.js +1 -1
  45. package/lib/es/math.js +1 -1
  46. package/lib/es/number.js +1 -1
  47. package/lib/es/object.js +1 -1
  48. package/lib/es/path.js +1 -1
  49. package/lib/es/qs.js +1 -1
  50. package/lib/es/random.js +1 -1
  51. package/lib/es/string.js +1 -1
  52. package/lib/es/tooltip.js +1 -1
  53. package/lib/es/tree.js +1 -1
  54. package/lib/es/type.js +1 -1
  55. package/lib/es/unique.js +1 -1
  56. package/lib/es/url.js +1 -1
  57. package/lib/es/validator.js +1 -1
  58. package/lib/es/variable.js +1 -1
  59. package/lib/es/watermark.js +1 -1
  60. package/lib/es/we-decode.js +1 -1
  61. package/lib/index.d.ts +15 -5
  62. package/lib/umd/index.js +2 -3218
  63. package/package.json +2 -1
package/lib/umd/index.js CHANGED
@@ -1,3222 +1,6 @@
1
1
  /*!
2
- * sculp-js v1.10.3
2
+ * sculp-js v1.10.4
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
6
-
7
- (function (global, factory) {
8
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('bezier-easing')) :
9
- typeof define === 'function' && define.amd ? define(['exports', 'bezier-easing'], factory) :
10
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.sculpJs = {}, global.bezier));
11
- })(this, (function (exports, bezier) { 'use strict';
12
-
13
- /**
14
- * 遍历数组,返回 false 中断遍历(支持continue和break操作)
15
- *
16
- * @param {ArrayLike<V>} array
17
- * @param {(val: V, idx: number) => any} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
18
- * @param {boolean} reverse 是否倒序
19
- * @returns {*}
20
- */
21
- function arrayEach(array, iterator, reverse = false) {
22
- if (reverse) {
23
- for (let idx = array.length - 1; idx >= 0; idx--) {
24
- const val = array[idx];
25
- const re = iterator(val, idx, array);
26
- if (re === false)
27
- break;
28
- else if (re === true)
29
- continue;
30
- }
31
- }
32
- else {
33
- for (let idx = 0, len = array.length; idx < len; idx++) {
34
- const val = array[idx];
35
- const re = iterator(val, idx, array);
36
- if (re === false)
37
- break;
38
- else if (re === true)
39
- continue;
40
- }
41
- }
42
- }
43
- /**
44
- * 异步遍历数组,返回 false 中断遍历
45
- * @param {ArrayLike<V>} array 数组
46
- * @param {(val: V, idx: number) => Promise<any>} iterator 支持Promise类型的回调函数
47
- * @param {boolean} reverse 是否反向遍历
48
- * @example
49
- * 使用范例如下:
50
- * const start = async () => {
51
- * await arrayEachAsync(result, async (item) => {
52
- * await request(item);
53
- * count++;
54
- * })
55
- * console.log('发送次数', count);
56
- * }
57
-
58
- * for await...of 使用范例如下
59
- * const loadImages = async (images) => {
60
- * for await (const item of images) {
61
- * await request(item);
62
- * count++;
63
- * }
64
- * }
65
- */
66
- async function arrayEachAsync(array, iterator, reverse = false) {
67
- if (reverse) {
68
- for (let idx = array.length - 1; idx >= 0; idx--) {
69
- const val = array[idx];
70
- if ((await iterator(val, idx)) === false)
71
- break;
72
- }
73
- }
74
- else {
75
- for (let idx = 0, len = array.length; idx < len; idx++) {
76
- const val = array[idx];
77
- if ((await iterator(val, idx)) === false)
78
- break;
79
- }
80
- }
81
- }
82
- /**
83
- * 插入到目标位置之前
84
- * @param {AnyArray} array
85
- * @param {number} start
86
- * @param {number} to
87
- * @returns {*}
88
- */
89
- function arrayInsertBefore(array, start, to) {
90
- if (start === to || start + 1 === to)
91
- return;
92
- const [source] = array.splice(start, 1);
93
- const insertIndex = to < start ? to : to - 1;
94
- array.splice(insertIndex, 0, source);
95
- }
96
- /**
97
- * 数组删除指定项目
98
- * @param {V[]} array
99
- * @param {(val: V, idx: number) => boolean} expect
100
- * @returns {V[]}
101
- */
102
- function arrayRemove(array, expect) {
103
- const indexes = [];
104
- // 这里重命名一下:是为了杜绝 jest 里的 expect 与之产生检查错误
105
- // eslint 的 jest 语法检查是遇到 expect 函数就以为是单元测试
106
- const _expect = expect;
107
- arrayEach(array, (val, idx) => {
108
- if (_expect(val, idx))
109
- indexes.push(idx);
110
- });
111
- indexes.forEach((val, idx) => {
112
- array.splice(val - idx, 1);
113
- });
114
- return array;
115
- }
116
-
117
- /**
118
- * 复制文本
119
- * @param {string} text
120
- */
121
- function copyText(text) {
122
- const textEl = document.createElement('textarea');
123
- textEl.style.position = 'absolute';
124
- textEl.style.top = '-9999px';
125
- textEl.style.left = '-9999px';
126
- textEl.value = text;
127
- document.body.appendChild(textEl);
128
- textEl.focus({ preventScroll: true });
129
- textEl.select();
130
- try {
131
- document.execCommand('copy');
132
- textEl.blur();
133
- document.body.removeChild(textEl);
134
- }
135
- catch (err) {
136
- // ignore
137
- }
138
- }
139
-
140
- // 常用类型定义
141
- /**
142
- * 判断对象内是否有该静态属性
143
- * @param {object} obj
144
- * @param {string} key
145
- * @returns {boolean}
146
- */
147
- function objectHas(obj, key) {
148
- return Object.prototype.hasOwnProperty.call(obj, key);
149
- }
150
- /**
151
- * 判断一个对象是否为类数组
152
- *
153
- * @param any
154
- * @returns {boolean}
155
- */
156
- function arrayLike(any) {
157
- if (isArray(any))
158
- return true;
159
- if (isString(any))
160
- return true;
161
- if (!isObject(any))
162
- return false;
163
- return objectHas(any, 'length');
164
- }
165
- /**
166
- * 判断任意值的数据类型,检查非对象时不如typeof、instanceof的性能高
167
- *
168
- * 当检查类对象时是不可靠的,对象可以通过定义 Symbol.toStringTag 属性来更改检查结果
169
- *
170
- * 详见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
171
- * @param {unknown} any
172
- * @returns
173
- */
174
- function typeIs(any) {
175
- return Object.prototype.toString.call(any).slice(8, -1);
176
- }
177
- // 基本数据类型判断
178
- const isString = (any) => typeof any === 'string';
179
- const isBoolean = (any) => typeof any === 'boolean';
180
- const isSymbol = (any) => typeof any === 'symbol';
181
- const isBigInt = (any) => typeof any === 'bigint';
182
- const isNumber = (any) => typeof any === 'number' && !Number.isNaN(any);
183
- const isUndefined = (any) => typeof any === 'undefined';
184
- const isNull = (any) => any === null;
185
- const isPrimitive = (any) => any === null || typeof any !== 'object';
186
- function isNullOrUnDef(val) {
187
- return isUndefined(val) || isNull(val);
188
- }
189
- // 复合数据类型判断
190
- const isObject = (any) => typeIs(any) === 'Object';
191
- const isArray = (any) => Array.isArray(any);
192
- /**
193
- * 判断是否为函数
194
- * @param {unknown} any
195
- * @returns {boolean}
196
- */
197
- const isFunction = (any) => typeof any === 'function';
198
- // 对象类型判断
199
- const isNaN = (any) => Number.isNaN(any);
200
- const isDate = (any) => typeIs(any) === 'Date';
201
- const isError = (any) => typeIs(any) === 'Error';
202
- const isRegExp = (any) => typeIs(any) === 'RegExp';
203
- /**
204
- * 判断一个字符串是否为有效的 JSON, 若有效则返回有效的JSON对象,否则false
205
- * @param {string} str
206
- * @returns {Object | boolean}
207
- */
208
- function isJsonString(str) {
209
- try {
210
- const parsed = JSON.parse(str);
211
- return typeof parsed === 'object' && parsed !== null ? parsed : false;
212
- }
213
- catch (e) {
214
- return false;
215
- }
216
- }
217
- /**
218
- * Checks if `value` is an empty object, collection, map, or set.
219
- *
220
- * Objects are considered empty if they have no own enumerable string keyed
221
- * properties.
222
- *
223
- * Array-like values such as `arguments` objects, arrays, buffers, strings, or
224
- * jQuery-like collections are considered empty if they have a `length` of `0`.
225
- * Similarly, maps and sets are considered empty if they have a `size` of `0`.
226
- *
227
- * @param {*} value The value to check.
228
- * @returns {boolean} Returns `true` if `value` is empty, else `false`.
229
- * @example
230
- *
231
- * isEmpty(null);
232
- * // => true
233
- *
234
- * isEmpty(true);
235
- * // => true
236
- *
237
- * isEmpty(1);
238
- * // => true
239
- *
240
- * isEmpty([1, 2, 3]);
241
- * // => false
242
- *
243
- * isEmpty({ 'a': 1 });
244
- * // => false
245
- */
246
- function isEmpty(value) {
247
- if (isNullOrUnDef(value) || Number.isNaN(value)) {
248
- return true;
249
- }
250
- if (arrayLike(value) && (isArray(value) || isString(value) || isFunction(value.splice))) {
251
- return !value.length;
252
- }
253
- return !Object.keys(value).length;
254
- }
255
-
256
- /**
257
- * 获取cookie
258
- * @param {string} name
259
- * @returns {string}
260
- */
261
- function cookieGet(name) {
262
- const { cookie } = document;
263
- if (!cookie)
264
- return '';
265
- const result = cookie.split(';');
266
- for (let i = 0; i < result.length; i++) {
267
- const item = result[i];
268
- const [key, val = ''] = item.split('=');
269
- if (key === name)
270
- return decodeURIComponent(val);
271
- }
272
- return '';
273
- }
274
- /**
275
- * 设置 cookie
276
- * @param {string} name
277
- * @param {string} value
278
- * @param {number | Date} [maxAge]
279
- */
280
- function cookieSet(name, value, maxAge) {
281
- const metas = [];
282
- const EXPIRES = 'expires';
283
- metas.push([name, encodeURIComponent(value)]);
284
- if (isNumber(maxAge)) {
285
- const d = new Date();
286
- d.setTime(d.getTime() + maxAge);
287
- metas.push([EXPIRES, d.toUTCString()]);
288
- }
289
- else if (isDate(maxAge)) {
290
- metas.push([EXPIRES, maxAge.toUTCString()]);
291
- }
292
- metas.push(['path', '/']);
293
- document.cookie = metas
294
- .map(item => {
295
- const [key, val] = item;
296
- return `${key}=${val}`;
297
- })
298
- .join(';');
299
- }
300
- /**
301
- * 删除单个 cookie
302
- * @param name cookie 名称
303
- */
304
- const cookieDel = (name) => cookieSet(name, '', -1);
305
-
306
- const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
307
- /* istanbul ignore next */
308
- const guessDateSeparator = (value) => {
309
- if (!isString(value))
310
- return;
311
- const value2 = value.replace(/-/g, '/');
312
- return new Date(value2);
313
- };
314
- /* istanbul ignore next */
315
- const guessDateTimezone = (value) => {
316
- if (!isString(value))
317
- return;
318
- const re = /([+-])(\d\d)(\d\d)$/;
319
- const matches = re.exec(value);
320
- if (!matches)
321
- return;
322
- const value2 = value.replace(re, 'Z');
323
- const d = new Date(value2);
324
- if (!isValidDate(d))
325
- return;
326
- const [, flag, hours, minutes] = matches;
327
- const hours2 = parseInt(hours, 10);
328
- const minutes2 = parseInt(minutes, 10);
329
- const offset = (a, b) => (flag === '+' ? a - b : a + b);
330
- d.setHours(offset(d.getHours(), hours2));
331
- d.setMinutes(offset(d.getMinutes(), minutes2));
332
- return d;
333
- };
334
- /**
335
- * 解析为Date对象
336
- * @param {DateValue} value - 可以是数值、字符串或 Date 对象
337
- * @returns {Date} - 转换后的目标Date
338
- */
339
- function dateParse(value) {
340
- const d1 = new Date(value);
341
- if (isValidDate(d1))
342
- return d1;
343
- // safari 浏览器的日期解析有问题
344
- // new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
345
- /* istanbul ignore next */
346
- const d2 = guessDateSeparator(value);
347
- /* istanbul ignore next */
348
- if (isValidDate(d2))
349
- return d2;
350
- // safari 浏览器的日期解析有问题
351
- // new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
352
- /* istanbul ignore next */
353
- const d3 = guessDateTimezone(value);
354
- /* istanbul ignore next */
355
- if (isValidDate(d3))
356
- return d3;
357
- throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
358
- }
359
- /**
360
- * 格式化为日期对象(带自定义格式化模板)
361
- * @param {DateValue} value 可以是数值、字符串或 Date 对象
362
- * @param {string} [format] 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
363
- * - YYYY:年
364
- * - yyyy: 年
365
- * - MM:月
366
- * - DD:日
367
- * - dd: 日
368
- * - HH:时(24 小时制)
369
- * - hh:时(12 小时制)
370
- * - mm:分
371
- * - ss:秒
372
- * - SSS:毫秒
373
- * @returns {string}
374
- */
375
- // export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
376
- // const date = dateParse(value);
377
- // let fmt = format;
378
- // let ret;
379
- // const opt: DateObj = {
380
- // 'Y+': `${date.getFullYear()}`, // 年
381
- // 'y+': `${date.getFullYear()}`, // 年
382
- // 'M+': `${date.getMonth() + 1}`, // 月
383
- // 'D+': `${date.getDate()}`, // 日
384
- // 'd+': `${date.getDate()}`, // 日
385
- // 'H+': `${date.getHours()}`, // 时
386
- // 'm+': `${date.getMinutes()}`, // 分
387
- // 's+': `${date.getSeconds()}`, // 秒
388
- // 'S+': `${date.getMilliseconds()}` // 豪秒
389
- // };
390
- // for (const k in opt) {
391
- // ret = new RegExp(`(${k})`).exec(fmt);
392
- // if (ret) {
393
- // fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
394
- // }
395
- // }
396
- // return fmt;
397
- // };
398
- /**
399
- * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
400
- * @param {DateValue} value
401
- * @returns {Date}
402
- */
403
- function dateToStart(value) {
404
- const d = dateParse(value);
405
- return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
406
- }
407
- /**
408
- * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
409
- * @param {DateValue} value
410
- * @returns {Date}
411
- */
412
- function dateToEnd(value) {
413
- const d = dateToStart(value);
414
- d.setDate(d.getDate() + 1);
415
- return dateParse(d.getTime() - 1);
416
- }
417
- /**
418
- * 格式化为日期对象(带自定义格式化模板)
419
- * @param {Date} value - 可以是数值、字符串或 Date 对象
420
- * @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
421
- * - YYYY:年
422
- * - yyyy: 年
423
- * - MM:月
424
- * - DD:日
425
- * - dd: 日
426
- * - HH:时(24 小时制)
427
- * - mm:分
428
- * - ss:秒
429
- * - SSS:毫秒
430
- * - ww: 周
431
- * @returns {string} 格式化后的日期字符串
432
- */
433
- function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
434
- const date = dateParse(value);
435
- let fmt = format;
436
- let ret;
437
- const opt = {
438
- 'Y+': `${date.getFullYear()}`,
439
- 'y+': `${date.getFullYear()}`,
440
- 'M+': `${date.getMonth() + 1}`,
441
- 'D+': `${date.getDate()}`,
442
- 'd+': `${date.getDate()}`,
443
- 'H+': `${date.getHours()}`,
444
- 'm+': `${date.getMinutes()}`,
445
- 's+': `${date.getSeconds()}`,
446
- 'S+': `${date.getMilliseconds()}`,
447
- 'w+': ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()] // 周
448
- // 有其他格式化字符需求可以继续添加,必须转化成字符串
449
- };
450
- for (const k in opt) {
451
- ret = new RegExp('(' + k + ')').exec(fmt);
452
- if (ret) {
453
- fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
454
- }
455
- }
456
- return fmt;
457
- }
458
- /**
459
- * 计算向前或向后N天的具体日期
460
- * @param {DateValue} originDate - 参考日期
461
- * @param {number} n - 正数:向后推算;负数:向前推算
462
- * @param {string} sep - 日期格式的分隔符
463
- * @returns {string} 计算后的目标日期
464
- */
465
- function calculateDate(originDate, n, sep = '-') {
466
- //originDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
467
- const date = new Date(originDate); //这边给定一个特定时间
468
- const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
469
- const millisecondGap = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
470
- const targetDate = new Date(millisecondGap);
471
- const finalNewDate = targetDate.getFullYear() +
472
- sep +
473
- String(targetDate.getMonth() + 1).padStart(2, '0') +
474
- '-' +
475
- String(targetDate.getDate()).padStart(2, '0');
476
- return finalNewDate;
477
- }
478
- /**
479
- * 计算向前或向后N天的具体日期时间
480
- * @param {DateValue} originDateTime - 参考日期时间
481
- * @param {number} n - 正数:向后推算;负数:向前推算
482
- * @param {string} dateSep - 日期分隔符
483
- * @param {string} timeSep - 时间分隔符
484
- * @returns {string} 转换后的目标日期时间
485
- */
486
- function calculateDateTime(originDateTime, n, dateSep = '-', timeSep = ':') {
487
- const date = new Date(originDateTime);
488
- const separator1 = dateSep;
489
- const separator2 = timeSep;
490
- const dateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
491
- const millisecondGap = dateTime.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
492
- const targetDateTime = new Date(millisecondGap);
493
- return (targetDateTime.getFullYear() +
494
- separator1 +
495
- String(targetDateTime.getMonth() + 1).padStart(2, '0') +
496
- separator1 +
497
- String(targetDateTime.getDate()).padStart(2, '0') +
498
- ' ' +
499
- String(targetDateTime.getHours()).padStart(2, '0') +
500
- separator2 +
501
- String(targetDateTime.getMinutes()).padStart(2, '0') +
502
- separator2 +
503
- String(targetDateTime.getSeconds()).padStart(2, '0'));
504
- }
505
-
506
- // @ref https://cubic-bezier.com/
507
- const easingDefines = {
508
- linear: [0, 0, 1, 1],
509
- ease: [0.25, 0.1, 0.25, 1],
510
- 'ease-in': [0.42, 0, 1, 1],
511
- 'ease-out': [0, 0, 0.58, 1],
512
- 'ease-in-out': [0.42, 0, 0.58, 1]
513
- };
514
- /**
515
- * 缓冲函数化,用于 js 计算缓冲进度
516
- * @param {EasingNameOrDefine} [name=linear]
517
- * @returns {EasingFunction}
518
- */
519
- function easingFunctional(name) {
520
- let fn;
521
- if (isArray(name)) {
522
- fn = bezier(...name);
523
- }
524
- else {
525
- const define = easingDefines[name];
526
- if (!define) {
527
- throw new Error(`${name} 缓冲函数未定义`);
528
- }
529
- fn = bezier(...define);
530
- }
531
- return (input) => fn(Math.max(0, Math.min(input, 1)));
532
- }
533
-
534
- /**
535
- * 判断对象是否为纯对象
536
- * @param {object} obj
537
- * @returns {boolean}
538
- */
539
- const isPlainObject = (obj) => {
540
- if (!isObject(obj))
541
- return false;
542
- const proto = Object.getPrototypeOf(obj);
543
- // 对象无原型
544
- if (!proto)
545
- return true;
546
- // 是否对象直接实例
547
- return proto === Object.prototype;
548
- };
549
- /**
550
- * 遍历对象,返回 false 中断遍历
551
- * @param {O} obj
552
- * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
553
- */
554
- function objectEach(obj, iterator) {
555
- for (const key in obj) {
556
- if (!objectHas(obj, key))
557
- continue;
558
- if (iterator(obj[key], key) === false)
559
- break;
560
- }
561
- }
562
- /**
563
- * 异步遍历对象,返回 false 中断遍历
564
- * @param {O} obj
565
- * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
566
- */
567
- async function objectEachAsync(obj, iterator) {
568
- for (const key in obj) {
569
- if (!objectHas(obj, key))
570
- continue;
571
- if ((await iterator(obj[key], key)) === false)
572
- break;
573
- }
574
- }
575
- /**
576
- * 对象映射
577
- * @param {O} obj
578
- * @param {(val: O[keyof O], key: Extract<keyof O, string>) => any} iterator
579
- * @returns {Record<Extract<keyof O, string>, T>}
580
- */
581
- function objectMap(obj, iterator) {
582
- const obj2 = {};
583
- for (const key in obj) {
584
- if (!objectHas(obj, key))
585
- continue;
586
- obj2[key] = iterator(obj[key], key);
587
- }
588
- return obj2;
589
- }
590
- /**
591
- * 对象提取
592
- * @param {O} obj
593
- * @param {K} keys
594
- * @returns {Pick<O, ArrayElements<K>>}
595
- */
596
- function objectPick(obj, keys) {
597
- const obj2 = {};
598
- objectEach(obj, (v, k) => {
599
- if (keys.includes(k)) {
600
- // @ts-ignore
601
- obj2[k] = v;
602
- }
603
- });
604
- return obj2;
605
- }
606
- /**
607
- * 对象去除
608
- * @param {O} obj
609
- * @param {K} keys
610
- * @returns {Pick<O, ArrayElements<K>>}
611
- */
612
- function objectOmit(obj, keys) {
613
- const obj2 = {};
614
- objectEach(obj, (v, k) => {
615
- if (!keys.includes(k)) {
616
- // @ts-ignore
617
- obj2[k] = v;
618
- }
619
- });
620
- return obj2;
621
- }
622
- const merge = (map, source, target) => {
623
- if (isUndefined(target))
624
- return source;
625
- const sourceType = typeIs(source);
626
- const targetType = typeIs(target);
627
- if (sourceType !== targetType) {
628
- if (isArray(target))
629
- return merge(map, [], target);
630
- if (isObject(target))
631
- return merge(map, {}, target);
632
- return target;
633
- }
634
- // 朴素对象
635
- if (isPlainObject(target)) {
636
- const exist = map.get(target);
637
- if (exist)
638
- return exist;
639
- map.set(target, source);
640
- objectEach(target, (val, key) => {
641
- source[key] = merge(map, source[key], val);
642
- });
643
- return source;
644
- }
645
- // 数组
646
- else if (isArray(target)) {
647
- const exist = map.get(target);
648
- if (exist)
649
- return exist;
650
- map.set(target, source);
651
- target.forEach((val, index) => {
652
- source[index] = merge(map, source[index], val);
653
- });
654
- return source;
655
- }
656
- return target;
657
- };
658
- /**
659
- * 对象合并,返回原始对象
660
- * @param {ObjectAssignItem} source
661
- * @param {ObjectAssignItem | undefined} targets
662
- * @returns {R}
663
- */
664
- function objectAssign(source, ...targets) {
665
- const map = new Map();
666
- for (let i = 0, len = targets.length; i < len; i++) {
667
- const target = targets[i];
668
- // @ts-ignore
669
- source = merge(map, source, target);
670
- }
671
- map.clear();
672
- return source;
673
- }
674
- /**
675
- * 对象填充
676
- * @param {Partial<R>} source
677
- * @param {Partial<R>} target
678
- * @param {(s: Partial<R>, t: Partial<R>, key: keyof R) => boolean} fillable
679
- * @returns {R}
680
- */
681
- function objectFill(source, target, fillable) {
682
- const _fillable = fillable || ((source, target, key) => source[key] === undefined);
683
- objectEach(target, (val, key) => {
684
- if (_fillable(source, target, key)) {
685
- source[key] = val;
686
- }
687
- });
688
- return source;
689
- }
690
- /**
691
- * 获取对象指定层级下的属性值(现在可用ES6+的可选链?.来替代)
692
- * @param {AnyObject} obj
693
- * @param {string} path
694
- * @param {boolean} strict
695
- * @returns
696
- */
697
- function objectGet(obj, path, strict = false) {
698
- path = path.replace(/\[(\w+)\]/g, '.$1');
699
- path = path.replace(/^\./, '');
700
- const keyArr = path.split('.');
701
- let tempObj = obj;
702
- let i = 0;
703
- for (let len = keyArr.length; i < len - 1; ++i) {
704
- const key = keyArr[i];
705
- if (isNumber(Number(key)) && Array.isArray(tempObj)) {
706
- tempObj = tempObj[key];
707
- }
708
- else if (isObject(tempObj) && objectHas(tempObj, key)) {
709
- tempObj = tempObj[key];
710
- }
711
- else {
712
- tempObj = undefined;
713
- if (strict) {
714
- throw new Error('[Object] objectGet path 路径不正确');
715
- }
716
- break;
717
- }
718
- }
719
- return {
720
- p: tempObj,
721
- k: tempObj ? keyArr[i] : undefined,
722
- v: tempObj ? tempObj[keyArr[i]] : undefined
723
- };
724
- }
725
-
726
- /**
727
- * 将字符串转换为驼峰格式
728
- * @param {string} string
729
- * @param {boolean} [bigger] 是否大写第一个字母
730
- * @returns {string}
731
- */
732
- function stringCamelCase(string, bigger) {
733
- let string2 = string;
734
- if (bigger) {
735
- string2 = string.replace(/^./, origin => origin.toUpperCase());
736
- }
737
- const HUMP_RE = /[\s_-](.)/g;
738
- return string2.replace(HUMP_RE, (orign, char) => char.toUpperCase());
739
- }
740
- /**
741
- * 将字符串转换为连字格式
742
- * @param {string} string
743
- * @param {string} [separator] 分隔符,默认是"-"(短横线)
744
- * @returns {string}
745
- */
746
- function stringKebabCase(string, separator = '-') {
747
- const string2 = string.replace(/^./, origin => origin.toLowerCase());
748
- return string2.replace(/[A-Z]/g, origin => `${separator}${origin.toLowerCase()}`);
749
- }
750
- const STRING_ARABIC_NUMERALS = '0123456789';
751
- const STRING_LOWERCASE_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
752
- const STRING_UPPERCASE_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
753
- const placeholderRE = /%[%sdo]/g;
754
- /**
755
- * 字符串格式化
756
- * @example
757
- * ```js
758
- * stringFormat("My name is %s.", "zhangsan")
759
- * // => "My name is zhangsan."
760
- * ```
761
- * @param {string} string 字符串模板,使用 %s 表示字符串,%d 表示数值,%o 表示对象,%% 表示百分号,参考 console.log
762
- * @param args
763
- * @returns {string}
764
- */
765
- function stringFormat(string, ...args) {
766
- let index = 0;
767
- const result = string.replace(placeholderRE, (origin) => {
768
- const arg = args[index++];
769
- switch (origin) {
770
- case '%%':
771
- index--;
772
- return '%';
773
- default:
774
- case '%s':
775
- return String(arg);
776
- case '%d':
777
- return String(Number(arg));
778
- case '%o':
779
- return JSON.stringify(arg);
780
- }
781
- });
782
- return [result, ...args.splice(index).map(String)].join(' ');
783
- }
784
- const ev = (expression, data) => {
785
- try {
786
- // eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-return
787
- return new Function('with(arguments[0]){' +
788
- /****/ `if(arguments[0].${expression} === undefined)throw "";` +
789
- /****/
790
- /****/ `return String(arguments[0].${expression})` +
791
- '}')(data);
792
- }
793
- catch (err) {
794
- throw new SyntaxError(`无法执行表达式:${expression}`);
795
- }
796
- };
797
- const templateRE = /\${(.*?)}/g;
798
- /**
799
- * 字符串赋值
800
- * @example
801
- * ```js
802
- * stringAssign('My name is ${user}.', { user: 'zhangsan' });
803
- * // => "My name is zhangsan."
804
- * ```
805
- * @param {string} template
806
- * @param {AnyObject} data
807
- * @returns {string}
808
- */
809
- const stringAssign = (template, data) => {
810
- return template.replace(templateRE, (origin, expression) => ev(expression, data));
811
- };
812
- /**
813
- * 字符串编码 HTML
814
- * @example
815
- * ```js
816
- * stringEscapeHtml('<b>You & Me speak "xixi"</b>')
817
- * // => "&lt;b&gt;You &amp; Me speak &quot;xixi&quot;&lt;/b&gt;"
818
- * ```
819
- * @param {string} html
820
- * @returns {string}
821
- */
822
- const stringEscapeHtml = (html) => {
823
- const htmlCharRE = /[&<>"]/g;
824
- const htmlCharReplacements = {
825
- '&': '&amp;',
826
- '<': '&lt;',
827
- '>': '&gt;',
828
- '"': '&quot;'
829
- };
830
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
831
- // @ts-ignore
832
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
833
- return html.replace(htmlCharRE, $0 => htmlCharReplacements[$0]);
834
- };
835
- /**
836
- * 字符串填充
837
- * @param {number} length
838
- * @param {string} value
839
- * @returns {string}
840
- */
841
- const stringFill = (length, value = ' ') => new Array(length).fill(value).join('');
842
- /**
843
- * 解析URL查询参数
844
- * @param {string} searchStr
845
- * @returns {Record<string, string | string[]>}
846
- */
847
- function parseQueryParams(searchStr = location.search) {
848
- const queryObj = {};
849
- Array.from(searchStr.matchAll(/[&?]?([^=&]+)=?([^=&]*)/g)).forEach((item, i) => {
850
- if (!queryObj[item[1]]) {
851
- queryObj[item[1]] = item[2];
852
- }
853
- else if (typeof queryObj[item[1]] === 'string') {
854
- queryObj[item[1]] = [queryObj[item[1]], item[2]];
855
- }
856
- else {
857
- queryObj[item[1]].push(item[2]);
858
- }
859
- });
860
- return queryObj;
861
- }
862
-
863
- /**
864
- * 判断元素是否包含某个样式名
865
- * @param {HTMLElement} el
866
- * @param {string} className
867
- * @returns {boolean}
868
- */
869
- function hasClass(el, className) {
870
- if (className.indexOf(' ') !== -1)
871
- throw new Error('className should not contain space.');
872
- return el.classList.contains(className);
873
- }
874
- const eachClassName = (classNames, func) => {
875
- const classNameList = classNames.split(/\s+/g);
876
- classNameList.forEach(func);
877
- };
878
- /**
879
- * 给元素增加样式名
880
- * @param {HTMLElement} el
881
- * @param {string} classNames
882
- */
883
- function addClass(el, classNames) {
884
- eachClassName(classNames, className => el.classList.add(className));
885
- }
886
- /**
887
- * 给元素移除样式名
888
- * @param {HTMLElement} el
889
- * @param {string} classNames
890
- */
891
- function removeClass(el, classNames) {
892
- eachClassName(classNames, className => el.classList.remove(className));
893
- }
894
- /**
895
- * 设置元素样式
896
- * @param {HTMLElement} el
897
- * @param {string | Style} key
898
- * @param {string} val
899
- */
900
- const setStyle = (el, key, val) => {
901
- if (isObject(key)) {
902
- objectEach(key, (val1, key1) => {
903
- setStyle(el, key1, val1);
904
- });
905
- }
906
- else {
907
- el.style.setProperty(stringKebabCase(key), val);
908
- }
909
- };
910
- /**
911
- * 获取元素样式
912
- * @param {HTMLElement} el 元素
913
- * @param {string} key
914
- * @returns {string}
915
- */
916
- function getStyle(el, key) {
917
- return getComputedStyle(el).getPropertyValue(key);
918
- }
919
- function smoothScroll(options) {
920
- return new Promise(resolve => {
921
- const defaults = {
922
- el: document,
923
- to: 0,
924
- duration: 567,
925
- easing: 'ease'
926
- };
927
- const { el, to, duration, easing } = objectAssign(defaults, options);
928
- const htmlEl = document.documentElement;
929
- const bodyEl = document.body;
930
- const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
931
- const els = globalMode ? [htmlEl, bodyEl] : [el];
932
- const query = () => {
933
- let value = 0;
934
- arrayEach(els, el => {
935
- if ('scrollTop' in el) {
936
- value = el.scrollTop;
937
- return false;
938
- }
939
- });
940
- return value;
941
- };
942
- const update = (val) => {
943
- els.forEach(el => {
944
- if ('scrollTop' in el) {
945
- el.scrollTop = val;
946
- }
947
- });
948
- };
949
- let startTime;
950
- const startValue = query();
951
- const length = to - startValue;
952
- const easingFn = easingFunctional(easing);
953
- const render = () => {
954
- const now = performance.now();
955
- const passingTime = startTime ? now - startTime : 0;
956
- const t = passingTime / duration;
957
- const p = easingFn(t);
958
- if (!startTime)
959
- startTime = now;
960
- update(startValue + length * p);
961
- if (t >= 1)
962
- resolve();
963
- else
964
- requestAnimationFrame(render);
965
- };
966
- render();
967
- });
968
- }
969
- /**
970
- * 获取元素样式属性的计算值
971
- * @param {HTMLElement} el
972
- * @param {string} property
973
- * @param {boolean} reNumber
974
- * @returns {string|number}
975
- */
976
- function getComputedCssVal(el, property, reNumber = true) {
977
- const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
978
- return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
979
- }
980
- /**
981
- * 字符串的像素宽度
982
- * @param {string} str 目标字符串
983
- * @param {number} fontSize 字符串字体大小
984
- * @param {boolean} isRemove 计算后是否移除创建的dom元素
985
- * @returns {*}
986
- */
987
- function getStrWidthPx(str, fontSize = 14, isRemove = true) {
988
- let strWidth = 0;
989
- console.assert(isString(str), `${str} 不是有效的字符串`);
990
- if (isString(str) && str.length > 0) {
991
- const id = 'getStrWidth1494304949567';
992
- let getEle = document.querySelector(`#${id}`);
993
- if (!getEle) {
994
- const _ele = document.createElement('span');
995
- _ele.id = id;
996
- _ele.style.fontSize = fontSize + 'px';
997
- _ele.style.whiteSpace = 'nowrap';
998
- _ele.style.visibility = 'hidden';
999
- _ele.style.position = 'absolute';
1000
- _ele.style.top = '-9999px';
1001
- _ele.style.left = '-9999px';
1002
- _ele.textContent = str;
1003
- document.body.appendChild(_ele);
1004
- getEle = _ele;
1005
- }
1006
- getEle.textContent = str;
1007
- strWidth = getEle.offsetWidth;
1008
- if (isRemove) {
1009
- getEle.remove();
1010
- }
1011
- }
1012
- return strWidth;
1013
- }
1014
-
1015
- /**
1016
- * 标准化路径
1017
- * @param {string} path
1018
- * @returns {string}
1019
- */
1020
- const pathNormalize = (path) => {
1021
- const slices = path
1022
- .replace(/\\/g, '/')
1023
- .replace(/\/{2,}/g, '/')
1024
- .replace(/\.{3,}/g, '..')
1025
- .replace(/\/\.\//g, '/')
1026
- .split('/')
1027
- .map(point => point.trim());
1028
- const isCurrentSlice = (slice) => slice === '.';
1029
- const isParentSlice = (slice) => slice === '..';
1030
- const points = [];
1031
- let inPoints = false;
1032
- const push = (point) => {
1033
- points.push(point);
1034
- };
1035
- const back = () => {
1036
- if (points.length === 0)
1037
- return;
1038
- const lastSlice = points[points.length - 1];
1039
- if (isParentSlice(lastSlice)) {
1040
- points.push('..');
1041
- }
1042
- else {
1043
- points.pop();
1044
- }
1045
- };
1046
- slices.forEach(slice => {
1047
- const isCurrent = isCurrentSlice(slice);
1048
- const isParent = isParentSlice(slice);
1049
- // 未进入实际路径
1050
- if (!inPoints) {
1051
- push(slice);
1052
- inPoints = !isCurrent && !isParent;
1053
- return;
1054
- }
1055
- if (isCurrent)
1056
- return;
1057
- if (isParent)
1058
- return back();
1059
- push(slice);
1060
- });
1061
- return points.join('/');
1062
- };
1063
- /**
1064
- * 路径合并
1065
- * @param {string} from
1066
- * @param {string} to
1067
- * @returns {string}
1068
- */
1069
- const pathJoin = (from, ...to) => pathNormalize([from, ...to].join('/'));
1070
-
1071
- /**
1072
- * 解析查询参数,内部使用的是浏览器内置的 URLSearchParams 进行处理
1073
- * @param {string} queryString
1074
- * @returns {Params}
1075
- */
1076
- function qsParse(queryString) {
1077
- const params = new URLSearchParams(queryString);
1078
- const result = {};
1079
- for (const [key, val] of params.entries()) {
1080
- if (isUndefined(result[key])) {
1081
- result[key] = val;
1082
- continue;
1083
- }
1084
- if (isArray(result[key])) {
1085
- continue;
1086
- }
1087
- result[key] = params.getAll(key);
1088
- }
1089
- return result;
1090
- }
1091
- const defaultReplacer = (val) => {
1092
- if (isString(val))
1093
- return val;
1094
- if (isNumber(val))
1095
- return String(val);
1096
- if (isBoolean(val))
1097
- return val ? 'true' : 'false';
1098
- if (isDate(val))
1099
- return val.toISOString();
1100
- return null;
1101
- };
1102
- /**
1103
- * 字符化查询对象,内部使用的是浏览器内置的 URLSearchParams 进行处理
1104
- * @param {LooseParams} query
1105
- * @param {Replacer} replacer
1106
- * @returns {string}
1107
- */
1108
- function qsStringify(query, replacer = defaultReplacer) {
1109
- const params = new URLSearchParams();
1110
- objectEach(query, (val, key) => {
1111
- if (isArray(val)) {
1112
- val.forEach(i => {
1113
- const replaced = replacer(i);
1114
- if (replaced === null)
1115
- return;
1116
- params.append(key.toString(), replaced);
1117
- });
1118
- }
1119
- else {
1120
- const replaced = replacer(val);
1121
- if (replaced === null)
1122
- return;
1123
- params.set(key.toString(), replaced);
1124
- }
1125
- });
1126
- return params.toString();
1127
- }
1128
-
1129
- /**
1130
- * url 解析
1131
- * @param {string} url
1132
- * @param {boolean} isModernApi 使用现代API:URL, 默认true (对无效url解析会抛错), 否则使用a标签来解析(兼容性更强)
1133
- * @returns {Url}
1134
- */
1135
- const urlParse = (url, isModernApi = true) => {
1136
- // @ts-ignore
1137
- let urlObj = null;
1138
- if (isFunction(URL) && isModernApi) {
1139
- urlObj = new URL(url);
1140
- }
1141
- else {
1142
- urlObj = document.createElement('a');
1143
- urlObj.href = url;
1144
- }
1145
- const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = urlObj;
1146
- // fix: ie 浏览器下,解析出来的 pathname 是没有 / 根的
1147
- const pathname = pathJoin('/', _pathname);
1148
- const auth = username && password ? `${username}:${password}` : '';
1149
- const query = search.replace(/^\?/, '');
1150
- const searchParams = qsParse(query);
1151
- const path = `${pathname}${search}`;
1152
- urlObj = null;
1153
- return {
1154
- protocol,
1155
- auth,
1156
- username,
1157
- password,
1158
- host,
1159
- port,
1160
- hostname,
1161
- hash,
1162
- search,
1163
- searchParams,
1164
- query,
1165
- pathname,
1166
- path,
1167
- href: url
1168
- };
1169
- };
1170
- /**
1171
- * url 字符化,url 对象里的 searchParams 会覆盖 url 原有的查询参数
1172
- * @param {Url} url
1173
- * @returns {string}
1174
- */
1175
- const urlStringify = (url) => {
1176
- const { protocol, auth, host, pathname, searchParams, hash } = url;
1177
- const authorize = auth ? `${auth}@` : '';
1178
- const querystring = qsStringify(searchParams);
1179
- const search = querystring ? `?${querystring}` : '';
1180
- let hashstring = hash.replace(/^#/, '');
1181
- hashstring = hashstring ? '#' + hashstring : '';
1182
- return `${protocol}//${authorize}${host}${pathname}${search}${hashstring}`;
1183
- };
1184
- /**
1185
- * 设置 url 查询参数
1186
- * @param {string} url
1187
- * @param {AnyObject} setter
1188
- * @returns {string}
1189
- */
1190
- const urlSetParams = (url, setter) => {
1191
- const p = urlParse(url);
1192
- Object.assign(p.searchParams, setter);
1193
- return urlStringify(p);
1194
- };
1195
- /**
1196
- * 删除 url 查询参数
1197
- * @param {string} url
1198
- * @param {string[]} removeKeys
1199
- * @returns {string}
1200
- */
1201
- const urlDelParams = (url, removeKeys) => {
1202
- const p = urlParse(url);
1203
- removeKeys.forEach(key => delete p.searchParams[key]);
1204
- return urlStringify(p);
1205
- };
1206
-
1207
- /**
1208
- * 通过打开新窗口的方式下载
1209
- * @param {string} url
1210
- * @param {LooseParams} params
1211
- */
1212
- function downloadURL(url, params) {
1213
- window.open(params ? urlSetParams(url, params) : url);
1214
- }
1215
- /**
1216
- * 通过 A 链接的方式下载
1217
- * @param {string} href
1218
- * @param {string} filename
1219
- * @param {Function} callback
1220
- */
1221
- function downloadHref(href, filename, callback) {
1222
- const eleLink = document.createElement('a');
1223
- eleLink.download = filename;
1224
- eleLink.style.display = 'none';
1225
- eleLink.href = href;
1226
- document.body.appendChild(eleLink);
1227
- eleLink.click();
1228
- setTimeout(() => {
1229
- document.body.removeChild(eleLink);
1230
- if (isFunction(callback)) {
1231
- callback();
1232
- }
1233
- });
1234
- }
1235
- /**
1236
- * 将大文件对象通过 A 链接的方式下载
1237
- * @param {Blob} blob
1238
- * @param {string} filename
1239
- * @param {Function} callback
1240
- */
1241
- function downloadBlob(blob, filename, callback) {
1242
- const objURL = URL.createObjectURL(blob);
1243
- downloadHref(objURL, filename);
1244
- setTimeout(() => {
1245
- URL.revokeObjectURL(objURL);
1246
- if (isFunction(callback)) {
1247
- callback();
1248
- }
1249
- });
1250
- }
1251
- /**
1252
- * 根据URL下载文件(解决跨域a.download不生效问题)
1253
- *
1254
- * 可定制下载成功的状态码status(浏览器原生状态码)
1255
- *
1256
- * 支持下载操作成功、失败后的回调
1257
- * @param {string} url
1258
- * @param {string} filename
1259
- * @param {CrossOriginDownloadParams} options
1260
- */
1261
- function crossOriginDownload(url, filename, options) {
1262
- const { successCode = 200, successCallback, failCallback } = isNullOrUnDef(options) ? { successCode: 200, successCallback: void 0, failCallback: void 0 } : options;
1263
- const xhr = new XMLHttpRequest();
1264
- xhr.open('GET', url, true);
1265
- xhr.responseType = 'blob';
1266
- xhr.onload = function () {
1267
- if (xhr.status === successCode)
1268
- downloadBlob(xhr.response, filename, successCallback);
1269
- else if (isFunction(failCallback)) {
1270
- const status = xhr.status;
1271
- const responseType = xhr.getResponseHeader('Content-Type');
1272
- if (isString(responseType) && responseType.includes('application/json')) {
1273
- const reader = new FileReader();
1274
- reader.onload = () => {
1275
- failCallback({ status, response: reader.result });
1276
- };
1277
- reader.readAsText(xhr.response);
1278
- }
1279
- else {
1280
- failCallback(xhr);
1281
- }
1282
- }
1283
- };
1284
- xhr.onerror = e => {
1285
- if (isFunction(failCallback)) {
1286
- failCallback({ status: 0, code: 'ERROR_CONNECTION_REFUSED' });
1287
- }
1288
- };
1289
- xhr.send();
1290
- }
1291
- /**
1292
- * 将指定数据格式通过 A 链接的方式下载
1293
- * @param {AnyObject | AnyObject[]} data
1294
- * @param {FileType} fileType 支持 json/csv/xls/xlsx 四种格式
1295
- * @param {string} filename
1296
- * @param {string[]} [headers]
1297
- */
1298
- function downloadData(data, fileType, filename, headers) {
1299
- filename = filename.replace(`.${fileType}`, '') + `.${fileType}`;
1300
- if (fileType === 'json') {
1301
- const blob = new Blob([JSON.stringify(data, null, 4)]);
1302
- downloadBlob(blob, filename);
1303
- }
1304
- else {
1305
- // xlsx实际生成的也为csv,仅后缀名名不同
1306
- if (!headers || !headers.length)
1307
- throw new Error('未传入表头数据');
1308
- if (!Array.isArray(data))
1309
- throw new Error('data error! expected array!');
1310
- const headerStr = headers.join(',') + '\n';
1311
- let bodyStr = '';
1312
- data.forEach(row => {
1313
- // \t防止数字被科学计数法显示
1314
- bodyStr += Object.values(row).join(',\t') + ',\n';
1315
- });
1316
- const MIMETypes = {
1317
- csv: 'text/csv',
1318
- xls: 'application/vnd.ms-excel',
1319
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1320
- };
1321
- // encodeURIComponent解决中文乱码
1322
- const href = 'data:' + MIMETypes[fileType] + ';charset=utf-8,\ufeff' + encodeURIComponent(headerStr + bodyStr);
1323
- downloadHref(href, filename);
1324
- }
1325
- }
1326
-
1327
- /**
1328
- * 等待一段时间
1329
- * @param {number} timeout 等待时间,单位毫秒
1330
- * @returns {Promise<void>}
1331
- */
1332
- function wait(timeout = 1) {
1333
- return new Promise(resolve => setTimeout(resolve, timeout));
1334
- }
1335
- /**
1336
- * 异步遍历
1337
- * @ref https://github.com/Kevnz/async-tools/blob/master/src/mapper.js
1338
- * @ref https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/@@iterator
1339
- * @param {Array<T>} list
1340
- * @param {(val: T, idx: number, list: ArrayLike<T>) => Promise<R>} mapper
1341
- * @param {number} concurrency 并发数量,默认无限
1342
- * @returns {Promise<R[]>}
1343
- */
1344
- function asyncMap(list, mapper, concurrency = Infinity) {
1345
- return new Promise((resolve, reject) => {
1346
- const iterator = list[Symbol.iterator]();
1347
- const limit = Math.min(list.length, concurrency);
1348
- const resolves = [];
1349
- let resolvedLength = 0;
1350
- let rejected;
1351
- let index = 0;
1352
- const next = () => {
1353
- if (rejected)
1354
- return reject(rejected);
1355
- const it = iterator.next();
1356
- if (it.done) {
1357
- if (resolvedLength === list.length)
1358
- resolve(resolves);
1359
- return;
1360
- }
1361
- const current = index++;
1362
- mapper(it.value, current, list)
1363
- .then(value => {
1364
- resolvedLength++;
1365
- resolves[current] = value;
1366
- next();
1367
- })
1368
- .catch(err => {
1369
- rejected = err;
1370
- next();
1371
- });
1372
- };
1373
- // 开始
1374
- for (let i = 0; i < limit; i++) {
1375
- next();
1376
- }
1377
- });
1378
- }
1379
-
1380
- /**
1381
- * 判断是否支持canvas
1382
- * @returns {boolean}
1383
- */
1384
- function supportCanvas() {
1385
- return !!document.createElement('canvas').getContext;
1386
- }
1387
- /**
1388
- * 选择本地文件
1389
- * @param {string} accept 上传的文件类型,用于过滤
1390
- * @param {Function} changeCb 选择文件回调
1391
- * @returns {HTMLInputElement}
1392
- */
1393
- function chooseLocalFile(accept, changeCb) {
1394
- const inputObj = document.createElement('input');
1395
- inputObj.setAttribute('id', String(Date.now()));
1396
- inputObj.setAttribute('type', 'file');
1397
- inputObj.setAttribute('style', 'visibility:hidden');
1398
- inputObj.setAttribute('accept', accept);
1399
- document.body.appendChild(inputObj);
1400
- inputObj.click();
1401
- // @ts-ignore
1402
- inputObj.onchange = (e) => {
1403
- changeCb(e.target.files);
1404
- setTimeout(() => document.body.removeChild(inputObj));
1405
- };
1406
- return inputObj;
1407
- }
1408
- /**
1409
- * 计算图片压缩后的尺寸
1410
- *
1411
- * @param {number} maxWidth
1412
- * @param {number} maxHeight
1413
- * @param {number} originWidth
1414
- * @param {number} originHeight
1415
- * @returns {*}
1416
- */
1417
- function calculateSize({ maxWidth, maxHeight, originWidth, originHeight }) {
1418
- let width = originWidth, height = originHeight;
1419
- // 图片尺寸超过限制
1420
- if (originWidth > maxWidth || originHeight > maxHeight) {
1421
- if (originWidth / originHeight > maxWidth / maxHeight) {
1422
- // 更宽,按照宽度限定尺寸
1423
- width = maxWidth;
1424
- height = Math.round(maxWidth * (originHeight / originWidth));
1425
- }
1426
- else {
1427
- height = maxHeight;
1428
- width = Math.round(maxHeight * (originWidth / originHeight));
1429
- }
1430
- }
1431
- return { width, height };
1432
- }
1433
- /**
1434
- * 根据原始图片的不同尺寸计算等比例缩放后的宽高尺寸
1435
- *
1436
- * @param {number} sizeKB Image volume size, unit KB
1437
- * @param {number} maxSize Image max size
1438
- * @param {number} originWidth Image original width, unit px
1439
- * @param {number} originHeight Image original height, unit px
1440
- * @returns {*} {width, height}
1441
- */
1442
- function scalingByAspectRatio({ sizeKB, maxSize, originWidth, originHeight }) {
1443
- let targetWidth = originWidth, targetHeight = originHeight;
1444
- if (isNumber(maxSize)) {
1445
- const { width, height } = calculateSize({ maxWidth: maxSize, maxHeight: maxSize, originWidth, originHeight });
1446
- targetWidth = width;
1447
- targetHeight = height;
1448
- }
1449
- else if (sizeKB < 500) {
1450
- // [50KB, 500KB)
1451
- const maxWidth = 1200, maxHeight = 1200;
1452
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1453
- targetWidth = width;
1454
- targetHeight = height;
1455
- }
1456
- else if (sizeKB < 5 * 1024) {
1457
- // [500KB, 5MB)
1458
- const maxWidth = 1400, maxHeight = 1400;
1459
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1460
- targetWidth = width;
1461
- targetHeight = height;
1462
- }
1463
- else if (sizeKB < 10 * 1024) {
1464
- // [5MB, 10MB)
1465
- const maxWidth = 1600, maxHeight = 1600;
1466
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1467
- targetWidth = width;
1468
- targetHeight = height;
1469
- }
1470
- else if (10 * 1024 <= sizeKB) {
1471
- // [10MB, Infinity)
1472
- const maxWidth = originWidth > 15000 ? 8192 : originWidth > 10000 ? 4096 : 2048, maxHeight = originHeight > 15000 ? 8192 : originHeight > 10000 ? 4096 : 2048;
1473
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1474
- targetWidth = width;
1475
- targetHeight = height;
1476
- }
1477
- return { width: targetWidth, height: targetHeight };
1478
- }
1479
- /**
1480
- * Web端:等比例压缩图片批量处理 (size小于50KB,不压缩), 支持压缩全景图或长截图
1481
- *
1482
- * 1. 默认根据图片原始size及宽高适当地调整quality、width、height
1483
- * 2. 可指定压缩的图片质量 quality(若不指定则根据原始图片大小来计算), 来适当调整width、height
1484
- * 3. 可指定压缩的图片最大宽高 maxSize(若不指定则根据原始图片宽高来计算), 满足大屏幕图片展示的场景
1485
- *
1486
- * @param {File | FileList} file 图片或图片数组
1487
- * @param {ICompressOptions} options 压缩图片配置项,default: {mime:'image/jpeg'}
1488
- * @returns {Promise<object> | undefined}
1489
- */
1490
- function compressImg(file, options = { mime: 'image/jpeg' }) {
1491
- if (!(file instanceof File || file instanceof FileList)) {
1492
- throw new Error(`${file} require be File or FileList`);
1493
- }
1494
- else if (!supportCanvas()) {
1495
- throw new Error(`Current runtime environment not support Canvas`);
1496
- }
1497
- const { quality, mime = 'image/jpeg', maxSize: size } = isObject(options) ? options : {};
1498
- let targetQuality = quality, maxSize;
1499
- if (quality) {
1500
- targetQuality = quality;
1501
- }
1502
- else if (file instanceof File) {
1503
- const sizeKB = +parseInt((file.size / 1024).toFixed(2));
1504
- if (sizeKB < 1 * 50) {
1505
- targetQuality = 1;
1506
- }
1507
- else if (sizeKB < 1 * 1024) {
1508
- targetQuality = 0.85;
1509
- }
1510
- else if (sizeKB < 5 * 1024) {
1511
- targetQuality = 0.8;
1512
- }
1513
- else {
1514
- targetQuality = 0.75;
1515
- }
1516
- }
1517
- if (isNumber(size)) {
1518
- maxSize = size >= 1200 ? size : 1200;
1519
- }
1520
- if (file instanceof FileList) {
1521
- return Promise.all(Array.from(file).map(el => compressImg(el, { maxSize, mime: mime, quality: targetQuality }))); // 如果是 file 数组返回 Promise 数组
1522
- }
1523
- else if (file instanceof File) {
1524
- return new Promise(resolve => {
1525
- const ext = {
1526
- 'image/webp': 'webp',
1527
- 'image/jpeg': 'jpg',
1528
- 'image/png': 'png'
1529
- };
1530
- const fileName = [...file.name.split('.').slice(0, -1), ext[mime]].join('.');
1531
- const sizeKB = +parseInt((file.size / 1024).toFixed(2));
1532
- if (sizeKB < 50) {
1533
- resolve({
1534
- file: file
1535
- });
1536
- }
1537
- else {
1538
- const reader = new FileReader(); // 创建 FileReader
1539
- // @ts-ignore
1540
- reader.onload = ({ target: { result: src } }) => {
1541
- const image = new Image(); // 创建 img 元素
1542
- image.onload = () => {
1543
- const canvas = document.createElement('canvas'); // 创建 canvas 元素
1544
- const context = canvas.getContext('2d');
1545
- const originWidth = image.width;
1546
- const originHeight = image.height;
1547
- const { width, height } = scalingByAspectRatio({ sizeKB, maxSize, originWidth, originHeight });
1548
- canvas.width = width;
1549
- canvas.height = height;
1550
- context.clearRect(0, 0, width, height);
1551
- context.drawImage(image, 0, 0, width, height); // 绘制 canvas
1552
- const canvasURL = canvas.toDataURL(mime, targetQuality);
1553
- const buffer = atob(canvasURL.split(',')[1]);
1554
- let length = buffer.length;
1555
- const bufferArray = new Uint8Array(new ArrayBuffer(length));
1556
- while (length--) {
1557
- bufferArray[length] = buffer.charCodeAt(length);
1558
- }
1559
- const miniFile = new File([bufferArray], fileName, {
1560
- type: mime
1561
- });
1562
- resolve({
1563
- file: miniFile,
1564
- bufferArray,
1565
- origin: file,
1566
- beforeSrc: src,
1567
- afterSrc: canvasURL,
1568
- beforeKB: Number((file.size / 1024).toFixed(2)),
1569
- afterKB: Number((miniFile.size / 1024).toFixed(2))
1570
- });
1571
- };
1572
- image.src = src;
1573
- };
1574
- reader.readAsDataURL(file);
1575
- }
1576
- });
1577
- }
1578
- }
1579
-
1580
- /*
1581
- * @created: Saturday, 2020-04-18 14:38:23
1582
- * @author: chendq
1583
- * ---------
1584
- * @desc 网页加水印的工具类
1585
- */
1586
- /**
1587
- * canvas 实现 水印, 具备防删除功能
1588
- * @param {ICanvasWM} canvasWM
1589
- * @example genCanvasWM({ content: 'QQMusicFE' })
1590
- */
1591
- function genCanvasWM(content = '请勿外传', canvasWM) {
1592
- const { rootContainer = document.body, width = '300px', height = '150px', textAlign = 'center', textBaseline = 'middle', font = '20px PingFangSC-Medium,PingFang SC',
1593
- // fontWeight = 500,
1594
- fillStyle = 'rgba(189, 177, 167, .3)', rotate = -20, zIndex = 2147483647, watermarkId = '__wm' } = isNullOrUnDef(canvasWM) ? {} : canvasWM;
1595
- const container = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
1596
- if (!container) {
1597
- throw new Error(`${rootContainer} is not valid Html Element or element selector`);
1598
- }
1599
- const canvas = document.createElement('canvas');
1600
- canvas.setAttribute('width', width);
1601
- canvas.setAttribute('height', height);
1602
- const ctx = canvas.getContext('2d');
1603
- ctx.textAlign = textAlign;
1604
- ctx.textBaseline = textBaseline;
1605
- ctx.font = font;
1606
- // ctx!.fontWeight = fontWeight;
1607
- ctx.fillStyle = fillStyle;
1608
- ctx.rotate((Math.PI / 180) * rotate);
1609
- ctx.fillText(content, parseFloat(width) / 4, parseFloat(height) / 2);
1610
- const base64Url = canvas.toDataURL();
1611
- const __wm = document.querySelector(`#${watermarkId}`);
1612
- const watermarkDiv = __wm || document.createElement('div');
1613
- const styleStr = `opacity: 1 !important; display: block !important; visibility: visible !important; position:absolute; left:0; top:0; width:100%; height:100%; z-index:${zIndex}; pointer-events:none; background-repeat:repeat; background-image:url('${base64Url}')`;
1614
- watermarkDiv.setAttribute('style', styleStr);
1615
- watermarkDiv.setAttribute('id', watermarkId);
1616
- watermarkDiv.classList.add('nav-height');
1617
- if (!__wm) {
1618
- container.style.position = 'relative';
1619
- container.appendChild(watermarkDiv);
1620
- }
1621
- const getMutableStyle = (ele) => {
1622
- const computedStyle = getComputedStyle(ele);
1623
- return {
1624
- opacity: computedStyle.getPropertyValue('opacity'),
1625
- zIndex: computedStyle.getPropertyValue('z-index'),
1626
- display: computedStyle.getPropertyValue('display'),
1627
- visibility: computedStyle.getPropertyValue('visibility')
1628
- };
1629
- };
1630
- //@ts-ignore
1631
- const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
1632
- if (MutationObserver) {
1633
- let mo = new MutationObserver(function () {
1634
- const __wm = document.querySelector(`#${watermarkId}`); // 只在__wm元素变动才重新调用 __canvasWM
1635
- if (!__wm) {
1636
- // 避免一直触发
1637
- // console.log('regenerate watermark by delete::')
1638
- mo.disconnect();
1639
- mo = null;
1640
- genCanvasWM(content, canvasWM);
1641
- }
1642
- else {
1643
- const { opacity, zIndex, display, visibility } = getMutableStyle(__wm);
1644
- if ((__wm && __wm.getAttribute('style') !== styleStr) ||
1645
- !__wm ||
1646
- !(opacity === '1' && zIndex === '2147483647' && display === 'block' && visibility === 'visible')) {
1647
- // 避免一直触发
1648
- // console.log('regenerate watermark by inline style changed ::')
1649
- mo.disconnect();
1650
- mo = null;
1651
- container.removeChild(__wm);
1652
- genCanvasWM(content, canvasWM);
1653
- }
1654
- }
1655
- });
1656
- mo.observe(container, { attributes: true, subtree: true, childList: true });
1657
- }
1658
- }
1659
-
1660
- /**
1661
- * 防抖函数
1662
- * 当函数被连续调用时,该函数并不执行,只有当其全部停止调用超过一定时间后才执行1次。
1663
- * 例如:上电梯的时候,大家陆陆续续进来,电梯的门不会关上,只有当一段时间都没有人上来,电梯才会关门。
1664
- * @param {F} func
1665
- * @param {number} wait
1666
- * @returns {DebounceFunc<F>}
1667
- */
1668
- const debounce = (func, wait) => {
1669
- let timeout;
1670
- let canceled = false;
1671
- const f = function (...args) {
1672
- if (canceled)
1673
- return;
1674
- clearTimeout(timeout);
1675
- timeout = setTimeout(() => {
1676
- func.call(this, ...args);
1677
- }, wait);
1678
- };
1679
- f.cancel = () => {
1680
- clearTimeout(timeout);
1681
- canceled = true;
1682
- };
1683
- return f;
1684
- };
1685
- /**
1686
- * 节流函数
1687
- * 节流就是节约流量,将连续触发的事件稀释成预设评率。 比如每间隔1秒执行一次函数,无论这期间触发多少次事件。
1688
- * 这有点像公交车,无论在站点等车的人多不多,公交车只会按时来一班,不会来一个人就来一辆公交车。
1689
- * @param {F} func
1690
- * @param {number} wait
1691
- * @param {boolean} immediate
1692
- * @returns {ThrottleFunc<F>}
1693
- */
1694
- const throttle = (func, wait, immediate) => {
1695
- let timeout;
1696
- let canceled = false;
1697
- let lastCalledTime = 0;
1698
- const f = function (...args) {
1699
- if (canceled)
1700
- return;
1701
- const now = Date.now();
1702
- const call = () => {
1703
- lastCalledTime = now;
1704
- func.call(this, ...args);
1705
- };
1706
- // 第一次执行
1707
- if (lastCalledTime === 0) {
1708
- if (immediate) {
1709
- return call();
1710
- }
1711
- else {
1712
- lastCalledTime = now;
1713
- return;
1714
- }
1715
- }
1716
- const remain = lastCalledTime + wait - now;
1717
- if (remain > 0) {
1718
- clearTimeout(timeout);
1719
- timeout = setTimeout(() => call(), wait);
1720
- }
1721
- else {
1722
- call();
1723
- }
1724
- };
1725
- f.cancel = () => {
1726
- clearTimeout(timeout);
1727
- canceled = true;
1728
- };
1729
- return f;
1730
- };
1731
- /**
1732
- * 单次函数
1733
- * @param {AnyFunc} func
1734
- * @returns {AnyFunc}
1735
- */
1736
- const once = (func) => {
1737
- let called = false;
1738
- let result;
1739
- return function (...args) {
1740
- if (called)
1741
- return result;
1742
- called = true;
1743
- result = func.call(this, ...args);
1744
- return result;
1745
- };
1746
- };
1747
- /**
1748
- * 设置全局变量
1749
- * @param {string | number | symbol} key
1750
- * @param val
1751
- */
1752
- function setGlobal(key, val) {
1753
- if (typeof globalThis !== 'undefined')
1754
- globalThis[key] = val;
1755
- else if (typeof window !== 'undefined')
1756
- window[key] = val;
1757
- else if (typeof global !== 'undefined')
1758
- global[key] = val;
1759
- else if (typeof self !== 'undefined')
1760
- self[key] = val;
1761
- else
1762
- throw new SyntaxError('当前环境下无法设置全局属性');
1763
- }
1764
- /**
1765
- * 获取全局变量
1766
- * @param {string | number | symbol} key
1767
- * @param val
1768
- */
1769
- function getGlobal(key) {
1770
- if (typeof globalThis !== 'undefined')
1771
- return globalThis[key];
1772
- else if (typeof window !== 'undefined')
1773
- return window[key];
1774
- else if (typeof global !== 'undefined')
1775
- return global[key];
1776
- else if (typeof self !== 'undefined')
1777
- return self[key];
1778
- }
1779
-
1780
- /**
1781
- * 随机整数
1782
- * @param {number} min
1783
- * @param {number} max
1784
- * @returns {number}
1785
- */
1786
- const randomNumber = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
1787
- const STRING_POOL = `${STRING_ARABIC_NUMERALS}${STRING_UPPERCASE_ALPHA}${STRING_LOWERCASE_ALPHA}`;
1788
- /**
1789
- * 随机字符串
1790
- * @param {number | string} length
1791
- * @param {string} pool
1792
- * @returns {string}
1793
- */
1794
- const randomString = (length, pool) => {
1795
- let _length = 0;
1796
- let _pool = STRING_POOL;
1797
- if (isString(pool)) {
1798
- _length = length;
1799
- _pool = pool;
1800
- }
1801
- else if (isNumber(length)) {
1802
- _length = length;
1803
- }
1804
- else if (isString(length)) {
1805
- _pool = length;
1806
- }
1807
- let times = Math.max(_length, 1);
1808
- let result = '';
1809
- const min = 0;
1810
- const max = _pool.length - 1;
1811
- if (max < 2)
1812
- throw new Error('字符串池长度不能少于 2');
1813
- while (times--) {
1814
- const index = randomNumber(min, max);
1815
- result += _pool[index];
1816
- }
1817
- return result;
1818
- };
1819
- /**
1820
- * 优先浏览器原生能力获取 UUID v4
1821
- * @returns {string}
1822
- */
1823
- function randomUuid() {
1824
- if (typeof URL === 'undefined' || !URL.createObjectURL || typeof Blob === 'undefined') {
1825
- const hex = '0123456789abcdef';
1826
- const model = 'xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx';
1827
- let str = '';
1828
- for (let i = 0; i < model.length; i++) {
1829
- const rnd = randomNumber(0, 15);
1830
- str += model[i] == '-' || model[i] == '4' ? model[i] : hex[rnd];
1831
- }
1832
- return str;
1833
- }
1834
- return /[^/]+$/.exec(URL.createObjectURL(new Blob()).slice())[0];
1835
- }
1836
-
1837
- const HEX_POOL = `${STRING_ARABIC_NUMERALS}${STRING_UPPERCASE_ALPHA}${STRING_LOWERCASE_ALPHA}`;
1838
- const supportBigInt = typeof BigInt !== 'undefined';
1839
- const jsbi = () => getGlobal('JSBI');
1840
- const toBigInt = (n) => (supportBigInt ? BigInt(n) : jsbi().BigInt(n));
1841
- const divide$1 = (x, y) => (supportBigInt ? x / y : jsbi().divide(x, y));
1842
- const remainder = (x, y) => (supportBigInt ? x % y : jsbi().remainder(x, y));
1843
- /**
1844
- * 将十进制转换成任意进制
1845
- * @param {number | string} decimal 十进制数值或字符串,可以是任意长度,会使用大数进行计算
1846
- * @param {string} [hexPool] 进制池,默认 62 进制
1847
- * @returns {string}
1848
- */
1849
- function numberToHex(decimal, hexPool = HEX_POOL) {
1850
- if (hexPool.length < 2)
1851
- throw new Error('进制池长度不能少于 2');
1852
- if (!supportBigInt) {
1853
- throw new Error('需要安装 jsbi 模块并将 JSBI 设置为全局变量:\nimport JSBI from "jsbi"; window.JSBI = JSBI;');
1854
- }
1855
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1856
- let bigInt = toBigInt(decimal);
1857
- const ret = [];
1858
- const { length } = hexPool;
1859
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1860
- const bigLength = toBigInt(length);
1861
- const execute = () => {
1862
- const y = Number(remainder(bigInt, bigLength));
1863
- bigInt = divide$1(bigInt, bigLength);
1864
- ret.unshift(hexPool[y]);
1865
- if (bigInt > 0) {
1866
- execute();
1867
- }
1868
- };
1869
- execute();
1870
- return ret.join('');
1871
- }
1872
- /**
1873
- * 将数字转换为携带单位的字符串
1874
- * @param {number | string} num
1875
- * @param {Array<string>} units
1876
- * @param {INumberAbbr} options default: { ratio: 1000, decimals: 0, separator: ' ' }
1877
- * @returns {string}
1878
- */
1879
- const numberAbbr = (num, units, options = { ratio: 1000, decimals: 0, separator: ' ' }) => {
1880
- const { ratio = 1000, decimals = 0, separator = ' ' } = options;
1881
- const { length } = units;
1882
- if (length === 0)
1883
- throw new Error('At least one unit is required');
1884
- let num2 = Number(num);
1885
- let times = 0;
1886
- while (num2 >= ratio && times < length - 1) {
1887
- num2 = num2 / ratio;
1888
- times++;
1889
- }
1890
- const value = num2.toFixed(decimals);
1891
- const unit = units[times];
1892
- return String(value) + separator + unit;
1893
- };
1894
- /**
1895
- * Converting file size in bytes to human-readable string
1896
- * reference: https://zh.wikipedia.org/wiki/%E5%8D%83%E5%AD%97%E8%8A%82
1897
- * @param {number | string} num bytes Number in Bytes
1898
- * @param {IHumanFileSizeOptions} options default: { decimals = 0, si = false, separator = ' ' }
1899
- * si: True to use metric (SI) units, aka powers of 1000, the units is
1900
- * ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'].
1901
- * False to use binary (IEC), aka powers of 1024, the units is
1902
- * ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
1903
- * @returns
1904
- */
1905
- function humanFileSize(num, options = { decimals: 0, si: false, separator: ' ' }) {
1906
- const { decimals = 0, si = false, separator = ' ', baseUnit, maxUnit } = options;
1907
- let units = si
1908
- ? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
1909
- : ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
1910
- if (!isNullOrUnDef(baseUnit)) {
1911
- const targetIndex = units.findIndex(el => el === baseUnit);
1912
- if (targetIndex !== -1) {
1913
- units = units.slice(targetIndex);
1914
- }
1915
- }
1916
- if (!isNullOrUnDef(maxUnit)) {
1917
- const targetIndex = units.findIndex(el => el === maxUnit);
1918
- if (targetIndex !== -1) {
1919
- units.splice(targetIndex + 1);
1920
- }
1921
- }
1922
- return numberAbbr(num, units, { ratio: si ? 1000 : 1024, decimals, separator });
1923
- }
1924
- /**
1925
- * 将数字格式化成千位分隔符显示的字符串
1926
- * @param {number|string} num 数字
1927
- * @param {number} decimals 格式化成指定小数位精度的参数
1928
- * @returns {string}
1929
- */
1930
- function formatNumber(num, decimals) {
1931
- if (isNullOrUnDef(decimals)) {
1932
- return parseInt(String(num)).toLocaleString();
1933
- }
1934
- let prec = 0;
1935
- if (!isNumber(decimals)) {
1936
- throw new Error('Decimals must be a positive number not less than zero');
1937
- }
1938
- else if (decimals > 0) {
1939
- prec = decimals;
1940
- }
1941
- return Number(Number(num).toFixed(prec)).toLocaleString('en-US');
1942
- }
1943
-
1944
- const padStartWithZero = (str, maxLength = 2) => String(str).padStart(maxLength, '0');
1945
- let safeNo = 0;
1946
- let lastTimestamp = 0;
1947
- // 安全后缀长度,按 1 毫秒运算 99999 次 JS 代码来进行估算
1948
- // 那么,补足长度为 5
1949
- // 时间戳模式长度为 13
1950
- // 取最长 5 + 13 = 18
1951
- const UNIQUE_NUMBER_SAFE_LENGTH = 18;
1952
- const FIX_SAFE_LENGTH = 5;
1953
- const TIMESTAMP_LENGTH = 13;
1954
- /**
1955
- * 生成唯一不重复数值
1956
- * @param {number} length
1957
- * @returns {string}
1958
- */
1959
- const uniqueNumber = (length = UNIQUE_NUMBER_SAFE_LENGTH) => {
1960
- const now = Date.now();
1961
- length = Math.max(length, UNIQUE_NUMBER_SAFE_LENGTH);
1962
- if (now !== lastTimestamp) {
1963
- lastTimestamp = now;
1964
- safeNo = 0;
1965
- }
1966
- const timestamp = `${now}`;
1967
- let random = '';
1968
- const rndLength = length - FIX_SAFE_LENGTH - TIMESTAMP_LENGTH;
1969
- if (rndLength > 0) {
1970
- const rndMin = 10 ** (rndLength - 1);
1971
- const rndMax = 10 ** rndLength - 1;
1972
- const rnd = randomNumber(rndMin, rndMax);
1973
- random = `${rnd}`;
1974
- }
1975
- const safe = padStartWithZero(safeNo, FIX_SAFE_LENGTH);
1976
- safeNo++;
1977
- return `${timestamp}${random}${safe}`;
1978
- };
1979
- const randomFromPool = (pool) => {
1980
- const poolIndex = randomNumber(0, pool.length - 1);
1981
- return pool[poolIndex];
1982
- };
1983
- /**
1984
- * 生成唯一不重复字符串
1985
- * @param {number | string} length
1986
- * @param {string} pool
1987
- * @returns {string}
1988
- */
1989
- const uniqueString = (length, pool) => {
1990
- let _length = 0;
1991
- let _pool = HEX_POOL;
1992
- if (isString(pool)) {
1993
- _length = length;
1994
- _pool = pool;
1995
- }
1996
- else if (isNumber(length)) {
1997
- _length = length;
1998
- }
1999
- else if (isString(length)) {
2000
- _pool = length;
2001
- }
2002
- let uniqueString = numberToHex(uniqueNumber(), _pool);
2003
- let insertLength = _length - uniqueString.length;
2004
- if (insertLength <= 0)
2005
- return uniqueString;
2006
- while (insertLength--) {
2007
- uniqueString += randomFromPool(_pool);
2008
- }
2009
- return uniqueString;
2010
- };
2011
-
2012
- /**
2013
- * 自定义的 tooltip, 支持鼠标移动动悬浮提示
2014
- * @Desc 自定义的tooltip方法, 支持拖动悬浮提示
2015
- * Created by chendeqiao on 2017/5/8.
2016
- * @example
2017
- * <span onmouseleave="handleMouseLeave('#root')"
2018
- * onmousemove="handleMouseEnter({rootContainer: '#root', title: 'title content', event: event})"
2019
- * onmouseenter="handleMouseEnter({rootContainer:'#root', title: 'title content', event: event})">
2020
- * title content
2021
- * </span>
2022
- */
2023
- /**
2024
- * 自定义title提示功能的mouseenter事件句柄
2025
- * @param {ITooltipParams} param
2026
- * @returns {*}
2027
- */
2028
- function handleMouseEnter({ rootContainer = '#root', title, event, bgColor = '#000', color = '#fff' }) {
2029
- try {
2030
- const $rootEl = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
2031
- if (!$rootEl) {
2032
- throw new Error(`${rootContainer} is not valid Html Element or element selector`);
2033
- }
2034
- let $customTitle = null;
2035
- const styleId = 'style-tooltip-inner1494304949567';
2036
- // 动态创建class样式,并加入到head中
2037
- if (!document.querySelector(`#${styleId}`)) {
2038
- const tooltipWrapperClass = document.createElement('style');
2039
- tooltipWrapperClass.type = 'text/css';
2040
- tooltipWrapperClass.id = styleId;
2041
- tooltipWrapperClass.innerHTML = `
2042
- .tooltip-inner1494304949567 {
2043
- max-width: 250px;
2044
- padding: 3px 8px;
2045
- color: ${color};
2046
- text-decoration: none;
2047
- border-radius: 4px;
2048
- text-align: left;
2049
- background-color: ${bgColor};
2050
- }
2051
- `;
2052
- document.querySelector('head').appendChild(tooltipWrapperClass);
2053
- }
2054
- $customTitle = document.querySelector('#customTitle1494304949567');
2055
- if ($customTitle) {
2056
- mouseenter($customTitle, title, event);
2057
- }
2058
- else {
2059
- const $contentContainer = document.createElement('div');
2060
- $contentContainer.id = 'customTitle1494304949567';
2061
- $contentContainer.style.cssText = 'z-index: 99999999; visibility: hidden; position: absolute;';
2062
- $contentContainer.innerHTML =
2063
- '<div class="tooltip-inner1494304949567" style="word-wrap: break-word; max-width: 44px;">皮肤</div>';
2064
- $rootEl.appendChild($contentContainer);
2065
- $customTitle = document.querySelector('#customTitle1494304949567');
2066
- if (title) {
2067
- //判断div显示的内容是否为空
2068
- mouseenter($customTitle, title, event);
2069
- $customTitle.style.visibility = 'visible';
2070
- }
2071
- }
2072
- }
2073
- catch (e) {
2074
- console.error(e.message);
2075
- }
2076
- }
2077
- /**
2078
- * 提示文案dom渲染的处理函数
2079
- * @param {HTMLDivElement} customTitle
2080
- * @param {string} title 提示的字符串
2081
- * @param {PointerEvent} e 事件对象
2082
- * @returns {*}
2083
- */
2084
- function mouseenter($customTitle, title, e) {
2085
- let diffValueX = 200 + 50; //默认设置弹出div的宽度为250px
2086
- let x = 13;
2087
- const y = 23;
2088
- const $contentEle = $customTitle.children[0];
2089
- if (getStrWidthPx(title, 12) < 180 + 50) {
2090
- //【弹出div自适应字符串宽度】若显示的字符串占用宽度小于180,则设置弹出div的宽度为“符串占用宽度”+20
2091
- $contentEle.style.maxWidth = getStrWidthPx(title, 12) + 20 + 50 + 'px';
2092
- diffValueX = e.clientX + (getStrWidthPx(title, 12) + 50) - document.body.offsetWidth;
2093
- }
2094
- else {
2095
- $contentEle.style.maxWidth = '250px';
2096
- diffValueX = e.clientX + 230 - document.body.offsetWidth; //计算div水平方向显示的内容超出屏幕多少宽度
2097
- }
2098
- $contentEle.innerHTML = title; //html方法可解析内容中换行标签,text方法不能
2099
- if (diffValueX > 0) {
2100
- //水平方向超出可见区域时
2101
- x -= diffValueX;
2102
- }
2103
- $customTitle.style.top = e.clientY + y + 'px';
2104
- $customTitle.style.left = e.clientX + x + 'px';
2105
- $customTitle.style.maxWidth = '250px';
2106
- const diffValueY = $customTitle.getBoundingClientRect().top + $contentEle.offsetHeight - document.body.offsetHeight;
2107
- if (diffValueY > 0) {
2108
- //垂直方向超出可见区域时
2109
- $customTitle.style.top = e.clientY - diffValueY + 'px';
2110
- }
2111
- }
2112
- /**
2113
- * 移除提示文案dom的事件句柄
2114
- * @param {string} rootContainer
2115
- * @returns {*}
2116
- */
2117
- function handleMouseLeave(rootContainer = '#root') {
2118
- const rootEl = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer, titleEl = document.querySelector('#customTitle1494304949567');
2119
- if (rootEl && titleEl) {
2120
- rootEl.removeChild(titleEl);
2121
- }
2122
- }
2123
- const tooltipEvent = { handleMouseEnter, handleMouseLeave };
2124
-
2125
- const defaultFieldOptions = { keyField: 'key', childField: 'children', pidField: 'pid' };
2126
- const defaultSearchTreeOptions = {
2127
- childField: 'children',
2128
- nameField: 'name',
2129
- removeEmptyChild: false,
2130
- ignoreCase: true
2131
- };
2132
- /**
2133
- * 深度优先遍历函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
2134
- * @param {ArrayLike<V>} tree 树形数据
2135
- * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
2136
- * @param {string} children 定制子元素的key
2137
- * @param {boolean} isReverse 是否反向遍历
2138
- * @returns {*}
2139
- */
2140
- function forEachDeep(tree, iterator, children = 'children', isReverse = false) {
2141
- let isBreak = false;
2142
- const walk = (arr, parent, level = 0) => {
2143
- if (isReverse) {
2144
- for (let i = arr.length - 1; i >= 0; i--) {
2145
- if (isBreak) {
2146
- break;
2147
- }
2148
- const re = iterator(arr[i], i, arr, tree, parent, level);
2149
- if (re === false) {
2150
- isBreak = true;
2151
- break;
2152
- }
2153
- else if (re === true) {
2154
- continue;
2155
- }
2156
- // @ts-ignore
2157
- if (arr[i] && Array.isArray(arr[i][children])) {
2158
- // @ts-ignore
2159
- walk(arr[i][children], arr[i], level + 1);
2160
- }
2161
- }
2162
- }
2163
- else {
2164
- for (let i = 0, len = arr.length; i < len; i++) {
2165
- if (isBreak) {
2166
- break;
2167
- }
2168
- const re = iterator(arr[i], i, arr, tree, parent, level);
2169
- if (re === false) {
2170
- isBreak = true;
2171
- break;
2172
- }
2173
- else if (re === true) {
2174
- continue;
2175
- }
2176
- // @ts-ignore
2177
- if (arr[i] && Array.isArray(arr[i][children])) {
2178
- // @ts-ignore
2179
- walk(arr[i][children], arr[i], level + 1);
2180
- }
2181
- }
2182
- }
2183
- };
2184
- walk(tree, null);
2185
- }
2186
- /**
2187
- * 创建一个新数组, 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
2188
- *
2189
- * 可遍历任何带有 length 属性和数字键的类数组对象
2190
- * @param {ArrayLike<V>} tree 树形数据
2191
- * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
2192
- * @param {string} children 定制子元素的key
2193
- * @param {boolean} isReverse 是否反向遍历
2194
- * @returns {any[]} 新的一棵树
2195
- */
2196
- function mapDeep(tree, iterator, children = 'children', isReverse = false) {
2197
- let isBreak = false;
2198
- const newTree = [];
2199
- const walk = (arr, parent, newTree, level = 0) => {
2200
- if (isReverse) {
2201
- for (let i = arr.length - 1; i >= 0; i--) {
2202
- if (isBreak) {
2203
- break;
2204
- }
2205
- const re = iterator(arr[i], i, arr, tree, parent, level);
2206
- if (re === false) {
2207
- isBreak = true;
2208
- break;
2209
- }
2210
- else if (re === true) {
2211
- continue;
2212
- }
2213
- newTree.push(objectOmit(re, [children]));
2214
- // @ts-ignore
2215
- if (arr[i] && Array.isArray(arr[i][children])) {
2216
- newTree[newTree.length - 1][children] = [];
2217
- // @ts-ignore
2218
- walk(arr[i][children], arr[i], newTree[newTree.length - 1][children], level + 1);
2219
- }
2220
- else {
2221
- // children非有效数组时,移除该属性字段
2222
- delete re[children];
2223
- }
2224
- }
2225
- }
2226
- else {
2227
- for (let i = 0; i < arr.length; i++) {
2228
- if (isBreak) {
2229
- break;
2230
- }
2231
- const re = iterator(arr[i], i, arr, tree, parent, level);
2232
- if (re === false) {
2233
- isBreak = true;
2234
- break;
2235
- }
2236
- else if (re === true) {
2237
- continue;
2238
- }
2239
- newTree.push(objectOmit(re, [children]));
2240
- // @ts-ignore
2241
- if (arr[i] && Array.isArray(arr[i][children])) {
2242
- newTree[newTree.length - 1][children] = [];
2243
- // @ts-ignore
2244
- walk(arr[i][children], arr[i], newTree[newTree.length - 1][children], level + 1);
2245
- }
2246
- else {
2247
- // children非有效数组时,移除该属性字段
2248
- delete re[children];
2249
- }
2250
- }
2251
- }
2252
- };
2253
- walk(tree, null, newTree);
2254
- return newTree;
2255
- }
2256
- /**
2257
- * 在树中找到 id 为某个值的节点,并返回上游的所有父级节点
2258
- *
2259
- * @param {ArrayLike<T>} tree - 树形数据
2260
- * @param {IdLike} nodeId - 元素ID
2261
- * @param {ITreeConf} config - 迭代配置项
2262
- * @returns {[IdLike[], ITreeItem<V>[]]} - 由parentId...childId, parentObject-childObject组成的二维数组
2263
- */
2264
- function searchTreeById(tree, nodeId, config) {
2265
- const { children = 'children', id = 'id' } = config || {};
2266
- const toFlatArray = (tree, parentId, parent) => {
2267
- return tree.reduce((t, _) => {
2268
- const child = _[children];
2269
- return [
2270
- ...t,
2271
- parentId ? { ..._, parentId, parent } : _,
2272
- ...(child && child.length ? toFlatArray(child, _[id], _) : [])
2273
- ];
2274
- }, []);
2275
- };
2276
- const getIds = (flatArray) => {
2277
- let child = flatArray.find(_ => _[id] === nodeId);
2278
- const { parent, parentId, ...other } = child;
2279
- let ids = [nodeId], nodes = [other];
2280
- while (child && child.parentId) {
2281
- ids = [child.parentId, ...ids];
2282
- nodes = [child.parent, ...nodes];
2283
- child = flatArray.find(_ => _[id] === child.parentId); // eslint-disable-line
2284
- }
2285
- return [ids, nodes];
2286
- };
2287
- return getIds(toFlatArray(tree));
2288
- }
2289
- /**
2290
- * 扁平化数组转换成树
2291
- * @param {any[]} list
2292
- * @param {IFieldOptions} options
2293
- * @returns {any[]}
2294
- */
2295
- function formatTree(list, options = defaultFieldOptions) {
2296
- const { keyField, childField, pidField } = options;
2297
- const treeArr = [];
2298
- const sourceMap = {};
2299
- arrayEach(list, item => {
2300
- sourceMap[item[keyField]] = item;
2301
- });
2302
- arrayEach(list, item => {
2303
- const parent = sourceMap[item[pidField]];
2304
- if (parent) {
2305
- (parent[childField] || (parent[childField] = [])).push(item);
2306
- }
2307
- else {
2308
- treeArr.push(item);
2309
- }
2310
- });
2311
- return treeArr;
2312
- }
2313
- /**
2314
- * 树形结构转扁平化
2315
- * @param {any} treeList
2316
- * @param {IFieldOptions} options
2317
- * @returns {*}
2318
- */
2319
- function flatTree(treeList, options = defaultFieldOptions) {
2320
- const { childField, keyField, pidField } = options;
2321
- let res = [];
2322
- arrayEach(treeList, node => {
2323
- const item = {
2324
- ...node,
2325
- [childField]: [] // 清空子级
2326
- };
2327
- objectHas(item, childField) && delete item[childField];
2328
- res.push(item);
2329
- if (node[childField]) {
2330
- const children = node[childField].map(item => ({
2331
- ...item,
2332
- [pidField]: node[keyField] || item.pid // 给子级设置pid
2333
- }));
2334
- res = res.concat(flatTree(children, options));
2335
- }
2336
- });
2337
- return res;
2338
- }
2339
- /**
2340
- * 模糊搜索函数,返回包含搜索字符的节点及其祖先节点, 适用于树型组件的字符过滤功能
2341
- * 以下搜索条件二选一,按先后优先级处理:
2342
- * 1. 过滤函数filter, 返回true/false
2343
- * 2. 匹配关键词,支持是否启用忽略大小写来判断
2344
- *
2345
- * 有以下特性:
2346
- * 1. 可配置removeEmptyChild字段,来决定是否移除搜索结果中的空children字段
2347
- * 2. 若无任何过滤条件或keyword模式匹配且keyword为空串,返回原对象;其他情况返回新数组
2348
- * @param {V[]} nodes
2349
- * @param {IFilterCondition} filterCondition
2350
- * @param {ISearchTreeOpts} options
2351
- * @returns {V[]}
2352
- */
2353
- function fuzzySearchTree(nodes, filterCondition, options = defaultSearchTreeOptions) {
2354
- if (!objectHas(filterCondition, 'filter') &&
2355
- (!objectHas(filterCondition, 'keyword') || isEmpty(filterCondition.keyword))) {
2356
- return nodes;
2357
- }
2358
- const result = [];
2359
- arrayEach(nodes, node => {
2360
- // 递归检查子节点是否匹配
2361
- const matchedChildren = node[options.childField] && node[options.childField].length > 0
2362
- ? fuzzySearchTree(node[options.childField] || [], filterCondition, options)
2363
- : [];
2364
- // 检查当前节点是否匹配或者有匹配的子节点
2365
- if ((objectHas(filterCondition, 'filter')
2366
- ? filterCondition.filter(node)
2367
- : !options.ignoreCase
2368
- ? node[options.nameField].includes(filterCondition.keyword)
2369
- : node[options.nameField].toLowerCase().includes(filterCondition.keyword.toLowerCase())) ||
2370
- matchedChildren.length > 0) {
2371
- // 将当前节点加入结果中
2372
- if (node[options.childField]) {
2373
- if (matchedChildren.length > 0) {
2374
- result.push({
2375
- ...node,
2376
- [options.childField]: matchedChildren // 包含匹配的子节点
2377
- });
2378
- }
2379
- else if (options.removeEmptyChild) {
2380
- node[options.childField] && delete node[options.childField];
2381
- result.push({
2382
- ...node
2383
- });
2384
- }
2385
- else {
2386
- result.push({
2387
- ...node,
2388
- ...{ [options.childField]: [] }
2389
- });
2390
- }
2391
- }
2392
- else {
2393
- node[options.childField] && delete node[options.childField];
2394
- result.push({
2395
- ...node
2396
- });
2397
- }
2398
- }
2399
- });
2400
- return result;
2401
- }
2402
-
2403
- /**
2404
- * 数值安全乘法
2405
- * @param arg1 数值1
2406
- * @param arg2 数值2
2407
- */
2408
- const multiply = (arg1, arg2) => {
2409
- let m = 0;
2410
- const s1 = arg1.toString();
2411
- const s2 = arg2.toString();
2412
- if (s1.split('.')[1] !== undefined)
2413
- m += s1.split('.')[1].length;
2414
- if (s2.split('.')[1] !== undefined)
2415
- m += s2.split('.')[1].length;
2416
- return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m);
2417
- };
2418
- /**
2419
- * 数值安全加法
2420
- * @param arg1 数值1
2421
- * @param arg2 数值2
2422
- */
2423
- const add = (arg1, arg2) => {
2424
- let r1 = 0;
2425
- let r2 = 0;
2426
- let m = 0;
2427
- try {
2428
- r1 = arg1.toString().split('.')[1].length;
2429
- }
2430
- catch (e) {
2431
- r1 = 0;
2432
- }
2433
- try {
2434
- r2 = arg2.toString().split('.')[1].length;
2435
- }
2436
- catch (e) {
2437
- r2 = 0;
2438
- }
2439
- m = 10 ** Math.max(r1, r2);
2440
- return (multiply(arg1, m) + multiply(arg2, m)) / m;
2441
- };
2442
- /**
2443
- * 数值安全减法
2444
- * @param arg1 数值1
2445
- * @param arg2 数值2
2446
- */
2447
- const subtract = (arg1, arg2) => add(arg1, -arg2);
2448
- /**
2449
- * 数值安全除法
2450
- * @param arg1 数值1
2451
- * @param arg2 数值2
2452
- */
2453
- const divide = (arg1, arg2) => {
2454
- let t1 = 0;
2455
- let t2 = 0;
2456
- let r1 = 0;
2457
- let r2 = 0;
2458
- if (arg1.toString().split('.')[1] !== undefined)
2459
- t1 = arg1.toString().split('.')[1].length;
2460
- if (arg2.toString().split('.')[1] !== undefined)
2461
- t2 = arg2.toString().split('.')[1].length;
2462
- r1 = Number(arg1.toString().replace('.', ''));
2463
- r2 = Number(arg2.toString().replace('.', ''));
2464
- return (r1 / r2) * Math.pow(10, t2 - t1);
2465
- };
2466
- /**
2467
- * Correct the given number to specifying significant digits.
2468
- *
2469
- * @param num The input number
2470
- * @param precision An integer specifying the number of significant digits
2471
- *
2472
- * @example strip(0.09999999999999998) === 0.1 // true
2473
- */
2474
- function strip(num, precision = 15) {
2475
- return +parseFloat(Number(num).toPrecision(precision));
2476
- }
2477
-
2478
- const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
2479
- // eslint-disable-next-line
2480
- const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
2481
- /**
2482
- * 字符串编码成Base64, 平替浏览器的btoa, 不包含中文的处理 (适用于任何环境,包括小程序)
2483
- * @param {string} string
2484
- * @returns {string}
2485
- */
2486
- function weBtoa(string) {
2487
- // 同window.btoa: 字符串编码成Base64
2488
- string = String(string);
2489
- let bitmap, a, b, c, result = '', i = 0;
2490
- const strLen = string.length;
2491
- const rest = strLen % 3;
2492
- for (; i < strLen;) {
2493
- if ((a = string.charCodeAt(i++)) > 255 || (b = string.charCodeAt(i++)) > 255 || (c = string.charCodeAt(i++)) > 255)
2494
- throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
2495
- bitmap = (a << 16) | (b << 8) | c;
2496
- result +=
2497
- b64.charAt((bitmap >> 18) & 63) +
2498
- b64.charAt((bitmap >> 12) & 63) +
2499
- b64.charAt((bitmap >> 6) & 63) +
2500
- b64.charAt(bitmap & 63);
2501
- }
2502
- return rest ? result.slice(0, rest - 3) + '==='.substring(rest) : result;
2503
- }
2504
- /**
2505
- * Base64解码为原始字符串,平替浏览器的atob, 不包含中文的处理(适用于任何环境,包括小程序)
2506
- * @param {string} string
2507
- * @returns {string}
2508
- */
2509
- function weAtob(string) {
2510
- // 同window.atob: Base64解码为原始字符串
2511
- string = String(string).replace(/[\t\n\f\r ]+/g, '');
2512
- if (!b64re.test(string))
2513
- throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
2514
- string += '=='.slice(2 - (string.length & 3));
2515
- let bitmap, result = '', r1, r2, i = 0;
2516
- for (const strLen = string.length; i < strLen;) {
2517
- bitmap =
2518
- (b64.indexOf(string.charAt(i++)) << 18) |
2519
- (b64.indexOf(string.charAt(i++)) << 12) |
2520
- ((r1 = b64.indexOf(string.charAt(i++))) << 6) |
2521
- (r2 = b64.indexOf(string.charAt(i++)));
2522
- result +=
2523
- r1 === 64
2524
- ? String.fromCharCode((bitmap >> 16) & 255)
2525
- : r2 === 64
2526
- ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255)
2527
- : String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255, bitmap & 255);
2528
- }
2529
- return result;
2530
- }
2531
- // function b64DecodeUnicode(str) {
2532
- // return decodeURIComponent(
2533
- // exports.weAtob(str).replace(/(.)/g, function (p) {
2534
- // let code = p.charCodeAt(0).toString(16).toUpperCase();
2535
- // if (code.length < 2) {
2536
- // code = '0' + code;
2537
- // }
2538
- // return '%' + code;
2539
- // })
2540
- // );
2541
- // }
2542
- // function base64_url_decode(str) {
2543
- // let output = str.replace(/-/g, '+').replace(/_/g, '/');
2544
- // switch (output.length % 4) {
2545
- // case 0:
2546
- // break;
2547
- // case 2:
2548
- // output += '==';
2549
- // break;
2550
- // case 3:
2551
- // output += '=';
2552
- // break;
2553
- // default:
2554
- // throw new Error('Illegal base64url string!');
2555
- // }
2556
- // try {
2557
- // return b64DecodeUnicode(output);
2558
- // } catch (err) {
2559
- // return exports.weAtob(output);
2560
- // }
2561
- // }
2562
- // export function weAppJwtDecode(token, options) {
2563
- // if (typeof token !== 'string') {
2564
- // throw new Error('Invalid token specified');
2565
- // }
2566
- // options = options || {};
2567
- // const pos = options.header === true ? 0 : 1;
2568
- // try {
2569
- // return JSON.parse(base64_url_decode(token.split('.')[pos]));
2570
- // } catch (e) {
2571
- // throw new Error('Invalid token specified: ' + (e as Error).message);
2572
- // }
2573
- // }
2574
-
2575
- function stringToUint8Array(str) {
2576
- const utf8 = encodeURIComponent(str); // 将字符串转换为 UTF-8 编码
2577
- const uint8Array = new Uint8Array(utf8.length); // 创建 Uint8Array
2578
- for (let i = 0; i < utf8.length; i++) {
2579
- uint8Array[i] = utf8.charCodeAt(i); // 填充 Uint8Array
2580
- }
2581
- return uint8Array;
2582
- }
2583
- function uint8ArrayToString(uint8Array) {
2584
- const utf8 = String.fromCharCode.apply(null, uint8Array); // 将 Uint8Array 转为字符串
2585
- return decodeURIComponent(utf8); // 将 UTF-8 字符串解码回正常字符串
2586
- }
2587
- /**
2588
- * 将base64编码的字符串转换为原始字符串,包括对中文内容的处理(高性能,且支持Web、Node、小程序等任意平台)
2589
- * @param base64 base64编码的字符串
2590
- * @returns 原始字符串,包括中文内容
2591
- */
2592
- function decodeFromBase64(base64) {
2593
- const binaryString = !isNullOrUnDef(getGlobal('atob')) ? getGlobal('atob')(base64) : weAtob(base64);
2594
- const len = binaryString.length;
2595
- const bytes = new Uint8Array(len);
2596
- for (let i = 0; i < len; i++) {
2597
- bytes[i] = binaryString.charCodeAt(i);
2598
- }
2599
- // 使用TextDecoder将Uint8Array转换为原始字符串,包括中文内容
2600
- return !isNullOrUnDef(getGlobal('TextDecoder'))
2601
- ? new (getGlobal('TextDecoder'))('utf-8').decode(bytes)
2602
- : uint8ArrayToString(bytes);
2603
- }
2604
- /**
2605
- * 将原始字符串,包括中文内容,转换为base64编码的字符串(高性能,且支持Web、Node、小程序等任意平台)
2606
- * @param rawStr 原始字符串,包括中文内容
2607
- * @returns base64编码的字符串
2608
- */
2609
- function encodeToBase64(rawStr) {
2610
- const utf8Array = !isNullOrUnDef(getGlobal('TextEncoder'))
2611
- ? new (getGlobal('TextEncoder'))().encode(rawStr)
2612
- : stringToUint8Array(rawStr);
2613
- // 将 Uint8Array 转换为二进制字符串
2614
- let binaryString = '';
2615
- const len = utf8Array.length;
2616
- for (let i = 0; i < len; i++) {
2617
- binaryString += String.fromCharCode(utf8Array[i]);
2618
- }
2619
- // 将二进制字符串转换为base64编码的字符串
2620
- return !isNullOrUnDef(getGlobal('btoa')) ? getGlobal('btoa')(binaryString) : weBtoa(binaryString);
2621
- }
2622
-
2623
- // 邮箱
2624
- const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
2625
- /**
2626
- * 判断字符串是否为邮箱格式,不对邮箱真实性做验证,如域名是否正确等
2627
- * @param {string} value
2628
- * @returns {boolean}
2629
- */
2630
- const isEmail = (value) => EMAIL_REGEX.test(value);
2631
- // 手机号码 (中国大陆)
2632
- // reference: https://www.runoob.com/regexp/regexp-syntax.html (?: 是非捕获元之一)
2633
- const PHONE_REGEX = /^(?:(?:\+|00)86)?1\d{10}$/;
2634
- /**
2635
- * 判断字符串是否为宽松手机格式,即首位为 1 的 11 位数字都属于手机号
2636
- * @param {string} value
2637
- * @returns {boolean}
2638
- */
2639
- const isPhone = (value) => PHONE_REGEX.test(value);
2640
- // 身份证号码
2641
- // http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/
2642
- // ["北京市", "天津市", "河北省", "山西省", "内蒙古自治区",
2643
- // "辽宁省", "吉林省", "黑龙江省",
2644
- // "上海市", "江苏省", "浙江省", "安徽省", "福建省", "江西省", "山东省",
2645
- // "河南省", "湖北省", "湖南省", "广东省", "广西壮族自治区", "海南省",
2646
- // "重庆市", "四川省", "贵州省", "云南省", "西藏自治区",
2647
- // "陕西省", "甘肃省", "青海省","宁夏回族自治区", "新疆维吾尔自治区",
2648
- // "台湾省",
2649
- // "香港特别行政区", "澳门特别行政区"]
2650
- // ["11", "12", "13", "14", "15",
2651
- // "21", "22", "23",
2652
- // "31", "32", "33", "34", "35", "36", "37",
2653
- // "41", "42", "43", "44", "45", "46",
2654
- // "50", "51", "52", "53", "54",
2655
- // "61", "62", "63", "64", "65",
2656
- // "71",
2657
- // "81", "82"]
2658
- // 91 国外
2659
- const IDNO_RE = /^(1[1-5]|2[1-3]|3[1-7]|4[1-6]|5[0-4]|6[1-5]|7[1]|8[1-2]|9[1])\d{4}(18|19|20)\d{2}[01]\d[0123]\d{4}[\dxX]$/;
2660
- /**
2661
- * 判断字符串是否为身份证号码格式
2662
- * @param {string} value
2663
- * @returns {boolean}
2664
- */
2665
- const isIdNo = (value) => {
2666
- const isSameFormat = IDNO_RE.test(value);
2667
- if (!isSameFormat)
2668
- return false;
2669
- const year = Number(value.slice(6, 10));
2670
- const month = Number(value.slice(10, 12));
2671
- const date = Number(value.slice(12, 14));
2672
- const d = new Date(year, month - 1, date);
2673
- const isSameDate = d.getFullYear() === year && d.getMonth() + 1 === month && d.getDate() === date;
2674
- if (!isSameDate)
2675
- return false;
2676
- // 将身份证号码前面的17位数分别乘以不同的系数;
2677
- // 从第一位到第十七位的系数分别为:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2
2678
- // 将这17位数字和系数相乘的结果相加;
2679
- // 用加出来和除以11,看余数是多少;
2680
- // 余数只可能有0-1-2-3-4-5-6-7-8-9-10这11个数字;
2681
- // 其分别对应的最后一位身份证的号码为1-0-X-9-8-7-6-5-4-3-2
2682
- // 通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。
2683
- const coefficientList = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
2684
- const residueList = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
2685
- let sum = 0;
2686
- for (let start = 0; start < 17; start++) {
2687
- sum += Number(value.slice(start, start + 1)) * coefficientList[start];
2688
- }
2689
- return residueList[sum % 11] === value.slice(-1);
2690
- };
2691
- const URL_REGEX = /^(https?|ftp):\/\/([^\s/$.?#].[^\s]*)$/i;
2692
- const HTTP_URL_REGEX = /^https?:\/\/([^\s/$.?#].[^\s]*)$/i;
2693
- /**
2694
- * 判断字符串是否为 url 格式,支持 http、https、ftp 协议,支持域名或者 ipV4
2695
- * @param {string} value
2696
- * @returns {boolean}
2697
- */
2698
- const isUrl = (url, includeFtp = false) => {
2699
- const regex = includeFtp ? URL_REGEX : HTTP_URL_REGEX;
2700
- return regex.test(url);
2701
- };
2702
- // ipv4
2703
- const IPV4_REGEX = /^(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/;
2704
- // ipv6
2705
- const IPV6_REGEX = /^(([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|([\da-fA-F]{1,4}:){1,7}:|([\da-fA-F]{1,4}:){1,6}:[\da-fA-F]{1,4}|([\da-fA-F]{1,4}:){1,5}(:[\da-fA-F]{1,4}){1,2}|([\da-fA-F]{1,4}:){1,4}(:[\da-fA-F]{1,4}){1,3}|([\da-fA-F]{1,4}:){1,3}(:[\da-fA-F]{1,4}){1,4}|([\da-fA-F]{1,4}:){1,2}(:[\da-fA-F]{1,4}){1,5}|[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,6})|:((:[\da-fA-F]{1,4}){1,7}|:)|fe80:(:[\da-fA-F]{0,4}){0,4}%[\da-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([\da-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))$/i;
2706
- /**
2707
- * 判断字符串是否为 IPV4 格式,不对 ip 真实性做验证
2708
- * @param {string} value
2709
- * @returns {boolean}
2710
- */
2711
- const isIpV4 = (value) => IPV4_REGEX.test(value);
2712
- /**
2713
- * 判断字符串是否为 IPV6 格式,不对 ip 真实性做验证
2714
- * @param {string} value
2715
- * @returns {boolean}
2716
- */
2717
- const isIpV6 = (value) => IPV6_REGEX.test(value);
2718
- const INTEGER_RE = /^(-?[1-9]\d*|0)$/;
2719
- /**
2720
- * 判断字符串是否为整数(自然数),即 ...,-3,-2,-1,0,1,2,3,...
2721
- * @param {string} value
2722
- * @returns {boolean}
2723
- */
2724
- const isInteger = (value) => INTEGER_RE.test(value);
2725
- const FLOAT_RE = /^-?([1-9]\d*|0)\.\d*[1-9]$/;
2726
- /**
2727
- * 判断字符串是否为浮点数,即必须有小数点的有理数
2728
- * @param {string} value
2729
- * @returns {boolean}
2730
- */
2731
- const isFloat = (value) => FLOAT_RE.test(value);
2732
- /**
2733
- * 判断字符串是否为正确数值,包括整数和浮点数
2734
- * @param {string} value
2735
- * @returns {boolean}
2736
- */
2737
- const isNumerical = (value) => isInteger(value) || isFloat(value);
2738
- const DIGIT_RE = /^\d+$/;
2739
- /**
2740
- * 判断字符串是否为数字,例如六位数字短信验证码(093031)
2741
- * @param {string} value
2742
- * @returns {boolean}
2743
- */
2744
- const isDigit = (value) => DIGIT_RE.test(value);
2745
-
2746
- /**
2747
- * 去除字符串中重复字符
2748
- * @param {string} str
2749
- * @returns string
2750
- * @example
2751
- *
2752
- * uniqueSymbol('1a1bac');
2753
- * // => '1abc'
2754
- */
2755
- function uniqueSymbol(str) {
2756
- return [...new Set(str.trim().split(''))].join('');
2757
- }
2758
- /**
2759
- * 转义所有特殊字符
2760
- * @param {string} str 原字符串
2761
- * reference: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions
2762
- * @returns string
2763
- */
2764
- function escapeRegExp(str) {
2765
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); //$&表示整个被匹配的字符串
2766
- }
2767
- /**
2768
- * 根据左右匹配符号生产解析变量(自动删除变量内的空白)
2769
- * @param {string} leftMatchSymbol
2770
- * @param {string} rightMatchSymbol
2771
- * @returns RegExp
2772
- */
2773
- function parseVariableRegExp(leftMatchSymbol, rightMatchSymbol) {
2774
- return new RegExp(`${escapeRegExp(leftMatchSymbol.trim())}\\s*([^${escapeRegExp(uniqueSymbol(leftMatchSymbol))}${escapeRegExp(uniqueSymbol(rightMatchSymbol))}\\s]*)\\s*${rightMatchSymbol.trim()}`, 'g');
2775
- }
2776
- /**
2777
- * 解析字符串的插值变量
2778
- * @param {string} str 字符串
2779
- * @param {string} leftMatchSymbol 变量左侧匹配符号,默认:{
2780
- * @param {string} rightMatchSymbol 变量右侧匹配符号,默认:}
2781
- * @returns string[]
2782
- * @example
2783
- *
2784
- * default match symbol {} same as /{\s*([^{}\s]*)\s*}/g
2785
- */
2786
- function parseVarFromString(str, leftMatchSymbol = '{', rightMatchSymbol = '}') {
2787
- // @ts-ignore
2788
- return Array.from(str.matchAll(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol))).map(el => isNullOrUnDef(el) ? void 0 : el[1]);
2789
- }
2790
- /**
2791
- * 替换字符串中的插值变量
2792
- * @param {string} sourceStr
2793
- * @param {Record<string, any>} targetObj
2794
- * @param {string} leftMatchSymbol 变量左侧匹配符号,默认:{
2795
- * @param {string} rightMatchSymbol 变量右侧匹配符号,默认:}
2796
- * @returns string
2797
- */
2798
- function replaceVarFromString(sourceStr, targetObj, leftMatchSymbol = '{', rightMatchSymbol = '}') {
2799
- return sourceStr.replace(new RegExp(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol)), function (m, p1) {
2800
- return objectHas(targetObj, p1) ? targetObj[p1] : m;
2801
- });
2802
- }
2803
- /**
2804
- * 在指定作用域中执行代码
2805
- * @param {string} code 要执行的代码(需包含 return 语句或表达式)
2806
- * @param {Object} scope 作用域对象(键值对形式的变量环境)
2807
- * @returns 代码执行结果
2808
- *
2809
- * @example
2810
- * // 测试用例 1: 基本变量访问
2811
- * executeInScope("return a + b;", { a: 1, b: 2 });
2812
- * // 3
2813
- *
2814
- * // 测试用例 2: 支持复杂表达式和运算
2815
- * executeInScope(
2816
- * "return Array.from({ length: 3 }, (_, i) => base + i);",
2817
- * { base: 100 }
2818
- * );
2819
- * // [100, 101, 102]
2820
- *
2821
- * // 支持外传函数作用域执行
2822
- * const scope = {
2823
- * $: {
2824
- * fun: {
2825
- * time: {
2826
- * now: function () {
2827
- * return new Date();
2828
- * },
2829
- * },
2830
- * },
2831
- * },
2832
- * };
2833
- * executeInScope("return $.fun.time.now()", scope)
2834
- */
2835
- function executeInScope(code, scope = {}) {
2836
- // 提取作用域对象的键和值
2837
- const keys = Object.keys(scope);
2838
- const values = keys.map(key => scope[key]);
2839
- try {
2840
- // 动态创建函数,将作用域的键作为参数,代码作为函数体
2841
- const func = new Function(...keys, `return (() => { ${code} })()`);
2842
- // 调用函数并传入作用域的值
2843
- return func(...values);
2844
- }
2845
- catch (error) {
2846
- throw new Error(`代码执行失败: ${error.message}`);
2847
- }
2848
- }
2849
-
2850
- /**
2851
- * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
2852
- *
2853
- * 包含对null、原始值、对象循环引用的处理
2854
- *
2855
- * 对Map、Set、ArrayBuffer、Date、RegExp、Array、Object及原型链属性方法执行深拷贝
2856
- * @param {T} source
2857
- * @param {WeakMap} map
2858
- * @returns {T}
2859
- */
2860
- function cloneDeep(source, map = new WeakMap()) {
2861
- // 处理原始类型和 null/undefined
2862
- if (source === null || typeof source !== 'object') {
2863
- return source;
2864
- }
2865
- // 处理循环引用
2866
- if (map.has(source)) {
2867
- return map.get(source);
2868
- }
2869
- // 处理 ArrayBuffer
2870
- if (source instanceof ArrayBuffer) {
2871
- const copy = new ArrayBuffer(source.byteLength);
2872
- new Uint8Array(copy).set(new Uint8Array(source));
2873
- map.set(source, copy);
2874
- return copy;
2875
- }
2876
- // 处理 DataView 和 TypedArray (Uint8Array 等)
2877
- if (ArrayBuffer.isView(source)) {
2878
- const constructor = source.constructor;
2879
- const bufferCopy = cloneDeep(source.buffer, map);
2880
- return new constructor(bufferCopy, source.byteOffset, source.length);
2881
- }
2882
- // 处理 Date 对象
2883
- if (source instanceof Date) {
2884
- const copy = new Date(source.getTime());
2885
- map.set(source, copy);
2886
- return copy;
2887
- }
2888
- // 处理 RegExp 对象
2889
- if (source instanceof RegExp) {
2890
- const copy = new RegExp(source.source, source.flags);
2891
- copy.lastIndex = source.lastIndex; // 保留匹配状态
2892
- map.set(source, copy);
2893
- return copy;
2894
- }
2895
- // 处理 Map
2896
- if (source instanceof Map) {
2897
- const copy = new Map();
2898
- map.set(source, copy);
2899
- source.forEach((value, key) => {
2900
- copy.set(cloneDeep(key, map), cloneDeep(value, map));
2901
- });
2902
- return copy;
2903
- }
2904
- // 处理 Set
2905
- if (source instanceof Set) {
2906
- const copy = new Set();
2907
- map.set(source, copy);
2908
- source.forEach(value => {
2909
- copy.add(cloneDeep(value, map));
2910
- });
2911
- return copy;
2912
- }
2913
- // 处理数组 (包含稀疏数组)
2914
- if (Array.isArray(source)) {
2915
- const copy = new Array(source.length);
2916
- map.set(source, copy);
2917
- // 克隆所有有效索引
2918
- for (let i = 0, len = source.length; i < len; i++) {
2919
- if (i in source) {
2920
- // 保留空位
2921
- copy[i] = cloneDeep(source[i], map);
2922
- }
2923
- }
2924
- // 克隆数组的自定义属性
2925
- const descriptors = Object.getOwnPropertyDescriptors(source);
2926
- for (const key of Reflect.ownKeys(descriptors)) {
2927
- Object.defineProperty(copy, key, {
2928
- ...descriptors[key],
2929
- value: cloneDeep(descriptors[key].value, map)
2930
- });
2931
- }
2932
- return copy;
2933
- }
2934
- // 处理普通对象和类实例
2935
- const copy = Object.create(Object.getPrototypeOf(source));
2936
- map.set(source, copy);
2937
- const descriptors = Object.getOwnPropertyDescriptors(source);
2938
- for (const key of Reflect.ownKeys(descriptors)) {
2939
- const descriptor = descriptors[key];
2940
- if ('value' in descriptor) {
2941
- // 克隆数据属性
2942
- descriptor.value = cloneDeep(descriptor.value, map);
2943
- }
2944
- else {
2945
- // 处理访问器属性 (getter/setter)
2946
- if (descriptor.get) {
2947
- descriptor.get = cloneDeep(descriptor.get, map);
2948
- }
2949
- if (descriptor.set) {
2950
- descriptor.set = cloneDeep(descriptor.set, map);
2951
- }
2952
- }
2953
- Object.defineProperty(copy, key, descriptor);
2954
- }
2955
- return copy;
2956
- }
2957
-
2958
- /**
2959
- * 比较两值是否相等,适用所有数据类型
2960
- * @param {Comparable} a
2961
- * @param {Comparable} b
2962
- * @returns {boolean}
2963
- */
2964
- function isEqual(a, b) {
2965
- return deepEqual(a, b);
2966
- }
2967
- function deepEqual(a, b, compared = new WeakMap()) {
2968
- // 相同值快速返回
2969
- if (Object.is(a, b))
2970
- return true;
2971
- // 类型不同直接返回false
2972
- const typeA = Object.prototype.toString.call(a);
2973
- const typeB = Object.prototype.toString.call(b);
2974
- if (typeA !== typeB)
2975
- return false;
2976
- // 只缓存对象类型
2977
- if (isObject(a) && isObject(b)) {
2978
- if (compared.has(a))
2979
- return compared.get(a) === b;
2980
- compared.set(a, b);
2981
- compared.set(b, a);
2982
- }
2983
- // 处理特殊对象类型
2984
- switch (typeA) {
2985
- case '[object Date]':
2986
- return a.getTime() === b.getTime();
2987
- case '[object RegExp]':
2988
- return a.toString() === b.toString();
2989
- case '[object Map]':
2990
- return compareMap(a, b, compared);
2991
- case '[object Set]':
2992
- return compareSet(a, b, compared);
2993
- case '[object ArrayBuffer]':
2994
- return compareArrayBuffer(a, b);
2995
- case '[object DataView]':
2996
- return compareDataView(a, b, compared);
2997
- case '[object Int8Array]':
2998
- case '[object Uint8Array]':
2999
- case '[object Uint8ClampedArray]':
3000
- case '[object Int16Array]':
3001
- case '[object Uint16Array]':
3002
- case '[object Int32Array]':
3003
- case '[object Uint32Array]':
3004
- case '[object Float32Array]':
3005
- case '[object Float64Array]':
3006
- return compareTypedArray(a, b, compared);
3007
- case '[object Object]':
3008
- return compareObjects(a, b, compared);
3009
- case '[object Array]':
3010
- return compareArrays(a, b, compared);
3011
- }
3012
- return false;
3013
- }
3014
- // 辅助比较函数
3015
- function compareMap(a, b, compared) {
3016
- if (a.size !== b.size)
3017
- return false;
3018
- for (const [key, value] of a) {
3019
- if (!b.has(key) || !deepEqual(value, b.get(key), compared))
3020
- return false;
3021
- }
3022
- return true;
3023
- }
3024
- function compareSet(a, b, compared) {
3025
- if (a.size !== b.size)
3026
- return false;
3027
- for (const value of a) {
3028
- let found = false;
3029
- for (const bValue of b) {
3030
- if (deepEqual(value, bValue, compared)) {
3031
- found = true;
3032
- break;
3033
- }
3034
- }
3035
- if (!found)
3036
- return false;
3037
- }
3038
- return true;
3039
- }
3040
- function compareArrayBuffer(a, b) {
3041
- if (a.byteLength !== b.byteLength)
3042
- return false;
3043
- return new DataView(a).getInt32(0) === new DataView(b).getInt32(0);
3044
- }
3045
- function compareDataView(a, b, compared) {
3046
- return a.byteLength === b.byteLength && deepEqual(new Uint8Array(a.buffer), new Uint8Array(b.buffer), compared);
3047
- }
3048
- function compareTypedArray(a, b, compared) {
3049
- return a.byteLength === b.byteLength && deepEqual(Array.from(a), Array.from(b), compared);
3050
- }
3051
- function compareObjects(a, b, compared) {
3052
- const keysA = Reflect.ownKeys(a);
3053
- const keysB = Reflect.ownKeys(b);
3054
- if (keysA.length !== keysB.length)
3055
- return false;
3056
- for (const key of keysA) {
3057
- if (!keysB.includes(key))
3058
- return false;
3059
- if (!deepEqual(a[key], b[key], compared))
3060
- return false;
3061
- }
3062
- // 原型链比较
3063
- return Object.getPrototypeOf(a) === Object.getPrototypeOf(b);
3064
- }
3065
- function compareArrays(a, b, compared) {
3066
- // 增加有效索引检查
3067
- const keysA = Object.keys(a).map(Number);
3068
- const keysB = Object.keys(b).map(Number);
3069
- if (keysA.length !== keysB.length)
3070
- return false;
3071
- // 递归比较每个元素
3072
- for (let i = 0, len = a.length; i < len; i++) {
3073
- if (!deepEqual(a[i], b[i], compared))
3074
- return false;
3075
- }
3076
- // 比较数组对象的其他属性
3077
- return compareObjects(a, b, compared);
3078
- }
3079
-
3080
- exports.EMAIL_REGEX = EMAIL_REGEX;
3081
- exports.HEX_POOL = HEX_POOL;
3082
- exports.HTTP_URL_REGEX = HTTP_URL_REGEX;
3083
- exports.IPV4_REGEX = IPV4_REGEX;
3084
- exports.IPV6_REGEX = IPV6_REGEX;
3085
- exports.PHONE_REGEX = PHONE_REGEX;
3086
- exports.STRING_ARABIC_NUMERALS = STRING_ARABIC_NUMERALS;
3087
- exports.STRING_LOWERCASE_ALPHA = STRING_LOWERCASE_ALPHA;
3088
- exports.STRING_POOL = STRING_POOL;
3089
- exports.STRING_UPPERCASE_ALPHA = STRING_UPPERCASE_ALPHA;
3090
- exports.UNIQUE_NUMBER_SAFE_LENGTH = UNIQUE_NUMBER_SAFE_LENGTH;
3091
- exports.URL_REGEX = URL_REGEX;
3092
- exports.add = add;
3093
- exports.addClass = addClass;
3094
- exports.arrayEach = arrayEach;
3095
- exports.arrayEachAsync = arrayEachAsync;
3096
- exports.arrayInsertBefore = arrayInsertBefore;
3097
- exports.arrayLike = arrayLike;
3098
- exports.arrayRemove = arrayRemove;
3099
- exports.asyncMap = asyncMap;
3100
- exports.calculateDate = calculateDate;
3101
- exports.calculateDateTime = calculateDateTime;
3102
- exports.chooseLocalFile = chooseLocalFile;
3103
- exports.cloneDeep = cloneDeep;
3104
- exports.compressImg = compressImg;
3105
- exports.cookieDel = cookieDel;
3106
- exports.cookieGet = cookieGet;
3107
- exports.cookieSet = cookieSet;
3108
- exports.copyText = copyText;
3109
- exports.crossOriginDownload = crossOriginDownload;
3110
- exports.dateParse = dateParse;
3111
- exports.dateToEnd = dateToEnd;
3112
- exports.dateToStart = dateToStart;
3113
- exports.debounce = debounce;
3114
- exports.decodeFromBase64 = decodeFromBase64;
3115
- exports.divide = divide;
3116
- exports.downloadBlob = downloadBlob;
3117
- exports.downloadData = downloadData;
3118
- exports.downloadHref = downloadHref;
3119
- exports.downloadURL = downloadURL;
3120
- exports.encodeToBase64 = encodeToBase64;
3121
- exports.escapeRegExp = escapeRegExp;
3122
- exports.executeInScope = executeInScope;
3123
- exports.flatTree = flatTree;
3124
- exports.forEachDeep = forEachDeep;
3125
- exports.formatDate = formatDate;
3126
- exports.formatMoney = formatNumber;
3127
- exports.formatNumber = formatNumber;
3128
- exports.formatTree = formatTree;
3129
- exports.fuzzySearchTree = fuzzySearchTree;
3130
- exports.genCanvasWM = genCanvasWM;
3131
- exports.getComputedCssVal = getComputedCssVal;
3132
- exports.getGlobal = getGlobal;
3133
- exports.getStrWidthPx = getStrWidthPx;
3134
- exports.getStyle = getStyle;
3135
- exports.hasClass = hasClass;
3136
- exports.humanFileSize = humanFileSize;
3137
- exports.isArray = isArray;
3138
- exports.isBigInt = isBigInt;
3139
- exports.isBoolean = isBoolean;
3140
- exports.isDate = isDate;
3141
- exports.isDigit = isDigit;
3142
- exports.isEmail = isEmail;
3143
- exports.isEmpty = isEmpty;
3144
- exports.isEqual = isEqual;
3145
- exports.isError = isError;
3146
- exports.isFloat = isFloat;
3147
- exports.isFunction = isFunction;
3148
- exports.isIdNo = isIdNo;
3149
- exports.isInteger = isInteger;
3150
- exports.isIpV4 = isIpV4;
3151
- exports.isIpV6 = isIpV6;
3152
- exports.isJsonString = isJsonString;
3153
- exports.isNaN = isNaN;
3154
- exports.isNull = isNull;
3155
- exports.isNullOrUnDef = isNullOrUnDef;
3156
- exports.isNullish = isNullOrUnDef;
3157
- exports.isNumber = isNumber;
3158
- exports.isNumerical = isNumerical;
3159
- exports.isObject = isObject;
3160
- exports.isPhone = isPhone;
3161
- exports.isPlainObject = isPlainObject;
3162
- exports.isPrimitive = isPrimitive;
3163
- exports.isRegExp = isRegExp;
3164
- exports.isString = isString;
3165
- exports.isSymbol = isSymbol;
3166
- exports.isUndefined = isUndefined;
3167
- exports.isUrl = isUrl;
3168
- exports.isValidDate = isValidDate;
3169
- exports.mapDeep = mapDeep;
3170
- exports.multiply = multiply;
3171
- exports.numberAbbr = numberAbbr;
3172
- exports.numberToHex = numberToHex;
3173
- exports.objectAssign = objectAssign;
3174
- exports.objectEach = objectEach;
3175
- exports.objectEachAsync = objectEachAsync;
3176
- exports.objectFill = objectFill;
3177
- exports.objectGet = objectGet;
3178
- exports.objectHas = objectHas;
3179
- exports.objectMap = objectMap;
3180
- exports.objectMerge = objectAssign;
3181
- exports.objectOmit = objectOmit;
3182
- exports.objectPick = objectPick;
3183
- exports.once = once;
3184
- exports.parseQueryParams = parseQueryParams;
3185
- exports.parseVarFromString = parseVarFromString;
3186
- exports.pathJoin = pathJoin;
3187
- exports.pathNormalize = pathNormalize;
3188
- exports.qsParse = qsParse;
3189
- exports.qsStringify = qsStringify;
3190
- exports.randomNumber = randomNumber;
3191
- exports.randomString = randomString;
3192
- exports.randomUuid = randomUuid;
3193
- exports.removeClass = removeClass;
3194
- exports.replaceVarFromString = replaceVarFromString;
3195
- exports.searchTreeById = searchTreeById;
3196
- exports.setGlobal = setGlobal;
3197
- exports.setStyle = setStyle;
3198
- exports.smoothScroll = smoothScroll;
3199
- exports.stringAssign = stringAssign;
3200
- exports.stringCamelCase = stringCamelCase;
3201
- exports.stringEscapeHtml = stringEscapeHtml;
3202
- exports.stringFill = stringFill;
3203
- exports.stringFormat = stringFormat;
3204
- exports.stringKebabCase = stringKebabCase;
3205
- exports.strip = strip;
3206
- exports.subtract = subtract;
3207
- exports.supportCanvas = supportCanvas;
3208
- exports.throttle = throttle;
3209
- exports.tooltipEvent = tooltipEvent;
3210
- exports.typeIs = typeIs;
3211
- exports.uniqueNumber = uniqueNumber;
3212
- exports.uniqueString = uniqueString;
3213
- exports.uniqueSymbol = uniqueSymbol;
3214
- exports.urlDelParams = urlDelParams;
3215
- exports.urlParse = urlParse;
3216
- exports.urlSetParams = urlSetParams;
3217
- exports.urlStringify = urlStringify;
3218
- exports.wait = wait;
3219
- exports.weAtob = weAtob;
3220
- exports.weBtoa = weBtoa;
3221
-
3222
- }));
6
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).sculpJs={})}(this,(function(e){"use strict";function t(e,t,n=!1){if(n)for(let n=e.length-1;n>=0;n--){const r=t(e[n],n,e);if(!1===r)break}else for(let n=0,r=e.length;n<r;n++){const r=t(e[n],n,e);if(!1===r)break}}function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function r(e){return!!f(e)||(!!i(e)||!!d(e)&&n(e,"length"))}function o(e){return Object.prototype.toString.call(e).slice(8,-1)}const i=e=>"string"==typeof e,s=e=>"boolean"==typeof e,c=e=>"number"==typeof e&&!Number.isNaN(e),a=e=>void 0===e,l=e=>null===e;function u(e){return a(e)||l(e)}const d=e=>"Object"===o(e),f=e=>Array.isArray(e),p=e=>"function"==typeof e,h=e=>Number.isNaN(e),g=e=>"Date"===o(e);function m(e){return!(!u(e)&&!Number.isNaN(e))||(r(e)&&(f(e)||i(e)||p(e.splice))?!e.length:!Object.keys(e).length)}function y(e,t,n){const r=[],o="expires";if(r.push([e,encodeURIComponent(t)]),c(n)){const e=new Date;e.setTime(e.getTime()+n),r.push([o,e.toUTCString()])}else g(n)&&r.push([o,n.toUTCString()]);r.push(["path","/"]),document.cookie=r.map((e=>{const[t,n]=e;return`${t}=${n}`})).join(";")}const b=e=>g(e)&&!h(e.getTime()),w=e=>{if(!i(e))return;const t=e.replace(/-/g,"/");return new Date(t)},x=e=>{if(!i(e))return;const t=/([+-])(\d\d)(\d\d)$/,n=t.exec(e);if(!n)return;const r=e.replace(t,"Z"),o=new Date(r);if(!b(o))return;const[,s,c,a]=n,l=parseInt(c,10),u=parseInt(a,10),d=(e,t)=>"+"===s?e-t:e+t;return o.setHours(d(o.getHours(),l)),o.setMinutes(d(o.getMinutes(),u)),o};function S(e){const t=new Date(e);if(b(t))return t;const n=w(e);if(b(n))return n;const r=x(e);if(b(r))return r;throw new SyntaxError(`${e.toString()} 不是一个合法的日期描述`)}function A(e){const t=S(e);return new Date(t.getFullYear(),t.getMonth(),t.getDate(),0,0,0,0)}function E(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var v=.1,F="function"==typeof Float32Array;function C(e,t){return 1-3*t+3*e}function j(e,t){return 3*t-6*e}function $(e){return 3*e}function T(e,t,n){return((C(t,n)*e+j(t,n))*e+$(t))*e}function O(e,t,n){return 3*C(t,n)*e*e+2*j(t,n)*e+$(t)}function R(e){return e}var I=E((function(e,t,n,r){if(!(0<=e&&e<=1&&0<=n&&n<=1))throw new Error("bezier x values must be in [0, 1] range");if(e===t&&n===r)return R;for(var o=F?new Float32Array(11):new Array(11),i=0;i<11;++i)o[i]=T(i*v,e,n);function s(t){for(var r=0,i=1;10!==i&&o[i]<=t;++i)r+=v;--i;var s=r+(t-o[i])/(o[i+1]-o[i])*v,c=O(s,e,n);return c>=.001?function(e,t,n,r){for(var o=0;o<4;++o){var i=O(t,n,r);if(0===i)return t;t-=(T(t,n,r)-e)/i}return t}(t,s,e,n):0===c?s:function(e,t,n,r,o){var i,s,c=0;do{(i=T(s=t+(n-t)/2,r,o)-e)>0?n=s:t=s}while(Math.abs(i)>1e-7&&++c<10);return s}(t,r,r+v,e,n)}return function(e){return 0===e?0:1===e?1:T(s(e),t,r)}}));const D={linear:[0,0,1,1],ease:[.25,.1,.25,1],"ease-in":[.42,0,1,1],"ease-out":[0,0,.58,1],"ease-in-out":[.42,0,.58,1]};const L=e=>{if(!d(e))return!1;const t=Object.getPrototypeOf(e);return!t||t===Object.prototype};function M(e,t){for(const r in e)if(n(e,r)&&!1===t(e[r],r))break}function N(e,t){const n={};return M(e,((e,r)=>{t.includes(r)||(n[r]=e)})),n}const P=(e,t,n)=>{if(a(n))return t;if(o(t)!==o(n))return f(n)?P(e,[],n):d(n)?P(e,{},n):n;if(L(n)){const r=e.get(n);return r||(e.set(n,t),M(n,((n,r)=>{t[r]=P(e,t[r],n)})),t)}if(f(n)){const r=e.get(n);return r||(e.set(n,t),n.forEach(((n,r)=>{t[r]=P(e,t[r],n)})),t)}return n};function B(e,...t){const n=new Map;for(let r=0,o=t.length;r<o;r++){const o=t[r];e=P(n,e,o)}return n.clear(),e}function U(e,t="-"){return e.replace(/^./,(e=>e.toLowerCase())).replace(/[A-Z]/g,(e=>`${t}${e.toLowerCase()}`))}const k="0123456789",H="abcdefghijklmnopqrstuvwxyz",z="ABCDEFGHIJKLMNOPQRSTUVWXYZ",W=/%[%sdo]/g;const q=/\${(.*?)}/g;const _=(e,t)=>{e.split(/\s+/g).forEach(t)};const G=(e,t,n)=>{d(t)?M(t,((t,n)=>{G(e,n,t)})):e.style.setProperty(U(t),n)};function V(e,t=14,n=!0){let r=0;if(console.assert(i(e),`${e} 不是有效的字符串`),i(e)&&e.length>0){const o="getStrWidth1494304949567";let i=document.querySelector(`#${o}`);if(!i){const n=document.createElement("span");n.id=o,n.style.fontSize=t+"px",n.style.whiteSpace="nowrap",n.style.visibility="hidden",n.style.position="absolute",n.style.top="-9999px",n.style.left="-9999px",n.textContent=e,document.body.appendChild(n),i=n}i.textContent=e,r=i.offsetWidth,n&&i.remove()}return r}const Y=e=>{const t=e.replace(/\\/g,"/").replace(/\/{2,}/g,"/").replace(/\.{3,}/g,"..").replace(/\/\.\//g,"/").split("/").map((e=>e.trim())),n=e=>".."===e,r=[];let o=!1;const i=e=>{r.push(e)};return t.forEach((e=>{const t=(e=>"."===e)(e),s=n(e);return o?t?void 0:s?(()=>{if(0===r.length)return;const e=r[r.length-1];n(e)?r.push(".."):r.pop()})():void i(e):(i(e),void(o=!t&&!s))})),r.join("/")},K=(e,...t)=>Y([e,...t].join("/"));function X(e){const t=new URLSearchParams(e),n={};for(const[e,r]of t.entries())a(n[e])?n[e]=r:f(n[e])||(n[e]=t.getAll(e));return n}const J=e=>i(e)?e:c(e)?String(e):s(e)?e?"true":"false":g(e)?e.toISOString():null;function Z(e,t=J){const n=new URLSearchParams;return M(e,((e,r)=>{if(f(e))e.forEach((e=>{const o=t(e);null!==o&&n.append(r.toString(),o)}));else{const o=t(e);if(null===o)return;n.set(r.toString(),o)}})),n.toString()}const Q=(e,t=!0)=>{let n=null;p(URL)&&t?n=new URL(e):(n=document.createElement("a"),n.href=e);const{protocol:r,username:o,password:i,host:s,port:c,hostname:a,hash:l,search:u,pathname:d}=n,f=K("/",d),h=o&&i?`${o}:${i}`:"",g=u.replace(/^\?/,"");return n=null,{protocol:r,auth:h,username:o,password:i,host:s,port:c,hostname:a,hash:l,search:u,searchParams:X(g),query:g,pathname:f,path:`${f}${u}`,href:e}},ee=e=>{const{protocol:t,auth:n,host:r,pathname:o,searchParams:i,hash:s}=e,c=n?`${n}@`:"",a=Z(i),l=a?`?${a}`:"";let u=s.replace(/^#/,"");return u=u?"#"+u:"",`${t}//${c}${r}${o}${l}${u}`},te=(e,t)=>{const n=Q(e);return Object.assign(n.searchParams,t),ee(n)};function ne(e,t,n){const r=document.createElement("a");r.download=t,r.style.display="none",r.href=e,document.body.appendChild(r),r.click(),setTimeout((()=>{document.body.removeChild(r),p(n)&&n()}))}function re(e,t,n){const r=URL.createObjectURL(e);ne(r,t),setTimeout((()=>{URL.revokeObjectURL(r),p(n)&&n()}))}function oe(){return!!document.createElement("canvas").getContext}function ie({maxWidth:e,maxHeight:t,originWidth:n,originHeight:r}){let o=n,i=r;return(n>e||r>t)&&(n/r>e/t?(o=e,i=Math.round(e*(r/n))):(i=t,o=Math.round(t*(n/r)))),{width:o,height:i}}function se(e){return"undefined"!=typeof globalThis?globalThis[e]:"undefined"!=typeof window?window[e]:"undefined"!=typeof global?global[e]:"undefined"!=typeof self?self[e]:void 0}const ce=(e,t)=>Math.floor(Math.random()*(t-e+1)+e),ae=`${k}${z}${H}`;const le=`${k}${z}${H}`,ue="undefined"!=typeof BigInt,de=()=>se("JSBI"),fe=e=>ue?BigInt(e):de().BigInt(e);function pe(e,t=le){if(t.length<2)throw new Error("进制池长度不能少于 2");if(!ue)throw new Error('需要安装 jsbi 模块并将 JSBI 设置为全局变量:\nimport JSBI from "jsbi"; window.JSBI = JSBI;');let n=fe(e);const r=[],{length:o}=t,i=fe(o),s=()=>{const e=Number(((e,t)=>ue?e%t:de().remainder(e,t))(n,i));n=((e,t)=>ue?e/t:de().divide(e,t))(n,i),r.unshift(t[e]),n>0&&s()};return s(),r.join("")}const he=(e,t,n={ratio:1e3,decimals:0,separator:" "})=>{const{ratio:r=1e3,decimals:o=0,separator:i=" "}=n,{length:s}=t;if(0===s)throw new Error("At least one unit is required");let c=Number(e),a=0;for(;c>=r&&a<s-1;)c/=r,a++;const l=c.toFixed(o),u=t[a];return String(l)+i+u};function ge(e,t){if(u(t))return parseInt(String(e)).toLocaleString();let n=0;if(!c(t))throw new Error("Decimals must be a positive number not less than zero");return t>0&&(n=t),Number(Number(e).toFixed(n)).toLocaleString("en-US")}let me=0,ye=0;const be=(e=18)=>{const t=Date.now();e=Math.max(e,18),t!==ye&&(ye=t,me=0);const n=`${t}`;let r="";const o=e-5-13;if(o>0){r=`${ce(10**(o-1),10**o-1)}`}const i=((e,t=2)=>String(e).padStart(t,"0"))(me,5);return me++,`${n}${r}${i}`},we=e=>e[ce(0,e.length-1)];function xe(e,t,n){let r=250,o=13;const i=e.children[0];V(t,12)<230?(i.style.maxWidth=V(t,12)+20+50+"px",r=n.clientX+(V(t,12)+50)-document.body.offsetWidth):(i.style.maxWidth="250px",r=n.clientX+230-document.body.offsetWidth),i.innerHTML=t,r>0&&(o-=r),e.style.top=n.clientY+23+"px",e.style.left=n.clientX+o+"px",e.style.maxWidth="250px";const s=e.getBoundingClientRect().top+i.offsetHeight-document.body.offsetHeight;s>0&&(e.style.top=n.clientY-s+"px")}const Se={handleMouseEnter:function({rootContainer:e="#root",title:t,event:n,bgColor:r="#000",color:o="#fff"}){try{const s=i(e)?document.querySelector(e):e;if(!s)throw new Error(`${e} is not valid Html Element or element selector`);let c=null;const a="style-tooltip-inner1494304949567";if(!document.querySelector(`#${a}`)){const e=document.createElement("style");e.type="text/css",e.id=a,e.innerHTML=`\n .tooltip-inner1494304949567 {\n max-width: 250px;\n padding: 3px 8px;\n color: ${o};\n text-decoration: none;\n border-radius: 4px;\n text-align: left;\n background-color: ${r};\n }\n `,document.querySelector("head").appendChild(e)}if(c=document.querySelector("#customTitle1494304949567"),c)xe(c,t,n);else{const e=document.createElement("div");e.id="customTitle1494304949567",e.style.cssText="z-index: 99999999; visibility: hidden; position: absolute;",e.innerHTML='<div class="tooltip-inner1494304949567" style="word-wrap: break-word; max-width: 44px;">皮肤</div>',s.appendChild(e),c=document.querySelector("#customTitle1494304949567"),t&&(xe(c,t,n),c.style.visibility="visible")}}catch(e){console.error(e.message)}},handleMouseLeave:function(e="#root"){const t=i(e)?document.querySelector(e):e,n=document.querySelector("#customTitle1494304949567");t&&n&&t.removeChild(n)}},Ae={keyField:"key",childField:"children",pidField:"pid"},Ee={childField:"children",nameField:"name",removeEmptyChild:!1,ignoreCase:!0};const ve=(e,t)=>{let n=0;const r=e.toString(),o=t.toString();return void 0!==r.split(".")[1]&&(n+=r.split(".")[1].length),void 0!==o.split(".")[1]&&(n+=o.split(".")[1].length),Number(r.replace(".",""))*Number(o.replace(".",""))/Math.pow(10,n)},Fe=(e,t)=>{let n=0,r=0,o=0;try{n=e.toString().split(".")[1].length}catch(e){n=0}try{r=t.toString().split(".")[1].length}catch(e){r=0}return o=10**Math.max(n,r),(ve(e,o)+ve(t,o))/o};const Ce="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",je=/^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;function $e(e){let t,n,r,o,i="",s=0;const c=(e=String(e)).length,a=c%3;for(;s<c;){if((n=e.charCodeAt(s++))>255||(r=e.charCodeAt(s++))>255||(o=e.charCodeAt(s++))>255)throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");t=n<<16|r<<8|o,i+=Ce.charAt(t>>18&63)+Ce.charAt(t>>12&63)+Ce.charAt(t>>6&63)+Ce.charAt(63&t)}return a?i.slice(0,a-3)+"===".substring(a):i}function Te(e){if(e=String(e).replace(/[\t\n\f\r ]+/g,""),!je.test(e))throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");let t,n,r,o="",i=0;for(const s=(e+="==".slice(2-(3&e.length))).length;i<s;)t=Ce.indexOf(e.charAt(i++))<<18|Ce.indexOf(e.charAt(i++))<<12|(n=Ce.indexOf(e.charAt(i++)))<<6|(r=Ce.indexOf(e.charAt(i++))),o+=64===n?String.fromCharCode(t>>16&255):64===r?String.fromCharCode(t>>16&255,t>>8&255):String.fromCharCode(t>>16&255,t>>8&255,255&t);return o}const Oe=/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,Re=/^(?:(?:\+|00)86)?1\d{10}$/,Ie=/^(1[1-5]|2[1-3]|3[1-7]|4[1-6]|5[0-4]|6[1-5]|7[1]|8[1-2]|9[1])\d{4}(18|19|20)\d{2}[01]\d[0123]\d{4}[\dxX]$/,De=/^(https?|ftp):\/\/([^\s/$.?#].[^\s]*)$/i,Le=/^https?:\/\/([^\s/$.?#].[^\s]*)$/i,Me=/^(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/,Ne=/^(([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|([\da-fA-F]{1,4}:){1,7}:|([\da-fA-F]{1,4}:){1,6}:[\da-fA-F]{1,4}|([\da-fA-F]{1,4}:){1,5}(:[\da-fA-F]{1,4}){1,2}|([\da-fA-F]{1,4}:){1,4}(:[\da-fA-F]{1,4}){1,3}|([\da-fA-F]{1,4}:){1,3}(:[\da-fA-F]{1,4}){1,4}|([\da-fA-F]{1,4}:){1,2}(:[\da-fA-F]{1,4}){1,5}|[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,6})|:((:[\da-fA-F]{1,4}){1,7}|:)|fe80:(:[\da-fA-F]{0,4}){0,4}%[\da-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([\da-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))$/i,Pe=/^(-?[1-9]\d*|0)$/,Be=e=>Pe.test(e),Ue=/^-?([1-9]\d*|0)\.\d*[1-9]$/,ke=e=>Ue.test(e),He=/^\d+$/;function ze(e){return[...new Set(e.trim().split(""))].join("")}function We(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function qe(e,t){return new RegExp(`${We(e.trim())}\\s*([^${We(ze(e))}${We(ze(t))}\\s]*)\\s*${t.trim()}`,"g")}function _e(e,t,n=new WeakMap){if(Object.is(e,t))return!0;const r=Object.prototype.toString.call(e);if(r!==Object.prototype.toString.call(t))return!1;if(d(e)&&d(t)){if(n.has(e))return n.get(e)===t;n.set(e,t),n.set(t,e)}switch(r){case"[object Date]":return e.getTime()===t.getTime();case"[object RegExp]":return e.toString()===t.toString();case"[object Map]":return function(e,t,n){if(e.size!==t.size)return!1;for(const[r,o]of e)if(!t.has(r)||!_e(o,t.get(r),n))return!1;return!0}(e,t,n);case"[object Set]":return function(e,t,n){if(e.size!==t.size)return!1;for(const r of e){let e=!1;for(const o of t)if(_e(r,o,n)){e=!0;break}if(!e)return!1}return!0}(e,t,n);case"[object ArrayBuffer]":return function(e,t){return e.byteLength===t.byteLength&&new DataView(e).getInt32(0)===new DataView(t).getInt32(0)}(e,t);case"[object DataView]":return function(e,t,n){return e.byteLength===t.byteLength&&_e(new Uint8Array(e.buffer),new Uint8Array(t.buffer),n)}(e,t,n);case"[object Int8Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Int16Array]":case"[object Uint16Array]":case"[object Int32Array]":case"[object Uint32Array]":case"[object Float32Array]":case"[object Float64Array]":return function(e,t,n){return e.byteLength===t.byteLength&&_e(Array.from(e),Array.from(t),n)}(e,t,n);case"[object Object]":return Ge(e,t,n);case"[object Array]":return function(e,t,n){const r=Object.keys(e).map(Number),o=Object.keys(t).map(Number);if(r.length!==o.length)return!1;for(let r=0,o=e.length;r<o;r++)if(!_e(e[r],t[r],n))return!1;return Ge(e,t,n)}(e,t,n)}return!1}function Ge(e,t,n){const r=Reflect.ownKeys(e),o=Reflect.ownKeys(t);if(r.length!==o.length)return!1;for(const i of r){if(!o.includes(i))return!1;if(!_e(e[i],t[i],n))return!1}return Object.getPrototypeOf(e)===Object.getPrototypeOf(t)}e.EMAIL_REGEX=Oe,e.HEX_POOL=le,e.HTTP_URL_REGEX=Le,e.IPV4_REGEX=Me,e.IPV6_REGEX=Ne,e.PHONE_REGEX=Re,e.STRING_ARABIC_NUMERALS=k,e.STRING_LOWERCASE_ALPHA=H,e.STRING_POOL=ae,e.STRING_UPPERCASE_ALPHA=z,e.UNIQUE_NUMBER_SAFE_LENGTH=18,e.URL_REGEX=De,e.add=Fe,e.addClass=function(e,t){_(t,(t=>e.classList.add(t)))},e.arrayEach=t,e.arrayEachAsync=async function(e,t,n=!1){if(n)for(let n=e.length-1;n>=0;n--){const r=e[n];if(!1===await t(r,n))break}else for(let n=0,r=e.length;n<r;n++){const r=e[n];if(!1===await t(r,n))break}},e.arrayInsertBefore=function(e,t,n){if(t===n||t+1===n)return;const[r]=e.splice(t,1),o=n<t?n:n-1;e.splice(o,0,r)},e.arrayLike=r,e.arrayRemove=function(e,n){const r=[],o=n;return t(e,((e,t)=>{o(e,t)&&r.push(t)})),r.forEach(((t,n)=>{e.splice(t-n,1)})),e},e.asyncMap=function(e,t,n=1/0){return new Promise(((r,o)=>{const i=e[Symbol.iterator](),s=Math.min(e.length,n),c=[];let a,l=0,u=0;const d=()=>{if(a)return o(a);const n=i.next();if(n.done)return void(l===e.length&&r(c));const s=u++;t(n.value,s,e).then((e=>{l++,c[s]=e,d()})).catch((e=>{a=e,d()}))};for(let e=0;e<s;e++)d()}))},e.calculateDate=function(e,t,n="-"){const r=new Date(e),o=new Date(r.getFullYear(),r.getMonth(),r.getDate()).getTime()+864e5*parseInt(String(t)),i=new Date(o);return i.getFullYear()+n+String(i.getMonth()+1).padStart(2,"0")+"-"+String(i.getDate()).padStart(2,"0")},e.calculateDateTime=function(e,t,n="-",r=":"){const o=new Date(e),i=n,s=r,c=new Date(o.getFullYear(),o.getMonth(),o.getDate(),o.getHours(),o.getMinutes(),o.getSeconds()).getTime()+864e5*parseInt(String(t)),a=new Date(c);return a.getFullYear()+i+String(a.getMonth()+1).padStart(2,"0")+i+String(a.getDate()).padStart(2,"0")+" "+String(a.getHours()).padStart(2,"0")+s+String(a.getMinutes()).padStart(2,"0")+s+String(a.getSeconds()).padStart(2,"0")},e.chooseLocalFile=function(e,t){const n=document.createElement("input");return n.setAttribute("id",String(Date.now())),n.setAttribute("type","file"),n.setAttribute("style","visibility:hidden"),n.setAttribute("accept",e),document.body.appendChild(n),n.click(),n.onchange=e=>{t(e.target.files),setTimeout((()=>document.body.removeChild(n)))},n},e.cloneDeep=function e(t,n=new WeakMap){if(null===t||"object"!=typeof t)return t;if(n.has(t))return n.get(t);if(t instanceof ArrayBuffer){const e=new ArrayBuffer(t.byteLength);return new Uint8Array(e).set(new Uint8Array(t)),n.set(t,e),e}if(ArrayBuffer.isView(t)){return new(0,t.constructor)(e(t.buffer,n),t.byteOffset,t.length)}if(t instanceof Date){const e=new Date(t.getTime());return n.set(t,e),e}if(t instanceof RegExp){const e=new RegExp(t.source,t.flags);return e.lastIndex=t.lastIndex,n.set(t,e),e}if(t instanceof Map){const r=new Map;return n.set(t,r),t.forEach(((t,o)=>{r.set(e(o,n),e(t,n))})),r}if(t instanceof Set){const r=new Set;return n.set(t,r),t.forEach((t=>{r.add(e(t,n))})),r}if(Array.isArray(t)){const r=new Array(t.length);n.set(t,r);for(let o=0,i=t.length;o<i;o++)o in t&&(r[o]=e(t[o],n));const o=Object.getOwnPropertyDescriptors(t);for(const t of Reflect.ownKeys(o))Object.defineProperty(r,t,{...o[t],value:e(o[t].value,n)});return r}const r=Object.create(Object.getPrototypeOf(t));n.set(t,r);const o=Object.getOwnPropertyDescriptors(t);for(const t of Reflect.ownKeys(o)){const i=o[t];"value"in i?i.value=e(i.value,n):(i.get&&(i.get=e(i.get,n)),i.set&&(i.set=e(i.set,n))),Object.defineProperty(r,t,i)}return r},e.compressImg=function e(t,n={mime:"image/jpeg",minFileSizeKB:50}){if(!(t instanceof File||t instanceof FileList))throw new Error(`${t} require be File or FileList`);if(!oe())throw new Error("Current runtime environment not support Canvas");const{quality:r,mime:o="image/jpeg",maxSize:i,minFileSizeKB:s=50}=d(n)?n:{};let a,l=r;if(r)l=r;else if(t instanceof File){const e=+parseInt((t.size/1024).toFixed(2));l=e<s?1:e<1024?.85:e<5120?.8:.75}return c(i)&&(a=i>=1200?i:1200),t instanceof FileList?Promise.all(Array.from(t).map((t=>e(t,{maxSize:a,mime:o,quality:l})))):t instanceof File?new Promise((e=>{const n=[...t.name.split(".").slice(0,-1),{"image/webp":"webp","image/jpeg":"jpg","image/png":"png"}[o]].join("."),r=+parseInt((t.size/1024).toFixed(2));if(r<s)e({file:t});else{const i=new FileReader;i.onload=({target:{result:i}})=>{const s=new Image;s.onload=()=>{const u=document.createElement("canvas"),d=u.getContext("2d"),f=s.width,p=s.height,{width:h,height:g}=function({sizeKB:e,maxSize:t,originWidth:n,originHeight:r}){let o=n,i=r;if(c(t)){const{width:e,height:s}=ie({maxWidth:t,maxHeight:t,originWidth:n,originHeight:r});o=e,i=s}else if(e<500){const e=1200,t=1200,{width:s,height:c}=ie({maxWidth:e,maxHeight:t,originWidth:n,originHeight:r});o=s,i=c}else if(e<5120){const e=1400,t=1400,{width:s,height:c}=ie({maxWidth:e,maxHeight:t,originWidth:n,originHeight:r});o=s,i=c}else if(e<10240){const e=1600,t=1600,{width:s,height:c}=ie({maxWidth:e,maxHeight:t,originWidth:n,originHeight:r});o=s,i=c}else if(10240<=e){const e=n>15e3?8192:n>1e4?4096:2048,t=r>15e3?8192:r>1e4?4096:2048,{width:s,height:c}=ie({maxWidth:e,maxHeight:t,originWidth:n,originHeight:r});o=s,i=c}return{width:o,height:i}}({sizeKB:r,maxSize:a,originWidth:f,originHeight:p});u.width=h,u.height=g,d.clearRect(0,0,h,g),d.drawImage(s,0,0,h,g);const m=u.toDataURL(o,l),y=atob(m.split(",")[1]);let b=y.length;const w=new Uint8Array(new ArrayBuffer(b));for(;b--;)w[b]=y.charCodeAt(b);const x=new File([w],n,{type:o});e({file:x,bufferArray:w,origin:t,beforeSrc:i,afterSrc:m,beforeKB:r,afterKB:Number((x.size/1024).toFixed(2))})},s.src=i},i.readAsDataURL(t)}})):Promise.resolve(null)},e.cookieDel=e=>y(e,"",-1),e.cookieGet=function(e){const{cookie:t}=document;if(!t)return"";const n=t.split(";");for(let t=0;t<n.length;t++){const r=n[t],[o,i=""]=r.split("=");if(o===e)return decodeURIComponent(i)}return""},e.cookieSet=y,e.copyText=function(e){const t=document.createElement("textarea");t.style.position="absolute",t.style.top="-9999px",t.style.left="-9999px",t.value=e,document.body.appendChild(t),t.focus({preventScroll:!0}),t.select();try{document.execCommand("copy"),t.blur(),document.body.removeChild(t)}catch(e){}},e.crossOriginDownload=function(e,t,n){const{successCode:r=200,successCallback:o,failCallback:s}=u(n)?{successCode:200,successCallback:void 0,failCallback:void 0}:n,c=new XMLHttpRequest;c.open("GET",e,!0),c.responseType="blob",c.onload=function(){if(c.status===r)re(c.response,t,o);else if(p(s)){const e=c.status,t=c.getResponseHeader("Content-Type");if(i(t)&&t.includes("application/json")){const t=new FileReader;t.onload=()=>{s({status:e,response:t.result})},t.readAsText(c.response)}else s(c)}},c.onerror=e=>{p(s)&&s({status:0,code:"ERROR_CONNECTION_REFUSED"})},c.send()},e.dateParse=S,e.dateToEnd=function(e){const t=A(e);return t.setDate(t.getDate()+1),S(t.getTime()-1)},e.dateToStart=A,e.debounce=(e,t)=>{let n,r=!1;const o=function(...o){r||(clearTimeout(n),n=setTimeout((()=>{e.call(this,...o)}),t))};return o.cancel=()=>{clearTimeout(n),r=!0},o},e.decodeFromBase64=function(e){const t=u(se("atob"))?Te(e):se("atob")(e),n=t.length,r=new Uint8Array(n);for(let e=0;e<n;e++)r[e]=t.charCodeAt(e);return u(se("TextDecoder"))?function(e){const t=String.fromCharCode.apply(null,e);return decodeURIComponent(t)}(r):new(se("TextDecoder"))("utf-8").decode(r)},e.divide=(e,t)=>{let n=0,r=0,o=0,i=0;return void 0!==e.toString().split(".")[1]&&(n=e.toString().split(".")[1].length),void 0!==t.toString().split(".")[1]&&(r=t.toString().split(".")[1].length),o=Number(e.toString().replace(".","")),i=Number(t.toString().replace(".","")),o/i*Math.pow(10,r-n)},e.downloadBlob=re,e.downloadData=function(e,t,n,r){if(n=n.replace(`.${t}`,"")+`.${t}`,"json"===t){re(new Blob([JSON.stringify(e,null,4)]),n)}else{if(!r||!r.length)throw new Error("未传入表头数据");if(!Array.isArray(e))throw new Error("data error! expected array!");const o=r.join(",")+"\n";let i="";e.forEach((e=>{i+=Object.values(e).join(",\t")+",\n"}));ne("data:"+{csv:"text/csv",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[t]+";charset=utf-8,\ufeff"+encodeURIComponent(o+i),n)}},e.downloadHref=ne,e.downloadURL=function(e,t){window.open(t?te(e,t):e)},e.encodeToBase64=function(e){const t=u(se("TextEncoder"))?function(e){const t=encodeURIComponent(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return n}(e):(new(se("TextEncoder"))).encode(e);let n="";const r=t.length;for(let e=0;e<r;e++)n+=String.fromCharCode(t[e]);return u(se("btoa"))?$e(n):se("btoa")(n)},e.escapeRegExp=We,e.executeInScope=function(e,t={}){const n=Object.keys(t),r=n.map((e=>t[e]));try{return new Function(...n,`return (() => { ${e} })()`)(...r)}catch(e){throw new Error(`代码执行失败: ${e.message}`)}},e.flatTree=function e(r,o=Ae){const{childField:i,keyField:s,pidField:c}=o;let a=[];return t(r,(t=>{const r={...t,[i]:[]};if(n(r,i)&&delete r[i],a.push(r),t[i]){const n=t[i].map((e=>({...e,[c]:t[s]||e.pid})));a=a.concat(e(n,o))}})),a},e.forEachDeep=function(e,t,n="children",r=!1){let o=!1;const i=(s,c,a=0)=>{if(r)for(let r=s.length-1;r>=0&&!o;r--){const l=t(s[r],r,s,e,c,a);if(!1===l){o=!0;break}!0!==l&&s[r]&&Array.isArray(s[r][n])&&i(s[r][n],s[r],a+1)}else for(let r=0,l=s.length;r<l&&!o;r++){const l=t(s[r],r,s,e,c,a);if(!1===l){o=!0;break}!0!==l&&s[r]&&Array.isArray(s[r][n])&&i(s[r][n],s[r],a+1)}};i(e,null)},e.formatDate=function(e,t="YYYY-MM-DD HH:mm:ss"){const n=S(e);let r,o=t;const i={"Y+":`${n.getFullYear()}`,"y+":`${n.getFullYear()}`,"M+":`${n.getMonth()+1}`,"D+":`${n.getDate()}`,"d+":`${n.getDate()}`,"H+":`${n.getHours()}`,"m+":`${n.getMinutes()}`,"s+":`${n.getSeconds()}`,"S+":`${n.getMilliseconds()}`,"w+":["周日","周一","周二","周三","周四","周五","周六"][n.getDay()]};for(const e in i)r=new RegExp("("+e+")").exec(o),r&&(o=o.replace(r[1],1===r[1].length?i[e]:i[e].padStart(r[1].length,"0")));return o},e.formatMoney=ge,e.formatNumber=ge,e.formatTree=function(e,n=Ae){const{keyField:r,childField:o,pidField:i}=n,s=[],c={};return t(e,(e=>{c[e[r]]=e})),t(e,(e=>{const t=c[e[i]];t?(t[o]||(t[o]=[])).push(e):s.push(e)})),s},e.fuzzySearchTree=function e(r,o,i=Ee){if(!n(o,"filter")&&(!n(o,"keyword")||m(o.keyword)))return r;const s=[];return t(r,(t=>{const r=t[i.childField]&&t[i.childField].length>0?e(t[i.childField]||[],o,i):[];((n(o,"filter")?o.filter(t):i.ignoreCase?t[i.nameField].toLowerCase().includes(o.keyword.toLowerCase()):t[i.nameField].includes(o.keyword))||r.length>0)&&(t[i.childField]?r.length>0?s.push({...t,[i.childField]:r}):i.removeEmptyChild?(t[i.childField]&&delete t[i.childField],s.push({...t})):s.push({...t,[i.childField]:[]}):(t[i.childField]&&delete t[i.childField],s.push({...t})))})),s},e.genCanvasWM=function e(t="请勿外传",n){const{rootContainer:r=document.body,width:o="300px",height:s="150px",textAlign:c="center",textBaseline:a="middle",font:l="20px PingFangSC-Medium,PingFang SC",fillStyle:d="rgba(189, 177, 167, .3)",rotate:f=-20,zIndex:p=2147483647,watermarkId:h="__wm"}=u(n)?{}:n,g=i(r)?document.querySelector(r):r;if(!g)throw new Error(`${r} is not valid Html Element or element selector`);const m=document.createElement("canvas");m.setAttribute("width",o),m.setAttribute("height",s);const y=m.getContext("2d");y.textAlign=c,y.textBaseline=a,y.font=l,y.fillStyle=d,y.rotate(Math.PI/180*f),y.fillText(t,parseFloat(o)/4,parseFloat(s)/2);const b=m.toDataURL(),w=document.querySelector(`#${h}`),x=w||document.createElement("div"),S=`opacity: 1 !important; display: block !important; visibility: visible !important; position:absolute; left:0; top:0; width:100%; height:100%; z-index:${p}; pointer-events:none; background-repeat:repeat; background-image:url('${b}')`;x.setAttribute("style",S),x.setAttribute("id",h),x.classList.add("nav-height"),w||(g.style.position="relative",g.appendChild(x));const A=window.MutationObserver||window.WebKitMutationObserver;if(A){let r=new A((function(){const o=document.querySelector(`#${h}`);if(o){const{opacity:i,zIndex:s,display:c,visibility:a}=(e=>{const t=getComputedStyle(e);return{opacity:t.getPropertyValue("opacity"),zIndex:t.getPropertyValue("z-index"),display:t.getPropertyValue("display"),visibility:t.getPropertyValue("visibility")}})(o);(o&&o.getAttribute("style")!==S||!o||"1"!==i||"2147483647"!==s||"block"!==c||"visible"!==a)&&(r.disconnect(),r=null,g.removeChild(o),e(t,n))}else r.disconnect(),r=null,e(t,n)}));r.observe(g,{attributes:!0,subtree:!0,childList:!0})}},e.getComputedCssVal=function(e,t,n=!0){const r=getComputedStyle(e).getPropertyValue(t)??"";return n?Number(r.replace(/([0-9]*)(.*)/g,"$1")):r},e.getGlobal=se,e.getStrWidthPx=V,e.getStyle=function(e,t){return getComputedStyle(e).getPropertyValue(t)},e.hasClass=function(e,t){if(-1!==t.indexOf(" "))throw new Error("className should not contain space.");return e.classList.contains(t)},e.humanFileSize=function(e,t={decimals:0,si:!1,separator:" "}){const{decimals:n=0,si:r=!1,separator:o=" ",baseUnit:i,maxUnit:s}=t;let c=r?["B","kB","MB","GB","TB","PB","EB","ZB","YB"]:["Byte","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"];if(!u(i)){const e=c.findIndex((e=>e===i));-1!==e&&(c=c.slice(e))}if(!u(s)){const e=c.findIndex((e=>e===s));-1!==e&&c.splice(e+1)}return he(e,c,{ratio:r?1e3:1024,decimals:n,separator:o})},e.isArray=f,e.isBigInt=e=>"bigint"==typeof e,e.isBoolean=s,e.isDate=g,e.isDigit=e=>He.test(e),e.isEmail=e=>Oe.test(e),e.isEmpty=m,e.isEqual=function(e,t){return _e(e,t)},e.isError=e=>"Error"===o(e),e.isFloat=ke,e.isFunction=p,e.isIdNo=e=>{if(!Ie.test(e))return!1;const t=Number(e.slice(6,10)),n=Number(e.slice(10,12)),r=Number(e.slice(12,14)),o=new Date(t,n-1,r);if(!(o.getFullYear()===t&&o.getMonth()+1===n&&o.getDate()===r))return!1;const i=[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];let s=0;for(let t=0;t<17;t++)s+=Number(e.slice(t,t+1))*i[t];return["1","0","X","9","8","7","6","5","4","3","2"][s%11]===e.slice(-1)},e.isInteger=Be,e.isIpV4=e=>Me.test(e),e.isIpV6=e=>Ne.test(e),e.isJsonString=function(e){try{const t=JSON.parse(e);return"object"==typeof t&&null!==t&&t}catch(e){return!1}},e.isNaN=h,e.isNull=l,e.isNullOrUnDef=u,e.isNullish=u,e.isNumber=c,e.isNumerical=e=>Be(e)||ke(e),e.isObject=d,e.isPhone=e=>Re.test(e),e.isPlainObject=L,e.isPrimitive=e=>null===e||"object"!=typeof e,e.isRegExp=e=>"RegExp"===o(e),e.isString=i,e.isSymbol=e=>"symbol"==typeof e,e.isUndefined=a,e.isUrl=(e,t=!1)=>(t?De:Le).test(e),e.isValidDate=b,e.mapDeep=function(e,t,n="children",r=!1){let o=!1;const i=[],s=(i,c,a,l=0)=>{if(r)for(let r=i.length-1;r>=0&&!o;r--){const u=t(i[r],r,i,e,c,l);if(!1===u){o=!0;break}!0!==u&&(a.push(N(u,[n])),i[r]&&Array.isArray(i[r][n])?(a[a.length-1][n]=[],s(i[r][n],i[r],a[a.length-1][n],l+1)):delete u[n])}else for(let r=0;r<i.length&&!o;r++){const u=t(i[r],r,i,e,c,l);if(!1===u){o=!0;break}!0!==u&&(a.push(N(u,[n])),i[r]&&Array.isArray(i[r][n])?(a[a.length-1][n]=[],s(i[r][n],i[r],a[a.length-1][n],l+1)):delete u[n])}};return s(e,null,i),i},e.multiply=ve,e.numberAbbr=he,e.numberToHex=pe,e.objectAssign=B,e.objectEach=M,e.objectEachAsync=async function(e,t){for(const r in e)if(n(e,r)&&!1===await t(e[r],r))break},e.objectFill=function(e,t,n){const r=n||((e,t,n)=>void 0===e[n]);return M(t,((n,o)=>{r(e,t,o)&&(e[o]=n)})),e},e.objectGet=function(e,t,r=!1){const o=(t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"")).split(".");let i=e,s=0;for(let e=o.length;s<e-1;++s){const e=o[s];if(c(Number(e))&&Array.isArray(i))i=i[e];else{if(!d(i)||!n(i,e)){if(i=void 0,r)throw new Error("[Object] objectGet path 路径不正确");break}i=i[e]}}return{p:i,k:i?o[s]:void 0,v:i?i[o[s]]:void 0}},e.objectHas=n,e.objectMap=function(e,t){const r={};for(const o in e)n(e,o)&&(r[o]=t(e[o],o));return r},e.objectMerge=B,e.objectOmit=N,e.objectPick=function(e,t){const n={};return M(e,((e,r)=>{t.includes(r)&&(n[r]=e)})),n},e.once=e=>{let t,n=!1;return function(...r){return n||(n=!0,t=e.call(this,...r)),t}},e.parseQueryParams=function(e=location.search){const t={};return Array.from(e.matchAll(/[&?]?([^=&]+)=?([^=&]*)/g)).forEach(((e,n)=>{t[e[1]]?"string"==typeof t[e[1]]?t[e[1]]=[t[e[1]],e[2]]:t[e[1]].push(e[2]):t[e[1]]=e[2]})),t},e.parseVarFromString=function(e,t="{",n="}"){return Array.from(e.matchAll(qe(t,n))).map((e=>u(e)?void 0:e[1]))},e.pathJoin=K,e.pathNormalize=Y,e.qsParse=X,e.qsStringify=Z,e.randomNumber=ce,e.randomString=(e,t)=>{let n=0,r=ae;i(t)?(n=e,r=t):c(e)?n=e:i(e)&&(r=e);let o=Math.max(n,1),s="";const a=r.length-1;if(a<2)throw new Error("字符串池长度不能少于 2");for(;o--;){s+=r[ce(0,a)]}return s},e.randomUuid=function(){if("undefined"==typeof URL||!URL.createObjectURL||"undefined"==typeof Blob){const e="0123456789abcdef",t="xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx";let n="";for(let r=0;r<t.length;r++){const o=ce(0,15);n+="-"==t[r]||"4"==t[r]?t[r]:e[o]}return n}return/[^/]+$/.exec(URL.createObjectURL(new Blob).slice())[0]},e.removeClass=function(e,t){_(t,(t=>e.classList.remove(t)))},e.replaceVarFromString=function(e,t,r="{",o="}"){return e.replace(new RegExp(qe(r,o)),(function(e,r){return n(t,r)?t[r]:e}))},e.searchTreeById=function(e,t,n){const{children:r="children",id:o="id"}=n||{},i=(e,t,n)=>e.reduce(((e,s)=>{const c=s[r];return[...e,t?{...s,parentId:t,parent:n}:s,...c&&c.length?i(c,s[o],s):[]]}),[]);return(e=>{let n=e.find((e=>e[o]===t));const{parent:r,parentId:i,...s}=n;let c=[t],a=[s];for(;n&&n.parentId;)c=[n.parentId,...c],a=[n.parent,...a],n=e.find((e=>e[o]===n.parentId));return[c,a]})(i(e))},e.setGlobal=function(e,t){if("undefined"!=typeof globalThis)globalThis[e]=t;else if("undefined"!=typeof window)window[e]=t;else if("undefined"!=typeof global)global[e]=t;else{if("undefined"==typeof self)throw new SyntaxError("当前环境下无法设置全局属性");self[e]=t}},e.setStyle=G,e.smoothScroll=function(e){return new Promise((n=>{const r={el:document,to:0,duration:567,easing:"ease"},{el:o,to:i,duration:s,easing:c}=B(r,e),a=document.documentElement,l=document.body,u=o===window||o===document||o===a||o===l?[a,l]:[o];let d;const p=(()=>{let e=0;return t(u,(t=>{if("scrollTop"in t)return e=t.scrollTop,!1})),e})(),h=i-p,g=function(e){let t;if(f(e))t=I(...e);else{const n=D[e];if(!n)throw new Error(`${e} 缓冲函数未定义`);t=I(...n)}return e=>t(Math.max(0,Math.min(e,1)))}(c),m=()=>{const e=performance.now(),t=(d?e-d:0)/s,r=g(t);var o;d||(d=e),o=p+h*r,u.forEach((e=>{"scrollTop"in e&&(e.scrollTop=o)})),t>=1?n():requestAnimationFrame(m)};m()}))},e.stringAssign=(e,t)=>e.replace(q,((e,n)=>((e,t)=>{try{return new Function(`with(arguments[0]){if(arguments[0].${e} === undefined)throw "";return String(arguments[0].${e})}`)(t)}catch(t){throw new SyntaxError(`无法执行表达式:${e}`)}})(n,t))),e.stringCamelCase=function(e,t){let n=e;return t&&(n=e.replace(/^./,(e=>e.toUpperCase()))),n.replace(/[\s_-](.)/g,((e,t)=>t.toUpperCase()))},e.stringEscapeHtml=e=>{const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"};return e.replace(/[&<>"]/g,(e=>t[e]))},e.stringFill=(e,t=" ")=>new Array(e).fill(t).join(""),e.stringFormat=function(e,...t){let n=0;return[e.replace(W,(e=>{const r=t[n++];switch(e){case"%%":return n--,"%";default:case"%s":return String(r);case"%d":return String(Number(r));case"%o":return JSON.stringify(r)}})),...t.splice(n).map(String)].join(" ")},e.stringKebabCase=U,e.strip=function(e,t=15){return+parseFloat(Number(e).toPrecision(t))},e.subtract=(e,t)=>Fe(e,-t),e.supportCanvas=oe,e.throttle=(e,t,n)=>{let r,o=!1,i=0;const s=function(...s){if(o)return;const c=Date.now(),a=()=>{i=c,e.call(this,...s)};if(0===i)return n?a():void(i=c);i+t-c>0?(clearTimeout(r),r=setTimeout((()=>a()),t)):a()};return s.cancel=()=>{clearTimeout(r),o=!0},s},e.tooltipEvent=Se,e.typeIs=o,e.uniqueNumber=be,e.uniqueString=(e,t)=>{let n=0,r=le;i(t)?(n=e,r=t):c(e)?n=e:i(e)&&(r=e);let o=pe(be(),r),s=n-o.length;if(s<=0)return o;for(;s--;)o+=we(r);return o},e.uniqueSymbol=ze,e.urlDelParams=(e,t)=>{const n=Q(e);return t.forEach((e=>delete n.searchParams[e])),ee(n)},e.urlParse=Q,e.urlSetParams=te,e.urlStringify=ee,e.wait=function(e=1){return new Promise((t=>setTimeout(t,e)))},e.weAtob=Te,e.weBtoa=$e}));