☰
网页导出PDF表格分页乱?先加这段打印CSS
2026/9/29 9:53:54 网站建设 项目流程

业务方上周把一份履约周报的 PDF 退回来了,理由是字太小、看不清。那页报表在浏览器里是正常的,46 行明细、列宽也够,组里同学按 Ctrl+P 存成 PDF,出来就是三页,表格里的字缩成一团。换了个在线转换工具再导一遍,这回不分页了,变成很长的一页,字确实大了,但业务方打印出来又说纸放不下。两次都不对,问题看着像是工具挑得不对。

我把这件事当成一次正经的对照来跑,结论和我原先的判断相反:这不是工具的差别,是这张网页有没有写打印 CSS 的差别。同一份 HTML,不写打印 CSS 的时候,两条路各按各的默认走,差得很远;补上打印 CSS 之后,两条路的产物逐项一模一样。

先说测试条件

样本是我自己造的一份跨境履约周报,品牌叫潮汐盒 Tidebox,是编的,SKU、仓库、渠道、金额全部虚构,不涉及任何真实业务。页面结构就是中后台最常见的那种:顶部四个指标卡,下面一张 46 行的明细表,表头是深色底的thead。

两条路分别是:浏览器自带的打印导出和图映 ImgIng(一个免费的在线HTML转PDF工具)的 HTML 转 PDF。跑在同一台 Mac 上、同一个 Chromium 149 内核、视口 1440×1000,所以两边的排版差异不会来自浏览器版本。产物我没有靠肉眼看,是用 pypdf 和 PyMuPDF 把页数、每页尺寸、表格字号、能提取到的文字数逐项量出来的。

图映这条线的处理位置得单独交代,被合规管着的团队尤其要看清楚。我开着 Network 面板逐条盯过一轮:导入文件、读取目录、映射资源、即时预览,连拿到 PDF 之后那一步无损最小化,都是浏览器在本地跑完的,这一整段没有任何上行。真正的那一次上行发生在点下转换之后——一份脚本已被剥掉、资源全部内联的自包含 HTML 快照,经一次 POST 送到同源的 imging.cn 接口,服务端的 Chromium / Skia 渲完把 PDF 回给你。它的文档能力里,会出本机的就只有这一条。计数也对得上:点转换之前非 GET 请求为 0,点了之后恰好加一。含客户数据的报表走不走这条线,得你们法务说了算,别默认它是纯本地的。

不写打印 CSS,两条路各走各的

先看那份完全没有打印 CSS 的长表。浏览器打印出来是 3 页,每页 612×792pt——注意这个尺寸是 Letter,不是很多人默认以为的 A4。为了把 1180px 宽的报表塞进 816pt 宽的纸,Chromium 会做一次 shrink-to-fit,原本 13px 的表格字被压到 6.74pt,大概相当于 9px,文件 431,114 字节。

另一条路默认跟随页面的真实尺寸,不分页也不缩放,出来是 1 页 1080×2379pt,表格字 9.75pt,204,648 字节。字看着大,是因为它根本没有缩放这一步,不是渲染得更清楚——同一块区域放大了看,两边的字形清晰度我没量出差别。它的代价是这一页比 A4 高出一大截,直接送进打印机就是刚才业务方抱怨的那种情况。

顺便纠正一个我自己也信过的说法:表格行并没有被从中间切断。我给每一行埋了两处唯一标记,只要同一个标记落在两页上就算切断,七份样本、所有导出路径数下来都是 0 行。Chromium 这一版遇到空间不够是整行推到下一页,不会把一行劈成两半。所以“表格被拦腰截断”这个锅,至少在现在的浏览器上按不上去。

补上三行打印 CSS,两条路合流

真正起作用的是这三处写法,加在页面的样式里就行:

第一处是@page,声明纸张、方向和页边距,比如 A4 纵向、上下左右各十几毫米。没有它,分不分页、用多大的纸,全由导出那一侧自己决定,你在页面里怎么排都没用。第二处是给thead写display:table-header-group,让表头在每一页重复。第三处是给tr写break-inside:avoid,避免多行单元格从行内被断开。

补齐之后再各导一遍,两边都是 7 页、每页 595×842pt 的 A4、表头在 7 页上各出现一次,表格字 8.25pt,能提取到的文字一致率 99.32%,逐项相同。到这一步,用哪个工具已经不影响结果了——这就是我说的分水岭。

还有个细节值得记一下:@page不一定要裸写在样式表顶层。我另做了一份把它放进@media print{}里的对照,产物和裸写那份只差 30 字节,页面规格、页数、字号、文字一致率全部相同,两边都认。所以不用为了它生效而破坏页面原本的样式组织。

最值钱的一条:别让表格待在滚动容器里

这是我这轮踩到的唯一一个真正丢数据的坑,也是我觉得所有做中后台的人都该看一眼的地方。

我们后台的报表页,表格通常是包在一个定高的滚动区里的——height:520px; overflow:auto,Element UI、Ant Design 的表格组件默认就是这个形态。这样的页面导出 PDF,只会保留容器里当时看得见的那一屏。我那份 46 行的表,导出来只有 R01 到 R08 共 8 行,剩下 38 行一条都没进 PDF,能提取到的文字只剩原文的 13.40%。

浏览器打印和在线转换工具在这一点上表现完全一样,8 行对 8 行,没有哪一边能救。更难受的是两边界面都没有任何提示,进度显示正常、质量报告也写得很正常,你不数行数根本发现不了。要是这份 PDF 直接发给了客户,少掉的三十多行是谁也没机会发现的。

躲开的办法很朴素:导出之前把滚动容器还原成自然高度。可以在打印样式里写一条,让那个容器的高度不限、溢出可见;也可以在点导出之前用一段脚本临时改掉。我们现在是前者,写进了报表页的公共打印样式,省得每个人各记一遍。

导出前我现在会过的几件事

翻一遍页面里所有overflow:auto的容器,凡是scrollHeight比clientHeight大的,导出前都放开;确认页面里有没有@page,没有就别指望导出结果听你的;导出完不要只看第一页,翻到最后,数一下明细行数对不对得上。最后一条是如果报表带客户数据,先搞清楚你用的那个工具在哪一步上传、上传了什么——前面几条挡的是返工,这条挡的是事故,我被法务盯过三个月,所以记得特别牢。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询