Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
csbr-daop
/
fe-data-trusted-space
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
371e8ad9
authored
2025-09-04 17:45:24 +0800
by
lihua
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
匿名化处理的接口联调
1 parent
24ff18ee
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
2545 additions
and
44 deletions
src/api/modules/dataAnonymization.ts
src/components/Drawer/index.vue
src/components/Form/index.vue
src/router/modules/dataAnonymization.ts
src/views/data_anonymization/anonResultView.vue
src/views/data_anonymization/anonTaskCreate.vue
src/views/data_anonymization/anonTaskStepTwo.vue
src/views/data_anonymization/generalizeFile.vue
src/views/data_anonymization/resultProcess.vue
src/views/data_quality/assessTemplate.vue
src/api/modules/dataAnonymization.ts
View file @
371e8ad
...
...
@@ -67,6 +67,12 @@ export const getGeneralizeFileList = (params) => request({
data
:
params
})
/** 获取泛化文件列表,包括名称和guid */
export
const
getGeneralizeFileNameList
=
()
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/generalize-file/name-list`
,
method
:
'post'
})
export
const
saveGeneralizeFile
=
(
data
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/generalize-file/save`
,
method
:
'post'
,
...
...
@@ -214,3 +220,104 @@ export const deleteAnonTask = (data) => request({
method
:
'delete'
,
data
})
/** 保存匿名化任务 */
export
const
saveAnonTask
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/save`
,
method
:
'post'
,
data
:
params
})
/** 更新匿名化任务 */
export
const
updateAnonTask
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/update`
,
method
:
'put'
,
data
:
params
})
/** 获取匿名化任务详情 */
export
const
getAnonTaskDetail
=
(
guid
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/detail?guid=
${
guid
}
`
,
method
:
'get'
})
/** 执行匿名化任务 */
export
const
execAnonTask
=
(
taskGuid
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/exec-task?taskGuid=
${
taskGuid
}
`
,
method
:
'post'
})
/** 匿名化任务检验接口 */
export
const
anonTaskCheck
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/check`
,
method
:
'post'
,
data
:
params
})
/** 获取匿名化任务分析结果数据 */
export
const
getAnonAnalyzeResult
=
(
execGuid
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/get-anon-analyze?taskExecGuid=
${
execGuid
}
`
,
method
:
'get'
})
/** 获取匿名化任务分析结果统计分页数据 */
export
const
getAnonAnalyzePageData
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/page-anon-analyze-data`
,
method
:
'post'
,
data
:
params
})
/** 获取匿名化任务结果数据 */
export
const
getAnonPageData
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/page-anon-data`
,
method
:
'post'
,
data
:
params
})
/** 字段中文转英文 */
export
const
chTransformEn
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_COMMON_URL
}
/common/convert-field-ch-name`
,
method
:
"post"
,
data
:
params
,
});
/** 根据选择的连接池获取表列表 */
export
const
getDsTableByDs
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DATA_SOURCE_URL
}
/data-source/schema-table-page-list`
,
method
:
'post'
,
data
:
params
})
/** 根据数据表获取表字段结构 */
export
const
getDsTableFieldColumn
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DATA_SOURCE_URL
}
/data-source/table-column-list`
,
method
:
'post'
,
data
:
params
});
/** 根据数据表获取表数据 */
export
const
getDsTableSampleData
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DATA_SOURCE_URL
}
/data-source/table-data-preview-page`
,
method
:
'post'
,
data
:
params
});
/** 根据字段名称获取敏感数据识别标签 */
export
const
getLableByFieldName
=
(
fieldName
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/sensitive-data-task/get-label-by-field-name?fieldName=
${
fieldName
}
`
,
method
:
'get'
});
/** 验证样本数据 */
export
const
validateAnonRule
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/check`
,
method
:
'post'
,
data
:
params
})
/** 导出匿名化结果数据 */
export
const
exportAnonExecData
=
(
params
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_ANONYMIZATION_BASEURL
}
/anon-task/export-anon-data?taskGuid=
${
params
.
taskGuid
}
&taskExecGuid=
${
params
.
execGuid
}
`
,
method
:
'get'
,
responseType
:
'blob'
})
\ No newline at end of file
...
...
src/components/Drawer/index.vue
View file @
371e8ad
...
...
@@ -13,6 +13,7 @@ const emits = defineEmits([
"drawerBtnClick"
,
"radioGroupChange"
,
"drawerSelectChange"
,
'drawerInputChange'
,
"drawerTableSelectChange"
,
"drawerTableBtnClick"
,
"drawerTableToolBtnClick"
,
...
...
@@ -98,15 +99,15 @@ const onClickOutside = (e: any) => {
emits
(
"onClickOutside"
);
};
const
getDrawerConRef
=
(
refName
)
=>
{
console
.
log
(
refName
,
'----------'
)
const
getDrawerConRef
=
(
refName
,
index
=
0
)
=>
{
//
console.log(refName, '----------')
if
(
refName
==
'drawerTableRef'
)
{
const
dtf
=
drawerTableRef
.
value
[
0
]
||
drawerTableRef
.
value
return
dtf
?.
tableRef
}
// const drawerForm = drawerFormRef.value[0] || drawerFormRef.value;
if
(
refName
==
'drawerFormRef'
)
{
const
drawerForm
=
drawerFormRef
.
value
?.[
0
]
||
drawerFormRef
.
value
;
const
drawerForm
=
drawerFormRef
.
value
?.[
index
]
||
drawerFormRef
.
value
;
return
drawerForm
}
}
...
...
@@ -187,6 +188,10 @@ const radioGroupChange = (val, info) => {
emits
(
"radioGroupChange"
,
val
,
info
);
};
const
formInputChange
=
(
val
,
row
,
info
)
=>
{
emits
(
"drawerInputChange"
,
val
,
row
,
info
);
}
const
formSelectChange
=
(
val
,
row
,
info
)
=>
{
emits
(
"drawerSelectChange"
,
val
,
row
,
info
);
};
...
...
@@ -319,10 +324,10 @@ const drawerClose = () => {
<
template
v-else
>
<Form
ref=
"drawerFormRef"
:itemList=
"con.formInfo.items"
:formId=
"con.formInfo.id"
:rules=
"con.formInfo.rules"
:col=
"con.formInfo.col"
:readonly=
"con.formInfo.readonly"
@
radioGroupChange=
"radioGroupChange"
@
selectChange=
"formSelectChange"
@
btnClick=
"formBtnClick"
>
@
selectChange=
"formSelectChange"
@
input-change=
"formInputChange"
@
btnClick=
"formBtnClick"
>
</Form>
<!-- 插槽内容 -->
<slot></slot>
<slot
v-if=
"con.showSlot !== false"
></slot>
</
template
>
</div>
</template>
...
...
src/components/Form/index.vue
View file @
371e8ad
...
...
@@ -189,27 +189,30 @@ const inputFocus = (event, item) => {
}
const
inputChange
=
(
val
,
row
)
=>
{
let
decimalCnt
=
row
.
decimalCnt
||
2
;
if
(
row
.
inputType
==
"moneyNumber"
||
row
.
inputType
==
'scoreNumber'
)
{
let
strArr
=
val
.
split
(
"."
);
if
(
strArr
.
length
>
1
)
{
let
right
=
strArr
[
1
];
if
(
right
===
""
||
right
.
length
<
2
)
{
formInline
.
value
[
row
.
field
]
=
val
=
parseFloat
(
val
||
0
).
toFixed
(
2
);
if
(
right
===
""
||
right
.
length
<
decimalCnt
)
{
formInline
.
value
[
row
.
field
]
=
val
=
parseFloat
(
val
||
0
).
toFixed
(
decimalCnt
);
}
}
else
{
formInline
.
value
[
row
.
field
]
=
val
=
parseFloat
(
val
||
0
).
toFixed
(
2
);
formInline
.
value
[
row
.
field
]
=
val
=
parseFloat
(
val
||
0
).
toFixed
(
decimalCnt
);
}
}
if
(
row
.
inputType
==
'scoreNumber'
&&
parseFloat
(
val
)
>
100
)
{
let
max
=
row
.
max
||
100
;
if
(
row
.
inputType
==
'scoreNumber'
&&
parseFloat
(
val
)
>
max
)
{
// 先去除非数字和小数点字符
val
=
val
.
replace
(
/
[^\d
.
]
/g
,
""
);
// 限制最多保留两位小数
val
=
val
.
replace
(
/
\.{2,}
/g
,
"."
);
val
=
val
.
replace
(
/^
(\d
+
)\.(\d{2})
.*$/
,
"$1.$2"
);
let
exp2
=
new
RegExp
(
`^(\d+)\.(\d{
${
decimalCnt
}
}).*$`
,
'g'
);
val
=
val
.
replace
(
exp2
,
"$1.$2"
);
let
num
=
parseFloat
(
val
);
if
(
num
>
100
)
{
num
=
100
;
// 超过100时将其设置为100
val
=
num
.
toFixed
(
2
);
// 保证显示为两位小数
if
(
num
>
max
)
{
num
=
max
;
// 超过100时将其设置为100
val
=
num
.
toFixed
(
decimalCnt
);
// 保证显示为两位小数
}
formInline
.
value
[
row
.
field
]
=
val
;
}
...
...
@@ -250,11 +253,15 @@ const inputEventChange = (val, item) => {
}
return
;
}
else
if
(
item
.
inputType
==
'scoreNumber'
)
{
//小于100的,保留两位小数
let
decimalCnt
=
item
.
decimalCnt
||
2
;
formInline
.
value
[
item
.
field
]
=
formInline
.
value
[
item
.
field
].
toString
().
replace
(
/
[^\d
.
]
/g
,
""
)
/** 连续两个小数点替换为一个小数点 */
formInline
.
value
[
item
.
field
]
=
formInline
.
value
[
item
.
field
].
toString
().
replace
(
/
\.{2,}
/g
,
"."
)
formInline
.
value
[
item
.
field
]
=
formInline
.
value
[
item
.
field
].
toString
().
replace
(
/^
\D
*
(\d{0,3}(?:\.\d{0,2})?)
.*$/g
,
"$1"
)
/** 最多取三位整数,2位小数 */
let
exp2
=
new
RegExp
(
`^\\D*(\\d{0,3}(?:\\.\\d{0,
${
decimalCnt
}
})?).*$`
,
'g'
);
formInline
.
value
[
item
.
field
]
=
formInline
.
value
[
item
.
field
].
toString
().
replace
(
exp2
,
"$1"
)
if
(
item
.
max
!=
null
&&
formInline
.
value
[
item
.
field
]
>
item
.
max
)
{
formInline
.
value
[
item
.
field
]
=
item
.
max
.
toFixed
(
2
);
formInline
.
value
[
item
.
field
]
=
item
.
max
.
toFixed
(
decimalCnt
);
}
return
;
}
else
if
(
item
.
inputType
==
'moneyNumber'
)
{
// 单位是元,保留两位小数。
...
...
@@ -755,21 +762,30 @@ const panelChange = (scope, row) => {
<span
v-if=
"child.prepend"
>
{{ child.prepend }}
</span>
<el-input
v-if=
"child.visible ?? true"
v-model
.
trim=
"formInline[child.field]"
:disabled=
"child.disabled || readonly"
:placeholder=
"child.placeholder"
:maxlength=
"child.maxlength ?? ''"
/>
:maxlength=
"child.maxlength ?? ''"
@
change=
"(val) => inputChange(val, child)"
@
input=
"(val) => inputEventChange(val, child)"
/>
<span
v-if=
"child.append"
>
{{ child.append }}
</span>
</div>
</div>
<div
class=
"checkbox_input"
:class=
"[item.col, { is_block: item.block }]"
v-else-if=
"item.type == 'checkbox-input-item'"
>
<el-checkbox
v-model=
"formInline[item.field]"
:disabled=
"item.disabled || readonly"
@
change=
"(val) => checkboxChange(val, item)"
:true-
label
=
"item.trueValue ?? true"
:false-
label
=
"item.falseValue ?? false"
>
{{ item.placeholder }}
</el-checkbox>
@
change=
"(val) => checkboxChange(val, item)"
:true-
value
=
"item.trueValue ?? true"
:false-
value
=
"item.falseValue ?? false"
>
{{ item.placeholder }}
</el-checkbox>
<div
class=
"input_panel"
v-for=
"child in item.children"
>
<el-form-item
v-if=
"child.visible ?? true"
:prop=
"child.field"
:validate-status=
"child.validateStatus ?? ''"
:error=
"child.error"
:class=
"[child.col, { is_block: child.block }]"
:style=
"child.style ?? {}"
>
<el-input
v-model
.
trim=
"formInline[child.field]"
:disabled=
"child.disabled || readonly"
:placeholder=
"child.placeholder"
:maxlength=
"child.maxlength ?? ''"
/>
<el-input
v-if=
"child.type == 'input'"
v-model
.
trim=
"formInline[child.field]"
:disabled=
"child.disabled || readonly"
:placeholder=
"child.placeholder"
:maxlength=
"child.maxlength ?? ''"
@
change=
"(val) => inputChange(val, child)"
@
input=
"(val) => inputEventChange(val, child)"
/>
<el-select
v-else-if=
"child.type == 'select'"
v-model=
"formInline[child.field]"
:placeholder=
"child.placeholder"
:disabled=
"child.disabled || readonly"
:filterable=
"child.filterable"
:clearable=
"child.clearable"
:teleported=
"child.teleported || true"
>
<el-option
v-for=
"opts in child.options"
:label=
"child.props?.label ? opts[child.props.label] : opts.label"
:value=
"child.props?.value ? opts[child.props.value] : opts.value"
:disabled=
"opts.disabled"
/>
</el-select>
</el-form-item>
</div>
</div>
...
...
src/router/modules/dataAnonymization.ts
View file @
371e8ad
...
...
@@ -147,7 +147,7 @@ const routes: RouteRecordRaw[] = [
name
:
'anonTaskCreate'
,
component
:
()
=>
import
(
'@/views/data_anonymization/anonTaskCreate.vue'
),
meta
:
{
title
:
'
新建
匿名化处理任务'
,
title
:
'匿名化处理任务'
,
sidebar
:
false
,
breadcrumb
:
false
,
cache
:
true
,
...
...
@@ -161,6 +161,23 @@ const routes: RouteRecordRaw[] = [
}
}
},
{
path
:
'anonResultView'
,
name
:
'anonResultView'
,
component
:
()
=>
import
(
'@/views/data_anonymization/anonResultView.vue'
),
meta
:
{
title
:
'查看数据'
,
sidebar
:
false
,
breadcrumb
:
false
,
cache
:
true
,
reuse
:
true
},
beforeEnter
:
(
to
,
from
)
=>
{
if
(
to
.
query
.
guid
)
{
to
.
meta
.
title
=
`查看数据-
${
to
.
query
.
taskName
}
`
;
}
}
},
],
},
]
...
...
src/views/data_anonymization/anonResultView.vue
0 → 100644
View file @
371e8ad
<route
lang=
"yaml"
>
name: anonResultView
</route>
<
script
lang=
"ts"
setup
name=
"anonResultView"
>
import
{
ref
}
from
"vue"
;
import
{
getAnonPageData
,
getAnonAnalyzeResult
,
exportAnonExecData
,
}
from
"@/api/modules/dataAnonymization"
;
import
{
calcColumnWidth
}
from
"@/utils/index"
;
import
Moment
from
'moment'
;
import
{
TableColumnWidth
}
from
"@/utils/enum"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
commonPageConfig
}
from
'@/components/PageNav/index'
;
import
{
download
}
from
"@/utils/common"
;
const
{
proxy
}
=
getCurrentInstance
()
as
any
;
const
route
=
useRoute
();
const
props
=
defineProps
({
isPage
:
{
default
:
true
,
type
:
Boolean
},
execGuid
:
{
default
:
''
,
type
:
String
}
});
const
tableData
:
any
=
ref
([]);
const
tableDataLoading
=
ref
(
false
);
const
tableFields
:
any
=
ref
([]);
const
pageInfo
:
any
=
ref
({
...
commonPageConfig
,
})
const
getData
=
()
=>
{
tableData
.
value
=
[];
if
(
!
tableFields
.
value
?.
length
)
{
return
;
}
tableDataLoading
.
value
=
true
;
getAnonPageData
({
pageIndex
:
pageInfo
.
value
.
curr
,
pageSize
:
pageInfo
.
value
.
limit
,
taskExecGuid
:
props
.
isPage
?
route
.
query
.
execGuid
:
props
.
execGuid
,
}).
then
((
res
:
any
)
=>
{
tableDataLoading
.
value
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
tableData
.
value
=
[];
res
.
data
?.
records
?.
forEach
(
d
=>
{
let
obj
=
{};
tableFields
.
value
.
forEach
(
t
=>
{
obj
[
t
.
enName
]
=
d
.
fieldValue
?.[
t
.
enName
];
});
tableData
.
value
.
push
(
obj
);
});
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
}
const
getTextAlign
=
(
field
)
=>
{
if
(
field
.
dataType
===
'decimal'
||
field
.
dataType
===
'int'
||
field
.
dataType
==
'bit'
||
field
.
dataType
==
'tinyint'
)
{
return
'right'
;
}
return
'left'
}
/** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */
const
calcTableColumnWidth
=
(
data
:
any
[],
prop
,
title
,
otherWidth
=
0
)
=>
{
let
d
:
any
[]
=
[];
data
.
forEach
((
dt
)
=>
d
.
push
(
dt
[
prop
]));
return
calcColumnWidth
(
d
,
title
,
{
fontSize
:
14
,
fontFamily
:
"SimSun"
,
},
{
fontSize
:
14
,
fontFamily
:
"SimSun"
,
},
otherWidth
);
};
/** 每列字段对应的列宽计算结果。 */
const
originTableFieldColumn
=
ref
({});
watch
(
tableData
,
(
val
:
any
[],
oldVal
)
=>
{
if
(
!
tableFields
.
value
?.
length
)
{
originTableFieldColumn
.
value
=
{};
return
;
}
originTableFieldColumn
.
value
=
{};
tableFields
.
value
.
forEach
((
field
,
index
)
=>
{
originTableFieldColumn
.
value
[
field
.
enName
]
=
calcTableColumnWidth
(
val
?.
slice
(
0
,
20
)
||
[],
field
.
enName
,
field
.
chName
,
24
);
});
},
{
deep
:
true
,
}
);
watch
(()
=>
props
.
execGuid
,
(
val
)
=>
{
if
(
!
val
)
{
return
;
}
tableDataLoading
.
value
=
true
;
getAnonAnalyzeResult
(
val
).
then
((
res
:
any
)
=>
{
tableDataLoading
.
value
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
let
column
=
res
.
data
?.
column
||
{};
tableFields
.
value
=
column
;
getData
();
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
},
{
immediate
:
true
})
onBeforeMount
(()
=>
{
if
(
!
props
.
isPage
)
{
return
;
}
tableDataLoading
.
value
=
true
;
getAnonAnalyzeResult
(
route
.
query
.
execGuid
).
then
((
res
:
any
)
=>
{
tableDataLoading
.
value
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
let
column
=
res
.
data
?.
column
||
{};
tableFields
.
value
=
column
;
getData
();
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
});
const
formatterPreviewDate
=
(
row
,
info
)
=>
{
let
enName
=
info
.
enName
;
let
v
=
row
[
enName
];
if
(
v
===
0
)
{
return
v
;
}
if
(
!
v
||
v
==
'null'
)
{
return
'--'
;
}
if
(
info
.
dataType
===
'datetime'
)
{
return
Moment
(
v
).
format
(
'YYYY-MM-DD HH:mm:ss'
);
}
if
(
info
.
dataType
===
'date'
)
{
if
(
isNaN
(
<
any
>
(
new
Date
(
v
))))
{
return
Moment
(
parseInt
(
v
)).
format
(
'YYYY-MM-DD'
);
}
else
{
return
Moment
(
v
).
format
(
'YYYY-MM-DD'
);
}
}
return
v
;
};
const
pageChange
=
(
info
)
=>
{
pageInfo
.
value
.
curr
=
Number
(
info
.
curr
);
pageInfo
.
value
.
limit
=
Number
(
info
.
limit
);
getData
();
}
const
promise
:
any
=
ref
(
null
);
const
exportData
=
()
=>
{
if
(
promise
)
{
return
;
}
promise
.
value
=
exportAnonExecData
({
taskGuid
:
route
.
query
.
guid
,
execGuid
:
route
.
query
.
execGuid
}).
then
((
res
:
any
)
=>
{
promise
.
value
=
null
;
if
(
res
&&
!
res
.
msg
)
{
download
(
res
,
route
.
query
.
taskName
+
'_匿名化数据.xlsx'
,
'excel'
)
}
else
{
res
?.
msg
&&
ElMessage
.
error
(
res
?.
msg
);
}
}).
catch
(()
=>
{
promise
.
value
=
null
;
})
}
</
script
>
<
template
>
<div
class=
"table_tool_wrap"
v-loading=
"tableDataLoading"
>
<el-button
v-show=
"props.isPage"
style=
"margin-bottom: 8px;"
type=
"primary"
@
click=
"exportData"
v-preReClick
>
导出数据
</el-button>
<el-table
ref=
"tableRef"
v-show=
"tableFields.length"
:data=
"tableData"
:highlight-current-row=
"true"
stripe
border
tooltip-effect=
"light"
height=
"100%"
row-key=
"guid"
:style=
"
{ width: '100%', height: 'calc(100% - 64px)' }">
<template
v-for=
"(item, index) in (tableFields || [])"
>
<el-table-column
:label=
"item.chName"
:width=
"item.dataType === 'datetime'
? TableColumnWidth.DATETIME
: item.dataType === 'date'
? TableColumnWidth.DATE
: originTableFieldColumn[item.enName]
"
:align=
"getTextAlign(item)"
:header-align=
"getTextAlign(item)"
:formatter=
"(row) => formatterPreviewDate(row, item)"
:show-overflow-tooltip=
"true"
>
</el-table-column>
</
template
>
</el-table>
<div
v-show=
"!tableFields.length"
class=
"empty-content"
>
<img
src=
"../../assets/images/empty-data.png"
:style=
"{ width: '168px', height: '96px' }"
/>
<div
class=
"empty-text"
>
暂无数据
</div>
</div>
<PageNav
:class=
"[pageInfo.type]"
:pageInfo=
"pageInfo"
@
pageChange=
"pageChange"
/>
</div>
</template>
<
style
lang=
"scss"
scoped
>
.table_tool_wrap
{
width
:
100%
;
height
:
100%
;
padding
:
8px
16px
16px
;
.tips_text
{
font-size
:
14px
;
color
:
var
(
--el-text-color-tip
);
display
:
block
;
font-weight
:
normal
;
margin-bottom
:
8px
;
line-height
:
21px
;
}
.el-table
{
display
:
inline-block
;
}
.empty-content
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
height
:
100%
;
width
:
100%
;
flex-direction
:
column
;
.empty-text
{
font-size
:
14px
;
color
:
#b2b2b2
;
}
}
}
</
style
>
\ No newline at end of file
src/views/data_anonymization/anonTaskCreate.vue
View file @
371e8ad
...
...
@@ -4,16 +4,48 @@
<
script
lang=
"ts"
setup
name=
"anonTaskCreate"
>
import
{
dataSourceTypeList
dataSourceTypeList
,
getAnonTaskDetail
,
getParamsList
,
chTransformEn
,
getAnonAnalyzeResult
,
getAnonAnalyzePageData
,
getDatabase
,
getDsTableByDs
,
getDsTableFieldColumn
,
getDsTableSampleData
,
saveAnonTask
,
updateAnonTask
,
exportAnonExecData
,
}
from
'@/api/modules/dataAnonymization'
;
import
{
parseAndDecodeUrl
,
getDownFileSignByUrl
,
obsDownloadRequest
}
from
"@/api/modules/obsService"
;
import
useUserStore
from
"@/store/modules/user"
;
import
{
useValidator
}
from
'@/hooks/useValidator'
;
import
{
TableColumnWidth
}
from
'@/utils/enum'
;
import
{
calcColumnWidth
}
from
"@/utils/index"
;
import
Moment
from
'moment'
;
import
anonTaskStepTwo
from
'./anonTaskStepTwo.vue'
;
import
*
as
XLSX
from
'xlsx'
;
import
{
ElMessage
}
from
'element-plus'
;
import
{
isEqual
,
cloneDeep
}
from
"lodash-es"
;
import
{
download
}
from
"@/utils/common"
;
import
anonResultView
from
'./anonResultView.vue'
;
const
{
proxy
}
=
getCurrentInstance
()
as
any
;
const
userStore
=
useUserStore
();
const
route
=
useRoute
();
const
router
=
useRouter
();
const
fullPath
=
route
.
fullPath
;
const
taskGuid
=
ref
(
route
.
query
.
guid
);
/** 提交保存和编辑后的执行guid */
const
taskExecGuid
=
ref
(
''
);
/** 是否执行结束 */
const
isExecEnd
=
ref
(
false
);
const
{
required
}
=
useValidator
();
const
fullscreenLoading
=
ref
(
false
);
const
step
=
ref
(
0
);
...
...
@@ -27,11 +59,17 @@ const stepsInfo = ref({
]
})
const
dataSourceList
=
ref
([]);
/** 数据源列表 */
const
dataSourceList
:
any
=
ref
([]);
/** 数据源对应的数据表 */
const
dsTableList
:
any
=
ref
([]);
/** 数据共享类型字段列表 */
const
dataSharingTypeList
=
ref
([]);
const
formRef
=
ref
();
/** 数据选择的表单配置信息 */
const
dataSelectInfoItems
=
ref
([{
label
:
'任务名称'
,
...
...
@@ -49,8 +87,8 @@ const dataSelectInfoItems = ref([{
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'dataSharingTypeCode'
,
default
:
''
,
options
:
dataSharingTypeList
,
default
:
'
01
'
,
options
:
dataSharingTypeList
.
value
,
props
:
{
label
:
"label"
,
value
:
"value"
,
...
...
@@ -60,6 +98,21 @@ const dataSelectInfoItems = ref([{
clearable
:
true
,
visible
:
true
,
},
{
label
:
'患者占总人口比'
,
type
:
'input'
,
placeholder
:
'数值,支持小数点9位'
,
field
:
'patientPopulationRate'
,
maxlength
:
11
,
min
:
0
,
max
:
1
,
inputType
:
'scoreNumber'
,
decimalCnt
:
9
,
default
:
''
,
required
:
true
,
filterable
:
true
,
clearable
:
true
,
visible
:
true
,
},
{
label
:
'数据来源'
,
type
:
'select'
,
placeholder
:
'请选择'
,
...
...
@@ -72,7 +125,6 @@ const dataSelectInfoItems = ref([{
},
required
:
true
,
filterable
:
true
,
clearable
:
true
,
visible
:
true
,
},
{
label
:
'数据源'
,
...
...
@@ -89,31 +141,694 @@ const dataSelectInfoItems = ref([{
visible
:
true
,
required
:
true
},
{
label
:
"数据表"
,
type
:
"select"
,
placeholder
:
"请选择"
,
field
:
"tableName"
,
options
:
dsTableList
.
value
,
props
:
{
label
:
'tableComment'
,
value
:
'tableName'
},
default
:
''
,
filterable
:
true
,
clearable
:
true
,
required
:
true
,
},
{
label
:
'文件上传'
,
tip
:
'支持扩展名:xlsx、xls、csv,文件大小不超过10MB'
,
type
:
'upload-file'
,
accept
:
'.xlsx, .xls, .csv'
,
limitSize
:
10
,
limit
:
1
,
isExcel
:
true
,
required
:
true
,
default
:
<
any
>
[],
block
:
true
,
block
:
false
,
col
:
'wid60'
,
visible
:
false
,
field
:
'file'
,
}]);
const
dataSelectInfoFormRules
=
ref
({
taskName
:
[
required
(
'请输入任务名称'
)],
dataSharingTypeCode
:
[
required
(
'请选择数据共享类型'
)],
patientPopulationRate
:
[
required
(
'请输入患者占总人口比'
)],
dataSourceGuid
:
[
required
(
'请选择数据源'
)],
tableName
:
[
required
(
'请选择数据表'
)],
file
:
[{
validator
:
(
rule
:
any
,
value
:
any
,
callback
:
any
)
=>
{
if
(
!
value
?.
length
)
{
callback
(
new
Error
(
'请上传文件'
))
}
else
{
callback
();
}
},
trigger
:
'change'
}]
});
/** 最新选中的 */
const
currDatasourceSelect
:
any
=
ref
({});
const
handleDataSelectFormSelectChange
=
async
(
val
,
row
,
formInfo
)
=>
{
if
(
row
.
field
==
'dataSource'
)
{
dataSelectInfoItems
.
value
[
4
].
visible
=
val
==
1
;
dataSelectInfoItems
.
value
[
5
].
visible
=
val
==
1
;
dataSelectInfoItems
.
value
[
6
].
visible
=
val
==
2
;
dataSelectInfoItems
.
value
.
forEach
(
d
=>
{
d
.
default
=
formInfo
[
d
.
field
];
if
(
d
.
field
==
'file'
)
{
d
.
default
=
!
d
.
default
?
[]
:
d
.
default
;
}
});
sampleTableFields
.
value
=
[];
parseFileDataSum
.
value
=
[];
sampleTableData
.
value
=
[];
}
else
if
(
row
.
field
==
'dataSourceGuid'
)
{
if
(
!
val
)
{
currDatasourceSelect
.
value
=
[];
sampleTableFields
.
value
=
[];
parseFileDataSum
.
value
=
[];
sampleTableData
.
value
=
[];
dataSelectInfoItems
.
value
.
forEach
(
d
=>
{
d
.
default
=
formInfo
[
d
.
field
];
if
(
d
.
field
==
'file'
)
{
d
.
default
=
!
d
.
default
?
[]
:
d
.
default
;
}
else
if
(
d
.
field
==
'tableName'
)
{
d
.
options
=
dsTableList
.
value
;
d
.
default
=
''
;
}
});
return
;
}
let
dsInfo
=
currDatasourceSelect
.
value
=
dataSourceList
.
value
.
find
(
d
=>
d
.
guid
==
val
);
//清除数据表得值,重新获取下拉列表
const
res
:
any
=
await
getDsTableByDs
({
pageSize
:
-
1
,
pageIndex
:
1
,
dataSourceGuid
:
val
,
database
:
dsInfo
.
databaseNameEn
,
databaseType
:
dsInfo
.
databaseType
,
tableName
:
''
,
hadFlag
:
false
});
if
(
res
.
code
==
proxy
.
$passCode
)
{
dsTableList
.
value
=
res
.
data
?.
records
||
[];
dataSelectInfoItems
.
value
.
forEach
(
d
=>
{
d
.
default
=
formInfo
[
d
.
field
];
if
(
d
.
field
==
'file'
)
{
d
.
default
=
!
d
.
default
?
[]
:
d
.
default
;
}
else
if
(
d
.
field
==
'tableName'
)
{
d
.
options
=
dsTableList
.
value
;
d
.
default
=
''
;
}
});
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
sampleTableFields
.
value
=
[];
parseFileDataSum
.
value
=
[];
sampleTableData
.
value
=
[];
}
else
if
(
row
.
field
==
'tableName'
)
{
if
(
!
val
)
{
sampleTableFields
.
value
=
[];
sampleTableData
.
value
=
[];
return
;
}
getDsTableFieldColumn
({
pageSize
:
50
,
pageIndex
:
1
,
dataSourceGuid
:
currDatasourceSelect
.
value
.
guid
,
database
:
currDatasourceSelect
.
value
.
databaseNameEn
,
databaseType
:
currDatasourceSelect
.
value
.
databaseType
,
tableName
:
val
,
}).
then
((
res
:
any
)
=>
{
if
(
res
.
code
==
proxy
.
$passCode
)
{
sampleTableFields
.
value
=
res
.
data
?.
map
(
d
=>
{
d
.
fieldDataType
=
d
.
dataType
;
d
.
enName
=
d
.
columnName
;
d
.
chName
=
d
.
columnZhName
;
return
d
;
})
||
[];
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
/** 判断有抽样数据,需要查询接口 */
getSampleDataByDsTable
();
}
}
const
dataSimpleFormRef
=
ref
();
/** 抽样数据预览 */
const
dataSimpleFormItems
=
ref
([{
label
:
'抽样开关'
,
type
:
'switch'
,
field
:
'enableSamplingRate'
,
default
:
'N'
,
col
:
'autoWidth'
,
activeValue
:
'Y'
,
inactiveValue
:
'N'
},
{
label
:
'抽样比例(%)'
,
type
:
'input'
,
placeholder
:
'请输入'
,
field
:
'samplingRate'
,
maxlength
:
3
,
min
:
0
,
//可以是0条。万一只是想看下字段呢
max
:
100
,
inputType
:
'integerNumber'
,
default
:
10
,
required
:
true
,
filterable
:
true
,
clearable
:
true
,
visible
:
false
,
}]);
const
dataSimpleFormRules
=
ref
({
samplingRate
:
[
required
(
'请填写抽样比例'
)],
});
const
changeStep
=
(
val
)
=>
{
const
oldSamplingRate
=
ref
(
'10'
);
const
handleDataSimpleFormSwitchChange
=
(
val
,
info
)
=>
{
if
(
val
==
'N'
)
{
oldSamplingRate
.
value
=
info
.
samplingRate
;
}
else
{
dataSimpleFormItems
.
value
[
1
].
default
=
oldSamplingRate
.
value
||
10
;
}
dataSimpleFormItems
.
value
[
1
].
visible
=
val
==
'Y'
;
dataSimpleFormItems
.
value
[
0
].
default
=
info
.
enableSamplingRate
||
'N'
;
if
(
formRef
.
value
?.
formInline
?.
file
?.
length
)
{
transferSampleData
();
}
else
{
getSampleDataByDsTable
();
}
}
const
exportResult
=
()
=>
{
/** 输入抽样比例值改变 */
const
handleDataSimpleFormChange
=
(
val
)
=>
{
if
(
formRef
.
value
?.
formInline
?.
file
?.
length
)
{
transferSampleData
();
}
else
{
getSampleDataByDsTable
();
}
}
/** 样本表格加载中 */
const
sampleTableDataLoading
=
ref
(
false
);
/** 样本表格的数据 */
const
sampleTableData
:
any
=
ref
([]);
/** 样本表格的字段 */
const
sampleTableFields
:
any
=
ref
([]);
/** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */
const
calcTableColumnWidth
=
(
data
:
any
[],
prop
,
title
,
otherWidth
=
0
)
=>
{
let
d
:
any
[]
=
[];
data
.
forEach
((
dt
)
=>
d
.
push
(
dt
[
prop
]));
return
calcColumnWidth
(
d
,
title
,
{
fontSize
:
14
,
fontFamily
:
"SimSun"
,
},
{
fontSize
:
14
,
fontFamily
:
"SimSun"
,
},
otherWidth
);
};
/** 每列字段对应的列宽计算结果。 */
const
originTableFieldColumn
=
ref
({});
const
getTextAlign
=
(
field
)
=>
{
if
(
field
.
dataType
===
'decimal'
||
field
.
dataType
===
'int'
)
{
return
'right'
;
}
return
'left'
}
watch
(
sampleTableData
,
(
val
:
any
[],
oldVal
)
=>
{
if
(
!
sampleTableFields
.
value
?.
length
)
{
originTableFieldColumn
.
value
=
{};
return
;
}
originTableFieldColumn
.
value
=
{};
sampleTableFields
.
value
.
forEach
((
field
,
index
)
=>
{
originTableFieldColumn
.
value
[
field
.
enName
]
=
calcTableColumnWidth
(
val
?.
slice
(
0
,
20
)
||
[],
field
.
enName
,
field
.
chName
,
24
);
});
},
{
deep
:
true
,
}
);
const
formatterPreviewDate
=
(
row
,
info
)
=>
{
let
enName
=
info
.
enName
;
let
v
=
row
[
enName
];
if
(
v
===
0
)
{
return
v
;
}
if
(
!
v
)
{
return
v
||
'--'
;
}
if
(
info
.
dataType
===
'datetime'
)
{
return
Moment
(
v
).
format
(
'YYYY-MM-DD HH:mm:ss'
);
}
if
(
info
.
dataType
===
'date'
)
{
if
(
isNaN
(
<
any
>
(
new
Date
(
v
))))
{
return
Moment
(
parseInt
(
v
)).
format
(
'YYYY-MM-DD'
);
}
else
{
return
Moment
(
v
).
format
(
'YYYY-MM-DD'
);
}
}
return
v
;
};
/** 解析的总的表格数据,方便后面修改抽样比例时使用 */
const
parseFileDataSum
:
any
=
ref
([]);
const
parseFileData
=
(
fileRaw
)
=>
{
sampleTableDataLoading
.
value
=
true
;
fileRaw
.
arrayBuffer
().
then
(
async
(
f
)
=>
{
const
wb
=
XLSX
.
read
(
f
,
{
raw
:
false
,
cellDates
:
true
});
const
sheet
=
wb
.
Sheets
[
wb
.
SheetNames
[
0
]];
const
json
:
any
[]
=
XLSX
.
utils
.
sheet_to_json
(
sheet
,
{
header
:
1
});
if
(
json
.
length
==
0
)
{
sampleTableFields
.
value
=
[];
sampleTableData
.
value
=
[];
}
else
{
const
res
=
await
chTransformEn
(
json
[
0
]);
let
fields
=
res
.
data
||
[];
sampleTableFields
.
value
=
fields
?.
map
((
j
,
index
)
=>
{
return
{
index
:
index
,
enName
:
j
.
enName
+
''
,
chName
:
j
.
chName
+
''
,
dataType
:
'varchar'
}
})
||
[];
parseFileDataSum
.
value
=
json
;
/** 粗略算出字段类型 */
json
.
slice
(
1
,
10
).
forEach
((
info
,
row
)
=>
{
json
[
0
].
forEach
((
name
,
col
)
=>
{
if
(
info
[
col
]
===
""
||
info
[
col
]
==
null
||
sampleTableFields
.
value
[
col
].
dataType
!=
'varchar'
)
{
return
;
}
else
{
var
cellRef
=
XLSX
.
utils
.
encode_cell
({
r
:
row
+
1
,
c
:
col
});
var
cell
=
sheet
[
cellRef
];
let
v
=
cell
.
w
||
info
[
col
];
let
isNum
=
cell
.
t
==
'n'
;
if
(
isNum
)
{
if
(
v
.
includes
(
'.'
)
&&
sampleTableFields
.
value
[
col
].
dataType
!=
'decimal'
)
{
sampleTableFields
.
value
[
col
].
dataType
=
'decimal'
;
}
else
{
sampleTableFields
.
value
[
col
].
dataType
=
'int'
;
}
}
}
});
})
transferSampleData
();
}
sampleTableDataLoading
.
value
=
false
;
});
}
/** 获取文件解析后根据抽样比例得出的表格数据 */
const
transferSampleData
=
()
=>
{
let
samplingRate
=
dataSimpleFormRef
.
value
?.
formInline
?.
samplingRate
;
if
(
parseFileDataSum
.
value
.
length
>
1
&&
samplingRate
)
{
let
totalCnt
=
parseFileDataSum
.
value
.
length
-
1
;
let
cnt
=
Math
.
ceil
(
samplingRate
*
0.01
*
totalCnt
)
+
1
;
sampleTableData
.
value
=
parseFileDataSum
.
value
.
slice
(
1
,
cnt
>
1000
?
1001
:
cnt
).
map
((
info
,
row
)
=>
{
let
object
=
{};
parseFileDataSum
.
value
[
0
].
forEach
((
chName
,
col
)
=>
{
let
name
=
sampleTableFields
.
value
[
col
].
enName
;
object
[
name
]
=
info
[
col
];
});
return
object
;
});
}
else
{
sampleTableData
.
value
=
[];
}
}
/** 获取选择的数据库表根据抽样比例得出的表格数据 */
const
getSampleDataByDsTable
=
()
=>
{
const
tableName
=
formRef
.
value
?.
formInline
?.
tableName
;
if
(
!
currDatasourceSelect
.
value
.
guid
||
!
tableName
)
{
sampleTableFields
.
value
=
[];
sampleTableData
.
value
=
[];
return
;
}
let
samplingRate
=
dataSimpleFormRef
.
value
?.
formInline
?.
samplingRate
;
if
(
!
samplingRate
)
{
sampleTableData
.
value
=
[];
return
;
}
let
totalCnt
=
dsTableList
.
value
.
find
(
t
=>
t
.
tableName
==
tableName
)?.
tableRows
||
0
;
let
cnt
=
Math
.
ceil
(
samplingRate
*
0.01
*
totalCnt
);
if
(
!
cnt
)
{
sampleTableData
.
value
=
[];
return
;
}
sampleTableDataLoading
.
value
=
true
;
getDsTableSampleData
({
limitNum
:
cnt
,
pageSize
:
cnt
,
pageIndex
:
1
,
dataSourceGuid
:
currDatasourceSelect
.
value
.
guid
,
database
:
currDatasourceSelect
.
value
.
databaseNameEn
,
databaseType
:
currDatasourceSelect
.
value
.
databaseType
,
tableName
:
tableName
,
hadFlag
:
false
,
}).
then
((
res
:
any
)
=>
{
sampleTableDataLoading
.
value
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
sampleTableData
.
value
=
res
.
data
?.
datas
||
[];
}
else
{
sampleTableData
.
value
=
[];
ElMessage
.
error
(
res
.
msg
);
}
});
}
const
uploadFileChange
=
(
file
)
=>
{
sampleTableFields
.
value
=
[];
sampleTableData
.
value
=
[];
if
(
!
file
.
length
)
{
sampleTableFields
.
value
=
[];
sampleTableData
.
value
=
[];
return
;
}
let
fileRaw
=
file
[
0
].
file
;
parseFileData
(
fileRaw
);
}
/** 第二步的配置组件引用。 */
const
anonTaskStepTwoRef
=
ref
();
const
changeStep
=
async
(
val
)
=>
{
if
(
val
==
2
)
{
formRef
.
value
?.
ruleFormRef
?.
validate
((
valid
)
=>
{
if
(
valid
)
{
dataSimpleFormRef
.
value
?.
ruleFormRef
?.
validate
((
valid
)
=>
{
if
(
valid
)
{
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
}
});
}
});
}
else
if
(
val
==
3
)
{
// 保存并提交 TODO。需要加个 记录旧值的,用来判断新值和旧值,是否发生变化,若变化则需要调用保存接口之后,再进行下一步。
let
configInfo
=
await
anonTaskStepTwoRef
.
value
?.
getStepTwoConfigInfo
();
if
(
!
configInfo
)
{
return
;
}
let
saveParams
:
any
=
{
...
formRef
.
value
.
formInline
};
if
(
saveParams
.
file
?.
length
)
{
saveParams
.
filePath
=
{
name
:
saveParams
.
file
[
0
].
name
,
url
:
saveParams
.
file
[
0
].
url
}
delete
saveParams
.
file
;
saveParams
.
dataSourceGuid
=
null
;
saveParams
.
tableName
=
null
;
}
else
{
saveParams
.
filePath
=
null
;
}
let
simpleFormInline
=
dataSimpleFormRef
.
value
.
formInline
;
if
(
simpleFormInline
.
enableSamplingRate
==
'Y'
)
{
saveParams
.
samplingRate
=
simpleFormInline
.
samplingRate
&&
parseInt
(
simpleFormInline
.
samplingRate
);
}
else
{
saveParams
.
samplingRate
=
null
;
}
let
privacy
=
configInfo
.
anonPrivacyMode
;
delete
privacy
.
isKaNumber
;
delete
privacy
.
isRiskThreshold
;
delete
privacy
.
isTcField
;
delete
privacy
.
isLdField
;
// 为空时为了跟原始值保持一致
privacy
.
kaNumber
=
(
privacy
.
kaNumber
&&
parseInt
(
privacy
.
kaNumber
))
??
null
;
privacy
.
riskThreshold
=
(
privacy
.
riskThreshold
&&
parseFloat
(
privacy
.
riskThreshold
))
??
null
;
privacy
.
tcFieldName
=
privacy
.
tcFieldName
??
null
;
privacy
.
tcThreshold
=
(
privacy
.
tcThreshold
&&
parseFloat
(
privacy
.
tcThreshold
))
??
null
;
privacy
.
ldFieldName
=
privacy
.
ldFieldName
??
null
;
privacy
.
ldNumber
=
(
privacy
.
ldNumber
&&
parseInt
(
privacy
.
ldNumber
))
??
null
;
Object
.
assign
(
saveParams
,
configInfo
);
if
(
taskGuid
.
value
)
{
saveParams
.
guid
=
taskGuid
.
value
;
}
if
(
isEqual
(
saveParams
,
oldAnonTaskValueInfo
.
value
))
{
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
return
;
}
if
(
!
taskGuid
.
value
)
{
//保存
fullscreenLoading
.
value
=
true
;
saveAnonTask
(
saveParams
).
then
((
res
:
any
)
=>
{
fullscreenLoading
.
value
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
taskGuid
.
value
=
res
.
data
?.
guid
;
isExecEnd
.
value
=
false
;
taskExecGuid
.
value
=
res
.
data
?.
lastExecGuid
;
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
oldAnonTaskValueInfo
.
value
=
saveParams
;
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
}
else
{
//更新
fullscreenLoading
.
value
=
true
;
updateAnonTask
(
saveParams
).
then
((
res
:
any
)
=>
{
fullscreenLoading
.
value
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
isExecEnd
.
value
=
false
;
taskExecGuid
.
value
=
res
.
data
;
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
oldAnonTaskValueInfo
.
value
=
saveParams
;
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
})
}
}
else
if
(
val
==
4
)
{
//下一步之后,调用分析结果。
getAnonAnalyzeResult
(
detailInfo
.
value
.
lastExecGuid
).
then
((
res
:
any
)
=>
{
debugger
});
getAnonAnalyzePageData
({
pageSize
:
-
1
}).
then
((
res
:
any
)
=>
{
debugger
});
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
}
else
if
(
val
<=
step
.
value
)
{
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
}
}
const
promise
:
any
=
ref
(
null
);
const
exportResult
=
()
=>
{
promise
.
value
=
exportAnonExecData
({
taskGuid
:
route
.
query
.
guid
,
execGuid
:
route
.
query
.
execGuid
}).
then
((
res
:
any
)
=>
{
promise
.
value
=
null
;
if
(
res
&&
!
res
.
msg
)
{
download
(
res
,
route
.
query
.
taskName
+
'_匿名化数据.xlsx'
,
'excel'
)
}
else
{
res
?.
msg
&&
ElMessage
.
error
(
res
?.
msg
);
}
}).
catch
(()
=>
{
promise
.
value
=
null
;
})
}
/** 获取字段类型的数据字典 */
const
fieldTypeList
:
any
=
ref
([]);
/** 编辑时获取的匿名化任务的详情信息 */
const
detailInfo
:
any
=
ref
({});
/** 记录原始的值信息,防止上一步之后未修改数据时不调用接口 */
const
oldAnonTaskValueInfo
:
any
=
ref
({});
onBeforeMount
(()
=>
{
if
(
taskGuid
.
value
)
{
fullscreenLoading
.
value
=
true
;
getAnonTaskDetail
(
taskGuid
.
value
).
then
(
async
(
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
detailInfo
.
value
=
res
.
data
||
{};
oldAnonTaskValueInfo
.
value
=
{
guid
:
detailInfo
.
value
.
guid
,
taskName
:
detailInfo
.
value
.
taskName
,
dataSource
:
detailInfo
.
value
.
dataSource
,
filePath
:
detailInfo
.
value
.
filePath
&&
cloneDeep
(
detailInfo
.
value
.
filePath
),
dataSourceGuid
:
detailInfo
.
value
.
dataSourceGuid
,
tableName
:
detailInfo
.
value
.
tableName
,
samplingRate
:
detailInfo
.
value
.
samplingRate
,
patientPopulationRate
:
detailInfo
.
value
.
patientPopulationRate
&&
typeof
detailInfo
.
value
.
patientPopulationRate
==
'number'
?
detailInfo
.
value
.
patientPopulationRate
?.
toFixed
(
9
)
:
detailInfo
.
value
.
patientPopulationRate
,
dataSharingTypeCode
:
detailInfo
.
value
.
dataSharingTypeCode
,
anonTaskRules
:
cloneDeep
(
detailInfo
.
value
.
anonTaskRules
),
anonPrivacyMode
:
{
kaNumber
:
detailInfo
.
value
.
anonPrivacyMode
?.
kaNumber
,
ldFieldName
:
detailInfo
.
value
.
anonPrivacyMode
?.
ldFieldName
,
ldNumber
:
detailInfo
.
value
.
anonPrivacyMode
?.
ldNumber
,
riskThreshold
:
detailInfo
.
value
.
anonPrivacyMode
?.
riskThreshold
,
tcFieldName
:
detailInfo
.
value
.
anonPrivacyMode
?.
tcFieldName
,
tcThreshold
:
detailInfo
.
value
.
anonPrivacyMode
?.
tcThreshold
,
}
}
dataSelectInfoItems
.
value
.
forEach
(
d
=>
{
d
.
default
=
detailInfo
.
value
[
d
.
field
];
if
(
d
.
field
==
'file'
)
{
d
.
default
=
detailInfo
.
value
.
filePath
?
[
detailInfo
.
value
.
filePath
]
:
[];
}
else
if
(
d
.
field
==
'patientPopulationRate'
)
{
if
(
d
.
default
&&
typeof
d
.
default
==
'number'
)
{
d
.
default
=
d
.
default
.
toFixed
(
d
.
decimalCnt
);
}
}
});
if
(
detailInfo
.
value
.
samplingRate
!=
null
)
{
dataSimpleFormItems
.
value
[
0
].
default
=
'Y'
;
dataSimpleFormItems
.
value
[
1
].
visible
=
true
;
dataSimpleFormItems
.
value
[
1
].
default
=
detailInfo
.
value
.
samplingRate
;
}
else
{
dataSimpleFormItems
.
value
[
0
].
default
=
'N'
;
dataSimpleFormItems
.
value
[
1
].
visible
=
false
;
}
let
dataSource
=
detailInfo
.
value
.
dataSource
;
dataSelectInfoItems
.
value
[
4
].
visible
=
dataSource
==
1
;
dataSelectInfoItems
.
value
[
5
].
visible
=
dataSource
==
1
;
dataSelectInfoItems
.
value
[
6
].
visible
=
dataSource
==
2
;
//文件解析
if
(
dataSource
==
2
)
{
let
url
=
detailInfo
.
value
.
filePath
?.
url
;
sampleTableDataLoading
.
value
=
true
;
const
refSignInfo
:
any
=
await
getDownFileSignByUrl
(
parseAndDecodeUrl
(
url
).
fileName
);
if
(
!
refSignInfo
?.
data
)
{
fullscreenLoading
.
value
=
false
;
refSignInfo
?.
msg
&&
ElMessage
.
error
(
refSignInfo
?.
msg
);
return
;
}
obsDownloadRequest
(
refSignInfo
?.
data
).
then
((
res
:
any
)
=>
{
sampleTableDataLoading
.
value
=
false
;
if
(
res
&&
!
res
.
msg
)
{
parseFileData
(
res
);
}
else
{
res
?.
msg
&&
ElMessage
.
error
(
res
?.
msg
);
}
})
}
else
{
const
res
:
any
=
await
getDatabase
({
connectStatus
:
1
});
if
(
res
?.
code
==
proxy
.
$passCode
)
{
dataSourceList
.
value
=
res
.
data
||
[];
let
item
=
dataSelectInfoItems
.
value
.
find
(
item
=>
item
.
field
==
'dataSourceGuid'
);
item
&&
(
item
.
options
=
dataSourceList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
currDatasourceSelect
.
value
=
dataSourceList
.
value
.
find
(
d
=>
d
.
guid
==
detailInfo
.
value
.
dataSourceGuid
);
const
tableRes
:
any
=
await
getDsTableByDs
({
pageSize
:
-
1
,
pageIndex
:
1
,
dataSourceGuid
:
detailInfo
.
value
.
dataSourceGuid
,
database
:
currDatasourceSelect
.
value
.
databaseNameEn
,
databaseType
:
currDatasourceSelect
.
value
.
databaseType
,
tableName
:
''
,
hadFlag
:
false
});
if
(
tableRes
?.
code
==
proxy
.
$passCode
)
{
dsTableList
.
value
=
tableRes
.
data
?.
records
||
[];
let
item
=
dataSelectInfoItems
.
value
.
find
(
item
=>
item
.
field
==
'tableName'
);
item
&&
(
item
.
options
=
dsTableList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
tableRes
.
msg
);
}
getDsTableFieldColumn
({
pageSize
:
50
,
pageIndex
:
1
,
dataSourceGuid
:
currDatasourceSelect
.
value
.
guid
,
database
:
currDatasourceSelect
.
value
.
databaseNameEn
,
databaseType
:
currDatasourceSelect
.
value
.
databaseType
,
tableName
:
detailInfo
.
value
.
tableName
,
}).
then
((
res
:
any
)
=>
{
if
(
res
.
code
==
proxy
.
$passCode
)
{
sampleTableFields
.
value
=
res
.
data
?.
map
(
d
=>
{
d
.
fieldDataType
=
d
.
dataType
;
d
.
enName
=
d
.
columnName
;
d
.
chName
=
d
.
columnZhName
;
return
d
;
})
||
[];
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
/** 判断有抽样数据,需要查询接口 */
getSampleDataByDsTable
();
}
fullscreenLoading
.
value
=
false
;
}
else
{
fullscreenLoading
.
value
=
false
;
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
}
else
{
getDatabase
({
connectStatus
:
1
}).
then
((
res
:
any
)
=>
{
if
(
res
.
code
==
proxy
.
$passCode
)
{
dataSourceList
.
value
=
res
.
data
||
[];
let
item
=
dataSelectInfoItems
.
value
.
find
(
item
=>
item
.
field
==
'dataSourceGuid'
);
item
&&
(
item
.
options
=
dataSourceList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
})
}
getParamsList
({
dictType
:
"数据共享类型"
,
}).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
dataSharingTypeList
.
value
=
res
.
data
||
[];
let
item
=
dataSelectInfoItems
.
value
.
find
(
item
=>
item
.
field
==
'dataSharingTypeCode'
);
item
&&
(
item
.
options
=
dataSharingTypeList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
getParamsList
({
dictType
:
"字段类型"
,
}).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
fieldTypeList
.
value
=
res
.
data
||
[];
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
})
const
cancelTask
=
()
=>
{
proxy
.
$openMessageBox
(
"当前页面尚未保存,确定放弃修改吗?"
,
()
=>
{
userStore
.
setTabbar
(
userStore
.
tabbar
.
filter
((
tab
:
any
)
=>
tab
.
fullPath
!==
fullPath
));
...
...
@@ -135,7 +850,50 @@ const cancelTask = () => {
</div>
<div
class=
"operator_panel_wrap"
v-show=
"step == 0"
>
<ContentWrap
id=
"id-baseInfo"
title=
"数据选择"
description=
""
style=
"margin-top: 8px;"
>
<Form
ref=
"formRef"
:itemList=
"dataSelectInfoItems"
:rules=
"dataSelectInfoFormRules"
formId=
"model-select-edit"
col=
"col3"
/>
<Form
ref=
"formRef"
:itemList=
"dataSelectInfoItems"
:rules=
"dataSelectInfoFormRules"
formId=
"model-select-edit"
col=
"col3 custom-form"
@
select-change=
"handleDataSelectFormSelectChange"
@
uploadFileChange=
"uploadFileChange"
/>
</ContentWrap>
<ContentWrap
id=
"id-previewData"
title=
"数据抽样预览"
description=
""
style=
"margin-top: 16px;"
>
<Form
ref=
"dataSimpleFormRef"
:itemList=
"dataSimpleFormItems"
:rules=
"dataSimpleFormRules"
formId=
"data-simple-edit"
col=
"col3 fixwidth-form"
@
switch-change=
"handleDataSimpleFormSwitchChange"
@
input-change=
"handleDataSimpleFormChange"
/>
<div
class=
"table-v2-main"
v-show=
"dataSimpleFormRef?.formInline?.enableSamplingRate == 'Y'"
v-loading=
"sampleTableDataLoading"
>
<el-table
ref=
"tableRef"
v-show=
"sampleTableFields.length"
:data=
"sampleTableData"
:highlight-current-row=
"true"
stripe
border
tooltip-effect=
"light"
height=
"100%"
row-key=
"guid"
:style=
"
{ width: '100%', height: '240px' }">
<el-table-column
label=
"序号"
type=
"index"
width=
"56px"
align=
"center"
show-overflow-tooltip
></el-table-column>
<template
v-for=
"(item, index) in (sampleTableFields || [])"
>
<el-table-column
:label=
"item.chName"
:width=
"item.dataType === 'datetime'
? TableColumnWidth.DATETIME
: item.dataType === 'date'
? TableColumnWidth.DATE
: originTableFieldColumn[item.enName]
"
:align=
"getTextAlign(item)"
:header-align=
"getTextAlign(item)"
:formatter=
"(row) => formatterPreviewDate(row, item)"
:show-overflow-tooltip=
"true"
>
</el-table-column>
</
template
>
</el-table>
<div
v-show=
"!sampleTableFields.length"
class=
"main-placeholder"
>
<img
src=
"../../assets/images/no-data.png"
:style=
"{ width: '96px', height: '96px' }"
/>
<div
class=
"empty-text"
>
暂无抽样数据
</div>
</div>
</div>
</ContentWrap>
</div>
<anonTaskStepTwo
ref=
"anonTaskStepTwoRef"
v-show=
"step == 1"
:anonTaskRules=
"detailInfo.anonTaskRules"
:isFile=
"formRef?.formInline?.file?.length > 0"
:anonPrivacyMode=
"detailInfo.anonPrivacyMode"
:fieldTypeList=
"fieldTypeList"
:fieldNameList=
"sampleTableFields"
>
</anonTaskStepTwo>
<div
class=
"operator_panel_wrap"
v-show=
"step == 2"
>
<ContentWrap
id=
"analysis-result"
title=
"匿名结果分析"
description=
""
style=
"margin-top: 8px;"
>
</ContentWrap>
</div>
<div
class=
"operator_panel_wrap"
v-show=
"step == 3"
>
<ContentWrap
id=
"analysis-result"
title=
"匿名化数据结果"
description=
""
style=
"margin-top: 8px;"
>
<anonResultView
:is-page=
"false"
:execGuid=
"isExecEnd ? taskExecGuid : ''"
></anonResultView>
</ContentWrap>
</div>
</div>
...
...
@@ -144,9 +902,16 @@ const cancelTask = () => {
<el-button
@
click=
"cancelTask"
>
取消
</el-button>
<el-button
type=
"primary"
@
click=
"changeStep(2)"
>
下一步
</el-button>
</
template
>
<
template
v-else-if=
"step == 1"
>
<el-button
@
click=
"changeStep(1)"
>
上一步
</el-button>
<el-button
type=
"primary"
@
click=
"changeStep(3)"
>
保存并下一步
</el-button>
</
template
>
<
template
v-else-if=
"step == 2"
>
<el-button
@
click=
"changeStep(2)"
>
上一步
</el-button>
<el-button
type=
"primary"
@
click=
"changeStep(4)"
>
下一步
</el-button>
</
template
>
<
template
v-else
>
<el-button
@
click=
"cancelTask"
>
取消
</el-button>
<el-button
type=
"primary"
@
click=
"changeStep(1)"
>
上一步
</el-button>
<el-button
type=
"primary"
@
click=
"changeStep(2)"
>
上一步
</el-button>
<el-button
type=
"primary"
v-preReClick
@
click=
"exportResult"
>
导出
</el-button>
</
template
>
</div>
...
...
@@ -181,4 +946,81 @@ const cancelTask = () => {
padding
:
0
16px
;
overflow
:
hidden
auto
;
}
.operator_panel_wrap
{
padding-bottom
:
12px
;
}
:deep
(
.custom-form
)
{
align-items
:
flex-start
;
.wid60.el-form-item
{
width
:
calc
(
66.66%
-
12px
);
}
}
:deep
(
.fixwidth-form
)
{
width
:
500px
;
.autoWidth.el-form-item
{
width
:
80px
;
}
}
.table-v2-main
{
width
:
100%
;
height
:
240px
;
.main-placeholder
{
height
:
100%
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
flex-direction
:
column
;
.empty-text
{
font-size
:
14px
;
color
:
#b2b2b2
;
}
}
}
:deep
(
.el-table-v2
)
{
.el-table-v2__main
{
border
:
1px
solid
#d9d9d9
;
background
:
#fff
;
}
.el-table-v2__body
tr
.hover-row.el-table-v2__row--striped.current-row
>
td
.el-table-v2__cell
{
background-color
:
var
(
--el-table-row-hover-bg-color
);
}
.el-table-v2__body
tr
.current-row
>
td
.el-table-v2__cell
{
background-color
:
var
(
--el-table-current-row-bg-color
);
}
.el-table-v2__header
{
width
:
100%
!important
;
}
.el-table-v2__header-cell
,
.el-table-v2__row-cell
{
border-right
:
1px
#d9d9d9
solid
;
}
.el-table-v2__header-cell-text
{
color
:
#000
;
font-weight
:
normal
;
}
.el-table-v2__empty
{
display
:
none
!important
;
}
.el-table-v2__header-row
{
border-bottom
:
1px
solid
#d9d9d9
;
}
}
</
style
>
\ No newline at end of file
...
...
src/views/data_anonymization/anonTaskStepTwo.vue
0 → 100644
View file @
371e8ad
<route
lang=
"yaml"
>
name: anonTaskStepTwo
</route>
<
script
lang=
"ts"
setup
name=
"anonTaskStepTwo"
>
import
{
TableColumnWidth
}
from
'@/utils/enum'
;
import
{
CirclePlus
,
Delete
}
from
'@element-plus/icons-vue'
;
import
{
getParamsList
,
getGeneralizeFileNameList
,
getLableByFieldName
,
validateAnonRule
,
}
from
'@/api/modules/dataAnonymization'
;
import
{
useValidator
}
from
'@/hooks/useValidator'
;
const
props
=
defineProps
({
fieldTypeList
:
{
default
:
[],
type
:
Array
<
any
>
},
isFile
:
{
default
:
false
,
type
:
Boolean
},
fieldNameList
:
{
default
:
[],
type
:
Array
<
any
>
},
anonTaskRules
:
{
default
:
[],
type
:
Array
<
any
>
},
anonPrivacyMode
:
{
default
:
{},
type
:
Object
}
})
const
{
required
}
=
useValidator
();
const
{
proxy
}
=
getCurrentInstance
()
as
any
;
const
drawerRef
=
ref
();
/** 泛化文件列表 */
const
generalizeFileNameList
:
any
=
ref
([]);
/** 脱敏规则字典列表 */
const
desensitiveRuleTypeList
:
any
=
ref
([]);
/** 标签类型字典列表 */
const
labelTypeList
:
any
=
ref
([]);
/** 加密算法字典列表 */
const
hashMethodList
=
ref
([]);
/** 当前正在编辑的表格索引 */
const
currTableRowIndex
:
any
=
ref
(
null
);
const
ruleModelTableInfo
=
ref
({
id
:
'rule-model-table'
,
loading
:
false
,
minHeight
:
'200px'
,
nodeKey
:
'guid'
,
height
:
'200px'
,
fields
:
[
{
label
:
"序号"
,
type
:
"index"
,
width
:
TableColumnWidth
.
INDEX
,
align
:
"center"
},
{
label
:
"字段中文名称"
,
field
:
"fieldChName"
,
width
:
150
},
{
label
:
"字段英文名称"
,
field
:
"fieldName"
,
width
:
150
},
{
label
:
"字段类型"
,
field
:
"fieldTypeName"
,
width
:
120
},
{
label
:
"数据类型"
,
field
:
"dataTypeName"
,
width
:
120
},
{
label
:
"脱敏方式"
,
field
:
"desensitiveRule"
,
width
:
120
,
getName
:
(
scope
)
=>
{
let
rule
=
scope
.
row
.
desensitiveRule
;
return
rule
?
rule
:
(
scope
.
row
.
generalizeFileGuid
?
'泛化'
:
'--'
);
}
},
],
data
:
<
any
>
[],
showPage
:
false
,
actionInfo
:
{
label
:
"操作"
,
type
:
"btn"
,
width
:
100
,
fixed
:
'right'
,
btns
:
[
{
label
:
"编辑"
,
value
:
"ruleEdit"
,
click
:
(
scope
)
=>
{
currTableRowIndex
.
value
=
scope
.
$index
;
drawerInfo
.
value
.
visible
=
true
;
drawerInfo
.
value
.
type
=
'edit'
;
drawerInfo
.
value
.
header
.
title
=
'编辑字段脱敏规则'
;
let
row
=
scope
.
row
;
fieldRulesFormItems
.
value
.
forEach
(
item
=>
{
item
.
default
=
row
[
item
.
field
];
if
(
item
.
field
==
'encryptionAlgorithmCode'
||
item
.
field
==
'salted'
)
{
item
.
visible
=
row
.
desensitiveRuleCode
==
'HASH'
;
}
else
if
(
item
.
field
==
'decimalPlaces'
)
{
item
.
visible
=
row
.
desensitiveRuleCode
==
'ROUNDING'
;
}
});
let
fieldNameList
=
props
.
fieldNameList
.
map
(
f
=>
{
if
(
f
.
enName
!=
row
.
fieldName
&&
ruleModelTableInfo
.
value
.
data
.
some
(
d
=>
d
.
fieldName
==
f
.
enName
))
{
f
.
disabled
=
true
;
}
else
{
f
.
disabled
=
false
;
}
return
f
;
});
fieldRulesFormItems
.
value
[
0
].
options
=
fieldNameList
;
fieldRulesFormInfo
.
value
.
formInfo
.
items
=
fieldRulesFormItems
.
value
;
drawerInfo
.
value
.
container
.
contents
[
0
]
=
fieldRulesFormInfo
.
value
;
desensitiveRuleDetail
.
value
=
{
dissembleType
:
1
,
/** 1从左往右,2.从右往左 */
ruleDetails
:
<
any
>
[{
digitType
:
1
,
}]
};
charReplaceRuleDetail
.
value
=
{
replaceType
:
1
,
/** 1从左往右,2.从右往左 */
ruleDetails
:
<
any
>
[{
digitType
:
1
,
ruleType
:
1
}]
};
rangeReplaceRuleDetails
.
value
=
[{
lowOperator
:
'≤'
,
fieldChName
:
row
.
fieldChName
,
upperOperator
:
'≤'
}];
if
(
row
.
desensitiveRuleCode
==
'DISSEMBLE'
)
{
desensitiveRuleDetail
.
value
=
row
.
desensitiveRuleDetail
||
{};
}
else
if
(
row
.
desensitiveRuleCode
==
'CHARREPLACE'
)
{
charReplaceRuleDetail
.
value
=
row
.
desensitiveRuleDetail
||
{};
}
else
if
(
row
.
desensitiveRuleCode
==
'RANGEREPLACE'
)
{
rangeReplaceRuleDetails
.
value
=
row
.
desensitiveRuleDetail
?.
ruleDetails
?.
map
(
rd
=>
{
rd
.
fieldChName
=
row
.
fieldChName
;
return
rd
;
})
||
[];
}
if
(
!
row
.
desensitiveRuleCode
)
{
drawerInfo
.
value
.
container
.
contents
=
[
fieldRulesFormInfo
.
value
];
}
else
{
drawerInfo
.
value
.
container
.
contents
=
[
fieldRulesFormInfo
.
value
,
fieldRulesEndFormInfo
.
value
];
}
}
},
{
label
:
"删除"
,
value
:
"delete"
,
click
:
(
scope
)
=>
{
proxy
.
$openMessageBox
(
"此操作将永久删除, 是否继续?"
,
()
=>
{
let
fieldName
=
scope
.
row
.
fieldName
;
ruleModelTableInfo
.
value
.
data
.
splice
(
scope
.
row
.
index
,
1
);
updatePrivacyFormFieldsOptions
(
fieldName
);
// 同步去掉隐私模型设置中的字段。
proxy
.
$ElMessage
.
success
(
'删除成功'
);
},
()
=>
{
proxy
.
$ElMessage
.
info
(
"已取消"
);
})
}
},
],
}
})
/** 字段脱敏规则表单配置 */
const
fieldRulesFormItems
=
ref
([{
label
:
'选择字段'
,
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'fieldName'
,
default
:
''
,
options
:
props
.
fieldNameList
,
props
:
{
label
:
'chName'
,
value
:
'enName'
,
disabled
:
'disabled'
},
filterable
:
true
,
clearable
:
false
,
required
:
true
},
{
label
:
'字段类型'
,
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'fieldTypeCode'
,
default
:
'varchar'
,
options
:
props
.
fieldTypeList
,
props
:
{
label
:
'label'
,
value
:
'value'
},
disabled
:
true
,
clearable
:
true
,
required
:
true
},
{
label
:
'数据类型'
,
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'dataTypeCode'
,
default
:
''
,
options
:
labelTypeList
.
value
,
props
:
{
label
:
'label'
,
value
:
'value'
},
filterable
:
true
,
clearable
:
true
,
required
:
false
},
{
label
:
'K匿名泛化'
,
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'generalizeFileGuid'
,
default
:
''
,
options
:
generalizeFileNameList
.
value
,
props
:
{
label
:
'generalizeFileName'
,
value
:
'guid'
},
filterable
:
true
,
clearable
:
true
,
required
:
false
},
{
label
:
'脱敏规则'
,
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'desensitiveRuleCode'
,
default
:
'DISSEMBLE'
,
options
:
desensitiveRuleTypeList
.
value
,
props
:
{
label
:
'label'
,
value
:
'value'
},
filterable
:
true
,
clearable
:
true
,
required
:
false
},
{
label
:
'加密算法'
,
type
:
'select'
,
placeholder
:
'请选择'
,
field
:
'encryptionAlgorithmCode'
,
default
:
''
,
options
:
hashMethodList
.
value
,
props
:
{
label
:
'label'
,
value
:
'value'
},
filterable
:
true
,
clearable
:
true
,
visible
:
false
,
required
:
true
},
{
label
:
'加盐值'
,
type
:
'input'
,
placeholder
:
'请输入0~9'
,
field
:
'salted'
,
maxlength
:
1
,
min
:
0
,
max
:
9
,
inputType
:
'integerNumber'
,
default
:
5
,
required
:
true
,
filterable
:
true
,
clearable
:
true
,
visible
:
false
,
},
{
label
:
'保留小数'
,
type
:
'input'
,
placeholder
:
'请输入0~5'
,
field
:
'decimalPlaces'
,
maxlength
:
1
,
min
:
0
,
max
:
5
,
inputType
:
'integerNumber'
,
default
:
2
,
required
:
true
,
filterable
:
true
,
clearable
:
true
,
visible
:
false
,
}]);
const
fieldRulesFormRules
=
ref
({
fieldName
:
[
required
(
'请选择字段'
)],
// dataTypeCode: [required('请选择数据类型')], //不填标识非敏感标识。
// desensitiveRuleCode: [required('请选择脱敏规则')], 脱敏规则和泛化文件选择一个即可。二者必选其一,可以两者共存。
encryptionAlgorithmCode
:
[
required
(
'请选择加密算法'
)],
salted
:
[
required
(
'请输入加盐值'
)],
decimalPlaces
:
[
required
(
'请输入保留小数'
)]
});
const
fieldRulesFormInfo
=
ref
({
type
:
"form"
,
title
:
""
,
col
:
"span"
,
formInfo
:
{
id
:
"add-class-form"
,
readonly
:
false
,
items
:
fieldRulesFormItems
.
value
,
rules
:
fieldRulesFormRules
.
value
,
},
});
const
fieldRulesEndFormInfo
=
ref
({
type
:
"form"
,
title
:
""
,
col
:
"mt8"
,
showSlot
:
false
,
formInfo
:
{
id
:
"add-rules-end-form"
,
readonly
:
false
,
items
:
[{
label
:
'样本数据'
,
type
:
'input'
,
placeholder
:
'请输入样本'
,
field
:
'testData'
,
default
:
''
,
required
:
false
,
clearable
:
true
,
block
:
true
,
col
:
'mb8'
,
validateBtn
:
{
value
:
'validate'
,
label
:
'验证'
,
click
:
()
=>
{
let
formInline
=
drawerRef
.
value
?.
getDrawerConRef
(
'drawerFormRef'
)?.
formInline
;
let
formInline1
=
drawerRef
.
value
?.
getDrawerConRef
(
'drawerFormRef'
,
1
)?.
formInline
;
if
(
!
formInline1
.
testData
)
{
proxy
.
$ElMessage
.
error
(
'样本数据不能为空'
);
return
;
}
validateAnonRule
({
desensitiveRuleCode
:
formInline
.
desensitiveRuleCode
,
value
:
formInline1
.
testData
,
desensitiveRuleDetail
:
getDesensitiveRuleDetailInfo
(
formInline
)
}).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
let
result
=
res
.
data
;
fieldRulesEndFormInfo
.
value
.
formInfo
.
items
[
0
].
default
=
formInline1
.
testData
;
fieldRulesEndFormInfo
.
value
.
formInfo
.
items
[
1
].
default
=
formInline
.
desensitiveRuleCode
==
'BLANK'
?
' '
:
result
;
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
})
}
}
},
{
label
:
'脱敏效果'
,
type
:
'input'
,
placeholder
:
'点击验证后展示脱敏效果'
,
field
:
'validateResult'
,
default
:
''
,
block
:
true
,
required
:
false
,
disabled
:
true
}],
rules
:
{},
},
});
/** 获取规则配置详情。 */
const
getDesensitiveRuleDetailInfo
=
(
formInline
)
=>
{
let
desensitiveRuleDetailInfo
:
any
=
{};
if
(
formInline
.
desensitiveRuleCode
==
'DISSEMBLE'
)
{
desensitiveRuleDetailInfo
=
desensitiveRuleDetail
.
value
;
}
else
if
(
formInline
.
desensitiveRuleCode
==
'CHARREPLACE'
)
{
desensitiveRuleDetailInfo
=
charReplaceRuleDetail
.
value
;
}
else
if
(
formInline
.
desensitiveRuleCode
==
'RANGEREPLACE'
)
{
desensitiveRuleDetailInfo
=
{
ruleDetails
:
rangeReplaceRuleDetails
.
value
};
}
else
if
(
formInline
.
desensitiveRuleCode
==
'ROUNDING'
)
{
desensitiveRuleDetailInfo
=
{
decimalPlaces
:
formInline
.
decimalPlaces
}
}
else
if
(
formInline
.
desensitiveRuleCode
==
'HASH'
)
{
desensitiveRuleDetailInfo
=
{
encryptionAlgorithmCode
:
formInline
.
encryptionAlgorithmCode
,
salted
:
formInline
.
salted
}
}
return
desensitiveRuleDetailInfo
;
}
const
drawerInfo
=
ref
({
visible
:
false
,
direction
:
'rtl'
,
size
:
540
,
header
:
{
title
:
'添加字段脱敏规则'
,
},
type
:
''
,
container
:
{
contents
:
[
fieldRulesFormInfo
.
value
,
fieldRulesEndFormInfo
.
value
],
},
footer
:
{
visible
:
true
,
btns
:
[
{
type
:
'default'
,
label
:
'取消'
,
value
:
'cancel'
},
{
type
:
'primary'
,
label
:
'确定'
,
value
:
'save'
,
loading
:
false
},
]
}
});
const
addRowRules
=
()
=>
{
drawerInfo
.
value
.
visible
=
true
;
drawerInfo
.
value
.
type
=
'add'
;
drawerInfo
.
value
.
header
.
title
=
'添加字段脱敏规则'
;
fieldRulesFormItems
.
value
.
forEach
(
item
=>
{
if
(
item
.
field
==
'fieldTypeCode'
)
{
item
.
default
=
'varchar'
;
}
else
if
(
item
.
field
==
'desensitiveRuleCode'
)
{
item
.
default
=
'DISSEMBLE'
;
}
else
if
(
item
.
field
==
'encryptionAlgorithmCode'
||
item
.
field
==
'salted'
)
{
item
.
visible
=
false
;
item
.
default
=
''
;
item
.
field
==
'salted'
&&
(
item
.
default
=
5
);
}
else
if
(
item
.
field
==
'decimalPlaces'
)
{
item
.
visible
=
false
;
item
.
default
=
2
;
}
else
{
item
.
default
=
''
;
}
});
let
fieldNameList
=
props
.
fieldNameList
.
map
(
f
=>
{
if
(
ruleModelTableInfo
.
value
.
data
.
some
(
d
=>
d
.
fieldName
==
f
.
enName
))
{
f
.
disabled
=
true
;
}
else
{
f
.
disabled
=
false
;
}
return
f
});
fieldRulesFormItems
.
value
[
0
].
options
=
fieldNameList
;
fieldRulesFormInfo
.
value
.
formInfo
.
items
=
fieldRulesFormItems
.
value
;
desensitiveRuleDetail
.
value
=
{
dissembleType
:
1
,
/** 1从左往右,2.从右往左 */
ruleDetails
:
<
any
>
[{
digitType
:
1
,
}]
};
charReplaceRuleDetail
.
value
=
{
replaceType
:
1
,
/** 1从左往右,2.从右往左 */
ruleDetails
:
<
any
>
[{
digitType
:
1
,
ruleType
:
1
}]
};
rangeReplaceRuleDetails
.
value
=
[{
lowOperator
:
'≤'
,
fieldChName
:
''
,
upperOperator
:
'≤'
}];
drawerInfo
.
value
.
container
.
contents
=
[
fieldRulesFormInfo
.
value
,
fieldRulesEndFormInfo
.
value
];
}
const
drawerBtnClick
=
async
(
btn
,
info
)
=>
{
if
(
btn
.
value
==
'cancel'
)
{
drawerInfo
.
value
.
visible
=
false
}
else
{
if
(
!
info
.
generalizeFileGuid
&&
!
info
.
desensitiveRuleCode
)
{
proxy
.
$ElMessage
.
error
(
'K匿名泛化与脱敏规则不能同时为空'
);
return
;
}
if
((
info
.
fieldTypeCode
==
'int'
||
info
.
fieldTypeCode
==
'decimal'
||
info
.
fieldTypeCode
==
'tinyint'
))
{
if
(
info
.
desensitiveRuleCode
==
'ROUNDING'
)
{
proxy
.
$ElMessage
.
error
(
'数值类型字段的脱敏规则不能设置取整'
);
return
;
}
if
(
info
.
desensitiveRuleCode
==
'RANGEREPLACE'
)
{
proxy
.
$ElMessage
.
error
(
'数值类型字段的脱敏规则不能设置区间替换'
);
return
;
}
}
drawerInfo
.
value
.
footer
.
btns
[
1
].
loading
=
true
;
let
desensitiveRuleDetailInfo
=
getDesensitiveRuleDetailInfo
(
info
);
// 脱敏规则为掩盖,字符,区间替换存在时需要调用接口检验
if
(
info
.
desensitiveRuleCode
==
'DISSEMBLE'
||
info
.
desensitiveRuleCode
==
'CHARREPLACE'
||
info
.
desensitiveRuleCode
==
'RANGEREPLACE'
)
{
let
res
:
any
=
await
validateAnonRule
({
desensitiveRuleCode
:
info
.
desensitiveRuleCode
,
value
:
''
,
desensitiveRuleDetail
:
desensitiveRuleDetailInfo
})
if
(
res
?.
code
!=
proxy
.
$passCode
)
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
}
drawerInfo
.
value
.
footer
.
btns
[
1
].
loading
=
false
;
let
saveData
:
any
=
{
...
info
};
saveData
.
desensitiveRuleDetail
=
desensitiveRuleDetailInfo
;
saveData
.
fieldChName
=
props
.
fieldNameList
?.
find
(
f
=>
f
.
enName
==
saveData
.
fieldName
)?.
chName
;
saveData
.
dataTypeName
=
saveData
.
dataTypeCode
&&
labelTypeList
.
value
.
find
(
l
=>
l
.
value
==
saveData
.
dataTypeCode
)?.
label
;
saveData
.
fieldTypeName
=
saveData
.
fieldTypeCode
&&
props
.
fieldTypeList
?.
find
(
f
=>
f
.
value
==
saveData
.
fieldTypeCode
)?.
label
;
saveData
.
desensitiveRule
=
info
.
desensitiveRuleCode
&&
desensitiveRuleTypeList
.
value
.
find
(
d
=>
d
.
value
==
info
.
desensitiveRuleCode
)?.
label
;
let
changeFields
=
''
;
if
(
drawerInfo
.
value
.
type
==
'add'
)
{
ruleModelTableInfo
.
value
.
data
.
push
(
saveData
);
}
else
if
(
drawerInfo
.
value
.
type
==
'edit'
)
{
if
(
!
currTableRowIndex
.
value
!=
null
)
{
let
originFieldName
=
ruleModelTableInfo
.
value
.
data
[
currTableRowIndex
.
value
]?.
fieldName
;
changeFields
=
originFieldName
==
saveData
.
fieldName
?
''
:
originFieldName
;
ruleModelTableInfo
.
value
.
data
[
currTableRowIndex
.
value
]
=
saveData
;
}
}
drawerInfo
.
value
.
visible
=
false
;
updatePrivacyFormFieldsOptions
(
changeFields
);
}
}
/** 更新下拉选择字段的下拉列表,若是字段被删除,需要同步去掉下拉选择值。 */
const
updatePrivacyFormFieldsOptions
=
(
changeFields
)
=>
{
let
optionsList
=
ruleModelTableInfo
.
value
.
data
?.
map
(
v
=>
{
return
{
fieldName
:
v
.
fieldName
,
fieldChName
:
v
.
fieldChName
}
})
||
[];
privacyFormItems
.
value
[
2
].
children
[
0
].
options
=
optionsList
;
privacyFormItems
.
value
[
3
].
children
[
0
].
options
=
optionsList
;
let
formInline
=
privacyFormRef
.
value
?.
formInline
;
privacyFormItems
.
value
.
forEach
((
item
,
index
)
=>
{
item
.
default
=
formInline
[
item
.
field
];
if
(
item
.
default
==
'Y'
)
{
item
.
children
?.
forEach
(
child
=>
{
child
.
default
=
formInline
[
child
.
field
];
if
(
changeFields
&&
(
child
.
field
==
'tcFieldName'
||
child
.
field
==
'ldFieldName'
)
&&
child
.
default
==
changeFields
)
{
child
.
default
=
''
}
});
}
})
}
/** 当前选择的字段对应的标签信息。 */
const
currLabelInfo
:
any
=
ref
({});
const
drawerSelectChange
=
(
val
,
row
,
info
)
=>
{
if
(
row
.
field
===
'desensitiveRuleCode'
)
{
let
methodItem
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'encryptionAlgorithmCode'
);
let
saltedItem
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'salted'
);
let
decimalPlaceItem
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'decimalPlaces'
);
methodItem
&&
(
methodItem
.
visible
=
val
==
'HASH'
);
saltedItem
&&
(
saltedItem
.
visible
=
val
==
'HASH'
);
decimalPlaceItem
&&
(
decimalPlaceItem
.
visible
=
val
==
'ROUNDING'
);
fieldRulesFormItems
.
value
.
forEach
(
item
=>
{
item
.
default
=
info
[
item
.
field
];
if
(
item
.
field
==
'decimalPlaces'
)
{
if
(
item
.
default
==
null
)
{
item
.
default
=
2
;
}
}
else
if
(
item
.
field
==
'salted'
)
{
if
(
item
.
default
==
null
)
{
item
.
default
=
5
;
}
}
});
if
(
!
val
)
{
drawerInfo
.
value
.
container
.
contents
=
[
fieldRulesFormInfo
.
value
];
}
else
{
drawerInfo
.
value
.
container
.
contents
=
[
fieldRulesFormInfo
.
value
,
fieldRulesEndFormInfo
.
value
];
}
}
// else if (row.field == 'fieldTypeCode') {
// fieldRulesFormItems.value.forEach(item => {
// item.default = info[item.field];
// if (item.field == 'desensitiveRuleCode') {
// if (!(val == 'int' || val == 'decimal' || val == 'tinyint')) {
// item.options = desensitiveRuleTypeList.value.filter(d => d.value != 'ROUNDING' && d.value != 'RANGEREPLACE');
// if (item.default == 'ROUNDING' || item.default == 'RANGEREPLACE') {
// item.default = 'DISSEMBLE';
// }
// } else {
// item.options = desensitiveRuleTypeList.value;
// }
// }
// });
// }
else
if
(
row
.
field
==
'fieldName'
)
{
//选择字段改变之后,调用接口。
let
tableField
=
props
.
fieldNameList
.
find
(
f
=>
f
.
enName
==
val
);
let
dataType
=
tableField
?.
dataType
||
'varchar'
;
fieldRulesFormItems
.
value
.
forEach
(
item
=>
{
item
.
default
=
info
[
item
.
field
];
if
(
item
.
field
==
'fieldTypeCode'
)
{
item
.
default
=
dataType
;
}
});
getLableByFieldName
(
val
).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
let
labelInfo
=
currLabelInfo
.
value
=
res
.
data
||
{};
fieldRulesFormItems
.
value
.
forEach
(
item
=>
{
item
.
default
=
info
[
item
.
field
];
if
(
item
.
field
==
'dataTypeCode'
)
{
item
.
default
=
labelInfo
.
labelTypeCode
;
}
});
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
})
if
(
info
.
desensitiveRuleCode
==
'RANGEREPLACE'
)
{
rangeReplaceRuleDetails
.
value
.
forEach
(
r
=>
{
r
.
fieldChName
=
tableField
?.
chName
||
val
;
});
}
}
}
const
drawerInputChange
=
(
val
,
row
,
info
)
=>
{
if
(
row
.
field
==
'testData'
)
{
//样本数据修改后,清空脱敏结果
fieldRulesEndFormInfo
.
value
.
formInfo
.
items
[
0
].
default
=
val
;
fieldRulesEndFormInfo
.
value
.
formInfo
.
items
[
1
].
default
=
''
;
}
}
/** 掩盖规则中的位数和剩余位数 */
const
digitTypeList
=
ref
([{
value
:
1
,
label
:
'位数'
},
{
value
:
2
,
label
:
'剩余位数'
}]);
const
dissembleRuleTypeList
=
ref
([{
value
:
1
,
label
:
'脱敏'
},
{
value
:
2
,
label
:
'不脱敏'
}]);
/** 掩盖类型的脱敏规则 分段规则配置 */
const
desensitiveRuleDetail
=
ref
({
dissembleType
:
1
,
/** 1从左往右,2.从右往左 */
ruleDetails
:
<
any
>
[{
digitType
:
1
,
}]
});
const
addSegmentRule
=
()
=>
{
desensitiveRuleDetail
.
value
.
ruleDetails
.
push
({
digitType
:
1
,
});
};
const
deleteSegmentRule
=
(
item
,
index
)
=>
{
desensitiveRuleDetail
.
value
.
ruleDetails
.
splice
(
index
,
1
);
}
const
inputEventDigitChange
=
(
val
,
item
,
prop
=
'digit'
)
=>
{
item
[
prop
]
=
item
[
prop
].
toString
().
replace
(
/
\.
/g
,
""
)
item
[
prop
]
=
item
[
prop
].
toString
().
replace
(
/^
\D
*
(\d{0,7}(?:\.\d{0})?)
.*$/g
,
"$1"
)
if
(
prop
==
'digit'
&&
val
!=
""
&&
item
.
digit
<
1
)
{
item
.
digit
=
1
;
}
}
/** 字符规则中的替换方式 */
const
charReplaceRuleTypeList
=
ref
([{
value
:
1
,
label
:
'随机替换'
},
{
value
:
2
,
label
:
'固定值替换'
}]);
/** 字符替换的脱敏规则 分段规则配置 */
const
charReplaceRuleDetail
=
ref
({
replaceType
:
1
,
/** 1从左往右,2.从右往左 */
ruleDetails
:
<
any
>
[{
digitType
:
1
,
ruleType
:
1
}]
});
const
addCharReplaceSegmentRule
=
()
=>
{
charReplaceRuleDetail
.
value
.
ruleDetails
.
push
({
digitType
:
1
,
ruleType
:
1
});
};
const
deleteCharReplaceSegmentRule
=
(
item
,
index
)
=>
{
charReplaceRuleDetail
.
value
.
ruleDetails
.
splice
(
index
,
1
);
}
/** ----- 区间替换规则 --- */
/** 区间替换的小于或小于等于字符列表 */
const
lowerOperatorList
:
any
=
ref
([{
value
:
'≤'
,
label
:
'≤'
},
{
value
:
'<'
,
label
:
'<'
}]);
/**
* 字符替换的脱敏规则 分段规则配置。
* { lowValue: , lowOperator: , upperValue: , upperOperator: , replaceValue: '' }
*/
const
rangeReplaceRuleDetails
:
any
=
ref
([{
lowOperator
:
'≤'
,
fieldChName
:
''
,
upperOperator
:
'≤'
}]);
const
addRangeReplaceSegmentRule
=
()
=>
{
let
fieldChName
=
''
;
if
(
rangeReplaceRuleDetails
.
value
?.
length
)
{
fieldChName
=
rangeReplaceRuleDetails
.
value
[
0
].
fieldChName
;
}
else
{
let
fieldName
=
drawerRef
.
value
?.
getDrawerConRef
(
'drawerFormRef'
)?.
formInline
?.
fieldName
;
let
tableField
=
fieldName
&&
props
.
fieldNameList
?.
find
(
f
=>
f
.
enName
==
fieldName
);
fieldChName
=
tableField
?.
chName
;
}
rangeReplaceRuleDetails
.
value
.
push
({
lowOperator
:
'≤'
,
fieldChName
:
fieldChName
,
upperOperator
:
'≤'
});
};
const
deleteRangeReplaceSegmentRule
=
(
item
,
index
)
=>
{
rangeReplaceRuleDetails
.
value
.
splice
(
index
,
1
);
}
/** 隐私模型设置 */
const
privacyFormItems
:
any
=
ref
([{
label
:
'选择准标识符等价类隐私模型'
,
type
:
'checkbox-input-item'
,
field
:
'isKaNumber'
,
default
:
'N'
,
placeholder
:
'K匿名'
,
trueValue
:
'Y'
,
falseValue
:
'N'
,
children
:
[
{
label
:
''
,
type
:
'input'
,
placeholder
:
'请输入'
,
field
:
'kaNumber'
,
inputType
:
'integerNumber'
,
default
:
''
,
min
:
1
,
maxlength
:
6
,
disabled
:
false
,
clearable
:
true
,
visible
:
false
,
col
:
'ka-checkbox-input'
,
style
:
{
width
:
'100px'
,
margin
:
0
}
}
],
required
:
true
,
col
:
'checkbox-input'
},
{
label
:
'设置重标识可接受风险阈值'
,
type
:
'checkbox-input-item'
,
field
:
'isRiskThreshold'
,
default
:
'N'
,
placeholder
:
'阈值'
,
trueValue
:
'Y'
,
falseValue
:
'N'
,
children
:
[
{
label
:
''
,
type
:
'input'
,
placeholder
:
'请输入'
,
field
:
'riskThreshold'
,
default
:
''
,
maxlength
:
6
,
min
:
0
,
max
:
1
,
inputType
:
'scoreNumber'
,
disabled
:
false
,
clearable
:
true
,
visible
:
false
,
col
:
'ka-checkbox-input'
,
style
:
{
width
:
'100px'
,
margin
:
0
}
}
],
required
:
true
,
col
:
'checkbox-input'
},
{
label
:
'设置L多样性及T接近'
,
type
:
'checkbox-input-item'
,
field
:
'isLdField'
,
default
:
'N'
,
placeholder
:
'L多样性'
,
trueValue
:
'Y'
,
falseValue
:
'N'
,
children
:
[
{
label
:
''
,
type
:
'select'
,
placeholder
:
'请选择字段'
,
field
:
'ldFieldName'
,
options
:
[],
props
:
{
label
:
'fieldChName'
,
value
:
'fieldName'
},
default
:
''
,
filterable
:
true
,
clearable
:
true
,
visible
:
false
,
col
:
'ka-checkbox-input'
,
style
:
{
width
:
'120px'
,
margin
:
0
}
},
{
label
:
''
,
type
:
'input'
,
placeholder
:
'请输入'
,
field
:
'ldNumber'
,
inputType
:
'integerNumber'
,
default
:
''
,
maxlength
:
6
,
min
:
1
,
disabled
:
false
,
clearable
:
true
,
visible
:
false
,
col
:
'ka-checkbox-input'
,
style
:
{
width
:
'100px'
,
margin
:
0
}
}
],
block
:
false
,
required
:
true
,
col
:
'checkbox-input'
},
{
label
:
''
,
type
:
'checkbox-input-item'
,
field
:
'isTcField'
,
default
:
'N'
,
placeholder
:
'T接近'
,
trueValue
:
'Y'
,
falseValue
:
'N'
,
children
:
[
{
label
:
''
,
type
:
'select'
,
placeholder
:
'请选择字段'
,
field
:
'tcFieldName'
,
default
:
''
,
options
:
[],
props
:
{
label
:
'fieldChName'
,
value
:
'fieldName'
},
filterable
:
true
,
clearable
:
true
,
visible
:
false
,
col
:
'ka-checkbox-input'
,
style
:
{
width
:
'120px'
,
margin
:
0
}
},
{
label
:
''
,
type
:
'input'
,
placeholder
:
'请输入'
,
field
:
'tcThreshold'
,
default
:
''
,
maxlength
:
6
,
min
:
0
,
max
:
1
,
inputType
:
'scoreNumber'
,
disabled
:
false
,
clearable
:
true
,
visible
:
false
,
col
:
'ka-checkbox-input'
,
style
:
{
width
:
'100px'
,
margin
:
0
}
}
],
required
:
false
,
block
:
true
,
col
:
'checkbox-input lmt12'
}]);
const
privacyFormRules
=
ref
({
kaNumber
:
[
required
(
'请输入K匿名值'
)],
riskThreshold
:
[
required
(
'请输入阈值'
)],
ldFieldName
:
[
required
(
'请选择L多样性字段'
)],
ldNumber
:
[
required
(
'请输入L多样性值'
)],
tcFieldName
:
[
required
(
'请选择T接近字段'
)],
tcThreshold
:
[
required
(
'请输入T接近阈值'
)]
});
/** 记录下旧的隐私模型设置 */
const
oldPrivacyModelValue
:
any
=
ref
({
isKaNumber
:
'N'
,
isRiskThreshold
:
'N'
,
isLdField
:
'N'
,
isTcField
:
'N'
});
const
handleCheckboxChange
=
(
val
,
value
,
row
)
=>
{
oldPrivacyModelValue
.
value
=
Object
.
assign
({},
oldPrivacyModelValue
.
value
,
value
);
privacyFormItems
.
value
.
forEach
(
item
=>
{
item
.
default
=
oldPrivacyModelValue
.
value
[
item
.
field
];
item
.
children
?.
forEach
(
child
=>
{
child
.
default
=
oldPrivacyModelValue
.
value
[
child
.
field
];
});
})
if
(
row
.
field
==
'isKaNumber'
)
{
let
kaItem
=
privacyFormItems
.
value
[
0
]?.
children
?.[
0
];
kaItem
&&
(
kaItem
.
visible
=
val
==
'Y'
);
}
else
if
(
row
.
field
==
'isRiskThreshold'
)
{
let
riskItem
=
privacyFormItems
.
value
[
1
]?.
children
?.[
0
];
riskItem
&&
(
riskItem
.
visible
=
val
==
'Y'
);
}
else
if
(
row
.
field
==
'isLdField'
)
{
let
childrenItem
=
privacyFormItems
.
value
[
2
]?.
children
;
childrenItem
?.[
0
]
&&
(
childrenItem
[
0
].
visible
=
val
==
"Y"
);
childrenItem
?.[
1
]
&&
(
childrenItem
[
1
].
visible
=
val
==
"Y"
);
}
else
if
(
row
.
field
==
'isTcField'
)
{
let
childrenItem
=
privacyFormItems
.
value
[
3
]?.
children
;
childrenItem
?.[
0
]
&&
(
childrenItem
[
0
].
visible
=
val
==
"Y"
);
childrenItem
?.[
1
]
&&
(
childrenItem
[
1
].
visible
=
val
==
"Y"
);
}
}
watch
(()
=>
props
.
fieldNameList
,
(
val
)
=>
{
fieldRulesFormItems
.
value
[
0
].
options
=
val
||
[];
if
(
props
.
isFile
)
{
fieldRulesFormItems
.
value
[
1
].
disabled
=
false
}
else
{
fieldRulesFormItems
.
value
[
1
].
disabled
=
true
;
}
},
{
immediate
:
true
})
watch
(()
=>
props
.
fieldTypeList
,
(
val
)
=>
{
fieldRulesFormItems
.
value
[
1
].
options
=
val
||
[];
},
{
immediate
:
true
})
watch
(()
=>
props
.
anonPrivacyMode
,
(
val
)
=>
{
if
(
!
val
)
{
return
;
}
let
hasKaNumber
=
val
.
kaNumber
!=
null
;
privacyFormItems
.
value
[
0
].
default
=
hasKaNumber
?
'Y'
:
'N'
;
privacyFormItems
.
value
[
0
].
children
[
0
].
visible
=
hasKaNumber
;
privacyFormItems
.
value
[
0
].
children
[
0
].
default
=
val
.
kaNumber
;
oldPrivacyModelValue
.
value
.
isKaNumber
=
hasKaNumber
?
'Y'
:
'N'
;
oldPrivacyModelValue
.
value
.
kaNumber
=
val
.
kaNumber
;
let
hasRiskThreshold
=
val
.
riskThreshold
!=
null
;
privacyFormItems
.
value
[
1
].
default
=
hasRiskThreshold
?
'Y'
:
'N'
;
privacyFormItems
.
value
[
1
].
children
[
0
].
visible
=
hasRiskThreshold
;
privacyFormItems
.
value
[
1
].
children
[
0
].
default
=
val
.
riskThreshold
;
oldPrivacyModelValue
.
value
.
isRiskThreshold
=
hasRiskThreshold
?
'Y'
:
'N'
;
oldPrivacyModelValue
.
value
.
riskThreshold
=
val
.
riskThreshold
;
let
hasldFieldName
=
!!
val
.
ldFieldName
;
privacyFormItems
.
value
[
2
].
default
=
hasldFieldName
?
'Y'
:
'N'
;
privacyFormItems
.
value
[
2
].
children
[
0
].
visible
=
hasldFieldName
;
privacyFormItems
.
value
[
2
].
children
[
1
].
visible
=
hasldFieldName
;
privacyFormItems
.
value
[
2
].
children
[
0
].
default
=
val
.
ldFieldName
;
privacyFormItems
.
value
[
2
].
children
[
1
].
default
=
val
.
ldNumber
;
oldPrivacyModelValue
.
value
.
isLdField
=
hasldFieldName
?
'Y'
:
'N'
;
oldPrivacyModelValue
.
value
.
ldFieldName
=
val
.
ldFieldName
;
oldPrivacyModelValue
.
value
.
ldNumber
=
val
.
ldNumber
;
let
hasTcField
=
!!
val
.
tcFieldName
;
privacyFormItems
.
value
[
3
].
default
=
hasTcField
?
'Y'
:
'N'
;
privacyFormItems
.
value
[
3
].
children
[
0
].
visible
=
hasTcField
;
privacyFormItems
.
value
[
3
].
children
[
1
].
visible
=
hasTcField
;
privacyFormItems
.
value
[
3
].
children
[
0
].
default
=
val
.
tcFieldName
;
privacyFormItems
.
value
[
3
].
children
[
1
].
default
=
val
.
tcThreshold
;
oldPrivacyModelValue
.
value
.
isTcField
=
hasTcField
?
'Y'
:
'N'
;
oldPrivacyModelValue
.
value
.
tcFieldName
=
val
.
tcFieldName
;
oldPrivacyModelValue
.
value
.
tcThreshold
=
val
.
tcThreshold
;
},
{
deep
:
true
})
watch
(()
=>
props
.
anonTaskRules
,
(
val
)
=>
{
ruleModelTableInfo
.
value
.
data
=
val
||
[];
let
optionsList
=
val
?.
map
(
v
=>
{
return
{
fieldName
:
v
.
fieldName
,
fieldChName
:
v
.
fieldChName
}
})
||
[];
privacyFormItems
.
value
[
2
].
children
[
0
].
options
=
optionsList
;
privacyFormItems
.
value
[
3
].
children
[
0
].
options
=
optionsList
;
})
onBeforeMount
(()
=>
{
getParamsList
({
dictType
:
"标签类型"
,
}).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
labelTypeList
.
value
=
res
.
data
||
[];
let
item
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'dataTypeCode'
);
item
&&
(
item
.
options
=
labelTypeList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
getParamsList
({
dictType
:
"脱敏规则"
,
}).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
desensitiveRuleTypeList
.
value
=
res
.
data
||
[];
let
item
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'desensitiveRuleCode'
);
item
&&
(
item
.
options
=
desensitiveRuleTypeList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
getParamsList
({
dictType
:
"加密算法"
,
}).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
hashMethodList
.
value
=
res
.
data
||
[];
let
item
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'encryptionAlgorithmCode'
);
item
&&
(
item
.
options
=
hashMethodList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
getGeneralizeFileNameList
().
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
generalizeFileNameList
.
value
=
res
.
data
||
[];
let
item
=
fieldRulesFormItems
.
value
.
find
(
item
=>
item
.
field
==
'generalizeFileGuid'
);
item
&&
(
item
.
options
=
generalizeFileNameList
.
value
);
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
});
})
/** 隐私模型设置表单 */
const
privacyFormRef
=
ref
();
const
getStepTwoConfigInfo
=
async
()
=>
{
if
(
!
ruleModelTableInfo
.
value
.
data
?.
length
)
{
proxy
.
$ElMessage
.
error
(
'字段脱敏规则不能为空'
);
return
false
;
}
try
{
await
privacyFormRef
.
value
?.
ruleFormRef
?.
validate
();
// 验证通过
return
{
anonPrivacyMode
:
privacyFormRef
.
value
?.
formInline
,
anonTaskRules
:
ruleModelTableInfo
.
value
.
data
};
}
catch
(
error
)
{
// 验证失败
return
false
;
}
}
defineExpose
({
getStepTwoConfigInfo
})
</
script
>
<
template
>
<div
class=
"operator_panel_wrap"
>
<ContentWrap
id=
"id-rules"
title=
"设置匿名化规则"
description=
""
style=
"margin-top: 8px;"
>
<Table
:tableInfo=
"ruleModelTableInfo"
/>
<div
class=
"row-add-btn"
>
<el-button
link
@
click=
"addRowRules"
:icon=
"CirclePlus"
v-preReClick
>
添加字段脱敏规则
</el-button>
</div>
</ContentWrap>
<ContentWrap
id=
"id-screctModel"
title=
"隐私模型设置"
description=
""
style=
"margin-top: 16px;"
>
<Form
style=
"width: 80%;max-width: 500px;"
ref=
"privacyFormRef"
:itemList=
"privacyFormItems"
:rules=
"privacyFormRules"
formId=
"model-select-edit"
@
checkboxChange=
"handleCheckboxChange"
/>
</ContentWrap>
<Drawer
ref=
"drawerRef"
:drawerInfo=
"drawerInfo"
@
drawerBtnClick=
"drawerBtnClick"
@
drawerInputChange=
"drawerInputChange"
@
drawerSelectChange=
"drawerSelectChange"
>
<template
v-slot:default
>
<div
v-show=
"drawerRef?.getDrawerConRef('drawerFormRef')?.formInline?.desensitiveRuleCode == 'DISSEMBLE'"
>
<div>
{{
'请配置分段是否脱敏'
+
`(${desensitiveRuleDetail.ruleDetails?.length
}
/10)`
}}
<
/div
>
<
el
-
radio
-
group
v
-
model
=
"desensitiveRuleDetail.dissembleType"
>
<
el
-
radio
:
value
=
"1"
>
从左往右
<
/el-radio
>
<
el
-
radio
:
value
=
"2"
>
从右往左
<
/el-radio
>
<
/el-radio-group
>
<
div
class
=
"seg-main"
>
<
div
class
=
"row-per"
v
-
for
=
"(item, index) in desensitiveRuleDetail.ruleDetails"
>
<
el
-
select
v
-
model
=
"item.digitType"
:
style
=
"
{
width
:
item
.
digitType
==
2
?
'322px'
:
'170px'
}
">
<el-option v-for="
item
in
digitTypeList
" :label="
item
.
label
" :value="
item
.
value
"
:key="
item
.
value
"></el-option>
</el-select>
<el-input v-show="
item
.
digitType
!=
2
" style="
width
:
137
px
;
margin
-
left
:
4
px
;
" v-model="
item
.
digit
"
:maxlength="
6
" :min="
1
" @input="
(
val
)
=>
inputEventDigitChange
(
val
,
item
)
" placeholder="
请输入
"></el-input>
<el-select v-model="
item
.
ruleType
" style="
width
:
170
px
;
margin
-
left
:
4
px
;
">
<el-option v-for="
item
in
dissembleRuleTypeList
" :label="
item
.
label
" :value="
item
.
value
"
:key="
item
.
value
"></el-option>
</el-select>
<div class="
title_tool
" @click="
deleteSegmentRule
(
item
,
index
)
">
<el-icon :size="
20
" color="
#
b2b2b2
">
<Delete />
</el-icon>
</div>
</div>
<div class="
row
-
add
-
btn
">
<el-button :disabled="
desensitiveRuleDetail
.
ruleDetails
?.
length
>
9
" link @click="
addSegmentRule
"
:icon="
CirclePlus
" v-preReClick>添加分段规则</el-button>
</div>
</div>
</div>
<div v-show="
drawerRef
?.
getDrawerConRef
(
'drawerFormRef'
)?.
formInline
?.
desensitiveRuleCode
==
'CHARREPLACE'
"
>
<
div
>
{{
'请配置分段的替换方式'
+
`(${charReplaceRuleDetail.ruleDetails?.length
}
/10)`
}}
<
/div
>
<
el
-
radio
-
group
v
-
model
=
"charReplaceRuleDetail.replaceType"
>
<
el
-
radio
:
value
=
"1"
>
从左往右
<
/el-radio
>
<
el
-
radio
:
value
=
"2"
>
从右往左
<
/el-radio
>
<
/el-radio-group
>
<
div
class
=
"seg-main"
>
<
div
class
=
"row-per"
v
-
for
=
"(item, index) in charReplaceRuleDetail.ruleDetails"
>
<
el
-
select
v
-
model
=
"item.digitType"
:
style
=
"
{
width
:
item
.
digitType
==
2
?
'220px'
:
'130px'
}
">
<el-option v-for="
item
in
digitTypeList
" :label="
item
.
label
" :value="
item
.
value
"
:key="
item
.
value
"></el-option>
</el-select>
<el-input v-show="
item
.
digitType
!=
2
" style="
width
:
86
px
;
margin
-
left
:
4
px
;
" v-model="
item
.
digit
"
:maxlength="
6
" :min="
1
" @input="
(
val
)
=>
inputEventDigitChange
(
val
,
item
)
" placeholder="
请输入
"></el-input>
<el-select v-model="
item
.
ruleType
"
:style="
{
width
:
item
.
ruleType
==
1
?
'244px'
:
'130px'
,
'margin-left'
:
'4px'
}
">
<el-option v-for="
item
in
charReplaceRuleTypeList
" :label="
item
.
label
" :value="
item
.
value
"
:key="
item
.
value
"></el-option>
</el-select>
<el-input v-model="
item
.
fixedValue
" v-show="
item
.
ruleType
==
2
" style="
width
:
110
px
;
margin
-
left
:
4
px
;
"
:maxlength="
50
" placeholder="
请输入
"></el-input>
<div class="
title_tool
" @click="
deleteCharReplaceSegmentRule
(
item
,
index
)
">
<el-icon :size="
20
" color="
#
b2b2b2
">
<Delete />
</el-icon>
</div>
</div>
<div class="
row
-
add
-
btn
">
<el-button :disabled="
charReplaceRuleDetail
.
ruleDetails
?.
length
>
9
" link
@click="
addCharReplaceSegmentRule
" :icon="
CirclePlus
" v-preReClick>添加分段规则</el-button>
</div>
</div>
</div>
<div v-show="
drawerRef
?.
getDrawerConRef
(
'drawerFormRef'
)?.
formInline
?.
desensitiveRuleCode
==
'RANGEREPLACE'
">
<div class="
mb8
"
>
{{
'请配置区间替换规则'
+
`(${rangeReplaceRuleDetails?.length
}
/10)`
}}
<
/div
>
<
div
class
=
"seg-main"
>
<
div
class
=
"row-per"
v
-
for
=
"(item, index) in rangeReplaceRuleDetails"
>
<
el
-
input
style
=
"width:16.5%"
v
-
model
=
"item.lowValue"
:
maxlength
=
"6"
@
input
=
"(val) => inputEventDigitChange(val, item, 'lowValue')"
placeholder
=
"请输入"
><
/el-input
>
<
el
-
select
v
-
model
=
"item.lowOperator"
style
=
"width: 16.5%;margin-left: 4px;"
>
<
el
-
option
v
-
for
=
"item in lowerOperatorList"
:
label
=
"item.label"
:
value
=
"item.value"
:
key
=
"item.value"
><
/el-option
>
<
/el-select
>
<
el
-
input
style
=
"width: 16.5%;margin-left: 4px;"
:
disabled
=
"true"
v
-
model
=
"item.fieldChName"
><
/el-input
>
<
el
-
select
v
-
model
=
"item.upperOperator"
style
=
"width: 16.5%;margin-left: 4px;"
>
<
el
-
option
v
-
for
=
"item in lowerOperatorList"
:
label
=
"item.label"
:
value
=
"item.value"
:
key
=
"item.value"
><
/el-option
>
<
/el-select
>
<
el
-
input
style
=
"width:16.5%;margin-left: 4px;"
v
-
model
=
"item.upperValue"
:
maxlength
=
"6"
@
input
=
"(val) => inputEventDigitChange(val, item, 'upperValue')"
placeholder
=
"请输入"
><
/el-input
>
<
el
-
input
v
-
model
=
"item.replaceValue"
style
=
"width:16.5%;margin-left: 4px;"
:
maxlength
=
"50"
placeholder
=
"替换值"
><
/el-input
>
<
div
class
=
"title_tool"
@
click
=
"deleteRangeReplaceSegmentRule(item, index)"
>
<
el
-
icon
:
size
=
"20"
color
=
"#b2b2b2"
>
<
Delete
/>
<
/el-icon
>
<
/div
>
<
/div
>
<
div
class
=
"row-add-btn"
>
<
el
-
button
:
disabled
=
"charReplaceRuleDetail.ruleDetails?.length > 9"
link
@
click
=
"addRangeReplaceSegmentRule"
:
icon
=
"CirclePlus"
v
-
preReClick
>
添加分段规则
<
/el-button
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/Drawer
>
<
/div
>
<
/template
>
<
style
lang
=
"scss"
scoped
>
.
row
-
add
-
btn
{
.
el
-
button
--
default
{
padding
:
4
px
0
px
;
}
:
deep
(.
el
-
icon
)
{
width
:
16
px
;
height
:
16
px
;
svg
{
width
:
16
px
;
height
:
16
px
;
}
}
}
.
seg
-
main
{
.
row
-
add
-
btn
{
margin
-
top
:
-
6
px
;
}
}
.
row
-
per
{
display
:
flex
;
align
-
items
:
center
;
margin
-
bottom
:
8
px
;
position
:
relative
;
.
title_tool
{
margin
-
left
:
4
px
;
// position: absolute;
// right: 4px;
cursor
:
pointer
;
:
deep
(.
el
-
icon
)
{
--
color
:
#
FB2323
!
important
;
svg
{
width
:
16
px
;
height
:
16
px
;
}
}
}
}
:
deep
(.
mt8
.
drawer_panel
)
{
margin
-
top
:
8
px
;
}
.
mb8
{
margin
-
bottom
:
8
px
;
}
:
deep
(.
el
-
form
-
item
.
ka
-
checkbox
-
input
)
{
.
el
-
input
{
min
-
width
:
50
px
!
important
;
}
}
:
deep
(.
el
-
form
)
{
.
lmt12
{
// margin-top: -4px; 验证信息会被遮挡
.
input_panel
{
flex
:
0
!
important
;
}
}
}
<
/style>
\ No newline at end of file
src/views/data_anonymization/generalizeFile.vue
View file @
371e8ad
...
...
@@ -95,6 +95,8 @@ const tableInfo = ref({
});
}
});
},
()
=>
{
proxy
.
$ElMessage
.
info
(
"已取消"
);
})
}
}]
...
...
src/views/data_anonymization/resultProcess.vue
View file @
371e8ad
...
...
@@ -72,18 +72,24 @@ const tableInfo = ref({
btns
:
(
scope
)
=>
{
return
[
{
label
:
"编辑"
,
value
:
"edit"
,
disabled
:
scope
.
row
.
status
==
'R'
,
click
:
(
scope
)
=>
{
router
.
push
({
name
:
'anonTaskCreate'
,
query
:
{
guid
:
scope
.
row
.
guid
,
taskName
:
scope
.
row
.
taskName
}
});
}
},
{
label
:
'查看数据'
,
value
:
'view'
,
disabled
:
scope
.
row
.
status
!=
'Y'
,
click
:
(
scope
)
=>
{
//
router.push({
// name: 'sensitiveIdentifyConfig
',
//
query: {
//
guid: scope.row.guid,
// execGuid: scope.row.e
xecGuid,
//
taskName: scope.row.taskName
//
}
//
});
router
.
push
({
name
:
'anonResultView
'
,
query
:
{
guid
:
scope
.
row
.
guid
,
execGuid
:
scope
.
row
.
lastE
xecGuid
,
taskName
:
scope
.
row
.
taskName
}
});
}
},
{
label
:
"删除"
,
value
:
"delete"
,
disabled
:
scope
.
row
.
status
==
'R'
,
click
:
(
scope
)
=>
{
...
...
src/views/data_quality/assessTemplate.vue
View file @
371e8ad
...
...
@@ -983,14 +983,14 @@ const btnFormClick = (btn, type) => {
modelsDialogVisible
.
value
=
true
;
if
(
!
databaseList
.
value
?.
length
)
{
getDataSourceListData
().
then
(()
=>
{
if
(
databaseInfo
.
value
==
databaseList
.
value
[
0
]?.
guid
??
""
)
{
if
(
databaseInfo
.
value
==
(
databaseList
.
value
[
0
]?.
guid
??
""
)
)
{
dsFromTreeData
.
value
=
JSON
.
parse
(
JSON
.
stringify
(
currentDsFromTreeData
.
value
));
}
else
{
databaseInfo
.
value
=
databaseList
.
value
[
0
]?.
guid
??
""
;
}
})
}
else
{
if
(
databaseInfo
.
value
==
databaseList
.
value
[
0
]?.
guid
??
""
)
{
if
(
databaseInfo
.
value
==
(
databaseList
.
value
[
0
]?.
guid
??
""
)
)
{
dsFromTreeData
.
value
=
JSON
.
parse
(
JSON
.
stringify
(
currentDsFromTreeData
.
value
));
}
else
{
databaseInfo
.
value
=
databaseList
.
value
[
0
]?.
guid
??
""
;
...
...
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment