@movk/core 1.0.2 → 1.1.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.d.mts CHANGED
@@ -1,1790 +1,2339 @@
1
- import { MaybeRefOrGetter, VNode, Ref, Component, DefineComponent } from 'vue';
2
- import { z } from 'zod/v4';
1
+ import { Ref, VNode, MaybeRefOrGetter, Component, DefineComponent } from 'vue';
2
+
3
+ type StorageType = 'localStorage' | 'sessionStorage';
4
+ interface StorageConfig<T = unknown> {
5
+ key: string;
6
+ defaultValue: T;
7
+ prefix: string;
8
+ storage: StorageType;
9
+ }
10
+ type StorageConfigInput<T = unknown> = Partial<Omit<StorageConfig<T>, 'key' | 'defaultValue'>> & {
11
+ key: string;
12
+ defaultValue: T;
13
+ };
14
+ interface AppStorageReturn<T> {
15
+ state: Ref<T>;
16
+ getItem: () => T;
17
+ setItem: (value: T) => void;
18
+ removeItem: () => void;
19
+ }
20
+
21
+ /**
22
+ * 应用存储管理的组合式函数,支持 localStorage 和 sessionStorage
23
+ *
24
+ * @category Composables
25
+ * @param config 存储配置对象
26
+ * @returns 存储管理对象,包含响应式状态和操作方法
27
+ * @example
28
+ * ```ts
29
+ * // 创建存储管理实例
30
+ * const { state, setItem, getItem, removeItem } = useAppStorage({
31
+ * key: 'user-preferences',
32
+ * defaultValue: {
33
+ * theme: 'light',
34
+ * language: 'zh-CN',
35
+ * fontSize: 16
36
+ * },
37
+ * storage: 'localStorage',
38
+ * prefix: 'app'
39
+ * })
40
+ *
41
+ * // 使用响应式状态
42
+ * console.log(state.value.theme) // 'light'
43
+ *
44
+ * // 更新设置
45
+ * setItem({
46
+ * theme: 'dark',
47
+ * language: 'en-US',
48
+ * fontSize: 18
49
+ * })
50
+ * ```
51
+ */
52
+ declare function useAppStorage<T = unknown>(config: StorageConfigInput<T>): AppStorageReturn<T>;
53
+
54
+ /**
55
+ * 复制文本到剪贴板的组合式函数
56
+ *
57
+ * @category Composables
58
+ * @param text 要复制的文本内容
59
+ * @returns 复制是否成功的Promise
60
+ * @example
61
+ * ```ts
62
+ * // 复制简单文本
63
+ * const copyText = async () => {
64
+ * const success = await useCopyCode('Hello, World!')
65
+ * if (success) {
66
+ * console.log('复制成功')
67
+ * } else {
68
+ * console.log('复制失败')
69
+ * }
70
+ * }
71
+ *
72
+ * // 复制代码块
73
+ * const copyCodeBlock = async () => {
74
+ * const code = `
75
+ * function hello() {
76
+ * console.log('Hello, World!')
77
+ * }
78
+ * `
79
+ * const success = await useCopyCode(code)
80
+ * if (success) {
81
+ * // 显示复制成功提示
82
+ * showNotification('代码已复制到剪贴板')
83
+ * }
84
+ * }
85
+ *
86
+ * // 在点击事件中使用
87
+ * const handleCopy = () => {
88
+ * useCopyCode(document.getElementById('code').textContent)
89
+ * }
90
+ * ```
91
+ */
92
+ declare function useCopyCode(text: string): Promise<boolean>;
93
+
94
+ /**
95
+ * 将SVG字符串转换为PNG格式的Blob对象
96
+ *
97
+ * @category File
98
+ * @param svg SVG字符串
99
+ * @returns PNG格式的Blob对象
100
+ * @throws 当SVG无效或转换失败时抛出错误
101
+ * @example
102
+ * ```ts
103
+ * const svgString = '<svg width="100" height="100"><circle cx="50" cy="50" r="40" fill="red"/></svg>'
104
+ *
105
+ * try {
106
+ * const pngBlob = await convertSvgToPng(svgString)
107
+ * const url = URL.createObjectURL(pngBlob)
108
+ *
109
+ * // 用于下载或显示
110
+ * const img = document.createElement('img')
111
+ * img.src = url
112
+ * document.body.appendChild(img)
113
+ * } catch (error) {
114
+ * console.error('SVG转换失败:', error)
115
+ * }
116
+ * ```
117
+ */
118
+ declare function convertSvgToPng(svg: string): Promise<Blob>;
119
+
120
+ /**
121
+ * 从响应头中提取文件名
122
+ *
123
+ * @category File
124
+ * @param headers 响应头对象
125
+ * @param fallbackName 默认文件名
126
+ * @returns 提取的文件名
127
+ * @example
128
+ * ```ts
129
+ * // 从响应头中提取文件名
130
+ * const headers = new Headers({
131
+ * 'content-disposition': 'attachment; filename="report.pdf"'
132
+ * })
133
+ * const filename = extractFilename(headers, 'download')
134
+ * console.log(filename) // 'report.pdf'
135
+ *
136
+ * // 处理编码的文件名
137
+ * const encodedHeaders = new Headers({
138
+ * 'content-disposition': 'attachment; filename*=UTF-8\'\'%E6%8A%A5%E5%91%8A.pdf'
139
+ * })
140
+ * const encodedFilename = extractFilename(encodedHeaders)
141
+ * console.log(encodedFilename) // '报告.pdf'
142
+ * ```
143
+ */
144
+ declare function extractFilename(headers?: Headers, fallbackName?: string): string;
145
+
146
+ /**
147
+ * 格式化文件大小,将字节数转换为可读的文件大小字符串
148
+ *
149
+ * @category File
150
+ * @param bytes 文件大小(字节)
151
+ * @returns 格式化后的文件大小字符串
152
+ * @example
153
+ * ```ts
154
+ * console.log(formatFileSize(1024)) // '1 KB'
155
+ * console.log(formatFileSize(1536)) // '1.5 KB'
156
+ * console.log(formatFileSize(1048576)) // '1 MB'
157
+ * console.log(formatFileSize(1073741824)) // '1 GB'
158
+ *
159
+ * // 处理边界情况
160
+ * console.log(formatFileSize(0)) // '0 Bytes'
161
+ * console.log(formatFileSize(-100)) // '0 Bytes'
162
+ * ```
163
+ */
164
+ declare function formatFileSize(bytes: number): string;
165
+
166
+ /**
167
+ * 替换SVG文件中的currentColor为指定颜色
168
+ *
169
+ * @category File
170
+ * @param path SVG文件路径
171
+ * @param color 替换的颜色值,不提供则返回原始SVG
172
+ * @returns 处理后的SVG字符串
173
+ * @throws 当文件获取失败或SVG无效时抛出错误
174
+ * @example
175
+ * ```ts
176
+ * // 获取并替换SVG中的currentColor
177
+ * try {
178
+ * const svgContent = await replaceCurrentColor('/icons/star.svg', '#ff0000')
179
+ * const container = document.createElement('div')
180
+ * container.innerHTML = svgContent
181
+ * document.body.appendChild(container)
182
+ * } catch (error) {
183
+ * console.error('SVG处理失败:', error)
184
+ * }
185
+ *
186
+ * // 只获取SVG内容,不替换颜色
187
+ * const originalSvg = await replaceCurrentColor('/icons/star.svg')
188
+ * ```
189
+ */
190
+ declare function replaceCurrentColor(path: string, color?: string): Promise<string>;
191
+
192
+ /**
193
+ * 触发浏览器下载文件
194
+ *
195
+ * @category File
196
+ * @param blob 文件数据
197
+ * @param filename 文件名
198
+ * @example
199
+ * ```ts
200
+ * // 下载文本文件
201
+ * const textBlob = new Blob(['Hello, World!'], { type: 'text/plain' })
202
+ * triggerDownload(textBlob, 'hello.txt')
203
+ *
204
+ * // 下载JSON数据
205
+ * const data = { name: 'John', age: 30 }
206
+ * const jsonBlob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
207
+ * triggerDownload(jsonBlob, 'data.json')
208
+ *
209
+ * // 下载图片
210
+ * const canvas = document.createElement('canvas')
211
+ * canvas.toBlob((blob) => {
212
+ * if (blob) {
213
+ * triggerDownload(blob, 'image.png')
214
+ * }
215
+ * })
216
+ * ```
217
+ */
218
+ declare function triggerDownload(blob: Blob, filename: string): void;
219
+
220
+ /**
221
+ * 深拷贝任意 JavaScript 值。
222
+ *
223
+ * - 优先使用原生 `structuredClone`(若可用),覆盖 `Map`/`Set`/`TypedArray`/`ArrayBuffer` 等内建类型。
224
+ * - 对不支持 `structuredClone` 的环境,使用回退实现:
225
+ * - 支持循环引用(`WeakMap` 记忆化)。
226
+ * - 保留原型与属性描述符(含 getter/setter),复制 symbol 键。
227
+ * - 内建类型专项处理:`Date`/`RegExp`/`Map`/`Set`/`ArrayBuffer`/`TypedArray`/`URL`/`Error`。
228
+ *
229
+ * @category Object
230
+ * @typeParam T 拷贝值的类型
231
+ * @param obj 要被深拷贝的值
232
+ * @param cache 内部使用的 `WeakMap`(循环引用记忆化),一般不需要传入
233
+ * @returns 新的深拷贝值,与输入值结构等价、引用独立
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * const source = { a: 1, d: new Date(), m: new Map([[1, { x: 2 }]]) }
238
+ * const cloned = deepClone(source)
239
+ * cloned !== source // true
240
+ * cloned.d !== source.d // true
241
+ * cloned.m !== source.m // true
242
+ * cloned.m.get(1) !== source.m.get(1) // true
243
+ * ```
244
+ *
245
+ * @remarks
246
+ * 若对象包含不可克隆资源(如带有原生句柄的自定义对象),请在外层进行自定义序列化逻辑或为该类型添加专用分支。
247
+ */
248
+ declare function deepClone<T>(obj: T, cache?: WeakMap<object, any>): T;
249
+
250
+ type UnknownObject = Record<string, unknown>;
251
+ type AnyObject = Record<string, any>;
252
+ /**
253
+ * Vue 渲染相关文本/节点类型: 可为字符串、`VNode` 或返回 `VNode` 的函数。
254
+ * @example
255
+ * // 在渲染 API 中允许三种形态:
256
+ * // - '标题'
257
+ * // - h('div', '标题') 产生的 VNode
258
+ * // - () => h('div', '标题') 的惰性渲染函数
259
+ */
260
+ type StringOrVNode = string | VNode | (() => VNode);
261
+ /**
262
+ * 合并两个对象类型,U 中的属性会覆盖 T 中的属性
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * type T = { a: number, c: string }
267
+ * type U = { a: string, b: boolean }
268
+ * type M = Merge<T, U> // { a: string, b: boolean, c: string }
269
+ * ```
270
+ */
271
+ type Merge<T, U> = Omit<T, keyof U> & U;
272
+ /**
273
+ * 判断类型 T 是否为纯对象类型
274
+ * 纯对象是指普通的对象字面量,排除数组、函数、Date 等特殊对象类型
275
+ * @example
276
+ * ```ts
277
+ * type Test1 = IsPlainObject<{ a: number }> // true
278
+ * type Test2 = IsPlainObject<string[]> // false
279
+ * type Test3 = IsPlainObject<() => void> // false
280
+ * type Test4 = IsPlainObject<Date> // false
281
+ * type Test5 = IsPlainObject<string> // false
282
+ * type Test6 = IsPlainObject<null> // false
283
+ * ```
284
+ */
285
+ type IsPlainObject<T> = NonNullable<T> extends Record<string, any> ? NonNullable<T> extends any[] ? false : NonNullable<T> extends (...args: any[]) => any ? false : NonNullable<T> extends Date ? false : true : false;
286
+
287
+ /**
288
+ * 递归将对象类型 `T` 的所有属性变为可选(深可选)。
289
+ * @typeParam T - 源对象类型
290
+ * @example
291
+ * // type Src = { a: { b: number } }
292
+ * // type R = DeepPartial<Src>
293
+ * // 结果: R 为 { a?: { b?: number | undefined } | undefined }
294
+ */
295
+ type DeepPartial<T> = {
296
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] | undefined;
297
+ };
298
+
299
+ /**
300
+ * 深度控制类型,用于限制类型递归的深度
301
+ * 防止类型计算超出 TypeScript 的递归限制
302
+ */
303
+ type Depth = [never, 0, 1, 2, 3, 4];
304
+ /**
305
+ * 当 `MaybeObject` 为对象时,返回键 `Key` 对应的属性类型; 否则为 `never`。
306
+ * @typeParam MaybeObject - 可能为对象的类型
307
+ * @typeParam Key - 目标键名(string)
308
+ * @example
309
+ * // type Obj = { id: number }
310
+ * // type R1 = GetObjectField<Obj, 'id'> // 结果: number
311
+ * // type R2 = GetObjectField<string, 'id'> // 结果: never
312
+ */
313
+ type GetObjectField<MaybeObject, Key extends string> = MaybeObject extends Record<string, any> ? MaybeObject[Key] : never;
314
+ /**
315
+ * 提取对象的嵌套键,支持点语法路径
316
+ *
317
+ * @template T 源对象类型
318
+ * @template D 递归深度,默认为2
319
+ * @example
320
+ * ```ts
321
+ * type User = {
322
+ * name: string
323
+ * address: {
324
+ * city: string
325
+ * country: string
326
+ * }
327
+ * }
328
+ * type Keys = NestedKeys<User> // 'name' | 'address' | 'address.city' | 'address.country'
329
+ * ```
330
+ */
331
+ type NestedKeys<T, D extends number = 2> = [D] extends [never] ? never : {
332
+ [K in keyof T & string]: IsPlainObject<T[K]> extends true ? K | `${K}.${NestedKeys<NonNullable<T[K]>, Depth[D]>}` : K;
333
+ }[keyof T & string];
334
+ /**
335
+ * 提取对象中所有纯对象字段的键(包括嵌套的),支持点语法路径
336
+ *
337
+ * @template T 源对象类型
338
+ * @template D 递归深度,默认为2
339
+ * @example
340
+ * ```ts
341
+ * type User = {
342
+ * name: string
343
+ * age: number
344
+ * address: {
345
+ * city: string
346
+ * location: {
347
+ * lat: number
348
+ * lng: number
349
+ * }
350
+ * }
351
+ * }
352
+ * type ObjectKeys = ObjectFieldKeys<User> // 'address' | 'address.location'
353
+ * ```
354
+ */
355
+ type ObjectFieldKeys<T, D extends number = 2> = [D] extends [never] ? never : {
356
+ [K in keyof T & string]: IsPlainObject<T[K]> extends true ? K | `${K}.${ObjectFieldKeys<NonNullable<T[K]>, Depth[D]>}` : never;
357
+ }[keyof T & string];
358
+ /**
359
+ * 提取对象中所有非对象字段的键
360
+ * 排除纯对象字段,只保留原始类型字段的键
361
+ *
362
+ * @template T 源对象类型
363
+ * @example
364
+ * ```ts
365
+ * type User = {
366
+ * name: string
367
+ * age: number
368
+ * address: {
369
+ * city: string
370
+ * }
371
+ * }
372
+ * type NonObjectKeys = NonObjectFieldKeys<User> // 'name' | 'age' | 'address.city'
373
+ * ```
374
+ */
375
+ type NonObjectFieldKeys<T> = Exclude<NestedKeys<T>, ObjectFieldKeys<T>>;
376
+ /**
377
+ * 提取对象中所有数组字段的键(包括嵌套的),支持点语法路径
378
+ *
379
+ * @template T 源对象类型
380
+ * @template D 递归深度,默认为2
381
+ * @example
382
+ * ```ts
383
+ * type User = {
384
+ * name: string
385
+ * tags: string[]
386
+ * posts: Array<{ title: string }>
387
+ * profile: {
388
+ * hobbies: string[]
389
+ * }
390
+ * }
391
+ * type ArrayKeys = ArrayFieldKeys<User> // 'tags' | 'posts' | 'profile.hobbies'
392
+ * ```
393
+ */
394
+ type ArrayFieldKeys<T, D extends number = 2> = [D] extends [never] ? never : {
395
+ [K in keyof T & string]: NonNullable<T[K]> extends any[] ? K : IsPlainObject<T[K]> extends true ? `${K}.${ArrayFieldKeys<NonNullable<T[K]>, Depth[D]>}` : never;
396
+ }[keyof T & string];
397
+ /**
398
+ * 根据路径字符串提取对象属性的类型,支持点语法和嵌套对象
399
+ * @example GetFieldValue<User, 'tags'> // string[]
400
+ * @example GetFieldValue<User, 'profile.bio'> // string
401
+ */
402
+ type GetFieldValue<T, P extends string> = P extends keyof T ? T[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? T[K] extends undefined ? undefined : GetFieldValue<NonNullable<T[K]>, Rest> : unknown : unknown;
403
+
404
+ /**
405
+ * 依据键名从对象类型 `T` 中剔除键 `K`。
406
+ * @typeParam T - 源对象类型
407
+ * @typeParam K - 要剔除的键(必须来自 `keyof T`)
408
+ * @example
409
+ * // type User = { id: string; name: string; age: number }
410
+ * // type R = OmitByKey<User, 'age'>
411
+ * // 结果: R 为 { id: string; name: string }
412
+ */
413
+ type OmitByKey<T, K extends keyof T> = {
414
+ [P in keyof T as P extends K ? never : P]: T[P];
415
+ };
416
+ /**
417
+ * 依据键名从对象类型 `T` 中挑选键 `K`。
418
+ * @typeParam T - 源对象类型
419
+ * @typeParam K - 要保留的键(必须来自 `keyof T`)
420
+ * @example
421
+ * // type User = { id: string; name: string; age: number }
422
+ * // type R = PickByKey<User, 'id' | 'name'>
423
+ * // 结果: R 为 { id: string; name: string }
424
+ */
425
+ type PickByKey<T, K extends keyof T> = {
426
+ [P in keyof T as P extends K ? P : never]: T[P];
427
+ };
428
+ /**
429
+ * 基于映射表 `Mapping` 对对象类型 `T` 的键进行重命名。
430
+ * 未在映射表中的键保持原名; 映射值为 `PropertyKey`(string/number/symbol)。
431
+ * @typeParam T - 源对象类型
432
+ * @typeParam Mapping - 旧键到新键名的映射
433
+ * @example
434
+ * // type Src = { a: number; b: string }
435
+ * // type R = RenameKeys<Src, { a: 'id' }>
436
+ * // 结果: R 为 { id: number; b: string }
437
+ */
438
+ type RenameKeys<T, Mapping extends {
439
+ [K in keyof T]?: PropertyKey;
440
+ }> = {
441
+ [K in keyof T as K extends keyof Mapping ? Exclude<Mapping[K], undefined> : K]: T[K];
442
+ };
443
+ /**
444
+ * 将对象类型 `T` 中的键 `K` 标记为必填(移除可选修饰)。
445
+ * @typeParam T - 源对象类型
446
+ * @typeParam K - 设为必填的键
447
+ * @example
448
+ * // type User = { id: string; name?: string }
449
+ * // type R = RequiredByKeys<User, 'name'>
450
+ * // 结果: R['name'] 为必填的 string
451
+ */
452
+ type RequiredByKeys<T, K extends keyof T> = T & {
453
+ [P in K]-?: T[P];
454
+ };
455
+ /**
456
+ * 将对象类型 `T` 中的键 `K` 标记为可选。
457
+ * @typeParam T - 源对象类型
458
+ * @typeParam K - 设为可选的键
459
+ * @example
460
+ * // type User = { id: string; name: string }
461
+ * // type R = PartialByKeys<User, 'name'>
462
+ * // 结果: R['name'] 为可选(可能为 undefined)
463
+ */
464
+ type PartialByKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
465
+ /**
466
+ * 将对象类型 `T` 中的键 `K` 标记为只读(浅只读)。
467
+ * @typeParam T - 源对象类型
468
+ * @typeParam K - 设为只读的键
469
+ * @example
470
+ * // type User = { id: string; name: string }
471
+ * // type R = ReadonlyByKeys<User, 'id'>
472
+ * // 结果: R['id'] 不可被重新赋值
473
+ */
474
+ type ReadonlyByKeys<T, K extends keyof T> = T & {
475
+ readonly [P in K]: T[P];
476
+ };
477
+ /**
478
+ * 取消对象类型 `T` 中键 `K` 的只读限制,使其可写(浅层)。
479
+ * @typeParam T - 源对象类型
480
+ * @typeParam K - 取消只读的键
481
+ * @example
482
+ * // type User = { readonly id: string; name: string }
483
+ * // type R = MutableByKeys<User, 'id'>
484
+ * // 结果: R['id'] 变为可写
485
+ */
486
+ type MutableByKeys<T, K extends keyof T> = {
487
+ -readonly [P in K]: T[P];
488
+ } & Omit<T, K>;
489
+
490
+ /**
491
+ * 将联合类型 `U` 转换为交叉类型,用于合并联合成员的属性。
492
+ * @typeParam U - 联合类型
493
+ * @example
494
+ * // type U = { a: number } | { b: string }
495
+ * // type R = UnionToIntersection<U>
496
+ * // 结果: R 为 { a: number } & { b: string }
497
+ */
498
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? {
499
+ [K in keyof I]: I[K];
500
+ } : never;
501
+ /**
502
+ * 若对象 `T` 在键 `K` 处的类型为元组,则提取其首个元素类型,否则为 `never`。
503
+ * @typeParam T - 具有元组属性的对象类型
504
+ * @typeParam K - 属性键
505
+ * @example
506
+ * // type Cfg = { params: [id: string, flag?: boolean] }
507
+ * // type R = FirstParam<Cfg, 'params'>
508
+ * // 结果: R 为 string
509
+ */
510
+ type FirstParam<T, K extends keyof T> = T[K] extends [infer P, ...any[]] ? P : never;
511
+ /**
512
+ * 从函数类型中提取首个参数类型; 若 `T` 非函数类型,则为 `undefined`。
513
+ * @typeParam T - 函数类型
514
+ * @example
515
+ * // type Fn = (x: number, y: string) => void
516
+ * // type R = FirstParameter<Fn>
517
+ * // 结果: R 为 number; 若 T 非函数,则为 undefined
518
+ */
519
+ type FirstParameter<T> = T extends (arg: infer P, ...args: any[]) => any ? P : undefined;
520
+
521
+ /**
522
+ * 从对象中排除指定的键,返回新对象
523
+ *
524
+ * @category Object
525
+ * @param obj 源对象
526
+ * @param keys 要排除的键数组
527
+ * @returns 排除指定键后的新对象
528
+ * @example
529
+ * ```ts
530
+ * const user = {
531
+ * id: 1,
532
+ * name: 'John',
533
+ * password: 'secret',
534
+ * email: 'john@example.com'
535
+ * }
536
+ *
537
+ * const publicUser = omit(user, ['password'])
538
+ * console.log(publicUser) // { id: 1, name: 'John', email: 'john@example.com' }
539
+ *
540
+ * const basicInfo = omit(user, ['password', 'email'])
541
+ * console.log(basicInfo) // { id: 1, name: 'John' }
542
+ * ```
543
+ */
544
+ declare function omit<T extends AnyObject, K extends keyof T>(obj: T, keys: K[]): OmitByKey<T, K>;
545
+
546
+ /**
547
+ * 从对象中排除值为undefined的键
548
+ *
549
+ * @category Object
550
+ * @param obj 源对象
551
+ * @returns 排除undefined值后的新对象
552
+ * @example
553
+ * ```ts
554
+ * const data = {
555
+ * name: 'John',
556
+ * age: undefined,
557
+ * city: 'New York',
558
+ * country: undefined
559
+ * }
560
+ *
561
+ * const cleaned = omitUndefined(data)
562
+ * console.log(cleaned) // { name: 'John', city: 'New York' }
563
+ *
564
+ * // 用于API请求前清理数据
565
+ * const requestData = omitUndefined({
566
+ * title: 'Post Title',
567
+ * content: 'Post content',
568
+ * tags: undefined,
569
+ * published: true
570
+ * })
571
+ * ```
572
+ */
573
+ declare function omitUndefined<T extends AnyObject>(obj: T): Partial<T>;
574
+
575
+ /**
576
+ * 从对象中选择指定的键,返回新对象
577
+ *
578
+ * @category Object
579
+ * @param obj 源对象
580
+ * @param keys 要选择的键数组
581
+ * @returns 只包含指定键的新对象
582
+ * @example
583
+ * ```ts
584
+ * const user = {
585
+ * id: 1,
586
+ * name: 'John',
587
+ * email: 'john@example.com',
588
+ * password: 'secret',
589
+ * createdAt: '2023-01-01',
590
+ * updatedAt: '2023-01-15'
591
+ * }
592
+ *
593
+ * const publicInfo = pick(user, ['id', 'name', 'email'])
594
+ * console.log(publicInfo) // { id: 1, name: 'John', email: 'john@example.com' }
595
+ *
596
+ * const basicInfo = pick(user, ['id', 'name'])
597
+ * console.log(basicInfo) // { id: 1, name: 'John' }
598
+ * ```
599
+ */
600
+ declare function pick<T extends AnyObject, K extends keyof T>(obj: T, keys: K[]): PickByKey<T, K>;
601
+
602
+ /**
603
+ * 将对象按指定键分离为两个对象
604
+ *
605
+ * @category Object
606
+ * @param obj 源对象
607
+ * @param keys 要分离的键数组
608
+ * @returns 包含picked和omitted两个对象的结果
609
+ * @example
610
+ * ```ts
611
+ * const user = {
612
+ * id: 1,
613
+ * name: 'John',
614
+ * email: 'john@example.com',
615
+ * password: 'secret',
616
+ * role: 'admin'
617
+ * }
618
+ *
619
+ * const { picked, omitted } = separate(user, ['id', 'name'])
620
+ * console.log(picked) // { id: 1, name: 'John' }
621
+ * console.log(omitted) // { email: 'john@example.com', password: 'secret', role: 'admin' }
622
+ *
623
+ * // 用于分离敏感信息
624
+ * const { picked: publicData, omitted: privateData } = separate(user, ['id', 'name', 'email'])
625
+ * ```
626
+ */
627
+ declare function separate<T extends AnyObject, K extends keyof T>(obj: T, keys: K[]): {
628
+ picked: PickByKey<T, K>;
629
+ omitted: OmitByKey<T, K>;
630
+ };
3
631
 
4
632
  /**
5
- * 统一同步/异步返回类型
6
- * @typeParam T - 类型
633
+ * 将对象按多分组键集合进行分离(浅层),返回各分组与 others
634
+ *
635
+ * - 键冲突策略:先到先得。若同一键出现在多个分组中,则归入第一个匹配到的分组
636
+ * - 仅处理对象自有的浅层键,不解析深层路径
637
+ * - 分组中包含不存在于对象的键将被忽略
638
+ *
639
+ * @category Object
640
+ * @param obj 源对象
641
+ * @param groups 分组映射,如 { a: ['x', 'y'], b: ['z'] }
642
+ * @returns 一个对象,包含每个分组的子对象以及 others(其余未被分组捕获的键)
7
643
  * @example
8
- * // type T = string | Promise<string>
9
- * // type R = ApiAwaitable<T>
10
- * // 结果:R string | Promise<string>
644
+ * ```ts
645
+ * const options = { id: 1, name: 'John', email: 'a@b.com', role: 'admin' }
646
+ * const { a, b, others } = separateMany(options, { a: ['id'], b: ['name'] as const })
647
+ * // a: { id: 1 }
648
+ * // b: { name: 'John' }
649
+ * // others: { email: 'a@b.com', role: 'admin' }
650
+ * ```
11
651
  */
12
- type ApiAwaitable<T> = T | Promise<T>;
652
+ declare function separateMany<T extends AnyObject, M extends Record<string, readonly (keyof T)[]>>(obj: T, groups: M): {
653
+ [P in keyof M]: PickByKey<T, M[P][number]>;
654
+ } & {
655
+ others: OmitByKey<T, M[keyof M][number]>;
656
+ };
657
+
658
+ type PathSegment = string | number;
659
+ type PathSegments = PathSegment[];
660
+ type PathInput = string | PathSegments;
13
661
  /**
14
- * 提取Promise类型
15
- * @typeParam T - 类型
662
+ * 将路径字符串解析为片段数组。
663
+ *
664
+ * - 支持点语法与方括号语法混用
665
+ * - 引号键支持单/双引号与反斜杠转义
666
+ * - 方括号内未引号的非负整数字面量解析为 number 段
667
+ * - 点语法中的纯数字段保持字符串(不转为索引)
668
+ *
669
+ * @category Path
670
+ * @param path 路径字符串或片段数组
671
+ * @returns 解析后的片段数组
16
672
  * @example
17
- * // type T = Promise<string>
18
- * // type R = ApiUnwrapPromise<T>
19
- * // 结果:R 为 string
673
+ * ```ts
674
+ * toPath('a.b[0].c') // ['a', 'b', 0, 'c']
675
+ * toPath("a['x.y']") // ['a', 'x.y']
676
+ * ```
20
677
  */
21
- type ApiUnwrapPromise<T> = T extends Promise<infer U> ? U : T;
678
+ declare function toPath(path: PathInput): PathSegments;
679
+
22
680
  /**
23
- * 提取函数返回类型
24
- * @typeParam TFn - 函数类型
681
+ * 读取对象指定路径的值。
682
+ *
683
+ * - 若取值结果为 undefined,则返回 defaultValue
684
+ * - 若取值结果为 null,则直接返回 null(不触发默认值)
685
+ * - 传入空路径时返回 object 本身
686
+ *
687
+ * @category Path
688
+ * @param object 源对象
689
+ * @param path 路径字符串或片段数组
690
+ * @param defaultValue 结果为 undefined 时返回的默认值
691
+ * @returns 读取到的值或默认值
25
692
  * @example
26
- * // type Fn = (x: number, y: string) => Promise<string>
27
- * // type R = ApiAwaitedReturn<Fn>
28
- * // 结果:R string
693
+ * ```ts
694
+ * const obj = { a: { b: { c: 1, d: undefined }, e: null }, arr: [{ x: 9 }] }
695
+ * getPath(obj, 'a.b.c') // 1
696
+ * getPath(obj, 'a.b.d', 42) // 42(d 为 undefined,使用默认值)
697
+ * getPath(obj, 'a.e', 100) // null(null 不触发默认值)
698
+ * getPath(obj, 'arr[0].x') // 9
699
+ * getPath(obj, '') // 返回 obj 本身
700
+ * ```
701
+ */
702
+ declare function getPath<T, D = undefined>(object: T, path: PathInput, defaultValue?: D): unknown | D;
703
+
704
+ /**
705
+ * 将片段数组序列化为路径字符串。
706
+ *
707
+ * 规则:
708
+ * - 合法标识符段使用点拼接(a.b.c)
709
+ * - 数字段转为索引([0])
710
+ * - 其它需要转义的键使用方括号引号(['x.y']),并转义 \\ 与 '\''
711
+ *
712
+ * @category Path
713
+ * @param segments 路径片段数组
714
+ * @returns 路径字符串
715
+ * @example
716
+ * ```ts
717
+ * const p = joinPath(['a', 'x.y', 0, 'space key'])
718
+ * // p: "a['x.y'][0]['space key']"
719
+ * // 与解析往返:toPath(p) => ['a', 'x.y', 0, 'space key']
720
+ * ```
721
+ */
722
+ declare function joinPath(segments: (string | number)[]): string;
723
+
724
+ /**
725
+ * 在对象指定路径写入值。缺失路径会被自动创建:
726
+ * - 下一段为 number(索引)时创建数组
727
+ * - 下一段为 string(属性)时创建对象
728
+ *
729
+ * 若中途遇到非容器类型(如字符串/数值/布尔),会被替换为正确的容器以继续写入。
730
+ *
731
+ * @category Path
732
+ * @param object 目标对象(原地修改并返回同一引用)
733
+ * @param path 路径字符串或片段数组
734
+ * @param value 要写入的值
735
+ * @returns 原对象(已修改)
736
+ * @example
737
+ * ```ts
738
+ * const obj: any = {}
739
+ * setPath(obj, 'a.b[0].c', 7)
740
+ * // obj => { a: { b: [{ c: 7 }] } }
741
+ *
742
+ * setPath(obj, 'a.b[2].d', 8)
743
+ * // 数组自动扩容到长度 3
744
+ * // obj.a.b[2] => { d: 8 }
745
+ *
746
+ * setPath(obj, 'a.0.b', 1) // 点语法数字键保持为字符串键
747
+ * // obj => { a: { 0: { b: 1 } } }
748
+ * setPath(obj, 'a[0].b', 2) // 索引用方括号
749
+ * // obj.a[0].b => 2
750
+ * ```
751
+ */
752
+ declare function setPath<T extends Record<string, any>>(object: T, path: PathInput, value: unknown): T;
753
+
754
+ /**
755
+ * 生成字符串的简单哈希值
756
+ *
757
+ * @category Helpers
758
+ * @param str 待哈希的字符串
759
+ * @returns 32位哈希值转换为36进制字符串
760
+ * @example
761
+ * ```ts
762
+ * const hash1 = simpleHash('hello world')
763
+ * console.log(hash1) // 'nf5xd4'
764
+ *
765
+ * const hash2 = simpleHash('hello world')
766
+ * console.log(hash1 === hash2) // true,相同字符串产生相同哈希
767
+ *
768
+ * const hash3 = simpleHash('hello world!')
769
+ * console.log(hash1 === hash3) // false,不同字符串产生不同哈希
770
+ * ```
771
+ */
772
+ declare function simpleHash(str: string): string;
773
+
774
+ /**
775
+ * 生成随机UUID字符串
776
+ *
777
+ * @category Helpers
778
+ * @returns 符合UUID v4格式的随机字符串
779
+ * @example
780
+ * ```ts
781
+ * const id1 = getRandomUUID()
782
+ * console.log(id1) // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
783
+ *
784
+ * const id2 = getRandomUUID()
785
+ * console.log(id2) // 'f47ac10b-58cc-4372-a567-0e02b2c3d480'
786
+ *
787
+ * // 用于生成唯一标识符
788
+ * const componentId = `component-${getRandomUUID()}`
789
+ * ```
790
+ */
791
+ declare function getRandomUUID(): string;
792
+
793
+ /**
794
+ * 将对象的键名转换为kebab-case格式
795
+ *
796
+ * @category Object
797
+ * @param obj 待转换的对象
798
+ * @param deep 是否深度转换嵌套对象,默认为false
799
+ * @returns 转换后的对象
800
+ * @example
801
+ * ```ts
802
+ * const obj = {
803
+ * firstName: 'John',
804
+ * lastName: 'Doe',
805
+ * userInfo: {
806
+ * birthDate: '1990-01-01',
807
+ * phoneNumber: '123-456-7890'
808
+ * }
809
+ * }
810
+ *
811
+ * const converted = convertToKebabCase(obj)
812
+ * console.log(converted)
813
+ * // {
814
+ * // 'first-name': 'John',
815
+ * // 'last-name': 'Doe',
816
+ * // 'user-info': { birthDate: '1990-01-01', phoneNumber: '123-456-7890' }
817
+ * // }
818
+ *
819
+ * const deepConverted = convertToKebabCase(obj, true)
820
+ * console.log(deepConverted)
821
+ * // {
822
+ * // 'first-name': 'John',
823
+ * // 'last-name': 'Doe',
824
+ * // 'user-info': { 'birth-date': '1990-01-01', 'phone-number': '123-456-7890' }
825
+ * // }
826
+ * ```
827
+ */
828
+ declare function convertToKebabCase<T extends AnyObject>(obj: T, deep?: boolean): T;
829
+
830
+ /**
831
+ * 将字符串转换为驼峰命名格式(第一个单词小写,后续单词首字母大写)。
832
+ *
833
+ * @category String
834
+ * @param str 要转换的字符串
835
+ * @returns 驼峰命名格式的字符串
836
+ * @example
837
+ * ```ts
838
+ * camelCase('First Name') // 'firstName'
839
+ * camelCase('first_name') // 'firstName'
840
+ * camelCase('first-name') // 'firstName'
841
+ * camelCase('XMLHttpRequest') // 'xmlHttpRequest'
842
+ * ```
843
+ */
844
+ declare function camelCase(str: string): string;
845
+
846
+ /**
847
+ * 将字符串首字母大写,其余字母小写。
848
+ *
849
+ * @category String
850
+ * @param str 要转换的字符串
851
+ * @returns 首字母大写的字符串
852
+ * @example
853
+ * ```ts
854
+ * capitalize('hello') // 'Hello'
855
+ * capitalize('HELLO') // 'Hello'
856
+ * capitalize('hello world') // 'Hello world'
857
+ * ```
858
+ */
859
+ declare function capitalize(str: string): string;
860
+
861
+ /**
862
+ * 将字符串转换为短横线命名格式(kebab-case)。
863
+ *
864
+ * @category String
865
+ * @param str 要转换的字符串
866
+ * @returns 短横线命名格式的字符串
867
+ * @example
868
+ * ```ts
869
+ * kebabCase('firstName') // 'first-name'
870
+ * kebabCase('First Name') // 'first-name'
871
+ * kebabCase('first_name') // 'first-name'
872
+ * kebabCase('XMLHttpRequest') // 'xml-http-request'
873
+ * ```
874
+ */
875
+ declare function kebabCase(str: string): string;
876
+
877
+ /**
878
+ * 将字符串转换为小写格式,单词之间用空格分隔。
879
+ *
880
+ * @category String
881
+ * @param str 要转换的字符串
882
+ * @returns 小写格式的字符串
883
+ * @example
884
+ * ```ts
885
+ * lowerCase('firstName') // 'first name'
886
+ * lowerCase('First_Name') // 'first name'
887
+ * lowerCase('FIRST-NAME') // 'first name'
888
+ * lowerCase('XMLHttpRequest') // 'xml http request'
889
+ * ```
29
890
  */
30
- type ApiAwaitedReturn<TFn> = TFn extends (...args: any[]) => ApiAwaitable<infer R> ? R : never;
891
+ declare function lowerCase(str: string): string;
31
892
 
32
893
  /**
33
- * 提供字符串字面量提示的同时允许任意字符串
34
- * 在 IDE 中提供 T 类型的自动补全提示,但不限制只能使用这些值
894
+ * 将字符串首字母小写,其余字母保持原样。
35
895
  *
896
+ * @category String
897
+ * @param str 要转换的字符串
898
+ * @returns 首字母小写的字符串
36
899
  * @example
37
900
  * ```ts
38
- * type Color = Suggest<'red' | 'blue' | 'green'>
39
- *
40
- * // IDE 会提示 'red', 'blue', 'green',但也可以使用其他字符串
41
- * const color1: Color = 'red' // 有提示
42
- * const color2: Color = 'yellow' // 也可以,虽然没有提示
901
+ * lowerFirst('Hello') // 'hello'
902
+ * lowerFirst('HELLO') // 'hELLO'
903
+ * lowerFirst('Hello World') // 'hello World'
43
904
  * ```
44
905
  */
45
- type Suggest<T extends string> = T | (string & {});
906
+ declare function lowerFirst(str: string): string;
907
+
46
908
  /**
47
- * 响应式值类型 - 基于 Vue 的 `MaybeRefOrGetter` 扩展,额外支持上下文回调
48
- *
49
- * @template T - 值类型
50
- * @template CTX - 上下文类型(用于回调函数)
909
+ * 将字符串转换为帕斯卡命名格式(PascalCase,每个单词首字母大写)。
51
910
  *
911
+ * @category String
912
+ * @param str 要转换的字符串
913
+ * @returns 帕斯卡命名格式的字符串
52
914
  * @example
53
915
  * ```ts
54
- * const value: ReactiveValue<boolean> = ref(false)
55
- * const getter: ReactiveValue<string> = () => name.value
56
- * const computed: ReactiveValue<number> = computed(() => count.value)
57
- * const withContext: ReactiveValue<boolean, Context> = (ctx) => ctx.value > 0
916
+ * pascalCase('firstName') // 'FirstName'
917
+ * pascalCase('first_name') // 'FirstName'
918
+ * pascalCase('first-name') // 'FirstName'
919
+ * pascalCase('XMLHttpRequest') // 'XmlHttpRequest'
58
920
  * ```
59
921
  */
60
- type ReactiveValue<T, CTX = never> = [CTX] extends [never] ? MaybeRefOrGetter<T> : MaybeRefOrGetter<T> | ((ctx: CTX) => T);
61
- type StripNullable<T> = T extends null | undefined ? never : T;
922
+ declare function pascalCase(str: string): string;
62
923
 
63
- type UnknownObject = Record<string, unknown>;
64
- type AnyObject = Record<string, any>;
65
924
  /**
66
- * 依据键名从对象类型 `T` 中剔除键 `K`。
67
- * @typeParam T - 源对象类型
68
- * @typeParam K - 要剔除的键(必须来自 `keyof T`)
925
+ * 将字符串转换为下划线命名格式(snake_case)。
926
+ *
927
+ * @category String
928
+ * @param str 要转换的字符串
929
+ * @returns 下划线命名格式的字符串
69
930
  * @example
70
- * // type User = { id: string; name: string; age: number }
71
- * // type R = OmitByKey<User, 'age'>
72
- * // 结果:R { id: string; name: string }
931
+ * ```ts
932
+ * snakeCase('firstName') // 'first_name'
933
+ * snakeCase('First Name') // 'first_name'
934
+ * snakeCase('first-name') // 'first_name'
935
+ * snakeCase('XMLHttpRequest') // 'xml_http_request'
936
+ * ```
73
937
  */
74
- type OmitByKey<T, K extends keyof T> = {
75
- [P in keyof T as P extends K ? never : P]: T[P];
76
- };
938
+ declare function snakeCase(str: string): string;
939
+
77
940
  /**
78
- * 依据键名从对象类型 `T` 中挑选键 `K`。
79
- * @typeParam T - 源对象类型
80
- * @typeParam K - 要保留的键(必须来自 `keyof T`)
941
+ * 将字符串转换为Start Case格式(每个单词首字母大写,用空格分隔)。
942
+ *
943
+ * @category String
944
+ * @param str 要转换的字符串
945
+ * @returns Start Case格式的字符串
81
946
  * @example
82
- * // type User = { id: string; name: string; age: number }
83
- * // type R = PickByKey<User, 'id' | 'name'>
84
- * // 结果:R 为 { id: string; name: string }
947
+ * ```ts
948
+ * startCase('firstName') // 'First Name'
949
+ * startCase('first_name') // 'First Name'
950
+ * startCase('first-name') // 'First Name'
951
+ * startCase('XMLHttpRequest') // 'XML Http Request'
952
+ * ```
85
953
  */
86
- type PickByKey<T, K extends keyof T> = {
87
- [P in keyof T as P extends K ? P : never]: T[P];
88
- };
954
+ declare function startCase(str: string): string;
955
+
89
956
  /**
90
- * 基于映射表 `Mapping` 对对象类型 `T` 的键进行重命名。
91
- * 未在映射表中的键保持原名;映射值为 `PropertyKey`(string/number/symbol)。
92
- * @typeParam T - 源对象类型
93
- * @typeParam Mapping - 旧键到新键名的映射
957
+ * 将字符串转换为大写格式,单词之间用空格分隔。
958
+ *
959
+ * @category String
960
+ * @param str 要转换的字符串
961
+ * @returns 大写格式的字符串
94
962
  * @example
95
- * // type Src = { a: number; b: string }
96
- * // type R = RenameKeys<Src, { a: 'id' }>
97
- * // 结果:R 为 { id: number; b: string }
963
+ * ```ts
964
+ * upperCase('firstName') // 'FIRST NAME'
965
+ * upperCase('first_name') // 'FIRST NAME'
966
+ * upperCase('first-name') // 'FIRST NAME'
967
+ * upperCase('XMLHttpRequest') // 'XML HTTP REQUEST'
968
+ * ```
98
969
  */
99
- type RenameKeys<T, Mapping extends {
100
- [K in keyof T]?: PropertyKey;
101
- }> = {
102
- [K in keyof T as K extends keyof Mapping ? Exclude<Mapping[K], undefined> : K]: T[K];
103
- };
970
+ declare function upperCase(str: string): string;
971
+
104
972
  /**
105
- * 将对象类型 `T` 中的键 `K` 标记为必填(移除可选修饰)。
106
- * @typeParam T - 源对象类型
107
- * @typeParam K - 设为必填的键
973
+ * 将字符串首字母大写,其余字母保持原样。
974
+ *
975
+ * @category String
976
+ * @param str 要转换的字符串
977
+ * @returns 首字母大写的字符串
108
978
  * @example
109
- * // type User = { id: string; name?: string }
110
- * // type R = RequiredByKeys<User, 'name'>
111
- * // 结果:R['name'] 为必填的 string
979
+ * ```ts
980
+ * upperFirst('hello') // 'Hello'
981
+ * upperFirst('hELLO') // 'HELLO'
982
+ * upperFirst('hello world') // 'Hello world'
983
+ * ```
112
984
  */
113
- type RequiredByKeys<T, K extends keyof T> = T & {
114
- [P in K]-?: T[P];
115
- };
985
+ declare function upperFirst(str: string): string;
986
+
116
987
  /**
117
- * 将对象类型 `T` 中的键 `K` 标记为可选。
118
- * @typeParam T - 源对象类型
119
- * @typeParam K - 设为可选的键
988
+ * 将字符串分解为单词数组。支持camelCase、snake_case、kebab-case等各种命名风格。
989
+ *
990
+ * @category String
991
+ * @param str 要分解的字符串
992
+ * @returns 单词数组
120
993
  * @example
121
- * // type User = { id: string; name: string }
122
- * // type R = PartialByKeys<User, 'name'>
123
- * // 结果:R['name'] 为可选(可能为 undefined)
994
+ * ```ts
995
+ * words('helloWorld') // ['hello', 'World']
996
+ * words('hello_world') // ['hello', 'world']
997
+ * words('hello-world') // ['hello', 'world']
998
+ * words('XMLHttpRequest') // ['XML', 'Http', 'Request']
999
+ * ```
124
1000
  */
125
- type PartialByKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
1001
+ declare function words(str: string): string[];
1002
+
126
1003
  /**
127
- * 将对象类型 `T` 中的键 `K` 标记为只读(浅只读)。
128
- * @typeParam T - 源对象类型
129
- * @typeParam K - 设为只读的键
130
- * @example
131
- * // type User = { id: string; name: string }
132
- * // type R = ReadonlyByKeys<User, 'id'>
133
- * // 结果:R['id'] 不可被重新赋值
1004
+ * 树节点类型定义
1005
+ *
1006
+ * @template T 节点数据类型
134
1007
  */
135
- type ReadonlyByKeys<T, K extends keyof T> = T & {
136
- readonly [P in K]: T[P];
1008
+ type TreeNode<T = any> = T & {
1009
+ children?: TreeNode<T>[];
1010
+ [key: string]: any;
137
1011
  };
138
1012
  /**
139
- * 取消对象类型 `T` 中键 `K` 的只读限制,使其可写(浅层)。
140
- * @typeParam T - 源对象类型
141
- * @typeParam K - 取消只读的键
142
- * @example
143
- * // type User = { readonly id: string; name: string }
144
- * // type R = MutableByKeys<User, 'id'>
145
- * // 结果:R['id'] 变为可写
146
- */
147
- type MutableByKeys<T, K extends keyof T> = {
148
- -readonly [P in K]: T[P];
149
- } & Omit<T, K>;
150
- /**
151
- * 将联合类型 `U` 转换为交叉类型,用于合并联合成员的属性。
152
- * @typeParam U - 联合类型
153
- * @example
154
- * // type U = { a: number } | { b: string }
155
- * // type R = UnionToIntersection<U>
156
- * // 结果:R 为 { a: number } & { b: string }
1013
+ * 树形配置类型
157
1014
  */
158
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? {
159
- [K in keyof I]: I[K];
160
- } : never;
1015
+ interface TreeConfig {
1016
+ id: string;
1017
+ pid: string;
1018
+ children: string;
1019
+ }
161
1020
  /**
162
- * 若对象 `T` 在键 `K` 处的类型为元组,则提取其首个元素类型,否则为 `never`。
163
- * @typeParam T - 具有元组属性的对象类型
164
- * @typeParam K - 属性键
165
- * @example
166
- * // type Cfg = { params: [id: string, flag?: boolean] }
167
- * // type R = FirstParam<Cfg, 'params'>
168
- * // 结果:R 为 string
1021
+ * 树形配置输入类型
169
1022
  */
170
- type FirstParam<T, K extends keyof T> = T[K] extends [infer P, ...any[]] ? P : never;
1023
+ type TreeConfigInput = Partial<TreeConfig>;
171
1024
  /**
172
- * 从函数类型中提取首个参数类型;若 `T` 非函数类型,则为 `undefined`。
173
- * @typeParam T - 函数类型
174
- * @example
175
- * // type Fn = (x: number, y: string) => void
176
- * // type R = FirstParameter<Fn>
177
- * // 结果:R 为 number;若 T 非函数,则为 undefined
1025
+ * 树统计信息类型
178
1026
  */
179
- type FirstParameter<T> = T extends (arg: infer P, ...args: any[]) => any ? P : undefined;
1027
+ interface TreeStats {
1028
+ total: number;
1029
+ leaves: number;
1030
+ depth: number;
1031
+ branches: number;
1032
+ }
180
1033
  /**
181
- * 递归将对象类型 `T` 的所有属性变为可选(深可选)。
182
- * @typeParam T - 源对象类型
183
- * @example
184
- * // type Src = { a: { b: number } }
185
- * // type R = DeepPartial<Src>
186
- * // 结果:R 为 { a?: { b?: number | undefined } | undefined }
1034
+ * 树节点谓词函数类型
1035
+ *
1036
+ * @template T 节点数据类型
1037
+ * @param params 包含节点信息的对象参数
1038
+ * @param params.node 当前节点
1039
+ * @param params.depth 节点深度(从0开始)
1040
+ * @param params.path 从根节点到当前节点的路径数组
1041
+ * @param params.index 节点在同级节点中的索引
1042
+ * @returns 是否满足条件
187
1043
  */
188
- type DeepPartial<T> = {
189
- [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] | undefined;
190
- };
1044
+ type TreePredicate<T = any> = (params: {
1045
+ node: TreeNode<T>;
1046
+ depth: number;
1047
+ path: readonly TreeNode<T>[];
1048
+ index: number;
1049
+ }) => boolean;
191
1050
  /**
192
- * 当 `MaybeObject` 为对象时,返回键 `Key` 对应的属性类型;否则为 `never`。
193
- * @typeParam MaybeObject - 可能为对象的类型
194
- * @typeParam Key - 目标键名(string)
195
- * @example
196
- * // type Obj = { id: number }
197
- * // type R1 = GetObjectField<Obj, 'id'> // 结果:number
198
- * // type R2 = GetObjectField<string, 'id'> // 结果:never
1051
+ * 树节点转换函数类型
1052
+ *
1053
+ * @template T 源节点数据类型
1054
+ * @template R 目标节点数据类型
1055
+ * @param params 包含节点信息的对象参数
1056
+ * @param params.node 当前节点
1057
+ * @param params.depth 节点深度(从0开始)
1058
+ * @param params.path 从根节点到当前节点的路径数组
1059
+ * @param params.index 节点在同级节点中的索引
1060
+ * @returns 转换后的节点数据
199
1061
  */
200
- type GetObjectField<MaybeObject, Key extends string> = MaybeObject extends Record<string, any> ? MaybeObject[Key] : never;
1062
+ type TreeTransformer<T = any, R = any> = (params: {
1063
+ node: TreeNode<T>;
1064
+ depth: number;
1065
+ path: readonly TreeNode<T>[];
1066
+ index: number;
1067
+ }) => R;
201
1068
  /**
202
- * Vue 渲染相关文本/节点类型:可为字符串、`VNode` 或返回 `VNode` 的函数。
203
- * @example
204
- * // 在渲染 API 中允许三种形态:
205
- * // - '标题'
206
- * // - h('div', '标题') 产生的 VNode
207
- * // - () => h('div', '标题') 的惰性渲染函数
1069
+ * 树节点访问函数类型
1070
+ *
1071
+ * @template T 节点数据类型
1072
+ * @param params 包含节点信息的对象参数
1073
+ * @param params.node 当前节点
1074
+ * @param params.depth 节点深度(从0开始)
1075
+ * @param params.path 从根节点到当前节点的路径数组
1076
+ * @param params.index 节点在同级节点中的索引
1077
+ * @returns 返回false可以终止遍历或跳过子节点
208
1078
  */
209
- type StringOrVNode = string | VNode | (() => VNode);
1079
+ type TreeVisitor<T = any> = (params: {
1080
+ node: TreeNode<T>;
1081
+ depth: number;
1082
+ path: readonly TreeNode<T>[];
1083
+ index: number;
1084
+ }) => void | boolean;
1085
+
210
1086
  /**
211
- * 合并两个对象类型,U 中的属性会覆盖 T 中的属性
1087
+ * 从扁平数组创建树形结构
212
1088
  *
1089
+ * @category Tree
1090
+ * @param list 扁平数组数据
1091
+ * @param config 树形配置选项
1092
+ * @returns 树形结构数组
213
1093
  * @example
214
1094
  * ```ts
215
- * type T = { a: number, c: string }
216
- * type U = { a: string, b: boolean }
217
- * type M = Merge<T, U> // { a: string, b: boolean, c: string }
1095
+ * const flatData = [
1096
+ * { id: '1', name: '部门1', parentId: null },
1097
+ * { id: '2', name: '部门1-1', parentId: '1' },
1098
+ * { id: '3', name: '部门1-2', parentId: '1' },
1099
+ * { id: '4', name: '部门1-1-1', parentId: '2' }
1100
+ * ]
1101
+ *
1102
+ * const tree = fromList(flatData, {
1103
+ * id: 'id',
1104
+ * pid: 'parentId',
1105
+ * children: 'children'
1106
+ * })
1107
+ *
1108
+ * console.log(tree) // 转换为树形结构
218
1109
  * ```
219
1110
  */
220
- type Merge<T, U> = Omit<T, keyof U> & U;
1111
+ declare function fromList<T = any>(list: T[], config?: TreeConfigInput): TreeNode<T>[];
221
1112
  /**
222
- * 判断类型 T 是否为纯对象类型
223
- * 纯对象是指普通的对象字面量,排除数组、函数、Date 等特殊对象类型
1113
+ * 将树形结构转换为扁平数组
1114
+ *
1115
+ * @category Tree
1116
+ * @param tree 树形结构(单个节点或节点数组)
1117
+ * @param config 树形配置选项
1118
+ * @returns 扁平数组
224
1119
  * @example
225
1120
  * ```ts
226
- * type Test1 = IsPlainObject<{ a: number }> // true
227
- * type Test2 = IsPlainObject<string[]> // false
228
- * type Test3 = IsPlainObject<() => void> // false
229
- * type Test4 = IsPlainObject<Date> // false
230
- * type Test5 = IsPlainObject<string> // false
231
- * type Test6 = IsPlainObject<null> // false
1121
+ * const tree = [
1122
+ * {
1123
+ * id: '1',
1124
+ * name: '根节点',
1125
+ * children: [
1126
+ * { id: '2', name: '子节点1', children: [] },
1127
+ * { id: '3', name: '子节点2', children: [] }
1128
+ * ]
1129
+ * }
1130
+ * ]
1131
+ *
1132
+ * const flatList = toList(tree)
1133
+ * console.log(flatList) // [{ id: '1', name: '根节点' }, { id: '2', name: '子节点1' }, ...]
232
1134
  * ```
233
1135
  */
234
- type IsPlainObject<T> = NonNullable<T> extends Record<string, any> ? NonNullable<T> extends any[] ? false : NonNullable<T> extends (...args: any[]) => any ? false : NonNullable<T> extends Date ? false : true : false;
235
- /**
236
- * 深度控制类型,用于限制类型递归的深度
237
- * 防止类型计算超出 TypeScript 的递归限制
238
- */
239
- type Depth = [never, 0, 1, 2, 3, 4];
1136
+ declare function toList<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): T[];
240
1137
  /**
241
- * 提取对象的嵌套键,支持点语法路径
1138
+ * 估算树形结构的节点数量
242
1139
  *
243
- * @template T 源对象类型
244
- * @template D 递归深度,默认为2
1140
+ * @category Tree
1141
+ * @param tree 树形结构(单个节点或节点数组)
1142
+ * @param config 树形配置选项
1143
+ * @returns 节点总数量
245
1144
  * @example
246
1145
  * ```ts
247
- * type User = {
248
- * name: string
249
- * address: {
250
- * city: string
251
- * country: string
1146
+ * const tree = [
1147
+ * {
1148
+ * id: '1',
1149
+ * name: '根节点',
1150
+ * children: [
1151
+ * { id: '2', name: '子节点1', children: [] },
1152
+ * { id: '3', name: '子节点2', children: [] }
1153
+ * ]
252
1154
  * }
253
- * }
254
- * type Keys = NestedKeys<User> // 'name' | 'address' | 'address.city' | 'address.country'
1155
+ * ]
1156
+ *
1157
+ * const size = estimateSize(tree)
1158
+ * console.log(size) // 3
255
1159
  * ```
256
1160
  */
257
- type NestedKeys<T, D extends number = 2> = [D] extends [never] ? never : {
258
- [K in keyof T & string]: IsPlainObject<T[K]> extends true ? K | `${K}.${NestedKeys<NonNullable<T[K]>, Depth[D]>}` : K;
259
- }[keyof T & string];
1161
+ declare function estimateSize<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): number;
1162
+
260
1163
  /**
261
- * 提取对象中所有纯对象字段的键(包括嵌套的),支持点语法路径
1164
+ * 在指定节点前插入新节点
262
1165
  *
263
- * @template T 源对象类型
264
- * @template D 递归深度,默认为2
1166
+ * @category Tree
1167
+ * @param tree 树形结构数组
1168
+ * @param targetId 目标节点的ID
1169
+ * @param newNode 要插入的新节点数据
1170
+ * @param config 树形配置选项
1171
+ * @returns 是否成功插入
265
1172
  * @example
266
1173
  * ```ts
267
- * type User = {
268
- * name: string
269
- * age: number
270
- * address: {
271
- * city: string
272
- * location: {
273
- * lat: number
274
- * lng: number
275
- * }
1174
+ * const tree = [
1175
+ * {
1176
+ * id: '1',
1177
+ * name: '节点1',
1178
+ * children: [
1179
+ * { id: '2', name: '节点2', children: [] }
1180
+ * ]
276
1181
  * }
277
- * }
278
- * type ObjectKeys = ObjectFieldKeys<User> // 'address' | 'address.location'
1182
+ * ]
1183
+ *
1184
+ * const success = insertBefore(tree, '2', { id: '1.5', name: '新节点' })
1185
+ * console.log(success) // true
279
1186
  * ```
280
1187
  */
281
- type ObjectFieldKeys<T, D extends number = 2> = [D] extends [never] ? never : {
282
- [K in keyof T & string]: IsPlainObject<T[K]> extends true ? K | `${K}.${ObjectFieldKeys<NonNullable<T[K]>, Depth[D]>}` : never;
283
- }[keyof T & string];
1188
+ declare function insertBefore<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
284
1189
  /**
285
- * 提取对象中所有非对象字段的键
286
- * 排除纯对象字段,只保留原始类型字段的键
1190
+ * 在指定节点后插入新节点
287
1191
  *
288
- * @template T 源对象类型
1192
+ * @category Tree
1193
+ * @param tree 树形结构数组
1194
+ * @param targetId 目标节点的ID
1195
+ * @param newNode 要插入的新节点数据
1196
+ * @param config 树形配置选项
1197
+ * @returns 是否成功插入
289
1198
  * @example
290
1199
  * ```ts
291
- * type User = {
292
- * name: string
293
- * age: number
294
- * address: {
295
- * city: string
1200
+ * const tree = [
1201
+ * {
1202
+ * id: '1',
1203
+ * name: '节点1',
1204
+ * children: [
1205
+ * { id: '2', name: '节点2', children: [] }
1206
+ * ]
296
1207
  * }
297
- * }
298
- * type NonObjectKeys = NonObjectFieldKeys<User> // 'name' | 'age' | 'address.city'
1208
+ * ]
1209
+ *
1210
+ * const success = insertAfter(tree, '2', { id: '3', name: '新节点' })
1211
+ * console.log(success) // true
299
1212
  * ```
300
1213
  */
301
- type NonObjectFieldKeys<T> = Exclude<NestedKeys<T>, ObjectFieldKeys<T>>;
1214
+ declare function insertAfter<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
302
1215
  /**
303
- * 提取对象中所有数组字段的键(包括嵌套的),支持点语法路径
1216
+ * 从树中删除指定节点
304
1217
  *
305
- * @template T 源对象类型
306
- * @template D 递归深度,默认为2
1218
+ * @category Tree
1219
+ * @param tree 树形结构数组
1220
+ * @param targetId 要删除的节点ID
1221
+ * @param config 树形配置选项
1222
+ * @returns 被删除的节点,未找到时返回undefined
307
1223
  * @example
308
1224
  * ```ts
309
- * type User = {
310
- * name: string
311
- * tags: string[]
312
- * posts: Array<{ title: string }>
313
- * profile: {
314
- * hobbies: string[]
1225
+ * const tree = [
1226
+ * {
1227
+ * id: '1',
1228
+ * name: '根节点',
1229
+ * children: [
1230
+ * { id: '2', name: '子节点', children: [] }
1231
+ * ]
315
1232
  * }
316
- * }
317
- * type ArrayKeys = ArrayFieldKeys<User> // 'tags' | 'posts' | 'profile.hobbies'
1233
+ * ]
1234
+ *
1235
+ * const removed = remove(tree, '2')
1236
+ * console.log(removed?.name) // '子节点'
318
1237
  * ```
319
1238
  */
320
- type ArrayFieldKeys<T, D extends number = 2> = [D] extends [never] ? never : {
321
- [K in keyof T & string]: NonNullable<T[K]> extends any[] ? K : IsPlainObject<T[K]> extends true ? `${K}.${ArrayFieldKeys<NonNullable<T[K]>, Depth[D]>}` : never;
322
- }[keyof T & string];
323
- /**
324
- * 根据路径字符串提取对象属性的类型,支持点语法和嵌套对象
325
- * @example GetFieldValue<User, 'tags'> // string[]
326
- * @example GetFieldValue<User, 'profile.bio'> // string
327
- */
328
- type GetFieldValue<T, P extends string> = P extends keyof T ? T[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? T[K] extends undefined ? undefined : GetFieldValue<NonNullable<T[K]>, Rest> : unknown : unknown;
329
-
330
- declare const StorageTypeSchema: z.ZodEnum<{
331
- localStorage: "localStorage";
332
- sessionStorage: "sessionStorage";
333
- }>;
334
- type StorageType = z.infer<typeof StorageTypeSchema>;
335
- declare function createStorageConfigSchema<T = unknown>(schema: z.ZodType<T>): z.ZodObject<{
336
- key: z.ZodString;
337
- schema: z.ZodCustom<z.ZodType<T, unknown, z.core.$ZodTypeInternals<T, unknown>>, z.ZodType<T, unknown, z.core.$ZodTypeInternals<T, unknown>>>;
338
- defaultValue: z.ZodCustom<T, T>;
339
- prefix: z.ZodDefault<z.ZodString>;
340
- storage: z.ZodDefault<z.ZodEnum<{
341
- localStorage: "localStorage";
342
- sessionStorage: "sessionStorage";
343
- }>>;
344
- }, z.core.$strip>;
345
- type StorageConfig<T = unknown> = z.infer<ReturnType<typeof createStorageConfigSchema<T>>>;
346
- type StorageConfigInput<T = unknown> = z.input<ReturnType<typeof createStorageConfigSchema<T>>>;
347
- interface AppStorageReturn<T> {
348
- state: Ref<T>;
349
- getItem: () => T;
350
- setItem: (value: T) => void;
351
- removeItem: () => void;
352
- }
353
-
354
- /**
355
- * vue-component-type-helpers
356
- * Copy from https://github.com/vuejs/language-tools/tree/master/packages/component-type-helpers
357
- */
358
- type IsComponent = StringOrVNode | Component | DefineComponent | ((...args: any[]) => any);
359
- type ComponentType<T> = T extends new (...args: any) => {} ? 1 : T extends (...args: any) => any ? 2 : 0;
360
- type ComponentProps<T> = T extends new (...args: any) => {
361
- $props: infer P;
362
- } ? NonNullable<P> : T extends (props: infer P, ...args: any) => any ? P : {};
363
- type ComponentSlots<T> = T extends new (...args: any) => {
364
- $slots: infer S;
365
- } ? NonNullable<S> : T extends (props: any, ctx: {
366
- slots: infer S;
367
- attrs: any;
368
- emit: any;
369
- }, ...args: any) => any ? NonNullable<S> : {};
370
- type ComponentAttrs<T> = T extends new (...args: any) => {
371
- $attrs: infer A;
372
- } ? NonNullable<A> : T extends (props: any, ctx: {
373
- slots: any;
374
- attrs: infer A;
375
- emit: any;
376
- }, ...args: any) => any ? NonNullable<A> : {};
377
- type ComponentEmit<T> = T extends new (...args: any) => {
378
- $emit: infer E;
379
- } ? NonNullable<E> : T extends (props: any, ctx: {
380
- slots: any;
381
- attrs: any;
382
- emit: infer E;
383
- }, ...args: any) => any ? NonNullable<E> : {};
384
- type ComponentExposed<T> = T extends new (...args: any) => infer E ? E : T extends (props: any, ctx: any, expose: (exposed: infer E) => any, ...args: any) => any ? NonNullable<E> : {};
1239
+ declare function remove<T = any>(tree: TreeNode<T>[], targetId: string, config?: TreeConfigInput): TreeNode<T> | undefined;
385
1240
 
386
1241
  /**
387
- * 应用存储管理的组合式函数,支持localStorage和sessionStorage
1242
+ * 查找树中第一个满足条件的节点
388
1243
  *
389
- * @category Composables
390
- * @param config 存储配置对象
391
- * @returns 存储管理对象,包含响应式状态和操作方法
1244
+ * @category Tree
1245
+ * @param tree 树形结构(单个节点或节点数组)
1246
+ * @param predicate 查找条件函数
1247
+ * @param config 树形配置选项
1248
+ * @returns 匹配的节点;未找到时返回undefined
392
1249
  * @example
393
1250
  * ```ts
394
- * import { z } from 'zod/v4'
395
- *
396
- * // 定义用户偏好设置的schema
397
- * const userPrefsSchema = z.object({
398
- * theme: z.enum(['light', 'dark']),
399
- * language: z.string(),
400
- * fontSize: z.number().min(12).max(24)
401
- * })
402
- *
403
- * // 创建存储管理实例
404
- * const { state, setItem, getItem, removeItem } = useAppStorage({
405
- * key: 'user-preferences',
406
- * defaultValue: {
407
- * theme: 'light',
408
- * language: 'zh-CN',
409
- * fontSize: 16
410
- * },
411
- * schema: userPrefsSchema,
412
- * storage: 'localStorage',
413
- * prefix: 'app'
414
- * })
415
- *
416
- * // 使用响应式状态
417
- * console.log(state.value.theme) // 'light'
1251
+ * const tree = [
1252
+ * {
1253
+ * id: '1',
1254
+ * name: '部门1',
1255
+ * children: [
1256
+ * { id: '2', name: '部门1-1', children: [] }
1257
+ * ]
1258
+ * }
1259
+ * ]
418
1260
  *
419
- * // 更新设置
420
- * setItem({
421
- * theme: 'dark',
422
- * language: 'en-US',
423
- * fontSize: 18
424
- * })
1261
+ * const result = find(tree, ({ node }) => node.name === '部门1-1')
1262
+ * console.log(result?.id) // '2'
425
1263
  * ```
426
1264
  */
427
- declare function useAppStorage<T = unknown>(config: StorageConfigInput<T>): AppStorageReturn<T>;
428
-
1265
+ declare function find<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T> | undefined;
429
1266
  /**
430
- * 复制文本到剪贴板的组合式函数
1267
+ * 查找树中所有满足条件的节点
431
1268
  *
432
- * @category Composables
433
- * @param text 要复制的文本内容
434
- * @returns 复制是否成功的Promise
1269
+ * @category Tree
1270
+ * @param tree 树形结构(单个节点或节点数组)
1271
+ * @param predicate 查找条件函数
1272
+ * @param config 树形配置选项
1273
+ * @returns 所有匹配的节点数组
435
1274
  * @example
436
1275
  * ```ts
437
- * // 复制简单文本
438
- * const copyText = async () => {
439
- * const success = await useCopyCode('Hello, World!')
440
- * if (success) {
441
- * console.log('复制成功')
442
- * } else {
443
- * console.log('复制失败')
444
- * }
445
- * }
446
- *
447
- * // 复制代码块
448
- * const copyCodeBlock = async () => {
449
- * const code = `
450
- * function hello() {
451
- * console.log('Hello, World!')
452
- * }
453
- * `
454
- * const success = await useCopyCode(code)
455
- * if (success) {
456
- * // 显示复制成功提示
457
- * showNotification('代码已复制到剪贴板')
1276
+ * const tree = [
1277
+ * {
1278
+ * id: '1',
1279
+ * type: 'folder',
1280
+ * name: '根目录',
1281
+ * children: [
1282
+ * { id: '2', type: 'file', name: '文件1', children: [] },
1283
+ * { id: '3', type: 'file', name: '文件2', children: [] }
1284
+ * ]
458
1285
  * }
459
- * }
1286
+ * ]
460
1287
  *
461
- * // 在点击事件中使用
462
- * const handleCopy = () => {
463
- * useCopyCode(document.getElementById('code').textContent)
464
- * }
1288
+ * const files = findAll(tree, ({ node }) => node.type === 'file')
1289
+ * console.log(files.length) // 2
465
1290
  * ```
466
1291
  */
467
- declare function useCopyCode(text: string): Promise<boolean>;
468
-
1292
+ declare function findAll<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
469
1293
  /**
470
- * 数组去重,返回去除重复元素后的新数组
1294
+ * 根据ID查找树中的节点
471
1295
  *
472
- * @category Array
473
- * @param arr 待去重的数组
474
- * @returns 去重后的新数组
1296
+ * @category Tree
1297
+ * @param tree 树形结构(单个节点或节点数组)
1298
+ * @param id 要查找的节点ID
1299
+ * @param config 树形配置选项
1300
+ * @returns 匹配的节点;未找到时返回undefined
475
1301
  * @example
476
1302
  * ```ts
477
- * const numbers = [1, 2, 2, 3, 3, 4]
478
- * const uniqueNumbers = unique(numbers)
479
- * console.log(uniqueNumbers) // [1, 2, 3, 4]
1303
+ * const tree = [
1304
+ * {
1305
+ * id: '1',
1306
+ * name: '根节点',
1307
+ * children: [
1308
+ * { id: '2', name: '子节点', children: [] }
1309
+ * ]
1310
+ * }
1311
+ * ]
480
1312
  *
481
- * const strings = ['a', 'b', 'a', 'c']
482
- * const uniqueStrings = unique(strings)
483
- * console.log(uniqueStrings) // ['a', 'b', 'c']
1313
+ * const result = findById(tree, '2')
1314
+ * console.log(result?.name) // '子节点'
484
1315
  * ```
485
1316
  */
486
- declare function unique<T>(arr: T[]): T[];
1317
+ declare function findById<T = any>(tree: TreeNode<T> | TreeNode<T>[], id: string, config?: TreeConfigInput): TreeNode<T> | undefined;
1318
+
487
1319
  /**
488
- * 将数组分割成指定大小的块
1320
+ * 过滤树形结构,保留满足条件的节点及其祖先和后代
489
1321
  *
490
- * @category Array
491
- * @param arr 待分割的数组
492
- * @param size 每个块的大小
493
- * @returns 分割后的二维数组
1322
+ * @category Tree
1323
+ * @param tree 树形结构(单个节点或节点数组)
1324
+ * @param predicate 过滤条件函数,接收对象参数 {node, depth, path, index}
1325
+ * @param config 树形配置选项
1326
+ * @returns 过滤后的树形结构数组
494
1327
  * @example
495
1328
  * ```ts
496
- * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
497
- * const chunks = chunk(numbers, 3)
498
- * console.log(chunks) // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
1329
+ * const tree = [
1330
+ * {
1331
+ * id: '1',
1332
+ * type: 'folder',
1333
+ * name: '根目录',
1334
+ * children: [
1335
+ * { id: '2', type: 'file', name: '文档.txt', children: [] },
1336
+ * { id: '3', type: 'folder', name: '子目录', children: [
1337
+ * { id: '4', type: 'file', name: '图片.jpg', children: [] }
1338
+ * ] }
1339
+ * ]
1340
+ * }
1341
+ * ]
499
1342
  *
500
- * const names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
501
- * const pairs = chunk(names, 2)
502
- * console.log(pairs) // [['Alice', 'Bob'], ['Charlie', 'David'], ['Eve']]
1343
+ * const filtered = filter(tree, ({ node }) => node.type === 'file')
1344
+ * // 返回包含所有文件节点及其父级路径的树结构
503
1345
  * ```
504
1346
  */
505
- declare function chunk<T>(arr: T[], size: number): T[][];
1347
+ declare function filter<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
506
1348
  /**
507
- * 数组扁平化,将嵌套数组展平到指定深度
1349
+ * 转换树形结构,将每个节点转换为新的结构
508
1350
  *
509
- * @category Array
510
- * @param arr 待扁平化的数组
511
- * @param depth 扁平化深度,默认为1
512
- * @returns 扁平化后的数组
1351
+ * @category Tree
1352
+ * @param tree 树形结构(单个节点或节点数组)
1353
+ * @param transformer 节点转换函数,接收对象参数 {node, depth, path, index}
1354
+ * @param config 树形配置选项
1355
+ * @returns 转换后的树形结构数组
513
1356
  * @example
514
1357
  * ```ts
515
- * const nested = [1, [2, 3], [4, [5, 6]]]
516
- * const flat1 = flatten(nested)
517
- * console.log(flat1) // [1, 2, 3, 4, [5, 6]]
1358
+ * const tree = [
1359
+ * {
1360
+ * id: '1',
1361
+ * name: '部门1',
1362
+ * children: [
1363
+ * { id: '2', name: '部门1-1', children: [] }
1364
+ * ]
1365
+ * }
1366
+ * ]
518
1367
  *
519
- * const flat2 = flatten(nested, 2)
520
- * console.log(flat2) // [1, 2, 3, 4, 5, 6]
1368
+ * const transformed = transform(tree, ({ node, depth }) => ({
1369
+ * key: node.id,
1370
+ * title: node.name,
1371
+ * level: depth
1372
+ * }))
1373
+ * // 转换为新的数据结构
521
1374
  * ```
522
1375
  */
523
- declare function flatten<T>(arr: T[], depth?: number): any[];
1376
+ declare function transform<T = any, R = any>(tree: TreeNode<T> | TreeNode<T>[], transformer: TreeTransformer<T, R>, config?: TreeConfigInput): TreeNode<R>[];
524
1377
 
525
1378
  /**
526
- * 防抖函数,在指定时间内多次触发只执行最后一次
1379
+ * 遍历树形结构的每个节点
527
1380
  *
528
- * @category Async
529
- * @param func 需要防抖的函数
530
- * @param wait 防抖延迟时间(毫秒)
531
- * @returns 防抖处理后的函数
1381
+ * @category Tree
1382
+ * @param tree 树形结构(单个节点或节点数组)
1383
+ * @param visitor 访问者函数,接收对象参数 {node, depth, path, index},返回false可以跳过子节点的遍历
1384
+ * @param config 树形配置选项
532
1385
  * @example
533
1386
  * ```ts
534
- * const debouncedSearch = debounce((query: string) => {
535
- * console.log('搜索:', query)
536
- * }, 300)
1387
+ * const tree = [
1388
+ * {
1389
+ * id: '1',
1390
+ * name: '根节点',
1391
+ * children: [
1392
+ * { id: '2', name: '子节点', children: [] }
1393
+ * ]
1394
+ * }
1395
+ * ]
537
1396
  *
538
- * // 连续调用,只有最后一次会执行
539
- * debouncedSearch('a')
540
- * debouncedSearch('ab')
541
- * debouncedSearch('abc') // 只有这次会在300ms后执行
1397
+ * forEach(tree, ({ node, depth }) => {
1398
+ * console.log(`${' '.repeat(depth * 2)}${node.name}`)
1399
+ * // 输出缩进的树结构
1400
+ * })
542
1401
  * ```
543
1402
  */
544
- declare function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void;
1403
+ declare function forEach<T = any>(tree: TreeNode<T> | TreeNode<T>[], visitor: TreeVisitor<T>, config?: TreeConfigInput): void;
545
1404
 
546
1405
  /**
547
- * 延迟执行函数,返回一个在指定时间后resolve的Promise
1406
+ * 获取树形结构的统计信息
548
1407
  *
549
- * @category Async
550
- * @param ms 延迟时间(毫秒)
551
- * @returns 延迟Promise
1408
+ * @category Tree
1409
+ * @param tree 树形结构(单个节点或节点数组)
1410
+ * @param config 树形配置选项
1411
+ * @returns 树的统计信息,包含总节点数、叶子节点数、最大深度和分支节点数
552
1412
  * @example
553
1413
  * ```ts
554
- * // 延迟1秒后继续执行
555
- * await sleep(1000)
556
- * console.log('1秒后执行')
1414
+ * const tree = [
1415
+ * {
1416
+ * id: '1',
1417
+ * name: '根节点',
1418
+ * children: [
1419
+ * { id: '2', name: '子节点1', children: [] },
1420
+ * { id: '3', name: '子节点2', children: [
1421
+ * { id: '4', name: '孙节点', children: [] }
1422
+ * ] }
1423
+ * ]
1424
+ * }
1425
+ * ]
557
1426
  *
558
- * // 在异步函数中使用
559
- * async function delayedOperation() {
560
- * console.log('开始')
561
- * await sleep(500)
562
- * console.log('500ms后执行')
563
- * }
1427
+ * const stats = getStats(tree)
1428
+ * console.log(stats) // { total: 4, leaves: 2, depth: 3, branches: 2 }
564
1429
  * ```
565
1430
  */
566
- declare function sleep(ms: number): Promise<void>;
1431
+ declare function getStats<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): TreeStats;
567
1432
  /**
568
- * 可取消的延迟函数,返回Promise和取消函数
1433
+ * 验证树形结构的有效性
569
1434
  *
570
- * @category Async
571
- * @param ms 延迟时间(毫秒)
572
- * @returns 包含Promise和取消函数的对象
1435
+ * @category Tree
1436
+ * @param tree 树形结构(单个节点或节点数组)
1437
+ * @param config 树形配置选项
1438
+ * @returns 验证结果,包含是否有效和错误信息数组
573
1439
  * @example
574
1440
  * ```ts
575
- * const { promise, cancel } = sleepWithCancel(5000)
576
- *
577
- * // 在另一个地方取消延迟
578
- * setTimeout(() => {
579
- * cancel() // 取消延迟
580
- * }, 2000)
1441
+ * const tree = [
1442
+ * {
1443
+ * id: '1',
1444
+ * name: '根节点',
1445
+ * children: [
1446
+ * { id: '2', name: '子节点', children: [] }
1447
+ * ]
1448
+ * }
1449
+ * ]
581
1450
  *
582
- * try {
583
- * await promise
584
- * console.log('5秒后执行')
585
- * } catch (error) {
586
- * console.log('延迟被取消')
587
- * }
1451
+ * const result = validate(tree)
1452
+ * console.log(result.isValid) // true
1453
+ * console.log(result.errors) // []
588
1454
  * ```
589
1455
  */
590
- declare function sleepWithCancel(ms: number): {
591
- promise: Promise<void>;
592
- cancel: () => void;
1456
+ declare function validate<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): {
1457
+ isValid: boolean;
1458
+ errors: string[];
593
1459
  };
594
1460
 
595
1461
  /**
596
- * 节流函数,在指定时间内多次触发只执行第一次
1462
+ * 树形数据结构操作工具类
597
1463
  *
598
- * @category Async
599
- * @param func 需要节流的函数
600
- * @param limit 节流时间间隔(毫秒)
601
- * @returns 节流处理后的函数
602
- * @example
603
- * ```ts
604
- * const throttledScroll = throttle((event: Event) => {
605
- * console.log('滚动事件处理')
606
- * }, 100)
1464
+ * 提供了一系列操作树形数据的静态方法,包括:
1465
+ * - 查找:find, findAll, findById
1466
+ * - 转换:fromList, toList, transform
1467
+ * - 过滤:filter
1468
+ * - 遍历:forEach
1469
+ * - 统计:estimateSize, getStats
1470
+ * - 修改:insertBefore, insertAfter, remove
1471
+ * - 验证:validate
607
1472
  *
608
- * // 监听滚动事件,每100ms最多执行一次
609
- * window.addEventListener('scroll', throttledScroll)
610
- * ```
611
- */
612
- declare function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void;
613
-
614
- /**
615
- * 将SVG字符串转换为PNG格式的Blob对象
1473
+ * 所有使用谓词函数或访问函数的方法都采用对象解构参数格式:
1474
+ * `({ node, depth, path, index }) => boolean`
616
1475
  *
617
- * @category File
618
- * @param svg SVG字符串
619
- * @returns PNG格式的Blob对象
620
- * @throws 当SVG无效或转换失败时抛出错误
621
1476
  * @example
622
1477
  * ```ts
623
- * const svgString = '<svg width="100" height="100"><circle cx="50" cy="50" r="40" fill="red"/></svg>'
1478
+ * // 1. 从扁平数组创建树形结构
1479
+ * const departments = [
1480
+ * { id: '1', name: '技术部', parentId: null },
1481
+ * { id: '2', name: '前端组', parentId: '1' },
1482
+ * { id: '3', name: '后端组', parentId: '1' },
1483
+ * { id: '4', name: 'UI 组', parentId: '2' },
1484
+ * { id: '5', name: '测试组', parentId: '2' }
1485
+ * ]
624
1486
  *
625
- * try {
626
- * const pngBlob = await convertSvgToPng(svgString)
627
- * const url = URL.createObjectURL(pngBlob)
1487
+ * const tree = Tree.fromList(departments, {
1488
+ * id: 'id',
1489
+ * pid: 'parentId',
1490
+ * children: 'children'
1491
+ * })
628
1492
  *
629
- * // 用于下载或显示
630
- * const img = document.createElement('img')
631
- * img.src = url
632
- * document.body.appendChild(img)
633
- * } catch (error) {
634
- * console.error('SVG转换失败:', error)
1493
+ * // 2. 查找节点
1494
+ * const frontend = Tree.find(tree, ({ node }) => node.name === '前端组')
1495
+ * console.log(frontend) // { id: '2', name: '前端组', children: [...] }
1496
+ *
1497
+ * const uiNode = Tree.findById(tree, '4')
1498
+ * console.log(uiNode) // { id: '4', name: 'UI 组', ... }
1499
+ *
1500
+ * // 3. 查找所有叶子节点
1501
+ * const leaves = Tree.findAll(tree, ({ node }) => {
1502
+ * return !node.children || node.children.length === 0
1503
+ * })
1504
+ * console.log(leaves) // [{ id: '4', ... }, { id: '5', ... }, { id: '3', ... }]
1505
+ *
1506
+ * // 4. 过滤节点(保留匹配节点及其祖先)
1507
+ * const filtered = Tree.filter(tree, ({ node }) => node.name.includes('组'))
1508
+ * // 返回包含所有 "组" 节点及其父级路径的树结构
1509
+ *
1510
+ * // 5. 转换节点结构
1511
+ * const menuTree = Tree.transform(tree, ({ node, depth }) => ({
1512
+ * key: node.id,
1513
+ * label: node.name,
1514
+ * level: depth,
1515
+ * indent: depth * 20
1516
+ * }))
1517
+ *
1518
+ * // 6. 遍历所有节点
1519
+ * Tree.forEach(tree, ({ node, depth, path }) => {
1520
+ * const indent = ' '.repeat(depth)
1521
+ * const breadcrumb = path.map(n => n.name).join(' > ')
1522
+ * console.log(`${indent}${node.name} (路径: ${breadcrumb})`)
1523
+ * })
1524
+ *
1525
+ * // 7. 修改树结构
1526
+ * Tree.insertBefore(tree, '3', { id: '6', name: '运维组' })
1527
+ * Tree.insertAfter(tree, '2', { id: '7', name: '移动组' })
1528
+ * const removed = Tree.remove(tree, '5')
1529
+ *
1530
+ * // 8. 获取统计信息
1531
+ * const stats = Tree.getStats(tree)
1532
+ * console.log(stats)
1533
+ * // { total: 5, leaves: 3, depth: 3, branches: 2 }
1534
+ *
1535
+ * // 9. 验证树结构
1536
+ * const validation = Tree.validate(tree)
1537
+ * if (!validation.isValid) {
1538
+ * console.error('树结构错误:', validation.errors)
635
1539
  * }
1540
+ *
1541
+ * // 10. 转换回扁平数组
1542
+ * const flatList = Tree.toList(tree)
1543
+ * console.log(flatList) // [{ id: '1', name: '技术部' }, ...]
636
1544
  * ```
637
1545
  */
638
- declare function convertSvgToPng(svg: string): Promise<Blob>;
1546
+ declare class Tree {
1547
+ static fromList: typeof fromList;
1548
+ static toList: typeof toList;
1549
+ static estimateSize: typeof estimateSize;
1550
+ static find: typeof find;
1551
+ static findAll: typeof findAll;
1552
+ static findById: typeof findById;
1553
+ static insertBefore: typeof insertBefore;
1554
+ static insertAfter: typeof insertAfter;
1555
+ static remove: typeof remove;
1556
+ static filter: typeof filter;
1557
+ static transform: typeof transform;
1558
+ static forEach: typeof forEach;
1559
+ static getStats: typeof getStats;
1560
+ static validate: typeof validate;
1561
+ }
639
1562
 
640
1563
  /**
641
- * 从响应头中提取文件名
1564
+ * 统一同步/异步返回类型
1565
+ * @typeParam T - 类型
1566
+ * @example
1567
+ * // type T = string | Promise<string>
1568
+ * // type R = ApiAwaitable<T>
1569
+ * // 结果:R 为 string | Promise<string>
1570
+ */
1571
+ type ApiAwaitable<T> = T | Promise<T>;
1572
+ /**
1573
+ * 提取Promise类型
1574
+ * @typeParam T - 类型
1575
+ * @example
1576
+ * // type T = Promise<string>
1577
+ * // type R = ApiUnwrapPromise<T>
1578
+ * // 结果:R 为 string
1579
+ */
1580
+ type ApiUnwrapPromise<T> = T extends Promise<infer U> ? U : T;
1581
+ /**
1582
+ * 提取函数返回类型
1583
+ * @typeParam TFn - 函数类型
1584
+ * @example
1585
+ * // type Fn = (x: number, y: string) => Promise<string>
1586
+ * // type R = ApiAwaitedReturn<Fn>
1587
+ * // 结果:R 为 string
1588
+ */
1589
+ type ApiAwaitedReturn<TFn> = TFn extends (...args: any[]) => ApiAwaitable<infer R> ? R : never;
1590
+
1591
+ /**
1592
+ * 提供字符串字面量提示的同时允许任意字符串
1593
+ * 在 IDE 中提供 T 类型的自动补全提示,但不限制只能使用这些值
642
1594
  *
643
- * @category File
644
- * @param headers 响应头对象
645
- * @param fallbackName 默认文件名
646
- * @returns 提取的文件名
647
1595
  * @example
648
1596
  * ```ts
649
- * // 从响应头中提取文件名
650
- * const headers = new Headers({
651
- * 'content-disposition': 'attachment; filename="report.pdf"'
652
- * })
653
- * const filename = extractFilename(headers, 'download')
654
- * console.log(filename) // 'report.pdf'
1597
+ * type Color = Suggest<'red' | 'blue' | 'green'>
655
1598
  *
656
- * // 处理编码的文件名
657
- * const encodedHeaders = new Headers({
658
- * 'content-disposition': 'attachment; filename*=UTF-8\'\'%E6%8A%A5%E5%91%8A.pdf'
659
- * })
660
- * const encodedFilename = extractFilename(encodedHeaders)
661
- * console.log(encodedFilename) // '报告.pdf'
1599
+ * // IDE 会提示 'red', 'blue', 'green',但也可以使用其他字符串
1600
+ * const color1: Color = 'red' // 有提示
1601
+ * const color2: Color = 'yellow' // 也可以,虽然没有提示
662
1602
  * ```
663
1603
  */
664
- declare function extractFilename(headers?: Headers, fallbackName?: string): string;
1604
+ type Suggest<T extends string> = T | (string & {});
665
1605
  /**
666
- * 触发浏览器下载文件
1606
+ * 响应式值类型 - 基于 Vue 的 `MaybeRefOrGetter` 扩展,额外支持上下文回调
1607
+ *
1608
+ * @template T - 值类型
1609
+ * @template CTX - 上下文类型(用于回调函数)
667
1610
  *
668
- * @category File
669
- * @param blob 文件数据
670
- * @param filename 文件名
671
1611
  * @example
672
1612
  * ```ts
673
- * // 下载文本文件
674
- * const textBlob = new Blob(['Hello, World!'], { type: 'text/plain' })
675
- * triggerDownload(textBlob, 'hello.txt')
676
- *
677
- * // 下载JSON数据
678
- * const data = { name: 'John', age: 30 }
679
- * const jsonBlob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
680
- * triggerDownload(jsonBlob, 'data.json')
681
- *
682
- * // 下载图片
683
- * const canvas = document.createElement('canvas')
684
- * canvas.toBlob((blob) => {
685
- * if (blob) {
686
- * triggerDownload(blob, 'image.png')
687
- * }
688
- * })
1613
+ * const value: ReactiveValue<boolean> = ref(false)
1614
+ * const getter: ReactiveValue<string> = () => name.value
1615
+ * const computed: ReactiveValue<number> = computed(() => count.value)
1616
+ * const withContext: ReactiveValue<boolean, Context> = (ctx) => ctx.value > 0
689
1617
  * ```
690
1618
  */
691
- declare function triggerDownload(blob: Blob, filename: string): void;
1619
+ type ReactiveValue<T, CTX = never> = [CTX] extends [never] ? MaybeRefOrGetter<T> : MaybeRefOrGetter<T> | ((ctx: CTX) => T);
1620
+ type StripNullable<T> = T extends null | undefined ? never : T;
1621
+
1622
+ interface ParsedUrl {
1623
+ /** 完整的原始 URL */
1624
+ href: string;
1625
+ /** 协议 (http:, https:, etc.) */
1626
+ protocol: string;
1627
+ /** 主机名 + 端口 */
1628
+ host: string;
1629
+ /** 主机名 */
1630
+ hostname: string;
1631
+ /** 端口号 */
1632
+ port: string;
1633
+ /** 路径部分 */
1634
+ pathname: string;
1635
+ /** 查询字符串 (包含 ?) */
1636
+ search: string;
1637
+ /** 哈希部分 (包含 #) */
1638
+ hash: string;
1639
+ /** 用户认证信息 (user:pass) */
1640
+ auth: string;
1641
+ /** 源 (protocol + host) */
1642
+ origin: string;
1643
+ }
1644
+ /**
1645
+ * 查询参数值类型
1646
+ */
1647
+ type QueryParamValue = string | number | boolean | null | undefined;
1648
+ /**
1649
+ * 查询参数对象类型
1650
+ */
1651
+ type QueryParams = Record<string, QueryParamValue | QueryParamValue[]>;
692
1652
 
693
1653
  /**
694
- * 格式化文件大小,将字节数转换为可读的文件大小字符串
1654
+ * vue-component-type-helpers
1655
+ * Copy from https://github.com/vuejs/language-tools/tree/master/packages/component-type-helpers
1656
+ */
1657
+ type IsComponent = StringOrVNode | Component | DefineComponent | ((...args: any[]) => any);
1658
+ type ComponentType<T> = T extends new (...args: any) => {} ? 1 : T extends (...args: any) => any ? 2 : 0;
1659
+ type ComponentProps<T> = T extends new (...args: any) => {
1660
+ $props: infer P;
1661
+ } ? NonNullable<P> : T extends (props: infer P, ...args: any) => any ? P : {};
1662
+ type ComponentSlots<T> = T extends new (...args: any) => {
1663
+ $slots: infer S;
1664
+ } ? NonNullable<S> : T extends (props: any, ctx: {
1665
+ slots: infer S;
1666
+ attrs: any;
1667
+ emit: any;
1668
+ }, ...args: any) => any ? NonNullable<S> : {};
1669
+ type ComponentAttrs<T> = T extends new (...args: any) => {
1670
+ $attrs: infer A;
1671
+ } ? NonNullable<A> : T extends (props: any, ctx: {
1672
+ slots: any;
1673
+ attrs: infer A;
1674
+ emit: any;
1675
+ }, ...args: any) => any ? NonNullable<A> : {};
1676
+ type ComponentEmit<T> = T extends new (...args: any) => {
1677
+ $emit: infer E;
1678
+ } ? NonNullable<E> : T extends (props: any, ctx: {
1679
+ slots: any;
1680
+ attrs: any;
1681
+ emit: infer E;
1682
+ }, ...args: any) => any ? NonNullable<E> : {};
1683
+ type ComponentExposed<T> = T extends new (...args: any) => infer E ? E : T extends (props: any, ctx: any, expose: (exposed: infer E) => any, ...args: any) => any ? NonNullable<E> : {};
1684
+
1685
+ /**
1686
+ * 将数组分割成指定大小的块
695
1687
  *
696
- * @category File
697
- * @param bytes 文件大小(字节)
698
- * @returns 格式化后的文件大小字符串
1688
+ * @category Array
1689
+ * @param arr 待分割的数组
1690
+ * @param size 每个块的大小
1691
+ * @returns 分割后的二维数组
699
1692
  * @example
700
1693
  * ```ts
701
- * console.log(formatFileSize(1024)) // '1 KB'
702
- * console.log(formatFileSize(1536)) // '1.5 KB'
703
- * console.log(formatFileSize(1048576)) // '1 MB'
704
- * console.log(formatFileSize(1073741824)) // '1 GB'
1694
+ * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
1695
+ * const chunks = chunk(numbers, 3)
1696
+ * console.log(chunks) // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
705
1697
  *
706
- * // 处理边界情况
707
- * console.log(formatFileSize(0)) // '0 Bytes'
708
- * console.log(formatFileSize(-100)) // '0 Bytes'
1698
+ * const names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
1699
+ * const pairs = chunk(names, 2)
1700
+ * console.log(pairs) // [['Alice', 'Bob'], ['Charlie', 'David'], ['Eve']]
709
1701
  * ```
710
1702
  */
711
- declare function formatFileSize(bytes: number): string;
1703
+ declare function chunk<T>(arr: T[], size: number): T[][];
712
1704
 
713
1705
  /**
714
- * 替换SVG文件中的currentColor为指定颜色
1706
+ * 数组扁平化,将嵌套数组展平到指定深度
715
1707
  *
716
- * @category File
717
- * @param path SVG文件路径
718
- * @param color 替换的颜色值,不提供则返回原始SVG
719
- * @returns 处理后的SVG字符串
720
- * @throws 当文件获取失败或SVG无效时抛出错误
1708
+ * @category Array
1709
+ * @param arr 待扁平化的数组
1710
+ * @param depth 扁平化深度,默认为1
1711
+ * @returns 扁平化后的数组
721
1712
  * @example
722
1713
  * ```ts
723
- * // 获取并替换SVG中的currentColor
724
- * try {
725
- * const svgContent = await replaceCurrentColor('/icons/star.svg', '#ff0000')
726
- * const container = document.createElement('div')
727
- * container.innerHTML = svgContent
728
- * document.body.appendChild(container)
729
- * } catch (error) {
730
- * console.error('SVG处理失败:', error)
731
- * }
1714
+ * const nested = [1, [2, 3], [4, [5, 6]]]
1715
+ * const flat1 = flatten(nested)
1716
+ * console.log(flat1) // [1, 2, 3, 4, [5, 6]]
732
1717
  *
733
- * // 只获取SVG内容,不替换颜色
734
- * const originalSvg = await replaceCurrentColor('/icons/star.svg')
1718
+ * const flat2 = flatten(nested, 2)
1719
+ * console.log(flat2) // [1, 2, 3, 4, 5, 6]
735
1720
  * ```
736
1721
  */
737
- declare function replaceCurrentColor(path: string, color?: string): Promise<string>;
1722
+ declare function flatten<T>(arr: T[], depth?: number): any[];
738
1723
 
739
1724
  /**
740
- * 将对象的键名转换为kebab-case格式
1725
+ * 数组去重,返回去除重复元素后的新数组
741
1726
  *
742
- * @category Object
743
- * @param obj 待转换的对象
744
- * @param deep 是否深度转换嵌套对象,默认为false
745
- * @returns 转换后的对象
1727
+ * @category Array
1728
+ * @param arr 待去重的数组
1729
+ * @returns 去重后的新数组
746
1730
  * @example
747
1731
  * ```ts
748
- * const obj = {
749
- * firstName: 'John',
750
- * lastName: 'Doe',
751
- * userInfo: {
752
- * birthDate: '1990-01-01',
753
- * phoneNumber: '123-456-7890'
754
- * }
755
- * }
756
- *
757
- * const converted = convertToKebabCase(obj)
758
- * console.log(converted)
759
- * // {
760
- * // 'first-name': 'John',
761
- * // 'last-name': 'Doe',
762
- * // 'user-info': { birthDate: '1990-01-01', phoneNumber: '123-456-7890' }
763
- * // }
1732
+ * const numbers = [1, 2, 2, 3, 3, 4]
1733
+ * const uniqueNumbers = unique(numbers)
1734
+ * console.log(uniqueNumbers) // [1, 2, 3, 4]
764
1735
  *
765
- * const deepConverted = convertToKebabCase(obj, true)
766
- * console.log(deepConverted)
767
- * // {
768
- * // 'first-name': 'John',
769
- * // 'last-name': 'Doe',
770
- * // 'user-info': { 'birth-date': '1990-01-01', 'phone-number': '123-456-7890' }
771
- * // }
1736
+ * const strings = ['a', 'b', 'a', 'c']
1737
+ * const uniqueStrings = unique(strings)
1738
+ * console.log(uniqueStrings) // ['a', 'b', 'c']
772
1739
  * ```
773
1740
  */
774
- declare function convertToKebabCase<T extends AnyObject>(obj: T, deep?: boolean): T;
1741
+ declare function unique<T>(arr: T[]): T[];
775
1742
 
776
1743
  /**
777
- * 深拷贝任意 JavaScript 值。
778
- *
779
- * - 优先使用原生 `structuredClone`(若可用),覆盖 `Map`/`Set`/`TypedArray`/`ArrayBuffer` 等内建类型。
780
- * - 对不支持 `structuredClone` 的环境,使用回退实现:
781
- * - 支持循环引用(`WeakMap` 记忆化)。
782
- * - 保留原型与属性描述符(含 getter/setter),复制 symbol 键。
783
- * - 内建类型专项处理:`Date`/`RegExp`/`Map`/`Set`/`ArrayBuffer`/`TypedArray`/`URL`/`Error`。
784
- *
785
- * @category Object
786
- * @typeParam T 拷贝值的类型
787
- * @param obj 要被深拷贝的值
788
- * @param cache 内部使用的 `WeakMap`(循环引用记忆化),一般不需要传入
789
- * @returns 新的深拷贝值,与输入值结构等价、引用独立
1744
+ * 防抖函数,在指定时间内多次触发只执行最后一次
790
1745
  *
1746
+ * @category Async
1747
+ * @param func 需要防抖的函数
1748
+ * @param wait 防抖延迟时间(毫秒)
1749
+ * @returns 防抖处理后的函数
791
1750
  * @example
792
1751
  * ```ts
793
- * const source = { a: 1, d: new Date(), m: new Map([[1, { x: 2 }]]) }
794
- * const cloned = deepClone(source)
795
- * cloned !== source // true
796
- * cloned.d !== source.d // true
797
- * cloned.m !== source.m // true
798
- * cloned.m.get(1) !== source.m.get(1) // true
799
- * ```
1752
+ * const debouncedSearch = debounce((query: string) => {
1753
+ * console.log('搜索:', query)
1754
+ * }, 300)
800
1755
  *
801
- * @remarks
802
- * 若对象包含不可克隆资源(如带有原生句柄的自定义对象),请在外层进行自定义序列化逻辑或为该类型添加专用分支。
1756
+ * // 连续调用,只有最后一次会执行
1757
+ * debouncedSearch('a')
1758
+ * debouncedSearch('ab')
1759
+ * debouncedSearch('abc') // 只有这次会在300ms后执行
1760
+ * ```
803
1761
  */
804
- declare function deepClone<T>(obj: T, cache?: WeakMap<object, any>): T;
1762
+ declare function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void;
805
1763
 
806
1764
  /**
807
- * 从对象中排除指定的键,返回新对象
1765
+ * 延迟执行函数,返回一个在指定时间后resolve的Promise
808
1766
  *
809
- * @category Object
810
- * @param obj 源对象
811
- * @param keys 要排除的键数组
812
- * @returns 排除指定键后的新对象
1767
+ * @category Async
1768
+ * @param ms 延迟时间(毫秒)
1769
+ * @returns 延迟Promise
813
1770
  * @example
814
1771
  * ```ts
815
- * const user = {
816
- * id: 1,
817
- * name: 'John',
818
- * password: 'secret',
819
- * email: 'john@example.com'
820
- * }
821
- *
822
- * const publicUser = omit(user, ['password'])
823
- * console.log(publicUser) // { id: 1, name: 'John', email: 'john@example.com' }
1772
+ * // 延迟1秒后继续执行
1773
+ * await sleep(1000)
1774
+ * console.log('1秒后执行')
824
1775
  *
825
- * const basicInfo = omit(user, ['password', 'email'])
826
- * console.log(basicInfo) // { id: 1, name: 'John' }
1776
+ * // 在异步函数中使用
1777
+ * async function delayedOperation() {
1778
+ * console.log('开始')
1779
+ * await sleep(500)
1780
+ * console.log('500ms后执行')
1781
+ * }
827
1782
  * ```
828
1783
  */
829
- declare function omit<T extends AnyObject, K extends keyof T>(obj: T, keys: K[]): OmitByKey<T, K>;
1784
+ declare function sleep(ms: number): Promise<void>;
1785
+
830
1786
  /**
831
- * 从对象中排除值为undefined的键
1787
+ * 可取消的延迟函数,返回Promise和取消函数
832
1788
  *
833
- * @category Object
834
- * @param obj 源对象
835
- * @returns 排除undefined值后的新对象
1789
+ * @category Async
1790
+ * @param ms 延迟时间(毫秒)
1791
+ * @returns 包含Promise和取消函数的对象
836
1792
  * @example
837
1793
  * ```ts
838
- * const data = {
839
- * name: 'John',
840
- * age: undefined,
841
- * city: 'New York',
842
- * country: undefined
843
- * }
1794
+ * const { promise, cancel } = sleepWithCancel(5000)
844
1795
  *
845
- * const cleaned = omitUndefined(data)
846
- * console.log(cleaned) // { name: 'John', city: 'New York' }
1796
+ * // 在另一个地方取消延迟
1797
+ * setTimeout(() => {
1798
+ * cancel() // 取消延迟
1799
+ * }, 2000)
847
1800
  *
848
- * // 用于API请求前清理数据
849
- * const requestData = omitUndefined({
850
- * title: 'Post Title',
851
- * content: 'Post content',
852
- * tags: undefined,
853
- * published: true
854
- * })
1801
+ * try {
1802
+ * await promise
1803
+ * console.log('5秒后执行')
1804
+ * } catch (error) {
1805
+ * console.log('延迟被取消')
1806
+ * }
855
1807
  * ```
856
1808
  */
857
- declare function omitUndefined<T extends AnyObject>(obj: T): Partial<T>;
858
-
859
- type PathSegment = string | number;
860
- type PathSegments = PathSegment[];
861
- type PathInput = string | PathSegments;
862
- /**
863
- * 检查值是否为有效的容器类型(对象或数组)
864
- *
865
- * - isObject: 仅检查纯对象,排除数组
866
- * - isValidContainer: 检查所有可作为容器的类型(对象 + 数组)
867
- *
868
- * 支持 Vue 3 的 Proxy 对象和 Proxy 数组。
1809
+ declare function sleepWithCancel(ms: number): {
1810
+ promise: Promise<void>;
1811
+ cancel: () => void;
1812
+ };
1813
+
1814
+ /**
1815
+ * 节流函数,在指定时间内多次触发只执行第一次
869
1816
  *
870
- * @category Validator
871
- * @param value - 待检查的值
872
- * @returns 是否为有效容器(对象或数组)
1817
+ * @category Async
1818
+ * @param func 需要节流的函数
1819
+ * @param limit 节流时间间隔(毫秒)
1820
+ * @returns 节流处理后的函数
873
1821
  * @example
874
1822
  * ```ts
875
- * isValidContainer({}) // true
876
- * isValidContainer([]) // true
877
- * isValidContainer(new Proxy({}, {})) // true
878
- * isValidContainer(null) // false
879
- * isValidContainer('string') // false
880
- * isValidContainer(123) // false
1823
+ * const throttledScroll = throttle((event: Event) => {
1824
+ * console.log('滚动事件处理')
1825
+ * }, 100)
1826
+ *
1827
+ * // 监听滚动事件,每100ms最多执行一次
1828
+ * window.addEventListener('scroll', throttledScroll)
881
1829
  * ```
882
1830
  */
883
- declare function isValidContainer(value: any): boolean;
1831
+ declare function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void;
1832
+
884
1833
  /**
885
- * 将路径字符串解析为片段数组。
886
- *
887
- * - 支持点语法与方括号语法混用
888
- * - 引号键支持单/双引号与反斜杠转义
889
- * - 方括号内未引号的非负整数字面量解析为 number 段
890
- * - 点语法中的纯数字段保持字符串(不转为索引)
1834
+ * 解析 URL 字符串为结构化对象
891
1835
  *
892
- * @category Object
893
- * @param path 路径字符串或片段数组
894
- * @returns 解析后的片段数组
1836
+ * @category URL
1837
+ * @param url 要解析的 URL 字符串
1838
+ * @param base 可选的基础 URL,用于解析相对路径
1839
+ * @returns 解析后的 URL 对象,解析失败返回 null
895
1840
  * @example
896
1841
  * ```ts
897
- * toPath('a.b[0].c') // ['a', 'b', 0, 'c']
898
- * toPath("a['x.y']") // ['a', 'x.y']
1842
+ * parseUrl('https://example.com:8080/path?query=1#hash')
1843
+ * // {
1844
+ * // href: 'https://example.com:8080/path?query=1#hash',
1845
+ * // protocol: 'https:',
1846
+ * // host: 'example.com:8080',
1847
+ * // hostname: 'example.com',
1848
+ * // port: '8080',
1849
+ * // pathname: '/path',
1850
+ * // search: '?query=1',
1851
+ * // hash: '#hash',
1852
+ * // auth: '',
1853
+ * // origin: 'https://example.com:8080'
1854
+ * // }
1855
+ *
1856
+ * parseUrl('/path', 'https://example.com')
1857
+ * // 解析相对 URL
899
1858
  * ```
900
1859
  */
901
- declare function toPath(path: PathInput): PathSegments;
1860
+ declare function parseUrl(url: string, base?: string): ParsedUrl | null;
902
1861
  /**
903
- * 读取对象指定路径的值。
1862
+ * 检查字符串是否为有效的 URL
904
1863
  *
905
- * - 若取值结果为 undefined,则返回 defaultValue
906
- * - 若取值结果为 null,则直接返回 null(不触发默认值)
907
- * - 传入空路径时返回 object 本身
908
- *
909
- * @category Object
910
- * @param object 源对象
911
- * @param path 路径字符串或片段数组
912
- * @param defaultValue 结果为 undefined 时返回的默认值
913
- * @returns 读取到的值或默认值
1864
+ * @category URL
1865
+ * @param url 要检查的字符串
1866
+ * @returns 是否为有效 URL
914
1867
  * @example
915
1868
  * ```ts
916
- * const obj = { a: { b: { c: 1, d: undefined }, e: null }, arr: [{ x: 9 }] }
917
- * getPath(obj, 'a.b.c') // 1
918
- * getPath(obj, 'a.b.d', 42) // 42(d 为 undefined,使用默认值)
919
- * getPath(obj, 'a.e', 100) // null(null 不触发默认值)
920
- * getPath(obj, 'arr[0].x') // 9
921
- * getPath(obj, '') // 返回 obj 本身
1869
+ * isValidUrl('https://example.com') // true
1870
+ * isValidUrl('not a url') // false
1871
+ * isValidUrl('ftp://files.example.com') // true
922
1872
  * ```
923
1873
  */
924
- declare function getPath<T, D = undefined>(object: T, path: PathInput, defaultValue?: D): unknown | D;
1874
+ declare function isValidUrl(url: string): boolean;
925
1875
  /**
926
- * 在对象指定路径写入值。缺失路径会被自动创建:
927
- * - 下一段为 number(索引)时创建数组
928
- * - 下一段为 string(属性)时创建对象
1876
+ * 检查 URL 是否为绝对路径
929
1877
  *
930
- * 若中途遇到非容器类型(如字符串/数值/布尔),会被替换为正确的容器以继续写入。
931
- *
932
- * @category Object
933
- * @param object 目标对象(原地修改并返回同一引用)
934
- * @param path 路径字符串或片段数组
935
- * @param value 要写入的值
936
- * @returns 原对象(已修改)
1878
+ * @category URL
1879
+ * @param url 要检查的 URL
1880
+ * @returns 是否为绝对路径
937
1881
  * @example
938
1882
  * ```ts
939
- * const obj: any = {}
940
- * setPath(obj, 'a.b[0].c', 7)
941
- * // obj => { a: { b: [{ c: 7 }] } }
942
- *
943
- * setPath(obj, 'a.b[2].d', 8)
944
- * // 数组自动扩容到长度 3
945
- * // obj.a.b[2] => { d: 8 }
946
- *
947
- * setPath(obj, 'a.0.b', 1) // 点语法数字键保持为字符串键
948
- * // obj => { a: { 0: { b: 1 } } }
949
- * setPath(obj, 'a[0].b', 2) // 索引用方括号
950
- * // obj.a[0].b => 2
1883
+ * isAbsoluteUrl('https://example.com') // true
1884
+ * isAbsoluteUrl('/path/to/page') // false
1885
+ * isAbsoluteUrl('//example.com/path') // true (protocol-relative)
951
1886
  * ```
952
1887
  */
953
- declare function setPath<T extends Record<string, any>>(object: T, path: PathInput, value: unknown): T;
1888
+ declare function isAbsoluteUrl(url: string): boolean;
954
1889
  /**
955
- * 将片段数组序列化为路径字符串。
1890
+ * 检查 URL 是否为相对路径
956
1891
  *
957
- * 规则:
958
- * - 合法标识符段使用点拼接(a.b.c)
959
- * - 数字段转为索引([0])
960
- * - 其它需要转义的键使用方括号引号(['x.y']),并转义 \\ 与 '\''
961
- *
962
- * @category Object
963
- * @param segments 路径片段数组
964
- * @returns 路径字符串
1892
+ * @category URL
1893
+ * @param url 要检查的 URL
1894
+ * @returns 是否为相对路径
965
1895
  * @example
966
1896
  * ```ts
967
- * const p = joinPath(['a', 'x.y', 0, 'space key'])
968
- * // p: "a['x.y'][0]['space key']"
969
- * // 与解析往返:toPath(p) => ['a', 'x.y', 0, 'space key']
1897
+ * isRelativeUrl('/path/to/page') // true
1898
+ * isRelativeUrl('./page') // true
1899
+ * isRelativeUrl('../page') // true
1900
+ * isRelativeUrl('https://example.com') // false
970
1901
  * ```
971
1902
  */
972
- declare function joinPath(segments: (string | number)[]): string;
973
-
1903
+ declare function isRelativeUrl(url: string): boolean;
974
1904
  /**
975
- * 从对象中选择指定的键,返回新对象
1905
+ * 获取 URL 的域名部分
976
1906
  *
977
- * @category Object
978
- * @param obj 源对象
979
- * @param keys 要选择的键数组
980
- * @returns 只包含指定键的新对象
1907
+ * @category URL
1908
+ * @param url URL 字符串
1909
+ * @returns 域名,解析失败返回空字符串
981
1910
  * @example
982
1911
  * ```ts
983
- * const user = {
984
- * id: 1,
985
- * name: 'John',
986
- * email: 'john@example.com',
987
- * password: 'secret',
988
- * createdAt: '2023-01-01',
989
- * updatedAt: '2023-01-15'
990
- * }
991
- *
992
- * const publicInfo = pick(user, ['id', 'name', 'email'])
993
- * console.log(publicInfo) // { id: 1, name: 'John', email: 'john@example.com' }
994
- *
995
- * const basicInfo = pick(user, ['id', 'name'])
996
- * console.log(basicInfo) // { id: 1, name: 'John' }
1912
+ * getDomain('https://sub.example.com/path') // 'sub.example.com'
1913
+ * getDomain('https://example.com:8080') // 'example.com'
997
1914
  * ```
998
1915
  */
999
- declare function pick<T extends AnyObject, K extends keyof T>(obj: T, keys: K[]): PickByKey<T, K>;
1000
-
1916
+ declare function getDomain(url: string): string;
1001
1917
  /**
1002
- * 将对象按指定键分离为两个对象
1918
+ * 获取 URL 的根域名(顶级域名 + 二级域名)
1003
1919
  *
1004
- * @category Object
1005
- * @param obj 源对象
1006
- * @param keys 要分离的键数组
1007
- * @returns 包含picked和omitted两个对象的结果
1920
+ * @category URL
1921
+ * @param url URL 字符串
1922
+ * @returns 根域名,解析失败返回空字符串
1008
1923
  * @example
1009
1924
  * ```ts
1010
- * const user = {
1011
- * id: 1,
1012
- * name: 'John',
1013
- * email: 'john@example.com',
1014
- * password: 'secret',
1015
- * role: 'admin'
1016
- * }
1017
- *
1018
- * const { picked, omitted } = separate(user, ['id', 'name'])
1019
- * console.log(picked) // { id: 1, name: 'John' }
1020
- * console.log(omitted) // { email: 'john@example.com', password: 'secret', role: 'admin' }
1021
- *
1022
- * // 用于分离敏感信息
1023
- * const { picked: publicData, omitted: privateData } = separate(user, ['id', 'name', 'email'])
1925
+ * getRootDomain('https://sub.example.com') // 'example.com'
1926
+ * getRootDomain('https://a.b.example.co.uk') // 'example.co.uk'
1024
1927
  * ```
1025
1928
  */
1026
- declare function separate<T extends AnyObject, K extends keyof T>(obj: T, keys: K[]): {
1027
- picked: PickByKey<T, K>;
1028
- omitted: OmitByKey<T, K>;
1029
- };
1929
+ declare function getRootDomain(url: string): string;
1030
1930
  /**
1031
- * 将对象按多分组键集合进行分离(浅层),返回各分组与 others
1032
- *
1033
- * - 键冲突策略:先到先得。若同一键出现在多个分组中,则归入第一个匹配到的分组
1034
- * - 仅处理对象自有的浅层键,不解析深层路径
1035
- * - 分组中包含不存在于对象的键将被忽略
1931
+ * 获取 URL 的文件扩展名
1036
1932
  *
1037
- * @category Object
1038
- * @param obj 源对象
1039
- * @param groups 分组映射,如 { a: ['x', 'y'], b: ['z'] }
1040
- * @returns 一个对象,包含每个分组的子对象以及 others(其余未被分组捕获的键)
1933
+ * @category URL
1934
+ * @param url URL 字符串
1935
+ * @returns 文件扩展名(不含点),无扩展名返回空字符串
1041
1936
  * @example
1042
1937
  * ```ts
1043
- * const options = { id: 1, name: 'John', email: 'a@b.com', role: 'admin' }
1044
- * const { a, b, others } = separateMany(options, { a: ['id'], b: ['name'] as const })
1045
- * // a: { id: 1 }
1046
- * // b: { name: 'John' }
1047
- * // others: { email: 'a@b.com', role: 'admin' }
1938
+ * getUrlExtension('https://example.com/file.pdf') // 'pdf'
1939
+ * getUrlExtension('https://example.com/file.tar.gz') // 'gz'
1940
+ * getUrlExtension('https://example.com/path/') // ''
1048
1941
  * ```
1049
1942
  */
1050
- declare function separateMany<T extends AnyObject, M extends Record<string, readonly (keyof T)[]>>(obj: T, groups: M): {
1051
- [P in keyof M]: PickByKey<T, M[P][number]>;
1052
- } & {
1053
- others: OmitByKey<T, M[keyof M][number]>;
1054
- };
1055
-
1943
+ declare function getUrlExtension(url: string): string;
1056
1944
  /**
1057
- * 将字符串转换为Start Case格式(每个单词首字母大写,用空格分隔)。
1945
+ * 获取 URL 的文件名
1058
1946
  *
1059
- * @category String
1060
- * @param str 要转换的字符串
1061
- * @returns Start Case格式的字符串
1947
+ * @category URL
1948
+ * @param url URL 字符串
1949
+ * @param includeExtension 是否包含扩展名,默认 true
1950
+ * @returns 文件名
1062
1951
  * @example
1063
1952
  * ```ts
1064
- * startCase('firstName') // 'First Name'
1065
- * startCase('first_name') // 'First Name'
1066
- * startCase('first-name') // 'First Name'
1067
- * startCase('XMLHttpRequest') // 'XML Http Request'
1953
+ * getUrlFilename('https://example.com/path/file.pdf') // 'file.pdf'
1954
+ * getUrlFilename('https://example.com/path/file.pdf', false) // 'file'
1955
+ * getUrlFilename('https://example.com/path/') // ''
1068
1956
  * ```
1069
1957
  */
1070
- declare function startCase(str: string): string;
1958
+ declare function getUrlFilename(url: string, includeExtension?: boolean): string;
1959
+
1071
1960
  /**
1072
- * 将字符串转换为驼峰命名格式(第一个单词小写,后续单词首字母大写)。
1961
+ * 解析 URL 查询字符串为对象
1073
1962
  *
1074
- * @category String
1075
- * @param str 要转换的字符串
1076
- * @returns 驼峰命名格式的字符串
1963
+ * @category URL
1964
+ * @param search 查询字符串(可带或不带 ?)
1965
+ * @returns 解析后的查询参数对象
1077
1966
  * @example
1078
1967
  * ```ts
1079
- * camelCase('First Name') // 'firstName'
1080
- * camelCase('first_name') // 'firstName'
1081
- * camelCase('first-name') // 'firstName'
1082
- * camelCase('XMLHttpRequest') // 'xmlHttpRequest'
1968
+ * parseQuery('?name=John&age=30')
1969
+ * // { name: 'John', age: '30' }
1970
+ *
1971
+ * parseQuery('tags=a&tags=b&tags=c')
1972
+ * // { tags: ['a', 'b', 'c'] }
1973
+ *
1974
+ * parseQuery('encoded=%E4%B8%AD%E6%96%87')
1975
+ * // { encoded: '中文' }
1083
1976
  * ```
1084
1977
  */
1085
- declare function camelCase(str: string): string;
1978
+ declare function parseQuery(search: string): Record<string, string | string[]>;
1086
1979
  /**
1087
- * 将字符串转换为短横线命名格式(kebab-case)。
1980
+ * 将对象序列化为查询字符串
1088
1981
  *
1089
- * @category String
1090
- * @param str 要转换的字符串
1091
- * @returns 短横线命名格式的字符串
1982
+ * @category URL
1983
+ * @param params 查询参数对象
1984
+ * @param options 序列化选项
1985
+ * @param options.skipNull 跳过 null 和 undefined 值
1986
+ * @param options.skipEmpty 跳过空字符串
1987
+ * @param options.arrayFormat 数组格式: 'repeat' (默认), 'bracket', 'index', 'comma'
1988
+ * @returns 查询字符串(不含 ?)
1092
1989
  * @example
1093
1990
  * ```ts
1094
- * kebabCase('firstName') // 'first-name'
1095
- * kebabCase('First Name') // 'first-name'
1096
- * kebabCase('first_name') // 'first-name'
1097
- * kebabCase('XMLHttpRequest') // 'xml-http-request'
1991
+ * stringifyQuery({ name: 'John', age: 30 })
1992
+ * // 'name=John&age=30'
1993
+ *
1994
+ * stringifyQuery({ tags: ['a', 'b', 'c'] })
1995
+ * // 'tags=a&tags=b&tags=c'
1996
+ *
1997
+ * stringifyQuery({ name: '中文' })
1998
+ * // 'name=%E4%B8%AD%E6%96%87'
1999
+ *
2000
+ * stringifyQuery({ a: null, b: undefined, c: '' }, { skipNull: true, skipEmpty: true })
2001
+ * // ''
1098
2002
  * ```
1099
2003
  */
1100
- declare function kebabCase(str: string): string;
2004
+ declare function stringifyQuery(params: QueryParams, options?: {
2005
+ /** 跳过 null 和 undefined 值 */
2006
+ skipNull?: boolean;
2007
+ /** 跳过空字符串 */
2008
+ skipEmpty?: boolean;
2009
+ /** 数组格式: 'repeat' (默认), 'bracket', 'index', 'comma' */
2010
+ arrayFormat?: 'repeat' | 'bracket' | 'index' | 'comma';
2011
+ }): string;
1101
2012
  /**
1102
- * 将字符串转换为下划线命名格式(snake_case)。
2013
+ * 从 URL 获取指定查询参数的值
1103
2014
  *
1104
- * @category String
1105
- * @param str 要转换的字符串
1106
- * @returns 下划线命名格式的字符串
2015
+ * @category URL
2016
+ * @param url URL 字符串
2017
+ * @param key 参数名
2018
+ * @returns 参数值,不存在返回 null
1107
2019
  * @example
1108
2020
  * ```ts
1109
- * snakeCase('firstName') // 'first_name'
1110
- * snakeCase('First Name') // 'first_name'
1111
- * snakeCase('first-name') // 'first_name'
1112
- * snakeCase('XMLHttpRequest') // 'xml_http_request'
2021
+ * getQueryParam('https://example.com?name=John&age=30', 'name')
2022
+ * // 'John'
2023
+ *
2024
+ * getQueryParam('https://example.com?tags=a&tags=b', 'tags')
2025
+ * // 'a' (返回第一个值)
2026
+ *
2027
+ * getQueryParam('https://example.com', 'name')
2028
+ * // null
1113
2029
  * ```
1114
2030
  */
1115
- declare function snakeCase(str: string): string;
2031
+ declare function getQueryParam(url: string, key: string): string | null;
1116
2032
  /**
1117
- * 将字符串转换为帕斯卡命名格式(PascalCase,每个单词首字母大写)。
2033
+ * 从 URL 获取所有指定查询参数的值(用于多值参数)
1118
2034
  *
1119
- * @category String
1120
- * @param str 要转换的字符串
1121
- * @returns 帕斯卡命名格式的字符串
2035
+ * @category URL
2036
+ * @param url URL 字符串
2037
+ * @param key 参数名
2038
+ * @returns 参数值数组
1122
2039
  * @example
1123
2040
  * ```ts
1124
- * pascalCase('firstName') // 'FirstName'
1125
- * pascalCase('first_name') // 'FirstName'
1126
- * pascalCase('first-name') // 'FirstName'
1127
- * pascalCase('XMLHttpRequest') // 'XmlHttpRequest'
2041
+ * getQueryParams('https://example.com?tags=a&tags=b&tags=c', 'tags')
2042
+ * // ['a', 'b', 'c']
2043
+ *
2044
+ * getQueryParams('https://example.com?name=John', 'name')
2045
+ * // ['John']
2046
+ *
2047
+ * getQueryParams('https://example.com', 'name')
2048
+ * // []
1128
2049
  * ```
1129
2050
  */
1130
- declare function pascalCase(str: string): string;
2051
+ declare function getQueryParams(url: string, key: string): string[];
1131
2052
  /**
1132
- * 将字符串首字母大写,其余字母小写。
2053
+ * 设置 URL 的查询参数
1133
2054
  *
1134
- * @category String
1135
- * @param str 要转换的字符串
1136
- * @returns 首字母大写的字符串
2055
+ * @category URL
2056
+ * @param url URL 字符串
2057
+ * @param key 参数名
2058
+ * @param value 参数值
2059
+ * @returns 新的 URL 字符串
1137
2060
  * @example
1138
2061
  * ```ts
1139
- * capitalize('hello') // 'Hello'
1140
- * capitalize('HELLO') // 'Hello'
1141
- * capitalize('hello world') // 'Hello world'
2062
+ * setQueryParam('https://example.com', 'page', 1)
2063
+ * // 'https://example.com?page=1'
2064
+ *
2065
+ * setQueryParam('https://example.com?page=1', 'page', 2)
2066
+ * // 'https://example.com?page=2'
2067
+ *
2068
+ * setQueryParam('https://example.com?page=1', 'sort', 'name')
2069
+ * // 'https://example.com?page=1&sort=name'
1142
2070
  * ```
1143
2071
  */
1144
- declare function capitalize(str: string): string;
2072
+ declare function setQueryParam(url: string, key: string, value: QueryParamValue): string;
1145
2073
  /**
1146
- * 将字符串首字母大写,其余字母保持原样。
2074
+ * 批量设置 URL 的查询参数
1147
2075
  *
1148
- * @category String
1149
- * @param str 要转换的字符串
1150
- * @returns 首字母大写的字符串
2076
+ * @category URL
2077
+ * @param url URL 字符串
2078
+ * @param params 要设置的参数对象
2079
+ * @returns 新的 URL 字符串
1151
2080
  * @example
1152
2081
  * ```ts
1153
- * upperFirst('hello') // 'Hello'
1154
- * upperFirst('hELLO') // 'HELLO'
1155
- * upperFirst('hello world') // 'Hello world'
2082
+ * setQueryParams('https://example.com', { page: 1, limit: 10 })
2083
+ * // 'https://example.com?page=1&limit=10'
2084
+ *
2085
+ * setQueryParams('https://example.com?page=1', { page: 2, sort: 'name' })
2086
+ * // 'https://example.com?page=2&sort=name'
1156
2087
  * ```
1157
2088
  */
1158
- declare function upperFirst(str: string): string;
2089
+ declare function setQueryParams(url: string, params: QueryParams): string;
1159
2090
  /**
1160
- * 将字符串首字母小写,其余字母保持原样。
2091
+ * 追加查询参数(不覆盖已有同名参数)
1161
2092
  *
1162
- * @category String
1163
- * @param str 要转换的字符串
1164
- * @returns 首字母小写的字符串
2093
+ * @category URL
2094
+ * @param url URL 字符串
2095
+ * @param key 参数名
2096
+ * @param value 参数值
2097
+ * @returns 新的 URL 字符串
1165
2098
  * @example
1166
2099
  * ```ts
1167
- * lowerFirst('Hello') // 'hello'
1168
- * lowerFirst('HELLO') // 'hELLO'
1169
- * lowerFirst('Hello World') // 'hello World'
2100
+ * appendQueryParam('https://example.com?tag=a', 'tag', 'b')
2101
+ * // 'https://example.com?tag=a&tag=b'
1170
2102
  * ```
1171
2103
  */
1172
- declare function lowerFirst(str: string): string;
2104
+ declare function appendQueryParam(url: string, key: string, value: QueryParamValue): string;
1173
2105
  /**
1174
- * 将字符串转换为大写格式,单词之间用空格分隔。
2106
+ * 删除 URL 的指定查询参数
1175
2107
  *
1176
- * @category String
1177
- * @param str 要转换的字符串
1178
- * @returns 大写格式的字符串
2108
+ * @category URL
2109
+ * @param url URL 字符串
2110
+ * @param key 要删除的参数名
2111
+ * @returns 新的 URL 字符串
1179
2112
  * @example
1180
2113
  * ```ts
1181
- * upperCase('firstName') // 'FIRST NAME'
1182
- * upperCase('first_name') // 'FIRST NAME'
1183
- * upperCase('first-name') // 'FIRST NAME'
1184
- * upperCase('XMLHttpRequest') // 'XML HTTP REQUEST'
2114
+ * removeQueryParam('https://example.com?page=1&sort=name', 'page')
2115
+ * // 'https://example.com?sort=name'
2116
+ *
2117
+ * removeQueryParam('https://example.com?page=1', 'page')
2118
+ * // 'https://example.com'
1185
2119
  * ```
1186
2120
  */
1187
- declare function upperCase(str: string): string;
2121
+ declare function removeQueryParam(url: string, key: string): string;
1188
2122
  /**
1189
- * 将字符串转换为小写格式,单词之间用空格分隔。
2123
+ * 检查 URL 是否包含指定查询参数
1190
2124
  *
1191
- * @category String
1192
- * @param str 要转换的字符串
1193
- * @returns 小写格式的字符串
2125
+ * @category URL
2126
+ * @param url URL 字符串
2127
+ * @param key 参数名
2128
+ * @returns 是否包含该参数
1194
2129
  * @example
1195
2130
  * ```ts
1196
- * lowerCase('firstName') // 'first name'
1197
- * lowerCase('First_Name') // 'first name'
1198
- * lowerCase('FIRST-NAME') // 'first name'
1199
- * lowerCase('XMLHttpRequest') // 'xml http request'
2131
+ * hasQueryParam('https://example.com?page=1', 'page') // true
2132
+ * hasQueryParam('https://example.com?page=1', 'sort') // false
2133
+ * hasQueryParam('https://example.com?flag', 'flag') // true (无值参数)
1200
2134
  * ```
1201
2135
  */
1202
- declare function lowerCase(str: string): string;
1203
- /**
1204
- * 将驼峰命名转换为kebab-case
1205
- * @deprecated Use `kebabCase` instead
1206
- */
1207
- declare const camelToKebab: typeof kebabCase;
1208
- /**
1209
- * 将kebab-case转换为驼峰命名
1210
- * @deprecated Use `camelCase` instead
1211
- */
1212
- declare const kebabToCamel: typeof camelCase;
2136
+ declare function hasQueryParam(url: string, key: string): boolean;
1213
2137
 
1214
2138
  /**
1215
- * 将字符串分解为单词数组。支持camelCase、snake_case、kebab-case等各种命名风格。
2139
+ * 连接 URL 路径片段
1216
2140
  *
1217
- * @category String
1218
- * @param str 要分解的字符串
1219
- * @returns 单词数组
2141
+ * @category URL
2142
+ * @param parts URL 片段
2143
+ * @returns 连接后的 URL
1220
2144
  * @example
1221
2145
  * ```ts
1222
- * words('helloWorld') // ['hello', 'World']
1223
- * words('hello_world') // ['hello', 'world']
1224
- * words('hello-world') // ['hello', 'world']
1225
- * words('XMLHttpRequest') // ['XML', 'Http', 'Request']
2146
+ * joinUrl('https://example.com', 'api', 'users')
2147
+ * // 'https://example.com/api/users'
2148
+ *
2149
+ * joinUrl('https://example.com/', '/api/', '/users/')
2150
+ * // 'https://example.com/api/users/'
2151
+ *
2152
+ * joinUrl('/api', 'users', '123')
2153
+ * // '/api/users/123'
1226
2154
  * ```
1227
2155
  */
1228
- declare function words(str: string): string[];
1229
-
2156
+ declare function joinUrl(...parts: string[]): string;
1230
2157
  /**
1231
- * 树节点类型定义
2158
+ * 规范化 URL 路径(移除多余斜杠、处理 . 和 ..)
1232
2159
  *
1233
- * @template T 节点数据类型
1234
- */
1235
- type TreeNode<T = any> = T & {
1236
- children?: TreeNode<T>[];
1237
- [key: string]: any;
1238
- };
1239
- declare const TreeConfigSchema: z.ZodObject<{
1240
- id: z.ZodDefault<z.ZodString>;
1241
- pid: z.ZodDefault<z.ZodString>;
1242
- children: z.ZodDefault<z.ZodString>;
1243
- }, z.core.$strip>;
1244
- /**
1245
- * 树形配置输入类型
2160
+ * @category URL
2161
+ * @param url URL 字符串
2162
+ * @returns 规范化后的 URL
2163
+ * @example
2164
+ * ```ts
2165
+ * normalizeUrl('https://example.com//api///users/')
2166
+ * // 'https://example.com/api/users/'
2167
+ *
2168
+ * normalizeUrl('https://example.com/api/../users')
2169
+ * // 'https://example.com/users'
2170
+ *
2171
+ * normalizeUrl('/api/./users/../posts')
2172
+ * // '/api/posts'
2173
+ * ```
1246
2174
  */
1247
- type TreeConfigInput = z.input<typeof TreeConfigSchema>;
1248
- declare const _TreeStatsSchema: z.ZodObject<{
1249
- total: z.ZodNumber;
1250
- leaves: z.ZodNumber;
1251
- depth: z.ZodNumber;
1252
- branches: z.ZodNumber;
1253
- }, z.core.$strip>;
2175
+ declare function normalizeUrl(url: string): string;
1254
2176
  /**
1255
- * 树统计信息类型
2177
+ * 移除 URL 的尾部斜杠
2178
+ *
2179
+ * @category URL
2180
+ * @param url URL 字符串
2181
+ * @returns 移除尾部斜杠后的 URL
2182
+ * @example
2183
+ * ```ts
2184
+ * removeTrailingSlash('https://example.com/') // 'https://example.com'
2185
+ * removeTrailingSlash('https://example.com/path/') // 'https://example.com/path'
2186
+ * removeTrailingSlash('https://example.com') // 'https://example.com'
2187
+ * ```
1256
2188
  */
1257
- type TreeStats = z.infer<typeof _TreeStatsSchema>;
2189
+ declare function removeTrailingSlash(url: string): string;
1258
2190
  /**
1259
- * 树节点谓词函数类型
2191
+ * 确保 URL 以斜杠结尾
1260
2192
  *
1261
- * @template T 节点数据类型
1262
- * @param params 包含节点信息的对象参数
1263
- * @param params.node 当前节点
1264
- * @param params.depth 节点深度(从0开始)
1265
- * @param params.path 从根节点到当前节点的路径数组
1266
- * @param params.index 节点在同级节点中的索引
1267
- * @returns 是否满足条件
2193
+ * @category URL
2194
+ * @param url URL 字符串
2195
+ * @returns 带尾部斜杠的 URL
2196
+ * @example
2197
+ * ```ts
2198
+ * ensureTrailingSlash('https://example.com') // 'https://example.com/'
2199
+ * ensureTrailingSlash('https://example.com/path') // 'https://example.com/path/'
2200
+ * ensureTrailingSlash('https://example.com/') // 'https://example.com/'
2201
+ * ```
1268
2202
  */
1269
- type TreePredicate<T = any> = (params: {
1270
- node: TreeNode<T>;
1271
- depth: number;
1272
- path: readonly TreeNode<T>[];
1273
- index: number;
1274
- }) => boolean;
2203
+ declare function ensureTrailingSlash(url: string): string;
1275
2204
  /**
1276
- * 树节点转换函数类型
2205
+ * 移除 URL 的开头斜杠
1277
2206
  *
1278
- * @template T 源节点数据类型
1279
- * @template R 目标节点数据类型
1280
- * @param params 包含节点信息的对象参数
1281
- * @param params.node 当前节点
1282
- * @param params.depth 节点深度(从0开始)
1283
- * @param params.path 从根节点到当前节点的路径数组
1284
- * @param params.index 节点在同级节点中的索引
1285
- * @returns 转换后的节点数据
2207
+ * @category URL
2208
+ * @param url URL 或路径字符串
2209
+ * @returns 移除开头斜杠后的字符串
2210
+ * @example
2211
+ * ```ts
2212
+ * removeLeadingSlash('/path/to/page') // 'path/to/page'
2213
+ * removeLeadingSlash('///path') // 'path'
2214
+ * removeLeadingSlash('path') // 'path'
2215
+ * ```
1286
2216
  */
1287
- type TreeTransformer<T = any, R = any> = (params: {
1288
- node: TreeNode<T>;
1289
- depth: number;
1290
- path: readonly TreeNode<T>[];
1291
- index: number;
1292
- }) => R;
2217
+ declare function removeLeadingSlash(url: string): string;
1293
2218
  /**
1294
- * 树节点访问函数类型
2219
+ * 确保路径以斜杠开头
1295
2220
  *
1296
- * @template T 节点数据类型
1297
- * @param params 包含节点信息的对象参数
1298
- * @param params.node 当前节点
1299
- * @param params.depth 节点深度(从0开始)
1300
- * @param params.path 从根节点到当前节点的路径数组
1301
- * @param params.index 节点在同级节点中的索引
1302
- * @returns 返回false可以终止遍历或跳过子节点
2221
+ * @category URL
2222
+ * @param path 路径字符串
2223
+ * @returns 带开头斜杠的路径
2224
+ * @example
2225
+ * ```ts
2226
+ * ensureLeadingSlash('path/to/page') // '/path/to/page'
2227
+ * ensureLeadingSlash('/path') // '/path'
2228
+ * ```
1303
2229
  */
1304
- type TreeVisitor<T = any> = (params: {
1305
- node: TreeNode<T>;
1306
- depth: number;
1307
- path: readonly TreeNode<T>[];
1308
- index: number;
1309
- }) => void | boolean;
2230
+ declare function ensureLeadingSlash(path: string): string;
1310
2231
  /**
1311
- * 树形数据结构操作工具类
1312
- *
1313
- * 提供了一系列操作树形数据的静态方法,包括:
1314
- * - 查找:find, findAll, findById
1315
- * - 转换:fromList, toList, transform
1316
- * - 过滤:filter
1317
- * - 遍历:forEach
1318
- * - 统计:estimateSize, getStats
1319
- * - 修改:insertBefore, insertAfter, remove
1320
- * - 验证:validate
1321
- *
1322
- * 所有使用谓词函数或访问函数的方法都采用对象解构参数格式:
1323
- * `({ node, depth, path, index }) => boolean`
2232
+ * 构建完整 URL
1324
2233
  *
2234
+ * @category URL
2235
+ * @param base 基础 URL
2236
+ * @param path 路径部分
2237
+ * @param query 查询参数
2238
+ * @param hash 哈希部分
2239
+ * @returns 完整 URL
1325
2240
  * @example
1326
2241
  * ```ts
1327
- * const tree = [
1328
- * {
1329
- * id: '1',
1330
- * name: '根节点',
1331
- * children: [
1332
- * { id: '2', name: '子节点1', children: [] },
1333
- * { id: '3', name: '子节点2', children: [] }
1334
- * ]
1335
- * }
1336
- * ]
1337
- *
1338
- * // 查找节点
1339
- * const node = Tree.find(tree, ({ node }) => node.name === '子节点1')
2242
+ * buildUrl('https://example.com', '/api/users', { page: 1, limit: 10 })
2243
+ * // 'https://example.com/api/users?page=1&limit=10'
1340
2244
  *
1341
- * // 遍历所有节点
1342
- * Tree.forEach(tree, ({ node, depth }) => {
1343
- * console.log(`${' '.repeat(depth)}${node.name}`)
1344
- * })
2245
+ * buildUrl('https://example.com', '/page', null, 'section')
2246
+ * // 'https://example.com/page#section'
1345
2247
  *
1346
- * // 转换节点结构
1347
- * const transformed = Tree.transform(tree, ({ node, depth }) => ({
1348
- * key: node.id,
1349
- * title: node.name,
1350
- * level: depth
1351
- * }))
2248
+ * buildUrl('https://example.com', '/api', { ids: [1, 2, 3] })
2249
+ * // 'https://example.com/api?ids=1&ids=2&ids=3'
1352
2250
  * ```
1353
2251
  */
1354
- declare class Tree {
1355
- private static dfsGenerator;
1356
- private static bfsGenerator;
1357
- private static selectStrategy;
1358
- /**
1359
- * 从扁平数组创建树形结构
1360
- *
1361
- * @category Data Structures
1362
- * @param list 扁平数组数据
1363
- * @param config 树形配置选项
1364
- * @returns 树形结构数组
1365
- * @example
1366
- * ```ts
1367
- * const flatData = [
1368
- * { id: '1', name: '部门1', parentId: null },
1369
- * { id: '2', name: '部门1-1', parentId: '1' },
1370
- * { id: '3', name: '部门1-2', parentId: '1' },
1371
- * { id: '4', name: '部门1-1-1', parentId: '2' }
1372
- * ]
1373
- *
1374
- * const tree = Tree.fromList(flatData, {
1375
- * id: 'id',
1376
- * pid: 'parentId',
1377
- * children: 'children'
1378
- * })
1379
- *
1380
- * console.log(tree) // 转换为树形结构
1381
- * ```
1382
- */
1383
- static fromList<T = any>(list: T[], config?: TreeConfigInput): TreeNode<T>[];
1384
- /**
1385
- * 将树形结构转换为扁平数组
1386
- *
1387
- * @category Data Structures
1388
- * @param tree 树形结构(单个节点或节点数组)
1389
- * @param config 树形配置选项
1390
- * @returns 扁平数组
1391
- * @example
1392
- * ```ts
1393
- * const tree = [
1394
- * {
1395
- * id: '1',
1396
- * name: '根节点',
1397
- * children: [
1398
- * { id: '2', name: '子节点1', children: [] },
1399
- * { id: '3', name: '子节点2', children: [] }
1400
- * ]
1401
- * }
1402
- * ]
1403
- *
1404
- * const flatList = Tree.toList(tree)
1405
- * console.log(flatList) // [{ id: '1', name: '根节点' }, { id: '2', name: '子节点1' }, ...]
1406
- * ```
1407
- */
1408
- static toList<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): T[];
1409
- /**
1410
- * 估算树形结构的节点数量
1411
- *
1412
- * @category Data Structures
1413
- * @param tree 树形结构(单个节点或节点数组)
1414
- * @param config 树形配置选项
1415
- * @returns 节点总数量
1416
- * @example
1417
- * ```ts
1418
- * const tree = [
1419
- * {
1420
- * id: '1',
1421
- * name: '根节点',
1422
- * children: [
1423
- * { id: '2', name: '子节点1', children: [] },
1424
- * { id: '3', name: '子节点2', children: [] }
1425
- * ]
1426
- * }
1427
- * ]
1428
- *
1429
- * const size = Tree.estimateSize(tree)
1430
- * console.log(size) // 3
1431
- * ```
1432
- */
1433
- static estimateSize<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): number;
1434
- /**
1435
- * 查找树中第一个满足条件的节点
1436
- *
1437
- * @category Data Structures
1438
- * @param tree 树形结构(单个节点或节点数组)
1439
- * @param predicate 查找条件函数
1440
- * @param config 树形配置选项
1441
- * @returns 匹配的节点;未找到时返回undefined
1442
- * @example
1443
- * ```ts
1444
- * const tree = [
1445
- * {
1446
- * id: '1',
1447
- * name: '部门1',
1448
- * children: [
1449
- * { id: '2', name: '部门1-1', children: [] }
1450
- * ]
1451
- * }
1452
- * ]
1453
- *
1454
- * const result = Tree.find(tree, ({ node }) => node.name === '部门1-1')
1455
- * console.log(result?.id) // '2'
1456
- * ```
1457
- */
1458
- static find<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T> | undefined;
1459
- /**
1460
- * 查找树中所有满足条件的节点
1461
- *
1462
- * @category Data Structures
1463
- * @param tree 树形结构(单个节点或节点数组)
1464
- * @param predicate 查找条件函数
1465
- * @param config 树形配置选项
1466
- * @returns 所有匹配的节点数组
1467
- * @example
1468
- * ```ts
1469
- * const tree = [
1470
- * {
1471
- * id: '1',
1472
- * type: 'folder',
1473
- * name: '根目录',
1474
- * children: [
1475
- * { id: '2', type: 'file', name: '文件1', children: [] },
1476
- * { id: '3', type: 'file', name: '文件2', children: [] }
1477
- * ]
1478
- * }
1479
- * ]
1480
- *
1481
- * const files = Tree.findAll(tree, ({ node }) => node.type === 'file')
1482
- * console.log(files.length) // 2
1483
- * ```
1484
- */
1485
- static findAll<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
1486
- /**
1487
- * 根据ID查找树中的节点
1488
- *
1489
- * @category Data Structures
1490
- * @param tree 树形结构(单个节点或节点数组)
1491
- * @param id 要查找的节点ID
1492
- * @param config 树形配置选项
1493
- * @returns 匹配的节点;未找到时返回undefined
1494
- * @example
1495
- * ```ts
1496
- * const tree = [
1497
- * {
1498
- * id: '1',
1499
- * name: '根节点',
1500
- * children: [
1501
- * { id: '2', name: '子节点', children: [] }
1502
- * ]
1503
- * }
1504
- * ]
1505
- *
1506
- * const result = Tree.findById(tree, '2')
1507
- * console.log(result?.name) // '子节点'
1508
- * ```
1509
- */
1510
- static findById<T = any>(tree: TreeNode<T> | TreeNode<T>[], id: string, config?: TreeConfigInput): TreeNode<T> | undefined;
1511
- /**
1512
- * 获取树形结构的统计信息
1513
- *
1514
- * @category Data Structures
1515
- * @param tree 树形结构(单个节点或节点数组)
1516
- * @param config 树形配置选项
1517
- * @returns 树的统计信息,包含总节点数、叶子节点数、最大深度和分支节点数
1518
- * @example
1519
- * ```ts
1520
- * const tree = [
1521
- * {
1522
- * id: '1',
1523
- * name: '根节点',
1524
- * children: [
1525
- * { id: '2', name: '子节点1', children: [] },
1526
- * { id: '3', name: '子节点2', children: [
1527
- * { id: '4', name: '孙节点', children: [] }
1528
- * ] }
1529
- * ]
1530
- * }
1531
- * ]
1532
- *
1533
- * const stats = Tree.getStats(tree)
1534
- * console.log(stats) // { total: 4, leaves: 2, depth: 3, branches: 2 }
1535
- * ```
1536
- */
1537
- static getStats<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): TreeStats;
1538
- /**
1539
- * 过滤树形结构,保留满足条件的节点及其祖先和后代
1540
- *
1541
- * @category Data Structures
1542
- * @param tree 树形结构(单个节点或节点数组)
1543
- * @param predicate 过滤条件函数,接收对象参数 {node, depth, path, index}
1544
- * @param config 树形配置选项
1545
- * @returns 过滤后的树形结构数组
1546
- * @example
1547
- * ```ts
1548
- * const tree = [
1549
- * {
1550
- * id: '1',
1551
- * type: 'folder',
1552
- * name: '根目录',
1553
- * children: [
1554
- * { id: '2', type: 'file', name: '文档.txt', children: [] },
1555
- * { id: '3', type: 'folder', name: '子目录', children: [
1556
- * { id: '4', type: 'file', name: '图片.jpg', children: [] }
1557
- * ] }
1558
- * ]
1559
- * }
1560
- * ]
1561
- *
1562
- * const filtered = Tree.filter(tree, ({ node }) => node.type === 'file')
1563
- * // 返回包含所有文件节点及其父级路径的树结构
1564
- * ```
1565
- */
1566
- static filter<T = any>(tree: TreeNode<T> | TreeNode<T>[], predicate: TreePredicate<T>, config?: TreeConfigInput): TreeNode<T>[];
1567
- /**
1568
- * 转换树形结构,将每个节点转换为新的结构
1569
- *
1570
- * @category Data Structures
1571
- * @param tree 树形结构(单个节点或节点数组)
1572
- * @param transformer 节点转换函数,接收对象参数 {node, depth, path, index}
1573
- * @param config 树形配置选项
1574
- * @returns 转换后的树形结构数组
1575
- * @example
1576
- * ```ts
1577
- * const tree = [
1578
- * {
1579
- * id: '1',
1580
- * name: '部门1',
1581
- * children: [
1582
- * { id: '2', name: '部门1-1', children: [] }
1583
- * ]
1584
- * }
1585
- * ]
1586
- *
1587
- * const transformed = Tree.transform(tree, ({ node, depth }) => ({
1588
- * key: node.id,
1589
- * title: node.name,
1590
- * level: depth
1591
- * }))
1592
- * // 转换为新的数据结构
1593
- * ```
1594
- */
1595
- static transform<T = any, R = any>(tree: TreeNode<T> | TreeNode<T>[], transformer: TreeTransformer<T, R>, config?: TreeConfigInput): TreeNode<R>[];
1596
- /**
1597
- * 遍历树形结构的每个节点
1598
- *
1599
- * @category Data Structures
1600
- * @param tree 树形结构(单个节点或节点数组)
1601
- * @param visitor 访问者函数,接收对象参数 {node, depth, path, index},返回false可以跳过子节点的遍历
1602
- * @param config 树形配置选项
1603
- * @example
1604
- * ```ts
1605
- * const tree = [
1606
- * {
1607
- * id: '1',
1608
- * name: '根节点',
1609
- * children: [
1610
- * { id: '2', name: '子节点', children: [] }
1611
- * ]
1612
- * }
1613
- * ]
1614
- *
1615
- * Tree.forEach(tree, ({ node, depth }) => {
1616
- * console.log(`${' '.repeat(depth * 2)}${node.name}`)
1617
- * // 输出缩进的树结构
1618
- * })
1619
- * ```
1620
- */
1621
- static forEach<T = any>(tree: TreeNode<T> | TreeNode<T>[], visitor: TreeVisitor<T>, config?: TreeConfigInput): void;
1622
- /**
1623
- * 在指定节点前插入新节点
1624
- *
1625
- * @category Data Structures
1626
- * @param tree 树形结构数组
1627
- * @param targetId 目标节点的ID
1628
- * @param newNode 要插入的新节点数据
1629
- * @param config 树形配置选项
1630
- * @returns 是否成功插入
1631
- * @example
1632
- * ```ts
1633
- * const tree = [
1634
- * {
1635
- * id: '1',
1636
- * name: '节点1',
1637
- * children: [
1638
- * { id: '2', name: '节点2', children: [] }
1639
- * ]
1640
- * }
1641
- * ]
1642
- *
1643
- * const success = Tree.insertBefore(tree, '2', { id: '1.5', name: '新节点' })
1644
- * console.log(success) // true
1645
- * ```
1646
- */
1647
- static insertBefore<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
1648
- /**
1649
- * 在指定节点后插入新节点
1650
- *
1651
- * @category Data Structures
1652
- * @param tree 树形结构数组
1653
- * @param targetId 目标节点的ID
1654
- * @param newNode 要插入的新节点数据
1655
- * @param config 树形配置选项
1656
- * @returns 是否成功插入
1657
- * @example
1658
- * ```ts
1659
- * const tree = [
1660
- * {
1661
- * id: '1',
1662
- * name: '节点1',
1663
- * children: [
1664
- * { id: '2', name: '节点2', children: [] }
1665
- * ]
1666
- * }
1667
- * ]
1668
- *
1669
- * const success = Tree.insertAfter(tree, '2', { id: '3', name: '新节点' })
1670
- * console.log(success) // true
1671
- * ```
1672
- */
1673
- static insertAfter<T = any>(tree: TreeNode<T>[], targetId: string, newNode: T, config?: TreeConfigInput): boolean;
1674
- /**
1675
- * 从树中删除指定节点
1676
- *
1677
- * @category Data Structures
1678
- * @param tree 树形结构数组
1679
- * @param targetId 要删除的节点ID
1680
- * @param config 树形配置选项
1681
- * @returns 被删除的节点,未找到时返回undefined
1682
- * @example
1683
- * ```ts
1684
- * const tree = [
1685
- * {
1686
- * id: '1',
1687
- * name: '根节点',
1688
- * children: [
1689
- * { id: '2', name: '子节点', children: [] }
1690
- * ]
1691
- * }
1692
- * ]
1693
- *
1694
- * const removed = Tree.remove(tree, '2')
1695
- * console.log(removed?.name) // '子节点'
1696
- * ```
1697
- */
1698
- static remove<T = any>(tree: TreeNode<T>[], targetId: string, config?: TreeConfigInput): TreeNode<T> | undefined;
1699
- /**
1700
- * 验证树形结构的有效性
1701
- *
1702
- * @category Data Structures
1703
- * @param tree 树形结构(单个节点或节点数组)
1704
- * @param config 树形配置选项
1705
- * @returns 验证结果,包含是否有效和错误信息数组
1706
- * @example
1707
- * ```ts
1708
- * const tree = [
1709
- * {
1710
- * id: '1',
1711
- * name: '根节点',
1712
- * children: [
1713
- * { id: '2', name: '子节点', children: [] }
1714
- * ]
1715
- * }
1716
- * ]
1717
- *
1718
- * const result = Tree.validate(tree)
1719
- * console.log(result.isValid) // true
1720
- * console.log(result.errors) // []
1721
- * ```
1722
- */
1723
- static validate<T = any>(tree: TreeNode<T> | TreeNode<T>[], config?: TreeConfigInput): {
1724
- isValid: boolean;
1725
- errors: string[];
1726
- };
1727
- }
1728
-
2252
+ declare function buildUrl(base: string, path?: string, query?: QueryParams | null, hash?: string): string;
1729
2253
  /**
1730
- * 生成字符串的简单哈希值
2254
+ * 解码 URL 组件(安全版本,失败返回原字符串)
1731
2255
  *
1732
- * @category Utilities
1733
- * @param str 待哈希的字符串
1734
- * @returns 32位哈希值转换为36进制字符串
2256
+ * @category URL
2257
+ * @param str 要解码的字符串
2258
+ * @returns 解码后的字符串
1735
2259
  * @example
1736
2260
  * ```ts
1737
- * const hash1 = simpleHash('hello world')
1738
- * console.log(hash1) // 'nf5xd4'
2261
+ * safeDecodeURIComponent('%E4%B8%AD%E6%96%87') // '中文'
2262
+ * safeDecodeURIComponent('hello%20world') // 'hello world'
2263
+ * safeDecodeURIComponent('%invalid%') // '%invalid%'
2264
+ * ```
2265
+ */
2266
+ declare function safeDecodeURIComponent(str: string): string;
2267
+ /**
2268
+ * 编码 URL 组件(安全版本)
1739
2269
  *
1740
- * const hash2 = simpleHash('hello world')
1741
- * console.log(hash1 === hash2) // true,相同字符串产生相同哈希
2270
+ * @category URL
2271
+ * @param str 要编码的字符串
2272
+ * @returns 编码后的字符串
2273
+ * @example
2274
+ * ```ts
2275
+ * safeEncodeURIComponent('中文') // '%E4%B8%AD%E6%96%87'
2276
+ * safeEncodeURIComponent('hello world') // 'hello%20world'
2277
+ * ```
2278
+ */
2279
+ declare function safeEncodeURIComponent(str: string): string;
2280
+ /**
2281
+ * 检查两个 URL 是否同源
1742
2282
  *
1743
- * const hash3 = simpleHash('hello world!')
1744
- * console.log(hash1 === hash3) // false,不同字符串产生不同哈希
2283
+ * @category URL
2284
+ * @param url1 第一个 URL
2285
+ * @param url2 第二个 URL
2286
+ * @returns 是否同源
2287
+ * @example
2288
+ * ```ts
2289
+ * isSameOrigin('https://example.com/a', 'https://example.com/b') // true
2290
+ * isSameOrigin('https://example.com', 'https://sub.example.com') // false
2291
+ * isSameOrigin('https://example.com', 'http://example.com') // false
1745
2292
  * ```
1746
2293
  */
1747
- declare function simpleHash(str: string): string;
1748
-
2294
+ declare function isSameOrigin(url1: string, url2: string): boolean;
1749
2295
  /**
1750
- * 生成随机UUID字符串
2296
+ * 将相对 URL 转换为绝对 URL
1751
2297
  *
1752
- * @category Utilities
1753
- * @returns 符合UUID v4格式的随机字符串
2298
+ * @category URL
2299
+ * @param relativeUrl 相对 URL
2300
+ * @param baseUrl 基础 URL
2301
+ * @returns 绝对 URL,转换失败返回原字符串
1754
2302
  * @example
1755
2303
  * ```ts
1756
- * const id1 = getRandomUUID()
1757
- * console.log(id1) // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
2304
+ * toAbsoluteUrl('/path', 'https://example.com')
2305
+ * // 'https://example.com/path'
1758
2306
  *
1759
- * const id2 = getRandomUUID()
1760
- * console.log(id2) // 'f47ac10b-58cc-4372-a567-0e02b2c3d480'
2307
+ * toAbsoluteUrl('../other', 'https://example.com/api/users')
2308
+ * // 'https://example.com/api/other'
1761
2309
  *
1762
- * // 用于生成唯一标识符
1763
- * const componentId = `component-${getRandomUUID()}`
2310
+ * toAbsoluteUrl('https://other.com', 'https://example.com')
2311
+ * // 'https://other.com' (已是绝对URL,不变)
1764
2312
  * ```
1765
2313
  */
1766
- declare function getRandomUUID(): string;
1767
-
2314
+ declare function toAbsoluteUrl(relativeUrl: string, baseUrl: string): string;
1768
2315
  /**
1769
- * 检查值是否为对象类型
2316
+ * 获取两个 URL 之间的相对路径
1770
2317
  *
1771
- * @category Validator
1772
- * @param value 待检查的值
1773
- * @returns 是否为对象类型
2318
+ * @category URL
2319
+ * @param from 起始 URL
2320
+ * @param to 目标 URL
2321
+ * @returns 相对路径
1774
2322
  * @example
1775
2323
  * ```ts
1776
- * console.log(isObject({})) // true
1777
- * console.log(isObject({ name: 'John' })) // true
1778
- * console.log(isObject([])) // false
1779
- * console.log(isObject(null)) // false
1780
- * console.log(isObject('string')) // false
2324
+ * getRelativePath('https://example.com/a/b', 'https://example.com/a/c')
2325
+ * // '../c'
2326
+ *
2327
+ * getRelativePath('https://example.com/a', 'https://example.com/a/b/c')
2328
+ * // 'b/c'
1781
2329
  * ```
1782
2330
  */
1783
- declare function isObject(value: any): value is AnyObject;
2331
+ declare function getRelativePath(from: string, to: string): string;
2332
+
1784
2333
  /**
1785
2334
  * 检查值是否为数组类型
1786
2335
  *
1787
- * @category Validator
2336
+ * @category Validators
1788
2337
  * @param value 待检查的值
1789
2338
  * @returns 是否为数组类型
1790
2339
  * @example
@@ -1796,40 +2345,31 @@ declare function isObject(value: any): value is AnyObject;
1796
2345
  * ```
1797
2346
  */
1798
2347
  declare function isArray(value: any): value is any[];
2348
+
1799
2349
  /**
1800
- * 检查值是否为字符串类型
1801
- *
1802
- * @category Validator
1803
- * @param value 待检查的值
1804
- * @returns 是否为字符串类型
1805
- * @example
1806
- * ```ts
1807
- * console.log(isString('hello')) // true
1808
- * console.log(isString('')) // true
1809
- * console.log(isString(123)) // false
1810
- * console.log(isString(null)) // false
1811
- * ```
1812
- */
1813
- declare function isString(value: any): value is string;
1814
- /**
1815
- * 检查值是否为有效数字类型
2350
+ * 检查值是否为空(null、undefined、空字符串、空数组、空对象)
1816
2351
  *
1817
- * @category Validator
2352
+ * @category Validators
1818
2353
  * @param value 待检查的值
1819
- * @returns 是否为有效数字类型
2354
+ * @returns 是否为空值
1820
2355
  * @example
1821
2356
  * ```ts
1822
- * console.log(isNumber(123)) // true
1823
- * console.log(isNumber(0)) // true
1824
- * console.log(isNumber(NaN)) // false
1825
- * console.log(isNumber('123')) // false
2357
+ * console.log(isEmpty(null)) // true
2358
+ * console.log(isEmpty(undefined)) // true
2359
+ * console.log(isEmpty('')) // true
2360
+ * console.log(isEmpty([])) // true
2361
+ * console.log(isEmpty({})) // true
2362
+ * console.log(isEmpty([1, 2])) // false
2363
+ * console.log(isEmpty({ name: 'John' })) // false
2364
+ * console.log(isEmpty('hello')) // false
1826
2365
  * ```
1827
2366
  */
1828
- declare function isNumber(value: any): value is number;
2367
+ declare function isEmpty(value: any): boolean;
2368
+
1829
2369
  /**
1830
2370
  * 检查值是否为函数类型
1831
2371
  *
1832
- * @category Validator
2372
+ * @category Validators
1833
2373
  * @param value 待检查的值
1834
2374
  * @returns 是否为函数类型
1835
2375
  * @example
@@ -1841,29 +2381,44 @@ declare function isNumber(value: any): value is number;
1841
2381
  * ```
1842
2382
  */
1843
2383
  declare function isFunction(value: any): value is (...args: any[]) => any;
2384
+
1844
2385
  /**
1845
- * 检查值是否为空(null、undefined、空字符串、空数组、空对象)
2386
+ * 检查值是否为有效数字类型
1846
2387
  *
1847
- * @category Validator
2388
+ * @category Validators
1848
2389
  * @param value 待检查的值
1849
- * @returns 是否为空值
2390
+ * @returns 是否为有效数字类型
1850
2391
  * @example
1851
2392
  * ```ts
1852
- * console.log(isEmpty(null)) // true
1853
- * console.log(isEmpty(undefined)) // true
1854
- * console.log(isEmpty('')) // true
1855
- * console.log(isEmpty([])) // true
1856
- * console.log(isEmpty({})) // true
1857
- * console.log(isEmpty([1, 2])) // false
1858
- * console.log(isEmpty({ name: 'John' })) // false
1859
- * console.log(isEmpty('hello')) // false
2393
+ * console.log(isNumber(123)) // true
2394
+ * console.log(isNumber(0)) // true
2395
+ * console.log(isNumber(NaN)) // false
2396
+ * console.log(isNumber('123')) // false
1860
2397
  * ```
1861
2398
  */
1862
- declare function isEmpty(value: any): boolean;
2399
+ declare function isNumber(value: any): value is number;
2400
+
2401
+ /**
2402
+ * 检查值是否为对象类型
2403
+ *
2404
+ * @category Validators
2405
+ * @param value 待检查的值
2406
+ * @returns 是否为对象类型
2407
+ * @example
2408
+ * ```ts
2409
+ * console.log(isObject({})) // true
2410
+ * console.log(isObject({ name: 'John' })) // true
2411
+ * console.log(isObject([])) // false
2412
+ * console.log(isObject(null)) // false
2413
+ * console.log(isObject('string')) // false
2414
+ * ```
2415
+ */
2416
+ declare function isObject(value: any): value is AnyObject;
2417
+
1863
2418
  /**
1864
2419
  * 判断值是否为纯对象(不包括数组、函数、日期等)
1865
2420
  *
1866
- * @category Object
2421
+ * @category Validators
1867
2422
  * @param value 要检查的值
1868
2423
  * @returns 是否为纯对象
1869
2424
  * @example
@@ -1876,5 +2431,44 @@ declare function isEmpty(value: any): boolean;
1876
2431
  */
1877
2432
  declare function isPlainObject(value: unknown): value is Record<string, any>;
1878
2433
 
1879
- export { StorageTypeSchema, Tree, camelCase, camelToKebab, capitalize, chunk, convertSvgToPng, convertToKebabCase, createStorageConfigSchema, debounce, deepClone, extractFilename, flatten, formatFileSize, getPath, getRandomUUID, isArray, isEmpty, isFunction, isNumber, isObject, isPlainObject, isString, isValidContainer, joinPath, kebabCase, kebabToCamel, lowerCase, lowerFirst, omit, omitUndefined, pascalCase, pick, replaceCurrentColor, separate, separateMany, setPath, simpleHash, sleep, sleepWithCancel, snakeCase, startCase, throttle, toPath, triggerDownload, unique, upperCase, upperFirst, useAppStorage, useCopyCode, words };
1880
- export type { AnyObject, ApiAwaitable, ApiAwaitedReturn, ApiUnwrapPromise, AppStorageReturn, ArrayFieldKeys, ComponentAttrs, ComponentEmit, ComponentExposed, ComponentProps, ComponentSlots, ComponentType, DeepPartial, FirstParam, FirstParameter, GetFieldValue, GetObjectField, IsComponent, IsPlainObject, Merge, MutableByKeys, NestedKeys, NonObjectFieldKeys, ObjectFieldKeys, OmitByKey, PartialByKeys, PickByKey, ReactiveValue, ReadonlyByKeys, RenameKeys, RequiredByKeys, StorageConfig, StorageConfigInput, StorageType, StringOrVNode, StripNullable, Suggest, UnionToIntersection, UnknownObject };
2434
+ /**
2435
+ * 检查值是否为字符串类型
2436
+ *
2437
+ * @category Validators
2438
+ * @param value 待检查的值
2439
+ * @returns 是否为字符串类型
2440
+ * @example
2441
+ * ```ts
2442
+ * console.log(isString('hello')) // true
2443
+ * console.log(isString('')) // true
2444
+ * console.log(isString(123)) // false
2445
+ * console.log(isString(null)) // false
2446
+ * ```
2447
+ */
2448
+ declare function isString(value: any): value is string;
2449
+
2450
+ /**
2451
+ * 检查值是否为有效的容器类型(对象或数组)
2452
+ *
2453
+ * - isObject: 仅检查纯对象,排除数组
2454
+ * - isValidContainer: 检查所有可作为容器的类型(对象 + 数组)
2455
+ *
2456
+ * 支持 Vue 3 的 Proxy 对象和 Proxy 数组。
2457
+ *
2458
+ * @category Validators
2459
+ * @param value - 待检查的值
2460
+ * @returns 是否为有效容器(对象或数组)
2461
+ * @example
2462
+ * ```ts
2463
+ * isValidContainer({}) // true
2464
+ * isValidContainer([]) // true
2465
+ * isValidContainer(new Proxy({}, {})) // true
2466
+ * isValidContainer(null) // false
2467
+ * isValidContainer('string') // false
2468
+ * isValidContainer(123) // false
2469
+ * ```
2470
+ */
2471
+ declare function isValidContainer(value: any): boolean;
2472
+
2473
+ export { Tree, appendQueryParam, buildUrl, camelCase, capitalize, chunk, convertSvgToPng, convertToKebabCase, debounce, deepClone, ensureLeadingSlash, ensureTrailingSlash, extractFilename, flatten, formatFileSize, getDomain, getPath, getQueryParam, getQueryParams, getRandomUUID, getRelativePath, getRootDomain, getUrlExtension, getUrlFilename, hasQueryParam, isAbsoluteUrl, isArray, isEmpty, isFunction, isNumber, isObject, isPlainObject, isRelativeUrl, isSameOrigin, isString, isValidContainer, isValidUrl, joinPath, joinUrl, kebabCase, lowerCase, lowerFirst, normalizeUrl, omit, omitUndefined, parseQuery, parseUrl, pascalCase, pick, removeLeadingSlash, removeQueryParam, removeTrailingSlash, replaceCurrentColor, safeDecodeURIComponent, safeEncodeURIComponent, separate, separateMany, setPath, setQueryParam, setQueryParams, simpleHash, sleep, sleepWithCancel, snakeCase, startCase, stringifyQuery, throttle, toAbsoluteUrl, toPath, triggerDownload, unique, upperCase, upperFirst, useAppStorage, useCopyCode, words };
2474
+ export type { AnyObject, ApiAwaitable, ApiAwaitedReturn, ApiUnwrapPromise, AppStorageReturn, ArrayFieldKeys, ComponentAttrs, ComponentEmit, ComponentExposed, ComponentProps, ComponentSlots, ComponentType, DeepPartial, FirstParam, FirstParameter, GetFieldValue, GetObjectField, IsComponent, IsPlainObject, Merge, MutableByKeys, NestedKeys, NonObjectFieldKeys, ObjectFieldKeys, OmitByKey, ParsedUrl, PartialByKeys, PathInput, PathSegment, PathSegments, PickByKey, QueryParamValue, QueryParams, ReactiveValue, ReadonlyByKeys, RenameKeys, RequiredByKeys, StorageConfig, StorageConfigInput, StorageType, StringOrVNode, StripNullable, Suggest, TreeConfig, TreeConfigInput, TreeNode, TreePredicate, TreeStats, TreeTransformer, TreeVisitor, UnionToIntersection, UnknownObject };