@liujitcn/kratos-admin-core 0.0.55 → 0.0.56

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.
@@ -1,7 +1,9 @@
1
- import type { ConfigService, GetConfigRequest, GetConfigResponse } from "../../../rpc/base/v1/config";
1
+ import type { ConfigService, GetConfigRequest, GetConfigResponse, GetI18nCustomRequest, GetI18nCustomResponse } from "../../../rpc/base/v1/config";
2
2
  /** 系统配置公共服务 */
3
3
  export declare class ConfigServiceImpl implements ConfigService {
4
4
  /** 获取系统配置 */
5
5
  GetConfig(request: GetConfigRequest): Promise<GetConfigResponse>;
6
+ /** 获取当前租户的自定义国际化覆盖项。 */
7
+ GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse>;
6
8
  }
7
9
  export declare const defConfigService: ConfigServiceImpl;
@@ -29,8 +29,16 @@ export interface GetConfigResponse {
29
29
  configs: ConfigItem[];
30
30
  /** AI 助手是否可用 */
31
31
  ai_enabled: boolean;
32
+ }
33
+ /** 当前租户自定义国际化覆盖项查询条件。 */
34
+ export interface GetI18nCustomRequest {
35
+ /** 位置:枚举【BaseConfigSite】 */
36
+ site: BaseConfigSite;
37
+ }
38
+ /** 当前租户自定义国际化覆盖项查询结果。 */
39
+ export interface GetI18nCustomResponse {
32
40
  /** 全部语言的自定义国际化覆盖项 */
33
- i18n_customs: I18nCustomItem[];
41
+ items: I18nCustomItem[];
34
42
  }
35
43
  /** 全部语言的自定义国际化覆盖项。 */
36
44
  export interface I18nCustomItem {
@@ -45,4 +53,6 @@ export interface I18nCustomItem {
45
53
  export interface ConfigService {
46
54
  /** 获取系统配置 */
47
55
  GetConfig(request: GetConfigRequest): Promise<GetConfigResponse>;
56
+ /** 获取当前租户的自定义国际化覆盖项 */
57
+ GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse>;
48
58
  }
@@ -23,6 +23,14 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"admin-conf
23
23
  adminLogo: string;
24
24
  background: string;
25
25
  }>;
26
+ /**
27
+ * 登录成功后加载当前租户的自定义国际化覆盖项。
28
+ */
29
+ loadI18nCustom(): Promise<void>;
30
+ /**
31
+ * 清空租户自定义翻译并恢复本地默认文案。
32
+ */
33
+ resetI18nCustom(): void;
26
34
  }>;
27
35
  /** 刷新当前管理端语言和站点运行配置。 */
28
36
  export declare function refreshAdminRuntimeConfig(): Promise<void>;
@@ -1,6 +1,11 @@
1
+ import type { SelectOptionResponse_Option } from "../rpc/common/v1/common";
1
2
  /** 默认租户编码。 */
2
3
  export declare const DEFAULT_TENANT_CODE = "0000";
4
+ /** 读取共享租户选项,同一时间只发起一次请求。 */
5
+ export declare function loadSharedTenantOptions(): Promise<SelectOptionResponse_Option[]>;
6
+ /** 清除共享租户选项,租户数据变更后由下一次读取重新加载。 */
7
+ export declare function invalidateTenantOptions(): void;
3
8
  /** 读取租户列表筛选选项。 */
4
9
  export declare function requestTenantOptions(): Promise<{
5
- data: import("../rpc/common/v1/common").SelectOptionResponse_Option[];
10
+ data: SelectOptionResponse_Option[];
6
11
  }>;
@@ -1,5 +1,11 @@
1
1
  import service from "../../../utils/request";
2
- import type { ConfigService, GetConfigRequest, GetConfigResponse } from "../../../rpc/base/v1/config";
2
+ import type {
3
+ ConfigService,
4
+ GetConfigRequest,
5
+ GetConfigResponse,
6
+ GetI18nCustomRequest,
7
+ GetI18nCustomResponse
8
+ } from "../../../rpc/base/v1/config";
3
9
 
4
10
  const CONFIG_URL = "/v1/base/config";
5
11
 
@@ -14,6 +20,15 @@ export class ConfigServiceImpl implements ConfigService {
14
20
  headers: { Authorization: "no-auth" }
15
21
  });
16
22
  }
23
+
24
+ /** 获取当前租户的自定义国际化覆盖项。 */
25
+ GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse> {
26
+ return service<GetI18nCustomRequest, GetI18nCustomResponse>({
27
+ url: `${CONFIG_URL}/i18n-custom`,
28
+ method: "get",
29
+ params: request
30
+ });
31
+ }
17
32
  }
18
33
 
19
34
  export const defConfigService = new ConfigServiceImpl();
@@ -1,7 +1,13 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Admin logo">
2
- <rect width="64" height="64" rx="16" fill="#2563eb"/>
3
- <path fill="#fff" d="M17 18.5A4.5 4.5 0 0 1 21.5 14h21a4.5 4.5 0 0 1 4.5 4.5v25a4.5 4.5 0 0 1-4.5 4.5h-21a4.5 4.5 0 0 1-4.5-4.5v-25Zm5 1.5v21h20V20.5H22Zm0 25h20v-1H22v1Z"/>
4
- <circle cx="23" cy="17.5" r="1.5" fill="#93c5fd"/>
5
- <circle cx="28" cy="17.5" r="1.5" fill="#93c5fd"/>
6
- <path fill="#93c5fd" d="M25 26h3v10h-3V26Zm5-3h3v13h-3V23Zm5 6h3v7h-3v-7Z"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 56" role="img" aria-label="Admin logo">
2
+ <rect x="3" y="8" width="54" height="43" rx="7" fill="#eef6ff" stroke="#244987" stroke-width="4"/>
3
+ <rect x="11" y="17" width="22" height="24" rx="4" fill="#fff"/>
4
+ <path d="M17 22a5 5 0 1 0 5 5" fill="none" stroke="#ffd15c" stroke-width="4"/>
5
+ <path d="M22 22a5 5 0 0 1 5 5" fill="none" stroke="#ffbd00" stroke-width="4"/>
6
+ <rect x="17" y="34" width="3" height="4" rx="1" fill="#8679e8"/>
7
+ <rect x="22" y="31" width="3" height="7" rx="1" fill="#6f5bd9"/>
8
+ <rect x="27" y="28" width="3" height="10" rx="1" fill="#5540cf"/>
9
+ <path d="M39 23h10M39 29h10M39 35h7" fill="none" stroke="#8a7dea" stroke-width="3" stroke-linecap="round"/>
10
+ <circle cx="52" cy="10" r="10" fill="#c9f8e4"/>
11
+ <circle cx="52" cy="10" r="7" fill="#56dba0"/>
12
+ <path d="m48.5 10 2.5 2.5 4.5-5" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
7
13
  </svg>
@@ -30,6 +30,9 @@ import { useUserStore } from "./stores/modules/user";
30
30
  import { getRouteMetaTitle } from "./utils";
31
31
  import { defLanguageService } from "./api/base/v1/language";
32
32
  import { setAdminDocumentTitle } from "./documentTitle";
33
+ import defaultLogoUrl from "./assets/images/logo.svg";
34
+ import defaultLoginBackgroundUrl from "./assets/images/login_bg.svg";
35
+ import defaultLoginIllustrationUrl from "./assets/images/login_left.png";
33
36
 
34
37
  /**
35
38
  * 管理端启动参数。
@@ -49,6 +52,15 @@ export async function bootstrapAdminApp(options: AdminBootstrapOptions = {}) {
49
52
  registerAdminModules(modules);
50
53
  registerLocaleMessages(modules);
51
54
  initializeLocale();
55
+
56
+ // 登录页图片与启动接口并行加载,并在首次挂载前完成解码,避免页面先于图片绘制。
57
+ const loginImagePreloads = [defaultLogoUrl, defaultLoginBackgroundUrl, defaultLoginIllustrationUrl].map(
58
+ imageUrl => {
59
+ const image = new Image();
60
+ image.src = imageUrl;
61
+ return image.decode();
62
+ }
63
+ );
52
64
  try {
53
65
  applyLanguageConfig(await defLanguageService.OptionLanguage({}));
54
66
  } catch {
@@ -82,6 +94,7 @@ export async function bootstrapAdminApp(options: AdminBootstrapOptions = {}) {
82
94
  }
83
95
  updateDocumentTitle();
84
96
 
97
+ await Promise.allSettled(loginImagePreloads);
85
98
  app.mount(options.mount ?? "#app");
86
99
  return app;
87
100
  }
@@ -92,7 +105,7 @@ async function refreshLocalizedRuntimeData() {
92
105
  const authStore = useAuthStore(pinia);
93
106
  const dictStore = useDictStore(pinia);
94
107
  const configStore = useConfigStore(pinia);
95
- await Promise.allSettled([configStore.loadDisplayConfig()]);
108
+ await Promise.allSettled([configStore.loadDisplayConfig(), ...(userStore.token ? [configStore.loadI18nCustom()] : [])]);
96
109
  if (userStore.token) {
97
110
  await Promise.allSettled([authStore.getAuthMenuList(), dictStore.updateDictionaryCache()]);
98
111
  syncLocalizedRouteState();
@@ -9,4 +9,4 @@
9
9
  | `en-US.json` | 英文公共语言包。 | 管理端切换为英文时由 Vue I18n 使用。 |
10
10
  | `ja-JP.json` | 日文公共语言包。 | 管理端切换为日文时由 Vue I18n 使用。 |
11
11
 
12
- 语言包是标准 JSON,不能在文件内直接写注释;新增语言或修改键后执行仓库根目录的 `make i18n-sync`,脚本会校验所有语言并生成注册产物。语言切换选项固定显示后端 `base_language.native_name`,接口不可用时使用代码内置的原生名称;`common.language.*` 仅用于普通文案和语言迁移初始数据。新增语言的完整文件清单见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。
12
+ 语言包是标准 JSON,不能在文件内直接写注释;新增语言或修改键后执行仓库根目录的 `make i18n`,脚本会校验所有语言并生成注册产物。语言切换选项固定显示后端 `base_language.native_name`,接口不可用时使用代码内置的原生名称;`common.language.*` 仅用于普通文案和语言迁移初始数据。新增语言的完整文件清单见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。
@@ -40,8 +40,18 @@ export interface GetConfigResponse {
40
40
  configs: ConfigItem[];
41
41
  /** AI 助手是否可用 */
42
42
  ai_enabled: boolean;
43
+ }
44
+
45
+ /** 当前租户自定义国际化覆盖项查询条件。 */
46
+ export interface GetI18nCustomRequest {
47
+ /** 位置:枚举【BaseConfigSite】 */
48
+ site: BaseConfigSite;
49
+ }
50
+
51
+ /** 当前租户自定义国际化覆盖项查询结果。 */
52
+ export interface GetI18nCustomResponse {
43
53
  /** 全部语言的自定义国际化覆盖项 */
44
- i18n_customs: I18nCustomItem[];
54
+ items: I18nCustomItem[];
45
55
  }
46
56
 
47
57
  /** 全部语言的自定义国际化覆盖项。 */
@@ -58,4 +68,6 @@ export interface I18nCustomItem {
58
68
  export interface ConfigService {
59
69
  /** 获取系统配置 */
60
70
  GetConfig(request: GetConfigRequest): Promise<GetConfigResponse>;
71
+ /** 获取当前租户的自定义国际化覆盖项 */
72
+ GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse>;
61
73
  }
@@ -118,18 +118,33 @@ export const useConfigStore = defineStore("admin-config", {
118
118
  });
119
119
  const configMap = buildConfigMap(configResponse.configs ?? []);
120
120
 
121
- applyCustomLocaleMessages(configResponse.i18n_customs ?? []);
122
121
  this.setDisplayConfig(normalizeSiteDisplayConfig(configMap));
123
122
  this.setLoginCaptchaConfig(normalizeLoginCaptchaConfig(configMap));
124
123
  this.showTenantCode = !["false", "0"].includes((configMap[SHOW_TENANT_CODE_KEY] ?? "true").toLowerCase());
125
124
  this.i18nDraftEnabled = configMap[I18N_DRAFT_ENABLED_KEY] === "true";
126
125
  this.aiEnabled = configResponse.ai_enabled === true;
127
126
  return this.display;
127
+ },
128
+ /**
129
+ * 登录成功后加载当前租户的自定义国际化覆盖项。
130
+ */
131
+ async loadI18nCustom() {
132
+ const response = await defConfigService.GetI18nCustom({
133
+ site: BaseConfigSite.BASE_CONFIG_SITE_ADMIN
134
+ });
135
+ applyCustomLocaleMessages(response.items ?? []);
136
+ },
137
+ /**
138
+ * 清空租户自定义翻译并恢复本地默认文案。
139
+ */
140
+ resetI18nCustom() {
141
+ applyCustomLocaleMessages([]);
128
142
  }
129
143
  }
130
144
  });
131
145
 
132
146
  /** 刷新当前管理端语言和站点运行配置。 */
133
147
  export async function refreshAdminRuntimeConfig(): Promise<void> {
134
- await useConfigStore().loadDisplayConfig();
148
+ const configStore = useConfigStore();
149
+ await Promise.all([configStore.loadDisplayConfig(), configStore.loadI18nCustom()]);
135
150
  }
@@ -10,6 +10,7 @@ import piniaPersistConfig from "../../stores/helper/persist";
10
10
  import { useDictStoreHook } from "../../stores/modules/dict";
11
11
  import { useLockScreenStore } from "../../stores/modules/lockScreen";
12
12
  import { useAuthStore } from "../../stores/modules/auth";
13
+ import { useConfigStore } from "../../stores/modules/config";
13
14
 
14
15
  const defaultUserInfo: UserInfoForm = {
15
16
  user_name: "",
@@ -100,6 +101,7 @@ export const useUserStore = defineStore("admin-user", {
100
101
  useDictStoreHook().clearDictionaryCache();
101
102
  useLockScreenStore().clearLock();
102
103
  useAuthStore().$reset();
104
+ useConfigStore().resetI18nCustom();
103
105
  this.authVersion += 1;
104
106
  this.authInvalidated = true;
105
107
  this.setToken("");
@@ -4,7 +4,11 @@ import type { ColumnProps, EnumProps, RenderScope, SearchType } from "./componen
4
4
  import { defBaseTenantService } from "./api/system/admin/v1/base_tenant";
5
5
  import type { SelectOptionResponse_Option } from "./rpc/common/v1/common";
6
6
  import { useUserStore } from "./stores/runtime";
7
- import { DEFAULT_TENANT_CODE, requestTenantOptions as requestTenantOptionData } from "./utils/tenant";
7
+ import {
8
+ DEFAULT_TENANT_CODE,
9
+ loadSharedTenantOptions,
10
+ requestTenantOptions as requestTenantOptionData
11
+ } from "./utils/tenant";
8
12
 
9
13
  export * from "./utils/tenant";
10
14
 
@@ -89,8 +93,7 @@ export function useTenantScope(): TenantScope {
89
93
  async function loadTenantOptions(includeCurrentTenant = false) {
90
94
  if (!includeCurrentTenant && !isDefaultTenant.value) return;
91
95
  if (tenantOptions.value.length) return;
92
- const response = await defBaseTenantService.OptionBaseTenant({ keyword: "" });
93
- tenantOptions.value = response.list ?? [];
96
+ tenantOptions.value = await loadSharedTenantOptions();
94
97
  }
95
98
 
96
99
  function toRequestTenantId(value: unknown) {
@@ -129,7 +132,12 @@ export function useTenantScope(): TenantScope {
129
132
  search:
130
133
  options.searchable === false
131
134
  ? undefined
132
- : { el: options.searchEl ?? "tenant-select", key: options.searchKey ?? prop, props: { filterable: true }, order: options.order },
135
+ : {
136
+ el: options.searchEl ?? "tenant-select",
137
+ key: options.searchKey ?? prop,
138
+ props: { filterable: true },
139
+ order: options.order ?? 1
140
+ },
133
141
  enum: options.enum ?? requestTenantOptionData,
134
142
  render: options.render ?? (scope => resolveTenantLabel(scope.row, prop))
135
143
  }
@@ -1,3 +1,4 @@
1
+ import axios from "axios";
1
2
  import { ElNotification } from "element-plus";
2
3
  import { t } from "../locales";
3
4
 
@@ -5,6 +6,7 @@ import { t } from "../locales";
5
6
  * @description 全局代码错误捕捉
6
7
  * */
7
8
  const errorHandler = (error: any) => {
9
+ if (axios.isCancel(error) || error === "canceled") return false;
8
10
  // 过滤 HTTP 请求错误
9
11
  if (error.status || error.status == 0) return false;
10
12
  let errorMap: { [key: string]: string } = {
@@ -152,6 +152,12 @@ function shouldSkipErrorMessage(config?: InternalAxiosRequestConfig) {
152
152
  return requestUrl === SESSION_URL && requestMethod === "delete";
153
153
  }
154
154
 
155
+ /** 判断当前是否处于退出或认证失效过渡状态。 */
156
+ function isAuthTransitioning() {
157
+ const userStore = getUserStore();
158
+ return userStore.isLoggingOut || userStore.authInvalidated;
159
+ }
160
+
155
161
  /** 判断响应是否要求用户先修改密码。 */
156
162
  function isPasswordChangeRequired(data?: ErrorResponseData) {
157
163
  return data?.metadata?.[PASSWORD_CHANGE_REQUIRED_METADATA_KEY] === "true";
@@ -262,7 +268,6 @@ export async function ensureAccessToken() {
262
268
  await handleTokenRefresh(false);
263
269
  return hasValidAccessToken();
264
270
  } catch {
265
- userStore.clearAuthData();
266
271
  return false;
267
272
  }
268
273
  }
@@ -304,7 +309,14 @@ export function handleAuthExpired() {
304
309
  // 请求拦截器
305
310
  service.interceptors.request.use(
306
311
  async (config: InternalAxiosRequestConfig) => {
312
+ const requestUrl = config.url ?? "";
313
+ const requestMethod = String(config.method ?? "").toLowerCase();
307
314
  const skipAuth = shouldSkipAuth(config);
315
+ const isLogoutRequest = requestUrl === SESSION_URL && requestMethod === "delete";
316
+ if (isAuthTransitioning() && !skipAuth && !isLogoutRequest) {
317
+ throw new axios.CanceledError();
318
+ }
319
+
308
320
  const accessToken = skipAuth ? "" : await getRequestAccessToken();
309
321
  Object.assign(config.headers, getLocaleRequestHeaders());
310
322
  config.headers[REFRESH_TOKEN_TRANSPORT_HEADER] = REFRESH_TOKEN_TRANSPORT_COOKIE;
@@ -341,14 +353,24 @@ service.interceptors.response.use(
341
353
  return Promise.reject(new Error(message || t("common.message.system_error")));
342
354
  },
343
355
  async (error: AxiosError) => {
356
+ if (axios.isCancel(error)) {
357
+ return Promise.reject(error);
358
+ }
359
+
344
360
  const status = error.response?.status;
345
361
  const data = error.response?.data as ErrorResponseData | undefined;
346
362
  const code = data?.code;
347
363
  const message = data?.message;
348
364
  const requestConfig = error.config as RetryableRequestConfig | undefined;
349
365
 
350
- // 业务请求仅在 401 时尝试刷新并重放,403 直接展示后端权限错误。
351
- if ((status === 401 || code === 401) && !shouldSkipAuthExpiredPrompt(requestConfig)) {
366
+ // 退出或认证失效后,页面中的在途请求返回 401 属于预期结果,不再重复刷新令牌、弹窗或提示错误。
367
+ if (isAuthTransitioning()) {
368
+ return Promise.reject(error);
369
+ }
370
+
371
+ // 业务请求仅在 401 时尝试刷新并重放,公共认证接口的 401 继续展示后端业务错误。
372
+ const isUnauthorized = status === 401 || code === 401;
373
+ if (isUnauthorized && !shouldSkipAuthExpiredPrompt(requestConfig)) {
352
374
  const userStore = getUserStore();
353
375
  if (requestConfig && !requestConfig._authRetried && !userStore.isLoggingOut && !userStore.authInvalidated && hasRefreshCookieHint()) {
354
376
  requestConfig._authRetried = true;
@@ -374,7 +396,7 @@ service.interceptors.response.use(
374
396
  } else if (!shouldSkipErrorMessage(requestConfig)) {
375
397
  showRequestError(error.message || t("common.message.system_error"));
376
398
  }
377
- return Promise.reject(error.message);
399
+ return Promise.reject(error);
378
400
  }
379
401
  );
380
402
 
@@ -389,12 +411,25 @@ export default request;
389
411
  let isRefreshing = false;
390
412
  let refreshPromise: Promise<void> | null = null;
391
413
 
414
+ /** 判断刷新令牌是否已被服务端判定为失效。 */
415
+ function isRefreshTokenUnauthorized(error: unknown) {
416
+ if (!axios.isAxiosError<ErrorResponseData>(error)) return false;
417
+
418
+ const status = error.response?.status;
419
+ const code = error.response?.data?.code;
420
+ return status === 401 || code === 401;
421
+ }
422
+
392
423
  /** 刷新 Token 处理 */
393
424
  async function handleTokenRefresh(promptOnFailure = true) {
394
425
  if (!isRefreshing) {
395
426
  isRefreshing = true;
396
427
  refreshPromise = refreshAccessToken()
397
428
  .catch(error => {
429
+ if (isRefreshTokenUnauthorized(error)) {
430
+ // 刷新令牌失效后立即终止后续自动刷新,避免旧 Cookie 被反复提交。
431
+ getUserStore().clearAuthData();
432
+ }
398
433
  if (promptOnFailure) {
399
434
  console.log("token 刷新失败", error);
400
435
  handleAuthExpired();
@@ -1,10 +1,42 @@
1
1
  import { defBaseTenantService } from "../api/system/admin/v1/base_tenant";
2
+ import type { SelectOptionResponse_Option } from "../rpc/common/v1/common";
2
3
 
3
4
  /** 默认租户编码。 */
4
5
  export const DEFAULT_TENANT_CODE = "0000";
5
6
 
7
+ const tenantOptions: SelectOptionResponse_Option[] = [];
8
+ let tenantOptionsLoaded = false;
9
+ let tenantOptionsRequest: Promise<SelectOptionResponse_Option[]> | undefined;
10
+ let tenantOptionsVersion = 0;
11
+
12
+ /** 读取共享租户选项,同一时间只发起一次请求。 */
13
+ export async function loadSharedTenantOptions() {
14
+ if (tenantOptionsLoaded) return tenantOptions;
15
+ if (!tenantOptionsRequest) {
16
+ const requestVersion = tenantOptionsVersion;
17
+ tenantOptionsRequest = defBaseTenantService.OptionBaseTenant({ keyword: "" })
18
+ .then(response => {
19
+ if (requestVersion !== tenantOptionsVersion) return tenantOptions;
20
+ tenantOptions.splice(0, tenantOptions.length, ...(response.list ?? []));
21
+ tenantOptionsLoaded = true;
22
+ return tenantOptions;
23
+ })
24
+ .finally(() => {
25
+ tenantOptionsRequest = undefined;
26
+ });
27
+ }
28
+ return tenantOptionsRequest;
29
+ }
30
+
31
+ /** 清除共享租户选项,租户数据变更后由下一次读取重新加载。 */
32
+ export function invalidateTenantOptions() {
33
+ tenantOptionsVersion += 1;
34
+ tenantOptions.splice(0, tenantOptions.length);
35
+ tenantOptionsLoaded = false;
36
+ tenantOptionsRequest = undefined;
37
+ }
38
+
6
39
  /** 读取租户列表筛选选项。 */
7
40
  export async function requestTenantOptions() {
8
- const response = await defBaseTenantService.OptionBaseTenant({ keyword: "" });
9
- return { data: response.list ?? [] };
41
+ return { data: await loadSharedTenantOptions() };
10
42
  }
@@ -629,6 +629,11 @@ const handleLoginResponse = async (result: LoginResponse) => {
629
629
  clearPasswordChangeRequired();
630
630
  }
631
631
  userStore.updateTokenAuth(result.access_token, result.token_type ?? "", result.expires_in);
632
+ try {
633
+ await configStore.loadI18nCustom();
634
+ } catch {
635
+ configStore.resetI18nCustom();
636
+ }
632
637
  await finishLogin(mustChangePassword);
633
638
  };
634
639
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-core",
3
3
  "private": false,
4
- "version": "0.0.55",
4
+ "version": "0.0.56",
5
5
  "type": "module",
6
6
  "description": "kratos-admin frontend runtime and base modules",
7
7
  "author": {
@@ -236,7 +236,7 @@
236
236
  "scripts": {
237
237
  "build": "pnpm build:package",
238
238
  "build:package": "node ../../scripts/build-package.mjs .",
239
- "test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js dist/test/tests/codegen-hmr.test.js dist/test/tests/dialog-request.test.js",
239
+ "test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js dist/test/tests/codegen-hmr.test.js dist/test/tests/dialog-request.test.js dist/test/tests/request-error.test.js",
240
240
  "type:check": "vue-tsc --noEmit --skipLibCheck"
241
241
  }
242
242
  }