@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 +2249 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +249 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +249 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2142 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
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
|