@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
@@ -144,14 +144,17 @@ async function requestBaseLanguageTable(params: PageBaseLanguageRequest) {
144
144
  async function handleOpenDialog(id?: number) {
145
145
  resetForm();
146
146
  dialog.editing = Boolean(id);
147
- dialog.visible = true;
148
- if (!id) return;
149
- Object.assign(formData, await defBaseLanguageService.GetBaseLanguage({ id }));
147
+ await formDialogRef.value?.open({
148
+ load: () => (id ? defBaseLanguageService.GetBaseLanguage({ id }) : undefined),
149
+ commit: data => {
150
+ if (data) Object.assign(formData, data);
151
+ }
152
+ });
150
153
  }
151
154
 
152
155
  /** 关闭语言编辑弹窗。 */
153
156
  function handleCloseDialog() {
154
- dialog.visible = false;
157
+ formDialogRef.value?.close();
155
158
  resetForm();
156
159
  }
157
160
 
@@ -442,23 +442,34 @@ async function requestTable(params: Record<string, unknown>) {
442
442
 
443
443
  /** 打开登录策略表单。 */
444
444
  async function openDialog(id?: number) {
445
- Object.assign(formData, defaultForm());
446
- dialog.titleKey = id ? "common.action.edit" : "common.action.create";
447
- await loadTenantOptions();
448
- if (id) {
449
- const detail = await defBaseLoginPolicyService.GetBaseLoginPolicy({ id });
450
- Object.assign(formData, detail, { initial_password: "", rules: detail.rules ?? [] });
451
- if (formData.scope_type === BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_USER && formData.tenant_id) {
452
- await loadUserOptions(formData.tenant_id);
445
+ await formDialogRef.value?.open({
446
+ load: async () => {
447
+ await loadTenantOptions();
448
+ const detail = id ? await defBaseLoginPolicyService.GetBaseLoginPolicy({ id }) : undefined;
449
+ const loadedUserOptions =
450
+ detail?.scope_type === BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_USER && detail.tenant_id
451
+ ? await requestUserOptions(detail.tenant_id)
452
+ : [];
453
+ return { detail, loadedUserOptions };
454
+ },
455
+ commit: ({ detail, loadedUserOptions }) => {
456
+ Object.assign(formData, defaultForm());
457
+ dialog.titleKey = id ? "common.action.edit" : "common.action.create";
458
+ if (detail) Object.assign(formData, detail, { initial_password: "", rules: detail.rules ?? [] });
459
+ userOptions.value = loadedUserOptions;
453
460
  }
454
- }
455
- dialog.visible = true;
461
+ });
456
462
  }
457
463
 
458
464
  /** 加载指定租户的用户选项。 */
459
465
  async function loadUserOptions(tenantId: number) {
466
+ userOptions.value = await requestUserOptions(tenantId);
467
+ }
468
+
469
+ /** 请求指定租户的用户选项。 */
470
+ async function requestUserOptions(tenantId: number) {
460
471
  const response = await defBaseUserService.OptionBaseUser({ keyword: "", tenant_id: tenantId });
461
- userOptions.value = response.list ?? [];
472
+ return response.list ?? [];
462
473
  }
463
474
 
464
475
  /** 提交登录策略表单。 */
@@ -1053,8 +1053,10 @@ function buildSubmitPayload(): BaseMenuForm {
1053
1053
  /** 加载菜单树选项和 API 列表,确保弹窗打开时相关数据已可用。 */
1054
1054
  async function loadDialogResources() {
1055
1055
  const [menuData, apiData] = await Promise.all([defBaseMenuService.TreeBaseMenu({}), defBaseApiService.OptionBaseApi({})]);
1056
- menuOptions.value = buildMenuOptions(menuData.base_menus ?? []);
1057
- apiList.value = apiData.base_apis ?? [];
1056
+ return {
1057
+ menuOptions: buildMenuOptions(menuData.base_menus ?? []),
1058
+ apiList: apiData.base_apis ?? []
1059
+ };
1058
1060
  }
1059
1061
 
1060
1062
  /** 根据关键字递归过滤菜单树,保留匹配节点及其父级。 */
@@ -1108,24 +1110,29 @@ function refreshTable() {
1108
1110
  * parentMenu 为新增时的固定父节点,menuId 为编辑时的菜单 ID。
1109
1111
  */
1110
1112
  async function handleOpenDialog(parentMenu?: BaseMenu, menuId?: number) {
1111
- await loadEnabledBaseLanguages();
1112
- await loadDialogResources();
1113
- dialog.parentLocked = Boolean(parentMenu || menuId);
1114
- dialog.editing = Boolean(menuId);
1115
- dialog.parentType = parentMenu?.type ?? BaseMenuType.BASE_MENU_TYPE_UNSPECIFIED;
1116
- resetForm(menuId ? undefined : { parent_id: parentMenu?.id });
1117
- dialog.visible = true;
1118
-
1119
- if (menuId) {
1120
- const data = await defBaseMenuService.GetBaseMenu({ id: menuId });
1121
- resetForm(data);
1122
- return;
1123
- }
1113
+ await formDialogRef.value?.open({
1114
+ load: async () => {
1115
+ await loadEnabledBaseLanguages();
1116
+ const [resources, data] = await Promise.all([
1117
+ loadDialogResources(),
1118
+ menuId ? defBaseMenuService.GetBaseMenu({ id: menuId }) : Promise.resolve(undefined)
1119
+ ]);
1120
+ return { resources, data };
1121
+ },
1122
+ commit: ({ resources, data }) => {
1123
+ menuOptions.value = resources.menuOptions;
1124
+ apiList.value = resources.apiList;
1125
+ dialog.parentLocked = Boolean(parentMenu || menuId);
1126
+ dialog.editing = Boolean(menuId);
1127
+ dialog.parentType = parentMenu?.type ?? BaseMenuType.BASE_MENU_TYPE_UNSPECIFIED;
1128
+ resetForm(data ?? (menuId ? undefined : { parent_id: parentMenu?.id }));
1129
+ }
1130
+ });
1124
1131
  }
1125
1132
 
1126
1133
  /** 关闭菜单弹窗并显式重置表单与校验状态。 */
1127
1134
  function handleCloseDialog() {
1128
- dialog.visible = false;
1135
+ formDialogRef.value?.close();
1129
1136
  dialog.parentType = BaseMenuType.BASE_MENU_TYPE_UNSPECIFIED;
1130
1137
  dialog.parentLocked = true;
1131
1138
  resetForm();
@@ -47,6 +47,7 @@
47
47
  </template>
48
48
  </FormDialog>
49
49
  <ProDialog
50
+ ref="contentDialogRef"
50
51
  v-model="content.visible"
51
52
  :title="content.data?.base_message?.title || t('system.base.message.content.title')"
52
53
  width="760px"
@@ -68,6 +69,7 @@
68
69
  </template>
69
70
  </ProDialog>
70
71
  <ProDialog
72
+ ref="detailDialogRef"
71
73
  v-model="detail.visible"
72
74
  :title="t('system.base.message.send_detail.title')"
73
75
  width="min(1200px, calc(100vw - 32px))"
@@ -112,7 +114,9 @@
112
114
  <el-table-column prop="include_children" :label="t('system.base.message.field.include_children')" width="120">
113
115
  <template #default="scope">{{ scope.row.include_children ? t("common.value.yes") : t("common.value.no") }}</template>
114
116
  </el-table-column>
115
- <el-table-column prop="status" :label="t('common.field.status')" width="120" />
117
+ <el-table-column prop="status" :label="t('common.field.status')" width="120">
118
+ <template #default="scope">{{ optionLabel(dispatchStatusOptions, scope.row.status) }}</template>
119
+ </el-table-column>
116
120
  <el-table-column prop="matched_total" :label="t('system.base.message.field.matched_total')" width="110" align="right" />
117
121
  <el-table-column prop="inserted_total" :label="t('system.base.message.field.inserted_total')" width="110" align="right" />
118
122
  <el-table-column prop="attempt_count" :label="t('system.base.message.field.attempt_count')" width="90" align="right" />
@@ -190,6 +194,8 @@ const { BUTTONS } = useAuthButtons();
190
194
  const { isDefaultTenant, tenantColumns, tenantFormField, loadTenantOptions } = useTenantScope();
191
195
  const proTable = ref<ProTableInstance>();
192
196
  const formDialogRef = ref<InstanceType<typeof FormDialog>>();
197
+ const contentDialogRef = ref<InstanceType<typeof ProDialog>>();
198
+ const detailDialogRef = ref<InstanceType<typeof ProDialog>>();
193
199
  const categoryOptions = ref<ProFormOption[]>([]);
194
200
  const dialog = reactive({ visible: false, titleKey: "common.action.create" });
195
201
  const content = reactive<{ visible: boolean; loading: boolean; data?: BaseMessageDetail }>({ visible: false, loading: false });
@@ -219,6 +225,13 @@ const statusOptions = computed<ProFormOption[]>(() => [
219
225
  { label: t("system.base.message.status.published"), value: MessageStatus.MESSAGE_STATUS_PUBLISHED },
220
226
  { label: t("system.base.message.status.revoked"), value: MessageStatus.MESSAGE_STATUS_REVOKED }
221
227
  ]);
228
+ const dispatchStatusOptions = computed<ProFormOption[]>(() => [
229
+ { label: t("system.base.message.dispatch_status.pending"), value: MessageDispatchStatus.MESSAGE_DISPATCH_STATUS_PENDING },
230
+ { label: t("system.base.message.dispatch_status.running"), value: MessageDispatchStatus.MESSAGE_DISPATCH_STATUS_RUNNING },
231
+ { label: t("system.base.message.dispatch_status.succeeded"), value: MessageDispatchStatus.MESSAGE_DISPATCH_STATUS_SUCCEEDED },
232
+ { label: t("system.base.message.dispatch_status.failed"), value: MessageDispatchStatus.MESSAGE_DISPATCH_STATUS_FAILED },
233
+ { label: t("system.base.message.dispatch_status.cancelled"), value: MessageDispatchStatus.MESSAGE_DISPATCH_STATUS_CANCELLED }
234
+ ]);
222
235
  onMounted(() => {
223
236
  void loadCategoryOptions();
224
237
  void loadTenantOptions();
@@ -539,26 +552,42 @@ async function requestTable(params: Record<string, unknown>) {
539
552
 
540
553
  /** 加载消息分类选项。 */
541
554
  async function loadCategoryOptions() {
555
+ categoryOptions.value = await requestCategoryOptions();
556
+ }
557
+
558
+ /** 请求消息分类选项。 */
559
+ async function requestCategoryOptions() {
542
560
  const result = await defBaseMessageCategoryService.OptionBaseMessageCategory({});
543
- categoryOptions.value = result.list.map(item => ({ label: item.label, value: item.value, disabled: item.disabled }));
561
+ return result.list.map(item => ({ label: item.label, value: item.value, disabled: item.disabled }));
544
562
  }
545
563
 
546
564
  /** 打开消息草稿表单。 */
547
565
  async function openDialog(id?: number) {
548
- await loadTenantOptions();
549
- Object.assign(formState, defaultForm());
550
- dialog.titleKey = id ? "common.action.edit" : "common.action.create";
551
- if (id) {
552
- const detail = await defBaseMessageService.GetBaseMessage({ id });
553
- Object.assign(formState, detail.form);
554
- formState.audiences = detail.form?.audiences?.map(item => ({ ...item })) ?? [defaultAudience()];
555
- }
556
- await loadCategoryOptions();
557
- dialog.visible = true;
566
+ await formDialogRef.value?.open({
567
+ load: async () => {
568
+ await loadTenantOptions();
569
+ const [detail, categories] = await Promise.all([
570
+ id ? defBaseMessageService.GetBaseMessage({ id }) : Promise.resolve(undefined),
571
+ requestCategoryOptions()
572
+ ]);
573
+ return { detail, categories };
574
+ },
575
+ commit: ({ detail, categories }) => {
576
+ Object.assign(formState, defaultForm());
577
+ dialog.titleKey = id ? "common.action.edit" : "common.action.create";
578
+ categoryOptions.value = categories;
579
+ if (detail) {
580
+ Object.assign(formState, detail.form);
581
+ formState.audiences = detail.form?.audiences?.map(item => ({ ...item })) ?? [defaultAudience()];
582
+ }
583
+ }
584
+ });
558
585
  }
559
586
 
560
587
  /** 提交消息草稿。 */
561
588
  async function handleSubmit() {
589
+ const valid = await formDialogRef.value?.validate();
590
+ if (!valid) return;
562
591
  const audiences = formState.audiences.length > 0 ? formState.audiences.map(item => ({ ...item })) : [defaultAudience()];
563
592
  const payload: BaseMessageForm = {
564
593
  ...formState,
@@ -576,21 +605,27 @@ async function handleSubmit() {
576
605
 
577
606
  /** 打开发送详情并加载投递进度。 */
578
607
  async function openDetail(id: number) {
579
- detail.data = await defBaseMessageService.GetBaseMessage({ id });
580
- detail.visible = true;
608
+ await detailDialogRef.value?.open({
609
+ load: () => defBaseMessageService.GetBaseMessage({ id }),
610
+ commit: data => {
611
+ detail.data = data;
612
+ }
613
+ });
581
614
  }
582
615
 
583
616
  /** 打开消息正文并单独展示内容。 */
584
617
  async function openContent(id: number) {
585
- content.visible = true;
586
- content.loading = true;
587
- content.data = undefined;
588
618
  try {
589
- content.data = await defBaseMessageService.GetBaseMessage({ id });
619
+ await contentDialogRef.value?.open({
620
+ load: () => defBaseMessageService.GetBaseMessage({ id }),
621
+ commit: data => {
622
+ content.data = data;
623
+ content.loading = false;
624
+ }
625
+ });
590
626
  } catch {
591
- content.visible = false;
592
- } finally {
593
627
  content.loading = false;
628
+ contentDialogRef.value?.close();
594
629
  }
595
630
  }
596
631
 
@@ -604,7 +639,16 @@ async function retryDispatch(id: number) {
604
639
 
605
640
  /** 删除草稿消息。 */
606
641
  async function handleDelete(value: BaseMessage | BaseMessage[] | number | number[]) {
607
- const ids = normalizeSelectedIds(value as Parameters<typeof normalizeSelectedIds>[0]);
642
+ const idList = Array.isArray(value)
643
+ ? value.map(item => (typeof item === "object" ? item.id : item))
644
+ : typeof value === "object"
645
+ ? [value.id]
646
+ : normalizeSelectedIds(value);
647
+ const ids = idList.filter(item => item !== undefined && item !== null && item !== "");
648
+ if (!ids.length) {
649
+ ElMessage.warning(t("common.message.select_delete_item"));
650
+ return;
651
+ }
608
652
  await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" });
609
653
  await defBaseMessageService.DeleteBaseMessage({ id: ids.join(",") });
610
654
  ElMessage.success(t("common.message.operation_success"));
@@ -302,14 +302,20 @@ async function requestTable(params: Record<string, unknown>) {
302
302
 
303
303
  /** 打开消息分类表单。 */
304
304
  async function openDialog(id?: number) {
305
- Object.assign(formData, defaultForm());
306
- dialog.titleKey = id ? "common.action.edit" : "common.action.create";
307
- if (id) Object.assign(formData, await defBaseMessageCategoryService.GetBaseMessageCategory({ id }));
308
- dialog.visible = true;
305
+ await formDialogRef.value?.open({
306
+ load: () => (id ? defBaseMessageCategoryService.GetBaseMessageCategory({ id }) : undefined),
307
+ commit: data => {
308
+ Object.assign(formData, defaultForm());
309
+ dialog.titleKey = id ? "common.action.edit" : "common.action.create";
310
+ if (data) Object.assign(formData, data);
311
+ }
312
+ });
309
313
  }
310
314
 
311
315
  /** 提交消息分类表单。 */
312
316
  async function handleSubmit() {
317
+ const valid = await formDialogRef.value?.validate();
318
+ if (!valid) return;
313
319
  const payload = formData as BaseMessageCategoryForm;
314
320
  if (payload.id) await defBaseMessageCategoryService.UpdateBaseMessageCategory({ base_message_category: payload });
315
321
  else await defBaseMessageCategoryService.CreateBaseMessageCategory({ base_message_category: payload });
@@ -266,12 +266,17 @@ async function loadApiOptions() {
266
266
  }
267
267
 
268
268
  async function handleOpenDialog(id?: number) {
269
- await loadTenantOptions();
270
- await loadApiOptions();
271
269
  resetForm();
272
270
  dialog.titleKey = id ? "common.action.edit" : "common.action.create";
273
- dialog.visible = true;
274
- if (id) Object.assign(formData, await defOauthClientService.GetOauthClient({ id }));
271
+ await formDialogRef.value?.open({
272
+ load: async () => ({
273
+ data: id ? await defOauthClientService.GetOauthClient({ id }) : undefined,
274
+ resources: await Promise.all([loadTenantOptions(), loadApiOptions()])
275
+ }),
276
+ commit: ({ data }) => {
277
+ if (data) Object.assign(formData, data);
278
+ }
279
+ });
275
280
  }
276
281
 
277
282
  function resetForm() {
@@ -289,7 +294,7 @@ function resetForm() {
289
294
  }
290
295
 
291
296
  function handleCloseDialog() {
292
- dialog.visible = false;
297
+ formDialogRef.value?.close();
293
298
  resetForm();
294
299
  }
295
300
 
@@ -369,6 +374,11 @@ function handleDelete(selected?: number | string | Array<number | string> | Oaut
369
374
  }
370
375
 
371
376
  async function handleCopyCredentials(row: OauthClient) {
377
+ await ElMessageBox.confirm(
378
+ t("system.base.oauth_client.confirm.rotate_credentials"),
379
+ t("common.title.warning"),
380
+ { type: "warning" }
381
+ );
372
382
  const credentials = await defOauthClientService.RotateOauthClientCredentials({ id: row.id });
373
383
  const content = [
374
384
  `${t("system.base.oauth_client.field.client_id")}:${credentials.client_id}`,
@@ -249,15 +249,21 @@ function refreshTable() {
249
249
  /** 打开岗位编辑弹窗。 */
250
250
  async function handleOpenDialog(id?: number) {
251
251
  resetForm();
252
- await loadTenantOptions();
253
252
  dialog.titleKey = id ? "common.action.edit_resource" : "common.action.create_resource";
254
- dialog.visible = true;
255
- if (id) Object.assign(formData, await defBasePostService.GetBasePost({ id }));
253
+ await formDialogRef.value?.open({
254
+ load: async () => ({
255
+ data: id ? await defBasePostService.GetBasePost({ id }) : undefined,
256
+ tenants: await loadTenantOptions()
257
+ }),
258
+ commit: ({ data }) => {
259
+ if (data) Object.assign(formData, data);
260
+ }
261
+ });
256
262
  }
257
263
 
258
264
  /** 关闭岗位弹窗并清理表单。 */
259
265
  function handleCloseDialog() {
260
- dialog.visible = false;
266
+ formDialogRef.value?.close();
261
267
  resetForm();
262
268
  }
263
269
 
@@ -207,6 +207,7 @@ const ruleOptions = ref<ProFormOption[]>([]);
207
207
  const ruleCatalog = ref<BaseRedactRule[]>([]);
208
208
  const dialog = reactive({ visible: false, titleKey: "common.action.create_resource" });
209
209
  const form = reactive<OutputFormState>(defaultForm());
210
+ let responseFieldsRequestRevision = 0;
210
211
  const statusOptions = computed<ProFormOption[]>(() => [{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE }, { label: t("common.status.disabled"), value: Status.STATUS_DISABLE }]);
211
212
  const serviceOptions = computed<ProFormOption[]>(() => {
212
213
  const services = new Map<string, string>();
@@ -254,21 +255,27 @@ const headerActions = computed<HeaderActionProps[]>(() => [{ label: t("common.ac
254
255
  /** 请求出库脱敏策略分页列表。 */
255
256
  async function requestTable(params: PageBaseRedactOutputPolicyRequest) { const data = await defBaseRedactOutputPolicyService.PageBaseRedactOutputPolicy(buildPageRequest(params)); return { data: { list: data.base_redact_output_policies ?? [], total: data.total } }; }
256
257
  /** 加载 API 选项。 */
257
- async function loadApis() { const data = await defBaseApiService.OptionBaseApi({ include_public: true }); apiCatalog.value = data.base_apis ?? []; }
258
+ async function loadApis() { apiCatalog.value = await requestApis(); }
259
+ /** 请求 API 选项。 */
260
+ async function requestApis() { const data = await defBaseApiService.OptionBaseApi({ include_public: true }); return data.base_apis ?? []; }
258
261
  /** 加载脱敏规则选项。 */
259
- async function loadRules() { const data = await defBaseRedactRuleService.PageBaseRedactRule({ code: "", name: "", rule_type: "", page_num: 1, page_size: 100 }); ruleCatalog.value = data.base_redact_rules ?? []; ruleOptions.value = ruleCatalog.value.map(item => ({ label: `${item.name} (${item.code})`, value: item.id, disabled: item.status !== Status.STATUS_ENABLE })); }
262
+ async function loadRules() { const rules = await requestRules(); ruleCatalog.value = rules.catalog; ruleOptions.value = rules.options; }
263
+ /** 请求脱敏规则选项。 */
264
+ async function requestRules() { const data = await defBaseRedactRuleService.PageBaseRedactRule({ code: "", name: "", rule_type: "", page_num: 1, page_size: 100 }); const catalog = data.base_redact_rules ?? []; return { catalog, options: catalog.map(item => ({ label: `${item.name} (${item.code})`, value: item.id, disabled: item.status !== Status.STATUS_ENABLE })) }; }
260
265
  /** 服务变更后清空接口和返回字段。 */
261
- function handleServiceChange(serviceName?: string) { form.service_name = serviceName ?? ""; form.api_id = undefined; form.operation = ""; form.message_ref = ""; form.field_path = ""; form.field_rows = []; }
266
+ function handleServiceChange(serviceName?: string) { responseFieldsRequestRevision += 1; form.service_name = serviceName ?? ""; form.api_id = undefined; form.operation = ""; form.message_ref = ""; form.field_path = ""; form.field_rows = []; }
262
267
  /** API 变更后重新加载返回字段。 */
263
268
  async function handleApiChange(apiId?: number) { const api = interfaceOptions.value.find(item => item.id === apiId); form.service_name = api?.service_name ?? form.service_name; form.operation = api?.operation ?? ""; form.field_rows = []; if (apiId !== undefined) await loadResponseFields(apiId); }
264
269
  /** 加载 API 返回字段并初始化表格行。 */
265
- async function loadResponseFields(apiId: number) { const api = apiCatalog.value.find(item => item.id === apiId); if (!api) return; const doc = await defBaseRedactOutputPolicyService.GetBaseRedactOutputFieldDoc({ api_id: apiId }); const rows: OutputFieldRow[] = []; for (const response of doc.responses ?? []) { if (response.body) collectFields(response.body, "", "", rows, true); } form.field_rows = [...new Map(rows.map(item => [String(item.value), item])).values()]; }
270
+ async function loadResponseFields(apiId: number) { const revision = ++responseFieldsRequestRevision; const rows = await requestResponseFields(apiId); if (revision === responseFieldsRequestRevision) form.field_rows = rows; }
271
+ /** 请求 API 返回字段并转换为表格行。 */
272
+ async function requestResponseFields(apiId: number) { const doc = await defBaseRedactOutputPolicyService.GetBaseRedactOutputFieldDoc({ api_id: apiId }); const rows: OutputFieldRow[] = []; for (const response of doc.responses ?? []) { if (response.body) collectFields(response.body, "", "", rows, true); } return [...new Map(rows.map(item => [String(item.value), item])).values()]; }
266
273
  /** 递归收集可配置的返回叶子字段。 */
267
274
  function collectFields(schema: BaseApiDocSchema, parentRef: string, parentPath: string, result: OutputFieldRow[], root: boolean) { const schemaRef = normalizeRef(schema.ref); const messageRef = schemaRef || parentRef; const startsNewMessage = Boolean(schemaRef && parentRef && schemaRef !== parentRef); const rootContainer = root || (!parentRef && (schema.name === "body" || schema.name === "body[]")); const path = rootContainer || startsNewMessage ? "" : schema.name ? (parentPath ? `${parentPath}.${schema.name}` : schema.name) : parentPath; if (!schema.children?.length) { const fieldName = path.split(".").pop()?.toLowerCase() ?? ""; if (path && !OUTPUT_SYSTEM_FIELD_NAMES.has(fieldName)) result.push(createFieldRow({ label: `${messageRef}.${path}${schema.description ? ` (${schema.description})` : ""}`, value: `${messageRef}\u0000${path}`, message_ref: messageRef, field_path: path, description: schema.description })); return; } for (const child of schema.children) collectFields(child, messageRef, path, result, false); }
268
275
  /** 规范化 OpenAPI 引用名称。 */
269
276
  function normalizeRef(ref?: string) { const parts = (ref ?? "").split("/"); return parts[parts.length - 1] || ""; }
270
277
  /** 打开新增或编辑弹窗。 */
271
- async function openDialog(id?: number) { resetForm(); await Promise.all([loadApis(), loadRules()]); if (id !== undefined) { const data = await defBaseRedactOutputPolicyService.GetBaseRedactOutputPolicy({ id }); Object.assign(form, data); const api = apiCatalog.value.find(item => item.operation === data.operation); if (api) { form.service_name = api.service_name; form.api_id = api.id; await loadResponseFields(api.id); const row = form.field_rows.find(item => item.message_ref === data.message_ref && item.field_path === data.field_path); if (row) applyPolicy(row, data); } } dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource"; dialog.visible = true; }
278
+ async function openDialog(id?: number) { await dialogRef.value?.open({ load: async () => { const [apis, rules, data] = await Promise.all([requestApis(), requestRules(), id !== undefined ? defBaseRedactOutputPolicyService.GetBaseRedactOutputPolicy({ id }) : Promise.resolve(undefined)]); const api = data ? apis.find(item => item.operation === data.operation) : undefined; const rows = api ? await requestResponseFields(api.id) : []; return { apis, rules, data, rows }; }, commit: ({ apis, rules, data, rows }) => { resetForm(); apiCatalog.value = apis; ruleCatalog.value = rules.catalog; ruleOptions.value = rules.options; if (data) { Object.assign(form, data); const api = apis.find(item => item.operation === data.operation); if (api) { form.service_name = api.service_name; form.api_id = api.id; form.field_rows = rows; const row = form.field_rows.find(item => item.message_ref === data.message_ref && item.field_path === data.field_path); if (row) applyPolicy(row, data); } } dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource"; } }); }
272
279
  /** 重置弹窗表单。 */
273
280
  function resetForm() { dialog.visible = false; dialogRef.value?.resetFields(); Object.assign(form, defaultForm()); }
274
281
  /** 保存表格中已配置的全部字段。 */
@@ -212,13 +212,14 @@ async function requestTable(params: PageBaseRedactRuleRequest) {
212
212
 
213
213
  /** 打开新增或编辑弹窗。 */
214
214
  async function openDialog(id?: number) {
215
- resetForm();
216
- if (id !== undefined) {
217
- const data = await defBaseRedactRuleService.GetBaseRedactRule({ id });
218
- Object.assign(form, data, { params: parseRuleParams(data.rule_type, data.rule) });
219
- }
220
- dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource";
221
- dialog.visible = true;
215
+ await dialogRef.value?.open({
216
+ load: () => (id !== undefined ? defBaseRedactRuleService.GetBaseRedactRule({ id }) : undefined),
217
+ commit: data => {
218
+ resetForm();
219
+ if (data) Object.assign(form, data, { params: parseRuleParams(data.rule_type, data.rule) });
220
+ dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource";
221
+ }
222
+ });
222
223
  }
223
224
 
224
225
  /** 重置规则编辑表单。 */
@@ -188,6 +188,7 @@ const ruleOptions = ref<ProFormOption[]>([]);
188
188
  const ruleCatalog = ref<BaseRedactRule[]>([]);
189
189
  const dialog = reactive({ visible: false, titleKey: "common.action.create_resource" });
190
190
  const form = reactive<StorageFormState>(defaultForm());
191
+ let columnsRequestRevision = 0;
191
192
  const statusOptions = computed<ProFormOption[]>(() => [{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE }, { label: t("common.status.disabled"), value: Status.STATUS_DISABLE }]);
192
193
  const configuredFieldCount = computed(() => form.column_rows.filter(row => Boolean(row.rule_id)).length);
193
194
  const fields = computed<ProFormField[]>(() => [
@@ -216,25 +217,48 @@ const headerActions = computed<HeaderActionProps[]>(() => [{ label: t("common.ac
216
217
  /** 请求入库脱敏策略分页列表。 */
217
218
  async function requestTable(params: PageBaseRedactStoragePolicyRequest) { const data = await defBaseRedactStoragePolicyService.PageBaseRedactStoragePolicy(buildPageRequest(params)); return { data: { list: data.base_redact_storage_policies ?? [], total: data.total } }; }
218
219
  /** 加载脱敏规则选项。 */
219
- async function loadRules() { const data = await defBaseRedactRuleService.PageBaseRedactRule({ code: "", name: "", rule_type: "", page_num: 1, page_size: 100 }); ruleCatalog.value = data.base_redact_rules ?? []; ruleOptions.value = ruleCatalog.value.map(item => ({ label: `${item.name} (${item.code})`, value: item.id, disabled: item.status !== Status.STATUS_ENABLE })); }
220
+ async function loadRules() { const rules = await requestRules(); ruleCatalog.value = rules.catalog; ruleOptions.value = rules.options; }
221
+ /** 请求脱敏规则选项。 */
222
+ async function requestRules() { const data = await defBaseRedactRuleService.PageBaseRedactRule({ code: "", name: "", rule_type: "", page_num: 1, page_size: 100 }); const catalog = data.base_redact_rules ?? []; return { catalog, options: catalog.map(item => ({ label: `${item.name} (${item.code})`, value: item.id, disabled: item.status !== Status.STATUS_ENABLE })) }; }
220
223
  /** 数据源变更后清空下级选择并加载数据表。 */
221
- async function handleSourceChange(sourceName: string) { form.source_name = sourceName; form.table_name = ""; form.column_name = ""; form.column_rows = []; tableOptions.value = []; await loadTables(); }
224
+ async function handleSourceChange(sourceName: string) { columnsRequestRevision += 1; form.source_name = sourceName; form.table_name = ""; form.column_name = ""; form.column_rows = []; tableOptions.value = []; await loadTables(); }
222
225
  /** 数据表变更后加载字段表格。 */
223
- async function handleTableChange(tableName: string) { form.table_name = tableName; form.column_name = ""; form.column_rows = []; await loadColumns(); }
226
+ async function handleTableChange(tableName: string) { columnsRequestRevision += 1; form.table_name = tableName; form.column_name = ""; form.column_rows = []; await loadColumns(); }
224
227
  /** 加载数据源选项。 */
225
- async function loadSourceOptions() { const data = await defBaseTableSourceService.OptionBaseTableSource({}); sourceOptions.value = (data.value ?? []).map(value => ({ label: value, value })); }
228
+ async function loadSourceOptions() { sourceOptions.value = await requestSourceOptions(); }
229
+ /** 请求数据源选项。 */
230
+ async function requestSourceOptions() { const data = await defBaseTableSourceService.OptionBaseTableSource({}); return (data.value ?? []).map(value => ({ label: value, value })); }
226
231
  /** 加载指定数据源的数据表选项。 */
227
- async function loadTables(sourceName = form.source_name) { if (!sourceName) { tableOptions.value = []; return; } const data = await defCodeGenTableService.ListCodeGenDatabaseTable({ source_name: sourceName }); tableOptions.value = (data.tables ?? []).map((item: CodeGenDatabaseTable) => ({ label: item.comment ? `${item.comment}(${item.name})` : item.name, value: item.name })); }
232
+ async function loadTables(sourceName = form.source_name) { tableOptions.value = await requestTables(sourceName); }
233
+ /** 请求指定数据源的数据表选项。 */
234
+ async function requestTables(sourceName: string) { if (!sourceName) return []; const data = await defCodeGenTableService.ListCodeGenDatabaseTable({ source_name: sourceName }); return (data.tables ?? []).map((item: CodeGenDatabaseTable) => ({ label: item.comment ? `${item.comment}(${item.name})` : item.name, value: item.name })); }
228
235
  /** 预加载数据表中文注释,供列表显示。 */
229
236
  async function loadTableComments() { await loadSourceOptions(); const comments = new Map<string, string>(); await Promise.all(sourceOptions.value.map(async option => { const sourceName = String(option.value); const data = await defCodeGenTableService.ListCodeGenDatabaseTable({ source_name: sourceName }); for (const item of data.tables ?? []) { if (item.comment) comments.set(`${sourceName}\x00${item.name}`, item.comment); } })); tableCommentMap.value = comments; }
230
237
  /** 加载并过滤数据库字段。 */
231
- async function loadColumns() { if (!form.source_name || !form.table_name) { form.column_rows = []; return; } const data = await defBaseRedactStoragePolicyService.ListBaseRedactStorageColumn({ source_name: form.source_name, table_name: form.table_name }); form.column_rows = (data.columns ?? []).filter(item => !STORAGE_AUDIT_COLUMN_NAMES.has(item.name.toLowerCase())).map(item => createColumnRow({ label: item.name, value: item.name, name: item.name, comment: item.comment, db_type: item.db_type })); }
238
+ async function loadColumns() { const revision = ++columnsRequestRevision; const rows = await requestColumns(form.source_name, form.table_name); if (revision === columnsRequestRevision) form.column_rows = rows; }
239
+ /** 请求并转换数据库字段。 */
240
+ async function requestColumns(sourceName: string, tableName: string) { if (!sourceName || !tableName) return []; const data = await defBaseRedactStoragePolicyService.ListBaseRedactStorageColumn({ source_name: sourceName, table_name: tableName }); return (data.columns ?? []).filter(item => !STORAGE_AUDIT_COLUMN_NAMES.has(item.name.toLowerCase())).map(item => createColumnRow({ label: item.name, value: item.name, name: item.name, comment: item.comment, db_type: item.db_type })); }
232
241
  /** 打开新增或编辑弹窗。 */
233
- async function openDialog(id?: number) { resetForm(); await Promise.all([loadSourceOptions(), loadRules()]); if (id !== undefined) { const data = await defBaseRedactStoragePolicyService.GetBaseRedactStoragePolicy({ id }); Object.assign(form, data); await loadTables(); await loadColumns(); const row = form.column_rows.find(item => item.name === data.column_name); if (row) applyPolicy(row, data); } dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource"; dialog.visible = true; }
242
+ async function openDialog(id?: number) { await dialogRef.value?.open({ load: async () => { const [sources, rules, data] = await Promise.all([requestSourceOptions(), requestRules(), id !== undefined ? defBaseRedactStoragePolicyService.GetBaseRedactStoragePolicy({ id }) : Promise.resolve(undefined)]); const sourceName = data?.source_name ?? ""; const tables = await requestTables(sourceName); const columns = data ? await requestColumns(sourceName, data.table_name) : []; return { sources, rules, data, tables, columns }; }, commit: ({ sources, rules, data, tables, columns }) => { resetForm(); sourceOptions.value = sources; tableOptions.value = tables; ruleCatalog.value = rules.catalog; ruleOptions.value = rules.options; if (data) { Object.assign(form, data); form.column_rows = columns; const row = form.column_rows.find(item => item.name === data.column_name); if (row) applyPolicy(row, data); } dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource"; } }); }
234
243
  /** 重置弹窗表单。 */
235
244
  function resetForm() { dialog.visible = false; dialogRef.value?.resetFields(); Object.assign(form, defaultForm()); sourceOptions.value = []; tableOptions.value = []; }
236
245
  /** 保存表格中已配置的全部字段。 */
237
- async function submit() { const valid = await dialogRef.value?.validate(); if (!valid || !form.column_rows.some(row => Boolean(row.rule_id))) return; const rows = form.column_rows.filter(row => Boolean(row.rule_id)); const createPolicies = rows.filter(row => !row.id).map(buildPayload); const updatePolicies = rows.filter(row => Boolean(row.id)).map(buildPayload); const requests: Promise<unknown>[] = []; if (createPolicies.length) requests.push(defBaseRedactStoragePolicyService.CreateBaseRedactStoragePolicy({ base_redact_storage_policy: createPolicies })); if (updatePolicies.length) requests.push(defBaseRedactStoragePolicyService.UpdateBaseRedactStoragePolicy({ base_redact_storage_policy: updatePolicies })); await Promise.all(requests); ElMessage.success(t("system.base.redact_storage_policy.message.batch_save_success", { count: rows.length })); resetForm(); table.value?.getTableList(); }
246
+ async function submit() {
247
+ const valid = await dialogRef.value?.validate();
248
+ const rows = form.column_rows.filter(row => Boolean(row.rule_id));
249
+ const removedIds = form.column_rows.filter(row => row.id > 0 && !row.rule_id).map(row => row.id);
250
+ if (!valid || (!rows.length && !removedIds.length)) return;
251
+ const createPolicies = rows.filter(row => !row.id).map(buildPayload);
252
+ const updatePolicies = rows.filter(row => Boolean(row.id)).map(buildPayload);
253
+ const requests: Promise<unknown>[] = [];
254
+ if (createPolicies.length) requests.push(defBaseRedactStoragePolicyService.CreateBaseRedactStoragePolicy({ base_redact_storage_policy: createPolicies }));
255
+ if (updatePolicies.length) requests.push(defBaseRedactStoragePolicyService.UpdateBaseRedactStoragePolicy({ base_redact_storage_policy: updatePolicies }));
256
+ if (removedIds.length) requests.push(defBaseRedactStoragePolicyService.DeleteBaseRedactStoragePolicy({ id: removedIds.join(",") }));
257
+ await Promise.all(requests);
258
+ ElMessage.success(t("system.base.redact_storage_policy.message.batch_save_success", { count: rows.length }));
259
+ resetForm();
260
+ table.value?.getTableList();
261
+ }
238
262
  /** 将表格行转换为入库策略请求。 */
239
263
  function buildPayload(row: StorageColumnRow): BaseRedactStoragePolicyForm { syncRowParams(row); return { id: row.id, source_name: form.source_name, table_name: form.table_name, column_name: row.name, rule_id: row.rule_id ?? 0, rule_params: row.rule_params, status: form.status, remark: form.remark }; }
240
264
  /** 将已保存的策略回填到对应字段行。 */
@@ -392,10 +392,13 @@ function refreshTable() {
392
392
  * 按目标角色加载可分配的菜单权限树数据。
393
393
  */
394
394
  async function loadMenuPermOptions(roleId?: number) {
395
- const optionBaseMenuRes = await defBaseMenuService.OptionBaseMenu({
396
- role_id: roleId
397
- });
398
- menuPermOptions.value = optionBaseMenuRes.list ?? [];
395
+ menuPermOptions.value = await requestMenuPermOptions(roleId);
396
+ }
397
+
398
+ /** 请求指定角色可分配的菜单权限树。 */
399
+ async function requestMenuPermOptions(roleId?: number) {
400
+ const optionBaseMenuRes = await defBaseMenuService.OptionBaseMenu({ role_id: roleId });
401
+ return optionBaseMenuRes.list ?? [];
399
402
  }
400
403
 
401
404
  /**
@@ -410,25 +413,29 @@ async function handleFormTenantChange() {
410
413
  * 打开角色弹窗。
411
414
  */
412
415
  async function handleOpenDialog(roleId?: number) {
413
- resetForm();
414
- await loadTenantOptions();
415
- dialog.editing = Boolean(roleId);
416
- dialog.visible = true;
417
- if (!roleId) {
418
- await loadMenuPermOptions();
419
- return;
420
- }
421
-
422
- const data = await defBaseRoleService.GetBaseRole({ id: roleId });
423
- Object.assign(formData, data);
424
- await loadMenuPermOptions(roleId);
416
+ await formDialogRef.value?.open({
417
+ load: async () => {
418
+ await loadTenantOptions();
419
+ const [data, menuOptions] = await Promise.all([
420
+ roleId ? defBaseRoleService.GetBaseRole({ id: roleId }) : Promise.resolve(undefined),
421
+ requestMenuPermOptions(roleId)
422
+ ]);
423
+ return { data, menuOptions };
424
+ },
425
+ commit: ({ data, menuOptions }) => {
426
+ resetForm();
427
+ dialog.editing = Boolean(roleId);
428
+ menuPermOptions.value = menuOptions;
429
+ if (data) Object.assign(formData, data);
430
+ }
431
+ });
425
432
  }
426
433
 
427
434
  /**
428
435
  * 关闭角色弹窗并恢复默认表单值。
429
436
  */
430
437
  function handleCloseDialog() {
431
- dialog.visible = false;
438
+ formDialogRef.value?.close();
432
439
  resetForm();
433
440
  }
434
441
 
@@ -329,18 +329,19 @@ function isProtectedManagementTenant(row?: BaseTenant) {
329
329
  async function handleOpenDialog(tenantId?: number) {
330
330
  resetForm();
331
331
  dialog.titleKey = tenantId ? "common.action.edit_resource" : "common.action.create_resource";
332
- dialog.visible = true;
333
- if (!tenantId) return;
334
-
335
- const data = await defBaseTenantService.GetBaseTenant({ id: tenantId });
336
- Object.assign(formData, data);
332
+ await formDialogRef.value?.open({
333
+ load: () => (tenantId ? defBaseTenantService.GetBaseTenant({ id: tenantId }) : undefined),
334
+ commit: data => {
335
+ if (data) Object.assign(formData, data);
336
+ }
337
+ });
337
338
  }
338
339
 
339
340
  /**
340
341
  * 关闭租户弹窗并恢复默认表单值。
341
342
  */
342
343
  function handleCloseDialog() {
343
- dialog.visible = false;
344
+ formDialogRef.value?.close();
344
345
  resetForm();
345
346
  }
346
347