
简介这是一套专为Vue开发者打造的高性能打印解决方案面向Web应用开发中需实现定制化报表、票据、证书等复杂打印场景的中高级前端工程师。资源提供hiprint在Vue2与Vue3双版本下的完整封装涵盖可视化设计器、拖拽式元素编辑、多数据源报表设计、所见即所得打印配置等核心能力显著降低打印功能开发门槛。压缩包共77个文件含12个Vue组件文件支撑设计器与预览界面、26个JS逻辑模块含核心打印引擎与数据绑定、15张PNG/SVG图标资源用于工具栏与模板展示、4个CSS样式文件及字体/图标等配套资产整体体积仅3.8MB轻量易集成。已有5137人学习下载资源结构清晰包含可直接运行的demo示例、多套预置打印模板template1–3.png、微信/支付宝等常用支付凭证样例及完整LICENSE与CHANGELOG开箱即用助力快速落地企业级打印需求。 我最近接了一个比较典型的订单打印需求电商后台需要把订单明细、物流面单、销售报表都纳入一套可自定义的打印方案里。最早我直接手写HTML模板配合window.print()硬上结果需求方隔三差五提一个这里字体调大一点”“那个字段加粗一下的需求每次都要改代码重新发版。后来我换成了hiprint配合Vue2/Vue3把这套打印、打印设计、可视化设计器、报表设计、元素编辑、可视化打印编辑全部串起来才算是把这个长期需求彻底做透了。这篇就分享一下我的接入思路、实操过程、踩坑记录以及如何用hiprint合并某一列所有单元格这类明细表格需求给正在做前端打印方案的朋友一个完整参考。1. 接到打印需求后我为什么放弃手写HTML模板1.1 传统浏览器打印方案的痛点很多前端做打印最开始的方案基本都是window.print()加一个隐藏DOM把要打印的内容塞进去然后调浏览器打印。这个方案在小需求下是能跑但一旦进入真实业务场景问题会非常明显。第一个痛点是分页控制。订单明细一多表格行数不定CSS分页符在大多数浏览器里表现很不稳定经常出现内容被截断、表头被挤到上一页中间、最后一行凭空消失的情况。每次遇到这种问题排查成本都特别高因为你根本没办法准确预测浏览器会把内容切在哪个位置。第二个痛点是样式隔离。window.print()会把整个页面的样式一起考虑进去虽然可以用media print做一些隐藏但页面上只要有一个组件的CSS写得不够干净打印出来的内容就会多出莫名其妙的边距、背景色或者浮动失效。而且不同浏览器的打印样式表现不一致前端要反复适配。第三个痛点是需求响应慢。一旦运营或者财务提出来“我要自己调整列宽”“我想加一个自定义字段”你根本没有办法把这种能力交给他们只能自己改代码、提测、发布。在一套成熟的运营后台里这种高频、低成本的小改动需求靠开发手动迭代是撑不住的。我早期的项目里就是被这三个痛点反复折磨才下决心换一套更完整的方案。1.2 hiprint的核心能力拆解hiprint能切入这个场景核心在于它把“打印”这件事拆成了几个可以独立使用又彼此串联的模块可视化设计器PrintDesigner在页面上拖拽字段、排版、设置样式所见即所得地设计打印模板。预览与打印PrintPreview将设计好的JSON模板渲染成可打印的页面填充数据支持打印、导出PDF、直接弹窗预览。报表设计能力内置文本、表格、图片、条形码、二维码等元素尤其适合订单明细和报表类的多行数据展示。元素编辑能力设计器里的每个元素都可以配置数据源字段、字体、对齐方式、边框、是否换行等属性灵活性非常高。它的核心思路是“模板与数据分离”开发者在设计器里拖出一个模板得到一个JSON对象运行时把JSON交给打印引擎引擎负责渲染布局和数据。因为模板是JSON所以可以存在后端数据库里甚至可以做到不同用户、不同门店、不同业务线使用完全不同的打印样式前端只需要负责加载和渲染。从我排查过的实际项目来看hiprint在技术实现上还有一个很关键的优势它把打印区域放到一个独立的iframe中渲染。这听起来很简单但实际解决了浏览器打印时页面样式互相干扰的难题。iframe隔离之后页面上的UI样式不会渗透到打印文档里打印文档的CSS也不会污染主页面这也是它打印效果相对稳定的原因。1.3 市面上其他打印方案的对比当时我也考虑过其他方案简单对比一下方便你判断自己是不是真的需要hiprint方案核心特点适合场景局限性window.print 手写DOM实现简单零依赖极简的单页打印分页不可控、样式易乱、不可视化设计vue-print-nb等指令插件封装了打印动作使用简单简单单据打印没有设计器模板改动仍需前端pdfmake / jsPDF以代码方式生成PDF服务端/纯客户端生成PDF上手成本高不支持可视化拖拽hiprint可视化设计器 JSON模板 iframe打印需要频繁调整样式的业务打印依赖jQuery需要花时间消化如果你是硬编码模板且业务上几乎不需要调整样式手写DOM确实够了。但只要“样式调整”这个动作会反复发生那可视化设计器带来的长期收益会远超学习成本。hiprint适合的不是一次性打印而是需要长期维护、样式会持续变化的打印需求。2. 环境准备Vue2/Vue3下hiprint的正确接入姿势2.1 版本选择与依赖关系先说一个最容易踩的坑hiprint是依赖jQuery的因为它的底层拖拽和DOM操作大量使用了jQuery。无论你用Vue2还是Vue3这一步都绕不开。很多人在Vue里装完hiprint直接报$ is not defined基本都是因为没有把jQuery挂到全局。另外hiprint的npm包版本和源码仓库版本在API上有一些差异我建议你先确认自己要用的版本。如果你从npm装hiprint包可能拿到的是打包后的dist版本默认会暴露window.hiprint如果你从GitHub下载源码自行构建可能会是模块化源码。两种方式我都试过结论是不要纠结用哪个版本先统一为打包后的dist引入方式这样问题最少。2.2 安装与初始化实操我实际项目中的做法是在index.html中直接通过script标签引入jQuery和hiprint的静态文件而不是在Vue组件里import。原因有两点第一jQuery作为全局变量挂到window上最省事第二hiprint本身会在全局注册hiprint对象Vue组件里只需要使用window.hiprint即可避免webpack打包时对这个非模块化库做各种兼容处理。示例代码如下!-- index.html -- script src/lib/jquery.min.js/script script src/lib/hiprint.bundle.js/script link relstylesheet href/lib/hiprint.css如果你非要走npm安装也可以但要注意顺序// main.js import $ from jquery window.jQuery $ window.$ $ import hiprint import hiprint/dist/hiprint.css这种写法的风险在于某些版本的hiprint对window.jQuery和window.$的引用方式不同如果你在import hiprint之前没有设置好全局jQuery它内部初始化的时候就会找不到jQuery。所以我更推荐直接静态引入简单粗暴而且不用考虑构建工具的变量注入问题。2.3 Vue2和Vue3的差异处理Vue2项目中一般直接在mounted生命周期里调用window.hiprint.init()在beforeDestroy时销毁对应实例。Vue3的写法基本一致只是生命周期换成了onMounted和onBeforeUnmount。有一个Vue3特有的坑需要注意如果你使用Vite构建Vite对全局脚本的引入顺序非常敏感。index.html里的script src会按顺序执行但如果你把这些脚本放在script typemodule之后模块脚本是异步执行的可能导致hiprint初始化时jQuery还没准备好。我踩过一次之后就把jQuery和hiprint的脚本放到了head里并加defer同时在Vue的onMounted里做二次确认如果window.hiprint还不存在就提示加载异常。初始化代码// Vue2 / Vue3通用思路 if (window.hiprint) { window.hiprint.init({ host: /lib // 这里可以指定资源目录一般不需要 }) console.log(hiprint init success) } else { console.error(hiprint not loaded) }其实hiprint.init主要用于从远程加载资源如果静态文件已经本地引入不调用也没问题。但养成调用的习惯是好的因为在某些版本里初始化阶段会注册样式、字体等资源。3. 核心API串联从模板设计到打印输出的完整链路3.1 设计器与模板JSON的生成先看可视化设计器怎么接入。我们的业务是让运营在后台自己设计打印模板所以前端需要一个区域用来承载设计器另外再放一个按钮用来导出JSON模板。设计器初始化的核心逻辑// 创建设计器实例 const designer new window.hiprint.PrintDesigner({ paperType: A4, // 也可以指定 paperWidth/paperHeight paperWidth: 210, paperHeight: 297, // 设计器是否允许拖拽、编辑 editable: true }) // 将设计器构建到某个DOM容器中 designer.build($(#hiprint-designer-container), {}) // 获取当前设计器的JSON模板 const templateJson designer.getJson()这里有几个关键点。paperType支持A4、A5、连续纸等预设也可以不填然后在paperWidth和paperHeight里自定义。需要注意的是打开设计器后它会创建一个iframe并往里注入设计画布这个iframe的宽高会直接影响设计器的显示效果。建议外层容器的高度至少不低于600px否则设计器里会出现滚动条拖拽体验会比较差。designer.build的第二个参数是初始化模板对象。你可以传入一个已存在的模板JSON这样就能在既有模板上继续编辑。这个设计很实用运营改模板时前端先向后端请求已保存的JSON再把它塞进设计器运营改完后再导出新的JSON整个闭环就形成了。模板JSON的基本结构大致是这样的{ panels: [ { paperType: A4, paperWidth: 210, paperHeight: 297, elements: [ { type: text, left: 10, top: 10, width: 100, height: 20, options: { textAlign: center, fontSize: 14pt, fontWeight: bold, text: 订单编号: ${orderNo} } } ] } ] }panels表示一页纸的集合如果你需要多页模板可以定义多个panelelements就是这一页上的所有元素每个元素的type可以是text、table、image、barcode、qrcode、custom等left/top/width/height是元素在设计画布上的位置options是元素的具体样式和数据绑定配置。3.2 模板渲染与数据填充设计器生成JSON后运行时要将这些JSON渲染成可打印的文档这个过程由PrintTemplate和PrintPreview来承接。不同版本的API名字略有差异但思路一致// 方式一使用 PrintTemplate const printTemplate new window.hiprint.PrintTemplate({ template: templateJson }) const preview printTemplate.getPreview() preview.setData({ orderNo: SO20240315001, customerName: 张三, productList: [ { name: 商品A, qty: 2, price: 19.9 }, { name: 商品B, qty: 1, price: 39.9 } ] }) preview.print()另一种是直接使用PrintPreview来构建预览区域const preview new window.hiprint.PrintPreview({ paperType: A4 }) preview.build($(#hiprint-preview-container), templateJson) preview.setData(formData) // 预览效果渲染后用户点击页面上的打印按钮setData是数据填充的关键。模板里的${orderNo}这类占位符会自动替换成data对象中对应的属性值。而表格类型元素type: table则更特殊它不会用${}占位符而是通过列字段配置来绑定数据集。如果你要打印的是表格数据结构一般是这样的{ type: table, left: 10, top: 40, width: 190, height: 100, options: { dataSource: productList, columns: [ { title: 商品名称, field: name, width: 80 }, { title: 数量, field: qty, width: 40 }, { title: 单价, field: price, width: 50 } ] } }dataSource指明数据源是formData.productList这个数组columns定义每一列的标题和字段映射。渲染时hiprint会遍历数组自动生成表格行。3.3 直接打印与导出PDF打印最常见的三种出口弹浏览器打印对话框、静默打印、导出PDF。弹浏览器打印对话框是最常用的preview.print()这会在iframe中生成打印内容然后调用iframe内部的window.print()。用户可以在系统打印对话框里选择打印机、纸张方向、缩放比例等。如果你的业务允许用户在系统弹窗里做最后的调整这种方式最简单可靠。导出PDF不需要额外引入组件直接依赖浏览器的“另存为PDF”能力。用户点击打印后在系统打印对话框中选择“Microsoft Print to PDF”或其他虚拟打印机即可。如果你想在代码里一键导出PDF不经过系统弹窗那需要额外做不少工作通常还要配合服务端生成这就不在hiprint的职责范围内了。静默打印是业务里比较常见的需求——不弹任何对话框直接把任务发给打印机。浏览器层面其实做不了真正的静默打印除非你用浏览器插件、端口监听或USB驱动方案。hiprint在web端能做的是“打开预览页面后自动调用打印接口”用户还是会看到系统打印弹窗但至少操作路径短了preview.print({ // 可以传参数但依然逃不开浏览器安全机制 })如果确实需要全自动静默打印通常要配合原生客户端或专用打印控件这属于打印机硬件层面的方案不在前端框架能控制的范围内。4. 跑通Demo之后必踩的坑样式丢失、iframe冲突、拖拽失效4.1 打印内容样式丢失的问题排查很多人在Vue里接入hiprint后发现打印出来的内容完全没有样式文本挤在一起表格没有边框跟设计器里的效果完全是两个东西。这个问题的根源在iframe和浏览器打印机制上。前面说过hiprint会创建一个隐藏的iframe把打印内容放到里面。这个iframe默认不加载主页面的样式它自己有一套基础样式。如果你在模板里没有显式配置字体大小、边框、间距打印出来的内容就会显得很“素”。解决办法是在设计器里给元素加样式或者直接在模板JSON的options里写明{ type: text, options: { text: 测试文本, fontSize: 14pt, fontFamily: Microsoft YaHei, borderWidth: 1pt, borderColor: #000000 } }另外还有一个更隐蔽的坑如果你在主页面里定义了全局*选择器比如* { box-sizing: border-box; }这种样式不会自动带到iframe里但如果你在开发时引入了normalize.css而且它的作用范围穿透了iframe那打印样式也可能受影响。最简单有效的排查方式是打开控制台选中打印iframe的document元素查看它实际加载的样式表。如果发现样式隔离不彻底可以在hiprint.init时指定独立的CSS资源或者手动往iframe里注入样式。4.2 jQuery与Vue的共存与冲突处理我在一个老项目里遇到过这样的问题页面本身用了Element UI和Vue没有其他依赖jQuery的库但全局引入了jQuery之后Element UI的某些弹窗组件出现了样式错乱。排查后发现问题出在jQuery对全局$的占用和Element UI内部的事件委托方式产生了冲突。最稳妥的规避方式有两种。第一种是引入jQuery后立刻执行jQuery.noConflict()释放$的占用权script src/lib/jquery.min.js/script script window.jQuery jQuery.noConflict() /script这样Vue和Element UI内部如果用了$不会误用到jQuery。hiprint内部使用jQuery时它一般会引用window.jQuery或自己内部保存的jQuery引用noConflict()不会影响它。第二种方式是彻底避免全局$在Vue组件里只通过window.jQuery去调用hiprint相关方法不直接操作带$的选择器。另外还要注意不要在Vue的响应式数据里直接保存hiprint的设计器实例或预览实例。这些实例内部包含大量的DOM引用和事件绑定如果放进Vue的data中会被Vue做响应式代理性能会明显下降甚至导致事件绑定异常。正确做法是挂在普通对象上或者直接存到this上export default { data() { return { templateJson: null } }, created() { // 不放入data避免被Vue响应式劫持 this.designer null this.preview null } }4.3 设计器内拖拽失效或定位偏移在设计器里拖拽元素出现定位偏移或者拖不动这个问题通常不是hiprint本身的bug而是它所在的父容器有transform属性。当一个区域设置了transform: scale()或transform: translate()后内部iframe的鼠标坐标会被浏览器做了坐标变换hiprint在计算拖拽位移时拿到的是变换后的坐标所以元素会出现偏移或拖不动。我的解决方式是把设计器的容器放在一个独立的、没有transform的DOM节点下面。如果你是在弹窗或抽屉里打开设计器尤其要注意弹窗组件经常会使用transform来做居中动画比如Element UI的el-dialog。这种场景下建议不要在弹窗内直接初始化设计器而是开一个全屏遮罩层或者单独的路由页面。另外如果页面上同时使用vuedraggable或者SortableJS这类拖拽库它们默认会在全局监听mousedown/mousemove事件。这也会干扰hiprint的拖拽逻辑导致元素被选中后无法响应。解决办法是在拖拽开始前暂停其他拖拽库的事件监听或者在打开设计器时延迟初始化其他拖拽能力。4.4 合并某一列所有单元格的实现思路表格中合并单元格是订单打印、报表打印里的高频需求。比如订单明细里的“仓库名称”列同一个仓库会有多行商品你希望只在该仓库的第一行显示仓库名其余行合并成一个单元格。hiprint官方文档里对这块说得比较含蓄但它确实支持通过表格的merge配置来合并相同值。具体做法是在表格列的配置里设置合并规则{ type: table, options: { dataSource: productList, columns: [ { title: 仓库, field: warehouse, width: 50, merge: true }, { title: 商品名称, field: name, width: 80 } ] } }merge: true会让hiprint在渲染表格时自动将同一列中连续出现的相同值合并为一个大单元格。注意“连续出现”这四个字因为底层逻辑是基于值变化判断是否合并的。如果数据源里同一个仓库的值不是连续的比如订单A是上海仓、订单B是北京仓、订单C又是上海仓那上海仓即使出现在两处也不会自动合并成一个整体单元格。所以在填充数据前你需要先对数据源进行排序把相同字段值的记录排在一起再渲染。示例formData.productList.sort((a, b) { return a.warehouse.localeCompare(b.warehouse) }) preview.setData(formData)排序完成后再调用setData才能达到“合并某一列所有单元格”的效果。如果你的业务要求保留原始顺序又希望自动合并那只能在后端输出数据时就保证相同值连续或者在前端额外做一个行聚合逻辑把跨行显示的数据预处理。我这里补充一个亲测有效的小技巧如果合并后的单元格高度自适应不正确通常是因为行高的默认设置不够。你可以在表格的options里加一个rowHeight字段或者给列设置height属性这样合并后的单元格显示会更饱满不会出现文字挤压换行的情况。5. 进阶配置多联打印、自定义纸张、批量打印与性能优化5.1 多联打印的无缝实现业务里经常有多联单的需求比如“客户联”“留存联”“财务联”三联之间只需要部分信息不同其他内容一致。实现多联打印有两种思路。第一种是模板层面做多panel。一个模板JSON里存在多个panel每个panel对应一联。打印时page1显示客户联page2显示留存联page3显示财务联。这种方式适合各联之间的差异比较大的情况比如联A需要显示价格联B需要隐藏价格。{ panels: [ { paperType: A4, elements: [ { type: text, options: { text: 客户联, fontSize: 18pt } }, { type: text, options: { text: 订单编号: ${orderNo} } } ] }, { paperType: A4, elements: [ { type: text, options: { text: 留存联 } } ] } ] }第二种是代码层面控制。在打印方法里根据条件选择不同的模板JSON比如从后端拉取两个模板分别生成预览后依次打印。这种方式更灵活但需要多写一部分模板管理逻辑。实际项目中我更倾向于第一种因为模板JSON可以由运营在设计器里维护前端不需要改代码。如果各联之间的纸张大小不一样比如客户联用A4纸留存联用80mm热敏纸那在panels里分别指定paperType或paperWidth/paperHeight就行。5.2 自定义纸张与连续纸设置自定义纸张是打印机方案里绕不开的。热敏纸标签、收银小票、物流面单它们的尺寸五花八门。hiprint里自定义纸张的方式很直接const preview new window.hiprint.PrintPreview({ paperType: custom, paperWidth: 100, // 单位mm paperHeight: 150 })但这里有一个很重要的细节浏览器的打印预览虽然会按照你设置的纸张尺寸生成内容但最终打印时打印机驱动里的纸张设置必须和模板里的尺寸一致否则浏览器会自动把内容缩放或者出现横向截断。你在代码里设置100mm宽如果打印机默认纸张是A4那打印出来的宽度比例就是不对的。实际对接热敏打印机时我通常是先指导用户在打印机驱动里添加一个自定义纸张规格比如100x150mm然后浏览器打印对话框里选择这个纸张再配合hiprint的paperType: custom和paperWidth/paperHeight这样才能真正达到标签不偏移、不错位的效果。连续纸例如收银小票纸也有一个小坑浏览器打印对话框会按页面高度来分页如果内容没有充满整个页面可能会自动补一页空白。要避免这个问题可以在模板设计时把高度设置得和实际纸张高度一致同时在preview.setSetting里关闭页边距preview.setSetting({ margin: 0 })5.3 批量打印的任务队列处理批量打印大量单据时如果直接写一个循环挨个调用preview.print()很容易出现浏览器连续弹窗被拦截或者打印任务丢单。我处理这个问题的思路是串行打印队列。async function batchPrint(templates) { for (const item of templates) { const printTemplate new window.hiprint.PrintTemplate({ template: item.templateJson }) const preview printTemplate.getPreview() preview.setData(item.data) await new Promise((resolve) { preview.print(resolve) // 打印回调完成后resolve }) } }理论上preview.print()可以传一个回调函数在打印流程结束后触发。利用这个回调配合async/await就可以保证一个单据打印完成后再打印下一个避免弹窗拦截。不过要注意浏览器对不同打印任务的响应速度不同如果数据量特别大建议还是分成小批次一次打印5~10个中间加一点延时体验更稳。5.4 性能优化与内存释放hiprint实例在创建时会生成iframe和DOM节点如果不做回收页面打开多了会越来越卡。我一般在不需要设计器或预览时调用destroy方法this.designer.destroy() this.preview.destroy()如果你在同一个页面上要反复打开设计器不要每次都重新new一个实例而是初始化一次后续只调用build来更新模板。这样能显著减少频繁创建iframe带来的卡顿和闪烁。大数据量表格渲染也是性能瓶颈之一。比如一次性要打印300行订单明细hiprint在渲染时会把所有行都生成到DOM里打印速度会变慢。实测下来超过200行的表格浏览器在打印前渲染时会有明显延迟。这种情况通常的做法是分页处理或者和后端约定好接口分页每次只取一页数据。但打印场景往往要求所有数据一次性出齐所以更实际的优化是给表格设置列宽时不要太宽减少内容换行字体不要用太大的字号避免在表格里嵌套过多的图片或样式。还有一个容易被忽略的点打印内容里的图片不要用base64内联尤其是多张图片时base64会让模板JSON体积暴增渲染速度明显下降。建议图片字段存URL打印时由浏览器加载如果是必须的固定图片比如Logo可以放到应用服务器的静态资源目录用URL引用。6. 从实际项目中总结的封装思路到这里核心功能基本都跑通了。最后聊一下我在Vue项目中是怎么封装hiprint的这个封装结构现在已经在多个项目里复用过。我会写一个PrintService类内部封装初始化、模板管理、打印调用class PrintService { constructor() { this.designer null this.preview null } init() { if (window.hiprint) { window.hiprint.init() } } createDesigner(container) { if (!this.designer) { this.designer new window.hiprint.PrintDesigner({ paperType: A4 }) } this.designer.build(container, {}) return this.designer } loadTemplate(container, templateJson) { if (this.preview) { this.preview.destroy() } this.preview new window.hiprint.PrintPreview({ paperType: A4 }) this.preview.build(container, templateJson) return this.preview } print(templateJson, data) { const printTemplate new window.hiprint.PrintTemplate({ template: templateJson }) const preview printTemplate.getPreview() preview.setData(data) preview.print() } destroy() { if (this.designer) { this.designer.destroy() this.designer null } if (this.preview) { this.preview.destroy() this.preview null } } } export default new PrintService()在Vue组件里使用时只需要关注业务逻辑比如从后端获取模板、把数据传给打印服务。这种封装能让业务代码整洁很多也让其他同事接入时不需要深入了解hiprint的内部API。有一点需要特别提醒模板JSON的版本兼容性。如果你在开发环境用了最新版hiprint生成了模板到了生产环境却发现打印效果不对很可能是两个环境的hiprint版本不一致。模板JSON里的某些属性字段在不同版本之间可能存在细微差异。建议固定版本号不要随意升级。最后说一个我个人的经验不要试图把hiprint变成一个“全自动无人工干预”的打印引擎。它在可视化设计和通用打印上确实很强但到了复杂打印机驱动、标签校准、静默打印这些边界需求依然需要外部设备的配合。把HIPRINT定位成“前端可视化打印的中间层”把设备和驱动的脏活留给专业方案你会省心很多。本文还有配套的精品资源点击获取