@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
@@ -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
 
@@ -208,22 +208,33 @@ async function requestProjectGrantTable(params: Record<string, unknown>) {
208
208
 
209
209
  /** 打开项目授权新增或编辑弹窗。 */
210
210
  async function handleOpenDialog(row?: BaseTenantProjectGrant) {
211
- resetForm();
212
- dialog.editing = Boolean(row);
213
- dialog.titleKey = row ? "common.action.edit_resource" : "common.action.create_resource";
214
211
  loading.value = true;
215
212
  try {
216
- await loadTenantOptions(true);
217
- if (!isDefaultTenant.value) formData.tenant_id = Number(tenantOptions.value[0]?.value) || undefined;
218
- if (row) {
219
- formData.tenant_id = row.tenant_id;
220
- formData.subject_type = row.subject_type;
221
- formData.subject_id = row.subject_id;
222
- formData.all = row.project_id.length === 1 && row.project_id[0] === 0;
223
- formData.project_id = formData.all ? [] : [...row.project_id];
224
- }
225
- await Promise.all([loadProjectOptions(), loadSubjectOptions()]);
226
- dialog.visible = true;
213
+ await formDialogRef.value?.open({
214
+ load: async () => {
215
+ await loadTenantOptions(true);
216
+ const nextForm = {
217
+ tenant_id: row?.tenant_id ?? (isDefaultTenant.value ? undefined : Number(tenantOptions.value[0]?.value) || undefined),
218
+ subject_type: row?.subject_type ?? DEFAULT_SUBJECT_TYPE,
219
+ subject_id: row?.subject_id,
220
+ project_id: row && !(row.project_id.length === 1 && row.project_id[0] === 0) ? [...row.project_id] : [],
221
+ all: Boolean(row && row.project_id.length === 1 && row.project_id[0] === 0)
222
+ } satisfies ProjectGrantFormState;
223
+ const [projects, subjects] = await Promise.all([
224
+ requestProjectOptions(nextForm.tenant_id),
225
+ requestSubjectOptions(nextForm.tenant_id, nextForm.subject_type)
226
+ ]);
227
+ return { nextForm, projects, subjects };
228
+ },
229
+ commit: ({ nextForm, projects, subjects }) => {
230
+ resetForm();
231
+ dialog.editing = Boolean(row);
232
+ dialog.titleKey = row ? "common.action.edit_resource" : "common.action.create_resource";
233
+ Object.assign(formData, nextForm);
234
+ projectOptions.value = projects;
235
+ subjectOptions.value = subjects;
236
+ }
237
+ });
227
238
  } finally {
228
239
  loading.value = false;
229
240
  }
@@ -231,28 +242,34 @@ async function handleOpenDialog(row?: BaseTenantProjectGrant) {
231
242
 
232
243
  /** 加载目标租户项目选项。 */
233
244
  async function loadProjectOptions() {
234
- projectOptions.value = formData.tenant_id
235
- ? (await defBaseTenantProjectService.OptionBaseTenantProject({ tenant_id: toRequestTenantId(formData.tenant_id) })).list ?? []
236
- : [];
245
+ projectOptions.value = await requestProjectOptions(formData.tenant_id);
246
+ }
247
+
248
+ /** 请求目标租户项目选项。 */
249
+ async function requestProjectOptions(tenantId?: number) {
250
+ if (!tenantId) return [];
251
+ return (await defBaseTenantProjectService.OptionBaseTenantProject({ tenant_id: toRequestTenantId(tenantId) })).list ?? [];
237
252
  }
238
253
 
239
254
  /** 根据授权类型加载岗位、角色、部门或用户主体选项。 */
240
255
  async function loadSubjectOptions() {
241
- subjectOptions.value = [];
242
- if (!formData.tenant_id || formData.subject_type === BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_UNSPECIFIED) return;
243
- switch (formData.subject_type) {
256
+ subjectOptions.value = await requestSubjectOptions(formData.tenant_id, formData.subject_type);
257
+ }
258
+
259
+ /** 请求指定租户和主体类型的主体选项。 */
260
+ async function requestSubjectOptions(tenantId: number | undefined, subjectType: BaseTenantProjectGrantSubjectType) {
261
+ if (!tenantId || subjectType === BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_UNSPECIFIED) return [];
262
+ switch (subjectType) {
244
263
  case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_POST:
245
- subjectOptions.value = (await defBasePostService.OptionBasePost({ tenant_id: formData.tenant_id })).list ?? [];
246
- return;
264
+ return (await defBasePostService.OptionBasePost({ tenant_id: tenantId })).list ?? [];
247
265
  case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_ROLE:
248
- subjectOptions.value = (await defBaseRoleService.OptionBaseRole({ tenant_id: formData.tenant_id })).list ?? [];
249
- return;
266
+ return (await defBaseRoleService.OptionBaseRole({ tenant_id: tenantId })).list ?? [];
250
267
  case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_DEPT:
251
- subjectOptions.value = ((await defBaseDeptService.OptionBaseDept({ tenant_id: formData.tenant_id })).list ?? []) as unknown as ProFormOption[];
252
- return;
268
+ return ((await defBaseDeptService.OptionBaseDept({ tenant_id: tenantId })).list ?? []) as unknown as ProFormOption[];
253
269
  case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_USER:
254
- subjectOptions.value = (await defBaseUserService.OptionBaseUser({ keyword: "", tenant_id: formData.tenant_id })).list ?? [];
270
+ return (await defBaseUserService.OptionBaseUser({ keyword: "", tenant_id: tenantId })).list ?? [];
255
271
  }
272
+ return [];
256
273
  }
257
274
 
258
275
  /** 切换目标租户时重置主体和项目选择。 */
@@ -22,7 +22,7 @@
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
- import { onBeforeMount, ref, watchEffect } from "vue";
25
+ import { nextTick, onBeforeMount, ref, watch } from "vue";
26
26
  import { useVModel } from "@vueuse/core";
27
27
  import { ElTree } from "element-plus";
28
28
  import { defBaseDeptService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_dept";
@@ -43,14 +43,9 @@ const emits = defineEmits(["nodeClick"]);
43
43
 
44
44
  const baseDeptId = useVModel(props, "modelValue", emits);
45
45
 
46
- watchEffect(
47
- () => {
48
- baseDeptTreeRef.value?.filter(name.value);
49
- },
50
- {
51
- flush: "post" // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
52
- }
53
- );
46
+ watch([name, baseDeptList], () => {
47
+ void nextTick(() => baseDeptTreeRef.value?.filter(name.value));
48
+ }, { flush: "post" });
54
49
 
55
50
  /**
56
51
  * 部门筛选
@@ -169,7 +169,7 @@ const formData = reactive<BaseUserFormState>({
169
169
  /** 密码 */
170
170
  pwd: "",
171
171
  /** 性别 */
172
- gender: 3,
172
+ gender: 1,
173
173
  /** 头像 */
174
174
  avatar: "",
175
175
  /** 用户状态 */
@@ -649,23 +649,30 @@ function refreshTable() {
649
649
  * 加载用户表单依赖的角色和部门选项。
650
650
  */
651
651
  async function loadFormOptions() {
652
+ const options = await requestFormOptions();
653
+ baseRoleOptions.value = options.roleOptions;
654
+ updateUndeletableRoleIds(baseRoleOptions.value);
655
+ basePostOptions.value = options.postOptions;
656
+ basedDeptOptions.value = options.deptOptions;
657
+ }
658
+
659
+ /** 请求用户表单依赖的角色和部门选项。 */
660
+ async function requestFormOptions(tenantId = formData.tenant_id) {
652
661
  // 默认租户必须先选择目标租户,避免角色和部门选项跨租户混用。
653
- if (isDefaultTenant.value && !formData.tenant_id) {
654
- baseRoleOptions.value = [];
655
- basePostOptions.value = [];
656
- basedDeptOptions.value = [];
657
- return;
662
+ if (isDefaultTenant.value && !tenantId) {
663
+ return { roleOptions: [], postOptions: [], deptOptions: [] };
658
664
  }
659
- const tenantId = toRequestTenantId(formData.tenant_id);
665
+ const requestTenantId = toRequestTenantId(tenantId);
660
666
  const [optionBaseRoleResponse, optionBaseDeptResponse, optionBasePostResponse] = await Promise.all([
661
- defBaseRoleService.OptionBaseRole({ tenant_id: tenantId }),
662
- defBaseDeptService.OptionBaseDept({ tenant_id: tenantId }),
663
- defBasePostService.OptionBasePost({ tenant_id: tenantId })
667
+ defBaseRoleService.OptionBaseRole({ tenant_id: requestTenantId }),
668
+ defBaseDeptService.OptionBaseDept({ tenant_id: requestTenantId }),
669
+ defBasePostService.OptionBasePost({ tenant_id: requestTenantId })
664
670
  ]);
665
- baseRoleOptions.value = optionBaseRoleResponse.list || [];
666
- updateUndeletableRoleIds(baseRoleOptions.value);
667
- basePostOptions.value = optionBasePostResponse.list || [];
668
- basedDeptOptions.value = optionBaseDeptResponse.list || [];
671
+ return {
672
+ roleOptions: optionBaseRoleResponse.list || [],
673
+ postOptions: optionBasePostResponse.list || [],
674
+ deptOptions: optionBaseDeptResponse.list || []
675
+ };
669
676
  }
670
677
 
671
678
  /**
@@ -682,18 +689,22 @@ async function handleFormTenantChange() {
682
689
  * 打开用户弹窗,并加载角色和部门下拉选项。
683
690
  */
684
691
  async function handleOpenDialog(id?: number) {
685
- resetForm();
686
- await loadTenantOptions();
687
- dialog.editing = Boolean(id);
688
- dialog.visible = true;
689
- if (!id) {
690
- await loadFormOptions();
691
- return;
692
- }
693
-
694
- defBaseUserService.GetBaseUser({ id }).then(async data => {
695
- Object.assign(formData, data);
696
- await loadFormOptions();
692
+ await formDialogRef.value?.open({
693
+ load: async () => {
694
+ await loadTenantOptions();
695
+ const data = id ? await defBaseUserService.GetBaseUser({ id }) : undefined;
696
+ const options = await requestFormOptions(data?.tenant_id);
697
+ return { data, options };
698
+ },
699
+ commit: ({ data, options }) => {
700
+ resetForm();
701
+ dialog.editing = Boolean(id);
702
+ if (data) Object.assign(formData, data);
703
+ baseRoleOptions.value = options.roleOptions;
704
+ updateUndeletableRoleIds(baseRoleOptions.value);
705
+ basePostOptions.value = options.postOptions;
706
+ basedDeptOptions.value = options.deptOptions;
707
+ }
697
708
  });
698
709
  }
699
710
 
@@ -701,7 +712,7 @@ async function handleOpenDialog(id?: number) {
701
712
  * 关闭用户弹窗并恢复默认表单值。
702
713
  */
703
714
  function handleCloseDialog() {
704
- dialog.visible = false;
715
+ formDialogRef.value?.close();
705
716
  resetForm();
706
717
  }
707
718
 
@@ -724,7 +735,7 @@ function resetForm() {
724
735
  formData.id_type = BaseUserIDType.BASE_USER_ID_TYPE_UNSPECIFIED;
725
736
  formData.id_code = "";
726
737
  formData.pwd = "";
727
- formData.gender = 3;
738
+ formData.gender = 1;
728
739
  formData.avatar = "";
729
740
  formData.status = Status.STATUS_ENABLE;
730
741
  formData.remark = "";
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="table-box">
3
3
  <ProTable ref="proTable" row-key="id" :columns="columns" :header-actions="headerActions" :request-api="requestTable" />
4
- <ProDialog v-model="detailVisible" :title="detail?.title" width="720px" destroy-on-close :show-footer="false">
4
+ <ProDialog ref="detailDialogRef" v-model="detailVisible" :title="detail?.title" width="720px" destroy-on-close :show-footer="false">
5
5
  <div v-if="detail" class="message-detail">
6
6
  <div class="message-meta">
7
7
  <span class="message-category" :style="{ color: detail.category_color || undefined }">
@@ -56,6 +56,7 @@ defineOptions({ name: "NotificationInbox", inheritAttrs: false });
56
56
 
57
57
  const proTable = ref<ProTableInstance>();
58
58
  const router = useRouter();
59
+ const detailDialogRef = ref<InstanceType<typeof ProDialog>>();
59
60
  const detailVisible = ref(false);
60
61
  const detail = ref<Notification>();
61
62
  const categoryOptions = ref<NotificationCategory[]>([]);
@@ -177,8 +178,12 @@ async function loadCategoryOptions() {
177
178
 
178
179
  /** 打开消息详情并标记已读。 */
179
180
  async function openDetail(row: Notification) {
180
- detail.value = await defNotificationService.GetNotification({ id: row.id });
181
- detailVisible.value = true;
181
+ await detailDialogRef.value?.open({
182
+ load: () => defNotificationService.GetNotification({ id: row.id }),
183
+ commit: data => {
184
+ detail.value = data;
185
+ }
186
+ });
182
187
  if (!row.read_at) {
183
188
  await defNotificationService.MarkNotificationRead({ ids: [row.id] });
184
189
  proTable.value?.getTableList();
@@ -120,7 +120,7 @@ const submitLoading = ref(false);
120
120
  const avatarSrc = ref(defaultAvatar);
121
121
  const accountForm = reactive<Pick<UserProfileForm, "nick_name" | "gender" | "email" | "id_type" | "id_code">>({
122
122
  nick_name: "",
123
- gender: 3,
123
+ gender: 1,
124
124
  email: "",
125
125
  id_type: BaseUserIDType.BASE_USER_ID_TYPE_UNSPECIFIED,
126
126
  id_code: ""
@@ -102,7 +102,7 @@ const userProfileForm = reactive<UserProfileForm>({
102
102
  user_name: "",
103
103
  nick_name: "",
104
104
  avatar: "",
105
- gender: 3,
105
+ gender: 1,
106
106
  phone: "",
107
107
  email: "",
108
108
  id_type: 0,
@@ -95,7 +95,7 @@ const columns = computed<ColumnProps[]>(() => [
95
95
  prop: "key",
96
96
  label: t("system.cache.field.key"),
97
97
  minWidth: 280,
98
- search: { el: "input", props: { clearable: true } },
98
+ search: { el: "input", key: "keyword", props: { clearable: true } },
99
99
  render: scope => h("code", { class: "cache-key-cell", title: scope.row.key }, scope.row.key)
100
100
  },
101
101
  {