4aed1325 by lihua

修改下载报告

1 parent c89a85a1
...@@ -149,6 +149,9 @@ export const dataSourceTypeList = [{ ...@@ -149,6 +149,9 @@ export const dataSourceTypeList = [{
149 }, { 149 }, {
150 value: 2, 150 value: 2,
151 label: '文件导入' 151 label: '文件导入'
152 }, {
153 value: 3,
154 label: '文件夹'
152 }]; 155 }];
153 156
154 /** 获取数据库选择列表 */ 157 /** 获取数据库选择列表 */
...@@ -341,3 +344,10 @@ export const exportAnonReport = (params) => request({ ...@@ -341,3 +344,10 @@ export const exportAnonReport = (params) => request({
341 method: 'post', 344 method: 'post',
342 responseType: 'blob' 345 responseType: 'blob'
343 }) 346 })
347
348 export const htmlToWord = (params) => request({
349 url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/download/html-to-word`,
350 method: 'postJsonD',
351 data: params,
352 responseType: 'blob'
353 })
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -8,12 +8,14 @@ import { ...@@ -8,12 +8,14 @@ import {
8 getAnonAnalyzePageData, 8 getAnonAnalyzePageData,
9 getAnonAnalyzeResult, 9 getAnonAnalyzeResult,
10 getAnonTaskDetail, 10 getAnonTaskDetail,
11 htmlToWord,
11 } from '@/api/modules/dataAnonymization'; 12 } from '@/api/modules/dataAnonymization';
12 import { changeNum, download } from '@/utils/common'; 13 import { changeNum, download } from '@/utils/common';
13 import { ElMessage } from 'element-plus'; 14 import { ElMessage } from 'element-plus';
14 import anonResultAnalysis from './components/anonResultAnalysis.vue'; 15 import anonResultAnalysis from './components/anonResultAnalysis.vue';
15 import { commonPageConfig } from '@/utils/enum'; 16 import { commonPageConfig } from '@/utils/enum';
16 import { calcColumnWidth } from '@/utils'; 17 import { calcColumnWidth } from '@/utils';
18 import html2canvas from 'html2canvas';
17 19
18 const route = useRoute(); 20 const route = useRoute();
19 const router = useRouter(); 21 const router = useRouter();
...@@ -52,6 +54,8 @@ const originResultTableFieldColumn = ref({}); ...@@ -52,6 +54,8 @@ const originResultTableFieldColumn = ref({});
52 54
53 /** 结果分析中的字段表格数据 */ 55 /** 结果分析中的字段表格数据 */
54 const resultData: any = ref([]); 56 const resultData: any = ref([]);
57 /** 全部未分页的数据,下载word时使用。 */
58 const fullResultData: any = ref([]);
55 59
56 /** 结果分析中的字段信息 */ 60 /** 结果分析中的字段信息 */
57 const analysisResultTableFields: any = ref([]); 61 const analysisResultTableFields: any = ref([]);
...@@ -100,16 +104,30 @@ watch( ...@@ -100,16 +104,30 @@ watch(
100 } 104 }
101 ); 105 );
102 106
103 const getAnalysisResultPageData = () => { 107 const getAnalysisResultPageData = (isFull = false) => {
104 analysisResultLoading.value = true; 108 analysisResultLoading.value = true;
105 getAnonAnalyzePageData({ 109 getAnonAnalyzePageData({
106 pageIndex: pageInfo.value.curr, 110 pageIndex: pageInfo.value.curr,
107 pageSize: pageInfo.value.limit, 111 pageSize: isFull ? -1 : pageInfo.value.limit,
108 taskExecGuid: taskExecGuid.value, 112 taskExecGuid: taskExecGuid.value,
109 }).then((res: any) => { 113 }).then((res: any) => {
110 analysisResultLoading.value = false; 114 analysisResultLoading.value = false;
111 if (res?.code == proxy.$passCode) { 115 if (res?.code == proxy.$passCode) {
112 pageInfo.value.rows = 116 if (isFull) {
117 fullResultData.value = [];
118 res.data?.records?.forEach(d => {
119 let obj = {};
120 analysisResultTableFields.value.forEach(t => {
121 obj[t.enName] = d.fieldValue?.[t.enName];
122 });
123 obj['equivalenceClassNum'] = changeNum(d.equivalenceClassNum || 0, 0);
124 obj['reIdentifyRisk'] = changeNum(d.reIdentifyRisk || 0, 2);
125 obj['isGtThreshold'] = d.isGtThreshold;
126 fullResultData.value.push(obj);
127 });
128 resultData.value = fullResultData.value.slice(0, pageInfo.value.limit);
129 pageInfo.value.rows = fullResultData.value.length;
130 } else {
113 resultData.value = []; 131 resultData.value = [];
114 res.data?.records?.forEach(d => { 132 res.data?.records?.forEach(d => {
115 let obj = {}; 133 let obj = {};
...@@ -122,30 +140,82 @@ const getAnalysisResultPageData = () => { ...@@ -122,30 +140,82 @@ const getAnalysisResultPageData = () => {
122 resultData.value.push(obj); 140 resultData.value.push(obj);
123 }); 141 });
124 pageInfo.value.rows = res.data?.totalRows ?? 0; 142 pageInfo.value.rows = res.data?.totalRows ?? 0;
143 }
125 } else { 144 } else {
126 proxy.$ElMessage.error(res.msg); 145 proxy.$ElMessage.error(res.msg);
127 } 146 }
128 }) 147 })
129 } 148 }
130 149
150 const isWordStyle = ref(false);
151
131 /** 下载评估报告 */ 152 /** 下载评估报告 */
132 const transfer = () => { 153 const transfer = () => {
154 isWordStyle.value = true;
155 }
156
157 const domClone: any = ref(null);
158
159 const resultReportRef = ref();
160
161 const convertHtml2Img = (dom, domClone) => {
162 const element = <HTMLElement>dom.querySelector('.kpi-content')
163 if (!element) {
164 return Promise.resolve();
165 }
166 return html2canvas(element, {
167 allowTaint: true,
168 useCORS: true,
169 scale: 2,
170 }).then((canvas: any) => {
171 document.documentElement.scrollTop = 0;
172 document.body.scrollTop = 0;
173 element.parentNode && ((<HTMLElement>element.parentNode).scrollTop = 0);
174 let url = canvas.toDataURL('image/jpeg');
175 let img = document.createElement('img');
176 if (url) {
177 // img.src = url.split(',')[1];
178 img.src = url;
179 img.width = 620;
180 img.height = 265;
181 img.crossOrigin = 'Anonymous';
182 }
183 const copyElement = <HTMLElement>domClone.querySelector('.kpi-content')
184 copyElement.parentNode?.replaceChild(img, copyElement);
185 })
186 }
187
188 const loadingText = ref('');
189
190 const getHTML = (reportResultContent) => {
191 let html = reportResultContent;
192 html = html.replace(/"/g, "'");
193 return html;
194 };
195
196 const downloadWord = () => {
133 if (downPromise.value) { 197 if (downPromise.value) {
134 return; 198 return;
135 } 199 }
136 downPromise.value = exportAnonReport({ 200 let dom = domClone.value || (domClone.value = document.createElement('div'));
137 taskGuid: route.query.guid, 201 let report = resultReportRef.value?.report;
138 execGuid: taskExecGuid.value 202 dom.innerHTML = report?.innerHTML;
139 }).then((res: any) => { 203 resultDataLoading.value = true;
140 downPromise.value = null; 204 loadingText.value = '报告正在下载中,请勿关闭浏览器...';
205 downPromise.value = convertHtml2Img(report, dom).then(() => {
206 htmlToWord({ html: encodeURIComponent(`<div>${getHTML(dom.innerHTML)}</div>`) }).then((res: any) => {
207 downPromise.value = null
208 loadingText.value = '';
209 resultDataLoading.value = false;
141 if (res && !res.msg) { 210 if (res && !res.msg) {
142 download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化评估报告.docx', 'word') 211 download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化评估报告.docx', 'word')
143 } else { 212 } else {
144 res?.msg && ElMessage.error(res?.msg); 213 res?.msg && ElMessage.error(res?.msg);
145 } 214 }
215 })
146 }).catch(() => { 216 }).catch(() => {
147 downPromise.value = null; 217 downPromise.value = null;
148 }) 218 });
149 } 219 }
150 220
151 onMounted(() => { 221 onMounted(() => {
...@@ -165,7 +235,7 @@ onBeforeMount(() => { ...@@ -165,7 +235,7 @@ onBeforeMount(() => {
165 analysisResultInfo.value = res.data || {}; 235 analysisResultInfo.value = res.data || {};
166 analysisResultTableFields.value = res.data?.column || []; 236 analysisResultTableFields.value = res.data?.column || [];
167 pageInfo.value.curr = 1; 237 pageInfo.value.curr = 1;
168 getAnalysisResultPageData(); 238 getAnalysisResultPageData(true);
169 } else { 239 } else {
170 res?.msg && proxy.$ElMessage.error(res.msg); 240 res?.msg && proxy.$ElMessage.error(res.msg);
171 } 241 }
...@@ -182,13 +252,24 @@ onBeforeMount(() => { ...@@ -182,13 +252,24 @@ onBeforeMount(() => {
182 </script> 252 </script>
183 253
184 <template> 254 <template>
185 <div class="table_tool_wrap" v-loading="resultDataLoading" ref="containerRef"> 255 <div class="table_tool_wrap" v-loading="resultDataLoading" ref="containerRef" :element-loading-text="loadingText">
186 <el-button style="margin-bottom: 8px;" type="primary" @click="transfer" v-preReClick>下载评估报告</el-button> 256 <el-button v-show="!isWordStyle" style="margin-bottom: 8px;" type="primary" @click="transfer"
187 <anonResultAnalysis :show-title="true" :analysis-result-info="analysisResultInfo" 257 v-preReClick>生成Word评估报告</el-button>
188 :analysis-result-loading="analysisResultLoading" :analysis-result-table-fields="analysisResultTableFields" 258 <div v-show="isWordStyle" style="margin-bottom: 8px;">
189 :old-anon-task-value-info="oldAnonTaskValueInfo" :container-width="containerWidth" 259 <el-button @click="isWordStyle = false">返回</el-button>
190 :origin-result-table-field-column="originResultTableFieldColumn" :page-info="pageInfo" :result-data="resultData" 260 <el-button type="primary" @click="downloadWord">下载评估报告</el-button>
191 @page-change="pageChange"></anonResultAnalysis> 261 </div>
262 <anonResultAnalysis ref="resultReportRef" :show-title="true" :analysis-result-info="analysisResultInfo"
263 :isWordStyle="isWordStyle" :style="isWordStyle ? {
264 height: 'calc(100% - 36px)',
265 'overflow-y': 'auto',
266 'margin-right': '-16px',
267 'padding-right': '16px',
268 width: 'auto'
269 } : null" :analysis-result-loading="analysisResultLoading"
270 :analysis-result-table-fields="analysisResultTableFields" :old-anon-task-value-info="oldAnonTaskValueInfo"
271 :container-width="containerWidth" :origin-result-table-field-column="originResultTableFieldColumn"
272 :page-info="pageInfo" :result-data="resultData" :fullResultData="fullResultData" @page-change="pageChange"></anonResultAnalysis>
192 </div> 273 </div>
193 </template> 274 </template>
194 275
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
17 formId="model-select-edit" col="col3 custom-form" @select-change="handleDataSelectFormSelectChange" 17 formId="model-select-edit" col="col3 custom-form" @select-change="handleDataSelectFormSelectChange"
18 @uploadFileChange="uploadFileChange" @checkboxChange="handleDataSelectFormCheckboxChange" /> 18 @uploadFileChange="uploadFileChange" @checkboxChange="handleDataSelectFormCheckboxChange" />
19 </ContentWrap> 19 </ContentWrap>
20 <ContentWrap id="id-previewData" title="数据抽样预览" description="" style="margin-top: 16px;"> 20 <ContentWrap v-show="formRef?.formInline?.dataSource != 3" id="id-previewData" title="数据抽样预览" description=""
21 style="margin-top: 16px;">
21 <!-- 选择抽样预览的表单设置 --> 22 <!-- 选择抽样预览的表单设置 -->
22 <Form ref="dataSimpleFormRef" :itemList="dataSimpleFormItems" :rules="dataSimpleFormRules" 23 <Form ref="dataSimpleFormRef" :itemList="dataSimpleFormItems" :rules="dataSimpleFormRules"
23 formId="data-simple-edit" col="col3 fixwidth-form" @switch-change="handleDataSimpleFormSwitchChange" 24 formId="data-simple-edit" col="col3 fixwidth-form" @switch-change="handleDataSimpleFormSwitchChange"
...@@ -47,6 +48,51 @@ ...@@ -47,6 +48,51 @@
47 </div> 48 </div>
48 </div> 49 </div>
49 </ContentWrap> 50 </ContentWrap>
51 <ContentWrap v-show="formRef?.formInline?.dataSource == 3" id="id-folder" title="提取文件" description=""
52 style="margin-top: 16px;">
53 <div class="folder-main">
54 <el-upload ref="uploadRef" class="upload-file" action="#" :file-list="folderList" :limit="1" directory
55 :http-request="(file) => hanlderUploadFolder(file)">
56 <template #trigger>
57 <el-button :icon="Upload" class="mr8">上传文件</el-button>
58 </template>
59 </el-upload>
60 <!-- 正在解析的状态 -->
61 <div class="folder-progress">
62 <div class="folder-title">正在解析</div>
63 <el-progress :percentage="30" :stroke-width="12" striped striped-flow :show-text="false" :duration="8" />
64 <div style="display: flex;"><span class="cnt">{{ '共' + changeNum(100, 0) + '个文件, 已解析30%' }}</span></div>
65 </div>
66 <!-- 解析失败的状态 -->
67 <div class="folder-progress">
68 <div class="folder-title">
69 <el-icon class="title-icon fail">
70 <CircleCloseFilled />
71 </el-icon><span>解析失败</span>
72 </div>
73 <el-progress :percentage="30" :stroke-width="12" color="#F30000" :show-text="false" />
74 <div style="display: flex;"><span class="cnt">{{ '已成功解析' + changeNum(100, 0) + '个文件, 失败' + changeNum(30,
75 0) +
76 '个文件' }}</span></div>
77 <div style="text-align: center;">
78 <el-button @click="reAnalyzing">重试</el-button>
79 </div>
80 </div>
81 <!-- 解析成功状态 -->
82 <div class="folder-progress">
83 <div class="folder-title">
84 <el-icon class="title-icon success">
85 <svg-icon name="icon-success" />
86 </el-icon><span>解析成功</span>
87 </div>
88 <el-progress :percentage="30" :stroke-width="12" color="#F30000" :show-text="false" />
89 <div style="display: flex;"><span class="cnt">{{ '已成功解析' + changeNum(100, 0) + '个文件, 失败' + changeNum(30,
90 0) +
91 '个文件' }}</span></div>
92 </div>
93 </div>
94
95 </ContentWrap>
50 </div> 96 </div>
51 <!-- 第二步 配置匿名化方案,单独抽取vue组件页面 --> 97 <!-- 第二步 配置匿名化方案,单独抽取vue组件页面 -->
52 <anonTaskStepTwo ref="anonTaskStepTwoRef" v-show="step == 1" :anonTaskRules="detailInfo.anonTaskRules" 98 <anonTaskStepTwo ref="anonTaskStepTwoRef" v-show="step == 1" :anonTaskRules="detailInfo.anonTaskRules"
...@@ -70,13 +116,19 @@ ...@@ -70,13 +116,19 @@
70 <div v-show="analysisResultInfo.errorMsg" class="error-desc">{{ '【' + analysisResultInfo.errorMsg + '】' }} 116 <div v-show="analysisResultInfo.errorMsg" class="error-desc">{{ '【' + analysisResultInfo.errorMsg + '】' }}
71 </div> 117 </div>
72 </div> 118 </div>
73 <anonResultAnalysis v-show="isExecEnd && analysisResultInfo.status == 'Y'" :analysis-result-info="analysisResultInfo" 119 <anonResultAnalysis v-show="isExecEnd && analysisResultInfo.status == 'Y'" ref="resultReportRef" v-loading="downloadLoading"
74 :analysis-result-loading="analysisResultLoading" :analysis-result-table-fields="analysisResultTableFields" :old-anon-task-value-info="oldAnonTaskValueInfo" 120 :analysis-result-info="analysisResultInfo" :is-word-style="isWordStyle" :element-loading-text="loadingText"
75 :container-width="containerWidth" :origin-result-table-field-column="originResultTableFieldColumn" :page-info="pageInfo" 121 :analysis-result-loading="analysisResultLoading" :analysis-result-table-fields="analysisResultTableFields"
76 :result-data="resultData" @page-change="pageChange"></anonResultAnalysis> 122 :old-anon-task-value-info="oldAnonTaskValueInfo" :container-width="containerWidth"
123 :origin-result-table-field-column="originResultTableFieldColumn" :page-info="pageInfo"
124 :result-data="resultData" :fullResultData="fullResultData" @page-change="pageChange"></anonResultAnalysis>
77 <template #header> 125 <template #header>
78 <el-button v-show="isExecEnd && analysisResultInfo.status == 'Y'" type="primary" v-loading="!!downPromise" 126 <el-button v-show="isExecEnd && analysisResultInfo.status == 'Y' && !isWordStyle" type="primary"
79 @click="transfer">下载评估报告</el-button> 127 v-loading="!!downPromise" @click="transfer">生成Word评估报告</el-button>
128 <div v-show="isWordStyle">
129 <el-button @click="isWordStyle = false">返回</el-button>
130 <el-button type="primary" @click="downloadWord">下载评估报告</el-button>
131 </div>
80 </template> 132 </template>
81 </ContentWrap> 133 </ContentWrap>
82 </div> 134 </div>
...@@ -105,7 +157,9 @@ ...@@ -105,7 +157,9 @@
105 <el-button v-show="formRef?.formInline?.handleType != '02'" type="primary" 157 <el-button v-show="formRef?.formInline?.handleType != '02'" type="primary"
106 :disabled="analysisResultInfo.status == 'R' || (isExecEnd && analysisResultInfo.status == 'E')" 158 :disabled="analysisResultInfo.status == 'R' || (isExecEnd && analysisResultInfo.status == 'E')"
107 @click="changeStep(4)">下一步</el-button> 159 @click="changeStep(4)">下一步</el-button>
108 <el-button type="primary" v-show="formRef?.formInline?.handleType == '02'" :disabled="analysisResultInfo.status == 'R' || (isExecEnd && analysisResultInfo.status == 'E')" v-preReClick @click="closeTask">关闭</el-button> 160 <el-button type="primary" v-show="formRef?.formInline?.handleType == '02'"
161 :disabled="analysisResultInfo.status == 'R' || (isExecEnd && analysisResultInfo.status == 'E')" v-preReClick
162 @click="closeTask">关闭</el-button>
109 </template> 163 </template>
110 <template v-else> 164 <template v-else>
111 <el-button @click="changeStep(3)">上一步</el-button> 165 <el-button @click="changeStep(3)">上一步</el-button>
...@@ -131,6 +185,7 @@ import { ...@@ -131,6 +185,7 @@ import {
131 updateAnonTask, 185 updateAnonTask,
132 exportAnonExecData, 186 exportAnonExecData,
133 exportAnonReport, 187 exportAnonReport,
188 htmlToWord,
134 } from '@/api/modules/dataAnonymization'; 189 } from '@/api/modules/dataAnonymization';
135 import { 190 import {
136 parseAndDecodeUrl, 191 parseAndDecodeUrl,
...@@ -154,8 +209,9 @@ import anonResultView from './anonResultView.vue'; ...@@ -154,8 +209,9 @@ import anonResultView from './anonResultView.vue';
154 import useDataAnonymizationStore from "@/store/modules/dataAnonymization"; 209 import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
155 import { RefreshRight, CircleCloseFilled, Right } from "@element-plus/icons-vue"; 210 import { RefreshRight, CircleCloseFilled, Right } from "@element-plus/icons-vue";
156 import { commonPageConfig } from '@/components/PageNav'; 211 import { commonPageConfig } from '@/components/PageNav';
157 import { QuestionFilled } from "@element-plus/icons-vue"; 212 import { Upload } from "@element-plus/icons-vue";
158 import anonResultAnalysis from './components/anonResultAnalysis.vue'; 213 import anonResultAnalysis from './components/anonResultAnalysis.vue';
214 import html2canvas from 'html2canvas';
159 215
160 const anonymizationStore = useDataAnonymizationStore(); 216 const anonymizationStore = useDataAnonymizationStore();
161 const { proxy } = getCurrentInstance() as any; 217 const { proxy } = getCurrentInstance() as any;
...@@ -845,6 +901,19 @@ const uploadFileChange = (file) => { ...@@ -845,6 +901,19 @@ const uploadFileChange = (file) => {
845 parseFileData(fileRaw); 901 parseFileData(fileRaw);
846 } 902 }
847 903
904 /*** ----------------------- 解析扫描文件 ------------------------------ */
905 const folderList: any = ref([]);
906
907 const hanlderUploadFolder = (file) => {
908 debugger
909 return Promise.resolve();
910 }
911
912 /** 重试 */
913 const reAnalyzing = () => {
914
915 }
916
848 /** 第二步的配置组件引用。 */ 917 /** 第二步的配置组件引用。 */
849 const anonTaskStepTwoRef = ref(); 918 const anonTaskStepTwoRef = ref();
850 919
...@@ -1259,7 +1328,7 @@ const processStepThreeResultView = (isRefresh = false) => { ...@@ -1259,7 +1328,7 @@ const processStepThreeResultView = (isRefresh = false) => {
1259 refreshTimer.value = null; 1328 refreshTimer.value = null;
1260 analysisResultTableFields.value = res.data?.column || []; 1329 analysisResultTableFields.value = res.data?.column || [];
1261 pageInfo.value.curr = 1; 1330 pageInfo.value.curr = 1;
1262 getAnalysisResultPageData(); 1331 getAnalysisResultPageData(true);
1263 } else if (analysisResultInfo.value.status == 'E') { 1332 } else if (analysisResultInfo.value.status == 'E') {
1264 isExecEnd.value = true 1333 isExecEnd.value = true
1265 refreshTimer.value && clearInterval(refreshTimer.value); 1334 refreshTimer.value && clearInterval(refreshTimer.value);
...@@ -1302,6 +1371,9 @@ const originResultTableFieldColumn = ref({}); ...@@ -1302,6 +1371,9 @@ const originResultTableFieldColumn = ref({});
1302 /** 结果分析中的字段表格数据 */ 1371 /** 结果分析中的字段表格数据 */
1303 const resultData: any = ref([]); 1372 const resultData: any = ref([]);
1304 1373
1374 /** 不分页的全部数据 */
1375 const fullResultData: any = ref([]);
1376
1305 /** 结果分析中的字段信息 */ 1377 /** 结果分析中的字段信息 */
1306 const analysisResultTableFields: any = ref([]); 1378 const analysisResultTableFields: any = ref([]);
1307 1379
...@@ -1329,16 +1401,30 @@ watch( ...@@ -1329,16 +1401,30 @@ watch(
1329 } 1401 }
1330 ); 1402 );
1331 1403
1332 const getAnalysisResultPageData = () => { 1404 const getAnalysisResultPageData = (isFull = false) => {
1333 analysisResultLoading.value = true; 1405 analysisResultLoading.value = true;
1334 getAnonAnalyzePageData({ 1406 getAnonAnalyzePageData({
1335 pageIndex: pageInfo.value.curr, 1407 pageIndex: pageInfo.value.curr,
1336 pageSize: pageInfo.value.limit, 1408 pageSize: isFull ? -1 : pageInfo.value.limit,
1337 taskExecGuid: taskExecGuid.value, 1409 taskExecGuid: taskExecGuid.value,
1338 }).then((res: any) => { 1410 }).then((res: any) => {
1339 analysisResultLoading.value = false; 1411 analysisResultLoading.value = false;
1340 if (res?.code == proxy.$passCode) { 1412 if (res?.code == proxy.$passCode) {
1341 pageInfo.value.rows = 1413 if (isFull) {
1414 fullResultData.value = [];
1415 res.data?.records?.forEach(d => {
1416 let obj = {};
1417 analysisResultTableFields.value.forEach(t => {
1418 obj[t.enName] = d.fieldValue?.[t.enName];
1419 });
1420 obj['equivalenceClassNum'] = changeNum(d.equivalenceClassNum || 0, 0);
1421 obj['reIdentifyRisk'] = changeNum(d.reIdentifyRisk || 0, 2);
1422 obj['isGtThreshold'] = d.isGtThreshold;
1423 fullResultData.value.push(obj);
1424 });
1425 resultData.value = fullResultData.value.slice(0, pageInfo.value.limit);
1426 pageInfo.value.rows = fullResultData.value.length;
1427 } else {
1342 resultData.value = []; 1428 resultData.value = [];
1343 res.data?.records?.forEach(d => { 1429 res.data?.records?.forEach(d => {
1344 let obj = {}; 1430 let obj = {};
...@@ -1351,6 +1437,7 @@ const getAnalysisResultPageData = () => { ...@@ -1351,6 +1437,7 @@ const getAnalysisResultPageData = () => {
1351 resultData.value.push(obj); 1437 resultData.value.push(obj);
1352 }); 1438 });
1353 pageInfo.value.rows = res.data?.totalRows ?? 0; 1439 pageInfo.value.rows = res.data?.totalRows ?? 0;
1440 }
1354 } else { 1441 } else {
1355 proxy.$ElMessage.error(res.msg); 1442 proxy.$ElMessage.error(res.msg);
1356 } 1443 }
...@@ -1359,24 +1446,76 @@ const getAnalysisResultPageData = () => { ...@@ -1359,24 +1446,76 @@ const getAnalysisResultPageData = () => {
1359 1446
1360 const downPromise: any = ref() 1447 const downPromise: any = ref()
1361 1448
1449 const isWordStyle = ref(false);
1362 /** 下载评估报告 */ 1450 /** 下载评估报告 */
1363 const transfer = () => { 1451 const transfer = () => {
1452 isWordStyle.value = true;
1453 }
1454
1455 const domClone: any = ref(null);
1456
1457 const resultReportRef = ref();
1458
1459 const convertHtml2Img = (dom, domClone) => {
1460 const element = <HTMLElement>dom.querySelector('.kpi-content')
1461 if (!element) {
1462 return Promise.resolve();
1463 }
1464 return html2canvas(element, {
1465 allowTaint: true,
1466 useCORS: true,
1467 scale: 2,
1468 }).then((canvas: any) => {
1469 document.documentElement.scrollTop = 0;
1470 document.body.scrollTop = 0;
1471 element.parentNode && ((<HTMLElement>element.parentNode).scrollTop = 0);
1472 let url = canvas.toDataURL('image/jpeg');
1473 let img = document.createElement('img');
1474 if (url) {
1475 // img.src = url.split(',')[1];
1476 img.src = url;
1477 img.width = 620;
1478 img.height = 265;
1479 img.crossOrigin = 'Anonymous';
1480 }
1481 const copyElement = <HTMLElement>domClone.querySelector('.kpi-content')
1482 copyElement.parentNode?.replaceChild(img, copyElement);
1483 })
1484 }
1485
1486 const loadingText = ref('');
1487
1488 const downloadLoading = ref(false);
1489
1490 const getHTML = (reportResultContent) => {
1491 let html = reportResultContent;
1492 html = html.replace(/"/g, "'");
1493 return html;
1494 };
1495
1496 const downloadWord = () => {
1364 if (downPromise.value) { 1497 if (downPromise.value) {
1365 return; 1498 return;
1366 } 1499 }
1367 downPromise.value = exportAnonReport({ 1500 let dom = domClone.value || (domClone.value = document.createElement('div'));
1368 taskGuid: route.query.guid, 1501 let report = resultReportRef.value?.report;
1369 execGuid: taskExecGuid.value 1502 dom.innerHTML = report?.innerHTML;
1370 }).then((res: any) => { 1503 loadingText.value = '报告正在下载中,请勿关闭浏览器...';
1371 downPromise.value = null; 1504 downloadLoading.value = true;
1505 downPromise.value = convertHtml2Img(report, dom).then(() => {
1506 htmlToWord({ html: encodeURIComponent(`<div>${getHTML(dom.innerHTML)}</div>`) }).then((res: any) => {
1507 downPromise.value = null
1508 loadingText.value = '';
1509 downloadLoading.value = false;
1372 if (res && !res.msg) { 1510 if (res && !res.msg) {
1373 download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化评估报告.docx', 'word') 1511 download(res, (route.query.taskName || oldAnonTaskValueInfo.value.taskName) + '_匿名化评估报告.docx', 'word')
1374 } else { 1512 } else {
1375 res?.msg && ElMessage.error(res?.msg); 1513 res?.msg && ElMessage.error(res?.msg);
1376 } 1514 }
1515 })
1377 }).catch(() => { 1516 }).catch(() => {
1378 downPromise.value = null; 1517 downPromise.value = null;
1379 }) 1518 });
1380 } 1519 }
1381 1520
1382 onUnmounted(() => { 1521 onUnmounted(() => {
...@@ -1704,4 +1843,57 @@ onUnmounted(() => { ...@@ -1704,4 +1843,57 @@ onUnmounted(() => {
1704 justify-content: space-between; 1843 justify-content: space-between;
1705 } 1844 }
1706 } 1845 }
1846
1847 .folder-main {
1848 height: 170px;
1849 display: flex;
1850 align-items: center;
1851 justify-content: center;
1852 flex-direction: column;
1853
1854 .folder-title {
1855 font-size: 14px;
1856 color: #212121;
1857 font-weight: 600;
1858 margin-bottom: 16px;
1859 text-align: center;
1860 line-height: 21px;
1861 display: flex;
1862 justify-content: center;
1863 }
1864
1865 :deep(.el-icon) {
1866 margin-right: 8px;
1867 width: 20px;
1868 height: 20px;
1869
1870 &.fail {
1871 color: #E63E33;
1872 }
1873
1874 &.success {
1875 color: #4FA55D;
1876 }
1877
1878 svg {
1879 width: 100%;
1880 height: 100%;
1881 }
1882 }
1883
1884 .folder-progress {
1885 width: 360px;
1886
1887 .cnt {
1888 font-size: 14px;
1889 color: #212121;
1890 line-height: 21px;
1891 margin-top: 8px;
1892 }
1893
1894 .el-button {
1895 margin-top: 12px;
1896 }
1897 }
1898 }
1707 </style> 1899 </style>
...\ No newline at end of file ...\ No newline at end of file
......
1 <template> 1 <template>
2 <div class="analysis-result-main"> 2 <div style="display: flex;justify-content: center;width: 100%;">
3 <div v-show="!isWordStyle" class="analysis-result-main">
3 <div v-if="showTitle" class="result-title">匿名结果分析</div> 4 <div v-if="showTitle" class="result-title">匿名结果分析</div>
4 <div class="kpi-content" v-show="Object.keys(analysisResultInfo).length > 0"> 5 <div class="kpi-content" v-show="Object.keys(analysisResultInfo).length > 0">
5 <div class="border-content"> 6 <div class="border-content">
...@@ -53,7 +54,7 @@ ...@@ -53,7 +54,7 @@
53 <span class="text">等价类门限风险<el-tooltip placement="top" effect="light" popper-class="table_tooltip"> 54 <span class="text">等价类门限风险<el-tooltip placement="top" effect="light" popper-class="table_tooltip">
54 <template #content> 55 <template #content>
55 <div style="max-width: 236px;"> 56 <div style="max-width: 236px;">
56 完全公开共享数据发布,门限阈值取值 1/20;受控公开共享数据发布,门限阈值取值 1/5;领地公开共享数据发布,门限阈值取值 57 完全公开共享数据发布,门限阈值取值 1/20;受控公开共享数据发布,门限阈值取值 1/5;领地公开共享数据发布,门限阈值取值
57 1/3;等价类门限风险为:等价类的重标识风险大于门限阈值为1,小于等于为0,求和后除以等价类个数。 58 1/3;等价类门限风险为:等价类的重标识风险大于门限阈值为1,小于等于为0,求和后除以等价类个数。
58 </div> 59 </div>
59 </template> 60 </template>
...@@ -89,8 +90,8 @@ ...@@ -89,8 +90,8 @@
89 </div> 90 </div>
90 <div class="result-title">重标识风险表</div> 91 <div class="result-title">重标识风险表</div>
91 <el-table ref="tableRef" v-show="analysisResultTableFields.length" :data="resultData" 92 <el-table ref="tableRef" v-show="analysisResultTableFields.length" :data="resultData"
92 v-loading="analysisResultLoading" :highlight-current-row="true" stripe border tooltip-effect="light" height="100%" 93 v-loading="analysisResultLoading" :highlight-current-row="true" stripe border tooltip-effect="light"
93 row-key="guid" :style="{ width: '100%', height: '280px' }"> 94 height="100%" row-key="guid" :style="{ width: '100%', height: '280px' }">
94 <el-table-column label="等价类" type="index" width="68px" align="center" show-overflow-tooltip> 95 <el-table-column label="等价类" type="index" width="68px" align="center" show-overflow-tooltip>
95 <template #default="scope"> 96 <template #default="scope">
96 <span>{{ 97 <span>{{
...@@ -134,7 +135,8 @@ ...@@ -134,7 +135,8 @@
134 :style="{ height: oldAnonTaskValueInfo.dataSharingTypeCode != '01' ? (containerWidth > 1397 ? '378px' : (containerWidth < 1048 ? '414px' : '396px')) : 'auto' }"> 135 :style="{ height: oldAnonTaskValueInfo.dataSharingTypeCode != '01' ? (containerWidth > 1397 ? '378px' : (containerWidth < 1048 ? '414px' : '396px')) : 'auto' }">
135 <el-table-column label="序号" type="index" width="56px" align="center" show-overflow-tooltip> 136 <el-table-column label="序号" type="index" width="56px" align="center" show-overflow-tooltip>
136 </el-table-column> 137 </el-table-column>
137 <el-table-column label="数据项" prop="chName" width="150px" align="left" show-overflow-tooltip></el-table-column> 138 <el-table-column label="数据项" prop="chName" width="150px" align="left"
139 show-overflow-tooltip></el-table-column>
138 <el-table-column label="唯一性分值" prop="uniqueScore" width="110px" align="right" 140 <el-table-column label="唯一性分值" prop="uniqueScore" width="110px" align="right"
139 show-overflow-tooltip></el-table-column> 141 show-overflow-tooltip></el-table-column>
140 <el-table-column label="影响力分值" prop="influenceScore" width="110" align="right" 142 <el-table-column label="影响力分值" prop="influenceScore" width="110" align="right"
...@@ -187,12 +189,161 @@ ...@@ -187,12 +189,161 @@
187 </div> 189 </div>
188 </div> 190 </div>
189 </div> 191 </div>
192 <div v-show="isWordStyle" class="analysis-result-main report-main" ref="report">
193 <div
194 style="font-family: simsun;height: 40px;display: block;line-height: 32px;font-size: 18px;color: #212121;font-weight: 700;text-align: center;margin-top: 10px;margin-bottom: 10px;">匿名化效果评估指标</div>
195 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;white-space: pre-wrap;color: #212121;">{{ ' 依据GB/T 42460-2023《信息安全技术个人信息去标识化效果评估指南》附录D,对去标识化以后的数据集基于K匿名模型进行去标识化效果的评估。' }}</p>
196 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;white-space: pre-wrap;color: #212121;">
197 {{ ` 总体方案为先计算数据集每行记录、整体数据集的重标识风险,进而计算环境重标识攻击概率,最后再结合环境重标识攻击概率计算整个数据集的重标识整体风险。去标识化效果评估结果为${analysisResultInfo.rating + '级'}。具体指标如下:` }}</p>
198 <div style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700;margin: 8px 0px;">匿名化结果分析</div>
199 <div class="kpi-content" v-show="Object.keys(analysisResultInfo).length > 0">
200 <div class="border-content">
201 <div class="text" style="color: #212121;">去标识化效果评估结果</div>
202 <span class="number score-color">{{ analysisResultInfo.rating + '级' }}</span>
203 </div>
204 <div class="border-content">
205 <span class="text" style="color: #212121;">重标识风险最大值</span>
206 <span class="number">{{ analysisResultInfo.reIdentifyRiskRb != null ?
207 (analysisResultInfo.reIdentifyRiskRb || 0) : '--'
208 }}</span>
209 </div>
210 <div class="border-content">
211 <span class="text" style="color: #212121;">重标识风险平均值</span>
212 <span class="number">{{ analysisResultInfo.reIdentifyRiskRc != null ?
213 (analysisResultInfo.reIdentifyRiskRc || 0) : '--' }}</span>
214 </div>
215 <div class="border-content">
216 <span class="text" style="color: #212121;">环境重标识攻击概率</span>
217 <span class="number">{{ analysisResultInfo.envReAttackPr != null ?
218 (analysisResultInfo.envReAttackPr || 0) : '--' }}</span>
219 </div>
220 <div class="border-content">
221 <span class="text" style="color: #212121;">等价类门限风险</span>
222 <span class="number">{{ analysisResultInfo.reIdentifyRiskRa != null ?
223 (analysisResultInfo.reIdentifyRiskRa || 0) : '--' }}</span>
224 </div>
225 <div class="border-content">
226 <span class="text" style="color: #212121;">重标识风险总体风险</span>
227 <span class="number">{{ analysisResultInfo.reIdentifyOverallRisk != null ?
228 (analysisResultInfo.reIdentifyOverallRisk || 0) : '--' }}</span>
229 </div>
230 <div class="border-content">
231 <span class="text" style="color: #212121;">重标识可接受风险阈值</span>
232 <span class="number">{{ oldAnonTaskValueInfo.anonPrivacyMode?.riskThreshold == null ? 0.05 :
233 (oldAnonTaskValueInfo.anonPrivacyMode?.riskThreshold || 0) }}</span>
234 </div>
235 </div>
236 <div style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700;margin: 8px 0px 4px;">重标识风险表</div>
237 <div style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;margin-bottom: 4px;white-space: pre-wrap;">{{ ' 门限阈值的取值:完全公开共享数据发布,取值 1/20;受控公开共享数据发布,取值 1/5;领地公开共享数据发布,取值 1/3。' }}</div>
238 <table border="0" cellspacing="0"
239 style="width:100%;word-break: break-all;margin: 0 auto;text-align: center;border-collapse: collapse;color: #212121">
240 <thead>
241 <tr>
242 <th v-for="(item, index) in resultTableFields" :key="index" style="border: 0.5px solid #d9d9d9">
243 <span>{{ item.chName }}</span>
244 </th>
245 </tr>
246 </thead>
247 <tbody>
248 <tr v-for="(recordItem, j) in fullResultData" :key="j">
249 <td v-for="(columnItem, i) in resultTableFields" :key="i"
250 :style="{border: '0.5px solid #d9d9d9', 'text-align': <any>(getTextAlign(columnItem) ?? 'left') }">
251 <span :style="{ 'word-break': 'break-all' }">
252 {{ columnItem.enName == 'index' ? (j + 1) : formatterPreviewDate(recordItem, columnItem) }}
253 </span>
254 </td>
255 </tr>
256 </tbody>
257 </table>
258 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
259 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700;margin: 8px 0px 4px;">内部故意攻击概率</div>
260 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
261 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;margin-bottom: 4px;white-space: pre-wrap;">{{ ' 重标识数据的动机和能力为低,从重标识攻击可能性分析表可得出在内部攻击方面,重标识攻击概率的取值为0.05。'}}</div>
262 <table v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'" border="0" cellspacing="0"
263 style="width:100%;word-break: break-all;margin: 0 auto;text-align: center;border-collapse: collapse;color: #212121;">
264 <thead>
265 <tr>
266 <th v-for="(item, index) in innerResultFields" :key="index" style="border: 0.5px solid #d9d9d9">
267 <span>{{ item.label }}</span>
268 </th>
269 </tr>
270 </thead>
271 <tbody>
272 <tr v-for="(recordItem, j) in innerResultData" :key="j">
273 <template v-for="(columnItem, i) in innerResultFields" :key="i">
274 <td :rowspan="i == 0 ? 3 : 1" v-show="!(j % 3 != 0 && i == 0)"
275 :style="{ border: '0.5px solid #d9d9d9', 'text-align': <any>(columnItem.align ?? 'left') }">
276 <span :style="{ 'word-break': 'break-all' }">
277 {{ recordItem[columnItem.field] }}
278 </span>
279 </td>
280 </template>
281 </tr>
282 </tbody>
283 </table>
284 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
285 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700;margin: 8px 0px;">数据集包含熟人概率</div>
286 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
287 style="font-family: simsun;text-align: center; display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700">pr=1-1-p^m</div>
288 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
289 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;white-space: pre-wrap;">
290 {{ ` 数据集包含熟人概率可通过以上公式计算,${new Date().getFullYear()}年我国最新的人口统计为${analysisResultInfo?.allPerson ||
291 0}亿人,其中该数据集的容量为${analysisResultInfo?.dataSetNum || 0}万,占总人口的比例为${analysisResultInfo.patientPopulationRate ||
292 0}%,m值取推荐值150,数据集包含熟人的概率为${analysisResultInfo.randomAcquaintancePr || 0}。` }}</div>
293 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
294 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700;margin: 8px 0px;">数据泄露概率
295 </div>
296 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
297 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;white-space: pre-wrap;">{{
298 ' 对于安全和隐私控制能力评估为低的情况,推荐将数据泄露概率设定为0.55。' }}</div>
299 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
300 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;white-space: pre-wrap;">{{ ' 对于安全和隐私控制能力评估为中的情况,推荐将数据泄露概率设定为0.27。' }}</div>
301 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
302 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;white-space: pre-wrap;">{{ ' 对于安全和隐私控制能力评估为高的情况,推荐将数据泄露概率设定为 0.14。' }}</div>
303 <div v-show="oldAnonTaskValueInfo.dataSharingTypeCode != '01'"
304 style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;white-space: pre-wrap;">{{ ` 数据接收方的安全和隐私控制能力为高,按照推荐值将数据泄露概率设定为${analysisResultInfo.dataBreachPr || 0}。` }}</div>
305 <div style="font-family: simsun;display: block;line-height: 21px;font-size: 14px;color: #212121;font-weight: 700;margin: 8px 0px;">备注:</div>
306 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;margin-top: 8px;white-space: pre-wrap;">{{ ' 重标识风险最大值:所有等价类的重标识风险最大值;' }}</p>
307 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 重标识风险平均值:所有等价类的重标识风险平均值;'}}</p>
308 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 环境重标识攻击概率:完全公开共享数据发布,攻击者对数据集进行环境重标识攻击的概率为1。领地公开共享与受控公开共享数据发布,环境重标识攻击概率为内部故意攻击概率、数据集包含熟人概率和数据泄露概率三者的最大值;' }}</p>
309 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 等价类门限风险:完全公开共享数据发布,门限阈值取值1/20;受控公开共享数据发布,门限阈值取值 1/5;领地公开共享数据发布,门限阈值取值 1/3;等价类门限风险为:等价类的重标识风险大于门限阈值为1,小于等于为0,求和后除以等价类个数;'}}</p>
310 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 重标识风险总体风险:完全公开共享,当等价类门限风险=0时,重标识风险总体风险公式为等价类重标识风险最大值*环境重标识攻击概率;当等价类门限风险!=0时,重标识风险总体风险为1。' }}</p>
311 <p style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 受控公开共享和领地公开共享,当等价类门限风险=0时,重标识风险总体风险公式为等价类重标识风险平均值*环境重标识攻击概率;当等价类门限风险!=0时,重标识风险总体风险为1。' }}</p>
312 <p v-show="analysisResultInfo.rating >= 3" style="font-family: simsun;margin: 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 依据《数据安全技术个人信息匿名化处理指南及评价方法(征求意见稿)》4.2,去标识化的数据集达到3级可展开对数据级的对抗性测试和不可复原性核验。对抗性测试关键变量如下表:' }}</p>
313 <table border="0" cellspacing="0" v-show="analysisResultInfo.rating >= 3"
314 style="width:100%;word-break: break-all;margin: 4px auto 0px auto;text-align: center;border-collapse: collapse;color: #212121;">
315 <thead>
316 <tr>
317 <th v-for="(item, index) in adversarialTestFields" :key="index" style="border: 0.5px solid #d9d9d9">
318 <span>{{ item.label }}</span>
319 </th>
320 </tr>
321 </thead>
322 <tbody>
323 <tr v-for="(recordItem, j) in analysisResultInfo?.adversarialTest || []" :key="j">
324 <td v-for="(columnItem, i) in adversarialTestFields" :key="i"
325 :style="{ border: '0.5px solid #d9d9d9', 'text-align': <any>(columnItem.align ?? 'left') }">
326 <span :style="{ 'word-break': 'break-all' }">
327 {{ columnItem.field == 'index' ? (j + 1) : recordItem[columnItem.field] === 0 ? 0 : (recordItem[columnItem.field] || '--') }}
328 </span>
329 </td>
330 </tr>
331 </tbody>
332 </table>
333 <p v-show="analysisResultInfo.rating >= 3" style="font-family: simsun;margin: 4px 0px 0px;font-size: 14px;line-height: 21px;color: #212121;white-space: pre-wrap;">{{ ' 通过识别关键变量,选择标识度高于0.5的属性作为攻击测试的关键变量,在攻击测试中,涉及到的攻击场景,其重标识概率均远小于所接受的风险水平0.05。另外,未发现任何其它可能的安全性问题。' }}</p>
334 </div>
335 </div>
190 </template> 336 </template>
191 <script lang="ts" setup name="anonResultAnalysis"> 337 <script lang="ts" setup name="anonResultAnalysis">
192 import { TableColumnWidth } from '@/utils/enum'; 338 import { TableColumnWidth } from '@/utils/enum';
339 import { QuestionFilled } from "@element-plus/icons-vue";
193 import Moment from 'moment'; 340 import Moment from 'moment';
194 341
195 defineProps({ 342 const props = defineProps({
343 isWordStyle: { //显示的是否是word报告的样式
344 type: Boolean,
345 default: false,
346 },
196 showTitle: { 347 showTitle: {
197 type: Boolean, 348 type: Boolean,
198 default: false, 349 default: false,
...@@ -213,7 +364,11 @@ defineProps({ ...@@ -213,7 +364,11 @@ defineProps({
213 type: Object, 364 type: Object,
214 default: {}, 365 default: {},
215 }, 366 },
216 resultData: { 367 resultData: { //分页后的数据
368 type: Array,
369 default: []
370 },
371 fullResultData: { //全部数据,下载word时使用。
217 type: Array, 372 type: Array,
218 default: [] 373 default: []
219 }, 374 },
...@@ -231,11 +386,33 @@ defineProps({ ...@@ -231,11 +386,33 @@ defineProps({
231 }, 386 },
232 }) 387 })
233 388
389 const report = ref();
390
391 const resultTableFields = computed(() => {
392 let arr: any[] = [{ chName: '等价类', dataType: 'int', enName: 'index' }]
393 return arr.concat(props.analysisResultTableFields).concat([{
394 chName: '等价类大小',
395 enName: 'equivalenceClassNum',
396 dataType: 'int'
397 }, {
398 chName: '重标识风险',
399 enName: 'reIdentifyRisk',
400 dataType: 'decimal'
401 }, {
402 chName: '判断重风险是否大于门限阈值',
403 enName: 'isGtThreshold',
404 dataType: 'text'
405 }]);
406 })
407
234 const emits = defineEmits([ 408 const emits = defineEmits([
235 "pageChange" 409 "pageChange"
236 ]); 410 ]);
237 411
238 const getTextAlign = (field) => { 412 const getTextAlign = (field) => {
413 if (field.enName === 'index') {
414 return 'center';
415 }
239 if (field.dataType === 'decimal' || field.dataType === 'int') { 416 if (field.dataType === 'decimal' || field.dataType === 'int') {
240 return 'right'; 417 return 'right';
241 } 418 }
...@@ -265,6 +442,17 @@ const formatterPreviewDate = (row, info) => { ...@@ -265,6 +442,17 @@ const formatterPreviewDate = (row, info) => {
265 }; 442 };
266 443
267 /** 内部故意攻击概率的表格 */ 444 /** 内部故意攻击概率的表格 */
445 const innerResultFields = ref([{
446 field: 'level',
447 label: '风险减缓控制水平'
448 }, {
449 field: 'competencyLevel',
450 label: '动机和能力'
451 }, {
452 field: 'value',
453 label: '重标识攻击概率',
454 align: 'right'
455 }]);
268 const innerResultData = ref([{ 456 const innerResultData = ref([{
269 guid: '1', 457 guid: '1',
270 level: '高', 458 level: '高',
...@@ -350,15 +538,42 @@ const handleInnerCellClass = ({ row, column, rowIndex, columnIndex }) => { ...@@ -350,15 +538,42 @@ const handleInnerCellClass = ({ row, column, rowIndex, columnIndex }) => {
350 return ''; 538 return '';
351 } 539 }
352 540
541 /** 对抗性测试变量表格字段配置 */
542 const adversarialTestFields = ref([{
543 field: 'index',
544 label: '序号',
545 align: 'center',
546 }, {
547 field: 'chName',
548 label: '数据项'
549 }, {
550 field: 'uniqueScore',
551 label: '唯一性分值',
552 align: 'right'
553 }, {
554 field: 'influenceScore',
555 label: '影响力分值',
556 align: 'right'
557 }, {
558 field: 'dataAttrIdentScore',
559 label: '数据属性标识度分值',
560 align: 'right'
561 }]);
562
353 const pageChange = (info) => { 563 const pageChange = (info) => {
354 emits('pageChange', info); 564 emits('pageChange', info);
355 } 565 }
356 566
567 defineExpose({
568 report,
569 })
570
357 </script> 571 </script>
358 572
359 <style lang="scss" scoped> 573 <style lang="scss" scoped>
360 .analysis-result-main { 574 .analysis-result-main {
361 min-height: 250px; 575 min-height: 250px;
576 width: 100%;
362 577
363 .value-desc { 578 .value-desc {
364 font-size: 14px; 579 font-size: 14px;
...@@ -480,4 +695,17 @@ const pageChange = (info) => { ...@@ -480,4 +695,17 @@ const pageChange = (info) => {
480 color: #b2b2b2; 695 color: #b2b2b2;
481 } 696 }
482 } 697 }
698
699 .report-main.analysis-result-main {
700 width: 625px;
701
702 .kpi-content {
703 margin-bottom: 0px;
704
705 .border-content {
706 width: calc(33% - 8px);
707 min-width: 168px;
708 }
709 }
710 }
483 </style> 711 </style>
...\ No newline at end of file ...\ No newline at end of file
......
1 { 1 {
2 "compilerOptions": { 2 "compilerOptions": {
3 "noUnusedLocals": true,
4 "noUnusedParameters": true,
3 "target": "ESNext", 5 "target": "ESNext",
4 "useDefineForClassFields": true, 6 "useDefineForClassFields": true,
5 "module": "ESNext", 7 "module": "ESNext",
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!