@peng_kai/kit 0.2.25 → 0.2.26
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/.vscode/settings.json +4 -1
- package/admin/components/permission-tree/PermissionTree.vue +224 -0
- package/admin/components/permission-tree/index.ts +1 -0
- package/antd/components/InputNumberRange.vue +3 -3
- package/antd/hooks/useAntdTheme.ts +1 -1
- package/package.json +26 -26
- package/utils/LocaleManager.ts +2 -1
- package/utils/locale/LocaleManager.ts +126 -0
- package/utils/locale/helpers.ts +47 -0
- package/utils/locale/index.ts +2 -0
package/.vscode/settings.json
CHANGED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Checkbox } from 'ant-design-vue';
|
|
3
|
+
import { computed, triggerRef, watch } from 'vue';
|
|
4
|
+
|
|
5
|
+
interface TTree {
|
|
6
|
+
id: number
|
|
7
|
+
label: string
|
|
8
|
+
checked: boolean
|
|
9
|
+
children?: TTree[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function treeToIds(tree: TTree[]) {
|
|
13
|
+
const ids = [] as number[];
|
|
14
|
+
|
|
15
|
+
const isCheckedFn = (node: TTree) => {
|
|
16
|
+
node.checked && ids.push(node.id);
|
|
17
|
+
node.children?.length && node.children.forEach(isCheckedFn);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
tree.forEach(isCheckedFn);
|
|
21
|
+
|
|
22
|
+
return ids;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function treeToTable(tree: TTree[]) {
|
|
26
|
+
interface TD {
|
|
27
|
+
id: number
|
|
28
|
+
label: string
|
|
29
|
+
span: number
|
|
30
|
+
}
|
|
31
|
+
const result: [(TD | undefined), (TD | undefined), TD[]][] = [];
|
|
32
|
+
const getTd = (node: any): TD => ({
|
|
33
|
+
id: node.id,
|
|
34
|
+
label: node.label,
|
|
35
|
+
span: 1,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
for (const mItem of tree) {
|
|
39
|
+
let m: TD | undefined = getTd(mItem);
|
|
40
|
+
|
|
41
|
+
if (m)
|
|
42
|
+
m.span = mItem?.children?.length ?? 1;
|
|
43
|
+
|
|
44
|
+
for (const pItem of mItem?.children ?? []) {
|
|
45
|
+
const p: TD | undefined = getTd(pItem);
|
|
46
|
+
const fList = (pItem?.children ?? []).map(getTd);
|
|
47
|
+
|
|
48
|
+
result.push([m, p, fList]);
|
|
49
|
+
m = m && undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface TNodeState {
|
|
57
|
+
checked: boolean
|
|
58
|
+
indet: boolean
|
|
59
|
+
parents: number[]
|
|
60
|
+
children: number[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function treeToStates(tree: TTree[]) {
|
|
64
|
+
const states: Record<number, TNodeState> = {};
|
|
65
|
+
|
|
66
|
+
const assId = (node: any) => {
|
|
67
|
+
const parentsNode = findAllParents(tree, node.id) ?? [];
|
|
68
|
+
const childrenNode = findAllChildren(parentsNode[parentsNode.length - 1]);
|
|
69
|
+
states[node.id] = {
|
|
70
|
+
indet: false,
|
|
71
|
+
checked: false,
|
|
72
|
+
parents: parentsNode.map(node => node.id).filter(id => id !== node.id),
|
|
73
|
+
children: childrenNode.map(node => node.id).filter(id => id !== node.id),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (node.children?.length)
|
|
77
|
+
node.children.forEach(assId);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
tree.forEach(assId);
|
|
81
|
+
|
|
82
|
+
// for (const id in ids) {
|
|
83
|
+
// const state = states[id];
|
|
84
|
+
// state.indet = isIndet(state.children.map(cid => states[cid].checked));
|
|
85
|
+
// state.children.forEach(cid => states[cid].checked = state.checked);
|
|
86
|
+
// [...state.parents].reverse().forEach((pid) => {
|
|
87
|
+
// const checks = states[pid].children.map(cid => states[cid].checked);
|
|
88
|
+
// state.indet = isIndet(checks);
|
|
89
|
+
// state.checked = checks.every(checked => checked);
|
|
90
|
+
// });
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
// Object.keys(states)
|
|
94
|
+
// [...ids].forEach((id) => {
|
|
95
|
+
// const state = states[id];
|
|
96
|
+
// state.checked = true;
|
|
97
|
+
// state.children.forEach((cid) => {
|
|
98
|
+
// states[cid].checked = true;
|
|
99
|
+
// states[cid].indet = false;
|
|
100
|
+
// });
|
|
101
|
+
// });
|
|
102
|
+
|
|
103
|
+
return states;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function updateStates(states: ReturnType<typeof treeToStates>, id: number, checked: boolean) {
|
|
107
|
+
const state = states[id];
|
|
108
|
+
state.checked = checked;
|
|
109
|
+
state.indet = false;
|
|
110
|
+
state.children.forEach((cid) => {
|
|
111
|
+
states[cid].checked = checked;
|
|
112
|
+
states[cid].indet = false;
|
|
113
|
+
});
|
|
114
|
+
[...state.parents].reverse().forEach((pid) => {
|
|
115
|
+
const checks = states[pid].children.map(pid => states[pid].checked);
|
|
116
|
+
states[pid].indet = isIndet(checks);
|
|
117
|
+
states[pid].checked = checks.every(checked => checked);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function statesToIds(states: ReturnType<typeof treeToStates>) {
|
|
122
|
+
return Object.keys(states).map(Number).filter(id => states[id].checked);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function findAllParents(tree: any[], id: number, path: any[] = []): any[] | undefined {
|
|
126
|
+
for (const item of tree) {
|
|
127
|
+
if (item.id === id)
|
|
128
|
+
return [...path, item];
|
|
129
|
+
|
|
130
|
+
if (item.children) {
|
|
131
|
+
const found = findAllParents(item?.children, id, [...path, item]);
|
|
132
|
+
if (found)
|
|
133
|
+
return found;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function findAllChildren(node: any): any[] {
|
|
139
|
+
const stack = [node];
|
|
140
|
+
const children = [];
|
|
141
|
+
|
|
142
|
+
while (stack.length) {
|
|
143
|
+
const item = stack.pop();
|
|
144
|
+
children.push(item);
|
|
145
|
+
|
|
146
|
+
if (item.children)
|
|
147
|
+
stack.push(...item.children);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return children;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isIndet(arr: boolean[]) {
|
|
154
|
+
return arr.includes(true) && arr.includes(false);
|
|
155
|
+
}
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<script setup lang="ts">
|
|
159
|
+
const props = defineProps<{
|
|
160
|
+
treeData: TTree[]
|
|
161
|
+
}>();
|
|
162
|
+
const ids = defineModel<number[]>('value', { default: [] });
|
|
163
|
+
const tableData = computed(() => treeToTable(props.treeData));
|
|
164
|
+
const stateMap = computed(() => treeToStates(props.treeData));
|
|
165
|
+
|
|
166
|
+
watch(ids, (newV, oldV) => {
|
|
167
|
+
if (newV === oldV)
|
|
168
|
+
return;
|
|
169
|
+
|
|
170
|
+
newV.forEach(id => updateStates(stateMap.value, id, true));
|
|
171
|
+
triggerRef(stateMap);
|
|
172
|
+
}, { immediate: true });
|
|
173
|
+
|
|
174
|
+
function onCheckChange(id: number) {
|
|
175
|
+
const checked = !ids.value.includes(id);
|
|
176
|
+
updateStates(stateMap.value, id, checked);
|
|
177
|
+
ids.value = statesToIds(stateMap.value);
|
|
178
|
+
}
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<table class="w-full border-collapse border-$antd-colorBorderSecondary" border>
|
|
183
|
+
<tr v-for="(row, ri) of tableData" :key="ri">
|
|
184
|
+
<!-- 模块 -->
|
|
185
|
+
<td v-if="row[0]" class="w-[6em] px-[1em] py-0.5em" :rowspan="row[0].span">
|
|
186
|
+
<Checkbox
|
|
187
|
+
:checked="stateMap[row[0].id].indet ? false : stateMap[row[0].id].checked"
|
|
188
|
+
:indeterminate="stateMap[row[0].id].indet" @change="onCheckChange(row[0].id)"
|
|
189
|
+
>
|
|
190
|
+
{{ row[0].label }}{{ row[0].id }}
|
|
191
|
+
</Checkbox>
|
|
192
|
+
</td>
|
|
193
|
+
<!-- 页面 -->
|
|
194
|
+
<td v-if="row[1]" class="w-[9em] px-[1em] py-0.5em">
|
|
195
|
+
<Checkbox
|
|
196
|
+
:checked="stateMap[row[1].id].indet ? false : stateMap[row[1].id].checked"
|
|
197
|
+
:indeterminate="stateMap[row[1].id].indet" @change="onCheckChange(row[1].id)"
|
|
198
|
+
>
|
|
199
|
+
{{ row[1].label }}
|
|
200
|
+
</Checkbox>
|
|
201
|
+
</td>
|
|
202
|
+
<!-- 功能 -->
|
|
203
|
+
<td v-if="row[2]" class="px-[1em] py-0.5em">
|
|
204
|
+
<div class="feat-list">
|
|
205
|
+
<Checkbox
|
|
206
|
+
v-for="fItem of row[2]" :key="fItem?.id" :checked="stateMap[fItem.id].checked"
|
|
207
|
+
@change="onCheckChange(fItem.id)"
|
|
208
|
+
>
|
|
209
|
+
{{ fItem.label }}
|
|
210
|
+
</Checkbox>
|
|
211
|
+
</div>
|
|
212
|
+
</td>
|
|
213
|
+
</tr>
|
|
214
|
+
</table>
|
|
215
|
+
</template>
|
|
216
|
+
|
|
217
|
+
<style scoped lang="scss">
|
|
218
|
+
.feat-list {
|
|
219
|
+
display: grid;
|
|
220
|
+
grid-template-columns: repeat(auto-fill, minmax(10em, 1fr));
|
|
221
|
+
align-items: center;
|
|
222
|
+
gap: 4px 1em;
|
|
223
|
+
}
|
|
224
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PermissionTreeCheckbox, treeToIds } from './PermissionTree.vue';
|
|
@@ -40,13 +40,13 @@ function onBlur() {
|
|
|
40
40
|
<template>
|
|
41
41
|
<div class="flex items-center">
|
|
42
42
|
<AInputNumber
|
|
43
|
-
v-model:value="rangeValue[0]" class="w-full" :min="props.min"
|
|
43
|
+
v-model:value="rangeValue[0]" class="w-full block" :min="props.min"
|
|
44
44
|
:max="props.max" :placeholder="props.placeholder?.[0]"
|
|
45
45
|
@blur="onBlur"
|
|
46
46
|
/>
|
|
47
|
-
<
|
|
47
|
+
<div class="w-4 h-1px bg-current mx-1" />
|
|
48
48
|
<AInputNumber
|
|
49
|
-
v-model:value="rangeValue[1]" class="w-full" :min="props.min"
|
|
49
|
+
v-model:value="rangeValue[1]" class="w-full block" :min="props.min"
|
|
50
50
|
:max="props.max" :placeholder="props.placeholder?.[1]"
|
|
51
51
|
@blur="onBlur"
|
|
52
52
|
/>
|
|
@@ -18,7 +18,7 @@ export function useAntdTheme(mode: Ref<string>, config: Ref<Record<string, any>>
|
|
|
18
18
|
const screenXXL = Number.parseFloat(_config.breakpoints.desktop);
|
|
19
19
|
const screenXXXL = Number.parseFloat(_config.breakpoints.desktop1k);
|
|
20
20
|
const token: ThemeConfig['token'] = {
|
|
21
|
-
...algorithm({ ...theme.defaultSeed, colorPrimary: _config.colors.primary.DEFAULT }),
|
|
21
|
+
...algorithm({ ...theme.defaultSeed, colorPrimary: _config.colors.primary.DEFAULT, colorInfo: '#17B2FF' }),
|
|
22
22
|
borderRadius: 4,
|
|
23
23
|
screenXS,
|
|
24
24
|
screenXSMin: screenXS,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peng_kai/kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.26",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
7
7
|
"license": "ISC",
|
|
@@ -13,42 +13,42 @@
|
|
|
13
13
|
"lint:fix": "eslint . --fix"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"ant-design-vue": "4.2.
|
|
17
|
-
"vue": "3.4.
|
|
18
|
-
"vue-router": "4.
|
|
16
|
+
"ant-design-vue": "4.2.3",
|
|
17
|
+
"vue": "3.4.31",
|
|
18
|
+
"vue-router": "4.4.0"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@aws-sdk/client-s3": "^3.
|
|
22
|
-
"@aws-sdk/lib-storage": "^3.
|
|
23
|
-
"@babel/generator": "^7.24.
|
|
24
|
-
"@babel/parser": "^7.24.
|
|
25
|
-
"@babel/traverse": "^7.24.
|
|
26
|
-
"@babel/types": "^7.24.
|
|
21
|
+
"@aws-sdk/client-s3": "^3.609.0",
|
|
22
|
+
"@aws-sdk/lib-storage": "^3.609.0",
|
|
23
|
+
"@babel/generator": "^7.24.7",
|
|
24
|
+
"@babel/parser": "^7.24.7",
|
|
25
|
+
"@babel/traverse": "^7.24.7",
|
|
26
|
+
"@babel/types": "^7.24.7",
|
|
27
27
|
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
|
28
|
-
"@fingerprintjs/fingerprintjs": "^4.
|
|
29
|
-
"@tanstack/vue-query": "^5.
|
|
30
|
-
"@vueuse/components": "^10.
|
|
31
|
-
"@vueuse/core": "^10.
|
|
32
|
-
"@vueuse/router": "^10.
|
|
28
|
+
"@fingerprintjs/fingerprintjs": "^4.4.1",
|
|
29
|
+
"@tanstack/vue-query": "^5.49.1",
|
|
30
|
+
"@vueuse/components": "^10.11.0",
|
|
31
|
+
"@vueuse/core": "^10.11.0",
|
|
32
|
+
"@vueuse/router": "^10.11.0",
|
|
33
33
|
"a-calc": "^1.3.12",
|
|
34
|
-
"ant-design-vue": "^4.2.
|
|
34
|
+
"ant-design-vue": "^4.2.3",
|
|
35
35
|
"archiver": "^7.0.1",
|
|
36
|
-
"axios": "^1.
|
|
36
|
+
"axios": "^1.7.2",
|
|
37
37
|
"bignumber.js": "^9.1.2",
|
|
38
38
|
"chokidar": "^3.6.0",
|
|
39
39
|
"crypto-es": "^2.1.0",
|
|
40
|
-
"dayjs": "^1.11.
|
|
40
|
+
"dayjs": "^1.11.11",
|
|
41
41
|
"echarts": "^5.4.3",
|
|
42
|
-
"execa": "^
|
|
42
|
+
"execa": "^9.3.0",
|
|
43
43
|
"fast-glob": "^3.3.2",
|
|
44
44
|
"localstorage-slim": "^2.7.1",
|
|
45
45
|
"lodash-es": "^4.17.21",
|
|
46
46
|
"nprogress": "^0.2.0",
|
|
47
47
|
"pinia": "^2.1.7",
|
|
48
|
-
"tsx": "^4.
|
|
49
|
-
"vue": "^3.4.
|
|
48
|
+
"tsx": "^4.16.00",
|
|
49
|
+
"vue": "^3.4.31",
|
|
50
50
|
"vue-i18n": "^9.13.1",
|
|
51
|
-
"vue-router": "^4.
|
|
51
|
+
"vue-router": "^4.4.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@ckeditor/ckeditor5-adapter-ckfinder": "^41.1.0",
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
"@types/archiver": "^6.0.2",
|
|
87
87
|
"@types/crypto-js": "^4.2.2",
|
|
88
88
|
"@types/lodash-es": "^4.17.12",
|
|
89
|
-
"@types/node": "^18.19.
|
|
89
|
+
"@types/node": "^18.19.39",
|
|
90
90
|
"@types/nprogress": "^0.2.3",
|
|
91
|
-
"type-fest": "^4.
|
|
92
|
-
"typescript": "^5.
|
|
93
|
-
"vue-component-type-helpers": "^2.0.
|
|
91
|
+
"type-fest": "^4.21.0",
|
|
92
|
+
"typescript": "^5.5.3",
|
|
93
|
+
"vue-component-type-helpers": "^2.0.24"
|
|
94
94
|
}
|
|
95
95
|
}
|
package/utils/LocaleManager.ts
CHANGED
|
@@ -92,7 +92,8 @@ export class LocaleManager {
|
|
|
92
92
|
|
|
93
93
|
const message = await this.messageLoaders[locale]?.().catch(() => undefined);
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
// 由于是异步加载,所以需要再判断是否已经加载过
|
|
96
|
+
if (message && !this.localesLoaded.includes(locale))
|
|
96
97
|
this.localesLoaded.push(locale);
|
|
97
98
|
|
|
98
99
|
return message;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { mapKeys } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
interface ILocaleMeta {
|
|
4
|
+
label: string
|
|
5
|
+
codes: string[]
|
|
6
|
+
icon?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ILoaders {
|
|
10
|
+
meta: Record<string, ILocaleMeta>
|
|
11
|
+
message: Record<string, () => Promise<Record<string, string>>>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const metaPathRE = /\/([-\w]*)\/(meta)\.ts$/;
|
|
15
|
+
const messagePathRE = /\/([-\w]*)\/(index)\.ts$/;
|
|
16
|
+
|
|
17
|
+
export class LocaleManager {
|
|
18
|
+
public localesLoaded: string[] = [];
|
|
19
|
+
|
|
20
|
+
private metaLoaders: ILoaders['meta'];
|
|
21
|
+
private messageLoaders: ILoaders['message'];
|
|
22
|
+
private localeStorageKey = 'LOCALE';
|
|
23
|
+
private _locale = '';
|
|
24
|
+
|
|
25
|
+
public constructor(loaders: ILoaders) {
|
|
26
|
+
this.metaLoaders = mapKeys(loaders.meta, (_, path) => path.match(metaPathRE)?.[1] ?? path);
|
|
27
|
+
this.messageLoaders = mapKeys(loaders.message, (_, path) => path.match(messagePathRE)?.[1] ?? path);
|
|
28
|
+
this._locale = this.defaultLocale;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 当前区域 */
|
|
32
|
+
public get locale() {
|
|
33
|
+
return this._locale;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 当前区域 */
|
|
37
|
+
public set locale(value: string) {
|
|
38
|
+
if (this.localesAvailable.includes(value)) {
|
|
39
|
+
document.documentElement.lang = value;
|
|
40
|
+
this.setStorageLocale(value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 可用的区域列表 */
|
|
45
|
+
public get localesAvailable() {
|
|
46
|
+
return Object.keys(this.metaLoaders);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 所有区域元数据 */
|
|
50
|
+
public get localeMetas() {
|
|
51
|
+
return this.metaLoaders;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 获取默认区域 */
|
|
55
|
+
public get defaultLocale() {
|
|
56
|
+
const searchParams = new URLSearchParams(location.search);
|
|
57
|
+
// 获取区域的优先级
|
|
58
|
+
const localeTargets = [searchParams.get('locale'), searchParams.get('lang'), this.getStorageLocale(), navigator.language]
|
|
59
|
+
.filter(locale => !!locale)
|
|
60
|
+
.map(locale => locale!.replace('_', '-'));
|
|
61
|
+
const codeMatrix = this.localesAvailable.map(locale => [locale, ...this.localeMetas[locale].codes]);
|
|
62
|
+
let localeFinal = '';
|
|
63
|
+
|
|
64
|
+
for (const target of localeTargets) {
|
|
65
|
+
for (const codes of codeMatrix) {
|
|
66
|
+
const matched = codes.some(code => code.toUpperCase().startsWith(target.toUpperCase()));
|
|
67
|
+
|
|
68
|
+
if (matched) {
|
|
69
|
+
localeFinal = codes[0];
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (localeFinal)
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
localeFinal = localeFinal || 'en-US';
|
|
79
|
+
this.clearUrlLocale();
|
|
80
|
+
|
|
81
|
+
return localeFinal;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 加载区域消息
|
|
86
|
+
* @param locale 区域
|
|
87
|
+
* @returns 返回加载的消息,如果加载失败则返回 undefined
|
|
88
|
+
*/
|
|
89
|
+
public async loadLocaleMessage(locale: string) {
|
|
90
|
+
if (!this.localesAvailable.includes(locale))
|
|
91
|
+
return;
|
|
92
|
+
|
|
93
|
+
const message = await this.messageLoaders[locale]?.().catch(() => undefined);
|
|
94
|
+
|
|
95
|
+
// 由于是异步加载,所以需要再判断是否已经加载过
|
|
96
|
+
if (message && !this.localesLoaded.includes(locale))
|
|
97
|
+
this.localesLoaded.push(locale);
|
|
98
|
+
|
|
99
|
+
return message;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 定义区域元数据
|
|
104
|
+
* @param meta 区域元数据对象
|
|
105
|
+
* @returns 区域元数据对象
|
|
106
|
+
*/
|
|
107
|
+
public static defineMeta(meta: ILocaleMeta) {
|
|
108
|
+
return meta;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private clearUrlLocale() {
|
|
112
|
+
const url = new URL(location.href);
|
|
113
|
+
url.searchParams.delete('locale');
|
|
114
|
+
url.searchParams.delete('lang');
|
|
115
|
+
|
|
116
|
+
history.replaceState(null, '', url.toString());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private getStorageLocale() {
|
|
120
|
+
return localStorage.getItem(this.localeStorageKey);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private setStorageLocale(locale: string) {
|
|
124
|
+
return localStorage.setItem(this.localeStorageKey, locale);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { omitBy } from '../../libs/lodash-es';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 标准化区域代码
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeLocaleCode(code: string) {
|
|
7
|
+
// 移除所有空白字符并转换为小写
|
|
8
|
+
const normalized = code.replace(/\s/g, '').toLowerCase();
|
|
9
|
+
|
|
10
|
+
// 处理特殊情况:中文
|
|
11
|
+
if (normalized === 'zh-cn' || normalized === 'zh-hans')
|
|
12
|
+
return 'zh-CN';
|
|
13
|
+
|
|
14
|
+
if (normalized === 'zh-tw' || normalized === 'zh-hant')
|
|
15
|
+
return 'zh-TW';
|
|
16
|
+
|
|
17
|
+
// 分割语言代码和国家/地区代码
|
|
18
|
+
const [language, country] = normalized.split(/[-_]/);
|
|
19
|
+
|
|
20
|
+
// 如果存在国家/地区代码,将其转换为大写
|
|
21
|
+
if (country)
|
|
22
|
+
return `${language}-${country.toUpperCase()}`;
|
|
23
|
+
|
|
24
|
+
// 如果只有语言代码,直接返回
|
|
25
|
+
return language;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 从模块中排除指定的语言包
|
|
30
|
+
*/
|
|
31
|
+
export function omitLocale(modules: Record<string, any>, excludes: string[] = []) {
|
|
32
|
+
return omitBy(modules, (_, path) => excludes.some(locale => path.includes(locale)));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 加密 JSON 消息
|
|
37
|
+
*/
|
|
38
|
+
export function encryptJsonMessage(message: Record<string, any>) {
|
|
39
|
+
return encodeURIComponent(JSON.stringify(message));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 解密 JSON 消息
|
|
44
|
+
*/
|
|
45
|
+
export function decryptJsonMessage(message: string) {
|
|
46
|
+
return JSON.parse(decodeURIComponent(message));
|
|
47
|
+
}
|