@liujitcn/kratos-admin-system 0.0.52 → 0.0.54

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 (50) hide show
  1. package/dist/package/declarations/src/api/base/v1/sse.d.ts +1 -1
  2. package/dist/package/declarations/src/locales/generated.d.ts +32 -0
  3. package/dist/package/declarations/src/rpc/base/v1/ai_search.d.ts +26 -0
  4. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +0 -1
  5. package/dist/package/src/api/base/v1/sse.ts +19 -12
  6. package/dist/package/src/components/log/LogTable.vue +25 -18
  7. package/dist/package/src/components/notification/Notification.vue +14 -7
  8. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +12 -6
  9. package/dist/package/src/components/tenant-project-grant/ProjectGrantDialog.vue +22 -7
  10. package/dist/package/src/locales/en-US.json +8 -0
  11. package/dist/package/src/locales/ja-JP.json +8 -0
  12. package/dist/package/src/locales/zh-CN.json +8 -0
  13. package/dist/package/src/locales/zh-TW.json +8 -0
  14. package/dist/package/src/rpc/base/v1/ai_search.ts +37 -0
  15. package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +0 -1
  16. package/dist/package/src/views/ai/chat/index.vue +16 -7
  17. package/dist/package/src/views/base/api-log/index.vue +1 -1
  18. package/dist/package/src/views/base/area/index.vue +15 -9
  19. package/dist/package/src/views/base/backup-management/archive-config/index.vue +22 -10
  20. package/dist/package/src/views/base/backup-management/backup-config/index.vue +13 -6
  21. package/dist/package/src/views/base/config/index.vue +38 -14
  22. package/dist/package/src/views/base/dashboard/index.vue +201 -53
  23. package/dist/package/src/views/base/dept/index.vue +31 -25
  24. package/dist/package/src/views/base/dict/index.vue +12 -8
  25. package/dist/package/src/views/base/dict/item.vue +12 -8
  26. package/dist/package/src/views/base/i18n-custom/index.vue +13 -7
  27. package/dist/package/src/views/base/job/index.vue +12 -8
  28. package/dist/package/src/views/base/job/log.vue +14 -9
  29. package/dist/package/src/views/base/language/index.vue +7 -4
  30. package/dist/package/src/views/base/login-policy/index.vue +22 -11
  31. package/dist/package/src/views/base/menu/index.vue +23 -16
  32. package/dist/package/src/views/base/message/index.vue +65 -21
  33. package/dist/package/src/views/base/message-category/index.vue +10 -4
  34. package/dist/package/src/views/base/oauth-client/index.vue +15 -5
  35. package/dist/package/src/views/base/post/index.vue +10 -4
  36. package/dist/package/src/views/base/redact-output-policy/index.vue +12 -5
  37. package/dist/package/src/views/base/redact-rule/index.vue +8 -7
  38. package/dist/package/src/views/base/redact-storage-policy/index.vue +32 -8
  39. package/dist/package/src/views/base/role/index.vue +24 -17
  40. package/dist/package/src/views/base/tenant/index.vue +7 -6
  41. package/dist/package/src/views/base/tenant-project-grant/index.vue +44 -27
  42. package/dist/package/src/views/base/user/components/dept-tree.vue +4 -9
  43. package/dist/package/src/views/base/user/index.vue +39 -28
  44. package/dist/package/src/views/message/inbox/index.vue +8 -3
  45. package/dist/package/src/views/profile/components/base.vue +1 -1
  46. package/dist/package/src/views/profile/index.vue +1 -1
  47. package/dist/package/src/views/tool/cache/index.vue +1 -1
  48. package/dist/package/src/views/tool/code-gen/columns/index.vue +35 -16
  49. package/dist/package/src/views/tool/code-gen/table/index.vue +58 -31
  50. package/package.json +2 -2
@@ -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
 
@@ -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}`,