sculp-js 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +22 -1
  3. package/lib/cjs/array.js +32 -55
  4. package/lib/cjs/async.js +3 -3
  5. package/lib/cjs/clipboard.js +3 -3
  6. package/lib/cjs/cookie.js +5 -5
  7. package/lib/cjs/date.js +142 -24
  8. package/lib/cjs/dom.js +24 -10
  9. package/lib/cjs/download.js +9 -9
  10. package/lib/cjs/easing.js +1 -1
  11. package/lib/cjs/file.js +5 -4
  12. package/lib/cjs/func.js +160 -0
  13. package/lib/cjs/index.js +28 -2
  14. package/lib/cjs/number.js +82 -0
  15. package/lib/cjs/object.js +13 -11
  16. package/lib/cjs/path.js +1 -1
  17. package/lib/cjs/qs.js +5 -5
  18. package/lib/cjs/random.js +72 -0
  19. package/lib/cjs/string.js +40 -7
  20. package/lib/cjs/type.js +12 -2
  21. package/lib/cjs/unique.js +83 -0
  22. package/lib/cjs/url.js +1 -1
  23. package/lib/cjs/watermark.js +8 -9
  24. package/lib/es/array.js +33 -55
  25. package/lib/es/async.js +3 -3
  26. package/lib/es/clipboard.js +3 -3
  27. package/lib/es/cookie.js +5 -5
  28. package/lib/es/date.js +139 -25
  29. package/lib/es/dom.js +24 -11
  30. package/lib/es/download.js +9 -9
  31. package/lib/es/easing.js +1 -1
  32. package/lib/es/file.js +5 -4
  33. package/lib/es/func.js +154 -0
  34. package/lib/es/index.js +10 -6
  35. package/lib/es/number.js +77 -0
  36. package/lib/es/object.js +12 -10
  37. package/lib/es/path.js +1 -1
  38. package/lib/es/qs.js +5 -5
  39. package/lib/es/random.js +67 -0
  40. package/lib/es/string.js +40 -8
  41. package/lib/es/type.js +12 -3
  42. package/lib/es/unique.js +79 -0
  43. package/lib/es/url.js +1 -1
  44. package/lib/es/watermark.js +8 -9
  45. package/lib/index.d.ts +254 -80
  46. package/lib/umd/index.js +637 -132
  47. package/package.json +36 -12
package/lib/umd/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v0.0.1
2
+ * sculp-js v1.0.0
3
3
  * (c) 2023-2023 chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -10,6 +10,11 @@
10
10
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.sculpJs = {}, global.bezier));
11
11
  })(this, (function (exports, bezier) { 'use strict';
12
12
 
13
+ /**
14
+ * 判断任意值的数据类型
15
+ * @param {unknown} any
16
+ * @returns {string}
17
+ */
13
18
  const typeIs = (any) => Object.prototype.toString.call(any).slice(8, -1);
14
19
  // 基本数据类型判断
15
20
  const isString = (any) => typeof any === 'string';
@@ -23,7 +28,11 @@
23
28
  // 复合数据类型判断
24
29
  const isObject = (any) => typeIs(any) === 'Object';
25
30
  const isArray = (any) => Array.isArray(any);
26
- // eslint-disable-next-line @typescript-eslint/ban-types
31
+ /**
32
+ * 判断是否为函数
33
+ * @param {unknown} any
34
+ * @returns {boolean}
35
+ */
27
36
  const isFunction = (any) => typeof any === 'function';
28
37
  // 对象类型判断
29
38
  const isNaN = (any) => Number.isNaN(any);
@@ -52,20 +61,22 @@
52
61
  * @param {string} key
53
62
  * @returns {boolean}
54
63
  */
55
- const objectHas = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
64
+ function objectHas(obj, key) {
65
+ return Object.prototype.hasOwnProperty.call(obj, key);
66
+ }
56
67
  /**
57
68
  * 遍历对象,返回 false 中断遍历
58
69
  * @param {O} obj
59
70
  * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
60
71
  */
61
- const objectEach = (obj, iterator) => {
72
+ function objectEach(obj, iterator) {
62
73
  for (const key in obj) {
63
74
  if (!objectHas(obj, key))
64
75
  continue;
65
76
  if (iterator(obj[key], key) === false)
66
77
  break;
67
78
  }
68
- };
79
+ }
69
80
  /**
70
81
  * 异步遍历对象,返回 false 中断遍历
71
82
  * @param {O} obj
@@ -171,7 +182,7 @@
171
182
  * @param {ObjectAssignItem | undefined} targets
172
183
  * @returns {R}
173
184
  */
174
- const objectAssign = (source, ...targets) => {
185
+ function objectAssign(source, ...targets) {
175
186
  const map = new Map();
176
187
  for (let i = 0; i < targets.length; i++) {
177
188
  const target = targets[i];
@@ -181,7 +192,7 @@
181
192
  }
182
193
  map.clear();
183
194
  return source;
184
- };
195
+ }
185
196
  /**
186
197
  * 对象填充
187
198
  * @param {Partial<R>} source
@@ -189,7 +200,7 @@
189
200
  * @param {(s: Partial<R>, t: Partial<R>, key: keyof R) => boolean} fillable
190
201
  * @returns {R}
191
202
  */
192
- const objectFill = (source, target, fillable) => {
203
+ function objectFill(source, target, fillable) {
193
204
  const _fillable = fillable || ((source, target, key) => source[key] === undefined);
194
205
  objectEach(target, (val, key) => {
195
206
  if (_fillable(source, target, key)) {
@@ -197,7 +208,7 @@
197
208
  }
198
209
  });
199
210
  return source;
200
- };
211
+ }
201
212
  function objectGet(obj, path, strict = false) {
202
213
  path = path.replace(/\[(\w+)\]/g, '.$1');
203
214
  path = path.replace(/^\./, '');
@@ -230,7 +241,7 @@
230
241
  * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
231
242
  * @param {AnyObject | AnyArray} obj
232
243
  * @param {WeakMap} map
233
- * @return {AnyObject | AnyArray}
244
+ * @returns {AnyObject | AnyArray}
234
245
  */
235
246
  function cloneDeep(obj, map = new WeakMap()) {
236
247
  if (obj instanceof Date)
@@ -252,10 +263,11 @@
252
263
 
253
264
  /**
254
265
  * 判断一个对象是否为类数组
266
+ *
255
267
  * @param any
256
268
  * @returns {boolean}
257
269
  */
258
- const arrayLike = (any) => {
270
+ function arrayLike(any) {
259
271
  if (isArray(any))
260
272
  return true;
261
273
  if (isString(any))
@@ -263,34 +275,36 @@
263
275
  if (!isObject(any))
264
276
  return false;
265
277
  return objectHas(any, 'length');
266
- };
278
+ }
267
279
  /**
268
280
  * 遍历数组,返回 false 中断遍历
281
+ *
269
282
  * @param {ArrayLike<V>} array
270
283
  * @param {(val: V, idx: number) => any} iterator
271
284
  * @param reverse {boolean} 是否倒序
285
+ * @returns {*}
272
286
  */
273
- const arrayEach = (array, iterator, reverse = false) => {
287
+ function arrayEach(array, iterator, reverse = false) {
274
288
  if (reverse) {
275
289
  for (let idx = array.length - 1; idx >= 0; idx--) {
276
290
  const val = array[idx];
277
- if (iterator(val, idx) === false)
291
+ if (iterator(val, idx, array) === false)
278
292
  break;
279
293
  }
280
294
  }
281
295
  else {
282
296
  for (let idx = 0; idx < array.length; idx++) {
283
297
  const val = array[idx];
284
- if (iterator(val, idx) === false)
298
+ if (iterator(val, idx, array) === false)
285
299
  break;
286
300
  }
287
301
  }
288
- };
302
+ }
289
303
  /**
290
304
  * 异步遍历数组,返回 false 中断遍历
291
- * @param {ArrayLike<V>} array
292
- * @param {(val: V, idx: number) => Promise<any>} iterator
293
- * @param {boolean} reverse
305
+ * @param {ArrayLike<V>} array 数组
306
+ * @param {(val: V, idx: number) => Promise<any>} iterator 支持Promise类型的回调函数
307
+ * @param {boolean} reverse 是否反向遍历
294
308
  */
295
309
  async function arrayEachAsync(array, iterator, reverse = false) {
296
310
  if (reverse) {
@@ -313,15 +327,16 @@
313
327
  * @param {AnyArray} array
314
328
  * @param {number} start
315
329
  * @param {number} to
330
+ * @returns {*}
316
331
  */
317
- const arrayInsertBefore = (array, start, to) => {
332
+ function arrayInsertBefore(array, start, to) {
318
333
  if (start === to || start + 1 === to)
319
334
  return;
320
335
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
321
336
  const [source] = array.splice(start, 1);
322
337
  const insertIndex = to < start ? to : to - 1;
323
338
  array.splice(insertIndex, 0, source);
324
- };
339
+ }
325
340
  /**
326
341
  * 数组删除指定项目
327
342
  * @param {V[]} array
@@ -342,17 +357,18 @@
342
357
  }
343
358
  /**
344
359
  * 自定义深度优先遍历函数(支持continue和break操作)
345
- * @param {array} deepList
346
- * @param {function} iterator
347
- * @param {array} children
348
- * @param {boolean} isReverse 是否反向遍历
360
+ * @param {ArrayLike<V>} tree 树形数据
361
+ * @param {Function} iterator 迭代函数
362
+ * @param {string} children 定制子元素的key
363
+ * @param {boolean} isReverse 是否反向遍历
364
+ * @returns {*}
349
365
  */
350
- const deepTraversal = (deepList, iterator, children = 'children', isReverse = false) => {
366
+ function deepTraversal(tree, iterator, children = 'children', isReverse = false) {
351
367
  let level = 0;
352
368
  const walk = (arr, parent) => {
353
369
  if (isReverse) {
354
370
  for (let i = arr.length - 1; i >= 0; i--) {
355
- const re = iterator(arr[i], i, deepList, parent, level);
371
+ const re = iterator(arr[i], i, tree, parent, level);
356
372
  if (re === 'break') {
357
373
  break;
358
374
  }
@@ -369,7 +385,7 @@
369
385
  }
370
386
  else {
371
387
  for (let i = 0; i < arr.length; i++) {
372
- const re = iterator(arr[i], i, deepList, parent, level);
388
+ const re = iterator(arr[i], i, tree, parent, level);
373
389
  if (re === 'break') {
374
390
  break;
375
391
  }
@@ -385,14 +401,15 @@
385
401
  }
386
402
  }
387
403
  };
388
- walk(deepList, null);
389
- };
404
+ walk(tree, null);
405
+ }
390
406
  /**
391
407
  * 在树中找到 id 为某个值的节点,并返回上游的所有父级节点
392
- * @param {ArrayLike<T>} tree
393
- * @param {IdLike} nodeId
394
- * @param {ITreeConf} config
395
- * @return {[IdLike[], ITreeItem<V>[]]}
408
+ *
409
+ * @param {ArrayLike<T>} tree - 树形数据
410
+ * @param {IdLike} nodeId - 元素ID
411
+ * @param {ITreeConf} config - 迭代配置项
412
+ * @returns {[IdLike[], ITreeItem<V>[]]} - 由parentId...childId, parentObject-childObject组成的二维数组
396
413
  */
397
414
  function getTreeIds(tree, nodeId, config) {
398
415
  const { children = 'children', id = 'id' } = config || {};
@@ -413,40 +430,12 @@
413
430
  while (child && child.parentId) {
414
431
  ids = [child.parentId, ...ids];
415
432
  nodes = [child.parent, ...nodes];
416
- child = flatArray.find(_ => _[id] === child.parentId);
433
+ child = flatArray.find(_ => _[id] === child.parentId); // eslint-disable-line
417
434
  }
418
435
  return [ids, nodes];
419
436
  };
420
437
  return getIds(toFlatArray(tree));
421
438
  }
422
- /**
423
- * 异步ForEach函数
424
- * @param {array} array
425
- * @param {asyncFuntion} callback
426
- * // asyncForEach 使用范例如下
427
- // const start = async () => {
428
- // await asyncForEach(result, async (item) => {
429
- // await request(item);
430
- // count++;
431
- // });
432
-
433
- // console.log('发送次数', count);
434
- // }
435
-
436
- // for await...of 使用范例如下
437
- // const loadImages = async (images) => {
438
- // for await (const item of images) {
439
- // await request(item);
440
- // count++;
441
- // }
442
- // }
443
- * @return {*}
444
- */
445
- async function asyncForEach(array, callback) {
446
- for (let index = 0, len = array.length; index < len; index++) {
447
- await callback(array[index], index, array);
448
- }
449
- }
450
439
 
451
440
  // @ref https://cubic-bezier.com/
452
441
  const easingDefines = {
@@ -482,24 +471,24 @@
482
471
  * @param {boolean} [bigger] 是否大写第一个字母
483
472
  * @returns {string}
484
473
  */
485
- const stringCamelCase = (string, bigger) => {
474
+ function stringCamelCase(string, bigger) {
486
475
  let string2 = string;
487
476
  if (bigger) {
488
477
  string2 = string.replace(/^./, origin => origin.toUpperCase());
489
478
  }
490
479
  const HUMP_RE = /[\s_-](.)/g;
491
480
  return string2.replace(HUMP_RE, (orign, char) => char.toUpperCase());
492
- };
481
+ }
493
482
  /**
494
483
  * 将字符串转换为连字格式
495
484
  * @param {string} string
496
485
  * @param {string} [separator] 分隔符,默认是"-"(短横线)
497
486
  * @returns {string}
498
487
  */
499
- const stringKebabCase = (string, separator = '-') => {
488
+ function stringKebabCase(string, separator = '-') {
500
489
  const string2 = string.replace(/^./, origin => origin.toLowerCase());
501
490
  return string2.replace(/[A-Z]/g, origin => `${separator}${origin.toLowerCase()}`);
502
- };
491
+ }
503
492
  const STRING_ARABIC_NUMERALS = '0123456789';
504
493
  const STRING_LOWERCASE_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
505
494
  const STRING_UPPERCASE_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
@@ -515,7 +504,7 @@
515
504
  * @param args
516
505
  * @returns {string}
517
506
  */
518
- const stringFormat = (string, ...args) => {
507
+ function stringFormat(string, ...args) {
519
508
  let index = 0;
520
509
  const result = string.replace(placeholderRE, (origin) => {
521
510
  const arg = args[index++];
@@ -533,7 +522,7 @@
533
522
  }
534
523
  });
535
524
  return [result, ...args.splice(index).map(String)].join(' ');
536
- };
525
+ }
537
526
  const ev = (expression, data) => {
538
527
  try {
539
528
  // eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-return
@@ -592,6 +581,36 @@
592
581
  * @returns {string}
593
582
  */
594
583
  const stringFill = (length, value = ' ') => new Array(length).fill(value).join('');
584
+ /**
585
+ * 字符串的像素宽度
586
+ * @param {string} str 目标字符串
587
+ * @param {number} fontSize 字符串字体大小
588
+ * @param {boolean} isRemoveDom 计算后是否移除中间dom元素
589
+ * @returns {*}
590
+ */
591
+ function getStrWidthPx(str, fontSize = 14, isRemoveDom = false) {
592
+ let strWidth = 0;
593
+ console.assert(isString(str), `${str} 不是有效的字符串`);
594
+ if (isString(str) && str.length > 0) {
595
+ let getEle = document.querySelector('#getStrWidth1494304949567');
596
+ if (!getEle) {
597
+ const _ele = document.createElement('span');
598
+ _ele.id = 'getStrWidth1494304949567';
599
+ _ele.style.fontSize = fontSize + 'px';
600
+ _ele.style.whiteSpace = 'nowrap';
601
+ _ele.style.visibility = 'hidden';
602
+ _ele.textContent = str;
603
+ document.body.appendChild(_ele);
604
+ getEle = _ele;
605
+ }
606
+ getEle.textContent = str;
607
+ strWidth = getEle.offsetWidth;
608
+ if (isRemoveDom) {
609
+ document.body.appendChild(getEle);
610
+ }
611
+ }
612
+ return strWidth;
613
+ }
595
614
 
596
615
  /**
597
616
  * 判断元素是否包含某个样式名
@@ -599,11 +618,11 @@
599
618
  * @param {string} className
600
619
  * @returns {boolean}
601
620
  */
602
- const hasClass = (el, className) => {
621
+ function hasClass(el, className) {
603
622
  if (className.indexOf(' ') !== -1)
604
623
  throw new Error('className should not contain space.');
605
624
  return el.classList.contains(className);
606
- };
625
+ }
607
626
  const eachClassName = (classNames, func) => {
608
627
  const classNameList = classNames.split(/\s+/g);
609
628
  classNameList.forEach(func);
@@ -613,17 +632,17 @@
613
632
  * @param {HTMLElement} el
614
633
  * @param {string} classNames
615
634
  */
616
- const addClass = (el, classNames) => {
635
+ function addClass(el, classNames) {
617
636
  eachClassName(classNames, className => el.classList.add(className));
618
- };
637
+ }
619
638
  /**
620
639
  * 给元素移除样式名
621
640
  * @param {HTMLElement} el
622
641
  * @param {string} classNames
623
642
  */
624
- const removeClass = (el, classNames) => {
643
+ function removeClass(el, classNames) {
625
644
  eachClassName(classNames, className => el.classList.remove(className));
626
- };
645
+ }
627
646
  /**
628
647
  * 设置元素样式
629
648
  * @param {HTMLElement} el
@@ -642,12 +661,14 @@
642
661
  };
643
662
  /**
644
663
  * 获取元素样式
645
- * @param {HTMLElement} el
664
+ * @param {HTMLElement} el 元素
646
665
  * @param {string} key
647
666
  * @returns {string}
648
667
  */
649
- const getStyle = (el, key) => getComputedStyle(el).getPropertyValue(key);
650
- async function smoothScroll(options) {
668
+ function getStyle(el, key) {
669
+ return getComputedStyle(el).getPropertyValue(key);
670
+ }
671
+ function smoothScroll(options) {
651
672
  return new Promise(resolve => {
652
673
  const defaults = {
653
674
  el: document,
@@ -722,6 +743,17 @@
722
743
  domReadyCallbacks.push(callback);
723
744
  }
724
745
  }
746
+ /**
747
+ * 获取元素样式属性的计算值
748
+ * @param {HTMLElement} el
749
+ * @param {string} property
750
+ * @param {boolean} reNumber
751
+ * @returns {string|number}
752
+ */
753
+ function getComputedCssVal(el, property, reNumber = true) {
754
+ const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
755
+ return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
756
+ }
725
757
 
726
758
  const textEl = document.createElement('textarea');
727
759
  setStyle(textEl, {
@@ -735,7 +767,7 @@
735
767
  * 复制文本
736
768
  * @param {string} text
737
769
  */
738
- const copyText = (text) => {
770
+ function copyText(text) {
739
771
  textEl.value = text;
740
772
  textEl.focus({ preventScroll: true });
741
773
  textEl.select();
@@ -746,14 +778,14 @@
746
778
  catch (err) {
747
779
  // ignore
748
780
  }
749
- };
781
+ }
750
782
 
751
783
  /**
752
784
  * 获取cookie
753
785
  * @param {string} name
754
786
  * @returns {string}
755
787
  */
756
- const cookieGet = (name) => {
788
+ function cookieGet(name) {
757
789
  const { cookie } = document;
758
790
  if (!cookie)
759
791
  return '';
@@ -765,14 +797,14 @@
765
797
  return decodeURIComponent(val);
766
798
  }
767
799
  return '';
768
- };
800
+ }
769
801
  /**
770
802
  * 设置 cookie
771
803
  * @param {string} name
772
804
  * @param {string} value
773
805
  * @param {number | Date} [maxAge]
774
806
  */
775
- const cookieSet = (name, value, maxAge) => {
807
+ function cookieSet(name, value, maxAge) {
776
808
  const metas = [];
777
809
  const EXPIRES = 'expires';
778
810
  metas.push([name, encodeURIComponent(value)]);
@@ -791,13 +823,66 @@
791
823
  return `${key}=${val}`;
792
824
  })
793
825
  .join(';');
794
- };
826
+ }
795
827
  /**
796
828
  * 删除单个 cookie
797
829
  * @param name cookie 名称
798
830
  */
799
831
  const cookieDel = (name) => cookieSet(name, '', -1);
800
832
 
833
+ const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
834
+ /* istanbul ignore next */
835
+ const guessDateSeparator = (value) => {
836
+ if (!isString(value))
837
+ return;
838
+ const value2 = value.replace(/-/g, '/');
839
+ return new Date(value2);
840
+ };
841
+ /* istanbul ignore next */
842
+ const guessDateTimezone = (value) => {
843
+ if (!isString(value))
844
+ return;
845
+ const re = /([+-])(\d\d)(\d\d)$/;
846
+ const matches = re.exec(value);
847
+ if (!matches)
848
+ return;
849
+ const value2 = value.replace(re, 'Z');
850
+ const d = new Date(value2);
851
+ if (!isValidDate(d))
852
+ return;
853
+ const [, flag, hours, minutes] = matches;
854
+ const hours2 = parseInt(hours, 10);
855
+ const minutes2 = parseInt(minutes, 10);
856
+ const offset = (a, b) => (flag === '+' ? a - b : a + b);
857
+ d.setHours(offset(d.getHours(), hours2));
858
+ d.setMinutes(offset(d.getMinutes(), minutes2));
859
+ return d;
860
+ };
861
+ /**
862
+ * 解析为Date对象
863
+ * @param {DateValue} value - 可以是数值、字符串或 Date 对象
864
+ * @returns {Date} - 转换后的目标Date
865
+ */
866
+ function dateParse(value) {
867
+ const d1 = new Date(value);
868
+ if (isValidDate(d1))
869
+ return d1;
870
+ // safari 浏览器的日期解析有问题
871
+ // new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
872
+ /* istanbul ignore next */
873
+ const d2 = guessDateSeparator(value);
874
+ /* istanbul ignore next */
875
+ if (isValidDate(d2))
876
+ return d2;
877
+ // safari 浏览器的日期解析有问题
878
+ // new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
879
+ /* istanbul ignore next */
880
+ const d3 = guessDateTimezone(value);
881
+ /* istanbul ignore next */
882
+ if (isValidDate(d3))
883
+ return d3;
884
+ throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
885
+ }
801
886
  /**
802
887
  * 格式化为日期对象(带自定义格式化模板)
803
888
  * @param {DateValue} value 可以是数值、字符串或 Date 对象
@@ -812,10 +897,69 @@
812
897
  * - mm:分
813
898
  * - ss:秒
814
899
  * - SSS:毫秒
815
- * - ww: 周
816
900
  * @returns {string}
817
901
  */
818
- const formatDate = (date = new Date(), format = 'YYYY-MM-DD HH:mm:ss') => {
902
+ // export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
903
+ // const date = dateParse(value);
904
+ // let fmt = format;
905
+ // let ret;
906
+ // const opt: DateObj = {
907
+ // 'Y+': `${date.getFullYear()}`, // 年
908
+ // 'y+': `${date.getFullYear()}`, // 年
909
+ // 'M+': `${date.getMonth() + 1}`, // 月
910
+ // 'D+': `${date.getDate()}`, // 日
911
+ // 'd+': `${date.getDate()}`, // 日
912
+ // 'H+': `${date.getHours()}`, // 时
913
+ // 'm+': `${date.getMinutes()}`, // 分
914
+ // 's+': `${date.getSeconds()}`, // 秒
915
+ // 'S+': `${date.getMilliseconds()}` // 豪秒
916
+ // };
917
+ // for (const k in opt) {
918
+ // ret = new RegExp(`(${k})`).exec(fmt);
919
+ // if (ret) {
920
+ // fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
921
+ // }
922
+ // }
923
+ // return fmt;
924
+ // };
925
+ /**
926
+ * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
927
+ * @param {DateValue} value
928
+ * @returns {Date}
929
+ */
930
+ function dateToStart(value) {
931
+ const d = dateParse(value);
932
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
933
+ }
934
+ /**
935
+ * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
936
+ * @param {DateValue} value
937
+ * @returns {Date}
938
+ */
939
+ function dateToEnd(value) {
940
+ const d = dateToStart(value);
941
+ d.setDate(d.getDate() + 1);
942
+ return dateParse(d.getTime() - 1);
943
+ }
944
+ /**
945
+ * 格式化为日期对象(带自定义格式化模板)
946
+ * @param {Date} value - 可以是数值、字符串或 Date 对象
947
+ * @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
948
+ * - YYYY:年
949
+ * - yyyy: 年
950
+ * - MM:月
951
+ * - DD:日
952
+ * - dd: 日
953
+ * - HH:时(24 小时制)
954
+ * - hh:时(12 小时制)
955
+ * - mm:分
956
+ * - ss:秒
957
+ * - SSS:毫秒
958
+ * - ww: 周
959
+ * @returns {string} 格式化后的日期字符串
960
+ */
961
+ function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
962
+ const date = dateParse(value);
819
963
  let fmt = format;
820
964
  let ret;
821
965
  const opt = {
@@ -838,39 +982,39 @@
838
982
  }
839
983
  }
840
984
  return fmt;
841
- };
985
+ }
842
986
  /**
843
987
  * 计算向前或向后N天的具体日期
844
- * @param {string} strDate 参考日期
845
- * @param {number} n 正数:向后推算;负数:向前推算
846
- * @param {string} sep 日期格式的分隔符
847
- * @return {*} 目标日期
988
+ * @param {string} strDate - 参考日期
989
+ * @param {number} n - 正数:向后推算;负数:向前推算
990
+ * @param {string} sep - 日期格式的分隔符
991
+ * @returns {string} 计算后的目标日期
848
992
  */
849
993
  function calculateDate(strDate, n, sep = '-') {
850
994
  //strDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
851
- let dateArr = strDate.split(sep); //这边给定一个特定时间
852
- let newDate = new Date(+dateArr[0], +dateArr[1] - 1, +dateArr[2]);
853
- let befminuts = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
854
- let beforeDat = new Date();
995
+ const dateArr = strDate.split(sep); //这边给定一个特定时间
996
+ const newDate = new Date(+dateArr[0], +dateArr[1] - 1, +dateArr[2]);
997
+ const befminuts = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
998
+ const beforeDat = new Date();
855
999
  beforeDat.setTime(befminuts);
856
- let befMonth = beforeDat.getMonth() + 1;
857
- let mon = befMonth >= 10 ? befMonth : '0' + befMonth;
858
- let befDate = beforeDat.getDate();
859
- let da = befDate >= 10 ? befDate : '0' + befDate;
860
- let finalNewDate = beforeDat.getFullYear() + '-' + mon + '-' + da;
1000
+ const befMonth = beforeDat.getMonth() + 1;
1001
+ const mon = befMonth >= 10 ? befMonth : '0' + befMonth;
1002
+ const befDate = beforeDat.getDate();
1003
+ const da = befDate >= 10 ? befDate : '0' + befDate;
1004
+ const finalNewDate = beforeDat.getFullYear() + '-' + mon + '-' + da;
861
1005
  return finalNewDate;
862
1006
  }
863
1007
  /**
864
1008
  * 计算向前或向后N天的具体时间日期
865
- * @param {number} n 正数:向后推算;负数:向前推算
866
- * @param {string} dateSep 日期分隔符
867
- * @param {string} timeSep 时间分隔符
868
- * @return {*}
1009
+ * @param {number} n - 正数:向后推算;负数:向前推算
1010
+ * @param {string} dateSep - 日期分隔符
1011
+ * @param {string} timeSep - 时间分隔符
1012
+ * @returns {string} 转换后的目标日期时间
869
1013
  */
870
1014
  function calculateDateTime(n, dateSep = '-', timeSep = ':') {
871
- let date = new Date();
872
- let separator1 = '-';
873
- let separator2 = ':';
1015
+ const date = new Date();
1016
+ const separator1 = '-';
1017
+ const separator2 = ':';
874
1018
  let year = date.getFullYear();
875
1019
  let month = date.getMonth() + 1;
876
1020
  let strDate = date.getDate() + n;
@@ -962,7 +1106,7 @@
962
1106
  * @param {string} queryString
963
1107
  * @returns {Params}
964
1108
  */
965
- const qsParse = (queryString) => {
1109
+ function qsParse(queryString) {
966
1110
  const params = new URLSearchParams(queryString);
967
1111
  const result = {};
968
1112
  for (const [key, val] of params.entries()) {
@@ -976,7 +1120,7 @@
976
1120
  result[key] = params.getAll(key);
977
1121
  }
978
1122
  return result;
979
- };
1123
+ }
980
1124
  const defaultReplacer = (val) => {
981
1125
  if (isString(val))
982
1126
  return val;
@@ -994,7 +1138,7 @@
994
1138
  * @param {Replacer} replacer
995
1139
  * @returns {string}
996
1140
  */
997
- const qsStringify = (query, replacer = defaultReplacer) => {
1141
+ function qsStringify(query, replacer = defaultReplacer) {
998
1142
  const params = new URLSearchParams();
999
1143
  objectEach(query, (val, key) => {
1000
1144
  if (isArray(val)) {
@@ -1013,7 +1157,7 @@
1013
1157
  }
1014
1158
  });
1015
1159
  return params.toString();
1016
- };
1160
+ }
1017
1161
 
1018
1162
  const anchorEl = document.createElement('a');
1019
1163
  /**
@@ -1089,15 +1233,15 @@
1089
1233
  * @param {string} url
1090
1234
  * @param {LooseParams} params
1091
1235
  */
1092
- const downloadURL = (url, params) => {
1236
+ function downloadURL(url, params) {
1093
1237
  window.open(params ? urlSetParams(url, params) : url);
1094
- };
1238
+ }
1095
1239
  /**
1096
1240
  * 通过 A 链接的方式下载
1097
1241
  * @param {string} href
1098
1242
  * @param {string} filename
1099
1243
  */
1100
- const downloadHref = (href, filename) => {
1244
+ function downloadHref(href, filename) {
1101
1245
  const eleLink = document.createElement('a');
1102
1246
  eleLink.download = filename;
1103
1247
  eleLink.style.display = 'none';
@@ -1105,17 +1249,17 @@
1105
1249
  document.body.appendChild(eleLink);
1106
1250
  eleLink.click();
1107
1251
  setTimeout(() => document.body.removeChild(eleLink));
1108
- };
1252
+ }
1109
1253
  /**
1110
1254
  * 将大文件对象通过 A 链接的方式下载
1111
1255
  * @param {Blob} blob
1112
1256
  * @param {string} filename
1113
1257
  */
1114
- const downloadBlob = (blob, filename) => {
1258
+ function downloadBlob(blob, filename) {
1115
1259
  const objURL = URL.createObjectURL(blob);
1116
1260
  downloadHref(objURL, filename);
1117
1261
  setTimeout(() => URL.revokeObjectURL(objURL));
1118
- };
1262
+ }
1119
1263
  /**
1120
1264
  * 将指定数据格式通过 A 链接的方式下载
1121
1265
  * @param {AnyObject | AnyObject[]} data
@@ -1123,7 +1267,7 @@
1123
1267
  * @param {string} filename
1124
1268
  * @param {string[]} [headers]
1125
1269
  */
1126
- const downloadData = (data, fileType, filename, headers) => {
1270
+ function downloadData(data, fileType, filename, headers) {
1127
1271
  filename = filename.replace(`.${fileType}`, '') + `.${fileType}`;
1128
1272
  if (fileType === 'json') {
1129
1273
  const blob = new Blob([JSON.stringify(data, null, 4)]);
@@ -1150,14 +1294,14 @@
1150
1294
  const href = 'data:' + MIMETypes[fileType] + ';charset=utf-8,\ufeff' + encodeURIComponent(headerStr + bodyStr);
1151
1295
  downloadHref(href, filename);
1152
1296
  }
1153
- };
1297
+ }
1154
1298
 
1155
1299
  /**
1156
1300
  * 等待一段时间
1157
1301
  * @param {number} timeout 等待时间,单位毫秒
1158
1302
  * @returns {Promise<void>}
1159
1303
  */
1160
- async function wait(timeout = 1) {
1304
+ function wait(timeout = 1) {
1161
1305
  return new Promise(resolve => setTimeout(resolve, timeout));
1162
1306
  }
1163
1307
  /**
@@ -1169,7 +1313,7 @@
1169
1313
  * @param {number} concurrency 并发数量,默认无限
1170
1314
  * @returns {Promise<R[]>}
1171
1315
  */
1172
- async function asyncMap(list, mapper, concurrency = Infinity) {
1316
+ function asyncMap(list, mapper, concurrency = Infinity) {
1173
1317
  return new Promise((resolve, reject) => {
1174
1318
  const iterator = list[Symbol.iterator]();
1175
1319
  const limit = Math.min(list.length, concurrency);
@@ -1207,10 +1351,11 @@
1207
1351
 
1208
1352
  /**
1209
1353
  * 选择本地文件
1210
- * @param {function} changeCb 选择文件回调
1211
- * @return {*}
1354
+ * @param {string} accept 上传的文件类型,用于过滤
1355
+ * @param {Function} changeCb 选择文件回调
1356
+ * @returns {HTMLInputElement}
1212
1357
  */
1213
- function chooseLocalFile({ accept }, changeCb) {
1358
+ function chooseLocalFile(accept, changeCb) {
1214
1359
  const inputObj = document.createElement('input');
1215
1360
  inputObj.setAttribute('id', String(Date.now()));
1216
1361
  inputObj.setAttribute('type', 'file');
@@ -1233,17 +1378,18 @@
1233
1378
  * @desc 网页加水印的工具类
1234
1379
  */
1235
1380
  /**
1236
- * canvas 实现 watermark
1381
+ * canvas 实现 水印, 具备防删除功能
1237
1382
  * @param {ICanvasWM} canvasWM
1383
+ * @example genCanvasWM({ content: 'QQMusicFE' })
1238
1384
  */
1239
- const genCanvasWM = (canvasWM) => {
1385
+ function genCanvasWM(canvasWM) {
1240
1386
  const { container = document.body, width = '300px', height = '200px', textAlign = 'center', textBaseline = 'middle', font = '20px PingFangSC-Medium,PingFang SC',
1241
1387
  // fontWeight = 500,
1242
1388
  fillStyle = 'rgba(189, 177, 167, .3)', content = '请勿外传', rotate = 30, zIndex = 2147483647 } = canvasWM;
1243
1389
  // 仅限主页面添加水印
1244
- if (!location.pathname.includes('/home')) {
1245
- return;
1246
- }
1390
+ // if (!location.pathname.includes('/home')) {
1391
+ // return;
1392
+ // }
1247
1393
  const args = canvasWM;
1248
1394
  const canvas = document.createElement('canvas');
1249
1395
  canvas.setAttribute('width', width);
@@ -1304,20 +1450,359 @@
1304
1450
  });
1305
1451
  mo.observe(container, { attributes: true, subtree: true, childList: true });
1306
1452
  }
1453
+ }
1454
+
1455
+ /**
1456
+ * 防抖函数
1457
+ * 当函数被连续调用时,该函数并不执行,只有当其全部停止调用超过一定时间后才执行1次。
1458
+ * 例如:上电梯的时候,大家陆陆续续进来,电梯的门不会关上,只有当一段时间都没有人上来,电梯才会关门。
1459
+ * @param {F} func
1460
+ * @param {number} wait
1461
+ * @returns {DebounceFunc<F>}
1462
+ */
1463
+ const debounce = (func, wait) => {
1464
+ let timeout;
1465
+ let canceled = false;
1466
+ const f = function (...args) {
1467
+ if (canceled)
1468
+ return;
1469
+ clearTimeout(timeout);
1470
+ timeout = setTimeout(() => {
1471
+ func.call(this, ...args);
1472
+ }, wait);
1473
+ };
1474
+ f.cancel = () => {
1475
+ clearTimeout(timeout);
1476
+ canceled = true;
1477
+ };
1478
+ return f;
1479
+ };
1480
+ /**
1481
+ * 节流函数
1482
+ * 节流就是节约流量,将连续触发的事件稀释成预设评率。 比如每间隔1秒执行一次函数,无论这期间触发多少次事件。
1483
+ * 这有点像公交车,无论在站点等车的人多不多,公交车只会按时来一班,不会来一个人就来一辆公交车。
1484
+ * @param {F} func
1485
+ * @param {number} wait
1486
+ * @param {boolean} immediate
1487
+ * @returns {ThrottleFunc<F>}
1488
+ */
1489
+ const throttle = (func, wait, immediate) => {
1490
+ let timeout;
1491
+ let canceled = false;
1492
+ let lastCalledTime = 0;
1493
+ const f = function (...args) {
1494
+ if (canceled)
1495
+ return;
1496
+ const now = Date.now();
1497
+ const call = () => {
1498
+ lastCalledTime = now;
1499
+ func.call(this, ...args);
1500
+ };
1501
+ // 第一次执行
1502
+ if (lastCalledTime === 0) {
1503
+ if (immediate) {
1504
+ return call();
1505
+ }
1506
+ else {
1507
+ lastCalledTime = now;
1508
+ return;
1509
+ }
1510
+ }
1511
+ const remain = lastCalledTime + wait - now;
1512
+ if (remain > 0) {
1513
+ clearTimeout(timeout);
1514
+ timeout = setTimeout(() => call(), wait);
1515
+ }
1516
+ else {
1517
+ call();
1518
+ }
1519
+ };
1520
+ f.cancel = () => {
1521
+ clearTimeout(timeout);
1522
+ canceled = true;
1523
+ };
1524
+ return f;
1525
+ };
1526
+ /**
1527
+ * 单次函数
1528
+ * @param {AnyFunc} func
1529
+ * @returns {AnyFunc}
1530
+ */
1531
+ const once = (func) => {
1532
+ let called = false;
1533
+ let result;
1534
+ return function (...args) {
1535
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
1536
+ if (called)
1537
+ return result;
1538
+ called = true;
1539
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1540
+ result = func.call(this, ...args);
1541
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
1542
+ return result;
1543
+ };
1544
+ };
1545
+ /**
1546
+ * 设置全局变量
1547
+ * @param {string | number | symbol} key
1548
+ * @param val
1549
+ */
1550
+ function setGlobal(key, val) {
1551
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1552
+ // @ts-ignore
1553
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1554
+ if (typeof globalThis !== 'undefined')
1555
+ globalThis[key] = val;
1556
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1557
+ // @ts-ignore
1558
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1559
+ else if (typeof window !== 'undefined')
1560
+ window[key] = val;
1561
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1562
+ // @ts-ignore
1563
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1564
+ else if (typeof global !== 'undefined')
1565
+ global[key] = val;
1566
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1567
+ // @ts-ignore
1568
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1569
+ else if (typeof self !== 'undefined')
1570
+ self[key] = val;
1571
+ else
1572
+ throw new SyntaxError('当前环境下无法设置全局属性');
1573
+ }
1574
+ /**
1575
+ * 设置全局变量
1576
+ * @param {string | number | symbol} key
1577
+ * @param val
1578
+ */
1579
+ function getGlobal(key) {
1580
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1581
+ // @ts-ignore
1582
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1583
+ if (typeof globalThis !== 'undefined')
1584
+ return globalThis[key];
1585
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1586
+ // @ts-ignore
1587
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1588
+ else if (typeof window !== 'undefined')
1589
+ return window[key];
1590
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1591
+ // @ts-ignore
1592
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1593
+ else if (typeof global !== 'undefined')
1594
+ return global[key];
1595
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1596
+ // @ts-ignore
1597
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1598
+ else if (typeof self !== 'undefined')
1599
+ return self[key];
1600
+ }
1601
+
1602
+ /**
1603
+ * 随机整数
1604
+ * @param {number} min
1605
+ * @param {number} max
1606
+ * @returns {number}
1607
+ */
1608
+ const randomNumber = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
1609
+ const STRING_POOL = `${STRING_ARABIC_NUMERALS}${STRING_UPPERCASE_ALPHA}${STRING_LOWERCASE_ALPHA}`;
1610
+ /**
1611
+ * 随机字符串
1612
+ * @param {number | string} length
1613
+ * @param {string} pool
1614
+ * @returns {string}
1615
+ */
1616
+ const randomString = (length, pool) => {
1617
+ let _length = 0;
1618
+ let _pool = STRING_POOL;
1619
+ if (isString(pool)) {
1620
+ _length = length;
1621
+ _pool = pool;
1622
+ }
1623
+ else if (isNumber(length)) {
1624
+ _length = length;
1625
+ }
1626
+ else if (isString(length)) {
1627
+ _pool = length;
1628
+ }
1629
+ let times = Math.max(_length, 1);
1630
+ let result = '';
1631
+ const min = 0;
1632
+ const max = _pool.length - 1;
1633
+ if (max < 2)
1634
+ throw new Error('字符串池长度不能少于 2');
1635
+ while (times--) {
1636
+ const index = randomNumber(min, max);
1637
+ result += _pool[index];
1638
+ }
1639
+ return result;
1640
+ };
1641
+ /**
1642
+ * 优先浏览器原生能力获取 UUID v4
1643
+ * @returns {string}
1644
+ */
1645
+ function randomUuid() {
1646
+ if (typeof URL === 'undefined' || !URL.createObjectURL || typeof Blob === 'undefined') {
1647
+ const hex = '0123456789abcdef';
1648
+ const model = 'xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx';
1649
+ let str = '';
1650
+ for (let i = 0; i < model.length; i++) {
1651
+ const rnd = randomNumber(0, 15);
1652
+ str += model[i] == '-' || model[i] == '4' ? model[i] : hex[rnd];
1653
+ }
1654
+ return str;
1655
+ }
1656
+ return /[^/]+$/.exec(URL.createObjectURL(new Blob()).slice())[0];
1657
+ }
1658
+
1659
+ const HEX_POOL = `${STRING_ARABIC_NUMERALS}${STRING_UPPERCASE_ALPHA}${STRING_LOWERCASE_ALPHA}`;
1660
+ const supportBigInt = typeof BigInt !== 'undefined';
1661
+ const jsbi = () => getGlobal('JSBI');
1662
+ const toBigInt = (n) => (supportBigInt ? BigInt(n) : jsbi().BigInt(n));
1663
+ const divide = (x, y) => (supportBigInt ? x / y : jsbi().divide(x, y));
1664
+ const remainder = (x, y) => (supportBigInt ? x % y : jsbi().remainder(x, y));
1665
+ /**
1666
+ * 将十进制转换成任意进制
1667
+ * @param {number | string} decimal 十进制数值或字符串,可以是任意长度,会使用大数进行计算
1668
+ * @param {string} [hexPool] 进制池,默认 62 进制
1669
+ * @returns {string}
1670
+ */
1671
+ function numberToHex(decimal, hexPool = HEX_POOL) {
1672
+ if (hexPool.length < 2)
1673
+ throw new Error('进制池长度不能少于 2');
1674
+ if (!supportBigInt) {
1675
+ throw new Error('需要安装 jsbi 模块并将 JSBI 设置为全局变量:\nimport JSBI from "jsbi"; window.JSBI = JSBI;');
1676
+ }
1677
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1678
+ let bigInt = toBigInt(decimal);
1679
+ const ret = [];
1680
+ const { length } = hexPool;
1681
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1682
+ const bigLength = toBigInt(length);
1683
+ const execute = () => {
1684
+ const y = Number(remainder(bigInt, bigLength));
1685
+ bigInt = divide(bigInt, bigLength);
1686
+ ret.unshift(hexPool[y]);
1687
+ if (bigInt > 0) {
1688
+ execute();
1689
+ }
1690
+ };
1691
+ execute();
1692
+ return ret.join('');
1693
+ }
1694
+ /**
1695
+ * 缩写
1696
+ * @param {number | string} num
1697
+ * @param {Array<string>} units
1698
+ * @param {number} ratio
1699
+ * @param {number} exponent
1700
+ * @returns {string}
1701
+ */
1702
+ const numberAbbr = (num, units, ratio = 1000, exponent) => {
1703
+ const { length } = units;
1704
+ if (length === 0)
1705
+ throw new Error('至少需要一个单位');
1706
+ let num2 = Number(num);
1707
+ let times = 0;
1708
+ while (num2 >= ratio && times < length - 1) {
1709
+ num2 = num2 / ratio;
1710
+ times++;
1711
+ }
1712
+ const value = num2.toFixed(exponent);
1713
+ const unit = units[times];
1714
+ return value.toString() + '' + unit;
1715
+ };
1716
+ /**
1717
+ * 将数字格式化成千位分隔符显示的字符串
1718
+ * @param {number} val 数字
1719
+ * @param {'int' | 'float'} type 展示分段显示的类型 int:整型 | float:浮点型
1720
+ * @returns {string}
1721
+ */
1722
+ function formatNumber(val, type = 'int') {
1723
+ return type === 'int' ? parseInt(String(val)).toLocaleString() : Number(val).toLocaleString('en-US');
1724
+ }
1725
+
1726
+ const padStartWithZero = (str, maxLength = 2) => String(str).padStart(maxLength, '0');
1727
+ let safeNo = 0;
1728
+ let lastTimestamp = 0;
1729
+ // 安全后缀长度,按 1 毫秒运算 99999 次 JS 代码来进行估算
1730
+ // 那么,补足长度为 5
1731
+ // 时间戳模式长度为 13
1732
+ // 取最长 5 + 13 = 18
1733
+ const UNIQUE_NUMBER_SAFE_LENGTH = 18;
1734
+ const FIX_SAFE_LENGTH = 5;
1735
+ const TIMESTAMP_LENGTH = 13;
1736
+ /**
1737
+ * 生成唯一不重复数值
1738
+ * @param {number} length
1739
+ * @returns {string}
1740
+ */
1741
+ const uniqueNumber = (length = UNIQUE_NUMBER_SAFE_LENGTH) => {
1742
+ const now = Date.now();
1743
+ length = Math.max(length, UNIQUE_NUMBER_SAFE_LENGTH);
1744
+ if (now !== lastTimestamp) {
1745
+ lastTimestamp = now;
1746
+ safeNo = 0;
1747
+ }
1748
+ const timestamp = `${now}`;
1749
+ let random = '';
1750
+ const rndLength = length - FIX_SAFE_LENGTH - TIMESTAMP_LENGTH;
1751
+ if (rndLength > 0) {
1752
+ const rndMin = 10 ** (rndLength - 1);
1753
+ const rndMax = 10 ** rndLength - 1;
1754
+ const rnd = randomNumber(rndMin, rndMax);
1755
+ random = `${rnd}`;
1756
+ }
1757
+ const safe = padStartWithZero(safeNo, FIX_SAFE_LENGTH);
1758
+ safeNo++;
1759
+ return `${timestamp}${random}${safe}`;
1760
+ };
1761
+ const randomFromPool = (pool) => {
1762
+ const poolIndex = randomNumber(0, pool.length - 1);
1763
+ return pool[poolIndex];
1764
+ };
1765
+ /**
1766
+ * 生成唯一不重复字符串
1767
+ * @param {number | string} length
1768
+ * @param {string} pool
1769
+ * @returns {string}
1770
+ */
1771
+ const uniqueString = (length, pool) => {
1772
+ let _length = 0;
1773
+ let _pool = HEX_POOL;
1774
+ if (isString(pool)) {
1775
+ _length = length;
1776
+ _pool = pool;
1777
+ }
1778
+ else if (isNumber(length)) {
1779
+ _length = length;
1780
+ }
1781
+ else if (isString(length)) {
1782
+ _pool = length;
1783
+ }
1784
+ let uniqueString = numberToHex(uniqueNumber(), _pool);
1785
+ let insertLength = _length - uniqueString.length;
1786
+ if (insertLength <= 0)
1787
+ return uniqueString;
1788
+ while (insertLength--) {
1789
+ uniqueString += randomFromPool(_pool);
1790
+ }
1791
+ return uniqueString;
1307
1792
  };
1308
- // 调用
1309
- // __canvasWM({ content: 'QQMusicFE' })
1310
1793
 
1794
+ exports.HEX_POOL = HEX_POOL;
1311
1795
  exports.STRING_ARABIC_NUMERALS = STRING_ARABIC_NUMERALS;
1312
1796
  exports.STRING_LOWERCASE_ALPHA = STRING_LOWERCASE_ALPHA;
1797
+ exports.STRING_POOL = STRING_POOL;
1313
1798
  exports.STRING_UPPERCASE_ALPHA = STRING_UPPERCASE_ALPHA;
1799
+ exports.UNIQUE_NUMBER_SAFE_LENGTH = UNIQUE_NUMBER_SAFE_LENGTH;
1314
1800
  exports.addClass = addClass;
1315
1801
  exports.arrayEach = arrayEach;
1316
1802
  exports.arrayEachAsync = arrayEachAsync;
1317
1803
  exports.arrayInsertBefore = arrayInsertBefore;
1318
1804
  exports.arrayLike = arrayLike;
1319
1805
  exports.arrayRemove = arrayRemove;
1320
- exports.asyncForEach = asyncForEach;
1321
1806
  exports.asyncMap = asyncMap;
1322
1807
  exports.calculateDate = calculateDate;
1323
1808
  exports.calculateDateTime = calculateDateTime;
@@ -1327,13 +1812,21 @@
1327
1812
  exports.cookieGet = cookieGet;
1328
1813
  exports.cookieSet = cookieSet;
1329
1814
  exports.copyText = copyText;
1815
+ exports.dateParse = dateParse;
1816
+ exports.dateToEnd = dateToEnd;
1817
+ exports.dateToStart = dateToStart;
1818
+ exports.debounce = debounce;
1330
1819
  exports.deepTraversal = deepTraversal;
1331
1820
  exports.downloadBlob = downloadBlob;
1332
1821
  exports.downloadData = downloadData;
1333
1822
  exports.downloadHref = downloadHref;
1334
1823
  exports.downloadURL = downloadURL;
1335
1824
  exports.formatDate = formatDate;
1825
+ exports.formatNumber = formatNumber;
1336
1826
  exports.genCanvasWM = genCanvasWM;
1827
+ exports.getComputedCssVal = getComputedCssVal;
1828
+ exports.getGlobal = getGlobal;
1829
+ exports.getStrWidthPx = getStrWidthPx;
1337
1830
  exports.getStyle = getStyle;
1338
1831
  exports.getTreeIds = getTreeIds;
1339
1832
  exports.hasClass = hasClass;
@@ -1354,6 +1847,9 @@
1354
1847
  exports.isString = isString;
1355
1848
  exports.isSymbol = isSymbol;
1356
1849
  exports.isUndefined = isUndefined;
1850
+ exports.isValidDate = isValidDate;
1851
+ exports.numberAbbr = numberAbbr;
1852
+ exports.numberToHex = numberToHex;
1357
1853
  exports.objectAssign = objectAssign;
1358
1854
  exports.objectEach = objectEach;
1359
1855
  exports.objectEachAsync = objectEachAsync;
@@ -1365,11 +1861,16 @@
1365
1861
  exports.objectOmit = objectOmit;
1366
1862
  exports.objectPick = objectPick;
1367
1863
  exports.onDomReady = onDomReady;
1864
+ exports.once = once;
1368
1865
  exports.pathJoin = pathJoin;
1369
1866
  exports.pathNormalize = pathNormalize;
1370
1867
  exports.qsParse = qsParse;
1371
1868
  exports.qsStringify = qsStringify;
1869
+ exports.randomNumber = randomNumber;
1870
+ exports.randomString = randomString;
1871
+ exports.randomUuid = randomUuid;
1372
1872
  exports.removeClass = removeClass;
1873
+ exports.setGlobal = setGlobal;
1373
1874
  exports.setStyle = setStyle;
1374
1875
  exports.smoothScroll = smoothScroll;
1375
1876
  exports.stringAssign = stringAssign;
@@ -1378,6 +1879,10 @@
1378
1879
  exports.stringFill = stringFill;
1379
1880
  exports.stringFormat = stringFormat;
1380
1881
  exports.stringKebabCase = stringKebabCase;
1882
+ exports.throttle = throttle;
1883
+ exports.typeIs = typeIs;
1884
+ exports.uniqueNumber = uniqueNumber;
1885
+ exports.uniqueString = uniqueString;
1381
1886
  exports.urlDelParams = urlDelParams;
1382
1887
  exports.urlParse = urlParse;
1383
1888
  exports.urlSetParams = urlSetParams;