@longmo-utils/common 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2142 @@
1
+ import { TinyColor, TinyColor as TinyColor$1 } from "@ctrl/tinycolor";
2
+ import { getColors } from "theme-colors";
3
+ import dayjs from "dayjs";
4
+ import timezone from "dayjs/plugin/timezone.js";
5
+ import utc from "dayjs/plugin/utc.js";
6
+
7
+ //#region package.json
8
+ var version = "1.0.0";
9
+
10
+ //#endregion
11
+ //#region src/_internal/_version.ts
12
+ /**
13
+ * 工具箱版本
14
+ * @public
15
+ */
16
+ const _version = version;
17
+
18
+ //#endregion
19
+ //#region src/array/unique.ts
20
+ /**
21
+ * 移除数组中的重复值
22
+ *
23
+ * @param arr - 输入数组
24
+ * @returns 去除重复值后的新数组
25
+ * @example
26
+ * ```ts
27
+ * unique([1, 2, 2, 3, 3, 3]) // [1, 2, 3]
28
+ * unique(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']
29
+ * ```
30
+ */
31
+ function unique(arr) {
32
+ return Array.from(new Set(arr));
33
+ }
34
+
35
+ //#endregion
36
+ //#region src/array/groupBy.ts
37
+ /**
38
+ * 根据键函数对数组元素进行分组
39
+ *
40
+ * @param arr - 要分组的输入数组
41
+ * @param keyFn - 返回每个元素的分组键的函数
42
+ * @returns 对象,其中键是分组结果,值是元素数组
43
+ * @example
44
+ * ```ts
45
+ * const data = [
46
+ * { name: 'Alice', role: 'admin' },
47
+ * { name: 'Bob', role: 'user' },
48
+ * { name: 'Charlie', role: 'admin' }
49
+ * ]
50
+ * groupBy(data, item => item.role)
51
+ * // { admin: [{ name: 'Alice', role: 'admin' }, { name: 'Charlie', role: 'admin' }],
52
+ * // user: [{ name: 'Bob', role: 'user' }] }
53
+ * ```
54
+ */
55
+ function groupBy(arr, keyFn) {
56
+ return arr.reduce((acc, item) => {
57
+ const key = keyFn(item);
58
+ if (!acc[key]) acc[key] = [];
59
+ acc[key].push(item);
60
+ return acc;
61
+ }, {});
62
+ }
63
+
64
+ //#endregion
65
+ //#region src/array/chunk.ts
66
+ /**
67
+ * 将数组拆分成指定大小的子数组
68
+ *
69
+ * @param arr - 要拆分的输入数组
70
+ * @param size - 每个子数组的大小。如果小于等于 0,则默认为 1
71
+ * @returns 子数组的数组
72
+ * @example
73
+ * ```ts
74
+ * chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
75
+ * chunk([1, 2, 3, 4, 5], 3) // [[1, 2, 3], [4, 5]]
76
+ * chunk([1, 2, 3], 5) // [[1, 2, 3]]
77
+ * chunk([1, 2, 3], 0) // [[1], [2], [3]] (size 默认为 1)
78
+ * chunk([1, 2, 3], -2) // [[1], [2], [3]] (size 默认为 1)
79
+ * ```
80
+ */
81
+ function chunk(arr, size) {
82
+ if (size <= 0) size = 1;
83
+ const result = [];
84
+ for (let i = 0; i < arr.length; i += size) result.push(arr.slice(i, i + size));
85
+ return result;
86
+ }
87
+
88
+ //#endregion
89
+ //#region src/array/uniqueByField.ts
90
+ /**
91
+ * @Author: longmo
92
+ * @Date: 2026-01-25 20:41:10
93
+ * @LastEditTime: 2026-01-25 20:41:19
94
+ * @FilePath: packages/common/src/array/uniqueByField.ts
95
+ * @Description:
96
+ */
97
+ /**
98
+ * 根据指定字段对对象数组进行去重
99
+ * @param arr 要去重的对象数组
100
+ * @param key 去重依据的字段名
101
+ * @returns 去重后的对象数组
102
+ */
103
+ function uniqueByField(arr, key) {
104
+ const seen = /* @__PURE__ */ new Map();
105
+ return arr.filter((item) => {
106
+ const value = item[key];
107
+ return seen.has(value) ? false : (seen.set(value, item), true);
108
+ });
109
+ }
110
+
111
+ //#endregion
112
+ //#region src/object/deepClone.ts
113
+ /**
114
+ * 深度克隆一个对象
115
+ * @param obj - 要克隆的对象
116
+ * @returns 克隆后的对象
117
+ * @example
118
+ * ```ts
119
+ * const obj = {a: 1, b: {c: 2}}
120
+ * const cloned = deepClone(obj)
121
+ * cloned.b.c = 3
122
+ * console.log(obj.b.c) // 2
123
+ * ```
124
+ * @public
125
+ */
126
+ function deepClone(obj) {
127
+ if (obj === null || typeof obj !== "object") return obj;
128
+ if (Array.isArray(obj)) return obj.map((item) => deepClone(item));
129
+ const cloned = {};
130
+ for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) cloned[key] = deepClone(obj[key]);
131
+ return cloned;
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/object/deepEqual.ts
136
+ /**
137
+ * 深度比较两个对象是否相等
138
+ * @param a - 第一个对象
139
+ * @param b - 第二个对象
140
+ * @returns 是否相等
141
+ * @example
142
+ * ```ts
143
+ * deepEqual({a: 1}, {a: 1}) // true
144
+ * deepEqual({a: {b: 1}}, {a: {b: 1}}) // true
145
+ * deepEqual({a: 1}, {a: 2}) // false
146
+ * ```
147
+ * @public
148
+ */
149
+ function deepEqual(a, b) {
150
+ if (a === b) return true;
151
+ if (typeof a !== typeof b) return false;
152
+ if (typeof a !== "object" || a === null || b === null) return false;
153
+ const keysA = Object.keys(a);
154
+ const keysB = Object.keys(b);
155
+ if (keysA.length !== keysB.length) return false;
156
+ for (const key of keysA) {
157
+ if (!keysB.includes(key)) return false;
158
+ if (!deepEqual(a[key], b[key])) return false;
159
+ }
160
+ return true;
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/object/deepMerge.ts
165
+ /**
166
+ * 深度合并多个对象
167
+ * @param objects - 要合并的对象数组
168
+ * @returns 合并后的对象
169
+ * @example
170
+ * ```ts
171
+ * deepMerge({a: 1}, {b: 2}) // {a: 1, b: 2}
172
+ * deepMerge({a: {x: 1}}, {a: {y: 2}}) // {a: {x: 1, y: 2}}
173
+ * ```
174
+ * @public
175
+ */
176
+ function deepMerge(...objects) {
177
+ const result = {};
178
+ for (const obj of objects) for (const key in obj) if (obj[key] !== void 0) if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key]) && typeof result[key] === "object" && result[key] !== null) result[key] = deepMerge(result[key], obj[key]);
179
+ else result[key] = obj[key];
180
+ return result;
181
+ }
182
+
183
+ //#endregion
184
+ //#region src/object/get.ts
185
+ /**
186
+ * 通过路径字符串获取对象的嵌套值
187
+ * @param obj - 对象
188
+ * @param path - 路径字符串,使用点分隔
189
+ * @param defaultValue - 默认值
190
+ * @returns 获取的值
191
+ * @example
192
+ * ```ts
193
+ * const obj = {a: {b: {c: 1}}}
194
+ * get(obj, 'a.b.c') // 1
195
+ * get(obj, 'a.b.d', 'default') // 'default'
196
+ * ```
197
+ * @public
198
+ */
199
+ function get(obj, path, defaultValue) {
200
+ const keys = path.split(".");
201
+ let result = obj;
202
+ for (const key of keys) {
203
+ if (result === null || result === void 0) return defaultValue;
204
+ result = result[key];
205
+ }
206
+ return result !== void 0 ? result : defaultValue;
207
+ }
208
+
209
+ //#endregion
210
+ //#region src/object/set.ts
211
+ /**
212
+ * 通过路径字符串设置对象的嵌套值
213
+ * @param obj - 对象
214
+ * @param path - 路径字符串,使用点分隔
215
+ * @param value - 要设置的值
216
+ * @example
217
+ * ```ts
218
+ * const obj = {a: {}}
219
+ * set(obj, 'a.b.c', 1)
220
+ * console.log(obj) // {a: {b: {c: 1}}}
221
+ * ```
222
+ * @public
223
+ */
224
+ function set(obj, path, value) {
225
+ const keys = path.split(".");
226
+ const lastKey = keys.pop();
227
+ let current = obj;
228
+ for (const key of keys) {
229
+ if (current[key] === void 0) current[key] = {};
230
+ current = current[key];
231
+ }
232
+ current[lastKey] = value;
233
+ }
234
+
235
+ //#endregion
236
+ //#region src/function/debounce.ts
237
+ /**
238
+ * 防抖函数,延迟执行函数
239
+ * @param fn - 要执行的函数
240
+ * @param delay - 延迟时间(毫秒)
241
+ * @returns 防抖后的函数
242
+ * @example
243
+ * ```ts
244
+ * const debouncedFn = debounce(() => console.log('hello'), 300)
245
+ * debouncedFn() // 300ms 后执行
246
+ * debouncedFn() // 重置计时器,300ms 后执行
247
+ * ```
248
+ * @public
249
+ */
250
+ function debounce(fn, delay) {
251
+ let timeoutId = null;
252
+ return function(...args) {
253
+ if (timeoutId !== null) clearTimeout(timeoutId);
254
+ timeoutId = setTimeout(() => {
255
+ fn.apply(this, args);
256
+ }, delay);
257
+ };
258
+ }
259
+
260
+ //#endregion
261
+ //#region src/function/memoize.ts
262
+ /**
263
+ * 创建函数的记忆化版本,缓存函数结果
264
+ * @param fn - 要记忆化的函数
265
+ * @param keyFn - 可选的缓存键生成函数
266
+ * @returns 记忆化后的函数
267
+ * @example
268
+ * ```ts
269
+ * const memoizedFn = memoize((a: number) => a * 2)
270
+ * memoizedFn(2) // 4,计算并缓存
271
+ * memoizedFn(2) // 4,从缓存返回
272
+ * ```
273
+ * @public
274
+ */
275
+ function memoize(fn, keyFn) {
276
+ const cache = /* @__PURE__ */ new Map();
277
+ return ((...args) => {
278
+ const key = keyFn ? keyFn(...args) : JSON.stringify(args);
279
+ if (cache.has(key)) return cache.get(key);
280
+ const result = fn(...args);
281
+ cache.set(key, result);
282
+ return result;
283
+ });
284
+ }
285
+
286
+ //#endregion
287
+ //#region src/function/partial.ts
288
+ /**
289
+ * 创建偏函数,预设部分参数
290
+ * @param fn - 原始函数
291
+ * @param presetArgs - 预设的参数
292
+ * @returns 偏函数
293
+ * @example
294
+ * ```ts
295
+ * const add = (a: number, b: number) => a + b
296
+ * const add5 = partial(add, 5)
297
+ * add5(3) // 8
298
+ * ```
299
+ * @public
300
+ */
301
+ function partial(fn, ...presetArgs) {
302
+ return function(...remainingArgs) {
303
+ return fn(...presetArgs, ...remainingArgs);
304
+ };
305
+ }
306
+
307
+ //#endregion
308
+ //#region src/function/throttle.ts
309
+ /**
310
+ * 节流函数,限制函数执行频率
311
+ * @param fn - 要执行的函数
312
+ * @param delay - 节流时间间隔(毫秒)
313
+ * @returns 节流后的函数
314
+ * @example
315
+ * ```ts
316
+ * const throttledFn = throttle(() => console.log('hello'), 300)
317
+ * throttledFn() // 立即执行
318
+ * throttledFn() // 300ms 内不执行
319
+ * ```
320
+ * @public
321
+ */
322
+ function throttle(fn, delay) {
323
+ let lastCall = 0;
324
+ let timeoutId = null;
325
+ return function(...args) {
326
+ const now = Date.now();
327
+ if (now - lastCall >= delay) {
328
+ lastCall = now;
329
+ fn.apply(this, args);
330
+ } else if (timeoutId === null) timeoutId = setTimeout(() => {
331
+ lastCall = Date.now();
332
+ timeoutId = null;
333
+ fn.apply(this, args);
334
+ }, delay - (now - lastCall));
335
+ };
336
+ }
337
+
338
+ //#endregion
339
+ //#region src/string/camelCase.ts
340
+ /**
341
+ * 将字符串转换为驼峰命名
342
+ * @param str - 输入字符串
343
+ * @returns 驼峰命名字符串
344
+ * @example
345
+ * ```ts
346
+ * camelCase('hello-world') // 'helloWorld'
347
+ * camelCase('hello_world') // 'helloWorld'
348
+ * camelCase('hello world') // 'helloWorld'
349
+ * ```
350
+ * @public
351
+ */
352
+ function camelCase(str) {
353
+ return str.replace(/[-_\s]+(.)?/g, (_, char) => char?.toUpperCase() || "").replace(/^(.)/, (_, char) => char?.toLowerCase() || "");
354
+ }
355
+
356
+ //#endregion
357
+ //#region src/string/capitalize.ts
358
+ /**
359
+ * 将字符串首字母大写
360
+ * @param str - 输入字符串
361
+ * @returns 首字母大写的字符串
362
+ * @example
363
+ * ```ts
364
+ * capitalize('hello') // 'Hello'
365
+ * capitalize('hello world') // 'Hello world'
366
+ * ```
367
+ * @public
368
+ */
369
+ function capitalize(str) {
370
+ return str.charAt(0).toUpperCase() + str.slice(1);
371
+ }
372
+
373
+ //#endregion
374
+ //#region src/string/isEmpty.ts
375
+ /**
376
+ * 检查字符串是否为空或只包含空白字符
377
+ * @param str - 输入字符串
378
+ * @returns 如果为空或只包含空白字符返回 true
379
+ * @example
380
+ * ```ts
381
+ * isEmpty('') // true
382
+ * isEmpty(' ') // true
383
+ * isEmpty(null) // true
384
+ * isEmpty(undefined) // true
385
+ * isEmpty('hello') // false
386
+ * ```
387
+ * @public
388
+ */
389
+ function isEmpty(str) {
390
+ return !str || str.trim().length === 0;
391
+ }
392
+
393
+ //#endregion
394
+ //#region src/string/kebabCase.ts
395
+ /**
396
+ * 将字符串转换为短横线命名
397
+ * @param str - 输入字符串
398
+ * @returns 短横线命名字符串
399
+ * @example
400
+ * ```ts
401
+ * kebabCase('helloWorld') // 'hello-world'
402
+ * kebabCase('hello_world') // 'hello-world'
403
+ * kebabCase('hello world') // 'hello-world'
404
+ * ```
405
+ * @public
406
+ */
407
+ function kebabCase(str) {
408
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
409
+ }
410
+
411
+ //#endregion
412
+ //#region src/string/random.ts
413
+ /**
414
+ * 生成指定长度的随机字符串
415
+ * @param length - 随机字符串的长度(默认: 8)
416
+ * @returns 随机字符串
417
+ * @example
418
+ * ```ts
419
+ * random() // 'aB3dE7fG'
420
+ * random(12) // 'xY9kL2mN3pQ'
421
+ * ```
422
+ * @public
423
+ */
424
+ function random(length = 8) {
425
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
426
+ let result = "";
427
+ for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * 62));
428
+ return result;
429
+ }
430
+
431
+ //#endregion
432
+ //#region src/string/snakeCase.ts
433
+ /**
434
+ * 将字符串转换为下划线命名
435
+ * @param str - 输入字符串
436
+ * @returns 下划线命名字符串
437
+ * @example
438
+ * ```ts
439
+ * snakeCase('helloWorld') // 'hello_world'
440
+ * snakeCase('hello-world') // 'hello_world'
441
+ * snakeCase('hello world') // 'hello_world'
442
+ * ```
443
+ * @public
444
+ */
445
+ function snakeCase(str) {
446
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
447
+ }
448
+
449
+ //#endregion
450
+ //#region src/string/truncate.ts
451
+ /**
452
+ * 截断字符串到指定长度
453
+ * @param str - 输入字符串
454
+ * @param length - 最大长度
455
+ * @param suffix - 截断时添加的后缀(默认: '...')
456
+ * @returns 截断后的字符串
457
+ * @example
458
+ * ```ts
459
+ * truncate('hello world', 5) // 'hello...'
460
+ * truncate('hello world', 11) // 'hello world'
461
+ * truncate('hello world', 5, '---') // 'hello---'
462
+ * ```
463
+ * @public
464
+ */
465
+ function truncate(str, length, suffix = "...") {
466
+ if (str.length <= length) return str;
467
+ return str.slice(0, length) + suffix;
468
+ }
469
+
470
+ //#endregion
471
+ //#region src/util/isTypeOf.ts
472
+ /**
473
+ * 安全的类型检查
474
+ * @param arg - 判断的参数
475
+ * @returns 返回类型字符串
476
+ * @example
477
+ * ```ts
478
+ * isTypeOf(10); // "number"
479
+ * isTypeOf("abc"); // "string"
480
+ * isTypeOf(true); // "boolean"
481
+ * isTypeOf(null); // "null"
482
+ * isTypeOf(undefined); // "undefined"
483
+ * isTypeOf({a: 1}); // "object"
484
+ * isTypeOf([1,2,3]); // "array"
485
+ * isTypeOf(new Date()); // "date"
486
+ * isTypeOf(/^\d{6}$/); // "regexp"
487
+ * isTypeOf(Symbol('1')); // "symbol"
488
+ * isTypeOf(function(){}); // "function"
489
+ * isTypeOf(new Error()); // "error"
490
+ * isTypeOf(new Promise(()=>{})); // "promise"
491
+ * isTypeOf(new Set()); // "set"
492
+ * isTypeOf(new Map()); // "map"
493
+ * isTypeOf(Math); // "math"
494
+ * ```
495
+ * @public
496
+ */
497
+ function isTypeOf(arg) {
498
+ return Object.prototype.toString.call(arg).replace(/\[object (\w+)\]/, "$1").toLowerCase();
499
+ }
500
+
501
+ //#endregion
502
+ //#region src/util/isArray.ts
503
+ /**
504
+ * 是否是数组
505
+ * @param arg - 参数
506
+ * @returns `true|false`
507
+ * @example
508
+ * ```ts
509
+ * isArray({}) // false
510
+ * isArray(1) // false
511
+ * isArray([]) // true
512
+ * ```
513
+ *
514
+ * @public
515
+ */
516
+ function isArray(arg) {
517
+ return isTypeOf(arg) === "array";
518
+ }
519
+
520
+ //#endregion
521
+ //#region src/util/isEmptyObject.ts
522
+ /**
523
+ * 是否是空对象
524
+ * @remarks
525
+ * ```
526
+ * 1、校验结果和`jquery.isEmptyObject()`一致
527
+ * 2、只有可枚举对象并且有枚举的值才返回false
528
+ * 3、不可枚举值,始终返回true
529
+ * ```
530
+ * @param arg
531
+ * @returns `true|false`
532
+ * @example
533
+ * ```ts
534
+ * isEmptyObject('') // true 不可枚举
535
+ * isEmptyObject(null) // true 不可枚举
536
+ * isEmptyObject(undefined) // true 不可枚举
537
+ * isEmptyObject({}) // true 可枚举,没有枚举属性
538
+ * isEmptyObject({a:1}) // false 可枚举,有枚举属性
539
+ * isEmptyObject(new Object()) // true 可枚举,没有属性
540
+ * isEmptyObject([]) // true 可枚举,没有枚举属性
541
+ * isEmptyObject([1]) // false 可枚举,有枚举属性
542
+ * ```
543
+ * @public
544
+ */
545
+ function isEmptyObject(arg) {
546
+ if (arg == null) return true;
547
+ for (const _key in arg) return false;
548
+ return true;
549
+ }
550
+
551
+ //#endregion
552
+ //#region src/util/isObject.ts
553
+ /**
554
+ * 是否是对象
555
+ * @param arg - 参数
556
+ * @returns `true|false`
557
+ * @example
558
+ * ```ts
559
+ * isObject({}) // true
560
+ * isObject(1) // false
561
+ * isObject([]) // false
562
+ * ```
563
+ *
564
+ * @public
565
+ */
566
+ function isObject(arg) {
567
+ return isTypeOf(arg) === "object";
568
+ }
569
+
570
+ //#endregion
571
+ //#region src/string/safeJsonStringify.ts
572
+ /**
573
+ * 安全的json序列化,只对object和array进行转换,其他原样返回
574
+ * @param value - 待序列化的值
575
+ * @returns 序列化的值
576
+ * @example
577
+ * ```ts
578
+ * safeJsonStringify({a:1}); // => `{"a":1}`
579
+ * safeJsonStringify([1,"2"]); // => `[1,"2"]`
580
+ * safeJsonStringify({}); // => null 空值处理
581
+ * safeJsonStringify([]); // => null 空值处理
582
+ * safeJsonStringify(null); // => null 空值处理
583
+ * safeJsonStringify("abc"); // => "abc" 原样返回
584
+ * safeJsonStringify(123); // => 123 原样返回
585
+ * ```
586
+ */
587
+ function safeJsonStringify(value) {
588
+ if (isObject(value)) return isEmptyObject(value) ? null : JSON.stringify(value);
589
+ else if (isArray(value)) return isEmptyObject(value) ? null : JSON.stringify(value);
590
+ else return value;
591
+ }
592
+
593
+ //#endregion
594
+ //#region src/util/isString.ts
595
+ /**
596
+ * 是否是字符串
597
+ * @param arg - 参数
598
+ * @returns `true|false`
599
+ * @example
600
+ * ```ts
601
+ * isString(123) // false
602
+ * isString("abc") // true
603
+ * isString(String(1)) // true
604
+ * ```
605
+ *
606
+ * @public
607
+ */
608
+ function isString(arg) {
609
+ return isTypeOf(arg) === "string";
610
+ }
611
+
612
+ //#endregion
613
+ //#region src/string/safeParseJson.ts
614
+ /**
615
+ * 安全的JSON字符串解析
616
+ * @param jsonString - 待转换的字符串
617
+ * @param rollback - 转换失败后回退的备选值,默认`{}`
618
+ * @returns JSONObject JSONArray
619
+ * @example
620
+ * ```ts
621
+ * safeParseJson('{"name": "abc", age: 20}'); // => {name: "abc", age: 20} 正常解析
622
+ * safeParseJson(0); // => {} 解析失败默认回退处理
623
+ * safeParseJson(`[name=123]`, []); // => [] 解析失败回退处理
624
+ * ```
625
+ */
626
+ function safeParseJson(jsonString, rollback = {}) {
627
+ if (isString(jsonString)) {
628
+ let json;
629
+ try {
630
+ json = JSON.parse(jsonString);
631
+ } catch (e) {
632
+ json = rollback;
633
+ }
634
+ return json;
635
+ } else return rollback;
636
+ }
637
+
638
+ //#endregion
639
+ //#region src/util/isBoolean.ts
640
+ /**
641
+ * 是否是布尔值
642
+ * @param arg - 参数
643
+ * @returns `true|false`
644
+ * @example
645
+ * ```ts
646
+ * isBoolean(123) // false
647
+ * isBoolean(Boolean('1')) // true
648
+ * isBoolean(true) // true
649
+ * ```
650
+ *
651
+ * @public
652
+ */
653
+ function isBoolean(arg) {
654
+ return isTypeOf(arg) === "boolean";
655
+ }
656
+
657
+ //#endregion
658
+ //#region src/util/isDate.ts
659
+ /**
660
+ * 是否是日期对象
661
+ * @param arg - 参数
662
+ * @returns `true|false`
663
+ * @example
664
+ * ```ts
665
+ * isDate(new Date()) // true
666
+ * ```
667
+ *
668
+ * @public
669
+ */
670
+ function isDate(arg) {
671
+ return isTypeOf(arg) === "date";
672
+ }
673
+
674
+ //#endregion
675
+ //#region src/util/isError.ts
676
+ /**
677
+ * 是否是错误对象
678
+ * @param arg - 参数
679
+ * @returns `true|false`
680
+ * @example
681
+ * ```ts
682
+ * isError(new Error('122')) // true
683
+ * ```
684
+ *
685
+ * @public
686
+ */
687
+ function isError(arg) {
688
+ return isTypeOf(arg) === "error";
689
+ }
690
+
691
+ //#endregion
692
+ //#region src/util/isFunction.ts
693
+ /**
694
+ * 是否是函数
695
+ * @param arg - 参数
696
+ * @returns `true|false`
697
+ * @example
698
+ * ```ts
699
+ * isFunction(function(){}) // true
700
+ * ```
701
+ *
702
+ * @public
703
+ */
704
+ function isFunction(arg) {
705
+ return isTypeOf(arg) === "function";
706
+ }
707
+
708
+ //#endregion
709
+ //#region src/util/isMap.ts
710
+ /**
711
+ * 是否是Map
712
+ * @param arg - 参数
713
+ * @returns `true|false`
714
+ * @example
715
+ * ```ts
716
+ * isMap(new Map()) // true
717
+ * ```
718
+ *
719
+ * @public
720
+ */
721
+ function isMap(arg) {
722
+ return isTypeOf(arg) === "map";
723
+ }
724
+
725
+ //#endregion
726
+ //#region src/util/isNull.ts
727
+ /**
728
+ * 是否是null
729
+ * @param arg - 参数
730
+ * @returns `true|false`
731
+ * @example
732
+ * ```ts
733
+ * isNull(null) // true
734
+ * isNull(1) // false
735
+ * ```
736
+ *
737
+ * @public
738
+ */
739
+ function isNull(arg) {
740
+ return isTypeOf(arg) === "null";
741
+ }
742
+
743
+ //#endregion
744
+ //#region src/util/isUndefined.ts
745
+ /**
746
+ * 是否是undefined
747
+ * @param arg - 参数
748
+ * @returns `true|false`
749
+ * @example
750
+ * ```ts
751
+ * isUndefined(undefined) // true
752
+ * isUndefined(a) // true a未定义
753
+ * isUndefined(void 0) // true
754
+ * ```
755
+ *
756
+ * @public
757
+ */
758
+ function isUndefined(arg) {
759
+ return typeof arg === "undefined";
760
+ }
761
+
762
+ //#endregion
763
+ //#region src/util/isNil.ts
764
+ /**
765
+ * 检测`value`是否是`null`或`undefined`
766
+ * @param value - 待检测值
767
+ * @returns boolean
768
+ * @example
769
+ * ```ts
770
+ * isNil(null); // => true
771
+ * isNil(void 0); // => true
772
+ * isNil(NaN); // => false
773
+ * ```
774
+ */
775
+ function isNil(value) {
776
+ if (isUndefined(value)) return true;
777
+ else return isNull(value);
778
+ }
779
+
780
+ //#endregion
781
+ //#region src/util/isNumber.ts
782
+ /**
783
+ * 是否是数字
784
+ * @param arg - 参数
785
+ * @returns `true|false`
786
+ * @example
787
+ * ```ts
788
+ * isNumber(123) // true
789
+ * isNumber(Number('1')) // true
790
+ * isNumber("abc") // false
791
+ * ```
792
+ *
793
+ * @public
794
+ */
795
+ function isNumber(arg) {
796
+ return isTypeOf(arg) === "number";
797
+ }
798
+
799
+ //#endregion
800
+ //#region src/util/isPromise.ts
801
+ /**
802
+ * 是否是Promise
803
+ * @param arg - 参数
804
+ * @returns `true|false`
805
+ * @example
806
+ * ```ts
807
+ * isPromise(new Promise(()=>{})) // true
808
+ * ```
809
+ *
810
+ * @public
811
+ */
812
+ function isPromise(arg) {
813
+ return isTypeOf(arg) === "promise";
814
+ }
815
+
816
+ //#endregion
817
+ //#region src/util/isRegExp.ts
818
+ /**
819
+ * 是否是正则
820
+ * @param arg - 参数
821
+ * @returns `true|false`
822
+ * @example
823
+ * ```ts
824
+ * isRegExp(/^\d+/) // true
825
+ * isRegExp(new RegExp("\w")) // true
826
+ * ```
827
+ *
828
+ * @public
829
+ */
830
+ function isRegExp(arg) {
831
+ return isTypeOf(arg) === "regexp";
832
+ }
833
+
834
+ //#endregion
835
+ //#region src/util/isSet.ts
836
+ /**
837
+ * 是否是Set
838
+ * @param arg - 参数
839
+ * @returns `true|false`
840
+ * @example
841
+ * ```ts
842
+ * isSet(new Set([1,2,3])) // true
843
+ * ```
844
+ *
845
+ * @public
846
+ */
847
+ function isSet(arg) {
848
+ return isTypeOf(arg) === "set";
849
+ }
850
+
851
+ //#endregion
852
+ //#region src/util/isSymbol.ts
853
+ /**
854
+ * 是否是符号
855
+ * @param arg - 参数
856
+ * @returns `true|false`
857
+ * @example
858
+ * ```ts
859
+ * isSymbol(Symbol(1)) // true
860
+ * ```
861
+ *
862
+ * @public
863
+ */
864
+ function isSymbol(arg) {
865
+ return isTypeOf(arg) === "symbol";
866
+ }
867
+
868
+ //#endregion
869
+ //#region src/url/querystringify.ts
870
+ /**
871
+ * 解码一个URI编码的字符串。
872
+ *
873
+ * @param input - URI编码字符串
874
+ * @returns 解码后的字符串
875
+ * @internal
876
+ */
877
+ function _decode(input) {
878
+ try {
879
+ return decodeURIComponent(input.replace(/\+/g, " "));
880
+ } catch (e) {
881
+ return null;
882
+ }
883
+ }
884
+ /**
885
+ * 尝试编码给定输入
886
+ *
887
+ * @param input - 需要编码的字符串
888
+ * @returns 编码后的字符串
889
+ * @internal
890
+ */
891
+ function _encode(input) {
892
+ try {
893
+ return encodeURIComponent(input);
894
+ } catch (e) {
895
+ return null;
896
+ }
897
+ }
898
+ /**
899
+ * querystring转换成对象
900
+ *
901
+ * @param obj - 需要被转换的查询对象
902
+ * @param prefix - 添加前缀
903
+ * @returns url查询字符串
904
+ * @example
905
+ * ```ts
906
+ * querystringify({ foo: bar }); // foo=bar
907
+ * querystringify({ foo: bar }, '#'); // #foo=bar
908
+ * querystringify({ foo: '' }, '&'); // &foo=
909
+ * ```
910
+ *
911
+ * @public
912
+ */
913
+ function querystringify(obj, prefix = "") {
914
+ let pairs = [];
915
+ let value;
916
+ let key;
917
+ for (key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) {
918
+ value = obj[key];
919
+ if (!value && (value === null || value === void 0 || isNaN(value))) value = "";
920
+ key = _encode(key);
921
+ value = _encode(value);
922
+ if (key === null) continue;
923
+ if (value === null) value = "";
924
+ pairs.push(key + "=" + value);
925
+ }
926
+ return pairs.length ? prefix + pairs.join("&") : "";
927
+ }
928
+
929
+ //#endregion
930
+ //#region src/url/parseQueryString.ts
931
+ /**
932
+ * 解析URL查询字符串,转换成JSON格式
933
+ *
934
+ * @param query - 查询字符串,可以是?开头也可以是#号开头
935
+ * @returns 解析为对象
936
+ * @example
937
+ * ```ts
938
+ * parseQueryString('?foo=bar'); // { foo: 'bar' }
939
+ * parseQueryString('#foo=bar'); // { foo: 'bar' }
940
+ * parseQueryString('foo=bar'); // { foo: 'bar' }
941
+ * parseQueryString('foo=bar&bar=foo'); // { foo: 'bar', bar: 'foo' }
942
+ * parseQueryString('foo&bar=foo'); // { foo: '', bar: 'foo' }
943
+ * ```
944
+ *
945
+ * @public
946
+ */
947
+ function parseQueryString(query) {
948
+ if (!query) return {};
949
+ let parser = /([^=?#&]+)=?([^&]*)/g, result = {}, part;
950
+ while (part = parser.exec(query)) {
951
+ let key = _decode(part[1]), value = _decode(part[2]);
952
+ if (key === null) continue;
953
+ if (value === null) value = void 0;
954
+ result[key] = value;
955
+ }
956
+ return result;
957
+ }
958
+
959
+ //#endregion
960
+ //#region src/url/urlParse.ts
961
+ /**
962
+ * url解析,在URL基础上扩展了查询字符串解析对象
963
+ * @param urlString - url格式字符串
964
+ * @returns IURL - 返回URL对象扩展了searchObject和hashSearchObject
965
+ * @example
966
+ * ```ts
967
+ * const urlStr = 'https://www.baidu.com/path1/path2?foo=1&bar=a/#/hashpath1/hashpath2?hashfoo=1&hashbar=a'
968
+ * const url = urlParse(urlStr)
969
+ * {
970
+ * hash: "#/hashpath1/hashpath2?hashfoo=1&hashbar=a",
971
+ * hashSearchObject: {
972
+ * hashfoo: '1',
973
+ * hashbar: 'a'
974
+ * }
975
+ * host: "www.baidu.com"
976
+ * hostname: "www.baidu.com"
977
+ * href: "https://www.baidu.com/path1/path2?foo=1&bar=a/#/hashpath1/hashpath2?hashfoo=1&hashbar=a"
978
+ * origin: "https://www.baidu.com"
979
+ * password: ""
980
+ * pathname: "/path1/path2"
981
+ * port: ""
982
+ * protocol: "https:"
983
+ * search: "?foo=1&bar=a/"
984
+ * searchObject: {
985
+ * foo: '1',
986
+ * bar: 'a'
987
+ * }
988
+ * username: ""
989
+ * }
990
+ * ```
991
+ *
992
+ * @public
993
+ */
994
+ function urlParse(urlString = "") {
995
+ try {
996
+ const url = new URL(urlString);
997
+ const parsed = {};
998
+ for (const key in url) if (key !== "toString" && key !== "toJSON" && key !== "searchParams") parsed[key] = url[key];
999
+ parsed["searchObject"] = parseQueryString(url.search.replace(/\/$/, ""));
1000
+ parsed["hashSearchObject"] = parseQueryString(url.hash.replace(/^#.+\?/, ""));
1001
+ return parsed;
1002
+ } catch (err) {
1003
+ return {};
1004
+ }
1005
+ }
1006
+
1007
+ //#endregion
1008
+ //#region src/accountant/convertChineseNumber.ts
1009
+ /**
1010
+ * 阿拉伯数字转中文数字
1011
+ * @param num 需要转换的数字
1012
+ * @returns 返回转换后的中文字符串
1013
+ * @example
1014
+ * ```ts
1015
+ * convertChineseNumber(0); // 零
1016
+ * convertChineseNumber(1); // 一
1017
+ * convertChineseNumber(10); // 十
1018
+ * convertChineseNumber(11); // 十一
1019
+ * convertChineseNumber(11.12); // 十一点一二
1020
+ * convertChineseNumber(100); // 一百
1021
+ * convertChineseNumber(123); // 一百二十三
1022
+ * ```
1023
+ * @public
1024
+ */
1025
+ function convertChineseNumber(num) {
1026
+ if (num === 0) return "零";
1027
+ const AA = [
1028
+ "零",
1029
+ "一",
1030
+ "二",
1031
+ "三",
1032
+ "四",
1033
+ "五",
1034
+ "六",
1035
+ "七",
1036
+ "八",
1037
+ "九",
1038
+ "十"
1039
+ ];
1040
+ const BB = [
1041
+ "",
1042
+ "十",
1043
+ "百",
1044
+ "千",
1045
+ "万",
1046
+ "亿",
1047
+ "点",
1048
+ ""
1049
+ ];
1050
+ const a = ("" + num).replace(/(^0*)/g, "").split(".");
1051
+ let k = 0;
1052
+ let re = "";
1053
+ for (let i = a[0].length - 1; i >= 0; i--) {
1054
+ switch (k) {
1055
+ case 0:
1056
+ re = BB[7] + re;
1057
+ break;
1058
+ case 4:
1059
+ if (!new RegExp("0{4}//d{" + (a[0].length - i - 1) + "}$").test(a[0])) re = BB[4] + re;
1060
+ break;
1061
+ case 8:
1062
+ re = BB[5] + re;
1063
+ BB[7] = BB[5];
1064
+ k = 0;
1065
+ break;
1066
+ }
1067
+ if (k % 4 == 2 && a[0].charAt(i + 2) !== "0" && a[0].charAt(i + 1) === "0") re = AA[0] + re;
1068
+ if (a[0].charAt(i) !== "0") re = AA[Number(a[0].charAt(i))] + BB[k % 4] + re;
1069
+ k++;
1070
+ }
1071
+ if (a.length > 1) {
1072
+ re += BB[6];
1073
+ for (let i = 0; i < a[1].length; i++) re += AA[Number(a[1].charAt(i))];
1074
+ }
1075
+ if (re == "一十") re = "十";
1076
+ if (re.match(/^一/) && re.length == 3) re = re.replace("一", "");
1077
+ return re;
1078
+ }
1079
+
1080
+ //#endregion
1081
+ //#region src/accountant/convertCurrency.ts
1082
+ const CN_CHAR = {
1083
+ "$0": "零",
1084
+ "$1": "壹",
1085
+ "$2": "贰",
1086
+ "$3": "叁",
1087
+ "$4": "肆",
1088
+ "$5": "伍",
1089
+ "$6": "陆",
1090
+ "$7": "柒",
1091
+ "$8": "捌",
1092
+ "$9": "玖",
1093
+ "$10": "拾",
1094
+ "$100": "佰",
1095
+ "$1000": "仟",
1096
+ "$10000": "万",
1097
+ "$100000000": "亿",
1098
+ "CN_SYMBOL": "人民币",
1099
+ "DOLLAR": "元",
1100
+ "$0.1": "角",
1101
+ "$0.01": "分",
1102
+ "$0.001": "毫",
1103
+ "$0.0001": "厘",
1104
+ "INTEGER": "整"
1105
+ };
1106
+ /**
1107
+ * 阿拉伯数字换成汉字大写金额
1108
+ * @remarks
1109
+ * ```
1110
+ * 小数点最多支持到4位单位厘
1111
+ * 金额最大支持1万亿
1112
+ * ```
1113
+ * @param currencyDigits - 转换的金额字符串或数字
1114
+ * @param prefixSymbol - 前缀默认false(true=人民币, false='')
1115
+ * @returns 转换成汉字大写金额格式
1116
+ * @example
1117
+ * ```ts
1118
+ * convertCurrency(0); // 零圆整
1119
+ * convertCurrency(1234) // 壹仟贰佰叁拾肆圆整
1120
+ * convertCurrency('9877662.090') // 玖佰捌拾柒万柒仟陆佰陆拾贰圆零角玖分
1121
+ * convertCurrency('9877662.097866') // 玖佰捌拾柒万柒仟陆佰陆拾贰圆零角玖分柒毫捌厘
1122
+ * convertCurrency('4231234.04', true)) // 人民币肆佰贰拾叁万壹仟贰佰叁拾肆圆零角肆分
1123
+ * ```
1124
+ * @public
1125
+ */
1126
+ function convertCurrency(currencyDigits, prefixSymbol = false) {
1127
+ const MAXIMUM_INTEGRAL = 999999999999;
1128
+ let integral;
1129
+ let decimal;
1130
+ let outputCharacters;
1131
+ let parts;
1132
+ let digits, radices, bigRadices, decimals;
1133
+ if (isNaN(Number(currencyDigits))) {
1134
+ console.warn("金额无效!");
1135
+ return "";
1136
+ }
1137
+ let currencyDigitsStr = currencyDigits.toString();
1138
+ if (currencyDigitsStr === "") {
1139
+ console.warn("请输入小写金额!");
1140
+ return "";
1141
+ }
1142
+ if (currencyDigitsStr.match(/[^,.\d]/) != null) {
1143
+ console.warn("小写金额含有无效字符!");
1144
+ return "";
1145
+ }
1146
+ if (currencyDigitsStr.match(/^((\d{1,3}(,\d{3})*(.((\d{3},)*\d{1,3}))?)|(\d+(.\d+)?))$/) == null) {
1147
+ console.warn("小写金额的格式不正确!");
1148
+ return "";
1149
+ }
1150
+ currencyDigitsStr = currencyDigitsStr.replace(/,/g, "");
1151
+ currencyDigitsStr = currencyDigitsStr.replace(/^0+/, "");
1152
+ parts = currencyDigitsStr.split(".");
1153
+ if (parts.length > 1) {
1154
+ integral = parts[0];
1155
+ decimal = parts[1];
1156
+ decimal = decimal.substr(0, 4);
1157
+ } else {
1158
+ integral = parts[0];
1159
+ decimal = "";
1160
+ }
1161
+ if (Number(integral) > MAXIMUM_INTEGRAL) integral = MAXIMUM_INTEGRAL + "";
1162
+ digits = [
1163
+ CN_CHAR.$0,
1164
+ CN_CHAR.$1,
1165
+ CN_CHAR.$2,
1166
+ CN_CHAR.$3,
1167
+ CN_CHAR.$4,
1168
+ CN_CHAR.$5,
1169
+ CN_CHAR.$6,
1170
+ CN_CHAR.$7,
1171
+ CN_CHAR.$8,
1172
+ CN_CHAR.$9
1173
+ ];
1174
+ radices = [
1175
+ "",
1176
+ CN_CHAR.$10,
1177
+ CN_CHAR.$100,
1178
+ CN_CHAR.$1000
1179
+ ];
1180
+ bigRadices = [
1181
+ "",
1182
+ CN_CHAR.$10000,
1183
+ CN_CHAR.$100000000
1184
+ ];
1185
+ decimals = [
1186
+ CN_CHAR["$0.1"],
1187
+ CN_CHAR["$0.01"],
1188
+ CN_CHAR["$0.001"],
1189
+ CN_CHAR["$0.0001"]
1190
+ ];
1191
+ outputCharacters = "";
1192
+ if (Number(integral) > 0) {
1193
+ let zeroCount = 0;
1194
+ for (let i = 0; i < integral.length; i++) {
1195
+ let p = integral.length - i - 1;
1196
+ let d = integral.substr(i, 1);
1197
+ let quotient = p / 4;
1198
+ let modulus = p % 4;
1199
+ if (d == "0") zeroCount++;
1200
+ else {
1201
+ if (zeroCount > 0) outputCharacters += digits[0];
1202
+ zeroCount = 0;
1203
+ outputCharacters += digits[Number(d)] + radices[modulus];
1204
+ }
1205
+ if (modulus == 0 && zeroCount < 4) {
1206
+ outputCharacters += bigRadices[quotient];
1207
+ zeroCount = 0;
1208
+ }
1209
+ }
1210
+ outputCharacters += CN_CHAR.DOLLAR;
1211
+ }
1212
+ decimal = decimal.replace(/0+$/, "");
1213
+ if (decimal !== "") for (let i = 0; i < decimal.length; i++) {
1214
+ let d = decimal.substr(i, 1);
1215
+ outputCharacters += digits[Number(d)] + decimals[i];
1216
+ }
1217
+ if (outputCharacters == "") outputCharacters = CN_CHAR.$0 + CN_CHAR.DOLLAR;
1218
+ if (decimal == "") outputCharacters += CN_CHAR.INTEGER;
1219
+ if (prefixSymbol) outputCharacters = CN_CHAR.CN_SYMBOL + outputCharacters;
1220
+ return outputCharacters;
1221
+ }
1222
+
1223
+ //#endregion
1224
+ //#region src/_internal/_numeral.ts
1225
+ let _numeral = null;
1226
+ /**
1227
+ * 异步获取 Numeral 实例(已配置中文本地化)
1228
+ * 这是一个懒加载导入,避免将 numeral 作为直接依赖
1229
+ *
1230
+ * @example
1231
+ * ```ts
1232
+ * const numeral = await getNumeral()
1233
+ * numeral(1000).format('$0,0.00') // '¥1,000.00'
1234
+ * ```
1235
+ */
1236
+ async function getNumeral() {
1237
+ if (_numeral) return _numeral;
1238
+ const numeral = await import("numeral");
1239
+ numeral.register("locale", "ch", {
1240
+ delimiters: {
1241
+ thousands: ",",
1242
+ decimal: "."
1243
+ },
1244
+ abbreviations: {
1245
+ thousand: "千",
1246
+ million: "百万",
1247
+ billion: "十亿",
1248
+ trillion: "兆"
1249
+ },
1250
+ ordinal: function() {
1251
+ return ".";
1252
+ },
1253
+ currency: { symbol: "¥" }
1254
+ });
1255
+ numeral.locale("ch");
1256
+ _numeral = numeral.default;
1257
+ return _numeral;
1258
+ }
1259
+ /**
1260
+ * 同步获取 Numeral 实例(仅用于测试环境)
1261
+ * 使用 ESM 动态导入,配合 vitest setupFiles 预加载使用
1262
+ *
1263
+ * 注意:此函数本质是异步的,但为了保持 API 一致性命名为 sync
1264
+ * 请确保在测试运行前通过 setupFiles 预加载
1265
+ *
1266
+ * @throws {Error} 如果 numeral 未安装
1267
+ *
1268
+ * @example
1269
+ * ```ts
1270
+ * // 在测试 setup 文件中预加载
1271
+ * import { getNumeralSync } from './_numeral'
1272
+ * await getNumeralSync()
1273
+ *
1274
+ * // 然后在代码中可以直接使用
1275
+ * const numeral = getNumeralSync() // 返回缓存实例
1276
+ * ```
1277
+ */
1278
+ function getNumeralSync() {
1279
+ if (_numeral) return _numeral;
1280
+ throw new Error("numeral 未加载。请确保在测试 setup 文件中调用 await getNumeralSync() 预加载");
1281
+ }
1282
+
1283
+ //#endregion
1284
+ //#region src/accountant/format.ts
1285
+ /**
1286
+ * 通用格式化,需要手动指定格式
1287
+ * @remarks
1288
+ * format用法:http://numeraljs.com/
1289
+ * @param input - 待格式化数字
1290
+ * @param format - 输出格式,默认'',默认不保留小数,格式整数
1291
+ * @returns 返回格式化字符串
1292
+ * @example
1293
+ * ```ts
1294
+ * format(1230974.998979).format() // "1,230,975"
1295
+ * ```
1296
+ * @public
1297
+ */
1298
+ function format(input, format = "") {
1299
+ try {
1300
+ return getNumeralSync()(input).format(format);
1301
+ } catch (e) {
1302
+ console.warn("格式化错误,请确保已安装 numeral: npm install numeral");
1303
+ return "";
1304
+ }
1305
+ }
1306
+
1307
+ //#endregion
1308
+ //#region src/accountant/formatMoney.ts
1309
+ /**
1310
+ * 金额格式化
1311
+ * @remarks
1312
+ * ```
1313
+ * `0,0.00`:尾端保留2位小数,四舍五入
1314
+ * format用法:http://numeraljs.com/
1315
+ * ```
1316
+ * @param input - 待格式化金额
1317
+ * @param format - 输出格式,默认'0,0.00',尾端保留2位小数,四舍五入,自定义需手动指定格式
1318
+ * @returns 返回千分位格式化字符串
1319
+ * @example
1320
+ * ```ts
1321
+ * formatMoney(1000.234) // 1,000.23
1322
+ * formatMoney(1000.235) // 1,000.24
1323
+ * formatMoney(1000.235, '$0,0.00') // ¥1,000.24 只设置货币
1324
+ * formatNumber(1230974, '0.0a') // 1.2千万
1325
+ * ```
1326
+ * @public
1327
+ */
1328
+ function formatMoney(input, format = "0,0.00") {
1329
+ try {
1330
+ return getNumeralSync()(input).format(format);
1331
+ } catch (e) {
1332
+ console.warn("金额格式化错误,请确保已安装 numeral: npm install numeral");
1333
+ return "";
1334
+ }
1335
+ }
1336
+
1337
+ //#endregion
1338
+ //#region src/accountant/formatNumber.ts
1339
+ /**
1340
+ * 数字格式化
1341
+ * @remarks
1342
+ * ```
1343
+ * `0,0[.]00` - 小数位(可选)有则四舍五入保留2位小数
1344
+ * format用法:http://numeraljs.com/
1345
+ * ```
1346
+ * @param input - 待格式化数字
1347
+ * @param format - 输出格式,默认'0,0[.]00',小数位(可选)有则四舍五入保留2位小数,自定义需手动指定格式
1348
+ * @returns 返回千分位格式化字符串
1349
+ * @example
1350
+ * ```ts
1351
+ * formatNumber(789789.025) // "789,789.03"
1352
+ * formatNumber(789789.00) // "789,789"
1353
+ * formatNumber(789789.005) // "789,789.01"
1354
+ * formatNumber(1230974, '0.0a') // 1.2千万
1355
+ * ```
1356
+ * @public
1357
+ */
1358
+ function formatNumber(input, format = "0,0[.]00") {
1359
+ try {
1360
+ return getNumeralSync()(input).format(format);
1361
+ } catch (e) {
1362
+ console.warn("数字格式化错误,请确保已安装 numeral: npm install numeral");
1363
+ return "";
1364
+ }
1365
+ }
1366
+
1367
+ //#endregion
1368
+ //#region src/accountant/formatPercentage.ts
1369
+ /**
1370
+ * 百分比格式化
1371
+ * @remarks
1372
+ * ```
1373
+ * `0.[00]%` - 小数位(可选)有则四舍五入保留2位小数
1374
+ * format用法:http://numeraljs.com/
1375
+ * ```
1376
+ * @param input - 待格式化数字
1377
+ * @param format - 输出格式,默认'0.[00]%',小数位(可选)有则四舍五入保留2位小数,自定义需手动指定格式
1378
+ * @returns 返回百分比格式化字符串
1379
+ * @example
1380
+ * ```ts
1381
+ * formatPercentage(0.4567) // "45.67%"
1382
+ * formatPercentage(0.456) // "45.6%"
1383
+ * formatPercentage(0.45600) // "46%"
1384
+ * formatPercentage(0.4567, '0.00%') // "45.67%"
1385
+ * ```
1386
+ * @public
1387
+ */
1388
+ function formatPercentage(input, format = "0.00%") {
1389
+ try {
1390
+ return getNumeralSync()(input).format(format);
1391
+ } catch (e) {
1392
+ console.warn("百分比格式化错误,请确保已安装 numeral: npm install numeral");
1393
+ return "";
1394
+ }
1395
+ }
1396
+
1397
+ //#endregion
1398
+ //#region src/accountant/getFormatStr.ts
1399
+ /**
1400
+ * 快速生成numeraljs格式字符串, 处理千分位和小数位,可以继续使用format方法进行格式化,小数位处理方式四舍五入
1401
+ * @remarks
1402
+ * @param options - 格式选项同数字组件的options { separate: true, point: 0 }
1403
+ * @param unit - 是否显示单位,formatType=“percent” 字段加上单位,如“%”, 其他字段使用自定义单元(如果设置了)
1404
+ * @returns 返回格式字符串
1405
+ * @example
1406
+ * ```ts
1407
+ * getFormatStr() // "0,0" 默认格式字符串
1408
+ * getFormatStr({ separate: true, point: 2 }) // '0,0.00'
1409
+ * getFormatStr({ separate: false, point: 2 }) // '00.00'
1410
+ * ```
1411
+ * @public
1412
+ */
1413
+ function getFormatStr(options = {
1414
+ separate: true,
1415
+ point: 0
1416
+ }) {
1417
+ const { separate, point } = options;
1418
+ let formatStr = "";
1419
+ formatStr = separate ? "0,0" : "0";
1420
+ if (point > 0) formatStr += "." + Array(point).fill(0).join("");
1421
+ return formatStr;
1422
+ }
1423
+
1424
+ //#endregion
1425
+ //#region src/accountant/toFixed.ts
1426
+ /**
1427
+ * 数字格式化,指定需要保留的小数位数,解决部分浏览器兼容性问题,如`(0.615).toFixed(2) === '0.61');`
1428
+ * @remarks
1429
+ * ```
1430
+ * 修复Number.prototype.toFixed兼容性问题
1431
+ * `(0.615).toFixed(2) === '0.61');`,按照银行家法原则应该是0.62''
1432
+ * 位数不够,后面补0
1433
+ * ```
1434
+ * @param number - 需要转换的数字
1435
+ * @param digits - 需要保留的位数,只允许 [0, 20] 之间的数字
1436
+ * @returns 返回格式化后的字符串
1437
+ * @example
1438
+ * ```ts
1439
+ * toFixed(25.198726354, 0); // 25
1440
+ * toFixed(25.198726354, 1); // 25.2
1441
+ * toFixed(25.198726354, 2); // 25.20
1442
+ * toFixed(25.198726354, 3); // 25.199
1443
+ * toFixed(25.198726354, 4); // 25.1987
1444
+ * toFixed(25.198726354, 5); // 25.19873
1445
+ * toFixed(25.198726354, 6); // 25.198726
1446
+ * toFixed(25, 2); // 25.00
1447
+ * toFixed(25.125, 4); // 25.1250
1448
+ * ```
1449
+ * @public
1450
+ */
1451
+ function toFixed(number, digits = 2) {
1452
+ if (digits < 0) digits = 0;
1453
+ if (digits > 20) digits = 20;
1454
+ return getNumeralSync()._.toFixed(number, digits, Math.round);
1455
+ }
1456
+
1457
+ //#endregion
1458
+ //#region src/accountant/unformat.ts
1459
+ /**
1460
+ * 反格式化,自动识别格式
1461
+ * @remarks
1462
+ * format用法:http://numeraljs.com/
1463
+ * @param input - 待格式化数字
1464
+ * @returns 返回转换后的数字,无法识别返回NaN
1465
+ *
1466
+ * @example
1467
+ * ```ts
1468
+ * unformat('18:13:09') // 65589 (秒)
1469
+ * unformat('34.78%') // 0.3478
1470
+ * unformat('7,883.9876') // 7883.9876
1471
+ * unformat('¥7,883.9876') // 7883.9876
1472
+ * unformat('1.8千') // 1800
1473
+ * unformat('1.8百万') // 1800000
1474
+ * unformat('1.8十亿') // 1800000000
1475
+ * unformat('1.8兆') // 1800000000000
1476
+ * ```
1477
+ *
1478
+ * @public
1479
+ */
1480
+ function unformat(input = "") {
1481
+ try {
1482
+ return getNumeralSync()(input).value();
1483
+ } catch (e) {
1484
+ console.warn("格式化错误,请确保已安装 numeral: npm install numeral");
1485
+ return NaN;
1486
+ }
1487
+ }
1488
+
1489
+ //#endregion
1490
+ //#region src/color/color.ts
1491
+ function isDarkColor(color) {
1492
+ return new TinyColor$1(color).isDark();
1493
+ }
1494
+ function isLightColor(color) {
1495
+ return new TinyColor$1(color).isLight();
1496
+ }
1497
+
1498
+ //#endregion
1499
+ //#region src/color/convert.ts
1500
+ /**
1501
+ * 将颜色转换为HSL格式。
1502
+ *
1503
+ * HSL是一种颜色模型,包括色相(Hue)、饱和度(Saturation)和亮度(Lightness)三个部分。
1504
+ *
1505
+ * @param {string} color 输入的颜色。
1506
+ * @returns {string} HSL格式的颜色字符串。
1507
+ */
1508
+ function convertToHsl(color) {
1509
+ const { a, h, l, s } = new TinyColor(color).toHsl();
1510
+ const hsl = `hsl(${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%)`;
1511
+ return a < 1 ? `${hsl} ${a}` : hsl;
1512
+ }
1513
+ /**
1514
+ * 将颜色转换为HSL CSS变量。
1515
+ *
1516
+ * 这个函数与convertToHsl函数类似,但是返回的字符串格式稍有不同,
1517
+ * 以便可以作为CSS变量使用。
1518
+ *
1519
+ * @param {string} color 输入的颜色。
1520
+ * @returns {string} 可以作为CSS变量使用的HSL格式的颜色字符串。
1521
+ */
1522
+ function convertToHslCssVar(color) {
1523
+ const { a, h, l, s } = new TinyColor(color).toHsl();
1524
+ const hsl = `${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
1525
+ return a < 1 ? `${hsl} / ${a}` : hsl;
1526
+ }
1527
+ /**
1528
+ * 将颜色转换为RGB颜色字符串
1529
+ * TinyColor无法处理hsl内包含'deg'、'grad'、'rad'或'turn'的字符串
1530
+ * 比如 hsl(231deg 98% 65%)将被解析为rgb(0, 0, 0)
1531
+ * 这里在转换之前先将这些单位去掉
1532
+ * @param str 表示HLS颜色值的字符串
1533
+ * @returns 如果颜色值有效,则返回对应的RGB颜色字符串;如果无效,则返回rgb(0, 0, 0)
1534
+ */
1535
+ function convertToRgb(str) {
1536
+ return new TinyColor(str.replace(/deg|grad|rad|turn/g, "")).toRgbString();
1537
+ }
1538
+ /**
1539
+ * 检查颜色是否有效
1540
+ * @param {string} color - 待检查的颜色
1541
+ * 如果颜色有效返回true,否则返回false
1542
+ */
1543
+ function isValidColor(color) {
1544
+ if (!color) return false;
1545
+ return new TinyColor(color).isValid;
1546
+ }
1547
+
1548
+ //#endregion
1549
+ //#region src/color/generator.ts
1550
+ function generatorColorVariables(colorItems) {
1551
+ const colorVariables = {};
1552
+ colorItems.forEach(({ alias, color, name }) => {
1553
+ if (color) {
1554
+ const colorsMap = getColors(new TinyColor(color).toHexString());
1555
+ let mainColor = colorsMap["500"];
1556
+ Object.keys(colorsMap).forEach((key) => {
1557
+ const colorValue = colorsMap[key];
1558
+ if (colorValue) {
1559
+ const hslColor = convertToHslCssVar(colorValue);
1560
+ colorVariables[`--${name}-${key}`] = hslColor;
1561
+ if (alias) colorVariables[`--${alias}-${key}`] = hslColor;
1562
+ if (key === "500") mainColor = hslColor;
1563
+ }
1564
+ });
1565
+ if (alias && mainColor) colorVariables[`--${alias}`] = mainColor;
1566
+ }
1567
+ });
1568
+ return colorVariables;
1569
+ }
1570
+
1571
+ //#endregion
1572
+ //#region src/date/formatDate.ts
1573
+ /**
1574
+ * 日期格式化模块
1575
+ */
1576
+ dayjs.extend(utc);
1577
+ dayjs.extend(timezone);
1578
+ /**
1579
+ * 格式化日期
1580
+ *
1581
+ * 将各种日期格式转换为指定格式的字符串,支持时区转换
1582
+ *
1583
+ * @param time - 日期值,可以是 Date 对象、dayjs 对象、时间戳或字符串
1584
+ * @param format - 输出格式,默认为 'YYYY-MM-DD'
1585
+ *
1586
+ * @returns 格式化后的日期字符串
1587
+ *
1588
+ * @example 格式化为日期
1589
+ * ```typescript
1590
+ * formatDate(new Date()); // '2024-01-25'
1591
+ * ```
1592
+ *
1593
+ * @example 格式化为日期时间
1594
+ * ```typescript
1595
+ * formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss'); // '2024-01-25 14:30:00'
1596
+ * ```
1597
+ *
1598
+ * @example 格式化为时间
1599
+ * ```typescript
1600
+ * formatDate(new Date(), 'HH:mm:ss'); // '14:30:00'
1601
+ * ```
1602
+ *
1603
+ * @example 格式化时间戳
1604
+ * ```typescript
1605
+ * formatDate(1706161800000, 'YYYY-MM-DD'); // '2024-01-25'
1606
+ * ```
1607
+ *
1608
+ * @example 格式化日期字符串
1609
+ * ```typescript
1610
+ * formatDate('2024-01-25T06:30:00Z', 'YYYY-MM-DD HH:mm'); // '2024-01-25 14:30'
1611
+ * ```
1612
+ *
1613
+ * @example 使用 dayjs 对象
1614
+ * ```typescript
1615
+ * import dayjs from 'dayjs';
1616
+ * const date = dayjs('2024-01-25');
1617
+ * formatDate(date, 'YYYY/MM/DD'); // '2024/01/25'
1618
+ * ```
1619
+ */
1620
+ function formatDate(time, format = "YYYY-MM-DD") {
1621
+ try {
1622
+ const date = dayjs.isDayjs(time) ? time : dayjs(time);
1623
+ if (!date.isValid()) throw new Error("Invalid date");
1624
+ return date.tz().format(format);
1625
+ } catch (error) {
1626
+ console.error(`Error formatting date: ${error}`);
1627
+ return String(time ?? "");
1628
+ }
1629
+ }
1630
+
1631
+ //#endregion
1632
+ //#region src/date/formatDateTime.ts
1633
+ /**
1634
+ * 格式化日期时间
1635
+ *
1636
+ * 将日期格式化为完整的日期时间字符串(YYYY-MM-DD HH:mm:ss)
1637
+ *
1638
+ * @param time - 日期值,可以是 Date 对象、dayjs 对象、时间戳或字符串
1639
+ *
1640
+ * @returns 格式化后的日期时间字符串
1641
+ *
1642
+ * @example 格式化当前日期时间
1643
+ * ```typescript
1644
+ * formatDateTime(); // '2024-01-25 14:30:00'
1645
+ * ```
1646
+ *
1647
+ * @example 格式化指定日期
1648
+ * ```typescript
1649
+ * formatDateTime(new Date(2024, 0, 25, 14, 30, 0)); // '2024-01-25 14:30:00'
1650
+ * ```
1651
+ *
1652
+ * @example 格式化时间戳
1653
+ * ```typescript
1654
+ * formatDateTime(1706161800000); // '2024-01-25 14:30:00'
1655
+ * ```
1656
+ *
1657
+ * @example 格式化字符串日期
1658
+ * ```typescript
1659
+ * formatDateTime('2024-01-25T06:30:00Z'); // '2024-01-25 14:30:00'
1660
+ * ```
1661
+ */
1662
+ function formatDateTime(time) {
1663
+ return formatDate(time, "YYYY-MM-DD HH:mm:ss");
1664
+ }
1665
+
1666
+ //#endregion
1667
+ //#region src/date/isDateInstance.ts
1668
+ /**
1669
+ * 日期实例类型检查模块
1670
+ */
1671
+ /**
1672
+ * 检查值是否为 Date 实例
1673
+ *
1674
+ * 使用 instanceof 操作符判断一个值是否为 Date 对象
1675
+ *
1676
+ * @param value - 要检查的值
1677
+ * @returns 是否为 Date 实例
1678
+ *
1679
+ * @example 检查 Date 对象
1680
+ * ```typescript
1681
+ * const date = new Date();
1682
+ * isDateInstance(date); // true
1683
+ * ```
1684
+ *
1685
+ * @example 检查非 Date 对象
1686
+ * ```typescript
1687
+ * isDateInstance('2024-01-25'); // false
1688
+ * isDateInstance(123456); // false
1689
+ * isDateInstance(null); // false
1690
+ * ```
1691
+ *
1692
+ * @example 类型守卫
1693
+ * ```typescript
1694
+ * function process(value: unknown) {
1695
+ * if (isDateInstance(value)) {
1696
+ * console.log(value.getFullYear()); // TypeScript 知道 value 是 Date
1697
+ * }
1698
+ * }
1699
+ * ```
1700
+ */
1701
+ function isDateInstance(value) {
1702
+ return value instanceof Date;
1703
+ }
1704
+
1705
+ //#endregion
1706
+ //#region src/date/isDayjsObject.ts
1707
+ /**
1708
+ * dayjs 对象类型检查模块
1709
+ */
1710
+ /**
1711
+ * 检查值是否为 dayjs 对象
1712
+ *
1713
+ * 使用 dayjs.isDayjs 方法判断一个值是否为 dayjs 对象
1714
+ *
1715
+ * @param value - 要检查的值
1716
+ * @returns 是否为 dayjs 对象
1717
+ *
1718
+ * @example 检查 dayjs 对象
1719
+ * ```typescript
1720
+ * const date = dayjs();
1721
+ * isDayjsObject(date); // true
1722
+ * ```
1723
+ *
1724
+ * @example 检查非 dayjs 对象
1725
+ * ```typescript
1726
+ * isDayjsObject(new Date()); // false
1727
+ * isDayjsObject('2024-01-25'); // false
1728
+ * isDayjsObject(123456); // false
1729
+ * ```
1730
+ *
1731
+ * @example 类型守卫
1732
+ * ```typescript
1733
+ * function process(value: unknown) {
1734
+ * if (isDayjsObject(value)) {
1735
+ * console.log(value.format('YYYY-MM-DD')); // TypeScript 知道 value 是 Dayjs
1736
+ * }
1737
+ * }
1738
+ * ```
1739
+ */
1740
+ function isDayjsObject(value) {
1741
+ return dayjs.isDayjs(value);
1742
+ }
1743
+
1744
+ //#endregion
1745
+ //#region src/date/constants.ts
1746
+ /**
1747
+ * 日期时间常量
1748
+ */
1749
+ dayjs.extend(utc);
1750
+ dayjs.extend(timezone);
1751
+ /**
1752
+ * 获取当前时区
1753
+ *
1754
+ * @returns 当前时区名称(如 'Asia/Shanghai')
1755
+ *
1756
+ * @example 获取系统时区
1757
+ * ```typescript
1758
+ * const tz = getSystemTimezone();
1759
+ * console.log(tz); // 'Asia/Shanghai'
1760
+ * ```
1761
+ */
1762
+ const getSystemTimezone = () => {
1763
+ return dayjs.tz.guess();
1764
+ };
1765
+ /**
1766
+ * 自定义设置的时区
1767
+ */
1768
+ let currentTimezone = getSystemTimezone();
1769
+ /**
1770
+ * 设置默认时区
1771
+ *
1772
+ * 设置后续所有日期操作使用的时区
1773
+ *
1774
+ * @param timezone - 时区字符串,如果为空则使用系统时区
1775
+ *
1776
+ * @example 设置为东八区
1777
+ * ```typescript
1778
+ * setCurrentTimezone('Asia/Shanghai');
1779
+ * ```
1780
+ *
1781
+ * @example 设置为 UTC
1782
+ * ```typescript
1783
+ * setCurrentTimezone('UTC');
1784
+ * ```
1785
+ *
1786
+ * @example 重置为系统时区
1787
+ * ```typescript
1788
+ * setCurrentTimezone();
1789
+ * ```
1790
+ */
1791
+ const setCurrentTimezone = (timezone) => {
1792
+ currentTimezone = timezone || getSystemTimezone();
1793
+ dayjs.tz.setDefault(currentTimezone);
1794
+ };
1795
+ /**
1796
+ * 获取设置的时区
1797
+ *
1798
+ * @returns 当前设置的时区名称
1799
+ *
1800
+ * @example 获取当前时区
1801
+ * ```typescript
1802
+ * setCurrentTimezone('Asia/Tokyo');
1803
+ * const tz = getCurrentTimezone();
1804
+ * console.log(tz); // 'Asia/Tokyo'
1805
+ * ```
1806
+ */
1807
+ const getCurrentTimezone = () => {
1808
+ return currentTimezone;
1809
+ };
1810
+
1811
+ //#endregion
1812
+ //#region src/tree/traverseTreeValues.ts
1813
+ /**
1814
+ * 遍历树形结构,并返回所有节点中指定的值
1815
+ *
1816
+ * 使用深度优先遍历(DFS)遍历树形结构的所有节点,提取每个节点的指定值
1817
+ *
1818
+ * @typeParam T - 树节点类型
1819
+ * @typeParam V - 节点值的类型
1820
+ *
1821
+ * @param tree - 树形结构数组
1822
+ * @param getValue - 获取节点值的函数
1823
+ * @param options - 配置选项
1824
+ * @param options.childProps - 子节点属性名称,默认为 'children'
1825
+ *
1826
+ * @returns 所有节点中指定的值的数组
1827
+ *
1828
+ * @example 提取所有节点的 id
1829
+ * ```typescript
1830
+ * const tree = [
1831
+ * { id: 1, name: 'A', children: [{ id: 2, name: 'A-1' }] },
1832
+ * { id: 3, name: 'B' }
1833
+ * ];
1834
+ * const ids = traverseTreeValues(tree, node => node.id);
1835
+ * console.log(ids); // [1, 2, 3]
1836
+ * ```
1837
+ *
1838
+ * @example 使用自定义子节点属性
1839
+ * ```typescript
1840
+ * const tree = [
1841
+ * { id: 1, items: [{ id: 2 }] }
1842
+ * ];
1843
+ * const ids = traverseTreeValues(
1844
+ * tree,
1845
+ * node => node.id,
1846
+ * { childProps: 'items' }
1847
+ * );
1848
+ * ```
1849
+ *
1850
+ * @example 提取所有节点的名称
1851
+ * ```typescript
1852
+ * const names = traverseTreeValues(tree, node => node.name);
1853
+ * console.log(names); // ['A', 'A-1', 'B']
1854
+ * ```
1855
+ */
1856
+ function traverseTreeValues(tree, getValue, options) {
1857
+ const result = [];
1858
+ const childProps = options?.childProps ?? "children";
1859
+ const dfs = (treeNode) => {
1860
+ const value = getValue(treeNode);
1861
+ result.push(value);
1862
+ const children = treeNode?.[childProps];
1863
+ if (!children) return;
1864
+ if (children.length > 0) for (const child of children) dfs(child);
1865
+ };
1866
+ for (const treeNode of tree) dfs(treeNode);
1867
+ return result.filter(Boolean);
1868
+ }
1869
+
1870
+ //#endregion
1871
+ //#region src/tree/filterTree.ts
1872
+ /**
1873
+ * 根据条件过滤给定树结构的节点
1874
+ *
1875
+ * 保留满足条件的节点及其祖先节点,保持原有的层级结构
1876
+ *
1877
+ * @typeParam T - 树节点类型,必须是对象类型
1878
+ *
1879
+ * @param tree - 要过滤的树结构的根节点数组
1880
+ * @param filter - 用于匹配每个节点的条件函数
1881
+ * @param options - 配置选项
1882
+ * @param options.childProps - 子节点属性名称,默认为 'children'
1883
+ *
1884
+ * @returns 包含所有匹配节点的数组
1885
+ *
1886
+ * @example 过滤包含特定属性的节点
1887
+ * ```typescript
1888
+ * const tree = [
1889
+ * { id: 1, active: true, children: [
1890
+ * { id: 2, active: false },
1891
+ * { id: 3, active: true }
1892
+ * ]},
1893
+ * { id: 4, active: false }
1894
+ * ];
1895
+ *
1896
+ * const result = filterTree(tree, node => node.active);
1897
+ * console.log(result);
1898
+ * // [
1899
+ * // { id: 1, active: true, children: [{ id: 3, active: true }] }
1900
+ * // ]
1901
+ * ```
1902
+ *
1903
+ * @example 过滤包含特定文本的节点
1904
+ * ```typescript
1905
+ * const tree = [
1906
+ * { name: 'A', children: [{ name: 'Apple' }] },
1907
+ * { name: 'B', children: [{ name: 'Banana' }] }
1908
+ * ];
1909
+ *
1910
+ * const result = filterTree(tree, node => node.name.includes('A'));
1911
+ * console.log(result);
1912
+ * // [
1913
+ * // { name: 'A', children: [{ name: 'Apple' }] }
1914
+ * // ]
1915
+ * ```
1916
+ *
1917
+ * @example 使用自定义子节点属性
1918
+ * ```typescript
1919
+ * const tree = [
1920
+ * { id: 1, items: [{ id: 2, visible: true }] }
1921
+ * ];
1922
+ *
1923
+ * const result = filterTree(
1924
+ * tree,
1925
+ * node => node.visible,
1926
+ * { childProps: 'items' }
1927
+ * );
1928
+ * ```
1929
+ */
1930
+ function filterTree(tree, filter, options) {
1931
+ const childProps = options?.childProps ?? "children";
1932
+ const _filterTree = (nodes) => {
1933
+ return nodes.filter((node) => {
1934
+ if (filter(node)) {
1935
+ if (node[childProps]) node[childProps] = _filterTree(node[childProps]);
1936
+ return true;
1937
+ }
1938
+ return false;
1939
+ });
1940
+ };
1941
+ return _filterTree(tree);
1942
+ }
1943
+
1944
+ //#endregion
1945
+ //#region src/tree/mapTree.ts
1946
+ /**
1947
+ * 根据条件重新映射给定树结构的节点
1948
+ *
1949
+ * 对树形结构的每个节点应用映射函数,生成新的树形结构
1950
+ *
1951
+ * @typeParam T - 源树节点类型
1952
+ * @typeParam V - 目标树节点类型,必须是对象类型
1953
+ *
1954
+ * @param tree - 要映射的树结构的根节点数组
1955
+ * @param mapper - 用于映射每个节点的函数
1956
+ * @param options - 配置选项
1957
+ * @param options.childProps - 子节点属性名称,默认为 'children'
1958
+ *
1959
+ * @returns 映射后的树形结构数组
1960
+ *
1961
+ * @example 添加新属性到节点
1962
+ * ```typescript
1963
+ * const tree = [
1964
+ * { id: 1, name: 'A', children: [{ id: 2, name: 'A-1' }] }
1965
+ * ];
1966
+ *
1967
+ * const result = mapTree(tree, node => ({
1968
+ * ...node,
1969
+ * label: `${node.name} (${node.id})`
1970
+ * }));
1971
+ * console.log(result);
1972
+ * // [
1973
+ * // { id: 1, name: 'A', label: 'A (1)', children: [
1974
+ * // { id: 2, name: 'A-1', label: 'A-1 (2)' }
1975
+ * // ]}
1976
+ * // ]
1977
+ * ```
1978
+ *
1979
+ * @example 转换节点类型
1980
+ * ```typescript
1981
+ * const tree = [
1982
+ * { value: 'a', children: [{ value: 'a-1' }] }
1983
+ * ];
1984
+ *
1985
+ * const result = mapTree(tree, node => ({
1986
+ * text: node.value,
1987
+ * checked: false
1988
+ * }));
1989
+ * console.log(result);
1990
+ * // [
1991
+ * // { text: 'a', checked: false, children: [{ text: 'a-1', checked: false }] }
1992
+ * // ]
1993
+ * ```
1994
+ *
1995
+ * @example 使用自定义子节点属性
1996
+ * ```typescript
1997
+ * const tree = [
1998
+ * { id: 1, items: [{ id: 2 }] }
1999
+ * ];
2000
+ *
2001
+ * const result = mapTree(
2002
+ * tree,
2003
+ * node => ({ ...node, mapped: true }),
2004
+ * { childProps: 'items' }
2005
+ * );
2006
+ * ```
2007
+ */
2008
+ function mapTree(tree, mapper, options) {
2009
+ const childProps = options?.childProps ?? "children";
2010
+ return tree.map((node) => {
2011
+ const mapperNode = mapper(node);
2012
+ if (mapperNode[childProps]) mapperNode[childProps] = mapTree(mapperNode[childProps], mapper, options);
2013
+ return mapperNode;
2014
+ });
2015
+ }
2016
+
2017
+ //#endregion
2018
+ //#region src/tree/sortTree.ts
2019
+ /**
2020
+ * 对树形结构数据进行递归排序
2021
+ *
2022
+ * 对树形结构的每一层节点应用排序函数,保持层级结构
2023
+ *
2024
+ * @typeParam T - 树节点类型,必须是对象类型
2025
+ *
2026
+ * @param treeData - 树形数据数组
2027
+ * @param sortFunction - 排序函数,用于定义排序规则
2028
+ * @param options - 配置选项
2029
+ * @param options.childProps - 子节点属性名称,默认为 'children'
2030
+ *
2031
+ * @returns 排序后的树形数据
2032
+ *
2033
+ * @example 按 id 升序排序
2034
+ * ```typescript
2035
+ * const tree = [
2036
+ * { id: 3, name: 'C', children: [{ id: 5, name: 'E' }] },
2037
+ * { id: 1, name: 'A', children: [{ id: 4, name: 'D' }, { id: 2, name: 'B' }] }
2038
+ * ];
2039
+ *
2040
+ * const result = sortTree(tree, (a, b) => a.id - b.id);
2041
+ * console.log(result);
2042
+ * // [
2043
+ * // { id: 1, name: 'A', children: [
2044
+ * // { id: 2, name: 'B' },
2045
+ * // { id: 4, name: 'D' }
2046
+ * // ]},
2047
+ * // { id: 3, name: 'C', children: [{ id: 5, name: 'E' }] }
2048
+ * // ]
2049
+ * ```
2050
+ *
2051
+ * @example 按名称字母顺序排序
2052
+ * ```typescript
2053
+ * const tree = [
2054
+ * { name: 'Zebra', children: [{ name: 'Y' }] },
2055
+ * { name: 'Apple', children: [{ name: 'Beta' }] }
2056
+ * ];
2057
+ *
2058
+ * const result = sortTree(tree, (a, b) => a.name.localeCompare(b.name));
2059
+ * console.log(result);
2060
+ * // [
2061
+ * // { name: 'Apple', children: [{ name: 'Beta' }] },
2062
+ * // { name: 'Zebra', children: [{ name: 'Y' }] }
2063
+ * // ]
2064
+ * ```
2065
+ *
2066
+ * @example 按自定义属性排序
2067
+ * ```typescript
2068
+ * const tree = [
2069
+ * { priority: 2, tasks: [{ priority: 3 }] },
2070
+ * { priority: 1, tasks: [{ priority: 2 }] }
2071
+ * ];
2072
+ *
2073
+ * const result = sortTree(
2074
+ * tree,
2075
+ * (a, b) => a.priority - b.priority,
2076
+ * { childProps: 'tasks' }
2077
+ * );
2078
+ * ```
2079
+ */
2080
+ function sortTree(treeData, sortFunction, options) {
2081
+ const childProps = options?.childProps ?? "children";
2082
+ return [...treeData].sort(sortFunction).map((item) => {
2083
+ const children = item[childProps];
2084
+ if (children && Array.isArray(children) && children.length > 0) {
2085
+ const result = { ...item };
2086
+ result[childProps] = sortTree(children, sortFunction, options);
2087
+ return result;
2088
+ }
2089
+ return item;
2090
+ });
2091
+ }
2092
+
2093
+ //#endregion
2094
+ //#region src/_internal/_dayjs.ts
2095
+ let _dayjs = null;
2096
+ /**
2097
+ * 异步获取 Dayjs 实例(已配置中文本地化和季度插件)
2098
+ * 这是一个懒加载导入,避免将 dayjs 作为直接依赖
2099
+ *
2100
+ * @example
2101
+ * ```ts
2102
+ * const dayjs = await getDayjs()
2103
+ * dayjs().format('YYYY-MM-DD') // '2024-01-25'
2104
+ * ```
2105
+ */
2106
+ async function getDayjs() {
2107
+ if (_dayjs) return _dayjs;
2108
+ const dayjs = await import("dayjs");
2109
+ const quarterOfYear = await import("dayjs/plugin/quarterOfYear");
2110
+ await import("dayjs/locale/zh-cn");
2111
+ dayjs.default.extend(quarterOfYear.default);
2112
+ dayjs.default.locale("zh-cn");
2113
+ _dayjs = dayjs.default;
2114
+ return _dayjs;
2115
+ }
2116
+ /**
2117
+ * 同步获取 Dayjs 实例(仅用于测试环境)
2118
+ * 使用 ESM 动态导入,配合 vitest setupFiles 预加载使用
2119
+ *
2120
+ * 注意:此函数本质是异步的,但为了保持 API 一致性命名为 sync
2121
+ * 请确保在测试运行前通过 setupFiles 预加载
2122
+ *
2123
+ * @throws {Error} 如果 dayjs 未安装
2124
+ *
2125
+ * @example
2126
+ * ```ts
2127
+ * // 在测试 setup 文件中预加载
2128
+ * import { getDayjsSync } from './_dayjs'
2129
+ * await getDayjsSync()
2130
+ *
2131
+ * // 然后在代码中可以直接使用
2132
+ * const dayjs = getDayjsSync() // 返回缓存实例
2133
+ * ```
2134
+ */
2135
+ function getDayjsSync() {
2136
+ if (_dayjs) return _dayjs;
2137
+ throw new Error("dayjs 未加载。请确保在测试 setup 文件中调用 await getDayjsSync() 预加载");
2138
+ }
2139
+
2140
+ //#endregion
2141
+ export { TinyColor, _decode, _encode, _version, camelCase, capitalize, chunk, convertChineseNumber, convertCurrency, convertToHsl, convertToHslCssVar, convertToRgb, debounce, deepClone, deepEqual, deepMerge, filterTree, format, formatDate, formatDateTime, formatMoney, formatNumber, formatPercentage, generatorColorVariables, get, getCurrentTimezone, getDayjs, getDayjsSync, getFormatStr, getNumeral, getNumeralSync, getSystemTimezone, groupBy, isArray, isBoolean, isDarkColor, isDate, isDateInstance, isDayjsObject, isEmpty, isEmptyObject, isError, isFunction, isLightColor, isMap, isNil, isNull, isNumber, isObject, isPromise, isRegExp, isSet, isString, isSymbol, isTypeOf, isUndefined, isValidColor, kebabCase, mapTree, memoize, parseQueryString, partial, querystringify, random, safeJsonStringify, safeParseJson, set, setCurrentTimezone, snakeCase, sortTree, throttle, toFixed, traverseTreeValues, truncate, unformat, unique, uniqueByField, urlParse };
2142
+ //# sourceMappingURL=index.mjs.map