ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaScript生成ZPL指令实现标签打印:从原理到实战全复盘

JavaScript生成ZPL指令实现标签打印:从原理到实战全复盘 先说说来龙去脉。做仓储管理系统的时候业务方提了个需求浏览器上点个按钮仓库的标签打印机就把商品信息打出来。听起来很简单但真做起来才发现普通前端打印方案在标签场景下根本不够用——位置偏差、切纸不准、浏览器兼容性问题一堆。折腾一圈之后我把方案定在了“JavaScript 生成 ZPL 指令通过网络发给打印机”这条路上。这篇就是把整个实现过程、踩过的坑、以及最终稳定的方案做个完整复盘。ZPL 是 Zebra 打印机默认支持的一套指令语言相当于给打印机写“打印脚本”。它不像驱动那样需要安装一堆东西直接把文本指令通过 TCP 端口发给打印机打印机就会按指令画出文字、条码、二维码最后从出纸口吐出一张完整标签。现在市面上大多数热敏标签打印机包括不少国产兼容机都支持 ZPL 子集所以这套方案不只局限于某一种设备普适性相当强。这篇内容适合谁看后端同学想给系统加标签打印功能、前端同学被浏览器打印标签折磨过、或者搞 IoT 设备对接但找不到合适打印方案的人都可以参考。下面从 ZPL 基础、JavaScript 拼指令、网络发送、实测排坑四个维度按我的真实经历展开说。1. ZPL指令和标签打印的基础认知1.1 ZPL到底是什么ZPL 全称是 Zebra Programming Language本质是一串纯文本指令。它不需要编译不需要驱动解释打印机收到后直接解析执行。一个 ZPL 指令块的结构是固定的^XA开头^XZ结尾中间是各种功能性指令。我第一次看到 ZPL 指令的时候觉得它有点像老式针式打印机的控制码但比那规整得多。每条指令以“^”开头后面跟两个大写字母有的还带参数。看个最简单的例子打印一行文字^XA ^FO50,50 ^A0N,32,32 ^FDHello, ZPL! ^FS ^XZ这段指令的意思是开始标签定义^XA把打印起点设在距离左上角 x50、y50 的位置^FO选择字体为内置字体 0、高 32 点、宽 32 点^A0N,32,32输入要打印的文字Hello, ZPL!^FD结束这个字段^FS最后整个标签结束^XZ。说白了^FO负责定位^FD负责内容^FS负责收尾。这种“定位 内容 结束”的写法贯穿所有 ZPL 指令理解这一条后面看任何指令都顺了。1.2 为什么选ZPL而不是其他方案决策之前我对比了几种常见方案这里把选型逻辑写清楚方案跨浏览器位置精度插件依赖维护成本ZPL 指令直发高高无低window.print CSS高低无中ActiveX / 浏览器插件低高有高第三方打印中间件中高视产品而定中图片打印Canvas 生成后打印高中无中先说大多数前端最先想到的 window.print。打印 A4 文档没问题但打标签不行——标签纸尺寸小还要精确定位。浏览器对 CSS 毫米单位的解析在不同系统上存在偏差按 40mm 设置的 div实际打出来可能就是 38mm 或 42mm。条码如果因此缩放失真扫码枪识别率会直线下降这在生产环境里是致命的。ActiveX 是老系统常用的方案但只能跑 IE 和 Windows现在项目都用 Chrome基本直接出局。ZPL 方案的核心优势在于标签上的每个元素文字、条码、二维码、线条都通过坐标精确定位单位是打印机物理分辨率下的点dot不存在浏览器像素换算问题而且它走网络协议不依赖操作系统打印驱动——只要打印机有网口管你是 Windows、Linux 还是 macOS都能通过 Socket 发指令打印。1.3 先搞懂ZPL核心指令和坐标换算常用指令我先列个表后面示例会反复用指令作用示例^XA/^XZ标签开始 / 结束^XA ... ^XZ^PW设置标签打印宽度点^PW480^LL设置标签打印长度点^LL320^FO设置字段起始坐标^FO40,40^FS结束当前字段^FS^FD输入字段内容^FD123456^FS^A0N,32,32设置字体和字号^A0N,32,32^BY设置条码模块参数^BY2,4^BCDraw Code 128 条码^BCN,80,Y,N,N^BQDraw QR 码^BQN,2,10^CI28声明后续内容为 UTF-8 编码^CI28有个基础概念必须提前说ZPL 的坐标单位是“点”不是毫米。点数和毫米的换算取决于打印机分辨率。大多数标签打印机是 203dpi每英寸 203 个点少数高精度机型是 300dpi。按 203dpi 算1 英寸 25.4mm 203 点所以1mm ≈ 8 点300dpi 下 1mm ≈ 11.8 点。坐标原点默认在标签左上角x 向右增大y 向下增大。^FO40,40表示从左上角往右 40 点、往下 40 点处开始放置内容。可能有人会问为什么不用毫米单位直接设置因为 ZPL 本来就是为工业打印机设计的点数才能对应到打印头的物理像素保证输出绝对精确。换算这一步虽然麻烦但恰恰是它能做到“所见即所得”的根本原因。2. JavaScript生成ZPL指令的完整实现2.1 先写一个基本标签模板需求背景给仓库商品打标签包含品名、SKU 条码、二维码标签纸尺寸 60mm 宽、40mm 高。打印机是 203dpi所以换算成点数宽 60 × 8 480 点高 40 × 8 320 点。用 JavaScript 函数生成 ZPLfunction buildProductLabel(product) { const labelWidthDots 60 * 8; // 60mm 480 dots const labelHeightDots 40 * 8; // 40mm 320 dots const zpl ^XA ^PW${labelWidthDots} ^LL${labelHeightDots} ^CI28 ^FO40,40^A0N,32,32^FD${product.name}^FS ^FO40,120^BY2,4^BCN,80,Y,N,N^FD${product.sku}^FS ^FO300,120^BQN,2,10^FDQA,${product.sku}^FS ^XZ ; return zpl; }每行指令的含义拆开解释^PW480告诉打印机标签纸宽 480 点。这个值必须跟实际标签纸宽度匹配否则打印内容会横向偏移或者跑到标签外面。^LL320标签高度 320 点。设置偏大打完整张标签后出纸会多走一段设置偏小内容会被截断。^CI28声明这段内容以 UTF-8 编码解析。这个指令对中文打印至关重要后面单独说。^FO40,40品名起点坐标 (40, 40)离左边和上边各约 5mm。^A0N,32,32A0N 是 Zebra 内置字体 0 的正常方向后面两个 32 分别表示字体高度、宽度点。^FO40,120^BY2,4^BCN,80,Y,N,N^FD${product.sku}^FSCode 128 条码。^BY2,4设置条码窄条宽度为 2 点、宽条与窄条比例为 4:1^BCN,80,Y,N,N中N 表示不打印可读字符行、80 为条码高度点、Y 表示在条码下方打印可读字符、后面两个 N 分别表示不打印起始/结束字符、不启用 UCC 校验。条码内容由^FD传入。^FO300,120^BQN,2,10^FDQA,${product.sku}^FSQR 码。^BQN的 N 表示普通模式2是纠错等级约 25% 纠错10是模块大小点。注意^FD后面要先写QA,再写内容QA是 QR 码的输入模式标识不能省。这一步做完我们已经能从一段“模板字符串”生成一个完整的标签指令。但这个实现还比较粗糙真正用起来有两个绕不开的问题中文编码、特殊字符转义。2.2 中文内容编码处理中文是 ZPL 打印里最容易踩的坑。最开始我把带中文的商品名直接拼进^FD里发过去打印出来全是乱码或空心方块。先后排查了文件编码、数据库编码、HTTP 请求编码最后定位到三个关键点第一模板文件本身必须是 UTF-8 编码。有时候开发环境在 Windows 上会把 JS 文件保存成 GBK代码里的中文字符串就变成了乱码。用 VSCode 的话可以在右下角确认编码统一切到 UTF-8。第二ZPL 指令块里必须声明^CI28表示“后续数据按 UTF-8 解析”。如果少了这条打印机会按默认编码解析中文字节流自然就乱码了。^CI28要放在^XA之后、所有包含中文的字段之前。第三打印机本身必须支持中文字库。Zebra 的高端机型如 ZT230、ZD421 等自带中文字库直接打没问题。但部分低成本机型或兼容打印机为了省成本没内置中文字库无论你编码多正确打印出来也是乱码。没有中文字库的打印机怎么办一种常用替代方案是把中文文本先在服务端渲染成图片再用 ZPL 的^GF指令图形字段把图片位图数据发给打印机。Node.js 可以用 sharp 或 canvas 把文字渲染成 PNG再用图像处理库转成单色位图最后封装成^GF指令。这个方案有额外的体积和性能开销文末踩坑部分我再讲怎么权衡。2.3 动态数据拼接时的转义和边界处理用模板字符串拼 ZPL 有个隐患如果业务数据里包含 ZPL 的特殊字符比如^和~打印结果会直接出错。^在 ZPL 里是“开始一条新指令”的标志。如果商品名里有个^打印机会把它后面的内容当成新的指令解析可能导致整个标签报废。~是配置类命令的前缀同样危险。这类字符虽然不常见但商品备注这类自由输入字段谁也不敢保证绝对没有。所以动态数据进^FD之前必须做清洗function sanitizeZplText(input ) { return String(input) .replace(/[\^~]/g, ) // 去掉指令标志符 .replace(/\s/g, ); // 压缩连续空白避免指令错乱 }另一个边界问题是内容超长。^FD后面如果内容过长超过标签宽度或指定字号能容纳的范围打印出来会溢出、重叠甚至自动换行导致布局错乱。处理方式是在后端截断字符串按字号估算最大字符数超出部分用省略号代替function fitText(text, maxWidthDots, fontSizeDots) { const maxChars Math.floor(maxWidthDots / fontSizeDots); if (text.length maxChars) return text; return text.slice(0, maxChars - 1) …; }这段逻辑的原理是估算一个全角中文字符在指定字号下大约占“字号点数”那么宽maxWidthDots / fontSizeDots就是能放下的最大字符数。严格的生产环境建议用 Canvas 测量实际渲染宽度但对标签这种内容通常比较格式化的场景估算方式已经够用。2.4 开发期必备工具Labelary 在线预览调 ZPL 指令如果全靠打印机实测效率太低。这里必须推荐一个工具Labelary Online Viewer直接搜 labelary zpl viewer 就能找到。它的功能是把 ZPL 指令粘贴到网页上即时渲染出标签效果图还支持导出 PNG、PDF。我在开发时的标准流程是改完模板函数先在 Node 里打印出生成的 ZPL 字符串复制到 Labelary 看一眼排版确认坐标和尺寸没问题再发到打印机实测。这样能把调试周期从“改一次打一次”压缩到十分钟内。Labelary 还能模拟不同打印机分辨率、不同标签尺寸下的显示效果。比如同一段 ZPL 在 203dpi 和 300dpi 下的视觉效果不一样因为同样的点数在不同分辨率下对应的物理尺寸不同。用这个工具预览时把分辨率选成实际打印机的值能避免很多“预览正常、打印偏移”的问题。3. 把ZPL指令送到打印机三种落地方式3.1 方式一Node.js通过TCP Socket直连网络打印机这是我最常用的方案也是 ZPL 网络打印的原生路径。支持网口的标签打印机通常默认监听 9100 端口这个端口没有复杂的协议只要把 ZPL 文本作为纯数据流发过去打印机就会按顺序执行。9100 端口在打印领域相当于 HTTP 的 80 端口约定俗成的存在。Node.js 的 net 模块可以直接创建 TCP 连接完整实现如下const net require(net); function sendZplToPrinter(zpl, host, port 9100) { return new Promise((resolve, reject) { const socket net.createConnection({ host, port }, () { socket.write(zpl, utf8, () { socket.end(); }); }); socket.setTimeout(5000, () { socket.destroy(); reject(new Error(连接打印机超时)); }); socket.on(error, (err) { socket.destroy(); reject(err); }); socket.on(close, () { resolve(true); }); }); } // 使用 const zpl buildProductLabel({ name: 无线鼠标, sku: WM-2024-001 }); sendZplToPrinter(zpl, 192.168.1.50) .then(() console.log(打印指令已发送)) .catch((err) console.error(打印失败, err.message));几个实现细节说明一下socket.write(zpl, utf8)这段必须显式指定 UTF-8 编码尤其是指令里包含中文的时候。默认编码可能不按 UTF-8 输出字节导致打印乱码。socket.end()表示数据发完关闭写端。ZPL 不像 HTTP 需要等待响应打印机收到^XZ就开始执行打印所以发完就可以关闭连接。连接成功回调里执行 write可以确保不会因为连接没建立就写数据而出错。5 秒超时是防止打印机休眠或 IP 变了导致进程挂起。这种方式的优点是极简、无额外依赖只要能在 Node 环境跑这段代码就能打印。部署上这段代码可以放在后端服务里也可以放在本地工具脚本里灵活度最高。3.2 方式二中间打印服务浏览器端调用浏览器里的 JavaScript 不能直接创建 TCP 连接这是浏览器安全模型决定的。所以纯前端的 Web 系统要打印标签必须绕一层。最优雅的绕法是把上面的 Socket 逻辑封装成一个 HTTP 接口浏览器只需提交 JSON 数据由后端转成 ZPL 再发给打印机。用 Express 实现一个简单接口const express require(express); const app express(); app.use(express.json()); app.post(/api/print, async (req, res) { const { printerIP, data } req.body; try { const zpl buildProductLabel(data); await sendZplToPrinter(zpl, printerIP); res.json({ success: true }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); app.listen(3000, () { console.log(打印服务已启动端口 3000); });前端调用就非常简单了const response await fetch(/api/print, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ printerIP: 192.168.1.50, data: { name: 无线鼠标, sku: WM-2024-001 } }) }); const result await response.json(); if (result.success) { alert(打印成功); }这个模式最贴合 Web 管理系统。业务页面不用管打印机在哪个网段、通讯协议是什么它只负责提交数据打印逻辑统一收敛到后端服务后续更换打印机品牌或者调整标签模板都不需要动前端代码。3.3 方式三本地小助手程序桥接USB打印机如果打印机没有网口只有 USB 口怎么办这时候 TCP 直连就走不通了但我仍然不想抛弃 ZPL 方案因为换驱动方案又是另一套维护成本。实际做法是在打印终端连接 USB 打印机的那台电脑上跑一个极简的本地 HTTP 服务监听127.0.0.1的某个端口。前端请求这个本地服务它再把 ZPL 文本通过操作系统的打印通道发送给 USB 打印机。Windows 下最简单的实现是这样的先用系统命令把打印机的输出重定向到一个共享名或设备名然后用文件复制的方式发送打印数据。Node.js 里可以通过 child_process 调用:const { exec } require(child_process); function printToUsb(zpl, printerName) { const tmpFile path.join(os.tmpdir(), print_label.zpl); fs.writeFileSync(tmpFile, zpl, utf8); // 使用 Windows copy 命令把 ZPL 文件发送到打印机 const cmd copy /b ${tmpFile} \\\\localhost\\${printerName}; exec(cmd, (err, stdout, stderr) { if (err) { console.error(打印失败, err); return; } console.log(打印指令已发送); }); }这其实是个“曲线救国”的桥接方案用系统内置的打印通道绕过了驱动匹配问题。ZPL 文本进去打印机原生解析不需要装品牌驱动。安全提醒这个本地服务务必绑定在127.0.0.1上不要暴露到局域网否则任何同网段的人都能向打印机发指令属于打印机的“远程投毒”攻击面。3.4 三种方式怎么选方案打印机要求服务部署位置适用场景局限TCP Socket 直连必须有网口后端服务或本地脚本局域网、后端触发打印浏览器不能直连HTTP 中间服务网口或 USB后端服务器Web 管理系统需要额外维护一个服务本地小助手桥接USB 即可每台业务电脑终端门店、USB 打印机每台电脑都要装一次我目前的项目把方案二作为主路线后端维护一套打印微服务前端调用。现场有几台老旧的 USB 打印机则在对应电脑上跑一个本地桥接脚本接到同一个打印服务上统一调度。两种方式互补不需要为老设备单开维护体系。4. 实测过程从调通第一张标签到稳定上线4.1 测试环境的准备纸上谈兵再多最后还是得拿真机打出来才算数。建议准备这些一台支持 ZPL 的标签打印机endor 或兼容。优先借/买带网口的型号没有的话 USB 型号也能用桥接方案跑通。一卷和业务实际一致的标签纸尺寸别搞混。电脑上安装 Node.js 环境能跑测试脚本。提前在浏览器打开 Labelary 网页。连接性测试有个小技巧把打印机网线插好之后先别急着写代码。用命令行工具 telnet 一下打印机的 9100 端口telnet 192.168.1.50 9100如果端口能通连接会成功窗口变黑或者提示转到 echo 模式。这时候直接输入^XA ^FO20,20 ^A0N,24,24 ^FDConnection OK ^FS ^XZ然后回车看打印机是否吐出一张写着 “Connection OK” 的标签。这一步能同时验证网络连通性、打印机型号兼容性和 ZPL 指令可用性比任何代码调试都直观。4.2 逐步验证标签效果拿到一台测试打印机后我的调试顺序是固定的每一步都只验证一个点第一步只打印最简单的文本。比如^FDHello目的是确认通信链路没问题。第二步打印条码。拿手机上的扫码 App 扫一下确认能识别同时观察条码清晰度、位置是否符合预期。第三步打印二维码和中文。这一步重点验证^CI28和中文字库。第四步把业务模板完整跑一遍核对标签上的每个字段是否正确、是否溢出。打印内容和预期不符是常态。比如我第一次在 60mm 宽的标签上打 QR 码发现总是比 Labelary 预览的偏右量了一下大概偏 2mm。排查下来是^PW设成了默认的 832 点这是 ZPL 的默认标签宽度对应 104mm 的标签实际标签才 480 点宽。打印机按 832 点理解标签宽度自然就把内容往中间挪了产生视觉偏移。改成^PW480之后立即恢复正常。还有一个典型问题标签高度设置偏差。表现为打印完内容后出纸总要多出一段空白或者切在内容中间。这也是^LL和实际标签高度不匹配造成的。出现这类问题第一反应不是调坐标而是检查^PW、^LL这两个基础参数。4.3 批量打印和并发处理的注意点批量打印是仓库系统的刚需——用户点一次按钮可能就要打 50 张标签。这里有个性能问题必须提前设计。最直观的做法是循环调用发送函数for (const item of items) { await sendZplToPrinter(buildProductLabel(item), printerIP); }实测下来这种写法在数量少时没问题但超过 20 张就会出现莫名其妙的丢单、错乱。原因是打印机在接收数据时有缓冲循环建立连接、断开连接的过程中上一次的数据可能还没被完全解析下一次连接又建立起来了两个任务在打印机端“打架”。正确做法是把多个标签拼接到同一个 ZPL 数据块里一次连接全部发出去。ZPL 允许在同一条数据流里包含多个^XA...^XZ块打印机会按顺序逐个出标签const batchZpl items .map((item) buildProductLabel(item)) .join(\n); await sendZplToPrinter(batchZpl, printerIP);如果业务量真的很大比如一单好几百张还需要在后端维护一个打印队列控制并发数。因为打印机的处理速度远低于网络传输速度十几张标签的数据瞬间就发完了但打印机可能要几十秒才能打完。队列的作用就是防止过多的任务一次性涌入导致打印机缓冲溢出。我目前的实现是给每台打印机加一个并发锁同一时间只允许一个打印任务运行后续任务排队等待。这个锁粒度按打印机 IP 维度实现避免不同打印机互相阻塞。5. 高频报错与排查技巧实录5.1 中文全部变成方块字或星号这个坑我踩得最深。症状英文、数字、条码一切正常但只要^FD里有中文打印出来就是一个个小方块或者直接变成星号。排查顺序确认模板文件本身是 UTF-8 编码。用 VSCode 打开看右下角编码格式GBK 就另存为 UTF-8。确认 ZPL 指令块里在^XA后加了^CI28。上一步没问题那基本可以确定是打印机没有内置中文字库。换一台支持中文的打印机测试或者改用位图方案就是把中文渲染成图片用^GF指令传输。位图方案的前期开发成本高一些性能也比纯文本差但在不支持中文字库的机器上它是唯一出路。开发量大概多半天左右性能上能接受单张标签增加几十毫秒传输时间。5.2 内容整体偏出标签纸现象内容打在正确的位置但整体往右或往下偏移甚至部分内容跑到标签外面。原因大多数是^PW、^LL与实际标签纸尺寸不匹配。打印机的坐标系是“基于标签纸的”你告诉它标签纸宽 480 点它就把 480 点当成一行宽度你告诉它 832 点它就按 832 点排版。两者不一致坐标就全错了。解决办法是拿直尺量标签纸的宽和高按分辨率换算成点数。203dpi 的换算公式是毫米数 × 8 点数。比如 60mm 宽 → 480 点40mm 高 → 320 点。如果打印机是 300dpi就把乘数换成 11.8。另一个容易忽略的是标签纸上的内容如果本身就有边距需求比如标签纸两头有不可打印区域需要把这个边距也折算进坐标通常预留 3mm 也就是 24 点起步。5.3 打印机连接超时或无响应现象代码报connect ETIMEDOUT或 Socket 超时。排查链路ping 打印机的 IP不通就检查网线、电源、路由器端口。ping 通了但 9100 端口连不上检查是不是有第三方软件占用端口或者打印机管理页面里关闭了网络打印功能。部分打印机默认禁用网络打印协议需要在面板或 Web 管理界面开启。打印机在长时间空闲后会自动休眠休眠状态下端口可能暂时不响应。唤醒后立即重试一般能成功。如果业务场景要求设备随时在线可以在打印机的电源设置里关闭休眠或缩短休眠时间。5.4 条码打印出来扫码枪识别不了现象条码打得出来但扫码枪经常扫不出或者偶尔扫错。原因集中在几个方面条码类型和内容的字符集不匹配。比如 SKU 里包含字母、数字、横线用 Code 128 没问题但如果你用 Code 39字符集限制会比较大。SKU 类数据建议优先 Code 128。条码高度不够。扫码枪的最佳扫描角度需要条码有足够的高度低于 60 点约 7.5mm会明显降低识别率。生产标签建议至少 80 点。条码窄条宽度太小。^BY2设置了窄条宽度为 2 点203dpi 下约 0.25mm已经接近物理极限过小易糊。最低建议用^BY2如果打印机打印头磨损严重升到^BY3更稳。打印头脏或标签纸质量差会导致条码线条发虚。用酒精棉签清理打印头换推荐的标签纸品牌。5.5 常见问题速查表现象可能原因解决办法中文乱码/方块编码非 UTF-8、缺^CI28、打印机无中文字库检查文件编码、加^CI28、换支持中文的打印机或用位图内容偏移/出界^PW/^LL与实际标签尺寸不符量标签尺寸按分辨率换算成点数打印机无响应网线、电源、休眠、端口占用ping telnet 排查唤醒打印机检查协议是否启用条码扫不出条码类型、高度、窄条宽度不合适换 Code 128调高^BC高度^BY不小于 2批量打印丢单多次连接打印机导致缓冲冲突拼接多标签为单个 ZPL 数据块一次发送内容被截断字段内容过长用fitText截断或调整字号/标签宽度同段 ZPL 在 Labelary 和打印机显示不同分辨率参数不一致把 Labelary 分辨率改成实际打印机的值5.6 一个容易忽视的细节多标签拼接的编码一致性批量打印拼接多个^XA...^XZ时中间用换行符连接。理论上没问题但实际使用中要注意不要在 ZPL 指令块中混入其他不可见字符比如 BOM。刚才提到 UTF-8 BOM 的问题这里再强调一次如果模板文件带 BOMBOM 会出现在拼接后数据流的开头部分打印机可能把 BOM 字节当作指令执行造成首个标签前多出一张空白或异常标签。检查方法很简单在 Node 里读取模板文件后打印一下第一个字符的 charCodeAt(0)如果是 652790xFEFF就是带了 BOM用编辑器去掉即可。这个坑藏得很深光看表面很难联想到是 BOM 在作怪但它影响最靠前的一个标签位会让业务方以为模板写错了。遇到“第一张标签有问题后面的正常”的怪现象先从 BOM 查起。结尾几点个人体会这套方案跑到现在已经一年多实际打印了十几万张标签整体稳定。我最深的体会是标签打印这类需求最忌讳的方案是“看起来能打出来就行”因为真正的坑全藏在批量、中文、尺寸这些边界情况里。一开始多花半天把 ZPL 基础、打印队列、编码细节整理清楚后面能省下好几天的现场调试时间。最后分享一个小技巧所有标签模板尽量统一由后端接口管理前端不要硬编码任何 ZPL 片段。业务方要调整模板时只需改后端配置前端和打印机都不动。我现在的实现是数据库里存 JSON 模板后端渲染成 ZPL前端只传业务数据。这样换标签纸尺寸、调排版开发人员全程不需要参与业务方自己在后台配置就完事了。这个改动带来的维护成本下降比很多人想象中要大得多。
返回列表