sculp-js 1.8.4 → 1.10.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 (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 +7 -11
  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 +11 -7
  9. package/lib/cjs/download.js +1 -1
  10. package/lib/cjs/easing.js +1 -1
  11. package/lib/cjs/file.js +1 -1
  12. package/lib/cjs/func.js +1 -1
  13. package/lib/cjs/index.js +3 -1
  14. package/lib/cjs/isEqual.js +1 -1
  15. package/lib/cjs/math.js +1 -1
  16. package/lib/cjs/number.js +55 -12
  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 +20 -16
  23. package/lib/cjs/tree.js +2 -2
  24. package/lib/cjs/type.js +1 -1
  25. package/lib/cjs/unique.js +1 -1
  26. package/lib/cjs/url.js +15 -5
  27. package/lib/cjs/validator.js +1 -1
  28. package/lib/cjs/variable.js +1 -1
  29. package/lib/cjs/watermark.js +20 -19
  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 +7 -11
  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 +11 -7
  39. package/lib/es/download.js +1 -1
  40. package/lib/es/easing.js +1 -1
  41. package/lib/es/file.js +1 -1
  42. package/lib/es/func.js +1 -1
  43. package/lib/es/index.js +2 -2
  44. package/lib/es/isEqual.js +1 -1
  45. package/lib/es/math.js +1 -1
  46. package/lib/es/number.js +54 -13
  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 +20 -16
  53. package/lib/es/tree.js +2 -2
  54. package/lib/es/type.js +1 -1
  55. package/lib/es/unique.js +1 -1
  56. package/lib/es/url.js +15 -5
  57. package/lib/es/validator.js +1 -1
  58. package/lib/es/variable.js +1 -1
  59. package/lib/es/watermark.js +20 -19
  60. package/lib/es/we-decode.js +1 -1
  61. package/lib/index.d.ts +55 -29
  62. package/lib/umd/index.js +607 -552
  63. package/package.json +2 -1
package/lib/umd/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.8.4
2
+ * sculp-js v1.10.0
3
3
  * (c) 2023-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -97,6 +97,29 @@
97
97
  return array;
98
98
  }
99
99
 
100
+ /**
101
+ * 复制文本
102
+ * @param {string} text
103
+ */
104
+ function copyText(text) {
105
+ const textEl = document.createElement('textarea');
106
+ textEl.style.position = 'absolute';
107
+ textEl.style.top = '-9999px';
108
+ textEl.style.left = '-9999px';
109
+ textEl.value = text;
110
+ document.body.appendChild(textEl);
111
+ textEl.focus({ preventScroll: true });
112
+ textEl.select();
113
+ try {
114
+ document.execCommand('copy');
115
+ textEl.blur();
116
+ document.body.removeChild(textEl);
117
+ }
118
+ catch (err) {
119
+ // ignore
120
+ }
121
+ }
122
+
100
123
  // 常用类型定义
101
124
  /**
102
125
  * 判断对象内是否有该静态属性
@@ -213,123 +236,373 @@
213
236
  return !Object.keys(value).length;
214
237
  }
215
238
 
216
- // @ref https://cubic-bezier.com/
217
- const easingDefines = {
218
- linear: [0, 0, 1, 1],
219
- ease: [0.25, 0.1, 0.25, 1],
220
- 'ease-in': [0.42, 0, 1, 1],
221
- 'ease-out': [0, 0, 0.58, 1],
222
- 'ease-in-out': [0.42, 0, 0.58, 1]
223
- };
224
239
  /**
225
- * 缓冲函数化,用于 js 计算缓冲进度
226
- * @param {EasingNameOrDefine} [name=linear]
227
- * @returns {EasingFunction}
240
+ * 获取cookie
241
+ * @param {string} name
242
+ * @returns {string}
228
243
  */
229
- function easingFunctional(name) {
230
- let fn;
231
- if (isArray(name)) {
232
- fn = bezier(...name);
244
+ function cookieGet(name) {
245
+ const { cookie } = document;
246
+ if (!cookie)
247
+ return '';
248
+ const result = cookie.split(';');
249
+ for (let i = 0; i < result.length; i++) {
250
+ const item = result[i];
251
+ const [key, val = ''] = item.split('=');
252
+ if (key === name)
253
+ return decodeURIComponent(val);
233
254
  }
234
- else {
235
- const define = easingDefines[name];
236
- if (!define) {
237
- throw new Error(`${name} 缓冲函数未定义`);
238
- }
239
- fn = bezier(...define);
255
+ return '';
256
+ }
257
+ /**
258
+ * 设置 cookie
259
+ * @param {string} name
260
+ * @param {string} value
261
+ * @param {number | Date} [maxAge]
262
+ */
263
+ function cookieSet(name, value, maxAge) {
264
+ const metas = [];
265
+ const EXPIRES = 'expires';
266
+ metas.push([name, encodeURIComponent(value)]);
267
+ if (isNumber(maxAge)) {
268
+ const d = new Date();
269
+ d.setTime(d.getTime() + maxAge);
270
+ metas.push([EXPIRES, d.toUTCString()]);
240
271
  }
241
- return (input) => fn(Math.max(0, Math.min(input, 1)));
272
+ else if (isDate(maxAge)) {
273
+ metas.push([EXPIRES, maxAge.toUTCString()]);
274
+ }
275
+ metas.push(['path', '/']);
276
+ document.cookie = metas
277
+ .map(item => {
278
+ const [key, val] = item;
279
+ return `${key}=${val}`;
280
+ })
281
+ .join(';');
242
282
  }
243
-
244
283
  /**
245
- * 判断对象是否为纯对象
246
- * @param {object} obj
247
- * @returns {boolean}
284
+ * 删除单个 cookie
285
+ * @param name cookie 名称
248
286
  */
249
- const isPlainObject = (obj) => {
250
- if (!isObject(obj))
251
- return false;
252
- const proto = Object.getPrototypeOf(obj);
253
- // 对象无原型
254
- if (!proto)
255
- return true;
256
- // 是否对象直接实例
257
- return proto === Object.prototype;
287
+ const cookieDel = (name) => cookieSet(name, '', -1);
288
+
289
+ const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
290
+ /* istanbul ignore next */
291
+ const guessDateSeparator = (value) => {
292
+ if (!isString(value))
293
+ return;
294
+ const value2 = value.replace(/-/g, '/');
295
+ return new Date(value2);
296
+ };
297
+ /* istanbul ignore next */
298
+ const guessDateTimezone = (value) => {
299
+ if (!isString(value))
300
+ return;
301
+ const re = /([+-])(\d\d)(\d\d)$/;
302
+ const matches = re.exec(value);
303
+ if (!matches)
304
+ return;
305
+ const value2 = value.replace(re, 'Z');
306
+ const d = new Date(value2);
307
+ if (!isValidDate(d))
308
+ return;
309
+ const [, flag, hours, minutes] = matches;
310
+ const hours2 = parseInt(hours, 10);
311
+ const minutes2 = parseInt(minutes, 10);
312
+ const offset = (a, b) => (flag === '+' ? a - b : a + b);
313
+ d.setHours(offset(d.getHours(), hours2));
314
+ d.setMinutes(offset(d.getMinutes(), minutes2));
315
+ return d;
258
316
  };
259
317
  /**
260
- * 遍历对象,返回 false 中断遍历
261
- * @param {O} obj
262
- * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
318
+ * 解析为Date对象
319
+ * @param {DateValue} value - 可以是数值、字符串或 Date 对象
320
+ * @returns {Date} - 转换后的目标Date
263
321
  */
264
- function objectEach(obj, iterator) {
265
- for (const key in obj) {
266
- if (!objectHas(obj, key))
267
- continue;
268
- if (iterator(obj[key], key) === false)
269
- break;
270
- }
322
+ function dateParse(value) {
323
+ const d1 = new Date(value);
324
+ if (isValidDate(d1))
325
+ return d1;
326
+ // safari 浏览器的日期解析有问题
327
+ // new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
328
+ /* istanbul ignore next */
329
+ const d2 = guessDateSeparator(value);
330
+ /* istanbul ignore next */
331
+ if (isValidDate(d2))
332
+ return d2;
333
+ // safari 浏览器的日期解析有问题
334
+ // new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
335
+ /* istanbul ignore next */
336
+ const d3 = guessDateTimezone(value);
337
+ /* istanbul ignore next */
338
+ if (isValidDate(d3))
339
+ return d3;
340
+ throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
271
341
  }
272
342
  /**
273
- * 异步遍历对象,返回 false 中断遍历
274
- * @param {O} obj
275
- * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
343
+ * 格式化为日期对象(带自定义格式化模板)
344
+ * @param {DateValue} value 可以是数值、字符串或 Date 对象
345
+ * @param {string} [format] 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
346
+ * - YYYY:年
347
+ * - yyyy: 年
348
+ * - MM:月
349
+ * - DD:日
350
+ * - dd: 日
351
+ * - HH:时(24 小时制)
352
+ * - hh:时(12 小时制)
353
+ * - mm:分
354
+ * - ss:秒
355
+ * - SSS:毫秒
356
+ * @returns {string}
276
357
  */
277
- async function objectEachAsync(obj, iterator) {
278
- for (const key in obj) {
279
- if (!objectHas(obj, key))
280
- continue;
281
- if ((await iterator(obj[key], key)) === false)
282
- break;
283
- }
284
- }
358
+ // export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
359
+ // const date = dateParse(value);
360
+ // let fmt = format;
361
+ // let ret;
362
+ // const opt: DateObj = {
363
+ // 'Y+': `${date.getFullYear()}`, // 年
364
+ // 'y+': `${date.getFullYear()}`, // 年
365
+ // 'M+': `${date.getMonth() + 1}`, // 月
366
+ // 'D+': `${date.getDate()}`, // 日
367
+ // 'd+': `${date.getDate()}`, // 日
368
+ // 'H+': `${date.getHours()}`, // 时
369
+ // 'm+': `${date.getMinutes()}`, // 分
370
+ // 's+': `${date.getSeconds()}`, // 秒
371
+ // 'S+': `${date.getMilliseconds()}` // 豪秒
372
+ // };
373
+ // for (const k in opt) {
374
+ // ret = new RegExp(`(${k})`).exec(fmt);
375
+ // if (ret) {
376
+ // fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
377
+ // }
378
+ // }
379
+ // return fmt;
380
+ // };
285
381
  /**
286
- * 对象映射
287
- * @param {O} obj
288
- * @param {(val: O[keyof O], key: Extract<keyof O, string>) => any} iterator
289
- * @returns {Record<Extract<keyof O, string>, T>}
382
+ * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
383
+ * @param {DateValue} value
384
+ * @returns {Date}
290
385
  */
291
- function objectMap(obj, iterator) {
292
- const obj2 = {};
293
- for (const key in obj) {
294
- if (!objectHas(obj, key))
295
- continue;
296
- obj2[key] = iterator(obj[key], key);
297
- }
298
- return obj2;
386
+ function dateToStart(value) {
387
+ const d = dateParse(value);
388
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
299
389
  }
300
390
  /**
301
- * 对象提取
302
- * @param {O} obj
303
- * @param {K} keys
304
- * @returns {Pick<O, ArrayElements<K>>}
391
+ * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
392
+ * @param {DateValue} value
393
+ * @returns {Date}
305
394
  */
306
- function objectPick(obj, keys) {
307
- const obj2 = {};
308
- objectEach(obj, (v, k) => {
309
- if (keys.includes(k)) {
310
- // @ts-ignore
311
- obj2[k] = v;
312
- }
313
- });
314
- return obj2;
395
+ function dateToEnd(value) {
396
+ const d = dateToStart(value);
397
+ d.setDate(d.getDate() + 1);
398
+ return dateParse(d.getTime() - 1);
315
399
  }
316
400
  /**
317
- * 对象去除
318
- * @param {O} obj
319
- * @param {K} keys
320
- * @returns {Pick<O, ArrayElements<K>>}
321
- */
322
- function objectOmit(obj, keys) {
323
- const obj2 = {};
324
- objectEach(obj, (v, k) => {
325
- if (!keys.includes(k)) {
326
- // @ts-ignore
327
- obj2[k] = v;
328
- }
329
- });
330
- return obj2;
331
- }
332
- const merge = (map, source, target) => {
401
+ * 格式化为日期对象(带自定义格式化模板)
402
+ * @param {Date} value - 可以是数值、字符串或 Date 对象
403
+ * @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
404
+ * - YYYY:年
405
+ * - yyyy: 年
406
+ * - MM:月
407
+ * - DD:日
408
+ * - dd:
409
+ * - HH:时(24 小时制)
410
+ * - mm:分
411
+ * - ss:秒
412
+ * - SSS:毫秒
413
+ * - ww: 周
414
+ * @returns {string} 格式化后的日期字符串
415
+ */
416
+ function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
417
+ const date = dateParse(value);
418
+ let fmt = format;
419
+ let ret;
420
+ const opt = {
421
+ 'Y+': `${date.getFullYear()}`,
422
+ 'y+': `${date.getFullYear()}`,
423
+ 'M+': `${date.getMonth() + 1}`,
424
+ 'D+': `${date.getDate()}`,
425
+ 'd+': `${date.getDate()}`,
426
+ 'H+': `${date.getHours()}`,
427
+ 'm+': `${date.getMinutes()}`,
428
+ 's+': `${date.getSeconds()}`,
429
+ 'S+': `${date.getMilliseconds()}`,
430
+ 'w+': ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()] // 周
431
+ // 有其他格式化字符需求可以继续添加,必须转化成字符串
432
+ };
433
+ for (const k in opt) {
434
+ ret = new RegExp('(' + k + ')').exec(fmt);
435
+ if (ret) {
436
+ fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
437
+ }
438
+ }
439
+ return fmt;
440
+ }
441
+ /**
442
+ * 计算向前或向后N天的具体日期
443
+ * @param {DateValue} originDate - 参考日期
444
+ * @param {number} n - 正数:向后推算;负数:向前推算
445
+ * @param {string} sep - 日期格式的分隔符
446
+ * @returns {string} 计算后的目标日期
447
+ */
448
+ function calculateDate(originDate, n, sep = '-') {
449
+ //originDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
450
+ const date = new Date(originDate); //这边给定一个特定时间
451
+ const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
452
+ const millisecondGap = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
453
+ const targetDate = new Date(millisecondGap);
454
+ const finalNewDate = targetDate.getFullYear() +
455
+ sep +
456
+ String(targetDate.getMonth() + 1).padStart(2, '0') +
457
+ '-' +
458
+ String(targetDate.getDate()).padStart(2, '0');
459
+ return finalNewDate;
460
+ }
461
+ /**
462
+ * 计算向前或向后N天的具体日期时间
463
+ * @param {DateValue} originDateTime - 参考日期时间
464
+ * @param {number} n - 正数:向后推算;负数:向前推算
465
+ * @param {string} dateSep - 日期分隔符
466
+ * @param {string} timeSep - 时间分隔符
467
+ * @returns {string} 转换后的目标日期时间
468
+ */
469
+ function calculateDateTime(originDateTime, n, dateSep = '-', timeSep = ':') {
470
+ const date = new Date(originDateTime);
471
+ const separator1 = dateSep;
472
+ const separator2 = timeSep;
473
+ const dateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
474
+ const millisecondGap = dateTime.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
475
+ const targetDateTime = new Date(millisecondGap);
476
+ return (targetDateTime.getFullYear() +
477
+ separator1 +
478
+ String(targetDateTime.getMonth() + 1).padStart(2, '0') +
479
+ separator1 +
480
+ String(targetDateTime.getDate()).padStart(2, '0') +
481
+ ' ' +
482
+ String(targetDateTime.getHours()).padStart(2, '0') +
483
+ separator2 +
484
+ String(targetDateTime.getMinutes()).padStart(2, '0') +
485
+ separator2 +
486
+ String(targetDateTime.getSeconds()).padStart(2, '0'));
487
+ }
488
+
489
+ // @ref https://cubic-bezier.com/
490
+ const easingDefines = {
491
+ linear: [0, 0, 1, 1],
492
+ ease: [0.25, 0.1, 0.25, 1],
493
+ 'ease-in': [0.42, 0, 1, 1],
494
+ 'ease-out': [0, 0, 0.58, 1],
495
+ 'ease-in-out': [0.42, 0, 0.58, 1]
496
+ };
497
+ /**
498
+ * 缓冲函数化,用于 js 计算缓冲进度
499
+ * @param {EasingNameOrDefine} [name=linear]
500
+ * @returns {EasingFunction}
501
+ */
502
+ function easingFunctional(name) {
503
+ let fn;
504
+ if (isArray(name)) {
505
+ fn = bezier(...name);
506
+ }
507
+ else {
508
+ const define = easingDefines[name];
509
+ if (!define) {
510
+ throw new Error(`${name} 缓冲函数未定义`);
511
+ }
512
+ fn = bezier(...define);
513
+ }
514
+ return (input) => fn(Math.max(0, Math.min(input, 1)));
515
+ }
516
+
517
+ /**
518
+ * 判断对象是否为纯对象
519
+ * @param {object} obj
520
+ * @returns {boolean}
521
+ */
522
+ const isPlainObject = (obj) => {
523
+ if (!isObject(obj))
524
+ return false;
525
+ const proto = Object.getPrototypeOf(obj);
526
+ // 对象无原型
527
+ if (!proto)
528
+ return true;
529
+ // 是否对象直接实例
530
+ return proto === Object.prototype;
531
+ };
532
+ /**
533
+ * 遍历对象,返回 false 中断遍历
534
+ * @param {O} obj
535
+ * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
536
+ */
537
+ function objectEach(obj, iterator) {
538
+ for (const key in obj) {
539
+ if (!objectHas(obj, key))
540
+ continue;
541
+ if (iterator(obj[key], key) === false)
542
+ break;
543
+ }
544
+ }
545
+ /**
546
+ * 异步遍历对象,返回 false 中断遍历
547
+ * @param {O} obj
548
+ * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
549
+ */
550
+ async function objectEachAsync(obj, iterator) {
551
+ for (const key in obj) {
552
+ if (!objectHas(obj, key))
553
+ continue;
554
+ if ((await iterator(obj[key], key)) === false)
555
+ break;
556
+ }
557
+ }
558
+ /**
559
+ * 对象映射
560
+ * @param {O} obj
561
+ * @param {(val: O[keyof O], key: Extract<keyof O, string>) => any} iterator
562
+ * @returns {Record<Extract<keyof O, string>, T>}
563
+ */
564
+ function objectMap(obj, iterator) {
565
+ const obj2 = {};
566
+ for (const key in obj) {
567
+ if (!objectHas(obj, key))
568
+ continue;
569
+ obj2[key] = iterator(obj[key], key);
570
+ }
571
+ return obj2;
572
+ }
573
+ /**
574
+ * 对象提取
575
+ * @param {O} obj
576
+ * @param {K} keys
577
+ * @returns {Pick<O, ArrayElements<K>>}
578
+ */
579
+ function objectPick(obj, keys) {
580
+ const obj2 = {};
581
+ objectEach(obj, (v, k) => {
582
+ if (keys.includes(k)) {
583
+ // @ts-ignore
584
+ obj2[k] = v;
585
+ }
586
+ });
587
+ return obj2;
588
+ }
589
+ /**
590
+ * 对象去除
591
+ * @param {O} obj
592
+ * @param {K} keys
593
+ * @returns {Pick<O, ArrayElements<K>>}
594
+ */
595
+ function objectOmit(obj, keys) {
596
+ const obj2 = {};
597
+ objectEach(obj, (v, k) => {
598
+ if (!keys.includes(k)) {
599
+ // @ts-ignore
600
+ obj2[k] = v;
601
+ }
602
+ });
603
+ return obj2;
604
+ }
605
+ const merge = (map, source, target) => {
333
606
  if (isUndefined(target))
334
607
  return source;
335
608
  const sourceType = typeIs(source);
@@ -570,427 +843,156 @@
570
843
  return queryObj;
571
844
  }
572
845
 
573
- /**
574
- * 判断元素是否包含某个样式名
575
- * @param {HTMLElement} el
576
- * @param {string} className
577
- * @returns {boolean}
578
- */
579
- function hasClass(el, className) {
580
- if (className.indexOf(' ') !== -1)
581
- throw new Error('className should not contain space.');
582
- return el.classList.contains(className);
583
- }
584
- const eachClassName = (classNames, func) => {
585
- const classNameList = classNames.split(/\s+/g);
586
- classNameList.forEach(func);
587
- };
588
- /**
589
- * 给元素增加样式名
590
- * @param {HTMLElement} el
591
- * @param {string} classNames
592
- */
593
- function addClass(el, classNames) {
594
- eachClassName(classNames, className => el.classList.add(className));
595
- }
596
- /**
597
- * 给元素移除样式名
598
- * @param {HTMLElement} el
599
- * @param {string} classNames
600
- */
601
- function removeClass(el, classNames) {
602
- eachClassName(classNames, className => el.classList.remove(className));
603
- }
604
- /**
605
- * 设置元素样式
606
- * @param {HTMLElement} el
607
- * @param {string | Style} key
608
- * @param {string} val
609
- */
610
- const setStyle = (el, key, val) => {
611
- if (isObject(key)) {
612
- objectEach(key, (val1, key1) => {
613
- setStyle(el, key1, val1);
614
- });
615
- }
616
- else {
617
- el.style.setProperty(stringKebabCase(key), val);
618
- }
619
- };
620
- /**
621
- * 获取元素样式
622
- * @param {HTMLElement} el 元素
623
- * @param {string} key
624
- * @returns {string}
625
- */
626
- function getStyle(el, key) {
627
- return getComputedStyle(el).getPropertyValue(key);
628
- }
629
- function smoothScroll(options) {
630
- return new Promise(resolve => {
631
- const defaults = {
632
- el: document,
633
- to: 0,
634
- duration: 567,
635
- easing: 'ease'
636
- };
637
- const { el, to, duration, easing } = objectAssign(defaults, options);
638
- const htmlEl = document.documentElement;
639
- const bodyEl = document.body;
640
- const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
641
- const els = globalMode ? [htmlEl, bodyEl] : [el];
642
- const query = () => {
643
- let value = 0;
644
- arrayEach(els, el => {
645
- if ('scrollTop' in el) {
646
- value = el.scrollTop;
647
- return false;
648
- }
649
- });
650
- return value;
651
- };
652
- const update = (val) => {
653
- els.forEach(el => {
654
- if ('scrollTop' in el) {
655
- el.scrollTop = val;
656
- }
657
- });
658
- };
659
- let startTime;
660
- const startValue = query();
661
- const length = to - startValue;
662
- const easingFn = easingFunctional(easing);
663
- const render = () => {
664
- const now = performance.now();
665
- const passingTime = startTime ? now - startTime : 0;
666
- const t = passingTime / duration;
667
- const p = easingFn(t);
668
- if (!startTime)
669
- startTime = now;
670
- update(startValue + length * p);
671
- if (t >= 1)
672
- resolve();
673
- else
674
- requestAnimationFrame(render);
675
- };
676
- render();
677
- });
678
- }
679
- /**
680
- * 获取元素样式属性的计算值
681
- * @param {HTMLElement} el
682
- * @param {string} property
683
- * @param {boolean} reNumber
684
- * @returns {string|number}
685
- */
686
- function getComputedCssVal(el, property, reNumber = true) {
687
- const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
688
- return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
689
- }
690
- /**
691
- * 字符串的像素宽度
692
- * @param {string} str 目标字符串
693
- * @param {number} fontSize 字符串字体大小
694
- * @param {boolean} isRemoveDom 计算后是否移除中间dom元素
695
- * @returns {*}
696
- */
697
- function getStrWidthPx(str, fontSize = 14, isRemoveDom = false) {
698
- let strWidth = 0;
699
- console.assert(isString(str), `${str} 不是有效的字符串`);
700
- if (isString(str) && str.length > 0) {
701
- let getEle = document.querySelector('#getStrWidth1494304949567');
702
- if (!getEle) {
703
- const _ele = document.createElement('span');
704
- _ele.id = 'getStrWidth1494304949567';
705
- _ele.style.fontSize = fontSize + 'px';
706
- _ele.style.whiteSpace = 'nowrap';
707
- _ele.style.visibility = 'hidden';
708
- _ele.textContent = str;
709
- document.body.appendChild(_ele);
710
- getEle = _ele;
711
- }
712
- getEle.textContent = str;
713
- strWidth = getEle.offsetWidth;
714
- if (isRemoveDom) {
715
- document.body.appendChild(getEle);
716
- }
717
- }
718
- return strWidth;
719
- }
720
-
721
- const textEl = document.createElement('textarea');
722
- setStyle(textEl, {
723
- position: 'absolute',
724
- top: '-9999px',
725
- left: '-9999px',
726
- opacity: 0
727
- });
728
- document.body.appendChild(textEl);
729
- /**
730
- * 复制文本
731
- * @param {string} text
732
- */
733
- function copyText(text) {
734
- textEl.value = text;
735
- textEl.focus({ preventScroll: true });
736
- textEl.select();
737
- try {
738
- document.execCommand('copy');
739
- textEl.blur();
740
- }
741
- catch (err) {
742
- // ignore
743
- }
744
- }
745
-
746
- /**
747
- * 获取cookie
748
- * @param {string} name
749
- * @returns {string}
750
- */
751
- function cookieGet(name) {
752
- const { cookie } = document;
753
- if (!cookie)
754
- return '';
755
- const result = cookie.split(';');
756
- for (let i = 0; i < result.length; i++) {
757
- const item = result[i];
758
- const [key, val = ''] = item.split('=');
759
- if (key === name)
760
- return decodeURIComponent(val);
761
- }
762
- return '';
763
- }
764
- /**
765
- * 设置 cookie
766
- * @param {string} name
767
- * @param {string} value
768
- * @param {number | Date} [maxAge]
769
- */
770
- function cookieSet(name, value, maxAge) {
771
- const metas = [];
772
- const EXPIRES = 'expires';
773
- metas.push([name, encodeURIComponent(value)]);
774
- if (isNumber(maxAge)) {
775
- const d = new Date();
776
- d.setTime(d.getTime() + maxAge);
777
- metas.push([EXPIRES, d.toUTCString()]);
778
- }
779
- else if (isDate(maxAge)) {
780
- metas.push([EXPIRES, maxAge.toUTCString()]);
781
- }
782
- metas.push(['path', '/']);
783
- document.cookie = metas
784
- .map(item => {
785
- const [key, val] = item;
786
- return `${key}=${val}`;
787
- })
788
- .join(';');
789
- }
790
- /**
791
- * 删除单个 cookie
792
- * @param name cookie 名称
793
- */
794
- const cookieDel = (name) => cookieSet(name, '', -1);
795
-
796
- const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
797
- /* istanbul ignore next */
798
- const guessDateSeparator = (value) => {
799
- if (!isString(value))
800
- return;
801
- const value2 = value.replace(/-/g, '/');
802
- return new Date(value2);
803
- };
804
- /* istanbul ignore next */
805
- const guessDateTimezone = (value) => {
806
- if (!isString(value))
807
- return;
808
- const re = /([+-])(\d\d)(\d\d)$/;
809
- const matches = re.exec(value);
810
- if (!matches)
811
- return;
812
- const value2 = value.replace(re, 'Z');
813
- const d = new Date(value2);
814
- if (!isValidDate(d))
815
- return;
816
- const [, flag, hours, minutes] = matches;
817
- const hours2 = parseInt(hours, 10);
818
- const minutes2 = parseInt(minutes, 10);
819
- const offset = (a, b) => (flag === '+' ? a - b : a + b);
820
- d.setHours(offset(d.getHours(), hours2));
821
- d.setMinutes(offset(d.getMinutes(), minutes2));
822
- return d;
823
- };
824
- /**
825
- * 解析为Date对象
826
- * @param {DateValue} value - 可以是数值、字符串或 Date 对象
827
- * @returns {Date} - 转换后的目标Date
828
- */
829
- function dateParse(value) {
830
- const d1 = new Date(value);
831
- if (isValidDate(d1))
832
- return d1;
833
- // safari 浏览器的日期解析有问题
834
- // new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
835
- /* istanbul ignore next */
836
- const d2 = guessDateSeparator(value);
837
- /* istanbul ignore next */
838
- if (isValidDate(d2))
839
- return d2;
840
- // safari 浏览器的日期解析有问题
841
- // new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
842
- /* istanbul ignore next */
843
- const d3 = guessDateTimezone(value);
844
- /* istanbul ignore next */
845
- if (isValidDate(d3))
846
- return d3;
847
- throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
848
- }
849
- /**
850
- * 格式化为日期对象(带自定义格式化模板)
851
- * @param {DateValue} value 可以是数值、字符串或 Date 对象
852
- * @param {string} [format] 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
853
- * - YYYY:年
854
- * - yyyy: 年
855
- * - MM:月
856
- * - DD:日
857
- * - dd: 日
858
- * - HH:时(24 小时制)
859
- * - hh:时(12 小时制)
860
- * - mm:分
861
- * - ss:秒
862
- * - SSS:毫秒
863
- * @returns {string}
846
+ /**
847
+ * 判断元素是否包含某个样式名
848
+ * @param {HTMLElement} el
849
+ * @param {string} className
850
+ * @returns {boolean}
864
851
  */
865
- // export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
866
- // const date = dateParse(value);
867
- // let fmt = format;
868
- // let ret;
869
- // const opt: DateObj = {
870
- // 'Y+': `${date.getFullYear()}`, //
871
- // 'y+': `${date.getFullYear()}`, // 年
872
- // 'M+': `${date.getMonth() + 1}`, // 月
873
- // 'D+': `${date.getDate()}`, // 日
874
- // 'd+': `${date.getDate()}`, // 日
875
- // 'H+': `${date.getHours()}`, // 时
876
- // 'm+': `${date.getMinutes()}`, // 分
877
- // 's+': `${date.getSeconds()}`, // 秒
878
- // 'S+': `${date.getMilliseconds()}` // 豪秒
879
- // };
880
- // for (const k in opt) {
881
- // ret = new RegExp(`(${k})`).exec(fmt);
882
- // if (ret) {
883
- // fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
884
- // }
885
- // }
886
- // return fmt;
887
- // };
852
+ function hasClass(el, className) {
853
+ if (className.indexOf(' ') !== -1)
854
+ throw new Error('className should not contain space.');
855
+ return el.classList.contains(className);
856
+ }
857
+ const eachClassName = (classNames, func) => {
858
+ const classNameList = classNames.split(/\s+/g);
859
+ classNameList.forEach(func);
860
+ };
888
861
  /**
889
- * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
890
- * @param {DateValue} value
891
- * @returns {Date}
862
+ * 给元素增加样式名
863
+ * @param {HTMLElement} el
864
+ * @param {string} classNames
892
865
  */
893
- function dateToStart(value) {
894
- const d = dateParse(value);
895
- return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
866
+ function addClass(el, classNames) {
867
+ eachClassName(classNames, className => el.classList.add(className));
896
868
  }
897
869
  /**
898
- * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
899
- * @param {DateValue} value
900
- * @returns {Date}
870
+ * 给元素移除样式名
871
+ * @param {HTMLElement} el
872
+ * @param {string} classNames
901
873
  */
902
- function dateToEnd(value) {
903
- const d = dateToStart(value);
904
- d.setDate(d.getDate() + 1);
905
- return dateParse(d.getTime() - 1);
874
+ function removeClass(el, classNames) {
875
+ eachClassName(classNames, className => el.classList.remove(className));
906
876
  }
907
877
  /**
908
- * 格式化为日期对象(带自定义格式化模板)
909
- * @param {Date} value - 可以是数值、字符串或 Date 对象
910
- * @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
911
- * - YYYY:年
912
- * - yyyy: 年
913
- * - MM:月
914
- * - DD:日
915
- * - dd: 日
916
- * - HH:时(24 小时制)
917
- * - mm:分
918
- * - ss:秒
919
- * - SSS:毫秒
920
- * - ww: 周
921
- * @returns {string} 格式化后的日期字符串
878
+ * 设置元素样式
879
+ * @param {HTMLElement} el
880
+ * @param {string | Style} key
881
+ * @param {string} val
922
882
  */
923
- function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
924
- const date = dateParse(value);
925
- let fmt = format;
926
- let ret;
927
- const opt = {
928
- 'Y+': `${date.getFullYear()}`,
929
- 'y+': `${date.getFullYear()}`,
930
- 'M+': `${date.getMonth() + 1}`,
931
- 'D+': `${date.getDate()}`,
932
- 'd+': `${date.getDate()}`,
933
- 'H+': `${date.getHours()}`,
934
- 'm+': `${date.getMinutes()}`,
935
- 's+': `${date.getSeconds()}`,
936
- 'S+': `${date.getMilliseconds()}`,
937
- 'w+': ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()] // 周
938
- // 有其他格式化字符需求可以继续添加,必须转化成字符串
939
- };
940
- for (const k in opt) {
941
- ret = new RegExp('(' + k + ')').exec(fmt);
942
- if (ret) {
943
- fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
944
- }
883
+ const setStyle = (el, key, val) => {
884
+ if (isObject(key)) {
885
+ objectEach(key, (val1, key1) => {
886
+ setStyle(el, key1, val1);
887
+ });
945
888
  }
946
- return fmt;
889
+ else {
890
+ el.style.setProperty(stringKebabCase(key), val);
891
+ }
892
+ };
893
+ /**
894
+ * 获取元素样式
895
+ * @param {HTMLElement} el 元素
896
+ * @param {string} key
897
+ * @returns {string}
898
+ */
899
+ function getStyle(el, key) {
900
+ return getComputedStyle(el).getPropertyValue(key);
901
+ }
902
+ function smoothScroll(options) {
903
+ return new Promise(resolve => {
904
+ const defaults = {
905
+ el: document,
906
+ to: 0,
907
+ duration: 567,
908
+ easing: 'ease'
909
+ };
910
+ const { el, to, duration, easing } = objectAssign(defaults, options);
911
+ const htmlEl = document.documentElement;
912
+ const bodyEl = document.body;
913
+ const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
914
+ const els = globalMode ? [htmlEl, bodyEl] : [el];
915
+ const query = () => {
916
+ let value = 0;
917
+ arrayEach(els, el => {
918
+ if ('scrollTop' in el) {
919
+ value = el.scrollTop;
920
+ return false;
921
+ }
922
+ });
923
+ return value;
924
+ };
925
+ const update = (val) => {
926
+ els.forEach(el => {
927
+ if ('scrollTop' in el) {
928
+ el.scrollTop = val;
929
+ }
930
+ });
931
+ };
932
+ let startTime;
933
+ const startValue = query();
934
+ const length = to - startValue;
935
+ const easingFn = easingFunctional(easing);
936
+ const render = () => {
937
+ const now = performance.now();
938
+ const passingTime = startTime ? now - startTime : 0;
939
+ const t = passingTime / duration;
940
+ const p = easingFn(t);
941
+ if (!startTime)
942
+ startTime = now;
943
+ update(startValue + length * p);
944
+ if (t >= 1)
945
+ resolve();
946
+ else
947
+ requestAnimationFrame(render);
948
+ };
949
+ render();
950
+ });
947
951
  }
948
952
  /**
949
- * 计算向前或向后N天的具体日期
950
- * @param {DateValue} originDate - 参考日期
951
- * @param {number} n - 正数:向后推算;负数:向前推算
952
- * @param {string} sep - 日期格式的分隔符
953
- * @returns {string} 计算后的目标日期
953
+ * 获取元素样式属性的计算值
954
+ * @param {HTMLElement} el
955
+ * @param {string} property
956
+ * @param {boolean} reNumber
957
+ * @returns {string|number}
954
958
  */
955
- function calculateDate(originDate, n, sep = '-') {
956
- //originDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
957
- const date = new Date(originDate); //这边给定一个特定时间
958
- const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
959
- const millisecondGap = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
960
- const targetDate = new Date(millisecondGap);
961
- const finalNewDate = targetDate.getFullYear() +
962
- sep +
963
- String(targetDate.getMonth() + 1).padStart(2, '0') +
964
- '-' +
965
- String(targetDate.getDate()).padStart(2, '0');
966
- return finalNewDate;
959
+ function getComputedCssVal(el, property, reNumber = true) {
960
+ const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
961
+ return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
967
962
  }
968
963
  /**
969
- * 计算向前或向后N天的具体日期时间
970
- * @param {DateValue} originDateTime - 参考日期时间
971
- * @param {number} n - 正数:向后推算;负数:向前推算
972
- * @param {string} dateSep - 日期分隔符
973
- * @param {string} timeSep - 时间分隔符
974
- * @returns {string} 转换后的目标日期时间
964
+ * 字符串的像素宽度
965
+ * @param {string} str 目标字符串
966
+ * @param {number} fontSize 字符串字体大小
967
+ * @param {boolean} isRemove 计算后是否移除创建的dom元素
968
+ * @returns {*}
975
969
  */
976
- function calculateDateTime(originDateTime, n, dateSep = '-', timeSep = ':') {
977
- const date = new Date(originDateTime);
978
- const separator1 = dateSep;
979
- const separator2 = timeSep;
980
- const dateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
981
- const millisecondGap = dateTime.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
982
- const targetDateTime = new Date(millisecondGap);
983
- return (targetDateTime.getFullYear() +
984
- separator1 +
985
- String(targetDateTime.getMonth() + 1).padStart(2, '0') +
986
- separator1 +
987
- String(targetDateTime.getDate()).padStart(2, '0') +
988
- ' ' +
989
- String(targetDateTime.getHours()).padStart(2, '0') +
990
- separator2 +
991
- String(targetDateTime.getMinutes()).padStart(2, '0') +
992
- separator2 +
993
- String(targetDateTime.getSeconds()).padStart(2, '0'));
970
+ function getStrWidthPx(str, fontSize = 14, isRemove = true) {
971
+ let strWidth = 0;
972
+ console.assert(isString(str), `${str} 不是有效的字符串`);
973
+ if (isString(str) && str.length > 0) {
974
+ const id = 'getStrWidth1494304949567';
975
+ let getEle = document.querySelector(`#${id}`);
976
+ if (!getEle) {
977
+ const _ele = document.createElement('span');
978
+ _ele.id = id;
979
+ _ele.style.fontSize = fontSize + 'px';
980
+ _ele.style.whiteSpace = 'nowrap';
981
+ _ele.style.visibility = 'hidden';
982
+ _ele.style.position = 'absolute';
983
+ _ele.style.top = '-9999px';
984
+ _ele.style.left = '-9999px';
985
+ _ele.textContent = str;
986
+ document.body.appendChild(_ele);
987
+ getEle = _ele;
988
+ }
989
+ getEle.textContent = str;
990
+ strWidth = getEle.offsetWidth;
991
+ if (isRemove) {
992
+ getEle.remove();
993
+ }
994
+ }
995
+ return strWidth;
994
996
  }
995
997
 
996
998
  /**
@@ -1107,21 +1109,30 @@
1107
1109
  return params.toString();
1108
1110
  }
1109
1111
 
1110
- const anchorEl = document.createElement('a');
1111
1112
  /**
1112
1113
  * url 解析
1113
1114
  * @param {string} url
1115
+ * @param {boolean} isModernApi 使用现代API:URL, 默认true (对无效url解析会抛错), 否则使用a标签来解析(兼容性更强)
1114
1116
  * @returns {Url}
1115
1117
  */
1116
- const urlParse = (url) => {
1117
- anchorEl.href = url;
1118
- const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = anchorEl;
1118
+ const urlParse = (url, isModernApi = true) => {
1119
+ // @ts-ignore
1120
+ let urlObj = null;
1121
+ if (isFunction(URL) && isModernApi) {
1122
+ urlObj = new URL(url);
1123
+ }
1124
+ else {
1125
+ urlObj = document.createElement('a');
1126
+ urlObj.href = url;
1127
+ }
1128
+ const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = urlObj;
1119
1129
  // fix: ie 浏览器下,解析出来的 pathname 是没有 / 根的
1120
1130
  const pathname = pathJoin('/', _pathname);
1121
1131
  const auth = username && password ? `${username}:${password}` : '';
1122
1132
  const query = search.replace(/^\?/, '');
1123
1133
  const searchParams = qsParse(query);
1124
1134
  const path = `${pathname}${search}`;
1135
+ urlObj = null;
1125
1136
  return {
1126
1137
  protocol,
1127
1138
  auth,
@@ -1599,15 +1610,14 @@
1599
1610
  * @param {ICanvasWM} canvasWM
1600
1611
  * @example genCanvasWM({ content: 'QQMusicFE' })
1601
1612
  */
1602
- function genCanvasWM(canvasWM) {
1603
- const { container = document.body, width = '300px', height = '200px', textAlign = 'center', textBaseline = 'middle', font = '20px PingFangSC-Medium,PingFang SC',
1613
+ function genCanvasWM(content = '请勿外传', canvasWM) {
1614
+ const { rootContainer = document.body, width = '300px', height = '150px', textAlign = 'center', textBaseline = 'middle', font = '20px PingFangSC-Medium,PingFang SC',
1604
1615
  // fontWeight = 500,
1605
- fillStyle = 'rgba(189, 177, 167, .3)', content = '请勿外传', rotate = 30, zIndex = 2147483647 } = canvasWM;
1606
- // 仅限主页面添加水印
1607
- // if (!location.pathname.includes('/home')) {
1608
- // return;
1609
- // }
1610
- const args = canvasWM;
1616
+ fillStyle = 'rgba(189, 177, 167, .3)', rotate = -20, zIndex = 2147483647, watermarkId = '__wm' } = isNullOrUnDef(canvasWM) ? {} : canvasWM;
1617
+ const container = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
1618
+ if (!container) {
1619
+ throw new Error(`${rootContainer} is not valid Html Element or element selector`);
1620
+ }
1611
1621
  const canvas = document.createElement('canvas');
1612
1622
  canvas.setAttribute('width', width);
1613
1623
  canvas.setAttribute('height', height);
@@ -1620,36 +1630,36 @@
1620
1630
  ctx.rotate((Math.PI / 180) * rotate);
1621
1631
  ctx.fillText(content, parseFloat(width) / 4, parseFloat(height) / 2);
1622
1632
  const base64Url = canvas.toDataURL();
1623
- const __wm = document.querySelector('.__wm');
1633
+ const __wm = document.querySelector(`#${watermarkId}`);
1624
1634
  const watermarkDiv = __wm || document.createElement('div');
1625
1635
  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}')`;
1626
1636
  watermarkDiv.setAttribute('style', styleStr);
1627
- watermarkDiv.classList.add('__wm');
1637
+ watermarkDiv.setAttribute('id', watermarkId);
1628
1638
  watermarkDiv.classList.add('nav-height');
1629
1639
  if (!__wm) {
1630
1640
  container.style.position = 'relative';
1631
1641
  container.appendChild(watermarkDiv);
1632
1642
  }
1633
1643
  const getMutableStyle = (ele) => {
1634
- const computedStle = getComputedStyle(ele);
1644
+ const computedStyle = getComputedStyle(ele);
1635
1645
  return {
1636
- opacity: computedStle.getPropertyValue('opacity'),
1637
- zIndex: computedStle.getPropertyValue('z-index'),
1638
- display: computedStle.getPropertyValue('display'),
1639
- visibility: computedStle.getPropertyValue('visibility')
1646
+ opacity: computedStyle.getPropertyValue('opacity'),
1647
+ zIndex: computedStyle.getPropertyValue('z-index'),
1648
+ display: computedStyle.getPropertyValue('display'),
1649
+ visibility: computedStyle.getPropertyValue('visibility')
1640
1650
  };
1641
1651
  };
1642
1652
  //@ts-ignore
1643
1653
  const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
1644
1654
  if (MutationObserver) {
1645
1655
  let mo = new MutationObserver(function () {
1646
- const __wm = document.querySelector('.__wm'); // 只在__wm元素变动才重新调用 __canvasWM
1656
+ const __wm = document.querySelector(`#${watermarkId}`); // 只在__wm元素变动才重新调用 __canvasWM
1647
1657
  if (!__wm) {
1648
1658
  // 避免一直触发
1649
1659
  // console.log('regenerate watermark by delete::')
1650
1660
  mo.disconnect();
1651
1661
  mo = null;
1652
- genCanvasWM(JSON.parse(JSON.stringify(args)));
1662
+ genCanvasWM(content, canvasWM);
1653
1663
  }
1654
1664
  else {
1655
1665
  const { opacity, zIndex, display, visibility } = getMutableStyle(__wm);
@@ -1661,7 +1671,7 @@
1661
1671
  mo.disconnect();
1662
1672
  mo = null;
1663
1673
  container.removeChild(__wm);
1664
- genCanvasWM(JSON.parse(JSON.stringify(args)));
1674
+ genCanvasWM(content, canvasWM);
1665
1675
  }
1666
1676
  }
1667
1677
  });
@@ -1882,35 +1892,75 @@
1882
1892
  return ret.join('');
1883
1893
  }
1884
1894
  /**
1885
- * 缩写
1895
+ * 将数字转换为携带单位的字符串
1886
1896
  * @param {number | string} num
1887
1897
  * @param {Array<string>} units
1888
- * @param {number} ratio
1889
- * @param {number} exponent
1898
+ * @param {INumberAbbr} options default: { ratio: 1000, decimals: 0, separator: ' ' }
1890
1899
  * @returns {string}
1891
1900
  */
1892
- const numberAbbr = (num, units, ratio = 1000, exponent) => {
1901
+ const numberAbbr = (num, units, options = { ratio: 1000, decimals: 0, separator: ' ' }) => {
1902
+ const { ratio = 1000, decimals = 0, separator = ' ' } = options;
1893
1903
  const { length } = units;
1894
1904
  if (length === 0)
1895
- throw new Error('至少需要一个单位');
1905
+ throw new Error('At least one unit is required');
1896
1906
  let num2 = Number(num);
1897
1907
  let times = 0;
1898
1908
  while (num2 >= ratio && times < length - 1) {
1899
1909
  num2 = num2 / ratio;
1900
1910
  times++;
1901
1911
  }
1902
- const value = num2.toFixed(exponent);
1912
+ const value = num2.toFixed(decimals);
1903
1913
  const unit = units[times];
1904
- return value.toString() + '' + unit;
1914
+ return String(value) + separator + unit;
1905
1915
  };
1916
+ /**
1917
+ * Converting file size in bytes to human-readable string
1918
+ * reference: https://zh.wikipedia.org/wiki/%E5%8D%83%E5%AD%97%E8%8A%82
1919
+ * @param {number | string} num bytes Number in Bytes
1920
+ * @param {IHumanFileSizeOptions} options default: { decimals = 0, si = false, separator = ' ' }
1921
+ * si: True to use metric (SI) units, aka powers of 1000, the units is
1922
+ * ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'].
1923
+ * False to use binary (IEC), aka powers of 1024, the units is
1924
+ * ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
1925
+ * @returns
1926
+ */
1927
+ function humanFileSize(num, options = { decimals: 0, si: false, separator: ' ' }) {
1928
+ const { decimals = 0, si = false, separator = ' ', baseUnit, maxUnit } = options;
1929
+ let units = si
1930
+ ? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
1931
+ : ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
1932
+ if (!isNullOrUnDef(baseUnit)) {
1933
+ const targetIndex = units.findIndex(el => el === baseUnit);
1934
+ if (targetIndex !== -1) {
1935
+ units = units.slice(targetIndex);
1936
+ }
1937
+ }
1938
+ if (!isNullOrUnDef(maxUnit)) {
1939
+ const targetIndex = units.findIndex(el => el === maxUnit);
1940
+ if (targetIndex !== -1) {
1941
+ units.splice(targetIndex + 1);
1942
+ }
1943
+ }
1944
+ return numberAbbr(num, units, { ratio: si ? 1000 : 1024, decimals, separator });
1945
+ }
1906
1946
  /**
1907
1947
  * 将数字格式化成千位分隔符显示的字符串
1908
- * @param {number} val 数字
1909
- * @param {'int' | 'float'} type 展示分段显示的类型 int:整型 | float:浮点型
1948
+ * @param {number|string} num 数字
1949
+ * @param {number} decimals 格式化成指定小数位精度的参数
1910
1950
  * @returns {string}
1911
1951
  */
1912
- function formatNumber(val, type = 'int') {
1913
- return type === 'int' ? parseInt(String(val)).toLocaleString() : Number(val).toLocaleString('en-US');
1952
+ function formatNumber(num, decimals) {
1953
+ if (isNullOrUnDef(decimals)) {
1954
+ return parseInt(String(num)).toLocaleString();
1955
+ }
1956
+ let prec = 0;
1957
+ if (!isNumber(decimals)) {
1958
+ throw new Error('Decimals must be a positive number not less than zero');
1959
+ }
1960
+ else if (decimals > 0) {
1961
+ prec = decimals;
1962
+ }
1963
+ return Number(Number(num).toFixed(prec)).toLocaleString('en-US');
1914
1964
  }
1915
1965
 
1916
1966
  const padStartWithZero = (str, maxLength = 2) => String(str).padStart(maxLength, '0');
@@ -1986,45 +2036,48 @@
1986
2036
  * @Desc 自定义的tooltip方法, 支持拖动悬浮提示
1987
2037
  * Created by chendeqiao on 2017/5/8.
1988
2038
  * @example
1989
- * <span onmouseleave="handleMouseLeave('#root')" onmousemove="handleMouseEnter({rootElId: '#root', title: 'title content', event: event})"
1990
- * onmouseenter="handleMouseEnter({'#root', title: 'title content', event: event})">title content </span>
2039
+ * <span onmouseleave="handleMouseLeave('#root')" onmousemove="handleMouseEnter({rootContainer: '#root', title: 'title content', event: event})"
2040
+ * onmouseenter="handleMouseEnter({rootContainer:'#root', title: 'title content', event: event})">title content </span>
1991
2041
  */
1992
2042
  /**
1993
2043
  * 自定义title提示功能的mouseenter事件句柄
1994
2044
  * @param {ITooltipParams} param1
1995
2045
  * @returns {*}
1996
2046
  */
1997
- function handleMouseEnter({ rootElId = '#root', title, event }) {
2047
+ function handleMouseEnter({ rootContainer = '#root', title, event, bgColor = '#000', color = '#fff' }) {
1998
2048
  try {
1999
- const $rootEl = document.querySelector(rootElId);
2000
- console.assert($rootEl !== null, `未找到id为 ${rootElId} 的dom元素`);
2049
+ const $rootEl = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
2050
+ if (!$rootEl) {
2051
+ throw new Error(`${rootContainer} is not valid Html Element or element selector`);
2052
+ }
2001
2053
  let $customTitle = null;
2054
+ const styleId = 'style-tooltip-inner1494304949567';
2002
2055
  // 动态创建class样式,并加入到head中
2003
- if (!document.querySelector('.tooltip-inner1494304949567')) {
2056
+ if (!document.querySelector(`#${styleId}`)) {
2004
2057
  const tooltipWrapperClass = document.createElement('style');
2005
2058
  tooltipWrapperClass.type = 'text/css';
2059
+ tooltipWrapperClass.id = styleId;
2006
2060
  tooltipWrapperClass.innerHTML = `
2007
2061
  .tooltip-inner1494304949567 {
2008
2062
  max-width: 250px;
2009
2063
  padding: 3px 8px;
2010
- color: #fff;
2064
+ color: ${color};
2011
2065
  text-decoration: none;
2012
2066
  border-radius: 4px;
2013
2067
  text-align: left;
2068
+ background-color: ${bgColor};
2014
2069
  }
2015
2070
  `;
2016
2071
  document.querySelector('head').appendChild(tooltipWrapperClass);
2017
2072
  }
2018
- if (document.querySelector('#customTitle1494304949567')) {
2019
- $customTitle = document.querySelector('#customTitle1494304949567');
2073
+ $customTitle = document.querySelector('#customTitle1494304949567');
2074
+ if ($customTitle) {
2020
2075
  mouseenter($customTitle, title, event);
2021
2076
  }
2022
2077
  else {
2023
2078
  const $contentContainer = document.createElement('div');
2024
- $contentContainer.className = 'customTitle';
2025
2079
  $contentContainer.id = 'customTitle1494304949567';
2026
- $contentContainer.className = 'tooltip';
2027
- $contentContainer.style.cssText = 'z-index: 99999999; visibility: hidden;';
2080
+ $contentContainer.style.cssText = 'z-index: 99999999; visibility: hidden; position: absolute;';
2028
2081
  $contentContainer.innerHTML =
2029
2082
  '<div class="tooltip-inner1494304949567" style="word-wrap: break-word; max-width: 44px;">皮肤</div>';
2030
2083
  $rootEl.appendChild($contentContainer);
@@ -2077,11 +2130,11 @@
2077
2130
  }
2078
2131
  /**
2079
2132
  * 移除提示文案dom的事件句柄
2080
- * @param {string} rootElId
2133
+ * @param {string} rootContainer
2081
2134
  * @returns {*}
2082
2135
  */
2083
- function handleMouseLeave(rootElId = '#root') {
2084
- const rootEl = document.querySelector(rootElId), titleEl = document.querySelector('#customTitle1494304949567');
2136
+ function handleMouseLeave(rootContainer = '#root') {
2137
+ const rootEl = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer, titleEl = document.querySelector('#customTitle1494304949567');
2085
2138
  if (rootEl && titleEl) {
2086
2139
  rootEl.removeChild(titleEl);
2087
2140
  }
@@ -2290,7 +2343,7 @@
2290
2343
  ...node,
2291
2344
  [childField]: [] // 清空子级
2292
2345
  };
2293
- item.hasOwnProperty([childField]) && delete item[childField];
2346
+ objectHas(item, childField) && delete item[childField];
2294
2347
  res.push(item);
2295
2348
  if (node[childField]) {
2296
2349
  const children = node[childField].map(item => ({
@@ -2992,6 +3045,7 @@
2992
3045
  exports.flatTree = flatTree;
2993
3046
  exports.forEachDeep = forEachDeep;
2994
3047
  exports.formatDate = formatDate;
3048
+ exports.formatMoney = formatNumber;
2995
3049
  exports.formatNumber = formatNumber;
2996
3050
  exports.formatTree = formatTree;
2997
3051
  exports.fuzzySearchTree = fuzzySearchTree;
@@ -3001,6 +3055,7 @@
3001
3055
  exports.getStrWidthPx = getStrWidthPx;
3002
3056
  exports.getStyle = getStyle;
3003
3057
  exports.hasClass = hasClass;
3058
+ exports.humanFileSize = humanFileSize;
3004
3059
  exports.isArray = isArray;
3005
3060
  exports.isBigInt = isBigInt;
3006
3061
  exports.isBoolean = isBoolean;