BodyBalanceEvaluation/frontend/src/renderer/src/views/PopUpOnlyReport.vue

541 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="PopUpOnlyReport-container" id="popup-report-root">
<div class="PopUpOnlyReport-container-body" id="pdf-content">
<div style="height: 100%; padding:0 90px; box-sizing: border-box;">
<div class="PopUpOnlyReport-container-bodytitle">体态测量报告单</div>
<div class="PopUpOnlyReport-container-display">
<div class="PopUpOnlyReport-container-userinfotext">检测时间{{ detectionInfo.start_time }}</div>
<div class="PopUpOnlyReport-container-userinfotext">ID{{ detectionInfo.id }}</div>
</div>
<div class="PopUpOnlyReport-container-userinfodisplay">
<div class="PopUpOnlyReport-container-userinfotext2">
ID{{ selectedPatient.id }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-210">
姓名{{ selectedPatient.name }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-195">
性别{{ selectedPatient.gender }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-195">
年龄{{ calculateAge(selectedPatient.birth_date) }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-235">
身高{{ selectedPatient.height }}cm
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-215">
体重{{ selectedPatient.weight }}kg
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-95">
鞋码{{ selectedPatient.shoe_size }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2">
电话{{ selectedPatient.phone }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-405">
邮箱{{ selectedPatient.email }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-430">
居住地{{ selectedPatient.residence }}
</div>
<div class="PopUpOnlyReport-container-userinfotext2 width-310">
职业{{ selectedPatient.occupation }}
</div>
</div>
<div class="PopUpOnlyReport-container-testdatatitle">检测数据</div>
<div class="PopUpOnlyReport-containerdisplay">
<div class="PopUpOnlyReport-container-leftbox">
<div class="displayflex">
<div class="displayflextext1">原始数据</div>
<div class="displayflextext1">
{{ rawData.id }}
</div>
</div>
<!-- 选中后显示内容 -->
<div v-if="rawData.id && rawData.id!=''">
<div class="PopUpOnlyReport-content-title">整体数据</div>
<div style="width: 600px;height: 387px; margin: auto;">
<img :src="BACKEND_URL+'/' + rawData.screen_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
<div class="PopUpOnlyReport-content-title">身体姿态</div>
<div style="width: 216px;height: 454px; display: flex;justify-content: center; margin: auto;">
<img :src="BACKEND_URL+'/' + rawData.body_image" alt="" srcset="" style="width: 100%;height: 100%;
object-fit:contain; ">
</div>
<div class="PopUpOnlyReport-content-title">头部姿态</div>
<div style="width: 630px;padding:20px 0; margin: auto; display: flex;justify-content: space-between;">
<img src="@/assets/archive/roll.png">
<img src="@/assets/archive/yaw.png">
<img src="@/assets/archive/pitch.png" style="margin-right: 100px;">
</div>
<div style="width: 630px;padding:20px 0; margin: auto; 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; margin: auto; 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="PopUpOnlyReport-content-title">足底压力</div>
<div style="width: 600px;height: 370px; margin: auto; 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="PopUpOnlyReport-content-title">视频1图片</div>
<div style="width: 600px;height: 338px; display: flex; margin: auto;">
<img :src="BACKEND_URL+'/' + rawData.foot1_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
<div class="PopUpOnlyReport-content-title">视频2图片</div>
<div style="width: 600px;height: 338px; display: flex; margin: auto;">
<img :src="BACKEND_URL+'/' + rawData.foot2_image" alt="" srcset="" style="width: 100%;height: 100%;">
</div>
</div>
</div>
</div>
<div class="PopUpOnlyReport-container-testdatatitle">【诊断结果】</div>
<div class="PopUpOnlyReport-title2">记录</div>
<div class="PopUpOnlyReport-border1">{{ detectionInfo.diagnosis_info }}</div>
<div class="PopUpOnlyReport-title2">处理</div>
<div class="PopUpOnlyReport-border2">{{ detectionInfo.treatment_info }}</div>
<div class="PopUpOnlyReport-title2">备注</div>
<div class="PopUpOnlyReport-border3">{{ detectionInfo.suggestion_info }}</div>
<div class="PopUpOnlyReport-footer">
<div style="margin-right: 80px;">检测时间:{{ detectionInfo.created_at }}</div>
<div style="margin-right: 80px;">报告时间{{ getFormattedTime() }}</div>
<div>检测医生{{ detectionInfo.creator_name }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getBackendUrl } from '@/services/api.js'
// import html2canvas from 'html2canvas';
// import jsPDF from 'jspdf';
const emit = defineEmits([ 'closePopUpOnlyReport' ]);
const props = defineProps({
selectedPatient: {
required: false,
type: Object,
default: null
},
detectionInfo: {
required: false,
type: Object,
default: {}
},
selectId: {
required: false,
type: String,
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 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.selectId){
rawData.value = imageList.value[i]
if(imageList.value[i].head_pose !=null){
headPoseMaxValuesLeft.value = JSON.parse(imageList.value[i].head_pose).headPoseMaxValues
}
}
}
setTimeout(() => {
generatePDF()
}, 500);
})
onUnmounted(() => {
})
function getFormattedTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始需+1
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0'); // 24小时制
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const generatePDF = async () => {
try {
const root = document.getElementById('popup-report-root')
if (!root) {
ElMessage.error('报告容器未找到')
return
}
if (!window.electronAPI) {
console.error('electronAPI 未定义')
return
}
const pdfBuffer = await window.electronAPI.generateReportPdf({
selector: '#popup-report-root',
pageSize: 'A4',
landscape: true, // 横向
printBackground: true
})
const blob = new Blob([pdfBuffer], { type: 'application/pdf' })
const form = new FormData()
// 使用检测ID作为文件名
const filename = `${props.detectionInfo.id || 'report'}.pdf`
form.append('file', blob, filename)
form.append('data_ids', rawData.value.id)
// 如果有detectionInfo.id则上传到后端
if (props.detectionInfo.id) {
const res = await fetch(`${BACKEND_URL}/api/reports/${props.detectionInfo.id}/upload`, {
method: 'POST',
body: form
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
}
const json = await res.json()
if (json.success) {
ElMessage.success('报告生成并上传成功')
emit('closePopUpOnlyReport', true)
} else {
throw new Error(json.error || '上传失败')
}
} else {
// 仅供预览或下载
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
emit('closePopUpOnlyReport', true)
}
} catch (e) {
console.error('报告生成异常:', e)
ElMessage.error(`报告生成失败:${e.message}`)
}
};
// 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 = '体态测量报告单.pdf';
// document.body.appendChild(a);
// a.click();
// document.body.removeChild(a);
// URL.revokeObjectURL(url);
// emit("closePopUpOnlyReport",false)
// };
</script>
<style scoped>
.PopUpOnlyReport-container{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: #fff;
z-index: 50;
overflow: auto;
}
.PopUpOnlyReport-container-body{
margin:20px auto 0;
width: 1600px;
background: #fff;
border-radius: 5px;
padding: 50px 0 20px;
}
.PopUpOnlyReport-container-display{
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 25px;
border-bottom: 1px solid #333;
}
.PopUpOnlyReport-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;
}
.PopUpOnlyReport-container-userinfotext{
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
color: #282828;
font-size: 18px;
}
.PopUpOnlyReport-container-userinfodisplay{
display: flex;
flex-wrap: wrap;
align-content:flex-start ;
padding: 20px 0;
padding-top: 30px;
border-bottom: 1px solid #333;
}
.PopUpOnlyReport-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; }
.PopUpOnlyReport-container-testdatatitle{
font-weight: 700;
font-style: normal;
color: #14AAFF;
font-size: 18px;
text-align: left;
padding: 20px 0;
}
.PopUpOnlyReport-containerdisplay{
display: flex;
}
.PopUpOnlyReport-container-leftbox{
width: 100%;
box-sizing: border-box;
}
.PopUpOnlyReport-container-rightbox{
width: 50%;
box-sizing: border-box;
padding-left: 80px;
}
.displayflex{
display: flex;
align-items: center;
}
.PopUpOnlyReport-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;
}
.PopUpOnlyReport-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;
}
.PopUpOnlyReport-title2{
font-weight: 700;
font-style: normal;
font-size: 18px;
color: #282828;
padding-top: 20px;
padding-bottom: 20px;
}
.PopUpOnlyReport-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;
}
.PopUpOnlyReport-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;
}
.PopUpOnlyReport-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;
}
.PopUpOnlyReport-border4{
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;
}
.PopUpOnlyReport-footer{
margin-top: 40px;
padding-top: 40px;
border-top: 1px solid #333;
display: flex;
font-weight: 700;
font-style: normal;
color: rgb(40, 40, 40);
font-size: 18px;
}
</style>