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