@liujitcn/kratos-admin-system 0.0.49 → 0.0.51

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 (107) hide show
  1. package/README.md +44 -8
  2. package/dist/package/declarations/src/api/system/admin/v1/base_i18n_custom.d.ts +18 -0
  3. package/dist/package/declarations/src/api/system/admin/v1/base_redact_output_policy.d.ts +4 -1
  4. package/dist/package/declarations/src/api/system/admin/v1/base_redact_storage_policy.d.ts +3 -1
  5. package/dist/package/declarations/src/api/system/admin/v1/base_tenant.d.ts +2 -2
  6. package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project.d.ts +23 -0
  7. package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project_grant.d.ts +16 -0
  8. package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +29 -0
  9. package/dist/package/declarations/src/components/tenant-project/TenantProjectSelect.vue.d.ts +31 -0
  10. package/dist/package/declarations/src/components/tenant-project/TenantProjectText.vue.d.ts +19 -0
  11. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +14 -0
  12. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +40 -0
  13. package/dist/package/declarations/src/components/tenant-project-grant/ProjectGrantDialog.vue.d.ts +8 -0
  14. package/dist/package/declarations/src/index.d.ts +2 -0
  15. package/dist/package/declarations/src/locales/generated.d.ts +196 -0
  16. package/dist/package/declarations/src/rpc/base/v1/config.d.ts +11 -0
  17. package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n_custom.d.ts +105 -0
  18. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +8 -0
  19. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_storage_policy.d.ts +25 -0
  20. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +10 -1
  21. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project.d.ts +141 -0
  22. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project_grant.d.ts +105 -0
  23. package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +2 -2
  24. package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +33 -0
  25. package/dist/package/declarations/src/views/base/i18n-custom/index.vue.d.ts +3 -0
  26. package/dist/package/declarations/src/views/base/tenant-project/index.vue.d.ts +3 -0
  27. package/dist/package/declarations/src/views/base/tenant-project-grant/index.vue.d.ts +3 -0
  28. package/dist/package/src/api/system/admin/v1/base_i18n_custom.ts +72 -0
  29. package/dist/package/src/api/system/admin/v1/base_redact_output_policy.ts +7 -0
  30. package/dist/package/src/api/system/admin/v1/base_redact_storage_policy.ts +7 -0
  31. package/dist/package/src/api/system/admin/v1/base_tenant.ts +3 -2
  32. package/dist/package/src/api/system/admin/v1/base_tenant_project.ts +94 -0
  33. package/dist/package/src/api/system/admin/v1/base_tenant_project_grant.ts +63 -0
  34. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +431 -0
  35. package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +83 -0
  36. package/dist/package/src/components/tenant-project/TenantProjectText.vue +37 -0
  37. package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +26 -0
  38. package/dist/package/src/components/tenant-project/tenant-project-manager.ts +48 -0
  39. package/dist/package/src/components/tenant-project-grant/ProjectGrantDialog.vue +108 -0
  40. package/dist/package/src/index.ts +12 -0
  41. package/dist/package/src/locales/README.md +1 -1
  42. package/dist/package/src/locales/en-US.json +51 -2
  43. package/dist/package/src/locales/ja-JP.json +51 -2
  44. package/dist/package/src/locales/zh-CN.json +51 -2
  45. package/dist/package/src/locales/zh-TW.json +51 -2
  46. package/dist/package/src/module.ts +3 -3
  47. package/dist/package/src/rpc/base/v1/config.ts +12 -0
  48. package/dist/package/src/rpc/system/admin/v1/base_i18n_custom.ts +126 -0
  49. package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +9 -0
  50. package/dist/package/src/rpc/system/admin/v1/base_redact_storage_policy.ts +30 -0
  51. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +11 -1
  52. package/dist/package/src/rpc/system/admin/v1/base_tenant_project.ts +166 -0
  53. package/dist/package/src/rpc/system/admin/v1/base_tenant_project_grant.ts +128 -0
  54. package/dist/package/src/views/ai/chat/components/XSender.vue +21 -28
  55. package/dist/package/src/views/ai/chat/components/speech-recognition.ts +156 -0
  56. package/dist/package/src/views/base/api-log/index.vue +3 -3
  57. package/dist/package/src/views/base/config/index.vue +3 -3
  58. package/dist/package/src/views/base/data-access-log/index.vue +3 -3
  59. package/dist/package/src/views/base/dept/index.vue +10 -45
  60. package/dist/package/src/views/base/dict/index.vue +3 -3
  61. package/dist/package/src/views/base/dict/item.vue +3 -3
  62. package/dist/package/src/views/base/file/index.vue +12 -3
  63. package/dist/package/src/views/base/i18n-custom/index.vue +321 -0
  64. package/dist/package/src/views/base/job/index.vue +85 -161
  65. package/dist/package/src/views/base/login-log/index.vue +13 -4
  66. package/dist/package/src/views/base/login-policy/index.vue +109 -40
  67. package/dist/package/src/views/base/menu/index.vue +3 -3
  68. package/dist/package/src/views/base/message/index.vue +9 -32
  69. package/dist/package/src/views/base/message-category/index.vue +2 -4
  70. package/dist/package/src/views/base/oauth-client/index.vue +8 -42
  71. package/dist/package/src/views/base/online-session/index.vue +8 -27
  72. package/dist/package/src/views/base/operation-log/index.vue +3 -3
  73. package/dist/package/src/views/base/permission-log/index.vue +3 -3
  74. package/dist/package/src/views/base/policy-evaluation-log/index.vue +3 -3
  75. package/dist/package/src/views/base/post/index.vue +9 -42
  76. package/dist/package/src/views/base/redact-output-policy/index.vue +19 -7
  77. package/dist/package/src/views/base/redact-rule/index.vue +70 -53
  78. package/dist/package/src/views/base/redact-storage-policy/index.vue +18 -7
  79. package/dist/package/src/views/base/role/index.vue +14 -48
  80. package/dist/package/src/views/base/tenant/index.vue +163 -19
  81. package/dist/package/src/views/base/tenant-project/index.vue +12 -0
  82. package/dist/package/src/views/base/tenant-project-grant/index.vue +338 -0
  83. package/dist/package/src/views/base/user/index.vue +30 -51
  84. package/dist/package/src/views/profile/components/login-log.vue +15 -2
  85. package/dist/package/src/views/profile/components/password.vue +1 -1
  86. package/dist/package/src/views/profile/components/security.vue +2 -2
  87. package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +2 -2
  88. package/dist/package/src/views/tool/code-gen/config.ts +2 -1
  89. package/package.json +2 -2
  90. /package/dist/package/declarations/src/components/{ChangePasswordForm.vue.d.ts → account/ChangePasswordForm.vue.d.ts} +0 -0
  91. /package/dist/package/declarations/src/components/{ForcedPasswordDialog.vue.d.ts → account/ForcedPasswordDialog.vue.d.ts} +0 -0
  92. /package/dist/package/declarations/src/components/{Ai.vue.d.ts → ai/Ai.vue.d.ts} +0 -0
  93. /package/dist/package/declarations/src/components/{DynamicI18nCell.vue.d.ts → i18n/DynamicI18nCell.vue.d.ts} +0 -0
  94. /package/dist/package/declarations/src/components/{DynamicI18nEditor.vue.d.ts → i18n/DynamicI18nEditor.vue.d.ts} +0 -0
  95. /package/dist/package/declarations/src/components/{dynamicI18n.d.ts → i18n/dynamicI18n.d.ts} +0 -0
  96. /package/dist/package/declarations/src/components/{LogTable.vue.d.ts → log/LogTable.vue.d.ts} +0 -0
  97. /package/dist/package/declarations/src/components/{log.d.ts → log/log.d.ts} +0 -0
  98. /package/dist/package/declarations/src/components/{Notification.vue.d.ts → notification/Notification.vue.d.ts} +0 -0
  99. /package/dist/package/src/components/{ChangePasswordForm.vue → account/ChangePasswordForm.vue} +0 -0
  100. /package/dist/package/src/components/{ForcedPasswordDialog.vue → account/ForcedPasswordDialog.vue} +0 -0
  101. /package/dist/package/src/components/{Ai.vue → ai/Ai.vue} +0 -0
  102. /package/dist/package/src/components/{DynamicI18nCell.vue → i18n/DynamicI18nCell.vue} +0 -0
  103. /package/dist/package/src/components/{DynamicI18nEditor.vue → i18n/DynamicI18nEditor.vue} +0 -0
  104. /package/dist/package/src/components/{dynamicI18n.ts → i18n/dynamicI18n.ts} +0 -0
  105. /package/dist/package/src/components/{LogTable.vue → log/LogTable.vue} +0 -0
  106. /package/dist/package/src/components/{log.ts → log/log.ts} +0 -0
  107. /package/dist/package/src/components/{Notification.vue → notification/Notification.vue} +0 -0
@@ -24,7 +24,7 @@
24
24
  </template>
25
25
 
26
26
  <script setup lang="ts">
27
- import { computed, reactive, ref } from "vue";
27
+ import { computed, onMounted, reactive, ref } from "vue";
28
28
  import { ElMessage, ElMessageBox } from "element-plus";
29
29
  import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
30
30
  import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
@@ -34,12 +34,9 @@ import type { ProFormField, ProFormOption } from "@liujitcn/kratos-admin-core/co
34
34
  import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
35
35
  import { defBasePostService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_post";
36
36
  import type { BasePost, BasePostForm, PageBasePostRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_post";
37
- import { defBaseTenantService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant";
38
- import type { SelectOptionResponse_Option } from "@liujitcn/kratos-admin-system/rpc/common/v1/common";
39
37
  import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
40
38
  import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
41
- import { DEFAULT_TENANT_CODE, requestTenantOptions } from "@liujitcn/kratos-admin-core/tenant";
42
- import { useUserStore } from "@liujitcn/kratos-admin-core/stores/runtime";
39
+ import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
43
40
  import { t } from "@liujitcn/kratos-admin-core";
44
41
 
45
42
  defineOptions({
@@ -54,7 +51,6 @@ type BasePostFormState = Omit<BasePostForm, "tenant_id"> & {
54
51
  };
55
52
 
56
53
  const { BUTTONS } = useAuthButtons();
57
- const userStore = useUserStore();
58
54
  const proTable = ref<ProTableInstance>();
59
55
  const formDialogRef = ref<InstanceType<typeof FormDialog>>();
60
56
 
@@ -62,7 +58,6 @@ const dialog = reactive({
62
58
  titleKey: "common.action.create_resource",
63
59
  visible: false
64
60
  });
65
- const tenantOptions = ref<SelectOptionResponse_Option[]>([]);
66
61
  const statusOptions = computed<ProFormOption[]>(() => [
67
62
  { label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
68
63
  { label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
@@ -132,23 +127,14 @@ const rules = computed(() => ({
132
127
  ]
133
128
  }));
134
129
 
135
- /** 当前登录账号是否默认租户。 */
136
- const isDefaultTenant = computed(() => userStore.userInfo.tenant_code === DEFAULT_TENANT_CODE);
130
+ const { isDefaultTenant, tenantColumns, tenantFormField, toRequestTenantId, loadTenantOptions } = useTenantScope();
131
+ onMounted(() => {
132
+ if (isDefaultTenant.value) void loadTenantOptions();
133
+ });
137
134
 
138
135
  /** 岗位表单字段配置。 */
139
136
  const formFields = computed<ProFormField[]>(() => [
140
- {
141
- prop: "tenant_id",
142
- label: t("common.field.tenant"),
143
- component: "select",
144
- props: {
145
- placeholder: t("common.placeholder.select"),
146
- filterable: true,
147
- disabled: Boolean(formData.id)
148
- },
149
- visible: () => isDefaultTenant.value,
150
- options: tenantOptions.value
151
- },
137
+ tenantFormField({ label: t("common.field.tenant"), disabledOnEdit: true }),
152
138
  {
153
139
  prop: "name",
154
140
  label: t("system.base.post.field.name"),
@@ -179,19 +165,7 @@ const formFields = computed<ProFormField[]>(() => [
179
165
  /** 岗位表格列配置。 */
180
166
  const columns = computed<ColumnProps[]>(() => [
181
167
  { type: "selection", width: 55 },
182
- ...(isDefaultTenant.value
183
- ? ([
184
- {
185
- prop: "tenant_id",
186
- label: t("common.field.tenant"),
187
- minWidth: 140,
188
- align: "left",
189
- showOverflowTooltip: true,
190
- search: { el: "select", key: "tenant_id", props: { filterable: true }, order: 1 },
191
- enum: requestTenantOptions
192
- }
193
- ] satisfies ColumnProps[])
194
- : []),
168
+ ...tenantColumns({ label: t("common.field.tenant"), order: 1 }),
195
169
  { prop: "name", label: t("system.base.post.field.name"), minWidth: 140, search: { el: "input" } },
196
170
  { prop: "code", label: t("system.base.post.field.code"), minWidth: 140, search: { el: "input" } },
197
171
  { prop: "sort", label: t("common.field.sort"), minWidth: 90, align: "right" },
@@ -262,7 +236,7 @@ const headerActions = computed<HeaderActionProps[]>(() => [
262
236
  async function requestBasePostTable(params: PageBasePostRequest) {
263
237
  const data = await defBasePostService.PageBasePost({
264
238
  ...buildPageRequest(params),
265
- tenant_id: isDefaultTenant.value ? params.tenant_id : undefined
239
+ tenant_id: toRequestTenantId(params.tenant_id)
266
240
  });
267
241
  return { data: { list: data.base_posts ?? [], total: data.total } };
268
242
  }
@@ -272,13 +246,6 @@ function refreshTable() {
272
246
  proTable.value?.getTableList();
273
247
  }
274
248
 
275
- /** 加载租户选项。 */
276
- async function loadTenantOptions() {
277
- if (!isDefaultTenant.value || tenantOptions.value.length) return;
278
- const response = await defBaseTenantService.OptionBaseTenant({ keyword: "" });
279
- tenantOptions.value = response.list ?? [];
280
- }
281
-
282
249
  /** 打开岗位编辑弹窗。 */
283
250
  async function handleOpenDialog(id?: number) {
284
251
  resetForm();
@@ -56,6 +56,7 @@
56
56
  <template v-if="row.rule_type === 'MASK'">
57
57
  <ParameterNumber v-model="row.params.keep_first" :label="t('system.base.redact_rule.parameter.keep_first')" />
58
58
  <ParameterNumber v-model="row.params.keep_last" :label="t('system.base.redact_rule.parameter.keep_last')" />
59
+ <ParameterNumber v-model="row.params.min_mask" :label="t('system.base.redact_rule.parameter.min_mask')" />
59
60
  <ParameterText v-model="row.params.mask_char" :label="t('system.base.redact_rule.parameter.mask_char')" />
60
61
  </template>
61
62
  <template v-else-if="row.rule_type === 'EMAIL'">
@@ -119,6 +120,7 @@ import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
119
120
  interface RuleParams {
120
121
  keep_first?: number;
121
122
  keep_last?: number;
123
+ min_mask?: number;
122
124
  mask_char?: string;
123
125
  keep_local_first?: number;
124
126
  mask_domain?: boolean;
@@ -260,7 +262,7 @@ function handleServiceChange(serviceName?: string) { form.service_name = service
260
262
  /** API 变更后重新加载返回字段。 */
261
263
  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); }
262
264
  /** 加载 API 返回字段并初始化表格行。 */
263
- async function loadResponseFields(apiId: number) { const api = apiCatalog.value.find(item => item.id === apiId); if (!api) return; const doc = await defBaseApiService.GetBaseApiDoc({ 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()]; }
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()]; }
264
266
  /** 递归收集可配置的返回叶子字段。 */
265
267
  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); }
266
268
  /** 规范化 OpenAPI 引用名称。 */
@@ -274,7 +276,7 @@ async function submit() { const valid = await dialogRef.value?.validate(); if (!
274
276
  /** 将表格行转换为出库策略请求。 */
275
277
  function buildPayload(row: OutputFieldRow): BaseRedactOutputPolicyForm { syncRowParams(row); return { id: row.id, operation: form.operation, service_name: form.service_name, message_ref: row.message_ref, field_path: row.field_path, mode: row.mode, rule_id: row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE ? row.rule_id ?? 0 : 0, rule_params: row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE ? row.rule_params : "{}", status: form.status, remark: form.remark }; }
276
278
  /** 将已保存的策略回填到对应字段行。 */
277
- function applyPolicy(row: OutputFieldRow, data: BaseRedactOutputPolicyForm) { row.id = data.id; row.mode = data.mode || BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL; row.rule_id = data.rule_id || undefined; row.rule_params = data.rule_params; if (row.rule_id) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = parseParams(row.rule_type, row.rule_params); } }
279
+ function applyPolicy(row: OutputFieldRow, data: BaseRedactOutputPolicyForm) { row.id = data.id; row.mode = data.mode || BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL; row.rule_id = data.rule_id || undefined; row.rule_params = data.rule_params; if (row.rule_id) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = rule ? parseParams(row.rule_type, row.rule_params, rule.rule) : {}; } }
278
280
  /** 处理行模式变更。 */
279
281
  function handleModeChange(row: OutputTableRow) { if (row.mode !== BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE) { row.rule_id = undefined; row.rule_type = ""; row.params = {}; row.rule_params = "{}"; } }
280
282
  /** 处理行规则变更。 */
@@ -285,7 +287,7 @@ function syncRowParams(row: OutputFieldRow) { const rule = ruleCatalog.value.fin
285
287
  function isConfiguredRow(row: OutputFieldRow) { return row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_HIDE || row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL || (row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE && Boolean(row.rule_id)); }
286
288
  /** 判断字段表格是否至少配置了一行。 */
287
289
  function isConfiguredRowList(rows: OutputFieldRow[] | undefined) { return Boolean(rows?.some(isConfiguredRow)); }
288
- /** 判断字段行是否使用规则模板。 */
290
+ /** 判断字段行是否使用脱敏规则。 */
289
291
  function isRuleRow(row: OutputTableRow) { return row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE && Boolean(row.rule_id); }
290
292
  /** 格式化 API 下拉项。 */
291
293
  function apiLabel(api: BaseApi) { return api.desc && api.desc !== api.operation ? `${api.desc}(${api.operation})` : api.operation; }
@@ -293,10 +295,20 @@ function apiLabel(api: BaseApi) { return api.desc && api.desc !== api.operation
293
295
  function isGetApi(api: BaseApi) { return api.method.toUpperCase() === "GET"; }
294
296
  /** 格式化服务下拉项。 */
295
297
  function serviceLabel(serviceName: string) { const service = apiCatalog.value.find(item => item.service_name === serviceName); return service?.service_desc && service.service_desc !== serviceName ? `${service.service_desc}(${serviceName})` : serviceName; }
296
- /** 解析规则参数。 */
297
- function parseParams(ruleType: string, raw: string): RuleParams { try { const value = JSON.parse(raw) as Record<string, RuleParams>; return value[ruleType.toLowerCase()] ?? defaultParams(ruleType); } catch { return defaultParams(ruleType); } }
298
- /** 返回规则默认参数。 */
299
- function defaultParams(ruleType: string): RuleParams { switch (ruleType) { case "MASK": return { keep_first: 3, keep_last: 4, mask_char: "*" }; case "EMAIL": return { keep_local_first: 2, mask_domain: false, mask_char: "*" }; case "REGEX": return { pattern: "(?s).+", replacement: "[REDACTED]" }; case "TRUNCATE": return { length: 10, suffix: "..." }; case "HASH": return { algo: "SHA256" }; case "IP": return { keep_octets: 2, mask_char: "x" }; case "URL": return { mask_query: true, mask_char: "*" }; case "FIXED_LENGTH": return { char: "X" }; default: return {}; } }
298
+ /** 解析规则参数,空策略参数时回退到数据库规则参数。 */
299
+ function parseParams(ruleType: string, raw: string, fallbackRaw = ""): RuleParams {
300
+ const candidates = fallbackRaw && fallbackRaw !== raw ? [raw, fallbackRaw] : [raw];
301
+ for (const candidate of candidates) {
302
+ try {
303
+ const value = JSON.parse(candidate) as Record<string, RuleParams>;
304
+ const params = value[ruleType.toLowerCase()];
305
+ if (params && typeof params === "object" && !Array.isArray(params)) return params;
306
+ } catch {
307
+ continue;
308
+ }
309
+ }
310
+ return {};
311
+ }
300
312
  /** 创建空的出库字段行。 */
301
313
  function createFieldRow(option: Pick<OutputFieldRow, "label" | "value" | "message_ref" | "field_path"> & { description?: string }): OutputFieldRow { return { ...option, id: 0, mode: BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL, rule_id: undefined, rule_type: "", params: {}, rule_params: "{}" }; }
302
314
  /** 创建默认出库表单。 */
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="table-box">
3
- <ProTable ref="table" row-key="id" :columns="columns" :request-api="requestTable" />
3
+ <ProTable ref="table" row-key="id" :columns="columns" :header-actions="headerActions" :request-api="requestTable" />
4
4
  <FormDialog
5
5
  v-model="dialog.visible"
6
6
  ref="dialogRef"
@@ -14,7 +14,7 @@
14
14
  >
15
15
  <template #rule>
16
16
  <div class="rule-editor">
17
- <div class="rule-description">{{ t(templateDescriptionKey) }}</div>
17
+ <div v-if="form.remark" class="rule-description">{{ form.remark }}</div>
18
18
  <div v-if="form.rule_type === 'MASK'" class="parameter-grid">
19
19
  <div class="parameter-item">
20
20
  <span>{{ t("system.base.redact_rule.parameter.keep_first") }}</span>
@@ -24,6 +24,10 @@
24
24
  <span>{{ t("system.base.redact_rule.parameter.keep_last") }}</span>
25
25
  <el-input-number v-model="form.params.keep_last" :min="0" :precision="0" controls-position="right" />
26
26
  </div>
27
+ <div class="parameter-item">
28
+ <span>{{ t("system.base.redact_rule.parameter.min_mask") }}</span>
29
+ <el-input-number v-model="form.params.min_mask" :min="0" :precision="0" controls-position="right" />
30
+ </div>
27
31
  <div class="parameter-item">
28
32
  <span>{{ t("system.base.redact_rule.parameter.mask_char") }}</span>
29
33
  <el-input v-model="form.params.mask_char" maxlength="8" />
@@ -109,13 +113,14 @@
109
113
  <script setup lang="ts">
110
114
  import { computed, reactive, ref } from "vue";
111
115
  import { ElMessage, ElMessageBox } from "element-plus";
112
- import { EditPen } from "@element-plus/icons-vue";
113
- import type { ColumnProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
116
+ import type { FormRules } from "element-plus";
117
+ import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
118
+ import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
114
119
  import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
115
120
  import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
116
121
  import type { ProFormField } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
117
122
  import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
118
- import { buildPageRequest } from "@liujitcn/kratos-admin-core/table";
123
+ import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
119
124
  import { t } from "@liujitcn/kratos-admin-core";
120
125
  import { defBaseRedactRuleService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_redact_rule";
121
126
  import type { BaseRedactRule, BaseRedactRuleForm, PageBaseRedactRuleRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_redact_rule";
@@ -124,6 +129,7 @@ import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
124
129
  interface RuleParams {
125
130
  keep_first?: number;
126
131
  keep_last?: number;
132
+ min_mask?: number;
127
133
  mask_char?: string;
128
134
  keep_local_first?: number;
129
135
  mask_domain?: boolean;
@@ -152,18 +158,21 @@ const statusOptions = computed(() => [
152
158
  { label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
153
159
  { label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
154
160
  ]);
155
- const templateDescriptionKey = computed(() => `system.base.redact_rule.description.${form.code || "unknown"}`);
156
-
161
+ /** 运行时支持的规则类型选项。 */
162
+ const ruleTypeOptions = ["MASK", "EMAIL", "REGEX", "TRUNCATE", "HASH", "UUID", "IP", "URL", "FIXED_LENGTH"].map(value => ({ label: value, value }));
157
163
  const fields = computed<ProFormField[]>(() => [
158
- { prop: "code", label: t("system.base.redact_rule.field.code"), component: "input", props: { disabled: true } },
159
- { prop: "name", label: t("system.base.redact_rule.field.name"), component: "input", props: { disabled: true } },
160
- { prop: "rule_type", label: t("system.base.redact_rule.field.rule_type"), component: "input", props: { disabled: true } },
164
+ { prop: "code", label: t("system.base.redact_rule.field.code"), component: "input", props: { disabled: Boolean(form.id) } },
165
+ { prop: "name", label: t("system.base.redact_rule.field.name"), component: "input", props: { disabled: Boolean(form.id) } },
166
+ { prop: "rule_type", label: t("system.base.redact_rule.field.rule_type"), component: "select", options: ruleTypeOptions, props: { disabled: Boolean(form.id), onChange: handleRuleTypeChange } },
161
167
  { prop: "rule", label: t("system.base.redact_rule.field.parameters"), component: "slot", slotName: "rule", colSpan: 24 },
162
168
  { prop: "status", label: t("common.field.status"), component: "radio-group", options: statusOptions.value },
163
169
  { prop: "remark", label: t("common.field.remark"), component: "textarea" }
164
170
  ]);
165
171
 
166
- const rules = computed(() => ({
172
+ const rules = computed<FormRules>(() => ({
173
+ code: [{ required: true, message: t("system.base.redact_rule.validation.code"), trigger: "blur" }],
174
+ name: [{ required: true, message: t("system.base.redact_rule.validation.name"), trigger: "blur" }],
175
+ rule_type: [{ required: true, message: t("system.base.redact_rule.validation.rule_type"), trigger: "change" }],
167
176
  rule: [{ required: true, message: t("system.base.redact_rule.validation.parameters"), trigger: "change" }]
168
177
  }));
169
178
 
@@ -190,36 +199,40 @@ const columns = computed<ColumnProps[]>(() => [
190
199
  prop: "actions",
191
200
  label: t("common.field.operation"),
192
201
  cellType: "actions",
193
- actions: [{ label: t("common.action.edit"), type: "primary", link: true, icon: EditPen, hidden: () => !BUTTONS.value["base:redact-rule:update"], onClick: scope => openDialog((scope.row as BaseRedactRule).id) }]
202
+ actions: [{ label: t("common.action.edit"), type: "primary", link: true, icon: EditPen, hidden: () => !BUTTONS.value["base:redact-rule:update"], onClick: scope => openDialog((scope.row as BaseRedactRule).id) }, { label: t("common.action.delete"), type: "danger", link: true, icon: Delete, hidden: () => !BUTTONS.value["base:redact-rule:delete"], onClick: scope => deleteItems(scope.row as BaseRedactRule) }]
194
203
  }
195
204
  ]);
205
+ const headerActions = computed<HeaderActionProps[]>(() => [{ label: t("common.action.create"), type: "success", icon: CirclePlus, hidden: () => !BUTTONS.value["base:redact-rule:create"], onClick: () => openDialog() }, { label: t("common.action.delete"), type: "danger", icon: Delete, hidden: () => !BUTTONS.value["base:redact-rule:delete"], disabled: scope => !scope.selectedList.length, onClick: scope => deleteItems(scope.selectedList as BaseRedactRule[]) }]);
196
206
 
197
- /** 请求固定规则模板列表。 */
207
+ /** 请求固定规则列表。 */
198
208
  async function requestTable(params: PageBaseRedactRuleRequest) {
199
209
  const data = await defBaseRedactRuleService.PageBaseRedactRule(buildPageRequest(params));
200
210
  return { data: { list: data.base_redact_rules ?? [], total: data.total } };
201
211
  }
202
212
 
203
- /** 打开固定规则模板编辑弹窗。 */
204
- async function openDialog(id: number) {
213
+ /** 打开新增或编辑弹窗。 */
214
+ async function openDialog(id?: number) {
205
215
  resetForm();
206
- const data = await defBaseRedactRuleService.GetBaseRedactRule({ id });
207
- Object.assign(form, data, { params: parseRuleParams(data.rule_type, data.rule) });
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";
208
221
  dialog.visible = true;
209
222
  }
210
223
 
211
- /** 重置规则模板编辑表单。 */
224
+ /** 重置规则编辑表单。 */
212
225
  function resetForm() {
213
226
  dialog.visible = false;
214
227
  dialogRef.value?.resetFields();
215
228
  Object.assign(form, defaultForm());
216
229
  }
217
230
 
218
- /** 提交固定规则模板参数。 */
231
+ /** 提交新增或编辑的规则。 */
219
232
  async function submit() {
233
+ syncRule();
220
234
  const valid = await dialogRef.value?.validate();
221
235
  if (!valid) return;
222
- syncRule();
223
236
  const payload: BaseRedactRuleForm = {
224
237
  id: form.id,
225
238
  code: form.code,
@@ -229,13 +242,17 @@ async function submit() {
229
242
  status: form.status,
230
243
  remark: form.remark
231
244
  };
232
- await defBaseRedactRuleService.UpdateBaseRedactRule({ base_redact_rule: payload });
233
- ElMessage.success(t("common.message.update_success", { resource: t("system.base.redact_rule.title") }));
245
+ if (form.id) {
246
+ await defBaseRedactRuleService.UpdateBaseRedactRule({ base_redact_rule: payload });
247
+ } else {
248
+ await defBaseRedactRuleService.CreateBaseRedactRule({ base_redact_rule: payload });
249
+ }
250
+ ElMessage.success(t(form.id ? "common.message.update_success" : "common.message.create_success", { resource: t("system.base.redact_rule.title") }));
234
251
  resetForm();
235
252
  table.value?.getTableList();
236
253
  }
237
254
 
238
- /** 切换固定规则模板状态。 */
255
+ /** 切换固定规则状态。 */
239
256
  async function changeStatus(row: BaseRedactRule) {
240
257
  const next = row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE;
241
258
  try {
@@ -257,46 +274,46 @@ async function changeStatus(row: BaseRedactRule) {
257
274
  }
258
275
  }
259
276
 
277
+ /** 删除未被引用的规则。 */
278
+ function deleteItems(selected?: BaseRedactRule | BaseRedactRule[] | number | string | Array<number | string>) {
279
+ const items = Array.isArray(selected) ? selected.filter((item): item is BaseRedactRule => typeof item === "object") : selected && typeof selected === "object" ? [selected] : [];
280
+ const ids = items.length ? items.map(item => item.id) : normalizeSelectedIds(selected as number | string | Array<number | string>);
281
+ if (!ids.length) {
282
+ ElMessage.warning(t("common.message.select_delete_item"));
283
+ return;
284
+ }
285
+ ElMessageBox.confirm(t("common.dialog.delete_selected", { resource: t("system.base.redact_rule.title") }), t("common.title.warning"), { type: "warning" }).then(async () => {
286
+ await defBaseRedactRuleService.DeleteBaseRedactRule({ id: ids.join(",") });
287
+ ElMessage.success(t("common.message.delete_success", { resource: t("system.base.redact_rule.title") }));
288
+ table.value?.getTableList();
289
+ });
290
+ }
291
+
260
292
  /** 将参数表单同步为运行时规则 JSON。 */
261
293
  function syncRule() {
262
- form.params = { ...defaultRuleParams(form.rule_type), ...form.params };
294
+ if (!form.rule_type) {
295
+ form.rule = "";
296
+ return;
297
+ }
263
298
  form.rule = JSON.stringify({ [form.rule_type.toLowerCase()]: form.params }, null, 2);
264
299
  }
265
300
 
266
- /** 解析规则 JSON 中当前类型的参数。 */
301
+ /** 切换规则类型时重置参数。 */
302
+ function handleRuleTypeChange(ruleType?: string) {
303
+ form.rule_type = ruleType ?? "";
304
+ form.params = {};
305
+ }
306
+
307
+ /** 解析数据库规则 JSON 中当前类型的参数。 */
267
308
  function parseRuleParams(ruleType: string, rawRule: string): RuleParams {
268
309
  try {
269
310
  const value = JSON.parse(rawRule) as Record<string, unknown>;
270
311
  const params = value[ruleType.toLowerCase()];
271
312
  if (params && typeof params === "object" && !Array.isArray(params)) return params as RuleParams;
272
313
  } catch {
273
- return defaultRuleParams(ruleType);
274
- }
275
- return defaultRuleParams(ruleType);
276
- }
277
-
278
- /** 返回固定规则模板的默认参数。 */
279
- function defaultRuleParams(ruleType: string): RuleParams {
280
- switch (ruleType) {
281
- case "MASK":
282
- return { keep_first: 3, keep_last: 4, mask_char: "*" };
283
- case "EMAIL":
284
- return { keep_local_first: 2, mask_domain: false, mask_char: "*" };
285
- case "REGEX":
286
- return { pattern: "(?s).+", replacement: "[REDACTED]" };
287
- case "TRUNCATE":
288
- return { length: 10, suffix: "..." };
289
- case "HASH":
290
- return { algo: "SHA256" };
291
- case "IP":
292
- return { keep_octets: 2, mask_char: "x" };
293
- case "URL":
294
- return { mask_query: true, mask_char: "*" };
295
- case "FIXED_LENGTH":
296
- return { char: "X" };
297
- default:
298
- return {};
314
+ return {};
299
315
  }
316
+ return {};
300
317
  }
301
318
 
302
319
  /** 返回规则参数的紧凑展示文本。 */
@@ -304,9 +321,9 @@ function ruleSummary(row: BaseRedactRule) {
304
321
  return JSON.stringify(parseRuleParams(row.rule_type, row.rule));
305
322
  }
306
323
 
307
- /** 返回规则模板表单的初始值。 */
324
+ /** 返回规则表单的初始值。 */
308
325
  function defaultForm(): RuleFormState {
309
- return { id: 0, code: "", name: "", rule_type: "MASK", rule: "", status: Status.STATUS_ENABLE, remark: "", params: defaultRuleParams("MASK") };
326
+ return { id: 0, code: "", name: "", rule_type: "", rule: "", status: Status.STATUS_ENABLE, remark: "", params: {} };
310
327
  }
311
328
  </script>
312
329
 
@@ -42,6 +42,7 @@
42
42
  <template v-if="row.rule_type === 'MASK'">
43
43
  <ParameterNumber v-model="row.params.keep_first" :label="t('system.base.redact_rule.parameter.keep_first')" />
44
44
  <ParameterNumber v-model="row.params.keep_last" :label="t('system.base.redact_rule.parameter.keep_last')" />
45
+ <ParameterNumber v-model="row.params.min_mask" :label="t('system.base.redact_rule.parameter.min_mask')" />
45
46
  <ParameterText v-model="row.params.mask_char" :label="t('system.base.redact_rule.parameter.mask_char')" />
46
47
  </template>
47
48
  <template v-else-if="row.rule_type === 'EMAIL'">
@@ -93,7 +94,6 @@ import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
93
94
  import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
94
95
  import { t } from "@liujitcn/kratos-admin-core";
95
96
  import { defBaseTableSourceService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_table_source";
96
- import { defCodeGenColumnService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/code_gen_column";
97
97
  import { defCodeGenTableService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/code_gen_table";
98
98
  import { defBaseRedactRuleService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_redact_rule";
99
99
  import { defBaseRedactStoragePolicyService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_redact_storage_policy";
@@ -106,6 +106,7 @@ import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
106
106
  interface RuleParams {
107
107
  keep_first?: number;
108
108
  keep_last?: number;
109
+ min_mask?: number;
109
110
  mask_char?: string;
110
111
  keep_local_first?: number;
111
112
  mask_domain?: boolean;
@@ -227,7 +228,7 @@ async function loadTables(sourceName = form.source_name) { if (!sourceName) { ta
227
228
  /** 预加载数据表中文注释,供列表显示。 */
228
229
  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; }
229
230
  /** 加载并过滤数据库字段。 */
230
- async function loadColumns() { if (!form.source_name || !form.table_name) { form.column_rows = []; return; } const data = await defCodeGenColumnService.ListCodeGenDatabaseColumn({ source_name: form.source_name, table_name: form.table_name }); form.column_rows = (data.columns ?? []).filter(item => !item.is_primary && !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 })); }
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 })); }
231
232
  /** 打开新增或编辑弹窗。 */
232
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; }
233
234
  /** 重置弹窗表单。 */
@@ -237,17 +238,27 @@ async function submit() { const valid = await dialogRef.value?.validate(); if (!
237
238
  /** 将表格行转换为入库策略请求。 */
238
239
  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 }; }
239
240
  /** 将已保存的策略回填到对应字段行。 */
240
- function applyPolicy(row: StorageColumnRow, data: BaseRedactStoragePolicyForm) { row.id = data.id; row.rule_id = data.rule_id || undefined; row.rule_params = data.rule_params; if (row.rule_id) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = parseParams(row.rule_type, row.rule_params); } }
241
+ function applyPolicy(row: StorageColumnRow, data: BaseRedactStoragePolicyForm) { row.id = data.id; row.rule_id = data.rule_id || undefined; row.rule_params = data.rule_params; if (row.rule_id) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = rule ? parseParams(row.rule_type, row.rule_params, rule.rule) : {}; } }
241
242
  /** 处理行规则变更。 */
242
243
  function handleRuleChange(row: StorageTableRow) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = rule ? parseParams(rule.rule_type, rule.rule) : {}; row.rule_params = rule ? rule.rule : "{}"; }
243
244
  /** 同步单行规则参数。 */
244
245
  function syncRowParams(row: StorageColumnRow) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); if (rule) { row.rule_type = rule.rule_type; row.rule_params = JSON.stringify({ [rule.rule_type.toLowerCase()]: row.params }); } }
245
246
  /** 创建空的入库字段行。 */
246
247
  function createColumnRow(option: Pick<StorageColumnRow, "label" | "value" | "name" | "comment" | "db_type">): StorageColumnRow { return { ...option, id: 0, rule_id: undefined, rule_type: "", params: {}, rule_params: "{}" }; }
247
- /** 解析规则参数。 */
248
- function parseParams(ruleType: string, raw: string): RuleParams { try { const value = JSON.parse(raw) as Record<string, RuleParams>; return value[ruleType.toLowerCase()] ?? defaultParams(ruleType); } catch { return defaultParams(ruleType); } }
249
- /** 返回规则默认参数。 */
250
- function defaultParams(ruleType: string): RuleParams { switch (ruleType) { case "MASK": return { keep_first: 3, keep_last: 4, mask_char: "*" }; case "EMAIL": return { keep_local_first: 2, mask_domain: false, mask_char: "*" }; case "REGEX": return { pattern: "(?s).+", replacement: "[REDACTED]" }; case "TRUNCATE": return { length: 10, suffix: "..." }; case "HASH": return { algo: "SHA256" }; case "IP": return { keep_octets: 2, mask_char: "x" }; case "URL": return { mask_query: true, mask_char: "*" }; case "FIXED_LENGTH": return { char: "X" }; default: return {}; } }
248
+ /** 解析规则参数,空策略参数时回退到数据库规则参数。 */
249
+ function parseParams(ruleType: string, raw: string, fallbackRaw = ""): RuleParams {
250
+ const candidates = fallbackRaw && fallbackRaw !== raw ? [raw, fallbackRaw] : [raw];
251
+ for (const candidate of candidates) {
252
+ try {
253
+ const value = JSON.parse(candidate) as Record<string, RuleParams>;
254
+ const params = value[ruleType.toLowerCase()];
255
+ if (params && typeof params === "object" && !Array.isArray(params)) return params;
256
+ } catch {
257
+ continue;
258
+ }
259
+ }
260
+ return {};
261
+ }
251
262
  /** 修改入库策略状态。 */
252
263
  async function changeStatus(row: BaseRedactStoragePolicy) { const next = row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE; try { await ElMessageBox.confirm(t("common.dialog.status_change", { action: t(next === Status.STATUS_ENABLE ? "common.status.enabled" : "common.status.disabled"), resource: t("system.base.redact_storage_policy.title"), field: t("system.base.redact_storage_policy.field.column_name"), value: row.column_name }), t("common.title.warning"), { type: "warning" }); await defBaseRedactStoragePolicyService.SetBaseRedactStoragePolicyStatus({ id: row.id, status: next }); table.value?.getTableList(); return true; } catch { return false; } }
253
264
  /** 删除选中的入库策略。 */
@@ -84,7 +84,7 @@
84
84
  </template>
85
85
 
86
86
  <script setup lang="ts">
87
- import { computed, nextTick, reactive, ref, watch } from "vue";
87
+ import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
88
88
  import { ElMessage, ElMessageBox, ElTree } from "element-plus";
89
89
  import type { CheckboxValueType } from "element-plus";
90
90
  import { CirclePlus, Delete, EditPen, Position, QuestionFilled, Search, Switch } from "@element-plus/icons-vue";
@@ -96,12 +96,10 @@ import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
96
96
  import { defBaseRoleService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_role";
97
97
  import type { BaseRole, BaseRoleForm, PageBaseRoleRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_role";
98
98
  import { defBaseMenuService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_menu";
99
- import { defBaseTenantService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant";
100
- import type { SelectOptionResponse_Option, TreeOptionResponse_Option } from "@liujitcn/kratos-admin-system/rpc/common/v1/common";
99
+ import type { TreeOptionResponse_Option } from "@liujitcn/kratos-admin-system/rpc/common/v1/common";
101
100
  import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
102
101
  import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
103
- import { useUserStore } from "@liujitcn/kratos-admin-core/stores/runtime";
104
- import { DEFAULT_TENANT_CODE, requestTenantOptions } from "@liujitcn/kratos-admin-core/tenant";
102
+ import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
105
103
  import { t } from "@liujitcn/kratos-admin-core";
106
104
 
107
105
  defineOptions({
@@ -122,7 +120,6 @@ type BaseRoleFormState = Omit<BaseRoleForm, "tenant_id"> & {
122
120
  };
123
121
 
124
122
  const { BUTTONS } = useAuthButtons();
125
- const userStore = useUserStore();
126
123
  const proTable = ref<ProTableInstance>();
127
124
  const formDialogRef = ref<InstanceType<typeof FormDialog>>();
128
125
  const permTreeRef = ref<InstanceType<typeof ElTree>>();
@@ -133,12 +130,12 @@ const dialog = reactive({
133
130
  });
134
131
 
135
132
  const menuPermOptions = ref<TreeOptionResponse_Option[]>([]);
136
- const tenantOptions = ref<SelectOptionResponse_Option[]>([]);
137
133
  const statusOptions = computed<ProFormOption[]>(() => [
138
134
  { label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
139
135
  { label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
140
136
  ]);
141
137
  const protectedRoleCodes = new Set(["admin", "authuser", "user"]);
138
+ const undeletableRoleCodes = new Set(["super", "tenant", "admin", "authuser", "user"]);
142
139
 
143
140
  const formData = reactive<BaseRoleFormState>({
144
141
  /** 角色ID */
@@ -236,26 +233,16 @@ const checkedBaseRole = ref<CheckedBaseRole>({});
236
233
  const assignPermDialogVisible = ref(false);
237
234
  const permKeywords = ref("");
238
235
  const isExpanded = ref(true);
239
- const parentChildLinked = ref(true);
236
+ const parentChildLinked = ref(false);
240
237
 
241
- /** 当前登录账号是否默认租户。 */
242
- const isDefaultTenant = computed(() => userStore.userInfo.tenant_code === DEFAULT_TENANT_CODE);
238
+ const { isDefaultTenant, tenantColumns, tenantFormField, toRequestTenantId, loadTenantOptions } = useTenantScope();
239
+ onMounted(() => {
240
+ if (isDefaultTenant.value) void loadTenantOptions();
241
+ });
243
242
 
244
243
  /** 角色表单字段配置。 */
245
244
  const formFields = computed<ProFormField[]>(() => [
246
- {
247
- prop: "tenant_id",
248
- label: t("common.field.tenant"),
249
- component: "select",
250
- props: {
251
- placeholder: t("common.placeholder.select"),
252
- filterable: true,
253
- disabled: Boolean(formData.id),
254
- onChange: handleFormTenantChange
255
- },
256
- visible: () => isDefaultTenant.value,
257
- options: tenantOptions.value
258
- },
245
+ tenantFormField({ label: t("common.field.tenant"), disabledOnEdit: true, props: { onChange: handleFormTenantChange } }),
259
246
  {
260
247
  prop: "name",
261
248
  label: t("system.base.role.field.name"),
@@ -302,19 +289,7 @@ const formFields = computed<ProFormField[]>(() => [
302
289
  /** 角色表格列配置。 */
303
290
  const columns = computed<ColumnProps[]>(() => [
304
291
  { type: "selection", width: 55, selectable: row => canDeleteRole(row as BaseRole) },
305
- ...(isDefaultTenant.value
306
- ? ([
307
- {
308
- prop: "tenant_id",
309
- label: t("common.field.tenant"),
310
- minWidth: 140,
311
- align: "left",
312
- showOverflowTooltip: true,
313
- search: { el: "select", key: "tenant_id", props: { filterable: true }, order: 1 },
314
- enum: requestTenantOptions
315
- }
316
- ] satisfies ColumnProps[])
317
- : []),
292
+ ...tenantColumns({ label: t("common.field.tenant"), order: 1 }),
318
293
  { prop: "name", label: t("system.base.role.field.name"), minWidth: 140, search: { el: "input" } },
319
294
  { prop: "code", label: t("system.base.role.field.code"), minWidth: 160, search: { el: "input" } },
320
295
  {
@@ -401,7 +376,7 @@ const headerActions = computed<HeaderActionProps[]>(() => [
401
376
  async function requestBaseRoleTable(params: PageBaseRoleRequest) {
402
377
  const data = await defBaseRoleService.PageBaseRole({
403
378
  ...buildPageRequest(params),
404
- tenant_id: isDefaultTenant.value ? params.tenant_id : undefined
379
+ tenant_id: toRequestTenantId(params.tenant_id)
405
380
  });
406
381
  return { data: { list: data.base_roles ?? [], total: data.total } };
407
382
  }
@@ -413,15 +388,6 @@ function refreshTable() {
413
388
  proTable.value?.getTableList();
414
389
  }
415
390
 
416
- /**
417
- * 加载租户下拉选项。
418
- */
419
- async function loadTenantOptions() {
420
- if (!isDefaultTenant.value || tenantOptions.value.length) return;
421
- const response = await defBaseTenantService.OptionBaseTenant({ keyword: "" });
422
- tenantOptions.value = response.list ?? [];
423
- }
424
-
425
391
  /**
426
392
  * 按目标角色加载可分配的菜单权限树数据。
427
393
  */
@@ -516,7 +482,7 @@ function canManageRole(row?: BaseRole) {
516
482
  return Boolean(row?.code && !row.is_protected);
517
483
  }
518
484
 
519
- /** 判断角色是否禁止切换状态和删除。 */
485
+ /** 判断角色是否禁止切换状态。 */
520
486
  function isRoleProtected(code?: string) {
521
487
  return Boolean(code && protectedRoleCodes.has(code));
522
488
  }
@@ -528,7 +494,7 @@ function canChangeRoleStatus(row?: BaseRole) {
528
494
 
529
495
  /** 判断当前账号是否允许删除目标角色。 */
530
496
  function canDeleteRole(row?: BaseRole) {
531
- return canManageRole(row) && !isRoleProtected(row?.code);
497
+ return canManageRole(row) && !undeletableRoleCodes.has(row?.code ?? "");
532
498
  }
533
499
 
534
500
  /**