befly-shared 1.4.5 → 2.0.1

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/package.json CHANGED
@@ -1,36 +1,25 @@
1
1
  {
2
2
  "name": "befly-shared",
3
- "version": "1.4.5",
4
- "gitHead": "607fe5ea9c06653ec6da8a9e4175d1f9c7b98c13",
3
+ "version": "2.0.1",
5
4
  "private": false,
6
- "description": "Befly 前端/Vite 共享工具包(仅供 admin 与 Vite 工具使用,禁止接口端/服务端导入)",
7
- "license": "Apache-2.0",
8
- "author": "chensuiyi <bimostyle@qq.com>",
9
- "files": [
10
- "dist/"
11
- ],
5
+ "description": "Befly Shared - 通用工具函数库",
12
6
  "type": "module",
13
7
  "exports": {
14
- "./utils/*": {
15
- "types": "./dist/utils/*.d.ts",
16
- "default": "./dist/utils/*.js"
17
- },
18
- "./types/*": {
19
- "types": "./dist/types/*.d.ts",
20
- "default": "./dist/types/*.js"
8
+ "./*": {
9
+ "import": "./utils/*.js",
10
+ "default": "./utils/*.js"
21
11
  }
22
12
  },
23
- "scripts": {
24
- "clean": "rimraf dist",
25
- "build": "rimraf dist && bunx tsgo -p tsconfig.build.json",
26
- "test": "bun test",
27
- "prepack": "bun run build && bun run ./scripts/ensureDist.ts",
28
- "typecheck": "bunx tsgo -p tsconfig.json --noEmit"
29
- },
30
- "dependencies": {
31
- "es-toolkit": "^1.44.0"
13
+ "files": [
14
+ "utils/",
15
+ "package.json"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org"
32
20
  },
33
21
  "engines": {
34
22
  "bun": ">=1.3.0"
35
- }
23
+ },
24
+ "gitHead": "25d1bca7ad984b894234524b4cdd37070b68aa2d"
36
25
  }
@@ -0,0 +1,90 @@
1
+ export function createHttp(options) {
2
+ const config = options && typeof options === "object" ? options : {};
3
+ const apiPath = typeof config.apiPath === "string" ? config.apiPath : "";
4
+ const getToken = typeof config.getToken === "function" ? config.getToken : () => "";
5
+
6
+ function shouldDrop(key, value, dropList, dropKeyMap) {
7
+ for (const dropValue of dropList) {
8
+ if (Object.is(value, dropValue)) {
9
+ return true;
10
+ }
11
+ }
12
+ if (Array.isArray(dropKeyMap[key])) {
13
+ for (const dropValue of dropKeyMap[key]) {
14
+ if (Object.is(value, dropValue)) {
15
+ return true;
16
+ }
17
+ }
18
+ }
19
+ return false;
20
+ }
21
+
22
+ return async function requestPost(url, data, dropValues, dropKeyValue) {
23
+ try {
24
+ const fullUrl = `${apiPath}${url}`;
25
+ const isForm = data instanceof FormData;
26
+ const dropList = Array.isArray(dropValues) ? dropValues : [null, undefined];
27
+ const dropKeyMap = dropKeyValue && typeof dropKeyValue === "object" ? dropKeyValue : {};
28
+ let payloadData = data ?? {};
29
+ if (isForm) {
30
+ const nextForm = new FormData();
31
+ for (const entry of payloadData.entries()) {
32
+ const key = entry[0];
33
+ const value = entry[1];
34
+ if (!shouldDrop(key, value, dropList, dropKeyMap)) {
35
+ nextForm.append(key, value);
36
+ }
37
+ }
38
+ payloadData = nextForm;
39
+ } else if (payloadData && typeof payloadData === "object") {
40
+ const nextData = {};
41
+ for (const key of Object.keys(payloadData)) {
42
+ const value = payloadData[key];
43
+ if (!shouldDrop(key, value, dropList, dropKeyMap)) {
44
+ nextData[key] = value;
45
+ }
46
+ }
47
+ payloadData = nextData;
48
+ }
49
+ const headers = new Headers();
50
+ if (!isForm) {
51
+ headers.set("Content-Type", "application/json");
52
+ }
53
+ const tokenValue = getToken();
54
+ if (tokenValue) {
55
+ headers.set("Authorization", "Bearer " + tokenValue);
56
+ }
57
+ const init = {
58
+ method: "POST",
59
+ headers: headers,
60
+ body: isForm ? payloadData : JSON.stringify(payloadData ?? {})
61
+ };
62
+ const res = await fetch(fullUrl, init);
63
+ let payload;
64
+ try {
65
+ payload = await res.json();
66
+ } catch {
67
+ if (res.ok) {
68
+ return Promise.reject({ msg: "响应解析失败" });
69
+ }
70
+ payload = {
71
+ code: res.status,
72
+ msg: `请求失败:HTTP ${res.status}`
73
+ };
74
+ }
75
+ if (payload.code !== 0) {
76
+ return Promise.reject({
77
+ msg: payload.msg ?? "请求失败",
78
+ code: payload.code,
79
+ detail: payload.detail
80
+ });
81
+ }
82
+ return Promise.resolve(payload);
83
+ } catch (error) {
84
+ if (error && typeof error === "object") {
85
+ return Promise.reject(error);
86
+ }
87
+ return Promise.reject({ msg: "网络连接失败" });
88
+ }
89
+ };
90
+ }
@@ -0,0 +1,125 @@
1
+ # createHttp
2
+
3
+ ## 功能说明
4
+
5
+ `createHttp(options)` 用于创建一个通用 POST 请求函数,支持:
6
+
7
+ - JSON 请求体自动序列化
8
+ - `FormData` 自动直传
9
+ - 默认值/按字段值过滤(`dropValues`、`dropKeyValue`)
10
+ - 自动附带 Bearer Token
11
+ - 统一响应与错误结构处理
12
+
13
+ 创建后返回的请求函数签名为:
14
+
15
+ - `requestPost(url, data, dropValues, dropKeyValue)`
16
+
17
+ ---
18
+
19
+ ## 入参
20
+
21
+ ### createHttp(options)
22
+
23
+ - `options.apiPath`:`string`
24
+ - 接口前缀,例如:`http://localhost:3000/api`
25
+ - `options.getToken`:`() => string`
26
+ - 获取 token 的函数
27
+ - 返回空字符串时不会添加 `Authorization`
28
+
29
+ ### requestPost(url, data, dropValues, dropKeyValue)
30
+
31
+ - `url`:`string`
32
+ - 业务接口路径,例如:`/core/login`
33
+ - `data`:`object | FormData`
34
+ - 普通对象会按 JSON 发送
35
+ - `FormData` 会按表单发送
36
+ - `dropValues`:`Array<any>`(可选)
37
+ - 全局过滤值列表
38
+ - 默认:`[null, undefined]`
39
+ - `dropKeyValue`:`Record<string, Array<any>>`(可选)
40
+ - 按字段过滤值
41
+ - 例如:`{ page: [0], keyword: [""] }`
42
+
43
+ ---
44
+
45
+ ## 返回值
46
+
47
+ ### 成功
48
+
49
+ 返回后端 JSON 对象(要求后端 `code === 0`)。
50
+
51
+ ### 失败
52
+
53
+ Promise reject 对象结构:
54
+
55
+ - `msg`: 错误消息
56
+ - `code`: 后端业务码(可选)
57
+ - `detail`: 后端明细(可选)
58
+
59
+ 特殊情况:
60
+
61
+ - 响应可读但 JSON 解析失败:`{ msg: "响应解析失败" }`
62
+ - 非 2xx 且无法按业务格式解析:`{ code: HTTP状态码, msg: "请求失败:HTTP xxx" }`
63
+ - 网络异常:`{ msg: "网络连接失败" }`
64
+
65
+ ---
66
+
67
+ ## 使用示例
68
+
69
+ ### 1)创建实例
70
+
71
+ ```javascript
72
+ import { createHttp } from "befly-shared/createHttp";
73
+
74
+ const $Http = createHttp({
75
+ apiPath: "http://localhost:3000/api",
76
+ getToken: () => localStorage.getItem("befly-admin-token") ?? ""
77
+ });
78
+ ```
79
+
80
+ ### 2)JSON 请求
81
+
82
+ ```javascript
83
+ const res = await $Http("/core/user/list", {
84
+ page: 1,
85
+ limit: 30,
86
+ keyword: ""
87
+ });
88
+ ```
89
+
90
+ ### 3)FormData 请求
91
+
92
+ ```javascript
93
+ const form = new FormData();
94
+ form.append("file", file);
95
+ form.append("remark", "头像");
96
+
97
+ const res = await $Http("/core/upload/file", form);
98
+ ```
99
+
100
+ ### 4)过滤默认值
101
+
102
+ ```javascript
103
+ const res = await $Http("/core/user/list", { page: 1, limit: 30, keyword: "", state: null }, [null, undefined, ""], { page: [0] });
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 行为细节
109
+
110
+ - 当 `data instanceof FormData` 时:
111
+ - 不设置 `Content-Type`(由浏览器自动带 boundary)
112
+ - `body` 直接传 `FormData`
113
+ - 当 `data` 是普通对象时:
114
+ - 设置 `Content-Type: application/json`
115
+ - `body` 为 `JSON.stringify(data)`
116
+ - 有 token 时自动设置:
117
+ - `Authorization: Bearer <token>`
118
+
119
+ ---
120
+
121
+ ## 注意事项
122
+
123
+ - 后端响应格式建议统一为 `{ code, msg, data, detail }`
124
+ - 仅当 `code === 0` 视为成功
125
+ - 该实现固定为 `POST` 请求
@@ -1,6 +0,0 @@
1
- export type ArrayToTreeResult<T extends Record<string, unknown>> = {
2
- flat: Array<T>;
3
- tree: Array<T>;
4
- map: Map<string, T>;
5
- };
6
- export type ArrayToTree = <T extends Record<string, unknown>>(items: Array<T>, id?: string, pid?: string, children?: string, sort?: string) => ArrayToTreeResult<T>;
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export type CleanParams = <TData extends Record<string, unknown>>(data: TData, dropValues?: readonly unknown[], dropKeyValue?: Record<string, readonly unknown[]>) => Partial<TData>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,11 +0,0 @@
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>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- /**
2
- * genShortId 类型定义(类型模块,仅供 type 引用)。
3
- */
4
- export type GenShortId = () => string;
@@ -1,4 +0,0 @@
1
- /**
2
- * genShortId 类型定义(类型模块,仅供 type 引用)。
3
- */
4
- export {};
@@ -1 +0,0 @@
1
- export type HashPassword = (password: string, salt?: string) => Promise<string>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,18 +0,0 @@
1
- /**
2
- * scanViewsDir 相关纯函数的类型定义(类型模块,仅供 type 引用)。
3
- */
4
- export type ViewDirMeta = {
5
- title: string;
6
- order?: number;
7
- };
8
- export type MenuNodeLike<T> = {
9
- name?: string;
10
- path?: string;
11
- sort?: number;
12
- children?: T[];
13
- };
14
- export type CleanDirName = (name: string) => string;
15
- export type NormalizeMenuPath = (path: string) => string;
16
- export type NormalizeMenuTree = <T extends MenuNodeLike<T>>(menus: T[]) => T[];
17
- export type ExtractScriptSetupBlock = (vueContent: string) => string | null;
18
- export type ExtractDefinePageMetaFromScriptSetup = (scriptSetup: string) => ViewDirMeta | null;
@@ -1,4 +0,0 @@
1
- /**
2
- * scanViewsDir 相关纯函数的类型定义(类型模块,仅供 type 引用)。
3
- */
4
- export {};
@@ -1,2 +0,0 @@
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 +0,0 @@
1
- export {};
@@ -1,14 +0,0 @@
1
- export type ArrayToTreeResult<T extends Record<string, unknown>> = {
2
- flat: Array<T>;
3
- tree: Array<T>;
4
- map: Map<string, T>;
5
- };
6
- /**
7
- * 将一维数组按 { id, pid } 组装为树形结构(纯函数 / 无副作用)。
8
- *
9
- * - 默认字段:id / pid / children / sort
10
- * - pid 为空字符串或父节点不存在时,视为根节点
11
- * - 内部会 clone 一份节点对象,并写入 children: []
12
- * - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序
13
- */
14
- export declare function arrayToTree<T extends Record<string, unknown>>(items: Array<T>, id?: string, pid?: string, children?: string, sort?: string): ArrayToTreeResult<T>;
@@ -1,115 +0,0 @@
1
- /**
2
- * 将一维数组按 { id, pid } 组装为树形结构(纯函数 / 无副作用)。
3
- *
4
- * - 默认字段:id / pid / children / sort
5
- * - pid 为空字符串或父节点不存在时,视为根节点
6
- * - 内部会 clone 一份节点对象,并写入 children: []
7
- * - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序
8
- */
9
- export function arrayToTree(items, id = "id", pid = "pid", children = "children", sort = "sort") {
10
- const idKey = typeof id === "string" && id.length > 0 ? id : "id";
11
- const pidKey = typeof pid === "string" && pid.length > 0 ? pid : "pid";
12
- const childrenKey = typeof children === "string" && children.length > 0 ? children : "children";
13
- const sortKey = typeof sort === "string" && sort.length > 0 ? sort : "sort";
14
- const map = new Map();
15
- const flat = [];
16
- const safeItems = Array.isArray(items) ? items : [];
17
- const normalizeKey = (value) => {
18
- if (typeof value === "string") {
19
- return value;
20
- }
21
- if (typeof value === "number" && Number.isFinite(value)) {
22
- return String(value);
23
- }
24
- return "";
25
- };
26
- for (const item of safeItems) {
27
- const itemObj = typeof item === "object" && item !== null ? item : null;
28
- const rawId = itemObj ? itemObj[idKey] : undefined;
29
- const rawPid = itemObj ? itemObj[pidKey] : undefined;
30
- const normalizedId = normalizeKey(rawId);
31
- const normalizedPid = normalizeKey(rawPid);
32
- const nextNode = Object.assign({}, item);
33
- const nextNodeObj = nextNode;
34
- nextNodeObj[idKey] = normalizedId;
35
- nextNodeObj[pidKey] = normalizedPid;
36
- nextNodeObj[childrenKey] = [];
37
- flat.push(nextNode);
38
- if (normalizedId.length > 0) {
39
- map.set(normalizedId, nextNode);
40
- }
41
- }
42
- const tree = [];
43
- for (const node of flat) {
44
- const nodeObj = node;
45
- const selfId = normalizeKey(nodeObj[idKey]);
46
- const parentId = normalizeKey(nodeObj[pidKey]);
47
- if (parentId.length > 0 && parentId !== selfId) {
48
- const parent = map.get(parentId);
49
- if (parent) {
50
- const parentObj = parent;
51
- const childrenValue = parentObj[childrenKey];
52
- if (Array.isArray(childrenValue)) {
53
- childrenValue.push(node);
54
- continue;
55
- }
56
- }
57
- }
58
- tree.push(node);
59
- }
60
- const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
61
- const getSortValue = (node) => {
62
- const nodeObj = node;
63
- const raw = nodeObj[sortKey];
64
- if (typeof raw !== "number") {
65
- return 999999;
66
- }
67
- if (!Number.isFinite(raw)) {
68
- return 999999;
69
- }
70
- if (raw < 1) {
71
- return 999999;
72
- }
73
- return raw;
74
- };
75
- const compareNode = (a, b) => {
76
- const aSort = getSortValue(a);
77
- const bSort = getSortValue(b);
78
- if (aSort !== bSort) {
79
- return aSort - bSort;
80
- }
81
- const aObj = a;
82
- const bObj = b;
83
- const aId = aObj[idKey];
84
- const bId = bObj[idKey];
85
- return collator.compare(typeof aId === "string" ? aId : "", typeof bId === "string" ? bId : "");
86
- };
87
- const sortTreeInPlace = (nodes, seen) => {
88
- if (!Array.isArray(nodes)) {
89
- return;
90
- }
91
- if (nodes.length > 1) {
92
- nodes.sort(compareNode);
93
- }
94
- for (const node of nodes) {
95
- if (typeof node !== "object" || node === null) {
96
- continue;
97
- }
98
- if (seen.has(node)) {
99
- continue;
100
- }
101
- seen.add(node);
102
- const nodeObj = node;
103
- const childNodes = nodeObj[childrenKey];
104
- if (Array.isArray(childNodes) && childNodes.length > 0) {
105
- sortTreeInPlace(childNodes, seen);
106
- }
107
- }
108
- };
109
- sortTreeInPlace(tree, new WeakSet());
110
- return {
111
- flat: flat,
112
- tree: tree,
113
- map: map
114
- };
115
- }
@@ -1 +0,0 @@
1
- export declare function cleanParams<TData extends Record<string, unknown>>(data: TData, dropValues?: readonly unknown[], dropKeyValue?: Record<string, readonly unknown[]>): Partial<TData>;
@@ -1,29 +0,0 @@
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 +0,0 @@
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 declare function fieldClear<T = unknown>(data: T | T[], options?: FieldClearOptions): FieldClearResult<T>;
@@ -1,62 +0,0 @@
1
- function isObject(val) {
2
- return val !== null && typeof val === "object" && !Array.isArray(val);
3
- }
4
- function isArray(val) {
5
- return Array.isArray(val);
6
- }
7
- export function fieldClear(data, options = {}) {
8
- const pickKeys = options.pickKeys;
9
- const omitKeys = options.omitKeys;
10
- const keepValues = options.keepValues;
11
- const excludeValues = options.excludeValues;
12
- const keepMap = options.keepMap;
13
- const filterObj = (obj) => {
14
- const result = {};
15
- let keys = Object.keys(obj);
16
- if (pickKeys && pickKeys.length) {
17
- keys = keys.filter((k) => pickKeys.includes(k));
18
- }
19
- if (omitKeys && omitKeys.length) {
20
- keys = keys.filter((k) => !omitKeys.includes(k));
21
- }
22
- for (const key of keys) {
23
- const value = obj[key];
24
- // 1. keepMap 优先
25
- if (keepMap && Object.hasOwn(keepMap, key)) {
26
- if (Object.is(keepMap[key], value)) {
27
- result[key] = value;
28
- continue;
29
- }
30
- }
31
- // 2. keepValues
32
- if (keepValues && keepValues.length && !keepValues.includes(value)) {
33
- continue;
34
- }
35
- // 3. excludeValues
36
- if (excludeValues && excludeValues.length && excludeValues.includes(value)) {
37
- continue;
38
- }
39
- result[key] = value;
40
- }
41
- return result;
42
- };
43
- if (isArray(data)) {
44
- return data
45
- .map((item) => {
46
- if (isObject(item)) {
47
- return filterObj(item);
48
- }
49
- return item;
50
- })
51
- .filter((item) => {
52
- if (isObject(item)) {
53
- return Object.keys(item).length > 0;
54
- }
55
- return true;
56
- });
57
- }
58
- if (isObject(data)) {
59
- return filterObj(data);
60
- }
61
- return data;
62
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * 生成短 ID
3
- * 由时间戳(base36)+ 随机字符组成,约 13 位
4
- * - 前 8 位:时间戳(可排序)
5
- * - 后 5 位:随机字符(防冲突)
6
- * @returns 短 ID 字符串
7
- * @example
8
- * genShortId() // "lxyz1a2b3c4"
9
- */
10
- export declare function genShortId(): string;
@@ -1,12 +0,0 @@
1
- /**
2
- * 生成短 ID
3
- * 由时间戳(base36)+ 随机字符组成,约 13 位
4
- * - 前 8 位:时间戳(可排序)
5
- * - 后 5 位:随机字符(防冲突)
6
- * @returns 短 ID 字符串
7
- * @example
8
- * genShortId() // "lxyz1a2b3c4"
9
- */
10
- export function genShortId() {
11
- return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
12
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * 使用 SHA-256 对密码进行哈希(前端实现:WebCrypto)。
3
- *
4
- * 注意:这是前端/管理后台用途的工具函数。
5
- * - 禁止在接口端/服务端使用该函数作为密码存储方案
6
- * - 服务端应使用 core 侧既定密码策略(例如 Cipher.hashPassword)
7
- */
8
- export declare function hashPassword(password: string, salt?: string): Promise<string>;
@@ -1,16 +0,0 @@
1
- /**
2
- * 使用 SHA-256 对密码进行哈希(前端实现:WebCrypto)。
3
- *
4
- * 注意:这是前端/管理后台用途的工具函数。
5
- * - 禁止在接口端/服务端使用该函数作为密码存储方案
6
- * - 服务端应使用 core 侧既定密码策略(例如 Cipher.hashPassword)
7
- */
8
- export async function hashPassword(password, salt = "befly") {
9
- const data = password + salt;
10
- const encoder = new TextEncoder();
11
- const dataBuffer = encoder.encode(data);
12
- const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
13
- const hashArray = Array.from(new Uint8Array(hashBuffer));
14
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
15
- return hashHex;
16
- }
@@ -1,43 +0,0 @@
1
- export type ViewDirMeta = {
2
- title: string;
3
- order?: number;
4
- };
5
- type MenuNodeLike<T> = {
6
- name?: string;
7
- path?: string;
8
- sort?: number;
9
- children?: T[];
10
- };
11
- /**
12
- * 清理目录名中的数字后缀
13
- * 如:login_1 → login, index_2 → index
14
- */
15
- export declare function cleanDirName(name: string): string;
16
- /**
17
- * 约束:统一 path 形态,避免隐藏菜单匹配、DB 同步出现重复
18
- * - 必须以 / 开头
19
- * - 折叠多个 /
20
- * - 去掉尾随 /(根 / 除外)
21
- */
22
- export declare function normalizeMenuPath(path: string): string;
23
- /**
24
- * 递归规范化并按 path 去重(同 path 的 children 合并)
25
- *
26
- * 说明:该函数是纯函数,不依赖任何运行时环境;会返回新数组,但会在内部对克隆对象做合并赋值。
27
- */
28
- export declare function normalizeMenuTree<T extends MenuNodeLike<T>>(menus: T[]): T[];
29
- /**
30
- * 只取第一个 <script ... setup ...> 块
31
- */
32
- export declare function extractScriptSetupBlock(vueContent: string): string | null;
33
- /**
34
- * 从 <script setup> 中提取 definePage({ meta })
35
- *
36
- * 简化约束:
37
- * - 每个页面只有一个 definePage
38
- * - title 是纯字符串字面量
39
- * - order 是数字字面量(可选)
40
- * - 不考虑变量/表达式/多段 meta 组合
41
- */
42
- export declare function extractDefinePageMetaFromScriptSetup(scriptSetup: string): ViewDirMeta | null;
43
- export {};
@@ -1,121 +0,0 @@
1
- /**
2
- * 清理目录名中的数字后缀
3
- * 如:login_1 → login, index_2 → index
4
- */
5
- export function cleanDirName(name) {
6
- return name.replace(/_\d+$/, "");
7
- }
8
- /**
9
- * 约束:统一 path 形态,避免隐藏菜单匹配、DB 同步出现重复
10
- * - 必须以 / 开头
11
- * - 折叠多个 /
12
- * - 去掉尾随 /(根 / 除外)
13
- */
14
- export function normalizeMenuPath(path) {
15
- let result = path;
16
- if (!result) {
17
- return "/";
18
- }
19
- if (!result.startsWith("/")) {
20
- result = `/${result}`;
21
- }
22
- result = result.replace(/\/+/g, "/");
23
- if (result.length > 1) {
24
- result = result.replace(/\/+$/, "");
25
- }
26
- return result;
27
- }
28
- /**
29
- * 递归规范化并按 path 去重(同 path 的 children 合并)
30
- *
31
- * 说明:该函数是纯函数,不依赖任何运行时环境;会返回新数组,但会在内部对克隆对象做合并赋值。
32
- */
33
- export function normalizeMenuTree(menus) {
34
- const map = new Map();
35
- for (const menu of menus) {
36
- const rawPath = menu.path;
37
- const menuPath = rawPath ? normalizeMenuPath(rawPath) : "";
38
- if (!menuPath) {
39
- continue;
40
- }
41
- // 不使用 structuredClone:
42
- // - 结构中可能出现函数/类实例等不可 clone 的值
43
- // - 这里我们只需要“保留额外字段 + 递归 children 生成新数组”
44
- // 用浅拷贝即可满足需求
45
- const cloned = Object.assign({}, menu);
46
- cloned.path = menuPath;
47
- const rawChildren = menu.children;
48
- if (rawChildren && rawChildren.length > 0) {
49
- cloned.children = normalizeMenuTree(rawChildren);
50
- }
51
- const existing = map.get(menuPath);
52
- if (existing) {
53
- const clonedChildren = cloned.children;
54
- if (clonedChildren && clonedChildren.length > 0) {
55
- let existingChildren = existing.children;
56
- if (!existingChildren) {
57
- existingChildren = [];
58
- existing.children = existingChildren;
59
- }
60
- for (const child of clonedChildren) {
61
- existingChildren.push(child);
62
- }
63
- existing.children = normalizeMenuTree(existingChildren);
64
- }
65
- if (typeof cloned.sort === "number") {
66
- existing.sort = cloned.sort;
67
- }
68
- if (typeof cloned.name === "string" && cloned.name) {
69
- existing.name = cloned.name;
70
- }
71
- }
72
- else {
73
- map.set(menuPath, cloned);
74
- }
75
- }
76
- const result = Array.from(map.values());
77
- result.sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999));
78
- return result;
79
- }
80
- /**
81
- * 只取第一个 <script ... setup ...> 块
82
- */
83
- export function extractScriptSetupBlock(vueContent) {
84
- const openTag = /<script\b[^>]*\bsetup\b[^>]*>/i.exec(vueContent);
85
- if (!openTag) {
86
- return null;
87
- }
88
- const start = openTag.index + openTag[0].length;
89
- const closeIndex = vueContent.indexOf("</script>", start);
90
- if (closeIndex < 0) {
91
- return null;
92
- }
93
- return vueContent.slice(start, closeIndex);
94
- }
95
- /**
96
- * 从 <script setup> 中提取 definePage({ meta })
97
- *
98
- * 简化约束:
99
- * - 每个页面只有一个 definePage
100
- * - title 是纯字符串字面量
101
- * - order 是数字字面量(可选)
102
- * - 不考虑变量/表达式/多段 meta 组合
103
- */
104
- export function extractDefinePageMetaFromScriptSetup(scriptSetup) {
105
- const titleMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?title\s*:\s*(["'`])([^"'`]+)\1/);
106
- if (!titleMatch) {
107
- return null;
108
- }
109
- const title = titleMatch[2];
110
- if (typeof title !== "string") {
111
- return null;
112
- }
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
- }
117
- return {
118
- title: title,
119
- order: Number(orderMatch[1])
120
- };
121
- }
@@ -1,11 +0,0 @@
1
- export type TableColumnLike = Record<string, unknown>;
2
- /**
3
- * 为表格列添加默认配置(纯函数)。
4
- *
5
- * 默认行为:
6
- * - base 默认:{ width: 200, ellipsis: true }
7
- * - 特殊列:operation/state/id
8
- * - colKey 以 At/At2 结尾时:默认 { align: "center" }
9
- * - customConfig 可覆盖/扩展默认 specialColumnConfig
10
- */
11
- export declare function withDefaultColumns(columns: Array<TableColumnLike>, customConfig?: Record<string, Record<string, unknown>>): Array<Record<string, unknown>>;
@@ -1,43 +0,0 @@
1
- /**
2
- * 为表格列添加默认配置(纯函数)。
3
- *
4
- * 默认行为:
5
- * - base 默认:{ width: 200, ellipsis: true }
6
- * - 特殊列:operation/state/id
7
- * - colKey 以 At/At2 结尾时:默认 { align: "center" }
8
- * - customConfig 可覆盖/扩展默认 specialColumnConfig
9
- */
10
- export function withDefaultColumns(columns, customConfig) {
11
- const safeColumns = Array.isArray(columns) ? columns : [];
12
- const specialColumnConfig = Object.assign({
13
- operation: { width: 100, align: "center", fixed: "right" },
14
- sort: { width: 80, align: "center" },
15
- state: { width: 100, align: "center" },
16
- result: { width: 80, align: "center" },
17
- duration: { width: 100, align: "center" },
18
- createTime: { width: 170, align: "center" },
19
- updateTime: { width: 170, align: "center" },
20
- operateTime: { width: 170, align: "center" },
21
- createdAt: { width: 170, align: "center" },
22
- updatedAt: { width: 170, align: "center" },
23
- id: { width: 200, align: "center" }
24
- }, customConfig || {});
25
- return safeColumns.map((col) => {
26
- const colObj = typeof col === "object" && col !== null ? col : {};
27
- const colKey = typeof colObj["colKey"] === "string" ? colObj["colKey"] : undefined;
28
- let specialConfig = colKey ? specialColumnConfig[colKey] : undefined;
29
- if (!specialConfig && typeof colKey === "string" && (colKey.endsWith("At") || colKey.endsWith("At2"))) {
30
- specialConfig = { align: "center" };
31
- }
32
- const base = {
33
- width: 200,
34
- ellipsis: true
35
- };
36
- const merged = Object.assign({}, base);
37
- if (specialConfig) {
38
- Object.assign(merged, specialConfig);
39
- }
40
- Object.assign(merged, colObj);
41
- return merged;
42
- });
43
- }