da2ff56e by lihua

支持dicom文件

1 parent 4aed1325
...@@ -350,4 +350,26 @@ export const htmlToWord = (params) => request({ ...@@ -350,4 +350,26 @@ export const htmlToWord = (params) => request({
350 method: 'postJsonD', 350 method: 'postJsonD',
351 data: params, 351 data: params,
352 responseType: 'blob' 352 responseType: 'blob'
353 })
354
355 export const scanFolder = (dirPath = '') => request({
356 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/directory/scan?dirPath=${dirPath}`,
357 method: 'get'
358 })
359
360 /** 获取扫描文件结果 */
361 export const getDicomMeta = (taskGuid) => request({
362 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/get-dicom-meta?taskGuid=${taskGuid}`,
363 method: 'get'
364 })
365
366 /** 获取扫描文件统计结果 */
367 export const getDicomStatistics = (taskGuid) => request({
368 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/get-dicom-statistics?taskGuid=${taskGuid}`,
369 method: 'get'
370 })
371
372 export const retryDicom = (taskGuid) => request({
373 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/anon-task/dicom/retry?taskGuid=${taskGuid}`,
374 method: 'get'
353 }) 375 })
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -273,7 +273,7 @@ defineExpose({ ...@@ -273,7 +273,7 @@ defineExpose({
273 273
274 <template v-else-if="prefixInfo"> 274 <template v-else-if="prefixInfo">
275 <span class="prefix-icon" style="margin-right: 4px"> 275 <span class="prefix-icon" style="margin-right: 4px">
276 <template v-if="data.type < 2"> 276 <template v-if="data.type < 2 || data.type == 'DIRECTORY'">
277 <el-icon v-if="node.expanded"> 277 <el-icon v-if="node.expanded">
278 <svg-icon name="file-open" /> 278 <svg-icon name="file-open" />
279 </el-icon> 279 </el-icon>
......
...@@ -862,8 +862,10 @@ export const tagMethod = (row, type) => { ...@@ -862,8 +862,10 @@ export const tagMethod = (row, type) => {
862 tag = '未执行'; 862 tag = '未执行';
863 } else if (row[type] == 'E') { //部分通过 863 } else if (row[type] == 'E') { //部分通过
864 tag = '失败'; 864 tag = '失败';
865 }else if (row[type] == 'R') { //部分通过 865 } else if (row[type] == 'R') { //部分通过
866 tag = '执行中'; 866 tag = '执行中';
867 } else {
868 tag = '--'
867 } 869 }
868 } else if (type == 'sensitiveIdentifyConfirmStatus') { 870 } else if (type == 'sensitiveIdentifyConfirmStatus') {
869 if (row[type] == 'Y') { 871 if (row[type] == 'Y') {
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
4 4
5 <script lang="ts" setup name="anonResultReportView"> 5 <script lang="ts" setup name="anonResultReportView">
6 import { 6 import {
7 exportAnonReport,
8 getAnonAnalyzePageData, 7 getAnonAnalyzePageData,
9 getAnonAnalyzeResult, 8 getAnonAnalyzeResult,
10 getAnonTaskDetail, 9 getAnonTaskDetail,
...@@ -253,15 +252,16 @@ onBeforeMount(() => { ...@@ -253,15 +252,16 @@ onBeforeMount(() => {
253 252
254 <template> 253 <template>
255 <div class="table_tool_wrap" v-loading="resultDataLoading" ref="containerRef" :element-loading-text="loadingText"> 254 <div class="table_tool_wrap" v-loading="resultDataLoading" ref="containerRef" :element-loading-text="loadingText">
256 <el-button v-show="!isWordStyle" style="margin-bottom: 8px;" type="primary" @click="transfer" 255 <!-- 连接器不需要显示下载报告按钮 -->
256 <!-- <el-button v-show="!isWordStyle" style="margin-bottom: 8px;" type="primary" @click="transfer"
257 v-preReClick>生成Word评估报告</el-button> 257 v-preReClick>生成Word评估报告</el-button>
258 <div v-show="isWordStyle" style="margin-bottom: 8px;"> 258 <div v-show="isWordStyle" style="margin-bottom: 8px;">
259 <el-button @click="isWordStyle = false">返回</el-button> 259 <el-button @click="isWordStyle = false">返回</el-button>
260 <el-button type="primary" @click="downloadWord">下载评估报告</el-button> 260 <el-button type="primary" @click="downloadWord">下载评估报告</el-button>
261 </div> 261 </div> -->
262 <anonResultAnalysis ref="resultReportRef" :show-title="true" :analysis-result-info="analysisResultInfo" 262 <anonResultAnalysis ref="resultReportRef" :show-title="true" :analysis-result-info="analysisResultInfo"
263 :isWordStyle="isWordStyle" :style="isWordStyle ? { 263 :isWordStyle="isWordStyle" :style="isWordStyle ? {
264 height: 'calc(100% - 36px)', 264 height: '100%',
265 'overflow-y': 'auto', 265 'overflow-y': 'auto',
266 'margin-right': '-16px', 266 'margin-right': '-16px',
267 'padding-right': '16px', 267 'padding-right': '16px',
......
...@@ -51,47 +51,87 @@ ...@@ -51,47 +51,87 @@
51 <ContentWrap v-show="formRef?.formInline?.dataSource == 3" id="id-folder" title="提取文件" description="" 51 <ContentWrap v-show="formRef?.formInline?.dataSource == 3" id="id-folder" title="提取文件" description=""
52 style="margin-top: 16px;"> 52 style="margin-top: 16px;">
53 <div class="folder-main"> 53 <div class="folder-main">
54 <el-upload ref="uploadRef" class="upload-file" action="#" :file-list="folderList" :limit="1" directory 54 <el-button v-show="!clickSelectNode.path && !Object.keys(dicomStatisticsData)?.length" :icon="Upload"
55 :http-request="(file) => hanlderUploadFolder(file)"> 55 class="mr8" @click=uploadFolder>上传文件</el-button>
56 <template #trigger> 56 <Dialog ref="dialogRef" :dialog-info="uploadFileDialogInfo" @btnClick="dialogBtnClick">
57 <el-button :icon="Upload" class="mr8">上传文件</el-button> 57 <template #extra-content>
58 <div class="folder-main-content" v-loading="uploadFileDialogInfo.contentLoading">
59 <Tree ref="treeInfoRef" :treeInfo="folderTreeInfo" @nodeClick="nodeClick" key="path"
60 @loadNode="loadFolderTreeNode" />
61 </div>
62 <div class="folder-foot">{{ '当前选中文件夹路径:' + (clickSelectNode.path || '--') }}</div>
58 </template> 63 </template>
59 </el-upload> 64 </Dialog>
65 <div v-show="clickSelectNode.path && dicomStatisticsData.state" class="folder-foot">{{ '当前提取文件夹路径:' +
66 (clickSelectNode.path || '--') }}
67 </div>
68 <!-- 正在扫描的状态 -->
69 <div class="folder-progress"
70 v-show="clickSelectNode.path && (dicomStatisticsData.state == 'S' || !Object.keys(dicomStatisticsData)?.length)">
71 <div class="folder-title">正在扫描</div>
72 <el-progress :percentage="dicomStatisticsData.progress || 0" :stroke-width="12" striped striped-flow
73 :show-text="false" :duration="8" />
74 <div v-show="Object.keys(dicomStatisticsData)?.length" style="display: flex;justify-content: space-between;"><span class="cnt">{{ '共扫描' +
75 changeNum(dicomStatisticsData.total, 0) + '个文件' }}</span><span v-show="dicomStatisticsData.remainingTime" class="desc">{{ '剩' +
76 transferTime(dicomStatisticsData.remainingTime)
77 }}</span></div>
78 </div>
60 <!-- 正在解析的状态 --> 79 <!-- 正在解析的状态 -->
61 <div class="folder-progress"> 80 <div class="folder-progress"
81 v-show="clickSelectNode.path && (dicomStatisticsData.state == 'R')">
62 <div class="folder-title">正在解析</div> 82 <div class="folder-title">正在解析</div>
63 <el-progress :percentage="30" :stroke-width="12" striped striped-flow :show-text="false" :duration="8" /> 83 <el-progress :percentage="dicomStatisticsData.progress || 0" :stroke-width="12" striped striped-flow
64 <div style="display: flex;"><span class="cnt">{{ '共' + changeNum(100, 0) + '个文件, 已解析30%' }}</span></div> 84 :show-text="false" :duration="8" />
85 <div style="display: flex;justify-content: space-between;"><span class="cnt">{{ '共' +
86 changeNum(dicomStatisticsData.total, 0) +
87 '个文件, 已解析'
88 + (dicomStatisticsData.progress || 0) + '%' }}</span><span v-show="dicomStatisticsData.remainingTime" class="desc">{{ '剩' +
89 transferTime(dicomStatisticsData.remainingTime)
90 }}</span></div>
65 </div> 91 </div>
66 <!-- 解析失败的状态 --> 92 <!-- 解析失败的状态 -->
67 <div class="folder-progress"> 93 <div class="folder-progress" v-show="clickSelectNode.path && dicomStatisticsData.state == 'E'">
68 <div class="folder-title"> 94 <div class="folder-title">
69 <el-icon class="title-icon fail"> 95 <el-icon class="title-icon fail">
70 <CircleCloseFilled /> 96 <CircleCloseFilled />
71 </el-icon><span>解析失败</span> 97 </el-icon><span>解析失败</span>
72 </div> 98 </div>
73 <el-progress :percentage="30" :stroke-width="12" color="#F30000" :show-text="false" /> 99 <el-progress :percentage="dicomStatisticsData.progress || 0" :stroke-width="12" color="#F30000"
74 <div style="display: flex;"><span class="cnt">{{ '已成功解析' + changeNum(100, 0) + '个文件, 失败' + changeNum(30, 100 :show-text="false" />
75 0) + 101 <div style="display: flex;justify-content: space-between;"><span class="cnt">{{ '已成功解析' +
76 '个文件' }}</span></div> 102 changeNum(dicomStatisticsData.successCount, 0) + '个文件, 失败' + changeNum(dicomStatisticsData.errorCount,
103 0) + '个文件' }}</span><span class="desc">{{ '共耗时'
104 + calculateElapsedTime(dicomStatisticsData.parsingTime, dicomStatisticsData.parsingCompletedTime)
105 }}</span></div>
77 <div style="text-align: center;"> 106 <div style="text-align: center;">
78 <el-button @click="reAnalyzing">重试</el-button> 107 <el-button @click="reAnalyzing">重试</el-button>
108 <el-button @click=deleteFolder>删除文件,重新上传解析</el-button>
79 </div> 109 </div>
80 </div> 110 </div>
81 <!-- 解析成功状态 --> 111 <!-- 解析成功状态 -->
82 <div class="folder-progress"> 112 <div class="folder-progress"
113 v-show="clickSelectNode.path && dicomStatisticsData.state == 'Y' && dicomStatisticsData.errorCount === 0">
83 <div class="folder-title"> 114 <div class="folder-title">
84 <el-icon class="title-icon success"> 115 <el-icon class="title-icon success">
85 <svg-icon name="icon-success" /> 116 <svg-icon name="icon-success" />
86 </el-icon><span>解析成功</span> 117 </el-icon><span>解析成功</span>
87 </div> 118 </div>
88 <el-progress :percentage="30" :stroke-width="12" color="#F30000" :show-text="false" /> 119 <el-progress :percentage="dicomStatisticsData.progress || 100" :stroke-width="12" color="#4FA55D"
89 <div style="display: flex;"><span class="cnt">{{ '已成功解析' + changeNum(100, 0) + '个文件, 失败' + changeNum(30, 120 :show-text="false" />
90 0) + 121 <div style="display: flex;justify-content: space-between;"><span class="cnt">{{ '已成功解析' +
91 '个文件' }}</span></div> 122 changeNum(dicomStatisticsData.successCount, 0)
123 + '个文件,失败' + changeNum(dicomStatisticsData.errorCount, 0) + '个文件' }}</span><span class="desc">{{ '共耗时'
124 + calculateElapsedTime(dicomStatisticsData.parsingTime, dicomStatisticsData.parsingCompletedTime)
125 }}</span>
126 </div>
92 </div> 127 </div>
93 </div> 128 </div>
94 129 <div v-show="clickSelectNode.path && folderFileTableInfo.data?.length" class="preview-title">预览文件仅展示5条数据
130 </div>
131 <Table v-show="clickSelectNode.path && folderFileTableInfo.data?.length" :tableInfo="folderFileTableInfo">
132 </Table>
133 <div v-show="clickSelectNode.path && dicomStatisticsData.state == 'Y'" class="folder-bottom"><el-button
134 @click=deleteFolder>删除文件,重新上传解析</el-button></div>
95 </ContentWrap> 135 </ContentWrap>
96 </div> 136 </div>
97 <!-- 第二步 配置匿名化方案,单独抽取vue组件页面 --> 137 <!-- 第二步 配置匿名化方案,单独抽取vue组件页面 -->
...@@ -116,20 +156,22 @@ ...@@ -116,20 +156,22 @@
116 <div v-show="analysisResultInfo.errorMsg" class="error-desc">{{ '【' + analysisResultInfo.errorMsg + '】' }} 156 <div v-show="analysisResultInfo.errorMsg" class="error-desc">{{ '【' + analysisResultInfo.errorMsg + '】' }}
117 </div> 157 </div>
118 </div> 158 </div>
119 <anonResultAnalysis v-show="isExecEnd && analysisResultInfo.status == 'Y'" ref="resultReportRef" v-loading="downloadLoading" 159 <anonResultAnalysis v-show="isExecEnd && analysisResultInfo.status == 'Y'" ref="resultReportRef"
120 :analysis-result-info="analysisResultInfo" :is-word-style="isWordStyle" :element-loading-text="loadingText" 160 v-loading="downloadLoading" :analysis-result-info="analysisResultInfo" :is-word-style="isWordStyle"
121 :analysis-result-loading="analysisResultLoading" :analysis-result-table-fields="analysisResultTableFields" 161 :element-loading-text="loadingText" :analysis-result-loading="analysisResultLoading"
122 :old-anon-task-value-info="oldAnonTaskValueInfo" :container-width="containerWidth" 162 :analysis-result-table-fields="analysisResultTableFields" :old-anon-task-value-info="oldAnonTaskValueInfo"
123 :origin-result-table-field-column="originResultTableFieldColumn" :page-info="pageInfo" 163 :container-width="containerWidth" :origin-result-table-field-column="originResultTableFieldColumn"
124 :result-data="resultData" :fullResultData="fullResultData" @page-change="pageChange"></anonResultAnalysis> 164 :page-info="pageInfo" :result-data="resultData" :fullResultData="fullResultData" @page-change="pageChange">
125 <template #header> 165 </anonResultAnalysis>
166 <!-- 连接器只能查看报告 -->
167 <!-- <template #header>
126 <el-button v-show="isExecEnd && analysisResultInfo.status == 'Y' && !isWordStyle" type="primary" 168 <el-button v-show="isExecEnd && analysisResultInfo.status == 'Y' && !isWordStyle" type="primary"
127 v-loading="!!downPromise" @click="transfer">生成Word评估报告</el-button> 169 v-loading="!!downPromise" @click="transfer">生成Word评估报告</el-button>
128 <div v-show="isWordStyle"> 170 <div v-show="isWordStyle">
129 <el-button @click="isWordStyle = false">返回</el-button> 171 <el-button @click="isWordStyle = false">返回</el-button>
130 <el-button type="primary" @click="downloadWord">下载评估报告</el-button> 172 <el-button type="primary" @click="downloadWord">下载评估报告</el-button>
131 </div> 173 </div>
132 </template> 174 </template> -->
133 </ContentWrap> 175 </ContentWrap>
134 </div> 176 </div>
135 <!-- 匿名化结果展示 --> 177 <!-- 匿名化结果展示 -->
...@@ -146,7 +188,9 @@ ...@@ -146,7 +188,9 @@
146 <div class="bottom_tool_wrap"> 188 <div class="bottom_tool_wrap">
147 <template v-if="step == 0"> 189 <template v-if="step == 0">
148 <el-button @click="cancelTask">取消</el-button> 190 <el-button @click="cancelTask">取消</el-button>
149 <el-button type="primary" @click="changeStep(formRef?.formInline?.handleType == '02' ? 3 : 2)">下一步</el-button> 191 <el-button type="primary"
192 :disabled="formRef?.formInline?.handleType == '02' && formRef?.formInline?.dataSource == 3 && dicomStatisticsData?.progress != 100"
193 @click="changeStep(formRef?.formInline?.handleType == '02' ? 3 : 2)">下一步</el-button>
150 </template> 194 </template>
151 <template v-else-if="step == 1"> 195 <template v-else-if="step == 1">
152 <el-button @click="changeStep(1)">上一步</el-button> 196 <el-button @click="changeStep(1)">上一步</el-button>
...@@ -184,8 +228,11 @@ import { ...@@ -184,8 +228,11 @@ import {
184 saveAnonTask, 228 saveAnonTask,
185 updateAnonTask, 229 updateAnonTask,
186 exportAnonExecData, 230 exportAnonExecData,
187 exportAnonReport,
188 htmlToWord, 231 htmlToWord,
232 scanFolder,
233 getDicomMeta,
234 getDicomStatistics,
235 retryDicom
189 } from '@/api/modules/dataAnonymization'; 236 } from '@/api/modules/dataAnonymization';
190 import { 237 import {
191 parseAndDecodeUrl, 238 parseAndDecodeUrl,
...@@ -228,6 +275,38 @@ const { required } = useValidator(); ...@@ -228,6 +275,38 @@ const { required } = useValidator();
228 const fullscreenLoading = ref(false); 275 const fullscreenLoading = ref(false);
229 const containerRef = ref(); 276 const containerRef = ref();
230 277
278 const qualifiedIdentifierFloderList = ref([{
279 enName: 'patient_birth_date',
280 chName: '患者出生日期',
281 }, {
282 enName: 'patient_birth_time',
283 chName: '患者出生时间'
284 }, {
285 enName: 'patient_sex',
286 chName: '患者性别'
287 }, {
288 enName: 'patient_age',
289 chName: '患者年龄'
290 }, {
291 enName: 'patient_weight',
292 chName: '患者体重'
293 }, {
294 enName: 'pregnancy_status',
295 chName: '怀孕状态'
296 }, {
297 enName: 'military_rank',
298 chName: '军衔'
299 }, {
300 enName: 'branch_of_service',
301 chName: '服役分支'
302 }, {
303 enName: 'ethnic_group',
304 chName: '种族'
305 }, {
306 enName: 'occupation',
307 chName: '职业'
308 }]);
309
231 const containerWidth = ref(containerRef.value?.offsetWidth || 0) 310 const containerWidth = ref(containerRef.value?.offsetWidth || 0)
232 311
233 const step = ref(0); 312 const step = ref(0);
...@@ -607,6 +686,9 @@ const setDataSelectFormItems = (info, isDetail = false) => { ...@@ -607,6 +686,9 @@ const setDataSelectFormItems = (info, isDetail = false) => {
607 } 686 }
608 } else if (item.field == 'qualifiedIdentifier') { 687 } else if (item.field == 'qualifiedIdentifier') {
609 item.visible = info['handleType'] == '02'; 688 item.visible = info['handleType'] == '02';
689 if (info['dataSource'] == 3) {
690 item.options = qualifiedIdentifierFloderList.value;
691 }
610 } 692 }
611 }); 693 });
612 stepsInfo.value = info.handleType == '02' ? reportStepsInfo.value : originStepsInfo.value; 694 stepsInfo.value = info.handleType == '02' ? reportStepsInfo.value : originStepsInfo.value;
...@@ -734,9 +816,12 @@ watch( ...@@ -734,9 +816,12 @@ watch(
734 ); 816 );
735 817
736 watch(() => sampleTableFields.value, (val) => { 818 watch(() => sampleTableFields.value, (val) => {
819 let formInfo = formRef.value.formInline;
820 if (formInfo.dataSource == 3) {
821 return;
822 }
737 let item = dataSelectInfoItems.value.find(selectItem => selectItem.field == 'qualifiedIdentifier'); 823 let item = dataSelectInfoItems.value.find(selectItem => selectItem.field == 'qualifiedIdentifier');
738 item && (item.options = val); 824 item && (item.options = val);
739 let formInfo = formRef.value.formInline;
740 if (formInfo.handleType == '02' && !(taskGuid.value && (formInfo.file?.[0] && formInfo.file?.[0]?.url == detailInfo.value.filePath?.url || (formInfo.tableName && formInfo.tableName == detailInfo.value.tableName)))) {//需要同步清除准标识符的字段选择 825 if (formInfo.handleType == '02' && !(taskGuid.value && (formInfo.file?.[0] && formInfo.file?.[0]?.url == detailInfo.value.filePath?.url || (formInfo.tableName && formInfo.tableName == detailInfo.value.tableName)))) {//需要同步清除准标识符的字段选择
741 setDataSelectFormItems(Object.assign({}, formInfo, { file: !formInfo['file'] ? [] : formInfo['file'], qualifiedIdentifier: [] })) 826 setDataSelectFormItems(Object.assign({}, formInfo, { file: !formInfo['file'] ? [] : formInfo['file'], qualifiedIdentifier: [] }))
742 } 827 }
...@@ -902,16 +987,343 @@ const uploadFileChange = (file) => { ...@@ -902,16 +987,343 @@ const uploadFileChange = (file) => {
902 } 987 }
903 988
904 /*** ----------------------- 解析扫描文件 ------------------------------ */ 989 /*** ----------------------- 解析扫描文件 ------------------------------ */
905 const folderList: any = ref([]);
906 990
907 const hanlderUploadFolder = (file) => { 991 /** 上传选择文件夹对话框 */
908 debugger 992 const uploadFileDialogInfo = ref({
909 return Promise.resolve(); 993 visible: false,
994 size: 700,
995 direction: "column",
996 header: {
997 title: "选择文件夹",
998 },
999 type: '',
1000 contents: [],
1001 footer: {
1002 btns: [
1003 { type: "default", label: "取消", value: "cancel" },
1004 { type: "primary", label: "确定", value: "submit", loading: false },
1005 ],
1006 },
1007 contentLoading: false,
1008 });
1009
1010 const folderRefreshTimer = ref();
1011
1012 const processFolderRefresh = async () => {
1013 await getDicomStatisticsData(taskGuid.value);
1014 if (!dicomStatisticsData.value.state || dicomStatisticsData.value.state == 'S' || dicomStatisticsData.value.state == 'R') {
1015 if (folderRefreshTimer.value) {
1016 return;
1017 }
1018 folderRefreshTimer.value = setInterval(async () => {
1019 processFolderRefresh();
1020 }, 10000);
1021 } else if (dicomStatisticsData.value.state == 'Y') {
1022 getDicomMetaData(taskGuid.value);
1023 analysisResultInfo.value = {};
1024 if (folderRefreshTimer.value) {
1025 clearInterval(folderRefreshTimer.value);
1026 folderRefreshTimer.value = null;
1027 }
1028 // processStepThreeResultView();
1029 } else if (dicomStatisticsData.value.state == 'E') {
1030 if (folderRefreshTimer.value) {
1031 clearInterval(folderRefreshTimer.value);
1032 folderRefreshTimer.value = null;
1033 }
1034 }
1035 }
1036
1037 // TODO,需要将selectNode与oldSelectNode即对话框展开的做区分处理。
1038
1039 const dialogBtnClick = (btn) => {
1040 if (btn.value == 'submit') {
1041 clickSelectNode.value = dialogOpenSelectNode.value;
1042 if (!clickSelectNode.value.path) {
1043 proxy.$ElMessage.error('请先选择文件夹');
1044 return;
1045 }
1046 let saveParams = { ...formRef.value.formInline };
1047 if (saveParams.coverageArea?.length) {
1048 saveParams.coverageArea = [saveParams.coverageArea];
1049 } else {
1050 saveParams.coverageArea = [];
1051 }
1052 saveParams.filePath = {
1053 url: clickSelectNode.value.path
1054 };
1055 saveParams.samplingRate = null;
1056 if (taskGuid.value) {
1057 saveParams.guid = taskGuid.value;
1058 }
1059 dicomStatisticsData.value = {};
1060 folderFileTableInfo.value.data = [];
1061 if (!taskGuid.value) { //保存
1062 uploadFileDialogInfo.value.footer.btns[1].loading = true;
1063 saveAnonTask(saveParams).then(async (res: any) => {
1064 uploadFileDialogInfo.value.footer.btns[1].loading = false;
1065 if (res.code == proxy.$passCode) {
1066 taskGuid.value = res.data?.taskGuid;
1067 taskExecGuid.value = res.data?.lastExecGuid;
1068 uploadFileDialogInfo.value.visible = false;
1069 anonymizationStore.setIsAnonPageRefresh(true);
1070 isExecEnd.value = false;
1071 oldAnonTaskValueInfo.value = saveParams;
1072 oldAnonTaskValueInfo.value.guid = taskGuid.value;
1073 if (folderRefreshTimer.value) {
1074 clearInterval(folderRefreshTimer.value);
1075 folderRefreshTimer.value = null;
1076 }
1077 await 100;
1078 processFolderRefresh();
1079 } else {
1080 ElMessage.error(res.msg);
1081 }
1082 });
1083 } else { //更新
1084 uploadFileDialogInfo.value.footer.btns[1].loading = true;
1085 updateAnonTask(saveParams).then(async (res: any) => {
1086 uploadFileDialogInfo.value.footer.btns[1].loading = false;
1087 if (res.code == proxy.$passCode) {
1088 taskExecGuid.value = res.data;
1089 uploadFileDialogInfo.value.visible = false;
1090 anonymizationStore.setIsAnonPageRefresh(true);
1091 isExecEnd.value = false;
1092 oldAnonTaskValueInfo.value = saveParams;
1093 if (folderRefreshTimer.value) {
1094 clearInterval(folderRefreshTimer.value);
1095 folderRefreshTimer.value = null;
1096 }
1097 await 100;
1098 processFolderRefresh();
1099 } else {
1100 ElMessage.error(res.msg);
1101 }
1102 })
1103 }
1104 } else if (btn.value == 'cancel') {
1105 // clickSelectNode.value = {};
1106 dialogOpenSelectNode.value = {};
1107 uploadFileDialogInfo.value.visible = false;
1108 }
1109 };
1110
1111 const folderTreeInfo = ref({
1112 id: "data-pickup-tree",
1113 filter: true,
1114 queryValue: "",
1115 queryPlaceholder: "输入关键字搜索",
1116 props: {
1117 label: "name",
1118 value: "path",
1119 isLeaf: "isLeaf",
1120 },
1121 prefix: {
1122 type: 'prefixIcon'
1123 },
1124 nodeKey: 'path',
1125 lazy: true,
1126 expandedKey: [],
1127 currentNodeKey: '',
1128 expandOnNodeClick: true,
1129 data: <any>[],
1130 //customFilter: true,
1131 loading: false
1132 });
1133
1134 const clickSelectNode: any = ref({});
1135
1136 const nodeClick = (data, node) => {
1137 dialogOpenSelectNode.value = data;
1138 }
1139
1140 const loadFolderTreeNode = (node, resolve) => {
1141 if (node.level === 0 || node.isLeaf) {
1142 return resolve([]);
1143 }
1144 scanFolder(node.data.path).then((res: any) => {
1145 if (res?.code == proxy.$passCode) {
1146 resolve(res.data || []);
1147 } else {
1148 ElMessage.error(res.msg);
1149 }
1150 })
1151 }
1152
1153 const dialogOpenSelectNode: any = ref({})
1154
1155 const uploadFolder = () => {
1156 // 先检查信息是否填写完整。
1157 formRef.value?.ruleFormRef?.validate((valid) => {
1158 if (valid) {
1159 let formInline = formRef.value?.formInline;
1160 if (formInline.handleType == '01' && formInline.dataSource == 3) {
1161 proxy.$ElMessage.warning('暂不支持处理类型为数据匿名化处理的文件夹数据,请修改');
1162 return;
1163 }
1164 clickSelectNode.value = {};
1165 dialogOpenSelectNode.value = {};
1166 uploadFileDialogInfo.value.visible = true;
1167 folderTreeInfo.value.loading = true;
1168 scanFolder().then((res: any) => {
1169 folderTreeInfo.value.loading = false;
1170 if (res?.code == proxy.$passCode) {
1171 folderTreeInfo.value.data = res.data || [];
1172 } else {
1173 ElMessage.error(res.msg);
1174 }
1175 })
1176 } else {
1177 proxy.$ElMessage.warning('请先填写完整数据选择基本信息');
1178 }
1179 })
1180 }
1181
1182 const deleteFolder = () => {
1183 proxy.$openMessageBox("确定要删除该文件夹扫描解析结果,重新上传吗?", () => {
1184 clickSelectNode.value = {};
1185 folderFileTableInfo.value.data = [];
1186 dicomStatisticsData.value = {};
1187 }, () => {
1188 proxy.$ElMessage.info("已取消删除");
1189 })
910 } 1190 }
911 1191
912 /** 重试 */ 1192 /** 重试 */
913 const reAnalyzing = () => { 1193 const reAnalyzing = () => {
1194 fullscreenLoading.value = true;
1195 retryDicom(taskGuid.value).then((res: any) => {
1196 fullscreenLoading.value = false;
1197 if (res?.code == proxy.$passCode) {
1198 if (folderRefreshTimer.value) {
1199 clearInterval(folderRefreshTimer.value);
1200 folderRefreshTimer.value = null;
1201 }
1202 processFolderRefresh();
1203 } else {
1204 res?.msg && ElMessage.error(res.msg);
1205 }
1206 })
1207 }
1208
1209 /** 上传成功之后的文件信息 */
1210 const folderFileTableInfo = ref({
1211 id: "exec-contract-table",
1212 height: '212px',
1213 fields: <any>[],
1214 data: [],
1215 showPage: false,
1216 actionInfo: {
1217 show: false
1218 },
1219 loading: false
1220 });
1221
1222 const dicomMetaData: any = ref({});
1223 const metaDataWidthsJson = ref({
1224 '患者姓名': 140,
1225 '患者ID': 140,
1226 '患者出生日期': 120,
1227 '患者出生时间': 130,
1228 '患者性别': 100,
1229 "怀孕状态": 110,
1230 "种族": 100,
1231 "患者体重": 90,
1232 '患者年龄': 110,
1233 });
1234 /** 获取预览的5条数据 */
1235 const getDicomMetaData = (taskGuid) => {
1236 folderFileTableInfo.value.loading = true;
1237 getDicomMeta(taskGuid).then((res: any) => {
1238 folderFileTableInfo.value.loading = false;
1239 if (res?.code == proxy.$passCode) {
1240 dicomMetaData.value = res.data || {};
1241 folderFileTableInfo.value.fields = [{ label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" }].concat(dicomMetaData.value?.column?.map(f => {
1242 return {
1243 label: f, field: f, width: metaDataWidthsJson.value[f] || 120
1244 }
1245 }));
1246 let column = dicomMetaData.value?.column || [];
1247 folderFileTableInfo.value.data = dicomMetaData.value.datas?.map(d => {
1248 let json = {};
1249 column.forEach((c, index) => {
1250 json[c] = d[index];
1251 })
1252 return json;
1253 })
1254 } else {
1255 res?.msg && proxy.$ElMessage.error(res.msg);
1256 }
1257 })
1258 }
1259
1260 const dicomStatisticsData: any = ref({});
914 1261
1262 /** 获取解析文件结果数据 */
1263 const getDicomStatisticsData = (taskGuid) => {
1264 return getDicomStatistics(taskGuid).then((res: any) => {
1265 if (res?.code == proxy.$passCode) {
1266 dicomStatisticsData.value = res.data || {};
1267 } else {
1268 res?.msg && proxy.$ElMessage.error(res.msg);
1269 }
1270 })
1271 }
1272
1273 /** 将时间转换为时分秒 */
1274 const transferTime = (time) => {
1275 if (time == null) {
1276 return '--';
1277 }
1278
1279 // 4. 将毫秒转换为小时、分钟和秒
1280 const totalSeconds = Math.floor(time / 1000);
1281 const hours = Math.floor(totalSeconds / 3600);
1282 const minutes = Math.floor((totalSeconds % 3600) / 60);
1283 const seconds = totalSeconds % 60;
1284
1285 // 5. 格式化输出
1286 const parts: any[] = [];
1287
1288 if (hours > 0) {
1289 parts.push(`${hours}小时`);
1290 }
1291
1292 if (minutes > 0 || hours > 0) { // 如果有小时,即使分钟为0也显示0分钟
1293 parts.push(`${minutes}分钟`);
1294 }
1295
1296 parts.push(`${seconds}秒`);
1297
1298 return parts.join('');
1299 }
1300
1301 /**
1302 * 计算两个时间之间的耗时
1303 * @param {string} parsingTime - 开始时间(ISO 8601格式)
1304 * @param {string} parsingCompletedTime - 结束时间(ISO 8601格式)
1305 * @returns {string} - 格式化的耗时字符串,如 "2小时30分钟"
1306 */
1307 const calculateElapsedTime = (parsingTime, parsingCompletedTime) => {
1308 // 1. 将时间字符串转换为Date对象
1309 const startTime = new Date(parsingTime);
1310 const endTime = new Date(parsingCompletedTime);
1311
1312 // 2. 计算时间差(毫秒)
1313 const timeDiff = endTime - startTime;
1314
1315 // 3. 检查时间是否有效
1316 if (isNaN(timeDiff)) {
1317 // return "时间格式错误";
1318 return '--';
1319 }
1320
1321 if (timeDiff < 0) {
1322 //return "结束时间早于开始时间";
1323 return '--';
1324 }
1325
1326 return transferTime(timeDiff);
915 } 1327 }
916 1328
917 /** 第二步的配置组件引用。 */ 1329 /** 第二步的配置组件引用。 */
...@@ -954,7 +1366,13 @@ const changeStep = async (val) => { ...@@ -954,7 +1366,13 @@ const changeStep = async (val) => {
954 saveParams.dataSourceGuid = null; 1366 saveParams.dataSourceGuid = null;
955 saveParams.tableName = null; 1367 saveParams.tableName = null;
956 } else { 1368 } else {
957 saveParams.filePath = null; 1369 if (saveParams.dataSource == 3 && clickSelectNode.value.path) {
1370 saveParams.filePath = {
1371 url: clickSelectNode.value.path
1372 };
1373 } else {
1374 saveParams.filePath = null;
1375 }
958 } 1376 }
959 let simpleFormInline = dataSimpleFormRef.value.formInline; 1377 let simpleFormInline = dataSimpleFormRef.value.formInline;
960 if (simpleFormInline.enableSamplingRate == 'Y') { 1378 if (simpleFormInline.enableSamplingRate == 'Y') {
...@@ -984,6 +1402,8 @@ const changeStep = async (val) => { ...@@ -984,6 +1402,8 @@ const changeStep = async (val) => {
984 taskGuid.value = res.data?.taskGuid; 1402 taskGuid.value = res.data?.taskGuid;
985 isExecEnd.value = false; 1403 isExecEnd.value = false;
986 taskExecGuid.value = res.data?.lastExecGuid; 1404 taskExecGuid.value = res.data?.lastExecGuid;
1405 oldAnonTaskValueInfo.value = saveParams;
1406 oldAnonTaskValueInfo.value.guid = taskGuid.value;
987 step.value = val - 1; 1407 step.value = val - 1;
988 stepsInfo.value.step = val - 1; 1408 stepsInfo.value.step = val - 1;
989 analysisResultInfo.value = {}; 1409 analysisResultInfo.value = {};
...@@ -992,7 +1412,6 @@ const changeStep = async (val) => { ...@@ -992,7 +1412,6 @@ const changeStep = async (val) => {
992 refreshTimer.value = null; 1412 refreshTimer.value = null;
993 } 1413 }
994 processStepThreeResultView(); 1414 processStepThreeResultView();
995 oldAnonTaskValueInfo.value = saveParams;
996 anonymizationStore.setIsAnonPageRefresh(true); 1415 anonymizationStore.setIsAnonPageRefresh(true);
997 } else { 1416 } else {
998 ElMessage.error(res.msg); 1417 ElMessage.error(res.msg);
...@@ -1045,16 +1464,25 @@ const changeStep = async (val) => { ...@@ -1045,16 +1464,25 @@ const changeStep = async (val) => {
1045 } else { 1464 } else {
1046 formRef.value?.ruleFormRef?.validate((valid, errorItem) => { 1465 formRef.value?.ruleFormRef?.validate((valid, errorItem) => {
1047 if (valid) { 1466 if (valid) {
1048 if (formRef.value?.formInline?.dataSource == 2 && !sampleTableFields.value?.length) { 1467 let dataSource = formRef.value?.formInline?.dataSource;
1468 if (dataSource == 2 && !sampleTableFields.value?.length) {
1049 proxy.$ElMessage.error('上传文件的字段不能为空'); 1469 proxy.$ElMessage.error('上传文件的字段不能为空');
1050 return; 1470 return;
1051 } 1471 }
1052 dataSimpleFormRef.value?.ruleFormRef?.validate((valid) => { 1472 if (dataSource != 3) {
1053 if (valid) { 1473 dataSimpleFormRef.value?.ruleFormRef?.validate((valid) => {
1054 Object.assign(saveParams, { riskThreshold: '0.05' }); 1474 if (valid) {
1055 exec(saveParams); 1475 // Object.assign(saveParams, { riskThreshold: '0.05' });
1476 exec(saveParams);
1477 }
1478 });
1479 } else {
1480 if (!clickSelectNode.value.path) {
1481 proxy.$ElMessage.error('请先上传文件');
1482 return;
1056 } 1483 }
1057 }); 1484 exec(saveParams);
1485 }
1058 } else { 1486 } else {
1059 var obj = Object.keys(errorItem); 1487 var obj = Object.keys(errorItem);
1060 formRef.value.ruleFormRef?.scrollToField(obj[0]) 1488 formRef.value.ruleFormRef?.scrollToField(obj[0])
...@@ -1101,26 +1529,45 @@ onBeforeMount(() => { ...@@ -1101,26 +1529,45 @@ onBeforeMount(() => {
1101 if (res?.code == proxy.$passCode) { 1529 if (res?.code == proxy.$passCode) {
1102 detailInfo.value = res.data || {}; 1530 detailInfo.value = res.data || {};
1103 taskExecGuid.value = detailInfo.value.lastExecGuid; 1531 taskExecGuid.value = detailInfo.value.lastExecGuid;
1104 oldAnonTaskValueInfo.value = { 1532 if (detailInfo.value.handleType == '01') {
1105 guid: detailInfo.value.guid, 1533 oldAnonTaskValueInfo.value = {
1106 taskName: detailInfo.value.taskName, 1534 guid: detailInfo.value.guid,
1107 dataSource: detailInfo.value.dataSource, 1535 taskName: detailInfo.value.taskName,
1108 filePath: detailInfo.value.filePath && cloneDeep(detailInfo.value.filePath), 1536 dataSource: detailInfo.value.dataSource,
1109 dataSourceGuid: detailInfo.value.dataSourceGuid, 1537 handleType: detailInfo.value.handleType,
1110 tableName: detailInfo.value.tableName, 1538 coverageArea: detailInfo.value.coverageArea || [],
1111 samplingRate: detailInfo.value.samplingRate, 1539 qualifiedIdentifier: detailInfo.value.qualifiedIdentifier,
1112 // patientPopulationRate: detailInfo.value.patientPopulationRate && typeof detailInfo.value.patientPopulationRate == 'number' ? detailInfo.value.patientPopulationRate?.toFixed(9) : detailInfo.value.patientPopulationRate, 1540 samplingRate: detailInfo.value.samplingRate,
1113 dataSharingTypeCode: detailInfo.value.dataSharingTypeCode, 1541 dataSharingTypeCode: detailInfo.value.dataSharingTypeCode,
1114 anonTaskRules: cloneDeep(detailInfo.value.anonTaskRules), 1542 anonTaskRules: cloneDeep(detailInfo.value.anonTaskRules),
1115 anonPrivacyMode: { 1543 anonPrivacyMode: {
1116 kaNumber: detailInfo.value.anonPrivacyMode?.kaNumber, 1544 kaNumber: detailInfo.value.anonPrivacyMode?.kaNumber,
1117 ldFieldName: detailInfo.value.anonPrivacyMode?.ldFieldName, 1545 ldFieldName: detailInfo.value.anonPrivacyMode?.ldFieldName,
1118 ldNumber: detailInfo.value.anonPrivacyMode?.ldNumber, 1546 ldNumber: detailInfo.value.anonPrivacyMode?.ldNumber,
1119 riskThreshold: detailInfo.value.anonPrivacyMode?.riskThreshold, 1547 riskThreshold: detailInfo.value.anonPrivacyMode?.riskThreshold,
1120 tcFieldName: detailInfo.value.anonPrivacyMode?.tcFieldName, 1548 tcFieldName: detailInfo.value.anonPrivacyMode?.tcFieldName,
1121 tcThreshold: detailInfo.value.anonPrivacyMode?.tcThreshold, 1549 tcThreshold: detailInfo.value.anonPrivacyMode?.tcThreshold,
1550 }
1122 } 1551 }
1552 } else {
1553 oldAnonTaskValueInfo.value = {
1554 guid: detailInfo.value.guid,
1555 taskName: detailInfo.value.taskName,
1556 dataSource: detailInfo.value.dataSource,
1557 handleType: detailInfo.value.handleType,
1558 coverageArea: detailInfo.value.coverageArea || [],
1559 qualifiedIdentifier: detailInfo.value.qualifiedIdentifier,
1560 samplingRate: detailInfo.value.samplingRate,
1561 dataSharingTypeCode: detailInfo.value.dataSharingTypeCode,
1562 }
1563 }
1564 if (detailInfo.value.dataSource == 1) {
1565 oldAnonTaskValueInfo.value.dataSourceGuid = detailInfo.value.dataSourceGuid;
1566 oldAnonTaskValueInfo.value.tableName = detailInfo.value.tableName;
1567 } else {
1568 oldAnonTaskValueInfo.value.filePath = detailInfo.value.filePath && cloneDeep(detailInfo.value.filePath);
1123 } 1569 }
1570 detailInfo.value.dataSource == 3 && (clickSelectNode.value.path = oldAnonTaskValueInfo.value.filePath?.url);
1124 setDataSelectFormItems(Object.assign(detailInfo.value, { file: detailInfo.value.filePath ? [detailInfo.value.filePath] : [] }), true); 1571 setDataSelectFormItems(Object.assign(detailInfo.value, { file: detailInfo.value.filePath ? [detailInfo.value.filePath] : [] }), true);
1125 if (detailInfo.value.samplingRate != null) { 1572 if (detailInfo.value.samplingRate != null) {
1126 dataSimpleFormItems.value[0].default = 'Y'; 1573 dataSimpleFormItems.value[0].default = 'Y';
...@@ -1135,7 +1582,7 @@ onBeforeMount(() => { ...@@ -1135,7 +1582,7 @@ onBeforeMount(() => {
1135 dataSelectInfoItems.value[6].visible = dataSource == 1; 1582 dataSelectInfoItems.value[6].visible = dataSource == 1;
1136 dataSelectInfoItems.value[8].visible = dataSource == 2; 1583 dataSelectInfoItems.value[8].visible = dataSource == 2;
1137 try { 1584 try {
1138 //文件解析 1585 // 文件解析
1139 if (dataSource == 2) { 1586 if (dataSource == 2) {
1140 let url = detailInfo.value.filePath?.url; 1587 let url = detailInfo.value.filePath?.url;
1141 sampleTableDataLoading.value = true; 1588 sampleTableDataLoading.value = true;
...@@ -1161,7 +1608,7 @@ onBeforeMount(() => { ...@@ -1161,7 +1608,7 @@ onBeforeMount(() => {
1161 } else { 1608 } else {
1162 proxy.$ElMessage.error(res.msg); 1609 proxy.$ElMessage.error(res.msg);
1163 } 1610 }
1164 } else { 1611 } else if (dataSource == 1) {
1165 const res: any = await getDatabase({ connectStatus: 1 }); 1612 const res: any = await getDatabase({ connectStatus: 1 });
1166 if (res?.code == proxy.$passCode) { 1613 if (res?.code == proxy.$passCode) {
1167 dataSourceList.value = res.data || []; 1614 dataSourceList.value = res.data || [];
...@@ -1208,6 +1655,9 @@ onBeforeMount(() => { ...@@ -1208,6 +1655,9 @@ onBeforeMount(() => {
1208 ElMessage.error(res.msg); 1655 ElMessage.error(res.msg);
1209 } 1656 }
1210 }); 1657 });
1658 } else if (dataSource == 3) {
1659 getDicomMetaData(taskGuid.value);
1660 await getDicomStatisticsData(taskGuid.value);
1211 } 1661 }
1212 fullscreenLoading.value = false; 1662 fullscreenLoading.value = false;
1213 } catch (error) { 1663 } catch (error) {
...@@ -1882,7 +2332,7 @@ onUnmounted(() => { ...@@ -1882,7 +2332,7 @@ onUnmounted(() => {
1882 } 2332 }
1883 2333
1884 .folder-progress { 2334 .folder-progress {
1885 width: 360px; 2335 width: 420px;
1886 2336
1887 .cnt { 2337 .cnt {
1888 font-size: 14px; 2338 font-size: 14px;
...@@ -1891,9 +2341,50 @@ onUnmounted(() => { ...@@ -1891,9 +2341,50 @@ onUnmounted(() => {
1891 margin-top: 8px; 2341 margin-top: 8px;
1892 } 2342 }
1893 2343
2344 .desc {
2345 font-size: 14px;
2346 color: #999999;
2347 line-height: 21px;
2348 margin-top: 8px;
2349 }
2350
1894 .el-button { 2351 .el-button {
1895 margin-top: 12px; 2352 margin-top: 12px;
1896 } 2353 }
1897 } 2354 }
1898 } 2355 }
2356
2357 .folder-main-content {
2358 height: 450px;
2359 padding: 8px 16px;
2360
2361 :deep(.tree_panel) {
2362 height: 100%;
2363
2364 .el-tree {
2365 overflow-y: auto;
2366 }
2367 }
2368 }
2369
2370 .folder-foot {
2371 padding: 0px 16px 8px;
2372 line-height: 21px;
2373 }
2374
2375 .preview-title {
2376 margin-bottom: 8px;
2377 font-size: 14px;
2378 color: #212121;
2379 line-height: 21px;
2380 font-weight: 600;
2381 }
2382
2383 .folder-bottom {
2384 width: 100%;
2385 padding: 8px 0px 0px;
2386 display: flex;
2387 justify-content: center;
2388 align-items: center;
2389 }
1899 </style> 2390 </style>
...\ No newline at end of file ...\ No newline at end of file
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!