Merge branches 'release-test' and 'release-test' of http://117.78.60.236:8000/cs…
…br-daop/fe-data-asset-management into release-test
Showing
14 changed files
with
1872 additions
and
19 deletions
| ... | @@ -50,6 +50,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service | ... | @@ -50,6 +50,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service |
| 50 | # VITE_APP_CHECK_BASEURL = /mock | 50 | # VITE_APP_CHECK_BASEURL = /mock |
| 51 | VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory | 51 | VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory |
| 52 | 52 | ||
| 53 | #匿名化管理接口 | ||
| 54 | VITE_APP_ANONYMIZATION_BASEURL = ms-daop-anonymization-service | ||
| 55 | |||
| 53 | #数据源接口地址 | 56 | #数据源接口地址 |
| 54 | VITE_APP_DATA_SOURCE_URL = ms-daop-data-source-service | 57 | VITE_APP_DATA_SOURCE_URL = ms-daop-data-source-service |
| 55 | 58 | ... | ... |
| ... | @@ -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 | ... | ... |
src/api/modules/dataAnonymization.ts
0 → 100644
| 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> | ... | ... |
src/router/modules/dataAnonymization.ts
0 → 100644
| 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 | ] | ... | ... |
src/store/modules/dataAnonymization.ts
0 → 100644
| 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: 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 |
-
Please register or sign in to post a comment