《优雅实现Web端会诊记录单打印:基于Vue 3与print-js的完整实践》

263 阅读5分钟

引言

在医疗管理后台、OA系统等To B场景中,将网页上的数据、表单精准无误地打印成纸质文档是一个高频且关键的需求。不同于普通的页面打印,业务单据打印要求格式固定、布局精确,且要屏蔽浏览器页眉页脚等无关信息。

本文将基于一个真实的Vue 3 + TypeScript + Element Plus医疗会诊项目,手把手带你实现一个功能完备的会诊记录单打印功能。我们将使用强大的vue3-print-nb库,解决打印过程中的各种痛点。

技术栈与效果预览

  • 框架: Vue 3 + TypeScript
  • UI库: Element Plus
  • 打印库vue3-print-nb

一、实现思路与流程

我们的目标是点击一个按钮,弹出一个选择框,用户选择“直接打印”后,能准确打印出另一份隐藏的、专为打印格式化的会诊单组件。

其核心流程如下图所示:

image.png

二、代码拆解与详解

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: '&nbsp',       // 打印页面的标题,设为空
  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>

打印样式核心技巧:

  1. display: none: 默认在屏幕上隐藏。
  2. @media print: 打印时生效的样式。
  3. visibility: hidden/visible: 一个经典的打印技巧,先隐藏所有,再单独显示要打印的部分,完美解决只打印特定区域的问题。
  4. page-break-after: always: 控制分页,确保每张会诊单都从新的一页开始打印。

三、常见问题与优化 (Q&A)

Q1: 为什么打印出来的样式和屏幕上不一样?
A:  浏览器打印使用默认的打印样式表,并且会忽略部分CSS属性(如position: fixed, 背景色)。务必在@media print中重写所有布局样式,使用cmmmpt等绝对单位更可靠。

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项目中集成打印功能。其核心思路是:

  1. 分离内容: 准备一个专为打印优化的隐藏组件。
  2. 精准控制: 使用id绑定和打印媒体查询样式,确保所见即所印。
  3. 良好体验: 通过指令的回调函数,可以轻松实现打印时的Loading状态等交互反馈。

这种方案清晰、解耦,是处理复杂业务打印需求的绝佳实践。