添加导出fpd报告功能

This commit is contained in:
limengnan 2025-12-08 15:21:37 +08:00
parent ed72ce3cd1
commit bf359d82cd
2 changed files with 544 additions and 43 deletions

View File

@ -9,7 +9,7 @@
<img src="@/assets/archive/close.png" alt="" @click="closeCancel" style="cursor: pointer;">
</div>
<div class="generateReport-container-body">
<el-scrollbar height="calc(100%)" style="padding:0 90px; box-sizing: border-box;" id="pdf-content">
<el-scrollbar height="calc(100%)" style="padding:0 90px; box-sizing: border-box;">
<div class="generateReport-container-bodytitle">体态测量报告单</div>
<div class="generateReport-container-display">
<div class="generateReport-container-userinfotext">检测时间{{ detectionInfo.start_time }}</div>
@ -200,25 +200,29 @@
</div>
<SelectData v-if="isSelectData" :imageList="imageList" @closeSelectData="closeSelectData"/>
<PopUpReport v-if="isPopUpReport"
:selectedPatient="selectedPatient"
:detectionInfo="detectionInfo"
:selectIds="[rawData.id,calibrationData.id]"
@closePopUpReport="closePopUpReport"
/>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { patientAPI, detectionAPI,historyAPI,getBackendUrl } from '@/services/api.js'
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import { historyAPI,getBackendUrl } from '@/services/api.js'
// import { ipcRenderer } from 'electron'
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import SelectData from '@/views/SelectData.vue'
import PopUpReport from '@/views/PopUpReport.vue'
const emit = defineEmits([ 'closeGenerateReport' ]);
const props = defineProps({
selectedPatient: {
required: false,
type: Object,
default: null
default: {}
},
detectionId: {
@ -227,6 +231,7 @@ const props = defineProps({
default: ""
}
})
const isPopUpReport = ref(false)
const BACKEND_URL = getBackendUrl()
const rawData = ref({}) //
const calibrationData = ref({}) //
@ -291,6 +296,11 @@ function closeCancel() {
emit("closeGenerateReport",false)
}
function confirmCancel() {
if(rawData.value.id == null){
ElMessage.error('请选择原始数据')
return
}
isPopUpReport.value = true
// generatePDF()
// emit("closeGenerateReport",false)
}
@ -321,41 +331,9 @@ function closeSelectData(is,val){
}
isSelectData.value = false
}
const generatePDF = async () => {
// 1. id DOM
const element = document.getElementById('pdf-content');
// 2. Canvas
const canvas = await html2canvas(element, {
scale: 2, //
useCORS: true, //
});
// 3. PDF
const pdf = new jsPDF('p', 'mm', 'a4'); // A4
const imgData = canvas.toDataURL('image/jpeg', 1.0);
// PDF
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
// PDF
pdf.addImage(imgData, 'JPEG', 0, 0, pdfWidth, pdfHeight);
// 4. PDF Blob
const pdfBlob = pdf.output('blob');
// 5. 使
// const formData = new FormData(); formData.append('file', pdfBlob);
//
const url = URL.createObjectURL(pdfBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.pdf';
a.click();
};
function closePopUpReport() {
isPopUpReport.value = false
}
</script>
<style scoped>

View File

@ -0,0 +1,523 @@
<template>
<div class="PopUpReport-container">
<div class="PopUpReport-container-body" id="pdf-content">
<div style="height: 100%; padding:0 90px; box-sizing: border-box;">
<div class="PopUpReport-container-bodytitle">体态测量报告单</div>
<div class="PopUpReport-container-display">
<div class="PopUpReport-container-userinfotext">检测时间{{ detectionInfo.start_time }}</div>
<div class="PopUpReport-container-userinfotext">ID{{ detectionInfo.id }}</div>
</div>
<div class="PopUpReport-container-userinfodisplay">
<div class="PopUpReport-container-userinfotext2">
ID{{ selectedPatient.id }}
</div>
<div class="PopUpReport-container-userinfotext2 width-210">
姓名{{ selectedPatient.name }}
</div>
<div class="PopUpReport-container-userinfotext2 width-195">
性别{{ selectedPatient.gender }}
</div>
<div class="PopUpReport-container-userinfotext2 width-195">
年龄{{ calculateAge(selectedPatient.birth_date) }}
</div>
<div class="PopUpReport-container-userinfotext2 width-235">
身高{{ selectedPatient.height }}cm
</div>
<div class="PopUpReport-container-userinfotext2 width-215">
体重{{ selectedPatient.weight }}kg
</div>
<div class="PopUpReport-container-userinfotext2 width-95">
鞋码{{ selectedPatient.shoe_size }}
</div>
<div class="PopUpReport-container-userinfotext2">
电话{{ selectedPatient.phone }}
</div>
<div class="PopUpReport-container-userinfotext2 width-405">
邮箱{{ selectedPatient.email }}
</div>
<div class="PopUpReport-container-userinfotext2 width-430">
居住地{{ selectedPatient.residence }}
</div>
<div class="PopUpReport-container-userinfotext2 width-310">
职业{{ selectedPatient.occupation }}
</div>
</div>
<div class="PopUpReport-container-testdatatitle">检测数据</div>
<div class="PopUpReport-containerdisplay">
<div class="PopUpReport-container-leftbox">
<div class="displayflex">
<div class="displayflextext1">原始数据</div>
<div class="displayflextext1">
{{ rawData.order }}
</div>
</div>
<!-- 选中后显示内容 -->
<div v-if="rawData.order && rawData.order!=''">
<div class="PopUpReport-content-title">整体数据</div>
<div style="width: 600px;height: 387px;">
<img :src="BACKEND_URL+'/' + rawData.screen_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
<div class="PopUpReport-content-title">身体姿态</div>
<div style="width: 100%;height: 454px; display: flex;justify-content: center;">
<img :src="BACKEND_URL+'/' + rawData.body_image" alt="" srcset="" style="width: 100%;height: 100%;
object-fit:contain; ">
</div>
<div class="PopUpReport-content-title">头部姿态</div>
<div style="width: 555px;padding:20px 0; display: flex;justify-content: space-between;">
<img src="@/assets/archive/roll.png">
<img src="@/assets/archive/yaw.png">
<img src="@/assets/archive/pitch.png">
</div>
<div style="width: 630px;padding:20px 0; display: flex;justify-content: space-between;">
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesLeft.rotationLeftMax}}°
</span>
</div>
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesLeft.tiltLeftMax}}°
</span></div>
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesLeft.pitchDownMax}}°
</span></div>
</div>
<div style="width: 630px;padding:20px 0; display: flex;justify-content: space-between;">
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesLeft.rotationRightMax}}°
</span></div>
<div class="rollyawpitchtext">
<span class="rollyawpitchtextcolor">
{{headPoseMaxValuesLeft.tiltRightMax}}°
</span>
</div>
<div class="rollyawpitchtext">
<span class="rollyawpitchtextcolor">
{{headPoseMaxValuesLeft.pitchUpMax}}°
</span>
</div>
</div>
<div class="PopUpReport-content-title">足底压力</div>
<div style="width: 600px;height: 370px; display: flex;margin-bottom: 100px;">
<img :src="BACKEND_URL+'/' + rawData.foot_data_image" alt="" srcset="" style="width: 100%;height: 100%;
object-fit:contain; ">
</div>
<div class="PopUpReport-content-title">视频1图片</div>
<div style="width: 600px;height: 338px; display: flex;">
<img :src="BACKEND_URL+'/' + rawData.foot1_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
<div class="PopUpReport-content-title">视频2图片</div>
<div style="width: 600px;height: 338px; display: flex;">
<img :src="BACKEND_URL+'/' + rawData.foot2_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
</div>
</div>
<div class="PopUpReport-container-rightbox">
<div class="displayflex">
<div class="displayflextext1">矫正数据</div>
<div class="displayflextext1">
{{ calibrationData.order }}
</div>
</div>
<!-- 选中后显示内容 -->
<div v-if="calibrationData.order && calibrationData.order!=''">
<div class="PopUpReport-content-title">整体数据</div>
<div style="width: 600px;height: 387px;">
<img :src="BACKEND_URL+'/' + calibrationData.screen_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
<div class="PopUpReport-content-title">身体姿态</div>
<div style="width: 100%;height: 454px; display: flex;justify-content: center;">
<img :src="BACKEND_URL+'/' + calibrationData.body_image" alt="" srcset="" style="width: 100%;height: 100%;
object-fit:contain; ">
</div>
<div class="PopUpReport-content-title">头部姿态</div>
<div style="width: 555px;padding:20px 0; display: flex;justify-content: space-between;">
<img src="@/assets/archive/roll.png">
<img src="@/assets/archive/yaw.png">
<img src="@/assets/archive/pitch.png">
</div>
<div style="width: 630px;padding:20px 0; display: flex;justify-content: space-between;">
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesRight.rotationLeftMax}}°
</span>
</div>
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesRight.tiltLeftMax}}°
</span></div>
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesRight.pitchDownMax}}°
</span></div>
</div>
<div style="width: 630px;padding:20px 0; display: flex;justify-content: space-between;">
<div class="rollyawpitchtext"><span class="rollyawpitchtextcolor">
{{headPoseMaxValuesRight.rotationRightMax}}°
</span></div>
<div class="rollyawpitchtext">
<span class="rollyawpitchtextcolor">
{{headPoseMaxValuesRight.tiltRightMax}}°
</span>
</div>
<div class="rollyawpitchtext">
<span class="rollyawpitchtextcolor">
{{headPoseMaxValuesRight.pitchUpMax}}°
</span>
</div>
</div>
<div class="PopUpReport-content-title">足底压力</div>
<div style="width: 600px;height: 370px; display: flex;margin-bottom: 100px;">
<img :src="BACKEND_URL+'/' + calibrationData.foot_data_image" alt="" srcset="" style="width: 100%;height: 100%;
object-fit:contain; ">
</div>
<div class="PopUpReport-content-title">视频1图片</div>
<div style="width: 600px;height: 338px; display: flex;">
<img :src="BACKEND_URL+'/' + calibrationData.foot1_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
<div class="PopUpReport-content-title">视频2图片</div>
<div style="width: 600px;height: 338px; display: flex;">
<img :src="BACKEND_URL+'/' + calibrationData.foot2_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
</div>
</div>
</div>
<div class="PopUpReport-container-testdatatitle">诊断结果</div>
<div class="PopUpReport-title2">记录</div>
<div class="PopUpReport-border1">{{ detectionInfo.diagnosis_info }}</div>
<div class="PopUpReport-title2">处理</div>
<div class="PopUpReport-border2">{{ detectionInfo.treatment_info }}</div>
<div class="PopUpReport-title2">备注</div>
<div class="PopUpReport-border3">{{ detectionInfo.suggestion_info }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { getBackendUrl } from '@/services/api.js'
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
const emit = defineEmits([ 'closePopUpReport' ]);
const props = defineProps({
selectedPatient: {
required: false,
type: Object,
default: null
},
detectionInfo: {
required: false,
type: Object,
default: {}
},
selectIds: {
required: false,
type: Array,
default: []
}
})
const BACKEND_URL = getBackendUrl()
const rawData = ref({}) //
const calibrationData = ref({}) //
const isSelectData = ref(false) //
const imageList = ref([])
const headPoseMaxValuesLeft = ref({
rotationLeftMax: 0, // -
rotationRightMax: 0, // -
tiltLeftMax: 0, // -
tiltRightMax: 0, // -
pitchUpMax: 0, // -
pitchDownMax: 0 })
const headPoseMaxValuesRight= ref({
rotationLeftMax: 0, // -
rotationRightMax: 0, // -
tiltLeftMax: 0, // -
tiltRightMax: 0, // -
pitchUpMax: 0, // -
pitchDownMax: 0 })
const calculateAge = (birthDate) => { //
if (!birthDate) return '—'
const today = new Date()
const birth = new Date(birthDate)
let age = today.getFullYear() - birth.getFullYear()
const monthDiff = today.getMonth() - birth.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--
}
return age
}
//
onMounted(() => {
imageList.value = props.detectionInfo.data
for (let i = 0; i < imageList.value.length; i++) {
if(imageList.value[i].id == props.selectIds[0]){
rawData.value = imageList.value[i]
if(imageList.value[i].head_pose !=null){
headPoseMaxValuesLeft.value = JSON.parse(imageList.value[i].head_pose).headPoseMaxValues
}
}
if(imageList.value[i].id == props.selectIds[1]){
calibrationData.value = imageList.value[i]
if(imageList.value[i].head_pose !=null){
headPoseMaxValuesRight.value = JSON.parse(imageList.value[i].head_pose).headPoseMaxValues
}
}
}
setTimeout(() => {
generatePDF()
}, 500);
})
onUnmounted(() => {
})
const generatePDF = async () => {
const element = document.getElementById('pdf-content');
// PDF
const pdf = new jsPDF('p', 'mm', 'a4');
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
//
const scale = 2; //
const elementWidth = element.offsetWidth;
const elementHeight = element.scrollHeight;
const widthRatio = pageWidth / (elementWidth / scale);
//
const pageContentHeight = (pageHeight / widthRatio) * scale;
let position = 0;
let currentPage = 1;
while (position < elementHeight) {
//
if (currentPage > 1) {
pdf.addPage();
}
//
const canvas = await html2canvas(element, {
scale,
useCORS: true,
windowHeight: pageContentHeight,
height: pageContentHeight,
y: position,
x: 0,
scrollY: -window.scrollY //
});
//
const imgData = canvas.toDataURL('image/jpeg', 1.0);
const imgWidth = pageWidth;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
// PDF
pdf.addImage(imgData, 'JPEG', 0, 0, imgWidth, imgHeight);
//
position += pageContentHeight;
currentPage++;
}
// PDF
const pdfBlob = pdf.output('blob');
const url = URL.createObjectURL(pdfBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
emit("closePopUpReport",false)
};
</script>
<style scoped>
.PopUpReport-container{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: #fff;
z-index: 50;
overflow: auto;
}
.PopUpReport-container-body{
margin:20px auto 0;
width: 1600px;
background: #fff;
border-radius: 5px;
padding: 50px 0 20px;
}
.PopUpReport-container-display{
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 25px;
border-bottom: 1px solid #333;
}
.PopUpReport-container-bodytitle{
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
font-size: 36px;
color: #282828;
text-align: center;
padding-bottom: 20px;
}
.PopUpReport-container-userinfotext{
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
color: #282828;
font-size: 18px;
}
.PopUpReport-container-userinfodisplay{
display: flex;
flex-wrap: wrap;
align-content:flex-start ;
padding: 20px 0;
padding-top: 30px;
border-bottom: 1px solid #333;
}
.PopUpReport-container-userinfotext2{
width: 240px;
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
color: #282828;
font-size: 18px;
padding-bottom: 10px;
}
.width-210{ width: 210px; }
.width-195{ width: 195px; }
.width-215{ width: 215px; }
.width-235{ width: 235px; }
.width-95{ width: 95px; }
.width-405{ width: 405px; }
.width-430{ width: 430px; }
.width-300{ width: 300px; }
.PopUpReport-container-testdatatitle{
font-weight: 700;
font-style: normal;
color: #14AAFF;
font-size: 18px;
text-align: left;
padding: 20px 0;
}
.PopUpReport-containerdisplay{
display: flex;
}
.PopUpReport-container-leftbox{
width: 50%;
border-right:1px solid rgb(208, 208, 208) ;
box-sizing: border-box;
}
.PopUpReport-container-rightbox{
width: 50%;
box-sizing: border-box;
padding-left: 80px;
}
.displayflex{
display: flex;
align-items: center;
}
.PopUpReport-container .displayflextext1{
font-weight: 700;
font-style: normal;
color: #14AAFF;
font-size: 18px;
text-align: left;
}
.displayflexselect{
position: relative;
width: 200px;
height: 40px;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: rgba(220, 223, 230, 1);
border-radius: 4px;
font-weight: 400;
font-style: normal;
font-size: 16px;
display: flex;
align-items: center;
padding-left: 10px;
margin-left: 20px;
cursor: pointer;
}
.displayflexselect-icon{
position: absolute;
top: 15px;
right: 10px;
}
.PopUpReport-content-title{
font-weight: 700;
font-style: normal;
color: #282828;
font-size: 18px;
padding-top:30px;
padding-bottom: 15px;
}
.rollyawpitchtext{
font-style: normal;
color: #282828;
font-size: 24px;
width: 170px;
}
.rollyawpitchtextcolor{
color:#14AAFF;
}
.PopUpReport-title2{
font-weight: 700;
font-style: normal;
font-size: 18px;
color: #282828;
padding-top: 20px;
padding-bottom: 20px;
}
.PopUpReport-border1{
padding: 5px;
width: 100%;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-radius: 4px;
font-weight: 400;
font-style: normal;
font-size: 18px;
text-decoration: none;
color: #383838;
}
.PopUpReport-border2{
width: 100%;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-radius: 4px;
font-style: normal;
font-size: 18px;
text-decoration: none;
color: #383838;
padding: 5px;
}
.PopUpReport-border3{
width: 100%;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-radius: 4px;
font-style: normal;
font-size: 18px;
text-decoration: none;
color: #383838;
padding: 5px;
}
</style>