标签打不出来、扫不出来、贴不上去,八成不是代码问题,是尺寸算错了。40×30 的价签和 100×180 的面单,算法其实是同一套,只是参数不同。
项目地址 :gitee.com/theGreatOld...
配图1:一张 40×30 标签的空间账 
先看四类标签的实测参数
| 类型 | 纸张 | 条码区 | 模块宽 | 二维码 | 字号 |
|---|---|---|---|---|---|
| 商品价签 | 40×30 | 36×10 | 0.22mm | 无 | 9pt |
| 生鲜称重 | 60×40 | 50×12 | 0.25mm | 12mm | 10pt |
| 服装吊牌 | 54×90 | 30×10 | 0.25mm | 无 | 10pt |
| 快递面单 | 100×180 | 84×20 | 0.42mm | 无 | 10pt |
这张表是几十次实测攒出来的,可以当起点,具体还要按打印机型号微调。
公式 1:可用区 = 纸张 − 死区
可用宽 = 纸宽 − 2×死区 (热敏机死区常见 1-2mm)
40mm 纸按 36mm 算,比按 40mm 算安全。
公式 2:Code128 模块数
模块数 = 11 × 字符数 + 35
12 位编码 → 167 个模块,36mm 宽下模块宽 0.215mm。
上面这套结构,vue-print-designer 里有一份现成实现,可以直接对照。
公式 3:模块宽度下限
模块宽度 ≥ 0.2mm(203dpi)
低于这个值,打印出来边缘粘连,扫码枪识别率断崖下跌。
公式 4:条码总宽校验
条码宽 = 模块数 × 模块宽
超过可用宽就缩短编码,别硬压字号------字号不影响条码。
公式 5:二维码最小边长
最小边长 = 模块数 × 0.3mm + 8mm(静区)
V6(41×41)算下来约 20mm,40mm 标签放 URL 二维码基本不可能。
公式 6:pt 与 mm 换算
ini
1pt = 0.3528mm 10pt ≈ 3.53mm
设计稿给 px 就按 1px = 0.75pt。
公式 7:每页行数
scss
每页行数 = floor((纸高 − 上下边距 − 表头 − 表尾) / 行高)
小票按 80mm 纸、行高 4mm 算,一屏约 18 行。
公式 8:打印偏移补偿
scss
实际坐标 = 模板坐标 + (dx, dy) // 按打印机型号存
同一份模板换台机器,偏移方向可能相反,所以补偿必须按型号。
公式怎么落到代码里
公式只是纸面计算,真正落地要写成一个"参数计算器",输入纸张和编码,输出所有尺寸:
javascript
function calcLabel({ paperW, paperH, deadZone = 2, code, modulesPerChar = 11 }) {
const usableW = paperW - deadZone * 2
const usableH = paperH - deadZone * 2
// Code128 模块数:11 * 长度 + 35(含起止符与校验位)
const modules = modulesPerChar * code.length + 35
const moduleW = usableW / modules
// 二维码:按内容长度估版本
const qrSize = code.length <= 20 ? 25 : code.length <= 50 ? 33 : 41
const qrEdge = qrSize * 0.3 + 8
return {
usableW, usableH, moduleW: +moduleW.toFixed(3),
qrEdge: +qrEdge.toFixed(1),
ok: moduleW >= 0.2 && qrEdge <= Math.min(usableW, usableH),
}
}
calcLabel({ paperW: 40, paperH: 30, code: '6901234567892' })
// { usableW: 36, usableH: 26, moduleW: 0.202, qrEdge: 15.5, ok: true }
这个计算器的价值有两个:
- 选型时先算:客户说"我要在 40×30 上放 13 位条码 + URL 二维码",先跑一遍就知道放不下,不用做完再返工;
- 改参数时实时校验 :改纸张宽度,
ok立刻变 false,提示"条码过密"。
我们把这段逻辑放进了模板编辑器的属性面板------拖拽调整纸张尺寸时,右侧会实时显示模块宽度和二维码边长,超阈值变红。
怎么用这 8 个公式
配图2:生鲜称重标签,公式 1/2/3 全用上了 
先算可用区,再定条码长度,最后反推字号。顺序反了会陷入"改字号 → 条码变长 → 放不下"的死循环。
配图3:模板市场里各类标签,本质是参数不同 
三个让公式失效的场景
场景一:打印机不是 203dpi。 300dpi 机器上 0.2mm 能吃下,150dpi 的老机器就糊了。公式里的下限是相对 203dpi 的,换机器要重算。
场景二:纸张有预印刷内容。 比如带底纹的价签纸,条码压上去对比度不够,实际可读宽度要打折 10%。
场景三:标签是曲面(瓶身)。 贴在圆柱面上的条码会被拉伸,模块宽度要按 1.2 倍预留,否则扫码枪对不准。
公式给的是起点,这三个场景要求在起点上再加余量。
从公式到模板:一份 40×30 价签的完整配置
json
{
"paper": { "width": 40, "height": 30, "unit": "mm", "margin": 0 },
"elements": [
{ "type": "text", "x": 2, "y": 1.5, "w": 36, "h": 7,
"props": { "fontSize": 9, "bold": true, "align": "left" },
"binding": "productName" },
{ "type": "barcode", "x": 2, "y": 9, "w": 36, "h": 10,
"props": { "format": "CODE128", "displayValue": false },
"binding": "sku" },
{ "type": "text", "x": 2, "y": 20, "w": 20, "h": 6,
"props": { "fontSize": 8, "align": "left" }, "binding": "spec" },
{ "type": "text", "x": 24, "y": 20, "w": 14, "h": 6,
"props": { "fontSize": 11, "bold": true, "align": "right" }, "binding": "price" }
]
}
把这份配置套进渲染器,输出就是一版可以直接打印的价签。改价签只需要改 props.fontSize 或纸张宽度,公式会自动重算条码区。
参数校验放在哪一层
三个位置都能校验,建议放在保存时:
javascript
function validateTemplate(tpl) {
const { width, height } = tpl.paper
const usable = width - 4 // 假设左右各 2mm 死区
for (const el of tpl.elements) {
if (el.type !== 'barcode') continue
const modules = 11 * (el.binding.length || 12) + 35
const moduleW = usable / modules
if (moduleW < 0.2) {
return { ok: false, msg: `条码过密:模块宽 ${moduleW.toFixed(3)}mm,建议缩短编码或加宽标签` }
}
}
return { ok: true }
}
保存时拦截,比打印出来才发现扫不出要省事得多。
一次真实的返工
生鲜区换价签,13 位商品码,40×30 纸。第一批打出来看着正常,第二天店长说扫不出来。
按公式 2、3 一算:13 位 → 178 个模块 → 36mm 下 0.202mm,正好卡在临界值。把条码区从 36mm 放宽到 40mm(去掉左右各 2mm 的留白),模块宽回到 0.225mm,问题消失。
纸没换、代码只改了一个参数,但如果没有公式,只能靠反复试。
这套参数在 vue-print-designer 的标签模板里都是变量:改纸宽,条码区和字号会自动重算,不用手工验算。
一个经验阈值:模块宽只要低于 0.22mm,就先按"可能扫不出"处理,别等门店反馈再返工。
这条阈值不是拍脑袋定的:203dpi 下 0.22mm 约等于 1.8 个点,低于它就容不下墨点扩散带来的边缘模糊,扫码枪的识别率会从 99% 掉到七八成。