@melt-js/libs-vben 0.0.0-stage → 2026.11.8-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/LICENSE +10 -0
- package/README.md +86 -2
- package/package.json +98 -3
- package/src/access/access-control.vue +47 -0
- package/src/access/accessible.ts +236 -0
- package/src/access/directive.ts +42 -0
- package/src/access/index.ts +4 -0
- package/src/access/use-access.ts +53 -0
- package/src/composables/index.ts +14 -0
- package/src/composables/use-is-mobile.ts +7 -0
- package/src/composables/use-layout-style.ts +51 -0
- package/src/composables/use-layout-viewport-height.ts +71 -0
- package/src/composables/use-namespace.ts +106 -0
- package/src/composables/use-priority-value.ts +96 -0
- package/src/composables/use-scroll-lock.ts +151 -0
- package/src/composables/use-simple-locale/README.md +3 -0
- package/src/composables/use-simple-locale/index.ts +27 -0
- package/src/composables/use-simple-locale/messages.ts +31 -0
- package/src/composables/use-sortable.ts +32 -0
- package/src/constants/core.ts +76 -0
- package/src/constants/index.ts +2 -0
- package/src/core-icons/create-icon.ts +14 -0
- package/src/core-icons/index.ts +11 -0
- package/src/core-icons/lucide.ts +95 -0
- package/src/core-preferences/config.ts +172 -0
- package/src/core-preferences/constants.ts +116 -0
- package/src/core-preferences/index.ts +24 -0
- package/src/core-preferences/preferences.ts +503 -0
- package/src/core-preferences/types.ts +471 -0
- package/src/core-preferences/update-css-variables.ts +129 -0
- package/src/core-preferences/use-preferences.ts +282 -0
- package/src/design/css/global.css +1 -0
- package/src/design/css/nprogress.css +61 -0
- package/src/design/css/transition.css +238 -0
- package/src/design/css/ui.css +101 -0
- package/src/design/design-tokens/dark.css +446 -0
- package/src/design/design-tokens/default.css +491 -0
- package/src/design/design-tokens/index.ts +2 -0
- package/src/design/index.ts +6 -0
- package/src/design/scss-bem/bem.scss +34 -0
- package/src/design/scss-bem/constants.scss +5 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/use-app-config.ts +39 -0
- package/src/hooks/use-content-maximize.ts +24 -0
- package/src/hooks/use-design-tokens.ts +421 -0
- package/src/hooks/use-hover-toggle.ts +163 -0
- package/src/hooks/use-pagination.ts +72 -0
- package/src/hooks/use-refresh.ts +16 -0
- package/src/hooks/use-tabs.ts +143 -0
- package/src/hooks/use-watermark.ts +84 -0
- package/src/icons/iconify/index.ts +5 -0
- package/src/icons/icons/empty-icon.vue +27 -0
- package/src/icons/index.ts +3 -0
- package/src/icons/svg/icons/antdv-logo.svg +29 -0
- package/src/icons/svg/icons/antdv-next-logo.svg +1 -0
- package/src/icons/svg/icons/avatar-1.svg +1 -0
- package/src/icons/svg/icons/avatar-2.svg +1 -0
- package/src/icons/svg/icons/avatar-3.svg +1 -0
- package/src/icons/svg/icons/avatar-4.svg +1 -0
- package/src/icons/svg/icons/bell.svg +1 -0
- package/src/icons/svg/icons/cake.svg +1 -0
- package/src/icons/svg/icons/card.svg +1 -0
- package/src/icons/svg/icons/dingding.svg +1 -0
- package/src/icons/svg/icons/download.svg +1 -0
- package/src/icons/svg/icons/github.svg +1 -0
- package/src/icons/svg/icons/google.svg +1 -0
- package/src/icons/svg/icons/qqchat.svg +1 -0
- package/src/icons/svg/icons/tdesign-logo.svg +39 -0
- package/src/icons/svg/icons/wechat.svg +1 -0
- package/src/icons/svg/index.ts +39 -0
- package/src/icons/svg/load.ts +81 -0
- package/src/locales/i18n.ts +147 -0
- package/src/locales/index.ts +30 -0
- package/src/locales/langs/en-US/authentication.json +65 -0
- package/src/locales/langs/en-US/common.json +27 -0
- package/src/locales/langs/en-US/preferences.json +245 -0
- package/src/locales/langs/en-US/profile.json +4 -0
- package/src/locales/langs/en-US/ui.json +165 -0
- package/src/locales/langs/zh-CN/authentication.json +65 -0
- package/src/locales/langs/zh-CN/common.json +27 -0
- package/src/locales/langs/zh-CN/preferences.json +245 -0
- package/src/locales/langs/zh-CN/profile.json +4 -0
- package/src/locales/langs/zh-CN/ui.json +165 -0
- package/src/locales/langs/zh-TW/authentication.json +65 -0
- package/src/locales/langs/zh-TW/common.json +27 -0
- package/src/locales/langs/zh-TW/preferences.json +245 -0
- package/src/locales/langs/zh-TW/profile.json +4 -0
- package/src/locales/langs/zh-TW/ui.json +165 -0
- package/src/locales/typing.ts +27 -0
- package/src/preferences/index.ts +42 -0
- package/src/request/index.ts +2 -0
- package/src/request/request-client/index.ts +3 -0
- package/src/request/request-client/modules/downloader.ts +60 -0
- package/src/request/request-client/modules/interceptor.ts +40 -0
- package/src/request/request-client/modules/sse.ts +134 -0
- package/src/request/request-client/modules/uploader.ts +79 -0
- package/src/request/request-client/preset-interceptors.ts +178 -0
- package/src/request/request-client/request-client.ts +163 -0
- package/src/request/request-client/types.ts +90 -0
- package/src/shared/cache/README.md +418 -0
- package/src/shared/cache/index.ts +5 -0
- package/src/shared/cache/indexeddb-driver.ts +137 -0
- package/src/shared/cache/local-storage-driver.ts +70 -0
- package/src/shared/cache/memory-storage-driver.ts +32 -0
- package/src/shared/cache/storage-manager.ts +146 -0
- package/src/shared/cache/types.ts +39 -0
- package/src/shared/color/color.ts +9 -0
- package/src/shared/color/convert.ts +62 -0
- package/src/shared/color/generator.ts +45 -0
- package/src/shared/color/index.ts +3 -0
- package/src/shared/constants/globals.ts +20 -0
- package/src/shared/constants/index.ts +2 -0
- package/src/shared/constants/vben.ts +30 -0
- package/src/shared/global-state.ts +45 -0
- package/src/shared/store.ts +1 -0
- package/src/shared/utils/cn.ts +10 -0
- package/src/shared/utils/date.ts +80 -0
- package/src/shared/utils/diff.ts +114 -0
- package/src/shared/utils/dom.ts +126 -0
- package/src/shared/utils/download.ts +186 -0
- package/src/shared/utils/error.ts +19 -0
- package/src/shared/utils/index.ts +22 -0
- package/src/shared/utils/inference.ts +164 -0
- package/src/shared/utils/letter.ts +47 -0
- package/src/shared/utils/merge.ts +11 -0
- package/src/shared/utils/nprogress.ts +52 -0
- package/src/shared/utils/resources.ts +21 -0
- package/src/shared/utils/stack.ts +103 -0
- package/src/shared/utils/state-handler.ts +61 -0
- package/src/shared/utils/to.ts +21 -0
- package/src/shared/utils/tree.ts +159 -0
- package/src/shared/utils/unique.ts +15 -0
- package/src/shared/utils/update-css-variables.ts +47 -0
- package/src/shared/utils/util.ts +50 -0
- package/src/shared/utils/window.ts +37 -0
- package/src/stores/index.ts +3 -0
- package/src/stores/modules/access.ts +130 -0
- package/src/stores/modules/dict.ts +244 -0
- package/src/stores/modules/index.ts +5 -0
- package/src/stores/modules/tabbar.ts +797 -0
- package/src/stores/modules/timezone.ts +132 -0
- package/src/stores/modules/user.ts +42 -0
- package/src/stores/setup.ts +79 -0
- package/src/stores/shim-pinia.d.ts +11 -0
- package/src/styles/antd/index.css +78 -0
- package/src/styles/antdv-next/index.css +90 -0
- package/src/styles/ele/index.css +44 -0
- package/src/styles/global/index.scss +1 -0
- package/src/styles/index.ts +1 -0
- package/src/styles/naive/index.css +26 -0
- package/src/styles/style-exports.d.ts +3 -0
- package/src/theme.css +592 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/index.ts +2 -0
- package/src/types/shims.d.ts +1 -0
- package/src/types/user.ts +20 -0
- package/src/typings/app.d.ts +128 -0
- package/src/typings/basic.d.ts +42 -0
- package/src/typings/helper.d.ts +147 -0
- package/src/typings/index.ts +16 -0
- package/src/typings/languages.ts +28 -0
- package/src/typings/menu-record.ts +82 -0
- package/src/typings/tabs.ts +8 -0
- package/src/typings/vue-router.d.ts +169 -0
- package/src/utils/helpers/find-menu-by-path.ts +37 -0
- package/src/utils/helpers/generate-menus.ts +89 -0
- package/src/utils/helpers/generate-routes-backend.ts +108 -0
- package/src/utils/helpers/generate-routes-frontend.ts +58 -0
- package/src/utils/helpers/get-popup-container.ts +10 -0
- package/src/utils/helpers/index.ts +8 -0
- package/src/utils/helpers/merge-route-modules.ts +28 -0
- package/src/utils/helpers/reset-routes.ts +30 -0
- package/src/utils/helpers/unmount-global-loading.ts +31 -0
- package/src/utils/index.ts +4 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zh_CN 栈数据结构
|
|
3
|
+
*/
|
|
4
|
+
export class Stack<T> {
|
|
5
|
+
/**
|
|
6
|
+
* @zh_CN 栈内元素数量
|
|
7
|
+
*/
|
|
8
|
+
get size() {
|
|
9
|
+
return this.items.length;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @zh_CN 是否去重
|
|
13
|
+
*/
|
|
14
|
+
private readonly dedup: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* @zh_CN 栈内元素
|
|
17
|
+
*/
|
|
18
|
+
private items: T[] = [];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @zh_CN 栈的最大容量
|
|
22
|
+
*/
|
|
23
|
+
private readonly maxSize?: number;
|
|
24
|
+
|
|
25
|
+
constructor(dedup = true, maxSize?: number) {
|
|
26
|
+
this.maxSize = maxSize;
|
|
27
|
+
this.dedup = dedup;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @zh_CN 清空栈内元素
|
|
32
|
+
*/
|
|
33
|
+
clear() {
|
|
34
|
+
this.items.length = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @zh_CN 查看栈顶元素
|
|
39
|
+
* @returns 栈顶元素
|
|
40
|
+
*/
|
|
41
|
+
peek(): T | undefined {
|
|
42
|
+
return this.items[this.items.length - 1];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @zh_CN 出栈
|
|
47
|
+
* @returns 栈顶元素
|
|
48
|
+
*/
|
|
49
|
+
pop(): T | undefined {
|
|
50
|
+
return this.items.pop();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @zh_CN 入栈
|
|
55
|
+
* @param items 要入栈的元素
|
|
56
|
+
*/
|
|
57
|
+
push(...items: T[]) {
|
|
58
|
+
items.forEach((item) => {
|
|
59
|
+
// 去重
|
|
60
|
+
if (this.dedup) {
|
|
61
|
+
const index = this.items.indexOf(item);
|
|
62
|
+
if (index !== -1) {
|
|
63
|
+
this.items.splice(index, 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
this.items.push(item);
|
|
67
|
+
if (this.maxSize && this.items.length > this.maxSize) {
|
|
68
|
+
this.items.splice(0, this.items.length - this.maxSize);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @zh_CN 移除栈内元素
|
|
74
|
+
* @param itemList 要移除的元素列表
|
|
75
|
+
*/
|
|
76
|
+
remove(...itemList: T[]) {
|
|
77
|
+
this.items = this.items.filter((i) => !itemList.includes(i));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* @zh_CN 保留栈内元素
|
|
81
|
+
* @param itemList 要保留的元素列表
|
|
82
|
+
*/
|
|
83
|
+
retain(itemList: T[]) {
|
|
84
|
+
this.items = this.items.filter((i) => itemList.includes(i));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @zh_CN 转换为数组
|
|
89
|
+
* @returns 栈内元素数组
|
|
90
|
+
*/
|
|
91
|
+
toArray(): T[] {
|
|
92
|
+
return [...this.items];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @zh_CN 创建一个栈实例
|
|
98
|
+
* @param dedup 是否去重
|
|
99
|
+
* @param maxSize 栈的最大容量
|
|
100
|
+
* @returns 栈实例
|
|
101
|
+
*/
|
|
102
|
+
export const createStack = <T>(dedup = true, maxSize?: number) =>
|
|
103
|
+
new Stack<T>(dedup, maxSize);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export class StateHandler {
|
|
2
|
+
private condition: boolean = false;
|
|
3
|
+
private rejectConditions: ((reason?: Error) => void)[] = [];
|
|
4
|
+
private resolveConditions: (() => void)[] = [];
|
|
5
|
+
|
|
6
|
+
isConditionTrue(): boolean {
|
|
7
|
+
return this.condition;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
reset() {
|
|
11
|
+
this.condition = false;
|
|
12
|
+
// Callers that awaited the condition must not be left pending: after a
|
|
13
|
+
// reset (e.g. unmount) the gate they wait for will not open for them.
|
|
14
|
+
const pendingRejects = this.rejectConditions;
|
|
15
|
+
this.clearPromises();
|
|
16
|
+
for (const reject of pendingRejects) {
|
|
17
|
+
reject(new Error('Condition was reset'));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 触发状态为 false 时,reject 所有等待中的 Promise
|
|
22
|
+
setConditionFalse() {
|
|
23
|
+
this.condition = false;
|
|
24
|
+
const pendingRejects = this.rejectConditions;
|
|
25
|
+
this.clearPromises();
|
|
26
|
+
for (const reject of pendingRejects) {
|
|
27
|
+
reject(new Error('Condition was set to false'));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 触发状态为 true 时,resolve 所有等待中的 Promise
|
|
32
|
+
setConditionTrue() {
|
|
33
|
+
this.condition = true;
|
|
34
|
+
const pendingResolves = this.resolveConditions;
|
|
35
|
+
this.clearPromises();
|
|
36
|
+
for (const resolve of pendingResolves) {
|
|
37
|
+
resolve();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 返回一个 Promise,等待 condition 变为 true;支持多个并发等待者
|
|
42
|
+
waitForCondition(): Promise<void> {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
if (this.condition) {
|
|
45
|
+
resolve(); // 如果 condition 已经为 true,立即 resolve
|
|
46
|
+
} else {
|
|
47
|
+
// condition 变为 true/false 前可能有多个调用方同时等待,
|
|
48
|
+
// 需要把所有 resolve/reject 都收集起来,切换时一次性结算,
|
|
49
|
+
// 否则后注册者会覆盖前一个句柄,导致先注册的等待方永远挂起。
|
|
50
|
+
this.resolveConditions.push(resolve);
|
|
51
|
+
this.rejectConditions.push(reject);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 清理所有等待中的 resolve/reject 函数
|
|
57
|
+
private clearPromises() {
|
|
58
|
+
this.resolveConditions = [];
|
|
59
|
+
this.rejectConditions = [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param { Readonly<Promise> } promise
|
|
3
|
+
* @param {object=} errorExt - Additional Information you can pass to the err object
|
|
4
|
+
* @return { Promise }
|
|
5
|
+
*/
|
|
6
|
+
export async function to<T, U = Error>(
|
|
7
|
+
promise: Readonly<Promise<T>>,
|
|
8
|
+
errorExt?: object,
|
|
9
|
+
): Promise<[null, T] | [U, undefined]> {
|
|
10
|
+
try {
|
|
11
|
+
const data = await promise;
|
|
12
|
+
const result: [null, T] = [null, data];
|
|
13
|
+
return result;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (errorExt) {
|
|
16
|
+
const parsedError = Object.assign({}, error, errorExt);
|
|
17
|
+
return [parsedError as U, undefined];
|
|
18
|
+
}
|
|
19
|
+
return [error as U, undefined];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
interface TreeConfigOptions {
|
|
2
|
+
// 子属性的名称,默认为'children'
|
|
3
|
+
childProps: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @zh_CN 遍历树形结构,并返回所有节点中指定的值。
|
|
8
|
+
* @param tree 树形结构数组
|
|
9
|
+
* @param getValue 获取节点值的函数
|
|
10
|
+
* @param options 作为子节点数组的可选属性名称。
|
|
11
|
+
* @returns 所有节点中指定的值的数组
|
|
12
|
+
*/
|
|
13
|
+
function traverseTreeValues<T, V>(
|
|
14
|
+
tree: T[],
|
|
15
|
+
getValue: (node: T) => V,
|
|
16
|
+
options?: TreeConfigOptions,
|
|
17
|
+
): V[] {
|
|
18
|
+
const result: V[] = [];
|
|
19
|
+
const { childProps } = options || {
|
|
20
|
+
childProps: 'children',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const dfs = (treeNode: T) => {
|
|
24
|
+
const value = getValue(treeNode);
|
|
25
|
+
result.push(value);
|
|
26
|
+
const children = (treeNode as Record<string, any>)?.[childProps];
|
|
27
|
+
if (!children) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (children.length > 0) {
|
|
31
|
+
for (const child of children) {
|
|
32
|
+
dfs(child);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
for (const treeNode of tree) {
|
|
38
|
+
dfs(treeNode);
|
|
39
|
+
}
|
|
40
|
+
return result.filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 根据条件过滤给定树结构的节点,并以原有顺序返回所有匹配节点的数组。
|
|
45
|
+
*
|
|
46
|
+
* 该函数是纯函数:不会把过滤后的结果写回传入的树(源数据不被修改)。
|
|
47
|
+
* 否则第二次用不同条件过滤同一棵树时(例如切换用户角色后重新生成路由),
|
|
48
|
+
* 上一次被过滤掉的子节点将永久丢失。
|
|
49
|
+
*
|
|
50
|
+
* 引用语义:只有当子节点序列真的发生变化时,命中的分支节点才会被浅拷贝;
|
|
51
|
+
* 子节点未发生变化(或节点没有子节点)时,仍按引用返回原节点,
|
|
52
|
+
* 因此按引用缓存或比较节点的调用方,其行为保持不变。
|
|
53
|
+
*
|
|
54
|
+
* @param tree 要过滤的树结构的根节点数组。
|
|
55
|
+
* @param filter 用于匹配每个节点的条件。
|
|
56
|
+
* @param options 作为子节点数组的可选属性名称。
|
|
57
|
+
* @returns 包含所有匹配节点的数组。
|
|
58
|
+
*/
|
|
59
|
+
function filterTree<T extends Record<string, any>>(
|
|
60
|
+
tree: T[],
|
|
61
|
+
filter: (node: T) => boolean,
|
|
62
|
+
options?: TreeConfigOptions,
|
|
63
|
+
): T[] {
|
|
64
|
+
const { childProps } = options || {
|
|
65
|
+
childProps: 'children',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const _filterTree = (nodes: T[]): T[] => {
|
|
69
|
+
const result: T[] = [];
|
|
70
|
+
|
|
71
|
+
for (const node of nodes) {
|
|
72
|
+
if (!filter(node)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const children = (node as Record<string, any>)[childProps];
|
|
77
|
+
|
|
78
|
+
if (!children) {
|
|
79
|
+
result.push(node);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const filteredChildren = _filterTree(children);
|
|
84
|
+
|
|
85
|
+
// 子节点序列没有变化时按引用返回原节点,保持原有的引用语义;
|
|
86
|
+
// 只有确实需要替换子节点时才复制节点,避免污染源数据
|
|
87
|
+
const childrenUnchanged =
|
|
88
|
+
filteredChildren.length === children.length &&
|
|
89
|
+
filteredChildren.every((child, index) => child === children[index]);
|
|
90
|
+
|
|
91
|
+
result.push(
|
|
92
|
+
childrenUnchanged ? node : { ...node, [childProps]: filteredChildren },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return _filterTree(tree);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 根据条件重新映射给定树结构的节
|
|
104
|
+
* @param tree 要过滤的树结构的根节点数组。
|
|
105
|
+
* @param mapper 用于map每个节点的条件。
|
|
106
|
+
* @param options 作为子节点数组的可选属性名称。
|
|
107
|
+
*/
|
|
108
|
+
function mapTree<T, V extends Record<string, any>>(
|
|
109
|
+
tree: T[],
|
|
110
|
+
mapper: (node: T, parent: null | V) => V,
|
|
111
|
+
options?: TreeConfigOptions,
|
|
112
|
+
parent: null | V = null,
|
|
113
|
+
): V[] {
|
|
114
|
+
const { childProps } = options || {
|
|
115
|
+
childProps: 'children',
|
|
116
|
+
};
|
|
117
|
+
return tree.map((node) => {
|
|
118
|
+
const mapperNode: Record<string, any> = mapper(node, parent);
|
|
119
|
+
if (mapperNode[childProps]) {
|
|
120
|
+
mapperNode[childProps] = mapTree(
|
|
121
|
+
mapperNode[childProps],
|
|
122
|
+
mapper,
|
|
123
|
+
options,
|
|
124
|
+
mapperNode as V,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return mapperNode as V;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 对树形结构数据进行递归排序
|
|
133
|
+
* @param treeData - 树形数据数组
|
|
134
|
+
* @param sortFunction - 排序函数,用于定义排序规则
|
|
135
|
+
* @param options - 配置选项,包括子节点属性名
|
|
136
|
+
* @returns 排序后的树形数据
|
|
137
|
+
*/
|
|
138
|
+
function sortTree<T extends Record<string, any>>(
|
|
139
|
+
treeData: T[],
|
|
140
|
+
sortFunction: (a: T, b: T) => number,
|
|
141
|
+
options?: TreeConfigOptions,
|
|
142
|
+
): T[] {
|
|
143
|
+
const { childProps } = options || {
|
|
144
|
+
childProps: 'children',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return treeData.toSorted(sortFunction).map((item) => {
|
|
148
|
+
const children = item[childProps];
|
|
149
|
+
if (children && Array.isArray(children) && children.length > 0) {
|
|
150
|
+
return {
|
|
151
|
+
...item,
|
|
152
|
+
[childProps]: sortTree(children, sortFunction, options),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return item;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { filterTree, mapTree, sortTree, traverseTreeValues };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 根据指定字段对对象数组进行去重
|
|
3
|
+
* @param arr 要去重的对象数组
|
|
4
|
+
* @param key 去重依据的字段名
|
|
5
|
+
* @returns 去重后的对象数组
|
|
6
|
+
*/
|
|
7
|
+
function uniqueByField<T>(arr: T[], key: keyof T): T[] {
|
|
8
|
+
const seen = new Map<any, T>();
|
|
9
|
+
return arr.filter((item) => {
|
|
10
|
+
const value = item[key];
|
|
11
|
+
return seen.has(value) ? false : (seen.set(value, item), true);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { uniqueByField };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 更新 CSS 变量的函数
|
|
3
|
+
* @param variables 要更新的 CSS 变量与其新值的映射
|
|
4
|
+
* @param id 内联样式表的 id,便于复用与覆盖
|
|
5
|
+
* @param selector CSS 变量挂载的选择器,默认 `:root`。
|
|
6
|
+
* 对于像 TDesign 这种将变量定义在 `:root[theme-mode='dark']` 等更高优先级选择器下的组件库,
|
|
7
|
+
* 需要传入相同(或更高)优先级的选择器才能正确覆盖。
|
|
8
|
+
*/
|
|
9
|
+
function updateCSSVariables(
|
|
10
|
+
variables: { [key: string]: string },
|
|
11
|
+
id = '__vben-styles__',
|
|
12
|
+
selector = ':root',
|
|
13
|
+
): void {
|
|
14
|
+
// 历史实现用 setTimeout 延迟插入创建的样式标签,若同一宏任务内被连续调用
|
|
15
|
+
// (如初始化偏好后又恢复用户偏好),querySelector 尚找不到未插入的标签,
|
|
16
|
+
// 会重复创建同 id 标签:运行时只更新第一个,而级联获胜的却是第二个(旧值),
|
|
17
|
+
// 导致主题色切换后部分组件不生效、刷新页面才恢复。
|
|
18
|
+
// 这里改为同步插入消除并发窗口,并顺带清理历史遗留的重复标签。
|
|
19
|
+
const existingStyles = document.querySelectorAll<HTMLElement>(`#${id}`);
|
|
20
|
+
existingStyles.forEach((element, index) => {
|
|
21
|
+
if (index > 0) {
|
|
22
|
+
element.remove();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
let styleElement = existingStyles[0] ?? null;
|
|
27
|
+
|
|
28
|
+
if (!styleElement) {
|
|
29
|
+
styleElement = document.createElement('style');
|
|
30
|
+
styleElement.id = id;
|
|
31
|
+
document.head.append(styleElement);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 构建要更新的 CSS 变量的样式文本
|
|
35
|
+
let cssText = `${selector} {`;
|
|
36
|
+
for (const key in variables) {
|
|
37
|
+
if (Object.prototype.hasOwnProperty.call(variables, key)) {
|
|
38
|
+
cssText += `${key}: ${variables[key]};`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
cssText += '}';
|
|
42
|
+
|
|
43
|
+
// 将样式文本赋值给内联样式表
|
|
44
|
+
styleElement.textContent = cssText;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { updateCSSVariables };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// 泛型参数是已发布的公共 API 契约(调用方以 bindMethods<MyClass>(instance)
|
|
2
|
+
// 显式传参),规则只看签名内出现次数、不评估跨包调用方,不能据此删除。
|
|
3
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters
|
|
4
|
+
export function bindMethods<T extends object>(instance: T): void {
|
|
5
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
6
|
+
const propertyNames = Object.getOwnPropertyNames(prototype);
|
|
7
|
+
|
|
8
|
+
propertyNames.forEach((propertyName) => {
|
|
9
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);
|
|
10
|
+
const propertyValue = instance[propertyName as keyof T];
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
typeof propertyValue === 'function' &&
|
|
14
|
+
propertyName !== 'constructor' &&
|
|
15
|
+
descriptor &&
|
|
16
|
+
!descriptor.get &&
|
|
17
|
+
!descriptor.set
|
|
18
|
+
) {
|
|
19
|
+
instance[propertyName as keyof T] = propertyValue.bind(instance);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取嵌套对象的字段值
|
|
26
|
+
* @param obj - 要查找的对象
|
|
27
|
+
* @param path - 用于查找字段的路径,使用小数点分隔
|
|
28
|
+
* @returns 字段值,或者未找到时返回 undefined
|
|
29
|
+
*/
|
|
30
|
+
// 同 bindMethods:getNestedValue<MyData>(data, path) 的显式类型实参是
|
|
31
|
+
// 已发布调用契约,保留泛型。
|
|
32
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters
|
|
33
|
+
export function getNestedValue<T>(obj: T, path: string): any {
|
|
34
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
35
|
+
throw new Error('Path must be a non-empty string');
|
|
36
|
+
}
|
|
37
|
+
// 把路径字符串按 "." 分割成数组
|
|
38
|
+
const keys = path.split('.') as (number | string)[];
|
|
39
|
+
|
|
40
|
+
let current: any = obj;
|
|
41
|
+
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
if (current === null || current === undefined) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
current = current[key as keyof typeof current];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface OpenWindowOptions {
|
|
2
|
+
noopener?: boolean;
|
|
3
|
+
noreferrer?: boolean;
|
|
4
|
+
target?: '_blank' | '_parent' | '_self' | '_top' | (string & {});
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 新窗口打开URL。
|
|
9
|
+
*
|
|
10
|
+
* @param url - 需要打开的网址。
|
|
11
|
+
* @param options - 打开窗口的选项。
|
|
12
|
+
*/
|
|
13
|
+
function openWindow(url: string, options: OpenWindowOptions = {}): void {
|
|
14
|
+
// 解构并设置默认值
|
|
15
|
+
const { noopener = true, noreferrer = true, target = '_blank' } = options;
|
|
16
|
+
|
|
17
|
+
// 基于选项创建特性字符串
|
|
18
|
+
const features = [noopener && 'noopener=yes', noreferrer && 'noreferrer=yes']
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(',');
|
|
21
|
+
|
|
22
|
+
// 打开窗口
|
|
23
|
+
window.open(url, target, features);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 在新窗口中打开路由。
|
|
28
|
+
* @param path
|
|
29
|
+
*/
|
|
30
|
+
function openRouteInNewWindow(path: string) {
|
|
31
|
+
const { hash, origin } = location;
|
|
32
|
+
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
33
|
+
const url = `${origin}${hash && !fullPath.includes('/#') ? '/#' : ''}${fullPath}`;
|
|
34
|
+
openWindow(url, { target: '_blank' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { openRouteInNewWindow, openWindow };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { RouteRecordRaw } from 'vue-router';
|
|
2
|
+
|
|
3
|
+
import type { MenuRecordRaw } from '@melt-js/libs-vben/typings';
|
|
4
|
+
|
|
5
|
+
import { acceptHMRUpdate, defineStore } from 'pinia';
|
|
6
|
+
|
|
7
|
+
type AccessToken = null | string;
|
|
8
|
+
|
|
9
|
+
interface AccessState {
|
|
10
|
+
/**
|
|
11
|
+
* 权限码
|
|
12
|
+
*/
|
|
13
|
+
accessCodes: string[];
|
|
14
|
+
/**
|
|
15
|
+
* 可访问的菜单列表
|
|
16
|
+
*/
|
|
17
|
+
accessMenus: MenuRecordRaw[];
|
|
18
|
+
/**
|
|
19
|
+
* 可访问的路由列表
|
|
20
|
+
*/
|
|
21
|
+
accessRoutes: RouteRecordRaw[];
|
|
22
|
+
/**
|
|
23
|
+
* 登录 accessToken
|
|
24
|
+
*/
|
|
25
|
+
accessToken: AccessToken;
|
|
26
|
+
/**
|
|
27
|
+
* 是否已经检查过权限
|
|
28
|
+
*/
|
|
29
|
+
isAccessChecked: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 是否锁屏状态
|
|
32
|
+
*/
|
|
33
|
+
isLockScreen: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* 锁屏密码
|
|
36
|
+
*/
|
|
37
|
+
lockScreenPassword?: string;
|
|
38
|
+
/**
|
|
39
|
+
* 登录是否过期
|
|
40
|
+
*/
|
|
41
|
+
loginExpired: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 登录 accessToken
|
|
44
|
+
*/
|
|
45
|
+
refreshToken: AccessToken;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @zh_CN 访问权限相关
|
|
50
|
+
*/
|
|
51
|
+
export const useAccessStore = defineStore('core-access', {
|
|
52
|
+
actions: {
|
|
53
|
+
getMenuByPath(path: string) {
|
|
54
|
+
function findMenu(
|
|
55
|
+
menus: MenuRecordRaw[],
|
|
56
|
+
path: string,
|
|
57
|
+
): MenuRecordRaw | undefined {
|
|
58
|
+
for (const menu of menus) {
|
|
59
|
+
if (menu.path === path) {
|
|
60
|
+
return menu;
|
|
61
|
+
}
|
|
62
|
+
if (menu.children) {
|
|
63
|
+
const matched = findMenu(menu.children, path);
|
|
64
|
+
if (matched) {
|
|
65
|
+
return matched;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return findMenu(this.accessMenus, path);
|
|
72
|
+
},
|
|
73
|
+
lockScreen(password: string) {
|
|
74
|
+
this.isLockScreen = true;
|
|
75
|
+
this.lockScreenPassword = password;
|
|
76
|
+
},
|
|
77
|
+
setAccessCodes(codes: string[]) {
|
|
78
|
+
this.accessCodes = codes;
|
|
79
|
+
},
|
|
80
|
+
setAccessMenus(menus: MenuRecordRaw[]) {
|
|
81
|
+
this.accessMenus = menus;
|
|
82
|
+
},
|
|
83
|
+
setAccessRoutes(routes: RouteRecordRaw[]) {
|
|
84
|
+
this.accessRoutes = routes;
|
|
85
|
+
},
|
|
86
|
+
setAccessToken(token: AccessToken) {
|
|
87
|
+
this.accessToken = token;
|
|
88
|
+
},
|
|
89
|
+
setIsAccessChecked(isAccessChecked: boolean) {
|
|
90
|
+
this.isAccessChecked = isAccessChecked;
|
|
91
|
+
},
|
|
92
|
+
setLoginExpired(loginExpired: boolean) {
|
|
93
|
+
this.loginExpired = loginExpired;
|
|
94
|
+
},
|
|
95
|
+
setRefreshToken(token: AccessToken) {
|
|
96
|
+
this.refreshToken = token;
|
|
97
|
+
},
|
|
98
|
+
unlockScreen() {
|
|
99
|
+
this.isLockScreen = false;
|
|
100
|
+
this.lockScreenPassword = undefined;
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
persist: {
|
|
104
|
+
// 持久化
|
|
105
|
+
pick: [
|
|
106
|
+
'accessToken',
|
|
107
|
+
'refreshToken',
|
|
108
|
+
'accessCodes',
|
|
109
|
+
'isLockScreen',
|
|
110
|
+
'lockScreenPassword',
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
state: (): AccessState => ({
|
|
114
|
+
accessCodes: [],
|
|
115
|
+
accessMenus: [],
|
|
116
|
+
accessRoutes: [],
|
|
117
|
+
accessToken: null,
|
|
118
|
+
isAccessChecked: false,
|
|
119
|
+
isLockScreen: false,
|
|
120
|
+
lockScreenPassword: undefined,
|
|
121
|
+
loginExpired: false,
|
|
122
|
+
refreshToken: null,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// 解决热更新问题
|
|
127
|
+
const hot = import.meta.hot;
|
|
128
|
+
if (hot) {
|
|
129
|
+
hot.accept(acceptHMRUpdate(useAccessStore, hot));
|
|
130
|
+
}
|