找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

339

积分

0

好友

39

主题
发表于 2025-12-24 16:48:40 | 查看: 30| 回复: 0

在前端开发中,尤其是在使用 Vue 与 ElementUI 构建管理后台时,表格数据的打印与导出为PDF是常见的需求。本文将分享两种实践方案,分别解决直接浏览器打印的样式问题,以及将HTML内容高质量转换为PDF文件的技术要点。

一、项目背景与核心问题

假设你在一个 Vue 项目中使用了 ElementUI 的表格组件,并希望实现以下功能:

  • 功能需求:用户点击“打印”按钮时,能将当前表格内容以整齐的样式、固定的列宽完整打印出来。
  • 技术实现:通过 :style 动态控制表格宽度(打印时固定为 200mm,页面显示时为 100%)。
  • 遇到问题
    1. 直接设置打印宽度可能影响页面正常显示的美观性。
    2. 表格的渲染是异步的,即使使用 nextTickMutationObserver,也可能在列宽计算完成前就触发了打印,导致打印内容显示不全。

二、方案一:浏览器直接打印优化

本方案的核心思想是:动态切换打印样式 -> 监听表格渲染完成 -> 构建独立的打印DOM -> 调用打印API -> 恢复页面状态

1. HTML 结构

在模板中,我们通过 isPrint 状态控制表格的宽度模式。

<el-button type="primary" icon="el-icon-printer" @click="print">打 印</el-button>

<el-table
  :style="{ width: isPrint ? '200mm' : '100%' }"
  ref="refTable"
  :data="tableList"
  border
>
  <el-table-column prop="name" label="第一列" width="100px" align="center"></el-table-column>
  <el-table-column prop="age" label="年龄" width="90px" align="center"></el-table-column>
  <!-- 更多列... -->
</el-table>

2. JavaScript 逻辑与解析

打印功能的关键在于确保表格完全渲染后再执行打印操作。

export default {
  data() {
    return {
      tableList: [],
      isPrint: false,
    };
  },
  methods: {
    print() {
      this.isPrint = true; // 1. 切换到打印宽度模式
      const el = this.$refs.refTable.$el;
      const body = el.querySelector('.el-table__body');
      const originWidth = body.offsetWidth; // 2. 记录初始宽度
      let setIntervalClear = undefined;

      // 3. 启动定时器监听宽度变化
      setIntervalClear = setInterval(() => {
        const width = body.offsetWidth;
        if (originWidth !== width) { // 4. 宽度变化,说明渲染完成
          // 5. 构建独立的打印DOM
          const dom = document.createElement('div');
          const style = `<style>@media print { @page { size: 210mm 297mm !important; margin: 2mm; } }</style>`;
          const html = el.outerHTML;
          dom.innerHTML = `<div>${style}${html}</div>`;

          // 6. 执行打印 (假设使用了this.$print插件或window.print())
          this.$print(dom);

          // 7. 清理并恢复状态
          clearInterval(setIntervalClear);
          this.isPrint = false;
        }
      }, 16); // 约60fps的频率检测,平衡性能与及时性
    },
  }
}

代码解析与要点

  • 切换模式this.isPrint = true 触发表格宽度变为固定值,为打印做准备。
  • 监听渲染:ElementUI表格的列宽计算是异步的。这里采用定时器循环检测表格内容区域(.el-table__body)的offsetWidth,当其发生变化时,表明内部布局已渲染稳定。这是解决打印不全的关键,单纯的 nextTick 无法捕获此变化。
  • 构建打印DOM:创建一个新的 div,将表格HTML和专为打印设计的 @page CSS规则注入其中,确保打印输出独立于页面布局,且纸张尺寸正确。
  • 恢复状态:打印指令发出后,立即清除定时器并将 isPrint 置回 false,表格恢复自适应宽度,不影响用户继续操作。

3. 方案一处理流程图

下图清晰地展示了上述浏览器打印方案的核心流程与决策点:

打印流程图

三、方案二:HTML 转 PDF 生成与下载

如果你需要生成PDF文件供用户下载,而不仅仅是调用打印机,可以结合 html2canvasjsPDF 库来实现。

1. 安装依赖

npm install html2canvas jspdf

2. 核心实现代码

此方案同样需要等待表格渲染完成,再进行截图和PDF生成。

import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

export default {
  methods: {
    async createAndUploadPDF() {
      // 显示加载提示
      const loading = this.$loading({ lock: true, text: '生成PDF中...' });

      const el = document.getElementById('idMain');
      const body = el.querySelector('.el-table__body');
      const originWidth = body.offsetWidth;
      let setIntervalClear = undefined;

      el.style.width = '210mm'; // 切换为PDF宽度
      // 监听渲染完成
      setIntervalClear = setInterval(async () => {
        const width = body.offsetWidth;
        if (originWidth === width) return; // 宽度未变,继续等待

        clearInterval(setIntervalClear);
        // 使用html2canvas将DOM转换为Canvas
        const canvas = await html2canvas(el, {
          scale: window.devicePixelRatio || 2, // 提升清晰度
          useCORS: true, // 处理图片跨域
          backgroundColor: '#FFFFFF',
        });

        // 使用jsPDF处理Canvas
        const pdf = new jsPDF('p', 'mm', 'a4');
        const imgData = canvas.toDataURL('image/png');
        const imgWidth = 210; // A4纸宽
        const imgHeight = (canvas.height * imgWidth) / canvas.width;

        // 动态生成文件名(处理IOS时区问题)
        let fileName = Date.now() + 8 * 60 * 60 * 1000; // 增加8小时(北京时间偏移)
        fileName = new Date(fileName).toISOString(); // 转为ISO字符串
        fileName = fileName.replace(/[-:Z]/g, '').replace(/[T.]/g, '_'); // 格式化

        // 添加图片到PDF,处理多页情况
        let position = 0;
        let heightLeft = imgHeight;
        pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
        while (heightLeft > 297) { // A4纸高297mm
          position -= 297;
          pdf.addPage();
          pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
          heightLeft -= 297;
        }
        // 保存PDF文件
        pdf.save(`${fileName}.pdf`);

        // 恢复状态
        el.style.width = '100%';
        loading.close();
      }, 16);
    }
  }
}

技术要点说明

  • 清晰度优化html2canvas 配置中的 scale 参数使用 devicePixelRatio,可以在高分辨率屏幕上获得更清晰的图片。
  • IOS时间格式化:在生成文件名时,对 Date.now() 增加了8小时偏移并调用 toISOString(),是为了避免在IOS设备上因时区处理差异导致的时间戳错误,这是一种常见的兼容性处理技巧。
  • 多页PDF处理:当转换的内容高度超过单页A4纸时,通过循环 addPage()addImage() 实现内容分页。

3. 相关样式

为目标区域添加一些基础内边距,使PDF内容看起来更舒适。

.pdf_area {
  padding: 18px;
  box-sizing: border-box;
}
.pdf_area h2 {
  margin-bottom: 18px;
}

四、总结

两种方案各有适用场景:

  • 方案一(直接打印):适合需要快速调用系统打印服务的场景,通过巧妙的异步监听确保了打印内容的完整性。
  • 方案二(HTML转PDF):适合需要生成文件供用户下载、分享或存档的场景,提供了更好的跨平台一致性,但依赖于前端库。

无论哪种方案,其核心挑战都在于如何确保动态渲染的组件(如ElementUI表格)在输出前已完全布局稳定。文中使用的定时器检测宽度变化的方法,是在 JavaScript 异步渲染环境下一种简单有效的解决方案。掌握这些技巧,能帮助你更从容地应对复杂的前端打印与导出需求,构建更健壮的 Node.js 全栈或前端应用。




上一篇:FreeVM虚拟机迁移实战:从vCenter导出到镜像转换与完整部署指南
下一篇:RDMA与RoCEv2十年反思:协议演进与无损网络陷阱
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-12 09:50 , Processed in 0.226686 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表