一、前言
在供应链业务系统中,打印是一项难以被完全数字化替代的基础能力。尽管系统形态不断向 Web 化、平台化演进,但在仓储、物流、配送等线下环节,大量核心流程依然依赖纸质单据(如面单、送货单、运输单)完成流转与交接。
打印的稳定性与一致性,往往直接影响现场作业效率。一旦打印出现异常,问题会迅速放大,甚至直接阻断线下业务流程。
基于真实的供应链项目实践,本文将围绕浏览器打印这一常被低估的能力,系统性地梳理主流方案的技术取舍,并重点展开基于 window.print 的实现思路与工程实践。
二、供应链系统中的主流打印方案调研
围绕“如何将单据稳定、可靠地打印出来”,在实际项目中常见的 Web 打印方案大致可以分为三类:
下面结合实际项目经验,对这三类方案进行简要分析。
1.基于DOM浏览器原生打印方案
这是目前最常见、也是前端介入成本最低的一种打印实现方式。
其核心思路是:
直接使用浏览器原生打印能力,将页面或指定 DOM 节点渲染为打印内容。
打印效果如下图:

实现方式
官网地址:传送门(https://printjs.crabbly.com/)
printJS({ printable: 'print-area', type: 'html', style: '@page { size: A4; margin: 10mm; }', targetStyles: ['*']});
优点
- 浏览器兼容性优秀:Chrome、Firefox、Safari、Edge 均支持
- 开发与维护成本低,对现代前端技术栈(React / Vue)非常友好
- 页面即模板,业务变更成本低,适合供应链中高频调整的单据场景
缺点
- 样式控制高度依赖 CSS,对分页、跨页控制要求较高
2.基于可视化模板的前端打印方案(HiPrint)
以 HiPrint 为代表的方案,通常基于 jQuery,通过拖拽方式设计打印模板,再将业务数据动态渲染到模板中进行打印。HiPrint 官网地址:传送门(http://hiprint.io/)
配置示例效果如下图:

实现方式
$('#element').hiPrint({ designer: true, templates: [...] });
优点
缺点
- 技术栈依赖性强:强依赖 jQuery,与 React / Vue 整合成本高
3.基于本地打印控件的软件方案(LODOP)
LODOP 是一类 依赖本地打印控件或客户端程序 的打印方案,通过浏览器与本地程序通信完成打印。在早期政务、财务、制造业系统中使用较多。官网地址:传送门(https://www.lodop.net/)
实现方式
import { getLodop } from "@/plugins/LodopFuncs";
let LODOP = getLodop()
LODOP.PRINT_INIT("打印任务");LODOP.ADD_PRINT_TEXT(50, 100, 200, 30, "供应链单据");LODOP.PRINT();
优点
缺点
4.方案对比一览表
| 维度 | window.print | jQuery HiPrint | LODOP |
|---|
| 开发成本 | 极低 | 中等 | 高 |
| 打印精度 | 一般(受限于浏览器) | 良好 | 极高 |
| 跨浏览器 | 优秀 | 良好 | 极差 |
| 部署成本 | 零部署 | 低 | 高(需安装客户端) |
| 维护成本 | 低 | 中等 | 高 |
5.方案选型结论
结合当前业务特点:
最终选择了 基于 DOM 的浏览器打印方案,并以 window.print 作为核心实现方式。
为什么直接使用 PrintJs ?
可能会有人疑惑,既然是 DOM 打印,为什么不使用 print-js 进行封装?主要原因如下:
- print-js 需要能够直接获取到打印节点(dom 的id 或者原始的html)
- 需要单独引入工程化依赖包CSS 资源。如: Antd等
- 额外样式需要么通过字符串方式注入,要么将工程的less 转换成css 引入,不利于后续维护。
总的来说,print-js 在工程化配置项目依旧不是那么方便还用。
三、window.print 的能力边界
window.print() 本质上是触发浏览器打印对话框。当打印动作触发时,浏览器会将当前页面最终渲染的结果输出为打印内容,或者导出为 PDF 文件。
也正因为这一点,window.print 天生存在一个非常重要的限制:
它无法直接指定“只打印某一个 DOM 区域”。
浏览器并不知道你只想打印某一个 div,它只会按照当前页面的渲染结果,完整地执行一次打印流程。
这意味着,如果页面上存在导航栏、按钮、筛选条件等元素,它们同样会被一并打印出来。
四、指定区域打印的实现思路
既然 window.print 无法直接指定打印区域,那么解决问题的思路就非常清晰了:
在触发打印之前,主动控制页面最终的渲染结果,让真正参与渲染的内容只剩下需要打印的部分。
顺着这个思路,第一个最直观的方案自然就出现了。
1.innerHTML 直接替换页面内容
既然浏览器只会打印当前页面的渲染结果,那么我们是否可以在打印前,直接用需要打印的内容替换整个页面?
实现方式也很简单:
- 将
document.body.innerHTML 替换为打印区域内容
代码示例如下:
const getPreviewContainerHtml = (component) => { const div = document.createElement('div');
flushSync(() => { ReactDOM.render(component, div); })
return div.innerHtml}
const prinit = () => { const oldHtml = document.body.innerHtml; const printHtml = getPreviewContainerHtml(<PreviewContainer />); document.body.innerHtml = printHtml; window.print() document.body.innerHtml = oldHtml;}
这种方案在早期项目中非常常见。但随着实践的深入,很快就会发现一个问题:
直接替换 innerHTML 会彻底打破原有页面的渲染结构。
在 React / Vue 等现代前端框架中,这种方式会导致:
因此,这种方案虽然思路直接,但在复杂系统中并不安全。
2.新开标签页打印
既然直接替换当前页面会破坏原有渲染结构,那么很自然就会想到:
如果不动当前页面,而是新开一个标签页,专门用来承载打印内容,是否就可以解决这个问题?
新开标签页后,我们可以:
从技术角度看,这个方案确实可以稳定地打印出我们想要的内容。但在真实业务场景中,很快又会暴露出新的问题:
对于仓库、物流等高频打印场景来说,这种额外的交互成本是不可忽略的。
于是问题进一步演化为:
能否在不新开标签页、不破坏当前页面的前提下,在当前页签内完成指定内容的打印?
3.iframe 当前页签打印
顺着这个问题继续往下推,自然而然就会引出 iframe 打印方案。
其核心思路是:
- 调用 iframe 内部的
print() 方法完成打印
示例代码如下:
const print = () => { const iframe = document.createElement('iframe'); iframe.src = `预览页地址`; iframe.setAttribute( 'style', 'visibility: hidden; height: 0; width: 0; position: absolute; border: 0' );
window.addEventListener('onafterprint', () => { console.log('打印完成'); iframe.remove(); });
document.body.appendChild(iframe);
iframe.onload = () => { setTimeout(function () { iframe.contentWindow?.print(); }, 1000); };};
从浏览器的角度来看,iframe 本身就是一个完整的页面环境,因此可以独立完成打印流程,而不会影响主页面的渲染状态。
这一方案在实践中具备明显优势:
4.批量打印
到目前为止,我们已经可以稳定地完成 单个单据的打印。但在供应链场景中,一个非常常见的需求是:
一次性打印多张面单、送货单或运输单。
在浏览器环境下,这一需求首先会受到一个客观限制:
- 调用
window.print() 时,浏览器会弹出系统打印对话框 - 在用户完成打印设置并关闭对话框之前,页面线程会被阻塞
这意味着,通过循环多次调用 window.print() 来实现批量打印并不可行。
批量渲染内容 + CSS 分页
基于上述限制,一个更合理的思路是:在一次打印动作中,承载所有需要打印的单据内容。具体做法是:
示例代码如下:
printPageList.map(() => { return <PrintContent className="single-order-page" /> })
.single-order-page { page-break-before: always; page-break-inside: avoid;}
.single-order-page:first-child { page-break-before: avoid;}
通过这种方式,浏览器会将每个单据视为一页内容,顺序稳定、实现简单,非常适合版式统一的批量单据打印场景。
大批量场景下的性能考量
需要注意的是,当单据数量非常大(例如超过 50 张)时,一次性渲染所有打印内容,可能会带来内存占用和页面卡顿的问题。在这种情况下,可以考虑引入串行队列式的打印方案。
该方案的核心思路是:
但需要特别注意的是,浏览器无法准确感知打印完成的时机,因此该方案通常需要额外的控制手段,例如:
这种方式更适合作为大批量或特殊场景下的补充方案,而不是默认选择。
五、核心 CSS 打印配置:从“能打”到“好用”
在基于浏览器的打印方案中,CSS 决定了最终的输出效果。合理的打印样式配置,往往可以显著减少版式错乱、内容截断等问题,也是浏览器打印能否真正落地的重要前提。
下面结合实际项目,整理几类最常用、也最容易被忽视的打印样式配置。
1.使用 @media print 定义专用打印样式
浏览器提供了 @media print 媒体查询,用于在打印场景下覆盖页面的默认样式。
通过该方式,可以做到:
@media print { .no-print { display: none !important; }}
2.页面尺寸与边距控制
如果不对打印页面的尺寸和边距进行控制,常见问题包括:
通过 @page 可以明确指定打印纸张规格与边距:
@media print { @page { size: A4; margin: 10mm; }}
3.避免表格内容被强制拆页
表格是供应链单据中最常见的结构,同时也是打印问题的高发区域。
如果不加控制,浏览器可能会在任意位置拆分页,导致一行数据被拆成两页。
@media print { table { border-collapse: collapse; page-break-inside: avoid; }
tr { page-break-inside: avoid; }}
4.主动控制分页位置
在部分业务场景中,需要对分页位置进行明确控制,例如:
此时可以通过自定义分页样式来实现:
@media print { .page-break { page-break-before: always; }}
5.打印颜色与背景处理
浏览器在打印时,默认会忽略背景颜色与部分样式,这在以下场景中尤为明显:
@media print { body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }}
注:Window 电脑如果不设置 -webkit-print-color-adjust: exact 打印输出水印内容, 会出现空白现象。
六、总结
浏览器打印在供应链系统中看似只是一个基础能力,但在真实项目中,往往涉及方案选型、技术栈适配、用户体验以及长期维护成本等多方面的权衡。
本文结合具体的业务背景,对主流 Web 打印方案进行了梳理,并重点分析了基于 window.print 的实现思路及其工程边界。
可以看到,window.print 本身并不复杂,真正的挑战更多来自页面渲染控制、样式管理以及打印流程的工程化处理。这也使得浏览器打印更像是一项系统能力的组合,而非单一 API 的简单调用。
在当前阶段,window.print 配合 iframe 依然是 Web 打印方案中性价比最高、可控性最强的一种实现方式。随着业务复杂度的提升,打印方案仍有进一步演进与细化的空间。
阅读原文:原文链接
该文章在 2026/2/4 15:51:02 编辑过