迁移数据匿名化代码
Showing
17 changed files
with
6897 additions
and
41 deletions
| ... | @@ -3,6 +3,51 @@ | ... | @@ -3,6 +3,51 @@ |
| 3 | */ | 3 | */ |
| 4 | import request from "@/utils/request"; | 4 | import request from "@/utils/request"; |
| 5 | 5 | ||
| 6 | /** 获取标签列表。 */ | ||
| 7 | export const getDataLabelList = (params) => request({ | ||
| 8 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/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_DIGITAL_CONTRACT_URL}/label/update-state`, | ||
| 16 | method: 'put', | ||
| 17 | params | ||
| 18 | }) | ||
| 19 | |||
| 20 | export const saveLabel = (data) => request({ | ||
| 21 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/label/save`, | ||
| 22 | method: 'post', | ||
| 23 | data | ||
| 24 | }) | ||
| 25 | |||
| 26 | export const deleteLabel = (data) => request({ | ||
| 27 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/label/delete`, | ||
| 28 | method: 'delete', | ||
| 29 | data | ||
| 30 | }) | ||
| 31 | |||
| 32 | export const updateLabel = (data) => request({ | ||
| 33 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/label/update`, | ||
| 34 | method: 'put', | ||
| 35 | data | ||
| 36 | }) | ||
| 37 | |||
| 38 | /** 获取标签详情 */ | ||
| 39 | export const getLabelDetail = (guid) => request({ | ||
| 40 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/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 | |||
| 6 | /** 字段类型 */ | 51 | /** 字段类型 */ |
| 7 | export const fieldTypeList = [{ | 52 | export const fieldTypeList = [{ |
| 8 | value: '1', | 53 | value: '1', |
| ... | @@ -59,3 +104,240 @@ export const parseGeneralizeFileData = (data) => request({ | ... | @@ -59,3 +104,240 @@ export const parseGeneralizeFileData = (data) => request({ |
| 59 | method: 'post', | 104 | method: 'post', |
| 60 | data | 105 | data |
| 61 | }) | 106 | }) |
| 107 | |||
| 108 | |||
| 109 | /** --------- 敏感数据识别接口 ------------------- */ | ||
| 110 | |||
| 111 | /** 获取敏感数据识别任务列表 */ | ||
| 112 | export const getSensitiveDataTaskList = (params) => request({ | ||
| 113 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/page-list`, | ||
| 114 | method: 'post', | ||
| 115 | data: params | ||
| 116 | }) | ||
| 117 | |||
| 118 | /** 新增敏感数据识别任务 */ | ||
| 119 | export const saveSensitiveDataTask = (params) => request({ | ||
| 120 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/save`, | ||
| 121 | method: 'post', | ||
| 122 | data: params | ||
| 123 | }) | ||
| 124 | |||
| 125 | /** 编辑修改敏感数据识别任务 */ | ||
| 126 | export const updateSensitiveDataTask = (params) => request({ | ||
| 127 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/update`, | ||
| 128 | method: 'put', | ||
| 129 | data: params | ||
| 130 | }) | ||
| 131 | |||
| 132 | /** 删除敏感数据识别任务 */ | ||
| 133 | export const deleteSensitiveDataTask = (data) => request({ | ||
| 134 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/delete`, | ||
| 135 | method: 'delete', | ||
| 136 | data | ||
| 137 | }) | ||
| 138 | |||
| 139 | /** 手动执行敏感任务 */ | ||
| 140 | export const execSensitiveDataTask = (guid) => request({ | ||
| 141 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/exec-task?taskGuid=${guid}`, | ||
| 142 | method: 'get' | ||
| 143 | }) | ||
| 144 | |||
| 145 | /** 数据来源类型 */ | ||
| 146 | export const dataSourceTypeList = [{ | ||
| 147 | value: 1, | ||
| 148 | label: '数据库' | ||
| 149 | }, { | ||
| 150 | value: 2, | ||
| 151 | label: '文件导入' | ||
| 152 | }]; | ||
| 153 | |||
| 154 | /** 获取数据库选择列表 */ | ||
| 155 | export const getDatabase = (params) => request({ | ||
| 156 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/get-source-list`, | ||
| 157 | method: 'post', | ||
| 158 | data: params | ||
| 159 | }) | ||
| 160 | |||
| 161 | /** 获取敏感数据任务执行的数据表列表 */ | ||
| 162 | export const getExecSensitiveTable = (execGuid) => request({ | ||
| 163 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/get-exec-sensitive-table?execGuid=${execGuid}`, | ||
| 164 | method: 'get' | ||
| 165 | }) | ||
| 166 | |||
| 167 | /** 根据数据源或表获取敏感数据任务执行的字段列表 */ | ||
| 168 | export const getExecSensitiveFieldTable = (params) => request({ | ||
| 169 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/get-exec-sensitive-field`, | ||
| 170 | method: 'post', | ||
| 171 | data: params | ||
| 172 | }) | ||
| 173 | |||
| 174 | /** 获取当前数据表下的执行字段 */ | ||
| 175 | export const getExecSensitiveFieldColumnListByCondition = (params) => request({ | ||
| 176 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/column-list-by-condition`, | ||
| 177 | method: 'post', | ||
| 178 | data: params | ||
| 179 | }) | ||
| 180 | |||
| 181 | /** 获取敏感数据识别任务执行后的统计结果 */ | ||
| 182 | export const getStatisticsNum = (params) => request({ | ||
| 183 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/statistics-num`, | ||
| 184 | method: 'get', | ||
| 185 | params | ||
| 186 | }) | ||
| 187 | |||
| 188 | /** 修改敏感数据识别字段标签 */ | ||
| 189 | export const updateSensitiveDataTaskFieldLabel = (params) => request({ | ||
| 190 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/batch-update-label`, | ||
| 191 | method: 'put', | ||
| 192 | data: params | ||
| 193 | }) | ||
| 194 | |||
| 195 | /** 批量修改确认状态 */ | ||
| 196 | export const batchUpdateSensitiveDataTaskFieldStatus = (params) => request({ | ||
| 197 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/batch-change-status`, | ||
| 198 | method: 'post', | ||
| 199 | data: params | ||
| 200 | }) | ||
| 201 | |||
| 202 | /** 修改任务状态 */ | ||
| 203 | export const confirmTaskStatus = (guid) => request({ | ||
| 204 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/confirm-task?execGuid=${guid}`, | ||
| 205 | method: 'get' | ||
| 206 | }) | ||
| 207 | |||
| 208 | /** 获取敏感数据识别任务执行日志 */ | ||
| 209 | export const getSensitiveDataTaskExecLog = (params) => request({ | ||
| 210 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/get-exec-sensitive-exec-log`, | ||
| 211 | method: 'post', | ||
| 212 | data: params | ||
| 213 | }) | ||
| 214 | |||
| 215 | /** ---------- 匿名化处理 ------------------ */ | ||
| 216 | |||
| 217 | /** 获取匿名化任务列表 */ | ||
| 218 | export const getAnonTaskList = (params) => request({ | ||
| 219 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/page-list`, | ||
| 220 | method: 'post', | ||
| 221 | data: params | ||
| 222 | }) | ||
| 223 | |||
| 224 | /** 删除匿名化任务 */ | ||
| 225 | export const deleteAnonTask = (data) => request({ | ||
| 226 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/delete`, | ||
| 227 | method: 'delete', | ||
| 228 | data | ||
| 229 | }) | ||
| 230 | |||
| 231 | /** 保存匿名化任务 */ | ||
| 232 | export const saveAnonTask = (params) => request({ | ||
| 233 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/save`, | ||
| 234 | method: 'post', | ||
| 235 | data: params | ||
| 236 | }) | ||
| 237 | |||
| 238 | /** 更新匿名化任务 */ | ||
| 239 | export const updateAnonTask = (params) => request({ | ||
| 240 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/update`, | ||
| 241 | method: 'put', | ||
| 242 | data: params | ||
| 243 | }) | ||
| 244 | |||
| 245 | /** 获取匿名化任务详情 */ | ||
| 246 | export const getAnonTaskDetail = (guid) => request({ | ||
| 247 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/detail?guid=${guid}`, | ||
| 248 | method: 'get' | ||
| 249 | }) | ||
| 250 | |||
| 251 | /** 执行匿名化任务 */ | ||
| 252 | export const execAnonTask = (taskGuid) => request({ | ||
| 253 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/exec-task?taskGuid=${taskGuid}`, | ||
| 254 | method: 'post' | ||
| 255 | }) | ||
| 256 | |||
| 257 | /** 匿名化任务检验接口 */ | ||
| 258 | export const anonTaskCheck = (params) => request({ | ||
| 259 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/check`, | ||
| 260 | method: 'post', | ||
| 261 | data: params | ||
| 262 | }) | ||
| 263 | |||
| 264 | /** 获取匿名化任务分析结果数据 */ | ||
| 265 | export const getAnonAnalyzeResult = (execGuid) => request({ | ||
| 266 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/get-anon-analyze?taskExecGuid=${execGuid}`, | ||
| 267 | method: 'get' | ||
| 268 | }) | ||
| 269 | |||
| 270 | /** 获取匿名化任务分析结果数据 */ | ||
| 271 | export const getLastAnonAnalyzeResult = (execGuid) => request({ | ||
| 272 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/get-anon-analyze?isResult=true&taskExecGuid=${execGuid}`, | ||
| 273 | method: 'get' | ||
| 274 | }) | ||
| 275 | |||
| 276 | /** 获取匿名化任务分析结果统计分页数据 */ | ||
| 277 | export const getAnonAnalyzePageData = (params) => request({ | ||
| 278 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/page-anon-analyze-data`, | ||
| 279 | method: 'post', | ||
| 280 | data: params | ||
| 281 | }) | ||
| 282 | |||
| 283 | /** 获取匿名化任务结果数据 */ | ||
| 284 | export const getAnonPageData = (params) => request({ | ||
| 285 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/page-anon-data`, | ||
| 286 | method: 'post', | ||
| 287 | data: params | ||
| 288 | }) | ||
| 289 | |||
| 290 | /** 字段中文转英文 */ | ||
| 291 | export const chTransformEn =(params)=> request({ | ||
| 292 | url: `${import.meta.env.VITE_APP_COMMON_URL}/common/convert-field-ch-name`, | ||
| 293 | method: "post", | ||
| 294 | data: params, | ||
| 295 | }); | ||
| 296 | |||
| 297 | /** 根据选择的连接池获取表列表 */ | ||
| 298 | export const getDsTableByDs = (params) => request({ | ||
| 299 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/schema-table-page-list`, | ||
| 300 | method: 'post', | ||
| 301 | data: params | ||
| 302 | }) | ||
| 303 | |||
| 304 | /** 根据数据表获取表字段结构 */ | ||
| 305 | export const getDsTableFieldColumn = (params) => request({ | ||
| 306 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/table-column-list`, | ||
| 307 | method: 'post', | ||
| 308 | data: params | ||
| 309 | }); | ||
| 310 | |||
| 311 | /** 根据数据表获取表数据 */ | ||
| 312 | export const getDsTableSampleData = (params) => request({ | ||
| 313 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/table-data-preview-page`, | ||
| 314 | method: 'post', | ||
| 315 | data: params | ||
| 316 | }); | ||
| 317 | |||
| 318 | /** 根据字段名称获取敏感数据识别标签 */ | ||
| 319 | export const getLableByFieldName = (fieldName) => request({ | ||
| 320 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/sensitive-data-task/get-label-by-field-name?fieldName=${fieldName}`, | ||
| 321 | method: 'get' | ||
| 322 | }); | ||
| 323 | |||
| 324 | /** 验证样本数据 */ | ||
| 325 | export const validateAnonRule = (params) => request({ | ||
| 326 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/check`, | ||
| 327 | method: 'post', | ||
| 328 | data: params | ||
| 329 | }) | ||
| 330 | |||
| 331 | /** 导出匿名化结果数据 */ | ||
| 332 | export const exportAnonExecData = (params) => request({ | ||
| 333 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/export-anon-data?taskGuid=${params.taskGuid}&taskExecGuid=${params.execGuid}`, | ||
| 334 | method: 'get', | ||
| 335 | responseType: 'blob' | ||
| 336 | }) | ||
| 337 | |||
| 338 | /** 下载匿名化评估报告 */ | ||
| 339 | export const exportAnonReport = (params) => request({ | ||
| 340 | url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/download-report?taskGuid=${params.taskGuid}&taskExecGuid=${params.execGuid}`, | ||
| 341 | method: 'post', | ||
| 342 | responseType: 'blob' | ||
| 343 | }) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
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 | path: 'sensitive-identify-config', | ||
| 90 | name: 'sensitiveIdentifyConfig', | ||
| 91 | component: () => import('@/views/data_anonymization/sensitiveIdentifyConfig.vue'), | ||
| 92 | meta: { | ||
| 93 | title: '敏感数据识别查看', | ||
| 94 | sidebar: false, | ||
| 95 | breadcrumb: false, | ||
| 96 | cache: true, | ||
| 97 | reuse: true, | ||
| 98 | editPage: false, | ||
| 99 | activeMenu: '/data-anonymization/sensitive-identify' | ||
| 100 | }, | ||
| 101 | beforeEnter: (to, from) => { | ||
| 102 | if (to.query.taskName) { | ||
| 103 | to.meta.title = `敏感数据${to.query.isLook == '1' ? '日志查看' : '查看'}-${to.query.taskName}`; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | }, | ||
| 107 | { | ||
| 108 | path: 'sensitive-identify-task-exec-log', | ||
| 109 | name: 'sensitiveIdentifyTaskExecLog', | ||
| 110 | component: () => import('@/views/data_anonymization/sensitiveIdentifyTaskExecLog.vue'), | ||
| 111 | meta: { | ||
| 112 | title: '执行日志', | ||
| 113 | sidebar: false, | ||
| 114 | breadcrumb: false, | ||
| 115 | cache: true, | ||
| 116 | reuse: true | ||
| 117 | }, | ||
| 118 | beforeEnter: (to, from) => { | ||
| 119 | if (to.query.guid) { | ||
| 120 | to.meta.title = `日志-${to.query.name}`; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | ], | ||
| 125 | }, | ||
| 126 | { | ||
| 127 | path: '/data-anonymization/result-process', | ||
| 128 | component: Layout, | ||
| 129 | meta: { | ||
| 130 | title: '匿名化处理', | ||
| 131 | icon: 'sidebar-videos', | ||
| 132 | }, | ||
| 133 | children: [ | ||
| 134 | { | ||
| 135 | path: '', | ||
| 136 | name: 'resultProcess', | ||
| 137 | component: () => import('@/views/data_anonymization/resultProcess.vue'), | ||
| 138 | meta: { | ||
| 139 | title: '匿名化处理', | ||
| 140 | sidebar: false, | ||
| 141 | breadcrumb: false, | ||
| 142 | cache: true | ||
| 143 | }, | ||
| 144 | }, | ||
| 145 | { | ||
| 146 | path: 'anon-task-create', | ||
| 147 | name: 'anonTaskCreate', | ||
| 148 | component: () => import('@/views/data_anonymization/anonTaskCreate.vue'), | ||
| 149 | meta: { | ||
| 150 | title: '匿名化处理任务', | ||
| 151 | sidebar: false, | ||
| 152 | breadcrumb: false, | ||
| 153 | cache: true, | ||
| 154 | reuse: true, | ||
| 155 | editPage: true, | ||
| 156 | activeMenu: '/data-anonymization/result-process' | ||
| 157 | }, | ||
| 158 | beforeEnter: (to, from) => { | ||
| 159 | if (to.query.taskName) { | ||
| 160 | to.meta.title = `编辑-${to.query.taskName}`; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | }, | ||
| 164 | { | ||
| 165 | path: 'anonResultView', | ||
| 166 | name: 'anonResultView', | ||
| 167 | component: () => import('@/views/data_anonymization/anonResultView.vue'), | ||
| 168 | meta: { | ||
| 169 | title: '查看数据', | ||
| 170 | sidebar: false, | ||
| 171 | breadcrumb: false, | ||
| 172 | cache: true, | ||
| 173 | reuse: true | ||
| 174 | }, | ||
| 175 | beforeEnter: (to, from) => { | ||
| 176 | if (to.query.guid) { | ||
| 177 | to.meta.title = `查看数据-${to.query.taskName}`; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | }, | ||
| 181 | { | ||
| 182 | path: 'anonResultReportView', | ||
| 183 | name: 'anonResultReportView', | ||
| 184 | component: () => import('@/views/data_anonymization/anonResultReportView.vue'), | ||
| 185 | meta: { | ||
| 186 | title: '查看报告', | ||
| 187 | sidebar: false, | ||
| 188 | breadcrumb: false, | ||
| 189 | cache: true, | ||
| 190 | reuse: true | ||
| 191 | }, | ||
| 192 | beforeEnter: (to, from) => { | ||
| 193 | if (to.query.guid) { | ||
| 194 | to.meta.title = `查看报告-${to.query.taskName}`; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | }, | ||
| 198 | ], | ||
| 199 | }, | ||
| 200 | ] | ||
| 201 | |||
| 202 | export default routes |
| ... | @@ -216,47 +216,7 @@ const routes: RouteRecordRaw[] = [ | ... | @@ -216,47 +216,7 @@ const routes: RouteRecordRaw[] = [ |
| 216 | editPage: true | 216 | editPage: true |
| 217 | }, | 217 | }, |
| 218 | }] | 218 | }] |
| 219 | }, | ||
| 220 | { | ||
| 221 | path: '/data-smart-contract-common/generalize-file', | ||
| 222 | component: Layout, | ||
| 223 | meta: { | ||
| 224 | title: '泛化文件管理', | ||
| 225 | icon: 'sidebar-videos', | ||
| 226 | }, | ||
| 227 | children: [ | ||
| 228 | { | ||
| 229 | path: '', | ||
| 230 | name: 'generalizeFile', | ||
| 231 | component: () => import('@/views/data_smart_contract/generalizeFile.vue'), | ||
| 232 | meta: { | ||
| 233 | title: '', | ||
| 234 | sidebar: false, | ||
| 235 | breadcrumb: false, | ||
| 236 | cache: true | ||
| 237 | }, | ||
| 238 | }, | ||
| 239 | { | ||
| 240 | path: 'generalize-file-edit', | ||
| 241 | name: 'generalizeFileEdit', | ||
| 242 | component: () => import('@/views/data_smart_contract/generalizeFileEdit.vue'), | ||
| 243 | meta: { | ||
| 244 | title: '新建泛化文件', | ||
| 245 | sidebar: false, | ||
| 246 | breadcrumb: false, | ||
| 247 | cache: true, | ||
| 248 | reuse: true, | ||
| 249 | editPage: true, | ||
| 250 | activeMenu: '/data-smart-contract-common/generalize-file' | ||
| 251 | }, | ||
| 252 | beforeEnter: (to, from) => { | ||
| 253 | if (to.query.fileName) { | ||
| 254 | to.meta.title = `编辑-${to.query.fileName}`; | ||
| 255 | } | 219 | } |
| 256 | } | ||
| 257 | }, | ||
| 258 | ], | ||
| 259 | }, | ||
| 260 | ] | 220 | ] |
| 261 | 221 | ||
| 262 | export default routes | 222 | export default routes |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -7,6 +7,7 @@ import DataSmartContract from './modules/dataSmartContract'; | ... | @@ -7,6 +7,7 @@ import DataSmartContract from './modules/dataSmartContract'; |
| 7 | import DataFacilitator from './modules/dataFacilitator'; | 7 | import DataFacilitator from './modules/dataFacilitator'; |
| 8 | import HomeIndex from './modules/homeIndex'; | 8 | import HomeIndex from './modules/homeIndex'; |
| 9 | import DataDelivery from './modules/dataDelivery'; | 9 | import DataDelivery from './modules/dataDelivery'; |
| 10 | import DataAnonymization from './modules/dataAnonymization'; | ||
| 10 | 11 | ||
| 11 | import useSettingsStore from '@/store/modules/settings' | 12 | import useSettingsStore from '@/store/modules/settings' |
| 12 | 13 | ||
| ... | @@ -99,6 +100,7 @@ const asyncRoutes: RouteRecordRaw[] = [ | ... | @@ -99,6 +100,7 @@ const asyncRoutes: RouteRecordRaw[] = [ |
| 99 | ...DataFacilitator, | 100 | ...DataFacilitator, |
| 100 | ...HomeIndex, | 101 | ...HomeIndex, |
| 101 | ...DataDelivery, | 102 | ...DataDelivery, |
| 103 | ...DataAnonymization, | ||
| 102 | // ...DataAssetRegistry, | 104 | // ...DataAssetRegistry, |
| 103 | ] | 105 | ] |
| 104 | 106 | ... | ... |
| 1 | <route lang="yaml"> | ||
| 2 | name: anonResultReportView | ||
| 3 | </route> | ||
| 4 | |||
| 5 | <script lang="ts" setup name="anonResultReportView"> | ||
| 6 | import { | ||
| 7 | exportAnonReport, | ||
| 8 | getAnonAnalyzePageData, | ||
| 9 | getAnonAnalyzeResult, | ||
| 10 | getAnonTaskDetail, | ||
| 11 | } from '@/api/modules/dataAnonymization'; | ||
| 12 | import { changeNum, download } from '@/utils/common'; | ||
| 13 | import { ElMessage } from 'element-plus'; | ||
| 14 | import anonResultAnalysis from './components/anonResultAnalysis.vue'; | ||
| 15 | import { commonPageConfig } from '@/utils/enum'; | ||
| 16 | import { calcColumnWidth } from '@/utils'; | ||
| 17 | |||
| 18 | const route = useRoute(); | ||
| 19 | const router = useRouter(); | ||
| 20 | const fullPath = route.fullPath; | ||
| 21 | const taskGuid = ref(route.query.guid); | ||
| 22 | const { proxy } = getCurrentInstance() as any; | ||
| 23 | const resultDataLoading = ref(false); | ||
| 24 | |||
| 25 | const downPromise: any = ref() | ||
| 26 | |||
| 27 | /** 提交保存和编辑后的执行guid */ | ||
| 28 | const taskExecGuid = ref(route.query.execGuid); | ||
| 29 | |||
| 30 | /** 记录原始的值信息,防止上一步之后未修改数据时不调用接口 */ | ||
| 31 | const oldAnonTaskValueInfo: any = ref({}); | ||
| 32 | /** 执行结果信息 */ | ||
| 33 | const analysisResultInfo: any = ref({}); | ||
| 34 | |||
| 35 | const containerRef = ref(); | ||
| 36 | |||
| 37 | const containerWidth = ref(containerRef.value?.offsetWidth || 0) | ||
| 38 | |||
| 39 | /** ------------------------- 匿名化分析结果页面数据展示 ---------------- */ | ||
| 40 | const pageInfo: any = ref({ | ||
| 41 | ...commonPageConfig, | ||
| 42 | }) | ||
| 43 | |||
| 44 | const pageChange = (info) => { | ||
| 45 | pageInfo.value.curr = Number(info.curr); | ||
| 46 | pageInfo.value.limit = Number(info.limit); | ||
| 47 | getAnalysisResultPageData(); | ||
| 48 | } | ||
| 49 | |||
| 50 | /** 每列字段对应的列宽计算结果。 */ | ||
| 51 | const originResultTableFieldColumn = ref({}); | ||
| 52 | |||
| 53 | /** 结果分析中的字段表格数据 */ | ||
| 54 | const resultData: any = ref([]); | ||
| 55 | |||
| 56 | /** 结果分析中的字段信息 */ | ||
| 57 | const analysisResultTableFields: any = ref([]); | ||
| 58 | |||
| 59 | const analysisResultLoading = ref(false); | ||
| 60 | |||
| 61 | /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */ | ||
| 62 | const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => { | ||
| 63 | let d: any[] = []; | ||
| 64 | data.forEach((dt) => d.push(dt[prop])); | ||
| 65 | //样式使用默认值。 | ||
| 66 | return calcColumnWidth( | ||
| 67 | d, | ||
| 68 | title, | ||
| 69 | { | ||
| 70 | fontSize: 14, | ||
| 71 | fontFamily: "SimSun", | ||
| 72 | }, | ||
| 73 | { | ||
| 74 | fontSize: 14, | ||
| 75 | fontFamily: "SimSun", | ||
| 76 | }, | ||
| 77 | otherWidth | ||
| 78 | ); | ||
| 79 | }; | ||
| 80 | |||
| 81 | watch( | ||
| 82 | resultData, | ||
| 83 | (val: any[], oldVal) => { | ||
| 84 | if (!analysisResultTableFields.value?.length) { | ||
| 85 | originResultTableFieldColumn.value = {}; | ||
| 86 | return; | ||
| 87 | } | ||
| 88 | originResultTableFieldColumn.value = {}; | ||
| 89 | analysisResultTableFields.value.forEach((field, index) => { | ||
| 90 | originResultTableFieldColumn.value[field.enName] = calcTableColumnWidth( | ||
| 91 | val?.slice(0, 20) || [], | ||
| 92 | field.enName, | ||
| 93 | field.chName, | ||
| 94 | 24 | ||
| 95 | ); | ||
| 96 | }); | ||
| 97 | }, | ||
| 98 | { | ||
| 99 | deep: true, | ||
| 100 | } | ||
| 101 | ); | ||
| 102 | |||
| 103 | const getAnalysisResultPageData = () => { | ||
| 104 | analysisResultLoading.value = true; | ||
| 105 | getAnonAnalyzePageData({ | ||
| 106 | pageIndex: pageInfo.value.curr, | ||
| 107 | pageSize: pageInfo.value.limit, | ||
| 108 | taskExecGuid: taskExecGuid.value, | ||
| 109 | }).then((res: any) => { | ||
| 110 | analysisResultLoading.value = false; | ||
| 111 | if (res?.code == proxy.$passCode) { | ||
| 112 | pageInfo.value.rows = | ||
| 113 | resultData.value = []; | ||
| 114 | res.data?.records?.forEach(d => { | ||
| 115 | let obj = {}; | ||
| 116 | analysisResultTableFields.value.forEach(t => { | ||
| 117 | obj[t.enName] = d.fieldValue?.[t.enName]; | ||
| 118 | }); | ||
| 119 | obj['equivalenceClassNum'] = changeNum(d.equivalenceClassNum || 0, 0); | ||
| 120 | obj['reIdentifyRisk'] = changeNum(d.reIdentifyRisk || 0, 2); | ||
| 121 | obj['isGtThreshold'] = d.isGtThreshold; | ||
| 122 | resultData.value.push(obj); | ||
| 123 | }); | ||
| 124 | pageInfo.value.rows = res.data?.totalRows ?? 0; | ||
| 125 | } else { | ||
| 126 | proxy.$ElMessage.error(res.msg); | ||
| 127 | } | ||
| 128 | }) | ||
| 129 | } | ||
| 130 | |||
| 131 | /** 下载评估报告 */ | ||
| 132 | const transfer = () => { | ||
| 133 | if (downPromise.value) { | ||
| 134 | return; | ||
| 135 | } | ||
| 136 | downPromise.value = exportAnonReport({ | ||
| 137 | taskGuid: route.query.guid, | ||
| 138 | execGuid: taskExecGuid.value | ||
| 139 | }).then((res: any) => { | ||
| 140 | downPromise.value = null; | ||
| 141 | if (res && !res.msg) { | ||
| 142 | download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化评估报告.docx', 'word') | ||
| 143 | } else { | ||
| 144 | res?.msg && ElMessage.error(res?.msg); | ||
| 145 | } | ||
| 146 | }).catch(() => { | ||
| 147 | downPromise.value = null; | ||
| 148 | }) | ||
| 149 | } | ||
| 150 | |||
| 151 | onMounted(() => { | ||
| 152 | nextTick(() => { | ||
| 153 | containerWidth.value = containerRef.value?.offsetWidth || 0; | ||
| 154 | }) | ||
| 155 | window.onresize = () => { | ||
| 156 | containerWidth.value = containerRef.value?.offsetWidth || 0; | ||
| 157 | } | ||
| 158 | }) | ||
| 159 | |||
| 160 | onBeforeMount(() => { | ||
| 161 | resultDataLoading.value = true; | ||
| 162 | getAnonAnalyzeResult(taskExecGuid.value).then((res: any) => { | ||
| 163 | resultDataLoading.value = false; | ||
| 164 | if (res?.code == proxy.$passCode) { | ||
| 165 | analysisResultInfo.value = res.data || {}; | ||
| 166 | analysisResultTableFields.value = res.data?.column || []; | ||
| 167 | pageInfo.value.curr = 1; | ||
| 168 | getAnalysisResultPageData(); | ||
| 169 | } else { | ||
| 170 | res?.msg && proxy.$ElMessage.error(res.msg); | ||
| 171 | } | ||
| 172 | }); | ||
| 173 | getAnonTaskDetail(taskGuid.value).then((res: any) => { | ||
| 174 | if (res?.code == proxy.$passCode) { | ||
| 175 | oldAnonTaskValueInfo.value = res.data || {}; | ||
| 176 | } else { | ||
| 177 | res?.msg && proxy.$ElMessage.error(res.msg); | ||
| 178 | } | ||
| 179 | }); | ||
| 180 | }) | ||
| 181 | |||
| 182 | </script> | ||
| 183 | |||
| 184 | <template> | ||
| 185 | <div class="table_tool_wrap" v-loading="resultDataLoading" ref="containerRef"> | ||
| 186 | <el-button style="margin-bottom: 8px;" type="primary" @click="transfer" v-preReClick>下载评估报告</el-button> | ||
| 187 | <anonResultAnalysis :show-title="true" :analysis-result-info="analysisResultInfo" | ||
| 188 | :analysis-result-loading="analysisResultLoading" :analysis-result-table-fields="analysisResultTableFields" | ||
| 189 | :old-anon-task-value-info="oldAnonTaskValueInfo" :container-width="containerWidth" | ||
| 190 | :origin-result-table-field-column="originResultTableFieldColumn" :page-info="pageInfo" :result-data="resultData" | ||
| 191 | @page-change="pageChange"></anonResultAnalysis> | ||
| 192 | </div> | ||
| 193 | </template> | ||
| 194 | |||
| 195 | <style lang="scss" scoped> | ||
| 196 | .table_tool_wrap { | ||
| 197 | width: 100%; | ||
| 198 | height: 100%; | ||
| 199 | padding: 8px 16px 16px; | ||
| 200 | overflow-y: auto; | ||
| 201 | } | ||
| 202 | </style> |
| 1 | <route lang="yaml"> | ||
| 2 | name: anonResultView | ||
| 3 | </route> | ||
| 4 | |||
| 5 | <script lang="ts" setup name="anonResultView"> | ||
| 6 | import { ref } from "vue"; | ||
| 7 | import { | ||
| 8 | getAnonPageData, | ||
| 9 | getLastAnonAnalyzeResult, | ||
| 10 | exportAnonExecData, | ||
| 11 | } from "@/api/modules/dataAnonymization"; | ||
| 12 | import { calcColumnWidth } from "@/utils/index"; | ||
| 13 | import Moment from 'moment'; | ||
| 14 | import { TableColumnWidth } from "@/utils/enum"; | ||
| 15 | import { ElMessage } from "element-plus"; | ||
| 16 | import { commonPageConfig } from '@/components/PageNav/index'; | ||
| 17 | import { download } from "@/utils/common"; | ||
| 18 | |||
| 19 | const { proxy } = getCurrentInstance() as any; | ||
| 20 | |||
| 21 | const route = useRoute(); | ||
| 22 | |||
| 23 | const props = defineProps({ | ||
| 24 | isPage: { | ||
| 25 | default: true, | ||
| 26 | type: Boolean | ||
| 27 | }, | ||
| 28 | execGuid: { | ||
| 29 | default: '', | ||
| 30 | type: String | ||
| 31 | } | ||
| 32 | }); | ||
| 33 | |||
| 34 | const tableData: any = ref([]); | ||
| 35 | const tableDataLoading = ref(false); | ||
| 36 | |||
| 37 | const tableFields: any = ref([]); | ||
| 38 | |||
| 39 | const pageInfo: any = ref({ | ||
| 40 | ...commonPageConfig, | ||
| 41 | rows: 0, | ||
| 42 | }) | ||
| 43 | |||
| 44 | const getData = () => { | ||
| 45 | tableData.value = []; | ||
| 46 | if (!tableFields.value?.length) { | ||
| 47 | return; | ||
| 48 | } | ||
| 49 | tableDataLoading.value = true; | ||
| 50 | getAnonPageData({ | ||
| 51 | pageIndex: pageInfo.value.curr, | ||
| 52 | pageSize: pageInfo.value.limit, | ||
| 53 | taskExecGuid: props.isPage ? route.query.execGuid : props.execGuid, | ||
| 54 | }).then((res: any) => { | ||
| 55 | tableDataLoading.value = false; | ||
| 56 | if (res.code == proxy.$passCode) { | ||
| 57 | tableData.value = []; | ||
| 58 | res.data?.records?.forEach(d => { | ||
| 59 | let obj = {}; | ||
| 60 | tableFields.value.forEach(t => { | ||
| 61 | obj[t.enName] = d.fieldValue?.[t.enName]; | ||
| 62 | }); | ||
| 63 | tableData.value.push(obj); | ||
| 64 | }); | ||
| 65 | pageInfo.value.rows = res.data?.totalRows ?? 0; | ||
| 66 | } else { | ||
| 67 | ElMessage.error(res.msg); | ||
| 68 | } | ||
| 69 | }); | ||
| 70 | } | ||
| 71 | |||
| 72 | const getTextAlign = (field) => { | ||
| 73 | if (field.dataType === 'decimal' || field.dataType === 'int' || field.dataType == 'bit' || field.dataType == 'tinyint') { | ||
| 74 | return 'right'; | ||
| 75 | } | ||
| 76 | return 'left' | ||
| 77 | } | ||
| 78 | |||
| 79 | /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */ | ||
| 80 | const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => { | ||
| 81 | let d: any[] = []; | ||
| 82 | data.forEach((dt) => d.push(dt[prop])); | ||
| 83 | return calcColumnWidth( | ||
| 84 | d, | ||
| 85 | title, | ||
| 86 | { | ||
| 87 | fontSize: 14, | ||
| 88 | fontFamily: "SimSun", | ||
| 89 | }, | ||
| 90 | { | ||
| 91 | fontSize: 14, | ||
| 92 | fontFamily: "SimSun", | ||
| 93 | }, | ||
| 94 | otherWidth | ||
| 95 | ); | ||
| 96 | }; | ||
| 97 | |||
| 98 | /** 每列字段对应的列宽计算结果。 */ | ||
| 99 | const originTableFieldColumn = ref({}); | ||
| 100 | |||
| 101 | watch( | ||
| 102 | tableData, | ||
| 103 | (val: any[], oldVal) => { | ||
| 104 | if (!tableFields.value?.length) { | ||
| 105 | originTableFieldColumn.value = {}; | ||
| 106 | return; | ||
| 107 | } | ||
| 108 | originTableFieldColumn.value = {}; | ||
| 109 | tableFields.value.forEach((field, index) => { | ||
| 110 | originTableFieldColumn.value[field.enName] = calcTableColumnWidth( | ||
| 111 | val?.slice(0, 20) || [], | ||
| 112 | field.enName, | ||
| 113 | field.chName, | ||
| 114 | 24 | ||
| 115 | ); | ||
| 116 | }); | ||
| 117 | }, | ||
| 118 | { | ||
| 119 | deep: true, | ||
| 120 | } | ||
| 121 | ); | ||
| 122 | |||
| 123 | watch(() => props.execGuid, (val) => { | ||
| 124 | if (!val) { | ||
| 125 | return; | ||
| 126 | } | ||
| 127 | tableDataLoading.value = true; | ||
| 128 | getLastAnonAnalyzeResult(val).then((res: any) => { | ||
| 129 | tableDataLoading.value = false; | ||
| 130 | if (res.code == proxy.$passCode) { | ||
| 131 | let column = res.data?.column || {}; | ||
| 132 | tableFields.value = column; | ||
| 133 | pageInfo.value.curr = 1; | ||
| 134 | getData(); | ||
| 135 | } else { | ||
| 136 | ElMessage.error(res.msg); | ||
| 137 | } | ||
| 138 | }); | ||
| 139 | }, { | ||
| 140 | immediate: true | ||
| 141 | }) | ||
| 142 | |||
| 143 | onBeforeMount(() => { | ||
| 144 | if (!props.isPage) { | ||
| 145 | return; | ||
| 146 | } | ||
| 147 | tableDataLoading.value = true; | ||
| 148 | getLastAnonAnalyzeResult(route.query.execGuid).then((res: any) => { | ||
| 149 | tableDataLoading.value = false; | ||
| 150 | if (res.code == proxy.$passCode) { | ||
| 151 | let column = res.data?.column || {}; | ||
| 152 | tableFields.value = column; | ||
| 153 | getData(); | ||
| 154 | } else { | ||
| 155 | ElMessage.error(res.msg); | ||
| 156 | } | ||
| 157 | }); | ||
| 158 | }); | ||
| 159 | |||
| 160 | const formatterPreviewDate = (row, info) => { | ||
| 161 | let enName = info.enName; | ||
| 162 | let v = row[enName]; | ||
| 163 | if (v === 0) { | ||
| 164 | return v; | ||
| 165 | } | ||
| 166 | if (!v || v == 'null') { | ||
| 167 | return '--'; | ||
| 168 | } | ||
| 169 | if (info.dataType === 'datetime') { | ||
| 170 | return Moment(v).format('YYYY-MM-DD HH:mm:ss'); | ||
| 171 | } | ||
| 172 | if (info.dataType === 'date') { | ||
| 173 | if (isNaN(<any>(new Date(v)))) { | ||
| 174 | return Moment(parseInt(v)).format('YYYY-MM-DD'); | ||
| 175 | } else { | ||
| 176 | return Moment(v).format('YYYY-MM-DD'); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | return v; | ||
| 180 | }; | ||
| 181 | |||
| 182 | const pageChange = (info) => { | ||
| 183 | pageInfo.value.curr = Number(info.curr); | ||
| 184 | pageInfo.value.limit = Number(info.limit); | ||
| 185 | getData(); | ||
| 186 | } | ||
| 187 | |||
| 188 | const promise: any = ref(null); | ||
| 189 | |||
| 190 | const exportData = () => { | ||
| 191 | if (promise.value) { | ||
| 192 | return; | ||
| 193 | } | ||
| 194 | promise.value = exportAnonExecData({ | ||
| 195 | taskGuid: route.query.guid, | ||
| 196 | execGuid: route.query.execGuid | ||
| 197 | }).then((res: any) => { | ||
| 198 | promise.value = null; | ||
| 199 | if (res && !res.msg) { | ||
| 200 | download(res, route.query.taskName + '_匿名化数据.xlsx', 'excel') | ||
| 201 | } else { | ||
| 202 | res?.msg && ElMessage.error(res?.msg); | ||
| 203 | } | ||
| 204 | }).catch(() => { | ||
| 205 | promise.value = null; | ||
| 206 | }) | ||
| 207 | } | ||
| 208 | |||
| 209 | </script> | ||
| 210 | |||
| 211 | <template> | ||
| 212 | <div class="table_tool_wrap" v-loading="tableDataLoading"> | ||
| 213 | <el-button v-show="props.isPage" style="margin-bottom: 8px;" type="primary" @click="exportData" | ||
| 214 | v-preReClick>导出数据</el-button> | ||
| 215 | <el-table ref="tableRef" v-show="tableFields.length" :data="tableData" :highlight-current-row="true" stripe border | ||
| 216 | tooltip-effect="light" height="100%" row-key="guid" :style="{ width: '100%', height: !props.isPage ? 'calc(100% - 34px)' : 'calc(100% - 64px)' }"> | ||
| 217 | <template v-for="(item, index) in (tableFields || [])"> | ||
| 218 | <el-table-column :label="item.chName" :width="item.dataType === 'datetime' | ||
| 219 | ? TableColumnWidth.DATETIME | ||
| 220 | : item.dataType === 'date' | ||
| 221 | ? TableColumnWidth.DATE | ||
| 222 | : originTableFieldColumn[item.enName] | ||
| 223 | " :align="getTextAlign(item)" :header-align="getTextAlign(item)" | ||
| 224 | :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true"> | ||
| 225 | </el-table-column> | ||
| 226 | </template> | ||
| 227 | </el-table> | ||
| 228 | <div v-show="!tableFields.length" class="empty-content"> | ||
| 229 | <img src="../../assets/images/empty-data.png" :style="{ width: '168px', height: '96px' }" /> | ||
| 230 | <div class="empty-text">暂无数据</div> | ||
| 231 | </div> | ||
| 232 | <PageNav :class="[pageInfo.type]" :pageInfo="pageInfo" @pageChange="pageChange" /> | ||
| 233 | </div> | ||
| 234 | </template> | ||
| 235 | |||
| 236 | <style lang="scss" scoped> | ||
| 237 | .table_tool_wrap { | ||
| 238 | width: 100%; | ||
| 239 | height: 100%; | ||
| 240 | padding: 8px 16px 16px; | ||
| 241 | |||
| 242 | .tips_text { | ||
| 243 | font-size: 14px; | ||
| 244 | color: var(--el-text-color-tip); | ||
| 245 | display: block; | ||
| 246 | font-weight: normal; | ||
| 247 | margin-bottom: 8px; | ||
| 248 | line-height: 21px; | ||
| 249 | } | ||
| 250 | |||
| 251 | .el-table { | ||
| 252 | display: inline-block; | ||
| 253 | } | ||
| 254 | |||
| 255 | .empty-content { | ||
| 256 | display: flex; | ||
| 257 | align-items: center; | ||
| 258 | justify-content: center; | ||
| 259 | height: 100%; | ||
| 260 | width: 100%; | ||
| 261 | flex-direction: column; | ||
| 262 | |||
| 263 | .empty-text { | ||
| 264 | font-size: 14px; | ||
| 265 | color: #b2b2b2; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | <route lang="yaml"> | ||
| 2 | name: anonTaskCreate | ||
| 3 | </route> | ||
| 4 | |||
| 5 | <template> | ||
| 6 | <div class="container_wrap full" v-loading="fullscreenLoading" ref="containerRef"> | ||
| 7 | <div class="content_main"> | ||
| 8 | <!-- 顶部步骤条 --> | ||
| 9 | <div class="top_tool_wrap"> | ||
| 10 | <StepBar :steps-info="stepsInfo" :style="{ width: stepsInfo.list.length == 2 ? '30%' : '60%' }" /> | ||
| 11 | </div> | ||
| 12 | <!-- 第一步 数据输入 --> | ||
| 13 | <div class="operator_panel_wrap" v-show="step == 0"> | ||
| 14 | <ContentWrap id="id-baseInfo" title="数据选择" description="" style="margin-top: 8px;"> | ||
| 15 | <!-- 数据选择相关属性表单设置 --> | ||
| 16 | <Form ref="formRef" :itemList="dataSelectInfoItems" :rules="dataSelectInfoFormRules" | ||
| 17 | formId="model-select-edit" col="col3 custom-form" @select-change="handleDataSelectFormSelectChange" | ||
| 18 | @uploadFileChange="uploadFileChange" @checkboxChange="handleDataSelectFormCheckboxChange" /> | ||
| 19 | </ContentWrap> | ||
| 20 | <ContentWrap id="id-previewData" title="数据抽样预览" description="" style="margin-top: 16px;"> | ||
| 21 | <!-- 选择抽样预览的表单设置 --> | ||
| 22 | <Form ref="dataSimpleFormRef" :itemList="dataSimpleFormItems" :rules="dataSimpleFormRules" | ||
| 23 | formId="data-simple-edit" col="col3 fixwidth-form" @switch-change="handleDataSimpleFormSwitchChange" | ||
| 24 | @input-change="handleDataSimpleFormChange" /> | ||
| 25 | <!-- 抽样预览的数据表格设置 --> | ||
| 26 | <div class="table-v2-main" v-show="dataSimpleFormRef?.formInline?.enableSamplingRate == 'Y'" | ||
| 27 | v-loading="sampleTableDataLoading"> | ||
| 28 | <el-table ref="tableRef" v-show="sampleTableFields.length" :data="sampleTableData" | ||
| 29 | :highlight-current-row="true" stripe border tooltip-effect="light" height="100%" row-key="guid" | ||
| 30 | :style="{ width: '100%', height: '240px' }"> | ||
| 31 | <el-table-column label="序号" type="index" width="56px" align="center" | ||
| 32 | show-overflow-tooltip></el-table-column> | ||
| 33 | <template v-for="(item, index) in (sampleTableFields || [])"> | ||
| 34 | <el-table-column :label="item.chName" :width="item.dataType === 'datetime' | ||
| 35 | ? TableColumnWidth.DATETIME | ||
| 36 | : item.dataType === 'date' | ||
| 37 | ? TableColumnWidth.DATE | ||
| 38 | : originTableFieldColumn[item.enName] | ||
| 39 | " :align="getTextAlign(item)" :header-align="getTextAlign(item)" | ||
| 40 | :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true"> | ||
| 41 | </el-table-column> | ||
| 42 | </template> | ||
| 43 | </el-table> | ||
| 44 | <div v-show="!sampleTableFields.length" class="main-placeholder"> | ||
| 45 | <img src="../../assets/images/no-data.png" :style="{ width: '96px', height: '96px' }" /> | ||
| 46 | <div class="empty-text">暂无抽样数据</div> | ||
| 47 | </div> | ||
| 48 | </div> | ||
| 49 | </ContentWrap> | ||
| 50 | </div> | ||
| 51 | <!-- 第二步 配置匿名化方案,单独抽取vue组件页面 --> | ||
| 52 | <anonTaskStepTwo ref="anonTaskStepTwoRef" v-show="step == 1" :anonTaskRules="detailInfo.anonTaskRules" | ||
| 53 | :isFile="formRef?.formInline?.file?.length > 0" :anonPrivacyMode="detailInfo.anonPrivacyMode" | ||
| 54 | :fieldTypeList="fieldTypeList" :fieldNameList="sampleTableFields"> | ||
| 55 | </anonTaskStepTwo> | ||
| 56 | <!-- 第三步 结果分析 --> | ||
| 57 | <div class="operator_panel_wrap" v-show="step == 2"> | ||
| 58 | <ContentWrap class="anlysis-content-wrap" id="analysis-result" title="匿名结果分析" description="" | ||
| 59 | style="margin-top: 8px;"> | ||
| 60 | <div class="wait-result-div" v-show="!isExecEnd"> | ||
| 61 | <img class="loading-img" src="../../assets/images/loading.gif" /> | ||
| 62 | <div class="desc">正在进行匿名化处理,请稍候...</div> | ||
| 63 | <el-button :icon="RefreshRight" link @click="refreshQueryData" v-preReClick>刷新查看结果</el-button> | ||
| 64 | </div> | ||
| 65 | <div class="wait-result-div" v-show="isExecEnd && analysisResultInfo.status == 'E'"> | ||
| 66 | <el-icon class="failed"> | ||
| 67 | <CircleCloseFilled /> | ||
| 68 | </el-icon> | ||
| 69 | <div class="error-desc">{{ '执行失败,请返回上一步修改配置或联系管理员' }}</div> | ||
| 70 | <div v-show="analysisResultInfo.errorMsg" class="error-desc">{{ '【' + analysisResultInfo.errorMsg + '】' }} | ||
| 71 | </div> | ||
| 72 | </div> | ||
| 73 | <anonResultAnalysis v-show="isExecEnd && analysisResultInfo.status == 'Y'" :analysis-result-info="analysisResultInfo" | ||
| 74 | :analysis-result-loading="analysisResultLoading" :analysis-result-table-fields="analysisResultTableFields" :old-anon-task-value-info="oldAnonTaskValueInfo" | ||
| 75 | :container-width="containerWidth" :origin-result-table-field-column="originResultTableFieldColumn" :page-info="pageInfo" | ||
| 76 | :result-data="resultData" @page-change="pageChange"></anonResultAnalysis> | ||
| 77 | <template #header> | ||
| 78 | <el-button v-show="isExecEnd && analysisResultInfo.status == 'Y'" type="primary" v-loading="!!downPromise" | ||
| 79 | @click="transfer">下载评估报告</el-button> | ||
| 80 | </template> | ||
| 81 | </ContentWrap> | ||
| 82 | </div> | ||
| 83 | <!-- 匿名化结果展示 --> | ||
| 84 | <div class="operator_panel_wrap step-result" v-show="step == 3" style="height: calc(100% - 88px);"> | ||
| 85 | <ContentWrap id="analysis-result" title="匿名化数据结果" description="" style="margin-top: 8px;height: 100%;"> | ||
| 86 | <!-- 匿名化结果数据查看页面,单独抽取业务组件,新开页面要使用 --> | ||
| 87 | <anonResultView :is-page="false" | ||
| 88 | :execGuid="analysisResultInfo.status == 'Y' && step == 3 ? taskExecGuid : ''"> | ||
| 89 | </anonResultView> | ||
| 90 | </ContentWrap> | ||
| 91 | </div> | ||
| 92 | </div> | ||
| 93 | <!-- 底部按钮,需要根据当前步骤条来展示对应的按钮 --> | ||
| 94 | <div class="bottom_tool_wrap"> | ||
| 95 | <template v-if="step == 0"> | ||
| 96 | <el-button @click="cancelTask">取消</el-button> | ||
| 97 | <el-button type="primary" @click="changeStep(formRef?.formInline?.handleType == '02' ? 3 : 2)">下一步</el-button> | ||
| 98 | </template> | ||
| 99 | <template v-else-if="step == 1"> | ||
| 100 | <el-button @click="changeStep(1)">上一步</el-button> | ||
| 101 | <el-button type="primary" @click="changeStep(3)">下一步</el-button> | ||
| 102 | </template> | ||
| 103 | <template v-else-if="step == 2"> | ||
| 104 | <el-button @click="changeStep(formRef?.formInline?.handleType == '02' ? 1 : 2)">上一步</el-button> | ||
| 105 | <el-button v-show="formRef?.formInline?.handleType != '02'" type="primary" | ||
| 106 | :disabled="analysisResultInfo.status == 'R' || (isExecEnd && analysisResultInfo.status == 'E')" | ||
| 107 | @click="changeStep(4)">下一步</el-button> | ||
| 108 | <el-button type="primary" v-show="formRef?.formInline?.handleType == '02'" :disabled="analysisResultInfo.status == 'R' || (isExecEnd && analysisResultInfo.status == 'E')" v-preReClick @click="closeTask">关闭</el-button> | ||
| 109 | </template> | ||
| 110 | <template v-else> | ||
| 111 | <el-button @click="changeStep(3)">上一步</el-button> | ||
| 112 | <el-button type="primary" v-preReClick @click="exportResult">导出</el-button> | ||
| 113 | </template> | ||
| 114 | </div> | ||
| 115 | </div> | ||
| 116 | </template> | ||
| 117 | |||
| 118 | <script lang="ts" setup name="anonTaskCreate"> | ||
| 119 | import { | ||
| 120 | dataSourceTypeList, | ||
| 121 | getAnonTaskDetail, | ||
| 122 | getParamsList, | ||
| 123 | chTransformEn, | ||
| 124 | getAnonAnalyzeResult, | ||
| 125 | getAnonAnalyzePageData, | ||
| 126 | getDatabase, | ||
| 127 | getDsTableByDs, | ||
| 128 | getDsTableFieldColumn, | ||
| 129 | getDsTableSampleData, | ||
| 130 | saveAnonTask, | ||
| 131 | updateAnonTask, | ||
| 132 | exportAnonExecData, | ||
| 133 | exportAnonReport, | ||
| 134 | } from '@/api/modules/dataAnonymization'; | ||
| 135 | import { | ||
| 136 | parseAndDecodeUrl, | ||
| 137 | getDownFileSignByUrl, | ||
| 138 | obsDownloadRequest | ||
| 139 | } from "@/api/modules/obsService"; | ||
| 140 | import { | ||
| 141 | getAreaData | ||
| 142 | } from "@/api/modules/queryService"; | ||
| 143 | import useUserStore from "@/store/modules/user"; | ||
| 144 | import { useValidator } from '@/hooks/useValidator'; | ||
| 145 | import { TableColumnWidth } from '@/utils/enum'; | ||
| 146 | import { calcColumnWidth } from "@/utils/index"; | ||
| 147 | import Moment from 'moment'; | ||
| 148 | import anonTaskStepTwo from './anonTaskStepTwo.vue'; | ||
| 149 | import * as XLSX from 'xlsx'; | ||
| 150 | import { ElMessage } from 'element-plus'; | ||
| 151 | import { isEqual, cloneDeep } from "lodash-es"; | ||
| 152 | import { changeNum, download } from "@/utils/common"; | ||
| 153 | import anonResultView from './anonResultView.vue'; | ||
| 154 | import useDataAnonymizationStore from "@/store/modules/dataAnonymization"; | ||
| 155 | import { RefreshRight, CircleCloseFilled, Right } from "@element-plus/icons-vue"; | ||
| 156 | import { commonPageConfig } from '@/components/PageNav'; | ||
| 157 | import { QuestionFilled } from "@element-plus/icons-vue"; | ||
| 158 | import anonResultAnalysis from './components/anonResultAnalysis.vue'; | ||
| 159 | |||
| 160 | const anonymizationStore = useDataAnonymizationStore(); | ||
| 161 | const { proxy } = getCurrentInstance() as any; | ||
| 162 | const userStore = useUserStore(); | ||
| 163 | const route = useRoute(); | ||
| 164 | const router = useRouter(); | ||
| 165 | const fullPath = route.fullPath; | ||
| 166 | const taskGuid = ref(route.query.guid); | ||
| 167 | /** 提交保存和编辑后的执行guid */ | ||
| 168 | const taskExecGuid = ref(''); | ||
| 169 | /** 是否执行结束,用于第四步获取执行结果 */ | ||
| 170 | const isExecEnd = ref(false); | ||
| 171 | const { required } = useValidator(); | ||
| 172 | const fullscreenLoading = ref(false); | ||
| 173 | const containerRef = ref(); | ||
| 174 | |||
| 175 | const containerWidth = ref(containerRef.value?.offsetWidth || 0) | ||
| 176 | |||
| 177 | const step = ref(0); | ||
| 178 | const originStepsInfo = ref({ | ||
| 179 | step: step.value, | ||
| 180 | list: [ | ||
| 181 | { title: '数据输入', value: 1 }, | ||
| 182 | { title: '配置匿名化方案', value: 2 }, | ||
| 183 | { title: '匿名结果分析', value: 3 }, | ||
| 184 | { title: '结果输出', value: 4 } | ||
| 185 | ] | ||
| 186 | }) | ||
| 187 | const stepsInfo = ref(originStepsInfo.value); | ||
| 188 | |||
| 189 | /** 已匿名化出具报告的 */ | ||
| 190 | const reportStepsInfo = ref({ | ||
| 191 | step: step.value, | ||
| 192 | list: [ | ||
| 193 | { title: '数据输入', value: 1 }, | ||
| 194 | // { title: '配置匿名化方案', value: 2 }, | ||
| 195 | { title: '匿名结果分析', value: 2 }, | ||
| 196 | // { title: '结果输出', value: 4 } | ||
| 197 | ] | ||
| 198 | }) | ||
| 199 | |||
| 200 | /** 数据源列表 */ | ||
| 201 | const dataSourceList: any = ref([]); | ||
| 202 | |||
| 203 | /** 数据源对应的数据表 */ | ||
| 204 | const dsTableList: any = ref([]); | ||
| 205 | |||
| 206 | /** 数据共享类型字段列表 */ | ||
| 207 | const dataSharingTypeList = ref([]); | ||
| 208 | |||
| 209 | /** 匿名化处理类型 */ | ||
| 210 | const handleTypeList = ref([]); | ||
| 211 | |||
| 212 | const getParentAreaPromise: any = ref(null); | ||
| 213 | const getAreaDataPromise: any = ref({}); | ||
| 214 | const getAreaDatas: any = ref({}); | ||
| 215 | const parentAreaData: any = ref([]); | ||
| 216 | |||
| 217 | const getArea = (node, resolve) => { | ||
| 218 | const { level } = node | ||
| 219 | let params = { | ||
| 220 | parentGuid: node.value | ||
| 221 | } | ||
| 222 | if (!node.value) { | ||
| 223 | if (getParentAreaPromise.value) { | ||
| 224 | getParentAreaPromise.value.then((res: any) => { | ||
| 225 | resolve(res); | ||
| 226 | }) | ||
| 227 | } else { | ||
| 228 | resolve(parentAreaData.value); | ||
| 229 | } | ||
| 230 | return; | ||
| 231 | } | ||
| 232 | if (node.loaded) { | ||
| 233 | resolve([]); | ||
| 234 | return; | ||
| 235 | } | ||
| 236 | if (getAreaDatas.value[node.value]?.length) { | ||
| 237 | resolve(getAreaDatas.value[node.value]); | ||
| 238 | return; | ||
| 239 | } | ||
| 240 | if (!getAreaDataPromise.value[node.value]) { | ||
| 241 | getAreaDataPromise.value[node.value] = getAreaData(params).then((res: any) => { | ||
| 242 | node.loaded = true; | ||
| 243 | getAreaDataPromise.value[node.value] = null; | ||
| 244 | if (res?.code == proxy.$passCode) { | ||
| 245 | const data = res.data ?? [] | ||
| 246 | data.map(item => { | ||
| 247 | item.leaf = level >= 1 | ||
| 248 | }) | ||
| 249 | resolve(data) | ||
| 250 | getAreaDatas.value[node.value] = data; | ||
| 251 | return data; | ||
| 252 | } | ||
| 253 | }) | ||
| 254 | } else { | ||
| 255 | getAreaDataPromise.value[node.value].then((data) => { | ||
| 256 | getAreaDataPromise.value[node.value] = null; | ||
| 257 | node.loaded = true; | ||
| 258 | data.map(item => { | ||
| 259 | item.leaf = level >= 1 | ||
| 260 | }) | ||
| 261 | resolve(data) | ||
| 262 | }) | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | const formRef = ref(); | ||
| 267 | |||
| 268 | /** 数据选择的表单配置信息 */ | ||
| 269 | const dataSelectInfoItems = ref([{ | ||
| 270 | label: '数据集名称', | ||
| 271 | type: 'input', | ||
| 272 | placeholder: '请输入', | ||
| 273 | field: 'taskName', | ||
| 274 | maxlength: 15, | ||
| 275 | default: '', | ||
| 276 | required: true, | ||
| 277 | filterable: true, | ||
| 278 | clearable: true, | ||
| 279 | visible: true, | ||
| 280 | }, { | ||
| 281 | label: '数据共享类型', | ||
| 282 | type: 'select', | ||
| 283 | placeholder: '请选择', | ||
| 284 | field: 'dataSharingTypeCode', | ||
| 285 | default: '01', | ||
| 286 | options: dataSharingTypeList.value, | ||
| 287 | props: { | ||
| 288 | label: "label", | ||
| 289 | value: "value", | ||
| 290 | }, | ||
| 291 | required: true, | ||
| 292 | filterable: true, | ||
| 293 | clearable: true, | ||
| 294 | visible: true, | ||
| 295 | }, | ||
| 296 | { | ||
| 297 | label: '所属地域', | ||
| 298 | type: "cascader", | ||
| 299 | placeholder: "请选择", | ||
| 300 | field: "coverageArea", | ||
| 301 | default: [], | ||
| 302 | showAllLevels: true, | ||
| 303 | props: { | ||
| 304 | label: 'name', | ||
| 305 | value: 'guid', | ||
| 306 | lazy: true, | ||
| 307 | checkStrictly: true, | ||
| 308 | lazyLoad: getArea, | ||
| 309 | multiple: false, | ||
| 310 | }, | ||
| 311 | collapse: true, | ||
| 312 | tagsTooltip: true, | ||
| 313 | // filterable: true, | ||
| 314 | clearable: true, | ||
| 315 | required: false, //不选默认表示全国。 | ||
| 316 | //col: 'checkbox-right', | ||
| 317 | visible: true | ||
| 318 | }, | ||
| 319 | // { 去掉,直接用数据集总行数/全国人口总数计算 | ||
| 320 | // label: '患者占总人口比', | ||
| 321 | // type: 'input', | ||
| 322 | // placeholder: '数值,支持小数点9位', | ||
| 323 | // field: 'patientPopulationRate', | ||
| 324 | // maxlength: 11, | ||
| 325 | // min: 0, | ||
| 326 | // max: 1, | ||
| 327 | // inputType: 'scoreNumber', | ||
| 328 | // decimalCnt: 9, | ||
| 329 | // default: '', | ||
| 330 | // required: false, | ||
| 331 | // filterable: true, | ||
| 332 | // clearable: true, | ||
| 333 | // visible: true, | ||
| 334 | // }, | ||
| 335 | { | ||
| 336 | label: '处理类型', | ||
| 337 | type: 'select', | ||
| 338 | placeholder: '请选择', | ||
| 339 | field: 'handleType', | ||
| 340 | default: '01', | ||
| 341 | options: handleTypeList.value, | ||
| 342 | props: { | ||
| 343 | label: "label", | ||
| 344 | value: "value", | ||
| 345 | }, | ||
| 346 | required: true, | ||
| 347 | filterable: true, | ||
| 348 | clearable: true, | ||
| 349 | visible: true, | ||
| 350 | }, | ||
| 351 | { | ||
| 352 | label: '数据来源', | ||
| 353 | type: 'select', | ||
| 354 | placeholder: '请选择', | ||
| 355 | field: 'dataSource', | ||
| 356 | default: 1, | ||
| 357 | options: dataSourceTypeList, | ||
| 358 | props: { | ||
| 359 | label: "label", | ||
| 360 | value: "value", | ||
| 361 | }, | ||
| 362 | required: true, | ||
| 363 | filterable: true, | ||
| 364 | visible: true, | ||
| 365 | }, { | ||
| 366 | label: '数据源', | ||
| 367 | type: 'select', | ||
| 368 | placeholder: '请选择', | ||
| 369 | field: 'dataSourceGuid', | ||
| 370 | default: '', | ||
| 371 | options: dataSourceList.value, | ||
| 372 | props: { | ||
| 373 | label: 'databaseNameZh', | ||
| 374 | value: 'guid' | ||
| 375 | }, | ||
| 376 | filterable: true, | ||
| 377 | visible: true, | ||
| 378 | required: true | ||
| 379 | }, { | ||
| 380 | label: "数据表", | ||
| 381 | type: "select", | ||
| 382 | placeholder: "请选择", | ||
| 383 | field: "tableName", | ||
| 384 | options: dsTableList.value, | ||
| 385 | props: { | ||
| 386 | label: 'tableComment', | ||
| 387 | value: 'tableName' | ||
| 388 | }, | ||
| 389 | default: '', | ||
| 390 | filterable: true, | ||
| 391 | clearable: true, | ||
| 392 | required: true, | ||
| 393 | }, { | ||
| 394 | label: "准标识符", | ||
| 395 | type: "select", | ||
| 396 | placeholder: "请选择", | ||
| 397 | field: "qualifiedIdentifier", | ||
| 398 | options: dsTableList.value, | ||
| 399 | props: { | ||
| 400 | label: 'chName', | ||
| 401 | value: 'enName' | ||
| 402 | }, | ||
| 403 | default: [], | ||
| 404 | multiple: true, | ||
| 405 | collapse: true, | ||
| 406 | tagsTooltip: true, | ||
| 407 | filterable: true, | ||
| 408 | clearable: true, | ||
| 409 | required: true, | ||
| 410 | visible: false, | ||
| 411 | }, { | ||
| 412 | label: '文件上传', | ||
| 413 | tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB', | ||
| 414 | type: 'upload-file', | ||
| 415 | accept: '.xlsx, .xls, .csv', | ||
| 416 | limitSize: 10, | ||
| 417 | limit: 1, | ||
| 418 | isExcel: true, | ||
| 419 | required: true, | ||
| 420 | default: <any>[], | ||
| 421 | block: false, | ||
| 422 | col: 'wid60', | ||
| 423 | visible: false, | ||
| 424 | field: 'file', | ||
| 425 | },]); | ||
| 426 | |||
| 427 | const dataSelectInfoFormRules = ref({ | ||
| 428 | taskName: [required('请输入数据集名称')], | ||
| 429 | dataSharingTypeCode: [required('请选择数据共享类型')], | ||
| 430 | // patientPopulationRate: [required('请输入患者占总人口比')], | ||
| 431 | dataSourceGuid: [required('请选择数据源')], | ||
| 432 | handleType: [required('请选择处理类型')], | ||
| 433 | tableName: [required('请选择数据表')], | ||
| 434 | qualifiedIdentifier: [{ type: 'array', required: true, trigger: 'change', message: "请选择准标识符" }], | ||
| 435 | file: [{ | ||
| 436 | validator: (rule: any, value: any, callback: any) => { | ||
| 437 | if (!value?.length) { | ||
| 438 | callback(new Error('请上传文件')) | ||
| 439 | } else { | ||
| 440 | callback(); | ||
| 441 | } | ||
| 442 | }, trigger: 'change' | ||
| 443 | }] | ||
| 444 | }); | ||
| 445 | |||
| 446 | /** 最新选中的 */ | ||
| 447 | const currDatasourceSelect: any = ref({}); | ||
| 448 | |||
| 449 | const handleDataSelectFormSelectChange = async (val, row, formInfo) => { | ||
| 450 | if (row.field == 'dataSource') { | ||
| 451 | dataSelectInfoItems.value[5].visible = val == 1; | ||
| 452 | dataSelectInfoItems.value[6].visible = val == 1; | ||
| 453 | dataSelectInfoItems.value[8].visible = val == 2; | ||
| 454 | setDataSelectFormItems(Object.assign({}, formInfo, { file: !formInfo['file'] ? [] : formInfo['file'] })) | ||
| 455 | sampleTableFields.value = []; | ||
| 456 | parseFileDataSum.value = []; | ||
| 457 | sampleTableData.value = []; | ||
| 458 | } else if (row.field == 'dataSourceGuid') { | ||
| 459 | if (!val) { | ||
| 460 | currDatasourceSelect.value = []; | ||
| 461 | sampleTableFields.value = []; | ||
| 462 | parseFileDataSum.value = []; | ||
| 463 | sampleTableData.value = []; | ||
| 464 | setDataSelectFormItems(Object.assign({}, formInfo, { file: !formInfo['file'] ? [] : formInfo['file'], tableName: '', qualifiedIdentifier: [] })) | ||
| 465 | let item = dataSelectInfoItems.value.find(d => d.field == 'tableName'); | ||
| 466 | item && (item.options = dsTableList.value); | ||
| 467 | return; | ||
| 468 | } | ||
| 469 | let dsInfo = currDatasourceSelect.value = dataSourceList.value.find(d => d.guid == val); | ||
| 470 | //清除数据表得值,重新获取下拉列表 | ||
| 471 | const res: any = await getDsTableByDs({ | ||
| 472 | pageSize: -1, | ||
| 473 | pageIndex: 1, | ||
| 474 | dataSourceGuid: val, | ||
| 475 | database: dsInfo.databaseNameEn, | ||
| 476 | databaseType: dsInfo.databaseType, | ||
| 477 | tableName: '', | ||
| 478 | hadFlag: false | ||
| 479 | }); | ||
| 480 | if (res.code == proxy.$passCode) { | ||
| 481 | dsTableList.value = res.data?.records || []; | ||
| 482 | setDataSelectFormItems(Object.assign({}, formInfo, { file: !formInfo['file'] ? [] : formInfo['file'], tableName: '', qualifiedIdentifier: [] })) | ||
| 483 | let item = dataSelectInfoItems.value.find(d => d.field == 'tableName'); | ||
| 484 | item && (item.options = dsTableList.value); | ||
| 485 | } else { | ||
| 486 | proxy.$ElMessage.error(res.msg); | ||
| 487 | } | ||
| 488 | sampleTableFields.value = []; | ||
| 489 | parseFileDataSum.value = []; | ||
| 490 | sampleTableData.value = []; | ||
| 491 | } else if (row.field == 'tableName') { | ||
| 492 | if (!val) { | ||
| 493 | sampleTableFields.value = []; | ||
| 494 | sampleTableData.value = []; | ||
| 495 | return; | ||
| 496 | } | ||
| 497 | getDsTableFieldColumn({ | ||
| 498 | pageSize: 50, | ||
| 499 | pageIndex: 1, | ||
| 500 | dataSourceGuid: currDatasourceSelect.value.guid, | ||
| 501 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 502 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 503 | tableName: val, | ||
| 504 | }).then((res: any) => { | ||
| 505 | if (res.code == proxy.$passCode) { | ||
| 506 | sampleTableFields.value = res.data?.map(d => { | ||
| 507 | d.fieldDataType = d.dataType; | ||
| 508 | d.enName = d.columnName; | ||
| 509 | d.chName = d.columnZhName; | ||
| 510 | return d; | ||
| 511 | }) || []; | ||
| 512 | /** 判断有抽样数据,需要查询接口 */ | ||
| 513 | getSampleDataByDsTable(); | ||
| 514 | } else { | ||
| 515 | ElMessage.error(res.msg); | ||
| 516 | } | ||
| 517 | }); | ||
| 518 | } else if (row.field == 'handleType') { | ||
| 519 | setDataSelectFormItems(formInfo); | ||
| 520 | } | ||
| 521 | } | ||
| 522 | |||
| 523 | const setDataSelectFormItems = (info, isDetail = false) => { | ||
| 524 | dataSelectInfoItems.value.forEach(item => { | ||
| 525 | item.default = info[item.field]; | ||
| 526 | if (item.field == 'coverageArea') { | ||
| 527 | // item && item.children?.length && (item.children[0].visible = info['coverageArea'] != 'all'); | ||
| 528 | if (!isDetail) { | ||
| 529 | return; | ||
| 530 | } | ||
| 531 | let coverageArea = info.coverageArea; | ||
| 532 | if (coverageArea && Array.isArray(coverageArea) && coverageArea.length > 0) { | ||
| 533 | item.default = coverageArea[0] as any; | ||
| 534 | let p: any = []; | ||
| 535 | coverageArea?.forEach(area => { | ||
| 536 | if (p.includes(area[0])) { | ||
| 537 | return; | ||
| 538 | } | ||
| 539 | p.push(area[0]); | ||
| 540 | getArea({ value: area[0], level: 1 }, () => { }) | ||
| 541 | }); | ||
| 542 | let ps: any = [] | ||
| 543 | for (const key in getAreaDataPromise.value) { | ||
| 544 | ps.push(getAreaDataPromise.value[key]) | ||
| 545 | } | ||
| 546 | Promise.all(ps).then(() => { | ||
| 547 | item.default = coverageArea[0]; | ||
| 548 | }); | ||
| 549 | } else { | ||
| 550 | item.default = ''; | ||
| 551 | } | ||
| 552 | } else if (item.field == 'qualifiedIdentifier') { | ||
| 553 | item.visible = info['handleType'] == '02'; | ||
| 554 | } | ||
| 555 | }); | ||
| 556 | stepsInfo.value = info.handleType == '02' ? reportStepsInfo.value : originStepsInfo.value; | ||
| 557 | } | ||
| 558 | |||
| 559 | const handleDataSelectFormCheckboxChange = (val, info, row) => { | ||
| 560 | row.field == 'coverageArea' && setDataSelectFormItems(info); | ||
| 561 | } | ||
| 562 | |||
| 563 | const dataSimpleFormRef = ref(); | ||
| 564 | |||
| 565 | /** 抽样数据预览 */ | ||
| 566 | const dataSimpleFormItems = ref([{ | ||
| 567 | label: '抽样开关', | ||
| 568 | type: 'switch', | ||
| 569 | field: 'enableSamplingRate', | ||
| 570 | default: 'N', | ||
| 571 | col: 'autoWidth', | ||
| 572 | activeValue: 'Y', | ||
| 573 | inactiveValue: 'N' | ||
| 574 | }, { | ||
| 575 | label: '抽样比例(%)', | ||
| 576 | type: 'input', | ||
| 577 | placeholder: '请输入', | ||
| 578 | field: 'samplingRate', | ||
| 579 | maxlength: 3, | ||
| 580 | min: 0, //可以是0条。万一只是想看下字段呢 | ||
| 581 | max: 100, | ||
| 582 | inputType: 'integerNumber', | ||
| 583 | default: 10, | ||
| 584 | required: true, | ||
| 585 | filterable: true, | ||
| 586 | clearable: true, | ||
| 587 | visible: false, | ||
| 588 | }]); | ||
| 589 | |||
| 590 | const dataSimpleFormRules = ref({ | ||
| 591 | samplingRate: [required('请填写抽样比例')], | ||
| 592 | }); | ||
| 593 | |||
| 594 | const oldSamplingRate = ref('10'); | ||
| 595 | |||
| 596 | const handleDataSimpleFormSwitchChange = (val, info) => { | ||
| 597 | if (val == 'N') { | ||
| 598 | oldSamplingRate.value = info.samplingRate; | ||
| 599 | } else { | ||
| 600 | dataSimpleFormItems.value[1].default = oldSamplingRate.value || 10; | ||
| 601 | } | ||
| 602 | dataSimpleFormItems.value[1].visible = val == 'Y'; | ||
| 603 | dataSimpleFormItems.value[0].default = info.enableSamplingRate || 'N'; | ||
| 604 | if (formRef.value?.formInline?.file?.length) { | ||
| 605 | transferSampleData(); | ||
| 606 | } else { | ||
| 607 | getSampleDataByDsTable(); | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | /** 输入抽样比例值改变 */ | ||
| 612 | const handleDataSimpleFormChange = (val) => { | ||
| 613 | if (formRef.value?.formInline?.file?.length) { | ||
| 614 | transferSampleData(); | ||
| 615 | } else { | ||
| 616 | getSampleDataByDsTable(); | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | /** 样本表格加载中 */ | ||
| 621 | const sampleTableDataLoading = ref(false); | ||
| 622 | |||
| 623 | /** 样本表格的数据 */ | ||
| 624 | const sampleTableData: any = ref([]); | ||
| 625 | |||
| 626 | /** 样本表格的字段 */ | ||
| 627 | const sampleTableFields: any = ref([]); | ||
| 628 | |||
| 629 | /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */ | ||
| 630 | const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => { | ||
| 631 | let d: any[] = []; | ||
| 632 | data.forEach((dt) => d.push(dt[prop])); | ||
| 633 | return calcColumnWidth( | ||
| 634 | d, | ||
| 635 | title, | ||
| 636 | { | ||
| 637 | fontSize: 14, | ||
| 638 | fontFamily: "SimSun", | ||
| 639 | }, | ||
| 640 | { | ||
| 641 | fontSize: 14, | ||
| 642 | fontFamily: "SimSun", | ||
| 643 | }, | ||
| 644 | otherWidth | ||
| 645 | ); | ||
| 646 | }; | ||
| 647 | |||
| 648 | /** 每列字段对应的列宽计算结果。 */ | ||
| 649 | const originTableFieldColumn = ref({}); | ||
| 650 | |||
| 651 | const getTextAlign = (field) => { | ||
| 652 | if (field.dataType === 'decimal' || field.dataType === 'int') { | ||
| 653 | return 'right'; | ||
| 654 | } | ||
| 655 | return 'left' | ||
| 656 | } | ||
| 657 | |||
| 658 | watch( | ||
| 659 | sampleTableData, | ||
| 660 | (val: any[], oldVal) => { | ||
| 661 | if (!sampleTableFields.value?.length) { | ||
| 662 | originTableFieldColumn.value = {}; | ||
| 663 | return; | ||
| 664 | } | ||
| 665 | originTableFieldColumn.value = {}; | ||
| 666 | sampleTableFields.value.forEach((field, index) => { | ||
| 667 | originTableFieldColumn.value[field.enName] = calcTableColumnWidth( | ||
| 668 | val?.slice(0, 20) || [], | ||
| 669 | field.enName, | ||
| 670 | field.chName, | ||
| 671 | 24 | ||
| 672 | ); | ||
| 673 | }); | ||
| 674 | }, | ||
| 675 | { | ||
| 676 | deep: true, | ||
| 677 | } | ||
| 678 | ); | ||
| 679 | |||
| 680 | watch(() => sampleTableFields.value, (val) => { | ||
| 681 | let item = dataSelectInfoItems.value.find(selectItem => selectItem.field == 'qualifiedIdentifier'); | ||
| 682 | item && (item.options = val); | ||
| 683 | let formInfo = formRef.value.formInline; | ||
| 684 | if (formInfo.handleType == '02' && !(taskGuid.value && (formInfo.file?.[0] && formInfo.file?.[0]?.url == detailInfo.value.filePath?.url || (formInfo.tableName && formInfo.tableName == detailInfo.value.tableName)))) {//需要同步清除准标识符的字段选择 | ||
| 685 | setDataSelectFormItems(Object.assign({}, formInfo, { file: !formInfo['file'] ? [] : formInfo['file'], qualifiedIdentifier: [] })) | ||
| 686 | } | ||
| 687 | }, { | ||
| 688 | deep: true | ||
| 689 | }) | ||
| 690 | |||
| 691 | const formatterPreviewDate = (row, info) => { | ||
| 692 | let enName = info.enName; | ||
| 693 | let v = row[enName]; | ||
| 694 | if (v === 0) { | ||
| 695 | return v; | ||
| 696 | } | ||
| 697 | if (!v || v == 'null') { | ||
| 698 | return '--'; | ||
| 699 | } | ||
| 700 | if (info.dataType === 'datetime') { | ||
| 701 | return Moment(v).format('YYYY-MM-DD HH:mm:ss'); | ||
| 702 | } | ||
| 703 | if (info.dataType === 'date') { | ||
| 704 | if (isNaN(<any>(new Date(v)))) { | ||
| 705 | return Moment(parseInt(v)).format('YYYY-MM-DD'); | ||
| 706 | } else { | ||
| 707 | return Moment(v).format('YYYY-MM-DD'); | ||
| 708 | } | ||
| 709 | } | ||
| 710 | return v; | ||
| 711 | }; | ||
| 712 | |||
| 713 | /** 解析的总的表格数据,方便后面修改抽样比例时使用 */ | ||
| 714 | const parseFileDataSum: any = ref([]); | ||
| 715 | const currentSheet: any = ref(); | ||
| 716 | const parseFileData = (fileRaw) => { | ||
| 717 | sampleTableDataLoading.value = true; | ||
| 718 | fileRaw.arrayBuffer().then(async (f) => { | ||
| 719 | const wb = XLSX.read(f, { | ||
| 720 | raw: false, cellDates: true | ||
| 721 | }); | ||
| 722 | const sheet = wb.Sheets[wb.SheetNames[0]]; | ||
| 723 | currentSheet.value = sheet; | ||
| 724 | const json: any[] = XLSX.utils.sheet_to_json(sheet, { header: 1 }); | ||
| 725 | if (json.length == 0) { | ||
| 726 | sampleTableFields.value = []; | ||
| 727 | sampleTableData.value = []; | ||
| 728 | } else { | ||
| 729 | try { | ||
| 730 | const res: any = await chTransformEn(json[0]); | ||
| 731 | if (res?.code != proxy.$passCode) { | ||
| 732 | sampleTableDataLoading.value = false; | ||
| 733 | proxy.$ElMessage.error(res.msg); | ||
| 734 | return; | ||
| 735 | } | ||
| 736 | let fields = res.data || []; | ||
| 737 | sampleTableFields.value = fields?.map((j, index) => { | ||
| 738 | return { | ||
| 739 | index: index, | ||
| 740 | enName: j.enName + '', | ||
| 741 | chName: j.chName + '', | ||
| 742 | dataType: 'varchar' | ||
| 743 | } | ||
| 744 | }) || []; | ||
| 745 | parseFileDataSum.value = json; | ||
| 746 | /** 粗略算出字段类型 */ | ||
| 747 | json.slice(1, 10).forEach((info, row) => { | ||
| 748 | json[0].forEach((name, col) => { | ||
| 749 | if (info[col] === "" || info[col] == null || sampleTableFields.value[col].dataType != 'varchar') { | ||
| 750 | return; | ||
| 751 | } else { | ||
| 752 | var cellRef = XLSX.utils.encode_cell({ r: row + 1, c: col }); | ||
| 753 | var cell = sheet[cellRef]; | ||
| 754 | let v = cell.w || info[col]; | ||
| 755 | let isNum = cell.t == 'n'; | ||
| 756 | if (isNum) { | ||
| 757 | if (v.includes('.') && sampleTableFields.value[col].dataType != 'decimal') { | ||
| 758 | sampleTableFields.value[col].dataType = 'decimal'; | ||
| 759 | } else { | ||
| 760 | sampleTableFields.value[col].dataType = 'int'; | ||
| 761 | } | ||
| 762 | } | ||
| 763 | } | ||
| 764 | }); | ||
| 765 | }) | ||
| 766 | transferSampleData(); | ||
| 767 | sampleTableDataLoading.value = false; | ||
| 768 | } catch (error) { | ||
| 769 | sampleTableDataLoading.value = false; | ||
| 770 | } | ||
| 771 | } | ||
| 772 | }); | ||
| 773 | } | ||
| 774 | |||
| 775 | /** 获取文件解析后根据抽样比例得出的表格数据,默认查看前500条数据 */ | ||
| 776 | const transferSampleData = () => { | ||
| 777 | let samplingRate = dataSimpleFormRef.value?.formInline?.samplingRate; | ||
| 778 | if (parseFileDataSum.value.length > 1 && samplingRate) { | ||
| 779 | let totalCnt = parseFileDataSum.value.length - 1; | ||
| 780 | let cnt = Math.ceil(samplingRate * 0.01 * totalCnt) + 1; | ||
| 781 | sampleTableData.value = parseFileDataSum.value.slice(1, cnt > 500 ? 501 : cnt).map((info, row) => { | ||
| 782 | let object = {}; | ||
| 783 | parseFileDataSum.value[0].forEach((chName, col) => { | ||
| 784 | let name = sampleTableFields.value[col].enName; | ||
| 785 | var cellRef = XLSX.utils.encode_cell({ r: row + 1, c: col }); | ||
| 786 | var cell = currentSheet.value[cellRef]; | ||
| 787 | let v = cell.w || info[col]; | ||
| 788 | object[name] = v; | ||
| 789 | }); | ||
| 790 | return object; | ||
| 791 | }); | ||
| 792 | } else { | ||
| 793 | sampleTableData.value = []; | ||
| 794 | } | ||
| 795 | } | ||
| 796 | |||
| 797 | /** 获取选择的数据库表根据抽样比例得出的表格数据 */ | ||
| 798 | const getSampleDataByDsTable = () => { | ||
| 799 | const tableName = formRef.value?.formInline?.tableName; | ||
| 800 | if (!currDatasourceSelect.value.guid || !tableName) { | ||
| 801 | sampleTableFields.value = []; | ||
| 802 | sampleTableData.value = []; | ||
| 803 | return; | ||
| 804 | } | ||
| 805 | let samplingRate = dataSimpleFormRef.value?.formInline?.samplingRate; | ||
| 806 | if (!samplingRate) { | ||
| 807 | sampleTableData.value = []; | ||
| 808 | return; | ||
| 809 | } | ||
| 810 | let totalCnt = dsTableList.value.find(t => t.tableName == tableName)?.tableRows || 0; | ||
| 811 | let cnt = Math.ceil(samplingRate * 0.01 * totalCnt); | ||
| 812 | if (!cnt) { | ||
| 813 | sampleTableData.value = []; | ||
| 814 | return; | ||
| 815 | } | ||
| 816 | sampleTableDataLoading.value = true; | ||
| 817 | getDsTableSampleData({ | ||
| 818 | limitNum: cnt > 500 ? 500 : cnt, | ||
| 819 | pageSize: cnt > 500 ? 500 : cnt, | ||
| 820 | pageIndex: 1, | ||
| 821 | dataSourceGuid: currDatasourceSelect.value.guid, | ||
| 822 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 823 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 824 | tableName: tableName, | ||
| 825 | hadFlag: false, | ||
| 826 | }).then((res: any) => { | ||
| 827 | sampleTableDataLoading.value = false; | ||
| 828 | if (res.code == proxy.$passCode) { | ||
| 829 | sampleTableData.value = res.data?.datas || []; | ||
| 830 | } else { | ||
| 831 | sampleTableData.value = []; | ||
| 832 | ElMessage.error(res.msg); | ||
| 833 | } | ||
| 834 | }); | ||
| 835 | } | ||
| 836 | |||
| 837 | const uploadFileChange = (file) => { | ||
| 838 | sampleTableData.value = []; | ||
| 839 | if (!file.length) { | ||
| 840 | sampleTableFields.value = []; | ||
| 841 | sampleTableData.value = []; | ||
| 842 | return; | ||
| 843 | } | ||
| 844 | let fileRaw = file[0].file; | ||
| 845 | parseFileData(fileRaw); | ||
| 846 | } | ||
| 847 | |||
| 848 | /** 第二步的配置组件引用。 */ | ||
| 849 | const anonTaskStepTwoRef = ref(); | ||
| 850 | |||
| 851 | const changeStep = async (val) => { | ||
| 852 | if (val <= step.value) { | ||
| 853 | step.value = val - 1; | ||
| 854 | stepsInfo.value.step = val - 1; | ||
| 855 | } else if (val == 2) { | ||
| 856 | formRef.value?.ruleFormRef?.validate((valid) => { | ||
| 857 | if (valid) { | ||
| 858 | if (formRef.value?.formInline?.dataSource == 2 && !sampleTableFields.value?.length) { | ||
| 859 | proxy.$ElMessage.error('上传文件的字段不能为空'); | ||
| 860 | return; | ||
| 861 | } | ||
| 862 | dataSimpleFormRef.value?.ruleFormRef?.validate((valid) => { | ||
| 863 | if (valid) { | ||
| 864 | // 第一步到第二步时,如果字段列表中与字段脱敏规则中的字段不匹配,应清空。 | ||
| 865 | step.value = val - 1; | ||
| 866 | stepsInfo.value.step = val - 1; | ||
| 867 | anonTaskStepTwoRef.value?.updateNextStepRules(); | ||
| 868 | } | ||
| 869 | }); | ||
| 870 | } | ||
| 871 | }); | ||
| 872 | } else if (val == 3) { | ||
| 873 | let exec = (saveParams) => { | ||
| 874 | if (saveParams.coverageArea?.length) { | ||
| 875 | saveParams.coverageArea = [saveParams.coverageArea]; | ||
| 876 | } else { | ||
| 877 | saveParams.coverageArea = []; | ||
| 878 | } | ||
| 879 | if (saveParams.file?.length) { | ||
| 880 | saveParams.filePath = { | ||
| 881 | name: saveParams.file[0].name, | ||
| 882 | url: saveParams.file[0].url | ||
| 883 | } | ||
| 884 | delete saveParams.file; | ||
| 885 | saveParams.dataSourceGuid = null; | ||
| 886 | saveParams.tableName = null; | ||
| 887 | } else { | ||
| 888 | saveParams.filePath = null; | ||
| 889 | } | ||
| 890 | let simpleFormInline = dataSimpleFormRef.value.formInline; | ||
| 891 | if (simpleFormInline.enableSamplingRate == 'Y') { | ||
| 892 | saveParams.samplingRate = simpleFormInline.samplingRate && parseInt(simpleFormInline.samplingRate); | ||
| 893 | } else { | ||
| 894 | saveParams.samplingRate = null; | ||
| 895 | } | ||
| 896 | if (taskGuid.value) { | ||
| 897 | saveParams.guid = taskGuid.value; | ||
| 898 | } | ||
| 899 | if (isEqual(saveParams, oldAnonTaskValueInfo.value)) { | ||
| 900 | isExecEnd.value = false; | ||
| 901 | step.value = val - 1; | ||
| 902 | stepsInfo.value.step = val - 1; | ||
| 903 | if (!analysisResultInfo.value?.status) { | ||
| 904 | processStepThreeResultView(); | ||
| 905 | } else { | ||
| 906 | isExecEnd.value = analysisResultInfo.value?.status == 'E' || analysisResultInfo.value?.status == 'Y'; | ||
| 907 | } | ||
| 908 | return; | ||
| 909 | } | ||
| 910 | if (!taskGuid.value) { //保存 | ||
| 911 | fullscreenLoading.value = true; | ||
| 912 | saveAnonTask(saveParams).then((res: any) => { | ||
| 913 | fullscreenLoading.value = false; | ||
| 914 | if (res.code == proxy.$passCode) { | ||
| 915 | taskGuid.value = res.data?.taskGuid; | ||
| 916 | isExecEnd.value = false; | ||
| 917 | taskExecGuid.value = res.data?.lastExecGuid; | ||
| 918 | step.value = val - 1; | ||
| 919 | stepsInfo.value.step = val - 1; | ||
| 920 | analysisResultInfo.value = {}; | ||
| 921 | if (refreshTimer.value) { | ||
| 922 | clearInterval(refreshTimer.value); | ||
| 923 | refreshTimer.value = null; | ||
| 924 | } | ||
| 925 | processStepThreeResultView(); | ||
| 926 | oldAnonTaskValueInfo.value = saveParams; | ||
| 927 | anonymizationStore.setIsAnonPageRefresh(true); | ||
| 928 | } else { | ||
| 929 | ElMessage.error(res.msg); | ||
| 930 | } | ||
| 931 | }); | ||
| 932 | } else { //更新 | ||
| 933 | fullscreenLoading.value = true; | ||
| 934 | updateAnonTask(saveParams).then((res: any) => { | ||
| 935 | fullscreenLoading.value = false; | ||
| 936 | if (res.code == proxy.$passCode) { | ||
| 937 | isExecEnd.value = false; | ||
| 938 | taskExecGuid.value = res.data; | ||
| 939 | step.value = val - 1; | ||
| 940 | stepsInfo.value.step = val - 1; | ||
| 941 | analysisResultInfo.value = {}; | ||
| 942 | if (refreshTimer.value) { | ||
| 943 | clearInterval(refreshTimer.value); | ||
| 944 | refreshTimer.value = null; | ||
| 945 | } | ||
| 946 | processStepThreeResultView(); | ||
| 947 | oldAnonTaskValueInfo.value = saveParams; | ||
| 948 | anonymizationStore.setIsAnonPageRefresh(true); | ||
| 949 | } else { | ||
| 950 | ElMessage.error(res.msg); | ||
| 951 | } | ||
| 952 | }) | ||
| 953 | } | ||
| 954 | } | ||
| 955 | let saveParams: any = { ...formRef.value.formInline }; | ||
| 956 | if (saveParams.handleType == '01') { | ||
| 957 | // 保存并提交 TODO。需要加个 记录旧值的,用来判断新值和旧值,是否发生变化,若变化则需要调用保存接口之后,再进行下一步。 | ||
| 958 | let configInfo = await anonTaskStepTwoRef.value?.getStepTwoConfigInfo(); | ||
| 959 | if (!configInfo) { | ||
| 960 | return; | ||
| 961 | } | ||
| 962 | let privacy = configInfo.anonPrivacyMode; //值是克隆过的,可以删除 | ||
| 963 | delete privacy.isKaNumber; | ||
| 964 | delete privacy.isRiskThreshold; | ||
| 965 | delete privacy.isTcField; | ||
| 966 | delete privacy.isLdField; | ||
| 967 | // 为空时为了跟原始值保持一致 | ||
| 968 | privacy.kaNumber = (privacy.kaNumber && parseInt(privacy.kaNumber)) ?? null; | ||
| 969 | privacy.riskThreshold = (privacy.riskThreshold && parseFloat(privacy.riskThreshold)) ?? null; | ||
| 970 | privacy.tcFieldName = privacy.tcFieldName ?? null; | ||
| 971 | privacy.tcThreshold = (privacy.tcThreshold && parseFloat(privacy.tcThreshold)) ?? null; | ||
| 972 | privacy.ldFieldName = privacy.ldFieldName ?? null; | ||
| 973 | privacy.ldNumber = (privacy.ldNumber && parseInt(privacy.ldNumber)) ?? null; | ||
| 974 | Object.assign(saveParams, configInfo); | ||
| 975 | exec(saveParams); | ||
| 976 | } else { | ||
| 977 | formRef.value?.ruleFormRef?.validate((valid, errorItem) => { | ||
| 978 | if (valid) { | ||
| 979 | if (formRef.value?.formInline?.dataSource == 2 && !sampleTableFields.value?.length) { | ||
| 980 | proxy.$ElMessage.error('上传文件的字段不能为空'); | ||
| 981 | return; | ||
| 982 | } | ||
| 983 | dataSimpleFormRef.value?.ruleFormRef?.validate((valid) => { | ||
| 984 | if (valid) { | ||
| 985 | Object.assign(saveParams, { riskThreshold: '0.05' }); | ||
| 986 | exec(saveParams); | ||
| 987 | } | ||
| 988 | }); | ||
| 989 | } else { | ||
| 990 | var obj = Object.keys(errorItem); | ||
| 991 | formRef.value.ruleFormRef?.scrollToField(obj[0]) | ||
| 992 | } | ||
| 993 | }) | ||
| 994 | } | ||
| 995 | } else if (val == 4) { | ||
| 996 | //下一步之后,设置执行结束, 查看结果。 | ||
| 997 | step.value = val - 1; | ||
| 998 | stepsInfo.value.step = val - 1; | ||
| 999 | } | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | const promise: any = ref(null); | ||
| 1003 | const exportResult = () => { | ||
| 1004 | promise.value = exportAnonExecData({ | ||
| 1005 | taskGuid: route.query.guid, | ||
| 1006 | execGuid: route.query.execGuid | ||
| 1007 | }).then((res: any) => { | ||
| 1008 | promise.value = null; | ||
| 1009 | if (res && !res.msg) { | ||
| 1010 | download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化数据.xlsx', 'excel') | ||
| 1011 | } else { | ||
| 1012 | res?.msg && ElMessage.error(res?.msg); | ||
| 1013 | } | ||
| 1014 | }).catch(() => { | ||
| 1015 | promise.value = null; | ||
| 1016 | }) | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | /** 获取字段类型的数据字典 */ | ||
| 1020 | const fieldTypeList: any = ref([]); | ||
| 1021 | |||
| 1022 | /** 编辑时获取的匿名化任务的详情信息 */ | ||
| 1023 | const detailInfo: any = ref({}); | ||
| 1024 | |||
| 1025 | /** 记录原始的值信息,防止上一步之后未修改数据时不调用接口 */ | ||
| 1026 | const oldAnonTaskValueInfo: any = ref({}); | ||
| 1027 | |||
| 1028 | onBeforeMount(() => { | ||
| 1029 | if (taskGuid.value) { | ||
| 1030 | fullscreenLoading.value = true; | ||
| 1031 | getAnonTaskDetail(taskGuid.value).then(async (res: any) => { | ||
| 1032 | if (res?.code == proxy.$passCode) { | ||
| 1033 | detailInfo.value = res.data || {}; | ||
| 1034 | taskExecGuid.value = detailInfo.value.lastExecGuid; | ||
| 1035 | oldAnonTaskValueInfo.value = { | ||
| 1036 | guid: detailInfo.value.guid, | ||
| 1037 | taskName: detailInfo.value.taskName, | ||
| 1038 | dataSource: detailInfo.value.dataSource, | ||
| 1039 | filePath: detailInfo.value.filePath && cloneDeep(detailInfo.value.filePath), | ||
| 1040 | dataSourceGuid: detailInfo.value.dataSourceGuid, | ||
| 1041 | tableName: detailInfo.value.tableName, | ||
| 1042 | samplingRate: detailInfo.value.samplingRate, | ||
| 1043 | // patientPopulationRate: detailInfo.value.patientPopulationRate && typeof detailInfo.value.patientPopulationRate == 'number' ? detailInfo.value.patientPopulationRate?.toFixed(9) : detailInfo.value.patientPopulationRate, | ||
| 1044 | dataSharingTypeCode: detailInfo.value.dataSharingTypeCode, | ||
| 1045 | anonTaskRules: cloneDeep(detailInfo.value.anonTaskRules), | ||
| 1046 | anonPrivacyMode: { | ||
| 1047 | kaNumber: detailInfo.value.anonPrivacyMode?.kaNumber, | ||
| 1048 | ldFieldName: detailInfo.value.anonPrivacyMode?.ldFieldName, | ||
| 1049 | ldNumber: detailInfo.value.anonPrivacyMode?.ldNumber, | ||
| 1050 | riskThreshold: detailInfo.value.anonPrivacyMode?.riskThreshold, | ||
| 1051 | tcFieldName: detailInfo.value.anonPrivacyMode?.tcFieldName, | ||
| 1052 | tcThreshold: detailInfo.value.anonPrivacyMode?.tcThreshold, | ||
| 1053 | } | ||
| 1054 | } | ||
| 1055 | setDataSelectFormItems(Object.assign(detailInfo.value, { file: detailInfo.value.filePath ? [detailInfo.value.filePath] : [] }), true); | ||
| 1056 | if (detailInfo.value.samplingRate != null) { | ||
| 1057 | dataSimpleFormItems.value[0].default = 'Y'; | ||
| 1058 | dataSimpleFormItems.value[1].visible = true; | ||
| 1059 | dataSimpleFormItems.value[1].default = detailInfo.value.samplingRate; | ||
| 1060 | } else { | ||
| 1061 | dataSimpleFormItems.value[0].default = 'N'; | ||
| 1062 | dataSimpleFormItems.value[1].visible = false; | ||
| 1063 | } | ||
| 1064 | let dataSource = detailInfo.value.dataSource; | ||
| 1065 | dataSelectInfoItems.value[5].visible = dataSource == 1; | ||
| 1066 | dataSelectInfoItems.value[6].visible = dataSource == 1; | ||
| 1067 | dataSelectInfoItems.value[8].visible = dataSource == 2; | ||
| 1068 | try { | ||
| 1069 | //文件解析 | ||
| 1070 | if (dataSource == 2) { | ||
| 1071 | let url = detailInfo.value.filePath?.url; | ||
| 1072 | sampleTableDataLoading.value = true; | ||
| 1073 | const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(url).fileName); | ||
| 1074 | if (!refSignInfo?.data) { | ||
| 1075 | fullscreenLoading.value = false; | ||
| 1076 | refSignInfo?.msg && ElMessage.error(refSignInfo?.msg); | ||
| 1077 | return; | ||
| 1078 | } | ||
| 1079 | const fileRes: any = await obsDownloadRequest(refSignInfo?.data); | ||
| 1080 | sampleTableDataLoading.value = false; | ||
| 1081 | if (fileRes && !fileRes.msg) { | ||
| 1082 | parseFileData(fileRes); | ||
| 1083 | } else { | ||
| 1084 | fileRes?.msg && ElMessage.error(fileRes?.msg); | ||
| 1085 | } | ||
| 1086 | // 会出现从文件切换到数据库时没有数据库列表的问题。 | ||
| 1087 | const res: any = await getDatabase({ connectStatus: 1 }); | ||
| 1088 | if (res?.code == proxy.$passCode) { | ||
| 1089 | dataSourceList.value = res.data || []; | ||
| 1090 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSourceGuid'); | ||
| 1091 | item && (item.options = dataSourceList.value); | ||
| 1092 | } else { | ||
| 1093 | proxy.$ElMessage.error(res.msg); | ||
| 1094 | } | ||
| 1095 | } else { | ||
| 1096 | const res: any = await getDatabase({ connectStatus: 1 }); | ||
| 1097 | if (res?.code == proxy.$passCode) { | ||
| 1098 | dataSourceList.value = res.data || []; | ||
| 1099 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSourceGuid'); | ||
| 1100 | item && (item.options = dataSourceList.value); | ||
| 1101 | } else { | ||
| 1102 | proxy.$ElMessage.error(res.msg); | ||
| 1103 | } | ||
| 1104 | currDatasourceSelect.value = dataSourceList.value.find(d => d.guid == detailInfo.value.dataSourceGuid); | ||
| 1105 | const tableRes: any = await getDsTableByDs({ | ||
| 1106 | pageSize: -1, | ||
| 1107 | pageIndex: 1, | ||
| 1108 | dataSourceGuid: detailInfo.value.dataSourceGuid, | ||
| 1109 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 1110 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 1111 | tableName: '', | ||
| 1112 | hadFlag: false | ||
| 1113 | }); | ||
| 1114 | if (tableRes?.code == proxy.$passCode) { | ||
| 1115 | dsTableList.value = tableRes.data?.records || []; | ||
| 1116 | let item = dataSelectInfoItems.value.find(item => item.field == 'tableName'); | ||
| 1117 | item && (item.options = dsTableList.value); | ||
| 1118 | } else { | ||
| 1119 | proxy.$ElMessage.error(tableRes.msg); | ||
| 1120 | } | ||
| 1121 | getDsTableFieldColumn({ | ||
| 1122 | pageSize: 50, | ||
| 1123 | pageIndex: 1, | ||
| 1124 | dataSourceGuid: currDatasourceSelect.value.guid, | ||
| 1125 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 1126 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 1127 | tableName: detailInfo.value.tableName, | ||
| 1128 | }).then((res: any) => { | ||
| 1129 | if (res.code == proxy.$passCode) { | ||
| 1130 | sampleTableFields.value = res.data?.map(d => { | ||
| 1131 | d.fieldDataType = d.dataType; | ||
| 1132 | d.enName = d.columnName; | ||
| 1133 | d.chName = d.columnZhName; | ||
| 1134 | return d; | ||
| 1135 | }) || []; | ||
| 1136 | /** 判断有抽样数据,需要查询接口 */ | ||
| 1137 | getSampleDataByDsTable(); | ||
| 1138 | } else { | ||
| 1139 | ElMessage.error(res.msg); | ||
| 1140 | } | ||
| 1141 | }); | ||
| 1142 | } | ||
| 1143 | fullscreenLoading.value = false; | ||
| 1144 | } catch (error) { | ||
| 1145 | fullscreenLoading.value = false; | ||
| 1146 | } | ||
| 1147 | } else { | ||
| 1148 | fullscreenLoading.value = false; | ||
| 1149 | proxy.$ElMessage.error(res.msg); | ||
| 1150 | } | ||
| 1151 | }); | ||
| 1152 | } else { | ||
| 1153 | getDatabase({ connectStatus: 1 }).then((res: any) => { | ||
| 1154 | if (res.code == proxy.$passCode) { | ||
| 1155 | dataSourceList.value = res.data || []; | ||
| 1156 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSourceGuid'); | ||
| 1157 | item && (item.options = dataSourceList.value); | ||
| 1158 | } else { | ||
| 1159 | proxy.$ElMessage.error(res.msg); | ||
| 1160 | } | ||
| 1161 | }) | ||
| 1162 | } | ||
| 1163 | getParamsList({ | ||
| 1164 | dictType: "数据共享类型", | ||
| 1165 | }).then((res: any) => { | ||
| 1166 | if (res?.code == proxy.$passCode) { | ||
| 1167 | dataSharingTypeList.value = res.data || []; | ||
| 1168 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSharingTypeCode'); | ||
| 1169 | item && (item.options = dataSharingTypeList.value); | ||
| 1170 | } else { | ||
| 1171 | proxy.$ElMessage.error(res.msg); | ||
| 1172 | } | ||
| 1173 | }); | ||
| 1174 | getParamsList({ | ||
| 1175 | dictType: "数据匿名化处理类型", | ||
| 1176 | }).then((res: any) => { | ||
| 1177 | if (res?.code == proxy.$passCode) { | ||
| 1178 | handleTypeList.value = res.data || []; | ||
| 1179 | let item = dataSelectInfoItems.value.find(item => item.field == 'handleType'); | ||
| 1180 | item && (item.options = handleTypeList.value); | ||
| 1181 | } else { | ||
| 1182 | proxy.$ElMessage.error(res.msg); | ||
| 1183 | } | ||
| 1184 | }); | ||
| 1185 | getParamsList({ | ||
| 1186 | dictType: "字段类型", | ||
| 1187 | }).then((res: any) => { | ||
| 1188 | if (res?.code == proxy.$passCode) { | ||
| 1189 | fieldTypeList.value = res.data || []; | ||
| 1190 | } else { | ||
| 1191 | proxy.$ElMessage.error(res.msg); | ||
| 1192 | } | ||
| 1193 | }); | ||
| 1194 | getParentAreaPromise.value = getAreaData({ parentId: null }).then((res: any) => { | ||
| 1195 | if (res?.code == proxy.$passCode) { | ||
| 1196 | parentAreaData.value = res.data ?? []; | ||
| 1197 | return parentAreaData.value; | ||
| 1198 | } | ||
| 1199 | }) | ||
| 1200 | }) | ||
| 1201 | |||
| 1202 | onMounted(() => { | ||
| 1203 | nextTick(() => { | ||
| 1204 | containerWidth.value = containerRef.value?.offsetWidth || 0; | ||
| 1205 | }) | ||
| 1206 | window.onresize = () => { | ||
| 1207 | containerWidth.value = containerRef.value?.offsetWidth || 0; | ||
| 1208 | } | ||
| 1209 | }) | ||
| 1210 | |||
| 1211 | const cancelTask = () => { | ||
| 1212 | proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => { | ||
| 1213 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); | ||
| 1214 | router.push({ | ||
| 1215 | name: 'resultProcess' | ||
| 1216 | }); | ||
| 1217 | }, () => { | ||
| 1218 | proxy.$ElMessage.info("已取消"); | ||
| 1219 | }); | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | /** 完成任务关闭 */ | ||
| 1223 | const closeTask = () => { | ||
| 1224 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); | ||
| 1225 | router.push({ | ||
| 1226 | name: 'resultProcess' | ||
| 1227 | }); | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | const refreshTimer = ref() | ||
| 1231 | |||
| 1232 | /** 执行结果信息 */ | ||
| 1233 | const analysisResultInfo: any = ref({}); | ||
| 1234 | |||
| 1235 | const getResultPromise: any = ref(null); | ||
| 1236 | |||
| 1237 | /** 第三步处理,定时刷新查看结果 */ | ||
| 1238 | const processStepThreeResultView = (isRefresh = false) => { | ||
| 1239 | let process = (isRefresh) => { | ||
| 1240 | getResultPromise.value = getAnonAnalyzeResult(taskExecGuid.value).then((res: any) => { | ||
| 1241 | getResultPromise.value = null; | ||
| 1242 | if (res?.code == proxy.$passCode) { | ||
| 1243 | analysisResultInfo.value = res.data || {}; | ||
| 1244 | if (analysisResultInfo.value.status == 'R') { //正在运行中 | ||
| 1245 | if (isRefresh) { | ||
| 1246 | proxy.$ElMessage.success('刷新成功,正在执行中...'); | ||
| 1247 | } | ||
| 1248 | //添加定时器。 | ||
| 1249 | if (refreshTimer.value) { | ||
| 1250 | return; | ||
| 1251 | } | ||
| 1252 | refreshTimer.value = setInterval(async () => { | ||
| 1253 | process(false); | ||
| 1254 | }, 20000); | ||
| 1255 | } else if (analysisResultInfo.value.status == 'Y') { | ||
| 1256 | //去获取结果。 | ||
| 1257 | isExecEnd.value = true; | ||
| 1258 | refreshTimer.value && clearInterval(refreshTimer.value); | ||
| 1259 | refreshTimer.value = null; | ||
| 1260 | analysisResultTableFields.value = res.data?.column || []; | ||
| 1261 | pageInfo.value.curr = 1; | ||
| 1262 | getAnalysisResultPageData(); | ||
| 1263 | } else if (analysisResultInfo.value.status == 'E') { | ||
| 1264 | isExecEnd.value = true | ||
| 1265 | refreshTimer.value && clearInterval(refreshTimer.value); | ||
| 1266 | refreshTimer.value = null; | ||
| 1267 | } | ||
| 1268 | } else { | ||
| 1269 | proxy.$ElMessage.error(res.msg); | ||
| 1270 | } | ||
| 1271 | }); | ||
| 1272 | } | ||
| 1273 | process(isRefresh); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | /** 随时点击刷新查看结果。 */ | ||
| 1277 | const refreshQueryData = () => { | ||
| 1278 | if (getResultPromise.value) { | ||
| 1279 | return; | ||
| 1280 | } | ||
| 1281 | if (refreshTimer.value) { | ||
| 1282 | clearInterval(refreshTimer.value); | ||
| 1283 | refreshTimer.value = null; | ||
| 1284 | } | ||
| 1285 | processStepThreeResultView(true); | ||
| 1286 | } | ||
| 1287 | |||
| 1288 | /** ------------------------- 匿名化分析结果页面数据展示 ---------------- */ | ||
| 1289 | const pageInfo: any = ref({ | ||
| 1290 | ...commonPageConfig, | ||
| 1291 | }) | ||
| 1292 | |||
| 1293 | const pageChange = (info) => { | ||
| 1294 | pageInfo.value.curr = Number(info.curr); | ||
| 1295 | pageInfo.value.limit = Number(info.limit); | ||
| 1296 | getAnalysisResultPageData(); | ||
| 1297 | } | ||
| 1298 | |||
| 1299 | /** 每列字段对应的列宽计算结果。 */ | ||
| 1300 | const originResultTableFieldColumn = ref({}); | ||
| 1301 | |||
| 1302 | /** 结果分析中的字段表格数据 */ | ||
| 1303 | const resultData: any = ref([]); | ||
| 1304 | |||
| 1305 | /** 结果分析中的字段信息 */ | ||
| 1306 | const analysisResultTableFields: any = ref([]); | ||
| 1307 | |||
| 1308 | const analysisResultLoading = ref(false); | ||
| 1309 | |||
| 1310 | watch( | ||
| 1311 | resultData, | ||
| 1312 | (val: any[], oldVal) => { | ||
| 1313 | if (!analysisResultTableFields.value?.length) { | ||
| 1314 | originResultTableFieldColumn.value = {}; | ||
| 1315 | return; | ||
| 1316 | } | ||
| 1317 | originResultTableFieldColumn.value = {}; | ||
| 1318 | analysisResultTableFields.value.forEach((field, index) => { | ||
| 1319 | originResultTableFieldColumn.value[field.enName] = calcTableColumnWidth( | ||
| 1320 | val?.slice(0, 20) || [], | ||
| 1321 | field.enName, | ||
| 1322 | field.chName, | ||
| 1323 | 24 | ||
| 1324 | ); | ||
| 1325 | }); | ||
| 1326 | }, | ||
| 1327 | { | ||
| 1328 | deep: true, | ||
| 1329 | } | ||
| 1330 | ); | ||
| 1331 | |||
| 1332 | const getAnalysisResultPageData = () => { | ||
| 1333 | analysisResultLoading.value = true; | ||
| 1334 | getAnonAnalyzePageData({ | ||
| 1335 | pageIndex: pageInfo.value.curr, | ||
| 1336 | pageSize: pageInfo.value.limit, | ||
| 1337 | taskExecGuid: taskExecGuid.value, | ||
| 1338 | }).then((res: any) => { | ||
| 1339 | analysisResultLoading.value = false; | ||
| 1340 | if (res?.code == proxy.$passCode) { | ||
| 1341 | pageInfo.value.rows = | ||
| 1342 | resultData.value = []; | ||
| 1343 | res.data?.records?.forEach(d => { | ||
| 1344 | let obj = {}; | ||
| 1345 | analysisResultTableFields.value.forEach(t => { | ||
| 1346 | obj[t.enName] = d.fieldValue?.[t.enName]; | ||
| 1347 | }); | ||
| 1348 | obj['equivalenceClassNum'] = changeNum(d.equivalenceClassNum || 0, 0); | ||
| 1349 | obj['reIdentifyRisk'] = changeNum(d.reIdentifyRisk || 0, 2); | ||
| 1350 | obj['isGtThreshold'] = d.isGtThreshold; | ||
| 1351 | resultData.value.push(obj); | ||
| 1352 | }); | ||
| 1353 | pageInfo.value.rows = res.data?.totalRows ?? 0; | ||
| 1354 | } else { | ||
| 1355 | proxy.$ElMessage.error(res.msg); | ||
| 1356 | } | ||
| 1357 | }) | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | const downPromise: any = ref() | ||
| 1361 | |||
| 1362 | /** 下载评估报告 */ | ||
| 1363 | const transfer = () => { | ||
| 1364 | if (downPromise.value) { | ||
| 1365 | return; | ||
| 1366 | } | ||
| 1367 | downPromise.value = exportAnonReport({ | ||
| 1368 | taskGuid: route.query.guid, | ||
| 1369 | execGuid: taskExecGuid.value | ||
| 1370 | }).then((res: any) => { | ||
| 1371 | downPromise.value = null; | ||
| 1372 | if (res && !res.msg) { | ||
| 1373 | download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化评估报告.docx', 'word') | ||
| 1374 | } else { | ||
| 1375 | res?.msg && ElMessage.error(res?.msg); | ||
| 1376 | } | ||
| 1377 | }).catch(() => { | ||
| 1378 | downPromise.value = null; | ||
| 1379 | }) | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | onUnmounted(() => { | ||
| 1383 | refreshTimer.value && clearInterval(refreshTimer.value); | ||
| 1384 | refreshTimer.value = null; | ||
| 1385 | }) | ||
| 1386 | |||
| 1387 | </script> | ||
| 1388 | |||
| 1389 | <style lang="scss" scoped> | ||
| 1390 | .top_tool_wrap { | ||
| 1391 | width: 100%; | ||
| 1392 | height: 72px; | ||
| 1393 | margin: 8px 0 0px; | ||
| 1394 | display: flex; | ||
| 1395 | justify-content: center; | ||
| 1396 | align-items: center; | ||
| 1397 | |||
| 1398 | :deep(.el-steps) { | ||
| 1399 | width: 60%; | ||
| 1400 | } | ||
| 1401 | } | ||
| 1402 | |||
| 1403 | .bottom_tool_wrap { | ||
| 1404 | height: 40px; | ||
| 1405 | padding: 0 16px; | ||
| 1406 | border-top: 1px solid #d9d9d9; | ||
| 1407 | display: flex; | ||
| 1408 | justify-content: center; | ||
| 1409 | align-items: center; | ||
| 1410 | } | ||
| 1411 | |||
| 1412 | .content_main { | ||
| 1413 | height: calc(100% - 40px); | ||
| 1414 | padding: 0 16px; | ||
| 1415 | overflow: hidden auto; | ||
| 1416 | } | ||
| 1417 | |||
| 1418 | .operator_panel_wrap { | ||
| 1419 | padding-bottom: 12px; | ||
| 1420 | } | ||
| 1421 | |||
| 1422 | .wait-result-div { | ||
| 1423 | height: 250px; | ||
| 1424 | display: flex; | ||
| 1425 | flex-direction: column; | ||
| 1426 | justify-content: center; | ||
| 1427 | align-items: center; | ||
| 1428 | |||
| 1429 | .loading-img { | ||
| 1430 | width: 40px; | ||
| 1431 | height: 40px; | ||
| 1432 | margin-bottom: 18px; | ||
| 1433 | } | ||
| 1434 | |||
| 1435 | .desc { | ||
| 1436 | color: #999; | ||
| 1437 | margin-bottom: 18px; | ||
| 1438 | margin-left: 26px; | ||
| 1439 | } | ||
| 1440 | |||
| 1441 | :deep(.el-icon.failed) { | ||
| 1442 | color: #E63E33; | ||
| 1443 | width: 32px; | ||
| 1444 | height: 32px; | ||
| 1445 | margin-bottom: 8px; | ||
| 1446 | |||
| 1447 | svg { | ||
| 1448 | width: 32px; | ||
| 1449 | height: 32px; | ||
| 1450 | } | ||
| 1451 | } | ||
| 1452 | |||
| 1453 | .error-desc { | ||
| 1454 | color: #E63E33; | ||
| 1455 | font-size: 14px; | ||
| 1456 | line-height: 21px; | ||
| 1457 | margin-bottom: 8px; | ||
| 1458 | font-weight: 600; | ||
| 1459 | } | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | .analysis-result-main { | ||
| 1463 | min-height: 250px; | ||
| 1464 | |||
| 1465 | .value-desc { | ||
| 1466 | font-size: 14px; | ||
| 1467 | color: #212121; | ||
| 1468 | line-height: 21px; | ||
| 1469 | } | ||
| 1470 | |||
| 1471 | .result-title { | ||
| 1472 | font-size: 16px; | ||
| 1473 | color: #212121; | ||
| 1474 | line-height: 24px; | ||
| 1475 | font-weight: 600; | ||
| 1476 | margin-bottom: 6px; | ||
| 1477 | } | ||
| 1478 | |||
| 1479 | .result-title-h1 { | ||
| 1480 | color: #212121; | ||
| 1481 | font-weight: 600; | ||
| 1482 | font-size: 24px; | ||
| 1483 | text-align: center; | ||
| 1484 | line-height: 36px; | ||
| 1485 | margin-top: 12px; | ||
| 1486 | } | ||
| 1487 | |||
| 1488 | .result-title-desc { | ||
| 1489 | color: #666; | ||
| 1490 | font-size: 14px; | ||
| 1491 | line-height: 21px; | ||
| 1492 | margin-top: 12px; | ||
| 1493 | } | ||
| 1494 | |||
| 1495 | .kpi-content { | ||
| 1496 | display: flex; | ||
| 1497 | flex-direction: row; | ||
| 1498 | column-gap: 12px; | ||
| 1499 | row-gap: 12px; | ||
| 1500 | flex-wrap: wrap; | ||
| 1501 | margin-bottom: 20px; | ||
| 1502 | } | ||
| 1503 | |||
| 1504 | .border-content { | ||
| 1505 | height: 76px; | ||
| 1506 | display: flex; | ||
| 1507 | flex-direction: column; | ||
| 1508 | align-items: left; | ||
| 1509 | padding-left: 16px; | ||
| 1510 | justify-content: center; | ||
| 1511 | border: 1px solid #d9d9d9; | ||
| 1512 | width: calc(20% - 8px); | ||
| 1513 | min-width: 228px; | ||
| 1514 | border-radius: 2px; | ||
| 1515 | padding-left: 16px; | ||
| 1516 | |||
| 1517 | .number { | ||
| 1518 | font-weight: 700; | ||
| 1519 | font-size: 20px; | ||
| 1520 | color: #212121; | ||
| 1521 | line-height: 30px; | ||
| 1522 | margin-top: 2px; | ||
| 1523 | |||
| 1524 | &.score-color { | ||
| 1525 | color: #FF5F1F; | ||
| 1526 | } | ||
| 1527 | } | ||
| 1528 | |||
| 1529 | .text { | ||
| 1530 | font-size: 14px; | ||
| 1531 | line-height: 21px; | ||
| 1532 | color: #666666; | ||
| 1533 | display: flex; | ||
| 1534 | |||
| 1535 | .el-icon { | ||
| 1536 | color: #b2b2b2; | ||
| 1537 | } | ||
| 1538 | } | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | .result-table-desc { | ||
| 1542 | font-size: 14px; | ||
| 1543 | color: #999999; | ||
| 1544 | line-height: 21px; | ||
| 1545 | } | ||
| 1546 | |||
| 1547 | .row-two-main { | ||
| 1548 | margin-top: 18px; | ||
| 1549 | display: flex; | ||
| 1550 | |||
| 1551 | .table-one { | ||
| 1552 | width: 586px; | ||
| 1553 | |||
| 1554 | &.border { | ||
| 1555 | border: 1px solid #d9d9d9; | ||
| 1556 | padding: 14px 18px 18px; | ||
| 1557 | } | ||
| 1558 | } | ||
| 1559 | |||
| 1560 | .table-two { | ||
| 1561 | margin-left: 20px; | ||
| 1562 | width: calc(100% - 606px); | ||
| 1563 | |||
| 1564 | &.border { | ||
| 1565 | border: 1px solid #d9d9d9; | ||
| 1566 | padding: 14px 18px 18px; | ||
| 1567 | } | ||
| 1568 | } | ||
| 1569 | } | ||
| 1570 | } | ||
| 1571 | |||
| 1572 | .step-result { | ||
| 1573 | :deep(.v-content-wrap) { | ||
| 1574 | height: 100%; | ||
| 1575 | |||
| 1576 | .el-card__body { | ||
| 1577 | height: calc(100% - 50px) !important; | ||
| 1578 | |||
| 1579 | .card-body-content { | ||
| 1580 | height: 100%; | ||
| 1581 | } | ||
| 1582 | } | ||
| 1583 | |||
| 1584 | .table_tool_wrap { | ||
| 1585 | padding: 0px; | ||
| 1586 | } | ||
| 1587 | } | ||
| 1588 | |||
| 1589 | } | ||
| 1590 | |||
| 1591 | :deep(.custom-form) { | ||
| 1592 | align-items: flex-start; | ||
| 1593 | |||
| 1594 | .wid60.el-form-item { | ||
| 1595 | width: calc(66.66% - 12px); | ||
| 1596 | } | ||
| 1597 | } | ||
| 1598 | |||
| 1599 | :deep(.fixwidth-form) { | ||
| 1600 | width: 500px; | ||
| 1601 | |||
| 1602 | .autoWidth.el-form-item { | ||
| 1603 | width: 80px; | ||
| 1604 | } | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | .table-v2-main { | ||
| 1608 | width: 100%; | ||
| 1609 | height: 240px; | ||
| 1610 | margin-top: 2px; | ||
| 1611 | |||
| 1612 | .main-placeholder { | ||
| 1613 | height: 100%; | ||
| 1614 | display: flex; | ||
| 1615 | justify-content: center; | ||
| 1616 | align-items: center; | ||
| 1617 | flex-direction: column; | ||
| 1618 | |||
| 1619 | .empty-text { | ||
| 1620 | font-size: 14px; | ||
| 1621 | color: #b2b2b2; | ||
| 1622 | } | ||
| 1623 | } | ||
| 1624 | } | ||
| 1625 | |||
| 1626 | :deep(.el-table-v2) { | ||
| 1627 | |||
| 1628 | .el-table-v2__main { | ||
| 1629 | border: 1px solid #d9d9d9; | ||
| 1630 | background: #fff; | ||
| 1631 | } | ||
| 1632 | |||
| 1633 | .el-table-v2__body tr.hover-row.el-table-v2__row--striped.current-row>td.el-table-v2__cell { | ||
| 1634 | background-color: var(--el-table-row-hover-bg-color); | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | .el-table-v2__body tr.current-row>td.el-table-v2__cell { | ||
| 1638 | background-color: var(--el-table-current-row-bg-color); | ||
| 1639 | } | ||
| 1640 | |||
| 1641 | .el-table-v2__header { | ||
| 1642 | width: 100% !important; | ||
| 1643 | } | ||
| 1644 | |||
| 1645 | .el-table-v2__header-cell, | ||
| 1646 | .el-table-v2__row-cell { | ||
| 1647 | border-right: 1px #d9d9d9 solid; | ||
| 1648 | } | ||
| 1649 | |||
| 1650 | .el-table-v2__header-cell-text { | ||
| 1651 | color: #000; | ||
| 1652 | font-weight: normal; | ||
| 1653 | } | ||
| 1654 | |||
| 1655 | .el-table-v2__empty { | ||
| 1656 | display: none !important; | ||
| 1657 | } | ||
| 1658 | |||
| 1659 | .el-table-v2__header-row { | ||
| 1660 | border-bottom: 1px solid #d9d9d9; | ||
| 1661 | } | ||
| 1662 | } | ||
| 1663 | |||
| 1664 | .empty-content { | ||
| 1665 | display: flex; | ||
| 1666 | align-items: center; | ||
| 1667 | justify-content: center; | ||
| 1668 | height: 316px; | ||
| 1669 | width: 100%; | ||
| 1670 | flex-direction: column; | ||
| 1671 | |||
| 1672 | .empty-text { | ||
| 1673 | font-size: 14px; | ||
| 1674 | color: #b2b2b2; | ||
| 1675 | } | ||
| 1676 | } | ||
| 1677 | |||
| 1678 | :deep(.el-form) { | ||
| 1679 | .checkbox-cascader { | ||
| 1680 | display: flex; | ||
| 1681 | |||
| 1682 | .el-cascader { | ||
| 1683 | margin-left: 8px; | ||
| 1684 | } | ||
| 1685 | } | ||
| 1686 | |||
| 1687 | .checkbox-right { | ||
| 1688 | width: calc(100% - 50px); | ||
| 1689 | |||
| 1690 | &.el-form-item { | ||
| 1691 | margin-bottom: 0px; | ||
| 1692 | margin-right: 0px; | ||
| 1693 | width: 100%; | ||
| 1694 | } | ||
| 1695 | } | ||
| 1696 | } | ||
| 1697 | |||
| 1698 | :deep(.cell-tooltip-bg) { | ||
| 1699 | background-color: #fff1d4 !important; | ||
| 1700 | } | ||
| 1701 | |||
| 1702 | :deep(.anlysis-content-wrap) { | ||
| 1703 | .card-title { | ||
| 1704 | justify-content: space-between; | ||
| 1705 | } | ||
| 1706 | } | ||
| 1707 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | <route lang="yaml"> | ||
| 2 | name: anonTaskStepTwo | ||
| 3 | </route> | ||
| 4 | |||
| 5 | <script lang="ts" setup name="anonTaskStepTwo"> | ||
| 6 | import { TableColumnWidth } from '@/utils/enum'; | ||
| 7 | import { CirclePlus, Delete } from '@element-plus/icons-vue'; | ||
| 8 | import { | ||
| 9 | getParamsList, | ||
| 10 | getGeneralizeFileNameList, | ||
| 11 | getLableByFieldName, | ||
| 12 | validateAnonRule, | ||
| 13 | } from '@/api/modules/dataAnonymization'; | ||
| 14 | import { useValidator } from '@/hooks/useValidator'; | ||
| 15 | import { cloneDeep } from 'lodash-es'; | ||
| 16 | |||
| 17 | const props = defineProps({ | ||
| 18 | fieldTypeList: { | ||
| 19 | default: [], | ||
| 20 | type: Array<any> | ||
| 21 | }, | ||
| 22 | isFile: { | ||
| 23 | default: false, | ||
| 24 | type: Boolean | ||
| 25 | }, | ||
| 26 | fieldNameList: { | ||
| 27 | default: [], | ||
| 28 | type: Array<any> | ||
| 29 | }, | ||
| 30 | anonTaskRules: { | ||
| 31 | default: [], | ||
| 32 | type: Array<any> | ||
| 33 | }, | ||
| 34 | anonPrivacyMode: { | ||
| 35 | default: {}, | ||
| 36 | type: Object | ||
| 37 | } | ||
| 38 | }) | ||
| 39 | |||
| 40 | const { required } = useValidator(); | ||
| 41 | const { proxy } = getCurrentInstance() as any; | ||
| 42 | |||
| 43 | const drawerRef = ref(); | ||
| 44 | |||
| 45 | /** 泛化文件列表 */ | ||
| 46 | const generalizeFileNameList: any = ref([]); | ||
| 47 | |||
| 48 | /** 脱敏规则字典列表 */ | ||
| 49 | const desensitiveRuleTypeList: any = ref([]); | ||
| 50 | |||
| 51 | /** 标签类型字典列表 */ | ||
| 52 | const labelTypeList: any = ref([]); | ||
| 53 | |||
| 54 | /** 加密算法字典列表 */ | ||
| 55 | const hashMethodList = ref([]); | ||
| 56 | |||
| 57 | /** 当前正在编辑的表格索引 */ | ||
| 58 | const currTableRowIndex: any = ref(null); | ||
| 59 | |||
| 60 | const ruleModelTableInfo = ref({ | ||
| 61 | id: 'rule-model-table', | ||
| 62 | loading: false, | ||
| 63 | minHeight: '150px', | ||
| 64 | nodeKey: 'guid', | ||
| 65 | height: '200px', | ||
| 66 | fields: [ | ||
| 67 | { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" }, | ||
| 68 | { label: "字段中文名称", field: "fieldChName", width: 150 }, | ||
| 69 | { label: "字段英文名称", field: "fieldName", width: 150 }, | ||
| 70 | { label: "字段类型", field: "fieldTypeName", width: 120 }, | ||
| 71 | { label: "数据类型", field: "dataTypeName", width: 120 }, | ||
| 72 | { | ||
| 73 | label: "脱敏方式", field: "desensitiveRule", width: 120, getName: (scope) => { | ||
| 74 | let rule = scope.row.desensitiveRule; | ||
| 75 | return rule ? rule : (scope.row.generalizeFileGuid ? '泛化' : '--'); | ||
| 76 | } | ||
| 77 | }, | ||
| 78 | ], | ||
| 79 | data: <any>[], | ||
| 80 | showPage: false, | ||
| 81 | actionInfo: { | ||
| 82 | label: "操作", | ||
| 83 | type: "btn", | ||
| 84 | width: 100, | ||
| 85 | fixed: 'right', | ||
| 86 | btns: [ | ||
| 87 | { | ||
| 88 | label: "编辑", value: "ruleEdit", click: (scope) => { | ||
| 89 | currTableRowIndex.value = scope.$index; | ||
| 90 | drawerInfo.value.visible = true; | ||
| 91 | drawerInfo.value.type = 'edit'; | ||
| 92 | drawerInfo.value.header.title = '编辑字段脱敏规则'; | ||
| 93 | let row = scope.row; | ||
| 94 | fieldRulesFormItems.value.forEach(item => { | ||
| 95 | item.default = row[item.field]; | ||
| 96 | if (item.field == 'encryptionAlgorithmCode' || item.field == 'salted') { | ||
| 97 | item.visible = row.desensitiveRuleCode == 'HASH'; | ||
| 98 | } else if (item.field == 'decimalPlaces') { | ||
| 99 | item.visible = row.desensitiveRuleCode == 'ROUNDING'; | ||
| 100 | } | ||
| 101 | }); | ||
| 102 | let fieldNameList = props.fieldNameList.map(f => { | ||
| 103 | if (f.enName != row.fieldName && ruleModelTableInfo.value.data.some(d => d.fieldName == f.enName)) { | ||
| 104 | f.disabled = true; | ||
| 105 | } else { | ||
| 106 | f.disabled = false; | ||
| 107 | } | ||
| 108 | return f; | ||
| 109 | }); | ||
| 110 | fieldRulesFormItems.value[0].options = fieldNameList; | ||
| 111 | fieldRulesFormInfo.value.formInfo.items = fieldRulesFormItems.value; | ||
| 112 | drawerInfo.value.container.contents[0] = fieldRulesFormInfo.value; | ||
| 113 | desensitiveRuleDetail.value = { | ||
| 114 | dissembleType: 1, /** 1从左往右,2.从右往左 */ | ||
| 115 | ruleDetails: <any>[{ | ||
| 116 | digitType: 1, | ||
| 117 | }] | ||
| 118 | }; | ||
| 119 | charReplaceRuleDetail.value = { | ||
| 120 | replaceType: 1, /** 1从左往右,2.从右往左 */ | ||
| 121 | ruleDetails: <any>[{ | ||
| 122 | digitType: 1, | ||
| 123 | ruleType: 1 | ||
| 124 | }] | ||
| 125 | }; | ||
| 126 | rangeReplaceRuleDetails.value = [{ | ||
| 127 | lowOperator: '≤', | ||
| 128 | fieldChName: row.fieldChName, | ||
| 129 | upperOperator: '≤' | ||
| 130 | }]; | ||
| 131 | |||
| 132 | if (row.desensitiveRuleCode == 'DISSEMBLE') { | ||
| 133 | desensitiveRuleDetail.value = row.desensitiveRuleDetail || {}; | ||
| 134 | } else if (row.desensitiveRuleCode == 'CHARREPLACE') { | ||
| 135 | charReplaceRuleDetail.value = row.desensitiveRuleDetail || {}; | ||
| 136 | } else if (row.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 137 | rangeReplaceRuleDetails.value = row.desensitiveRuleDetail?.ruleDetails?.map(rd => { | ||
| 138 | rd.fieldChName = row.fieldChName; | ||
| 139 | return rd; | ||
| 140 | }) || []; | ||
| 141 | } | ||
| 142 | fieldRulesEndFormInfo.value.formInfo.items[0].default = ''; | ||
| 143 | fieldRulesEndFormInfo.value.formInfo.items[1].default = ''; | ||
| 144 | if (!row.desensitiveRuleCode) { | ||
| 145 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value]; | ||
| 146 | } else { | ||
| 147 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value]; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | }, | ||
| 151 | { | ||
| 152 | label: "删除", value: "delete", click: (scope) => { | ||
| 153 | proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => { | ||
| 154 | let fieldName = scope.row.fieldName; | ||
| 155 | ruleModelTableInfo.value.data.splice(scope.$index, 1); | ||
| 156 | // updatePrivacyFormFieldsOptions(fieldName); | ||
| 157 | // 同步去掉隐私模型设置中的字段。 | ||
| 158 | proxy.$ElMessage.success('删除成功'); | ||
| 159 | }, () => { | ||
| 160 | proxy.$ElMessage.info("已取消"); | ||
| 161 | }) | ||
| 162 | } | ||
| 163 | }, | ||
| 164 | ], | ||
| 165 | } | ||
| 166 | }) | ||
| 167 | |||
| 168 | /** 字段脱敏规则表单配置 */ | ||
| 169 | const fieldRulesFormItems = ref([{ | ||
| 170 | label: '选择字段', | ||
| 171 | type: 'select', | ||
| 172 | placeholder: '请选择', | ||
| 173 | field: 'fieldName', | ||
| 174 | default: '', | ||
| 175 | options: props.fieldNameList, | ||
| 176 | props: { | ||
| 177 | label: 'chName', | ||
| 178 | value: 'enName', | ||
| 179 | disabled: 'disabled' | ||
| 180 | }, | ||
| 181 | filterable: true, | ||
| 182 | clearable: false, | ||
| 183 | required: true | ||
| 184 | }, { | ||
| 185 | label: '字段类型', | ||
| 186 | type: 'select', | ||
| 187 | placeholder: '请选择', | ||
| 188 | field: 'fieldTypeCode', | ||
| 189 | default: 'varchar', | ||
| 190 | options: props.fieldTypeList, | ||
| 191 | props: { | ||
| 192 | label: 'label', | ||
| 193 | value: 'value' | ||
| 194 | }, | ||
| 195 | disabled: true, | ||
| 196 | clearable: true, | ||
| 197 | required: true | ||
| 198 | }, { | ||
| 199 | label: '数据类型', | ||
| 200 | type: 'select', | ||
| 201 | placeholder: '请选择', | ||
| 202 | field: 'dataTypeCode', | ||
| 203 | default: '', | ||
| 204 | options: labelTypeList.value, | ||
| 205 | props: { | ||
| 206 | label: 'label', | ||
| 207 | value: 'value' | ||
| 208 | }, | ||
| 209 | filterable: true, | ||
| 210 | clearable: true, | ||
| 211 | required: false | ||
| 212 | }, { | ||
| 213 | label: 'K匿名泛化', | ||
| 214 | type: 'select', | ||
| 215 | placeholder: '请选择', | ||
| 216 | field: 'generalizeFileGuid', | ||
| 217 | default: '', | ||
| 218 | options: generalizeFileNameList.value, | ||
| 219 | props: { | ||
| 220 | label: 'generalizeFileName', | ||
| 221 | value: 'guid' | ||
| 222 | }, | ||
| 223 | filterable: true, | ||
| 224 | clearable: true, | ||
| 225 | required: false | ||
| 226 | }, { | ||
| 227 | label: '脱敏规则', | ||
| 228 | type: 'select', | ||
| 229 | placeholder: '请选择', | ||
| 230 | field: 'desensitiveRuleCode', | ||
| 231 | default: 'DISSEMBLE', | ||
| 232 | options: desensitiveRuleTypeList.value, | ||
| 233 | props: { | ||
| 234 | label: 'label', | ||
| 235 | value: 'value' | ||
| 236 | }, | ||
| 237 | filterable: true, | ||
| 238 | clearable: true, | ||
| 239 | required: false | ||
| 240 | }, { | ||
| 241 | label: '加密算法', | ||
| 242 | type: 'select', | ||
| 243 | placeholder: '请选择', | ||
| 244 | field: 'encryptionAlgorithmCode', | ||
| 245 | default: '', | ||
| 246 | options: hashMethodList.value, | ||
| 247 | props: { | ||
| 248 | label: 'label', | ||
| 249 | value: 'value' | ||
| 250 | }, | ||
| 251 | filterable: true, | ||
| 252 | clearable: true, | ||
| 253 | visible: false, | ||
| 254 | required: true | ||
| 255 | }, { | ||
| 256 | label: '加盐值', | ||
| 257 | type: 'input', | ||
| 258 | placeholder: '请输入0~9', | ||
| 259 | field: 'salted', | ||
| 260 | maxlength: 1, | ||
| 261 | min: 0, | ||
| 262 | max: 9, | ||
| 263 | inputType: 'integerNumber', | ||
| 264 | default: 5, | ||
| 265 | required: true, | ||
| 266 | filterable: true, | ||
| 267 | clearable: true, | ||
| 268 | visible: false, | ||
| 269 | }, { | ||
| 270 | label: '保留小数', | ||
| 271 | type: 'input', | ||
| 272 | placeholder: '请输入0~5', | ||
| 273 | field: 'decimalPlaces', | ||
| 274 | maxlength: 1, | ||
| 275 | min: 0, | ||
| 276 | max: 5, | ||
| 277 | inputType: 'integerNumber', | ||
| 278 | default: 2, | ||
| 279 | required: true, | ||
| 280 | filterable: true, | ||
| 281 | clearable: true, | ||
| 282 | visible: false, | ||
| 283 | }]); | ||
| 284 | |||
| 285 | const fieldRulesFormRules = ref({ | ||
| 286 | fieldName: [required('请选择字段')], | ||
| 287 | dataTypeCode: [required('请选择数据类型')], //不填标识非敏感标识。 | ||
| 288 | // desensitiveRuleCode: [required('请选择脱敏规则')], 脱敏规则和泛化文件选择一个即可。二者必选其一,可以两者共存。 | ||
| 289 | encryptionAlgorithmCode: [required('请选择加密算法')], | ||
| 290 | salted: [required('请输入加盐值')], | ||
| 291 | decimalPlaces: [required('请输入保留小数')] | ||
| 292 | }); | ||
| 293 | |||
| 294 | const fieldRulesFormInfo = ref({ | ||
| 295 | type: "form", | ||
| 296 | title: "", | ||
| 297 | col: "span", | ||
| 298 | formInfo: { | ||
| 299 | id: "add-class-form", | ||
| 300 | readonly: false, | ||
| 301 | items: fieldRulesFormItems.value, | ||
| 302 | rules: fieldRulesFormRules.value, | ||
| 303 | }, | ||
| 304 | }); | ||
| 305 | |||
| 306 | const fieldRulesEndFormInfo = ref({ | ||
| 307 | type: "form", | ||
| 308 | title: "", | ||
| 309 | col: "mt8", | ||
| 310 | showSlot: false, | ||
| 311 | formInfo: { | ||
| 312 | id: "add-rules-end-form", | ||
| 313 | readonly: false, | ||
| 314 | items: [{ | ||
| 315 | label: '样本数据', | ||
| 316 | type: 'input', | ||
| 317 | placeholder: '请输入样本', | ||
| 318 | field: 'testData', | ||
| 319 | default: '', | ||
| 320 | required: false, | ||
| 321 | clearable: true, | ||
| 322 | block: true, | ||
| 323 | col: 'mb8', | ||
| 324 | validateBtn: { | ||
| 325 | value: 'validate', | ||
| 326 | label: '验证', | ||
| 327 | click: () => { | ||
| 328 | let formInline = drawerRef.value?.getDrawerConRef('drawerFormRef')?.formInline; | ||
| 329 | let formInline1 = drawerRef.value?.getDrawerConRef('drawerFormRef', 1)?.formInline; | ||
| 330 | if (!formInline1.testData) { | ||
| 331 | proxy.$ElMessage.error('样本数据不能为空'); | ||
| 332 | return; | ||
| 333 | } | ||
| 334 | validateAnonRule({ | ||
| 335 | desensitiveRuleCode: formInline.desensitiveRuleCode, | ||
| 336 | value: formInline1.testData, | ||
| 337 | desensitiveRuleDetail: getDesensitiveRuleDetailInfo(formInline) | ||
| 338 | }).then((res: any) => { | ||
| 339 | if (res?.code == proxy.$passCode) { | ||
| 340 | let result = res.data; | ||
| 341 | fieldRulesEndFormInfo.value.formInfo.items[0].default = formInline1.testData; | ||
| 342 | fieldRulesEndFormInfo.value.formInfo.items[1].default = formInline.desensitiveRuleCode == 'BLANK' ? ' ' : result; | ||
| 343 | } else { | ||
| 344 | proxy.$ElMessage.error(res.msg); | ||
| 345 | } | ||
| 346 | }) | ||
| 347 | } | ||
| 348 | } | ||
| 349 | }, { | ||
| 350 | label: '脱敏效果', | ||
| 351 | type: 'input', | ||
| 352 | placeholder: '点击验证后展示脱敏效果', | ||
| 353 | field: 'validateResult', | ||
| 354 | default: '', | ||
| 355 | block: true, | ||
| 356 | required: false, | ||
| 357 | disabled: true | ||
| 358 | }], | ||
| 359 | rules: {}, | ||
| 360 | }, | ||
| 361 | }); | ||
| 362 | |||
| 363 | /** 获取规则配置详情。 */ | ||
| 364 | const getDesensitiveRuleDetailInfo = (formInline) => { | ||
| 365 | let desensitiveRuleDetailInfo: any = {}; | ||
| 366 | if (formInline.desensitiveRuleCode == 'DISSEMBLE') { | ||
| 367 | desensitiveRuleDetailInfo = desensitiveRuleDetail.value; | ||
| 368 | } else if (formInline.desensitiveRuleCode == 'CHARREPLACE') { | ||
| 369 | desensitiveRuleDetailInfo = charReplaceRuleDetail.value; | ||
| 370 | } else if (formInline.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 371 | desensitiveRuleDetailInfo = { | ||
| 372 | ruleDetails: rangeReplaceRuleDetails.value | ||
| 373 | }; | ||
| 374 | } else if (formInline.desensitiveRuleCode == 'ROUNDING') { | ||
| 375 | desensitiveRuleDetailInfo = { | ||
| 376 | decimalPlaces: formInline.decimalPlaces | ||
| 377 | } | ||
| 378 | } else if (formInline.desensitiveRuleCode == 'HASH') { | ||
| 379 | desensitiveRuleDetailInfo = { | ||
| 380 | encryptionAlgorithmCode: formInline.encryptionAlgorithmCode, | ||
| 381 | salted: formInline.salted | ||
| 382 | } | ||
| 383 | } | ||
| 384 | return desensitiveRuleDetailInfo; | ||
| 385 | } | ||
| 386 | |||
| 387 | const drawerInfo = ref({ | ||
| 388 | visible: false, | ||
| 389 | direction: 'rtl', | ||
| 390 | size: 540, | ||
| 391 | header: { | ||
| 392 | title: '添加字段脱敏规则', | ||
| 393 | }, | ||
| 394 | type: '', | ||
| 395 | container: { | ||
| 396 | contents: [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value], | ||
| 397 | }, | ||
| 398 | footer: { | ||
| 399 | visible: true, | ||
| 400 | btns: [ | ||
| 401 | { type: 'default', label: '取消', value: 'cancel' }, | ||
| 402 | { type: 'primary', label: '确定', value: 'save', loading: false }, | ||
| 403 | ] | ||
| 404 | } | ||
| 405 | }); | ||
| 406 | |||
| 407 | const addRowRules = () => { | ||
| 408 | drawerInfo.value.visible = true; | ||
| 409 | drawerInfo.value.type = 'add'; | ||
| 410 | drawerInfo.value.header.title = '添加字段脱敏规则'; | ||
| 411 | fieldRulesFormItems.value.forEach(item => { | ||
| 412 | if (item.field == 'fieldTypeCode') { | ||
| 413 | item.default = 'varchar'; | ||
| 414 | } else if (item.field == 'desensitiveRuleCode') { | ||
| 415 | item.default = 'DISSEMBLE'; | ||
| 416 | } else if (item.field == 'encryptionAlgorithmCode' || item.field == 'salted') { | ||
| 417 | item.visible = false; | ||
| 418 | item.default = ''; | ||
| 419 | item.field == 'salted' && (item.default = 5); | ||
| 420 | } else if (item.field == 'decimalPlaces') { | ||
| 421 | item.visible = false; | ||
| 422 | item.default = 2; | ||
| 423 | } else { | ||
| 424 | item.default = ''; | ||
| 425 | } | ||
| 426 | }); | ||
| 427 | let fieldNameList = props.fieldNameList.map(f => { | ||
| 428 | if (ruleModelTableInfo.value.data.some(d => d.fieldName == f.enName)) { | ||
| 429 | f.disabled = true; | ||
| 430 | } else { | ||
| 431 | f.disabled = false; | ||
| 432 | } | ||
| 433 | return f | ||
| 434 | }); | ||
| 435 | fieldRulesFormItems.value[0].options = fieldNameList; | ||
| 436 | fieldRulesFormInfo.value.formInfo.items = fieldRulesFormItems.value; | ||
| 437 | desensitiveRuleDetail.value = { | ||
| 438 | dissembleType: 1, /** 1从左往右,2.从右往左 */ | ||
| 439 | ruleDetails: <any>[{ | ||
| 440 | digitType: 1, | ||
| 441 | }] | ||
| 442 | }; | ||
| 443 | charReplaceRuleDetail.value = { | ||
| 444 | replaceType: 1, /** 1从左往右,2.从右往左 */ | ||
| 445 | ruleDetails: <any>[{ | ||
| 446 | digitType: 1, | ||
| 447 | ruleType: 1 | ||
| 448 | }] | ||
| 449 | }; | ||
| 450 | rangeReplaceRuleDetails.value = [{ | ||
| 451 | lowOperator: '≤', | ||
| 452 | fieldChName: '', | ||
| 453 | upperOperator: '≤' | ||
| 454 | }]; | ||
| 455 | fieldRulesEndFormInfo.value.formInfo.items[0].default = ''; | ||
| 456 | fieldRulesEndFormInfo.value.formInfo.items[1].default = ''; | ||
| 457 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value]; | ||
| 458 | } | ||
| 459 | |||
| 460 | const drawerBtnClick = async (btn, info) => { | ||
| 461 | if (btn.value == 'cancel') { | ||
| 462 | drawerInfo.value.visible = false | ||
| 463 | } else { | ||
| 464 | if (!info.generalizeFileGuid && !info.desensitiveRuleCode) { | ||
| 465 | proxy.$ElMessage.error('K匿名泛化与脱敏规则不能同时为空'); | ||
| 466 | return; | ||
| 467 | } | ||
| 468 | if (!(info.fieldTypeCode == 'int' || info.fieldTypeCode == 'decimal' || info.fieldTypeCode == 'tinyint')) { | ||
| 469 | if (info.desensitiveRuleCode == 'ROUNDING') { | ||
| 470 | proxy.$ElMessage.error('非数值类型字段的脱敏规则不能设置取整'); | ||
| 471 | return; | ||
| 472 | } | ||
| 473 | if (info.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 474 | proxy.$ElMessage.error('非数值类型字段的脱敏规则不能设置区间替换'); | ||
| 475 | return; | ||
| 476 | } | ||
| 477 | } | ||
| 478 | drawerInfo.value.footer.btns[1].loading = true; | ||
| 479 | let desensitiveRuleDetailInfo = getDesensitiveRuleDetailInfo(info); | ||
| 480 | // 脱敏规则为掩盖,字符,区间替换存在时需要调用接口检验 | ||
| 481 | if (info.desensitiveRuleCode == 'DISSEMBLE' || info.desensitiveRuleCode == 'CHARREPLACE' || info.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 482 | try { | ||
| 483 | let res: any = await validateAnonRule({ | ||
| 484 | desensitiveRuleCode: info.desensitiveRuleCode, | ||
| 485 | value: '', | ||
| 486 | desensitiveRuleDetail: desensitiveRuleDetailInfo | ||
| 487 | }) | ||
| 488 | if (res?.code != proxy.$passCode) { | ||
| 489 | proxy.$ElMessage.error(res.msg); | ||
| 490 | drawerInfo.value.footer.btns[1].loading = false; | ||
| 491 | return; | ||
| 492 | } | ||
| 493 | } catch (error) { | ||
| 494 | drawerInfo.value.footer.btns[1].loading = false; | ||
| 495 | } | ||
| 496 | } | ||
| 497 | drawerInfo.value.footer.btns[1].loading = false; | ||
| 498 | let saveData: any = { ...info }; | ||
| 499 | saveData.desensitiveRuleDetail = desensitiveRuleDetailInfo; | ||
| 500 | saveData.fieldChName = props.fieldNameList?.find(f => f.enName == saveData.fieldName)?.chName; | ||
| 501 | saveData.dataTypeName = saveData.dataTypeCode && labelTypeList.value.find(l => l.value == saveData.dataTypeCode)?.label; | ||
| 502 | saveData.fieldTypeName = saveData.fieldTypeCode && props.fieldTypeList?.find(f => f.value == saveData.fieldTypeCode)?.label; | ||
| 503 | saveData.desensitiveRule = info.desensitiveRuleCode && desensitiveRuleTypeList.value.find(d => d.value == info.desensitiveRuleCode)?.label; | ||
| 504 | let changeFields = ''; | ||
| 505 | if (drawerInfo.value.type == 'add') { | ||
| 506 | ruleModelTableInfo.value.data.push(saveData); | ||
| 507 | } else if (drawerInfo.value.type == 'edit') { | ||
| 508 | if (!currTableRowIndex.value != null) { | ||
| 509 | let originFieldName = ruleModelTableInfo.value.data[currTableRowIndex.value]?.fieldName; | ||
| 510 | changeFields = originFieldName == saveData.fieldName ? '' : originFieldName; | ||
| 511 | ruleModelTableInfo.value.data[currTableRowIndex.value] = saveData; | ||
| 512 | } | ||
| 513 | } | ||
| 514 | drawerInfo.value.visible = false; | ||
| 515 | // updatePrivacyFormFieldsOptions(changeFields); | ||
| 516 | } | ||
| 517 | } | ||
| 518 | |||
| 519 | /** 更新下拉选择字段的下拉列表,若是字段被删除,需要同步去掉下拉选择值。 */ | ||
| 520 | const updatePrivacyFormFieldsOptions = (changeFields) => { | ||
| 521 | privacyFormItems.value[2].children[0].options = props.fieldNameList; | ||
| 522 | privacyFormItems.value[3].children[0].options = props.fieldNameList; | ||
| 523 | let formInline = privacyFormRef.value?.formInline; | ||
| 524 | privacyFormItems.value.forEach((item, index) => { | ||
| 525 | item.default = formInline[item.field]; | ||
| 526 | if (item.default == 'Y') { | ||
| 527 | item.children?.forEach(child => { | ||
| 528 | child.default = formInline[child.field]; | ||
| 529 | if (changeFields && (child.field == 'tcFieldName' || child.field == 'ldFieldName') && child.default == changeFields) { | ||
| 530 | child.default = '' | ||
| 531 | } | ||
| 532 | }); | ||
| 533 | } | ||
| 534 | }) | ||
| 535 | } | ||
| 536 | |||
| 537 | /** 当前选择的字段对应的标签信息。 */ | ||
| 538 | const currLabelInfo: any = ref({}); | ||
| 539 | |||
| 540 | const drawerSelectChange = (val, row, info) => { | ||
| 541 | if (row.field === 'desensitiveRuleCode') { | ||
| 542 | let methodItem = fieldRulesFormItems.value.find(item => item.field == 'encryptionAlgorithmCode'); | ||
| 543 | let saltedItem = fieldRulesFormItems.value.find(item => item.field == 'salted'); | ||
| 544 | let decimalPlaceItem = fieldRulesFormItems.value.find(item => item.field == 'decimalPlaces'); | ||
| 545 | methodItem && (methodItem.visible = val == 'HASH'); | ||
| 546 | saltedItem && (saltedItem.visible = val == 'HASH'); | ||
| 547 | decimalPlaceItem && (decimalPlaceItem.visible = val == 'ROUNDING'); | ||
| 548 | fieldRulesFormItems.value.forEach(item => { | ||
| 549 | item.default = info[item.field]; | ||
| 550 | if (item.field == 'decimalPlaces') { | ||
| 551 | if (item.default == null) { | ||
| 552 | item.default = 2; | ||
| 553 | } | ||
| 554 | } else if (item.field == 'salted') { | ||
| 555 | if (item.default == null) { | ||
| 556 | item.default = 5; | ||
| 557 | } | ||
| 558 | } | ||
| 559 | }); | ||
| 560 | if (val == 'RANGEREPLACE') { | ||
| 561 | let fieldName = info.fieldName; | ||
| 562 | let fieldChName = fieldName && props.fieldNameList.find(f => f.enName == fieldName)?.chName; | ||
| 563 | rangeReplaceRuleDetails.value.forEach(r => { | ||
| 564 | if (fieldChName && r.fieldChName != fieldChName) { | ||
| 565 | r.fieldChName = fieldChName; | ||
| 566 | } | ||
| 567 | }); | ||
| 568 | } | ||
| 569 | if (!val) { | ||
| 570 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value]; | ||
| 571 | } else { | ||
| 572 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value]; | ||
| 573 | } | ||
| 574 | } | ||
| 575 | // else if (row.field == 'fieldTypeCode') { | ||
| 576 | // fieldRulesFormItems.value.forEach(item => { | ||
| 577 | // item.default = info[item.field]; | ||
| 578 | // if (item.field == 'desensitiveRuleCode') { | ||
| 579 | // if (!(val == 'int' || val == 'decimal' || val == 'tinyint')) { | ||
| 580 | // item.options = desensitiveRuleTypeList.value.filter(d => d.value != 'ROUNDING' && d.value != 'RANGEREPLACE'); | ||
| 581 | // if (item.default == 'ROUNDING' || item.default == 'RANGEREPLACE') { | ||
| 582 | // item.default = 'DISSEMBLE'; | ||
| 583 | // } | ||
| 584 | // } else { | ||
| 585 | // item.options = desensitiveRuleTypeList.value; | ||
| 586 | // } | ||
| 587 | // } | ||
| 588 | // }); | ||
| 589 | // } | ||
| 590 | else if (row.field == 'fieldName') { //选择字段改变之后,调用接口。 | ||
| 591 | let tableField = props.fieldNameList.find(f => f.enName == val); | ||
| 592 | let dataType = tableField?.dataType || 'varchar'; | ||
| 593 | fieldRulesFormItems.value.forEach(item => { | ||
| 594 | item.default = info[item.field]; | ||
| 595 | if (item.field == 'fieldTypeCode') { | ||
| 596 | item.default = dataType; | ||
| 597 | } | ||
| 598 | }); | ||
| 599 | getLableByFieldName(val).then((res: any) => { | ||
| 600 | if (res?.code == proxy.$passCode) { | ||
| 601 | let labelInfo = currLabelInfo.value = res.data || {}; | ||
| 602 | fieldRulesFormItems.value.forEach(item => { | ||
| 603 | item.default = info[item.field]; | ||
| 604 | if (item.field == 'dataTypeCode') { | ||
| 605 | item.default = labelInfo.labelTypeCode; | ||
| 606 | } else if (item.field == 'fieldTypeCode') { | ||
| 607 | item.default = dataType; | ||
| 608 | } | ||
| 609 | }); | ||
| 610 | } else { | ||
| 611 | proxy.$ElMessage.error(res.msg); | ||
| 612 | } | ||
| 613 | }) | ||
| 614 | if (info.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 615 | rangeReplaceRuleDetails.value.forEach(r => { | ||
| 616 | r.fieldChName = tableField?.chName || val; | ||
| 617 | }); | ||
| 618 | } | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | const drawerInputChange = (val, row, info) => { | ||
| 623 | if (row.field == 'testData') { //样本数据修改后,清空脱敏结果 | ||
| 624 | fieldRulesEndFormInfo.value.formInfo.items[0].default = val; | ||
| 625 | fieldRulesEndFormInfo.value.formInfo.items[1].default = ''; | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | /** 掩盖规则中的位数和剩余位数 */ | ||
| 630 | const digitTypeList = ref([{ | ||
| 631 | value: 1, | ||
| 632 | label: '位数' | ||
| 633 | }, { | ||
| 634 | value: 2, | ||
| 635 | label: '剩余位数' | ||
| 636 | }]); | ||
| 637 | |||
| 638 | const dissembleRuleTypeList = ref([{ | ||
| 639 | value: 1, | ||
| 640 | label: '脱敏' | ||
| 641 | }, { | ||
| 642 | value: 2, | ||
| 643 | label: '不脱敏' | ||
| 644 | }]); | ||
| 645 | |||
| 646 | /** 掩盖类型的脱敏规则 分段规则配置 */ | ||
| 647 | const desensitiveRuleDetail = ref({ | ||
| 648 | dissembleType: 1, /** 1从左往右,2.从右往左 */ | ||
| 649 | ruleDetails: <any>[{ | ||
| 650 | digitType: 1, | ||
| 651 | }] | ||
| 652 | }); | ||
| 653 | |||
| 654 | const addSegmentRule = () => { | ||
| 655 | desensitiveRuleDetail.value.ruleDetails.push({ | ||
| 656 | digitType: 1, | ||
| 657 | }); | ||
| 658 | }; | ||
| 659 | |||
| 660 | const deleteSegmentRule = (item, index) => { | ||
| 661 | desensitiveRuleDetail.value.ruleDetails.splice(index, 1); | ||
| 662 | } | ||
| 663 | |||
| 664 | const inputEventDigitChange = (val, item, prop = 'digit') => { | ||
| 665 | item[prop] = item[prop].toString().replace(/\./g, "") | ||
| 666 | item[prop] = item[prop].toString().replace(/^\D*(\d{0,7}(?:\.\d{0})?).*$/g, "$1") | ||
| 667 | if (prop == 'digit' && val != "" && item.digit < 1) { | ||
| 668 | item.digit = 1; | ||
| 669 | } | ||
| 670 | } | ||
| 671 | |||
| 672 | /** 字符规则中的替换方式 */ | ||
| 673 | const charReplaceRuleTypeList = ref([{ | ||
| 674 | value: 1, | ||
| 675 | label: '随机替换' | ||
| 676 | }, { | ||
| 677 | value: 2, | ||
| 678 | label: '固定值替换' | ||
| 679 | }]); | ||
| 680 | |||
| 681 | /** 字符替换的脱敏规则 分段规则配置 */ | ||
| 682 | const charReplaceRuleDetail = ref({ | ||
| 683 | replaceType: 1, /** 1从左往右,2.从右往左 */ | ||
| 684 | ruleDetails: <any>[{ | ||
| 685 | digitType: 1, | ||
| 686 | ruleType: 1 | ||
| 687 | }] | ||
| 688 | }); | ||
| 689 | |||
| 690 | const addCharReplaceSegmentRule = () => { | ||
| 691 | charReplaceRuleDetail.value.ruleDetails.push({ | ||
| 692 | digitType: 1, | ||
| 693 | ruleType: 1 | ||
| 694 | }); | ||
| 695 | }; | ||
| 696 | |||
| 697 | const deleteCharReplaceSegmentRule = (item, index) => { | ||
| 698 | charReplaceRuleDetail.value.ruleDetails.splice(index, 1); | ||
| 699 | } | ||
| 700 | |||
| 701 | /** ----- 区间替换规则 --- */ | ||
| 702 | /** 区间替换的小于或小于等于字符列表 */ | ||
| 703 | const lowerOperatorList: any = ref([{ | ||
| 704 | value: '≤', | ||
| 705 | label: '≤' | ||
| 706 | }, { | ||
| 707 | value: '<', | ||
| 708 | label: '<' | ||
| 709 | }]); | ||
| 710 | |||
| 711 | /** | ||
| 712 | * 字符替换的脱敏规则 分段规则配置。 | ||
| 713 | * { lowValue: , lowOperator: , upperValue: , upperOperator: , replaceValue: '' } | ||
| 714 | */ | ||
| 715 | const rangeReplaceRuleDetails: any = ref([{ | ||
| 716 | lowOperator: '≤', | ||
| 717 | fieldChName: '', | ||
| 718 | upperOperator: '≤' | ||
| 719 | }]); | ||
| 720 | |||
| 721 | const addRangeReplaceSegmentRule = () => { | ||
| 722 | let fieldChName = ''; | ||
| 723 | if (rangeReplaceRuleDetails.value?.length) { | ||
| 724 | fieldChName = rangeReplaceRuleDetails.value[0].fieldChName; | ||
| 725 | } else { | ||
| 726 | let fieldName = drawerRef.value?.getDrawerConRef('drawerFormRef')?.formInline?.fieldName; | ||
| 727 | let tableField = fieldName && props.fieldNameList?.find(f => f.enName == fieldName); | ||
| 728 | fieldChName = tableField?.chName; | ||
| 729 | } | ||
| 730 | rangeReplaceRuleDetails.value.push({ | ||
| 731 | lowOperator: '≤', | ||
| 732 | fieldChName: fieldChName, | ||
| 733 | upperOperator: '≤' | ||
| 734 | }); | ||
| 735 | }; | ||
| 736 | |||
| 737 | const deleteRangeReplaceSegmentRule = (item, index) => { | ||
| 738 | rangeReplaceRuleDetails.value.splice(index, 1); | ||
| 739 | } | ||
| 740 | |||
| 741 | /** 隐私模型设置 */ | ||
| 742 | const privacyFormItems: any = ref([{ | ||
| 743 | label: '选择准标识符等价类隐私模型', | ||
| 744 | type: 'checkbox-input-item', | ||
| 745 | field: 'isKaNumber', | ||
| 746 | default: 'N', | ||
| 747 | placeholder: 'K匿名', | ||
| 748 | trueValue: 'Y', | ||
| 749 | falseValue: 'N', | ||
| 750 | children: [ | ||
| 751 | { | ||
| 752 | label: '', | ||
| 753 | type: 'input', | ||
| 754 | placeholder: '请输入', | ||
| 755 | field: 'kaNumber', | ||
| 756 | inputType: 'integerNumber', | ||
| 757 | default: '', | ||
| 758 | min: 1, | ||
| 759 | maxlength: 6, | ||
| 760 | disabled: false, | ||
| 761 | clearable: true, | ||
| 762 | visible: false, | ||
| 763 | col: 'ka-checkbox-input', | ||
| 764 | style: { width: '100px', margin: 0 } | ||
| 765 | } | ||
| 766 | ], | ||
| 767 | required: true, | ||
| 768 | col: 'checkbox-input' | ||
| 769 | }, { | ||
| 770 | label: '设置重标识可接受风险阈值', | ||
| 771 | type: 'checkbox-input-item', | ||
| 772 | field: 'isRiskThreshold', | ||
| 773 | default: 'N', | ||
| 774 | placeholder: '阈值', | ||
| 775 | trueValue: 'Y', | ||
| 776 | falseValue: 'N', | ||
| 777 | children: [ | ||
| 778 | { | ||
| 779 | label: '', | ||
| 780 | type: 'input', | ||
| 781 | placeholder: '请输入', | ||
| 782 | field: 'riskThreshold', | ||
| 783 | default: '', | ||
| 784 | maxlength: 6, | ||
| 785 | min: 0, | ||
| 786 | max: 1, | ||
| 787 | inputType: 'scoreNumber', | ||
| 788 | disabled: false, | ||
| 789 | clearable: true, | ||
| 790 | visible: false, | ||
| 791 | col: 'ka-checkbox-input', | ||
| 792 | style: { width: '100px', margin: 0 } | ||
| 793 | } | ||
| 794 | ], | ||
| 795 | required: true, | ||
| 796 | col: 'checkbox-input' | ||
| 797 | }, { | ||
| 798 | label: '设置L多样性及T接近', | ||
| 799 | type: 'checkbox-input-item', | ||
| 800 | field: 'isLdField', | ||
| 801 | default: 'N', | ||
| 802 | placeholder: 'L多样性', | ||
| 803 | trueValue: 'Y', | ||
| 804 | falseValue: 'N', | ||
| 805 | children: [ | ||
| 806 | { | ||
| 807 | label: '', | ||
| 808 | type: 'select', | ||
| 809 | placeholder: '请选择字段', | ||
| 810 | field: 'ldFieldName', | ||
| 811 | options: props.fieldNameList || [], | ||
| 812 | props: { | ||
| 813 | label: 'chName', | ||
| 814 | value: 'enName', | ||
| 815 | disabled: false | ||
| 816 | }, | ||
| 817 | default: '', | ||
| 818 | filterable: true, | ||
| 819 | clearable: true, | ||
| 820 | visible: false, | ||
| 821 | col: 'ka-checkbox-input', | ||
| 822 | style: { width: '120px', margin: 0 } | ||
| 823 | }, | ||
| 824 | { | ||
| 825 | label: '', | ||
| 826 | type: 'input', | ||
| 827 | placeholder: '请输入', | ||
| 828 | field: 'ldNumber', | ||
| 829 | inputType: 'integerNumber', | ||
| 830 | default: '', | ||
| 831 | maxlength: 6, | ||
| 832 | min: 1, | ||
| 833 | disabled: false, | ||
| 834 | clearable: true, | ||
| 835 | visible: false, | ||
| 836 | col: 'ka-checkbox-input', | ||
| 837 | style: { width: '100px', margin: 0 } | ||
| 838 | } | ||
| 839 | ], | ||
| 840 | block: false, | ||
| 841 | required: true, | ||
| 842 | col: 'checkbox-input' | ||
| 843 | }, { | ||
| 844 | label: '', | ||
| 845 | type: 'checkbox-input-item', | ||
| 846 | field: 'isTcField', | ||
| 847 | default: 'N', | ||
| 848 | placeholder: 'T接近', | ||
| 849 | trueValue: 'Y', | ||
| 850 | falseValue: 'N', | ||
| 851 | style: { 'margin-top': '2px' }, | ||
| 852 | children: [ | ||
| 853 | { | ||
| 854 | label: '', | ||
| 855 | type: 'select', | ||
| 856 | placeholder: '请选择字段', | ||
| 857 | field: 'tcFieldName', | ||
| 858 | default: '', | ||
| 859 | options: props.fieldNameList || [], | ||
| 860 | props: { | ||
| 861 | label: 'chName', | ||
| 862 | value: 'enName', | ||
| 863 | disabled: false | ||
| 864 | }, | ||
| 865 | filterable: true, | ||
| 866 | clearable: true, | ||
| 867 | visible: false, | ||
| 868 | col: 'ka-checkbox-input', | ||
| 869 | style: { width: '120px', margin: 0 } | ||
| 870 | }, | ||
| 871 | { | ||
| 872 | label: '', | ||
| 873 | type: 'input', | ||
| 874 | placeholder: '请输入', | ||
| 875 | field: 'tcThreshold', | ||
| 876 | default: '', | ||
| 877 | maxlength: 6, | ||
| 878 | min: 0, | ||
| 879 | max: 1, | ||
| 880 | inputType: 'scoreNumber', | ||
| 881 | disabled: false, | ||
| 882 | clearable: true, | ||
| 883 | visible: false, | ||
| 884 | col: 'ka-checkbox-input', | ||
| 885 | style: { width: '100px', margin: 0 } | ||
| 886 | } | ||
| 887 | ], | ||
| 888 | required: false, | ||
| 889 | block: true, | ||
| 890 | col: 'checkbox-input lmt12' | ||
| 891 | }]); | ||
| 892 | |||
| 893 | const privacyFormRules = ref({ | ||
| 894 | kaNumber: [required('请输入K匿名值')], | ||
| 895 | riskThreshold: [required('请输入阈值')], | ||
| 896 | ldFieldName: [required('请选择L多样性字段')], | ||
| 897 | ldNumber: [required('请输入L多样性值')], | ||
| 898 | tcFieldName: [required('请选择T接近字段')], | ||
| 899 | tcThreshold: [required('请输入T接近阈值')] | ||
| 900 | }); | ||
| 901 | |||
| 902 | /** 记录下旧的隐私模型设置 */ | ||
| 903 | const oldPrivacyModelValue: any = ref({ | ||
| 904 | isKaNumber: 'N', | ||
| 905 | isRiskThreshold: 'N', | ||
| 906 | isLdField: 'N', | ||
| 907 | isTcField: 'N' | ||
| 908 | }); | ||
| 909 | |||
| 910 | const handleCheckboxChange = (val, value, row) => { | ||
| 911 | oldPrivacyModelValue.value = Object.assign({}, oldPrivacyModelValue.value, value); | ||
| 912 | privacyFormItems.value.forEach(item => { | ||
| 913 | item.default = oldPrivacyModelValue.value[item.field]; | ||
| 914 | item.children?.forEach(child => { | ||
| 915 | child.default = oldPrivacyModelValue.value[child.field]; | ||
| 916 | }); | ||
| 917 | }) | ||
| 918 | if (row.field == 'isKaNumber') { | ||
| 919 | let kaItem = privacyFormItems.value[0]?.children?.[0]; | ||
| 920 | kaItem && (kaItem.visible = val == 'Y'); | ||
| 921 | } else if (row.field == 'isRiskThreshold') { | ||
| 922 | let riskItem = privacyFormItems.value[1]?.children?.[0]; | ||
| 923 | riskItem && (riskItem.visible = val == 'Y'); | ||
| 924 | } else if (row.field == 'isLdField') { | ||
| 925 | let childrenItem = privacyFormItems.value[2]?.children; | ||
| 926 | childrenItem?.[0] && (childrenItem[0].visible = val == "Y"); | ||
| 927 | childrenItem?.[1] && (childrenItem[1].visible = val == "Y"); | ||
| 928 | } else if (row.field == 'isTcField') { | ||
| 929 | let childrenItem = privacyFormItems.value[3]?.children; | ||
| 930 | childrenItem?.[0] && (childrenItem[0].visible = val == "Y"); | ||
| 931 | childrenItem?.[1] && (childrenItem[1].visible = val == "Y"); | ||
| 932 | } | ||
| 933 | } | ||
| 934 | |||
| 935 | watch(() => props.fieldNameList, (val) => { | ||
| 936 | fieldRulesFormItems.value[0].options = val || []; | ||
| 937 | if (props.isFile) { | ||
| 938 | fieldRulesFormItems.value[1].disabled = false | ||
| 939 | } else { | ||
| 940 | fieldRulesFormItems.value[1].disabled = true; | ||
| 941 | } | ||
| 942 | }, { | ||
| 943 | immediate: true | ||
| 944 | }) | ||
| 945 | |||
| 946 | watch(() => props.fieldTypeList, (val) => { | ||
| 947 | fieldRulesFormItems.value[1].options = val || []; | ||
| 948 | }, { | ||
| 949 | immediate: true | ||
| 950 | }) | ||
| 951 | |||
| 952 | watch(() => props.anonPrivacyMode, (val) => { | ||
| 953 | if (!val) { | ||
| 954 | return; | ||
| 955 | } | ||
| 956 | let hasKaNumber = val.kaNumber != null; | ||
| 957 | privacyFormItems.value[0].default = hasKaNumber ? 'Y' : 'N'; | ||
| 958 | privacyFormItems.value[0].children[0].visible = hasKaNumber; | ||
| 959 | privacyFormItems.value[0].children[0].default = val.kaNumber; | ||
| 960 | oldPrivacyModelValue.value.isKaNumber = hasKaNumber ? 'Y' : 'N'; | ||
| 961 | oldPrivacyModelValue.value.kaNumber = val.kaNumber; | ||
| 962 | let hasRiskThreshold = val.riskThreshold != null; | ||
| 963 | privacyFormItems.value[1].default = hasRiskThreshold ? 'Y' : 'N'; | ||
| 964 | privacyFormItems.value[1].children[0].visible = hasRiskThreshold; | ||
| 965 | privacyFormItems.value[1].children[0].default = val.riskThreshold; | ||
| 966 | oldPrivacyModelValue.value.isRiskThreshold = hasRiskThreshold ? 'Y' : 'N'; | ||
| 967 | oldPrivacyModelValue.value.riskThreshold = val.riskThreshold; | ||
| 968 | let hasldFieldName = !!val.ldFieldName; | ||
| 969 | privacyFormItems.value[2].default = hasldFieldName ? 'Y' : 'N'; | ||
| 970 | privacyFormItems.value[2].children[0].visible = hasldFieldName; | ||
| 971 | privacyFormItems.value[2].children[1].visible = hasldFieldName; | ||
| 972 | privacyFormItems.value[2].children[0].default = val.ldFieldName; | ||
| 973 | privacyFormItems.value[2].children[1].default = val.ldNumber; | ||
| 974 | oldPrivacyModelValue.value.isLdField = hasldFieldName ? 'Y' : 'N'; | ||
| 975 | oldPrivacyModelValue.value.ldFieldName = val.ldFieldName; | ||
| 976 | oldPrivacyModelValue.value.ldNumber = val.ldNumber; | ||
| 977 | let hasTcField = !!val.tcFieldName; | ||
| 978 | privacyFormItems.value[3].default = hasTcField ? 'Y' : 'N'; | ||
| 979 | privacyFormItems.value[3].children[0].visible = hasTcField; | ||
| 980 | privacyFormItems.value[3].children[1].visible = hasTcField; | ||
| 981 | privacyFormItems.value[3].children[0].default = val.tcFieldName; | ||
| 982 | privacyFormItems.value[3].children[1].default = val.tcThreshold; | ||
| 983 | oldPrivacyModelValue.value.isTcField = hasTcField ? 'Y' : 'N'; | ||
| 984 | oldPrivacyModelValue.value.tcFieldName = val.tcFieldName; | ||
| 985 | oldPrivacyModelValue.value.tcThreshold = val.tcThreshold; | ||
| 986 | }, { | ||
| 987 | deep: true | ||
| 988 | }) | ||
| 989 | |||
| 990 | watch(() => props.anonTaskRules, (val) => { | ||
| 991 | ruleModelTableInfo.value.data = val || []; | ||
| 992 | let optionsList = val?.map(v => { | ||
| 993 | return { | ||
| 994 | fieldName: v.fieldName, | ||
| 995 | fieldChName: v.fieldChName | ||
| 996 | } | ||
| 997 | }) || []; | ||
| 998 | privacyFormItems.value[2].children[0].options = optionsList; | ||
| 999 | privacyFormItems.value[3].children[0].options = optionsList; | ||
| 1000 | }) | ||
| 1001 | |||
| 1002 | onBeforeMount(() => { | ||
| 1003 | getParamsList({ | ||
| 1004 | dictType: "标签类型", | ||
| 1005 | }).then((res: any) => { | ||
| 1006 | if (res?.code == proxy.$passCode) { | ||
| 1007 | labelTypeList.value = res.data || []; | ||
| 1008 | let item = fieldRulesFormItems.value.find(item => item.field == 'dataTypeCode'); | ||
| 1009 | item && (item.options = labelTypeList.value); | ||
| 1010 | } else { | ||
| 1011 | proxy.$ElMessage.error(res.msg); | ||
| 1012 | } | ||
| 1013 | }); | ||
| 1014 | getParamsList({ | ||
| 1015 | dictType: "脱敏规则", | ||
| 1016 | }).then((res: any) => { | ||
| 1017 | if (res?.code == proxy.$passCode) { | ||
| 1018 | desensitiveRuleTypeList.value = res.data || []; | ||
| 1019 | let item = fieldRulesFormItems.value.find(item => item.field == 'desensitiveRuleCode'); | ||
| 1020 | item && (item.options = desensitiveRuleTypeList.value); | ||
| 1021 | } else { | ||
| 1022 | proxy.$ElMessage.error(res.msg); | ||
| 1023 | } | ||
| 1024 | }); | ||
| 1025 | getParamsList({ | ||
| 1026 | dictType: "加密算法", | ||
| 1027 | }).then((res: any) => { | ||
| 1028 | if (res?.code == proxy.$passCode) { | ||
| 1029 | hashMethodList.value = res.data || []; | ||
| 1030 | let item = fieldRulesFormItems.value.find(item => item.field == 'encryptionAlgorithmCode'); | ||
| 1031 | item && (item.options = hashMethodList.value); | ||
| 1032 | } else { | ||
| 1033 | proxy.$ElMessage.error(res.msg); | ||
| 1034 | } | ||
| 1035 | }); | ||
| 1036 | getGeneralizeFileNameList().then((res: any) => { | ||
| 1037 | if (res?.code == proxy.$passCode) { | ||
| 1038 | generalizeFileNameList.value = res.data || []; | ||
| 1039 | let item = fieldRulesFormItems.value.find(item => item.field == 'generalizeFileGuid'); | ||
| 1040 | item && (item.options = generalizeFileNameList.value); | ||
| 1041 | } else { | ||
| 1042 | proxy.$ElMessage.error(res.msg); | ||
| 1043 | } | ||
| 1044 | }); | ||
| 1045 | }) | ||
| 1046 | |||
| 1047 | /** 隐私模型设置表单 */ | ||
| 1048 | const privacyFormRef = ref(); | ||
| 1049 | |||
| 1050 | const getStepTwoConfigInfo = async () => { | ||
| 1051 | if (!ruleModelTableInfo.value.data?.length) { | ||
| 1052 | proxy.$ElMessage.error('字段脱敏规则不能为空'); | ||
| 1053 | return false; | ||
| 1054 | } | ||
| 1055 | try { | ||
| 1056 | await privacyFormRef.value?.ruleFormRef?.validate(); | ||
| 1057 | // 验证通过 | ||
| 1058 | return { | ||
| 1059 | anonPrivacyMode: cloneDeep(privacyFormRef.value?.formInline), | ||
| 1060 | anonTaskRules: cloneDeep(ruleModelTableInfo.value.data) | ||
| 1061 | }; | ||
| 1062 | } catch (error) { | ||
| 1063 | // 验证失败 | ||
| 1064 | return false; | ||
| 1065 | } | ||
| 1066 | } | ||
| 1067 | |||
| 1068 | /** 字段改变,可能是切换了表,需要清空规则配置。判断 第一步到第二步时,如果字段列表中与字段脱敏规则中的字段不匹配,应清空,并同时清空T接近字段。 */ | ||
| 1069 | const updateNextStepRules = () => { | ||
| 1070 | ruleModelTableInfo.value.data = ruleModelTableInfo.value.data.filter(rule => { | ||
| 1071 | if (!props.fieldNameList?.some(v => v.enName == rule.fieldName)) { | ||
| 1072 | return false; | ||
| 1073 | } | ||
| 1074 | return true; | ||
| 1075 | }); | ||
| 1076 | privacyFormItems.value[2].children[0].options = props.fieldNameList; | ||
| 1077 | privacyFormItems.value[3].children[0].options = props.fieldNameList; | ||
| 1078 | let formInline = privacyFormRef.value?.formInline; | ||
| 1079 | privacyFormItems.value.forEach((item, index) => { | ||
| 1080 | item.default = formInline[item.field]; | ||
| 1081 | if (item.default == 'Y') { | ||
| 1082 | item.children?.forEach(child => { | ||
| 1083 | child.default = formInline[child.field]; | ||
| 1084 | if ((child.field == 'tcFieldName' || child.field == 'ldFieldName') && !props.fieldNameList.some(f => f.enName == child.default)) { | ||
| 1085 | child.default = '' | ||
| 1086 | } | ||
| 1087 | }); | ||
| 1088 | } | ||
| 1089 | }) | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | defineExpose({ | ||
| 1093 | getStepTwoConfigInfo, | ||
| 1094 | updateNextStepRules | ||
| 1095 | }) | ||
| 1096 | |||
| 1097 | </script> | ||
| 1098 | |||
| 1099 | <template> | ||
| 1100 | <div class="operator_panel_wrap"> | ||
| 1101 | <ContentWrap id="id-rules" title="设置匿名化规则" description="" style="margin-top: 8px;"> | ||
| 1102 | <Table :tableInfo="ruleModelTableInfo" /> | ||
| 1103 | <div class="row-add-btn"> | ||
| 1104 | <el-button link @click="addRowRules" :icon="CirclePlus" v-preReClick>添加字段脱敏规则</el-button> | ||
| 1105 | </div> | ||
| 1106 | </ContentWrap> | ||
| 1107 | <ContentWrap id="id-screctModel" title="隐私模型设置" description="" style="margin-top: 16px;"> | ||
| 1108 | <Form style="width: 80%;max-width: 500px;" ref="privacyFormRef" :itemList="privacyFormItems" | ||
| 1109 | :rules="privacyFormRules" formId="model-select-edit" @checkboxChange="handleCheckboxChange" /> | ||
| 1110 | </ContentWrap> | ||
| 1111 | |||
| 1112 | <Drawer ref="drawerRef" :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick" | ||
| 1113 | @drawerInputChange="drawerInputChange" @drawerSelectChange="drawerSelectChange"> | ||
| 1114 | <template v-slot:default> | ||
| 1115 | <div v-show="drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'DISSEMBLE'"> | ||
| 1116 | <div>{{ '请配置分段是否脱敏' + `(${desensitiveRuleDetail.ruleDetails?.length}/10)` }}</div> | ||
| 1117 | <el-radio-group v-model="desensitiveRuleDetail.dissembleType"> | ||
| 1118 | <el-radio :value="1">从左往右</el-radio> | ||
| 1119 | <el-radio :value="2">从右往左</el-radio> | ||
| 1120 | </el-radio-group> | ||
| 1121 | <div class="seg-main"> | ||
| 1122 | <div class="row-per" v-for="(item, index) in desensitiveRuleDetail.ruleDetails"> | ||
| 1123 | <el-select v-model="item.digitType" :style="{ width: item.digitType == 2 ? '322px' : '170px' }"> | ||
| 1124 | <el-option v-for="item in digitTypeList" :label="item.label" :value="item.value" | ||
| 1125 | :key="item.value"></el-option> | ||
| 1126 | </el-select> | ||
| 1127 | <el-input v-show="item.digitType != 2" style="width:137px;margin-left: 4px;" v-model="item.digit" | ||
| 1128 | :maxlength="6" :min="1" @input="(val) => inputEventDigitChange(val, item)" placeholder="请输入"></el-input> | ||
| 1129 | <el-select v-model="item.ruleType" style="width:170px;margin-left: 4px;"> | ||
| 1130 | <el-option v-for="item in dissembleRuleTypeList" :label="item.label" :value="item.value" | ||
| 1131 | :key="item.value"></el-option> | ||
| 1132 | </el-select> | ||
| 1133 | <div class="title_tool" @click="deleteSegmentRule(item, index)"> | ||
| 1134 | <el-icon :size="20" color="#b2b2b2"> | ||
| 1135 | <Delete /> | ||
| 1136 | </el-icon> | ||
| 1137 | </div> | ||
| 1138 | </div> | ||
| 1139 | <div class="row-add-btn"> | ||
| 1140 | <el-button :disabled="desensitiveRuleDetail.ruleDetails?.length > 9" link @click="addSegmentRule" | ||
| 1141 | :icon="CirclePlus" v-preReClick>添加分段规则</el-button> | ||
| 1142 | </div> | ||
| 1143 | </div> | ||
| 1144 | </div> | ||
| 1145 | <div v-show="drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'CHARREPLACE'"> | ||
| 1146 | <div>{{ '请配置分段的替换方式' + `(${charReplaceRuleDetail.ruleDetails?.length}/10)` }}</div> | ||
| 1147 | <el-radio-group v-model="charReplaceRuleDetail.replaceType"> | ||
| 1148 | <el-radio :value="1">从左往右</el-radio> | ||
| 1149 | <el-radio :value="2">从右往左</el-radio> | ||
| 1150 | </el-radio-group> | ||
| 1151 | <div class="seg-main"> | ||
| 1152 | <div class="row-per" v-for="(item, index) in charReplaceRuleDetail.ruleDetails"> | ||
| 1153 | <el-select v-model="item.digitType" :style="{ width: item.digitType == 2 ? '220px' : '130px' }"> | ||
| 1154 | <el-option v-for="item in digitTypeList" :label="item.label" :value="item.value" | ||
| 1155 | :key="item.value"></el-option> | ||
| 1156 | </el-select> | ||
| 1157 | <el-input v-show="item.digitType != 2" style="width:86px;margin-left: 4px;" v-model="item.digit" | ||
| 1158 | :maxlength="6" :min="1" @input="(val) => inputEventDigitChange(val, item)" placeholder="请输入"></el-input> | ||
| 1159 | <el-select v-model="item.ruleType" | ||
| 1160 | :style="{ width: item.ruleType == 1 ? '244px' : '130px', 'margin-left': '4px' }"> | ||
| 1161 | <el-option v-for="item in charReplaceRuleTypeList" :label="item.label" :value="item.value" | ||
| 1162 | :key="item.value"></el-option> | ||
| 1163 | </el-select> | ||
| 1164 | <el-input v-model="item.fixedValue" v-show="item.ruleType == 2" style="width:110px;margin-left: 4px;" | ||
| 1165 | :maxlength="50" placeholder="请输入"></el-input> | ||
| 1166 | <div class="title_tool" @click="deleteCharReplaceSegmentRule(item, index)"> | ||
| 1167 | <el-icon :size="20" color="#b2b2b2"> | ||
| 1168 | <Delete /> | ||
| 1169 | </el-icon> | ||
| 1170 | </div> | ||
| 1171 | </div> | ||
| 1172 | <div class="row-add-btn"> | ||
| 1173 | <el-button :disabled="charReplaceRuleDetail.ruleDetails?.length > 9" link | ||
| 1174 | @click="addCharReplaceSegmentRule" :icon="CirclePlus" v-preReClick>添加分段规则</el-button> | ||
| 1175 | </div> | ||
| 1176 | </div> | ||
| 1177 | </div> | ||
| 1178 | <div v-show="drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'RANGEREPLACE'"> | ||
| 1179 | <div class="mb8">{{ '请配置区间替换规则' + `(${rangeReplaceRuleDetails?.length}/10)` }}</div> | ||
| 1180 | <div class="seg-main"> | ||
| 1181 | <div class="row-per" v-for="(item, index) in rangeReplaceRuleDetails"> | ||
| 1182 | <el-input style="width:16.5%" v-model="item.lowValue" :maxlength="6" | ||
| 1183 | @input="(val) => inputEventDigitChange(val, item, 'lowValue')" placeholder="请输入"></el-input> | ||
| 1184 | <el-select v-model="item.lowOperator" style="width: 16.5%;margin-left: 4px;"> | ||
| 1185 | <el-option v-for="item in lowerOperatorList" :label="item.label" :value="item.value" | ||
| 1186 | :key="item.value"></el-option> | ||
| 1187 | </el-select> | ||
| 1188 | <el-input style="width: 16.5%;margin-left: 4px;" :disabled="true" v-model="item.fieldChName"></el-input> | ||
| 1189 | <el-select v-model="item.upperOperator" style="width: 16.5%;margin-left: 4px;"> | ||
| 1190 | <el-option v-for="item in lowerOperatorList" :label="item.label" :value="item.value" | ||
| 1191 | :key="item.value"></el-option> | ||
| 1192 | </el-select> | ||
| 1193 | <el-input style="width:16.5%;margin-left: 4px;" v-model="item.upperValue" :maxlength="6" | ||
| 1194 | @input="(val) => inputEventDigitChange(val, item, 'upperValue')" placeholder="请输入"></el-input> | ||
| 1195 | <el-input v-model="item.replaceValue" style="width:16.5%;margin-left: 4px;" :maxlength="50" | ||
| 1196 | placeholder="替换值"></el-input> | ||
| 1197 | <div class="title_tool" @click="deleteRangeReplaceSegmentRule(item, index)"> | ||
| 1198 | <el-icon :size="20" color="#b2b2b2"> | ||
| 1199 | <Delete /> | ||
| 1200 | </el-icon> | ||
| 1201 | </div> | ||
| 1202 | </div> | ||
| 1203 | <div class="row-add-btn"> | ||
| 1204 | <el-button :disabled="charReplaceRuleDetail.ruleDetails?.length > 9" link | ||
| 1205 | @click="addRangeReplaceSegmentRule" :icon="CirclePlus" v-preReClick>添加分段规则</el-button> | ||
| 1206 | </div> | ||
| 1207 | </div> | ||
| 1208 | </div> | ||
| 1209 | </template> | ||
| 1210 | </Drawer> | ||
| 1211 | |||
| 1212 | </div> | ||
| 1213 | </template> | ||
| 1214 | |||
| 1215 | <style lang="scss" scoped> | ||
| 1216 | .row-add-btn { | ||
| 1217 | .el-button--default { | ||
| 1218 | padding: 4px 0px; | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | :deep(.el-icon) { | ||
| 1222 | width: 16px; | ||
| 1223 | height: 16px; | ||
| 1224 | |||
| 1225 | svg { | ||
| 1226 | width: 16px; | ||
| 1227 | height: 16px; | ||
| 1228 | } | ||
| 1229 | } | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | .seg-main { | ||
| 1233 | .row-add-btn { | ||
| 1234 | margin-top: -6px; | ||
| 1235 | } | ||
| 1236 | } | ||
| 1237 | |||
| 1238 | .row-per { | ||
| 1239 | display: flex; | ||
| 1240 | align-items: center; | ||
| 1241 | margin-bottom: 8px; | ||
| 1242 | position: relative; | ||
| 1243 | |||
| 1244 | .title_tool { | ||
| 1245 | margin-left: 4px; | ||
| 1246 | // position: absolute; | ||
| 1247 | // right: 4px; | ||
| 1248 | cursor: pointer; | ||
| 1249 | |||
| 1250 | :deep(.el-icon) { | ||
| 1251 | --color: #FB2323 !important; | ||
| 1252 | |||
| 1253 | svg { | ||
| 1254 | width: 16px; | ||
| 1255 | height: 16px; | ||
| 1256 | } | ||
| 1257 | } | ||
| 1258 | } | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | :deep(.mt8.drawer_panel) { | ||
| 1262 | margin-top: 8px; | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | .mb8 { | ||
| 1266 | margin-bottom: 8px; | ||
| 1267 | } | ||
| 1268 | |||
| 1269 | :deep(.el-form-item.ka-checkbox-input) { | ||
| 1270 | .el-input { | ||
| 1271 | min-width: 50px !important; | ||
| 1272 | } | ||
| 1273 | } | ||
| 1274 | |||
| 1275 | :deep(.el-form) { | ||
| 1276 | .lmt12 { | ||
| 1277 | // margin-top: -4px; 验证信息会被遮挡 | ||
| 1278 | |||
| 1279 | .input_panel { | ||
| 1280 | flex: 0 !important; | ||
| 1281 | } | ||
| 1282 | } | ||
| 1283 | } | ||
| 1284 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | <template> | ||
| 2 | <div class="analysis-result-main"> | ||
| 3 | <div v-if="showTitle" class="result-title">匿名结果分析</div> | ||
| 4 | <div class="kpi-content" v-show="Object.keys(analysisResultInfo).length > 0"> | ||
| 5 | <div class="border-content"> | ||
| 6 | <div class="text">去标识化效果评估结果</div> | ||
| 7 | <span class="number score-color">{{ analysisResultInfo.rating + '级' }}</span> | ||
| 8 | </div> | ||
| 9 | <div class="border-content"> | ||
| 10 | <span class="text">重标识风险最大值<el-tooltip placement="top" effect="light" popper-class="table_tooltip"> | ||
| 11 | <template #content> | ||
| 12 | <div style="max-width: 236px;"> | ||
| 13 | 所有等价类的重标识风险最大值 | ||
| 14 | </div> | ||
| 15 | </template> | ||
| 16 | <el-icon style="margin-left: 2px;margin-top: 2px;"> | ||
| 17 | <QuestionFilled /> | ||
| 18 | </el-icon> | ||
| 19 | </el-tooltip></span> | ||
| 20 | <span class="number">{{ analysisResultInfo.reIdentifyRiskRb != null ? | ||
| 21 | (analysisResultInfo.reIdentifyRiskRb || 0) : '--' | ||
| 22 | }}</span> | ||
| 23 | </div> | ||
| 24 | <div class="border-content"> | ||
| 25 | <span class="text">重标识风险平均值<el-tooltip placement="top" effect="light" popper-class="table_tooltip"> | ||
| 26 | <template #content> | ||
| 27 | <div style="max-width: 236px;"> | ||
| 28 | 所有等价类的重标识风险平均值 | ||
| 29 | </div> | ||
| 30 | </template> | ||
| 31 | <el-icon style="margin-left: 2px;margin-top: 2px;"> | ||
| 32 | <QuestionFilled /> | ||
| 33 | </el-icon> | ||
| 34 | </el-tooltip></span> | ||
| 35 | <span class="number">{{ analysisResultInfo.reIdentifyRiskRc != null ? | ||
| 36 | (analysisResultInfo.reIdentifyRiskRc || 0) : '--' }}</span> | ||
| 37 | </div> | ||
| 38 | <div class="border-content"> | ||
| 39 | <span class="text">环境重标识攻击概率<el-tooltip placement="top" effect="light" popper-class="table_tooltip"> | ||
| 40 | <template #content> | ||
| 41 | <div style="max-width: 236px;"> | ||
| 42 | 完全公开共享数据发布,攻击者对数据集进行环境重标识攻击的概率为1。领地公开共享与受控公开共享数据发布,环境重标识攻击概率为内部故意攻击概率、数据集包含熟人概率和数据泄露概率三者的最大值。 | ||
| 43 | </div> | ||
| 44 | </template> | ||
| 45 | <el-icon style="margin-left: 2px;margin-top: 2px;"> | ||
| 46 | <QuestionFilled /> | ||
| 47 | </el-icon> | ||
| 48 | </el-tooltip></span> | ||
| 49 | <span class="number">{{ analysisResultInfo.envReAttackPr != null ? | ||
| 50 | (analysisResultInfo.envReAttackPr || 0) : '--' }}</span> | ||
| 51 | </div> | ||
| 52 | <div class="border-content"> | ||
| 53 | <span class="text">等价类门限风险<el-tooltip placement="top" effect="light" popper-class="table_tooltip"> | ||
| 54 | <template #content> | ||
| 55 | <div style="max-width: 236px;"> | ||
| 56 | 完全公开共享数据发布,门限阈值取值 1/20;受控公开共享数据发布,门限阈值取值 1/5;领地公开共享数据发布,门限阈值取值 | ||
| 57 | 1/3;等价类门限风险为:等价类的重标识风险大于门限阈值为1,小于等于为0,求和后除以等价类个数。 | ||
| 58 | </div> | ||
| 59 | </template> | ||
| 60 | <el-icon style="margin-left: 2px;margin-top: 2px;"> | ||
| 61 | <QuestionFilled /> | ||
| 62 | </el-icon> | ||
| 63 | </el-tooltip></span> | ||
| 64 | <span class="number">{{ analysisResultInfo.reIdentifyRiskRa != null ? | ||
| 65 | (analysisResultInfo.reIdentifyRiskRa || 0) : '--' }}</span> | ||
| 66 | </div> | ||
| 67 | <div class="border-content"> | ||
| 68 | <span class="text">重标识风险总体风险<el-tooltip placement="top" effect="light" popper-class="table_tooltip"> | ||
| 69 | <template #content> | ||
| 70 | <div style="max-width: 236px;"> | ||
| 71 | 完全公开共享,当等价类门限风险=0时,重标识风险总体风险公式为等价类重标识风险最大值*环境重标识攻击概率;当等价类门限风险!=0时,重标识风险总体风险为1。 | ||
| 72 | 受控公开共享和领地公开共享,当等价类门限风险=0时,重标识风险总体风险公式为等价类重标识风险平均值*环境重标识攻击概率;当等价类门限风险!=0时,重标识风险总体风险为1。 | ||
| 73 | <!-- {{ oldAnonTaskValueInfo.dataSharingTypeCode == '01' ? '完全公开共享,当等价类门限风险=0时,重标识风险总体风险公式为等价类重标识风险最大值*环境重标识攻击概率;当等价类门限风险!=0时,重标识风险总体风险为1。' | ||
| 74 | : `${oldAnonTaskValueInfo.dataSharingTypeCode == '02' ? '受控公开共享' : '领地公开共享'},当等价类门限风险=0时,重标识风险总体风险公式为等价类重标识风险平均值*环境重标识攻击概率;当等价类门限风险!=0时,重标识风险总体风险为1。` }} --> | ||
| 75 | </div> | ||
| 76 | </template> | ||
| 77 | <el-icon style="margin-left: 2px;margin-top: 2px;"> | ||
| 78 | <QuestionFilled /> | ||
| 79 | </el-icon> | ||
| 80 | </el-tooltip></span> | ||
| 81 | <span class="number">{{ analysisResultInfo.reIdentifyOverallRisk != null ? | ||
| 82 | (analysisResultInfo.reIdentifyOverallRisk || 0) : '--' }}</span> | ||
| 83 | </div> | ||
| 84 | <div class="border-content"> | ||
| 85 | <span class="text">重标识可接受风险阈值</span> | ||
| 86 | <span class="number">{{ oldAnonTaskValueInfo.anonPrivacyMode?.riskThreshold == null ? 0.05 : | ||
| 87 | (oldAnonTaskValueInfo.anonPrivacyMode?.riskThreshold || 0) }}</span> | ||
| 88 | </div> | ||
| 89 | </div> | ||
| 90 | <div class="result-title">重标识风险表</div> | ||
| 91 | <el-table ref="tableRef" v-show="analysisResultTableFields.length" :data="resultData" | ||
| 92 | v-loading="analysisResultLoading" :highlight-current-row="true" stripe border tooltip-effect="light" height="100%" | ||
| 93 | row-key="guid" :style="{ width: '100%', height: '280px' }"> | ||
| 94 | <el-table-column label="等价类" type="index" width="68px" align="center" show-overflow-tooltip> | ||
| 95 | <template #default="scope"> | ||
| 96 | <span>{{ | ||
| 97 | pageInfo.curr !== undefined | ||
| 98 | ? (pageInfo.curr - 1) * pageInfo.limit + scope.$index + 1 | ||
| 99 | : scope.$index + 1 | ||
| 100 | }}</span> | ||
| 101 | </template> | ||
| 102 | </el-table-column> | ||
| 103 | <template v-for="(item, index) in (analysisResultTableFields || [])"> | ||
| 104 | <el-table-column :label="item.chName" :width="item.dataType === 'datetime' | ||
| 105 | ? TableColumnWidth.DATETIME | ||
| 106 | : item.dataType === 'date' | ||
| 107 | ? TableColumnWidth.DATE | ||
| 108 | : originResultTableFieldColumn[item.enName] | ||
| 109 | " :align="getTextAlign(item)" :header-align="getTextAlign(item)" | ||
| 110 | :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true"> | ||
| 111 | </el-table-column> | ||
| 112 | </template> | ||
| 113 | <el-table-column label="等价类大小" prop="equivalenceClassNum" width="98" align="right" fixed="right" | ||
| 114 | show-overflow-tooltip></el-table-column> | ||
| 115 | <el-table-column label="重标识风险" prop="reIdentifyRisk" width="96" align="right" fixed="right" | ||
| 116 | show-overflow-tooltip></el-table-column> | ||
| 117 | <el-table-column label="判断重风险是否大于门限阈值" prop="isGtThreshold" width="130" align="left" fixed="right" | ||
| 118 | show-overflow-tooltip></el-table-column> | ||
| 119 | </el-table> | ||
| 120 | <div v-show="!analysisResultTableFields.length" class="empty-content"> | ||
| 121 | <img src="../../../assets/images/empty-data.png" :style="{ width: '168px', height: '96px' }" /> | ||
| 122 | <div class="empty-text">暂无数据</div> | ||
| 123 | </div> | ||
| 124 | <div v-show="analysisResultTableFields.length" class="result-table-desc">门限阈值的取值:完全公开共享数据发布,取值 | ||
| 125 | 1/20;受控公开共享数据发布,取值 | ||
| 126 | 1/5;领地公开共享数据发布,取值 1/3</div> | ||
| 127 | <PageNav v-show="analysisResultTableFields.length" :class="[pageInfo.type]" :pageInfo="pageInfo" | ||
| 128 | @pageChange="pageChange" /> | ||
| 129 | <div class="row-two-main" style="margin-top: 12px;"> | ||
| 130 | <div class="table-one"> | ||
| 131 | <div class="result-title">对抗性测试关键变量</div> | ||
| 132 | <el-table ref="varTableRef" :data="analysisResultInfo?.adversarialTest || []" :highlight-current-row="true" | ||
| 133 | stripe border tooltip-effect="light" height="100%" row-key="guid" | ||
| 134 | :style="{ height: oldAnonTaskValueInfo.dataSharingTypeCode != '01' ? (containerWidth > 1397 ? '378px' : (containerWidth < 1048 ? '414px' : '396px')) : 'auto' }"> | ||
| 135 | <el-table-column label="序号" type="index" width="56px" align="center" show-overflow-tooltip> | ||
| 136 | </el-table-column> | ||
| 137 | <el-table-column label="数据项" prop="chName" width="150px" align="left" show-overflow-tooltip></el-table-column> | ||
| 138 | <el-table-column label="唯一性分值" prop="uniqueScore" width="110px" align="right" | ||
| 139 | show-overflow-tooltip></el-table-column> | ||
| 140 | <el-table-column label="影响力分值" prop="influenceScore" width="110" align="right" | ||
| 141 | show-overflow-tooltip></el-table-column> | ||
| 142 | <el-table-column label="数据属性标识度分值" prop="dataAttrIdentScore" width="160" align="right" | ||
| 143 | show-overflow-tooltip></el-table-column> | ||
| 144 | </el-table> | ||
| 145 | </div> | ||
| 146 | <div class="table-two" v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"> | ||
| 147 | <div class="result-title">内部故意攻击概率</div> | ||
| 148 | <div class="desc" style="margin-bottom: 4px;color: #999;line-height: 18px;"> | ||
| 149 | 重标识数据的动机和能力为低,从重标识攻击可能性分析表可得出在内部攻击方面,重标识攻击概率的取值为0.05。</div> | ||
| 150 | <el-table ref="innerTableRef" :data="innerResultData" :highlight-current-row="true" stripe border | ||
| 151 | tooltip-effect="light" height="100%" row-key="guid" :style="{ height: '356px' }" | ||
| 152 | :span-method="arrayInnerSpanMethod" :cell-class-name="handleInnerCellClass"> | ||
| 153 | <!-- <el-table-column label="序号" type="index" width="56px" align="center" show-overflow-tooltip> | ||
| 154 | </el-table-column> --> | ||
| 155 | <el-table-column label="风险减缓控制水平" prop="level" width="150px" align="left" | ||
| 156 | show-overflow-tooltip></el-table-column> | ||
| 157 | <el-table-column label="动机和能力" prop="competencyLevel" width="140px" align="left" | ||
| 158 | show-overflow-tooltip></el-table-column> | ||
| 159 | <el-table-column label="重标识攻击概率" prop="value" width="140" align="right" show-overflow-tooltip> | ||
| 160 | <template #default="scope"> | ||
| 161 | <span>{{ scope.row.value }}</span> | ||
| 162 | </template> | ||
| 163 | </el-table-column> | ||
| 164 | </el-table> | ||
| 165 | </div> | ||
| 166 | </div> | ||
| 167 | <div class="row-two-main" v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"> | ||
| 168 | <div class="table-one border"> | ||
| 169 | <div class="result-title">数据集包含熟人概率</div> | ||
| 170 | <div class="result-title-h1">pr=1-(1-p)^m</div> | ||
| 171 | <div class="result-title-desc"> | ||
| 172 | {{ ` 数据集包含熟人概率可通过以上公式计算,${new | ||
| 173 | Date().getFullYear()}年我国最新的人口统计为${analysisResultInfo?.allPerson || | ||
| 174 | 0}亿人,其中该数据集的容量为${analysisResultInfo?.dataSetNum || 0}万, | ||
| 175 | 占总人口的比例为${analysisResultInfo.patientPopulationRate || | ||
| 176 | 0}%,m值取推荐值150,数据集包含熟人的概率为${analysisResultInfo.randomAcquaintancePr || 0}。` }} | ||
| 177 | </div> | ||
| 178 | </div> | ||
| 179 | <div class="table-two border"> | ||
| 180 | <div class="result-title">数据泄露概率</div> | ||
| 181 | <div class="result-title-desc"> | ||
| 182 | <div>对于安全和隐私控制能力评估为低的情况,推荐将数据泄露概率设定为0.55。</div> | ||
| 183 | <div>对于安全和隐私控制能力评估为中的情况,推荐将数据泄露概率设定为0.27。</div> | ||
| 184 | <div>对于安全和隐私控制能力评估为高的情况,推荐将数据泄露概率设定为 0.14。</div> | ||
| 185 | <div>{{ `数据接收方的安全和隐私控制能力为高,按照推荐值将数据泄露概率设定为${analysisResultInfo.dataBreachPr || 0}。` }}</div> | ||
| 186 | </div> | ||
| 187 | </div> | ||
| 188 | </div> | ||
| 189 | </div> | ||
| 190 | </template> | ||
| 191 | <script lang="ts" setup name="anonResultAnalysis"> | ||
| 192 | import { TableColumnWidth } from '@/utils/enum'; | ||
| 193 | import Moment from 'moment'; | ||
| 194 | |||
| 195 | defineProps({ | ||
| 196 | showTitle: { | ||
| 197 | type: Boolean, | ||
| 198 | default: false, | ||
| 199 | }, | ||
| 200 | analysisResultInfo: { | ||
| 201 | type: Object, | ||
| 202 | default: {}, | ||
| 203 | }, | ||
| 204 | pageInfo: { | ||
| 205 | type: Object, | ||
| 206 | default: {}, | ||
| 207 | }, | ||
| 208 | analysisResultLoading: { | ||
| 209 | type: Boolean, | ||
| 210 | default: false, | ||
| 211 | }, | ||
| 212 | oldAnonTaskValueInfo: { | ||
| 213 | type: Object, | ||
| 214 | default: {}, | ||
| 215 | }, | ||
| 216 | resultData: { | ||
| 217 | type: Array, | ||
| 218 | default: [] | ||
| 219 | }, | ||
| 220 | analysisResultTableFields: { | ||
| 221 | type: Array, | ||
| 222 | default: [], | ||
| 223 | }, | ||
| 224 | originResultTableFieldColumn: { | ||
| 225 | type: Object, | ||
| 226 | default: {}, | ||
| 227 | }, | ||
| 228 | containerWidth: { | ||
| 229 | type: Number, | ||
| 230 | default: 1200, | ||
| 231 | }, | ||
| 232 | }) | ||
| 233 | |||
| 234 | const emits = defineEmits([ | ||
| 235 | "pageChange" | ||
| 236 | ]); | ||
| 237 | |||
| 238 | const getTextAlign = (field) => { | ||
| 239 | if (field.dataType === 'decimal' || field.dataType === 'int') { | ||
| 240 | return 'right'; | ||
| 241 | } | ||
| 242 | return 'left' | ||
| 243 | } | ||
| 244 | |||
| 245 | const formatterPreviewDate = (row, info) => { | ||
| 246 | let enName = info.enName; | ||
| 247 | let v = row[enName]; | ||
| 248 | if (v === 0) { | ||
| 249 | return v; | ||
| 250 | } | ||
| 251 | if (!v || v == 'null') { | ||
| 252 | return '--'; | ||
| 253 | } | ||
| 254 | if (info.dataType === 'datetime') { | ||
| 255 | return Moment(v).format('YYYY-MM-DD HH:mm:ss'); | ||
| 256 | } | ||
| 257 | if (info.dataType === 'date') { | ||
| 258 | if (isNaN(<any>(new Date(v)))) { | ||
| 259 | return Moment(parseInt(v)).format('YYYY-MM-DD'); | ||
| 260 | } else { | ||
| 261 | return Moment(v).format('YYYY-MM-DD'); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | return v; | ||
| 265 | }; | ||
| 266 | |||
| 267 | /** 内部故意攻击概率的表格 */ | ||
| 268 | const innerResultData = ref([{ | ||
| 269 | guid: '1', | ||
| 270 | level: '高', | ||
| 271 | competencyLevel: '低', | ||
| 272 | value: '0.05' | ||
| 273 | }, { | ||
| 274 | guid: '2', | ||
| 275 | level: '高', | ||
| 276 | competencyLevel: '中', | ||
| 277 | tooltip: true, | ||
| 278 | value: '0.1' | ||
| 279 | }, { | ||
| 280 | guid: '3', | ||
| 281 | level: '高', | ||
| 282 | competencyLevel: '高', | ||
| 283 | tooltip: true, | ||
| 284 | value: '0.2' | ||
| 285 | }, { | ||
| 286 | guid: '4', | ||
| 287 | level: '中', | ||
| 288 | competencyLevel: '低', | ||
| 289 | value: '0.2' | ||
| 290 | }, { | ||
| 291 | guid: '5', | ||
| 292 | level: '中', | ||
| 293 | competencyLevel: '中', | ||
| 294 | value: '0.3' | ||
| 295 | }, { | ||
| 296 | guid: '6', | ||
| 297 | level: '中', | ||
| 298 | competencyLevel: '高', | ||
| 299 | value: '0.4' | ||
| 300 | }, { | ||
| 301 | guid: '7', | ||
| 302 | level: '低', | ||
| 303 | competencyLevel: '低', | ||
| 304 | value: '0.4' | ||
| 305 | }, { | ||
| 306 | guid: '8', | ||
| 307 | level: '低', | ||
| 308 | competencyLevel: '中', | ||
| 309 | value: '0.5' | ||
| 310 | }, { | ||
| 311 | guid: '9', | ||
| 312 | level: '低', | ||
| 313 | competencyLevel: '高', | ||
| 314 | value: '0.6' | ||
| 315 | }]); | ||
| 316 | |||
| 317 | const arrayInnerSpanMethod = ({ row, column, rowIndex, columnIndex }) => { | ||
| 318 | if (columnIndex > 0) { | ||
| 319 | return [1, 1]; | ||
| 320 | } | ||
| 321 | |||
| 322 | // 查找当前字段值相同的连续行 | ||
| 323 | let startRow = rowIndex; | ||
| 324 | let endRow = rowIndex; | ||
| 325 | let field = 'level'; | ||
| 326 | // 向前查找 | ||
| 327 | while (startRow > 0 && innerResultData.value[startRow - 1][field] === innerResultData.value[rowIndex][field]) { | ||
| 328 | startRow--; | ||
| 329 | } | ||
| 330 | |||
| 331 | // 向后查找 | ||
| 332 | while (endRow < innerResultData.value.length - 1 && innerResultData.value[endRow + 1][field] === innerResultData.value[rowIndex][field]) { | ||
| 333 | endRow++; | ||
| 334 | } | ||
| 335 | |||
| 336 | // 如果当前行不是相同值组的起始行,则不显示 | ||
| 337 | if (startRow !== rowIndex) { | ||
| 338 | return [0, 0]; | ||
| 339 | } | ||
| 340 | |||
| 341 | // 返回合并的行数 | ||
| 342 | const rowspan = endRow - startRow + 1; | ||
| 343 | return [rowspan, 1]; | ||
| 344 | } | ||
| 345 | |||
| 346 | const handleInnerCellClass = ({ row, column, rowIndex, columnIndex }) => { | ||
| 347 | if (rowIndex == 0 && columnIndex > 0) { | ||
| 348 | return 'cell-tooltip-bg'; | ||
| 349 | } | ||
| 350 | return ''; | ||
| 351 | } | ||
| 352 | |||
| 353 | const pageChange = (info) => { | ||
| 354 | emits('pageChange', info); | ||
| 355 | } | ||
| 356 | |||
| 357 | </script> | ||
| 358 | |||
| 359 | <style lang="scss" scoped> | ||
| 360 | .analysis-result-main { | ||
| 361 | min-height: 250px; | ||
| 362 | |||
| 363 | .value-desc { | ||
| 364 | font-size: 14px; | ||
| 365 | color: #212121; | ||
| 366 | line-height: 21px; | ||
| 367 | } | ||
| 368 | |||
| 369 | .result-title { | ||
| 370 | font-size: 16px; | ||
| 371 | color: #212121; | ||
| 372 | line-height: 24px; | ||
| 373 | font-weight: 600; | ||
| 374 | margin-bottom: 6px; | ||
| 375 | } | ||
| 376 | |||
| 377 | .result-title-h1 { | ||
| 378 | color: #212121; | ||
| 379 | font-weight: 600; | ||
| 380 | font-size: 24px; | ||
| 381 | text-align: center; | ||
| 382 | line-height: 36px; | ||
| 383 | margin-top: 12px; | ||
| 384 | } | ||
| 385 | |||
| 386 | .result-title-desc { | ||
| 387 | color: #666; | ||
| 388 | font-size: 14px; | ||
| 389 | line-height: 21px; | ||
| 390 | margin-top: 12px; | ||
| 391 | } | ||
| 392 | |||
| 393 | .kpi-content { | ||
| 394 | display: flex; | ||
| 395 | flex-direction: row; | ||
| 396 | column-gap: 12px; | ||
| 397 | row-gap: 12px; | ||
| 398 | flex-wrap: wrap; | ||
| 399 | margin-bottom: 20px; | ||
| 400 | } | ||
| 401 | |||
| 402 | .border-content { | ||
| 403 | height: 76px; | ||
| 404 | display: flex; | ||
| 405 | flex-direction: column; | ||
| 406 | align-items: left; | ||
| 407 | padding-left: 16px; | ||
| 408 | justify-content: center; | ||
| 409 | border: 1px solid #d9d9d9; | ||
| 410 | width: calc(20% - 8px); | ||
| 411 | min-width: 228px; | ||
| 412 | border-radius: 2px; | ||
| 413 | padding-left: 16px; | ||
| 414 | |||
| 415 | .number { | ||
| 416 | font-weight: 700; | ||
| 417 | font-size: 20px; | ||
| 418 | color: #212121; | ||
| 419 | line-height: 30px; | ||
| 420 | margin-top: 2px; | ||
| 421 | |||
| 422 | &.score-color { | ||
| 423 | color: #FF5F1F; | ||
| 424 | } | ||
| 425 | } | ||
| 426 | |||
| 427 | .text { | ||
| 428 | font-size: 14px; | ||
| 429 | line-height: 21px; | ||
| 430 | color: #666666; | ||
| 431 | display: flex; | ||
| 432 | |||
| 433 | .el-icon { | ||
| 434 | color: #b2b2b2; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | .result-table-desc { | ||
| 440 | font-size: 14px; | ||
| 441 | color: #999999; | ||
| 442 | line-height: 21px; | ||
| 443 | } | ||
| 444 | |||
| 445 | .row-two-main { | ||
| 446 | margin-top: 18px; | ||
| 447 | display: flex; | ||
| 448 | |||
| 449 | .table-one { | ||
| 450 | width: 586px; | ||
| 451 | |||
| 452 | &.border { | ||
| 453 | border: 1px solid #d9d9d9; | ||
| 454 | padding: 14px 18px 18px; | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | .table-two { | ||
| 459 | margin-left: 20px; | ||
| 460 | width: calc(100% - 606px); | ||
| 461 | |||
| 462 | &.border { | ||
| 463 | border: 1px solid #d9d9d9; | ||
| 464 | padding: 14px 18px 18px; | ||
| 465 | } | ||
| 466 | } | ||
| 467 | } | ||
| 468 | } | ||
| 469 | |||
| 470 | .empty-content { | ||
| 471 | display: flex; | ||
| 472 | align-items: center; | ||
| 473 | justify-content: center; | ||
| 474 | height: 316px; | ||
| 475 | width: 100%; | ||
| 476 | flex-direction: column; | ||
| 477 | |||
| 478 | .empty-text { | ||
| 479 | font-size: 14px; | ||
| 480 | color: #b2b2b2; | ||
| 481 | } | ||
| 482 | } | ||
| 483 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
File moved
| 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: "orderNum", width: 90, align: "center" }, | ||
| 56 | { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME }, | ||
| 57 | { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME }, | ||
| 58 | ], | ||
| 59 | data: [], | ||
| 60 | page: { | ||
| 61 | type: "normal", | ||
| 62 | rows: 0, | ||
| 63 | ...page.value, | ||
| 64 | }, | ||
| 65 | actionInfo: { | ||
| 66 | label: "操作", | ||
| 67 | type: "btn", | ||
| 68 | width: 120, | ||
| 69 | fixed: 'right', | ||
| 70 | btns: (scope) => { | ||
| 71 | let btnsArr: any = []; | ||
| 72 | btnsArr.push({ | ||
| 73 | label: "编辑", value: "edit", click: (scope) => { | ||
| 74 | currTableData.value = scope.row; | ||
| 75 | getLabelDetail(scope.row.guid).then((res: any) => { | ||
| 76 | if (res?.code == proxy.$passCode) { | ||
| 77 | const detail = res.data || {}; | ||
| 78 | currTableData.value = Object.assign({}, currTableData.value, detail); | ||
| 79 | newCreateLabelFormItems.value.forEach(item => { | ||
| 80 | if (item.children?.length) { | ||
| 81 | item.children.forEach(child => child.default = detail[child.field]); | ||
| 82 | } else { | ||
| 83 | item.default = detail[item.field]; | ||
| 84 | } | ||
| 85 | item.default = detail[item.field]; | ||
| 86 | }); | ||
| 87 | let labelRuleField = detail.labelRuleField || {}; | ||
| 88 | matchChValue.value.value = labelRuleField.matchChValue; | ||
| 89 | matchChValue.value.disabled = false; | ||
| 90 | matchEnValue.value.value = labelRuleField.matchEnValue; | ||
| 91 | matchEnValue.value.disabled = false; | ||
| 92 | formRows.value = labelRuleField.vagueMatchRule || [{ matchValue: '', position: '', name: '', disabled: false }]; | ||
| 93 | tabsInfo.value.activeName = 'labelRuleField'; | ||
| 94 | let labelRuleContent = detail.labelRuleContent || {}; | ||
| 95 | |||
| 96 | ruleContentFormItems.value[1].visible = labelRuleContent.ruleType == 1; | ||
| 97 | ruleContentFormItems.value[2].visible = labelRuleContent.ruleType == 2; | ||
| 98 | ruleContentFormItems.value.forEach(item => { | ||
| 99 | item.default = labelRuleContent[item.field]; | ||
| 100 | if (item.field == 'regularTestData') { | ||
| 101 | item.validateMsg = null; | ||
| 102 | } | ||
| 103 | }); | ||
| 104 | |||
| 105 | newCreateLabelDialogInfo.value.title = '编辑标签'; | ||
| 106 | newCreateLabelDialogInfo.value.visible = true; | ||
| 107 | newCreateLabelDialogInfo.value.type = 'update'; | ||
| 108 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 109 | } else { | ||
| 110 | proxy.$ElMessage({ | ||
| 111 | type: 'error', | ||
| 112 | message: res.msg, | ||
| 113 | }) | ||
| 114 | } | ||
| 115 | }); | ||
| 116 | } | ||
| 117 | }) | ||
| 118 | btnsArr.push({ | ||
| 119 | label: "删除", value: "delete", disabled: scope.row.bizState == 'Y', click: (scope) => { | ||
| 120 | proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => { | ||
| 121 | let guids = [scope.row.guid]; | ||
| 122 | deleteLabel(guids).then((res: any) => { | ||
| 123 | if (res?.code == proxy.$passCode) { | ||
| 124 | getTableData(); | ||
| 125 | proxy.$ElMessage({ | ||
| 126 | type: "success", | ||
| 127 | message: "删除成功", | ||
| 128 | }); | ||
| 129 | } else { | ||
| 130 | proxy.$ElMessage({ | ||
| 131 | type: "error", | ||
| 132 | message: res.msg, | ||
| 133 | }); | ||
| 134 | } | ||
| 135 | }); | ||
| 136 | }) | ||
| 137 | } | ||
| 138 | }); | ||
| 139 | return btnsArr | ||
| 140 | }, | ||
| 141 | }, | ||
| 142 | loading: false | ||
| 143 | }) | ||
| 144 | |||
| 145 | const toSearch = (val: any, clear: boolean = false) => { | ||
| 146 | if (clear) { | ||
| 147 | searchItemList.value.map((item) => (item.default = "")); | ||
| 148 | page.value.labelName = ''; | ||
| 149 | } else { | ||
| 150 | page.value.labelName = val.labelName; | ||
| 151 | } | ||
| 152 | getTableData(); | ||
| 153 | }; | ||
| 154 | |||
| 155 | const getTableData = () => { | ||
| 156 | tableInfo.value.loading = true | ||
| 157 | getDataLabelList({ | ||
| 158 | pageIndex: page.value.curr, | ||
| 159 | pageSize: page.value.limit, | ||
| 160 | labelName: page.value.labelName | ||
| 161 | }).then((res: any) => { | ||
| 162 | if (res?.code == proxy.$passCode) { | ||
| 163 | const data = res.data || {} | ||
| 164 | tableInfo.value.data = data.records || [] | ||
| 165 | tableInfo.value.page.limit = data.pageSize | ||
| 166 | tableInfo.value.page.curr = data.pageIndex | ||
| 167 | tableInfo.value.page.rows = data.totalRows | ||
| 168 | } else { | ||
| 169 | proxy.$ElMessage({ | ||
| 170 | type: 'error', | ||
| 171 | message: res.msg, | ||
| 172 | }) | ||
| 173 | } | ||
| 174 | tableInfo.value.loading = false | ||
| 175 | }) | ||
| 176 | }; | ||
| 177 | |||
| 178 | const tableSwitchBeforeChange = (scope, field, callback) => { | ||
| 179 | const msg = `确定【${scope.row[field] == 'Y' ? '禁用' : '启用'}】${scope.row.labelName}?` | ||
| 180 | proxy.$openMessageBox(msg, () => { | ||
| 181 | const state = scope.row[field] == 'Y' ? 'N' : 'Y' | ||
| 182 | const result = tableSwitchChange(state, scope, field) | ||
| 183 | callback(result) | ||
| 184 | }, () => { | ||
| 185 | callback(false) | ||
| 186 | }); | ||
| 187 | } | ||
| 188 | |||
| 189 | const tableSwitchChange = (val, scope, field) => { | ||
| 190 | return new Promise((resolve, reject) => { | ||
| 191 | let params = { | ||
| 192 | guid: scope.row.guid, | ||
| 193 | bizState: val | ||
| 194 | } | ||
| 195 | updateLabelState(params).then((res: any) => { | ||
| 196 | if (res?.code == proxy.$passCode && res.data) { | ||
| 197 | getTableData(); | ||
| 198 | proxy.$ElMessage({ | ||
| 199 | type: "success", | ||
| 200 | message: `${scope.row.labelName}【${val == 'Y' ? '启用' : '禁用'}】成功`, | ||
| 201 | }); | ||
| 202 | resolve(true) | ||
| 203 | } else { | ||
| 204 | proxy.$ElMessage({ | ||
| 205 | type: "error", | ||
| 206 | message: res.msg, | ||
| 207 | }); | ||
| 208 | reject(false) | ||
| 209 | } | ||
| 210 | }).catch(() => { | ||
| 211 | reject(false) | ||
| 212 | }) | ||
| 213 | }) | ||
| 214 | } | ||
| 215 | |||
| 216 | const tablePageChange = (info) => { | ||
| 217 | page.value.curr = Number(info.curr); | ||
| 218 | page.value.limit = Number(info.limit); | ||
| 219 | getTableData(); | ||
| 220 | }; | ||
| 221 | |||
| 222 | const newCreateLabelFormItems = ref<any>([{ | ||
| 223 | label: '标签名称', | ||
| 224 | type: 'input', | ||
| 225 | placeholder: '请输入', | ||
| 226 | field: 'labelName', | ||
| 227 | default: '', | ||
| 228 | required: true, | ||
| 229 | filterable: true, | ||
| 230 | clearable: true, | ||
| 231 | visible: true, | ||
| 232 | }, | ||
| 233 | { | ||
| 234 | label: ' ', | ||
| 235 | type: 'select-group', | ||
| 236 | placeholder: '请选择', | ||
| 237 | field: 'labelgroup', | ||
| 238 | children: [{ | ||
| 239 | label: '标签类型', | ||
| 240 | type: 'select', | ||
| 241 | placeholder: '请选择', | ||
| 242 | field: 'labelTypeCode', | ||
| 243 | default: '', | ||
| 244 | options: labelTypeList.value, | ||
| 245 | props: { | ||
| 246 | label: "label", | ||
| 247 | value: "value", | ||
| 248 | }, | ||
| 249 | required: true, | ||
| 250 | filterable: true, | ||
| 251 | clearable: true, | ||
| 252 | visible: true, | ||
| 253 | }, { | ||
| 254 | label: '界面排序', | ||
| 255 | type: 'input', | ||
| 256 | placeholder: '请输入', | ||
| 257 | field: 'orderNum', | ||
| 258 | maxlength: 6, | ||
| 259 | regexp: /\D/g, | ||
| 260 | required: false, | ||
| 261 | clearable: true, | ||
| 262 | }], | ||
| 263 | }, { | ||
| 264 | type: 'radio-group', | ||
| 265 | label: '规则配置', | ||
| 266 | field: 'matchType', | ||
| 267 | default: 1, | ||
| 268 | required: false, | ||
| 269 | block: true, | ||
| 270 | options: [ | ||
| 271 | { label: '字段识别和内容识别满足其一即可', value: 1 }, | ||
| 272 | { label: '字段识别和内容识别需满足全部', value: 2 }, | ||
| 273 | ], | ||
| 274 | }]); | ||
| 275 | |||
| 276 | const newCreateLabelFormRules = ref({ | ||
| 277 | labelName: [required('请输入标签名称')], | ||
| 278 | labelTypeCode: [required('请选择标签类型')] | ||
| 279 | }); | ||
| 280 | |||
| 281 | const ruleContentFormRef = ref(); | ||
| 282 | |||
| 283 | const newCreateLabelDialogInfo = ref({ | ||
| 284 | visible: false, | ||
| 285 | size: 600, | ||
| 286 | title: "添加标签", | ||
| 287 | type: "", | ||
| 288 | formInfo: { | ||
| 289 | id: "label-form", | ||
| 290 | items: newCreateLabelFormItems.value, | ||
| 291 | rules: newCreateLabelFormRules.value, | ||
| 292 | }, | ||
| 293 | submitBtnLoading: false, | ||
| 294 | btns: { | ||
| 295 | cancel: () => { | ||
| 296 | newCreateLabelDialogInfo.value.visible = false; | ||
| 297 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 298 | }, | ||
| 299 | submit: (btn, info) => { | ||
| 300 | // 需要验证两个 | ||
| 301 | let validateRuleField = (showMsg = true) => { | ||
| 302 | if (!matchChValue.value.value && !matchEnValue.value.value && (!formRows.value.length || (formRows.value.length == 1 && !formRows.value[0].matchValue && | ||
| 303 | !formRows.value[0].position && !formRows.value[0].name))) { | ||
| 304 | showMsg && proxy.$ElMessage.error('字段识别匹配规则不能为空'); | ||
| 305 | return false; | ||
| 306 | } | ||
| 307 | for (let i = 0; i < formRows.value.length; i++) { | ||
| 308 | const row = formRows.value[i]; | ||
| 309 | // 如果某一条数据的 matchValue, position, name 都为空,则跳过,不校验 | ||
| 310 | if (!row.matchValue && !row.position && !row.name) { | ||
| 311 | continue; // 如果全为空,跳过这一行的校验 | ||
| 312 | } | ||
| 313 | if (!row.matchValue || !row.position || !row.name) { | ||
| 314 | showMsg && proxy.$ElMessage.error('请填写完整的模糊匹配规则'); | ||
| 315 | return false; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | return true; | ||
| 319 | } | ||
| 320 | let submitLabel = () => { | ||
| 321 | let params = Object.assign({}, info, { | ||
| 322 | bizState: 'Y', | ||
| 323 | orderNum: info.orderNum != null ? parseInt(info.orderNum) : null, | ||
| 324 | labelRuleField: { | ||
| 325 | matchChValue: matchChValue.value.value, | ||
| 326 | matchEnValue: matchEnValue.value.value, | ||
| 327 | vagueMatchRule: formRows.value?.filter(f => f.matchValue!= '' || f.position!= '' || f.name != '') | ||
| 328 | }, | ||
| 329 | labelRuleContent: ruleContentFormRef.value.formInline | ||
| 330 | }); | ||
| 331 | newCreateLabelDialogInfo.value.submitBtnLoading = true; | ||
| 332 | if (newCreateLabelDialogInfo.value.type == 'add') { | ||
| 333 | saveLabel(params).then((res: any) => { | ||
| 334 | if (res?.code == proxy.$passCode) { | ||
| 335 | proxy.$ElMessage.success('标签新建成功'); | ||
| 336 | newCreateLabelDialogInfo.value.visible = false; | ||
| 337 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 338 | page.value.curr = 1; | ||
| 339 | getTableData(); | ||
| 340 | } else { | ||
| 341 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 342 | proxy.$ElMessage.error(res.msg); | ||
| 343 | } | ||
| 344 | }).catch(() => { | ||
| 345 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 346 | }); | ||
| 347 | } else { | ||
| 348 | newCreateLabelDialogInfo.value.submitBtnLoading = true; | ||
| 349 | params.guid = currTableData.value.guid; | ||
| 350 | params.labelRuleContent.guid = currTableData.value.labelRuleContent?.guid; | ||
| 351 | params.labelRuleField.guid = currTableData.value.labelRuleField?.guid; | ||
| 352 | updateLabel(params).then((res: any) => { | ||
| 353 | if (res?.code == proxy.$passCode) { | ||
| 354 | proxy.$ElMessage.success('标签编辑成功'); | ||
| 355 | newCreateLabelDialogInfo.value.visible = false; | ||
| 356 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 357 | getTableData(); | ||
| 358 | } else { | ||
| 359 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 360 | proxy.$ElMessage.error(res.msg); | ||
| 361 | } | ||
| 362 | }).catch(() => { | ||
| 363 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 364 | }); | ||
| 365 | } | ||
| 366 | } | ||
| 367 | if (info.matchType == 2) { | ||
| 368 | if (tabsInfo.value.activeName == 'labelRuleField') { | ||
| 369 | if (!validateRuleField()) { | ||
| 370 | return; | ||
| 371 | } | ||
| 372 | ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => { | ||
| 373 | if (valid) { | ||
| 374 | submitLabel(); | ||
| 375 | } else { | ||
| 376 | tabsInfo.value.activeName = 'labelRuleContent'; | ||
| 377 | } | ||
| 378 | }) | ||
| 379 | } else { | ||
| 380 | ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => { | ||
| 381 | if (valid) { | ||
| 382 | if (!validateRuleField()) { | ||
| 383 | tabsInfo.value.activeName = 'labelRuleField'; | ||
| 384 | return; | ||
| 385 | } | ||
| 386 | submitLabel(); | ||
| 387 | } | ||
| 388 | }) | ||
| 389 | } | ||
| 390 | } else { //只需匹配一项即可。 | ||
| 391 | if (tabsInfo.value.activeName == 'labelRuleField') { //先验证字段识别 | ||
| 392 | if (!matchChValue.value.value && !matchEnValue.value.value && (!formRows.value.length || (formRows.value.length == 1 && (!formRows.value[0].matchValue && | ||
| 393 | !formRows.value[0].position && !formRows.value[0].name)))) { // 没有配置字段识别内容 | ||
| 394 | ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => { | ||
| 395 | if (valid) { | ||
| 396 | submitLabel(); | ||
| 397 | } else { | ||
| 398 | proxy.$ElMessage.error('字段识别和内容识别不能同时为空'); | ||
| 399 | return; | ||
| 400 | } | ||
| 401 | }) | ||
| 402 | } else { | ||
| 403 | ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => { | ||
| 404 | if (valid) { | ||
| 405 | submitLabel(); //只需要满足一个即可。 | ||
| 406 | } else { | ||
| 407 | ruleContentFormRef.value?.ruleFormRef?.clearValidate(['builtInRuleCode', 'regularExpression', 'hitRate']);// 先清除验证 | ||
| 408 | // 去 验证 字段识别 | ||
| 409 | if (!validateRuleField(true)) { | ||
| 410 | return; | ||
| 411 | } | ||
| 412 | submitLabel(); | ||
| 413 | } | ||
| 414 | }) | ||
| 415 | } | ||
| 416 | } else { | ||
| 417 | ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => { | ||
| 418 | if (valid) { | ||
| 419 | submitLabel(); //只需要满足一个即可。 | ||
| 420 | } else { | ||
| 421 | if (!validateRuleField(false)) { | ||
| 422 | return; | ||
| 423 | } | ||
| 424 | submitLabel(); | ||
| 425 | } | ||
| 426 | }) | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | } | ||
| 431 | }); | ||
| 432 | |||
| 433 | const formRows = ref([ | ||
| 434 | { matchValue: '', position: '', name: '', disabled: false }, // 初始行 | ||
| 435 | ]); | ||
| 436 | |||
| 437 | // 位置选项 | ||
| 438 | const positionOptions = [ | ||
| 439 | { label: '前面', value: 'B' }, | ||
| 440 | { label: '后面', value: 'A' }, | ||
| 441 | { label: '任意位置', value: 'C' }, | ||
| 442 | ]; | ||
| 443 | // 语言options | ||
| 444 | const languageOptions = [ | ||
| 445 | { label: '中文名', value: 'chName' }, | ||
| 446 | { label: '英文名', value: 'enName' }, | ||
| 447 | ]; | ||
| 448 | |||
| 449 | // 新增行 | ||
| 450 | const addRow = () => { | ||
| 451 | formRows.value.push({ matchValue: '', position: '', name: '', disabled: false }); | ||
| 452 | }; | ||
| 453 | |||
| 454 | // 删除行 | ||
| 455 | const deleteRow = (index: number) => { | ||
| 456 | formRows.value.splice(index, 1); | ||
| 457 | }; | ||
| 458 | const matchChValue = ref({ | ||
| 459 | value: '', | ||
| 460 | disabled: false | ||
| 461 | }) | ||
| 462 | const matchEnValue = ref({ | ||
| 463 | value: '', | ||
| 464 | disabled: false | ||
| 465 | }) | ||
| 466 | |||
| 467 | const tabsInfo = ref({ | ||
| 468 | activeName: 'labelRuleField', | ||
| 469 | tabs: [ | ||
| 470 | { label: '字段识别', name: 'labelRuleField' }, | ||
| 471 | { label: '内容识别', name: 'labelRuleContent' }, | ||
| 472 | ] | ||
| 473 | }); | ||
| 474 | |||
| 475 | const tabChange = (val) => { | ||
| 476 | tabsInfo.value.activeName = val; | ||
| 477 | } | ||
| 478 | |||
| 479 | const handleCreate = () => { | ||
| 480 | newCreateLabelFormItems.value.forEach(item => { | ||
| 481 | if (item.children?.length) { | ||
| 482 | item.children[0].default = '' | ||
| 483 | item.children[1].default = '' | ||
| 484 | } else { | ||
| 485 | if (item.field == 'matchType') { | ||
| 486 | item.default = 1; | ||
| 487 | } else { | ||
| 488 | item.default = ''; | ||
| 489 | } | ||
| 490 | } | ||
| 491 | }); | ||
| 492 | matchChValue.value.value = ''; | ||
| 493 | matchChValue.value.disabled = false; | ||
| 494 | matchEnValue.value.value = ''; | ||
| 495 | matchEnValue.value.disabled = false; | ||
| 496 | formRows.value = [{ matchValue: '', position: '', name: '', disabled: false }]; | ||
| 497 | |||
| 498 | tabsInfo.value.activeName = 'labelRuleField'; | ||
| 499 | |||
| 500 | ruleContentFormItems.value[1].visible = true; | ||
| 501 | ruleContentFormItems.value[2].visible = false; | ||
| 502 | ruleContentFormItems.value.forEach(item => { | ||
| 503 | item.default = item.field == 'ruleType' ? 1 : ''; | ||
| 504 | if (item.field == 'regularTestData') { | ||
| 505 | item.validateMsg = null; | ||
| 506 | } | ||
| 507 | }); | ||
| 508 | |||
| 509 | newCreateLabelDialogInfo.value.title = '添加标签'; | ||
| 510 | newCreateLabelDialogInfo.value.visible = true; | ||
| 511 | newCreateLabelDialogInfo.value.type = 'add'; | ||
| 512 | newCreateLabelDialogInfo.value.submitBtnLoading = false; | ||
| 513 | } | ||
| 514 | |||
| 515 | /** 规则配置的内容识别内容表单配置 */ | ||
| 516 | const ruleContentFormItems = ref([{ | ||
| 517 | label: '规则类型', | ||
| 518 | type: 'select', | ||
| 519 | placeholder: '请选择', | ||
| 520 | field: 'ruleType', | ||
| 521 | default: 1, | ||
| 522 | options: [{ | ||
| 523 | value: 1, | ||
| 524 | label: '内置规则' | ||
| 525 | }, { | ||
| 526 | value: 2, | ||
| 527 | label: '自定义规则' | ||
| 528 | }], | ||
| 529 | props: { | ||
| 530 | label: "label", | ||
| 531 | value: "value", | ||
| 532 | }, | ||
| 533 | required: true, | ||
| 534 | filterable: true, | ||
| 535 | clearable: false, | ||
| 536 | block: true, | ||
| 537 | visible: true, | ||
| 538 | }, { | ||
| 539 | label: '内置规则', | ||
| 540 | type: 'select', | ||
| 541 | placeholder: '请选择', | ||
| 542 | field: 'builtInRuleCode', | ||
| 543 | default: '', | ||
| 544 | options: builtInRuleList.value, | ||
| 545 | props: { | ||
| 546 | label: "label", | ||
| 547 | value: "value", | ||
| 548 | }, | ||
| 549 | required: true, | ||
| 550 | filterable: true, | ||
| 551 | clearable: true, | ||
| 552 | block: true, | ||
| 553 | visible: true, | ||
| 554 | }, { | ||
| 555 | label: '规则表达式', | ||
| 556 | type: 'input', | ||
| 557 | placeholder: '请输入正则表达式', | ||
| 558 | field: 'regularExpression', | ||
| 559 | default: '', | ||
| 560 | required: true, | ||
| 561 | filterable: true, | ||
| 562 | clearable: true, | ||
| 563 | block: true, | ||
| 564 | visible: false, | ||
| 565 | }, { | ||
| 566 | label: '测试验证', | ||
| 567 | type: 'input', | ||
| 568 | placeholder: '请输入测试数据', | ||
| 569 | field: 'regularTestData', | ||
| 570 | default: '', | ||
| 571 | required: false, | ||
| 572 | clearable: true, | ||
| 573 | col: 'mb8', | ||
| 574 | block: true, | ||
| 575 | visible: true, | ||
| 576 | validateBtn: { | ||
| 577 | value: 'validate', | ||
| 578 | label: '验证', | ||
| 579 | click: () => { | ||
| 580 | let info = ruleContentFormRef.value.formInline; | ||
| 581 | if (info.ruleType == 1 && !info.builtInRuleCode) { | ||
| 582 | proxy.$ElMessage.error('请先选择内置规则'); | ||
| 583 | return; | ||
| 584 | } | ||
| 585 | if (info.ruleType == 2 && !info.regularExpression) { | ||
| 586 | proxy.$ElMessage.error('请先输入规则表达式'); | ||
| 587 | return; | ||
| 588 | } | ||
| 589 | if (!info.regularTestData) { | ||
| 590 | proxy.$ElMessage.error('请先输入测试数据'); | ||
| 591 | return; | ||
| 592 | } | ||
| 593 | let exp = info.ruleType == 1 ? builtInRuleList.value.find(b => b.value == info.builtInRuleCode)?.remarks : info.regularExpression; | ||
| 594 | let result = exp && new RegExp(exp).test(info.regularTestData); | ||
| 595 | ruleContentFormItems.value.forEach(item => { | ||
| 596 | item.default = info[item.field]; | ||
| 597 | if (item.field == 'regularTestData') { | ||
| 598 | item.validateMsg = result ? { | ||
| 599 | status: 'success', | ||
| 600 | msg: '符合识别规则' | ||
| 601 | } : { | ||
| 602 | status: 'error', | ||
| 603 | msg: '不符合识别规则' | ||
| 604 | } | ||
| 605 | } | ||
| 606 | }); | ||
| 607 | } | ||
| 608 | }, | ||
| 609 | validateMsg: <any>null | ||
| 610 | }, { | ||
| 611 | label: '命中率设置(%)', | ||
| 612 | type: 'input', | ||
| 613 | placeholder: '请输入1~100', | ||
| 614 | field: 'hitRate', | ||
| 615 | min: 1, | ||
| 616 | max: 100, | ||
| 617 | inputType: 'integerNumber', | ||
| 618 | default: null, | ||
| 619 | required: true, | ||
| 620 | block: true, | ||
| 621 | clearable: true, | ||
| 622 | visible: true, | ||
| 623 | width: '100px', | ||
| 624 | beforeMsg: '一列数据中的非空数据,大于等于', | ||
| 625 | afterMsg: ' %的数据符合以上识别条件,则认为命中该识别规则。' | ||
| 626 | }]); | ||
| 627 | |||
| 628 | /** 规则配置的内容识别内容表单规则配置 */ | ||
| 629 | const ruleContentFormRules = ref({ | ||
| 630 | builtInRuleCode: [required('请选择内置规则')], | ||
| 631 | regularExpression: [required('请输入正则表达式'), regexpRuleValidate()], | ||
| 632 | hitRate: [required(' ')] | ||
| 633 | }); | ||
| 634 | |||
| 635 | const handleRuleContentSelectChange = (val, row, info) => { | ||
| 636 | if (row.field == 'ruleType') { | ||
| 637 | ruleContentFormItems.value[1].visible = val == 1; | ||
| 638 | ruleContentFormItems.value[2].visible = val == 2; | ||
| 639 | ruleContentFormItems.value.forEach(item => { | ||
| 640 | item.default = info[item.field]; | ||
| 641 | }) | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | const handleRuleContentInputChange = (val, row) => { | ||
| 646 | if (row.field == 'regularTestData') { | ||
| 647 | let item = ruleContentFormItems.value.at(-2); | ||
| 648 | item && (item.validateMsg = null); | ||
| 649 | } | ||
| 650 | } | ||
| 651 | |||
| 652 | onBeforeMount(() => { | ||
| 653 | toSearch({}); | ||
| 654 | getParamsList({ | ||
| 655 | dictType: "标签类型", | ||
| 656 | }).then((res: any) => { | ||
| 657 | if (res?.code == proxy.$passCode) { | ||
| 658 | labelTypeList.value = res.data || []; | ||
| 659 | let item = newCreateLabelFormItems.value.find(item => item.field == 'labelgroup'); | ||
| 660 | item && (item.children[0].options = labelTypeList.value); | ||
| 661 | } else { | ||
| 662 | proxy.$ElMessage.error(res.msg); | ||
| 663 | } | ||
| 664 | }); | ||
| 665 | getParamsList({ | ||
| 666 | dictType: "内置规则", | ||
| 667 | }).then((res: any) => { | ||
| 668 | if (res?.code == proxy.$passCode) { | ||
| 669 | builtInRuleList.value = res.data || []; | ||
| 670 | ruleContentFormItems.value[1].options = builtInRuleList.value; | ||
| 671 | } else { | ||
| 672 | proxy.$ElMessage.error(res.msg); | ||
| 673 | } | ||
| 674 | }) | ||
| 675 | }) | ||
| 676 | |||
| 677 | </script> | ||
| 678 | |||
| 679 | <template> | ||
| 680 | <div class="container_wrap"> | ||
| 681 | <div class="table_tool_wrap"> | ||
| 682 | <!-- 头部搜索 --> | ||
| 683 | <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" /> | ||
| 684 | <div class="tools_btns"> | ||
| 685 | <el-button type="primary" @click="handleCreate">新建</el-button> | ||
| 686 | </div> | ||
| 687 | </div> | ||
| 688 | <div class="table_panel_wrap"> | ||
| 689 | <!-- 右侧标签管理表格 --> | ||
| 690 | <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" | ||
| 691 | @tableSwitchBeforeChange="tableSwitchBeforeChange" /> | ||
| 692 | </div> | ||
| 693 | <!-- 新建编辑标签对话框 --> | ||
| 694 | <Dialog_form ref="dialogLabelFormRef" :dialogConfigInfo="newCreateLabelDialogInfo" class="v-dialog-form"> | ||
| 695 | <template v-slot:default> | ||
| 696 | <Tabs :tabs-info="tabsInfo" @tab-change="tabChange" style="margin-top: -20px;" /> | ||
| 697 | <div v-show="tabsInfo.activeName == 'labelRuleField'"> | ||
| 698 | <div> | ||
| 699 | <div class="dim-label"> | ||
| 700 | <span class="front">精确匹配</span> | ||
| 701 | <el-icon> | ||
| 702 | <Warning /> | ||
| 703 | </el-icon> | ||
| 704 | <span class="tip"> | ||
| 705 | 精确匹配使用英文","分隔每个规则 | ||
| 706 | </span> | ||
| 707 | </div> | ||
| 708 | <div class="v-match"> | ||
| 709 | <el-input v-model="matchChValue.value" maxlength="200" style="width: 272px;height:94px;" show-word-limit | ||
| 710 | type="textarea" class="no-resize" placeholder="请输入字段中文" /> | ||
| 711 | <el-input v-model="matchEnValue.value" maxlength="200" style="width: 272px;height:94px;" show-word-limit | ||
| 712 | type="textarea" class="no-resize" placeholder="请输入字段英文" /> | ||
| 713 | </div> | ||
| 714 | </div> | ||
| 715 | <div class="dim-label" style="margin-top: 16px;"> | ||
| 716 | <span class="front">模糊匹配</span> | ||
| 717 | <el-icon> | ||
| 718 | <Warning /> | ||
| 719 | </el-icon> | ||
| 720 | <span class="tip"> | ||
| 721 | 模糊匹配是或的关系,可配置多个模糊匹配规则 | ||
| 722 | </span> | ||
| 723 | </div> | ||
| 724 | <!-- 渲染行 --> | ||
| 725 | <div v-for="(row, index) in formRows" :key="index" class="match-content-wrapper"> | ||
| 726 | <div class="match-content"> | ||
| 727 | <!-- 位置映射下拉框 --> | ||
| 728 | <el-select v-model="row.name" placeholder="请选择" class="v-select"> | ||
| 729 | <el-option v-for="option in languageOptions" :key="option.value" :label="option.label" | ||
| 730 | :value="option.value" /> | ||
| 731 | </el-select> | ||
| 732 | <el-select v-model="row.position" placeholder="请选择位置" class="v-select"> | ||
| 733 | <el-option v-for="option in positionOptions" :key="option.value" :label="option.label" | ||
| 734 | :value="option.value" /> | ||
| 735 | </el-select> | ||
| 736 | <el-input v-model="row.matchValue" class="v-input" placeholder="请输入匹配值" /> | ||
| 737 | |||
| 738 | <!-- 删除按钮 --> | ||
| 739 | <el-button class="extra-icon" :icon="Delete" @click="deleteRow(index)" circle style="margin-left: 8px;" /> | ||
| 740 | </div> | ||
| 741 | </div> | ||
| 742 | |||
| 743 | <!-- 新增按钮 --> | ||
| 744 | <div class="add-Icon" @click="addRow"> | ||
| 745 | <el-icon class="icon-add" color="#4fa1a4" :size="30"> | ||
| 746 | <CirclePlus /> | ||
| 747 | </el-icon> | ||
| 748 | <span class="word-des">模糊匹配规则</span> | ||
| 749 | </div> | ||
| 750 | </div> | ||
| 751 | <div v-show="tabsInfo.activeName == 'labelRuleContent'"> | ||
| 752 | <Form ref="ruleContentFormRef" :itemList="ruleContentFormItems" formId="rule-content-form" | ||
| 753 | :rules="ruleContentFormRules" @select-change="handleRuleContentSelectChange" | ||
| 754 | @inputChange="handleRuleContentInputChange" /> | ||
| 755 | </div> | ||
| 756 | </template> | ||
| 757 | </Dialog_form> | ||
| 758 | </div> | ||
| 759 | </template> | ||
| 760 | |||
| 761 | <style scoped lang="scss"> | ||
| 762 | .table_tool_wrap { | ||
| 763 | width: 100%; | ||
| 764 | height: 84px !important; | ||
| 765 | padding: 0 8px; | ||
| 766 | |||
| 767 | .tools_btns { | ||
| 768 | padding: 0px 0 0; | ||
| 769 | } | ||
| 770 | } | ||
| 771 | |||
| 772 | .table_panel_wrap { | ||
| 773 | width: 100%; | ||
| 774 | height: calc(100% - 84px); | ||
| 775 | padding: 0px 8px 0; | ||
| 776 | } | ||
| 777 | |||
| 778 | :deep(.v-dialog-form) { | ||
| 779 | .title-label { | ||
| 780 | font-size: 16px; | ||
| 781 | color: #212121; | ||
| 782 | line-height: 24px; | ||
| 783 | font-weight: 600; | ||
| 784 | } | ||
| 785 | |||
| 786 | .el-dialog__body { | ||
| 787 | height: 480px; | ||
| 788 | overflow: auto; | ||
| 789 | } | ||
| 790 | |||
| 791 | .dim-label { | ||
| 792 | height: 10px; | ||
| 793 | display: flex; | ||
| 794 | align-items: center; | ||
| 795 | margin-top: 6px; | ||
| 796 | |||
| 797 | .el-icon svg { | ||
| 798 | height: 16px; | ||
| 799 | width: 16px; | ||
| 800 | color: #999999; | ||
| 801 | } | ||
| 802 | |||
| 803 | .front { | ||
| 804 | margin-right: 16px; | ||
| 805 | } | ||
| 806 | |||
| 807 | .tip { | ||
| 808 | margin-left: 4px; | ||
| 809 | font-size: 12px; | ||
| 810 | color: #999999; | ||
| 811 | } | ||
| 812 | } | ||
| 813 | |||
| 814 | .match-content-wrapper { | ||
| 815 | width: 100%; | ||
| 816 | |||
| 817 | .match-content { | ||
| 818 | display: flex; | ||
| 819 | align-items: center; | ||
| 820 | margin-top: 8px; | ||
| 821 | |||
| 822 | .v-select { | ||
| 823 | margin-right: 8px; | ||
| 824 | width: 33%; | ||
| 825 | } | ||
| 826 | |||
| 827 | .v-input { | ||
| 828 | width: calc(33% - 50px); | ||
| 829 | } | ||
| 830 | |||
| 831 | .extra-icon { | ||
| 832 | transition: opacity 1s; | ||
| 833 | display: none; | ||
| 834 | } | ||
| 835 | |||
| 836 | &:hover { | ||
| 837 | .extra-icon { | ||
| 838 | display: flex; | ||
| 839 | } | ||
| 840 | } | ||
| 841 | } | ||
| 842 | } | ||
| 843 | |||
| 844 | .add-Icon { | ||
| 845 | display: flex; | ||
| 846 | align-items: center; | ||
| 847 | margin-top: 4px; | ||
| 848 | width: 50%; | ||
| 849 | margin-left: -4px; | ||
| 850 | |||
| 851 | .el-icon svg { | ||
| 852 | height: 19px; | ||
| 853 | width: 19px; | ||
| 854 | } | ||
| 855 | |||
| 856 | .word-des { | ||
| 857 | color: #4fa1a4 | ||
| 858 | } | ||
| 859 | } | ||
| 860 | |||
| 861 | .v-match { | ||
| 862 | display: flex; | ||
| 863 | justify-content: space-between; | ||
| 864 | margin-top: 10px; | ||
| 865 | margin-bottom: 10px; | ||
| 866 | } | ||
| 867 | |||
| 868 | .no-resize { | ||
| 869 | height: 94px; | ||
| 870 | |||
| 871 | .el-textarea__inner { | ||
| 872 | min-height: 94px !important; | ||
| 873 | resize: none; | ||
| 874 | } | ||
| 875 | } | ||
| 876 | } | ||
| 877 | </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 | import TableTools from "@/components/Tools/table_tools.vue"; | ||
| 7 | import { commonPageConfig } from '@/components/PageNav/index'; | ||
| 8 | import { TableColumnWidth } from "@/utils/enum"; | ||
| 9 | import { | ||
| 10 | dataSourceTypeList, | ||
| 11 | getAnonTaskList, | ||
| 12 | deleteAnonTask, | ||
| 13 | } from '@/api/modules/dataAnonymization'; | ||
| 14 | import { useValidator } from '@/hooks/useValidator'; | ||
| 15 | import useDataAnonymizationStore from "@/store/modules/dataAnonymization"; | ||
| 16 | |||
| 17 | const anonymizationStore = useDataAnonymizationStore(); | ||
| 18 | const router = useRouter() | ||
| 19 | const { proxy } = getCurrentInstance() as any; | ||
| 20 | const { required } = useValidator(); | ||
| 21 | |||
| 22 | const searchItemList = ref([{ | ||
| 23 | type: "input", | ||
| 24 | label: "", | ||
| 25 | field: "taskName", | ||
| 26 | default: "", | ||
| 27 | placeholder: "数据集名称", | ||
| 28 | clearable: true, | ||
| 29 | }, { | ||
| 30 | type: "select", | ||
| 31 | label: "", | ||
| 32 | field: "dataSource", | ||
| 33 | default: null, | ||
| 34 | options: dataSourceTypeList, | ||
| 35 | placeholder: "数据来源", | ||
| 36 | clearable: true, | ||
| 37 | filterable: true, | ||
| 38 | }]) | ||
| 39 | |||
| 40 | /** 分页及搜索传参信息配置。 */ | ||
| 41 | const page = ref({ | ||
| 42 | ...commonPageConfig, | ||
| 43 | taskName: '', | ||
| 44 | dataSource: null | ||
| 45 | }); | ||
| 46 | |||
| 47 | const tableInfo = ref({ | ||
| 48 | id: 'data-file-table', | ||
| 49 | fields: [ | ||
| 50 | { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" }, | ||
| 51 | { label: "数据集名称", field: "taskName", width: 160 }, | ||
| 52 | { | ||
| 53 | label: "数据来源", field: "dataSource", width: 100, getName: (scope) => { | ||
| 54 | return scope.row.dataSource && dataSourceTypeList.find(f => f.value == scope.row.dataSource)?.label || '--'; | ||
| 55 | } | ||
| 56 | }, | ||
| 57 | { label: "任务状态", field: "sensitiveIdentifyTaskStatus", width: TableColumnWidth.STATE, align: 'center', type: "tag" }, | ||
| 58 | { label: "导出时间", field: "exportTime", width: TableColumnWidth.DATETIME }, | ||
| 59 | { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME }, | ||
| 60 | { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME }, | ||
| 61 | ], | ||
| 62 | data: [], | ||
| 63 | page: { | ||
| 64 | type: "normal", | ||
| 65 | rows: 0, | ||
| 66 | ...page.value, | ||
| 67 | }, | ||
| 68 | loading: false, | ||
| 69 | actionInfo: { | ||
| 70 | label: "操作", | ||
| 71 | type: "btn", | ||
| 72 | width: 230, | ||
| 73 | fixed: 'right', | ||
| 74 | btns: (scope) => { | ||
| 75 | return [{ | ||
| 76 | label: "编辑", value: "edit", click: (scope) => { | ||
| 77 | router.push({ | ||
| 78 | name: 'anonTaskCreate', | ||
| 79 | query: { | ||
| 80 | guid: scope.row.guid, | ||
| 81 | taskName: scope.row.taskName | ||
| 82 | } | ||
| 83 | }); | ||
| 84 | } | ||
| 85 | }, { | ||
| 86 | label: '查看报告', value: 'report', disabled: scope.row.status != 'Y', click: (scope) => { | ||
| 87 | router.push({ | ||
| 88 | name: 'anonResultReportView', | ||
| 89 | query: { | ||
| 90 | guid: scope.row.guid, | ||
| 91 | execGuid: scope.row.lastExecGuid, | ||
| 92 | taskName: scope.row.taskName | ||
| 93 | } | ||
| 94 | }); | ||
| 95 | } | ||
| 96 | }, { | ||
| 97 | label: '查看数据', value: 'view', disabled: scope.row.status != 'Y' || scope.row.handleType == '02', click: (scope) => { | ||
| 98 | router.push({ | ||
| 99 | name: 'anonResultView', | ||
| 100 | query: { | ||
| 101 | guid: scope.row.guid, | ||
| 102 | execGuid: scope.row.lastExecGuid, | ||
| 103 | taskName: scope.row.taskName | ||
| 104 | } | ||
| 105 | }); | ||
| 106 | } | ||
| 107 | }, { | ||
| 108 | label: "删除", value: "delete", click: (scope) => { | ||
| 109 | proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => { | ||
| 110 | let guids = [scope.row.guid]; | ||
| 111 | deleteAnonTask(guids).then((res: any) => { | ||
| 112 | if (res?.code == proxy.$passCode) { | ||
| 113 | page.value.curr = 1; | ||
| 114 | getTableData(); | ||
| 115 | proxy.$ElMessage({ | ||
| 116 | type: "success", | ||
| 117 | message: "删除成功", | ||
| 118 | }); | ||
| 119 | } else { | ||
| 120 | proxy.$ElMessage({ | ||
| 121 | type: "error", | ||
| 122 | message: res.msg, | ||
| 123 | }); | ||
| 124 | } | ||
| 125 | }); | ||
| 126 | }) | ||
| 127 | } | ||
| 128 | }] | ||
| 129 | } | ||
| 130 | } | ||
| 131 | }) | ||
| 132 | |||
| 133 | const toSearch = (val: any, clear: boolean = false) => { | ||
| 134 | if (clear) { | ||
| 135 | searchItemList.value.map((item) => (item.default = "")); | ||
| 136 | page.value.taskName = ''; | ||
| 137 | page.value.dataSource = null; | ||
| 138 | } else { | ||
| 139 | page.value.taskName = val.taskName; | ||
| 140 | page.value.dataSource = val.dataSource; | ||
| 141 | } | ||
| 142 | getTableData(); | ||
| 143 | }; | ||
| 144 | |||
| 145 | const getTableData = () => { | ||
| 146 | tableInfo.value.loading = true | ||
| 147 | getAnonTaskList({ | ||
| 148 | pageIndex: page.value.curr, | ||
| 149 | pageSize: page.value.limit, | ||
| 150 | taskName: page.value.taskName, | ||
| 151 | dataSource: page.value.dataSource | ||
| 152 | }).then((res: any) => { | ||
| 153 | if (res?.code == proxy.$passCode) { | ||
| 154 | const data = res.data || {}; | ||
| 155 | tableInfo.value.data = data.records?.map(d => { | ||
| 156 | d.sensitiveIdentifyTaskStatus = d.status; | ||
| 157 | return d; | ||
| 158 | }) || [] | ||
| 159 | tableInfo.value.page.limit = data.pageSize | ||
| 160 | tableInfo.value.page.curr = data.pageIndex | ||
| 161 | tableInfo.value.page.rows = data.totalRows | ||
| 162 | } else { | ||
| 163 | proxy.$ElMessage({ | ||
| 164 | type: 'error', | ||
| 165 | message: res.msg, | ||
| 166 | }) | ||
| 167 | } | ||
| 168 | tableInfo.value.loading = false | ||
| 169 | }) | ||
| 170 | }; | ||
| 171 | |||
| 172 | const tablePageChange = (info) => { | ||
| 173 | page.value.curr = Number(info.curr); | ||
| 174 | page.value.limit = Number(info.limit); | ||
| 175 | getTableData(); | ||
| 176 | }; | ||
| 177 | |||
| 178 | const handleCreate = () => { | ||
| 179 | router.push({ | ||
| 180 | name: 'anonTaskCreate' | ||
| 181 | }); | ||
| 182 | } | ||
| 183 | |||
| 184 | onBeforeMount(() => { | ||
| 185 | toSearch({}); | ||
| 186 | anonymizationStore?.setIsAnonPageRefresh?.(false); | ||
| 187 | }) | ||
| 188 | |||
| 189 | onActivated(() => { | ||
| 190 | if (anonymizationStore.isAnonPageRefresh) {//如果是首次加载,则不需要调用 | ||
| 191 | page.value.curr = 1; | ||
| 192 | getTableData(); | ||
| 193 | anonymizationStore.setIsAnonPageRefresh(false); | ||
| 194 | } | ||
| 195 | }); | ||
| 196 | |||
| 197 | </script> | ||
| 198 | |||
| 199 | <template> | ||
| 200 | <div class="container_wrap"> | ||
| 201 | <div class="table_tool_wrap"> | ||
| 202 | <!-- 头部搜索 --> | ||
| 203 | <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" /> | ||
| 204 | <div class="tools_btns"> | ||
| 205 | <el-button type="primary" @click="handleCreate">新建</el-button> | ||
| 206 | </div> | ||
| 207 | </div> | ||
| 208 | <div class="table_panel_wrap"> | ||
| 209 | <!-- 右侧标签管理表格 --> | ||
| 210 | <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" /> | ||
| 211 | </div> | ||
| 212 | </div> | ||
| 213 | </template> | ||
| 214 | |||
| 215 | <style lang="scss" scoped> | ||
| 216 | .table_tool_wrap { | ||
| 217 | width: 100%; | ||
| 218 | height: 84px !important; | ||
| 219 | padding: 0 8px; | ||
| 220 | |||
| 221 | .tools_btns { | ||
| 222 | padding: 0px 0 0; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | .table_panel_wrap { | ||
| 227 | width: 100%; | ||
| 228 | height: calc(100% - 84px); | ||
| 229 | padding: 0px 8px 0; | ||
| 230 | } | ||
| 231 | </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 | dataSourceTypeList, | ||
| 12 | deleteSensitiveDataTask, | ||
| 13 | saveSensitiveDataTask, | ||
| 14 | updateSensitiveDataTask, | ||
| 15 | getDatabase, | ||
| 16 | execSensitiveDataTask, | ||
| 17 | } from '@/api/modules/dataAnonymization'; | ||
| 18 | import { useValidator } from '@/hooks/useValidator'; | ||
| 19 | |||
| 20 | const router = useRouter() | ||
| 21 | const { proxy } = getCurrentInstance() as any; | ||
| 22 | const { required } = useValidator(); | ||
| 23 | |||
| 24 | const searchItemList = ref([{ | ||
| 25 | type: "input", | ||
| 26 | label: "", | ||
| 27 | field: "taskName", | ||
| 28 | default: "", | ||
| 29 | placeholder: "任务名称", | ||
| 30 | clearable: true, | ||
| 31 | }, { | ||
| 32 | type: "select", | ||
| 33 | label: "", | ||
| 34 | field: "dataSource", | ||
| 35 | default: null, | ||
| 36 | options: dataSourceTypeList, | ||
| 37 | placeholder: "数据来源", | ||
| 38 | clearable: true, | ||
| 39 | filterable: true, | ||
| 40 | }]) | ||
| 41 | |||
| 42 | /** 分页及搜索传参信息配置。 */ | ||
| 43 | const page = ref({ | ||
| 44 | ...commonPageConfig, | ||
| 45 | taskName: '', | ||
| 46 | dataSource: null | ||
| 47 | }); | ||
| 48 | |||
| 49 | const currTableData = ref(); | ||
| 50 | |||
| 51 | const tableInfo = ref({ | ||
| 52 | id: 'data-file-table', | ||
| 53 | fields: [ | ||
| 54 | { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" }, | ||
| 55 | { label: "任务名称", field: "taskName", width: 170 }, | ||
| 56 | { | ||
| 57 | label: "数据来源", field: "dataSource", width: 100, getName: (scope) => { | ||
| 58 | return scope.row.dataSource && dataSourceTypeList.find(f => f.value == scope.row.dataSource)?.label || '--'; | ||
| 59 | } | ||
| 60 | }, | ||
| 61 | { label: "任务状态", field: "sensitiveIdentifyTaskStatus", width: TableColumnWidth.STATE, align: 'center', type: "tag" }, | ||
| 62 | { label: "执行人", field: "execUserName", width: 100 }, | ||
| 63 | { label: "执行时间", field: "execTime", width: 170 }, | ||
| 64 | { label: "修改人", field: "updateUserName", width: 100 }, | ||
| 65 | { label: "修改时间", field: "updateTime", width: 170 }, | ||
| 66 | ], | ||
| 67 | data: [], | ||
| 68 | page: { | ||
| 69 | type: "normal", | ||
| 70 | rows: 0, | ||
| 71 | ...page.value, | ||
| 72 | }, | ||
| 73 | loading: false, | ||
| 74 | actionInfo: { | ||
| 75 | label: "操作", | ||
| 76 | type: "btn", | ||
| 77 | width: 304, | ||
| 78 | fixed: 'right', | ||
| 79 | btns: (scope) => { | ||
| 80 | return [{ | ||
| 81 | label: '敏感数据查看', value: 'view', disabled: scope.row.status != 'Y', click: (scope) => { | ||
| 82 | router.push({ | ||
| 83 | name: 'sensitiveIdentifyConfig', | ||
| 84 | query: { | ||
| 85 | guid: scope.row.guid, | ||
| 86 | execGuid: scope.row.execGuid, | ||
| 87 | taskName: scope.row.taskName | ||
| 88 | } | ||
| 89 | }); | ||
| 90 | } | ||
| 91 | }, { | ||
| 92 | label: '手动执行', value: 'execute', disabled: scope.row.status == 'R', click: (scope) => { | ||
| 93 | execSensitiveDataTask(scope.row.guid).then((res: any) => { | ||
| 94 | if (res?.code == proxy.$passCode) { | ||
| 95 | getTableData(); | ||
| 96 | proxy.$ElMessage.success('该任务手动执行提交成功'); | ||
| 97 | } else { | ||
| 98 | proxy.$ElMessage.error(res.msg); | ||
| 99 | } | ||
| 100 | }) | ||
| 101 | } | ||
| 102 | }, { | ||
| 103 | label: "编辑", value: "edit", disabled: scope.row.status == 'R', click: (scope) => { | ||
| 104 | let row = scope.row; | ||
| 105 | currTableData.value = row; | ||
| 106 | newCreateTaskDialogInfo.value.visible = true; | ||
| 107 | newCreateTaskDialogInfo.value.title = '编辑数据敏感识别任务'; | ||
| 108 | newCreateTaskDialogInfo.value.type = 'edit'; | ||
| 109 | newCreateTaskFormItems.value[0].default = row.taskName; | ||
| 110 | newCreateTaskFormItems.value[1].default = row.dataSource; | ||
| 111 | newCreateTaskFormItems.value[2].default = row.dataSourceGuid || ''; | ||
| 112 | newCreateTaskFormItems.value[2].visible = row.dataSource == 1; | ||
| 113 | newCreateTaskFormItems.value[3].default = row.filePath || []; | ||
| 114 | newCreateTaskFormItems.value[3].visible = row.dataSource == 2; | ||
| 115 | } | ||
| 116 | }, { | ||
| 117 | label: "删除", value: "delete", disabled: scope.row.status == 'R', click: (scope) => { | ||
| 118 | proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => { | ||
| 119 | let guids = [scope.row.guid]; | ||
| 120 | deleteSensitiveDataTask(guids).then((res: any) => { | ||
| 121 | if (res?.code == proxy.$passCode) { | ||
| 122 | getTableData(); | ||
| 123 | proxy.$ElMessage({ | ||
| 124 | type: "success", | ||
| 125 | message: "删除成功", | ||
| 126 | }); | ||
| 127 | } else { | ||
| 128 | proxy.$ElMessage({ | ||
| 129 | type: "error", | ||
| 130 | message: res.msg, | ||
| 131 | }); | ||
| 132 | } | ||
| 133 | }); | ||
| 134 | }) | ||
| 135 | } | ||
| 136 | }, { | ||
| 137 | label: '日志', value: 'log', click: (scope) => { | ||
| 138 | router.push({ | ||
| 139 | name: 'sensitiveIdentifyTaskExecLog', | ||
| 140 | query: { | ||
| 141 | guid: scope.row.guid, | ||
| 142 | name: scope.row.taskName, | ||
| 143 | } | ||
| 144 | }); | ||
| 145 | } | ||
| 146 | }] | ||
| 147 | } | ||
| 148 | } | ||
| 149 | }) | ||
| 150 | |||
| 151 | const toSearch = (val: any, clear: boolean = false) => { | ||
| 152 | if (clear) { | ||
| 153 | searchItemList.value.map((item) => (item.default = "")); | ||
| 154 | page.value.taskName = ''; | ||
| 155 | page.value.dataSource = null; | ||
| 156 | } else { | ||
| 157 | page.value.taskName = val.taskName; | ||
| 158 | page.value.dataSource = val.dataSource; | ||
| 159 | } | ||
| 160 | getTableData(); | ||
| 161 | }; | ||
| 162 | |||
| 163 | const getTableData = () => { | ||
| 164 | tableInfo.value.loading = true | ||
| 165 | getSensitiveDataTaskList({ | ||
| 166 | pageIndex: page.value.curr, | ||
| 167 | pageSize: page.value.limit, | ||
| 168 | taskName: page.value.taskName, | ||
| 169 | dataSource: page.value.dataSource | ||
| 170 | }).then((res: any) => { | ||
| 171 | if (res?.code == proxy.$passCode) { | ||
| 172 | const data = res.data || {}; | ||
| 173 | tableInfo.value.data = data.records?.map(d => { | ||
| 174 | d.sensitiveIdentifyTaskStatus = d.status; | ||
| 175 | return d; | ||
| 176 | }) || [] | ||
| 177 | tableInfo.value.page.limit = data.pageSize | ||
| 178 | tableInfo.value.page.curr = data.pageIndex | ||
| 179 | tableInfo.value.page.rows = data.totalRows | ||
| 180 | } else { | ||
| 181 | proxy.$ElMessage({ | ||
| 182 | type: 'error', | ||
| 183 | message: res.msg, | ||
| 184 | }) | ||
| 185 | } | ||
| 186 | tableInfo.value.loading = false | ||
| 187 | }) | ||
| 188 | }; | ||
| 189 | |||
| 190 | const tablePageChange = (info) => { | ||
| 191 | page.value.curr = Number(info.curr); | ||
| 192 | page.value.limit = Number(info.limit); | ||
| 193 | getTableData(); | ||
| 194 | }; | ||
| 195 | |||
| 196 | const dataSourceList = ref([]); | ||
| 197 | |||
| 198 | const newCreateTaskFormItems = ref([{ | ||
| 199 | label: '任务名称', | ||
| 200 | type: 'input', | ||
| 201 | placeholder: '请输入', | ||
| 202 | field: 'taskName', | ||
| 203 | maxlength: 15, | ||
| 204 | default: '', | ||
| 205 | required: true, | ||
| 206 | filterable: true, | ||
| 207 | clearable: true, | ||
| 208 | visible: true, | ||
| 209 | }, | ||
| 210 | { | ||
| 211 | label: '数据来源', | ||
| 212 | type: 'select', | ||
| 213 | placeholder: '请选择', | ||
| 214 | field: 'dataSource', | ||
| 215 | default: 1, | ||
| 216 | options: dataSourceTypeList, | ||
| 217 | props: { | ||
| 218 | label: "label", | ||
| 219 | value: "value", | ||
| 220 | }, | ||
| 221 | required: true, | ||
| 222 | filterable: true, | ||
| 223 | clearable: true, | ||
| 224 | visible: true, | ||
| 225 | }, { | ||
| 226 | label: '数据源', | ||
| 227 | type: 'select', | ||
| 228 | placeholder: '请选择', | ||
| 229 | field: 'dataSourceGuid', | ||
| 230 | default: '', | ||
| 231 | options: dataSourceList.value, | ||
| 232 | props: { | ||
| 233 | label: 'databaseNameZh', | ||
| 234 | value: 'guid' | ||
| 235 | }, | ||
| 236 | filterable: true, | ||
| 237 | visible: true, | ||
| 238 | required: true | ||
| 239 | }, { | ||
| 240 | label: '文件上传', | ||
| 241 | tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB', | ||
| 242 | type: 'upload-file', | ||
| 243 | accept: '.xlsx, .xls, .csv', | ||
| 244 | limitSize: 10, | ||
| 245 | isExcel: true, | ||
| 246 | required: true, | ||
| 247 | default: <any>[], | ||
| 248 | block: true, | ||
| 249 | visible: false, | ||
| 250 | field: 'file', | ||
| 251 | }]); | ||
| 252 | |||
| 253 | const newCreateTaskFormRules = ref({ | ||
| 254 | taskName: [required('请输入任务名称')], | ||
| 255 | dataSource: [required('请选择数据来源')], | ||
| 256 | dataSourceGuid: [required('请选择数据源')], | ||
| 257 | file: [{ | ||
| 258 | validator: (rule: any, value: any, callback: any) => { | ||
| 259 | if (!value?.length) { | ||
| 260 | callback(new Error('请上传文件')) | ||
| 261 | } else { | ||
| 262 | callback(); | ||
| 263 | } | ||
| 264 | }, trigger: 'change' | ||
| 265 | }] | ||
| 266 | }); | ||
| 267 | |||
| 268 | const newCreateTaskDialogInfo = ref({ | ||
| 269 | visible: false, | ||
| 270 | size: 550, | ||
| 271 | title: "添加数据敏感识别任务", | ||
| 272 | type: "", | ||
| 273 | formInfo: { | ||
| 274 | id: "label-form", | ||
| 275 | items: newCreateTaskFormItems.value, | ||
| 276 | rules: newCreateTaskFormRules.value, | ||
| 277 | }, | ||
| 278 | submitBtnLoading: false, | ||
| 279 | btns: { | ||
| 280 | cancel: () => { | ||
| 281 | newCreateTaskDialogInfo.value.visible = false; | ||
| 282 | newCreateTaskDialogInfo.value.submitBtnLoading = false; | ||
| 283 | }, | ||
| 284 | submit: (btn, info) => { | ||
| 285 | let params = Object.assign({}, info, { | ||
| 286 | filePath: info.file?.map(f => { | ||
| 287 | return { | ||
| 288 | name: f.name, | ||
| 289 | url: f.url | ||
| 290 | } | ||
| 291 | }) || [] | ||
| 292 | }); | ||
| 293 | delete params.file; | ||
| 294 | newCreateTaskDialogInfo.value.submitBtnLoading = true; | ||
| 295 | if (newCreateTaskDialogInfo.value.type == 'add') { | ||
| 296 | saveSensitiveDataTask(params).then((res: any) => { | ||
| 297 | if (res?.code == proxy.$passCode) { | ||
| 298 | proxy.$ElMessage.success('标签新建成功'); | ||
| 299 | newCreateTaskDialogInfo.value.visible = false; | ||
| 300 | newCreateTaskDialogInfo.value.submitBtnLoading = false; | ||
| 301 | page.value.curr = 1; | ||
| 302 | getTableData(); | ||
| 303 | } else { | ||
| 304 | newCreateTaskDialogInfo.value.submitBtnLoading = false; | ||
| 305 | proxy.$ElMessage.error(res.msg); | ||
| 306 | } | ||
| 307 | }); | ||
| 308 | } else { | ||
| 309 | newCreateTaskDialogInfo.value.submitBtnLoading = true; | ||
| 310 | params.guid = currTableData.value.guid; | ||
| 311 | updateSensitiveDataTask(params).then((res: any) => { | ||
| 312 | if (res?.code == proxy.$passCode) { | ||
| 313 | proxy.$ElMessage.success('标签编辑成功'); | ||
| 314 | newCreateTaskDialogInfo.value.visible = false; | ||
| 315 | newCreateTaskDialogInfo.value.submitBtnLoading = false; | ||
| 316 | getTableData(); | ||
| 317 | } else { | ||
| 318 | newCreateTaskDialogInfo.value.submitBtnLoading = false; | ||
| 319 | proxy.$ElMessage.error(res.msg); | ||
| 320 | } | ||
| 321 | }); | ||
| 322 | } | ||
| 323 | } | ||
| 324 | } | ||
| 325 | }); | ||
| 326 | |||
| 327 | const handleTaskSelectChange = (val, row, item) => { | ||
| 328 | if (item.field == 'dataSource') { | ||
| 329 | newCreateTaskFormItems.value[0].default = row.taskName; | ||
| 330 | newCreateTaskFormItems.value[1].default = val; | ||
| 331 | newCreateTaskFormItems.value[2].default = row.dataSourceGuid || ''; | ||
| 332 | newCreateTaskFormItems.value[2].visible = val == 1; | ||
| 333 | newCreateTaskFormItems.value[3].default = row.file || []; | ||
| 334 | newCreateTaskFormItems.value[3].visible = val == 2; | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | const handleCreate = () => { | ||
| 339 | newCreateTaskDialogInfo.value.visible = true; | ||
| 340 | newCreateTaskDialogInfo.value.title = '添加数据敏感识别任务'; | ||
| 341 | newCreateTaskDialogInfo.value.type = 'add'; | ||
| 342 | newCreateTaskFormItems.value[0].default = ''; | ||
| 343 | newCreateTaskFormItems.value[1].default = 1; | ||
| 344 | newCreateTaskFormItems.value[2].default = ''; | ||
| 345 | newCreateTaskFormItems.value[2].visible = true; | ||
| 346 | newCreateTaskFormItems.value[3].default = []; | ||
| 347 | newCreateTaskFormItems.value[3].visible = false; | ||
| 348 | } | ||
| 349 | |||
| 350 | onBeforeMount(() => { | ||
| 351 | toSearch({}); | ||
| 352 | getDatabase({ connectStatus: 1 }).then((res: any) => { | ||
| 353 | if (res?.code == proxy.$passCode) { | ||
| 354 | dataSourceList.value = res.data || []; | ||
| 355 | newCreateTaskFormItems.value[2].options = dataSourceList.value; | ||
| 356 | } else { | ||
| 357 | proxy.$ElMessage({ | ||
| 358 | type: "error", | ||
| 359 | message: res.msg, | ||
| 360 | }); | ||
| 361 | } | ||
| 362 | }) | ||
| 363 | }) | ||
| 364 | |||
| 365 | </script> | ||
| 366 | |||
| 367 | <template> | ||
| 368 | <div class="container_wrap"> | ||
| 369 | <div class="table_tool_wrap"> | ||
| 370 | <!-- 头部搜索 --> | ||
| 371 | <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" /> | ||
| 372 | <div class="tools_btns"> | ||
| 373 | <el-button type="primary" @click="handleCreate">新建</el-button> | ||
| 374 | </div> | ||
| 375 | </div> | ||
| 376 | <div class="table_panel_wrap"> | ||
| 377 | <!-- 右侧标签管理表格 --> | ||
| 378 | <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" /> | ||
| 379 | </div> | ||
| 380 | <Dialog_form ref="dialogTaskFormRef" :dialogConfigInfo="newCreateTaskDialogInfo" | ||
| 381 | @formDialogSelectChange="handleTaskSelectChange"></Dialog_form> | ||
| 382 | </div> | ||
| 383 | </template> | ||
| 384 | |||
| 385 | <style lang="scss" scoped> | ||
| 386 | .table_tool_wrap { | ||
| 387 | width: 100%; | ||
| 388 | height: 84px !important; | ||
| 389 | padding: 0 8px; | ||
| 390 | |||
| 391 | .tools_btns { | ||
| 392 | padding: 0px 0 0; | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | .table_panel_wrap { | ||
| 397 | width: 100%; | ||
| 398 | height: calc(100% - 84px); | ||
| 399 | padding: 0px 8px 0; | ||
| 400 | } | ||
| 401 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | <route lang="yaml"> | ||
| 2 | name: sensitiveIdentifyConfig | ||
| 3 | </route> | ||
| 4 | |||
| 5 | <script lang="ts" setup name="sensitiveIdentifyConfig"> | ||
| 6 | import { | ||
| 7 | getExecSensitiveTable, | ||
| 8 | getExecSensitiveFieldTable, | ||
| 9 | getExecSensitiveFieldColumnListByCondition, | ||
| 10 | getDataLabelList, | ||
| 11 | getStatisticsNum, | ||
| 12 | getParamsList, | ||
| 13 | updateSensitiveDataTaskFieldLabel, | ||
| 14 | batchUpdateSensitiveDataTaskFieldStatus, | ||
| 15 | confirmTaskStatus, | ||
| 16 | } from '@/api/modules/dataAnonymization'; | ||
| 17 | import PageNav from "@/components/PageNav/index.vue"; | ||
| 18 | import { commonPageConfig } from '@/components/PageNav/index'; | ||
| 19 | import { ElMessageBox } from 'element-plus'; | ||
| 20 | import { changeNum } from "@/utils/common"; | ||
| 21 | import BtnPopover from "@/components/Popover/index.vue"; | ||
| 22 | import useUserStore from "@/store/modules/user"; | ||
| 23 | |||
| 24 | const route = useRoute(); | ||
| 25 | const router = useRouter(); | ||
| 26 | const { proxy } = getCurrentInstance() as any; | ||
| 27 | const userStore = useUserStore(); | ||
| 28 | const fullPath = route.fullPath; | ||
| 29 | const isLook = ref(route.query.isLook == '1'); | ||
| 30 | |||
| 31 | const fullScreenLoading = ref(false); | ||
| 32 | |||
| 33 | const treeInfo = ref({ | ||
| 34 | id: "data-list-tree", | ||
| 35 | filter: true, | ||
| 36 | queryValue: "", | ||
| 37 | queryPlaceholder: "请输入关键字搜索", | ||
| 38 | props: { | ||
| 39 | label: "label", | ||
| 40 | value: "value", | ||
| 41 | isLeaf: "isLeaf", | ||
| 42 | children: 'tableList' | ||
| 43 | }, | ||
| 44 | nodeKey: 'value', | ||
| 45 | expandedKey: <any>[], | ||
| 46 | currentNodeKey: '', | ||
| 47 | data: [], | ||
| 48 | expandOnNodeClick: false, | ||
| 49 | loading: false, | ||
| 50 | currentObj: <any>{} | ||
| 51 | }); | ||
| 52 | |||
| 53 | const treeRef = ref(); | ||
| 54 | |||
| 55 | const nodeClick = (data, node) => { | ||
| 56 | let exec = () => { | ||
| 57 | pageInfo.value.labelGuids = []; | ||
| 58 | pageInfo.value.labelTypeCodes = []; | ||
| 59 | pageInfo.value.confirmStatus = []; | ||
| 60 | treeInfo.value.currentNodeKey = data.value; | ||
| 61 | treeInfo.value.currentObj = data; | ||
| 62 | if (data.parent) { | ||
| 63 | pageInfo.value.databaseName = data.parent; | ||
| 64 | pageInfo.value.tableName = data.tableName; | ||
| 65 | } else { | ||
| 66 | pageInfo.value.databaseName = data.databaseName; | ||
| 67 | pageInfo.value.tableName = ''; | ||
| 68 | } | ||
| 69 | pageInfo.value.curr = 1; | ||
| 70 | getSensitiveTableFieldData(); | ||
| 71 | getCntSumInfo(); | ||
| 72 | getSensitiveFieldLabelData(); | ||
| 73 | } | ||
| 74 | if (checkTableSave()) { | ||
| 75 | ElMessageBox.confirm( | ||
| 76 | '存在未保存的数据,确定放弃修改吗?', | ||
| 77 | '提示', | ||
| 78 | { | ||
| 79 | confirmButtonText: '确定', | ||
| 80 | cancelButtonText: '取消', | ||
| 81 | type: 'warning', | ||
| 82 | } | ||
| 83 | ).then(() => { | ||
| 84 | exec(); | ||
| 85 | }).catch(() => { | ||
| 86 | treeRef.value.setCurrentKey(treeInfo.value.currentObj.value); | ||
| 87 | }) | ||
| 88 | } else { | ||
| 89 | exec(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | const batchConfirm = () => { | ||
| 94 | if (!selectTableFieldRows.value.length) { | ||
| 95 | proxy.$ElMessage.error('请先勾选待确认的字段'); | ||
| 96 | return; | ||
| 97 | } | ||
| 98 | if (checkTableSave()) { | ||
| 99 | ElMessageBox.confirm( | ||
| 100 | '存在未保存的数据,确定放弃修改吗?', | ||
| 101 | '提示', | ||
| 102 | { | ||
| 103 | confirmButtonText: '确定', | ||
| 104 | cancelButtonText: '取消', | ||
| 105 | type: 'warning', | ||
| 106 | } | ||
| 107 | ).then(() => { | ||
| 108 | batchUpdateSensitiveDataTaskFieldStatus(selectTableFieldRows.value.map(s => s.guid)).then((res: any) => { | ||
| 109 | if (res?.code == proxy.$passCode) { | ||
| 110 | proxy.$ElMessage.success('批量确认字段成功'); | ||
| 111 | getSensitiveTableFieldData(); | ||
| 112 | } else { | ||
| 113 | proxy.$ElMessage.error(res.msg); | ||
| 114 | } | ||
| 115 | }) | ||
| 116 | }) | ||
| 117 | } else { | ||
| 118 | batchUpdateSensitiveDataTaskFieldStatus(selectTableFieldRows.value.map(s => s.guid)).then((res: any) => { | ||
| 119 | if (res?.code == proxy.$passCode) { | ||
| 120 | proxy.$ElMessage.success('批量确认字段成功'); | ||
| 121 | getSensitiveTableFieldData(); | ||
| 122 | } else { | ||
| 123 | proxy.$ElMessage.error(res.msg); | ||
| 124 | } | ||
| 125 | }) | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | /** 数量统计信息 */ | ||
| 130 | const cntSumInfo: any = ref({}); | ||
| 131 | |||
| 132 | const getCntSumInfo = () => { | ||
| 133 | getStatisticsNum({ | ||
| 134 | execGuid: route.query.execGuid, | ||
| 135 | databaseName: pageInfo.value.databaseName, | ||
| 136 | tableName: pageInfo.value.tableName | ||
| 137 | }).then((res: any) => { | ||
| 138 | if (res?.code == proxy.$passCode) { | ||
| 139 | cntSumInfo.value = res.data || {}; | ||
| 140 | } else { | ||
| 141 | proxy.$ElMessage.error(res.msg); | ||
| 142 | } | ||
| 143 | }) | ||
| 144 | } | ||
| 145 | |||
| 146 | const dataSource = ref(null); | ||
| 147 | |||
| 148 | const getExecSensitiveTableData = () => { | ||
| 149 | treeInfo.value.loading = true; | ||
| 150 | getExecSensitiveTable(route.query.execGuid).then((res: any) => { | ||
| 151 | treeInfo.value.loading = false; | ||
| 152 | if (res?.code == proxy.$passCode) { | ||
| 153 | treeInfo.value.data = res.data?.map((d, index) => { | ||
| 154 | d.value = `${d.databaseName}-ds`; //解决文件名称和文件的sheet名称一样的树形选中问题 | ||
| 155 | d.label = d.databaseChName; | ||
| 156 | if (index == 0) { | ||
| 157 | dataSource.value = d.dataSource; | ||
| 158 | } | ||
| 159 | d.tableList = d.tableList?.map(t => { | ||
| 160 | t.value = `${d.databaseName}-${t.tableName}`; | ||
| 161 | t.label = dataSource.value == 1 ? (t.tableChName || t.tableName) : t.tableChName; | ||
| 162 | t.parent = d.databaseName; | ||
| 163 | return t; | ||
| 164 | }) || [] | ||
| 165 | return d; | ||
| 166 | }) || []; | ||
| 167 | let treeData: any = treeInfo.value.data || []; | ||
| 168 | if (treeData.length) { | ||
| 169 | treeInfo.value.expandedKey = [treeData[0].value]; | ||
| 170 | treeInfo.value.currentNodeKey = treeData[0]?.value; | ||
| 171 | treeInfo.value.currentObj = treeData[0]; | ||
| 172 | treeData[0].tableList?.[0] && nodeClick(treeData[0], {}); | ||
| 173 | } | ||
| 174 | } else { | ||
| 175 | proxy.$ElMessage.error(res.msg); | ||
| 176 | } | ||
| 177 | }) | ||
| 178 | } | ||
| 179 | |||
| 180 | /** 所有的标签列表供编辑下拉显示 */ | ||
| 181 | const allDataLabelList: any = ref([]); | ||
| 182 | |||
| 183 | onBeforeMount(() => { | ||
| 184 | getExecSensitiveTableData(); | ||
| 185 | getDataLabelList({ | ||
| 186 | pageSize: -1, | ||
| 187 | bizState: 'Y' | ||
| 188 | }).then((res: any) => { | ||
| 189 | if (res?.code == proxy.$passCode) { | ||
| 190 | allDataLabelList.value = res.data?.records || []; | ||
| 191 | } else { | ||
| 192 | proxy.$ElMessage.error(res.msg); | ||
| 193 | } | ||
| 194 | }) | ||
| 195 | getParamsList({ | ||
| 196 | dictType: "标签类型", | ||
| 197 | }).then((res: any) => { | ||
| 198 | if (res?.code == proxy.$passCode) { | ||
| 199 | labelTypeList.value = res.data?.map(d => { | ||
| 200 | d.guid = d.value; | ||
| 201 | return d; | ||
| 202 | }) || []; | ||
| 203 | } else { | ||
| 204 | proxy.$ElMessage.error(res.msg); | ||
| 205 | } | ||
| 206 | }); | ||
| 207 | }) | ||
| 208 | |||
| 209 | /** ------------------------- 敏感数据识别字段列表操作 --------------------------------- */ | ||
| 210 | |||
| 211 | const labelList = ref([]); | ||
| 212 | |||
| 213 | /** 标签类型的字典列表 */ | ||
| 214 | const labelTypeList: any = ref([]); | ||
| 215 | |||
| 216 | /** 查询数据的标签类型列表 */ | ||
| 217 | const labelTypeDataList: any = ref([]); | ||
| 218 | |||
| 219 | /** 分页设置 */ | ||
| 220 | const pageInfo = ref({ | ||
| 221 | ...commonPageConfig, | ||
| 222 | taskExecGuid: route.query.execGuid, | ||
| 223 | databaseName: '', | ||
| 224 | tableName: '', | ||
| 225 | rows: 0, | ||
| 226 | labelGuids: [], //列头筛选 | ||
| 227 | labelTypeCodes: [], | ||
| 228 | confirmStatus: [] | ||
| 229 | }); | ||
| 230 | |||
| 231 | /** 敏感数据 */ | ||
| 232 | const sensitiveTableData: any = ref([]); | ||
| 233 | |||
| 234 | const sensitiveTableDataLoading = ref(false); | ||
| 235 | |||
| 236 | const getSensitiveTableFieldData = () => { | ||
| 237 | sensitiveTableDataLoading.value = true; | ||
| 238 | let confirmStatus = pageInfo.value.confirmStatus || []; | ||
| 239 | getExecSensitiveFieldTable({ | ||
| 240 | pageIndex: pageInfo.value.curr, | ||
| 241 | pageSize: pageInfo.value.limit, | ||
| 242 | taskExecGuid: pageInfo.value.taskExecGuid, | ||
| 243 | databaseName: pageInfo.value.databaseName, | ||
| 244 | tableName: pageInfo.value.tableName, | ||
| 245 | labelGuids: pageInfo.value.labelGuids, | ||
| 246 | labelTypeCodes: pageInfo.value.labelTypeCodes, | ||
| 247 | confirmStatus: confirmStatus?.length > 1 ? null : confirmStatus[0] | ||
| 248 | }).then((res: any) => { | ||
| 249 | sensitiveTableDataLoading.value = false; | ||
| 250 | if (res?.code == proxy.$passCode) { | ||
| 251 | const data = res.data || {}; | ||
| 252 | sensitiveTableData.value = data.records || []; | ||
| 253 | pageInfo.value.limit = data.pageSize | ||
| 254 | pageInfo.value.curr = data.pageIndex | ||
| 255 | pageInfo.value.rows = data.totalRows | ||
| 256 | } else { | ||
| 257 | proxy.$ElMessage({ | ||
| 258 | type: 'error', | ||
| 259 | message: res.msg, | ||
| 260 | }) | ||
| 261 | } | ||
| 262 | }) | ||
| 263 | } | ||
| 264 | |||
| 265 | /** 获取当前选中的左侧库表下对应的数据标签数组,做列头筛选时使用。 */ | ||
| 266 | const getSensitiveFieldLabelData = (info?: { | ||
| 267 | filterLabel?: Boolean, | ||
| 268 | filterType?: Boolean, | ||
| 269 | filterStatus?: Boolean, | ||
| 270 | setLable?: Boolean, | ||
| 271 | setType?: Boolean, | ||
| 272 | setStatus?: Boolean | ||
| 273 | }) => { | ||
| 274 | getExecSensitiveFieldColumnListByCondition({ | ||
| 275 | pageSize: -1, | ||
| 276 | taskExecGuid: pageInfo.value.taskExecGuid, | ||
| 277 | databaseName: pageInfo.value.databaseName, | ||
| 278 | tableName: pageInfo.value.tableName, | ||
| 279 | labelGuids: !info?.filterLabel ? [] : pageInfo.value.labelGuids, | ||
| 280 | labelTypeCodes: !info?.filterType ? [] : pageInfo.value.labelTypeCodes, | ||
| 281 | confirmStatus: !info?.filterStatus ? [] : pageInfo.value.confirmStatus | ||
| 282 | }).then((res: any) => { | ||
| 283 | if (res?.code == proxy.$passCode) { | ||
| 284 | let data = res.data || {}; | ||
| 285 | (info?.setLable !== false || !pageInfo.value.labelGuids?.length) && (labelList.value = data['标签']?.map(d => { | ||
| 286 | d.guid = d.labelGuid; | ||
| 287 | if (!d.labelGuid && !d.labelName) { | ||
| 288 | d.guid = `${d.labelGuid}`; | ||
| 289 | d.labelName = '--'; | ||
| 290 | } | ||
| 291 | return d; | ||
| 292 | }) || []); | ||
| 293 | let list = data['确认状态']; | ||
| 294 | if (info?.setStatus !== false || !pageInfo.value.confirmStatus?.length) { | ||
| 295 | confirmStatusList.value = list?.map(d => { | ||
| 296 | d.guid = d.confirmStatus; | ||
| 297 | d.label = d.confirmStatus == 'Y' ? '已确认' : '未确认'; | ||
| 298 | return d; | ||
| 299 | }) || [] | ||
| 300 | }; | ||
| 301 | (info?.setType !== false || !pageInfo.value.labelTypeCodes?.length) && (labelTypeDataList.value = data['标签类型']?.map(d => { | ||
| 302 | d.guid = d.labelTypeCode; | ||
| 303 | if (!d.labelTypeCode) { | ||
| 304 | d.guid = `${d.labelTypeCode}`; | ||
| 305 | d.labelTypeName = '--'; | ||
| 306 | } | ||
| 307 | return d; | ||
| 308 | }) || []) | ||
| 309 | } else { | ||
| 310 | proxy.$ElMessage({ | ||
| 311 | type: 'error', | ||
| 312 | message: res.msg, | ||
| 313 | }) | ||
| 314 | } | ||
| 315 | }) | ||
| 316 | } | ||
| 317 | |||
| 318 | const popoverLabelListInfo = computed(() => { | ||
| 319 | return { | ||
| 320 | type: 'checkbox-list-btns', | ||
| 321 | data: labelList.value, | ||
| 322 | placeholder: '请输入关键字搜索', | ||
| 323 | scope: { | ||
| 324 | row: {} | ||
| 325 | }, | ||
| 326 | placement: 'right-start', | ||
| 327 | props: { | ||
| 328 | value: 'guid', | ||
| 329 | label: 'labelName' | ||
| 330 | }, | ||
| 331 | checked: pageInfo.value.labelGuids, | ||
| 332 | btn: { | ||
| 333 | value: 'filter' | ||
| 334 | } | ||
| 335 | } | ||
| 336 | }); | ||
| 337 | |||
| 338 | const handleLabelPopoverClick = (scope) => { | ||
| 339 | if (checkTableSave()) { | ||
| 340 | ElMessageBox.confirm( | ||
| 341 | '存在未保存的数据,确定放弃修改吗?', | ||
| 342 | '提示', | ||
| 343 | { | ||
| 344 | confirmButtonText: '确定', | ||
| 345 | cancelButtonText: '取消', | ||
| 346 | type: 'warning', | ||
| 347 | } | ||
| 348 | ).then(() => { | ||
| 349 | pageInfo.value.labelGuids = scope.row.selectedData || []; | ||
| 350 | pageInfo.value.curr = 1; | ||
| 351 | getSensitiveTableFieldData(); | ||
| 352 | getSensitiveFieldLabelData({ | ||
| 353 | filterLabel: true, | ||
| 354 | filterStatus: true, | ||
| 355 | filterType: true, | ||
| 356 | setLable: false, | ||
| 357 | setStatus: true, | ||
| 358 | setType: true | ||
| 359 | }); | ||
| 360 | }) | ||
| 361 | } else { | ||
| 362 | pageInfo.value.labelGuids = scope.row.selectedData || []; | ||
| 363 | pageInfo.value.curr = 1; | ||
| 364 | getSensitiveTableFieldData(); | ||
| 365 | getSensitiveFieldLabelData({ | ||
| 366 | filterLabel: true, | ||
| 367 | filterStatus: true, | ||
| 368 | filterType: true, | ||
| 369 | setLable: false, | ||
| 370 | setStatus: true, | ||
| 371 | setType: true | ||
| 372 | }); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | const popoverLabelTypeListInfo = computed(() => { | ||
| 377 | return { | ||
| 378 | type: 'checkbox-list-btns', | ||
| 379 | data: labelTypeDataList.value, | ||
| 380 | placeholder: '请输入关键字搜索', | ||
| 381 | scope: { | ||
| 382 | row: {} | ||
| 383 | }, | ||
| 384 | placement: 'right-start', | ||
| 385 | props: { | ||
| 386 | value: 'guid', | ||
| 387 | label: 'labelTypeName' | ||
| 388 | }, | ||
| 389 | checked: pageInfo.value.labelTypeCodes, | ||
| 390 | btn: { | ||
| 391 | value: 'filter' | ||
| 392 | } | ||
| 393 | } | ||
| 394 | }); | ||
| 395 | |||
| 396 | const handleLabelTypePopoverClick = (scope) => { | ||
| 397 | if (checkTableSave()) { | ||
| 398 | ElMessageBox.confirm( | ||
| 399 | '存在未保存的数据,确定放弃修改吗?', | ||
| 400 | '提示', | ||
| 401 | { | ||
| 402 | confirmButtonText: '确定', | ||
| 403 | cancelButtonText: '取消', | ||
| 404 | type: 'warning', | ||
| 405 | } | ||
| 406 | ).then(() => { | ||
| 407 | pageInfo.value.labelTypeCodes = scope.row.selectedData || []; | ||
| 408 | pageInfo.value.curr = 1; | ||
| 409 | getSensitiveTableFieldData(); | ||
| 410 | getSensitiveFieldLabelData({ | ||
| 411 | filterLabel: true, | ||
| 412 | filterStatus: true, | ||
| 413 | filterType: true, | ||
| 414 | setLable: true, | ||
| 415 | setStatus: true, | ||
| 416 | setType: false | ||
| 417 | }); | ||
| 418 | }) | ||
| 419 | } else { | ||
| 420 | pageInfo.value.labelTypeCodes = scope.row.selectedData || []; | ||
| 421 | pageInfo.value.curr = 1; | ||
| 422 | getSensitiveTableFieldData(); | ||
| 423 | getSensitiveFieldLabelData({ | ||
| 424 | filterLabel: true, | ||
| 425 | filterStatus: true, | ||
| 426 | filterType: true, | ||
| 427 | setLable: true, | ||
| 428 | setStatus: true, | ||
| 429 | setType: false | ||
| 430 | }); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | const confirmStatusList: any = ref([]); | ||
| 435 | |||
| 436 | const popoverStatusListInfo = computed(() => { | ||
| 437 | return { | ||
| 438 | type: 'checkbox-list-btns', | ||
| 439 | data: confirmStatusList.value, | ||
| 440 | placeholder: '请输入关键字搜索', | ||
| 441 | scope: { | ||
| 442 | row: {} | ||
| 443 | }, | ||
| 444 | placement: 'right-start', | ||
| 445 | props: { | ||
| 446 | value: 'guid', | ||
| 447 | label: 'label' | ||
| 448 | }, | ||
| 449 | checked: pageInfo.value.confirmStatus, | ||
| 450 | btn: { | ||
| 451 | value: 'filter' | ||
| 452 | } | ||
| 453 | } | ||
| 454 | }); | ||
| 455 | |||
| 456 | const handleStatusPopoverClick = (scope) => { | ||
| 457 | if (checkTableSave()) { | ||
| 458 | ElMessageBox.confirm( | ||
| 459 | '存在未保存的数据,确定放弃修改吗?', | ||
| 460 | '提示', | ||
| 461 | { | ||
| 462 | confirmButtonText: '确定', | ||
| 463 | cancelButtonText: '取消', | ||
| 464 | type: 'warning', | ||
| 465 | } | ||
| 466 | ).then(() => { | ||
| 467 | pageInfo.value.confirmStatus = scope.row.selectedData || []; | ||
| 468 | pageInfo.value.curr = 1; | ||
| 469 | getSensitiveTableFieldData(); | ||
| 470 | getSensitiveFieldLabelData({ | ||
| 471 | filterLabel: true, | ||
| 472 | filterStatus: true, | ||
| 473 | filterType: true, | ||
| 474 | setLable: true, | ||
| 475 | setStatus: false, | ||
| 476 | setType: true | ||
| 477 | }); | ||
| 478 | }) | ||
| 479 | } else { | ||
| 480 | pageInfo.value.confirmStatus = scope.row.selectedData || []; | ||
| 481 | pageInfo.value.curr = 1; | ||
| 482 | getSensitiveTableFieldData(); | ||
| 483 | getSensitiveFieldLabelData({ | ||
| 484 | filterLabel: true, | ||
| 485 | filterStatus: true, | ||
| 486 | filterType: true, | ||
| 487 | setLable: true, | ||
| 488 | setStatus: false, | ||
| 489 | setType: true | ||
| 490 | }); | ||
| 491 | } | ||
| 492 | } | ||
| 493 | |||
| 494 | /** 标签选择的值改变,标签类型跟着变化。 */ | ||
| 495 | const handleSelectChange = (val, scope) => { | ||
| 496 | //暂时不处理,因为未保存会出现清空的场景,需要先存下之前的值。 | ||
| 497 | let label = allDataLabelList.value.find(label => label.guid == val); | ||
| 498 | if (label) { | ||
| 499 | scope.row.labelTypeCode = label.labelTypeCode; | ||
| 500 | scope.row.labelTypeName = label.labelTypeName; | ||
| 501 | scope.row.labelName = label.labelName; | ||
| 502 | } else { | ||
| 503 | scope.row.labelTypeCode = null; | ||
| 504 | scope.row.labelTypeName = null; | ||
| 505 | scope.row.labelName = null; | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | const sensitiveTableSelectable = (row, index) => { | ||
| 510 | // return row.confirmStatus == 'N'; | ||
| 511 | return true; | ||
| 512 | } | ||
| 513 | |||
| 514 | const selectTableFieldRows: any = ref([]); | ||
| 515 | |||
| 516 | /** 勾选字段标准选中变化。 */ | ||
| 517 | const selectionDataChange = (val) => { | ||
| 518 | selectTableFieldRows.value = val; | ||
| 519 | }; | ||
| 520 | |||
| 521 | const handleFieldClickEdit = (scope) => { | ||
| 522 | scope.row.isEdit = true; | ||
| 523 | } | ||
| 524 | |||
| 525 | const handleFieldClickSave = (scope) => { | ||
| 526 | let labelName = ''; | ||
| 527 | let labelTypeCode = ''; | ||
| 528 | if (scope.row.labelGuid) { | ||
| 529 | let label = allDataLabelList.value.find(label => label.guid == scope.row.labelGuid); | ||
| 530 | if (label) { | ||
| 531 | labelName = label.labelName; | ||
| 532 | labelTypeCode = label.labelTypeCode; | ||
| 533 | } | ||
| 534 | } | ||
| 535 | sensitiveTableDataLoading.value = true; | ||
| 536 | updateSensitiveDataTaskFieldLabel([{ | ||
| 537 | guid: scope.row.guid, | ||
| 538 | labelGuid: scope.row.labelGuid, | ||
| 539 | labelName: labelName, | ||
| 540 | labelTypeCode: labelTypeCode | ||
| 541 | }]).then((res: any) => { | ||
| 542 | sensitiveTableDataLoading.value = false; | ||
| 543 | if (res?.code == proxy.$passCode) { | ||
| 544 | getSensitiveTableFieldData(); | ||
| 545 | getSensitiveFieldLabelData({ | ||
| 546 | filterLabel: true, | ||
| 547 | filterStatus: true, | ||
| 548 | filterType: true | ||
| 549 | }); | ||
| 550 | getCntSumInfo(); | ||
| 551 | proxy.$ElMessage.success('字段的标签修改成功'); | ||
| 552 | } else { | ||
| 553 | proxy.$ElMessage.error(res.msg); | ||
| 554 | } | ||
| 555 | }); | ||
| 556 | } | ||
| 557 | |||
| 558 | const checkTableSave = () => { | ||
| 559 | return sensitiveTableData.value.some(s => s.isEdit == true); | ||
| 560 | } | ||
| 561 | |||
| 562 | const pageChange = (info) => { | ||
| 563 | const toChange = checkTableSave() | ||
| 564 | const changeCont = () => { | ||
| 565 | pageInfo.value.curr = Number(info.curr) | ||
| 566 | pageInfo.value.limit = Number(info.limit) | ||
| 567 | getSensitiveTableFieldData(); | ||
| 568 | } | ||
| 569 | if (toChange) { | ||
| 570 | ElMessageBox.confirm( | ||
| 571 | '存在未保存的数据,确定放弃修改吗?', | ||
| 572 | '提示', | ||
| 573 | { | ||
| 574 | confirmButtonText: '确定', | ||
| 575 | cancelButtonText: '取消', | ||
| 576 | type: 'warning', | ||
| 577 | } | ||
| 578 | ).then(() => { | ||
| 579 | changeCont(); | ||
| 580 | }) | ||
| 581 | } else { | ||
| 582 | changeCont(); | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | const cancel = () => { | ||
| 587 | if (checkTableSave()) { | ||
| 588 | proxy.$openMessageBox("当前页面存在未保存的数据,确定放弃修改吗?", () => { | ||
| 589 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); | ||
| 590 | router.push({ | ||
| 591 | name: 'sensitiveIdentify' | ||
| 592 | }); | ||
| 593 | }, () => { | ||
| 594 | proxy.$ElMessage.info("已取消"); | ||
| 595 | }); | ||
| 596 | } else { | ||
| 597 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); | ||
| 598 | router.push({ | ||
| 599 | name: 'sensitiveIdentify' | ||
| 600 | }); | ||
| 601 | } | ||
| 602 | } | ||
| 603 | |||
| 604 | const pageConfirm = () => { | ||
| 605 | fullScreenLoading.value = true; | ||
| 606 | confirmTaskStatus(route.query.execGuid).then((res: any) => { | ||
| 607 | fullScreenLoading.value = false; | ||
| 608 | if (res?.code == proxy.$passCode) { | ||
| 609 | proxy.$ElMessage.success('确认成功'); | ||
| 610 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); | ||
| 611 | router.push({ | ||
| 612 | name: 'sensitiveIdentify' | ||
| 613 | }); | ||
| 614 | } else { | ||
| 615 | proxy.$ElMessage.error(res.msg); | ||
| 616 | } | ||
| 617 | }) | ||
| 618 | } | ||
| 619 | |||
| 620 | const cntLabelMap = ref({ | ||
| 621 | '01': 'directIdentifierNum', | ||
| 622 | '02': 'standardIdentifierNum', | ||
| 623 | '03': 'sensitiveNum', | ||
| 624 | '04': 'nonSensitiveNum' | ||
| 625 | }) | ||
| 626 | |||
| 627 | </script> | ||
| 628 | |||
| 629 | <template> | ||
| 630 | <div class="container_wrap full flex" v-loading="fullScreenLoading"> | ||
| 631 | <div class="aside_wrap" :style="{ height: isLook ? '100%' : 'calc(100% - 40px)' }"> | ||
| 632 | <div class="aside_title">数据表列表</div> | ||
| 633 | <Tree ref="treeRef" :treeInfo="treeInfo" @nodeClick="nodeClick" /> | ||
| 634 | </div> | ||
| 635 | <div class="main_wrap" :style="{ height: isLook ? '100%' : 'calc(100% - 40px)' }"> | ||
| 636 | <div class="table_tool_wrap"> | ||
| 637 | <div class="cnt-desc">{{'表总数:' + changeNum(cntSumInfo.tableNum || 0, 0) + | ||
| 638 | '张 字段总数:' | ||
| 639 | + changeNum(cntSumInfo.fieldNum || 0, 0) + '个 敏感表总数:' + | ||
| 640 | changeNum(cntSumInfo.sensitiveTableNum || 0, 0) + '张 敏感字段总数:' + changeNum(cntSumInfo.sensitiveFieldNum || 0, | ||
| 641 | 0) + | ||
| 642 | '个,其中' + labelTypeList?.map(l => `${l.label}${changeNum(cntSumInfo[cntLabelMap[l.value]] || 0, | ||
| 643 | 0)}个`).join(',') + ',非敏感' + changeNum(cntSumInfo['nonSensitiveNum'] || 0, 0) + '个' | ||
| 644 | }}</div> | ||
| 645 | <div class="tools_btns" v-if="!isLook"> | ||
| 646 | <el-button type="primary" @click="batchConfirm">批量确认</el-button> | ||
| 647 | </div> | ||
| 648 | </div> | ||
| 649 | <div class="table_panel_wrap" :style="{ height: isLook ? 'calc(100% - 54px)' : 'calc(100% - 91px)' }"> | ||
| 650 | <!-- 右侧表字段标签匹配管理表格 --> | ||
| 651 | <el-table ref="sensitiveTableRef" :data="sensitiveTableData" v-loading="sensitiveTableDataLoading" | ||
| 652 | :highlight-current-row="true" stripe border height="100%" row-key="guid" | ||
| 653 | @selection-change="selectionDataChange" tooltip-effect="light" :style="{ | ||
| 654 | width: '100%', | ||
| 655 | 'max-height': 'calc(100% - 16px)', | ||
| 656 | display: 'inline-block', | ||
| 657 | }"> | ||
| 658 | <el-table-column type="selection" v-if="!isLook" :selectable="sensitiveTableSelectable" :width="32" | ||
| 659 | align="center" /> | ||
| 660 | <el-table-column label="序号" type="index" width="56px" align="center" show-overflow-tooltip> | ||
| 661 | <template #default="scope"> | ||
| 662 | <span>{{ | ||
| 663 | pageInfo.curr !== undefined | ||
| 664 | ? (pageInfo.curr - 1) * pageInfo.limit + scope.$index + 1 | ||
| 665 | : scope.$index + 1 | ||
| 666 | }}</span> | ||
| 667 | </template> | ||
| 668 | </el-table-column> | ||
| 669 | <el-table-column label="字段中文名" prop="fieldChName" width="160" align="left" show-overflow-tooltip> | ||
| 670 | <template #default="scope"> | ||
| 671 | <span>{{ scope.row.fieldChName || '--' }}</span> | ||
| 672 | </template> | ||
| 673 | </el-table-column> | ||
| 674 | <el-table-column label="字段英文名" prop="fieldName" width="150" align="left" show-overflow-tooltip> | ||
| 675 | <template #default="scope"> | ||
| 676 | <span>{{ scope.row.fieldName || '--' }}</span> | ||
| 677 | </template> | ||
| 678 | </el-table-column> | ||
| 679 | <el-table-column label="标签" prop="labelGuid" class-name="filter-cell" width="140" align="left" | ||
| 680 | show-overflow-tooltip> | ||
| 681 | <template #header> | ||
| 682 | <span>标签</span> | ||
| 683 | <BtnPopover v-show="labelList.length" :popoverInfo="popoverLabelListInfo" | ||
| 684 | @popverBtnClick="handleLabelPopoverClick" /> | ||
| 685 | </template> | ||
| 686 | <template #default="scope"> | ||
| 687 | <el-select-v2 v-if="scope.row['isEdit']" v-model="scope.row['labelGuid']" filterable | ||
| 688 | popper-class="el-select-v2-popper" :options="allDataLabelList" placeholder="请选择" clearable | ||
| 689 | :props="{ value: 'guid', label: 'labelName' }" @change="(val) => handleSelectChange(val, scope)"> | ||
| 690 | <template #default="{ item }"> | ||
| 691 | <ellipsis-tooltip :content="item.labelName ?? ''" class-name="w100f" | ||
| 692 | :refName="'tooltipOver' + item.guid"></ellipsis-tooltip> | ||
| 693 | </template> | ||
| 694 | </el-select-v2> | ||
| 695 | <span v-else>{{ scope.row["labelName"] || '--' }}</span> | ||
| 696 | </template> | ||
| 697 | </el-table-column> | ||
| 698 | <el-table-column label="标签类型" prop="labelTypeName" class-name="filter-cell" width="130" align="left" | ||
| 699 | show-overflow-tooltip> | ||
| 700 | <template #header> | ||
| 701 | <span>标签类型</span> | ||
| 702 | <BtnPopover v-show="labelTypeDataList.length" :popoverInfo="popoverLabelTypeListInfo" | ||
| 703 | @popverBtnClick="handleLabelTypePopoverClick" /> | ||
| 704 | </template> | ||
| 705 | <template #default="scope"> | ||
| 706 | <span>{{ scope.row.labelTypeName || '--' }}</span> | ||
| 707 | </template> | ||
| 708 | </el-table-column> | ||
| 709 | <el-table-column label="所属表中文名" prop="tableChName" width="140" align="left" show-overflow-tooltip> | ||
| 710 | <template #default="scope"> | ||
| 711 | <span>{{ scope.row.tableChName || '--' }}</span> | ||
| 712 | </template> | ||
| 713 | </el-table-column> | ||
| 714 | <el-table-column label="所属表英文名" prop="tableName" :width="dataSource == 1 ? 160 : 130" align="left" show-overflow-tooltip> | ||
| 715 | </el-table-column> | ||
| 716 | <el-table-column label="确认状态" prop="confirmStatus" class-name="filter-cell" width="120" align="center" | ||
| 717 | show-overflow-tooltip> | ||
| 718 | <template #header> | ||
| 719 | <span>确认状态</span> | ||
| 720 | <BtnPopover v-show="confirmStatusList.length" :popoverInfo="popoverStatusListInfo" | ||
| 721 | @popverBtnClick="handleStatusPopoverClick" /> | ||
| 722 | </template> | ||
| 723 | <template #default="scope"> | ||
| 724 | <el-tag v-if="scope.row.confirmStatus != null" | ||
| 725 | :type="scope.row.confirmStatus == 'Y' ? 'success' : 'warning'">{{ | ||
| 726 | scope.row.confirmStatus == 'Y' ? '已确认' : '未确认' | ||
| 727 | }}</el-tag> | ||
| 728 | <span v-else>{{ '--' }}</span> | ||
| 729 | </template> | ||
| 730 | </el-table-column> | ||
| 731 | <el-table-column v-if="!isLook" label="操作" width="90px" align="left" fixed="right" show-overflow-tooltip> | ||
| 732 | <template #default="scope"> | ||
| 733 | <span class="text_btn" v-if="!scope.row['isEdit']" @click="handleFieldClickEdit(scope)" | ||
| 734 | v-preReClick>编辑</span> | ||
| 735 | <span class="text_btn" v-else @click="handleFieldClickSave(scope)" v-preReClick>保存</span> | ||
| 736 | </template> | ||
| 737 | </el-table-column> | ||
| 738 | </el-table> | ||
| 739 | <PageNav :pageInfo="pageInfo" @pageChange="pageChange" /> | ||
| 740 | </div> | ||
| 741 | </div> | ||
| 742 | <div class="bottom_tool_wrap"> | ||
| 743 | <el-button @click="cancel" v-preReClick>取消</el-button> | ||
| 744 | <el-button type="primary" @click="pageConfirm" v-preReClick>确认</el-button> | ||
| 745 | </div> | ||
| 746 | </div> | ||
| 747 | </template> | ||
| 748 | |||
| 749 | <style lang="scss" scoped> | ||
| 750 | .container_wrap { | ||
| 751 | flex-wrap: wrap; | ||
| 752 | |||
| 753 | .aside_wrap { | ||
| 754 | width: 200px; | ||
| 755 | height: calc(100% - 40px); | ||
| 756 | } | ||
| 757 | |||
| 758 | .table_tool_wrap { | ||
| 759 | display: flex; | ||
| 760 | flex-direction: column; | ||
| 761 | min-height: 25px; | ||
| 762 | |||
| 763 | .cnt-desc { | ||
| 764 | line-height: 24px; | ||
| 765 | white-space: break-spaces; | ||
| 766 | margin-top: 4px; | ||
| 767 | } | ||
| 768 | } | ||
| 769 | |||
| 770 | .tools_btns { | ||
| 771 | padding-top: 0px; | ||
| 772 | } | ||
| 773 | |||
| 774 | .table_panel_wrap { | ||
| 775 | height: calc(100% - 91px); | ||
| 776 | } | ||
| 777 | |||
| 778 | .bottom_tool_wrap { | ||
| 779 | height: 40px; | ||
| 780 | width: 100%; | ||
| 781 | padding: 0 16px; | ||
| 782 | border-top: 1px solid #d9d9d9; | ||
| 783 | display: flex; | ||
| 784 | justify-content: center; | ||
| 785 | align-items: center; | ||
| 786 | } | ||
| 787 | } | ||
| 788 | |||
| 789 | .tree_panel { | ||
| 790 | height: 100%; | ||
| 791 | padding-top: 0; | ||
| 792 | |||
| 793 | :deep(.el-tree) { | ||
| 794 | margin: 0; | ||
| 795 | height: calc(100% - 68px); | ||
| 796 | overflow: hidden auto; | ||
| 797 | } | ||
| 798 | } | ||
| 799 | |||
| 800 | :deep(.el-table) { | ||
| 801 | & td.el-table__cell { | ||
| 802 | .cell { | ||
| 803 | padding: 0px 10px; | ||
| 804 | } | ||
| 805 | |||
| 806 | padding: 2px 0px; | ||
| 807 | height: 36px; | ||
| 808 | } | ||
| 809 | } | ||
| 810 | |||
| 811 | :deep(.filter-cell) { | ||
| 812 | .cell { | ||
| 813 | position: relative; | ||
| 814 | |||
| 815 | .el-icon.filter-icon { | ||
| 816 | position: absolute; | ||
| 817 | right: 6px; | ||
| 818 | top: 2px; | ||
| 819 | width: 18px; | ||
| 820 | height: 18px; | ||
| 821 | |||
| 822 | svg { | ||
| 823 | width: 18px; | ||
| 824 | height: 18px; | ||
| 825 | } | ||
| 826 | } | ||
| 827 | } | ||
| 828 | } | ||
| 829 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | <route lang="yaml"> | ||
| 2 | name: sensitiveIdentifyTaskExecLog | ||
| 3 | </route> | ||
| 4 | |||
| 5 | <script lang="ts" setup name="sensitiveIdentifyTaskExecLog"> | ||
| 6 | import { ref } from "vue"; | ||
| 7 | import { useRouter, useRoute } from "vue-router"; | ||
| 8 | import Table from "@/components/Table/index.vue"; | ||
| 9 | import { ElMessage } from "element-plus"; | ||
| 10 | import { commonPageConfig } from '@/components/PageNav/index'; | ||
| 11 | import { | ||
| 12 | getSensitiveDataTaskExecLog, | ||
| 13 | } from '@/api/modules/dataAnonymization'; | ||
| 14 | import { TableColumnWidth } from "@/utils/enum"; | ||
| 15 | |||
| 16 | |||
| 17 | const { proxy } = getCurrentInstance() as any; | ||
| 18 | |||
| 19 | const router = useRouter(); | ||
| 20 | const route = useRoute(); | ||
| 21 | const guid = route.query.guid; | ||
| 22 | const wordName = route.query.name | ||
| 23 | |||
| 24 | const page = ref({ | ||
| 25 | ...commonPageConfig | ||
| 26 | }); | ||
| 27 | |||
| 28 | const tableInfo = ref({ | ||
| 29 | id: "word-log-table", | ||
| 30 | loading: false, | ||
| 31 | fields: [ | ||
| 32 | { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" }, | ||
| 33 | { label: "执行人", field: "createUserName", width: TableColumnWidth.USERNAME }, | ||
| 34 | { label: "执行时间", field: "execTime", width: TableColumnWidth.DATETIME }, | ||
| 35 | { label: "执行状态", field: "sensitiveIdentifyTaskStatus", width: TableColumnWidth.STATE, align: 'center', type: "tag" }, | ||
| 36 | { label: "确认人", field: "confirmUserName", width: TableColumnWidth.USERNAME }, | ||
| 37 | { label: "确认时间", field: "confirmTime", width: TableColumnWidth.DATETIME }, | ||
| 38 | { label: "确认状态", field: "sensitiveIdentifyConfirmStatus", width: TableColumnWidth.STATE, align: 'center', type: "tag" }, | ||
| 39 | ], | ||
| 40 | data: [], | ||
| 41 | page: { | ||
| 42 | type: "normal", | ||
| 43 | rows: 0, | ||
| 44 | ...page.value, | ||
| 45 | }, | ||
| 46 | actionInfo: { | ||
| 47 | label: "操作", | ||
| 48 | type: "btn", | ||
| 49 | width: 100, | ||
| 50 | fixed: 'right', | ||
| 51 | btns: (scope) => { | ||
| 52 | return [{ | ||
| 53 | label: "查看", value: "report", disabled: scope.row['status'] != 'Y', click: (scope) => { | ||
| 54 | router.push({ | ||
| 55 | name: 'sensitiveIdentifyConfig', | ||
| 56 | query: { | ||
| 57 | guid: route.query.guid, | ||
| 58 | execGuid: scope.row.guid, | ||
| 59 | taskName: route.query.name, | ||
| 60 | isLook: '1', | ||
| 61 | } | ||
| 62 | }); | ||
| 63 | } | ||
| 64 | }]; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | }); | ||
| 68 | |||
| 69 | const getTableData = () => { | ||
| 70 | tableInfo.value.loading = true; | ||
| 71 | getSensitiveDataTaskExecLog({ pageIndex: page.value.curr, pageSize: page.value.limit, taskGuid: guid }).then((res: any) => { | ||
| 72 | tableInfo.value.loading = false; | ||
| 73 | if (res?.code == proxy.$passCode) { | ||
| 74 | const data = res.data || {} | ||
| 75 | tableInfo.value.data = data.records?.map(d => { | ||
| 76 | d.sensitiveIdentifyTaskStatus = d.status; | ||
| 77 | d.sensitiveIdentifyConfirmStatus = d.confirmStatus; | ||
| 78 | return d; | ||
| 79 | }) || [] | ||
| 80 | tableInfo.value.page.limit = data.pageSize | ||
| 81 | tableInfo.value.page.curr = data.pageIndex | ||
| 82 | tableInfo.value.page.rows = data.totalRows | ||
| 83 | } else { | ||
| 84 | ElMessage.error(res.msg); | ||
| 85 | } | ||
| 86 | }) | ||
| 87 | }; | ||
| 88 | |||
| 89 | const tableBtnClick = (scope, btn) => { | ||
| 90 | const type = btn.value; | ||
| 91 | const row = scope.row; | ||
| 92 | if (type == 'reportView') { | ||
| 93 | router.push({ | ||
| 94 | name: 'analysisReport', | ||
| 95 | query: { | ||
| 96 | planGuid: row.planGuid, | ||
| 97 | reportExecGuid: row.guid, | ||
| 98 | name: wordName | ||
| 99 | } | ||
| 100 | }); | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | |||
| 104 | onActivated(() => { | ||
| 105 | getTableData(); | ||
| 106 | }); | ||
| 107 | |||
| 108 | </script> | ||
| 109 | |||
| 110 | <template> | ||
| 111 | <div class="container_wrap"> | ||
| 112 | <div class="table_panel_wrap"> | ||
| 113 | <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" /> | ||
| 114 | </div> | ||
| 115 | </div> | ||
| 116 | </template> | ||
| 117 | |||
| 118 | <style lang="scss" scoped> | ||
| 119 | .container_wrap { | ||
| 120 | padding: 0; | ||
| 121 | |||
| 122 | .table_panel_wrap { | ||
| 123 | height: 100%; | ||
| 124 | padding: 16px 16px 0; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -1387,7 +1387,7 @@ onActivated(() => { | ... | @@ -1387,7 +1387,7 @@ onActivated(() => { |
| 1387 | <ContentWrap v-show="!currentStep && detailType == 'sign' || currentStep == 3" id="sign-info" title="合约签署" | 1387 | <ContentWrap v-show="!currentStep && detailType == 'sign' || currentStep == 3" id="sign-info" title="合约签署" |
| 1388 | expandSwicth style="margin-top: 15px" :isExpand="expandSign" @expand="(v) => (expandSign = v)" description=""> | 1388 | expandSwicth style="margin-top: 15px" :isExpand="expandSign" @expand="(v) => (expandSign = v)" description=""> |
| 1389 | <template v-for="(item, index) in signDetailInfo"> | 1389 | <template v-for="(item, index) in signDetailInfo"> |
| 1390 | <div :class="{ 'h-title': true, 'mt6': index > 0 }">{{ item.executionerTypeName }}</div> | 1390 | <div :class="{ 'h-title': true, 'mt6': <number>index > 0 }">{{ item.executionerTypeName }}</div> |
| 1391 | <div class="list_panel"> | 1391 | <div class="list_panel"> |
| 1392 | <div class="list_item wrap"> | 1392 | <div class="list_item wrap"> |
| 1393 | <span class="item_label">签署主体标识</span> | 1393 | <span class="item_label">签署主体标识</span> | ... | ... |
-
Please register or sign in to post a comment