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