引言
在医疗管理后台、OA系统等To B场景中,将网页上的数据、表单精准无误地打印成纸质文档是一个高频且关键的需求。不同于普通的页面打印,业务单据打印要求格式固定、布局精确,且要屏蔽浏览器页眉页脚等无关信息。
本文将基于一个真实的Vue 3 + TypeScript + Element Plus医疗会诊项目,手把手带你实现一个功能完备的会诊记录单打印功能。我们将使用强大的vue3-print-nb库,解决打印过程中的各种痛点。
技术栈与效果预览
- 框架: Vue 3 + TypeScript
- UI库: Element Plus
- 打印库: vue3-print-nb
一、实现思路与流程
我们的目标是点击一个按钮,弹出一个选择框,用户选择“直接打印”后,能准确打印出另一份隐藏的、专为打印格式化的会诊单组件。
其核心流程如下图所示:
二、代码拆解与详解
1. 打印入口弹窗 (PrintDialog.vue)
这个组件是打印功能的控制中心。
<template>
<el-dialog v-model="dialogVisible" title="打印会诊单" width="600">
<!-- ... 对话框内容 ... -->
<div v-print="printObj" class="img_box printer_box" @click="handleSelect('printer')">
<el-icon><Printer /></el-icon>
<div>直接打印</div>
</div>
<!-- ... -->
</el-dialog>
<!-- 关键: 隐藏的打印内容组件 -->
<ConsultationSheet :print-array="printParams" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
// 1. 导入打印指令
import print from 'vue3-print-nb';
// 2. 控制弹窗显示与否
const dialogVisible = ref(false);
// 3. 存储要打印的数据
const printParams = ref([]);
// 4. 打开弹窗并接收数据的方法 (由父组件调用)
function openDialog(params: any) {
dialogVisible.value = true;
printParams.value = params; // 接收来自父组件的会诊单数据
console.log('打印数据:', params);
}
// 5. 定义打印配置对象
const printObj = ref({
id: 'consultationPrint', // 🎯 核心: 指定要打印的DOM元素的ID
popTitle: ' ', // 打印页面的标题,设为空
extraHead: '', // 可注入额外的HTML到打印文档的<head>中,用于自定义样式
previewTitle: '打印预览', // 预览窗口的标题
previewPrintBtnLabel: '确认打印', // 预览按钮文字
zIndex: 20003, // 预览窗口的z-index,确保在最上层
// 一系列生命周期回调函数,用于处理打印前后的状态
beforeOpenCallback(vue) {
console.log('打印开始前');
vue.printLoading = true;
},
openCallback(vue) {
console.log('打印窗口打开');
vue.printLoading = false;
},
closeCallback() {
console.log('打印窗口关闭');
},
});
// 将方法暴露给父组件,这是<script setup>的用法
defineExpose({ openDialog });
</script>
代码注释:
v-print="printObj":vue3-print-nb提供的指令,绑定打印配置。id: 'consultationPrint': 这是最重要的配置,指向被打印组件的根元素ID。ConsultationSheet: 这是实际渲染打印内容的子组件,通过:print-array接收数据。它平时在页面中是隐藏的,只有在打印时才会被vue3-print-nb处理并显示。
2. 打印内容组件 (ConsultationSheet.vue)
这个组件是打印出来的具体内容,它的核心在于样式,必须使用打印媒体查询来精确控制它在纸张上的样子。
<template>
<!-- 🎯 打印指令就是通过这个ID找到这个组件的 -->
<div id="consultationPrint" class="consultation-sheet-wrapper">
<div v-for="(item, index) in printArray" :key="index" class="page">
<!-- 这里是具体的会诊单HTML结构 -->
<h1>会诊记录单</h1>
<div class="patient-info">
<span>姓名: {{ item.name }}</span>
<span>会诊号: {{ item.consultationNo }}</span>
<!-- ... 其他字段 ... -->
</div>
<div class="diagnosis">
<h3>诊断意见</h3>
<p>{{ item.diagnosisOpinion }}</p>
</div>
<!-- ... 更多内容 ... -->
</div>
</div>
</template>
<script setup lang="ts">
defineProps({
printArray: {
type: Array,
default: () => [],
},
});
</script>
<style scoped>
/* 默认情况下,在屏幕上隐藏打印组件 */
.consultation-sheet-wrapper {
display: none;
}
/* 打印样式 - 当媒体类型为打印时生效 */
@media print {
body * {
visibility: hidden; /* 1. 隐藏body下的所有元素 */
}
#consultationPrint,
#consultationPrint * {
visibility: visible; /* 2. 只让打印组件及其子元素显示 */
}
#consultationPrint {
display: block !important; /* 3. 确保打印组件是块级元素 */
position: absolute;
left: 0;
top: 0;
width: 100%;
}
/* 4. 精确控制分页,避免内容被切断 */
.page {
page-break-after: always; /* 每一份会诊单后都强制分页 */
}
}
</style>
打印样式核心技巧:
display: none: 默认在屏幕上隐藏。@media print: 打印时生效的样式。visibility: hidden/visible: 一个经典的打印技巧,先隐藏所有,再单独显示要打印的部分,完美解决只打印特定区域的问题。page-break-after: always: 控制分页,确保每张会诊单都从新的一页开始打印。
三、常见问题与优化 (Q&A)
Q1: 为什么打印出来的样式和屏幕上不一样?
A: 浏览器打印使用默认的打印样式表,并且会忽略部分CSS属性(如position: fixed, 背景色)。务必在@media print中重写所有布局样式,使用cm, mm, pt等绝对单位更可靠。
Q2: 如何自定义打印的页眉页脚?
A: 浏览器限制,无法通过CSS完全去除或自定义浏览器的默认页眉页脚。但可以通过以下方式影响:
- 在打印设置中选择“更多设置” -> 选项 -> 页眉和页脚,设置为“无”。
- 使用
@page { margin: 0; }将页边距设为0,使默认页眉页脚“消失”在页面外(内容可能会被裁剪,需谨慎)。
Q3: 打印多页时,如何避免表格行被拆断?
A: 在表格元素的样式中添加:
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
四、总结
通过vue3-print-nb库,我们可以非常便捷地在Vue 3项目中集成打印功能。其核心思路是:
- 分离内容: 准备一个专为打印优化的隐藏组件。
- 精准控制: 使用
id绑定和打印媒体查询样式,确保所见即所印。 - 良好体验: 通过指令的回调函数,可以轻松实现打印时的Loading状态等交互反馈。
这种方案清晰、解耦,是处理复杂业务打印需求的绝佳实践。