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
733b166a
authored
2026-02-11 10:27:43 +0800
by
lihua
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
支持dicom文件
1 parent
6eb7eff7
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
584 additions
and
69 deletions
src/api/modules/dataAnonymization.ts
src/components/Tree/index.vue
src/utils/common.ts
src/views/data_anonymization/anonResultReportView.vue
src/views/data_anonymization/anonTaskCreate.vue
src/api/modules/dataAnonymization.ts
View file @
733b166
...
...
@@ -350,4 +350,26 @@ export const htmlToWord = (params) => request({
method
:
'postJsonD'
,
data
:
params
,
responseType
:
'blob'
})
export
const
scanFolder
=
(
dirPath
=
''
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DIGITAL_CONTRACT_URL
}
/anon-task/directory/scan?dirPath=
${
dirPath
}
`
,
method
:
'get'
})
/** 获取扫描文件结果 */
export
const
getDicomMeta
=
(
taskGuid
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DIGITAL_CONTRACT_URL
}
/anon-task/get-dicom-meta?taskGuid=
${
taskGuid
}
`
,
method
:
'get'
})
/** 获取扫描文件统计结果 */
export
const
getDicomStatistics
=
(
taskGuid
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DIGITAL_CONTRACT_URL
}
/anon-task/get-dicom-statistics?taskGuid=
${
taskGuid
}
`
,
method
:
'get'
})
export
const
retryDicom
=
(
taskGuid
)
=>
request
({
url
:
`
${
import
.
meta
.
env
.
VITE_APP_DIGITAL_CONTRACT_URL
}
/anon-task/dicom/retry?taskGuid=
${
taskGuid
}
`
,
method
:
'get'
})
\ No newline at end of file
...
...
src/components/Tree/index.vue
View file @
733b166
...
...
@@ -273,7 +273,7 @@ defineExpose({
<
template
v-else-if=
"prefixInfo"
>
<span
class=
"prefix-icon"
style=
"margin-right: 4px"
>
<template
v-if=
"data.type
< 2
"
>
<template
v-if=
"data.type
< 2
||
data
.
type =
=
'
DIRECTORY
'
"
>
<el-icon
v-if=
"node.expanded"
>
<svg-icon
name=
"file-open"
/>
</el-icon>
...
...
src/utils/common.ts
View file @
733b166
...
...
@@ -862,8 +862,10 @@ export const tagMethod = (row, type) => {
tag
=
'未执行'
;
}
else
if
(
row
[
type
]
==
'E'
)
{
//部分通过
tag
=
'失败'
;
}
else
if
(
row
[
type
]
==
'R'
)
{
//部分通过
}
else
if
(
row
[
type
]
==
'R'
)
{
//部分通过
tag
=
'执行中'
;
}
else
{
tag
=
'--'
}
}
else
if
(
type
==
'sensitiveIdentifyConfirmStatus'
)
{
if
(
row
[
type
]
==
'Y'
)
{
...
...
src/views/data_anonymization/anonResultReportView.vue
View file @
733b166
...
...
@@ -4,7 +4,6 @@
<
script
lang=
"ts"
setup
name=
"anonResultReportView"
>
import
{
exportAnonReport
,
getAnonAnalyzePageData
,
getAnonAnalyzeResult
,
getAnonTaskDetail
,
...
...
@@ -253,15 +252,16 @@ onBeforeMount(() => {
<
template
>
<div
class=
"table_tool_wrap"
v-loading=
"resultDataLoading"
ref=
"containerRef"
:element-loading-text=
"loadingText"
>
<el-button
v-show=
"!isWordStyle"
style=
"margin-bottom: 8px;"
type=
"primary"
@
click=
"transfer"
<!-- 连接器不需要显示下载报告按钮 -->
<!--
<el-button
v-show=
"!isWordStyle"
style=
"margin-bottom: 8px;"
type=
"primary"
@
click=
"transfer"
v-preReClick
>
生成Word评估报告
</el-button>
<div
v-show=
"isWordStyle"
style=
"margin-bottom: 8px;"
>
<el-button
@
click=
"isWordStyle = false"
>
返回
</el-button>
<el-button
type=
"primary"
@
click=
"downloadWord"
>
下载评估报告
</el-button>
</div>
</div>
-->
<anonResultAnalysis
ref=
"resultReportRef"
:show-title=
"true"
:analysis-result-info=
"analysisResultInfo"
:isWordStyle=
"isWordStyle"
:style=
"isWordStyle ?
{
height: '
calc(100% - 36px)
',
height: '
100%
',
'overflow-y': 'auto',
'margin-right': '-16px',
'padding-right': '16px',
...
...
src/views/data_anonymization/anonTaskCreate.vue
View file @
733b166
...
...
@@ -51,47 +51,87 @@
<ContentWrap
v-show=
"formRef?.formInline?.dataSource == 3"
id=
"id-folder"
title=
"提取文件"
description=
""
style=
"margin-top: 16px;"
>
<div
class=
"folder-main"
>
<el-upload
ref=
"uploadRef"
class=
"upload-file"
action=
"#"
:file-list=
"folderList"
:limit=
"1"
directory
:http-request=
"(file) => hanlderUploadFolder(file)"
>
<
template
#
trigger
>
<el-button
:icon=
"Upload"
class=
"mr8"
>
上传文件
</el-button>
<el-button
v-show=
"!clickSelectNode.path && !Object.keys(dicomStatisticsData)?.length"
:icon=
"Upload"
class=
"mr8"
@
click=
uploadFolder
>
上传文件
</el-button>
<Dialog
ref=
"dialogRef"
:dialog-info=
"uploadFileDialogInfo"
@
btnClick=
"dialogBtnClick"
>
<
template
#
extra-content
>
<div
class=
"folder-main-content"
v-loading=
"uploadFileDialogInfo.contentLoading"
>
<Tree
ref=
"treeInfoRef"
:treeInfo=
"folderTreeInfo"
@
nodeClick=
"nodeClick"
key=
"path"
@
loadNode=
"loadFolderTreeNode"
/>
</div>
<div
class=
"folder-foot"
>
{{
'当前选中文件夹路径:'
+
(
clickSelectNode
.
path
||
'--'
)
}}
</div>
</
template
>
</el-upload>
</Dialog>
<div
v-show=
"clickSelectNode.path && dicomStatisticsData.state"
class=
"folder-foot"
>
{{ '当前提取文件夹路径:' +
(clickSelectNode.path || '--') }}
</div>
<!-- 正在扫描的状态 -->
<div
class=
"folder-progress"
v-show=
"clickSelectNode.path && (dicomStatisticsData.state == 'S' || !Object.keys(dicomStatisticsData)?.length)"
>
<div
class=
"folder-title"
>
正在扫描
</div>
<el-progress
:percentage=
"dicomStatisticsData.progress || 0"
:stroke-width=
"12"
striped
striped-flow
:show-text=
"false"
:duration=
"8"
/>
<div
v-show=
"Object.keys(dicomStatisticsData)?.length"
style=
"display: flex;justify-content: space-between;"
><span
class=
"cnt"
>
{{ '共扫描' +
changeNum(dicomStatisticsData.total, 0) + '个文件' }}
</span><span
v-show=
"dicomStatisticsData.remainingTime"
class=
"desc"
>
{{ '剩' +
transferTime(dicomStatisticsData.remainingTime)
}}
</span></div>
</div>
<!-- 正在解析的状态 -->
<div
class=
"folder-progress"
>
<div
class=
"folder-progress"
v-show=
"clickSelectNode.path && (dicomStatisticsData.state == 'R')"
>
<div
class=
"folder-title"
>
正在解析
</div>
<el-progress
:percentage=
"30"
:stroke-width=
"12"
striped
striped-flow
:show-text=
"false"
:duration=
"8"
/>
<div
style=
"display: flex;"
><span
class=
"cnt"
>
{{ '共' + changeNum(100, 0) + '个文件, 已解析30%' }}
</span></div>
<el-progress
:percentage=
"dicomStatisticsData.progress || 0"
:stroke-width=
"12"
striped
striped-flow
:show-text=
"false"
:duration=
"8"
/>
<div
style=
"display: flex;justify-content: space-between;"
><span
class=
"cnt"
>
{{ '共' +
changeNum(dicomStatisticsData.total, 0) +
'个文件, 已解析'
+ (dicomStatisticsData.progress || 0) + '%' }}
</span><span
v-show=
"dicomStatisticsData.remainingTime"
class=
"desc"
>
{{ '剩' +
transferTime(dicomStatisticsData.remainingTime)
}}
</span></div>
</div>
<!-- 解析失败的状态 -->
<div
class=
"folder-progress"
>
<div
class=
"folder-progress"
v-show=
"clickSelectNode.path && dicomStatisticsData.state == 'E'"
>
<div
class=
"folder-title"
>
<el-icon
class=
"title-icon fail"
>
<CircleCloseFilled
/>
</el-icon><span>
解析失败
</span>
</div>
<el-progress
:percentage=
"30"
:stroke-width=
"12"
color=
"#F30000"
:show-text=
"false"
/>
<div
style=
"display: flex;"
><span
class=
"cnt"
>
{{ '已成功解析' + changeNum(100, 0) + '个文件, 失败' + changeNum(30,
0) +
'个文件' }}
</span></div>
<el-progress
:percentage=
"dicomStatisticsData.progress || 0"
:stroke-width=
"12"
color=
"#F30000"
:show-text=
"false"
/>
<div
style=
"display: flex;justify-content: space-between;"
><span
class=
"cnt"
>
{{ '已成功解析' +
changeNum(dicomStatisticsData.successCount, 0) + '个文件, 失败' + changeNum(dicomStatisticsData.errorCount,
0) + '个文件' }}
</span><span
class=
"desc"
>
{{ '共耗时'
+ calculateElapsedTime(dicomStatisticsData.parsingTime, dicomStatisticsData.parsingCompletedTime)
}}
</span></div>
<div
style=
"text-align: center;"
>
<el-button
@
click=
"reAnalyzing"
>
重试
</el-button>
<el-button
@
click=
deleteFolder
>
删除文件,重新上传解析
</el-button>
</div>
</div>
<!-- 解析成功状态 -->
<div
class=
"folder-progress"
>
<div
class=
"folder-progress"
v-show=
"clickSelectNode.path && dicomStatisticsData.state == 'Y' && dicomStatisticsData.errorCount === 0"
>
<div
class=
"folder-title"
>
<el-icon
class=
"title-icon success"
>
<svg-icon
name=
"icon-success"
/>
</el-icon><span>
解析成功
</span>
</div>
<el-progress
:percentage=
"30"
:stroke-width=
"12"
color=
"#F30000"
:show-text=
"false"
/>
<div
style=
"display: flex;"
><span
class=
"cnt"
>
{{ '已成功解析' + changeNum(100, 0) + '个文件, 失败' + changeNum(30,
0) +
'个文件' }}
</span></div>
<el-progress
:percentage=
"dicomStatisticsData.progress || 100"
:stroke-width=
"12"
color=
"#4FA55D"
:show-text=
"false"
/>
<div
style=
"display: flex;justify-content: space-between;"
><span
class=
"cnt"
>
{{ '已成功解析' +
changeNum(dicomStatisticsData.successCount, 0)
+ '个文件,失败' + changeNum(dicomStatisticsData.errorCount, 0) + '个文件' }}
</span><span
class=
"desc"
>
{{ '共耗时'
+ calculateElapsedTime(dicomStatisticsData.parsingTime, dicomStatisticsData.parsingCompletedTime)
}}
</span>
</div>
</div>
</div>
<div
v-show=
"clickSelectNode.path && folderFileTableInfo.data?.length"
class=
"preview-title"
>
预览文件仅展示5条数据
</div>
<Table
v-show=
"clickSelectNode.path && folderFileTableInfo.data?.length"
:tableInfo=
"folderFileTableInfo"
>
</Table>
<div
v-show=
"clickSelectNode.path && dicomStatisticsData.state == 'Y'"
class=
"folder-bottom"
><el-button
@
click=
deleteFolder
>
删除文件,重新上传解析
</el-button></div>
</ContentWrap>
</div>
<!-- 第二步 配置匿名化方案,单独抽取vue组件页面 -->
...
...
@@ -116,20 +156,22 @@
<div
v-show=
"analysisResultInfo.errorMsg"
class=
"error-desc"
>
{{ '【' + analysisResultInfo.errorMsg + '】' }}
</div>
</div>
<anonResultAnalysis
v-show=
"isExecEnd && analysisResultInfo.status == 'Y'"
ref=
"resultReportRef"
v-loading=
"downloadLoading"
:analysis-result-info=
"analysisResultInfo"
:is-word-style=
"isWordStyle"
:element-loading-text=
"loadingText"
:analysis-result-loading=
"analysisResultLoading"
:analysis-result-table-fields=
"analysisResultTableFields"
:old-anon-task-value-info=
"oldAnonTaskValueInfo"
:container-width=
"containerWidth"
:origin-result-table-field-column=
"originResultTableFieldColumn"
:page-info=
"pageInfo"
:result-data=
"resultData"
:fullResultData=
"fullResultData"
@
page-change=
"pageChange"
></anonResultAnalysis>
<
template
#
header
>
<anonResultAnalysis
v-show=
"isExecEnd && analysisResultInfo.status == 'Y'"
ref=
"resultReportRef"
v-loading=
"downloadLoading"
:analysis-result-info=
"analysisResultInfo"
:is-word-style=
"isWordStyle"
:element-loading-text=
"loadingText"
:analysis-result-loading=
"analysisResultLoading"
:analysis-result-table-fields=
"analysisResultTableFields"
:old-anon-task-value-info=
"oldAnonTaskValueInfo"
:container-width=
"containerWidth"
:origin-result-table-field-column=
"originResultTableFieldColumn"
:page-info=
"pageInfo"
:result-data=
"resultData"
:fullResultData=
"fullResultData"
@
page-change=
"pageChange"
>
</anonResultAnalysis>
<!-- 连接器只能查看报告 -->
<!-- <template #header>
<el-button v-show="isExecEnd && analysisResultInfo.status == 'Y' && !isWordStyle" type="primary"
v-loading="!!downPromise" @click="transfer">生成Word评估报告</el-button>
<div v-show="isWordStyle">
<el-button @click="isWordStyle = false">返回</el-button>
<el-button type="primary" @click="downloadWord">下载评估报告</el-button>
</div>
</
template
>
</template>
-->
</ContentWrap>
</div>
<!-- 匿名化结果展示 -->
...
...
@@ -146,7 +188,9 @@
<div
class=
"bottom_tool_wrap"
>
<
template
v-if=
"step == 0"
>
<el-button
@
click=
"cancelTask"
>
取消
</el-button>
<el-button
type=
"primary"
@
click=
"changeStep(formRef?.formInline?.handleType == '02' ? 3 : 2)"
>
下一步
</el-button>
<el-button
type=
"primary"
:disabled=
"formRef?.formInline?.handleType == '02' && formRef?.formInline?.dataSource == 3 && dicomStatisticsData?.progress != 100"
@
click=
"changeStep(formRef?.formInline?.handleType == '02' ? 3 : 2)"
>
下一步
</el-button>
</
template
>
<
template
v-else-if=
"step == 1"
>
<el-button
@
click=
"changeStep(1)"
>
上一步
</el-button>
...
...
@@ -184,8 +228,11 @@ import {
saveAnonTask
,
updateAnonTask
,
exportAnonExecData
,
exportAnonReport
,
htmlToWord
,
scanFolder
,
getDicomMeta
,
getDicomStatistics
,
retryDicom
}
from
'@/api/modules/dataAnonymization'
;
import
{
parseAndDecodeUrl
,
...
...
@@ -228,6 +275,38 @@ const { required } = useValidator();
const
fullscreenLoading
=
ref
(
false
);
const
containerRef
=
ref
();
const
qualifiedIdentifierFloderList
=
ref
([{
enName
:
'patient_birth_date'
,
chName
:
'患者出生日期'
,
},
{
enName
:
'patient_birth_time'
,
chName
:
'患者出生时间'
},
{
enName
:
'patient_sex'
,
chName
:
'患者性别'
},
{
enName
:
'patient_age'
,
chName
:
'患者年龄'
},
{
enName
:
'patient_weight'
,
chName
:
'患者体重'
},
{
enName
:
'pregnancy_status'
,
chName
:
'怀孕状态'
},
{
enName
:
'military_rank'
,
chName
:
'军衔'
},
{
enName
:
'branch_of_service'
,
chName
:
'服役分支'
},
{
enName
:
'ethnic_group'
,
chName
:
'种族'
},
{
enName
:
'occupation'
,
chName
:
'职业'
}]);
const
containerWidth
=
ref
(
containerRef
.
value
?.
offsetWidth
||
0
)
const
step
=
ref
(
0
);
...
...
@@ -607,6 +686,9 @@ const setDataSelectFormItems = (info, isDetail = false) => {
}
}
else
if
(
item
.
field
==
'qualifiedIdentifier'
)
{
item
.
visible
=
info
[
'handleType'
]
==
'02'
;
if
(
info
[
'dataSource'
]
==
3
)
{
item
.
options
=
qualifiedIdentifierFloderList
.
value
;
}
}
});
stepsInfo
.
value
=
info
.
handleType
==
'02'
?
reportStepsInfo
.
value
:
originStepsInfo
.
value
;
...
...
@@ -734,9 +816,12 @@ watch(
);
watch
(()
=>
sampleTableFields
.
value
,
(
val
)
=>
{
let
formInfo
=
formRef
.
value
.
formInline
;
if
(
formInfo
.
dataSource
==
3
)
{
return
;
}
let
item
=
dataSelectInfoItems
.
value
.
find
(
selectItem
=>
selectItem
.
field
==
'qualifiedIdentifier'
);
item
&&
(
item
.
options
=
val
);
let
formInfo
=
formRef
.
value
.
formInline
;
if
(
formInfo
.
handleType
==
'02'
&&
!
(
taskGuid
.
value
&&
(
formInfo
.
file
?.[
0
]
&&
formInfo
.
file
?.[
0
]?.
url
==
detailInfo
.
value
.
filePath
?.
url
||
(
formInfo
.
tableName
&&
formInfo
.
tableName
==
detailInfo
.
value
.
tableName
))))
{
//需要同步清除准标识符的字段选择
setDataSelectFormItems
(
Object
.
assign
({},
formInfo
,
{
file
:
!
formInfo
[
'file'
]
?
[]
:
formInfo
[
'file'
],
qualifiedIdentifier
:
[]
}))
}
...
...
@@ -902,16 +987,343 @@ const uploadFileChange = (file) => {
}
/*** ----------------------- 解析扫描文件 ------------------------------ */
const
folderList
:
any
=
ref
([]);
const
hanlderUploadFolder
=
(
file
)
=>
{
debugger
return
Promise
.
resolve
();
/** 上传选择文件夹对话框 */
const
uploadFileDialogInfo
=
ref
({
visible
:
false
,
size
:
700
,
direction
:
"column"
,
header
:
{
title
:
"选择文件夹"
,
},
type
:
''
,
contents
:
[],
footer
:
{
btns
:
[
{
type
:
"default"
,
label
:
"取消"
,
value
:
"cancel"
},
{
type
:
"primary"
,
label
:
"确定"
,
value
:
"submit"
,
loading
:
false
},
],
},
contentLoading
:
false
,
});
const
folderRefreshTimer
=
ref
();
const
processFolderRefresh
=
async
()
=>
{
await
getDicomStatisticsData
(
taskGuid
.
value
);
if
(
!
dicomStatisticsData
.
value
.
state
||
dicomStatisticsData
.
value
.
state
==
'S'
||
dicomStatisticsData
.
value
.
state
==
'R'
)
{
if
(
folderRefreshTimer
.
value
)
{
return
;
}
folderRefreshTimer
.
value
=
setInterval
(
async
()
=>
{
processFolderRefresh
();
},
10000
);
}
else
if
(
dicomStatisticsData
.
value
.
state
==
'Y'
)
{
getDicomMetaData
(
taskGuid
.
value
);
analysisResultInfo
.
value
=
{};
if
(
folderRefreshTimer
.
value
)
{
clearInterval
(
folderRefreshTimer
.
value
);
folderRefreshTimer
.
value
=
null
;
}
// processStepThreeResultView();
}
else
if
(
dicomStatisticsData
.
value
.
state
==
'E'
)
{
if
(
folderRefreshTimer
.
value
)
{
clearInterval
(
folderRefreshTimer
.
value
);
folderRefreshTimer
.
value
=
null
;
}
}
}
// TODO,需要将selectNode与oldSelectNode即对话框展开的做区分处理。
const
dialogBtnClick
=
(
btn
)
=>
{
if
(
btn
.
value
==
'submit'
)
{
clickSelectNode
.
value
=
dialogOpenSelectNode
.
value
;
if
(
!
clickSelectNode
.
value
.
path
)
{
proxy
.
$ElMessage
.
error
(
'请先选择文件夹'
);
return
;
}
let
saveParams
=
{
...
formRef
.
value
.
formInline
};
if
(
saveParams
.
coverageArea
?.
length
)
{
saveParams
.
coverageArea
=
[
saveParams
.
coverageArea
];
}
else
{
saveParams
.
coverageArea
=
[];
}
saveParams
.
filePath
=
{
url
:
clickSelectNode
.
value
.
path
};
saveParams
.
samplingRate
=
null
;
if
(
taskGuid
.
value
)
{
saveParams
.
guid
=
taskGuid
.
value
;
}
dicomStatisticsData
.
value
=
{};
folderFileTableInfo
.
value
.
data
=
[];
if
(
!
taskGuid
.
value
)
{
//保存
uploadFileDialogInfo
.
value
.
footer
.
btns
[
1
].
loading
=
true
;
saveAnonTask
(
saveParams
).
then
(
async
(
res
:
any
)
=>
{
uploadFileDialogInfo
.
value
.
footer
.
btns
[
1
].
loading
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
taskGuid
.
value
=
res
.
data
?.
taskGuid
;
taskExecGuid
.
value
=
res
.
data
?.
lastExecGuid
;
uploadFileDialogInfo
.
value
.
visible
=
false
;
anonymizationStore
.
setIsAnonPageRefresh
(
true
);
isExecEnd
.
value
=
false
;
oldAnonTaskValueInfo
.
value
=
saveParams
;
oldAnonTaskValueInfo
.
value
.
guid
=
taskGuid
.
value
;
if
(
folderRefreshTimer
.
value
)
{
clearInterval
(
folderRefreshTimer
.
value
);
folderRefreshTimer
.
value
=
null
;
}
await
100
;
processFolderRefresh
();
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
});
}
else
{
//更新
uploadFileDialogInfo
.
value
.
footer
.
btns
[
1
].
loading
=
true
;
updateAnonTask
(
saveParams
).
then
(
async
(
res
:
any
)
=>
{
uploadFileDialogInfo
.
value
.
footer
.
btns
[
1
].
loading
=
false
;
if
(
res
.
code
==
proxy
.
$passCode
)
{
taskExecGuid
.
value
=
res
.
data
;
uploadFileDialogInfo
.
value
.
visible
=
false
;
anonymizationStore
.
setIsAnonPageRefresh
(
true
);
isExecEnd
.
value
=
false
;
oldAnonTaskValueInfo
.
value
=
saveParams
;
if
(
folderRefreshTimer
.
value
)
{
clearInterval
(
folderRefreshTimer
.
value
);
folderRefreshTimer
.
value
=
null
;
}
await
100
;
processFolderRefresh
();
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
})
}
}
else
if
(
btn
.
value
==
'cancel'
)
{
// clickSelectNode.value = {};
dialogOpenSelectNode
.
value
=
{};
uploadFileDialogInfo
.
value
.
visible
=
false
;
}
};
const
folderTreeInfo
=
ref
({
id
:
"data-pickup-tree"
,
filter
:
true
,
queryValue
:
""
,
queryPlaceholder
:
"输入关键字搜索"
,
props
:
{
label
:
"name"
,
value
:
"path"
,
isLeaf
:
"isLeaf"
,
},
prefix
:
{
type
:
'prefixIcon'
},
nodeKey
:
'path'
,
lazy
:
true
,
expandedKey
:
[],
currentNodeKey
:
''
,
expandOnNodeClick
:
true
,
data
:
<
any
>
[],
//customFilter: true,
loading
:
false
});
const
clickSelectNode
:
any
=
ref
({});
const
nodeClick
=
(
data
,
node
)
=>
{
dialogOpenSelectNode
.
value
=
data
;
}
const
loadFolderTreeNode
=
(
node
,
resolve
)
=>
{
if
(
node
.
level
===
0
||
node
.
isLeaf
)
{
return
resolve
([]);
}
scanFolder
(
node
.
data
.
path
).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
resolve
(
res
.
data
||
[]);
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
})
}
const
dialogOpenSelectNode
:
any
=
ref
({})
const
uploadFolder
=
()
=>
{
// 先检查信息是否填写完整。
formRef
.
value
?.
ruleFormRef
?.
validate
((
valid
)
=>
{
if
(
valid
)
{
let
formInline
=
formRef
.
value
?.
formInline
;
if
(
formInline
.
handleType
==
'01'
&&
formInline
.
dataSource
==
3
)
{
proxy
.
$ElMessage
.
warning
(
'暂不支持处理类型为数据匿名化处理的文件夹数据,请修改'
);
return
;
}
clickSelectNode
.
value
=
{};
dialogOpenSelectNode
.
value
=
{};
uploadFileDialogInfo
.
value
.
visible
=
true
;
folderTreeInfo
.
value
.
loading
=
true
;
scanFolder
().
then
((
res
:
any
)
=>
{
folderTreeInfo
.
value
.
loading
=
false
;
if
(
res
?.
code
==
proxy
.
$passCode
)
{
folderTreeInfo
.
value
.
data
=
res
.
data
||
[];
}
else
{
ElMessage
.
error
(
res
.
msg
);
}
})
}
else
{
proxy
.
$ElMessage
.
warning
(
'请先填写完整数据选择基本信息'
);
}
})
}
const
deleteFolder
=
()
=>
{
proxy
.
$openMessageBox
(
"确定要删除该文件夹扫描解析结果,重新上传吗?"
,
()
=>
{
clickSelectNode
.
value
=
{};
folderFileTableInfo
.
value
.
data
=
[];
dicomStatisticsData
.
value
=
{};
},
()
=>
{
proxy
.
$ElMessage
.
info
(
"已取消删除"
);
})
}
/** 重试 */
const
reAnalyzing
=
()
=>
{
fullscreenLoading
.
value
=
true
;
retryDicom
(
taskGuid
.
value
).
then
((
res
:
any
)
=>
{
fullscreenLoading
.
value
=
false
;
if
(
res
?.
code
==
proxy
.
$passCode
)
{
if
(
folderRefreshTimer
.
value
)
{
clearInterval
(
folderRefreshTimer
.
value
);
folderRefreshTimer
.
value
=
null
;
}
processFolderRefresh
();
}
else
{
res
?.
msg
&&
ElMessage
.
error
(
res
.
msg
);
}
})
}
/** 上传成功之后的文件信息 */
const
folderFileTableInfo
=
ref
({
id
:
"exec-contract-table"
,
height
:
'212px'
,
fields
:
<
any
>
[],
data
:
[],
showPage
:
false
,
actionInfo
:
{
show
:
false
},
loading
:
false
});
const
dicomMetaData
:
any
=
ref
({});
const
metaDataWidthsJson
=
ref
({
'患者姓名'
:
140
,
'患者ID'
:
140
,
'患者出生日期'
:
120
,
'患者出生时间'
:
130
,
'患者性别'
:
100
,
"怀孕状态"
:
110
,
"种族"
:
100
,
"患者体重"
:
90
,
'患者年龄'
:
110
,
});
/** 获取预览的5条数据 */
const
getDicomMetaData
=
(
taskGuid
)
=>
{
folderFileTableInfo
.
value
.
loading
=
true
;
getDicomMeta
(
taskGuid
).
then
((
res
:
any
)
=>
{
folderFileTableInfo
.
value
.
loading
=
false
;
if
(
res
?.
code
==
proxy
.
$passCode
)
{
dicomMetaData
.
value
=
res
.
data
||
{};
folderFileTableInfo
.
value
.
fields
=
[{
label
:
"序号"
,
type
:
"index"
,
width
:
TableColumnWidth
.
INDEX
,
align
:
"center"
}].
concat
(
dicomMetaData
.
value
?.
column
?.
map
(
f
=>
{
return
{
label
:
f
,
field
:
f
,
width
:
metaDataWidthsJson
.
value
[
f
]
||
120
}
}));
let
column
=
dicomMetaData
.
value
?.
column
||
[];
folderFileTableInfo
.
value
.
data
=
dicomMetaData
.
value
.
datas
?.
map
(
d
=>
{
let
json
=
{};
column
.
forEach
((
c
,
index
)
=>
{
json
[
c
]
=
d
[
index
];
})
return
json
;
})
}
else
{
res
?.
msg
&&
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
})
}
const
dicomStatisticsData
:
any
=
ref
({});
/** 获取解析文件结果数据 */
const
getDicomStatisticsData
=
(
taskGuid
)
=>
{
return
getDicomStatistics
(
taskGuid
).
then
((
res
:
any
)
=>
{
if
(
res
?.
code
==
proxy
.
$passCode
)
{
dicomStatisticsData
.
value
=
res
.
data
||
{};
}
else
{
res
?.
msg
&&
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
})
}
/** 将时间转换为时分秒 */
const
transferTime
=
(
time
)
=>
{
if
(
time
==
null
)
{
return
'--'
;
}
// 4. 将毫秒转换为小时、分钟和秒
const
totalSeconds
=
Math
.
floor
(
time
/
1000
);
const
hours
=
Math
.
floor
(
totalSeconds
/
3600
);
const
minutes
=
Math
.
floor
((
totalSeconds
%
3600
)
/
60
);
const
seconds
=
totalSeconds
%
60
;
// 5. 格式化输出
const
parts
:
any
[]
=
[];
if
(
hours
>
0
)
{
parts
.
push
(
`
${
hours
}
小时`
);
}
if
(
minutes
>
0
||
hours
>
0
)
{
// 如果有小时,即使分钟为0也显示0分钟
parts
.
push
(
`
${
minutes
}
分钟`
);
}
parts
.
push
(
`
${
seconds
}
秒`
);
return
parts
.
join
(
''
);
}
/**
* 计算两个时间之间的耗时
* @param {string} parsingTime - 开始时间(ISO 8601格式)
* @param {string} parsingCompletedTime - 结束时间(ISO 8601格式)
* @returns {string} - 格式化的耗时字符串,如 "2小时30分钟"
*/
const
calculateElapsedTime
=
(
parsingTime
,
parsingCompletedTime
)
=>
{
// 1. 将时间字符串转换为Date对象
const
startTime
=
new
Date
(
parsingTime
);
const
endTime
=
new
Date
(
parsingCompletedTime
);
// 2. 计算时间差(毫秒)
const
timeDiff
=
endTime
-
startTime
;
// 3. 检查时间是否有效
if
(
isNaN
(
timeDiff
))
{
// return "时间格式错误";
return
'--'
;
}
if
(
timeDiff
<
0
)
{
//return "结束时间早于开始时间";
return
'--'
;
}
return
transferTime
(
timeDiff
);
}
/** 第二步的配置组件引用。 */
...
...
@@ -954,7 +1366,13 @@ const changeStep = async (val) => {
saveParams
.
dataSourceGuid
=
null
;
saveParams
.
tableName
=
null
;
}
else
{
saveParams
.
filePath
=
null
;
if
(
saveParams
.
dataSource
==
3
&&
clickSelectNode
.
value
.
path
)
{
saveParams
.
filePath
=
{
url
:
clickSelectNode
.
value
.
path
};
}
else
{
saveParams
.
filePath
=
null
;
}
}
let
simpleFormInline
=
dataSimpleFormRef
.
value
.
formInline
;
if
(
simpleFormInline
.
enableSamplingRate
==
'Y'
)
{
...
...
@@ -984,6 +1402,8 @@ const changeStep = async (val) => {
taskGuid
.
value
=
res
.
data
?.
taskGuid
;
isExecEnd
.
value
=
false
;
taskExecGuid
.
value
=
res
.
data
?.
lastExecGuid
;
oldAnonTaskValueInfo
.
value
=
saveParams
;
oldAnonTaskValueInfo
.
value
.
guid
=
taskGuid
.
value
;
step
.
value
=
val
-
1
;
stepsInfo
.
value
.
step
=
val
-
1
;
analysisResultInfo
.
value
=
{};
...
...
@@ -992,7 +1412,6 @@ const changeStep = async (val) => {
refreshTimer
.
value
=
null
;
}
processStepThreeResultView
();
oldAnonTaskValueInfo
.
value
=
saveParams
;
anonymizationStore
.
setIsAnonPageRefresh
(
true
);
}
else
{
ElMessage
.
error
(
res
.
msg
);
...
...
@@ -1045,16 +1464,25 @@ const changeStep = async (val) => {
}
else
{
formRef
.
value
?.
ruleFormRef
?.
validate
((
valid
,
errorItem
)
=>
{
if
(
valid
)
{
if
(
formRef
.
value
?.
formInline
?.
dataSource
==
2
&&
!
sampleTableFields
.
value
?.
length
)
{
let
dataSource
=
formRef
.
value
?.
formInline
?.
dataSource
;
if
(
dataSource
==
2
&&
!
sampleTableFields
.
value
?.
length
)
{
proxy
.
$ElMessage
.
error
(
'上传文件的字段不能为空'
);
return
;
}
dataSimpleFormRef
.
value
?.
ruleFormRef
?.
validate
((
valid
)
=>
{
if
(
valid
)
{
Object
.
assign
(
saveParams
,
{
riskThreshold
:
'0.05'
});
exec
(
saveParams
);
if
(
dataSource
!=
3
)
{
dataSimpleFormRef
.
value
?.
ruleFormRef
?.
validate
((
valid
)
=>
{
if
(
valid
)
{
// Object.assign(saveParams, { riskThreshold: '0.05' });
exec
(
saveParams
);
}
});
}
else
{
if
(
!
clickSelectNode
.
value
.
path
)
{
proxy
.
$ElMessage
.
error
(
'请先上传文件'
);
return
;
}
});
exec
(
saveParams
);
}
}
else
{
var
obj
=
Object
.
keys
(
errorItem
);
formRef
.
value
.
ruleFormRef
?.
scrollToField
(
obj
[
0
])
...
...
@@ -1101,26 +1529,45 @@ onBeforeMount(() => {
if
(
res
?.
code
==
proxy
.
$passCode
)
{
detailInfo
.
value
=
res
.
data
||
{};
taskExecGuid
.
value
=
detailInfo
.
value
.
lastExecGuid
;
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
,
if
(
detailInfo
.
value
.
handleType
==
'01'
)
{
oldAnonTaskValueInfo
.
value
=
{
guid
:
detailInfo
.
value
.
guid
,
taskName
:
detailInfo
.
value
.
taskName
,
dataSource
:
detailInfo
.
value
.
dataSource
,
handleType
:
detailInfo
.
value
.
handleType
,
coverageArea
:
detailInfo
.
value
.
coverageArea
||
[],
qualifiedIdentifier
:
detailInfo
.
value
.
qualifiedIdentifier
,
samplingRate
:
detailInfo
.
value
.
samplingRate
,
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
,
}
}
}
else
{
oldAnonTaskValueInfo
.
value
=
{
guid
:
detailInfo
.
value
.
guid
,
taskName
:
detailInfo
.
value
.
taskName
,
dataSource
:
detailInfo
.
value
.
dataSource
,
handleType
:
detailInfo
.
value
.
handleType
,
coverageArea
:
detailInfo
.
value
.
coverageArea
||
[],
qualifiedIdentifier
:
detailInfo
.
value
.
qualifiedIdentifier
,
samplingRate
:
detailInfo
.
value
.
samplingRate
,
dataSharingTypeCode
:
detailInfo
.
value
.
dataSharingTypeCode
,
}
}
if
(
detailInfo
.
value
.
dataSource
==
1
)
{
oldAnonTaskValueInfo
.
value
.
dataSourceGuid
=
detailInfo
.
value
.
dataSourceGuid
;
oldAnonTaskValueInfo
.
value
.
tableName
=
detailInfo
.
value
.
tableName
;
}
else
{
oldAnonTaskValueInfo
.
value
.
filePath
=
detailInfo
.
value
.
filePath
&&
cloneDeep
(
detailInfo
.
value
.
filePath
);
}
detailInfo
.
value
.
dataSource
==
3
&&
(
clickSelectNode
.
value
.
path
=
oldAnonTaskValueInfo
.
value
.
filePath
?.
url
);
setDataSelectFormItems
(
Object
.
assign
(
detailInfo
.
value
,
{
file
:
detailInfo
.
value
.
filePath
?
[
detailInfo
.
value
.
filePath
]
:
[]
}),
true
);
if
(
detailInfo
.
value
.
samplingRate
!=
null
)
{
dataSimpleFormItems
.
value
[
0
].
default
=
'Y'
;
...
...
@@ -1135,7 +1582,7 @@ onBeforeMount(() => {
dataSelectInfoItems
.
value
[
6
].
visible
=
dataSource
==
1
;
dataSelectInfoItems
.
value
[
8
].
visible
=
dataSource
==
2
;
try
{
//文件解析
//
文件解析
if
(
dataSource
==
2
)
{
let
url
=
detailInfo
.
value
.
filePath
?.
url
;
sampleTableDataLoading
.
value
=
true
;
...
...
@@ -1161,7 +1608,7 @@ onBeforeMount(() => {
}
else
{
proxy
.
$ElMessage
.
error
(
res
.
msg
);
}
}
else
{
}
else
if
(
dataSource
==
1
)
{
const
res
:
any
=
await
getDatabase
({
connectStatus
:
1
});
if
(
res
?.
code
==
proxy
.
$passCode
)
{
dataSourceList
.
value
=
res
.
data
||
[];
...
...
@@ -1208,6 +1655,9 @@ onBeforeMount(() => {
ElMessage
.
error
(
res
.
msg
);
}
});
}
else
if
(
dataSource
==
3
)
{
getDicomMetaData
(
taskGuid
.
value
);
await
getDicomStatisticsData
(
taskGuid
.
value
);
}
fullscreenLoading
.
value
=
false
;
}
catch
(
error
)
{
...
...
@@ -1882,7 +2332,7 @@ onUnmounted(() => {
}
.folder-progress
{
width
:
36
0px
;
width
:
42
0px
;
.cnt
{
font-size
:
14px
;
...
...
@@ -1891,9 +2341,50 @@ onUnmounted(() => {
margin-top
:
8px
;
}
.desc
{
font-size
:
14px
;
color
:
#999999
;
line-height
:
21px
;
margin-top
:
8px
;
}
.el-button
{
margin-top
:
12px
;
}
}
}
.folder-main-content
{
height
:
450px
;
padding
:
8px
16px
;
:deep(.tree_panel)
{
height
:
100%
;
.el-tree
{
overflow-y
:
auto
;
}
}
}
.folder-foot
{
padding
:
0px
16px
8px
;
line-height
:
21px
;
}
.preview-title
{
margin-bottom
:
8px
;
font-size
:
14px
;
color
:
#212121
;
line-height
:
21px
;
font-weight
:
600
;
}
.folder-bottom
{
width
:
100%
;
padding
:
8px
0px
0px
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
}
</
style
>
\ No newline at end of file
...
...
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