@peng_kai/kit 0.2.0-beta.9 → 0.2.1-beta.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.
Files changed (43) hide show
  1. package/admin/adminPlugin.ts +11 -1
  2. package/admin/components/filter/src/FilterReset.vue +12 -9
  3. package/admin/components/rich-text/index.ts +1 -1
  4. package/admin/components/rich-text/src/RichText.new.vue +164 -0
  5. package/admin/components/rich-text/src/RichText.vue +3 -6
  6. package/admin/components/rich-text/src/editorConfig.ts +126 -0
  7. package/admin/components/rich-text/src/imageUploader.ts +20 -18
  8. package/admin/components/scroll-nav/src/ScrollNav.vue +1 -1
  9. package/admin/components/settings/index.ts +1 -0
  10. package/admin/components/settings/src/Settings.vue +333 -0
  11. package/admin/components/upload/index.ts +1 -0
  12. package/admin/components/upload/src/PictureCardUpload.vue +1 -1
  13. package/admin/components/upload/src/helpers.ts +37 -0
  14. package/admin/defines/route/defineRoute.ts +2 -4
  15. package/admin/defines/route/getRoutes.ts +4 -4
  16. package/admin/defines/route/index.ts +1 -1
  17. package/admin/defines/route-guard/defineRouteGuard.ts +1 -4
  18. package/admin/defines/route-guard/getRouteGuards.ts +5 -7
  19. package/admin/defines/startup/defineStartup.ts +1 -3
  20. package/admin/defines/startup/runStartup.ts +4 -6
  21. package/admin/layout/large/Breadcrumb.vue +2 -3
  22. package/admin/layout/large/Content.vue +2 -2
  23. package/admin/layout/large/Menu.vue +2 -2
  24. package/admin/layout/large/PageTab.vue +2 -2
  25. package/admin/permission/routerGuard.ts +1 -1
  26. package/admin/permission/vuePlugin.ts +1 -0
  27. package/admin/stores/createUsePageStore.ts +1 -3
  28. package/admin/styles/classCover.scss +123 -3
  29. package/admin/styles/index.scss +18 -12
  30. package/antd/hooks/useAntdForm.ts +1 -1
  31. package/antd/hooks/useAntdModal.ts +4 -1
  32. package/antd/hooks/useAntdTable.ts +2 -1
  33. package/libs/dayjs.ts +6 -6
  34. package/libs/echarts.ts +1 -1
  35. package/libs/vue-i18n.ts +21 -0
  36. package/package.json +36 -35
  37. package/request/interceptors/returnResultType.ts +3 -3
  38. package/request/interceptors/toLogin.ts +27 -10
  39. package/request/request.ts +0 -3
  40. package/request/type.d.ts +4 -4
  41. package/utils/LocaleManager.ts +125 -0
  42. package/vite/index.mjs +34 -8
  43. package/vue/components/echarts/src/ECharts.vue +6 -3
@@ -173,17 +173,137 @@
173
173
  }
174
174
  }
175
175
 
176
+ // ACard 组件的 actions 右对齐
177
+ .ant-card.antd-cover__actions-right-align {
178
+ .ant-card-actions {
179
+ justify-content: flex-end !important;
180
+ padding: 0 24px;
181
+
182
+ > li {
183
+ width: auto !important;
184
+ border-inline-end: none !important;
185
+
186
+ &:not(:last-child) {
187
+ margin-right: 8px;
188
+ }
189
+ }
190
+ }
191
+ }
192
+
176
193
  // 查询器表单基本样式
177
194
  .ant-form.ant-form__filter {
178
195
  display: flex;
179
196
  flex-wrap: wrap;
180
197
  --uno: 'gap-4 mb-4';
181
198
 
199
+ .ant-form-item-label {
200
+ line-height: 1.2em;
201
+ width: 4.8em;
202
+ text-align: start;
203
+ overflow: unset;
204
+ white-space: unset;
205
+
206
+ > label::after {
207
+ // display: none;
208
+ content: "";
209
+ }
210
+ }
211
+
182
212
  .ant-form-item {
183
213
  margin-bottom: 0;
184
- min-width: 220px;
185
- width: 250px;
186
- max-width: 280px;
214
+ min-width: 250px;
215
+ width: 300px;
216
+ max-width: 380px;
187
217
  flex: 1 1 auto;
188
218
  }
189
219
  }
220
+
221
+ // Modal 组件中的 Card 组件样式(为了和原 Modal 组件一样)
222
+ .ant-card.antd-cover__card-in-modal {
223
+ --padding-size: 22px;
224
+
225
+ pointer-events: all;
226
+
227
+ @media bp-lt-mobilel {
228
+ --padding-size: 16px;
229
+ }
230
+
231
+ .ant-card-head {
232
+ min-height: 56px;
233
+ padding: 0 var(--padding-size);
234
+
235
+ @media bp-lt-mobilel {
236
+ min-height: 51px;
237
+ }
238
+ }
239
+
240
+ .ant-card-body {
241
+ height: var(--body-height, auto);
242
+ min-height: var(--min-body-height, auto);
243
+ max-height: var(--max-body-height, auto);
244
+ padding: var(--padding-size);
245
+ overflow-y: auto;
246
+ }
247
+
248
+ .ant-card-extra .close-btn {
249
+ --icon-color: var(--antd-colorTextTertiary);
250
+ --bg-color: transparent;
251
+
252
+ position: relative;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ width: 22px;
257
+ height: 22px;
258
+ padding: 0;
259
+ font-size: 18px;
260
+ cursor: pointer;
261
+ background-color: var(--bg-color);
262
+ border: 0;
263
+ border-radius: 4px;
264
+ transition: all 100ms;
265
+
266
+ &::before,
267
+ &::after {
268
+ position: absolute;
269
+ top: 50%;
270
+ left: 50%;
271
+ display: block;
272
+ width: 70%;
273
+ height: 1.5px;
274
+ content: '';
275
+ background-color: var(--icon-color);
276
+ }
277
+
278
+ &::before {
279
+ transform: translate(-50%, -50%) rotateZ(45deg);
280
+ }
281
+
282
+ &::after {
283
+ transform: translate(-50%, -50%) rotateZ(-45deg);
284
+ }
285
+
286
+ &:hover {
287
+ --icon-color: var(--antd-colorText);
288
+ --bg-color: var(--antd-colorFillSecondary);
289
+ }
290
+ }
291
+
292
+ .ant-card-actions {
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: flex-end !important;
296
+ min-height: 56px;
297
+ padding: 0 var(--padding-size);
298
+
299
+ > li {
300
+ width: auto !important;
301
+ margin: 0;
302
+ border-inline-end: none !important;
303
+
304
+ &:not(:last-child) {
305
+ margin-right: 8px;
306
+ }
307
+ }
308
+ }
309
+ }
@@ -2,26 +2,28 @@
2
2
  @import './globalCover.scss';
3
3
 
4
4
  @media (pointer: fine) {
5
- * {
6
- scrollbar-color: rgb(0 0 0 / 50%) transparent;
7
- scrollbar-width: thin;
8
5
 
9
- /* overflow-y: overlay; */
6
+ *::-webkit-scrollbar-thumb {
7
+ border-radius: 10px;
8
+ background-color: rgb(133 133 133 / 20%);
9
+ }
10
+
11
+ *::-webkit-scrollbar-thumb:hover {
12
+ background-color: rgb(133 133 133 / 60%);
10
13
  }
11
14
 
12
- ::-webkit-scrollbar {
15
+ *::-webkit-scrollbar {
13
16
  width: 6px;
14
17
  height: 6px;
15
18
  background-color: transparent;
16
19
  }
17
20
 
18
- ::-webkit-scrollbar-thumb {
19
- border-radius: 10px;
20
- background-color: rgb(133 133 133 / 20%);
21
- }
22
-
23
- ::-webkit-scrollbar-thumb:hover {
24
- background-color: rgb(133 133 133 / 60%);
21
+ /* 不支持`::-webkit-scrollbar-*`的浏览器 */
22
+ @supports not selector(::-webkit-scrollbar) {
23
+ * {
24
+ scrollbar-color: rgb(0 0 0 / 50%) transparent;
25
+ scrollbar-width: thin;
26
+ }
25
27
  }
26
28
  }
27
29
 
@@ -31,4 +33,8 @@
31
33
 
32
34
  :root.dark {
33
35
  color-scheme: dark;
36
+ }
37
+
38
+ .operation {
39
+
34
40
  }
@@ -25,7 +25,7 @@ interface Options<S, TS> {
25
25
  transform?: (state: S) => TS
26
26
  }
27
27
 
28
- function useAntdForm<S extends Record<string, any>, TS = S>(schemas: MaybeRefOrGetter<SchemaConfig<S>>, options?: Options<S, TS>) {
28
+ function useAntdForm<S extends Record<string, unknown>, TS = S>(schemas: MaybeRefOrGetter<SchemaConfig<S>>, options?: Options<S, TS>) {
29
29
  const schemasR = toRef(schemas);
30
30
 
31
31
  const state = ref({} as S);
@@ -33,7 +33,10 @@ export function useAntdModal<Comp extends Component>(
33
33
  ...defaultModalProps,
34
34
  ...isProxy(modalProps) ? toRefs(modalProps) : modalProps,
35
35
  confirmLoading: toRef(() => (compRef as any)?.loading),
36
- onOk: () => (compRef as any)?.confirm?.(),
36
+ onOk: (e: MouseEvent) => {
37
+ (compRef as any)?.confirm?.(e);
38
+ modalProps.onOk?.(e);
39
+ },
37
40
  });
38
41
  const modalSlotName = _comp.type === 'body' ? 'default' : 'modalRender';
39
42
 
@@ -55,9 +55,10 @@ export function useAntdTable<
55
55
  }
56
56
  };
57
57
  const defineColumns = (columnsGetter: () => LocalColumnsType) => computed(columnsGetter);
58
- const defineRowSelection = (rowSelectionGetter: () => LocalTableRowSelection) => {
58
+ const defineRowSelection = (rowSelectionGetter: () => LocalTableRowSelection = () => ({})) => {
59
59
  const rowSelection = reactive(rowSelectionGetter());
60
60
 
61
+ rowSelection.preserveSelectedRowKeys ??= true;
61
62
  rowSelection.selectedRowKeys ??= [];
62
63
  rowSelection.onChange ??= keys => rowSelection.selectedRowKeys = keys;
63
64
 
package/libs/dayjs.ts CHANGED
@@ -1,11 +1,11 @@
1
- import dayjs from 'dayjs';
1
+ import dayjs from 'dayjs/esm';
2
2
 
3
- import relativeTime from 'dayjs/plugin/relativeTime';
4
- import 'dayjs/locale/zh';
5
- import 'dayjs/locale/en';
3
+ import relativeTime from 'dayjs/esm/plugin/relativeTime';
4
+ import 'dayjs/esm/locale/zh';
5
+ import 'dayjs/esm/locale/en';
6
6
 
7
- export { isDayjs, unix, locale, extend } from 'dayjs';
8
- export type { Dayjs, PluginFunc, UnitType, UnitTypeLong, UnitTypeLongPlural, UnitTypeShort, QUnitType, ConfigType, ConfigTypeMap, OpUnitType, OptionType, ManipulateType } from 'dayjs';
7
+ export { isDayjs, unix, locale, extend } from 'dayjs/esm';
8
+ export type { Dayjs, PluginFunc, UnitType, UnitTypeLong, UnitTypeLongPlural, UnitTypeShort, QUnitType, ConfigType, ConfigTypeMap, OpUnitType, OptionType, ManipulateType } from 'dayjs/esm';
9
9
  export default dayjs;
10
10
 
11
11
  dayjs.locale('zh');
package/libs/echarts.ts CHANGED
@@ -1,4 +1,4 @@
1
- import echarts from 'echarts';
1
+ import * as echarts from 'echarts';
2
2
 
3
3
  export type * from 'echarts/types/dist/echarts';
4
4
 
@@ -0,0 +1,21 @@
1
+ import type { I18nOptions } from 'vue-i18n';
2
+
3
+ export * from 'vue-i18n';
4
+
5
+ export type TDatetimeStyles = Record<
6
+ 'date' | 'time' | 'full' | 'mdhms' | 'mdhm',
7
+ NonNullable<I18nOptions['datetimeFormats']>[string][string]
8
+ >;
9
+
10
+ export const presetDatetimeStyles: TDatetimeStyles = {
11
+ /** 年/月/日 */
12
+ date: { year: 'numeric', month: '2-digit', day: '2-digit' },
13
+ /** 时:分:秒 */
14
+ time: { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
15
+ /** 年/月/日 时:分:秒 */
16
+ full: { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
17
+ /** 月/日 时:分:秒 */
18
+ mdhms: { month: '2-digit', day: '2-digit', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false },
19
+ /** 月/日 时:分 */
20
+ mdhm: { month: '2-digit', day: '2-digit', hour: 'numeric', minute: 'numeric', hour12: false },
21
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peng_kai/kit",
3
3
  "type": "module",
4
- "version": "0.2.0-beta.9",
4
+ "version": "0.2.1-beta.1",
5
5
  "description": "",
6
6
  "author": "",
7
7
  "license": "ISC",
@@ -14,16 +14,41 @@
14
14
  },
15
15
  "peerDependencies": {
16
16
  "ant-design-vue": "4.1.2",
17
- "vue": "3.4.19",
18
- "vue-router": "4.2.5"
17
+ "vue": "3.4.21",
18
+ "vue-router": "4.3.0"
19
19
  },
20
20
  "dependencies": {
21
- "@aws-sdk/client-s3": "^3.515.0",
22
- "@aws-sdk/lib-storage": "^3.515.0",
21
+ "@aws-sdk/client-s3": "^3.525.0",
22
+ "@aws-sdk/lib-storage": "^3.525.1",
23
23
  "@babel/generator": "^7.23.6",
24
- "@babel/parser": "^7.23.9",
25
- "@babel/traverse": "^7.23.9",
26
- "@babel/types": "^7.23.9",
24
+ "@babel/parser": "^7.24.0",
25
+ "@babel/traverse": "^7.24.0",
26
+ "@babel/types": "^7.24.0",
27
+ "@ckeditor/ckeditor5-vue": "^5.1.0",
28
+ "@tanstack/vue-query": "^5.25.0",
29
+ "@vueuse/components": "^10.9.0",
30
+ "@vueuse/core": "^10.9.0",
31
+ "@vueuse/router": "^10.9.0",
32
+ "a-calc": "^1.3.11",
33
+ "ant-design-vue": "^4.1.2",
34
+ "archiver": "^6.0.2",
35
+ "axios": "^1.6.7",
36
+ "bignumber.js": "^9.1.2",
37
+ "chokidar": "^3.6.0",
38
+ "dayjs": "^1.11.10",
39
+ "echarts": "^5.4.3",
40
+ "execa": "^8.0.1",
41
+ "fast-glob": "^3.3.2",
42
+ "localstorage-slim": "^2.7.0",
43
+ "lodash-es": "^4.17.21",
44
+ "nprogress": "^0.2.0",
45
+ "pinia": "^2.1.7",
46
+ "tsx": "^4.7.1",
47
+ "vue": "^3.4.21",
48
+ "vue-i18n": "^9.10.1",
49
+ "vue-router": "^4.3.0"
50
+ },
51
+ "devDependencies": {
27
52
  "@ckeditor/ckeditor5-adapter-ckfinder": "^41.1.0",
28
53
  "@ckeditor/ckeditor5-alignment": "^41.1.0",
29
54
  "@ckeditor/ckeditor5-autoformat": "^41.1.0",
@@ -54,38 +79,14 @@
54
79
  "@ckeditor/ckeditor5-theme-lark": "^41.1.0",
55
80
  "@ckeditor/ckeditor5-typing": "^41.1.0",
56
81
  "@ckeditor/ckeditor5-upload": "^41.1.0",
57
- "@ckeditor/ckeditor5-vue": "^5.1.0",
58
82
  "@ckeditor/ckeditor5-word-count": "^41.1.0",
59
- "@tanstack/vue-query": "^5.21.4",
60
- "@vueuse/components": "^10.7.2",
61
- "@vueuse/core": "^10.7.2",
62
- "@vueuse/router": "^10.7.2",
63
- "a-calc": "^1.3.8",
64
- "ant-design-vue": "^4.1.2",
65
- "archiver": "^6.0.1",
66
- "axios": "^1.6.7",
67
- "bignumber.js": "^9.1.2",
68
- "chokidar": "^3.6.0",
69
- "dayjs": "^1.11.10",
70
- "echarts": "^5.4.3",
71
- "execa": "^8.0.1",
72
- "fast-glob": "^3.3.2",
73
- "localstorage-slim": "^2.7.0",
74
- "lodash-es": "^4.17.21",
75
- "nprogress": "^0.2.0",
76
- "pinia": "^2.1.7",
77
- "tsx": "^4.7.1",
78
- "vue": "^3.4.19",
79
- "vue-router": "^4.2.5"
80
- },
81
- "devDependencies": {
82
83
  "@peng_kai/lint": "^0.1.0",
83
84
  "@types/archiver": "^6.0.2",
84
85
  "@types/lodash-es": "^4.17.12",
85
86
  "@types/node": "18.19.15",
86
87
  "@types/nprogress": "^0.2.3",
87
- "type-fest": "^4.10.2",
88
- "typescript": "^5.3.3",
89
- "vue-component-type-helpers": "^1.8.27"
88
+ "type-fest": "^4.11.1",
89
+ "typescript": "^5.4.2",
90
+ "vue-component-type-helpers": "^2.0.6"
90
91
  }
91
92
  }
@@ -7,15 +7,15 @@ import { ApiCode, ApiError, isTimeout } from '../helpers';
7
7
  export function returnResultType(): Parameters<AxiosInterceptorManager<any>['use']> {
8
8
  return [
9
9
  (resp) => {
10
- const { resultType } = resp?.config;
10
+ const resultType = resp?.config?.resultType;
11
11
 
12
12
  if (resultType === 'api')
13
- return resp.data;
13
+ return resp?.data;
14
14
 
15
15
  else if (resultType === 'axios')
16
16
  return resp;
17
17
 
18
- return resp.data?.data;
18
+ return resp?.data?.data;
19
19
  },
20
20
  (error) => {
21
21
  let code = error?.response?.data?.code || error?.response?.status || ApiCode.UNKNOWN;
@@ -2,25 +2,42 @@ import type { AxiosInterceptorManager } from 'axios';
2
2
 
3
3
  const REDIRECT_KEY = 'redirect';
4
4
 
5
- export function toLogin(authPath = `${window.location.origin}/auth/login`, code = 15001): Parameters<AxiosInterceptorManager<any>['use']> {
5
+ interface IParams {
6
+ loginPath?: string
7
+ code?: number
8
+ toLogin?: (targetUrl: URL) => void
9
+ }
10
+
11
+ const defaultParams: Required<IParams> = {
12
+ loginPath: `${window.location.origin}/auth/login`,
13
+ code: 15001,
14
+ toLogin(targetUrl: URL) {
15
+ window.history.replaceState(null, '', targetUrl.toString());
16
+ },
17
+ };
18
+
19
+ export function toLogin(params: IParams = defaultParams): Parameters<AxiosInterceptorManager<any>['use']> {
20
+ const _params = { ...defaultParams, ...params };
21
+
6
22
  return [
7
23
  undefined,
8
24
  (err) => {
9
- if (err.code !== code)
25
+ if (err.code !== _params.code)
10
26
  throw err;
11
27
 
12
- const currentURL = new URL(window.location.href);
13
- const targetURL = new URL(authPath);
28
+ const currentUrl = new URL(window.location.href);
29
+ const targetUrl = new URL(_params.loginPath);
14
30
 
15
- if (currentURL.searchParams.has(REDIRECT_KEY))
16
- targetURL.searchParams.set(REDIRECT_KEY, currentURL.searchParams.get(REDIRECT_KEY)!);
31
+ if (currentUrl.searchParams.has(REDIRECT_KEY))
32
+ targetUrl.searchParams.set(REDIRECT_KEY, currentUrl.searchParams.get(REDIRECT_KEY)!);
17
33
  else
18
- targetURL.searchParams.set(REDIRECT_KEY, window.location.href);
34
+ targetUrl.searchParams.set(REDIRECT_KEY, window.location.href);
19
35
 
20
- if (currentURL.toString() === targetURL.toString())
21
- return;
36
+ if (currentUrl.toString() === targetUrl.toString())
37
+ throw err;
22
38
 
23
- window.location.href = targetURL.toString();
39
+ _params.toLogin(targetUrl);
40
+ throw err;
24
41
  },
25
42
  ];
26
43
  }
@@ -17,9 +17,6 @@ function createRequest<Req, OResp, Resp = Api.TransformPageResult<OResp>>(
17
17
  // 返回 API 数据中的 data 字段值,默认
18
18
  async function request(reqData: Req, config?: ReqConfig): Promise<Api.GetDataField<Resp>>;
19
19
  async function request(reqData: Req, config?: ReqConfig): Promise<any> {
20
- // if (isSSR())
21
- // return null;
22
-
23
20
  const params = paramBuilder(reqData);
24
21
  const serviceName = params.headers?.['Service-Name'] ?? '';
25
22
  const service = createRequest.services[serviceName]?.server;
package/request/type.d.ts CHANGED
@@ -51,19 +51,19 @@ declare namespace Api {
51
51
  type TransformPageResult<R> = R extends { pagination: infer P, data: infer D }
52
52
  ? D extends Record<string, any>
53
53
  ? D extends { list: any }
54
- ? {
54
+ ? import('type-fest').Simplify<{
55
55
  [Rk in Exclude<keyof R, 'data' | 'pagination'>]: R[Rk];
56
56
  } & {
57
57
  data: D & { pagination: P }
58
- }
59
- : {
58
+ }>
59
+ : import('type-fest').Simplify<{
60
60
  [Rk in Exclude<keyof R, 'data' | 'pagination'>]: R[Rk];
61
61
  } & {
62
62
  data: {
63
63
  list: D
64
64
  pagination: P
65
65
  }
66
- }
66
+ }>
67
67
  : R
68
68
  : R;
69
69
  }
@@ -0,0 +1,125 @@
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
+ if (message)
96
+ this.localesLoaded.push(locale);
97
+
98
+ return message;
99
+ }
100
+
101
+ /**
102
+ * 定义区域元数据
103
+ * @param meta 区域元数据对象
104
+ * @returns 区域元数据对象
105
+ */
106
+ public static defineMeta(meta: ILocaleMeta) {
107
+ return meta;
108
+ }
109
+
110
+ private clearUrlLocale() {
111
+ const url = new URL(location.href);
112
+ url.searchParams.delete('locale');
113
+ url.searchParams.delete('lang');
114
+
115
+ history.replaceState(null, '', url.toString());
116
+ }
117
+
118
+ private getStorageLocale() {
119
+ return localStorage.getItem(this.localeStorageKey);
120
+ }
121
+
122
+ private setStorageLocale(locale: string) {
123
+ return localStorage.setItem(this.localeStorageKey, locale);
124
+ }
125
+ }
package/vite/index.mjs CHANGED
@@ -3,22 +3,48 @@
3
3
  * @param env 环境变量对象
4
4
  * @returns 代理配置对象
5
5
  */
6
+ // export function getProxy(env) {
7
+ // const keyRE = /^VITE_(HTTP|WS)_PREFIX/;
8
+ // const valueRE = /^.+? => .+?$/;
9
+ // const proxys = {};
10
+
11
+ // for (const [k, v] of Object.entries(env)) {
12
+ // if (!(keyRE.test(k) && valueRE.test(v)))
13
+ // continue;
14
+
15
+ // const isWs = k.includes('_WS');
16
+ // const [prefix, target] = v.split(' => ').map(i => i.trim());
17
+
18
+ // proxys[prefix] = {
19
+ // target,
20
+ // changeOrigin: true,
21
+ // ws: isWs,
22
+ // rewrite: path => path.replace(new RegExp(`^${prefix}`), ''),
23
+ // };
24
+ // }
25
+
26
+ // return proxys;
27
+ // }
28
+
6
29
  export function getProxy(env) {
7
- const keyRE = /^VITE_(HTTP|WS)_PREFIX/;
8
- const valueRE = /^.+? => .+?$/;
30
+ const prefixKeyRE = /^VITE_URL_PREFIX_(.*)$/;
31
+ const socketUrlRE = /^wss?:\/\//;
32
+ const keys = Object.keys(env).filter(k => prefixKeyRE.test(k));
9
33
  const proxys = {};
10
34
 
11
- for (const [k, v] of Object.entries(env)) {
12
- if (!(keyRE.test(k) && valueRE.test(v)))
13
- continue;
35
+ for (const k of keys) {
36
+ const name = k.match(prefixKeyRE)?.[1] ?? '';
37
+ const prefix = env[`VITE_URL_PREFIX_${name}`];
38
+ const target = env[`VITE_URL_PROXY_${name}`];
39
+ const proxyable = prefix && target;
14
40
 
15
- const isWs = k.includes('_WS');
16
- const [prefix, target] = v.split(' => ').map(i => i.trim());
41
+ if (!proxyable)
42
+ continue;
17
43
 
18
44
  proxys[prefix] = {
19
45
  target,
20
46
  changeOrigin: true,
21
- ws: isWs,
47
+ ws: socketUrlRE.test(target),
22
48
  rewrite: path => path.replace(new RegExp(`^${prefix}`), ''),
23
49
  };
24
50
  }