sculp-js 0.1.1 → 1.0.1

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 (49) hide show
  1. package/README.md +3 -0
  2. package/lib/cjs/array.js +56 -36
  3. package/lib/cjs/async.js +3 -3
  4. package/lib/cjs/clipboard.js +3 -3
  5. package/lib/cjs/cookie.js +5 -5
  6. package/lib/cjs/date.js +130 -12
  7. package/lib/cjs/dom.js +13 -11
  8. package/lib/cjs/download.js +9 -9
  9. package/lib/cjs/easing.js +1 -1
  10. package/lib/cjs/file.js +5 -4
  11. package/lib/cjs/func.js +1 -1
  12. package/lib/cjs/index.js +9 -3
  13. package/lib/cjs/number.js +4 -4
  14. package/lib/cjs/object.js +11 -9
  15. package/lib/cjs/path.js +1 -1
  16. package/lib/cjs/qs.js +5 -5
  17. package/lib/cjs/random.js +1 -1
  18. package/lib/cjs/string.js +8 -8
  19. package/lib/cjs/tooltip.js +118 -0
  20. package/lib/cjs/type.js +11 -2
  21. package/lib/cjs/unique.js +1 -1
  22. package/lib/cjs/url.js +1 -1
  23. package/lib/cjs/watermark.js +8 -9
  24. package/lib/es/array.js +55 -35
  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 +127 -13
  29. package/lib/es/dom.js +13 -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 +1 -1
  34. package/lib/es/index.js +4 -3
  35. package/lib/es/number.js +4 -4
  36. package/lib/es/object.js +11 -9
  37. package/lib/es/path.js +1 -1
  38. package/lib/es/qs.js +5 -5
  39. package/lib/es/random.js +1 -1
  40. package/lib/es/string.js +8 -8
  41. package/lib/es/tooltip.js +116 -0
  42. package/lib/es/type.js +11 -2
  43. package/lib/es/unique.js +1 -1
  44. package/lib/es/url.js +1 -1
  45. package/lib/es/watermark.js +8 -9
  46. package/lib/index.d.ts +155 -66
  47. package/lib/tsdoc-metadata.json +11 -0
  48. package/lib/umd/index.js +365 -108
  49. package/package.json +3 -2
package/lib/umd/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v0.1.0
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,42 @@
263
275
  if (!isObject(any))
264
276
  return false;
265
277
  return objectHas(any, 'length');
266
- };
278
+ }
267
279
  /**
268
- * 遍历数组,返回 false 中断遍历
280
+ * 遍历数组,返回 false 中断遍历(支持continue和break操作)
281
+ *
269
282
  * @param {ArrayLike<V>} array
270
- * @param {(val: V, idx: number) => any} iterator
271
- * @param reverse {boolean} 是否倒序
283
+ * @param {(val: V, idx: number) => any} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
284
+ * @param {boolean} reverse 是否倒序
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, array) === false)
291
+ const re = iterator(val, idx, array);
292
+ if (re === false)
278
293
  break;
294
+ else if (re === true)
295
+ continue;
279
296
  }
280
297
  }
281
298
  else {
282
299
  for (let idx = 0; idx < array.length; idx++) {
283
300
  const val = array[idx];
284
- if (iterator(val, idx, array) === false)
301
+ const re = iterator(val, idx, array);
302
+ if (re === false)
285
303
  break;
304
+ else if (re === true)
305
+ continue;
286
306
  }
287
307
  }
288
- };
308
+ }
289
309
  /**
290
310
  * 异步遍历数组,返回 false 中断遍历
291
- * @param {ArrayLike<V>} array
292
- * @param {(val: V, idx: number) => Promise<any>} iterator
293
- * @param {boolean} reverse
311
+ * @param {ArrayLike<V>} array 数组
312
+ * @param {(val: V, idx: number) => Promise<any>} iterator 支持Promise类型的回调函数
313
+ * @param {boolean} reverse 是否反向遍历
294
314
  */
295
315
  async function arrayEachAsync(array, iterator, reverse = false) {
296
316
  if (reverse) {
@@ -313,15 +333,16 @@
313
333
  * @param {AnyArray} array
314
334
  * @param {number} start
315
335
  * @param {number} to
336
+ * @returns {*}
316
337
  */
317
- const arrayInsertBefore = (array, start, to) => {
338
+ function arrayInsertBefore(array, start, to) {
318
339
  if (start === to || start + 1 === to)
319
340
  return;
320
341
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
321
342
  const [source] = array.splice(start, 1);
322
343
  const insertIndex = to < start ? to : to - 1;
323
344
  array.splice(insertIndex, 0, source);
324
- };
345
+ }
325
346
  /**
326
347
  * 数组删除指定项目
327
348
  * @param {V[]} array
@@ -342,21 +363,26 @@
342
363
  }
343
364
  /**
344
365
  * 自定义深度优先遍历函数(支持continue和break操作)
345
- * @param {array} deepList
346
- * @param {function} iterator
347
- * @param {array} children
348
- * @param {boolean} isReverse 是否反向遍历
349
- */
350
- const deepTraversal = (deepList, iterator, children = 'children', isReverse = false) => {
351
- let level = 0;
366
+ * @param {ArrayLike<V>} tree 树形数据
367
+ * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
368
+ * @param {string} children 定制子元素的key
369
+ * @param {boolean} isReverse 是否反向遍历
370
+ * @returns {*}
371
+ */
372
+ function forEachDeep(tree, iterator, children = 'children', isReverse = false) {
373
+ let level = 0, isBreak = false;
352
374
  const walk = (arr, parent) => {
353
375
  if (isReverse) {
354
376
  for (let i = arr.length - 1; i >= 0; i--) {
355
- const re = iterator(arr[i], i, deepList, parent, level);
356
- if (re === 'break') {
377
+ if (isBreak) {
378
+ break;
379
+ }
380
+ const re = iterator(arr[i], i, tree, parent, level);
381
+ if (re === false) {
382
+ isBreak = true;
357
383
  break;
358
384
  }
359
- else if (re === 'continue') {
385
+ else if (re === true) {
360
386
  continue;
361
387
  }
362
388
  // @ts-ignore
@@ -369,11 +395,15 @@
369
395
  }
370
396
  else {
371
397
  for (let i = 0; i < arr.length; i++) {
372
- const re = iterator(arr[i], i, deepList, parent, level);
373
- if (re === 'break') {
398
+ if (isBreak) {
374
399
  break;
375
400
  }
376
- else if (re === 'continue') {
401
+ const re = iterator(arr[i], i, tree, parent, level);
402
+ if (re === false) {
403
+ isBreak = true;
404
+ break;
405
+ }
406
+ else if (re === true) {
377
407
  continue;
378
408
  }
379
409
  // @ts-ignore
@@ -385,16 +415,17 @@
385
415
  }
386
416
  }
387
417
  };
388
- walk(deepList, null);
389
- };
418
+ walk(tree, null);
419
+ }
390
420
  /**
391
421
  * 在树中找到 id 为某个值的节点,并返回上游的所有父级节点
392
- * @param {ArrayLike<T>} tree
393
- * @param {IdLike} nodeId
394
- * @param {ITreeConf} config
395
- * @return {[IdLike[], ITreeItem<V>[]]}
422
+ *
423
+ * @param {ArrayLike<T>} tree - 树形数据
424
+ * @param {IdLike} nodeId - 元素ID
425
+ * @param {ITreeConf} config - 迭代配置项
426
+ * @returns {[IdLike[], ITreeItem<V>[]]} - 由parentId...childId, parentObject-childObject组成的二维数组
396
427
  */
397
- function getTreeIds(tree, nodeId, config) {
428
+ function searchTreeById(tree, nodeId, config) {
398
429
  const { children = 'children', id = 'id' } = config || {};
399
430
  const toFlatArray = (tree, parentId, parent) => {
400
431
  return tree.reduce((t, _) => {
@@ -454,24 +485,24 @@
454
485
  * @param {boolean} [bigger] 是否大写第一个字母
455
486
  * @returns {string}
456
487
  */
457
- const stringCamelCase = (string, bigger) => {
488
+ function stringCamelCase(string, bigger) {
458
489
  let string2 = string;
459
490
  if (bigger) {
460
491
  string2 = string.replace(/^./, origin => origin.toUpperCase());
461
492
  }
462
493
  const HUMP_RE = /[\s_-](.)/g;
463
494
  return string2.replace(HUMP_RE, (orign, char) => char.toUpperCase());
464
- };
495
+ }
465
496
  /**
466
497
  * 将字符串转换为连字格式
467
498
  * @param {string} string
468
499
  * @param {string} [separator] 分隔符,默认是"-"(短横线)
469
500
  * @returns {string}
470
501
  */
471
- const stringKebabCase = (string, separator = '-') => {
502
+ function stringKebabCase(string, separator = '-') {
472
503
  const string2 = string.replace(/^./, origin => origin.toLowerCase());
473
504
  return string2.replace(/[A-Z]/g, origin => `${separator}${origin.toLowerCase()}`);
474
- };
505
+ }
475
506
  const STRING_ARABIC_NUMERALS = '0123456789';
476
507
  const STRING_LOWERCASE_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
477
508
  const STRING_UPPERCASE_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
@@ -487,7 +518,7 @@
487
518
  * @param args
488
519
  * @returns {string}
489
520
  */
490
- const stringFormat = (string, ...args) => {
521
+ function stringFormat(string, ...args) {
491
522
  let index = 0;
492
523
  const result = string.replace(placeholderRE, (origin) => {
493
524
  const arg = args[index++];
@@ -505,7 +536,7 @@
505
536
  }
506
537
  });
507
538
  return [result, ...args.splice(index).map(String)].join(' ');
508
- };
539
+ }
509
540
  const ev = (expression, data) => {
510
541
  try {
511
542
  // eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-return
@@ -569,7 +600,7 @@
569
600
  * @param {string} str 目标字符串
570
601
  * @param {number} fontSize 字符串字体大小
571
602
  * @param {boolean} isRemoveDom 计算后是否移除中间dom元素
572
- * @return {*}
603
+ * @returns {*}
573
604
  */
574
605
  function getStrWidthPx(str, fontSize = 14, isRemoveDom = false) {
575
606
  let strWidth = 0;
@@ -601,11 +632,11 @@
601
632
  * @param {string} className
602
633
  * @returns {boolean}
603
634
  */
604
- const hasClass = (el, className) => {
635
+ function hasClass(el, className) {
605
636
  if (className.indexOf(' ') !== -1)
606
637
  throw new Error('className should not contain space.');
607
638
  return el.classList.contains(className);
608
- };
639
+ }
609
640
  const eachClassName = (classNames, func) => {
610
641
  const classNameList = classNames.split(/\s+/g);
611
642
  classNameList.forEach(func);
@@ -615,17 +646,17 @@
615
646
  * @param {HTMLElement} el
616
647
  * @param {string} classNames
617
648
  */
618
- const addClass = (el, classNames) => {
649
+ function addClass(el, classNames) {
619
650
  eachClassName(classNames, className => el.classList.add(className));
620
- };
651
+ }
621
652
  /**
622
653
  * 给元素移除样式名
623
654
  * @param {HTMLElement} el
624
655
  * @param {string} classNames
625
656
  */
626
- const removeClass = (el, classNames) => {
657
+ function removeClass(el, classNames) {
627
658
  eachClassName(classNames, className => el.classList.remove(className));
628
- };
659
+ }
629
660
  /**
630
661
  * 设置元素样式
631
662
  * @param {HTMLElement} el
@@ -644,12 +675,14 @@
644
675
  };
645
676
  /**
646
677
  * 获取元素样式
647
- * @param {HTMLElement} el
678
+ * @param {HTMLElement} el 元素
648
679
  * @param {string} key
649
680
  * @returns {string}
650
681
  */
651
- const getStyle = (el, key) => getComputedStyle(el).getPropertyValue(key);
652
- async function smoothScroll(options) {
682
+ function getStyle(el, key) {
683
+ return getComputedStyle(el).getPropertyValue(key);
684
+ }
685
+ function smoothScroll(options) {
653
686
  return new Promise(resolve => {
654
687
  const defaults = {
655
688
  el: document,
@@ -729,7 +762,7 @@
729
762
  * @param {HTMLElement} el
730
763
  * @param {string} property
731
764
  * @param {boolean} reNumber
732
- * @return {string|number}
765
+ * @returns {string|number}
733
766
  */
734
767
  function getComputedCssVal(el, property, reNumber = true) {
735
768
  const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
@@ -748,7 +781,7 @@
748
781
  * 复制文本
749
782
  * @param {string} text
750
783
  */
751
- const copyText = (text) => {
784
+ function copyText(text) {
752
785
  textEl.value = text;
753
786
  textEl.focus({ preventScroll: true });
754
787
  textEl.select();
@@ -759,14 +792,14 @@
759
792
  catch (err) {
760
793
  // ignore
761
794
  }
762
- };
795
+ }
763
796
 
764
797
  /**
765
798
  * 获取cookie
766
799
  * @param {string} name
767
800
  * @returns {string}
768
801
  */
769
- const cookieGet = (name) => {
802
+ function cookieGet(name) {
770
803
  const { cookie } = document;
771
804
  if (!cookie)
772
805
  return '';
@@ -778,14 +811,14 @@
778
811
  return decodeURIComponent(val);
779
812
  }
780
813
  return '';
781
- };
814
+ }
782
815
  /**
783
816
  * 设置 cookie
784
817
  * @param {string} name
785
818
  * @param {string} value
786
819
  * @param {number | Date} [maxAge]
787
820
  */
788
- const cookieSet = (name, value, maxAge) => {
821
+ function cookieSet(name, value, maxAge) {
789
822
  const metas = [];
790
823
  const EXPIRES = 'expires';
791
824
  metas.push([name, encodeURIComponent(value)]);
@@ -804,13 +837,66 @@
804
837
  return `${key}=${val}`;
805
838
  })
806
839
  .join(';');
807
- };
840
+ }
808
841
  /**
809
842
  * 删除单个 cookie
810
843
  * @param name cookie 名称
811
844
  */
812
845
  const cookieDel = (name) => cookieSet(name, '', -1);
813
846
 
847
+ const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
848
+ /* istanbul ignore next */
849
+ const guessDateSeparator = (value) => {
850
+ if (!isString(value))
851
+ return;
852
+ const value2 = value.replace(/-/g, '/');
853
+ return new Date(value2);
854
+ };
855
+ /* istanbul ignore next */
856
+ const guessDateTimezone = (value) => {
857
+ if (!isString(value))
858
+ return;
859
+ const re = /([+-])(\d\d)(\d\d)$/;
860
+ const matches = re.exec(value);
861
+ if (!matches)
862
+ return;
863
+ const value2 = value.replace(re, 'Z');
864
+ const d = new Date(value2);
865
+ if (!isValidDate(d))
866
+ return;
867
+ const [, flag, hours, minutes] = matches;
868
+ const hours2 = parseInt(hours, 10);
869
+ const minutes2 = parseInt(minutes, 10);
870
+ const offset = (a, b) => (flag === '+' ? a - b : a + b);
871
+ d.setHours(offset(d.getHours(), hours2));
872
+ d.setMinutes(offset(d.getMinutes(), minutes2));
873
+ return d;
874
+ };
875
+ /**
876
+ * 解析为Date对象
877
+ * @param {DateValue} value - 可以是数值、字符串或 Date 对象
878
+ * @returns {Date} - 转换后的目标Date
879
+ */
880
+ function dateParse(value) {
881
+ const d1 = new Date(value);
882
+ if (isValidDate(d1))
883
+ return d1;
884
+ // safari 浏览器的日期解析有问题
885
+ // new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
886
+ /* istanbul ignore next */
887
+ const d2 = guessDateSeparator(value);
888
+ /* istanbul ignore next */
889
+ if (isValidDate(d2))
890
+ return d2;
891
+ // safari 浏览器的日期解析有问题
892
+ // new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
893
+ /* istanbul ignore next */
894
+ const d3 = guessDateTimezone(value);
895
+ /* istanbul ignore next */
896
+ if (isValidDate(d3))
897
+ return d3;
898
+ throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
899
+ }
814
900
  /**
815
901
  * 格式化为日期对象(带自定义格式化模板)
816
902
  * @param {DateValue} value 可以是数值、字符串或 Date 对象
@@ -825,10 +911,69 @@
825
911
  * - mm:分
826
912
  * - ss:秒
827
913
  * - SSS:毫秒
828
- * - ww: 周
829
914
  * @returns {string}
830
915
  */
831
- const formatDate = (date = new Date(), format = 'YYYY-MM-DD HH:mm:ss') => {
916
+ // export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
917
+ // const date = dateParse(value);
918
+ // let fmt = format;
919
+ // let ret;
920
+ // const opt: DateObj = {
921
+ // 'Y+': `${date.getFullYear()}`, // 年
922
+ // 'y+': `${date.getFullYear()}`, // 年
923
+ // 'M+': `${date.getMonth() + 1}`, // 月
924
+ // 'D+': `${date.getDate()}`, // 日
925
+ // 'd+': `${date.getDate()}`, // 日
926
+ // 'H+': `${date.getHours()}`, // 时
927
+ // 'm+': `${date.getMinutes()}`, // 分
928
+ // 's+': `${date.getSeconds()}`, // 秒
929
+ // 'S+': `${date.getMilliseconds()}` // 豪秒
930
+ // };
931
+ // for (const k in opt) {
932
+ // ret = new RegExp(`(${k})`).exec(fmt);
933
+ // if (ret) {
934
+ // fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
935
+ // }
936
+ // }
937
+ // return fmt;
938
+ // };
939
+ /**
940
+ * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
941
+ * @param {DateValue} value
942
+ * @returns {Date}
943
+ */
944
+ function dateToStart(value) {
945
+ const d = dateParse(value);
946
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
947
+ }
948
+ /**
949
+ * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
950
+ * @param {DateValue} value
951
+ * @returns {Date}
952
+ */
953
+ function dateToEnd(value) {
954
+ const d = dateToStart(value);
955
+ d.setDate(d.getDate() + 1);
956
+ return dateParse(d.getTime() - 1);
957
+ }
958
+ /**
959
+ * 格式化为日期对象(带自定义格式化模板)
960
+ * @param {Date} value - 可以是数值、字符串或 Date 对象
961
+ * @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
962
+ * - YYYY:年
963
+ * - yyyy: 年
964
+ * - MM:月
965
+ * - DD:日
966
+ * - dd: 日
967
+ * - HH:时(24 小时制)
968
+ * - hh:时(12 小时制)
969
+ * - mm:分
970
+ * - ss:秒
971
+ * - SSS:毫秒
972
+ * - ww: 周
973
+ * @returns {string} 格式化后的日期字符串
974
+ */
975
+ function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
976
+ const date = dateParse(value);
832
977
  let fmt = format;
833
978
  let ret;
834
979
  const opt = {
@@ -851,13 +996,13 @@
851
996
  }
852
997
  }
853
998
  return fmt;
854
- };
999
+ }
855
1000
  /**
856
1001
  * 计算向前或向后N天的具体日期
857
- * @param {string} strDate 参考日期
858
- * @param {number} n 正数:向后推算;负数:向前推算
859
- * @param {string} sep 日期格式的分隔符
860
- * @return {*} 目标日期
1002
+ * @param {string} strDate - 参考日期
1003
+ * @param {number} n - 正数:向后推算;负数:向前推算
1004
+ * @param {string} sep - 日期格式的分隔符
1005
+ * @returns {string} 计算后的目标日期
861
1006
  */
862
1007
  function calculateDate(strDate, n, sep = '-') {
863
1008
  //strDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
@@ -875,10 +1020,10 @@
875
1020
  }
876
1021
  /**
877
1022
  * 计算向前或向后N天的具体时间日期
878
- * @param {number} n 正数:向后推算;负数:向前推算
879
- * @param {string} dateSep 日期分隔符
880
- * @param {string} timeSep 时间分隔符
881
- * @return {*}
1023
+ * @param {number} n - 正数:向后推算;负数:向前推算
1024
+ * @param {string} dateSep - 日期分隔符
1025
+ * @param {string} timeSep - 时间分隔符
1026
+ * @returns {string} 转换后的目标日期时间
882
1027
  */
883
1028
  function calculateDateTime(n, dateSep = '-', timeSep = ':') {
884
1029
  const date = new Date();
@@ -975,7 +1120,7 @@
975
1120
  * @param {string} queryString
976
1121
  * @returns {Params}
977
1122
  */
978
- const qsParse = (queryString) => {
1123
+ function qsParse(queryString) {
979
1124
  const params = new URLSearchParams(queryString);
980
1125
  const result = {};
981
1126
  for (const [key, val] of params.entries()) {
@@ -989,7 +1134,7 @@
989
1134
  result[key] = params.getAll(key);
990
1135
  }
991
1136
  return result;
992
- };
1137
+ }
993
1138
  const defaultReplacer = (val) => {
994
1139
  if (isString(val))
995
1140
  return val;
@@ -1007,7 +1152,7 @@
1007
1152
  * @param {Replacer} replacer
1008
1153
  * @returns {string}
1009
1154
  */
1010
- const qsStringify = (query, replacer = defaultReplacer) => {
1155
+ function qsStringify(query, replacer = defaultReplacer) {
1011
1156
  const params = new URLSearchParams();
1012
1157
  objectEach(query, (val, key) => {
1013
1158
  if (isArray(val)) {
@@ -1026,7 +1171,7 @@
1026
1171
  }
1027
1172
  });
1028
1173
  return params.toString();
1029
- };
1174
+ }
1030
1175
 
1031
1176
  const anchorEl = document.createElement('a');
1032
1177
  /**
@@ -1102,15 +1247,15 @@
1102
1247
  * @param {string} url
1103
1248
  * @param {LooseParams} params
1104
1249
  */
1105
- const downloadURL = (url, params) => {
1250
+ function downloadURL(url, params) {
1106
1251
  window.open(params ? urlSetParams(url, params) : url);
1107
- };
1252
+ }
1108
1253
  /**
1109
1254
  * 通过 A 链接的方式下载
1110
1255
  * @param {string} href
1111
1256
  * @param {string} filename
1112
1257
  */
1113
- const downloadHref = (href, filename) => {
1258
+ function downloadHref(href, filename) {
1114
1259
  const eleLink = document.createElement('a');
1115
1260
  eleLink.download = filename;
1116
1261
  eleLink.style.display = 'none';
@@ -1118,17 +1263,17 @@
1118
1263
  document.body.appendChild(eleLink);
1119
1264
  eleLink.click();
1120
1265
  setTimeout(() => document.body.removeChild(eleLink));
1121
- };
1266
+ }
1122
1267
  /**
1123
1268
  * 将大文件对象通过 A 链接的方式下载
1124
1269
  * @param {Blob} blob
1125
1270
  * @param {string} filename
1126
1271
  */
1127
- const downloadBlob = (blob, filename) => {
1272
+ function downloadBlob(blob, filename) {
1128
1273
  const objURL = URL.createObjectURL(blob);
1129
1274
  downloadHref(objURL, filename);
1130
1275
  setTimeout(() => URL.revokeObjectURL(objURL));
1131
- };
1276
+ }
1132
1277
  /**
1133
1278
  * 将指定数据格式通过 A 链接的方式下载
1134
1279
  * @param {AnyObject | AnyObject[]} data
@@ -1136,7 +1281,7 @@
1136
1281
  * @param {string} filename
1137
1282
  * @param {string[]} [headers]
1138
1283
  */
1139
- const downloadData = (data, fileType, filename, headers) => {
1284
+ function downloadData(data, fileType, filename, headers) {
1140
1285
  filename = filename.replace(`.${fileType}`, '') + `.${fileType}`;
1141
1286
  if (fileType === 'json') {
1142
1287
  const blob = new Blob([JSON.stringify(data, null, 4)]);
@@ -1163,14 +1308,14 @@
1163
1308
  const href = 'data:' + MIMETypes[fileType] + ';charset=utf-8,\ufeff' + encodeURIComponent(headerStr + bodyStr);
1164
1309
  downloadHref(href, filename);
1165
1310
  }
1166
- };
1311
+ }
1167
1312
 
1168
1313
  /**
1169
1314
  * 等待一段时间
1170
1315
  * @param {number} timeout 等待时间,单位毫秒
1171
1316
  * @returns {Promise<void>}
1172
1317
  */
1173
- async function wait(timeout = 1) {
1318
+ function wait(timeout = 1) {
1174
1319
  return new Promise(resolve => setTimeout(resolve, timeout));
1175
1320
  }
1176
1321
  /**
@@ -1182,7 +1327,7 @@
1182
1327
  * @param {number} concurrency 并发数量,默认无限
1183
1328
  * @returns {Promise<R[]>}
1184
1329
  */
1185
- async function asyncMap(list, mapper, concurrency = Infinity) {
1330
+ function asyncMap(list, mapper, concurrency = Infinity) {
1186
1331
  return new Promise((resolve, reject) => {
1187
1332
  const iterator = list[Symbol.iterator]();
1188
1333
  const limit = Math.min(list.length, concurrency);
@@ -1220,10 +1365,11 @@
1220
1365
 
1221
1366
  /**
1222
1367
  * 选择本地文件
1223
- * @param {function} changeCb 选择文件回调
1224
- * @return {*}
1368
+ * @param {string} accept 上传的文件类型,用于过滤
1369
+ * @param {Function} changeCb 选择文件回调
1370
+ * @returns {HTMLInputElement}
1225
1371
  */
1226
- function chooseLocalFile({ accept }, changeCb) {
1372
+ function chooseLocalFile(accept, changeCb) {
1227
1373
  const inputObj = document.createElement('input');
1228
1374
  inputObj.setAttribute('id', String(Date.now()));
1229
1375
  inputObj.setAttribute('type', 'file');
@@ -1246,17 +1392,18 @@
1246
1392
  * @desc 网页加水印的工具类
1247
1393
  */
1248
1394
  /**
1249
- * canvas 实现 watermark
1395
+ * canvas 实现 水印, 具备防删除功能
1250
1396
  * @param {ICanvasWM} canvasWM
1397
+ * @example genCanvasWM({ content: 'QQMusicFE' })
1251
1398
  */
1252
- const genCanvasWM = (canvasWM) => {
1399
+ function genCanvasWM(canvasWM) {
1253
1400
  const { container = document.body, width = '300px', height = '200px', textAlign = 'center', textBaseline = 'middle', font = '20px PingFangSC-Medium,PingFang SC',
1254
1401
  // fontWeight = 500,
1255
1402
  fillStyle = 'rgba(189, 177, 167, .3)', content = '请勿外传', rotate = 30, zIndex = 2147483647 } = canvasWM;
1256
1403
  // 仅限主页面添加水印
1257
- if (!location.pathname.includes('/home')) {
1258
- return;
1259
- }
1404
+ // if (!location.pathname.includes('/home')) {
1405
+ // return;
1406
+ // }
1260
1407
  const args = canvasWM;
1261
1408
  const canvas = document.createElement('canvas');
1262
1409
  canvas.setAttribute('width', width);
@@ -1317,9 +1464,7 @@
1317
1464
  });
1318
1465
  mo.observe(container, { attributes: true, subtree: true, childList: true });
1319
1466
  }
1320
- };
1321
- // 调用
1322
- // __canvasWM({ content: 'QQMusicFE' })
1467
+ }
1323
1468
 
1324
1469
  /**
1325
1470
  * 防抖函数
@@ -1537,7 +1682,7 @@
1537
1682
  * @param {string} [hexPool] 进制池,默认 62 进制
1538
1683
  * @returns {string}
1539
1684
  */
1540
- const numberToHex = (decimal, hexPool = HEX_POOL) => {
1685
+ function numberToHex(decimal, hexPool = HEX_POOL) {
1541
1686
  if (hexPool.length < 2)
1542
1687
  throw new Error('进制池长度不能少于 2');
1543
1688
  if (!supportBigInt) {
@@ -1559,7 +1704,7 @@
1559
1704
  };
1560
1705
  execute();
1561
1706
  return ret.join('');
1562
- };
1707
+ }
1563
1708
  /**
1564
1709
  * 缩写
1565
1710
  * @param {number | string} num
@@ -1586,7 +1731,7 @@
1586
1731
  * 将数字格式化成千位分隔符显示的字符串
1587
1732
  * @param {number} val 数字
1588
1733
  * @param {'int' | 'float'} type 展示分段显示的类型 int:整型 | float:浮点型
1589
- * @return {string}
1734
+ * @returns {string}
1590
1735
  */
1591
1736
  function formatNumber(val, type = 'int') {
1592
1737
  return type === 'int' ? parseInt(String(val)).toLocaleString() : Number(val).toLocaleString('en-US');
@@ -1660,6 +1805,113 @@
1660
1805
  return uniqueString;
1661
1806
  };
1662
1807
 
1808
+ /**
1809
+ * @title tooltip
1810
+ * @Desc 自定义的tooltip方法, 支持拖动悬浮提示
1811
+ * Created by chendeqiao on 2017/5/8.
1812
+ * @example
1813
+ * <span onmouseleave="handleMouseLeave('#root')" onmousemove="handleMouseEnter({rootElId: '#root', title: 'title content', event: event})"
1814
+ * onmouseenter="handleMouseEnter({'#root', title: 'title content', event: event})">title content </span>
1815
+ */
1816
+ /**
1817
+ * 自定义title提示功能的mouseenter事件句柄
1818
+ * @param {ITooltipParams} param1
1819
+ * @returns {*}
1820
+ */
1821
+ function handleMouseEnter({ rootElId = '#root', title, event }) {
1822
+ try {
1823
+ const $rootEl = document.querySelector(rootElId);
1824
+ console.assert($rootEl !== null, `未找到id为 ${rootElId} 的dom元素`);
1825
+ let $customTitle = null;
1826
+ // 动态创建class样式,并加入到head中
1827
+ if (!document.querySelector('.tooltip-inner1494304949567')) {
1828
+ const tooltipWrapperClass = document.createElement('style');
1829
+ tooltipWrapperClass.type = 'text/css';
1830
+ tooltipWrapperClass.innerHTML = `
1831
+ .tooltip-inner1494304949567 {
1832
+ max-width: 250px;
1833
+ padding: 3px 8px;
1834
+ color: #fff;
1835
+ text-decoration: none;
1836
+ border-radius: 4px;
1837
+ text-align: left;
1838
+ }
1839
+ `;
1840
+ document.querySelector('head').appendChild(tooltipWrapperClass);
1841
+ }
1842
+ if (document.querySelector('#customTitle1494304949567')) {
1843
+ $customTitle = document.querySelector('#customTitle1494304949567');
1844
+ mouseenter($customTitle, title, event);
1845
+ }
1846
+ else {
1847
+ const $contentContainer = document.createElement('div');
1848
+ $contentContainer.className = 'customTitle';
1849
+ $contentContainer.id = 'customTitle1494304949567';
1850
+ $contentContainer.className = 'tooltip';
1851
+ $contentContainer.style.cssText = 'z-index: 99999999; visibility: hidden;';
1852
+ $contentContainer.innerHTML =
1853
+ '<div class="tooltip-inner1494304949567" style="word-wrap: break-word; max-width: 44px;">皮肤</div>';
1854
+ $rootEl.appendChild($contentContainer);
1855
+ $customTitle = document.querySelector('#customTitle1494304949567');
1856
+ if (title) {
1857
+ //判断div显示的内容是否为空
1858
+ mouseenter($customTitle, title, event);
1859
+ $customTitle.style.visibility = 'visible';
1860
+ }
1861
+ }
1862
+ }
1863
+ catch (e) {
1864
+ console.error(e.message);
1865
+ }
1866
+ }
1867
+ /**
1868
+ * 提示文案dom渲染的处理函数
1869
+ * @param {HTMLDivElement} customTitle
1870
+ * @param {string} title 提示的字符串
1871
+ * @param {PointerEvent} e 事件对象
1872
+ * @returns {*}
1873
+ */
1874
+ function mouseenter($customTitle, title, e) {
1875
+ let diffValueX = 200 + 50; //默认设置弹出div的宽度为250px
1876
+ let x = 13;
1877
+ const y = 23;
1878
+ const $contentEle = $customTitle.children[0];
1879
+ if (getStrWidthPx(title, 12) < 180 + 50) {
1880
+ //【弹出div自适应字符串宽度】若显示的字符串占用宽度小于180,则设置弹出div的宽度为“符串占用宽度”+20
1881
+ $contentEle.style.maxWidth = getStrWidthPx(title, 12) + 20 + 50 + 'px';
1882
+ diffValueX = e.clientX + (getStrWidthPx(title, 12) + 50) - document.body.offsetWidth;
1883
+ }
1884
+ else {
1885
+ $contentEle.style.maxWidth = '250px';
1886
+ diffValueX = e.clientX + 230 - document.body.offsetWidth; //计算div水平方向显示的内容超出屏幕多少宽度
1887
+ }
1888
+ $contentEle.innerHTML = title; //html方法可解析内容中换行标签,text方法不能
1889
+ if (diffValueX > 0) {
1890
+ //水平方向超出可见区域时
1891
+ x -= diffValueX;
1892
+ }
1893
+ $customTitle.style.top = e.clientY + y + 'px';
1894
+ $customTitle.style.left = e.clientX + x + 'px';
1895
+ $customTitle.style.maxWidth = '250px';
1896
+ const diffValueY = $customTitle.getBoundingClientRect().top + $contentEle.offsetHeight - document.body.offsetHeight;
1897
+ if (diffValueY > 0) {
1898
+ //垂直方向超出可见区域时
1899
+ $customTitle.style.top = e.clientY - diffValueY + 'px';
1900
+ }
1901
+ }
1902
+ /**
1903
+ * 移除提示文案dom的事件句柄
1904
+ * @param {string} rootElId
1905
+ * @returns {*}
1906
+ */
1907
+ function handleMouseLeave(rootElId = '#root') {
1908
+ const rootEl = document.querySelector(rootElId), titleEl = document.querySelector('#customTitle1494304949567');
1909
+ if (rootEl && titleEl) {
1910
+ rootEl.removeChild(titleEl);
1911
+ }
1912
+ }
1913
+ const tooltipEvent = { handleMouseEnter, handleMouseLeave };
1914
+
1663
1915
  exports.HEX_POOL = HEX_POOL;
1664
1916
  exports.STRING_ARABIC_NUMERALS = STRING_ARABIC_NUMERALS;
1665
1917
  exports.STRING_LOWERCASE_ALPHA = STRING_LOWERCASE_ALPHA;
@@ -1681,12 +1933,15 @@
1681
1933
  exports.cookieGet = cookieGet;
1682
1934
  exports.cookieSet = cookieSet;
1683
1935
  exports.copyText = copyText;
1936
+ exports.dateParse = dateParse;
1937
+ exports.dateToEnd = dateToEnd;
1938
+ exports.dateToStart = dateToStart;
1684
1939
  exports.debounce = debounce;
1685
- exports.deepTraversal = deepTraversal;
1686
1940
  exports.downloadBlob = downloadBlob;
1687
1941
  exports.downloadData = downloadData;
1688
1942
  exports.downloadHref = downloadHref;
1689
1943
  exports.downloadURL = downloadURL;
1944
+ exports.forEachDeep = forEachDeep;
1690
1945
  exports.formatDate = formatDate;
1691
1946
  exports.formatNumber = formatNumber;
1692
1947
  exports.genCanvasWM = genCanvasWM;
@@ -1694,7 +1949,6 @@
1694
1949
  exports.getGlobal = getGlobal;
1695
1950
  exports.getStrWidthPx = getStrWidthPx;
1696
1951
  exports.getStyle = getStyle;
1697
- exports.getTreeIds = getTreeIds;
1698
1952
  exports.hasClass = hasClass;
1699
1953
  exports.isArray = isArray;
1700
1954
  exports.isBigInt = isBigInt;
@@ -1713,6 +1967,7 @@
1713
1967
  exports.isString = isString;
1714
1968
  exports.isSymbol = isSymbol;
1715
1969
  exports.isUndefined = isUndefined;
1970
+ exports.isValidDate = isValidDate;
1716
1971
  exports.numberAbbr = numberAbbr;
1717
1972
  exports.numberToHex = numberToHex;
1718
1973
  exports.objectAssign = objectAssign;
@@ -1735,6 +1990,7 @@
1735
1990
  exports.randomString = randomString;
1736
1991
  exports.randomUuid = randomUuid;
1737
1992
  exports.removeClass = removeClass;
1993
+ exports.searchTreeById = searchTreeById;
1738
1994
  exports.setGlobal = setGlobal;
1739
1995
  exports.setStyle = setStyle;
1740
1996
  exports.smoothScroll = smoothScroll;
@@ -1745,6 +2001,7 @@
1745
2001
  exports.stringFormat = stringFormat;
1746
2002
  exports.stringKebabCase = stringKebabCase;
1747
2003
  exports.throttle = throttle;
2004
+ exports.tooltipEvent = tooltipEvent;
1748
2005
  exports.typeIs = typeIs;
1749
2006
  exports.uniqueNumber = uniqueNumber;
1750
2007
  exports.uniqueString = uniqueString;