@liujitcn/kratos-admin-system 0.0.50 → 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 (29) hide show
  1. package/README.md +2 -2
  2. package/dist/package/declarations/src/api/system/admin/v1/base_redact_output_policy.d.ts +4 -1
  3. package/dist/package/declarations/src/api/system/admin/v1/base_redact_storage_policy.d.ts +3 -1
  4. package/dist/package/declarations/src/locales/generated.d.ts +24 -0
  5. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +8 -0
  6. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_storage_policy.d.ts +25 -0
  7. package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +33 -0
  8. package/dist/package/src/api/system/admin/v1/base_redact_output_policy.ts +7 -0
  9. package/dist/package/src/api/system/admin/v1/base_redact_storage_policy.ts +7 -0
  10. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +14 -35
  11. package/dist/package/src/locales/en-US.json +6 -0
  12. package/dist/package/src/locales/ja-JP.json +6 -0
  13. package/dist/package/src/locales/zh-CN.json +6 -0
  14. package/dist/package/src/locales/zh-TW.json +6 -0
  15. package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +9 -0
  16. package/dist/package/src/rpc/system/admin/v1/base_redact_storage_policy.ts +30 -0
  17. package/dist/package/src/views/ai/chat/components/XSender.vue +17 -25
  18. package/dist/package/src/views/ai/chat/components/speech-recognition.ts +156 -0
  19. package/dist/package/src/views/base/job/index.vue +82 -158
  20. package/dist/package/src/views/base/login-policy/index.vue +12 -6
  21. package/dist/package/src/views/base/redact-output-policy/index.vue +19 -7
  22. package/dist/package/src/views/base/redact-rule/index.vue +70 -53
  23. package/dist/package/src/views/base/redact-storage-policy/index.vue +18 -7
  24. package/dist/package/src/views/base/role/index.vue +1 -1
  25. package/dist/package/src/views/base/{project-grant → tenant-project-grant}/index.vue +3 -1
  26. package/package.json +2 -2
  27. /package/dist/package/declarations/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue.d.ts +0 -0
  28. /package/dist/package/declarations/src/views/base/{project-grant → tenant-project-grant}/index.vue.d.ts +0 -0
  29. /package/dist/package/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue +0 -0
@@ -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
  /** 删除选中的入库策略。 */
@@ -233,7 +233,7 @@ const checkedBaseRole = ref<CheckedBaseRole>({});
233
233
  const assignPermDialogVisible = ref(false);
234
234
  const permKeywords = ref("");
235
235
  const isExpanded = ref(true);
236
- const parentChildLinked = ref(true);
236
+ const parentChildLinked = ref(false);
237
237
 
238
238
  const { isDefaultTenant, tenantColumns, tenantFormField, toRequestTenantId, loadTenantOptions } = useTenantScope();
239
239
  onMounted(() => {
@@ -231,7 +231,9 @@ async function handleOpenDialog(row?: BaseTenantProjectGrant) {
231
231
 
232
232
  /** 加载目标租户项目选项。 */
233
233
  async function loadProjectOptions() {
234
- projectOptions.value = formData.tenant_id ? (await defBaseTenantProjectService.OptionBaseTenantProject({ tenant_id: formData.tenant_id })).list ?? [] : [];
234
+ projectOptions.value = formData.tenant_id
235
+ ? (await defBaseTenantProjectService.OptionBaseTenantProject({ tenant_id: toRequestTenantId(formData.tenant_id) })).list ?? []
236
+ : [];
235
237
  }
236
238
 
237
239
  /** 根据授权类型加载岗位、角色、部门或用户主体选项。 */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-system",
3
3
  "private": false,
4
- "version": "0.0.50",
4
+ "version": "0.0.51",
5
5
  "type": "module",
6
6
  "description": "kratos-admin system management module",
7
7
  "license": "MIT",
@@ -16,7 +16,7 @@
16
16
  "sortablejs": "^1.15.2",
17
17
  "swagger-ui-dist": "^5.32.15",
18
18
  "vue-element-plus-x": "^2.0.3",
19
- "@liujitcn/kratos-admin-core": "0.0.50"
19
+ "@liujitcn/kratos-admin-core": "0.0.51"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "@element-plus/icons-vue": "^2.3.1",