sculp-js 1.8.3 → 1.9.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 +1 -1
  9. package/lib/cjs/download.js +29 -5
  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 +49 -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 +1 -1
  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 +3 -2
  29. package/lib/cjs/watermark.js +1 -1
  30. package/lib/cjs/we-decode.js +1 -1
  31. package/lib/es/array.js +1 -1
  32. package/lib/es/async.js +1 -1
  33. package/lib/es/base64.js +1 -1
  34. package/lib/es/clipboard.js +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 +1 -1
  39. package/lib/es/download.js +30 -6
  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 +48 -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 +1 -1
  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 +4 -3
  59. package/lib/es/watermark.js +1 -1
  60. package/lib/es/we-decode.js +1 -1
  61. package/lib/index.d.ts +43 -11
  62. package/lib/umd/index.js +603 -535
  63. package/package.json +1 -1
package/lib/umd/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * sculp-js v1.8.3
2
+ * sculp-js v1.9.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);
@@ -560,437 +833,162 @@
560
833
  if (!queryObj[item[1]]) {
561
834
  queryObj[item[1]] = item[2];
562
835
  }
563
- else if (typeof queryObj[item[1]] === 'string') {
564
- queryObj[item[1]] = [queryObj[item[1]], item[2]];
565
- }
566
- else {
567
- queryObj[item[1]].push(item[2]);
568
- }
569
- });
570
- return queryObj;
571
- }
572
-
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}
864
- */
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
- // };
836
+ else if (typeof queryObj[item[1]] === 'string') {
837
+ queryObj[item[1]] = [queryObj[item[1]], item[2]];
838
+ }
839
+ else {
840
+ queryObj[item[1]].push(item[2]);
841
+ }
842
+ });
843
+ return queryObj;
844
+ }
845
+
888
846
  /**
889
- * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
890
- * @param {DateValue} value
891
- * @returns {Date}
847
+ * 判断元素是否包含某个样式名
848
+ * @param {HTMLElement} el
849
+ * @param {string} className
850
+ * @returns {boolean}
892
851
  */
893
- function dateToStart(value) {
894
- const d = dateParse(value);
895
- return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
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);
896
856
  }
857
+ const eachClassName = (classNames, func) => {
858
+ const classNameList = classNames.split(/\s+/g);
859
+ classNameList.forEach(func);
860
+ };
897
861
  /**
898
- * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
899
- * @param {DateValue} value
900
- * @returns {Date}
862
+ * 给元素增加样式名
863
+ * @param {HTMLElement} el
864
+ * @param {string} classNames
901
865
  */
902
- function dateToEnd(value) {
903
- const d = dateToStart(value);
904
- d.setDate(d.getDate() + 1);
905
- return dateParse(d.getTime() - 1);
866
+ function addClass(el, classNames) {
867
+ eachClassName(classNames, className => el.classList.add(className));
906
868
  }
907
869
  /**
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} 格式化后的日期字符串
870
+ * 给元素移除样式名
871
+ * @param {HTMLElement} el
872
+ * @param {string} classNames
922
873
  */
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
- }
874
+ function removeClass(el, classNames) {
875
+ eachClassName(classNames, className => el.classList.remove(className));
876
+ }
877
+ /**
878
+ * 设置元素样式
879
+ * @param {HTMLElement} el
880
+ * @param {string | Style} key
881
+ * @param {string} val
882
+ */
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} isRemoveDom 计算后是否移除中间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, isRemoveDom = false) {
971
+ let strWidth = 0;
972
+ console.assert(isString(str), `${str} 不是有效的字符串`);
973
+ if (isString(str) && str.length > 0) {
974
+ let getEle = document.querySelector('#getStrWidth1494304949567');
975
+ if (!getEle) {
976
+ const _ele = document.createElement('span');
977
+ _ele.id = 'getStrWidth1494304949567';
978
+ _ele.style.fontSize = fontSize + 'px';
979
+ _ele.style.whiteSpace = 'nowrap';
980
+ _ele.style.visibility = 'hidden';
981
+ _ele.textContent = str;
982
+ document.body.appendChild(_ele);
983
+ getEle = _ele;
984
+ }
985
+ getEle.textContent = str;
986
+ strWidth = getEle.offsetWidth;
987
+ if (isRemoveDom) {
988
+ document.body.appendChild(getEle);
989
+ }
990
+ }
991
+ return strWidth;
994
992
  }
995
993
 
996
994
  /**
@@ -1107,21 +1105,30 @@
1107
1105
  return params.toString();
1108
1106
  }
1109
1107
 
1110
- const anchorEl = document.createElement('a');
1111
1108
  /**
1112
1109
  * url 解析
1113
1110
  * @param {string} url
1111
+ * @param {boolean} isModernApi 使用现代API:URL, 默认true (对无效url解析会抛错), 否则使用a标签来解析(兼容性更强)
1114
1112
  * @returns {Url}
1115
1113
  */
1116
- const urlParse = (url) => {
1117
- anchorEl.href = url;
1118
- const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = anchorEl;
1114
+ const urlParse = (url, isModernApi = true) => {
1115
+ // @ts-ignore
1116
+ let urlObj = null;
1117
+ if (isFunction(URL) && isModernApi) {
1118
+ urlObj = new URL(url);
1119
+ }
1120
+ else {
1121
+ urlObj = document.createElement('a');
1122
+ urlObj.href = url;
1123
+ }
1124
+ const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = urlObj;
1119
1125
  // fix: ie 浏览器下,解析出来的 pathname 是没有 / 根的
1120
1126
  const pathname = pathJoin('/', _pathname);
1121
1127
  const auth = username && password ? `${username}:${password}` : '';
1122
1128
  const query = search.replace(/^\?/, '');
1123
1129
  const searchParams = qsParse(query);
1124
1130
  const path = `${pathname}${search}`;
1131
+ urlObj = null;
1125
1132
  return {
1126
1133
  protocol,
1127
1134
  auth,
@@ -1222,17 +1229,41 @@
1222
1229
  }
1223
1230
  /**
1224
1231
  * 根据URL下载文件(解决跨域a.download不生效问题)
1232
+ *
1233
+ * 可定制下载成功的状态码status(浏览器原生状态码)
1234
+ *
1235
+ * 支持下载操作成功、失败后的回调
1225
1236
  * @param {string} url
1226
1237
  * @param {string} filename
1227
- * @param {Function} callback
1238
+ * @param {CrossOriginDownloadParams} options
1228
1239
  */
1229
- function crossOriginDownload(url, filename, callback) {
1240
+ function crossOriginDownload(url, filename, options) {
1241
+ const { successCode = 200, successCallback, failCallback } = isNullOrUnDef(options) ? { successCode: 200, successCallback: void 0, failCallback: void 0 } : options;
1230
1242
  const xhr = new XMLHttpRequest();
1231
1243
  xhr.open('GET', url, true);
1232
1244
  xhr.responseType = 'blob';
1233
1245
  xhr.onload = function () {
1234
- if (xhr.status === 200)
1235
- downloadBlob(xhr.response, filename, callback);
1246
+ if (xhr.status === successCode)
1247
+ downloadBlob(xhr.response, filename, successCallback);
1248
+ else if (isFunction(failCallback)) {
1249
+ const status = xhr.status;
1250
+ const responseType = xhr.getResponseHeader('Content-Type');
1251
+ if (isString(responseType) && responseType.includes('application/json')) {
1252
+ const reader = new FileReader();
1253
+ reader.onload = () => {
1254
+ failCallback({ status, response: reader.result });
1255
+ };
1256
+ reader.readAsText(xhr.response);
1257
+ }
1258
+ else {
1259
+ failCallback(xhr);
1260
+ }
1261
+ }
1262
+ };
1263
+ xhr.onerror = e => {
1264
+ if (isFunction(failCallback)) {
1265
+ failCallback({ status: 0, code: 'ERROR_CONNECTION_REFUSED' });
1266
+ }
1236
1267
  };
1237
1268
  xhr.send();
1238
1269
  }
@@ -1858,35 +1889,69 @@
1858
1889
  return ret.join('');
1859
1890
  }
1860
1891
  /**
1861
- * 缩写
1892
+ * 将数字转换为携带单位的字符串
1862
1893
  * @param {number | string} num
1863
1894
  * @param {Array<string>} units
1864
- * @param {number} ratio
1865
- * @param {number} exponent
1895
+ * @param {INumberAbbr} options default: { ratio: 1000, decimals: 0, separator: ' ' }
1866
1896
  * @returns {string}
1867
1897
  */
1868
- const numberAbbr = (num, units, ratio = 1000, exponent) => {
1898
+ const numberAbbr = (num, units, options = { ratio: 1000, decimals: 0, separator: ' ' }) => {
1899
+ const { ratio = 1000, decimals = 0, separator = ' ' } = options;
1869
1900
  const { length } = units;
1870
1901
  if (length === 0)
1871
- throw new Error('至少需要一个单位');
1902
+ throw new Error('At least one unit is required');
1872
1903
  let num2 = Number(num);
1873
1904
  let times = 0;
1874
1905
  while (num2 >= ratio && times < length - 1) {
1875
1906
  num2 = num2 / ratio;
1876
1907
  times++;
1877
1908
  }
1878
- const value = num2.toFixed(exponent);
1909
+ const value = num2.toFixed(decimals);
1879
1910
  const unit = units[times];
1880
- return value.toString() + '' + unit;
1911
+ return String(value) + separator + unit;
1881
1912
  };
1913
+ /**
1914
+ * Converting file size in bytes to human-readable string
1915
+ * reference: https://zh.wikipedia.org/wiki/%E5%8D%83%E5%AD%97%E8%8A%82
1916
+ * @param {number | string} num bytes Number in Bytes
1917
+ * @param {IHumanFileSizeOptions} options default: { decimals = 0, si = false, separator = ' ' }
1918
+ * si: True to use metric (SI) units, aka powers of 1000, the units is
1919
+ * ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'].
1920
+ * False to use binary (IEC), aka powers of 1024, the units is
1921
+ * ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
1922
+ * @returns
1923
+ */
1924
+ function humanFileSize(num, options) {
1925
+ const { decimals = 0, si = false, separator = ' ', maxUnit } = options;
1926
+ const units = si
1927
+ ? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
1928
+ : ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
1929
+ if (!isNullOrUnDef(maxUnit)) {
1930
+ const targetIndex = units.findIndex(el => el === maxUnit);
1931
+ if (targetIndex !== -1) {
1932
+ units.splice(targetIndex + 1);
1933
+ }
1934
+ }
1935
+ return numberAbbr(num, units, { ratio: si ? 1000 : 1024, decimals, separator });
1936
+ }
1882
1937
  /**
1883
1938
  * 将数字格式化成千位分隔符显示的字符串
1884
- * @param {number} val 数字
1885
- * @param {'int' | 'float'} type 展示分段显示的类型 int:整型 | float:浮点型
1939
+ * @param {number|string} num 数字
1940
+ * @param {number} decimals 格式化成指定小数位精度的参数
1886
1941
  * @returns {string}
1887
1942
  */
1888
- function formatNumber(val, type = 'int') {
1889
- return type === 'int' ? parseInt(String(val)).toLocaleString() : Number(val).toLocaleString('en-US');
1943
+ function formatNumber(num, decimals) {
1944
+ if (isNullOrUnDef(decimals)) {
1945
+ return parseInt(String(num)).toLocaleString();
1946
+ }
1947
+ let prec = 0;
1948
+ if (!isNumber(decimals)) {
1949
+ throw new Error('Decimals must be a positive number not less than zero');
1950
+ }
1951
+ else if (decimals > 0) {
1952
+ prec = decimals;
1953
+ }
1954
+ return Number(Number(num).toFixed(prec)).toLocaleString('en-US');
1890
1955
  }
1891
1956
 
1892
1957
  const padStartWithZero = (str, maxLength = 2) => String(str).padStart(maxLength, '0');
@@ -2266,7 +2331,7 @@
2266
2331
  ...node,
2267
2332
  [childField]: [] // 清空子级
2268
2333
  };
2269
- item.hasOwnProperty([childField]) && delete item[childField];
2334
+ objectHas(item, childField) && delete item[childField];
2270
2335
  res.push(item);
2271
2336
  if (node[childField]) {
2272
2337
  const children = node[childField].map(item => ({
@@ -2629,7 +2694,8 @@
2629
2694
  * default match symbol {} same as /{\s*([^{}\s]*)\s*}/g
2630
2695
  */
2631
2696
  function parseVarFromString(str, leftMatchSymbol = '{', rightMatchSymbol = '}') {
2632
- return Array.from(str.matchAll(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol))).map(el => el?.[1]);
2697
+ // @ts-ignore
2698
+ return Array.from(str.matchAll(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol))).map(el => isNullOrUnDef(el) ? void 0 : el[1]);
2633
2699
  }
2634
2700
  /**
2635
2701
  * 替换字符串中的插值变量
@@ -2967,6 +3033,7 @@
2967
3033
  exports.flatTree = flatTree;
2968
3034
  exports.forEachDeep = forEachDeep;
2969
3035
  exports.formatDate = formatDate;
3036
+ exports.formatMoney = formatNumber;
2970
3037
  exports.formatNumber = formatNumber;
2971
3038
  exports.formatTree = formatTree;
2972
3039
  exports.fuzzySearchTree = fuzzySearchTree;
@@ -2976,6 +3043,7 @@
2976
3043
  exports.getStrWidthPx = getStrWidthPx;
2977
3044
  exports.getStyle = getStyle;
2978
3045
  exports.hasClass = hasClass;
3046
+ exports.humanFileSize = humanFileSize;
2979
3047
  exports.isArray = isArray;
2980
3048
  exports.isBigInt = isBigInt;
2981
3049
  exports.isBoolean = isBoolean;