Vue3 前端生成 PDF:会员证书与活动签到表的三种打印方案与踩坑记录

商协会系统里有几处固定要出纸质件:会员证书、活动签到表、邀请函。最早我们是在服务端生成 PDF 再让用户下载,后来发现这条路有个绕不开的问题------模板改一点就要发一次版,而且服务端的字体和排版跟设计稿总有点出入。

后来把这几处改成前端生成。这篇把三种方案的实际效果和我们最后的选型记下来。

先说需求特点,它决定了选型方向。这类文档有几个共同点:版式固定、有公章或背景图、中文字体不能乱、要能直接进打印机。其中"直接进打印机"这一条很关键------用户点一下就要出纸,不接受先下载再打开。

第一种是浏览器原生打印,也就是 print CSS。

思路是把要打印的内容渲染好,用 @media print 控制打印时的样式,调用 window.print() 唤起系统打印对话框,用户在里面选"另存为 PDF"。

优点是清晰度高(矢量输出)、分页由浏览器处理、中文字体直接用系统字体不会乱、零依赖。缺点也很直接:不同浏览器打印效果有差异,页边距和分页位置不完全可控。

```css

@media print {

@page {

size: A4;

margin: 12mm;

}

/* 打印时隐藏页面其他部分,只留目标容器 */

body * {

visibility: hidden;

}

.print-area,

.print-area * {

visibility: visible;

}

.print-area {

position: absolute;

left: 0;

top: 0;

width: 100%;

}

/* 关键:避免内容被从中间截断 */

.cert-item,

.sign-row {

page-break-inside: avoid;

break-inside: avoid;

}

/* 背景图和底色默认不打印,需要显式开启 */

.stamp,

.bg {

-webkit-print-color-adjust: exact;

print-color-adjust: exact;

}

ahref::after {

content: '';

}

}

```

这段样式里有两个坑值得单独说。

一是背景不打印。浏览器默认不输出背景色和背景图,公章、底纹这些会整块消失。要显式加 print-color-adjust: exact,同时提醒用户在打印对话框里勾上"背景图形"。

二是隐藏其他元素用 visibility 而不是 display。用 display: none 会让被隐藏的元素不再占位,打印区域的位置就跑偏了;visibility 保留占位,版面不动。

第二种是 html2canvas 加 jsPDF,也就是截图式。

把 DOM 转成图片再塞进 PDF。优点是所见即所得,浏览器之间一致性好,适合版式复杂、有渐变和阴影的设计。缺点也明显:本质是位图,放大就模糊;长列表分页要自己算高度切图,容易从行中间切断;中文如果字体没加载完就截图会出方块。

我们只在一种情况下用它:邀请函这类单页、版式花哨、不需要打印清晰度的内容。

第三种是纯前端用 PDF 库直接绘制,比如 jsPDF 直接写文本和图形。清晰度好、体积小,但所有坐标都要手算,中文还要处理字体嵌入,工作量偏大。批量证书这种版式固定的场景其实合适,但我们评估后觉得维护成本不划算,没采用。

最后的选型是分场景:签到表和批量证书走 print CSS,邀请函走截图式。

代码实现上,我们封了一个组合式函数,把"打开打印区域---等资源加载---调用打印---清理"这几步串起来。

```ts

import { nextTick } from 'vue';

export function usePrint() {

const printing = ref(false);

async function print(el: HTMLElement) {

printing.value = true;

document.body.classList.add('printing');

el.classList.add('print-area');

try {

// 等图片和字体就绪,否则截图方案会缺图

await Promise.all(

Array.from(el.querySelectorAll('img'))

.filter((img) => !img.complete)

.map((img) => new Promise((r) => { img.onload = img.onerror = r; }))

);

await document.fonts?.ready;

await nextTick();

window.print();

} finally {

// afterprint 事件在部分浏览器不触发,这里兜底清理

setTimeout(() => {

el.classList.remove('print-area');

document.body.classList.remove('printing');

printing.value = false;

}, 300);

}

}

return { printing, print };

}

```

等图片和字体这一步不能省。截图式方案里如果图片没加载完就转 canvas,出来的 PDF 是空白块;print CSS 方案里字体没加载完,打印机会用兜底字体顶上,版面全乱。

还有几个实测出来的细节。

批量打印证书时,每页一张要控制分页。做法是每个证书外层套一个固定高度的容器,高度按 A4 减去边距算好,配合 page-break-inside: avoid,浏览器就会一页一张。如果高度算错几毫米,就会出现一页挤两张或者第二页空白的情况。

签到表这类长表格,表头要在每页重复。用 thead 标签浏览器会自动处理,但如果是 div 模拟的表格就没这个效果,这种情况要么改用 table,要么用 position: fixed 的表头(打印时固定定位行为不一致,不太推荐)。

页眉页脚加页码只能靠 @page 里的 margin box,但浏览器支持度一般。我们的做法是在页面内容里预留一个页码位,用 CSS counter 配合分页符手动递增,虽然笨但可控。

最后说说生成质量的验收。我们列了三条:A4 纸直接打印不缩放、公章颜色正确、中文无方块。这三条过了,其他细节都好商量。

我们这边在跑的商会管理系统叫未来漫城·商会互联平台,会员证书和签到表用的就是 print CSS 那条路,模板改版式直接在前端改,不用发服务端版本。

一句话总结:能交给浏览器打印的,就别自己截图。清晰度和可维护性都是前者赢,截图方案留给确实没法用 print CSS 表达的复杂版式。

相关推荐
奕鼎竜瑆7 小时前
Solid 前端响应式开发从零到精通
前端·人工智能
动恰客流统计8 小时前
景区客流统计怎么做?兼顾管控与运营的实施方案解析
大数据·前端·人工智能
派小心.8 小时前
A/B测试工具免费版够用吗?
前端·数据分析
前端snow9 小时前
ai agent --- redis 缓存
前端
duanshu_10 小时前
官网表单与下载异常怎么排查?五个环节定位故障
前端·自动化·flyweight
Zhou14113610 小时前
SpringBoot_03_Web开发
前端·spring boot·python
辻弋20110 小时前
【无标题】
大数据·服务器·前端·搜索引擎·开源软件
FluxArt11 小时前
GPT Image 2.5 中文提示词教程:Flux Art 可复用模板
前端·gpt·算法
搬砖的小码农_Sky12 小时前
AI Agent:Windows上Node.js安装后 npm -v报错
前端·npm·node.js·人机交互
计算机魔术师12 小时前
半年翻倍!OpenAI再融300亿,估值冲到1.4万亿
前端