ee6138f3 by lxs

Merge branch 'develop' of http://117.78.60.236:8000/csbr-daop/fe-data-asset-management into develop

2 parents 2e7924af 8f19d70a
...@@ -49,6 +49,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service ...@@ -49,6 +49,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service
49 # VITE_APP_CHECK_BASEURL = /mock 49 # VITE_APP_CHECK_BASEURL = /mock
50 VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory 50 VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory
51 51
52 #匿名化管理接口
53 VITE_APP_ANONYMIZATION_BASEURL = ms-daop-anonymization-service
54
52 #数据源接口地址 55 #数据源接口地址
53 VITE_APP_DATA_SOURCE_URL = ms-daop-data-source-service 56 VITE_APP_DATA_SOURCE_URL = ms-daop-data-source-service
54 57
......
...@@ -82,6 +82,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service ...@@ -82,6 +82,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service
82 # VITE_APP_CHECK_BASEURL = /mock 82 # VITE_APP_CHECK_BASEURL = /mock
83 VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory 83 VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory
84 84
85 #匿名化管理接口
86 VITE_APP_ANONYMIZATION_BASEURL = ms-daop-anonymization-service
87
85 # 数据字典接口地址 88 # 数据字典接口地址
86 VITE_APP_CONFIG_URL = 'ms-daop-configure-service' 89 VITE_APP_CONFIG_URL = 'ms-daop-configure-service'
87 90
......
1 /**
2 * 匿名化管理的接口api文件
3 */
4 import request from "@/utils/request";
5
6 /** 获取标签列表。 */
7 export const getDataLabelList = (params) => request({
8 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/page-list`,
9 method: 'post',
10 data: params
11 })
12
13 /** 修改标签启用禁用状态 */
14 export const updateLabelState = (params) => request({
15 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/update-state`,
16 method: 'put',
17 params
18 })
19
20 export const saveLabel = (data) => request({
21 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/save`,
22 method: 'post',
23 data
24 })
25
26 export const deleteLabel = (data) => request({
27 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/delete`,
28 method: 'delete',
29 data
30 })
31
32 export const updateLabel = (data) => request({
33 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/update`,
34 method: 'put',
35 data
36 })
37
38 /** 获取标签详情 */
39 export const getLabelDetail = (guid) => request({
40 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/detail?guid=${guid}`,
41 method: 'get'
42 })
43
44 /** 获取数据字典配置 */
45 export const getParamsList = (params) => request({
46 url: `${import.meta.env.VITE_APP_CONFIG_URL}/dict/data/get-by-dictType`,
47 method: 'get',
48 params
49 })
50
51 /** 字段类型 */
52 export const fieldTypeList = [{
53 value: '1',
54 label: '日期'
55 }, {
56 value: '2',
57 label: '字符串'
58 }, {
59 value: '3',
60 label: '数值'
61 }]
62
63 /** 获取泛化文件列表 */
64 export const getGeneralizeFileList = (params) => request({
65 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/page-list`,
66 method: 'post',
67 data: params
68 })
69
70 export const saveGeneralizeFile = (data) => request({
71 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/save`,
72 method: 'post',
73 data
74 })
75
76 /** 删除泛化文件 */
77 export const deleteGeneralizeFile = (data) => request({
78 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/delete`,
79 method: 'delete',
80 data
81 })
82
83 /** 获取泛化文件详情 */
84 export const getGeneralizeFileDetail = (guid) => request({
85 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/detail?guid=${guid}`,
86 method: 'get'
87 })
88
89 export const updateGeneralizeFile = (data) => request({
90 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/update`,
91 method: 'put',
92 data
93 })
94
95
96 /** --------- 敏感数据识别接口 ------------------- */
97
98 /** 获取敏感数据识别任务列表 */
99 export const getSensitiveDataTaskList = (params) => request({
100 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/page-list`,
101 method: 'post',
102 data: params
103 })
104
105 /** 新增敏感数据识别任务 */
106 export const saveSensitiveDataTask = (params) => request({
107 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/save`,
108 method: 'post',
109 data: params
110 })
111
112 /** 编辑修改敏感数据识别任务 */
113 export const updateSensitiveDataTask = (params) => request({
114 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/update`,
115 method: 'post',
116 params
117 })
118
119 /** 删除敏感数据识别任务 */
120 export const deleteSensitiveDataTask = (data) => request({
121 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/delete`,
122 method: 'delete',
123 data
124 })
125
126 /** 数据来源类型 */
127 //export const
...\ No newline at end of file ...\ No newline at end of file
...@@ -11,6 +11,8 @@ import { ...@@ -11,6 +11,8 @@ import {
11 MoreFilled, 11 MoreFilled,
12 QuestionFilled, 12 QuestionFilled,
13 WarningFilled, 13 WarningFilled,
14 CircleCheckFilled,
15 CircleCloseFilled,
14 } from "@element-plus/icons-vue"; 16 } from "@element-plus/icons-vue";
15 // import Table from "../Table/index.vue"; 17 // import Table from "../Table/index.vue";
16 import Tabs from "../Tabs/index.vue"; 18 import Tabs from "../Tabs/index.vue";
...@@ -1504,7 +1506,8 @@ const panelChange = (scope, row) => { ...@@ -1504,7 +1506,8 @@ const panelChange = (scope, row) => {
1504 :autocomplete="item.autocompleteSetting?.autocomplete || item.autocomplete 1506 :autocomplete="item.autocompleteSetting?.autocomplete || item.autocomplete
1505 " :readonly="item.autocompleteSetting?.readonly || item.readonly" @click="handlerIptClick(item)" /> 1507 " :readonly="item.autocompleteSetting?.readonly || item.readonly" @click="handlerIptClick(item)" />
1506 <div class="input_def" v-else> 1508 <div class="input_def" v-else>
1507 <el-input :class="[item.col, { is_block: item.block }]" v-model.trim="formInline[item.field]" 1509 <span v-if="item.beforeMsg" style="color: #212121;">{{ item.beforeMsg }}</span>
1510 <el-input :class="[item.col, { is_block: item.block }]" v-model.trim="formInline[item.field]" :style="item.width ? { width: item.width } : null"
1508 :placeholder="item.placeholder" :type="item.inputType ?? 'text'" :clearable="item.clearable" 1511 :placeholder="item.placeholder" :type="item.inputType ?? 'text'" :clearable="item.clearable"
1509 :disabled="item.disabled || readonly" :min="item.min" :max="item.max" :maxlength="item.maxlength ?? ''" 1512 :disabled="item.disabled || readonly" :min="item.min" :max="item.max" :maxlength="item.maxlength ?? ''"
1510 :autocomplete="item.autocompleteSetting?.autocomplete || item.autocomplete 1513 :autocomplete="item.autocompleteSetting?.autocomplete || item.autocomplete
...@@ -1520,25 +1523,13 @@ const panelChange = (scope, row) => { ...@@ -1520,25 +1523,13 @@ const panelChange = (scope, row) => {
1520 <QuestionFilled /> 1523 <QuestionFilled />
1521 </el-icon> 1524 </el-icon>
1522 </el-tooltip> 1525 </el-tooltip>
1526 <!-- 输入框右上角显示验证按钮 -->
1527 <span v-if="item.validateBtn" style="position: absolute;right: 0;top: 0px" class="text_btn" @click="item.validateBtn.click">{{ item.validateBtn.label }}</span>
1528 <div v-if="item.validateMsg?.msg" class="validate-input-after-msg" :style="{ color: item.validateMsg.status == 'success' ? '#4FA55D' : '#E63E33', right: item.validateMsg.status == 'success' ? '-106px' : '-120px' }"><el-icon class="title-icon"><CircleCloseFilled v-if="item.validateMsg.status != 'success'" />
1529 <CircleCheckFilled v-else /></el-icon><span class="title_text">{{ item.validateMsg?.msg }}</span></div>
1530 <span v-if="item.afterMsg" style="color: #212121">{{ item.afterMsg }}</span>
1523 </div> 1531 </div>
1524 </el-form-item> 1532 </el-form-item>
1525 <!-- <el-form-item v-if="item.type === 'formAndSelect'" class="form-and-select" :prop="item.field"
1526 style="width: 100%;display: flex;">
1527 <div v-for="(child, index) in item.children" :key="index" style="margin-right: 8px;"
1528 @mouseenter="handleMouseEnter(index, item.children)" @mouseleave="handleMouseLeave(index)">
1529 <template v-if="child.type === 'select'">
1530 <el-select v-model="formInline[child.field]" :options="child.options" :props="child.props"
1531 :placeholder="child.placeholder" :filterable="child.filterable" :clearable="child.clearable"
1532 class="main-select" style="width: 255px;" />
1533 </template>
1534 <template v-if="child.type === 'input'">
1535 <el-input v-model="formInline[child.field]" :placeholder="child.placeholder" :maxlength="child.maxlength"
1536 :clearable="child.clearable" style="width: 255px;" />
1537 </template>
1538 <el-button v-if="child.showDeleteButton && extraIcon" class="extra-icon" :icon="extraIcon.icon"
1539 @click="extraIcon.click" circle style="margin-left: 8px;" />
1540 </div>
1541 </el-form-item> -->
1542 1533
1543 </template> 1534 </template>
1544 <!-- 默认插槽内容 --> 1535 <!-- 默认插槽内容 -->
...@@ -2505,4 +2496,21 @@ const panelChange = (scope, row) => { ...@@ -2505,4 +2496,21 @@ const panelChange = (scope, row) => {
2505 flex-wrap: nowrap !important; 2496 flex-wrap: nowrap !important;
2506 } 2497 }
2507 } 2498 }
2499
2500 .validate-input-after-msg {
2501 display: flex;
2502 align-items: center;
2503 position: absolute;
2504 top: 26px;
2505 right: -120px;
2506
2507 :deep(.el-icon) {
2508 width: 16px;
2509 height: 16px;
2510 svg {
2511 width: 16px;
2512 height: 16px;
2513 }
2514 }
2515 }
2508 </style> 2516 </style>
......
...@@ -148,9 +148,38 @@ export const useValidator = () => { ...@@ -148,9 +148,38 @@ export const useValidator = () => {
148 } 148 }
149 } 149 }
150 150
151 /** 判断输入的是否是正则表达式的规则 */
152 const regexpRuleValidate = () => {
153 return {
154 validator: (rule: any, value: any, callback: any) => {
155 function isValidRegex(pattern) {
156 try {
157 new RegExp(pattern);
158 return true;
159 } catch (e) {
160 return false;
161 }
162 }
163 if (!value) {
164 callback(new Error("输入正则表达式"));
165 return;
166 }
167 if (isValidRegex(value)) {
168 callback();
169 return;
170 } else {
171 callback(new Error("正则表达式语法不正确"));
172 return;
173 }
174 },
175 trigger: "blur",
176 };
177 }
178
151 return { 179 return {
152 required, 180 required,
153 regexpValidate, 181 regexpValidate,
182 regexpRuleValidate,
154 orderNum, 183 orderNum,
155 description, 184 description,
156 chOrEnPreffix, 185 chOrEnPreffix,
......
...@@ -55,7 +55,7 @@ onMounted(() => { ...@@ -55,7 +55,7 @@ onMounted(() => {
55 55
56 <div class="v-uerinfo"> 56 <div class="v-uerinfo">
57 <div class="v-top">{{ userStore.userName }}</div> 57 <div class="v-top">{{ userStore.userName }}</div>
58 <div class="v-top"> {{ JSON.parse(loaclStorageInfo).abbreviation }}</div> 58 <div class="v-top"> {{ JSON.parse(loaclStorageInfo)?.abbreviation }}</div>
59 </div> 59 </div>
60 <el-avatar size="small"> 60 <el-avatar size="small">
61 <el-icon> 61 <el-icon>
......
1 import type { RouteRecordRaw } from 'vue-router'
2
3 function Layout() {
4 return import('@/layouts/index.vue')
5 }
6
7 const routes: RouteRecordRaw[] = [
8 {
9 path: '/data-anonymization/label-management',
10 component: Layout,
11 meta: {
12 title: '标签管理',
13 icon: 'sidebar-videos',
14 },
15 children: [
16 {
17 path: '',
18 name: 'labelManagement',
19 component: () => import('@/views/data_anonymization/labelManagement.vue'),
20 meta: {
21 title: '标签管理',
22 sidebar: false,
23 breadcrumb: false,
24 cache: true
25 },
26 }
27 ],
28 },
29 {
30 path: '/data-anonymization/generalize-file',
31 component: Layout,
32 meta: {
33 title: '泛化文件管理',
34 icon: 'sidebar-videos',
35 },
36 children: [
37 {
38 path: '',
39 name: 'generalizeFile',
40 component: () => import('@/views/data_anonymization/generalizeFile.vue'),
41 meta: {
42 title: '泛化文件管理',
43 sidebar: false,
44 breadcrumb: false,
45 cache: true
46 },
47 },
48 {
49 path: 'generalize-file-edit',
50 name: 'generalizeFileEdit',
51 component: () => import('@/views/data_anonymization/generalizeFileEdit.vue'),
52 meta: {
53 title: '新建泛化文件',
54 sidebar: false,
55 breadcrumb: false,
56 cache: true,
57 reuse: true,
58 editPage: true,
59 activeMenu: '/data-anonymization/generalize-file'
60 },
61 beforeEnter: (to, from) => {
62 if (to.query.fileName) {
63 to.meta.title = `编辑-${to.query.fileName}`;
64 }
65 }
66 },
67 ],
68 },
69 {
70 path: '/data-anonymization/sensitive-identify',
71 component: Layout,
72 meta: {
73 title: '敏感数据识别',
74 icon: 'sidebar-videos',
75 },
76 children: [
77 {
78 path: '',
79 name: 'sensitiveIdentify',
80 component: () => import('@/views/data_anonymization/sensitiveIdentify.vue'),
81 meta: {
82 title: '敏感数据识别',
83 sidebar: false,
84 breadcrumb: false,
85 cache: true
86 },
87 },
88 ],
89 },
90 {
91 path: '/data-anonymization/result-process',
92 component: Layout,
93 meta: {
94 title: '匿名化处理',
95 icon: 'sidebar-videos',
96 },
97 children: [
98 {
99 path: '',
100 name: 'resultProcess',
101 component: () => import('@/views/data_anonymization/resultProcess.vue'),
102 meta: {
103 title: '匿名化处理',
104 sidebar: false,
105 breadcrumb: false,
106 cache: true
107 },
108 },
109 ],
110 },
111 ]
112
113 export default routes
...@@ -5,6 +5,7 @@ import DataAssess from './modules/dataAsset'; ...@@ -5,6 +5,7 @@ import DataAssess from './modules/dataAsset';
5 import DataMeta from './modules/dataMeta'; 5 import DataMeta from './modules/dataMeta';
6 import DataQuality from './modules/dataQuality'; 6 import DataQuality from './modules/dataQuality';
7 import DataInventory from './modules/dataInventory'; 7 import DataInventory from './modules/dataInventory';
8 import DataAnonymization from './modules/dataAnonymization';
8 import AssetIndex from './modules/assetIndex'; 9 import AssetIndex from './modules/assetIndex';
9 import DataTrustedSpace from './modules/dataTrustedSpace'; 10 import DataTrustedSpace from './modules/dataTrustedSpace';
10 import DataAssetRegistry from './modules/dataAssetRegistry'; 11 import DataAssetRegistry from './modules/dataAssetRegistry';
...@@ -113,6 +114,7 @@ const asyncRoutes: RouteRecordRaw[] = [ ...@@ -113,6 +114,7 @@ const asyncRoutes: RouteRecordRaw[] = [
113 ...DataMeta, 114 ...DataMeta,
114 ...DataQuality, 115 ...DataQuality,
115 ...DataInventory, 116 ...DataInventory,
117 ...DataAnonymization,
116 ...DataTrustedSpace, 118 ...DataTrustedSpace,
117 ...DataPricing 119 ...DataPricing
118 ] 120 ]
......
1 const useDataAnonymizationStore = defineStore(
2 // 资产目录guid
3 "isRefresh",
4 () => {
5 const isRefresh = ref<boolean>(false);
6 function setIsRefresh(v: boolean) {
7 isRefresh.value = v;
8 }
9
10 return {
11 isRefresh,
12 setIsRefresh,
13 };
14 }
15 );
16
17 export default useDataAnonymizationStore;
1 <route lang="yaml">
2 name: generalizeFile
3 </route>
4
5 <script lang="ts" setup name="generalizeFile">
6 import TableTools from "@/components/Tools/table_tools.vue";
7 import { commonPageConfig } from '@/components/PageNav/index';
8 import { TableColumnWidth } from "@/utils/enum";
9 import {
10 deleteGeneralizeFile,
11 getGeneralizeFileList,
12 fieldTypeList,
13 } from '@/api/modules/dataAnonymization';
14 import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
15
16 const anonymizationStore = useDataAnonymizationStore();
17 const router = useRouter()
18 const { proxy } = getCurrentInstance() as any;
19
20 const searchItemList = ref([{
21 type: "input",
22 label: "",
23 field: "generalizeFileName",
24 default: "",
25 placeholder: "泛化文件名称",
26 clearable: true,
27 }, {
28 type: "select",
29 label: "",
30 field: "fieldType",
31 default: null,
32 options: fieldTypeList,
33 placeholder: "字段类型",
34 clearable: true,
35 filterable: true,
36 }])
37
38 /** 分页及搜索传参信息配置。 */
39 const page = ref({
40 ...commonPageConfig,
41 generalizeFileName: '',
42 fieldType: ''
43 });
44
45 const tableInfo = ref({
46 id: 'data-file-table',
47 fields: [
48 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
49 { label: "泛化文件名称", field: "generalizeFileName", width: 160 },
50 { label: "字段类型", field: "fieldType", width: 120, getName: (scope) => {
51 return scope.row.fieldType && fieldTypeList.find(f => f.value == scope.row.fieldType)?.label || '--';
52 } },
53 { label: "泛化层级", field: "generalizeLevel", width: 120, align: 'right', type: 'chnum' },
54 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
55 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
56 ],
57 data: [],
58 page: {
59 type: "normal",
60 rows: 0,
61 ...page.value,
62 },
63 loading: false,
64 actionInfo: {
65 label: "操作",
66 type: "btn",
67 width: 120,
68 fixed: 'right',
69 btns: (scope) => {
70 return [{
71 label: "编辑", value: "edit", click: (scope) => {
72 router.push({
73 name: 'generalizeFileEdit',
74 query: {
75 guid: scope.row.guid,
76 fileName: scope.row.generalizeFileName
77 }
78 });
79 }
80 }, {
81 label: "删除", value: "delete", disabled: scope.row.bizState == 'Y', click: (scope) => {
82 proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => {
83 let guids = [scope.row.guid];
84 deleteGeneralizeFile(guids).then((res: any) => {
85 if (res.code == proxy.$passCode) {
86 getTableData();
87 proxy.$ElMessage({
88 type: "success",
89 message: "删除成功",
90 });
91 } else {
92 proxy.$ElMessage({
93 type: "error",
94 message: res.msg,
95 });
96 }
97 });
98 })
99 }
100 }]
101 }
102 }
103 })
104
105 const toSearch = (val: any, clear: boolean = false) => {
106 if (clear) {
107 searchItemList.value.map((item) => (item.default = ""));
108 page.value.generalizeFileName = '';
109 page.value.fieldType = '';
110 } else {
111 page.value.generalizeFileName = val.generalizeFileName;
112 page.value.fieldType = val.fieldType;
113 }
114 getTableData();
115 };
116
117 const getTableData = () => {
118 tableInfo.value.loading = true
119 getGeneralizeFileList({
120 pageIndex: page.value.curr,
121 pageSize: page.value.limit,
122 generalizeFileName: page.value.generalizeFileName,
123 fieldType: page.value.fieldType
124 }).then((res: any) => {
125 if (res.code == proxy.$passCode) {
126 const data = res.data || {}
127 tableInfo.value.data = data.records || []
128 tableInfo.value.page.limit = data.pageSize
129 tableInfo.value.page.curr = data.pageIndex
130 tableInfo.value.page.rows = data.totalRows
131 } else {
132 proxy.$ElMessage({
133 type: 'error',
134 message: res.msg,
135 })
136 }
137 tableInfo.value.loading = false
138 })
139 };
140
141 const tablePageChange = (info) => {
142 page.value.curr = Number(info.curr);
143 page.value.limit = Number(info.limit);
144 getTableData();
145 };
146
147 onBeforeMount(() => {
148 toSearch({});
149 })
150
151 onActivated(() => {
152 if (anonymizationStore.isRefresh) {//如果是首次加载,则不需要调用
153 page.value.curr = 1;
154 getTableData();
155 anonymizationStore.setIsRefresh(false);
156 }
157 })
158
159 const handleCreate = () => {
160 router.push({
161 name: 'generalizeFileEdit'
162 });
163 }
164
165 </script>
166
167 <template>
168 <div class="container_wrap">
169 <div class="table_tool_wrap">
170 <!-- 头部搜索 -->
171 <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
172 <div class="tools_btns">
173 <el-button type="primary" @click="handleCreate">新建</el-button>
174 </div>
175 </div>
176 <div class="table_panel_wrap">
177 <!-- 右侧标签管理表格 -->
178 <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" />
179 </div>
180 </div>
181 </template>
182
183 <style lang="scss" scoped>
184 .table_tool_wrap {
185 width: 100%;
186 height: 84px !important;
187 padding: 0 8px;
188
189 .tools_btns {
190 padding: 0px 0 0;
191 }
192 }
193
194 .table_panel_wrap {
195 width: 100%;
196 height: calc(100% - 84px);
197 padding: 0px 8px 0;
198 }
199 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <script lang="ts" setup name="generalizeFileEdit">
2 import {
3 fieldTypeList,
4 saveGeneralizeFile,
5 getGeneralizeFileDetail,
6 updateGeneralizeFile,
7 deleteGeneralizeFile,
8 } from '@/api/modules/dataAnonymization';
9 import {
10 parseAndDecodeUrl,
11 getDownFileSignByUrl,
12 obsDownloadRequest
13 } from "@/api/modules/obsService";
14 import useUserStore from "@/store/modules/user";
15 import { useValidator } from '@/hooks/useValidator';
16 import * as XLSX from 'xlsx';
17 import { TableColumnWidth } from '@/utils/enum';
18 import { calcColumnWidth } from "@/utils/index";
19 import Moment from 'moment';
20 import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
21 import { ElMessage } from 'element-plus';
22
23 const anonymizationStore = useDataAnonymizationStore();
24 const { required } = useValidator();
25 const userStore = useUserStore();
26 const router = useRouter();
27 const route = useRoute();
28 const { proxy } = getCurrentInstance() as any;
29 const fullPath = route.fullPath;
30 const guid = ref(route.query.guid);
31
32 const baseInfoItems = ref([{
33 label: '泛化文件名称',
34 type: 'input',
35 maxlength: 15,
36 placeholder: '请输入',
37 field: 'generalizeFileName',
38 default: '',
39 block: false,
40 clearable: true,
41 required: true
42 }, {
43 type: "select",
44 label: "字段类型",
45 field: "fieldType",
46 default: 2,
47 options: fieldTypeList,
48 placeholder: "请选择",
49 clearable: true,
50 filterable: true,
51 required: true,
52 block: false,
53 }, {
54 label: '泛化层级',
55 field: 'generalizeLevel',
56 placeholder: "请输入",
57 type: 'input',
58 inputType: 'integerNumber',
59 maxlength: 6,
60 default: null,
61 clearable: true,
62 required: true,
63 }]);
64
65 const baseInfoFormRules = ref({
66 generalizeFileName: [required('请输入泛化文件名称')],
67 fieldType: [required('请选择字段类型')],
68 generalizeLevel: [required('请输入泛化层级')]
69 });
70
71 /** 文件上传表单配置。 */
72 const fileFormItems: any = ref([
73 {
74 label: '选择文件上传',
75 tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB',
76 type: 'upload-file',
77 accept: '.xlsx, .xls, .csv',
78 limit: 1,
79 limitSize: 10,
80 isExcel: true,
81 required: true,
82 default: [],
83 block: false,
84 field: 'file',
85 }
86 ]);
87
88 const fileFormRules = ref({
89 file: [{
90 validator: (rule: any, value: any, callback: any) => {
91 if (!value?.length) {
92 callback(new Error('请上传文件'))
93 } else {
94 callback();
95 }
96 }, trigger: 'change'
97 }]
98 });
99
100 /** 上传文件之后解析出字段和数据 */
101 const fileFieldLoading = ref(false);
102
103 const fileTableFields: any = ref([]);
104
105 const fileTableData: any = ref([]);
106
107 const parseFileData = (fileRaw) => {
108 fileFieldLoading.value = true;
109 fileRaw.arrayBuffer().then((f) => {
110 const wb = XLSX.read(f, {
111 raw: false, cellDates: true
112 });
113 const sheet = wb.Sheets[wb.SheetNames[0]];
114 const json: any[] = XLSX.utils.sheet_to_json(sheet, { header: 1 });
115 if (json.length == 0) {
116 fileTableFields.value = [];
117 fileTableData.value = [];
118 } else {
119 fileTableFields.value = json[0]?.map((j, index) => {
120 return {
121 index: index,
122 enName: j + '',
123 chName: j + '',
124 dataType: 'varchar'
125 }
126 }) || [];
127 if (json.length > 1) {
128 fileTableData.value = json.slice(1, 51).map((info, row) => {
129 let object = {};
130 json[0].forEach((name, col) => {
131 if (info[col] === "" || info[col] == null) {
132 object[name] = info[col];
133 } else {
134 var cellRef = XLSX.utils.encode_cell({ r: row + 1, c: col });
135 var cell = sheet[cellRef];
136 object[name] = cell.w || info[col];
137 let isNum = cell.t == 'n';
138 if (isNum && fileTableFields.value[col].dataType != 'int') {
139 fileTableFields.value[col].dataType = 'int';
140 }
141 }
142 });
143 return object;
144 });
145 } else {
146 fileTableData.value = [];
147 }
148 }
149 fileFieldLoading.value = false;
150 });
151 }
152
153 const uploadFileChange = (file) => {
154 fileTableFields.value = [];
155 fileTableData.value = [];
156 if (!file.length) {
157 fileTableFields.value = [];
158 fileTableData.value = [];
159 fileFormItems.value[0].default = file;
160 return;
161 }
162 let fileRaw = file[0].file;
163 fileFormItems.value[0].default = file;
164 parseFileData(fileRaw);
165 }
166
167 /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */
168 const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => {
169 let d: any[] = [];
170 data.forEach((dt) => d.push(dt[prop]));
171 return calcColumnWidth(
172 d,
173 title,
174 {
175 fontSize: 14,
176 fontFamily: "SimSun",
177 },
178 {
179 fontSize: 14,
180 fontFamily: "SimSun",
181 },
182 otherWidth
183 );
184 };
185
186 /** 每列字段对应的列宽计算结果。 */
187 const originTableFieldColumn = ref({});
188
189 const getTextAlign = (field) => {
190 if (field.dataType === 'decimal' || field.dataType === 'int') {
191 return 'right';
192 }
193 return 'left'
194 }
195
196 watch(
197 fileTableData,
198 (val: any[], oldVal) => {
199 if (!fileTableFields.value?.length) {
200 originTableFieldColumn.value = {};
201 return;
202 }
203 originTableFieldColumn.value = {};
204 fileTableFields.value.forEach((field, index) => {
205 originTableFieldColumn.value[field.enName] = calcTableColumnWidth(
206 val?.slice(0, 20) || [],
207 field.enName,
208 field.chName,
209 24
210 );
211 });
212 },
213 {
214 deep: true,
215 }
216 );
217
218 const formatterPreviewDate = (row, info) => {
219 let enName = info.enName;
220 let v = row[enName];
221 if (v === 0) {
222 return v;
223 }
224 if (!v) {
225 return v || '--';
226 }
227 if (info.dataType === 'datetime') {
228 return Moment(v).format('YYYY-MM-DD HH:mm:ss');
229 }
230 if (info.dataType === 'date') {
231 if (isNaN(<any>(new Date(v)))) {
232 return Moment(parseInt(v)).format('YYYY-MM-DD');
233 } else {
234 return Moment(v).format('YYYY-MM-DD');
235 }
236 }
237 return v;
238 };
239
240 const cancel = () => {
241 proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => {
242 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
243 router.push({
244 name: 'generalizeFile'
245 });
246 }, () => {
247 proxy.$ElMessage.info("已取消");
248 });
249 }
250
251 const formRef = ref();
252
253 /** 文件表单 */
254 const fileFormRef = ref();
255
256 const saveLoading = ref(false);
257 const saveUpdate = async () => {
258 formRef.value?.ruleFormRef?.validate((valid, errorItem) => {
259 fileFormRef.value?.ruleFormRef?.validate((fileValid, errorItem) => {
260 if (valid && fileValid) {
261 let fileJson = fileFormRef.value?.formInline.file || [];
262 let params = Object.assign({}, formRef.value.formInline, {
263 filePath: {
264 name: fileJson[0]?.name,
265 url: fileJson[0]?.url
266 }
267 })
268 if (!guid.value) {
269 saveLoading.value = true;
270 saveGeneralizeFile(params).then((res: any) => {
271 if (res.code == proxy.$passCode) {
272 proxy.$ElMessage.success('泛化文件新建成功');
273 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
274 anonymizationStore.setIsRefresh(true);
275 router.push({
276 name: 'generalizeFile'
277 });
278 saveLoading.value = false;
279 } else {
280 saveLoading.value = false;
281 proxy.$ElMessage.error(res.msg);
282 }
283 })
284 } else {
285 params.guid = guid.value;
286 saveLoading.value = true;
287 updateGeneralizeFile(params).then((res: any) => {
288 if (res.code == proxy.$passCode) {
289 proxy.$ElMessage.success('泛化文件编辑成功');
290 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
291 anonymizationStore.setIsRefresh(true);
292 router.push({
293 name: 'generalizeFile'
294 });
295 saveLoading.value = false;
296 } else {
297 saveLoading.value = false;
298 proxy.$ElMessage.error(res.msg);
299 }
300 })
301 }
302 }
303 });
304 })
305 }
306
307 const fullscreenLoading = ref(false);
308
309 onBeforeMount(() => {
310 if (guid.value) {
311 fullscreenLoading.value = true;
312 getGeneralizeFileDetail(guid.value).then(async (res: any) => {
313 if (res.code == proxy.$passCode) {
314 let detail = res.data || {};
315 baseInfoItems.value.forEach(item => {
316 item.default = detail[item.field];
317 });
318 fileFormItems.value.forEach(item => {
319 item.default = [{
320 name: detail.filePath.name,
321 url: detail.filePath.url
322 }]
323 });
324 let url = detail.filePath.url;
325 const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(url).fileName);
326 if (!refSignInfo?.data) {
327 fullscreenLoading.value = false;
328 refSignInfo?.msg && ElMessage.error(refSignInfo?.msg);
329 return;
330 }
331 obsDownloadRequest(refSignInfo?.data).then((res: any) => {
332 if (res && !res.msg) {
333 parseFileData(res);
334 fullscreenLoading.value = false;
335 } else {
336 fullscreenLoading.value = false;
337 res?.msg && ElMessage.error(res?.msg);
338 }
339 })
340 } else {
341 fullscreenLoading.value = false;
342 proxy.$ElMessage.error(res.msg);
343 }
344 });
345 }
346 })
347
348 onActivated(() => {
349 const fileName = route.query.fileName;
350 let tab: any = userStore.tabbar.find((tab: any) => tab.fullPath === fullPath);
351 if (tab) {
352 if (fileName) {
353 tab.meta.title = `编辑-${fileName}`;
354 }
355 if (fullPath === route.fullPath) {
356 document.title = tab.meta.title;
357 }
358 }
359 });
360
361 </script>
362
363 <template>
364 <div class="container_wrap" v-loading="fullscreenLoading">
365 <div class="content_main">
366 <ContentWrap id="id-baseInfo" title="基本信息" description="" style="margin-top: 8px;">
367 <Form ref="formRef" :itemList="baseInfoItems" :rules="baseInfoFormRules" formId="main-model-edit" col="col3" />
368 </ContentWrap>
369 <ContentWrap id="id-grade-info" title="上传文件" class="detail-content" description="" style="margin-top: 8px;">
370 <Form ref="fileFormRef" :itemList="fileFormItems" :noUpload="false" formId="file-form" :rules="fileFormRules"
371 @uploadFileChange="uploadFileChange" />
372 <div v-show="fileTableFields.length" class="preview-data-totals">
373 <span>该表仅显示</span>
374 <span class="fontC-4fa1a4"> 50 </span>
375 <span>条数据</span>
376 </div>
377 <el-table ref="tableRef" v-loading="fileFieldLoading" v-show="fileTableFields.length" :data="fileTableData"
378 :highlight-current-row="true" stripe border tooltip-effect="light" height="100%" row-key="guid"
379 :style="{ width: '100%', height: '280px' }">
380 <template v-for="(item, index) in (fileTableFields || [])">
381 <el-table-column :label="item.chName" :width="item.dataType === 'datetime'
382 ? TableColumnWidth.DATETIME
383 : item.dataType === 'date'
384 ? TableColumnWidth.DATE
385 : originTableFieldColumn[item.enName]
386 " :align="getTextAlign(item)" :header-align="getTextAlign(item)"
387 :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true">
388 </el-table-column>
389 </template>
390 </el-table>
391 </ContentWrap>
392 </div>
393 <div class="bottom_tool_wrap">
394 <el-button @click="cancel">取消</el-button>
395 <el-button type="primary" @click="saveUpdate" :loading="saveLoading">提交</el-button>
396 </div>
397 </div>
398 </template>
399
400 <style lang="scss" scoped>
401 .container_wrap {
402 padding: 0px;
403 }
404
405 .content_main {
406 height: calc(100% - 44px);
407 padding: 10px 16px;
408 overflow: auto;
409
410 .table-top-btns {
411 margin-bottom: 12px;
412 }
413 }
414
415 .bottom_tool_wrap {
416 height: 44px;
417 padding: 0 16px;
418 border-top: 1px solid #d9d9d9;
419 display: flex;
420 justify-content: center;
421 align-items: center;
422 }
423
424 .tools_btns {
425 position: relative;
426 margin-bottom: 16px;
427
428 .show-change-btn {
429 position: absolute;
430 right: 0px;
431 }
432 }
433
434 .detail-content {
435 position: relative;
436 }
437
438 .preview-data-totals {
439 position: absolute;
440 top: 150px;
441 right: 16px;
442 font-size: 14px;
443 color: #666666;
444 font-weight: 400;
445
446 .fontC-4fa1a4 {
447 color: var(--el-color-primary);
448 }
449 }
450 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: labelManagement
3 </route>
4
5 <script lang="ts" setup name="labelManagement">
6 import { commonPageConfig } from '@/components/PageNav/index';
7 import { TableColumnWidth } from '@/utils/enum';
8 import TableTools from "@/components/Tools/table_tools.vue";
9 import {
10 getDataLabelList,
11 updateLabelState,
12 saveLabel,
13 updateLabel,
14 deleteLabel,
15 getLabelDetail,
16 getParamsList,
17 } from '@/api/modules/dataAnonymization';
18 import { useValidator } from '@/hooks/useValidator';
19 import { Delete, CirclePlus, Warning } from "@element-plus/icons-vue";
20
21 const { required, regexpRuleValidate } = useValidator();
22 const { proxy } = getCurrentInstance() as any;
23
24 /** 标签类型字典列表 */
25 const labelTypeList: any = ref([]);
26
27 /** 内置规则字典列表 */
28 const builtInRuleList: any = ref([]);
29
30 const searchItemList = ref([{
31 type: "input",
32 label: "",
33 field: "labelName",
34 default: "",
35 placeholder: "标签名称",
36 clearable: true,
37 }])
38
39 /** 分页及搜索传参信息配置。 */
40 const page = ref({
41 ...commonPageConfig,
42 labelName: '',
43 });
44
45 const currTableData: any = ref();
46
47 const tableInfo = ref({
48 id: 'data-label-table',
49 // multiple:true,
50 fields: [
51 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
52 { label: "标签名称", field: "labelName", width: 140 },
53 { label: "标签类型", field: "labelTypeName", width: 140 },
54 { label: '状态', field: 'bizState', type: 'switch', activeText: '启用', inactiveText: '禁用', activeValue: 'Y', inactiveValue: 'N', switchWidth: 56, width: 100, align: 'center' },
55 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
56 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
57 ],
58 data: [],
59 page: {
60 type: "normal",
61 rows: 0,
62 ...page.value,
63 },
64 actionInfo: {
65 label: "操作",
66 type: "btn",
67 width: 120,
68 fixed: 'right',
69 btns: (scope) => {
70 let btnsArr: any = [];
71 btnsArr.push({
72 label: "编辑", value: "edit", click: (scope) => {
73 currTableData.value = scope.row;
74 getLabelDetail(scope.row.guid).then((res: any) => {
75 if (res.code == proxy.$passCode) {
76 const detail = res.data || {};
77 currTableData.value = Object.assign({}, currTableData.value, detail);
78 newCreateLabelFormItems.value.forEach(item => {
79 item.default = detail[item.field];
80 });
81 let labelRuleField = detail.labelRuleField || {};
82 matchChValue.value.value = labelRuleField.matchChValue;
83 matchChValue.value.disabled = false;
84 matchEnValue.value.value = labelRuleField.matchEnValue;
85 matchEnValue.value.disabled = false;
86 formRows.value = labelRuleField.vagueMatchRule || [{ matchValue: '', position: '', name: '', disabled: false }];
87
88 let labelRuleContent = detail.labelRuleContent || {};
89 ruleContentFormItems.value.forEach(item => {
90 item.default = labelRuleContent[item.field];
91 if (item.field == 'regularTestData') {
92 item.validateMsg = null;
93 }
94 });
95
96 newCreateLabelDialogInfo.value.title = '编辑标签';
97 newCreateLabelDialogInfo.value.visible = true;
98 newCreateLabelDialogInfo.value.type = 'update';
99 newCreateLabelDialogInfo.value.submitBtnLoading = false;
100 } else {
101 proxy.$ElMessage({
102 type: 'error',
103 message: res.msg,
104 })
105 }
106 });
107 }
108 })
109 btnsArr.push({
110 label: "删除", value: "delete", disabled: scope.row.bizState == 'Y', click: (scope) => {
111 proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => {
112 let guids = [scope.row.guid];
113 deleteLabel(guids).then((res: any) => {
114 if (res.code == proxy.$passCode) {
115 getTableData();
116 proxy.$ElMessage({
117 type: "success",
118 message: "删除成功",
119 });
120 } else {
121 proxy.$ElMessage({
122 type: "error",
123 message: res.msg,
124 });
125 }
126 });
127 })
128 }
129 });
130 return btnsArr
131 },
132 },
133 loading: false
134 })
135
136 const toSearch = (val: any, clear: boolean = false) => {
137 if (clear) {
138 searchItemList.value.map((item) => (item.default = ""));
139 page.value.labelName = '';
140 } else {
141 page.value.labelName = val.labelName;
142 }
143 getTableData();
144 };
145
146 const getTableData = () => {
147 tableInfo.value.loading = true
148 getDataLabelList({
149 pageIndex: page.value.curr,
150 pageSize: page.value.limit,
151 labelName: page.value.labelName
152 }).then((res: any) => {
153 if (res.code == proxy.$passCode) {
154 const data = res.data || {}
155 tableInfo.value.data = data.records || []
156 tableInfo.value.page.limit = data.pageSize
157 tableInfo.value.page.curr = data.pageIndex
158 tableInfo.value.page.rows = data.totalRows
159 } else {
160 proxy.$ElMessage({
161 type: 'error',
162 message: res.msg,
163 })
164 }
165 tableInfo.value.loading = false
166 })
167 };
168
169 const tableSwitchBeforeChange = (scope, field, callback) => {
170 const msg = `确定【${scope.row[field] == 'Y' ? '禁用' : '启用'}${scope.row.labelName}?`
171 proxy.$openMessageBox(msg, () => {
172 const state = scope.row[field] == 'Y' ? 'N' : 'Y'
173 const result = tableSwitchChange(state, scope, field)
174 callback(result)
175 }, () => {
176 callback(false)
177 });
178 }
179
180 const tableSwitchChange = (val, scope, field) => {
181 return new Promise((resolve, reject) => {
182 let params = {
183 guid: scope.row.guid,
184 bizState: val
185 }
186 updateLabelState(params).then((res: any) => {
187 if (res.code == proxy.$passCode && res.data) {
188 getTableData();
189 proxy.$ElMessage({
190 type: "success",
191 message: `${scope.row.labelName}${val == 'Y' ? '启用' : '禁用'}】成功`,
192 });
193 resolve(true)
194 } else {
195 proxy.$ElMessage({
196 type: "error",
197 message: res.msg,
198 });
199 reject(false)
200 }
201 }).catch(() => {
202 reject(false)
203 })
204 })
205 }
206
207 const tablePageChange = (info) => {
208 page.value.curr = Number(info.curr);
209 page.value.limit = Number(info.limit);
210 getTableData();
211 };
212
213 const newCreateLabelFormItems = ref<any>([{
214 label: '标签名称',
215 type: 'input',
216 placeholder: '请选择',
217 field: 'labelName',
218 default: '',
219 required: true,
220 filterable: true,
221 clearable: true,
222 visible: true,
223 },
224 {
225 label: '标签类型',
226 type: 'select',
227 placeholder: '请选择',
228 field: 'labelTypeCode',
229 default: '',
230 options: labelTypeList.value,
231 props: {
232 label: "label",
233 value: "value",
234 },
235 required: true,
236 filterable: true,
237 clearable: true,
238 visible: true,
239 }, {
240 type: 'radio-group',
241 label: '规则配置',
242 field: 'matchType',
243 default: 1,
244 required: false,
245 block: true,
246 options: [
247 { label: '字段识别和内容识别满足其一即可', value: 1 },
248 { label: '字段识别和内容识别需满足全部', value: 2 },
249 ],
250 }]);
251
252 const newCreateLabelFormRules = ref({
253 labelName: [required('请输入标签名称')],
254 labelTypeCode: [required('请选择标签类型')],
255 });
256
257 const ruleContentFormRef = ref();
258
259 const newCreateLabelDialogInfo = ref({
260 visible: false,
261 size: 600,
262 title: "添加标签",
263 type: "",
264 formInfo: {
265 id: "label-form",
266 items: newCreateLabelFormItems.value,
267 rules: newCreateLabelFormRules.value,
268 },
269 submitBtnLoading: false,
270 btns: {
271 cancel: () => {
272 newCreateLabelDialogInfo.value.visible = false;
273 newCreateLabelDialogInfo.value.submitBtnLoading = false;
274 },
275 submit: (btn, info) => {
276 // 需要验证两个
277 let validateRuleField = () => {
278 if (!matchChValue.value.value && !matchEnValue.value.value && (!formRows.value.length || (formRows.value.length == 1 && formRows.value[0].matchValue &&
279 !formRows.value[0].position && !formRows.value[0].name))) {
280 proxy.$ElMessage.error('字段识别匹配规则不能为空');
281 return false;
282 }
283 for (let i = 0; i < formRows.value.length; i++) {
284 const row = formRows.value[i];
285 // 如果某一条数据的 matchValue, position, name 都为空,则跳过,不校验
286 if (!row.matchValue && !row.position && !row.name) {
287 continue; // 如果全为空,跳过这一行的校验
288 }
289 if (!row.matchValue || !row.position || !row.name) {
290 proxy.$ElMessage.error('请填写完整的模糊匹配规则');
291 return false;
292 }
293 }
294 return true;
295 }
296 let submitLabel = () => {
297 let params = Object.assign({}, info, {
298 bizState: 'Y',
299 labelRuleField: {
300 matchChValue: matchChValue.value.value,
301 matchEnValue: matchEnValue.value.value,
302 vagueMatchRule: formRows.value.filter(f => f.matchValue != '')
303 },
304 labelRuleContent: ruleContentFormRef.value.formInline
305 });
306 newCreateLabelDialogInfo.value.submitBtnLoading = true;
307 if (newCreateLabelDialogInfo.value.type == 'add') {
308 saveLabel(params).then((res: any) => {
309 if (res.code == proxy.$passCode) {
310 proxy.$ElMessage.success('标签新建成功');
311 newCreateLabelDialogInfo.value.visible = false;
312 newCreateLabelDialogInfo.value.submitBtnLoading = false;
313 page.value.curr = 1;
314 getTableData();
315 } else {
316 newCreateLabelDialogInfo.value.submitBtnLoading = false;
317 proxy.$ElMessage.error(res.msg);
318 }
319 });
320 } else {
321 newCreateLabelDialogInfo.value.submitBtnLoading = true;
322 params.guid = currTableData.value.guid;
323 params.labelRuleContent.guid = currTableData.value.labelRuleContent?.guid;
324 params.labelRuleField.guid = currTableData.value.labelRuleField?.guid;
325 updateLabel(params).then((res: any) => {
326 if (res.code == proxy.$passCode) {
327 proxy.$ElMessage.success('标签编辑成功');
328 newCreateLabelDialogInfo.value.visible = false;
329 newCreateLabelDialogInfo.value.submitBtnLoading = false;
330 getTableData();
331 } else {
332 newCreateLabelDialogInfo.value.submitBtnLoading = false;
333 proxy.$ElMessage.error(res.msg);
334 }
335 });
336 }
337 }
338 if (info.matchType == 2) {
339 if (tabsInfo.value.activeName == 'labelRuleField') {
340 if (!validateRuleField()) {
341 return;
342 }
343 ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => {
344 if (valid) {
345 submitLabel();
346 } else {
347 tabsInfo.value.activeName == 'labelRuleContent';
348 }
349 })
350 } else {
351 ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => {
352 if (valid) {
353 if (!validateRuleField()) {
354 tabsInfo.value.activeName == 'labelRuleField';
355 return;
356 }
357 submitLabel();
358 }
359 })
360 }
361 } else { //只需匹配一项即可。
362 if (!matchChValue.value.value && !matchEnValue.value.value && (!formRows.value.length || (formRows.value.length == 1 && formRows.value[0].matchValue &&
363 !formRows.value[0].position && !formRows.value[0].name))) { // 没有配置字段识别内容
364 ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => {
365 if (valid) {
366 submitLabel();
367 } else {
368 proxy.$ElMessage.error('字段识别和内容识别不能同时为空');
369 return;
370 }
371 })
372 } else {
373 submitLabel();
374 }
375 }
376 }
377 }
378 });
379
380 const formRows = ref([
381 { matchValue: '', position: '', name: '', disabled: false }, // 初始行
382 ]);
383
384 // 位置选项
385 const positionOptions = [
386 { label: '前面', value: 'B' },
387 { label: '后面', value: 'A' },
388 { label: '任意位置', value: 'C' },
389 ];
390 // 语言options
391 const languageOptions = [
392 { label: '中文名', value: 'chName' },
393 { label: '英文名', value: 'enName' },
394 ];
395
396 // 新增行
397 const addRow = () => {
398 formRows.value.push({ matchValue: '', position: '', name: '', disabled: false });
399 };
400
401 // 删除行
402 const deleteRow = (index: number) => {
403 formRows.value.splice(index, 1);
404 };
405 const matchChValue = ref({
406 value: '',
407 disabled: false
408 })
409 const matchEnValue = ref({
410 value: '',
411 disabled: false
412 })
413
414 const tabsInfo = ref({
415 activeName: 'labelRuleField',
416 tabs: [
417 { label: '字段识别', name: 'labelRuleField' },
418 { label: '内容识别', name: 'labelRuleContent' },
419 ]
420 });
421
422 const tabChange = (val) => {
423 tabsInfo.value.activeName = val;
424 }
425
426 const handleCreate = () => {
427 newCreateLabelFormItems.value.forEach(item => {
428 if (item.field == 'matchType') {
429 item.default = 1;
430 } else {
431 item.default = '';
432 }
433 });
434 matchChValue.value.value = '';
435 matchChValue.value.disabled = false;
436 matchEnValue.value.value = '';
437 matchEnValue.value.disabled = false;
438 formRows.value = [{ matchValue: '', position: '', name: '', disabled: false }];
439
440 ruleContentFormItems.value.forEach(item => {
441 item.default = item.field == 'ruleType' ? 1 : '';
442 if (item.field == 'regularTestData') {
443 item.validateMsg = null;
444 }
445 });
446
447 newCreateLabelDialogInfo.value.title = '添加标签';
448 newCreateLabelDialogInfo.value.visible = true;
449 newCreateLabelDialogInfo.value.type = 'add';
450 newCreateLabelDialogInfo.value.submitBtnLoading = false;
451 }
452
453 /** 规则配置的内容识别内容表单配置 */
454 const ruleContentFormItems = ref([{
455 label: '规则类型',
456 type: 'select',
457 placeholder: '请选择',
458 field: 'ruleType',
459 default: 1,
460 options: [{
461 value: 1,
462 label: '内置规则'
463 }, {
464 value: 2,
465 label: '自定义规则'
466 }],
467 props: {
468 label: "label",
469 value: "value",
470 },
471 required: true,
472 filterable: true,
473 clearable: false,
474 block: true,
475 visible: true,
476 }, {
477 label: '内置规则',
478 type: 'select',
479 placeholder: '请选择',
480 field: 'builtInRuleCode',
481 default: '',
482 options: builtInRuleList.value,
483 props: {
484 label: "label",
485 value: "value",
486 },
487 required: true,
488 filterable: true,
489 clearable: true,
490 block: true,
491 visible: true,
492 }, {
493 label: '规则表达式',
494 type: 'input',
495 placeholder: '请输入正则表达式',
496 field: 'regularExpression',
497 default: '',
498 required: true,
499 filterable: true,
500 clearable: true,
501 block: true,
502 visible: false,
503 }, {
504 label: '测试验证',
505 type: 'input',
506 placeholder: '请输入测试数据',
507 field: 'regularTestData',
508 default: '',
509 required: false,
510 clearable: true,
511 block: false,
512 visible: true,
513 validateBtn: {
514 value: 'validate',
515 label: '验证',
516 click: () => {
517 let info = ruleContentFormRef.value.formInline;
518 if (info.ruleType == 1 && !info.builtInRuleCode) {
519 proxy.$ElMessage.error('请先选择内置规则');
520 return;
521 }
522 if (info.ruleType == 2 && !info.regularExpression) {
523 proxy.$ElMessage.error('请先输入规则表达式');
524 return;
525 }
526 if (!info.regularTestData) {
527 proxy.$ElMessage.error('请先输入测试数据');
528 return;
529 }
530 let exp = info.ruleType == 1 ? builtInRuleList.value.find(b => b.value == info.builtInRuleCode)?.remarks : info.regularExpression;
531 let result = exp && new RegExp(exp).test(info.regularTestData);
532 ruleContentFormItems.value.forEach(item => {
533 item.default = info[item.field];
534 if (item.field == 'regularTestData') {
535 item.validateMsg = result ? {
536 status: 'success',
537 msg: '符合识别规则'
538 } : {
539 status: 'error',
540 msg: '不符合识别规则'
541 }
542 }
543 });
544 }
545 },
546 validateMsg: <any>null
547 }, {
548 label: '命中率设置(%)',
549 type: 'input',
550 placeholder: '请输入0~100',
551 field: 'hitRate',
552 min: 1,
553 max: 100,
554 inputType: 'integerNumber',
555 default: null,
556 required: true,
557 block: true,
558 clearable: true,
559 visible: true,
560 width: '110px',
561 beforeMsg: '一列数据中的非空数据,大于等于',
562 afterMsg: ' %的数据符合以上识别条件,则认为命中该识别规则。'
563 }]);
564
565 /** 规则配置的内容识别内容表单规则配置 */
566 const ruleContentFormRules = ref({
567 builtInRuleCode: [required('请选择内置规则')],
568 regularExpression: [required('请输入正则表达式'), regexpRuleValidate()],
569 hitRate: [required(' ')]
570 });
571
572 const handleRuleContentSelectChange = (val, row, info) => {
573 if (row.field == 'ruleType') {
574 ruleContentFormItems.value[1].visible = val == 1;
575 ruleContentFormItems.value[2].visible = val == 2;
576 ruleContentFormItems.value.forEach(item => {
577 item.default = info[item.field];
578 })
579 }
580 }
581
582 const handleRuleContentInputChange = (val, row) => {
583 if (row.field == 'regularTestData') {
584 let item = ruleContentFormItems.value.at(-2);
585 item && (item.validateMsg = null);
586 }
587 }
588
589 onBeforeMount(() => {
590 toSearch({});
591 getParamsList({
592 dictType: "标签类型",
593 }).then((res: any) => {
594 if (res.code == proxy.$passCode) {
595 labelTypeList.value = res.data || [];
596 let item = newCreateLabelFormItems.value.find(item => item.field == 'labelTypeCode');
597 item && (item.options = labelTypeList.value);
598 } else {
599 proxy.$ElMessage.error(res.msg);
600 }
601 });
602 getParamsList({
603 dictType: "内置规则",
604 }).then((res: any) => {
605 if (res.code == proxy.$passCode) {
606 builtInRuleList.value = res.data || [];
607 ruleContentFormItems.value[1].options = builtInRuleList.value;
608 } else {
609 proxy.$ElMessage.error(res.msg);
610 }
611 })
612 })
613
614 </script>
615
616 <template>
617 <div class="container_wrap">
618 <div class="table_tool_wrap">
619 <!-- 头部搜索 -->
620 <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
621 <div class="tools_btns">
622 <el-button type="primary" @click="handleCreate">新建</el-button>
623 </div>
624 </div>
625 <div class="table_panel_wrap">
626 <!-- 右侧标签管理表格 -->
627 <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange"
628 @tableSwitchBeforeChange="tableSwitchBeforeChange" />
629 </div>
630 <!-- 新建编辑标签对话框 -->
631 <Dialog_form ref="dialogLabelFormRef" :dialogConfigInfo="newCreateLabelDialogInfo" class="v-dialog-form">
632 <template v-slot:default>
633 <Tabs :tabs-info="tabsInfo" @tab-change="tabChange" style="margin-top: -20px;" />
634 <div v-show="tabsInfo.activeName == 'labelRuleField'">
635 <div>
636 <div class="dim-label">
637 <span class="front">精确匹配</span>
638 <el-icon>
639 <Warning />
640 </el-icon>
641 <span class="tip">
642 精确匹配使用英文","分隔每个规则
643 </span>
644 </div>
645 <div class="v-match">
646 <el-input v-model="matchChValue.value" maxlength="200"
647 style="width: 272px;height:94px;" show-word-limit type="textarea" class="no-resize"
648 placeholder="请输入字段中文" />
649 <el-input v-model="matchEnValue.value" maxlength="200"
650 style="width: 272px;height:94px;" show-word-limit type="textarea" class="no-resize"
651 placeholder="请输入字段英文" />
652 </div>
653 </div>
654 <div class="dim-label" style="margin-top: 16px;">
655 <span class="front">模糊匹配</span>
656 <el-icon>
657 <Warning />
658 </el-icon>
659 <span class="tip">
660 模糊匹配是或的关系,可配置多个模糊匹配规则
661 </span>
662 </div>
663 <!-- 渲染行 -->
664 <div v-for="(row, index) in formRows" :key="index" class="match-content-wrapper">
665 <div class="match-content">
666 <!-- 位置映射下拉框 -->
667 <el-select v-model="row.name" placeholder="请选择" class="v-select">
668 <el-option v-for="option in languageOptions" :key="option.value" :label="option.label"
669 :value="option.value" />
670 </el-select>
671 <el-select v-model="row.position" placeholder="请选择位置" class="v-select">
672 <el-option v-for="option in positionOptions" :key="option.value" :label="option.label"
673 :value="option.value" />
674 </el-select>
675 <el-input v-model="row.matchValue" class="v-input" placeholder="请输入匹配值" />
676
677 <!-- 删除按钮 -->
678 <el-button class="extra-icon" :icon="Delete" @click="deleteRow(index)" circle
679 style="margin-left: 8px;" />
680 </div>
681 </div>
682
683 <!-- 新增按钮 -->
684 <div class="add-Icon" @click="addRow">
685 <el-icon class="icon-add" color="#4fa1a4" :size="30">
686 <CirclePlus />
687 </el-icon>
688 <span class="word-des">模糊匹配规则</span>
689 </div>
690 </div>
691 <div v-show="tabsInfo.activeName == 'labelRuleContent'">
692 <Form ref="ruleContentFormRef" :itemList="ruleContentFormItems" formId="rule-content-form"
693 :rules="ruleContentFormRules" @select-change="handleRuleContentSelectChange"
694 @inputChange="handleRuleContentInputChange" />
695 </div>
696 </template>
697 </Dialog_form>
698 </div>
699 </template>
700
701 <style scoped lang="scss">
702 .table_tool_wrap {
703 width: 100%;
704 height: 84px !important;
705 padding: 0 8px;
706
707 .tools_btns {
708 padding: 0px 0 0;
709 }
710 }
711
712 .table_panel_wrap {
713 width: 100%;
714 height: calc(100% - 84px);
715 padding: 0px 8px 0;
716 }
717
718 :deep(.v-dialog-form) {
719 .title-label {
720 font-size: 16px;
721 color: #212121;
722 line-height: 24px;
723 font-weight: 600;
724 }
725
726 .el-dialog__body {
727 height: 480px;
728 overflow: auto;
729 }
730
731 .dim-label {
732 height: 10px;
733 display: flex;
734 align-items: center;
735 margin-top: 6px;
736
737 .el-icon svg {
738 height: 16px;
739 width: 16px;
740 color: #999999;
741 }
742
743 .front {
744 margin-right: 16px;
745 }
746
747 .tip {
748 margin-left: 4px;
749 font-size: 12px;
750 color: #999999;
751 }
752 }
753
754 .match-content-wrapper {
755 width: 100%;
756
757 .match-content {
758 display: flex;
759 align-items: center;
760 margin-top: 8px;
761
762 .v-select {
763 margin-right: 8px;
764 width: 33%;
765 }
766
767 .v-input {
768 width: calc(33% - 50px);
769 }
770
771 .extra-icon {
772 transition: opacity 1s;
773 display: none;
774 }
775
776 &:hover {
777 .extra-icon {
778 display: flex;
779 }
780 }
781 }
782 }
783
784 .add-Icon {
785 display: flex;
786 align-items: center;
787 margin-top: 13px;
788 width: 50%;
789
790 .el-icon svg {
791 height: 19px;
792 width: 19px;
793 }
794
795 .word-des {
796 color: #4fa1a4
797 }
798 }
799
800 .v-match {
801 display: flex;
802 justify-content: space-between;
803 margin-top: 10px;
804 margin-bottom: 10px;
805 }
806
807 .no-resize {
808 height: 94px;
809
810 .el-textarea__inner {
811 min-height: 94px !important;
812 resize: none;
813 }
814 }
815 }
816 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: resultProcess
3 </route>
4
5 <script lang="ts" setup name="resultProcess">
6
7 </script>
8
9 <template>
10 <div>匿名化处理</div>
11 </template>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: sensitiveIdentify
3 </route>
4
5 <script lang="ts" setup name="sensitiveIdentify">
6 import TableTools from "@/components/Tools/table_tools.vue";
7 import { commonPageConfig } from '@/components/PageNav/index';
8 import { TableColumnWidth } from "@/utils/enum";
9 import {
10 getSensitiveDataTaskList,
11 } from '@/api/modules/dataAnonymization';
12
13 const router = useRouter()
14 const { proxy } = getCurrentInstance() as any;
15
16 const searchItemList = ref([{
17 type: "input",
18 label: "",
19 field: "taskName",
20 default: "",
21 placeholder: "任务名称",
22 clearable: true,
23 }, {
24 type: "select",
25 label: "",
26 field: "fieldType",
27 default: null,
28 options: [],
29 placeholder: "数据来源",
30 clearable: true,
31 filterable: true,
32 }])
33
34 /** 分页及搜索传参信息配置。 */
35 const page = ref({
36 ...commonPageConfig,
37 generalizeFileName: '',
38 fieldType: ''
39 });
40
41 </script>
42
43 <template>
44 <div class="container_wrap">
45 <div class="table_tool_wrap">
46 <!-- 头部搜索 -->
47 <!-- <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
48 <div class="tools_btns">
49 <el-button type="primary" @click="handleCreate">新建</el-button>
50 </div> -->
51 </div>
52 <div class="table_panel_wrap">
53 <!-- 右侧标签管理表格 -->
54 <!-- <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" /> -->
55 </div>
56 </div>
57 </template>
58
59 <style lang="scss" scoped>
60 .table_tool_wrap {
61 width: 100%;
62 height: 84px !important;
63 padding: 0 8px;
64
65 .tools_btns {
66 padding: 0px 0 0;
67 }
68 }
69
70 .table_panel_wrap {
71 width: 100%;
72 height: calc(100% - 84px);
73 padding: 0px 8px 0;
74 }
75 </style>
...\ No newline at end of file ...\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!