@liujitcn/kratos-admin-system 0.0.51 → 0.0.53

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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/package/declarations/src/api/base/v1/sse.d.ts +1 -1
  3. package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +1 -2
  4. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +10 -0
  5. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +8 -3
  6. package/dist/package/declarations/src/index.d.ts +1 -1
  7. package/dist/package/declarations/src/locales/generated.d.ts +32 -0
  8. package/dist/package/declarations/src/rpc/base/v1/ai_search.d.ts +26 -0
  9. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +0 -1
  10. package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +6 -2
  11. package/dist/package/src/api/base/v1/sse.ts +19 -12
  12. package/dist/package/src/components/log/LogTable.vue +25 -18
  13. package/dist/package/src/components/notification/Notification.vue +14 -7
  14. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +106 -62
  15. package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +39 -1
  16. package/dist/package/src/components/tenant-project/tenant-project-manager.ts +19 -4
  17. package/dist/package/src/components/tenant-project-grant/ProjectGrantDialog.vue +22 -7
  18. package/dist/package/src/index.ts +2 -0
  19. package/dist/package/src/locales/en-US.json +8 -0
  20. package/dist/package/src/locales/ja-JP.json +8 -0
  21. package/dist/package/src/locales/zh-CN.json +8 -0
  22. package/dist/package/src/locales/zh-TW.json +8 -0
  23. package/dist/package/src/rpc/base/v1/ai_search.ts +37 -0
  24. package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +0 -1
  25. package/dist/package/src/views/ai/chat/components/XSender.vue +1 -36
  26. package/dist/package/src/views/ai/chat/components/speech-recognition.ts +28 -7
  27. package/dist/package/src/views/ai/chat/index.vue +16 -7
  28. package/dist/package/src/views/base/api-log/index.vue +1 -1
  29. package/dist/package/src/views/base/area/index.vue +15 -9
  30. package/dist/package/src/views/base/backup-management/archive-config/index.vue +22 -10
  31. package/dist/package/src/views/base/backup-management/backup-config/index.vue +13 -6
  32. package/dist/package/src/views/base/config/index.vue +38 -14
  33. package/dist/package/src/views/base/dept/index.vue +31 -25
  34. package/dist/package/src/views/base/dict/index.vue +12 -8
  35. package/dist/package/src/views/base/dict/item.vue +12 -8
  36. package/dist/package/src/views/base/i18n-custom/index.vue +13 -7
  37. package/dist/package/src/views/base/job/index.vue +12 -8
  38. package/dist/package/src/views/base/job/log.vue +14 -9
  39. package/dist/package/src/views/base/language/index.vue +7 -4
  40. package/dist/package/src/views/base/login-policy/index.vue +22 -11
  41. package/dist/package/src/views/base/menu/index.vue +23 -16
  42. package/dist/package/src/views/base/message/index.vue +65 -21
  43. package/dist/package/src/views/base/message-category/index.vue +10 -4
  44. package/dist/package/src/views/base/oauth-client/index.vue +15 -5
  45. package/dist/package/src/views/base/post/index.vue +10 -4
  46. package/dist/package/src/views/base/redact-output-policy/index.vue +12 -5
  47. package/dist/package/src/views/base/redact-rule/index.vue +8 -7
  48. package/dist/package/src/views/base/redact-storage-policy/index.vue +32 -8
  49. package/dist/package/src/views/base/role/index.vue +24 -17
  50. package/dist/package/src/views/base/tenant/index.vue +7 -6
  51. package/dist/package/src/views/base/tenant-project-grant/index.vue +44 -27
  52. package/dist/package/src/views/base/user/components/dept-tree.vue +4 -9
  53. package/dist/package/src/views/base/user/index.vue +39 -28
  54. package/dist/package/src/views/message/inbox/index.vue +8 -3
  55. package/dist/package/src/views/profile/components/base.vue +1 -1
  56. package/dist/package/src/views/profile/index.vue +1 -1
  57. package/dist/package/src/views/tool/cache/index.vue +1 -1
  58. package/dist/package/src/views/tool/code-gen/columns/index.vue +35 -16
  59. package/dist/package/src/views/tool/code-gen/table/index.vue +58 -31
  60. package/package.json +2 -2
@@ -5,6 +5,7 @@ export type SpeechResultList = ArrayLike<{
5
5
  0?: {
6
6
  transcript: string;
7
7
  };
8
+ isFinal?: boolean;
8
9
  length: number;
9
10
  }>;
10
11
 
@@ -17,6 +18,7 @@ export type SpeechRecognitionError = {
17
18
 
18
19
  type SpeechRecognitionResultEvent = {
19
20
  results: SpeechResultList;
21
+ resultIndex: number;
20
22
  };
21
23
 
22
24
  type SpeechRecognitionInstance = {
@@ -49,13 +51,28 @@ type SpeechRecognitionOptions = {
49
51
  onResult?: (text: string) => void;
50
52
  };
51
53
 
52
- /** 收集一次识别事件中的完整文本。 */
53
- export function collectSpeechResultText(results: SpeechResultList) {
54
- let text = "";
55
- for (let index = 0; index < results.length; index++) {
56
- text += results[index]?.[0]?.transcript ?? "";
54
+ /** 增量累积语音识别结果,避免连续模式下 interim 结果重复计入已确认文本。 */
55
+ export function accumulateSpeechResultText(
56
+ results: SpeechResultList,
57
+ resultIndex: number,
58
+ previousFinalText: string
59
+ ): { finalText: string; text: string } {
60
+ let finalText = previousFinalText;
61
+ let interimText = "";
62
+ for (let index = resultIndex; index < results.length; index++) {
63
+ const result = results[index];
64
+ const transcript = result?.[0]?.transcript ?? "";
65
+ if (result?.isFinal) {
66
+ finalText += transcript;
67
+ } else {
68
+ interimText += transcript;
69
+ }
70
+ }
71
+ // Chrome 连续模式下 interim 结果会累计包含已确认的 final 文本,去除重复前缀。
72
+ if (interimText && finalText && interimText.startsWith(finalText)) {
73
+ interimText = interimText.slice(finalText.length);
57
74
  }
58
- return text;
75
+ return { finalText, text: finalText + interimText };
59
76
  }
60
77
 
61
78
  /** 创建浏览器语音识别实例,并统一结果、状态和卸载处理。 */
@@ -64,6 +81,7 @@ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: Spee
64
81
  const value = ref("");
65
82
  let recognition: SpeechRecognitionInstance | null = null;
66
83
  let recognitionSession = 0;
84
+ let finalText = "";
67
85
 
68
86
  /** 获取当前浏览器支持的语音识别构造函数。 */
69
87
  function resolveRecognitionConstructor() {
@@ -84,6 +102,7 @@ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: Spee
84
102
  const nextRecognition = new RecognitionConstructor();
85
103
  const currentSession = ++recognitionSession;
86
104
  recognition = nextRecognition;
105
+ finalText = "";
87
106
  const isCurrentRecognition = () => recognition === nextRecognition && recognitionSession === currentSession;
88
107
  nextRecognition.continuous = true;
89
108
  nextRecognition.interimResults = true;
@@ -96,7 +115,9 @@ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: Spee
96
115
  };
97
116
  nextRecognition.onresult = event => {
98
117
  if (!isCurrentRecognition()) return;
99
- value.value = collectSpeechResultText(event.results);
118
+ const accumulated = accumulateSpeechResultText(event.results, event.resultIndex, finalText);
119
+ finalText = accumulated.finalText;
120
+ value.value = accumulated.text;
100
121
  onResult?.(value.value);
101
122
  };
102
123
  nextRecognition.onerror = error => {
@@ -107,6 +107,7 @@ const pendingDeltaMap = new Map<string, AiStreamPayload>();
107
107
  const runningStreamTaskMap = new Map<string, AiStreamTask>();
108
108
  let pendingDeltaFrame = 0;
109
109
  let speakingMessageID = "";
110
+ let createSessionPromise: Promise<string | undefined> | undefined;
110
111
 
111
112
  /** 当前会话的流式任务状态。 */
112
113
  type AiStreamTask = {
@@ -728,13 +729,21 @@ async function ensureActiveSession() {
728
729
 
729
730
  /** 创建新的助手会话,并同步到本地列表。 */
730
731
  async function createSession(options?: { title?: string }) {
731
- const response = await defAiSessionService.CreateAiSession({
732
- title: options?.title || t("system.ai.chat.value.new_conversation"),
733
- terminal: Terminal.TERMINAL_ADMIN
734
- });
735
- const normalizedSession = normalizeSession(response.session);
736
- upsertSession(normalizedSession);
737
- return normalizedSession.id;
732
+ if (createSessionPromise) return createSessionPromise;
733
+ createSessionPromise = (async () => {
734
+ const response = await defAiSessionService.CreateAiSession({
735
+ title: options?.title || t("system.ai.chat.value.new_conversation"),
736
+ terminal: Terminal.TERMINAL_ADMIN
737
+ });
738
+ const normalizedSession = normalizeSession(response.session);
739
+ upsertSession(normalizedSession);
740
+ return normalizedSession.id;
741
+ })();
742
+ try {
743
+ return await createSessionPromise;
744
+ } finally {
745
+ createSessionPromise = undefined;
746
+ }
738
747
  }
739
748
 
740
749
  /** 使用当前消息内容生成一个易识别的分支会话标题。 */
@@ -35,7 +35,7 @@ const resultOptions = computed(() =>
35
35
  ])
36
36
  );
37
37
  const columns = computed<ColumnProps[]>(() => [
38
- { prop: "operation", label: t("system.base.log.field.operation"), minWidth: 320, search: { el: "input" } },
38
+ { prop: "operation", label: t("system.base.log.field.operation"), minWidth: 320, search: { el: "input", key: "keyword" } },
39
39
  { prop: "method", label: t("system.base.log.field.method"), minWidth: 90 },
40
40
  { prop: "status_code", label: t("system.base.log.field.status_code"), minWidth: 100, align: "right" },
41
41
  {
@@ -113,7 +113,9 @@ async function loadParentIdFormTreeOptions(
113
113
  resolve(normalizeLazyTreeOptions((response.list ?? []) as GeneratedTreeOption[]));
114
114
  }
115
115
 
116
- void loadFormOptions();
116
+ void loadFormOptions().then(options => {
117
+ parentIdFormOptions.value = options;
118
+ });
117
119
 
118
120
  /** 行政区域表单字段配置。 */
119
121
  const formFields = computed<ProFormField[]>(() => [
@@ -218,7 +220,7 @@ async function loadFormOptions() {
218
220
  const parentIdFormResponse = await defBaseAreaService.OptionBaseArea({ parent_id: 0, lazy: true } as Parameters<
219
221
  typeof defBaseAreaService.OptionBaseArea
220
222
  >[0]);
221
- parentIdFormOptions.value = [
223
+ return [
222
224
  { label: t("system.base.area.value.root"), value: 0 },
223
225
  ...normalizeLazyTreeOptions((parentIdFormResponse.list ?? []) as GeneratedTreeOption[]).filter(
224
226
  option => Number(option.value) !== 0
@@ -231,19 +233,23 @@ async function loadFormOptions() {
231
233
  */
232
234
  async function handleOpenDialog(id?: number) {
233
235
  resetForm();
234
- await loadFormOptions();
235
236
  dialog.titleKey = id ? "system.base.area.action.edit" : "system.base.area.action.create";
236
- dialog.visible = true;
237
- if (!id) return;
238
-
239
- const data = await defBaseAreaService.GetBaseArea({ id });
240
- Object.assign(formData, data);
237
+ await formDialogRef.value?.open({
238
+ load: async () => ({
239
+ options: await loadFormOptions(),
240
+ data: id ? await defBaseAreaService.GetBaseArea({ id }) : undefined
241
+ }),
242
+ commit: ({ options, data }) => {
243
+ parentIdFormOptions.value = options;
244
+ if (data) Object.assign(formData, data);
245
+ }
246
+ });
241
247
  }
242
248
  /**
243
249
  * 关闭行政区域弹窗。
244
250
  */
245
251
  function handleCloseDialog() {
246
- dialog.visible = false;
252
+ formDialogRef.value?.close();
247
253
  resetForm();
248
254
  }
249
255
  /**
@@ -83,13 +83,21 @@ function refresh() { proTable.value?.getTableList(); }
83
83
  function defaultForm(): BaseTableArchiveForm { return { id: 0, source_name: "", table_name: "", archive_mode: BaseTableArchiveMode.BASE_TABLE_ARCHIVE_MODE_INTERNAL_DATABASE, online_retention_days: 180, archive_retention_days: 3650, batch_size: 5000, delete_after_verify: false, oss_prefix: "archive", status: Status.STATUS_ENABLE }; }
84
84
  function resetForm() { dialog.visible = false; formDialogRef.value?.resetFields(); Object.assign(formData, defaultForm()); }
85
85
  async function openDialog(id?: number) {
86
- await loadSourceOptions();
87
- Object.assign(formData, defaultForm());
88
- dialog.editing = Boolean(id);
89
- if (id) Object.assign(formData, await defBaseTableArchiveService.GetBaseTableArchive({ id }));
90
- if (!formData.source_name) formData.source_name = String(sourceOptions.value[0]?.value ?? "");
91
- await loadTableOptions(formData.source_name);
92
- dialog.visible = true;
86
+ await formDialogRef.value?.open({
87
+ load: async () => {
88
+ await loadSourceOptions();
89
+ const data = id ? await defBaseTableArchiveService.GetBaseTableArchive({ id }) : undefined;
90
+ const form = { ...defaultForm(), ...(data ?? {}) };
91
+ if (!form.source_name) form.source_name = String(sourceOptions.value[0]?.value ?? "");
92
+ const loadedTableOptions = form.source_name ? await requestTableOptions(form.source_name) : [];
93
+ return { form, loadedTableOptions };
94
+ },
95
+ commit: ({ form, loadedTableOptions }) => {
96
+ Object.assign(formData, form);
97
+ dialog.editing = Boolean(id);
98
+ tableOptions.value = loadedTableOptions;
99
+ }
100
+ });
93
101
  }
94
102
  async function loadSourceOptions() {
95
103
  if (sourceOptions.value.length || loadingSources.value) return;
@@ -107,12 +115,16 @@ async function handleSourceChange(value: string | number | boolean | undefined)
107
115
  await loadTableOptions(formData.source_name);
108
116
  }
109
117
  async function loadTableOptions(sourceName: string) {
110
- tableOptions.value = [];
111
- if (!sourceName) return;
118
+ tableOptions.value = await requestTableOptions(sourceName);
119
+ }
120
+
121
+ /** 请求指定数据源下的数据表选项。 */
122
+ async function requestTableOptions(sourceName: string) {
123
+ if (!sourceName) return [];
112
124
  loadingTables.value = true;
113
125
  try {
114
126
  const data = await defBaseTableSourceService.OptionBaseTable({ source_name: sourceName });
115
- tableOptions.value = (data.value ?? []).map(value => ({ label: value, value }));
127
+ return (data.value ?? []).map(value => ({ label: value, value }));
116
128
  } finally {
117
129
  loadingTables.value = false;
118
130
  }
@@ -68,12 +68,19 @@ function refresh() { proTable.value?.getTableList(); }
68
68
  function defaultForm(): BaseTableBackupForm { return { id: 0, source_name: "", backup_type: BaseTableBackupType.BASE_TABLE_BACKUP_TYPE_FULL, oss_prefix: "backup/database", retention_count: 7, status: Status.STATUS_ENABLE }; }
69
69
  function resetForm() { dialog.visible = false; formDialogRef.value?.resetFields(); Object.assign(formData, defaultForm()); }
70
70
  async function openDialog(id?: number) {
71
- await loadSourceOptions();
72
- Object.assign(formData, defaultForm());
73
- dialog.editing = Boolean(id);
74
- if (id) Object.assign(formData, await defBaseTableBackupService.GetBaseTableBackup({ id }));
75
- if (!formData.source_name) formData.source_name = String(sourceOptions.value[0]?.value ?? "");
76
- dialog.visible = true;
71
+ await formDialogRef.value?.open({
72
+ load: async () => {
73
+ await loadSourceOptions();
74
+ const data = id ? await defBaseTableBackupService.GetBaseTableBackup({ id }) : undefined;
75
+ const form = { ...defaultForm(), ...(data ?? {}) };
76
+ if (!form.source_name) form.source_name = String(sourceOptions.value[0]?.value ?? "");
77
+ return form;
78
+ },
79
+ commit: form => {
80
+ Object.assign(formData, form);
81
+ dialog.editing = Boolean(id);
82
+ }
83
+ });
77
84
  }
78
85
  async function loadSourceOptions() {
79
86
  if (sourceOptions.value.length || loadingSources.value) return;
@@ -10,10 +10,11 @@
10
10
  />
11
11
 
12
12
  <ProDialog
13
+ ref="dialogRef"
13
14
  v-model="dialog.visible"
14
15
  class="system-config-dialog"
15
16
  :title="t(dialog.titleKey, { resource: t('system.base.config.resource') })"
16
- width="min(1040px, calc(100vw - 32px))"
17
+ width="min(1280px, calc(100vw - 32px))"
17
18
  top="4vh"
18
19
  :confirm-loading="saving"
19
20
  @confirm="handleSubmit"
@@ -32,6 +33,7 @@
32
33
  :rules="rules"
33
34
  :col-span="12"
34
35
  :gutter="24"
36
+ label-width="10em"
35
37
  label-position="right"
36
38
  scroll-to-error
37
39
  >
@@ -96,6 +98,7 @@
96
98
  :fields="localizedFormFields"
97
99
  :col-span="12"
98
100
  :gutter="24"
101
+ label-width="10em"
99
102
  label-position="right"
100
103
  scroll-to-error
101
104
  />
@@ -160,6 +163,7 @@ defineOptions({
160
163
 
161
164
  const { BUTTONS } = useAuthButtons();
162
165
  const proTable = ref<ProTableInstance>();
166
+ const dialogRef = ref<InstanceType<typeof ProDialog>>();
163
167
  const basicFormRef = ref<ProFormInstance>();
164
168
  const activeTab = ref("basic");
165
169
 
@@ -635,29 +639,48 @@ function refreshTable() {
635
639
  * 打开系统配置弹窗。
636
640
  */
637
641
  async function handleOpenDialog(configId?: number) {
638
- await loadEnabledBaseLanguages();
639
642
  resetForm();
640
643
  dialog.titleKey = configId ? "common.action.edit_resource" : "common.action.create_resource";
641
- if (configId) {
642
- Object.assign(formData, await defBaseConfigService.GetBaseConfig({ id: configId }));
643
- if (formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM) {
644
- loadFormValue(formData.value);
644
+ await dialogRef.value?.open({
645
+ load: async () => ({
646
+ data: configId ? await defBaseConfigService.GetBaseConfig({ id: configId }) : undefined,
647
+ languages: await loadEnabledBaseLanguages()
648
+ }),
649
+ commit: ({ data }) => {
650
+ if (data) {
651
+ Object.assign(formData, data);
652
+ if (formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM) loadFormValue(formData.value);
653
+ }
654
+ activeTab.value = configId && formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM ? "form" : "basic";
645
655
  }
646
- }
647
- activeTab.value = configId && formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM ? "form" : "basic";
648
- dialog.visible = true;
656
+ });
649
657
  }
650
658
 
651
659
  /** 按注册定义创建独立模型,并用已保存的配置值覆盖默认值。 */
652
660
  function loadFormValue(value?: string) {
653
661
  const definition = formDefinition.value;
662
+ const parsed = value ? parseJSONValue(value) : {};
654
663
  formValue.value =
655
- formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM && definition
656
- ? Object.assign(definition.createModel(), value ? JSON.parse(value) : {})
657
- : {};
664
+ formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM && definition && isJSONObject(parsed)
665
+ ? Object.assign(definition.createModel(), parsed)
666
+ : definition?.createModel() ?? {};
658
667
  valueFormRef.value?.clearValidate();
659
668
  }
660
669
 
670
+ /** 安全解析运行配置 JSON,脏数据回退到表单默认模型。 */
671
+ function parseJSONValue(value: string): unknown {
672
+ try {
673
+ return JSON.parse(value);
674
+ } catch {
675
+ return undefined;
676
+ }
677
+ }
678
+
679
+ /** 判断运行配置 JSON 是否为可合并对象。 */
680
+ function isJSONObject(value: unknown): value is Record<string, unknown> {
681
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
682
+ }
683
+
661
684
  /** 按字段声明恢复运行配置提交值的 JSON 类型。 */
662
685
  function normalizeRuntimeConfigValue() {
663
686
  if (!formDefinition.value) return;
@@ -669,7 +692,8 @@ function normalizeRuntimeConfigValue() {
669
692
  } else if (field.valueType === "boolean" && typeof value === "string") {
670
693
  setRuntimeConfigFieldValue(field.prop, value === "true");
671
694
  } else if (field.valueType === "json" && typeof value === "string" && value !== "") {
672
- setRuntimeConfigFieldValue(field.prop, JSON.parse(value));
695
+ const parsed = parseJSONValue(value);
696
+ if (parsed !== undefined) setRuntimeConfigFieldValue(field.prop, parsed);
673
697
  }
674
698
  }
675
699
  }
@@ -690,7 +714,7 @@ function setRuntimeConfigFieldValue(prop: string, value: unknown) {
690
714
  * 关闭系统配置弹窗并恢复默认表单值。
691
715
  */
692
716
  function handleCloseDialog() {
693
- dialog.visible = false;
717
+ dialogRef.value?.close();
694
718
  resetForm();
695
719
  }
696
720
 
@@ -301,16 +301,15 @@ function refreshTable() {
301
301
  /**
302
302
  * 加载部门下拉树数据,供弹窗选择上级部门。
303
303
  */
304
- async function loadDeptOptions() {
304
+ async function requestDeptOptions(tenantId = formData.tenant_id) {
305
305
  // 默认租户未选择目标租户时仅保留顶级部门,避免混入其他租户的部门树。
306
- if (isDefaultTenant.value && !formData.tenant_id) {
307
- deptOptions.value = [{ value: 0, label: t("system.base.dept.value.root"), disabled: false, has_children: true, children: [] }];
308
- return;
306
+ if (isDefaultTenant.value && !tenantId) {
307
+ return [{ value: 0, label: t("system.base.dept.value.root"), disabled: false, has_children: true, children: [] }];
309
308
  }
310
309
  const optionBaseDeptResponse = await defBaseDeptService.OptionBaseDept({
311
- tenant_id: toRequestTenantId(formData.tenant_id)
310
+ tenant_id: toRequestTenantId(tenantId)
312
311
  });
313
- deptOptions.value = [
312
+ return [
314
313
  {
315
314
  value: 0,
316
315
  label: t("system.base.dept.value.root"),
@@ -321,6 +320,11 @@ async function loadDeptOptions() {
321
320
  ];
322
321
  }
323
322
 
323
+ /** 加载部门下拉树数据并更新表单选项。 */
324
+ async function loadDeptOptions() {
325
+ deptOptions.value = await requestDeptOptions();
326
+ }
327
+
324
328
  /**
325
329
  * 切换表单租户时重新加载对应租户的上级部门树。
326
330
  */
@@ -333,31 +337,33 @@ async function handleFormTenantChange() {
333
337
  * 打开部门弹窗。
334
338
  */
335
339
  async function handleOpenDialog(parent_id?: number, deptId?: number, tenantId?: number) {
336
- resetForm();
337
- await loadTenantOptions();
338
- if (deptId) {
339
- dialog.titleKey = "common.action.edit_resource";
340
- dialog.visible = true;
341
- defBaseDeptService.GetBaseDept({ id: deptId }).then(async data => {
342
- Object.assign(formData, data);
343
- await loadDeptOptions();
344
- });
345
- return;
346
- }
347
-
348
- // 从部门行新增子部门时,继承父部门租户并加载同租户上级部门树。
349
- formData.tenant_id = tenantId;
350
- await loadDeptOptions();
351
- dialog.titleKey = "common.action.create_resource";
352
- dialog.visible = true;
353
- formData.parent_id = parent_id ?? 0;
340
+ await formDialogRef.value?.open({
341
+ load: async () => {
342
+ await loadTenantOptions();
343
+ const data = deptId ? await defBaseDeptService.GetBaseDept({ id: deptId }) : undefined;
344
+ const options = await requestDeptOptions(data?.tenant_id ?? tenantId);
345
+ return { data, options };
346
+ },
347
+ commit: ({ data, options }) => {
348
+ resetForm();
349
+ deptOptions.value = options;
350
+ dialog.titleKey = deptId ? "common.action.edit_resource" : "common.action.create_resource";
351
+ if (data) {
352
+ Object.assign(formData, data);
353
+ } else {
354
+ // 从部门行新增子部门时,继承父部门租户并加载同租户上级部门树。
355
+ formData.tenant_id = tenantId;
356
+ formData.parent_id = parent_id ?? 0;
357
+ }
358
+ }
359
+ });
354
360
  }
355
361
 
356
362
  /**
357
363
  * 关闭部门弹窗。
358
364
  */
359
365
  function handleCloseDialog() {
360
- dialog.visible = false;
366
+ formDialogRef.value?.close();
361
367
  resetForm();
362
368
  }
363
369
 
@@ -247,22 +247,26 @@ function refreshTable() {
247
247
  * 打开字典编辑弹窗。
248
248
  */
249
249
  async function handleOpenDialog(dictId?: number) {
250
- await loadEnabledBaseLanguages();
251
250
  resetForm();
252
251
  dialog.editing = Boolean(dictId);
253
- dialog.visible = true;
254
- if (!dictId) return;
255
-
256
- const data = await defBaseDictService.GetBaseDict({ id: dictId });
257
- Object.assign(formData, data);
258
- i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
252
+ await formDialogRef.value?.open({
253
+ load: async () => ({
254
+ data: dictId ? await defBaseDictService.GetBaseDict({ id: dictId }) : undefined,
255
+ languages: await loadEnabledBaseLanguages()
256
+ }),
257
+ commit: ({ data }) => {
258
+ if (!data) return;
259
+ Object.assign(formData, data);
260
+ i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
261
+ }
262
+ });
259
263
  }
260
264
 
261
265
  /**
262
266
  * 关闭字典弹窗并恢复表单初始值。
263
267
  */
264
268
  function handleCloseDialog() {
265
- dialog.visible = false;
269
+ formDialogRef.value?.close();
266
270
  resetForm();
267
271
  }
268
272
 
@@ -310,22 +310,26 @@ function refreshTable() {
310
310
  * 打开字典项编辑弹窗。
311
311
  */
312
312
  async function handleOpenDialog(dictItemId?: number) {
313
- await loadEnabledBaseLanguages();
314
313
  resetForm();
315
314
  dialog.editing = Boolean(dictItemId);
316
- dialog.visible = true;
317
- if (!dictItemId) return;
318
-
319
- const data = await defBaseDictItemService.GetBaseDictItem({ id: dictItemId });
320
- Object.assign(formData, data);
321
- i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
315
+ await formDialogRef.value?.open({
316
+ load: async () => ({
317
+ data: dictItemId ? await defBaseDictItemService.GetBaseDictItem({ id: dictItemId }) : undefined,
318
+ languages: await loadEnabledBaseLanguages()
319
+ }),
320
+ commit: ({ data }) => {
321
+ if (!data) return;
322
+ Object.assign(formData, data);
323
+ i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
324
+ }
325
+ });
322
326
  }
323
327
 
324
328
  /**
325
329
  * 关闭字典项弹窗并恢复默认值。
326
330
  */
327
331
  function handleCloseDialog() {
328
- dialog.visible = false;
332
+ formDialogRef.value?.close();
329
333
  resetForm();
330
334
  }
331
335
 
@@ -213,14 +213,14 @@ const headerActions = computed<HeaderActionProps[]>(() => [
213
213
 
214
214
  /** 请求国际化自定义翻译分页列表。 */
215
215
  async function requestBaseI18nCustomTable(params: PageBaseI18nCustomRequest) {
216
- await loadLanguages();
216
+ enabledLanguages.value = await loadLanguages();
217
217
  const data = await defBaseI18nCustomService.PageBaseI18nCustom(buildPageRequest(params));
218
218
  return { data: { list: data.items ?? [], total: data.total } };
219
219
  }
220
220
 
221
221
  /** 加载启用语言选项。 */
222
222
  async function loadLanguages() {
223
- enabledLanguages.value = await loadEnabledBaseLanguages();
223
+ return loadEnabledBaseLanguages();
224
224
  }
225
225
 
226
226
  /** 返回语言显示名称。 */
@@ -231,17 +231,23 @@ function getLanguageLabel(locale: string) {
231
231
 
232
232
  /** 打开国际化自定义翻译编辑弹窗。 */
233
233
  async function handleOpenDialog(id?: number) {
234
- await loadLanguages();
235
234
  resetForm();
236
235
  dialog.editing = Boolean(id);
237
- dialog.visible = true;
238
- if (!id) return;
239
- Object.assign(formData, await defBaseI18nCustomService.GetBaseI18nCustom({ id }));
236
+ await formDialogRef.value?.open({
237
+ load: async () => ({
238
+ languages: await loadLanguages(),
239
+ data: id ? await defBaseI18nCustomService.GetBaseI18nCustom({ id }) : undefined
240
+ }),
241
+ commit: ({ languages, data }) => {
242
+ enabledLanguages.value = languages;
243
+ if (data) Object.assign(formData, data);
244
+ }
245
+ });
240
246
  }
241
247
 
242
248
  /** 关闭编辑弹窗并重置表单。 */
243
249
  function handleCloseDialog() {
244
- dialog.visible = false;
250
+ formDialogRef.value?.close();
245
251
  resetForm();
246
252
  }
247
253
 
@@ -368,22 +368,26 @@ function refreshTable() {
368
368
  * 打开定时任务弹窗。
369
369
  */
370
370
  async function handleOpenDialog(jobId?: number) {
371
- await loadEnabledBaseLanguages();
372
371
  resetForm();
373
372
  dialog.editing = Boolean(jobId);
374
- dialog.visible = true;
375
- if (!jobId) return;
376
-
377
- const data = await defBaseJobService.GetBaseJob({ id: jobId });
378
- Object.assign(formData, data);
379
- i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
373
+ await formDialogRef.value?.open({
374
+ load: async () => ({
375
+ data: jobId ? await defBaseJobService.GetBaseJob({ id: jobId }) : undefined,
376
+ languages: await loadEnabledBaseLanguages()
377
+ }),
378
+ commit: ({ data }) => {
379
+ if (!data) return;
380
+ Object.assign(formData, data);
381
+ i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
382
+ }
383
+ });
380
384
  }
381
385
 
382
386
  /**
383
387
  * 关闭定时任务弹窗并恢复默认表单值。
384
388
  */
385
389
  function handleCloseDialog() {
386
- dialog.visible = false;
390
+ formDialogRef.value?.close();
387
391
  resetForm();
388
392
  }
389
393
 
@@ -2,7 +2,7 @@
2
2
  <div class="table-box">
3
3
  <ProTable ref="proTable" row-key="id" :columns="columns" :request-api="requestBaseJobLogTable" />
4
4
 
5
- <ProDialog v-model="dialog.visible" :title="t('system.base.job.log.title.detail')" width="1200px" @close="handleCloseDialog">
5
+ <ProDialog ref="dialogRef" v-model="dialog.visible" :title="t('system.base.job.log.title.detail')" width="1200px" @close="handleCloseDialog">
6
6
  <div class="detail-container">
7
7
  <el-descriptions :title="t('system.base.job.log.section.basic')" border :column="2">
8
8
  <el-descriptions-item :label="t('common.field.status')">
@@ -60,6 +60,7 @@ defineOptions({
60
60
 
61
61
  const route = useRoute();
62
62
  const proTable = ref<ProTableInstance>();
63
+ const dialogRef = ref<InstanceType<typeof ProDialog>>();
63
64
  const initialJobID = Number(route.query.jobId ?? 0);
64
65
 
65
66
  const dialog = reactive({
@@ -180,21 +181,25 @@ async function requestBaseJobLogTable(params: PageBaseJobLogRequest) {
180
181
  /**
181
182
  * 打开定时任务日志详情弹窗。
182
183
  */
183
- function handleOpenDialog(logId?: number) {
184
+ async function handleOpenDialog(logId?: number) {
184
185
  resetDetail();
185
- dialog.visible = true;
186
- if (!logId) return;
187
-
188
- defBaseJobLogService.GetBaseJobLog({ id: logId }).then(data => {
189
- Object.assign(detail, data);
190
- });
186
+ try {
187
+ await dialogRef.value?.open({
188
+ load: () => (logId ? defBaseJobLogService.GetBaseJobLog({ id: logId }) : undefined),
189
+ commit: data => {
190
+ if (data) Object.assign(detail, data);
191
+ }
192
+ });
193
+ } catch {
194
+ dialogRef.value?.close();
195
+ }
191
196
  }
192
197
 
193
198
  /**
194
199
  * 关闭定时任务日志详情弹窗。
195
200
  */
196
201
  function handleCloseDialog() {
197
- dialog.visible = false;
202
+ dialogRef.value?.close();
198
203
  resetDetail();
199
204
  }
200
205