增加心电模块打印以及分享功能

This commit is contained in:
lxd 2024-12-03 10:26:02 +08:00
parent e683cd1604
commit 36e95da4bd
9 changed files with 1380 additions and 66 deletions

View File

@ -67,6 +67,7 @@
"vue": "3.4.21",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.10.2",
"vue-qrcode": "^2.2.2",
"vue-router": "^4.3.0",
"vue-types": "^5.1.1",
"vue-video-player": "^6.0.0",

View File

@ -134,6 +134,9 @@ importers:
vue-i18n:
specifier: 9.10.2
version: 9.10.2(vue@3.4.21(typescript@5.3.3))
vue-qrcode:
specifier: ^2.2.2
version: 2.2.2(qrcode@1.5.3)(vue@3.4.21(typescript@5.3.3))
vue-router:
specifier: ^4.3.0
version: 4.3.2(vue@3.4.21(typescript@5.3.3))
@ -4893,7 +4896,7 @@ packages:
engines: {node: '>=0.10.0'}
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://r2.cnpmjs.org/source-map/-/source-map-0.6.1.tgz}
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
split-string@3.1.0:
@ -5423,6 +5426,12 @@ packages:
peerDependencies:
vue: ^3.0.0
vue-qrcode@2.2.2:
resolution: {integrity: sha512-SbrXq/mSb1g2tbDyXPe9gy9KiMYsvxWKRErlpij1BqiFoHwQckheZV63CTw6yRLLUVG2RXAVlX+APkpdCK7SQQ==}
peerDependencies:
qrcode: ^1.0.0
vue: ^2.7.0 || ^3.0.0
vue-router@4.3.2:
resolution: {integrity: sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==}
peerDependencies:
@ -11307,6 +11316,12 @@ snapshots:
'@vue/devtools-api': 6.6.1
vue: 3.4.21(typescript@5.3.3)
vue-qrcode@2.2.2(qrcode@1.5.3)(vue@3.4.21(typescript@5.3.3)):
dependencies:
qrcode: 1.5.3
tslib: 2.6.2
vue: 3.4.21(typescript@5.3.3)
vue-router@4.3.2(vue@3.4.21(typescript@5.3.3)):
dependencies:
'@vue/devtools-api': 6.6.1

View File

@ -34,6 +34,7 @@ export interface PatientexamlistVO {
billDoctorDepartment:string// 开单科室 送检科室
StudyInsta:string
isFavourite:string //是否收藏
pdfurl:String//pdf地址
}
export interface inspdfscreenshotVO {

View File

@ -1,5 +1,5 @@
import html2Canvas from 'html2canvas';
import jsPDF from 'jspdf';
import html2Canvas from 'html2canvas'
import jsPDF from 'jspdf'
const htmlToPdf = {
getPdfBase64(title, id) {
@ -10,52 +10,43 @@ const htmlToPdf = {
logging: false,
useCORS: true,
dpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍
scale: 4, // 按比例增加分辨率
scale: 4 // 按比例增加分辨率
}).then((canvas) => {
const pdf = new jsPDF('p', 'mm', 'a4'); // A4纸纵向
const pdf = new jsPDF('p', 'mm', 'a4') // A4纸纵向
const ctx = canvas.getContext('2d'),
a4w = 190,
a4h = 272; // A4大小210mm x 297mm四边各保留10mm的边距显示区域190x277
a4h = 272 // A4大小210mm x 297mm四边各保留10mm的边距显示区域190x277
let imgHeight = Math.floor((a4h * canvas.width) / a4w), // 按A4显示比例换算一页图像的像素高度
renderedHeight = 0;
renderedHeight = 0
while (renderedHeight < canvas.height) {
const page = document.createElement('canvas');
page.width = canvas.width;
const pageHeight = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页
page.height = pageHeight;
const page = document.createElement('canvas')
page.width = canvas.width
const pageHeight = Math.min(imgHeight, canvas.height - renderedHeight) // 可能内容不足一页
page.height = pageHeight
// 用getImageData剪裁指定区域并画到前面创建的canvas对象中
page
.getContext('2d')
.putImageData(
ctx.getImageData(
0,
renderedHeight,
canvas.width,
pageHeight,
),
0,
0,
);
.putImageData(ctx.getImageData(0, renderedHeight, canvas.width, pageHeight), 0, 0)
pdf.addImage(
page.toDataURL('image/jpeg', 1.0),
'JPEG',
10,
10,
a4w,
Math.min(a4h, (a4w * page.height) / page.width),
); // 添加图像到页面保留10mm边距
Math.min(a4h, (a4w * page.height) / page.width)
) // 添加图像到页面保留10mm边距
renderedHeight += pageHeight;
renderedHeight += pageHeight
if (renderedHeight < canvas.height) {
pdf.addPage(); // 如果后面还有内容,添加一个空页
pdf.addPage() // 如果后面还有内容,添加一个空页
}
}
const pdfBase64String = pdf.output('datauristring'); // 获取 base64 编码的 PDF 文件
resolve(pdfBase64String); // 解析 base64 字符串
});
});
const pdfBase64String = pdf.output('datauristring') // 获取 base64 编码的 PDF 文件
resolve(pdfBase64String) // 解析 base64 字符串
})
})
},
getPdf(title, id) {
@ -65,51 +56,181 @@ const htmlToPdf = {
logging: false,
useCORS: true,
dpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍
scale: 4, // 按比例增加分辨率
scale: 4 // 按比例增加分辨率
}).then((canvas) => {
const pdf = new jsPDF('p', 'mm', 'a4'); // A4纸纵向
const pdf = new jsPDF('p', 'mm', 'a4') // A4纸纵向
const ctx = canvas.getContext('2d'),
a4w = 190,
a4h = 272; // A4大小210mm x 297mm四边各保留10mm的边距显示区域190x277
a4h = 272 // A4大小210mm x 297mm四边各保留10mm的边距显示区域190x277
let imgHeight = Math.floor((a4h * canvas.width) / a4w), // 按A4显示比例换算一页图像的像素高度
renderedHeight = 0;
renderedHeight = 0
while (renderedHeight < canvas.height) {
const page = document.createElement('canvas');
page.width = canvas.width;
const pageHeight = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页
page.height = pageHeight;
const page = document.createElement('canvas')
page.width = canvas.width
const pageHeight = Math.min(imgHeight, canvas.height - renderedHeight) // 可能内容不足一页
page.height = pageHeight
// 用getImageData剪裁指定区域并画到前面创建的canvas对象中
page
.getContext('2d')
.putImageData(
ctx.getImageData(
0,
renderedHeight,
canvas.width,
pageHeight,
),
0,
0,
);
.putImageData(ctx.getImageData(0, renderedHeight, canvas.width, pageHeight), 0, 0)
pdf.addImage(
page.toDataURL('image/jpeg', 1.0),
'JPEG',
10,
10,
a4w,
Math.min(a4h, (a4w * page.height) / page.width),
); // 添加图像到页面保留10mm边距
Math.min(a4h, (a4w * page.height) / page.width)
) // 添加图像到页面保留10mm边距
renderedHeight += pageHeight;
renderedHeight += pageHeight
if (renderedHeight < canvas.height) {
pdf.addPage(); // 如果后面还有内容,添加一个空页
pdf.addPage() // 如果后面还有内容,添加一个空页
}
}
pdf.save(title + '.pdf'); // 保存 PDF 文件
});
pdf.save(title + '.pdf') // 保存 PDF 文件
})
},
};
getPdfh(title, id) {
html2Canvas(document.querySelector(id), {
allowTaint: false,
taintTest: false,
logging: false,
useCORS: true,
dpi: 300, // 将分辨率提高到特定的DPI 提高四倍
scale: 1 // 按比例增加分辨率
}).then((canvas) => {
const pdf = new jsPDF('l', 'mm', 'a4') // A4纸纵向
const ctx = canvas.getContext('2d'),
a4w = 190,
a4h = 272 // A4大小210mm x 297mm四边各保留10mm的边距显示区域190x277
let imgHeight = Math.floor((a4h * canvas.width) / a4w), // 按A4显示比例换算一页图像的像素高度
renderedHeight = 0
export default htmlToPdf;
while (renderedHeight < canvas.height) {
const page = document.createElement('canvas')
page.width = canvas.width
const pageHeight = Math.min(imgHeight, canvas.height - renderedHeight) // 可能内容不足一页
page.height = pageHeight
// 用getImageData剪裁指定区域并画到前面创建的canvas对象中
page
.getContext('2d')
.putImageData(ctx.getImageData(0, renderedHeight, canvas.width, pageHeight), 0, 0)
pdf.addImage(
page.toDataURL('image/jpeg', 1.0),
'JPEG',
10,
10,
a4w,
Math.min(a4h, (a4w * page.height) / page.width)
) // 添加图像到页面保留10mm边距
renderedHeight += pageHeight
if (renderedHeight < canvas.height) {
pdf.addPage() // 如果后面还有内容,添加一个空页
}
}
pdf.save(title + '.pdf') // 保存 PDF 文件
})
},
getDivContentAsBase64(divId) {
return new Promise((resolve, reject) => {
// 获取div元素
const divElement = document.querySelector(divId)
if (!divElement) {
console.error('未找到指定ID的div元素')
reject('未找到指定ID的div元素')
return
}
// 使用html2canvas捕捉div内容
html2Canvas(divElement, {
dpi: 300, // 设置300DPI
scale: 3.125, // 保持原始比例
useCORS: true, // 如果内容包含跨域图片需要设置为true
allowTaint: false // 是否允许跨域图片导致画布被污染
})
.then((canvas) => {
// const pdf = new jsPDF({
// orientation: 'l', // 纵向
// unit: 'px', // 使用像素作为单位
// format: [1920, 820] // 自定义页面尺寸,与图片尺寸一致
// });
const pdf = new jsPDF({
orientation: 'l',
unit: 'px',
format: [canvas.width, canvas.height]
});
// 将canvas转换为图片数据URL
const imgData = canvas.toDataURL('image/jpeg', 1) // 1.0表示最高质量
// pdf.addImage(imgData, 'JPEG', 0, 0, canvas.width, canvas.height);
// // 设置 PDF 页面尺寸
// // pdf.addImage(imgData, 'JPEG', 10, 10, 1920, 820)
// const link = document.createElement('a');
// link.download = `${divId}-image.jpeg`;
// link.href = imgData;
// link.click();
// pdf.save('111.pdf')
// 解决Promise并返回图片的base64字符串
// pdf.save(`${divId}-content.pdf`);
resolve(imgData)
})
.catch((error) => {
console.error('生成图片过程中发生错误:', error)
reject(error)
})
})
},
createPdfFromBase64(base64Image) {
return new Promise((resolve, reject) => {
// 使用html2canvas捕捉div内容这里不需要但保留以备后续使用
// html2Canvas(divElement, { ... }).then(canvas => {
// 将base64字符串转换为Blob对象
const blob = base64ToBlob(base64Image);
// 创建一个URL对象
const url = URL.createObjectURL(blob);
// 创建一个新的Image对象
const img = new Image();
img.onload = () => {
// 创建PDF对象
const pdf = new jsPDF({
orientation: 'l',
unit: 'px',
format: [img.width, img.height]
});
// 将图片添加到PDF
pdf.addImage(img, 'JPEG', 0, 0, img.width, img.height);
// 获取PDF的base64字符串
const pdfBase64 = pdf.output('datauristring');
// 解决Promise并返回PDF的base64字符串
resolve(pdfBase64);
// 清理URL对象
URL.revokeObjectURL(url);
};
img.onerror = (error) => {
console.error('图片加载失败:', error);
reject(error);
};
img.src = url;
});
}
}
// 辅助函数将base64字符串转换为Blob对象
function base64ToBlob(base64) {
const binary = atob(base64.split(',')[1]);
const array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
}
export default htmlToPdf

View File

@ -31,7 +31,23 @@
<el-button type="primary" plain
><el-icon><Filter /></el-icon> </el-button
>
<el-button type="primary" plain :icon="Share">分享</el-button>
<el-popover
placement="bottom"
title="分享二维码"
:width="250"
trigger="click"
:disabled="isshare"
>
<template #reference>
<el-button type="primary" plain :icon="Share" @click="shareclick()"
>分享</el-button
>
</template>
<div>
<canvas ref="qrcodeRef" width="200" height="200"></canvas>
</div>
</el-popover>
<el-button type="primary" plain>
<el-icon><RefreshLeft /></el-icon> </el-button
>
@ -42,7 +58,7 @@
@click="getuporghiorgid(Primarykey, orgid)"
>申请诊断</el-button
>
<el-button type="primary" plain
<el-button type="primary" plain @click="print"
><el-icon><Printer /></el-icon></el-button
>
</el-button-group>
@ -305,7 +321,9 @@
</el-form-item>
</el-col>
</el-row>
<el-tag type="info" style="font-size: 16px; width: 100%;color: black;">心电事件快照</el-tag>
<el-tag type="info" style="font-size: 16px; width: 100%; color: black"
>心电事件快照</el-tag
>
<el-input
v-model="snapshotTime"
style="width: 100%"
@ -313,7 +331,7 @@
type="textarea"
placeholder=""
/>
<el-tag type="info" style="font-size: 16px; width: 100%;color: black;">智能词库</el-tag>
<el-tag type="info" style="font-size: 16px; width: 100%; color: black">智能词库</el-tag>
<el-input
v-model="queryParams.autoDiagResult"
style="width: 100%"
@ -321,7 +339,7 @@
type="textarea"
placeholder=""
/>
<el-tag type="info" style="font-size: 16px; width: 100%;color: black;"
<el-tag type="info" style="font-size: 16px; width: 100%; color: black"
>医生诊断结论 <el-button @click="zdmodle" type="primary" text>诊断模版</el-button>
</el-tag>
<el-input
@ -406,19 +424,62 @@
:expand-on-click-node="false"
/>
</el-drawer>
<!--打印弹窗-->
<el-dialog
title=""
v-model="isdiagshow"
:fullscreen="true"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
@close="printclose"
>
<!--width: 1000px; height: 700px;-->
<ECGprint :imagebase64="imagebase64" />
</el-dialog>
<!--生成图片的组件-->
<div style="position: fixed; top: 0; left: 0; z-index: -100">
<div id="hiddenPdfDiv" v-if="isprintimage">
<!--心电图-->
<ECGhtmlprint
style="height: 1080px; width: 1920px; page-break-after: always; visibility: hidden"
:extraInfo="queryParams"
:jsonurl="queryParams.ecgJsonDataFilePath"
:-isgrid="Isgrid"
:-ismeasure="Ismeasure"
:-isfd="IsFD"
:lineratio="0.025"
:suduratio="suduratio"
:isrefresh="isrefresh"
:iscorrect="correct"
@update:value="handleUpdate"
:printimage="isprintimage"
@update:image="updateimagebase"
:age="age"
:billDoctorDepartment="rowinfo.billDoctorDepartment"
:doctorname="rowinfo.doctorname"
:regId="id"
:pname="rowinfo.pname"
:gender="rowinfo.gender"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ArrowLeft, ArrowRight, Delete, Edit, Share, Check } from '@element-plus/icons-vue'
import ECGhtml from '@/views/ECG/ECGhtml.vue'
import ECGhtmlprint from '@/views/ECG/ECGhtmlprint.vue'
import { EcganalysisparasApi, EcganalysisparasVO } from '@/api/tblist/ecganalysisparas'
import { dateFormatter, beginOfDay } from '@/utils/formatTime'
import { formatDate, getCurrentLocalDateTime } from '@/utils/formatTime'
import { formatDate } from '@/utils/formatTime'
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
import { ultrasoniccomApi, updateexamineimageVO } from '@/api/ultrasoniccom'
import { PatientexamlistApi, PatientexamlistVO } from '@/api/tblist/patientexamlist'
import QRCode from 'qrcode'
import ECGprint from '@/views/ECG/ECGprint.vue'
import htmlToPdf from '@/utils/htmlPdf'
/** 提交表单 */
const emit = defineEmits(['success']) // success
const message = useMessage() //
@ -445,6 +506,11 @@ const diagnosemodel = ref() //存放诊断模版
const isrefresh = ref(false) //
const correct = ref(false) //
const eltextrow = ref(4) //
const isshare = ref(true) //
const qrcodeRef = ref(null)
const isdiagshow = ref(false) //
const imagebase64 = ref()
const isprintimage = ref(false) //base64
//
const treeDefaultProps = {
@ -458,10 +524,12 @@ const treeData = ref([])
const open = async (row: any) => {
dialogVisible.value = true
dialogTitle.value = '心电分析'
resetForm()
id.value = row.regId
Primarykey.value = row.id
orgid.value = row.orgId
rowinfo.value = row
// console.log( rowinfo.value)
const data = await EcganalysisparasApi.getexamIDdata('MZCF0191729074962197_44')
queryParams.value = data
snapshotTime.value = formattedDate(queryParams.value.snapshotTime)
@ -544,10 +612,68 @@ async function save() {
// saveFormVO.value.departName=queryParams.value.departName
const ret = await EcganalysisparasApi.SaveEcganalysisparas(saveFormVO.value)
if (ret) {
//pdf
const data = await EcganalysisparasApi.getexamIDdata('MZCF0191729074962197_44')
queryParams.value = data
message.alertSuccess('保存成功')
isprintimage.value = true
nextTick(async () => {
setTimeout(async () => {
try {
//PDF
htmlToPdf
.createPdfFromBase64(imagebase64.value)
.then((pdfBase64) => {
//PDF
PatientexamlistApi.ftppdf({
id: Primarykey.value,
imagebase: pdfBase64,
model: '0',
folderPath: 'C:\\work\\pacs' + '\\' + Primarykey.value
})
})
.catch((error) => {
console.log(error)
})
} finally {
}
}, 1000)
})
}
}
//
function updateimagebase(newValue) {
imagebase64.value = newValue
}
//
function resetForm()
{
Isgrid.value=1 // 1 0
Ismeasure.value =0 // 1 0
IsFD.value = false //
lineratio.value =0.05 //线
suduratio.value = 1 //线
}
//
function printclose() {
imagebase64.value=''
isdiagshow.value=false
isprintimage.value=false
}
//
function print() {
if (!queryParams.value.autoDiagTime) {
isdiagshow.value = false
isprintimage.value = false
message.alertError('请保存后再进行打印')
} else {
isdiagshow.value = true
isprintimage.value = true
}
}
//
const handleUpdate = (newValue) => {
correct.value = newValue
@ -634,7 +760,20 @@ function FD() {
message.alertSuccess('关闭放大')
}
}
//
function shareclick() {
if (rowinfo.value.pdfurl) {
isshare.value = false
// DOM
QRCode.toCanvas(qrcodeRef.value, rowinfo.value.pdfurl, function (error) {
if (error) console.error(error)
console.log('QR code generated!')
})
} else {
isshare.value = true
message.alertError('请保存后再进行分享')
}
}
//
const insertValue = (textvalue: String) => {
const textarea = inputRef.value.$el.querySelector('textarea') // textarea DOM
@ -731,6 +870,7 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
flex-direction: column;
align-items: center;
}
/*心电图区域样式*/
/*心电图区域样式*/

View File

@ -240,7 +240,7 @@ nextTick(() => {
const integer = Math.round(adjustedWidth)
const awidth = integer * 20
canvasContainer.style.width = awidth - 20 + 'px'
canvasContainer1.style.width = awidth - 20 + 'px'
canvasContainer1.style.width = (awidth+41) - 20 + 'px' //61 3
console.log('adjustedWidth 不是整数')
}
if (Number.isInteger(adjustedheight)) {

View File

@ -0,0 +1,939 @@
<template>
<div>
<div class="ecg-report" id="PDF">
<!-- 头部 -->
<div>
<div style="text-align: center; margin-bottom: -16px">
Shenzhen Bao'an People's Hospital
</div>
<div style="text-align: right; font-size: 12px">报告日期{{formatDate(extraInfo.doctorDiagTime, 'YYYY-MM-DD HH:mm:ss')}}</div>
</div>
<hr v-if="true" />
<!-- 基本信息 -->
<div style="margin: 4px 6px; line-height: 1.14">
<el-row>
<el-col :span="7">编号{{extraInfo.examId}}</el-col>
<el-col :span="5">HR{{extraInfo.hr}}</el-col>
<el-col :span="12">诊断提示{{extraInfo.autoDiagResult}}</el-col>
</el-row>
<el-row>
<el-col :span="7">姓名{{ infoParams.pname }}</el-col>
<el-col :span="5">P-R{{ extraInfo.pr }}</el-col>
</el-row>
<el-row>
<el-col :span="7">性别{{ infoParams.gender }}</el-col>
<el-col :span="5">QRS{{ extraInfo.qrsAxle }}</el-col>
</el-row>
<el-row>
<el-col :span="7">年龄{{infoParams.age }}</el-col>
<el-col :span="5">QT/QTc{{ extraInfo.qt }}/{{ extraInfo.qtc }}</el-col>
</el-row>
<el-row>
<el-col :span="7">病室</el-col>
<el-col :span="5">RV5/SV1{{ extraInfo.rv5 }}/{{ extraInfo.sv1 }}</el-col>
</el-row>
<el-row>
<el-col :span="7">住院号{{infoParams.regId}}</el-col>
<el-col :span="5">RV5+SV1{{ extraInfo.rv5Sv1 }}</el-col>
<el-col :span="7">科室{{ infoParams.billDoctorDepartment }}</el-col>
<el-col :span="5">报告医生{{infoParams.doctorname}}</el-col>
</el-row>
</div>
<hr v-if="false" />
<div class="ecg-main">
<div class="printcontainer">
<div id="canvas-containerprint">
<canvas ref="leftCanvasprint" id="leftCanvasprint" width="21" height="921"></canvas>
<canvas
ref="bottomCanvasprint"
id="bottomCanvasprint"
width="1920"
height="921"
></canvas>
<canvas
ref="topCanvasprint"
id="topCanvasprint"
width="1920"
height="921"
@mousedown="handleMouseDown($event, 'L')"
></canvas>
</div>
<div id="canvas-containerprint1">
<canvas ref="rightCanvasprint" id="rightCanvasprint" width="21" height="921"></canvas>
<canvas
ref="bottomCanvasprint1"
id="bottomCanvasprint1"
width="1920"
height="921"
></canvas>
<canvas
ref="topCanvasprint1"
id="topCanvasprint1"
width="1920"
height="921"
@mousedown="handleMouseDown($event, 'R')"
></canvas>
</div>
</div>
</div>
<!-- 心电图 -->
<!-- 尾部 -->
<div style="width: 96.88%; position: absolute; bottom: 20px; right: 20px; z-index: 1000;">
<hr v-if="false" />
<el-row style="font-size: 12px">
<el-col :span="8" class="pl-6px"> 地址 </el-col>
<el-col :span="5" style="text-align: right">{{formatDate(extraInfo.doctorDiagTime, 'YYYY-MM-DD HH:mm:ss')}}</el-col>
<el-col :span="10" style="text-align: right"> <span class="myrtip"
>{{ '&nbsp;走速:' + convert(suduratio, 0) + 'mm/s ' }}
{{ '&nbsp;振幅:' + convert(lineratio, 1) + 'mm/mv ' }}
{{ '&nbsp;报告模式6X2' }}</span
> </el-col>
</el-row>
<div style="font-size: 10.6px; text-align: center; margin-top: 4.2px; margin-bottom: -14px">
本报告仅供临床医师参考不作疾病证明
</div>
</div>
</div>
<!-- <el-button @click="image">打印1</el-button> -->
</div>
<!--纠错功能-->
<el-dialog v-model="isdialog" title="导联纠错" width="600" @close="close">
<div>
<el-checkbox-group v-model="transfer" :min="0" :max="2" class="mycheckbox-group">
<el-checkbox
v-for="city in transferleads"
:key="city"
:label="city"
:value="city"
class="mycheckbox-item"
@change="checkboxchage"
>
{{ city }}
</el-checkbox>
</el-checkbox-group>
</div>
<div style="margin-top: 10px">
<el-button type="primary" @click="clickdl" :disabled="checkes">交换导联</el-button>
<el-button type="primary" @click="recovery()">恢复正常</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { number, object } from 'vue-types'
import sb from './ECGSB.vue'
import htmlToPdf from '@/utils/htmlPdf'
import { formatDate } from '@/utils/formatTime'
//json
const text = ref()
const sliderValue = ref(0) //
//
const emits = defineEmits(['update:value', 'update:image'])
const lineratio1 = ref(0.05) //线
const previousValue = ref(0) //
const swipeDirection = ref('') //
//
const leftCanvasprint = ref(null)
const rightCanvasprint = ref(null)
const bottomCanvasprint = ref(null)
const bottomCanvasprint1 = ref(null)
const topCanvasprint = ref(null)
const topCanvasprint1 = ref(null)
const url = ref()
/*鼠标绘制相关*/
const ctx = ref(null)
const isVertical = ref(false) // 线true false
const lastPoint = ref(null) //
const isdialog = ref(false) //
const transfer = ref() //
const checkes = ref(true) //
//
const beatArray1 = ref([])
const beatArray2 = ref([]) //
let offset = 0
const spacing = 20
const heightoff = ref(0) //
const FD = ref(false)
const Info = ref()
async function image() {
const imagebase64 = await htmlToPdf.getDivContentAsBase64('#PDF')
emits('update:image', imagebase64)
}
function handleMouseDown(event, type) {
if (type === 'L') {
ctx.value = topCanvasprint.value.getContext('2d')
} else if (type === 'R') {
ctx.value = topCanvasprint1.value.getContext('2d')
}
if (infoParams.Ismeasure !== 1) {
return
}
const rect =
type === 'L'
? topCanvasprint.value.getBoundingClientRect()
: topCanvasprint1.value.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
if (lastPoint.value) {
// 使线
drawLineAndDistance(lastPoint.value, { x, y })
lastPoint.value = null //
} else {
//
lastPoint.value = { x, y }
}
}
function calculateDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
}
function drawLineAndDistance(point1, point2) {
//ctx.value.clearRect(0, 0, topCanvasprint.value.width, topCanvasprint.value.height); //
//线
if (Math.abs(point2.x - point1.x) <= 20) {
point2.x = point1.x
lineTo(point1.x, point1.y, point2.x, point2.y, 1)
} else if (Math.abs(point2.y - point1.y) <= 20) {
//
point2.y = point1.y
lineTo(point1.x, point1.y, point2.x, point2.y, 0)
} else {
lastPoint.value = null
}
}
function lineTo(x, y, x1, y1, type) {
//
ctx.value.fillStyle = 'black'
ctx.value.beginPath()
ctx.value.arc(x, y, 1, 0, 2 * Math.PI)
ctx.value.fill()
ctx.value.beginPath()
ctx.value.arc(x1, y1, 1, 0, 2 * Math.PI)
ctx.value.fill()
// 线
ctx.value.beginPath()
ctx.value.moveTo(x, y)
ctx.value.lineTo(x1, y1)
ctx.value.strokeStyle = 'black'
ctx.value.lineWidth = 1
ctx.value.stroke()
//
const distance = calculateDistance(x, y, x1, y1)
const ms = (25.4 / 96) * (25 / 1000) * distance
ctx.value.font = '11px Arial'
ctx.value.fillStyle = 'black'
if (type === 1) {
ctx.value.fillText(`距离: ${ms.toFixed(2)}ms`, x + 5, y + 30)
} else {
ctx.value.fillText(`距离: ${ms.toFixed(2)}ms`, x + 10, y - 10)
}
}
/*鼠标绘制相关*/
const handleSliderChange = (value) => {
//
const difference = value - previousValue.value
const swipeDifference = Math.abs(difference)
console.log('滑动差值' + swipeDifference)
if (value > previousValue.value) {
swipeDirection.value = '右滑' //
moveCanvas('left', swipeDifference)
} else if (value < previousValue.value) {
swipeDirection.value = '左滑' //
moveCanvas('right', swipeDifference)
} else {
swipeDirection.value = '未滑动' //
}
previousValue.value = value //
console.log('滑块值改变为:', value)
}
//
onMounted(async () => {
console.log('挂载完成')
Info.value = infoParams.extraInfo
// topCanvasprint.value = document.getElementById('topCanvasprint');
})
nextTick(async () => {
/* 计算宽度和高度*/
var canvasContainer = document.getElementById('canvas-containerprint')
var canvasContainer1 = document.getElementById('canvas-containerprint1')
var leftCanvas = document.getElementById('leftCanvasprint')
//
var style = window.getComputedStyle(canvasContainer)
//
var widthPercentage = style.width
//
var heightPercentage = style.height
var widthInPixels = canvasContainer.offsetWidth
var heightInPixels = canvasContainer.offsetHeight
// 20
var adjustedWidth = widthInPixels / 20
var adjustedheight = heightInPixels / 20
// adjustedWidth
if (Number.isInteger(adjustedWidth)) {
canvasContainer.style.width = widthInPixels + 'px'
} else {
const integer = Math.round(adjustedWidth)
const awidth = integer * 20
canvasContainer.style.width = awidth - 20 + 'px'
canvasContainer1.style.width = awidth + 1 + 'px' //61 3
console.log('adjustedWidth 不是整数')
}
if (Number.isInteger(adjustedheight)) {
canvasContainer.style.height = heightInPixels + 'px'
// leftCanvas.height = heightInPixels + 'px'
} else {
const integer = Math.round(adjustedheight)
const ahe = integer * 20
canvasContainer.style.height = ahe - 118 + 'px'
canvasContainer1.style.height = ahe - 118 + 'px'
// leftCanvas.height = ahe - 118
heightoff.value = (ahe - 118) / 6
}
await fetchData()
nextTick(() => {
image()
})
})
//
const calculateAge = (birthdate) => {
if (!birthdate) {
age.value = null
return
}
const today = new Date()
const birth = new Date(birthdate)
let yearsDiff = today.getFullYear() - birth.getFullYear()
// Check if the birthday hasn't occurred yet this year
const hasBirthdayPassed =
today.getMonth() > birth.getMonth() ||
(today.getMonth() === birth.getMonth() && today.getDate() >= birth.getDate())
if (!hasBirthdayPassed) {
yearsDiff--
}
age.value = yearsDiff
}
//
const fetchData = async () => {
try {
text.value = await fetchFileContent(infoParams.jsonurl)
handleFileChange()
Bottom()
// console.log(':', text.value);
} catch (error) {
console.error('获取数据时出错:', error)
}
}
const inputSlider = (value) => {
setScrollTop(value)
}
const formatTooltip = (value) => {
return `${value} px`
}
async function fetchFileContent(url) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error('网络响应不是 ok')
}
return await response.text() //
} catch (error) {
console.error('获取文件内容时出错:', error)
throw error // 便
}
}
///
const infoParams = defineProps({
jsonurl: String,
examId: String,
age:String,
regId:String,
pname:String,
gender:String,
billDoctorDepartment:String,
doctorname:String,
Isgrid: Number,
Ismeasure: Number,
Isfd: Boolean,
lineratio: Number,
suduratio: Number,
isrefresh: Boolean,
iscorrect: Boolean,
printimage: Boolean,
extraInfo: {
type: Object,
default: () => ({
id: '',
orgId: '',
examId: '',
collectionTime: undefined,
hr: '',
paxle: '',
qrsAxle: '',
taxle: '',
ptimeLimit: '',
pr: '',
qrsTimeLimit: '',
qt: '',
qtc: '',
rv5: '',
sv1: '',
rv5Sv1: '',
snapshotTime: undefined,
autoDiagResult: '',
autoDiagTime: undefined,
doctorDiagResult: '',
doctorDiagTime: undefined,
doctorName: '',
doctorId: '',
departId: '',
departName: '',
isDelete: '',
deleteTime: undefined,
deleteDoctorName: '',
deleteDoctorId: '',
ecgDataFilePath: '',
ecgJsonDataFilePath: '',
createDate: undefined
})
}
})
//
watch(
[
() => infoParams.Isgrid,
() => infoParams.Ismeasure,
() => infoParams.lineratio,
() => infoParams.suduratio,
() => infoParams.isrefresh,
() => infoParams.iscorrect,
() => infoParams.Isfd
],
(
[
newIsGridValue,
newAnotherValue,
newlineratio,
newsuduratio,
newisrefresh,
newiniscorrect,
newfd
],
[oldIsGridView, oldAnotherValue, oldlineratio, oldsuduratio, oldisrefresh, oldiscorrect, oldds]
) => {
// isGrid
if (newIsGridValue !== oldIsGridView) {
//
Repaint()
}
// anotherValue
if (newAnotherValue !== oldAnotherValue) {
//
}
//
if (newlineratio !== oldlineratio) {
Repaint()
}
//
if (newsuduratio !== oldsuduratio) {
Repaint()
}
//
if (newisrefresh !== oldisrefresh) {
console.log(newisrefresh)
Repaint()
}
//
if (newiniscorrect !== oldiscorrect) {
isdialog.value = newiniscorrect
}
if (newfd !== oldds) {
FD.value = newfd
console.log(FD.value)
}
}
)
//
function recovery() {
transfer.value = []
beatArray1.value = beatArray2.value.slice()
Repaint()
}
//
function checkboxchage() {
checkes.value = transfer.value.length === 2 ? false : true
}
//
function clickdl() {
const one = transfer.value[0]
const two = transfer.value[1]
const oneindex = transferleads.indexOf(one)
const twoindex = transferleads.indexOf(two)
swapElements(oneindex, twoindex)
Repaint()
}
//
const swapElements = (index1, index2) => {
//
if (
index1 < 0 ||
index2 < 0 ||
index1 >= beatArray1.value.length ||
index2 >= beatArray1.value.length
) {
console.log('索引超出数组范围')
return
}
//
const temp = beatArray1.value[index1]
beatArray1.value[index1] = beatArray1.value[index2]
beatArray1.value[index2] = temp
}
//
function close() {
isdialog.value = false
transfer.value = []
emits('update:value', isdialog.value)
}
//
const lineratioMap = {
0.012: '2.5',
0.025: '5',
0.05: '10',
0.1: '20',
0.2: '40'
}
const suduratioMap = {
0.2: '5',
0.4: '10',
0.5: '12.5',
1: '25',
2: '50'
}
function convert(command, type) {
if (type === 0) {
return suduratioMap[command] || null
} else {
return lineratioMap[command] || null
}
}
//
function Repaint() {
eliminate(leftCanvasprint.value)
eliminate(rightCanvasprint.value)
eliminate(bottomCanvasprint.value)
eliminate(bottomCanvasprint1.value)
eliminate(topCanvasprint.value)
eliminate(topCanvasprint1.value)
Bottom()
}
//
const leads = [
'LEAD_I',
'LEAD_II',
'LEAD_III',
'LEAD_AVR',
'LEAD_AVL',
'LEAD_AVF',
'LEAD_V1',
'LEAD_V2',
'LEAD_V3',
'LEAD_V4',
'LEAD_V5',
'LEAD_V6'
]
const transferleads = ['I', 'II', 'III', 'avR', 'avL', 'avF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']
//
const lead_name = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF']
const rlead_name = ['V1', 'V2', 'V3', 'V4', 'V5', 'V6']
//
function handleFileChange() {
const json = JSON.parse(text.value)
leads.forEach((lead, index) => {
const value = json[lead].trim()
const commaSeparatedValue = value.replace(/\s+/g, ',')
const numberArray = commaSeparatedValue.split(',').map(Number)
beatArray1.value.push(numberArray)
})
beatArray2.value = beatArray1.value.slice()
}
//
function moveCanvas(direction, count) {
const xoffset = count === 1 ? 1 : count
if (direction === 'left') {
if (offset !== -20 * 60) offset -= spacing * xoffset
} else if (direction === 'right') {
if (offset !== 0) offset += spacing * xoffset
}
// if (direction === 'left') {
// offset -= spacing
// } else if (direction === 'right') {
// offset += spacing
// }
topCanvasprint.value.style.left = offset + 'px'
topCanvasprint1.value.style.left = offset + 'px'
}
//
function eliminate(c_canvas) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
}
function Bottom() {
drawbottomCanvasprint()
drawtopCanvasprint()
}
//
function drawbottomCanvasprint() {
if (infoParams.Isgrid === 1) {
lbackcanvas(leftCanvasprint.value)
rbackcanvas(rightCanvasprint.value)
drawMultipleLines(bottomCanvasprint.value, beatArray1.value)
drawMultipleLines(bottomCanvasprint1.value, beatArray1.value)
}
begin(leftCanvasprint.value, beatArray1.value)
beginr(rightCanvasprint.value, beatArray1.value)
}
//
function drawtopCanvasprint() {
drawMultipleLinesl(topCanvasprint.value, beatArray1.value)
drawMultipleLinesr(topCanvasprint1.value, beatArray1.value)
}
//
function lbackcanvas(c_canvas) {
drawBigBG(c_canvas)
drawSmallGrid(c_canvas)
drawMediumGrid(c_canvas)
}
function rbackcanvas(c_canvas) {
drawBigBG(c_canvas)
drawSmallGrid(c_canvas)
drawMediumGrid(c_canvas)
}
//
function drawBigBG(c_canvas) {
const context = c_canvas.getContext('2d')
context.globalAlpha = 1
context.fillStyle = 'rgba(0,0,0,0)'
context.fillRect(0, 0, c_canvas.width + 21, c_canvas.height + 21)
}
//
function drawSmallGrid(c_canvas) {
const context = c_canvas.getContext('2d')
context.globalAlpha = 1
context.fillStyle = '#ff6b64'
const dotMarginX = 20 / 5,
dotMarginY = 20 / 5
for (let i = dotMarginX; i < c_canvas.width; i += dotMarginX) {
if (i % 20 !== 0) {
for (let j = dotMarginY; j < c_canvas.height; j += dotMarginY) {
if (j % 20 !== 0) {
context.rect(i, j, 1, 0.5)
}
}
}
}
context.fill()
}
//
function drawMediumGrid(c_canvas) {
const context = c_canvas.getContext('2d')
context.globalAlpha = 1
context.strokeStyle = '#ff6b64'
context.strokeWidth = 1.5
context.beginPath()
for (let x = 0.5; x < c_canvas.width; x += 20) {
context.moveTo(x, 0)
context.lineTo(x, c_canvas.height)
}
for (let y = 0.5; y < c_canvas.height; y += 20) {
context.moveTo(0, y)
context.lineTo(c_canvas.width, y)
}
context.stroke()
context.closePath()
}
function begin(c_canvas, beatArray) {
const ctx = c_canvas.getContext('2d')
ctx.globalAlpha = 1
ctx.strokeStyle = '#000000'
let offset = -50
const spacing = 100
beatArray.forEach((dataArray, index) => {
if (index <= 6) {
const x = 0
const y = offset - dataArray[index - 1] * 0.025
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 15, y - 40)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 15, y - 40)
ctx.lineTo(x + 15, y)
ctx.moveTo(x + 15, y)
ctx.lineTo(x + 20, y)
ctx.stroke()
ctx.closePath()
if (index > 0 && index < 4) {
ctx.font = '15px Arial'
ctx.fillStyle = 'black'
ctx.fillText(lead_name[index - 1], x + 4, y + 14)
} else {
ctx.font = '10px Arial'
ctx.fillStyle = 'black'
ctx.fillText(lead_name[index - 1], x + 1, y + 12)
}
offset += spacing
}
})
ctx.strokeStyle = '#000000'
}
function beginr(c_canvas, beatArray) {
const ctx = c_canvas.getContext('2d')
ctx.globalAlpha = 1
ctx.strokeStyle = '#000000'
let offset = -50
const spacing = 100
beatArray.forEach((dataArray, index) => {
if (index <= 6) {
const x = 0
const y = offset - dataArray[index - 1] * 0.025
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 15, y - 40)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 15, y - 40)
ctx.lineTo(x + 15, y)
ctx.moveTo(x + 15, y)
ctx.lineTo(x + 20, y)
ctx.stroke()
ctx.closePath()
ctx.font = '11px Arial'
ctx.fillStyle = 'black'
ctx.fillText(rlead_name[index - 1], x + 2, y + 12)
offset += spacing
}
})
ctx.strokeStyle = '#000000'
}
//
function drawMultipleLines(c_canvas, beatArrays) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
drawBigBG(c_canvas)
drawSmallGrid(c_canvas)
drawMediumGrid(c_canvas)
}
//
function drawMultipleLinesl(c_canvas, beatArrays) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
let offset = 50
const spacing = 100
beatArrays.forEach((dataArray, index) => {
if (index <= 5) {
drawLine1(c_canvas, dataArray, offset, index)
offset += spacing
}
})
}
//
function drawMultipleLinesr(c_canvas, beatArrays) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
let offset = 50
const spacing = 100
beatArrays.forEach((dataArray, index) => {
if (index > 5) {
drawLine1(c_canvas, dataArray, offset, index)
offset += spacing
}
})
}
//线
function drawLine1(c_canvas, beatArray, offset, index) {
const ctx = c_canvas.getContext('2d')
ctx.lineCap = 'butt' // 线
ctx.lineJoin = 'miter' // 线
ctx.strokeStyle = '#030101'
ctx.lineWidth = 1
ctx.beginPath()
if (index <= 5) {
if (beatArray.length > 0) {
const firstX = 0
const firstY =0- beatArray[0] * infoParams.lineratio + offset
ctx.moveTo(firstX, firstY)
for (let i = 0; i < beatArray.length - 1; i++) {
const x2 = (0 + (i + 1) / 10) * infoParams.suduratio
const y2 = (0)-beatArray[i + 1] * infoParams.lineratio + offset
ctx.lineTo(x2, y2)
}
ctx.stroke()
ctx.closePath()
}
}
if (index > 5) {
if (beatArray.length > 0) {
const firstX = 0
const firstY = (0)-beatArray[0] * infoParams.lineratio + offset
ctx.moveTo(firstX, firstY)
for (let i = 0; i < beatArray.length - 1; i++) {
const x2 = (0 + (i + 1) / 10) * infoParams.suduratio
const y2 =(0)- beatArray[i + 1] * infoParams.lineratio + offset
ctx.lineTo(x2, y2)
}
ctx.stroke()
ctx.closePath()
}
}
}
</script>
<style scoped>
.printcontainer {
display: flex;
justify-content: center;
align-items: flex-start;
width: 1920px;
overflow-x: auto;
overflow-y: hidden;
height: 100%;
max-width: 3920px;
max-height: 3920px;
}
#canvas-containerprint,
#canvas-containerprint1 {
overflow-x: hidden;
overflow-y: hidden;
position: relative;
width: 50%;
height: 921px;
margin-right: 0;
}
#bottomCanvasprint,
#bottomCanvasprint1 {
position: absolute;
/* width: 1240; */
/* height: 75vh; */
z-index: 0;
margin-left: 20px;
}
#leftCanvasprint,
#rightCanvasprint {
position: absolute;
width: 21;
/* height: 1080px; */
z-index: 99;
background-color: rgb(255, 255, 255);
}
#topCanvasprint,
#topCanvasprint1 {
position: absolute;
width: 1920px;
/* height: 600px; */
z-index: 1;
background: transparent;
margin-left: 20px;
}
canvas {
position: absolute;
}
.myrtip {
display: flex;
flex-direction: row-reverse;
margin-right: 30px;
}
.mycheckbox-group {
display: flex;
flex-wrap: wrap;
}
.mycheckbox-item {
margin-left: 5px;
margin-right: 30px; /* 根据需要调整间距 */
margin-bottom: 10px; /* 根据需要调整间距 */
width: 30px;
}
.ecg-report {
font-family: Arial, sans-serif;
margin: 3px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
position: relative;
/* width: 90%; */
width: 1920px;
/* height: 921px; */
}
.ecg-main {
margin-top: 6px;
margin-bottom: 32px;
overflow-x: hidden;
overflow-y: hidden;
width: 100%;
}
/* 添加其他样式 */
</style>

View File

@ -0,0 +1,96 @@
<template>
<div class="myecg-report">
<!-- 心电图 -->
<div class="ecg-main">
<div class="container">
<el-image
style="width: 100%; height: 90vh"
:src="infoParams.imagebase64"
fit="fill"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'ReportInfoECG' })
/*
* 李传洋
* ReportInfoECG
**/
/** 导入内容 **/
const infoParams = defineProps({
id: String,
examId: String,
regId: String,
orgId: String,
pname: String,
gender: String,
birthday: String,
billDoctorDepartment: String,
applicationDate: String,
examDescription: String,
diagResults: String,
examItemName: String,
deviceName: String,
deviceType: String,
diagDoctor: String,
diagDate: String,
reviewDoctor: String,
reviewDate: String,
imagebase64: String
})
/** 钩子方法 **/
onMounted(async () => {})
/** 导出内容 **/
/**
* 备注
*
* **/
</script>
<style lang="scss" scoped>
/*** 报告单 ***/
.myecg-report {
font-family: Arial, sans-serif;
margin: 3px auto;
padding: 5px;
border-radius: 8px;
position: relative;
width: 90%;
overflow: hidden;
//min-height: 200mm;
}
.ecg-main {
// margin-top: 6px;
// margin-bottom: 32px;
}
.container {
display: flex;
justify-content: center;
align-items: flex-start;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
height: 100%;
max-width: 3920px;
max-height: 3920px;
}
canvas {
position: absolute;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>

View File

@ -333,7 +333,8 @@ const queryParams = reactive({
reviewDate: [],
thumbnailImgUrl: undefined,
createTime: [],
isFavourite: ''
isFavourite: '',
pdfurl:''
})
const queryFormRef = ref() //
const exportLoading = ref(false) //