|
|
|
|
<template>
|
|
|
|
|
<el-dialog
|
|
|
|
|
title="打印预览"
|
|
|
|
|
:visible.sync="visible"
|
|
|
|
|
width="800px"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
>
|
|
|
|
|
<!-- 外层容器:预留底部footer高度,防止遮挡 -->
|
|
|
|
|
<div id="print" class="print-container">
|
|
|
|
|
<div
|
|
|
|
|
v-if="templateContent && templateLoaded"
|
|
|
|
|
v-html="renderedTemplate"
|
|
|
|
|
class="report-body"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="loading">模板加载中...</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
|
|
|
<el-button @click="visible = false">取 消</el-button>
|
|
|
|
|
<el-button type="primary" @click="handlePrint" v-print="'#print'">
|
|
|
|
|
打 印
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
<script>
|
|
|
|
|
import {
|
|
|
|
|
patientSexList,
|
|
|
|
|
patientAgeTypeList,
|
|
|
|
|
postReportPrint,
|
|
|
|
|
} from "@/api/cases/index.js";
|
|
|
|
|
import { mapGetters } from "vuex";
|
|
|
|
|
export default {
|
|
|
|
|
name: "UltrasoundReportPrint",
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
visible: false,
|
|
|
|
|
report: {},
|
|
|
|
|
templateContent: "",
|
|
|
|
|
templateLoaded: false,
|
|
|
|
|
patientSexList: patientSexList(),
|
|
|
|
|
patientAgeTypeList: patientAgeTypeList(),
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
...mapGetters(["userInfo", "loginInfo", "netConfig"]),
|
|
|
|
|
renderedTemplate() {
|
|
|
|
|
if (!this.templateContent || !this.report) return "";
|
|
|
|
|
let content = this.templateContent;
|
|
|
|
|
const data = this.report;
|
|
|
|
|
// 占位符映射 + 空值兜底
|
|
|
|
|
const mappings = {
|
|
|
|
|
$$logo$$: this.renderLogo(),
|
|
|
|
|
$$checkbody$$: data.positions.length
|
|
|
|
|
? data.positions.map((item) => item.level2.name).join("|") +
|
|
|
|
|
(data.position_text ? "|" + data.position_text : "")
|
|
|
|
|
: data.position_text,
|
|
|
|
|
$$checknumber$$: data.id || "",
|
|
|
|
|
$$name$$: data.patient_name || "",
|
|
|
|
|
$$sex$$:
|
|
|
|
|
this.patientSexList.find((i) => i.value == data.patient_sex).label ||
|
|
|
|
|
"",
|
|
|
|
|
$$age$$:
|
|
|
|
|
data.patient_age + data.patient_age_type
|
|
|
|
|
? this.patientAgeTypeList.find(
|
|
|
|
|
(i) => i.value == data.patient_age_type
|
|
|
|
|
).label || ""
|
|
|
|
|
: "" || "",
|
|
|
|
|
$$examroom$$: data.exam_rooms.map((item) => item.name).join(", "),
|
|
|
|
|
$$requestdoctor$$: data.request_doctor || "",
|
|
|
|
|
$$pid$$: data.patient_id || "",
|
|
|
|
|
$$hospitalnumber$$: data.hospitalization_number || "",
|
|
|
|
|
$$areanumber$$: data.area_number || "",
|
|
|
|
|
$$bednumber$$: data.bed_number || "",
|
|
|
|
|
$$ctimgtable$$: this.renderImages(),
|
|
|
|
|
$$checkview$$: data.text_comment || "",
|
|
|
|
|
$$checkconclusion$$: data.text_conclusion || "",
|
|
|
|
|
$$reportor_sig$$: this.renderSignature(),
|
|
|
|
|
$$createtime$$: data.create_time || "",
|
|
|
|
|
$$creati$$: data.create_time || "",
|
|
|
|
|
};
|
|
|
|
|
// 转义$符号,安全全局替换
|
|
|
|
|
Object.keys(mappings).forEach((key) => {
|
|
|
|
|
const reg = new RegExp(key.replace(/\$/g, "\\$"), "g");
|
|
|
|
|
content = content.replace(reg, mappings[key]);
|
|
|
|
|
});
|
|
|
|
|
return content;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
async print(report) {
|
|
|
|
|
this.report = JSON.parse(JSON.stringify(report));
|
|
|
|
|
this.templateLoaded = false;
|
|
|
|
|
this.templateContent = "";
|
|
|
|
|
await this.loadTemplate();
|
|
|
|
|
this.visible = true;
|
|
|
|
|
},
|
|
|
|
|
async loadTemplate() {
|
|
|
|
|
const tpl = this.loginInfo?.report_tpl;
|
|
|
|
|
if (tpl) {
|
|
|
|
|
const templateUrl =
|
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + tpl;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(templateUrl);
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
this.templateContent = await res.text();
|
|
|
|
|
this.templateLoaded = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn("远程模板加载失败");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await this.loadLocalTemplate();
|
|
|
|
|
this.templateLoaded = true;
|
|
|
|
|
},
|
|
|
|
|
async loadLocalTemplate() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/report-template.html");
|
|
|
|
|
this.templateContent = res.ok ? await res.text() : "";
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("本地模板加载失败", e);
|
|
|
|
|
this.templateContent = "";
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
renderLogo() {
|
|
|
|
|
const logoSrc = this.loginInfo?.report_logo;
|
|
|
|
|
if (!logoSrc) return "";
|
|
|
|
|
const baseUrl =
|
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS || "";
|
|
|
|
|
// 关键:设置Logo高度和对齐方式,和第二张图一致
|
|
|
|
|
return `<img src="${baseUrl}${logoSrc}" style="float: left;height: 100px;width: auto;margin: 0 auto;display: block;" alt="医院logo"/>`;
|
|
|
|
|
},
|
|
|
|
|
renderImages() {
|
|
|
|
|
const images =
|
|
|
|
|
this.report.attachment?.filter((x) => x.showInDoc === 1) || [];
|
|
|
|
|
if (images.length === 0)
|
|
|
|
|
return '<div style="text-align:center;padding:20px;">未附超声图像</div>';
|
|
|
|
|
const baseUrl =
|
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS || "";
|
|
|
|
|
let html =
|
|
|
|
|
'<div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:center;">';
|
|
|
|
|
images.forEach((item) => {
|
|
|
|
|
html += `<img src="${baseUrl}${item.bucket_compress}/${item.object_compress}" style="width:100px;height:80px;object-fit:cover;">`;
|
|
|
|
|
});
|
|
|
|
|
return html + "</div>";
|
|
|
|
|
},
|
|
|
|
|
renderSignature() {
|
|
|
|
|
const sign = this.loginInfo?.signature;
|
|
|
|
|
if (!sign) return "";
|
|
|
|
|
const baseUrl =
|
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS || "";
|
|
|
|
|
return `<img src="${baseUrl}${sign}" style="height:13px;vertical-align:middle;">`;
|
|
|
|
|
},
|
|
|
|
|
handlePrint() {
|
|
|
|
|
postReportPrint(this.report.id);
|
|
|
|
|
setTimeout(() => (this.visible = false), 300);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
/* 预览容器:核心 - 底部留出足够间距,防止绝对定位footer被遮挡 */
|
|
|
|
|
.print-container {
|
|
|
|
|
position: relative;
|
|
|
|
|
/* 给底部footer预留高度,正文内容永远不会盖住它 */
|
|
|
|
|
padding-bottom: 120px;
|
|
|
|
|
min-height: 600px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.report-body {
|
|
|
|
|
width: 100%;
|
|
|
|
|
font-family: "宋体", SimSun, sans-serif;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.loading {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 60px 0;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========== 标准打印样式(修复页眉页脚) ========== */
|
|
|
|
|
@media print {
|
|
|
|
|
@page {
|
|
|
|
|
size: A4;
|
|
|
|
|
margin: 15mm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
html,
|
|
|
|
|
body {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 隐藏弹窗头部、底部按钮栏 */
|
|
|
|
|
.el-dialog__header,
|
|
|
|
|
.el-dialog__footer {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 打印时取消内边距,还原真实报表 */
|
|
|
|
|
.print-container {
|
|
|
|
|
padding-bottom: 0 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 打印时footer固定在页面底部,不分页、不遮挡 */
|
|
|
|
|
::v-deep .footer {
|
|
|
|
|
position: fixed !important;
|
|
|
|
|
bottom: 15mm !important;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 98% !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 清除code默认样式,避免报告单样式错乱 */
|
|
|
|
|
::v-deep code {
|
|
|
|
|
background: transparent !important;
|
|
|
|
|
padding: 0 !important;
|
|
|
|
|
font-family: inherit !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|