befly-shared 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/arrayToTree.d.ts +2 -5
- package/dist/types/arrayToTree.js +0 -3
- package/dist/types/cleanParams.d.ts +1 -0
- package/dist/types/cleanParams.js +1 -0
- package/dist/types/fieldClear.d.ts +11 -0
- package/dist/types/fieldClear.js +1 -0
- package/dist/types/hashPassword.d.ts +0 -3
- package/dist/types/hashPassword.js +0 -3
- package/dist/types/withDefaultColumns.d.ts +2 -4
- package/dist/types/withDefaultColumns.js +0 -3
- package/dist/utils/arrayToTree.d.ts +3 -3
- package/dist/utils/arrayToTree.js +26 -15
- package/dist/utils/cleanParams.d.ts +1 -0
- package/dist/utils/cleanParams.js +29 -0
- package/dist/utils/fieldClear.d.ts +4 -4
- package/dist/utils/fieldClear.js +1 -3
- package/dist/utils/hashPassword.d.ts +5 -8
- package/dist/utils/hashPassword.js +5 -11
- package/dist/utils/scanViewsDir.js +9 -2
- package/dist/utils/withDefaultColumns.d.ts +3 -6
- package/dist/utils/withDefaultColumns.js +4 -7
- package/package.json +3 -3
- package/dist/types/deepTransformKeys.d.ts +0 -12
- package/dist/types/deepTransformKeys.js +0 -6
- package/dist/types/normalizePathnameListInput.d.ts +0 -4
- package/dist/types/normalizePathnameListInput.js +0 -4
- package/dist/utils/deepTransformKeys.d.ts +0 -84
- package/dist/utils/deepTransformKeys.js +0 -139
- package/dist/utils/normalizePathnameListInput.d.ts +0 -16
- package/dist/utils/normalizePathnameListInput.js +0 -48
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* arrayToTree 类型定义(类型模块,仅供 type 引用)。
|
|
3
|
-
*/
|
|
4
|
-
export type ArrayToTreeResult<T extends Record<string, any>> = {
|
|
1
|
+
export type ArrayToTreeResult<T extends Record<string, unknown>> = {
|
|
5
2
|
flat: Array<T>;
|
|
6
3
|
tree: Array<T>;
|
|
7
4
|
map: Map<string, T>;
|
|
8
5
|
};
|
|
9
|
-
export type ArrayToTree = <T extends Record<string,
|
|
6
|
+
export type ArrayToTree = <T extends Record<string, unknown>>(items: Array<T>, id?: string, pid?: string, children?: string, sort?: string) => ArrayToTreeResult<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type CleanParams = <TData extends Record<string, unknown>>(data: TData, dropValues?: readonly unknown[], dropKeyValue?: Record<string, readonly unknown[]>) => Partial<TData>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface FieldClearOptions {
|
|
2
|
+
pickKeys?: string[];
|
|
3
|
+
omitKeys?: string[];
|
|
4
|
+
keepValues?: unknown[];
|
|
5
|
+
excludeValues?: unknown[];
|
|
6
|
+
keepMap?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export type FieldClearResult<T> = T extends Array<infer U> ? Array<FieldClearResult<U>> : T extends object ? {
|
|
9
|
+
[K in keyof T]?: T[K];
|
|
10
|
+
} : T;
|
|
11
|
+
export type FieldClear = <T = unknown>(data: T | T[], options?: FieldClearOptions) => FieldClearResult<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
export type WithDefaultColumns = (columns: any[], customConfig?: Record<string, any>) => any[];
|
|
1
|
+
export type TableColumnLike = Record<string, unknown>;
|
|
2
|
+
export type WithDefaultColumns = (columns: Array<TableColumnLike>, customConfig?: Record<string, Record<string, unknown>>) => Array<Record<string, unknown>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ArrayToTreeResult<T extends Record<string,
|
|
1
|
+
export type ArrayToTreeResult<T extends Record<string, unknown>> = {
|
|
2
2
|
flat: Array<T>;
|
|
3
3
|
tree: Array<T>;
|
|
4
4
|
map: Map<string, T>;
|
|
@@ -9,6 +9,6 @@ export type ArrayToTreeResult<T extends Record<string, any>> = {
|
|
|
9
9
|
* - 默认字段:id / pid / children / sort
|
|
10
10
|
* - pid 为空字符串或父节点不存在时,视为根节点
|
|
11
11
|
* - 内部会 clone 一份节点对象,并写入 children: []
|
|
12
|
-
* - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序
|
|
12
|
+
* - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序
|
|
13
13
|
*/
|
|
14
|
-
export declare function arrayToTree<T extends Record<string,
|
|
14
|
+
export declare function arrayToTree<T extends Record<string, unknown>>(items: Array<T>, id?: string, pid?: string, children?: string, sort?: string): ArrayToTreeResult<T>;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - 默认字段:id / pid / children / sort
|
|
5
5
|
* - pid 为空字符串或父节点不存在时,视为根节点
|
|
6
6
|
* - 内部会 clone 一份节点对象,并写入 children: []
|
|
7
|
-
* - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序
|
|
7
|
+
* - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序
|
|
8
8
|
*/
|
|
9
9
|
export function arrayToTree(items, id = "id", pid = "pid", children = "children", sort = "sort") {
|
|
10
10
|
const idKey = typeof id === "string" && id.length > 0 ? id : "id";
|
|
@@ -24,14 +24,16 @@ export function arrayToTree(items, id = "id", pid = "pid", children = "children"
|
|
|
24
24
|
return "";
|
|
25
25
|
};
|
|
26
26
|
for (const item of safeItems) {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
const itemObj = typeof item === "object" && item !== null ? item : null;
|
|
28
|
+
const rawId = itemObj ? itemObj[idKey] : undefined;
|
|
29
|
+
const rawPid = itemObj ? itemObj[pidKey] : undefined;
|
|
29
30
|
const normalizedId = normalizeKey(rawId);
|
|
30
31
|
const normalizedPid = normalizeKey(rawPid);
|
|
31
32
|
const nextNode = Object.assign({}, item);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const nextNodeObj = nextNode;
|
|
34
|
+
nextNodeObj[idKey] = normalizedId;
|
|
35
|
+
nextNodeObj[pidKey] = normalizedPid;
|
|
36
|
+
nextNodeObj[childrenKey] = [];
|
|
35
37
|
flat.push(nextNode);
|
|
36
38
|
if (normalizedId.length > 0) {
|
|
37
39
|
map.set(normalizedId, nextNode);
|
|
@@ -39,20 +41,26 @@ export function arrayToTree(items, id = "id", pid = "pid", children = "children"
|
|
|
39
41
|
}
|
|
40
42
|
const tree = [];
|
|
41
43
|
for (const node of flat) {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
+
const nodeObj = node;
|
|
45
|
+
const selfId = normalizeKey(nodeObj[idKey]);
|
|
46
|
+
const parentId = normalizeKey(nodeObj[pidKey]);
|
|
44
47
|
if (parentId.length > 0 && parentId !== selfId) {
|
|
45
48
|
const parent = map.get(parentId);
|
|
46
|
-
if (parent
|
|
47
|
-
parent
|
|
48
|
-
|
|
49
|
+
if (parent) {
|
|
50
|
+
const parentObj = parent;
|
|
51
|
+
const childrenValue = parentObj[childrenKey];
|
|
52
|
+
if (Array.isArray(childrenValue)) {
|
|
53
|
+
childrenValue.push(node);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
58
|
tree.push(node);
|
|
52
59
|
}
|
|
53
60
|
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
|
54
61
|
const getSortValue = (node) => {
|
|
55
|
-
const
|
|
62
|
+
const nodeObj = node;
|
|
63
|
+
const raw = nodeObj[sortKey];
|
|
56
64
|
if (typeof raw !== "number") {
|
|
57
65
|
return 999999;
|
|
58
66
|
}
|
|
@@ -70,8 +78,10 @@ export function arrayToTree(items, id = "id", pid = "pid", children = "children"
|
|
|
70
78
|
if (aSort !== bSort) {
|
|
71
79
|
return aSort - bSort;
|
|
72
80
|
}
|
|
73
|
-
const
|
|
74
|
-
const
|
|
81
|
+
const aObj = a;
|
|
82
|
+
const bObj = b;
|
|
83
|
+
const aId = aObj[idKey];
|
|
84
|
+
const bId = bObj[idKey];
|
|
75
85
|
return collator.compare(typeof aId === "string" ? aId : "", typeof bId === "string" ? bId : "");
|
|
76
86
|
};
|
|
77
87
|
const sortTreeInPlace = (nodes, seen) => {
|
|
@@ -89,7 +99,8 @@ export function arrayToTree(items, id = "id", pid = "pid", children = "children"
|
|
|
89
99
|
continue;
|
|
90
100
|
}
|
|
91
101
|
seen.add(node);
|
|
92
|
-
const
|
|
102
|
+
const nodeObj = node;
|
|
103
|
+
const childNodes = nodeObj[childrenKey];
|
|
93
104
|
if (Array.isArray(childNodes) && childNodes.length > 0) {
|
|
94
105
|
sortTreeInPlace(childNodes, seen);
|
|
95
106
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cleanParams<TData extends Record<string, unknown>>(data: TData, dropValues?: readonly unknown[], dropKeyValue?: Record<string, readonly unknown[]>): Partial<TData>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function cleanParams(data, dropValues, dropKeyValue) {
|
|
2
|
+
const globalDropValues = dropValues ?? [];
|
|
3
|
+
const perKeyDropValues = dropKeyValue ?? {};
|
|
4
|
+
const globalDropSet = new Set(globalDropValues);
|
|
5
|
+
const out = {};
|
|
6
|
+
for (const key of Object.keys(data)) {
|
|
7
|
+
const value = data[key];
|
|
8
|
+
// 默认强制移除 null / undefined
|
|
9
|
+
if (value === null || value === undefined) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
// 如果该 key 配了规则:以 key 规则为准,不再应用全局 dropValues
|
|
13
|
+
if (Object.hasOwn(perKeyDropValues, key)) {
|
|
14
|
+
const keyDropValues = perKeyDropValues[key] ?? [];
|
|
15
|
+
const keyDropSet = new Set(keyDropValues);
|
|
16
|
+
if (keyDropSet.has(value)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
out[key] = value;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
// 未配置 key 规则:应用全局 dropValues
|
|
23
|
+
if (globalDropSet.has(value)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
out[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export interface FieldClearOptions {
|
|
2
2
|
pickKeys?: string[];
|
|
3
3
|
omitKeys?: string[];
|
|
4
|
-
keepValues?:
|
|
5
|
-
excludeValues?:
|
|
6
|
-
keepMap?: Record<string,
|
|
4
|
+
keepValues?: unknown[];
|
|
5
|
+
excludeValues?: unknown[];
|
|
6
|
+
keepMap?: Record<string, unknown>;
|
|
7
7
|
}
|
|
8
8
|
export type FieldClearResult<T> = T extends Array<infer U> ? Array<FieldClearResult<U>> : T extends object ? {
|
|
9
9
|
[K in keyof T]?: T[K];
|
|
10
10
|
} : T;
|
|
11
|
-
export declare function fieldClear<T =
|
|
11
|
+
export declare function fieldClear<T = unknown>(data: T | T[], options?: FieldClearOptions): FieldClearResult<T>;
|
package/dist/utils/fieldClear.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// fieldClear 工具函数实现(shared 版)
|
|
2
|
-
// 支持 pick/omit/keepValues/excludeValues/keepMap,处理对象和数组
|
|
3
1
|
function isObject(val) {
|
|
4
2
|
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
5
3
|
}
|
|
@@ -24,7 +22,7 @@ export function fieldClear(data, options = {}) {
|
|
|
24
22
|
for (const key of keys) {
|
|
25
23
|
const value = obj[key];
|
|
26
24
|
// 1. keepMap 优先
|
|
27
|
-
if (keepMap && Object.
|
|
25
|
+
if (keepMap && Object.hasOwn(keepMap, key)) {
|
|
28
26
|
if (Object.is(keepMap[key], value)) {
|
|
29
27
|
result[key] = value;
|
|
30
28
|
continue;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* @param password - 原始密码
|
|
8
|
-
* @param salt - 盐值,默认为 befly
|
|
9
|
-
* @returns 哈希后的密码(十六进制字符串)
|
|
2
|
+
* 使用 SHA-256 对密码进行哈希(前端实现:WebCrypto)。
|
|
3
|
+
*
|
|
4
|
+
* 注意:这是前端/管理后台用途的工具函数。
|
|
5
|
+
* - 禁止在接口端/服务端使用该函数作为密码存储方案
|
|
6
|
+
* - 服务端应使用 core 侧既定密码策略(例如 Cipher.hashPassword)
|
|
10
7
|
*/
|
|
11
8
|
export declare function hashPassword(password: string, salt?: string): Promise<string>;
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* @param password - 原始密码
|
|
8
|
-
* @param salt - 盐值,默认为 befly
|
|
9
|
-
* @returns 哈希后的密码(十六进制字符串)
|
|
2
|
+
* 使用 SHA-256 对密码进行哈希(前端实现:WebCrypto)。
|
|
3
|
+
*
|
|
4
|
+
* 注意:这是前端/管理后台用途的工具函数。
|
|
5
|
+
* - 禁止在接口端/服务端使用该函数作为密码存储方案
|
|
6
|
+
* - 服务端应使用 core 侧既定密码策略(例如 Cipher.hashPassword)
|
|
10
7
|
*/
|
|
11
8
|
export async function hashPassword(password, salt = "befly") {
|
|
12
9
|
const data = password + salt;
|
|
13
|
-
// 将字符串转换为 Uint8Array
|
|
14
10
|
const encoder = new TextEncoder();
|
|
15
11
|
const dataBuffer = encoder.encode(data);
|
|
16
|
-
// 使用 Web Crypto API 进行 SHA-256 哈希
|
|
17
12
|
const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
|
|
18
|
-
// 将 ArrayBuffer 转换为十六进制字符串
|
|
19
13
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
20
14
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21
15
|
return hashHex;
|
|
@@ -106,9 +106,16 @@ export function extractDefinePageMetaFromScriptSetup(scriptSetup) {
|
|
|
106
106
|
if (!titleMatch) {
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
|
+
const title = titleMatch[2];
|
|
110
|
+
if (typeof title !== "string") {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
109
113
|
const orderMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?order\s*:\s*(\d+)/);
|
|
114
|
+
if (!orderMatch) {
|
|
115
|
+
return { title: title };
|
|
116
|
+
}
|
|
110
117
|
return {
|
|
111
|
-
title:
|
|
112
|
-
order:
|
|
118
|
+
title: title,
|
|
119
|
+
order: Number(orderMatch[1])
|
|
113
120
|
};
|
|
114
121
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
+
export type TableColumnLike = Record<string, unknown>;
|
|
1
2
|
/**
|
|
2
3
|
* 为表格列添加默认配置(纯函数)。
|
|
3
4
|
*
|
|
4
|
-
* 设计目标:
|
|
5
|
-
* - 不依赖浏览器/Node 特定 API
|
|
6
|
-
* - 不修改入参 columns 的对象引用(会返回新数组,并对每个列对象做浅拷贝合并)
|
|
7
|
-
*
|
|
8
5
|
* 默认行为:
|
|
9
6
|
* - base 默认:{ width: 200, ellipsis: true }
|
|
10
7
|
* - 特殊列:operation/state/id
|
|
11
8
|
* - colKey 以 At/At2 结尾时:默认 { align: "center" }
|
|
12
|
-
* - customConfig 可覆盖/扩展默认 specialColumnConfig
|
|
9
|
+
* - customConfig 可覆盖/扩展默认 specialColumnConfig
|
|
13
10
|
*/
|
|
14
|
-
export declare function withDefaultColumns(columns:
|
|
11
|
+
export declare function withDefaultColumns(columns: Array<TableColumnLike>, customConfig?: Record<string, Record<string, unknown>>): Array<Record<string, unknown>>;
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 为表格列添加默认配置(纯函数)。
|
|
3
3
|
*
|
|
4
|
-
* 设计目标:
|
|
5
|
-
* - 不依赖浏览器/Node 特定 API
|
|
6
|
-
* - 不修改入参 columns 的对象引用(会返回新数组,并对每个列对象做浅拷贝合并)
|
|
7
|
-
*
|
|
8
4
|
* 默认行为:
|
|
9
5
|
* - base 默认:{ width: 200, ellipsis: true }
|
|
10
6
|
* - 特殊列:operation/state/id
|
|
11
7
|
* - colKey 以 At/At2 结尾时:默认 { align: "center" }
|
|
12
|
-
* - customConfig 可覆盖/扩展默认 specialColumnConfig
|
|
8
|
+
* - customConfig 可覆盖/扩展默认 specialColumnConfig
|
|
13
9
|
*/
|
|
14
10
|
export function withDefaultColumns(columns, customConfig) {
|
|
15
11
|
const safeColumns = Array.isArray(columns) ? columns : [];
|
|
@@ -19,7 +15,8 @@ export function withDefaultColumns(columns, customConfig) {
|
|
|
19
15
|
id: { width: 200, align: "center" }
|
|
20
16
|
}, customConfig || {});
|
|
21
17
|
return safeColumns.map((col) => {
|
|
22
|
-
const
|
|
18
|
+
const colObj = typeof col === "object" && col !== null ? col : {};
|
|
19
|
+
const colKey = typeof colObj["colKey"] === "string" ? colObj["colKey"] : undefined;
|
|
23
20
|
let specialConfig = colKey ? specialColumnConfig[colKey] : undefined;
|
|
24
21
|
if (!specialConfig && typeof colKey === "string" && (colKey.endsWith("At") || colKey.endsWith("At2"))) {
|
|
25
22
|
specialConfig = { align: "center" };
|
|
@@ -32,7 +29,7 @@ export function withDefaultColumns(columns, customConfig) {
|
|
|
32
29
|
if (specialConfig) {
|
|
33
30
|
Object.assign(merged, specialConfig);
|
|
34
31
|
}
|
|
35
|
-
Object.assign(merged,
|
|
32
|
+
Object.assign(merged, colObj);
|
|
36
33
|
return merged;
|
|
37
34
|
});
|
|
38
35
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly-shared",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "1.4.2",
|
|
4
|
+
"gitHead": "540d9551c589cb33d711c593e561f0fffc0aac0c",
|
|
5
5
|
"private": false,
|
|
6
|
-
"description": "Befly
|
|
6
|
+
"description": "Befly 前端/Vite 共享工具包(仅供 admin 与 Vite 工具使用,禁止接口端/服务端导入)",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"author": "chensuiyi <bimostyle@qq.com>",
|
|
9
9
|
"files": [
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* deepTransformKeys 类型定义(类型模块,仅供 type 引用)。
|
|
3
|
-
*
|
|
4
|
-
* 注意:types 模块不包含运行时实现;运行时请从 befly-shared/utils/* 引入。
|
|
5
|
-
*/
|
|
6
|
-
export type KeyTransformer = (key: string) => string;
|
|
7
|
-
export type PresetTransform = "camel" | "snake" | "kebab" | "pascal" | "upper" | "lower";
|
|
8
|
-
export interface TransformOptions {
|
|
9
|
-
maxDepth?: number;
|
|
10
|
-
excludeKeys?: string[];
|
|
11
|
-
}
|
|
12
|
-
export type DeepTransformKeys = <T = any>(data: any, transformer: KeyTransformer | PresetTransform, options?: TransformOptions) => T;
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 键名转换函数类型
|
|
3
|
-
*/
|
|
4
|
-
export type KeyTransformer = (key: string) => string;
|
|
5
|
-
/**
|
|
6
|
-
* 预设的转换方式
|
|
7
|
-
* - camel: 小驼峰 user_id → userId
|
|
8
|
-
* - snake: 下划线 userId → user_id
|
|
9
|
-
* - kebab: 短横线 userId → user-id
|
|
10
|
-
* - pascal: 大驼峰 user_id → UserId
|
|
11
|
-
* - upper: 大写 userId → USERID
|
|
12
|
-
* - lower: 小写 UserId → userid
|
|
13
|
-
*/
|
|
14
|
-
export type PresetTransform = "camel" | "snake" | "kebab" | "pascal" | "upper" | "lower";
|
|
15
|
-
/**
|
|
16
|
-
* 转换选项
|
|
17
|
-
*/
|
|
18
|
-
export interface TransformOptions {
|
|
19
|
-
/** 最大递归深度,默认 100。设置为 0 表示不限制深度(不推荐) */
|
|
20
|
-
maxDepth?: number;
|
|
21
|
-
/** 排除的键名列表,这些键名不会被转换 */
|
|
22
|
-
excludeKeys?: string[];
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* 深度递归遍历数据结构,转换所有键名
|
|
26
|
-
* 支持嵌套对象和数组,自动防止循环引用和栈溢出
|
|
27
|
-
*
|
|
28
|
-
* @param data - 源数据(对象、数组或其他类型)
|
|
29
|
-
* @param transformer - 转换函数或预设方式
|
|
30
|
-
* @param options - 转换选项
|
|
31
|
-
* @returns 键名转换后的新数据
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // 小驼峰
|
|
35
|
-
* deepTransformKeys({ user_id: 123, user_info: { first_name: 'John' } }, 'camel')
|
|
36
|
-
* // { userId: 123, userInfo: { firstName: 'John' } }
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* // 下划线
|
|
40
|
-
* deepTransformKeys({ userId: 123, userInfo: { firstName: 'John' } }, 'snake')
|
|
41
|
-
* // { user_id: 123, user_info: { first_name: 'John' } }
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* // 短横线
|
|
45
|
-
* deepTransformKeys({ userId: 123, userInfo: { firstName: 'John' } }, 'kebab')
|
|
46
|
-
* // { 'user-id': 123, 'user-info': { 'first-name': 'John' } }
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* // 大驼峰
|
|
50
|
-
* deepTransformKeys({ user_id: 123 }, 'pascal')
|
|
51
|
-
* // { UserId: 123 }
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* // 大写
|
|
55
|
-
* deepTransformKeys({ userId: 123 }, 'upper')
|
|
56
|
-
* // { USERID: 123 }
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* // 小写
|
|
60
|
-
* deepTransformKeys({ UserId: 123 }, 'lower')
|
|
61
|
-
* // { userid: 123 }
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* // 自定义转换函数
|
|
65
|
-
* deepTransformKeys({ user_id: 123 }, (key) => `prefix_${key}`)
|
|
66
|
-
* // { prefix_user_id: 123 }
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* // 限制递归深度
|
|
70
|
-
* deepTransformKeys(deepData, 'camel', { maxDepth: 10 })
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* // 排除特定键名
|
|
74
|
-
* deepTransformKeys({ _id: '123', user_name: 'John' }, 'camel', { excludeKeys: ['_id'] })
|
|
75
|
-
* // { _id: '123', userName: 'John' }
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* // 嵌套数组和对象
|
|
79
|
-
* deepTransformKeys({
|
|
80
|
-
* user_list: [{ user_id: 1, user_tags: [{ tag_name: 'vip' }] }]
|
|
81
|
-
* }, 'camel')
|
|
82
|
-
* // { userList: [{ userId: 1, userTags: [{ tagName: 'vip' }] }] }
|
|
83
|
-
*/
|
|
84
|
-
export declare const deepTransformKeys: <T = any>(data: any, transformer: KeyTransformer | PresetTransform, options?: TransformOptions) => T;
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { isPlainObject } from "es-toolkit/compat";
|
|
2
|
-
import { camelCase, snakeCase, kebabCase, pascalCase } from "es-toolkit/string";
|
|
3
|
-
/**
|
|
4
|
-
* 深度递归遍历数据结构,转换所有键名
|
|
5
|
-
* 支持嵌套对象和数组,自动防止循环引用和栈溢出
|
|
6
|
-
*
|
|
7
|
-
* @param data - 源数据(对象、数组或其他类型)
|
|
8
|
-
* @param transformer - 转换函数或预设方式
|
|
9
|
-
* @param options - 转换选项
|
|
10
|
-
* @returns 键名转换后的新数据
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* // 小驼峰
|
|
14
|
-
* deepTransformKeys({ user_id: 123, user_info: { first_name: 'John' } }, 'camel')
|
|
15
|
-
* // { userId: 123, userInfo: { firstName: 'John' } }
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* // 下划线
|
|
19
|
-
* deepTransformKeys({ userId: 123, userInfo: { firstName: 'John' } }, 'snake')
|
|
20
|
-
* // { user_id: 123, user_info: { first_name: 'John' } }
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* // 短横线
|
|
24
|
-
* deepTransformKeys({ userId: 123, userInfo: { firstName: 'John' } }, 'kebab')
|
|
25
|
-
* // { 'user-id': 123, 'user-info': { 'first-name': 'John' } }
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* // 大驼峰
|
|
29
|
-
* deepTransformKeys({ user_id: 123 }, 'pascal')
|
|
30
|
-
* // { UserId: 123 }
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* // 大写
|
|
34
|
-
* deepTransformKeys({ userId: 123 }, 'upper')
|
|
35
|
-
* // { USERID: 123 }
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* // 小写
|
|
39
|
-
* deepTransformKeys({ UserId: 123 }, 'lower')
|
|
40
|
-
* // { userid: 123 }
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* // 自定义转换函数
|
|
44
|
-
* deepTransformKeys({ user_id: 123 }, (key) => `prefix_${key}`)
|
|
45
|
-
* // { prefix_user_id: 123 }
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* // 限制递归深度
|
|
49
|
-
* deepTransformKeys(deepData, 'camel', { maxDepth: 10 })
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* // 排除特定键名
|
|
53
|
-
* deepTransformKeys({ _id: '123', user_name: 'John' }, 'camel', { excludeKeys: ['_id'] })
|
|
54
|
-
* // { _id: '123', userName: 'John' }
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* // 嵌套数组和对象
|
|
58
|
-
* deepTransformKeys({
|
|
59
|
-
* user_list: [{ user_id: 1, user_tags: [{ tag_name: 'vip' }] }]
|
|
60
|
-
* }, 'camel')
|
|
61
|
-
* // { userList: [{ userId: 1, userTags: [{ tagName: 'vip' }] }] }
|
|
62
|
-
*/
|
|
63
|
-
export const deepTransformKeys = (data, transformer, options = {}) => {
|
|
64
|
-
const { maxDepth = 100, excludeKeys = [] } = options;
|
|
65
|
-
// 获取实际的转换函数
|
|
66
|
-
let transformFn;
|
|
67
|
-
if (typeof transformer === "function") {
|
|
68
|
-
transformFn = transformer;
|
|
69
|
-
}
|
|
70
|
-
else if (transformer === "camel") {
|
|
71
|
-
transformFn = camelCase;
|
|
72
|
-
}
|
|
73
|
-
else if (transformer === "snake") {
|
|
74
|
-
transformFn = snakeCase;
|
|
75
|
-
}
|
|
76
|
-
else if (transformer === "kebab") {
|
|
77
|
-
transformFn = kebabCase;
|
|
78
|
-
}
|
|
79
|
-
else if (transformer === "pascal") {
|
|
80
|
-
transformFn = pascalCase;
|
|
81
|
-
}
|
|
82
|
-
else if (transformer === "upper") {
|
|
83
|
-
transformFn = (key) => key.toUpperCase();
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
transformFn = (key) => key.toLowerCase();
|
|
87
|
-
}
|
|
88
|
-
// 用于检测循环引用的 WeakSet
|
|
89
|
-
const visited = new WeakSet();
|
|
90
|
-
// 创建排除键名集合,提升查找性能
|
|
91
|
-
const excludeSet = new Set(excludeKeys);
|
|
92
|
-
// 递归转换函数
|
|
93
|
-
const transform = (value, depth) => {
|
|
94
|
-
// 处理 null 和 undefined
|
|
95
|
-
if (value === null || value === undefined) {
|
|
96
|
-
return value;
|
|
97
|
-
}
|
|
98
|
-
// 处理数组
|
|
99
|
-
if (Array.isArray(value)) {
|
|
100
|
-
// 检测循环引用
|
|
101
|
-
if (visited.has(value)) {
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
visited.add(value);
|
|
105
|
-
// 检查深度限制:如果达到限制,返回原数组
|
|
106
|
-
if (maxDepth > 0 && depth >= maxDepth) {
|
|
107
|
-
visited.delete(value);
|
|
108
|
-
return value;
|
|
109
|
-
}
|
|
110
|
-
const result = value.map((item) => transform(item, depth + 1));
|
|
111
|
-
visited.delete(value);
|
|
112
|
-
return result;
|
|
113
|
-
}
|
|
114
|
-
// 处理对象
|
|
115
|
-
if (isPlainObject(value)) {
|
|
116
|
-
// 检测循环引用
|
|
117
|
-
if (visited.has(value)) {
|
|
118
|
-
return value;
|
|
119
|
-
}
|
|
120
|
-
visited.add(value);
|
|
121
|
-
// 检查深度限制:如果达到限制,返回原对象
|
|
122
|
-
if (maxDepth > 0 && depth >= maxDepth) {
|
|
123
|
-
visited.delete(value);
|
|
124
|
-
return value;
|
|
125
|
-
}
|
|
126
|
-
const result = {};
|
|
127
|
-
for (const [key, val] of Object.entries(value)) {
|
|
128
|
-
// 检查是否在排除列表中
|
|
129
|
-
const transformedKey = excludeSet.has(key) ? key : transformFn(key);
|
|
130
|
-
result[transformedKey] = transform(val, depth + 1);
|
|
131
|
-
}
|
|
132
|
-
visited.delete(value);
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
// 其他类型(字符串、数字、布尔值等)直接返回
|
|
136
|
-
return value;
|
|
137
|
-
};
|
|
138
|
-
return transform(data, 0);
|
|
139
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 将未知输入规范化为 pathname 字符串数组。
|
|
3
|
-
*
|
|
4
|
-
* 规则(契约):
|
|
5
|
-
* - value 为“假值”(null/undefined/""/0/false/NaN)时返回空数组 []。
|
|
6
|
-
* - value 必须是 string[],否则抛错:`${fieldLabel} 必须是字符串数组`。
|
|
7
|
-
* - 数组元素必须满足:
|
|
8
|
-
* - 类型为 string
|
|
9
|
-
* - 不允许为空字符串
|
|
10
|
-
* - 不允许包含任何空白字符(空格/制表符/换行等)
|
|
11
|
-
* - 必须以 "/" 开头(pathname)
|
|
12
|
-
* - forbidMethodPrefix=true 时,禁止 "GET/POST/..." 等 method 前缀,并给出更明确的错误提示。
|
|
13
|
-
*
|
|
14
|
-
* 注意:该函数不会做任何隐式修复/转换(例如 trim/split/JSON.parse/去重/排序)。
|
|
15
|
-
*/
|
|
16
|
-
export declare function normalizePathnameListInput(value: unknown, fieldLabel: string, forbidMethodPrefix: boolean): string[];
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
const HTTP_METHOD_PREFIX_RE = /^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/i;
|
|
2
|
-
/**
|
|
3
|
-
* 将未知输入规范化为 pathname 字符串数组。
|
|
4
|
-
*
|
|
5
|
-
* 规则(契约):
|
|
6
|
-
* - value 为“假值”(null/undefined/""/0/false/NaN)时返回空数组 []。
|
|
7
|
-
* - value 必须是 string[],否则抛错:`${fieldLabel} 必须是字符串数组`。
|
|
8
|
-
* - 数组元素必须满足:
|
|
9
|
-
* - 类型为 string
|
|
10
|
-
* - 不允许为空字符串
|
|
11
|
-
* - 不允许包含任何空白字符(空格/制表符/换行等)
|
|
12
|
-
* - 必须以 "/" 开头(pathname)
|
|
13
|
-
* - forbidMethodPrefix=true 时,禁止 "GET/POST/..." 等 method 前缀,并给出更明确的错误提示。
|
|
14
|
-
*
|
|
15
|
-
* 注意:该函数不会做任何隐式修复/转换(例如 trim/split/JSON.parse/去重/排序)。
|
|
16
|
-
*/
|
|
17
|
-
export function normalizePathnameListInput(value, fieldLabel, forbidMethodPrefix) {
|
|
18
|
-
// “假值”统一视为空数组:null/undefined/""/0/false/NaN
|
|
19
|
-
if (!value)
|
|
20
|
-
return [];
|
|
21
|
-
if (!Array.isArray(value)) {
|
|
22
|
-
throw new Error(`${fieldLabel} 必须是字符串数组`);
|
|
23
|
-
}
|
|
24
|
-
const out = [];
|
|
25
|
-
for (let i = 0; i < value.length; i += 1) {
|
|
26
|
-
const item = value[i];
|
|
27
|
-
const itemLabel = `${fieldLabel}[${i}]`;
|
|
28
|
-
if (typeof item !== "string") {
|
|
29
|
-
throw new Error(`${itemLabel} 必须是字符串`);
|
|
30
|
-
}
|
|
31
|
-
if (item.length === 0) {
|
|
32
|
-
throw new Error(`${itemLabel} 不允许为空字符串`);
|
|
33
|
-
}
|
|
34
|
-
// 优先给出 method 前缀提示(更明确)
|
|
35
|
-
if (forbidMethodPrefix && HTTP_METHOD_PREFIX_RE.test(item)) {
|
|
36
|
-
throw new Error(`${itemLabel} 不允许包含 method 前缀,应为 url.pathname(例如 /api/app/xxx)`);
|
|
37
|
-
}
|
|
38
|
-
// 不做 trim 自动转换:含任何空白字符都视为不合法
|
|
39
|
-
if (/\s/.test(item)) {
|
|
40
|
-
throw new Error(`${itemLabel} 不允许包含空白字符(空格/制表符/换行等)`);
|
|
41
|
-
}
|
|
42
|
-
if (!item.startsWith("/")) {
|
|
43
|
-
throw new Error(`${itemLabel} 必须是 pathname(以 / 开头)`);
|
|
44
|
-
}
|
|
45
|
-
out.push(item);
|
|
46
|
-
}
|
|
47
|
-
return out;
|
|
48
|
-
}
|