@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.
- package/README.md +44 -8
- package/dist/package/declarations/src/api/system/admin/v1/base_i18n_custom.d.ts +18 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_redact_output_policy.d.ts +4 -1
- package/dist/package/declarations/src/api/system/admin/v1/base_redact_storage_policy.d.ts +3 -1
- package/dist/package/declarations/src/api/system/admin/v1/base_tenant.d.ts +2 -2
- package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project.d.ts +23 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project_grant.d.ts +16 -0
- package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +29 -0
- package/dist/package/declarations/src/components/tenant-project/TenantProjectSelect.vue.d.ts +31 -0
- package/dist/package/declarations/src/components/tenant-project/TenantProjectText.vue.d.ts +19 -0
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +14 -0
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +40 -0
- package/dist/package/declarations/src/components/tenant-project-grant/ProjectGrantDialog.vue.d.ts +8 -0
- package/dist/package/declarations/src/index.d.ts +2 -0
- package/dist/package/declarations/src/locales/generated.d.ts +196 -0
- package/dist/package/declarations/src/rpc/base/v1/config.d.ts +11 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n_custom.d.ts +105 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +8 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_storage_policy.d.ts +25 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +10 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project.d.ts +141 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project_grant.d.ts +105 -0
- package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +2 -2
- package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +33 -0
- package/dist/package/declarations/src/views/base/i18n-custom/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/tenant-project/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/tenant-project-grant/index.vue.d.ts +3 -0
- package/dist/package/src/api/system/admin/v1/base_i18n_custom.ts +72 -0
- package/dist/package/src/api/system/admin/v1/base_redact_output_policy.ts +7 -0
- package/dist/package/src/api/system/admin/v1/base_redact_storage_policy.ts +7 -0
- package/dist/package/src/api/system/admin/v1/base_tenant.ts +3 -2
- package/dist/package/src/api/system/admin/v1/base_tenant_project.ts +94 -0
- package/dist/package/src/api/system/admin/v1/base_tenant_project_grant.ts +63 -0
- package/dist/package/src/components/tenant-project/TenantProjectManager.vue +431 -0
- package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +83 -0
- package/dist/package/src/components/tenant-project/TenantProjectText.vue +37 -0
- package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +26 -0
- package/dist/package/src/components/tenant-project/tenant-project-manager.ts +48 -0
- package/dist/package/src/components/tenant-project-grant/ProjectGrantDialog.vue +108 -0
- package/dist/package/src/index.ts +12 -0
- package/dist/package/src/locales/README.md +1 -1
- package/dist/package/src/locales/en-US.json +51 -2
- package/dist/package/src/locales/ja-JP.json +51 -2
- package/dist/package/src/locales/zh-CN.json +51 -2
- package/dist/package/src/locales/zh-TW.json +51 -2
- package/dist/package/src/module.ts +3 -3
- package/dist/package/src/rpc/base/v1/config.ts +12 -0
- package/dist/package/src/rpc/system/admin/v1/base_i18n_custom.ts +126 -0
- package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +9 -0
- package/dist/package/src/rpc/system/admin/v1/base_redact_storage_policy.ts +30 -0
- package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +11 -1
- package/dist/package/src/rpc/system/admin/v1/base_tenant_project.ts +166 -0
- package/dist/package/src/rpc/system/admin/v1/base_tenant_project_grant.ts +128 -0
- package/dist/package/src/views/ai/chat/components/XSender.vue +21 -28
- package/dist/package/src/views/ai/chat/components/speech-recognition.ts +156 -0
- package/dist/package/src/views/base/api-log/index.vue +3 -3
- package/dist/package/src/views/base/config/index.vue +3 -3
- package/dist/package/src/views/base/data-access-log/index.vue +3 -3
- package/dist/package/src/views/base/dept/index.vue +10 -45
- package/dist/package/src/views/base/dict/index.vue +3 -3
- package/dist/package/src/views/base/dict/item.vue +3 -3
- package/dist/package/src/views/base/file/index.vue +12 -3
- package/dist/package/src/views/base/i18n-custom/index.vue +321 -0
- package/dist/package/src/views/base/job/index.vue +85 -161
- package/dist/package/src/views/base/login-log/index.vue +13 -4
- package/dist/package/src/views/base/login-policy/index.vue +109 -40
- package/dist/package/src/views/base/menu/index.vue +3 -3
- package/dist/package/src/views/base/message/index.vue +9 -32
- package/dist/package/src/views/base/message-category/index.vue +2 -4
- package/dist/package/src/views/base/oauth-client/index.vue +8 -42
- package/dist/package/src/views/base/online-session/index.vue +8 -27
- package/dist/package/src/views/base/operation-log/index.vue +3 -3
- package/dist/package/src/views/base/permission-log/index.vue +3 -3
- package/dist/package/src/views/base/policy-evaluation-log/index.vue +3 -3
- package/dist/package/src/views/base/post/index.vue +9 -42
- package/dist/package/src/views/base/redact-output-policy/index.vue +19 -7
- package/dist/package/src/views/base/redact-rule/index.vue +70 -53
- package/dist/package/src/views/base/redact-storage-policy/index.vue +18 -7
- package/dist/package/src/views/base/role/index.vue +14 -48
- package/dist/package/src/views/base/tenant/index.vue +163 -19
- package/dist/package/src/views/base/tenant-project/index.vue +12 -0
- package/dist/package/src/views/base/tenant-project-grant/index.vue +338 -0
- package/dist/package/src/views/base/user/index.vue +30 -51
- package/dist/package/src/views/profile/components/login-log.vue +15 -2
- package/dist/package/src/views/profile/components/password.vue +1 -1
- package/dist/package/src/views/profile/components/security.vue +2 -2
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +2 -2
- package/dist/package/src/views/tool/code-gen/config.ts +2 -1
- package/package.json +2 -2
- /package/dist/package/declarations/src/components/{ChangePasswordForm.vue.d.ts → account/ChangePasswordForm.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{ForcedPasswordDialog.vue.d.ts → account/ForcedPasswordDialog.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{Ai.vue.d.ts → ai/Ai.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{DynamicI18nCell.vue.d.ts → i18n/DynamicI18nCell.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{DynamicI18nEditor.vue.d.ts → i18n/DynamicI18nEditor.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{dynamicI18n.d.ts → i18n/dynamicI18n.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{LogTable.vue.d.ts → log/LogTable.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{log.d.ts → log/log.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{Notification.vue.d.ts → notification/Notification.vue.d.ts} +0 -0
- /package/dist/package/src/components/{ChangePasswordForm.vue → account/ChangePasswordForm.vue} +0 -0
- /package/dist/package/src/components/{ForcedPasswordDialog.vue → account/ForcedPasswordDialog.vue} +0 -0
- /package/dist/package/src/components/{Ai.vue → ai/Ai.vue} +0 -0
- /package/dist/package/src/components/{DynamicI18nCell.vue → i18n/DynamicI18nCell.vue} +0 -0
- /package/dist/package/src/components/{DynamicI18nEditor.vue → i18n/DynamicI18nEditor.vue} +0 -0
- /package/dist/package/src/components/{dynamicI18n.ts → i18n/dynamicI18n.ts} +0 -0
- /package/dist/package/src/components/{LogTable.vue → log/LogTable.vue} +0 -0
- /package/dist/package/src/components/{log.ts → log/log.ts} +0 -0
- /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 {
|
|
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
|
-
|
|
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
|
-
...(
|
|
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:
|
|
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
|
|
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
|
|
298
|
-
|
|
299
|
-
|
|
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">{{
|
|
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 {
|
|
113
|
-
import
|
|
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
|
-
|
|
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:
|
|
159
|
-
{ prop: "name", label: t("system.base.redact_rule.field.name"), component: "input", props: { disabled:
|
|
160
|
-
{ prop: "rule_type", label: t("system.base.redact_rule.field.rule_type"), component: "
|
|
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
|
|
213
|
+
/** 打开新增或编辑弹窗。 */
|
|
214
|
+
async function openDialog(id?: number) {
|
|
205
215
|
resetForm();
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
236
|
+
const parentChildLinked = ref(false);
|
|
240
237
|
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
...(
|
|
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:
|
|
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) && !
|
|
497
|
+
return canManageRole(row) && !undeletableRoleCodes.has(row?.code ?? "");
|
|
532
498
|
}
|
|
533
499
|
|
|
534
500
|
/**
|