匿名化处理的接口联调
Showing
10 changed files
with
2546 additions
and
45 deletions
| ... | @@ -67,6 +67,12 @@ export const getGeneralizeFileList = (params) => request({ | ... | @@ -67,6 +67,12 @@ export const getGeneralizeFileList = (params) => request({ |
| 67 | data: params | 67 | data: params |
| 68 | }) | 68 | }) |
| 69 | 69 | ||
| 70 | /** 获取泛化文件列表,包括名称和guid */ | ||
| 71 | export const getGeneralizeFileNameList = () => request({ | ||
| 72 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/name-list`, | ||
| 73 | method: 'post' | ||
| 74 | }) | ||
| 75 | |||
| 70 | export const saveGeneralizeFile = (data) => request({ | 76 | export const saveGeneralizeFile = (data) => request({ |
| 71 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/save`, | 77 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/save`, |
| 72 | method: 'post', | 78 | method: 'post', |
| ... | @@ -213,4 +219,105 @@ export const deleteAnonTask = (data) => request({ | ... | @@ -213,4 +219,105 @@ export const deleteAnonTask = (data) => request({ |
| 213 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/delete`, | 219 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/delete`, |
| 214 | method: 'delete', | 220 | method: 'delete', |
| 215 | data | 221 | data |
| 222 | }) | ||
| 223 | |||
| 224 | /** 保存匿名化任务 */ | ||
| 225 | export const saveAnonTask = (params) => request({ | ||
| 226 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/save`, | ||
| 227 | method: 'post', | ||
| 228 | data: params | ||
| 229 | }) | ||
| 230 | |||
| 231 | /** 更新匿名化任务 */ | ||
| 232 | export const updateAnonTask = (params) => request({ | ||
| 233 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/update`, | ||
| 234 | method: 'put', | ||
| 235 | data: params | ||
| 236 | }) | ||
| 237 | |||
| 238 | /** 获取匿名化任务详情 */ | ||
| 239 | export const getAnonTaskDetail = (guid) => request({ | ||
| 240 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/detail?guid=${guid}`, | ||
| 241 | method: 'get' | ||
| 242 | }) | ||
| 243 | |||
| 244 | /** 执行匿名化任务 */ | ||
| 245 | export const execAnonTask = (taskGuid) => request({ | ||
| 246 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/exec-task?taskGuid=${taskGuid}`, | ||
| 247 | method: 'post' | ||
| 248 | }) | ||
| 249 | |||
| 250 | /** 匿名化任务检验接口 */ | ||
| 251 | export const anonTaskCheck = (params) => request({ | ||
| 252 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/check`, | ||
| 253 | method: 'post', | ||
| 254 | data: params | ||
| 255 | }) | ||
| 256 | |||
| 257 | /** 获取匿名化任务分析结果数据 */ | ||
| 258 | export const getAnonAnalyzeResult = (execGuid) => request({ | ||
| 259 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/get-anon-analyze?taskExecGuid=${execGuid}`, | ||
| 260 | method: 'get' | ||
| 261 | }) | ||
| 262 | |||
| 263 | /** 获取匿名化任务分析结果统计分页数据 */ | ||
| 264 | export const getAnonAnalyzePageData = (params) => request({ | ||
| 265 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/page-anon-analyze-data`, | ||
| 266 | method: 'post', | ||
| 267 | data: params | ||
| 268 | }) | ||
| 269 | |||
| 270 | /** 获取匿名化任务结果数据 */ | ||
| 271 | export const getAnonPageData = (params) => request({ | ||
| 272 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/page-anon-data`, | ||
| 273 | method: 'post', | ||
| 274 | data: params | ||
| 275 | }) | ||
| 276 | |||
| 277 | /** 字段中文转英文 */ | ||
| 278 | export const chTransformEn =(params)=> request({ | ||
| 279 | url: `${import.meta.env.VITE_APP_COMMON_URL}/common/convert-field-ch-name`, | ||
| 280 | method: "post", | ||
| 281 | data: params, | ||
| 282 | }); | ||
| 283 | |||
| 284 | /** 根据选择的连接池获取表列表 */ | ||
| 285 | export const getDsTableByDs = (params) => request({ | ||
| 286 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/schema-table-page-list`, | ||
| 287 | method: 'post', | ||
| 288 | data: params | ||
| 289 | }) | ||
| 290 | |||
| 291 | /** 根据数据表获取表字段结构 */ | ||
| 292 | export const getDsTableFieldColumn = (params) => request({ | ||
| 293 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/table-column-list`, | ||
| 294 | method: 'post', | ||
| 295 | data: params | ||
| 296 | }); | ||
| 297 | |||
| 298 | /** 根据数据表获取表数据 */ | ||
| 299 | export const getDsTableSampleData = (params) => request({ | ||
| 300 | url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/table-data-preview-page`, | ||
| 301 | method: 'post', | ||
| 302 | data: params | ||
| 303 | }); | ||
| 304 | |||
| 305 | /** 根据字段名称获取敏感数据识别标签 */ | ||
| 306 | export const getLableByFieldName = (fieldName) => request({ | ||
| 307 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/get-label-by-field-name?fieldName=${fieldName}`, | ||
| 308 | method: 'get' | ||
| 309 | }); | ||
| 310 | |||
| 311 | /** 验证样本数据 */ | ||
| 312 | export const validateAnonRule = (params) => request({ | ||
| 313 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/check`, | ||
| 314 | method: 'post', | ||
| 315 | data: params | ||
| 316 | }) | ||
| 317 | |||
| 318 | /** 导出匿名化结果数据 */ | ||
| 319 | export const exportAnonExecData = (params) => request({ | ||
| 320 | url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/export-anon-data?taskGuid=${params.taskGuid}&taskExecGuid=${params.execGuid}`, | ||
| 321 | method: 'get', | ||
| 322 | responseType: 'blob' | ||
| 216 | }) | 323 | }) |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -13,6 +13,7 @@ const emits = defineEmits([ | ... | @@ -13,6 +13,7 @@ const emits = defineEmits([ |
| 13 | "drawerBtnClick", | 13 | "drawerBtnClick", |
| 14 | "radioGroupChange", | 14 | "radioGroupChange", |
| 15 | "drawerSelectChange", | 15 | "drawerSelectChange", |
| 16 | 'drawerInputChange', | ||
| 16 | "drawerTableSelectChange", | 17 | "drawerTableSelectChange", |
| 17 | "drawerTableBtnClick", | 18 | "drawerTableBtnClick", |
| 18 | "drawerTableToolBtnClick", | 19 | "drawerTableToolBtnClick", |
| ... | @@ -98,15 +99,15 @@ const onClickOutside = (e: any) => { | ... | @@ -98,15 +99,15 @@ const onClickOutside = (e: any) => { |
| 98 | emits("onClickOutside"); | 99 | emits("onClickOutside"); |
| 99 | }; | 100 | }; |
| 100 | 101 | ||
| 101 | const getDrawerConRef = (refName) => { | 102 | const getDrawerConRef = (refName, index = 0) => { |
| 102 | console.log(refName, '----------') | 103 | //console.log(refName, '----------') |
| 103 | if (refName == 'drawerTableRef') { | 104 | if (refName == 'drawerTableRef') { |
| 104 | const dtf = drawerTableRef.value[0] || drawerTableRef.value | 105 | const dtf = drawerTableRef.value[0] || drawerTableRef.value |
| 105 | return dtf?.tableRef | 106 | return dtf?.tableRef |
| 106 | } | 107 | } |
| 107 | // const drawerForm = drawerFormRef.value[0] || drawerFormRef.value; | 108 | // const drawerForm = drawerFormRef.value[0] || drawerFormRef.value; |
| 108 | if (refName == 'drawerFormRef') { | 109 | if (refName == 'drawerFormRef') { |
| 109 | const drawerForm = drawerFormRef.value?.[0] || drawerFormRef.value; | 110 | const drawerForm = drawerFormRef.value?.[index] || drawerFormRef.value; |
| 110 | return drawerForm | 111 | return drawerForm |
| 111 | } | 112 | } |
| 112 | } | 113 | } |
| ... | @@ -187,6 +188,10 @@ const radioGroupChange = (val, info) => { | ... | @@ -187,6 +188,10 @@ const radioGroupChange = (val, info) => { |
| 187 | emits("radioGroupChange", val, info); | 188 | emits("radioGroupChange", val, info); |
| 188 | }; | 189 | }; |
| 189 | 190 | ||
| 191 | const formInputChange = (val, row, info) => { | ||
| 192 | emits("drawerInputChange", val, row, info); | ||
| 193 | } | ||
| 194 | |||
| 190 | const formSelectChange = (val, row, info) => { | 195 | const formSelectChange = (val, row, info) => { |
| 191 | emits("drawerSelectChange", val, row, info); | 196 | emits("drawerSelectChange", val, row, info); |
| 192 | }; | 197 | }; |
| ... | @@ -319,10 +324,10 @@ const drawerClose = () => { | ... | @@ -319,10 +324,10 @@ const drawerClose = () => { |
| 319 | <template v-else> | 324 | <template v-else> |
| 320 | <Form ref="drawerFormRef" :itemList="con.formInfo.items" :formId="con.formInfo.id" :rules="con.formInfo.rules" | 325 | <Form ref="drawerFormRef" :itemList="con.formInfo.items" :formId="con.formInfo.id" :rules="con.formInfo.rules" |
| 321 | :col="con.formInfo.col" :readonly="con.formInfo.readonly" @radioGroupChange="radioGroupChange" | 326 | :col="con.formInfo.col" :readonly="con.formInfo.readonly" @radioGroupChange="radioGroupChange" |
| 322 | @selectChange="formSelectChange" @btnClick="formBtnClick"> | 327 | @selectChange="formSelectChange" @input-change="formInputChange" @btnClick="formBtnClick"> |
| 323 | </Form> | 328 | </Form> |
| 324 | <!-- 插槽内容 --> | 329 | <!-- 插槽内容 --> |
| 325 | <slot></slot> | 330 | <slot v-if="con.showSlot !== false"></slot> |
| 326 | </template> | 331 | </template> |
| 327 | </div> | 332 | </div> |
| 328 | </template> | 333 | </template> | ... | ... |
| ... | @@ -189,27 +189,30 @@ const inputFocus = (event, item) => { | ... | @@ -189,27 +189,30 @@ const inputFocus = (event, item) => { |
| 189 | } | 189 | } |
| 190 | 190 | ||
| 191 | const inputChange = (val, row) => { | 191 | const inputChange = (val, row) => { |
| 192 | let decimalCnt = row.decimalCnt || 2; | ||
| 192 | if (row.inputType == "moneyNumber" || row.inputType == 'scoreNumber') { | 193 | if (row.inputType == "moneyNumber" || row.inputType == 'scoreNumber') { |
| 193 | let strArr = val.split("."); | 194 | let strArr = val.split("."); |
| 194 | if (strArr.length > 1) { | 195 | if (strArr.length > 1) { |
| 195 | let right = strArr[1]; | 196 | let right = strArr[1]; |
| 196 | if (right === "" || right.length < 2) { | 197 | if (right === "" || right.length < decimalCnt) { |
| 197 | formInline.value[row.field] = val = parseFloat(val || 0).toFixed(2); | 198 | formInline.value[row.field] = val = parseFloat(val || 0).toFixed(decimalCnt); |
| 198 | } | 199 | } |
| 199 | } else { | 200 | } else { |
| 200 | formInline.value[row.field] = val = parseFloat(val || 0).toFixed(2); | 201 | formInline.value[row.field] = val = parseFloat(val || 0).toFixed(decimalCnt); |
| 201 | } | 202 | } |
| 202 | } | 203 | } |
| 203 | if (row.inputType == 'scoreNumber' && parseFloat(val) > 100) { | 204 | let max = row.max || 100; |
| 205 | if (row.inputType == 'scoreNumber' && parseFloat(val) > max) { | ||
| 204 | // 先去除非数字和小数点字符 | 206 | // 先去除非数字和小数点字符 |
| 205 | val = val.replace(/[^\d.]/g, ""); | 207 | val = val.replace(/[^\d.]/g, ""); |
| 206 | // 限制最多保留两位小数 | 208 | // 限制最多保留两位小数 |
| 207 | val = val.replace(/\.{2,}/g, "."); | 209 | val = val.replace(/\.{2,}/g, "."); |
| 208 | val = val.replace(/^(\d+)\.(\d{2}).*$/, "$1.$2"); | 210 | let exp2 = new RegExp(`^(\d+)\.(\d{${decimalCnt}}).*$`, 'g'); |
| 211 | val = val.replace(exp2, "$1.$2"); | ||
| 209 | let num = parseFloat(val); | 212 | let num = parseFloat(val); |
| 210 | if (num > 100) { | 213 | if (num > max) { |
| 211 | num = 100; // 超过100时将其设置为100 | 214 | num = max; // 超过100时将其设置为100 |
| 212 | val = num.toFixed(2); // 保证显示为两位小数 | 215 | val = num.toFixed(decimalCnt); // 保证显示为两位小数 |
| 213 | } | 216 | } |
| 214 | formInline.value[row.field] = val; | 217 | formInline.value[row.field] = val; |
| 215 | } | 218 | } |
| ... | @@ -250,11 +253,15 @@ const inputEventChange = (val, item) => { | ... | @@ -250,11 +253,15 @@ const inputEventChange = (val, item) => { |
| 250 | } | 253 | } |
| 251 | return; | 254 | return; |
| 252 | } else if (item.inputType == 'scoreNumber') {//小于100的,保留两位小数 | 255 | } else if (item.inputType == 'scoreNumber') {//小于100的,保留两位小数 |
| 256 | let decimalCnt = item.decimalCnt || 2; | ||
| 253 | formInline.value[item.field] = formInline.value[item.field].toString().replace(/[^\d.]/g, "") | 257 | formInline.value[item.field] = formInline.value[item.field].toString().replace(/[^\d.]/g, "") |
| 258 | /** 连续两个小数点替换为一个小数点 */ | ||
| 254 | formInline.value[item.field] = formInline.value[item.field].toString().replace(/\.{2,}/g, ".") | 259 | formInline.value[item.field] = formInline.value[item.field].toString().replace(/\.{2,}/g, ".") |
| 255 | formInline.value[item.field] = formInline.value[item.field].toString().replace(/^\D*(\d{0,3}(?:\.\d{0,2})?).*$/g, "$1") | 260 | /** 最多取三位整数,2位小数 */ |
| 261 | let exp2 = new RegExp(`^\\D*(\\d{0,3}(?:\\.\\d{0,${decimalCnt}})?).*$`, 'g'); | ||
| 262 | formInline.value[item.field] = formInline.value[item.field].toString().replace(exp2, "$1") | ||
| 256 | if (item.max != null && formInline.value[item.field] > item.max) { | 263 | if (item.max != null && formInline.value[item.field] > item.max) { |
| 257 | formInline.value[item.field] = item.max.toFixed(2); | 264 | formInline.value[item.field] = item.max.toFixed(decimalCnt); |
| 258 | } | 265 | } |
| 259 | return; | 266 | return; |
| 260 | } else if (item.inputType == 'moneyNumber') {// 单位是元,保留两位小数。 | 267 | } else if (item.inputType == 'moneyNumber') {// 单位是元,保留两位小数。 |
| ... | @@ -754,22 +761,31 @@ const panelChange = (scope, row) => { | ... | @@ -754,22 +761,31 @@ const panelChange = (scope, row) => { |
| 754 | <div class="input_panel" v-for="child in item.children"> | 761 | <div class="input_panel" v-for="child in item.children"> |
| 755 | <span v-if="child.prepend">{{ child.prepend }}</span> | 762 | <span v-if="child.prepend">{{ child.prepend }}</span> |
| 756 | <el-input v-if="child.visible ?? true" v-model.trim="formInline[child.field]" | 763 | <el-input v-if="child.visible ?? true" v-model.trim="formInline[child.field]" |
| 757 | :disabled="child.disabled || readonly" :placeholder="child.placeholder" | 764 | :disabled="child.disabled || readonly" :placeholder="child.placeholder" |
| 758 | :maxlength="child.maxlength ?? ''" /> | 765 | :maxlength="child.maxlength ?? ''" @change="(val) => inputChange(val, child)" |
| 766 | @input="(val) => inputEventChange(val, child)" /> | ||
| 759 | <span v-if="child.append">{{ child.append }}</span> | 767 | <span v-if="child.append">{{ child.append }}</span> |
| 760 | </div> | 768 | </div> |
| 761 | </div> | 769 | </div> |
| 762 | <div class="checkbox_input" :class="[item.col, { is_block: item.block }]" | 770 | <div class="checkbox_input" :class="[item.col, { is_block: item.block }]" |
| 763 | v-else-if="item.type == 'checkbox-input-item'"> | 771 | v-else-if="item.type == 'checkbox-input-item'"> |
| 764 | <el-checkbox v-model="formInline[item.field]" :disabled="item.disabled || readonly" | 772 | <el-checkbox v-model="formInline[item.field]" :disabled="item.disabled || readonly" |
| 765 | @change="(val) => checkboxChange(val, item)" :true-label="item.trueValue ?? true" | 773 | @change="(val) => checkboxChange(val, item)" :true-value="item.trueValue ?? true" |
| 766 | :false-label="item.falseValue ?? false">{{ item.placeholder }}</el-checkbox> | 774 | :false-value="item.falseValue ?? false">{{ item.placeholder }}</el-checkbox> |
| 767 | <div class="input_panel" v-for="child in item.children"> | 775 | <div class="input_panel" v-for="child in item.children"> |
| 768 | <el-form-item v-if="child.visible ?? true" :prop="child.field" | 776 | <el-form-item v-if="child.visible ?? true" :prop="child.field" |
| 769 | :validate-status="child.validateStatus ?? ''" :error="child.error" | 777 | :validate-status="child.validateStatus ?? ''" :error="child.error" |
| 770 | :class="[child.col, { is_block: child.block }]" :style="child.style ?? {}"> | 778 | :class="[child.col, { is_block: child.block }]" :style="child.style ?? {}"> |
| 771 | <el-input v-model.trim="formInline[child.field]" :disabled="child.disabled || readonly" | 779 | <el-input v-if="child.type == 'input'" v-model.trim="formInline[child.field]" :disabled="child.disabled || readonly" |
| 772 | :placeholder="child.placeholder" :maxlength="child.maxlength ?? ''" /> | 780 | :placeholder="child.placeholder" :maxlength="child.maxlength ?? ''" @change="(val) => inputChange(val, child)" |
| 781 | @input="(val) => inputEventChange(val, child)" /> | ||
| 782 | <el-select v-else-if="child.type == 'select'" v-model="formInline[child.field]" | ||
| 783 | :placeholder="child.placeholder" :disabled="child.disabled || readonly" :filterable="child.filterable" :clearable="child.clearable" | ||
| 784 | :teleported="child.teleported || true"> | ||
| 785 | <el-option v-for="opts in child.options" | ||
| 786 | :label="child.props?.label ? opts[child.props.label] : opts.label" | ||
| 787 | :value="child.props?.value ? opts[child.props.value] : opts.value" :disabled="opts.disabled" /> | ||
| 788 | </el-select> | ||
| 773 | </el-form-item> | 789 | </el-form-item> |
| 774 | </div> | 790 | </div> |
| 775 | </div> | 791 | </div> | ... | ... |
| ... | @@ -147,7 +147,7 @@ const routes: RouteRecordRaw[] = [ | ... | @@ -147,7 +147,7 @@ const routes: RouteRecordRaw[] = [ |
| 147 | name: 'anonTaskCreate', | 147 | name: 'anonTaskCreate', |
| 148 | component: () => import('@/views/data_anonymization/anonTaskCreate.vue'), | 148 | component: () => import('@/views/data_anonymization/anonTaskCreate.vue'), |
| 149 | meta: { | 149 | meta: { |
| 150 | title: '新建匿名化处理任务', | 150 | title: '匿名化处理任务', |
| 151 | sidebar: false, | 151 | sidebar: false, |
| 152 | breadcrumb: false, | 152 | breadcrumb: false, |
| 153 | cache: true, | 153 | cache: true, |
| ... | @@ -161,6 +161,23 @@ const routes: RouteRecordRaw[] = [ | ... | @@ -161,6 +161,23 @@ const routes: RouteRecordRaw[] = [ |
| 161 | } | 161 | } |
| 162 | } | 162 | } |
| 163 | }, | 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 | }, | ||
| 164 | ], | 181 | ], |
| 165 | }, | 182 | }, |
| 166 | ] | 183 | ] | ... | ... |
| 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 | getAnonAnalyzeResult, | ||
| 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 | }) | ||
| 42 | |||
| 43 | const getData = () => { | ||
| 44 | tableData.value = []; | ||
| 45 | if (!tableFields.value?.length) { | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | tableDataLoading.value = true; | ||
| 49 | getAnonPageData({ | ||
| 50 | pageIndex: pageInfo.value.curr, | ||
| 51 | pageSize: pageInfo.value.limit, | ||
| 52 | taskExecGuid: props.isPage ? route.query.execGuid : props.execGuid, | ||
| 53 | }).then((res: any) => { | ||
| 54 | tableDataLoading.value = false; | ||
| 55 | if (res.code == proxy.$passCode) { | ||
| 56 | tableData.value = []; | ||
| 57 | res.data?.records?.forEach(d => { | ||
| 58 | let obj = {}; | ||
| 59 | tableFields.value.forEach(t => { | ||
| 60 | obj[t.enName] = d.fieldValue?.[t.enName]; | ||
| 61 | }); | ||
| 62 | tableData.value.push(obj); | ||
| 63 | }); | ||
| 64 | } else { | ||
| 65 | ElMessage.error(res.msg); | ||
| 66 | } | ||
| 67 | }); | ||
| 68 | } | ||
| 69 | |||
| 70 | const getTextAlign = (field) => { | ||
| 71 | if (field.dataType === 'decimal' || field.dataType === 'int' || field.dataType == 'bit' || field.dataType == 'tinyint') { | ||
| 72 | return 'right'; | ||
| 73 | } | ||
| 74 | return 'left' | ||
| 75 | } | ||
| 76 | |||
| 77 | /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */ | ||
| 78 | const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => { | ||
| 79 | let d: any[] = []; | ||
| 80 | data.forEach((dt) => d.push(dt[prop])); | ||
| 81 | return calcColumnWidth( | ||
| 82 | d, | ||
| 83 | title, | ||
| 84 | { | ||
| 85 | fontSize: 14, | ||
| 86 | fontFamily: "SimSun", | ||
| 87 | }, | ||
| 88 | { | ||
| 89 | fontSize: 14, | ||
| 90 | fontFamily: "SimSun", | ||
| 91 | }, | ||
| 92 | otherWidth | ||
| 93 | ); | ||
| 94 | }; | ||
| 95 | |||
| 96 | /** 每列字段对应的列宽计算结果。 */ | ||
| 97 | const originTableFieldColumn = ref({}); | ||
| 98 | |||
| 99 | watch( | ||
| 100 | tableData, | ||
| 101 | (val: any[], oldVal) => { | ||
| 102 | if (!tableFields.value?.length) { | ||
| 103 | originTableFieldColumn.value = {}; | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | originTableFieldColumn.value = {}; | ||
| 107 | tableFields.value.forEach((field, index) => { | ||
| 108 | originTableFieldColumn.value[field.enName] = calcTableColumnWidth( | ||
| 109 | val?.slice(0, 20) || [], | ||
| 110 | field.enName, | ||
| 111 | field.chName, | ||
| 112 | 24 | ||
| 113 | ); | ||
| 114 | }); | ||
| 115 | }, | ||
| 116 | { | ||
| 117 | deep: true, | ||
| 118 | } | ||
| 119 | ); | ||
| 120 | |||
| 121 | watch(() => props.execGuid, (val) => { | ||
| 122 | if (!val) { | ||
| 123 | return; | ||
| 124 | } | ||
| 125 | tableDataLoading.value = true; | ||
| 126 | getAnonAnalyzeResult(val).then((res: any) => { | ||
| 127 | tableDataLoading.value = false; | ||
| 128 | if (res.code == proxy.$passCode) { | ||
| 129 | let column = res.data?.column || {}; | ||
| 130 | tableFields.value = column; | ||
| 131 | getData(); | ||
| 132 | } else { | ||
| 133 | ElMessage.error(res.msg); | ||
| 134 | } | ||
| 135 | }); | ||
| 136 | }, { | ||
| 137 | immediate: true | ||
| 138 | }) | ||
| 139 | |||
| 140 | onBeforeMount(() => { | ||
| 141 | if (!props.isPage) { | ||
| 142 | return; | ||
| 143 | } | ||
| 144 | tableDataLoading.value = true; | ||
| 145 | getAnonAnalyzeResult(route.query.execGuid).then((res: any) => { | ||
| 146 | tableDataLoading.value = false; | ||
| 147 | if (res.code == proxy.$passCode) { | ||
| 148 | let column = res.data?.column || {}; | ||
| 149 | tableFields.value = column; | ||
| 150 | getData(); | ||
| 151 | } else { | ||
| 152 | ElMessage.error(res.msg); | ||
| 153 | } | ||
| 154 | }); | ||
| 155 | }); | ||
| 156 | |||
| 157 | const formatterPreviewDate = (row, info) => { | ||
| 158 | let enName = info.enName; | ||
| 159 | let v = row[enName]; | ||
| 160 | if (v === 0) { | ||
| 161 | return v; | ||
| 162 | } | ||
| 163 | if (!v || v == 'null') { | ||
| 164 | return '--'; | ||
| 165 | } | ||
| 166 | if (info.dataType === 'datetime') { | ||
| 167 | return Moment(v).format('YYYY-MM-DD HH:mm:ss'); | ||
| 168 | } | ||
| 169 | if (info.dataType === 'date') { | ||
| 170 | if (isNaN(<any>(new Date(v)))) { | ||
| 171 | return Moment(parseInt(v)).format('YYYY-MM-DD'); | ||
| 172 | } else { | ||
| 173 | return Moment(v).format('YYYY-MM-DD'); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | return v; | ||
| 177 | }; | ||
| 178 | |||
| 179 | const pageChange = (info) => { | ||
| 180 | pageInfo.value.curr = Number(info.curr); | ||
| 181 | pageInfo.value.limit = Number(info.limit); | ||
| 182 | getData(); | ||
| 183 | } | ||
| 184 | |||
| 185 | const promise: any = ref(null); | ||
| 186 | |||
| 187 | const exportData = () => { | ||
| 188 | if (promise) { | ||
| 189 | return; | ||
| 190 | } | ||
| 191 | promise.value = exportAnonExecData({ | ||
| 192 | taskGuid: route.query.guid, | ||
| 193 | execGuid: route.query.execGuid | ||
| 194 | }).then((res: any) => { | ||
| 195 | promise.value = null; | ||
| 196 | if (res && !res.msg) { | ||
| 197 | download(res, route.query.taskName + '_匿名化数据.xlsx', 'excel') | ||
| 198 | } else { | ||
| 199 | res?.msg && ElMessage.error(res?.msg); | ||
| 200 | } | ||
| 201 | }).catch(() => { | ||
| 202 | promise.value = null; | ||
| 203 | }) | ||
| 204 | } | ||
| 205 | |||
| 206 | </script> | ||
| 207 | |||
| 208 | <template> | ||
| 209 | <div class="table_tool_wrap" v-loading="tableDataLoading"> | ||
| 210 | <el-button v-show="props.isPage" style="margin-bottom: 8px;" type="primary" @click="exportData" | ||
| 211 | v-preReClick>导出数据</el-button> | ||
| 212 | <el-table ref="tableRef" v-show="tableFields.length" :data="tableData" :highlight-current-row="true" stripe border | ||
| 213 | tooltip-effect="light" height="100%" row-key="guid" :style="{ width: '100%', height: 'calc(100% - 64px)' }"> | ||
| 214 | <template v-for="(item, index) in (tableFields || [])"> | ||
| 215 | <el-table-column :label="item.chName" :width="item.dataType === 'datetime' | ||
| 216 | ? TableColumnWidth.DATETIME | ||
| 217 | : item.dataType === 'date' | ||
| 218 | ? TableColumnWidth.DATE | ||
| 219 | : originTableFieldColumn[item.enName] | ||
| 220 | " :align="getTextAlign(item)" :header-align="getTextAlign(item)" | ||
| 221 | :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true"> | ||
| 222 | </el-table-column> | ||
| 223 | </template> | ||
| 224 | </el-table> | ||
| 225 | <div v-show="!tableFields.length" class="empty-content"> | ||
| 226 | <img src="../../assets/images/empty-data.png" :style="{ width: '168px', height: '96px' }" /> | ||
| 227 | <div class="empty-text">暂无数据</div> | ||
| 228 | </div> | ||
| 229 | <PageNav :class="[pageInfo.type]" :pageInfo="pageInfo" @pageChange="pageChange" /> | ||
| 230 | </div> | ||
| 231 | </template> | ||
| 232 | |||
| 233 | <style lang="scss" scoped> | ||
| 234 | .table_tool_wrap { | ||
| 235 | width: 100%; | ||
| 236 | height: 100%; | ||
| 237 | padding: 8px 16px 16px; | ||
| 238 | |||
| 239 | .tips_text { | ||
| 240 | font-size: 14px; | ||
| 241 | color: var(--el-text-color-tip); | ||
| 242 | display: block; | ||
| 243 | font-weight: normal; | ||
| 244 | margin-bottom: 8px; | ||
| 245 | line-height: 21px; | ||
| 246 | } | ||
| 247 | |||
| 248 | .el-table { | ||
| 249 | display: inline-block; | ||
| 250 | } | ||
| 251 | |||
| 252 | .empty-content { | ||
| 253 | display: flex; | ||
| 254 | align-items: center; | ||
| 255 | justify-content: center; | ||
| 256 | height: 100%; | ||
| 257 | width: 100%; | ||
| 258 | flex-direction: column; | ||
| 259 | |||
| 260 | .empty-text { | ||
| 261 | font-size: 14px; | ||
| 262 | color: #b2b2b2; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -4,16 +4,48 @@ | ... | @@ -4,16 +4,48 @@ |
| 4 | 4 | ||
| 5 | <script lang="ts" setup name="anonTaskCreate"> | 5 | <script lang="ts" setup name="anonTaskCreate"> |
| 6 | import { | 6 | import { |
| 7 | dataSourceTypeList | 7 | dataSourceTypeList, |
| 8 | getAnonTaskDetail, | ||
| 9 | getParamsList, | ||
| 10 | chTransformEn, | ||
| 11 | getAnonAnalyzeResult, | ||
| 12 | getAnonAnalyzePageData, | ||
| 13 | getDatabase, | ||
| 14 | getDsTableByDs, | ||
| 15 | getDsTableFieldColumn, | ||
| 16 | getDsTableSampleData, | ||
| 17 | saveAnonTask, | ||
| 18 | updateAnonTask, | ||
| 19 | exportAnonExecData, | ||
| 8 | } from '@/api/modules/dataAnonymization'; | 20 | } from '@/api/modules/dataAnonymization'; |
| 21 | import { | ||
| 22 | parseAndDecodeUrl, | ||
| 23 | getDownFileSignByUrl, | ||
| 24 | obsDownloadRequest | ||
| 25 | } from "@/api/modules/obsService"; | ||
| 9 | import useUserStore from "@/store/modules/user"; | 26 | import useUserStore from "@/store/modules/user"; |
| 27 | import { useValidator } from '@/hooks/useValidator'; | ||
| 28 | import { TableColumnWidth } from '@/utils/enum'; | ||
| 29 | import { calcColumnWidth } from "@/utils/index"; | ||
| 30 | import Moment from 'moment'; | ||
| 31 | import anonTaskStepTwo from './anonTaskStepTwo.vue'; | ||
| 32 | import * as XLSX from 'xlsx'; | ||
| 33 | import { ElMessage } from 'element-plus'; | ||
| 34 | import { isEqual, cloneDeep } from "lodash-es"; | ||
| 35 | import { download } from "@/utils/common"; | ||
| 36 | import anonResultView from './anonResultView.vue'; | ||
| 10 | 37 | ||
| 11 | const { proxy } = getCurrentInstance() as any; | 38 | const { proxy } = getCurrentInstance() as any; |
| 12 | const userStore = useUserStore(); | 39 | const userStore = useUserStore(); |
| 13 | const route = useRoute(); | 40 | const route = useRoute(); |
| 14 | const router = useRouter(); | 41 | const router = useRouter(); |
| 15 | const fullPath = route.fullPath; | 42 | const fullPath = route.fullPath; |
| 16 | 43 | const taskGuid = ref(route.query.guid); | |
| 44 | /** 提交保存和编辑后的执行guid */ | ||
| 45 | const taskExecGuid = ref(''); | ||
| 46 | /** 是否执行结束 */ | ||
| 47 | const isExecEnd = ref(false); | ||
| 48 | const { required } = useValidator(); | ||
| 17 | const fullscreenLoading = ref(false); | 49 | const fullscreenLoading = ref(false); |
| 18 | 50 | ||
| 19 | const step = ref(0); | 51 | const step = ref(0); |
| ... | @@ -27,11 +59,17 @@ const stepsInfo = ref({ | ... | @@ -27,11 +59,17 @@ const stepsInfo = ref({ |
| 27 | ] | 59 | ] |
| 28 | }) | 60 | }) |
| 29 | 61 | ||
| 30 | const dataSourceList = ref([]); | 62 | /** 数据源列表 */ |
| 63 | const dataSourceList: any = ref([]); | ||
| 64 | |||
| 65 | /** 数据源对应的数据表 */ | ||
| 66 | const dsTableList: any = ref([]); | ||
| 31 | 67 | ||
| 32 | /** 数据共享类型字段列表 */ | 68 | /** 数据共享类型字段列表 */ |
| 33 | const dataSharingTypeList = ref([]); | 69 | const dataSharingTypeList = ref([]); |
| 34 | 70 | ||
| 71 | const formRef = ref(); | ||
| 72 | |||
| 35 | /** 数据选择的表单配置信息 */ | 73 | /** 数据选择的表单配置信息 */ |
| 36 | const dataSelectInfoItems = ref([{ | 74 | const dataSelectInfoItems = ref([{ |
| 37 | label: '任务名称', | 75 | label: '任务名称', |
| ... | @@ -49,8 +87,8 @@ const dataSelectInfoItems = ref([{ | ... | @@ -49,8 +87,8 @@ const dataSelectInfoItems = ref([{ |
| 49 | type: 'select', | 87 | type: 'select', |
| 50 | placeholder: '请选择', | 88 | placeholder: '请选择', |
| 51 | field: 'dataSharingTypeCode', | 89 | field: 'dataSharingTypeCode', |
| 52 | default: '', | 90 | default: '01', |
| 53 | options: dataSharingTypeList, | 91 | options: dataSharingTypeList.value, |
| 54 | props: { | 92 | props: { |
| 55 | label: "label", | 93 | label: "label", |
| 56 | value: "value", | 94 | value: "value", |
| ... | @@ -60,6 +98,21 @@ const dataSelectInfoItems = ref([{ | ... | @@ -60,6 +98,21 @@ const dataSelectInfoItems = ref([{ |
| 60 | clearable: true, | 98 | clearable: true, |
| 61 | visible: true, | 99 | visible: true, |
| 62 | }, { | 100 | }, { |
| 101 | label: '患者占总人口比', | ||
| 102 | type: 'input', | ||
| 103 | placeholder: '数值,支持小数点9位', | ||
| 104 | field: 'patientPopulationRate', | ||
| 105 | maxlength: 11, | ||
| 106 | min: 0, | ||
| 107 | max: 1, | ||
| 108 | inputType: 'scoreNumber', | ||
| 109 | decimalCnt: 9, | ||
| 110 | default: '', | ||
| 111 | required: true, | ||
| 112 | filterable: true, | ||
| 113 | clearable: true, | ||
| 114 | visible: true, | ||
| 115 | }, { | ||
| 63 | label: '数据来源', | 116 | label: '数据来源', |
| 64 | type: 'select', | 117 | type: 'select', |
| 65 | placeholder: '请选择', | 118 | placeholder: '请选择', |
| ... | @@ -72,7 +125,6 @@ const dataSelectInfoItems = ref([{ | ... | @@ -72,7 +125,6 @@ const dataSelectInfoItems = ref([{ |
| 72 | }, | 125 | }, |
| 73 | required: true, | 126 | required: true, |
| 74 | filterable: true, | 127 | filterable: true, |
| 75 | clearable: true, | ||
| 76 | visible: true, | 128 | visible: true, |
| 77 | }, { | 129 | }, { |
| 78 | label: '数据源', | 130 | label: '数据源', |
| ... | @@ -89,31 +141,694 @@ const dataSelectInfoItems = ref([{ | ... | @@ -89,31 +141,694 @@ const dataSelectInfoItems = ref([{ |
| 89 | visible: true, | 141 | visible: true, |
| 90 | required: true | 142 | required: true |
| 91 | }, { | 143 | }, { |
| 144 | label: "数据表", | ||
| 145 | type: "select", | ||
| 146 | placeholder: "请选择", | ||
| 147 | field: "tableName", | ||
| 148 | options: dsTableList.value, | ||
| 149 | props: { | ||
| 150 | label: 'tableComment', | ||
| 151 | value: 'tableName' | ||
| 152 | }, | ||
| 153 | default: '', | ||
| 154 | filterable: true, | ||
| 155 | clearable: true, | ||
| 156 | required: true, | ||
| 157 | }, { | ||
| 92 | label: '文件上传', | 158 | label: '文件上传', |
| 93 | tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB', | 159 | tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB', |
| 94 | type: 'upload-file', | 160 | type: 'upload-file', |
| 95 | accept: '.xlsx, .xls, .csv', | 161 | accept: '.xlsx, .xls, .csv', |
| 96 | limitSize: 10, | 162 | limitSize: 10, |
| 163 | limit: 1, | ||
| 97 | isExcel: true, | 164 | isExcel: true, |
| 98 | required: true, | 165 | required: true, |
| 99 | default: <any>[], | 166 | default: <any>[], |
| 100 | block: true, | 167 | block: false, |
| 168 | col: 'wid60', | ||
| 101 | visible: false, | 169 | visible: false, |
| 102 | field: 'file', | 170 | field: 'file', |
| 103 | }]); | 171 | }]); |
| 104 | 172 | ||
| 105 | const dataSelectInfoFormRules = ref({ | 173 | const dataSelectInfoFormRules = ref({ |
| 174 | taskName: [required('请输入任务名称')], | ||
| 175 | dataSharingTypeCode: [required('请选择数据共享类型')], | ||
| 176 | patientPopulationRate: [required('请输入患者占总人口比')], | ||
| 177 | dataSourceGuid: [required('请选择数据源')], | ||
| 178 | tableName: [required('请选择数据表')], | ||
| 179 | file: [{ | ||
| 180 | validator: (rule: any, value: any, callback: any) => { | ||
| 181 | if (!value?.length) { | ||
| 182 | callback(new Error('请上传文件')) | ||
| 183 | } else { | ||
| 184 | callback(); | ||
| 185 | } | ||
| 186 | }, trigger: 'change' | ||
| 187 | }] | ||
| 188 | }); | ||
| 189 | |||
| 190 | /** 最新选中的 */ | ||
| 191 | const currDatasourceSelect: any = ref({}); | ||
| 192 | |||
| 193 | const handleDataSelectFormSelectChange = async (val, row, formInfo) => { | ||
| 194 | if (row.field == 'dataSource') { | ||
| 195 | dataSelectInfoItems.value[4].visible = val == 1; | ||
| 196 | dataSelectInfoItems.value[5].visible = val == 1; | ||
| 197 | dataSelectInfoItems.value[6].visible = val == 2; | ||
| 198 | dataSelectInfoItems.value.forEach(d => { | ||
| 199 | d.default = formInfo[d.field]; | ||
| 200 | if (d.field == 'file') { | ||
| 201 | d.default = !d.default ? [] : d.default; | ||
| 202 | } | ||
| 203 | }); | ||
| 204 | sampleTableFields.value = []; | ||
| 205 | parseFileDataSum.value = []; | ||
| 206 | sampleTableData.value = []; | ||
| 207 | } else if (row.field == 'dataSourceGuid') { | ||
| 208 | if (!val) { | ||
| 209 | currDatasourceSelect.value = []; | ||
| 210 | sampleTableFields.value = []; | ||
| 211 | parseFileDataSum.value = []; | ||
| 212 | sampleTableData.value = []; | ||
| 213 | dataSelectInfoItems.value.forEach(d => { | ||
| 214 | d.default = formInfo[d.field]; | ||
| 215 | if (d.field == 'file') { | ||
| 216 | d.default = !d.default ? [] : d.default; | ||
| 217 | } else if (d.field == 'tableName') { | ||
| 218 | d.options = dsTableList.value; | ||
| 219 | d.default = ''; | ||
| 220 | } | ||
| 221 | }); | ||
| 222 | return; | ||
| 223 | } | ||
| 224 | let dsInfo = currDatasourceSelect.value = dataSourceList.value.find(d => d.guid == val); | ||
| 225 | //清除数据表得值,重新获取下拉列表 | ||
| 226 | const res: any = await getDsTableByDs({ | ||
| 227 | pageSize: -1, | ||
| 228 | pageIndex: 1, | ||
| 229 | dataSourceGuid: val, | ||
| 230 | database: dsInfo.databaseNameEn, | ||
| 231 | databaseType: dsInfo.databaseType, | ||
| 232 | tableName: '', | ||
| 233 | hadFlag: false | ||
| 234 | }); | ||
| 235 | if (res.code == proxy.$passCode) { | ||
| 236 | dsTableList.value = res.data?.records || []; | ||
| 237 | dataSelectInfoItems.value.forEach(d => { | ||
| 238 | d.default = formInfo[d.field]; | ||
| 239 | if (d.field == 'file') { | ||
| 240 | d.default = !d.default ? [] : d.default; | ||
| 241 | } else if (d.field == 'tableName') { | ||
| 242 | d.options = dsTableList.value; | ||
| 243 | d.default = ''; | ||
| 244 | } | ||
| 245 | }); | ||
| 246 | } else { | ||
| 247 | proxy.$ElMessage.error(res.msg); | ||
| 248 | } | ||
| 249 | sampleTableFields.value = []; | ||
| 250 | parseFileDataSum.value = []; | ||
| 251 | sampleTableData.value = []; | ||
| 252 | } else if (row.field == 'tableName') { | ||
| 253 | if (!val) { | ||
| 254 | sampleTableFields.value = []; | ||
| 255 | sampleTableData.value = []; | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | getDsTableFieldColumn({ | ||
| 259 | pageSize: 50, | ||
| 260 | pageIndex: 1, | ||
| 261 | dataSourceGuid: currDatasourceSelect.value.guid, | ||
| 262 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 263 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 264 | tableName: val, | ||
| 265 | }).then((res: any) => { | ||
| 266 | if (res.code == proxy.$passCode) { | ||
| 267 | sampleTableFields.value = res.data?.map(d => { | ||
| 268 | d.fieldDataType = d.dataType; | ||
| 269 | d.enName = d.columnName; | ||
| 270 | d.chName = d.columnZhName; | ||
| 271 | return d; | ||
| 272 | }) || []; | ||
| 273 | } else { | ||
| 274 | ElMessage.error(res.msg); | ||
| 275 | } | ||
| 276 | }); | ||
| 277 | /** 判断有抽样数据,需要查询接口 */ | ||
| 278 | getSampleDataByDsTable(); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | const dataSimpleFormRef = ref(); | ||
| 283 | |||
| 284 | /** 抽样数据预览 */ | ||
| 285 | const dataSimpleFormItems = ref([{ | ||
| 286 | label: '抽样开关', | ||
| 287 | type: 'switch', | ||
| 288 | field: 'enableSamplingRate', | ||
| 289 | default: 'N', | ||
| 290 | col: 'autoWidth', | ||
| 291 | activeValue: 'Y', | ||
| 292 | inactiveValue: 'N' | ||
| 293 | }, { | ||
| 294 | label: '抽样比例(%)', | ||
| 295 | type: 'input', | ||
| 296 | placeholder: '请输入', | ||
| 297 | field: 'samplingRate', | ||
| 298 | maxlength: 3, | ||
| 299 | min: 0, //可以是0条。万一只是想看下字段呢 | ||
| 300 | max: 100, | ||
| 301 | inputType: 'integerNumber', | ||
| 302 | default: 10, | ||
| 303 | required: true, | ||
| 304 | filterable: true, | ||
| 305 | clearable: true, | ||
| 306 | visible: false, | ||
| 307 | }]); | ||
| 106 | 308 | ||
| 309 | const dataSimpleFormRules = ref({ | ||
| 310 | samplingRate: [required('请填写抽样比例')], | ||
| 107 | }); | 311 | }); |
| 108 | 312 | ||
| 109 | const changeStep = (val) => { | 313 | const oldSamplingRate = ref('10'); |
| 110 | 314 | ||
| 315 | const handleDataSimpleFormSwitchChange = (val, info) => { | ||
| 316 | if (val == 'N') { | ||
| 317 | oldSamplingRate.value = info.samplingRate; | ||
| 318 | } else { | ||
| 319 | dataSimpleFormItems.value[1].default = oldSamplingRate.value || 10; | ||
| 320 | } | ||
| 321 | dataSimpleFormItems.value[1].visible = val == 'Y'; | ||
| 322 | dataSimpleFormItems.value[0].default = info.enableSamplingRate || 'N'; | ||
| 323 | if (formRef.value?.formInline?.file?.length) { | ||
| 324 | transferSampleData(); | ||
| 325 | } else { | ||
| 326 | getSampleDataByDsTable(); | ||
| 327 | } | ||
| 111 | } | 328 | } |
| 112 | 329 | ||
| 113 | const exportResult = () => { | 330 | /** 输入抽样比例值改变 */ |
| 331 | const handleDataSimpleFormChange = (val) => { | ||
| 332 | if (formRef.value?.formInline?.file?.length) { | ||
| 333 | transferSampleData(); | ||
| 334 | } else { | ||
| 335 | getSampleDataByDsTable(); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | /** 样本表格加载中 */ | ||
| 340 | const sampleTableDataLoading = ref(false); | ||
| 341 | |||
| 342 | /** 样本表格的数据 */ | ||
| 343 | const sampleTableData: any = ref([]); | ||
| 344 | |||
| 345 | /** 样本表格的字段 */ | ||
| 346 | const sampleTableFields: any = ref([]); | ||
| 347 | |||
| 348 | /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */ | ||
| 349 | const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => { | ||
| 350 | let d: any[] = []; | ||
| 351 | data.forEach((dt) => d.push(dt[prop])); | ||
| 352 | return calcColumnWidth( | ||
| 353 | d, | ||
| 354 | title, | ||
| 355 | { | ||
| 356 | fontSize: 14, | ||
| 357 | fontFamily: "SimSun", | ||
| 358 | }, | ||
| 359 | { | ||
| 360 | fontSize: 14, | ||
| 361 | fontFamily: "SimSun", | ||
| 362 | }, | ||
| 363 | otherWidth | ||
| 364 | ); | ||
| 365 | }; | ||
| 366 | |||
| 367 | /** 每列字段对应的列宽计算结果。 */ | ||
| 368 | const originTableFieldColumn = ref({}); | ||
| 369 | |||
| 370 | const getTextAlign = (field) => { | ||
| 371 | if (field.dataType === 'decimal' || field.dataType === 'int') { | ||
| 372 | return 'right'; | ||
| 373 | } | ||
| 374 | return 'left' | ||
| 375 | } | ||
| 376 | |||
| 377 | watch( | ||
| 378 | sampleTableData, | ||
| 379 | (val: any[], oldVal) => { | ||
| 380 | if (!sampleTableFields.value?.length) { | ||
| 381 | originTableFieldColumn.value = {}; | ||
| 382 | return; | ||
| 383 | } | ||
| 384 | originTableFieldColumn.value = {}; | ||
| 385 | sampleTableFields.value.forEach((field, index) => { | ||
| 386 | originTableFieldColumn.value[field.enName] = calcTableColumnWidth( | ||
| 387 | val?.slice(0, 20) || [], | ||
| 388 | field.enName, | ||
| 389 | field.chName, | ||
| 390 | 24 | ||
| 391 | ); | ||
| 392 | }); | ||
| 393 | }, | ||
| 394 | { | ||
| 395 | deep: true, | ||
| 396 | } | ||
| 397 | ); | ||
| 398 | |||
| 399 | const formatterPreviewDate = (row, info) => { | ||
| 400 | let enName = info.enName; | ||
| 401 | let v = row[enName]; | ||
| 402 | if (v === 0) { | ||
| 403 | return v; | ||
| 404 | } | ||
| 405 | if (!v) { | ||
| 406 | return v || '--'; | ||
| 407 | } | ||
| 408 | if (info.dataType === 'datetime') { | ||
| 409 | return Moment(v).format('YYYY-MM-DD HH:mm:ss'); | ||
| 410 | } | ||
| 411 | if (info.dataType === 'date') { | ||
| 412 | if (isNaN(<any>(new Date(v)))) { | ||
| 413 | return Moment(parseInt(v)).format('YYYY-MM-DD'); | ||
| 414 | } else { | ||
| 415 | return Moment(v).format('YYYY-MM-DD'); | ||
| 416 | } | ||
| 417 | } | ||
| 418 | return v; | ||
| 419 | }; | ||
| 420 | |||
| 421 | /** 解析的总的表格数据,方便后面修改抽样比例时使用 */ | ||
| 422 | const parseFileDataSum: any = ref([]); | ||
| 423 | |||
| 424 | const parseFileData = (fileRaw) => { | ||
| 425 | sampleTableDataLoading.value = true; | ||
| 426 | fileRaw.arrayBuffer().then(async (f) => { | ||
| 427 | const wb = XLSX.read(f, { | ||
| 428 | raw: false, cellDates: true | ||
| 429 | }); | ||
| 430 | const sheet = wb.Sheets[wb.SheetNames[0]]; | ||
| 431 | const json: any[] = XLSX.utils.sheet_to_json(sheet, { header: 1 }); | ||
| 432 | if (json.length == 0) { | ||
| 433 | sampleTableFields.value = []; | ||
| 434 | sampleTableData.value = []; | ||
| 435 | } else { | ||
| 436 | const res = await chTransformEn(json[0]); | ||
| 437 | let fields = res.data || []; | ||
| 438 | sampleTableFields.value = fields?.map((j, index) => { | ||
| 439 | return { | ||
| 440 | index: index, | ||
| 441 | enName: j.enName + '', | ||
| 442 | chName: j.chName + '', | ||
| 443 | dataType: 'varchar' | ||
| 444 | } | ||
| 445 | }) || []; | ||
| 446 | parseFileDataSum.value = json; | ||
| 447 | /** 粗略算出字段类型 */ | ||
| 448 | json.slice(1, 10).forEach((info, row) => { | ||
| 449 | json[0].forEach((name, col) => { | ||
| 450 | if (info[col] === "" || info[col] == null || sampleTableFields.value[col].dataType != 'varchar') { | ||
| 451 | return; | ||
| 452 | } else { | ||
| 453 | var cellRef = XLSX.utils.encode_cell({ r: row + 1, c: col }); | ||
| 454 | var cell = sheet[cellRef]; | ||
| 455 | let v = cell.w || info[col]; | ||
| 456 | let isNum = cell.t == 'n'; | ||
| 457 | if (isNum) { | ||
| 458 | if (v.includes('.') && sampleTableFields.value[col].dataType != 'decimal') { | ||
| 459 | sampleTableFields.value[col].dataType = 'decimal'; | ||
| 460 | } else { | ||
| 461 | sampleTableFields.value[col].dataType = 'int'; | ||
| 462 | } | ||
| 463 | } | ||
| 464 | } | ||
| 465 | }); | ||
| 466 | }) | ||
| 467 | transferSampleData(); | ||
| 468 | } | ||
| 469 | sampleTableDataLoading.value = false; | ||
| 470 | }); | ||
| 471 | } | ||
| 472 | |||
| 473 | /** 获取文件解析后根据抽样比例得出的表格数据 */ | ||
| 474 | const transferSampleData = () => { | ||
| 475 | let samplingRate = dataSimpleFormRef.value?.formInline?.samplingRate; | ||
| 476 | if (parseFileDataSum.value.length > 1 && samplingRate) { | ||
| 477 | let totalCnt = parseFileDataSum.value.length - 1; | ||
| 478 | let cnt = Math.ceil(samplingRate * 0.01 * totalCnt) + 1; | ||
| 479 | sampleTableData.value = parseFileDataSum.value.slice(1, cnt > 1000 ? 1001 : cnt).map((info, row) => { | ||
| 480 | let object = {}; | ||
| 481 | parseFileDataSum.value[0].forEach((chName, col) => { | ||
| 482 | let name = sampleTableFields.value[col].enName; | ||
| 483 | object[name] = info[col]; | ||
| 484 | }); | ||
| 485 | return object; | ||
| 486 | }); | ||
| 487 | } else { | ||
| 488 | sampleTableData.value = []; | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | /** 获取选择的数据库表根据抽样比例得出的表格数据 */ | ||
| 493 | const getSampleDataByDsTable = () => { | ||
| 494 | const tableName = formRef.value?.formInline?.tableName; | ||
| 495 | if (!currDatasourceSelect.value.guid || !tableName) { | ||
| 496 | sampleTableFields.value = []; | ||
| 497 | sampleTableData.value = []; | ||
| 498 | return; | ||
| 499 | } | ||
| 500 | let samplingRate = dataSimpleFormRef.value?.formInline?.samplingRate; | ||
| 501 | if (!samplingRate) { | ||
| 502 | sampleTableData.value = []; | ||
| 503 | return; | ||
| 504 | } | ||
| 505 | let totalCnt = dsTableList.value.find(t => t.tableName == tableName)?.tableRows || 0; | ||
| 506 | let cnt = Math.ceil(samplingRate * 0.01 * totalCnt); | ||
| 507 | if (!cnt) { | ||
| 508 | sampleTableData.value = []; | ||
| 509 | return; | ||
| 510 | } | ||
| 511 | sampleTableDataLoading.value = true; | ||
| 512 | getDsTableSampleData({ | ||
| 513 | limitNum: cnt, | ||
| 514 | pageSize: cnt, | ||
| 515 | pageIndex: 1, | ||
| 516 | dataSourceGuid: currDatasourceSelect.value.guid, | ||
| 517 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 518 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 519 | tableName: tableName, | ||
| 520 | hadFlag: false, | ||
| 521 | }).then((res: any) => { | ||
| 522 | sampleTableDataLoading.value = false; | ||
| 523 | if (res.code == proxy.$passCode) { | ||
| 524 | sampleTableData.value = res.data?.datas || []; | ||
| 525 | } else { | ||
| 526 | sampleTableData.value = []; | ||
| 527 | ElMessage.error(res.msg); | ||
| 528 | } | ||
| 529 | }); | ||
| 530 | } | ||
| 531 | |||
| 532 | const uploadFileChange = (file) => { | ||
| 533 | sampleTableFields.value = []; | ||
| 534 | sampleTableData.value = []; | ||
| 535 | if (!file.length) { | ||
| 536 | sampleTableFields.value = []; | ||
| 537 | sampleTableData.value = []; | ||
| 538 | return; | ||
| 539 | } | ||
| 540 | let fileRaw = file[0].file; | ||
| 541 | parseFileData(fileRaw); | ||
| 542 | } | ||
| 543 | |||
| 544 | /** 第二步的配置组件引用。 */ | ||
| 545 | const anonTaskStepTwoRef = ref(); | ||
| 114 | 546 | ||
| 547 | const changeStep = async (val) => { | ||
| 548 | if (val == 2) { | ||
| 549 | formRef.value?.ruleFormRef?.validate((valid) => { | ||
| 550 | if (valid) { | ||
| 551 | dataSimpleFormRef.value?.ruleFormRef?.validate((valid) => { | ||
| 552 | if (valid) { | ||
| 553 | step.value = val - 1; | ||
| 554 | stepsInfo.value.step = val - 1; | ||
| 555 | } | ||
| 556 | }); | ||
| 557 | } | ||
| 558 | }); | ||
| 559 | } else if (val == 3) { | ||
| 560 | // 保存并提交 TODO。需要加个 记录旧值的,用来判断新值和旧值,是否发生变化,若变化则需要调用保存接口之后,再进行下一步。 | ||
| 561 | let configInfo = await anonTaskStepTwoRef.value?.getStepTwoConfigInfo(); | ||
| 562 | if (!configInfo) { | ||
| 563 | return; | ||
| 564 | } | ||
| 565 | let saveParams: any = { ...formRef.value.formInline }; | ||
| 566 | if (saveParams.file?.length) { | ||
| 567 | saveParams.filePath = { | ||
| 568 | name: saveParams.file[0].name, | ||
| 569 | url: saveParams.file[0].url | ||
| 570 | } | ||
| 571 | delete saveParams.file; | ||
| 572 | saveParams.dataSourceGuid = null; | ||
| 573 | saveParams.tableName = null; | ||
| 574 | } else { | ||
| 575 | saveParams.filePath = null; | ||
| 576 | } | ||
| 577 | let simpleFormInline = dataSimpleFormRef.value.formInline; | ||
| 578 | if (simpleFormInline.enableSamplingRate == 'Y') { | ||
| 579 | saveParams.samplingRate = simpleFormInline.samplingRate && parseInt(simpleFormInline.samplingRate); | ||
| 580 | } else { | ||
| 581 | saveParams.samplingRate = null; | ||
| 582 | } | ||
| 583 | let privacy = configInfo.anonPrivacyMode; | ||
| 584 | delete privacy.isKaNumber; | ||
| 585 | delete privacy.isRiskThreshold; | ||
| 586 | delete privacy.isTcField; | ||
| 587 | delete privacy.isLdField; | ||
| 588 | // 为空时为了跟原始值保持一致 | ||
| 589 | privacy.kaNumber = (privacy.kaNumber && parseInt(privacy.kaNumber)) ?? null; | ||
| 590 | privacy.riskThreshold = (privacy.riskThreshold && parseFloat(privacy.riskThreshold)) ?? null; | ||
| 591 | privacy.tcFieldName = privacy.tcFieldName ?? null; | ||
| 592 | privacy.tcThreshold = (privacy.tcThreshold && parseFloat(privacy.tcThreshold)) ?? null; | ||
| 593 | privacy.ldFieldName = privacy.ldFieldName ?? null; | ||
| 594 | privacy.ldNumber = (privacy.ldNumber && parseInt(privacy.ldNumber)) ?? null; | ||
| 595 | Object.assign(saveParams, configInfo); | ||
| 596 | if (taskGuid.value) { | ||
| 597 | saveParams.guid = taskGuid.value; | ||
| 598 | } | ||
| 599 | if (isEqual(saveParams, oldAnonTaskValueInfo.value)) { | ||
| 600 | step.value = val - 1; | ||
| 601 | stepsInfo.value.step = val - 1; | ||
| 602 | return; | ||
| 603 | } | ||
| 604 | if (!taskGuid.value) { //保存 | ||
| 605 | fullscreenLoading.value = true; | ||
| 606 | saveAnonTask(saveParams).then((res: any) => { | ||
| 607 | fullscreenLoading.value = false; | ||
| 608 | if (res.code == proxy.$passCode) { | ||
| 609 | taskGuid.value = res.data?.guid; | ||
| 610 | isExecEnd.value = false; | ||
| 611 | taskExecGuid.value = res.data?.lastExecGuid; | ||
| 612 | step.value = val - 1; | ||
| 613 | stepsInfo.value.step = val - 1; | ||
| 614 | oldAnonTaskValueInfo.value = saveParams; | ||
| 615 | } else { | ||
| 616 | ElMessage.error(res.msg); | ||
| 617 | } | ||
| 618 | }); | ||
| 619 | } else { //更新 | ||
| 620 | fullscreenLoading.value = true; | ||
| 621 | updateAnonTask(saveParams).then((res: any) => { | ||
| 622 | fullscreenLoading.value = false; | ||
| 623 | if (res.code == proxy.$passCode) { | ||
| 624 | isExecEnd.value = false; | ||
| 625 | taskExecGuid.value = res.data; | ||
| 626 | step.value = val - 1; | ||
| 627 | stepsInfo.value.step = val - 1; | ||
| 628 | oldAnonTaskValueInfo.value = saveParams; | ||
| 629 | } else { | ||
| 630 | ElMessage.error(res.msg); | ||
| 631 | } | ||
| 632 | }) | ||
| 633 | } | ||
| 634 | } else if (val == 4) { | ||
| 635 | //下一步之后,调用分析结果。 | ||
| 636 | |||
| 637 | getAnonAnalyzeResult(detailInfo.value.lastExecGuid).then((res: any) => { | ||
| 638 | debugger | ||
| 639 | }); | ||
| 640 | getAnonAnalyzePageData({ | ||
| 641 | pageSize: -1 | ||
| 642 | }).then((res: any) => { | ||
| 643 | debugger | ||
| 644 | }); | ||
| 645 | step.value = val - 1; | ||
| 646 | stepsInfo.value.step = val - 1; | ||
| 647 | } else if (val <= step.value) { | ||
| 648 | step.value = val - 1; | ||
| 649 | stepsInfo.value.step = val - 1; | ||
| 650 | } | ||
| 115 | } | 651 | } |
| 116 | 652 | ||
| 653 | const promise: any = ref(null); | ||
| 654 | const exportResult = () => { | ||
| 655 | promise.value = exportAnonExecData({ | ||
| 656 | taskGuid: route.query.guid, | ||
| 657 | execGuid: route.query.execGuid | ||
| 658 | }).then((res: any) => { | ||
| 659 | promise.value = null; | ||
| 660 | if (res && !res.msg) { | ||
| 661 | download(res, route.query.taskName + '_匿名化数据.xlsx', 'excel') | ||
| 662 | } else { | ||
| 663 | res?.msg && ElMessage.error(res?.msg); | ||
| 664 | } | ||
| 665 | }).catch(() => { | ||
| 666 | promise.value = null; | ||
| 667 | }) | ||
| 668 | } | ||
| 669 | |||
| 670 | /** 获取字段类型的数据字典 */ | ||
| 671 | const fieldTypeList: any = ref([]); | ||
| 672 | |||
| 673 | /** 编辑时获取的匿名化任务的详情信息 */ | ||
| 674 | const detailInfo: any = ref({}); | ||
| 675 | |||
| 676 | /** 记录原始的值信息,防止上一步之后未修改数据时不调用接口 */ | ||
| 677 | const oldAnonTaskValueInfo: any = ref({}); | ||
| 678 | |||
| 679 | onBeforeMount(() => { | ||
| 680 | if (taskGuid.value) { | ||
| 681 | fullscreenLoading.value = true; | ||
| 682 | getAnonTaskDetail(taskGuid.value).then(async (res: any) => { | ||
| 683 | if (res?.code == proxy.$passCode) { | ||
| 684 | detailInfo.value = res.data || {}; | ||
| 685 | oldAnonTaskValueInfo.value = { | ||
| 686 | guid: detailInfo.value.guid, | ||
| 687 | taskName: detailInfo.value.taskName, | ||
| 688 | dataSource: detailInfo.value.dataSource, | ||
| 689 | filePath: detailInfo.value.filePath && cloneDeep(detailInfo.value.filePath), | ||
| 690 | dataSourceGuid: detailInfo.value.dataSourceGuid, | ||
| 691 | tableName: detailInfo.value.tableName, | ||
| 692 | samplingRate: detailInfo.value.samplingRate, | ||
| 693 | patientPopulationRate: detailInfo.value.patientPopulationRate && typeof detailInfo.value.patientPopulationRate == 'number' ? detailInfo.value.patientPopulationRate?.toFixed(9) : detailInfo.value.patientPopulationRate, | ||
| 694 | dataSharingTypeCode: detailInfo.value.dataSharingTypeCode, | ||
| 695 | anonTaskRules: cloneDeep(detailInfo.value.anonTaskRules), | ||
| 696 | anonPrivacyMode: { | ||
| 697 | kaNumber: detailInfo.value.anonPrivacyMode?.kaNumber, | ||
| 698 | ldFieldName: detailInfo.value.anonPrivacyMode?.ldFieldName, | ||
| 699 | ldNumber: detailInfo.value.anonPrivacyMode?.ldNumber, | ||
| 700 | riskThreshold: detailInfo.value.anonPrivacyMode?.riskThreshold, | ||
| 701 | tcFieldName: detailInfo.value.anonPrivacyMode?.tcFieldName, | ||
| 702 | tcThreshold: detailInfo.value.anonPrivacyMode?.tcThreshold, | ||
| 703 | } | ||
| 704 | } | ||
| 705 | dataSelectInfoItems.value.forEach(d => { | ||
| 706 | d.default = detailInfo.value[d.field]; | ||
| 707 | if (d.field == 'file') { | ||
| 708 | d.default = detailInfo.value.filePath ? [detailInfo.value.filePath] : []; | ||
| 709 | } else if (d.field == 'patientPopulationRate') { | ||
| 710 | if (d.default && typeof d.default == 'number') { | ||
| 711 | d.default = d.default.toFixed(d.decimalCnt); | ||
| 712 | } | ||
| 713 | } | ||
| 714 | }); | ||
| 715 | if (detailInfo.value.samplingRate != null) { | ||
| 716 | dataSimpleFormItems.value[0].default = 'Y'; | ||
| 717 | dataSimpleFormItems.value[1].visible = true; | ||
| 718 | dataSimpleFormItems.value[1].default = detailInfo.value.samplingRate; | ||
| 719 | } else { | ||
| 720 | dataSimpleFormItems.value[0].default = 'N'; | ||
| 721 | dataSimpleFormItems.value[1].visible = false; | ||
| 722 | } | ||
| 723 | let dataSource = detailInfo.value.dataSource; | ||
| 724 | dataSelectInfoItems.value[4].visible = dataSource == 1; | ||
| 725 | dataSelectInfoItems.value[5].visible = dataSource == 1; | ||
| 726 | dataSelectInfoItems.value[6].visible = dataSource == 2; | ||
| 727 | //文件解析 | ||
| 728 | if (dataSource == 2) { | ||
| 729 | let url = detailInfo.value.filePath?.url; | ||
| 730 | sampleTableDataLoading.value = true; | ||
| 731 | const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(url).fileName); | ||
| 732 | if (!refSignInfo?.data) { | ||
| 733 | fullscreenLoading.value = false; | ||
| 734 | refSignInfo?.msg && ElMessage.error(refSignInfo?.msg); | ||
| 735 | return; | ||
| 736 | } | ||
| 737 | obsDownloadRequest(refSignInfo?.data).then((res: any) => { | ||
| 738 | sampleTableDataLoading.value = false; | ||
| 739 | if (res && !res.msg) { | ||
| 740 | parseFileData(res); | ||
| 741 | } else { | ||
| 742 | res?.msg && ElMessage.error(res?.msg); | ||
| 743 | } | ||
| 744 | }) | ||
| 745 | } else { | ||
| 746 | const res: any = await getDatabase({ connectStatus: 1 }); | ||
| 747 | if (res?.code == proxy.$passCode) { | ||
| 748 | dataSourceList.value = res.data || []; | ||
| 749 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSourceGuid'); | ||
| 750 | item && (item.options = dataSourceList.value); | ||
| 751 | } else { | ||
| 752 | proxy.$ElMessage.error(res.msg); | ||
| 753 | } | ||
| 754 | currDatasourceSelect.value = dataSourceList.value.find(d => d.guid == detailInfo.value.dataSourceGuid); | ||
| 755 | const tableRes: any = await getDsTableByDs({ | ||
| 756 | pageSize: -1, | ||
| 757 | pageIndex: 1, | ||
| 758 | dataSourceGuid: detailInfo.value.dataSourceGuid, | ||
| 759 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 760 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 761 | tableName: '', | ||
| 762 | hadFlag: false | ||
| 763 | }); | ||
| 764 | if (tableRes?.code == proxy.$passCode) { | ||
| 765 | dsTableList.value = tableRes.data?.records || []; | ||
| 766 | let item = dataSelectInfoItems.value.find(item => item.field == 'tableName'); | ||
| 767 | item && (item.options = dsTableList.value); | ||
| 768 | } else { | ||
| 769 | proxy.$ElMessage.error(tableRes.msg); | ||
| 770 | } | ||
| 771 | getDsTableFieldColumn({ | ||
| 772 | pageSize: 50, | ||
| 773 | pageIndex: 1, | ||
| 774 | dataSourceGuid: currDatasourceSelect.value.guid, | ||
| 775 | database: currDatasourceSelect.value.databaseNameEn, | ||
| 776 | databaseType: currDatasourceSelect.value.databaseType, | ||
| 777 | tableName: detailInfo.value.tableName, | ||
| 778 | }).then((res: any) => { | ||
| 779 | if (res.code == proxy.$passCode) { | ||
| 780 | sampleTableFields.value = res.data?.map(d => { | ||
| 781 | d.fieldDataType = d.dataType; | ||
| 782 | d.enName = d.columnName; | ||
| 783 | d.chName = d.columnZhName; | ||
| 784 | return d; | ||
| 785 | }) || []; | ||
| 786 | } else { | ||
| 787 | ElMessage.error(res.msg); | ||
| 788 | } | ||
| 789 | }); | ||
| 790 | /** 判断有抽样数据,需要查询接口 */ | ||
| 791 | getSampleDataByDsTable(); | ||
| 792 | } | ||
| 793 | fullscreenLoading.value = false; | ||
| 794 | } else { | ||
| 795 | fullscreenLoading.value = false; | ||
| 796 | proxy.$ElMessage.error(res.msg); | ||
| 797 | } | ||
| 798 | }); | ||
| 799 | } else { | ||
| 800 | getDatabase({ connectStatus: 1 }).then((res: any) => { | ||
| 801 | if (res.code == proxy.$passCode) { | ||
| 802 | dataSourceList.value = res.data || []; | ||
| 803 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSourceGuid'); | ||
| 804 | item && (item.options = dataSourceList.value); | ||
| 805 | } else { | ||
| 806 | proxy.$ElMessage.error(res.msg); | ||
| 807 | } | ||
| 808 | }) | ||
| 809 | } | ||
| 810 | getParamsList({ | ||
| 811 | dictType: "数据共享类型", | ||
| 812 | }).then((res: any) => { | ||
| 813 | if (res?.code == proxy.$passCode) { | ||
| 814 | dataSharingTypeList.value = res.data || []; | ||
| 815 | let item = dataSelectInfoItems.value.find(item => item.field == 'dataSharingTypeCode'); | ||
| 816 | item && (item.options = dataSharingTypeList.value); | ||
| 817 | } else { | ||
| 818 | proxy.$ElMessage.error(res.msg); | ||
| 819 | } | ||
| 820 | }); | ||
| 821 | getParamsList({ | ||
| 822 | dictType: "字段类型", | ||
| 823 | }).then((res: any) => { | ||
| 824 | if (res?.code == proxy.$passCode) { | ||
| 825 | fieldTypeList.value = res.data || []; | ||
| 826 | } else { | ||
| 827 | proxy.$ElMessage.error(res.msg); | ||
| 828 | } | ||
| 829 | }); | ||
| 830 | }) | ||
| 831 | |||
| 117 | const cancelTask = () => { | 832 | const cancelTask = () => { |
| 118 | proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => { | 833 | proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => { |
| 119 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); | 834 | userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath)); |
| ... | @@ -135,7 +850,50 @@ const cancelTask = () => { | ... | @@ -135,7 +850,50 @@ const cancelTask = () => { |
| 135 | </div> | 850 | </div> |
| 136 | <div class="operator_panel_wrap" v-show="step == 0"> | 851 | <div class="operator_panel_wrap" v-show="step == 0"> |
| 137 | <ContentWrap id="id-baseInfo" title="数据选择" description="" style="margin-top: 8px;"> | 852 | <ContentWrap id="id-baseInfo" title="数据选择" description="" style="margin-top: 8px;"> |
| 138 | <Form ref="formRef" :itemList="dataSelectInfoItems" :rules="dataSelectInfoFormRules" formId="model-select-edit" col="col3" /> | 853 | <Form ref="formRef" :itemList="dataSelectInfoItems" :rules="dataSelectInfoFormRules" |
| 854 | formId="model-select-edit" col="col3 custom-form" @select-change="handleDataSelectFormSelectChange" | ||
| 855 | @uploadFileChange="uploadFileChange" /> | ||
| 856 | </ContentWrap> | ||
| 857 | <ContentWrap id="id-previewData" title="数据抽样预览" description="" style="margin-top: 16px;"> | ||
| 858 | <Form ref="dataSimpleFormRef" :itemList="dataSimpleFormItems" :rules="dataSimpleFormRules" | ||
| 859 | formId="data-simple-edit" col="col3 fixwidth-form" @switch-change="handleDataSimpleFormSwitchChange" | ||
| 860 | @input-change="handleDataSimpleFormChange" /> | ||
| 861 | <div class="table-v2-main" v-show="dataSimpleFormRef?.formInline?.enableSamplingRate == 'Y'" | ||
| 862 | v-loading="sampleTableDataLoading"> | ||
| 863 | <el-table ref="tableRef" v-show="sampleTableFields.length" :data="sampleTableData" | ||
| 864 | :highlight-current-row="true" stripe border tooltip-effect="light" height="100%" row-key="guid" | ||
| 865 | :style="{ width: '100%', height: '240px' }"> | ||
| 866 | <el-table-column label="序号" type="index" width="56px" align="center" | ||
| 867 | show-overflow-tooltip></el-table-column> | ||
| 868 | <template v-for="(item, index) in (sampleTableFields || [])"> | ||
| 869 | <el-table-column :label="item.chName" :width="item.dataType === 'datetime' | ||
| 870 | ? TableColumnWidth.DATETIME | ||
| 871 | : item.dataType === 'date' | ||
| 872 | ? TableColumnWidth.DATE | ||
| 873 | : originTableFieldColumn[item.enName] | ||
| 874 | " :align="getTextAlign(item)" :header-align="getTextAlign(item)" | ||
| 875 | :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true"> | ||
| 876 | </el-table-column> | ||
| 877 | </template> | ||
| 878 | </el-table> | ||
| 879 | <div v-show="!sampleTableFields.length" class="main-placeholder"> | ||
| 880 | <img src="../../assets/images/no-data.png" :style="{ width: '96px', height: '96px' }" /> | ||
| 881 | <div class="empty-text">暂无抽样数据</div> | ||
| 882 | </div> | ||
| 883 | </div> | ||
| 884 | </ContentWrap> | ||
| 885 | </div> | ||
| 886 | <anonTaskStepTwo ref="anonTaskStepTwoRef" v-show="step == 1" :anonTaskRules="detailInfo.anonTaskRules" :isFile="formRef?.formInline?.file?.length > 0" | ||
| 887 | :anonPrivacyMode="detailInfo.anonPrivacyMode" :fieldTypeList="fieldTypeList" :fieldNameList="sampleTableFields"> | ||
| 888 | </anonTaskStepTwo> | ||
| 889 | <div class="operator_panel_wrap" v-show="step == 2"> | ||
| 890 | <ContentWrap id="analysis-result" title="匿名结果分析" description="" style="margin-top: 8px;"> | ||
| 891 | |||
| 892 | </ContentWrap> | ||
| 893 | </div> | ||
| 894 | <div class="operator_panel_wrap" v-show="step == 3"> | ||
| 895 | <ContentWrap id="analysis-result" title="匿名化数据结果" description="" style="margin-top: 8px;"> | ||
| 896 | <anonResultView :is-page="false" :execGuid="isExecEnd ? taskExecGuid : ''"></anonResultView> | ||
| 139 | </ContentWrap> | 897 | </ContentWrap> |
| 140 | </div> | 898 | </div> |
| 141 | </div> | 899 | </div> |
| ... | @@ -144,9 +902,16 @@ const cancelTask = () => { | ... | @@ -144,9 +902,16 @@ const cancelTask = () => { |
| 144 | <el-button @click="cancelTask">取消</el-button> | 902 | <el-button @click="cancelTask">取消</el-button> |
| 145 | <el-button type="primary" @click="changeStep(2)">下一步</el-button> | 903 | <el-button type="primary" @click="changeStep(2)">下一步</el-button> |
| 146 | </template> | 904 | </template> |
| 905 | <template v-else-if="step == 1"> | ||
| 906 | <el-button @click="changeStep(1)">上一步</el-button> | ||
| 907 | <el-button type="primary" @click="changeStep(3)">保存并下一步</el-button> | ||
| 908 | </template> | ||
| 909 | <template v-else-if="step == 2"> | ||
| 910 | <el-button @click="changeStep(2)">上一步</el-button> | ||
| 911 | <el-button type="primary" @click="changeStep(4)">下一步</el-button> | ||
| 912 | </template> | ||
| 147 | <template v-else> | 913 | <template v-else> |
| 148 | <el-button @click="cancelTask">取消</el-button> | 914 | <el-button type="primary" @click="changeStep(2)">上一步</el-button> |
| 149 | <el-button type="primary" @click="changeStep(1)">上一步</el-button> | ||
| 150 | <el-button type="primary" v-preReClick @click="exportResult">导出</el-button> | 915 | <el-button type="primary" v-preReClick @click="exportResult">导出</el-button> |
| 151 | </template> | 916 | </template> |
| 152 | </div> | 917 | </div> |
| ... | @@ -181,4 +946,81 @@ const cancelTask = () => { | ... | @@ -181,4 +946,81 @@ const cancelTask = () => { |
| 181 | padding: 0 16px; | 946 | padding: 0 16px; |
| 182 | overflow: hidden auto; | 947 | overflow: hidden auto; |
| 183 | } | 948 | } |
| 949 | |||
| 950 | .operator_panel_wrap { | ||
| 951 | padding-bottom: 12px; | ||
| 952 | } | ||
| 953 | |||
| 954 | :deep(.custom-form) { | ||
| 955 | align-items: flex-start; | ||
| 956 | |||
| 957 | .wid60.el-form-item { | ||
| 958 | width: calc(66.66% - 12px); | ||
| 959 | } | ||
| 960 | } | ||
| 961 | |||
| 962 | :deep(.fixwidth-form) { | ||
| 963 | width: 500px; | ||
| 964 | |||
| 965 | .autoWidth.el-form-item { | ||
| 966 | width: 80px; | ||
| 967 | } | ||
| 968 | } | ||
| 969 | |||
| 970 | .table-v2-main { | ||
| 971 | width: 100%; | ||
| 972 | height: 240px; | ||
| 973 | |||
| 974 | |||
| 975 | .main-placeholder { | ||
| 976 | height: 100%; | ||
| 977 | display: flex; | ||
| 978 | justify-content: center; | ||
| 979 | align-items: center; | ||
| 980 | flex-direction: column; | ||
| 981 | |||
| 982 | .empty-text { | ||
| 983 | font-size: 14px; | ||
| 984 | color: #b2b2b2; | ||
| 985 | } | ||
| 986 | } | ||
| 987 | } | ||
| 988 | |||
| 989 | :deep(.el-table-v2) { | ||
| 990 | |||
| 991 | .el-table-v2__main { | ||
| 992 | border: 1px solid #d9d9d9; | ||
| 993 | background: #fff; | ||
| 994 | } | ||
| 995 | |||
| 996 | .el-table-v2__body tr.hover-row.el-table-v2__row--striped.current-row>td.el-table-v2__cell { | ||
| 997 | background-color: var(--el-table-row-hover-bg-color); | ||
| 998 | } | ||
| 999 | |||
| 1000 | .el-table-v2__body tr.current-row>td.el-table-v2__cell { | ||
| 1001 | background-color: var(--el-table-current-row-bg-color); | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | .el-table-v2__header { | ||
| 1005 | width: 100% !important; | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | .el-table-v2__header-cell, | ||
| 1009 | .el-table-v2__row-cell { | ||
| 1010 | border-right: 1px #d9d9d9 solid; | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | .el-table-v2__header-cell-text { | ||
| 1014 | color: #000; | ||
| 1015 | font-weight: normal; | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | .el-table-v2__empty { | ||
| 1019 | display: none !important; | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | .el-table-v2__header-row { | ||
| 1023 | border-bottom: 1px solid #d9d9d9; | ||
| 1024 | } | ||
| 1025 | } | ||
| 184 | </style> | 1026 | </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 | |||
| 16 | const props = defineProps({ | ||
| 17 | fieldTypeList: { | ||
| 18 | default: [], | ||
| 19 | type: Array<any> | ||
| 20 | }, | ||
| 21 | isFile: { | ||
| 22 | default: false, | ||
| 23 | type: Boolean | ||
| 24 | }, | ||
| 25 | fieldNameList: { | ||
| 26 | default: [], | ||
| 27 | type: Array<any> | ||
| 28 | }, | ||
| 29 | anonTaskRules: { | ||
| 30 | default: [], | ||
| 31 | type: Array<any> | ||
| 32 | }, | ||
| 33 | anonPrivacyMode: { | ||
| 34 | default: {}, | ||
| 35 | type: Object | ||
| 36 | } | ||
| 37 | }) | ||
| 38 | |||
| 39 | const { required } = useValidator(); | ||
| 40 | const { proxy } = getCurrentInstance() as any; | ||
| 41 | |||
| 42 | const drawerRef = ref(); | ||
| 43 | |||
| 44 | /** 泛化文件列表 */ | ||
| 45 | const generalizeFileNameList: any = ref([]); | ||
| 46 | |||
| 47 | /** 脱敏规则字典列表 */ | ||
| 48 | const desensitiveRuleTypeList: any = ref([]); | ||
| 49 | |||
| 50 | /** 标签类型字典列表 */ | ||
| 51 | const labelTypeList: any = ref([]); | ||
| 52 | |||
| 53 | /** 加密算法字典列表 */ | ||
| 54 | const hashMethodList = ref([]); | ||
| 55 | |||
| 56 | /** 当前正在编辑的表格索引 */ | ||
| 57 | const currTableRowIndex: any = ref(null); | ||
| 58 | |||
| 59 | const ruleModelTableInfo = ref({ | ||
| 60 | id: 'rule-model-table', | ||
| 61 | loading: false, | ||
| 62 | minHeight: '200px', | ||
| 63 | nodeKey: 'guid', | ||
| 64 | height: '200px', | ||
| 65 | fields: [ | ||
| 66 | { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" }, | ||
| 67 | { label: "字段中文名称", field: "fieldChName", width: 150 }, | ||
| 68 | { label: "字段英文名称", field: "fieldName", width: 150 }, | ||
| 69 | { label: "字段类型", field: "fieldTypeName", width: 120 }, | ||
| 70 | { label: "数据类型", field: "dataTypeName", width: 120 }, | ||
| 71 | { | ||
| 72 | label: "脱敏方式", field: "desensitiveRule", width: 120, getName: (scope) => { | ||
| 73 | let rule = scope.row.desensitiveRule; | ||
| 74 | return rule ? rule : (scope.row.generalizeFileGuid ? '泛化' : '--'); | ||
| 75 | } | ||
| 76 | }, | ||
| 77 | ], | ||
| 78 | data: <any>[], | ||
| 79 | showPage: false, | ||
| 80 | actionInfo: { | ||
| 81 | label: "操作", | ||
| 82 | type: "btn", | ||
| 83 | width: 100, | ||
| 84 | fixed: 'right', | ||
| 85 | btns: [ | ||
| 86 | { | ||
| 87 | label: "编辑", value: "ruleEdit", click: (scope) => { | ||
| 88 | currTableRowIndex.value = scope.$index; | ||
| 89 | drawerInfo.value.visible = true; | ||
| 90 | drawerInfo.value.type = 'edit'; | ||
| 91 | drawerInfo.value.header.title = '编辑字段脱敏规则'; | ||
| 92 | let row = scope.row; | ||
| 93 | fieldRulesFormItems.value.forEach(item => { | ||
| 94 | item.default = row[item.field]; | ||
| 95 | if (item.field == 'encryptionAlgorithmCode' || item.field == 'salted') { | ||
| 96 | item.visible = row.desensitiveRuleCode == 'HASH'; | ||
| 97 | } else if (item.field == 'decimalPlaces') { | ||
| 98 | item.visible = row.desensitiveRuleCode == 'ROUNDING'; | ||
| 99 | } | ||
| 100 | }); | ||
| 101 | let fieldNameList = props.fieldNameList.map(f => { | ||
| 102 | if (f.enName != row.fieldName && ruleModelTableInfo.value.data.some(d => d.fieldName == f.enName)) { | ||
| 103 | f.disabled = true; | ||
| 104 | } else { | ||
| 105 | f.disabled = false; | ||
| 106 | } | ||
| 107 | return f; | ||
| 108 | }); | ||
| 109 | fieldRulesFormItems.value[0].options = fieldNameList; | ||
| 110 | fieldRulesFormInfo.value.formInfo.items = fieldRulesFormItems.value; | ||
| 111 | drawerInfo.value.container.contents[0] = fieldRulesFormInfo.value; | ||
| 112 | desensitiveRuleDetail.value = { | ||
| 113 | dissembleType: 1, /** 1从左往右,2.从右往左 */ | ||
| 114 | ruleDetails: <any>[{ | ||
| 115 | digitType: 1, | ||
| 116 | }] | ||
| 117 | }; | ||
| 118 | charReplaceRuleDetail.value = { | ||
| 119 | replaceType: 1, /** 1从左往右,2.从右往左 */ | ||
| 120 | ruleDetails: <any>[{ | ||
| 121 | digitType: 1, | ||
| 122 | ruleType: 1 | ||
| 123 | }] | ||
| 124 | }; | ||
| 125 | rangeReplaceRuleDetails.value = [{ | ||
| 126 | lowOperator: '≤', | ||
| 127 | fieldChName: row.fieldChName, | ||
| 128 | upperOperator: '≤' | ||
| 129 | }]; | ||
| 130 | |||
| 131 | if (row.desensitiveRuleCode == 'DISSEMBLE') { | ||
| 132 | desensitiveRuleDetail.value = row.desensitiveRuleDetail || {}; | ||
| 133 | } else if (row.desensitiveRuleCode == 'CHARREPLACE') { | ||
| 134 | charReplaceRuleDetail.value = row.desensitiveRuleDetail || {}; | ||
| 135 | } else if (row.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 136 | rangeReplaceRuleDetails.value = row.desensitiveRuleDetail?.ruleDetails?.map(rd => { | ||
| 137 | rd.fieldChName = row.fieldChName; | ||
| 138 | return rd; | ||
| 139 | }) || []; | ||
| 140 | } | ||
| 141 | if (!row.desensitiveRuleCode) { | ||
| 142 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value]; | ||
| 143 | } else { | ||
| 144 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value]; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | }, | ||
| 148 | { | ||
| 149 | label: "删除", value: "delete", click: (scope) => { | ||
| 150 | proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => { | ||
| 151 | let fieldName = scope.row.fieldName; | ||
| 152 | ruleModelTableInfo.value.data.splice(scope.row.index, 1); | ||
| 153 | updatePrivacyFormFieldsOptions(fieldName); | ||
| 154 | // 同步去掉隐私模型设置中的字段。 | ||
| 155 | proxy.$ElMessage.success('删除成功'); | ||
| 156 | }, () => { | ||
| 157 | proxy.$ElMessage.info("已取消"); | ||
| 158 | }) | ||
| 159 | } | ||
| 160 | }, | ||
| 161 | ], | ||
| 162 | } | ||
| 163 | }) | ||
| 164 | |||
| 165 | /** 字段脱敏规则表单配置 */ | ||
| 166 | const fieldRulesFormItems = ref([{ | ||
| 167 | label: '选择字段', | ||
| 168 | type: 'select', | ||
| 169 | placeholder: '请选择', | ||
| 170 | field: 'fieldName', | ||
| 171 | default: '', | ||
| 172 | options: props.fieldNameList, | ||
| 173 | props: { | ||
| 174 | label: 'chName', | ||
| 175 | value: 'enName', | ||
| 176 | disabled: 'disabled' | ||
| 177 | }, | ||
| 178 | filterable: true, | ||
| 179 | clearable: false, | ||
| 180 | required: true | ||
| 181 | }, { | ||
| 182 | label: '字段类型', | ||
| 183 | type: 'select', | ||
| 184 | placeholder: '请选择', | ||
| 185 | field: 'fieldTypeCode', | ||
| 186 | default: 'varchar', | ||
| 187 | options: props.fieldTypeList, | ||
| 188 | props: { | ||
| 189 | label: 'label', | ||
| 190 | value: 'value' | ||
| 191 | }, | ||
| 192 | disabled: true, | ||
| 193 | clearable: true, | ||
| 194 | required: true | ||
| 195 | }, { | ||
| 196 | label: '数据类型', | ||
| 197 | type: 'select', | ||
| 198 | placeholder: '请选择', | ||
| 199 | field: 'dataTypeCode', | ||
| 200 | default: '', | ||
| 201 | options: labelTypeList.value, | ||
| 202 | props: { | ||
| 203 | label: 'label', | ||
| 204 | value: 'value' | ||
| 205 | }, | ||
| 206 | filterable: true, | ||
| 207 | clearable: true, | ||
| 208 | required: false | ||
| 209 | }, { | ||
| 210 | label: 'K匿名泛化', | ||
| 211 | type: 'select', | ||
| 212 | placeholder: '请选择', | ||
| 213 | field: 'generalizeFileGuid', | ||
| 214 | default: '', | ||
| 215 | options: generalizeFileNameList.value, | ||
| 216 | props: { | ||
| 217 | label: 'generalizeFileName', | ||
| 218 | value: 'guid' | ||
| 219 | }, | ||
| 220 | filterable: true, | ||
| 221 | clearable: true, | ||
| 222 | required: false | ||
| 223 | }, { | ||
| 224 | label: '脱敏规则', | ||
| 225 | type: 'select', | ||
| 226 | placeholder: '请选择', | ||
| 227 | field: 'desensitiveRuleCode', | ||
| 228 | default: 'DISSEMBLE', | ||
| 229 | options: desensitiveRuleTypeList.value, | ||
| 230 | props: { | ||
| 231 | label: 'label', | ||
| 232 | value: 'value' | ||
| 233 | }, | ||
| 234 | filterable: true, | ||
| 235 | clearable: true, | ||
| 236 | required: false | ||
| 237 | }, { | ||
| 238 | label: '加密算法', | ||
| 239 | type: 'select', | ||
| 240 | placeholder: '请选择', | ||
| 241 | field: 'encryptionAlgorithmCode', | ||
| 242 | default: '', | ||
| 243 | options: hashMethodList.value, | ||
| 244 | props: { | ||
| 245 | label: 'label', | ||
| 246 | value: 'value' | ||
| 247 | }, | ||
| 248 | filterable: true, | ||
| 249 | clearable: true, | ||
| 250 | visible: false, | ||
| 251 | required: true | ||
| 252 | }, { | ||
| 253 | label: '加盐值', | ||
| 254 | type: 'input', | ||
| 255 | placeholder: '请输入0~9', | ||
| 256 | field: 'salted', | ||
| 257 | maxlength: 1, | ||
| 258 | min: 0, | ||
| 259 | max: 9, | ||
| 260 | inputType: 'integerNumber', | ||
| 261 | default: 5, | ||
| 262 | required: true, | ||
| 263 | filterable: true, | ||
| 264 | clearable: true, | ||
| 265 | visible: false, | ||
| 266 | }, { | ||
| 267 | label: '保留小数', | ||
| 268 | type: 'input', | ||
| 269 | placeholder: '请输入0~5', | ||
| 270 | field: 'decimalPlaces', | ||
| 271 | maxlength: 1, | ||
| 272 | min: 0, | ||
| 273 | max: 5, | ||
| 274 | inputType: 'integerNumber', | ||
| 275 | default: 2, | ||
| 276 | required: true, | ||
| 277 | filterable: true, | ||
| 278 | clearable: true, | ||
| 279 | visible: false, | ||
| 280 | }]); | ||
| 281 | |||
| 282 | const fieldRulesFormRules = ref({ | ||
| 283 | fieldName: [required('请选择字段')], | ||
| 284 | // dataTypeCode: [required('请选择数据类型')], //不填标识非敏感标识。 | ||
| 285 | // desensitiveRuleCode: [required('请选择脱敏规则')], 脱敏规则和泛化文件选择一个即可。二者必选其一,可以两者共存。 | ||
| 286 | encryptionAlgorithmCode: [required('请选择加密算法')], | ||
| 287 | salted: [required('请输入加盐值')], | ||
| 288 | decimalPlaces: [required('请输入保留小数')] | ||
| 289 | }); | ||
| 290 | |||
| 291 | const fieldRulesFormInfo = ref({ | ||
| 292 | type: "form", | ||
| 293 | title: "", | ||
| 294 | col: "span", | ||
| 295 | formInfo: { | ||
| 296 | id: "add-class-form", | ||
| 297 | readonly: false, | ||
| 298 | items: fieldRulesFormItems.value, | ||
| 299 | rules: fieldRulesFormRules.value, | ||
| 300 | }, | ||
| 301 | }); | ||
| 302 | |||
| 303 | const fieldRulesEndFormInfo = ref({ | ||
| 304 | type: "form", | ||
| 305 | title: "", | ||
| 306 | col: "mt8", | ||
| 307 | showSlot: false, | ||
| 308 | formInfo: { | ||
| 309 | id: "add-rules-end-form", | ||
| 310 | readonly: false, | ||
| 311 | items: [{ | ||
| 312 | label: '样本数据', | ||
| 313 | type: 'input', | ||
| 314 | placeholder: '请输入样本', | ||
| 315 | field: 'testData', | ||
| 316 | default: '', | ||
| 317 | required: false, | ||
| 318 | clearable: true, | ||
| 319 | block: true, | ||
| 320 | col: 'mb8', | ||
| 321 | validateBtn: { | ||
| 322 | value: 'validate', | ||
| 323 | label: '验证', | ||
| 324 | click: () => { | ||
| 325 | let formInline = drawerRef.value?.getDrawerConRef('drawerFormRef')?.formInline; | ||
| 326 | let formInline1 = drawerRef.value?.getDrawerConRef('drawerFormRef', 1)?.formInline; | ||
| 327 | if (!formInline1.testData) { | ||
| 328 | proxy.$ElMessage.error('样本数据不能为空'); | ||
| 329 | return; | ||
| 330 | } | ||
| 331 | validateAnonRule({ | ||
| 332 | desensitiveRuleCode: formInline.desensitiveRuleCode, | ||
| 333 | value: formInline1.testData, | ||
| 334 | desensitiveRuleDetail: getDesensitiveRuleDetailInfo(formInline) | ||
| 335 | }).then((res: any) => { | ||
| 336 | if (res?.code == proxy.$passCode) { | ||
| 337 | let result = res.data; | ||
| 338 | fieldRulesEndFormInfo.value.formInfo.items[0].default = formInline1.testData; | ||
| 339 | fieldRulesEndFormInfo.value.formInfo.items[1].default = formInline.desensitiveRuleCode == 'BLANK' ? ' ' : result; | ||
| 340 | } else { | ||
| 341 | proxy.$ElMessage.error(res.msg); | ||
| 342 | } | ||
| 343 | }) | ||
| 344 | } | ||
| 345 | } | ||
| 346 | }, { | ||
| 347 | label: '脱敏效果', | ||
| 348 | type: 'input', | ||
| 349 | placeholder: '点击验证后展示脱敏效果', | ||
| 350 | field: 'validateResult', | ||
| 351 | default: '', | ||
| 352 | block: true, | ||
| 353 | required: false, | ||
| 354 | disabled: true | ||
| 355 | }], | ||
| 356 | rules: {}, | ||
| 357 | }, | ||
| 358 | }); | ||
| 359 | |||
| 360 | /** 获取规则配置详情。 */ | ||
| 361 | const getDesensitiveRuleDetailInfo = (formInline) => { | ||
| 362 | let desensitiveRuleDetailInfo: any = {}; | ||
| 363 | if (formInline.desensitiveRuleCode == 'DISSEMBLE') { | ||
| 364 | desensitiveRuleDetailInfo = desensitiveRuleDetail.value; | ||
| 365 | } else if (formInline.desensitiveRuleCode == 'CHARREPLACE') { | ||
| 366 | desensitiveRuleDetailInfo = charReplaceRuleDetail.value; | ||
| 367 | } else if (formInline.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 368 | desensitiveRuleDetailInfo = { | ||
| 369 | ruleDetails: rangeReplaceRuleDetails.value | ||
| 370 | }; | ||
| 371 | } else if (formInline.desensitiveRuleCode == 'ROUNDING') { | ||
| 372 | desensitiveRuleDetailInfo = { | ||
| 373 | decimalPlaces: formInline.decimalPlaces | ||
| 374 | } | ||
| 375 | } else if (formInline.desensitiveRuleCode == 'HASH') { | ||
| 376 | desensitiveRuleDetailInfo = { | ||
| 377 | encryptionAlgorithmCode: formInline.encryptionAlgorithmCode, | ||
| 378 | salted: formInline.salted | ||
| 379 | } | ||
| 380 | } | ||
| 381 | return desensitiveRuleDetailInfo; | ||
| 382 | } | ||
| 383 | |||
| 384 | const drawerInfo = ref({ | ||
| 385 | visible: false, | ||
| 386 | direction: 'rtl', | ||
| 387 | size: 540, | ||
| 388 | header: { | ||
| 389 | title: '添加字段脱敏规则', | ||
| 390 | }, | ||
| 391 | type: '', | ||
| 392 | container: { | ||
| 393 | contents: [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value], | ||
| 394 | }, | ||
| 395 | footer: { | ||
| 396 | visible: true, | ||
| 397 | btns: [ | ||
| 398 | { type: 'default', label: '取消', value: 'cancel' }, | ||
| 399 | { type: 'primary', label: '确定', value: 'save', loading: false }, | ||
| 400 | ] | ||
| 401 | } | ||
| 402 | }); | ||
| 403 | |||
| 404 | const addRowRules = () => { | ||
| 405 | drawerInfo.value.visible = true; | ||
| 406 | drawerInfo.value.type = 'add'; | ||
| 407 | drawerInfo.value.header.title = '添加字段脱敏规则'; | ||
| 408 | fieldRulesFormItems.value.forEach(item => { | ||
| 409 | if (item.field == 'fieldTypeCode') { | ||
| 410 | item.default = 'varchar'; | ||
| 411 | } else if (item.field == 'desensitiveRuleCode') { | ||
| 412 | item.default = 'DISSEMBLE'; | ||
| 413 | } else if (item.field == 'encryptionAlgorithmCode' || item.field == 'salted') { | ||
| 414 | item.visible = false; | ||
| 415 | item.default = ''; | ||
| 416 | item.field == 'salted' && (item.default = 5); | ||
| 417 | } else if (item.field == 'decimalPlaces') { | ||
| 418 | item.visible = false; | ||
| 419 | item.default = 2; | ||
| 420 | } else { | ||
| 421 | item.default = ''; | ||
| 422 | } | ||
| 423 | }); | ||
| 424 | let fieldNameList = props.fieldNameList.map(f => { | ||
| 425 | if (ruleModelTableInfo.value.data.some(d => d.fieldName == f.enName)) { | ||
| 426 | f.disabled = true; | ||
| 427 | } else { | ||
| 428 | f.disabled = false; | ||
| 429 | } | ||
| 430 | return f | ||
| 431 | }); | ||
| 432 | fieldRulesFormItems.value[0].options = fieldNameList; | ||
| 433 | fieldRulesFormInfo.value.formInfo.items = fieldRulesFormItems.value; | ||
| 434 | desensitiveRuleDetail.value = { | ||
| 435 | dissembleType: 1, /** 1从左往右,2.从右往左 */ | ||
| 436 | ruleDetails: <any>[{ | ||
| 437 | digitType: 1, | ||
| 438 | }] | ||
| 439 | }; | ||
| 440 | charReplaceRuleDetail.value = { | ||
| 441 | replaceType: 1, /** 1从左往右,2.从右往左 */ | ||
| 442 | ruleDetails: <any>[{ | ||
| 443 | digitType: 1, | ||
| 444 | ruleType: 1 | ||
| 445 | }] | ||
| 446 | }; | ||
| 447 | rangeReplaceRuleDetails.value = [{ | ||
| 448 | lowOperator: '≤', | ||
| 449 | fieldChName: '', | ||
| 450 | upperOperator: '≤' | ||
| 451 | }]; | ||
| 452 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value]; | ||
| 453 | } | ||
| 454 | |||
| 455 | const drawerBtnClick = async (btn, info) => { | ||
| 456 | if (btn.value == 'cancel') { | ||
| 457 | drawerInfo.value.visible = false | ||
| 458 | } else { | ||
| 459 | if (!info.generalizeFileGuid && !info.desensitiveRuleCode) { | ||
| 460 | proxy.$ElMessage.error('K匿名泛化与脱敏规则不能同时为空'); | ||
| 461 | return; | ||
| 462 | } | ||
| 463 | if ((info.fieldTypeCode == 'int' || info.fieldTypeCode == 'decimal' || info.fieldTypeCode == 'tinyint')) { | ||
| 464 | if (info.desensitiveRuleCode == 'ROUNDING') { | ||
| 465 | proxy.$ElMessage.error('数值类型字段的脱敏规则不能设置取整'); | ||
| 466 | return; | ||
| 467 | } | ||
| 468 | if (info.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 469 | proxy.$ElMessage.error('数值类型字段的脱敏规则不能设置区间替换'); | ||
| 470 | return; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | drawerInfo.value.footer.btns[1].loading = true; | ||
| 474 | let desensitiveRuleDetailInfo = getDesensitiveRuleDetailInfo(info); | ||
| 475 | // 脱敏规则为掩盖,字符,区间替换存在时需要调用接口检验 | ||
| 476 | if (info.desensitiveRuleCode == 'DISSEMBLE' || info.desensitiveRuleCode == 'CHARREPLACE' || info.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 477 | let res: any = await validateAnonRule({ | ||
| 478 | desensitiveRuleCode: info.desensitiveRuleCode, | ||
| 479 | value: '', | ||
| 480 | desensitiveRuleDetail: desensitiveRuleDetailInfo | ||
| 481 | }) | ||
| 482 | if (res?.code != proxy.$passCode) { | ||
| 483 | proxy.$ElMessage.error(res.msg); | ||
| 484 | } | ||
| 485 | } | ||
| 486 | drawerInfo.value.footer.btns[1].loading = false; | ||
| 487 | let saveData: any = { ...info }; | ||
| 488 | saveData.desensitiveRuleDetail = desensitiveRuleDetailInfo; | ||
| 489 | saveData.fieldChName = props.fieldNameList?.find(f => f.enName == saveData.fieldName)?.chName; | ||
| 490 | saveData.dataTypeName = saveData.dataTypeCode && labelTypeList.value.find(l => l.value == saveData.dataTypeCode)?.label; | ||
| 491 | saveData.fieldTypeName = saveData.fieldTypeCode && props.fieldTypeList?.find(f => f.value == saveData.fieldTypeCode)?.label; | ||
| 492 | saveData.desensitiveRule = info.desensitiveRuleCode && desensitiveRuleTypeList.value.find(d => d.value == info.desensitiveRuleCode)?.label; | ||
| 493 | let changeFields = ''; | ||
| 494 | if (drawerInfo.value.type == 'add') { | ||
| 495 | ruleModelTableInfo.value.data.push(saveData); | ||
| 496 | } else if (drawerInfo.value.type == 'edit') { | ||
| 497 | if (!currTableRowIndex.value != null) { | ||
| 498 | let originFieldName = ruleModelTableInfo.value.data[currTableRowIndex.value]?.fieldName; | ||
| 499 | changeFields = originFieldName == saveData.fieldName ? '' : originFieldName; | ||
| 500 | ruleModelTableInfo.value.data[currTableRowIndex.value] = saveData; | ||
| 501 | } | ||
| 502 | } | ||
| 503 | drawerInfo.value.visible = false; | ||
| 504 | updatePrivacyFormFieldsOptions(changeFields); | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | /** 更新下拉选择字段的下拉列表,若是字段被删除,需要同步去掉下拉选择值。 */ | ||
| 509 | const updatePrivacyFormFieldsOptions = (changeFields) => { | ||
| 510 | let optionsList = ruleModelTableInfo.value.data?.map(v => { | ||
| 511 | return { | ||
| 512 | fieldName: v.fieldName, | ||
| 513 | fieldChName: v.fieldChName | ||
| 514 | } | ||
| 515 | }) || []; | ||
| 516 | privacyFormItems.value[2].children[0].options = optionsList; | ||
| 517 | privacyFormItems.value[3].children[0].options = optionsList; | ||
| 518 | let formInline = privacyFormRef.value?.formInline; | ||
| 519 | privacyFormItems.value.forEach((item, index) => { | ||
| 520 | item.default = formInline[item.field]; | ||
| 521 | if (item.default == 'Y') { | ||
| 522 | item.children?.forEach(child => { | ||
| 523 | child.default = formInline[child.field]; | ||
| 524 | if (changeFields && (child.field == 'tcFieldName' || child.field == 'ldFieldName') && child.default == changeFields) { | ||
| 525 | child.default = '' | ||
| 526 | } | ||
| 527 | }); | ||
| 528 | } | ||
| 529 | }) | ||
| 530 | } | ||
| 531 | |||
| 532 | /** 当前选择的字段对应的标签信息。 */ | ||
| 533 | const currLabelInfo: any = ref({}); | ||
| 534 | |||
| 535 | const drawerSelectChange = (val, row, info) => { | ||
| 536 | if (row.field === 'desensitiveRuleCode') { | ||
| 537 | let methodItem = fieldRulesFormItems.value.find(item => item.field == 'encryptionAlgorithmCode'); | ||
| 538 | let saltedItem = fieldRulesFormItems.value.find(item => item.field == 'salted'); | ||
| 539 | let decimalPlaceItem = fieldRulesFormItems.value.find(item => item.field == 'decimalPlaces'); | ||
| 540 | methodItem && (methodItem.visible = val == 'HASH'); | ||
| 541 | saltedItem && (saltedItem.visible = val == 'HASH'); | ||
| 542 | decimalPlaceItem && (decimalPlaceItem.visible = val == 'ROUNDING'); | ||
| 543 | fieldRulesFormItems.value.forEach(item => { | ||
| 544 | item.default = info[item.field]; | ||
| 545 | if (item.field == 'decimalPlaces') { | ||
| 546 | if (item.default == null) { | ||
| 547 | item.default = 2; | ||
| 548 | } | ||
| 549 | } else if (item.field == 'salted') { | ||
| 550 | if (item.default == null) { | ||
| 551 | item.default = 5; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | }); | ||
| 555 | if (!val) { | ||
| 556 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value]; | ||
| 557 | } else { | ||
| 558 | drawerInfo.value.container.contents = [fieldRulesFormInfo.value, fieldRulesEndFormInfo.value]; | ||
| 559 | } | ||
| 560 | } | ||
| 561 | // else if (row.field == 'fieldTypeCode') { | ||
| 562 | // fieldRulesFormItems.value.forEach(item => { | ||
| 563 | // item.default = info[item.field]; | ||
| 564 | // if (item.field == 'desensitiveRuleCode') { | ||
| 565 | // if (!(val == 'int' || val == 'decimal' || val == 'tinyint')) { | ||
| 566 | // item.options = desensitiveRuleTypeList.value.filter(d => d.value != 'ROUNDING' && d.value != 'RANGEREPLACE'); | ||
| 567 | // if (item.default == 'ROUNDING' || item.default == 'RANGEREPLACE') { | ||
| 568 | // item.default = 'DISSEMBLE'; | ||
| 569 | // } | ||
| 570 | // } else { | ||
| 571 | // item.options = desensitiveRuleTypeList.value; | ||
| 572 | // } | ||
| 573 | // } | ||
| 574 | // }); | ||
| 575 | // } | ||
| 576 | else if (row.field == 'fieldName') { //选择字段改变之后,调用接口。 | ||
| 577 | let tableField = props.fieldNameList.find(f => f.enName == val); | ||
| 578 | let dataType = tableField?.dataType || 'varchar'; | ||
| 579 | fieldRulesFormItems.value.forEach(item => { | ||
| 580 | item.default = info[item.field]; | ||
| 581 | if (item.field == 'fieldTypeCode') { | ||
| 582 | item.default = dataType; | ||
| 583 | } | ||
| 584 | }); | ||
| 585 | getLableByFieldName(val).then((res: any) => { | ||
| 586 | if (res?.code == proxy.$passCode) { | ||
| 587 | let labelInfo = currLabelInfo.value = res.data || {}; | ||
| 588 | fieldRulesFormItems.value.forEach(item => { | ||
| 589 | item.default = info[item.field]; | ||
| 590 | if (item.field == 'dataTypeCode') { | ||
| 591 | item.default = labelInfo.labelTypeCode; | ||
| 592 | } | ||
| 593 | }); | ||
| 594 | } else { | ||
| 595 | proxy.$ElMessage.error(res.msg); | ||
| 596 | } | ||
| 597 | }) | ||
| 598 | if (info.desensitiveRuleCode == 'RANGEREPLACE') { | ||
| 599 | rangeReplaceRuleDetails.value.forEach(r => { | ||
| 600 | r.fieldChName = tableField?.chName || val; | ||
| 601 | }); | ||
| 602 | } | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | const drawerInputChange = (val, row, info) => { | ||
| 607 | if (row.field == 'testData') { //样本数据修改后,清空脱敏结果 | ||
| 608 | fieldRulesEndFormInfo.value.formInfo.items[0].default = val; | ||
| 609 | fieldRulesEndFormInfo.value.formInfo.items[1].default = ''; | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 613 | /** 掩盖规则中的位数和剩余位数 */ | ||
| 614 | const digitTypeList = ref([{ | ||
| 615 | value: 1, | ||
| 616 | label: '位数' | ||
| 617 | }, { | ||
| 618 | value: 2, | ||
| 619 | label: '剩余位数' | ||
| 620 | }]); | ||
| 621 | |||
| 622 | const dissembleRuleTypeList = ref([{ | ||
| 623 | value: 1, | ||
| 624 | label: '脱敏' | ||
| 625 | }, { | ||
| 626 | value: 2, | ||
| 627 | label: '不脱敏' | ||
| 628 | }]); | ||
| 629 | |||
| 630 | /** 掩盖类型的脱敏规则 分段规则配置 */ | ||
| 631 | const desensitiveRuleDetail = ref({ | ||
| 632 | dissembleType: 1, /** 1从左往右,2.从右往左 */ | ||
| 633 | ruleDetails: <any>[{ | ||
| 634 | digitType: 1, | ||
| 635 | }] | ||
| 636 | }); | ||
| 637 | |||
| 638 | const addSegmentRule = () => { | ||
| 639 | desensitiveRuleDetail.value.ruleDetails.push({ | ||
| 640 | digitType: 1, | ||
| 641 | }); | ||
| 642 | }; | ||
| 643 | |||
| 644 | const deleteSegmentRule = (item, index) => { | ||
| 645 | desensitiveRuleDetail.value.ruleDetails.splice(index, 1); | ||
| 646 | } | ||
| 647 | |||
| 648 | const inputEventDigitChange = (val, item, prop = 'digit') => { | ||
| 649 | item[prop] = item[prop].toString().replace(/\./g, "") | ||
| 650 | item[prop] = item[prop].toString().replace(/^\D*(\d{0,7}(?:\.\d{0})?).*$/g, "$1") | ||
| 651 | if (prop == 'digit' && val != "" && item.digit < 1) { | ||
| 652 | item.digit = 1; | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | /** 字符规则中的替换方式 */ | ||
| 657 | const charReplaceRuleTypeList = ref([{ | ||
| 658 | value: 1, | ||
| 659 | label: '随机替换' | ||
| 660 | }, { | ||
| 661 | value: 2, | ||
| 662 | label: '固定值替换' | ||
| 663 | }]); | ||
| 664 | |||
| 665 | /** 字符替换的脱敏规则 分段规则配置 */ | ||
| 666 | const charReplaceRuleDetail = ref({ | ||
| 667 | replaceType: 1, /** 1从左往右,2.从右往左 */ | ||
| 668 | ruleDetails: <any>[{ | ||
| 669 | digitType: 1, | ||
| 670 | ruleType: 1 | ||
| 671 | }] | ||
| 672 | }); | ||
| 673 | |||
| 674 | const addCharReplaceSegmentRule = () => { | ||
| 675 | charReplaceRuleDetail.value.ruleDetails.push({ | ||
| 676 | digitType: 1, | ||
| 677 | ruleType: 1 | ||
| 678 | }); | ||
| 679 | }; | ||
| 680 | |||
| 681 | const deleteCharReplaceSegmentRule = (item, index) => { | ||
| 682 | charReplaceRuleDetail.value.ruleDetails.splice(index, 1); | ||
| 683 | } | ||
| 684 | |||
| 685 | /** ----- 区间替换规则 --- */ | ||
| 686 | /** 区间替换的小于或小于等于字符列表 */ | ||
| 687 | const lowerOperatorList: any = ref([{ | ||
| 688 | value: '≤', | ||
| 689 | label: '≤' | ||
| 690 | }, { | ||
| 691 | value: '<', | ||
| 692 | label: '<' | ||
| 693 | }]); | ||
| 694 | |||
| 695 | /** | ||
| 696 | * 字符替换的脱敏规则 分段规则配置。 | ||
| 697 | * { lowValue: , lowOperator: , upperValue: , upperOperator: , replaceValue: '' } | ||
| 698 | */ | ||
| 699 | const rangeReplaceRuleDetails: any = ref([{ | ||
| 700 | lowOperator: '≤', | ||
| 701 | fieldChName: '', | ||
| 702 | upperOperator: '≤' | ||
| 703 | }]); | ||
| 704 | |||
| 705 | const addRangeReplaceSegmentRule = () => { | ||
| 706 | let fieldChName = ''; | ||
| 707 | if (rangeReplaceRuleDetails.value?.length) { | ||
| 708 | fieldChName = rangeReplaceRuleDetails.value[0].fieldChName; | ||
| 709 | } else { | ||
| 710 | let fieldName = drawerRef.value?.getDrawerConRef('drawerFormRef')?.formInline?.fieldName; | ||
| 711 | let tableField = fieldName && props.fieldNameList?.find(f => f.enName == fieldName); | ||
| 712 | fieldChName = tableField?.chName; | ||
| 713 | } | ||
| 714 | rangeReplaceRuleDetails.value.push({ | ||
| 715 | lowOperator: '≤', | ||
| 716 | fieldChName: fieldChName, | ||
| 717 | upperOperator: '≤' | ||
| 718 | }); | ||
| 719 | }; | ||
| 720 | |||
| 721 | const deleteRangeReplaceSegmentRule = (item, index) => { | ||
| 722 | rangeReplaceRuleDetails.value.splice(index, 1); | ||
| 723 | } | ||
| 724 | |||
| 725 | /** 隐私模型设置 */ | ||
| 726 | const privacyFormItems: any = ref([{ | ||
| 727 | label: '选择准标识符等价类隐私模型', | ||
| 728 | type: 'checkbox-input-item', | ||
| 729 | field: 'isKaNumber', | ||
| 730 | default: 'N', | ||
| 731 | placeholder: 'K匿名', | ||
| 732 | trueValue: 'Y', | ||
| 733 | falseValue: 'N', | ||
| 734 | children: [ | ||
| 735 | { | ||
| 736 | label: '', | ||
| 737 | type: 'input', | ||
| 738 | placeholder: '请输入', | ||
| 739 | field: 'kaNumber', | ||
| 740 | inputType: 'integerNumber', | ||
| 741 | default: '', | ||
| 742 | min: 1, | ||
| 743 | maxlength: 6, | ||
| 744 | disabled: false, | ||
| 745 | clearable: true, | ||
| 746 | visible: false, | ||
| 747 | col: 'ka-checkbox-input', | ||
| 748 | style: { width: '100px', margin: 0 } | ||
| 749 | } | ||
| 750 | ], | ||
| 751 | required: true, | ||
| 752 | col: 'checkbox-input' | ||
| 753 | }, { | ||
| 754 | label: '设置重标识可接受风险阈值', | ||
| 755 | type: 'checkbox-input-item', | ||
| 756 | field: 'isRiskThreshold', | ||
| 757 | default: 'N', | ||
| 758 | placeholder: '阈值', | ||
| 759 | trueValue: 'Y', | ||
| 760 | falseValue: 'N', | ||
| 761 | children: [ | ||
| 762 | { | ||
| 763 | label: '', | ||
| 764 | type: 'input', | ||
| 765 | placeholder: '请输入', | ||
| 766 | field: 'riskThreshold', | ||
| 767 | default: '', | ||
| 768 | maxlength: 6, | ||
| 769 | min: 0, | ||
| 770 | max: 1, | ||
| 771 | inputType: 'scoreNumber', | ||
| 772 | disabled: false, | ||
| 773 | clearable: true, | ||
| 774 | visible: false, | ||
| 775 | col: 'ka-checkbox-input', | ||
| 776 | style: { width: '100px', margin: 0 } | ||
| 777 | } | ||
| 778 | ], | ||
| 779 | required: true, | ||
| 780 | col: 'checkbox-input' | ||
| 781 | }, { | ||
| 782 | label: '设置L多样性及T接近', | ||
| 783 | type: 'checkbox-input-item', | ||
| 784 | field: 'isLdField', | ||
| 785 | default: 'N', | ||
| 786 | placeholder: 'L多样性', | ||
| 787 | trueValue: 'Y', | ||
| 788 | falseValue: 'N', | ||
| 789 | children: [ | ||
| 790 | { | ||
| 791 | label: '', | ||
| 792 | type: 'select', | ||
| 793 | placeholder: '请选择字段', | ||
| 794 | field: 'ldFieldName', | ||
| 795 | options: [], | ||
| 796 | props: { | ||
| 797 | label: 'fieldChName', | ||
| 798 | value: 'fieldName' | ||
| 799 | }, | ||
| 800 | default: '', | ||
| 801 | filterable: true, | ||
| 802 | clearable: true, | ||
| 803 | visible: false, | ||
| 804 | col: 'ka-checkbox-input', | ||
| 805 | style: { width: '120px', margin: 0 } | ||
| 806 | }, | ||
| 807 | { | ||
| 808 | label: '', | ||
| 809 | type: 'input', | ||
| 810 | placeholder: '请输入', | ||
| 811 | field: 'ldNumber', | ||
| 812 | inputType: 'integerNumber', | ||
| 813 | default: '', | ||
| 814 | maxlength: 6, | ||
| 815 | min: 1, | ||
| 816 | disabled: false, | ||
| 817 | clearable: true, | ||
| 818 | visible: false, | ||
| 819 | col: 'ka-checkbox-input', | ||
| 820 | style: { width: '100px', margin: 0 } | ||
| 821 | } | ||
| 822 | ], | ||
| 823 | block: false, | ||
| 824 | required: true, | ||
| 825 | col: 'checkbox-input' | ||
| 826 | }, { | ||
| 827 | label: '', | ||
| 828 | type: 'checkbox-input-item', | ||
| 829 | field: 'isTcField', | ||
| 830 | default: 'N', | ||
| 831 | placeholder: 'T接近', | ||
| 832 | trueValue: 'Y', | ||
| 833 | falseValue: 'N', | ||
| 834 | children: [ | ||
| 835 | { | ||
| 836 | label: '', | ||
| 837 | type: 'select', | ||
| 838 | placeholder: '请选择字段', | ||
| 839 | field: 'tcFieldName', | ||
| 840 | default: '', | ||
| 841 | options: [], | ||
| 842 | props: { | ||
| 843 | label: 'fieldChName', | ||
| 844 | value: 'fieldName' | ||
| 845 | }, | ||
| 846 | filterable: true, | ||
| 847 | clearable: true, | ||
| 848 | visible: false, | ||
| 849 | col: 'ka-checkbox-input', | ||
| 850 | style: { width: '120px', margin: 0 } | ||
| 851 | }, | ||
| 852 | { | ||
| 853 | label: '', | ||
| 854 | type: 'input', | ||
| 855 | placeholder: '请输入', | ||
| 856 | field: 'tcThreshold', | ||
| 857 | default: '', | ||
| 858 | maxlength: 6, | ||
| 859 | min: 0, | ||
| 860 | max: 1, | ||
| 861 | inputType: 'scoreNumber', | ||
| 862 | disabled: false, | ||
| 863 | clearable: true, | ||
| 864 | visible: false, | ||
| 865 | col: 'ka-checkbox-input', | ||
| 866 | style: { width: '100px', margin: 0 } | ||
| 867 | } | ||
| 868 | ], | ||
| 869 | required: false, | ||
| 870 | block: true, | ||
| 871 | col: 'checkbox-input lmt12' | ||
| 872 | }]); | ||
| 873 | |||
| 874 | const privacyFormRules = ref({ | ||
| 875 | kaNumber: [required('请输入K匿名值')], | ||
| 876 | riskThreshold: [required('请输入阈值')], | ||
| 877 | ldFieldName: [required('请选择L多样性字段')], | ||
| 878 | ldNumber: [required('请输入L多样性值')], | ||
| 879 | tcFieldName: [required('请选择T接近字段')], | ||
| 880 | tcThreshold: [required('请输入T接近阈值')] | ||
| 881 | }); | ||
| 882 | |||
| 883 | /** 记录下旧的隐私模型设置 */ | ||
| 884 | const oldPrivacyModelValue: any = ref({ | ||
| 885 | isKaNumber: 'N', | ||
| 886 | isRiskThreshold: 'N', | ||
| 887 | isLdField: 'N', | ||
| 888 | isTcField: 'N' | ||
| 889 | }); | ||
| 890 | |||
| 891 | const handleCheckboxChange = (val, value, row) => { | ||
| 892 | oldPrivacyModelValue.value = Object.assign({}, oldPrivacyModelValue.value, value); | ||
| 893 | privacyFormItems.value.forEach(item => { | ||
| 894 | item.default = oldPrivacyModelValue.value[item.field]; | ||
| 895 | item.children?.forEach(child => { | ||
| 896 | child.default = oldPrivacyModelValue.value[child.field]; | ||
| 897 | }); | ||
| 898 | }) | ||
| 899 | if (row.field == 'isKaNumber') { | ||
| 900 | let kaItem = privacyFormItems.value[0]?.children?.[0]; | ||
| 901 | kaItem && (kaItem.visible = val == 'Y'); | ||
| 902 | } else if (row.field == 'isRiskThreshold') { | ||
| 903 | let riskItem = privacyFormItems.value[1]?.children?.[0]; | ||
| 904 | riskItem && (riskItem.visible = val == 'Y'); | ||
| 905 | } else if (row.field == 'isLdField') { | ||
| 906 | let childrenItem = privacyFormItems.value[2]?.children; | ||
| 907 | childrenItem?.[0] && (childrenItem[0].visible = val == "Y"); | ||
| 908 | childrenItem?.[1] && (childrenItem[1].visible = val == "Y"); | ||
| 909 | } else if (row.field == 'isTcField') { | ||
| 910 | let childrenItem = privacyFormItems.value[3]?.children; | ||
| 911 | childrenItem?.[0] && (childrenItem[0].visible = val == "Y"); | ||
| 912 | childrenItem?.[1] && (childrenItem[1].visible = val == "Y"); | ||
| 913 | } | ||
| 914 | } | ||
| 915 | |||
| 916 | watch(() => props.fieldNameList, (val) => { | ||
| 917 | fieldRulesFormItems.value[0].options = val || []; | ||
| 918 | if (props.isFile) { | ||
| 919 | fieldRulesFormItems.value[1].disabled = false | ||
| 920 | } else { | ||
| 921 | fieldRulesFormItems.value[1].disabled = true; | ||
| 922 | } | ||
| 923 | }, { | ||
| 924 | immediate: true | ||
| 925 | }) | ||
| 926 | |||
| 927 | watch(() => props.fieldTypeList, (val) => { | ||
| 928 | fieldRulesFormItems.value[1].options = val || []; | ||
| 929 | }, { | ||
| 930 | immediate: true | ||
| 931 | }) | ||
| 932 | |||
| 933 | watch(() => props.anonPrivacyMode, (val) => { | ||
| 934 | if (!val) { | ||
| 935 | return; | ||
| 936 | } | ||
| 937 | let hasKaNumber = val.kaNumber != null; | ||
| 938 | privacyFormItems.value[0].default = hasKaNumber ? 'Y' : 'N'; | ||
| 939 | privacyFormItems.value[0].children[0].visible = hasKaNumber; | ||
| 940 | privacyFormItems.value[0].children[0].default = val.kaNumber; | ||
| 941 | oldPrivacyModelValue.value.isKaNumber = hasKaNumber ? 'Y' : 'N'; | ||
| 942 | oldPrivacyModelValue.value.kaNumber = val.kaNumber; | ||
| 943 | let hasRiskThreshold = val.riskThreshold != null; | ||
| 944 | privacyFormItems.value[1].default = hasRiskThreshold ? 'Y' : 'N'; | ||
| 945 | privacyFormItems.value[1].children[0].visible = hasRiskThreshold; | ||
| 946 | privacyFormItems.value[1].children[0].default = val.riskThreshold; | ||
| 947 | oldPrivacyModelValue.value.isRiskThreshold = hasRiskThreshold ? 'Y' : 'N'; | ||
| 948 | oldPrivacyModelValue.value.riskThreshold = val.riskThreshold; | ||
| 949 | let hasldFieldName = !!val.ldFieldName; | ||
| 950 | privacyFormItems.value[2].default = hasldFieldName ? 'Y' : 'N'; | ||
| 951 | privacyFormItems.value[2].children[0].visible = hasldFieldName; | ||
| 952 | privacyFormItems.value[2].children[1].visible = hasldFieldName; | ||
| 953 | privacyFormItems.value[2].children[0].default = val.ldFieldName; | ||
| 954 | privacyFormItems.value[2].children[1].default = val.ldNumber; | ||
| 955 | oldPrivacyModelValue.value.isLdField = hasldFieldName ? 'Y' : 'N'; | ||
| 956 | oldPrivacyModelValue.value.ldFieldName = val.ldFieldName; | ||
| 957 | oldPrivacyModelValue.value.ldNumber = val.ldNumber; | ||
| 958 | let hasTcField = !!val.tcFieldName; | ||
| 959 | privacyFormItems.value[3].default = hasTcField ? 'Y' : 'N'; | ||
| 960 | privacyFormItems.value[3].children[0].visible = hasTcField; | ||
| 961 | privacyFormItems.value[3].children[1].visible = hasTcField; | ||
| 962 | privacyFormItems.value[3].children[0].default = val.tcFieldName; | ||
| 963 | privacyFormItems.value[3].children[1].default = val.tcThreshold; | ||
| 964 | oldPrivacyModelValue.value.isTcField = hasTcField ? 'Y' : 'N'; | ||
| 965 | oldPrivacyModelValue.value.tcFieldName = val.tcFieldName; | ||
| 966 | oldPrivacyModelValue.value.tcThreshold = val.tcThreshold; | ||
| 967 | }, { | ||
| 968 | deep: true | ||
| 969 | }) | ||
| 970 | |||
| 971 | watch(() => props.anonTaskRules, (val) => { | ||
| 972 | ruleModelTableInfo.value.data = val || []; | ||
| 973 | let optionsList = val?.map(v => { | ||
| 974 | return { | ||
| 975 | fieldName: v.fieldName, | ||
| 976 | fieldChName: v.fieldChName | ||
| 977 | } | ||
| 978 | }) || []; | ||
| 979 | privacyFormItems.value[2].children[0].options = optionsList; | ||
| 980 | privacyFormItems.value[3].children[0].options = optionsList; | ||
| 981 | }) | ||
| 982 | |||
| 983 | onBeforeMount(() => { | ||
| 984 | getParamsList({ | ||
| 985 | dictType: "标签类型", | ||
| 986 | }).then((res: any) => { | ||
| 987 | if (res?.code == proxy.$passCode) { | ||
| 988 | labelTypeList.value = res.data || []; | ||
| 989 | let item = fieldRulesFormItems.value.find(item => item.field == 'dataTypeCode'); | ||
| 990 | item && (item.options = labelTypeList.value); | ||
| 991 | } else { | ||
| 992 | proxy.$ElMessage.error(res.msg); | ||
| 993 | } | ||
| 994 | }); | ||
| 995 | getParamsList({ | ||
| 996 | dictType: "脱敏规则", | ||
| 997 | }).then((res: any) => { | ||
| 998 | if (res?.code == proxy.$passCode) { | ||
| 999 | desensitiveRuleTypeList.value = res.data || []; | ||
| 1000 | let item = fieldRulesFormItems.value.find(item => item.field == 'desensitiveRuleCode'); | ||
| 1001 | item && (item.options = desensitiveRuleTypeList.value); | ||
| 1002 | } else { | ||
| 1003 | proxy.$ElMessage.error(res.msg); | ||
| 1004 | } | ||
| 1005 | }); | ||
| 1006 | getParamsList({ | ||
| 1007 | dictType: "加密算法", | ||
| 1008 | }).then((res: any) => { | ||
| 1009 | if (res?.code == proxy.$passCode) { | ||
| 1010 | hashMethodList.value = res.data || []; | ||
| 1011 | let item = fieldRulesFormItems.value.find(item => item.field == 'encryptionAlgorithmCode'); | ||
| 1012 | item && (item.options = hashMethodList.value); | ||
| 1013 | } else { | ||
| 1014 | proxy.$ElMessage.error(res.msg); | ||
| 1015 | } | ||
| 1016 | }); | ||
| 1017 | getGeneralizeFileNameList().then((res: any) => { | ||
| 1018 | if (res?.code == proxy.$passCode) { | ||
| 1019 | generalizeFileNameList.value = res.data || []; | ||
| 1020 | let item = fieldRulesFormItems.value.find(item => item.field == 'generalizeFileGuid'); | ||
| 1021 | item && (item.options = generalizeFileNameList.value); | ||
| 1022 | } else { | ||
| 1023 | proxy.$ElMessage.error(res.msg); | ||
| 1024 | } | ||
| 1025 | }); | ||
| 1026 | }) | ||
| 1027 | |||
| 1028 | /** 隐私模型设置表单 */ | ||
| 1029 | const privacyFormRef = ref(); | ||
| 1030 | |||
| 1031 | const getStepTwoConfigInfo = async () => { | ||
| 1032 | if (!ruleModelTableInfo.value.data?.length) { | ||
| 1033 | proxy.$ElMessage.error('字段脱敏规则不能为空'); | ||
| 1034 | return false; | ||
| 1035 | } | ||
| 1036 | try { | ||
| 1037 | await privacyFormRef.value?.ruleFormRef?.validate(); | ||
| 1038 | // 验证通过 | ||
| 1039 | return { | ||
| 1040 | anonPrivacyMode: privacyFormRef.value?.formInline, | ||
| 1041 | anonTaskRules: ruleModelTableInfo.value.data | ||
| 1042 | }; | ||
| 1043 | } catch (error) { | ||
| 1044 | // 验证失败 | ||
| 1045 | return false; | ||
| 1046 | } | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | defineExpose({ | ||
| 1050 | getStepTwoConfigInfo | ||
| 1051 | }) | ||
| 1052 | |||
| 1053 | </script> | ||
| 1054 | |||
| 1055 | <template> | ||
| 1056 | <div class="operator_panel_wrap"> | ||
| 1057 | <ContentWrap id="id-rules" title="设置匿名化规则" description="" style="margin-top: 8px;"> | ||
| 1058 | <Table :tableInfo="ruleModelTableInfo" /> | ||
| 1059 | <div class="row-add-btn"> | ||
| 1060 | <el-button link @click="addRowRules" :icon="CirclePlus" v-preReClick>添加字段脱敏规则</el-button> | ||
| 1061 | </div> | ||
| 1062 | </ContentWrap> | ||
| 1063 | <ContentWrap id="id-screctModel" title="隐私模型设置" description="" style="margin-top: 16px;"> | ||
| 1064 | <Form style="width: 80%;max-width: 500px;" ref="privacyFormRef" :itemList="privacyFormItems" | ||
| 1065 | :rules="privacyFormRules" formId="model-select-edit" @checkboxChange="handleCheckboxChange" /> | ||
| 1066 | </ContentWrap> | ||
| 1067 | |||
| 1068 | <Drawer ref="drawerRef" :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick" | ||
| 1069 | @drawerInputChange="drawerInputChange" @drawerSelectChange="drawerSelectChange"> | ||
| 1070 | <template v-slot:default> | ||
| 1071 | <div v-show="drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'DISSEMBLE'"> | ||
| 1072 | <div>{{ '请配置分段是否脱敏' + `(${desensitiveRuleDetail.ruleDetails?.length}/10)` }}</div> | ||
| 1073 | <el-radio-group v-model="desensitiveRuleDetail.dissembleType"> | ||
| 1074 | <el-radio :value="1">从左往右</el-radio> | ||
| 1075 | <el-radio :value="2">从右往左</el-radio> | ||
| 1076 | </el-radio-group> | ||
| 1077 | <div class="seg-main"> | ||
| 1078 | <div class="row-per" v-for="(item, index) in desensitiveRuleDetail.ruleDetails"> | ||
| 1079 | <el-select v-model="item.digitType" :style="{ width: item.digitType == 2 ? '322px' : '170px' }"> | ||
| 1080 | <el-option v-for="item in digitTypeList" :label="item.label" :value="item.value" | ||
| 1081 | :key="item.value"></el-option> | ||
| 1082 | </el-select> | ||
| 1083 | <el-input v-show="item.digitType != 2" style="width:137px;margin-left: 4px;" v-model="item.digit" | ||
| 1084 | :maxlength="6" :min="1" @input="(val) => inputEventDigitChange(val, item)" placeholder="请输入"></el-input> | ||
| 1085 | <el-select v-model="item.ruleType" style="width:170px;margin-left: 4px;"> | ||
| 1086 | <el-option v-for="item in dissembleRuleTypeList" :label="item.label" :value="item.value" | ||
| 1087 | :key="item.value"></el-option> | ||
| 1088 | </el-select> | ||
| 1089 | <div class="title_tool" @click="deleteSegmentRule(item, index)"> | ||
| 1090 | <el-icon :size="20" color="#b2b2b2"> | ||
| 1091 | <Delete /> | ||
| 1092 | </el-icon> | ||
| 1093 | </div> | ||
| 1094 | </div> | ||
| 1095 | <div class="row-add-btn"> | ||
| 1096 | <el-button :disabled="desensitiveRuleDetail.ruleDetails?.length > 9" link @click="addSegmentRule" | ||
| 1097 | :icon="CirclePlus" v-preReClick>添加分段规则</el-button> | ||
| 1098 | </div> | ||
| 1099 | </div> | ||
| 1100 | </div> | ||
| 1101 | <div v-show="drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'CHARREPLACE'"> | ||
| 1102 | <div>{{ '请配置分段的替换方式' + `(${charReplaceRuleDetail.ruleDetails?.length}/10)` }}</div> | ||
| 1103 | <el-radio-group v-model="charReplaceRuleDetail.replaceType"> | ||
| 1104 | <el-radio :value="1">从左往右</el-radio> | ||
| 1105 | <el-radio :value="2">从右往左</el-radio> | ||
| 1106 | </el-radio-group> | ||
| 1107 | <div class="seg-main"> | ||
| 1108 | <div class="row-per" v-for="(item, index) in charReplaceRuleDetail.ruleDetails"> | ||
| 1109 | <el-select v-model="item.digitType" :style="{ width: item.digitType == 2 ? '220px' : '130px' }"> | ||
| 1110 | <el-option v-for="item in digitTypeList" :label="item.label" :value="item.value" | ||
| 1111 | :key="item.value"></el-option> | ||
| 1112 | </el-select> | ||
| 1113 | <el-input v-show="item.digitType != 2" style="width:86px;margin-left: 4px;" v-model="item.digit" | ||
| 1114 | :maxlength="6" :min="1" @input="(val) => inputEventDigitChange(val, item)" placeholder="请输入"></el-input> | ||
| 1115 | <el-select v-model="item.ruleType" | ||
| 1116 | :style="{ width: item.ruleType == 1 ? '244px' : '130px', 'margin-left': '4px' }"> | ||
| 1117 | <el-option v-for="item in charReplaceRuleTypeList" :label="item.label" :value="item.value" | ||
| 1118 | :key="item.value"></el-option> | ||
| 1119 | </el-select> | ||
| 1120 | <el-input v-model="item.fixedValue" v-show="item.ruleType == 2" style="width:110px;margin-left: 4px;" | ||
| 1121 | :maxlength="50" placeholder="请输入"></el-input> | ||
| 1122 | <div class="title_tool" @click="deleteCharReplaceSegmentRule(item, index)"> | ||
| 1123 | <el-icon :size="20" color="#b2b2b2"> | ||
| 1124 | <Delete /> | ||
| 1125 | </el-icon> | ||
| 1126 | </div> | ||
| 1127 | </div> | ||
| 1128 | <div class="row-add-btn"> | ||
| 1129 | <el-button :disabled="charReplaceRuleDetail.ruleDetails?.length > 9" link | ||
| 1130 | @click="addCharReplaceSegmentRule" :icon="CirclePlus" v-preReClick>添加分段规则</el-button> | ||
| 1131 | </div> | ||
| 1132 | </div> | ||
| 1133 | </div> | ||
| 1134 | <div v-show="drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'RANGEREPLACE'"> | ||
| 1135 | <div class="mb8">{{ '请配置区间替换规则' + `(${rangeReplaceRuleDetails?.length}/10)` }}</div> | ||
| 1136 | <div class="seg-main"> | ||
| 1137 | <div class="row-per" v-for="(item, index) in rangeReplaceRuleDetails"> | ||
| 1138 | <el-input style="width:16.5%" v-model="item.lowValue" :maxlength="6" | ||
| 1139 | @input="(val) => inputEventDigitChange(val, item, 'lowValue')" placeholder="请输入"></el-input> | ||
| 1140 | <el-select v-model="item.lowOperator" style="width: 16.5%;margin-left: 4px;"> | ||
| 1141 | <el-option v-for="item in lowerOperatorList" :label="item.label" :value="item.value" | ||
| 1142 | :key="item.value"></el-option> | ||
| 1143 | </el-select> | ||
| 1144 | <el-input style="width: 16.5%;margin-left: 4px;" :disabled="true" v-model="item.fieldChName"></el-input> | ||
| 1145 | <el-select v-model="item.upperOperator" style="width: 16.5%;margin-left: 4px;"> | ||
| 1146 | <el-option v-for="item in lowerOperatorList" :label="item.label" :value="item.value" | ||
| 1147 | :key="item.value"></el-option> | ||
| 1148 | </el-select> | ||
| 1149 | <el-input style="width:16.5%;margin-left: 4px;" v-model="item.upperValue" :maxlength="6" | ||
| 1150 | @input="(val) => inputEventDigitChange(val, item, 'upperValue')" placeholder="请输入"></el-input> | ||
| 1151 | <el-input v-model="item.replaceValue" style="width:16.5%;margin-left: 4px;" :maxlength="50" | ||
| 1152 | placeholder="替换值"></el-input> | ||
| 1153 | <div class="title_tool" @click="deleteRangeReplaceSegmentRule(item, index)"> | ||
| 1154 | <el-icon :size="20" color="#b2b2b2"> | ||
| 1155 | <Delete /> | ||
| 1156 | </el-icon> | ||
| 1157 | </div> | ||
| 1158 | </div> | ||
| 1159 | <div class="row-add-btn"> | ||
| 1160 | <el-button :disabled="charReplaceRuleDetail.ruleDetails?.length > 9" link | ||
| 1161 | @click="addRangeReplaceSegmentRule" :icon="CirclePlus" v-preReClick>添加分段规则</el-button> | ||
| 1162 | </div> | ||
| 1163 | </div> | ||
| 1164 | </div> | ||
| 1165 | </template> | ||
| 1166 | </Drawer> | ||
| 1167 | |||
| 1168 | </div> | ||
| 1169 | </template> | ||
| 1170 | |||
| 1171 | <style lang="scss" scoped> | ||
| 1172 | .row-add-btn { | ||
| 1173 | .el-button--default { | ||
| 1174 | padding: 4px 0px; | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | :deep(.el-icon) { | ||
| 1178 | width: 16px; | ||
| 1179 | height: 16px; | ||
| 1180 | |||
| 1181 | svg { | ||
| 1182 | width: 16px; | ||
| 1183 | height: 16px; | ||
| 1184 | } | ||
| 1185 | } | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | .seg-main { | ||
| 1189 | .row-add-btn { | ||
| 1190 | margin-top: -6px; | ||
| 1191 | } | ||
| 1192 | } | ||
| 1193 | |||
| 1194 | .row-per { | ||
| 1195 | display: flex; | ||
| 1196 | align-items: center; | ||
| 1197 | margin-bottom: 8px; | ||
| 1198 | position: relative; | ||
| 1199 | |||
| 1200 | .title_tool { | ||
| 1201 | margin-left: 4px; | ||
| 1202 | // position: absolute; | ||
| 1203 | // right: 4px; | ||
| 1204 | cursor: pointer; | ||
| 1205 | |||
| 1206 | :deep(.el-icon) { | ||
| 1207 | --color: #FB2323 !important; | ||
| 1208 | |||
| 1209 | svg { | ||
| 1210 | width: 16px; | ||
| 1211 | height: 16px; | ||
| 1212 | } | ||
| 1213 | } | ||
| 1214 | } | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | :deep(.mt8.drawer_panel) { | ||
| 1218 | margin-top: 8px; | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | .mb8 { | ||
| 1222 | margin-bottom: 8px; | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | :deep(.el-form-item.ka-checkbox-input) { | ||
| 1226 | .el-input { | ||
| 1227 | min-width: 50px !important; | ||
| 1228 | } | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | :deep(.el-form) { | ||
| 1232 | .lmt12 { | ||
| 1233 | // margin-top: -4px; 验证信息会被遮挡 | ||
| 1234 | |||
| 1235 | .input_panel { | ||
| 1236 | flex: 0 !important; | ||
| 1237 | } | ||
| 1238 | } | ||
| 1239 | } | ||
| 1240 | </style> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -72,18 +72,24 @@ const tableInfo = ref({ | ... | @@ -72,18 +72,24 @@ const tableInfo = ref({ |
| 72 | btns: (scope) => { | 72 | btns: (scope) => { |
| 73 | return [ { | 73 | return [ { |
| 74 | label: "编辑", value: "edit", disabled: scope.row.status == 'R', click: (scope) => { | 74 | label: "编辑", value: "edit", disabled: scope.row.status == 'R', click: (scope) => { |
| 75 | 75 | router.push({ | |
| 76 | name: 'anonTaskCreate', | ||
| 77 | query: { | ||
| 78 | guid: scope.row.guid, | ||
| 79 | taskName: scope.row.taskName | ||
| 80 | } | ||
| 81 | }); | ||
| 76 | } | 82 | } |
| 77 | }, { | 83 | }, { |
| 78 | label: '查看数据', value: 'view', disabled: scope.row.status != 'Y', click: (scope) => { | 84 | label: '查看数据', value: 'view', disabled: scope.row.status != 'Y', click: (scope) => { |
| 79 | // router.push({ | 85 | router.push({ |
| 80 | // name: 'sensitiveIdentifyConfig', | 86 | name: 'anonResultView', |
| 81 | // query: { | 87 | query: { |
| 82 | // guid: scope.row.guid, | 88 | guid: scope.row.guid, |
| 83 | // execGuid: scope.row.execGuid, | 89 | execGuid: scope.row.lastExecGuid, |
| 84 | // taskName: scope.row.taskName | 90 | taskName: scope.row.taskName |
| 85 | // } | 91 | } |
| 86 | // }); | 92 | }); |
| 87 | } | 93 | } |
| 88 | }, { | 94 | }, { |
| 89 | label: "删除", value: "delete", disabled: scope.row.status == 'R', click: (scope) => { | 95 | label: "删除", value: "delete", disabled: scope.row.status == 'R', click: (scope) => { | ... | ... |
| ... | @@ -983,14 +983,14 @@ const btnFormClick = (btn, type) => { | ... | @@ -983,14 +983,14 @@ const btnFormClick = (btn, type) => { |
| 983 | modelsDialogVisible.value = true; | 983 | modelsDialogVisible.value = true; |
| 984 | if (!databaseList.value?.length) { | 984 | if (!databaseList.value?.length) { |
| 985 | getDataSourceListData().then(() => { | 985 | getDataSourceListData().then(() => { |
| 986 | if (databaseInfo.value == databaseList.value[0]?.guid ?? "") { | 986 | if (databaseInfo.value == (databaseList.value[0]?.guid ?? "")) { |
| 987 | dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value)); | 987 | dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value)); |
| 988 | } else { | 988 | } else { |
| 989 | databaseInfo.value = databaseList.value[0]?.guid ?? ""; | 989 | databaseInfo.value = databaseList.value[0]?.guid ?? ""; |
| 990 | } | 990 | } |
| 991 | }) | 991 | }) |
| 992 | } else { | 992 | } else { |
| 993 | if (databaseInfo.value == databaseList.value[0]?.guid ?? "") { | 993 | if (databaseInfo.value == (databaseList.value[0]?.guid ?? "")) { |
| 994 | dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value)); | 994 | dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value)); |
| 995 | } else { | 995 | } else { |
| 996 | databaseInfo.value = databaseList.value[0]?.guid ?? ""; | 996 | databaseInfo.value = databaseList.value[0]?.guid ?? ""; | ... | ... |
-
Please register or sign in to post a comment