标签有四条输出路径,各自布局机制如下(已核对,非调研二手):
| 路径 | 位置 | 何时走 | 布局机制 | 条码 / 二维码 |
|---|---|---|---|---|
| 画布 | print_util_stub.dart:246 _renderLabelBitmap | 预览弹窗 + 热敏文字光栅(同一函数,drawQr/drawBarcode=false 供热敏) | 绝对坐标(逻辑 320×160,4× 超采样) | 条码 _drawBarcode 画黑条(:315);QR drawImageRect(:295) |
| 热敏 TSPL | 同文件 _printFlatLabelThermal:351 | 检测到 DL-888B 等热敏机(真实主力) | 文字=画布光栅 BITMAP(:394);绝对坐标 | 条码=原生 BARCODE(:451);QR=位图 BITMAP(:427) |
| PDF 回退 | 同文件 printProductLabelImpl:463 | 只在检测不到热敏机时(:491-499) | flexbox 流式(pw.Column/Row/Expanded),无绝对坐标 | 条码 pw.BarcodeWidget 矢量(:578);QR pw.Image(:593) |
| Web HTML | print_util_web.dart:21 | Web 构建(window.print()) | CSS flexbox 流式(:82-124),无绝对坐标 | 条码 toSvg 矢量(:64);QR base64 img(:143) |
BITMAP $qrX,$qrY,...(第 427 行)——位图,不是原生 QRCODE 指令。它「相对清晰」的原因是独立渲染:把 QR PNG 按目标尺寸 130×130 单独解码 + 1:1 逐像素 128 阈值二值化(:422-436),不经过文字那条 4×4 超采样降采样链路。只有条码是真·原生 TSPL BARCODE 指令(:451)。pw.Column/Row/Expanded,Web 用 CSS flex)。这意味着——如果编辑器让用户「把字段拖到某个 x/y」,这套坐标天然只对得上画布/热敏,对不上 PDF/Web 的 flex 流。要让 PDF/Web 认坐标,就得把它们的 flex 布局整个换掉。这正是下一节改方案的原因。关键细节(_renderLabelBitmap:319-340):品名字号 _fitFont 按宽度自适应 clamp(12,18);文字块(品名 + 两行副字段)在剩余竖直空间里自动均分留白居中;副字段 _labelSubFields:222 把「编号+系列」拼一行、「规格+日期」拼一行,空字段自动跳过、整行塌缩。也就是说当前版式不是一组固定像素位置,而是「几个区域 + 自动排版」。这点直接决定版式模型该怎么建(见 §3)。
参与渲染:shopName / name / code / series / spec / productionDate / qr / barcode。已在 LabelData 但从不渲染:batchNo / remark / shopAddress / shopPhone(label_data.dart:15-20) → 编辑器可直接把它们做成可选字段,数据层零改动。
device_management_screen.dart:991 → _snack('模板编辑即将上线');「预览」_previewLabelTemplate:1002 造示例 LabelData 弹 LabelPreviewDialog。打印模板卡仅桌面渲染 → 编辑器只做桌面。print_util.dart:15 printProductLabel(...) 和 :45 renderLabelPreview(label) 都不带模板概念 → 要加一个 template 参数贯穿下去(改动集中在 facade + 两个 Impl)。shop.go:68-79 读现有 custom_fields → 合并新键 → 保留旧键、店隔离,还有钉死契约的测试 shop_test.go:31。前端 _savePeripherals:1031 就是这么存外设的。→ 模板存 custom_fields 零后端改动、且不会冲掉外设等已有键。上一版我提「抽一层绘制指令 IR 给四路径各写 adapter」。读完代码后这是过度设计——因为 PDF/Web 是流式布局、且只是回退/次要面。更省的做法:
| 面 | 改法 | 条码/QR 清晰度 |
|---|---|---|
| 预览 | 画布 → PNG(现状即是) | 屏幕显示,无所谓 |
| 热敏(主力) | 文字=画布光栅(现状);条码=原生 BARCODE、QR=独立 BITMAP(都保留);唯一改动:它们的坐标从写死的 const 改成由模板算出 | 不降级:条码原生矢量、QR 独立 1:1 阈值,扫码可靠性照旧 |
| PDF 回退 | 整个 pw.* 组件树换成单张 pw.Image(画布位图) 满页铺 | 回退面向办公激打/喷墨(≥300dpi),画布 1280px≈812dpi 栅格化,条码可扫 |
| Web | HTML flex 换成 <img> 画布 PNG(40×20mm) | 同上,Web 打印走高 dpi,无虞 |
注:条码/QR 光栅化「扫不出」的风险只发生在热敏 203dpi——所以热敏那条坚决保留原生条码 + 独立 QR,不动。PDF/Web 是高 dpi 面,贴位图完全够用。
因为 §1.1 里现状是「区域 + 自动排版」而非死坐标,模型不宜做成纯自由画布(否则丢掉品名自适应、竖直自动居中这些现有优点,且短名不再放大、隐藏字段不再回填空间 → 反而变差)。建议区域(region) + 元素(element) 混合模型:
const(headerH=24 / qrSize=134 / barTop=114 …)原样编码;渲染器保留自动栈算法。用字体探针 / golden 位图对照守逐像素一致。模板存 shop.custom_fields.label_templates(数组)+ label_template_active(默认 id)。依据:
shop.go:68-79 对 custom_fields 做增量 merge 并有测试钉死 → 写 label_templates 不碰 peripherals 等旧键,店隔离。无需新表/迁移/接口。_savePeripherals:1031:读 shopInfoProvider → 改 customFields map → shopRepositoryProvider.updateInfo({...})。label_printer(label_preview_dialog.dart:11) 不动。价签卡「编辑」→ 大对话框。左预览右属性,改动 debounce ~150ms 调 renderLabelPreview(sample, template) 重渲染(预览路径现成)。
你要「可调位置」,但源码现状是自适应排版(品名按宽度放大、文字块自动竖直居中、空字段塌缩)。三档:
推荐 ②。若你更想要 ③ 的「每个字段都能拖」,我按纯坐标做,但要接受品名不再自动放大、隐藏字段不再自动回填空间。
_drawText:166 写死 fontFamily:'NotoSansSC',且热敏要求打包字体。支持「换字体」= 再打包字体(如宋体,每款中文约 4–8MB 增包体)+ 参数化 fontFamily。MVP 建议只做字号/粗体(零增包),多字体二期按需。| 期 | 内容 | 验收 |
|---|---|---|
| P0 建模 | 定义 LabelTemplate(区域+自动栈)+ 画布渲染器读模型;内置默认模板=现 const;facade/Impl 加 template 参数 | 字体探针 / golden:默认模板逐像素零回归 |
| P1 原型 | design/prototype/ 新增编辑器桌面原型 | 5180 serve URL 评审,过了再写代码 |
| P2 编辑器 | 编辑器 UI + custom_fields 存储 + 预览联动 + 多模板/重命名;热敏条码/QR 坐标改模板驱动 | 桌面建/存/切模板,预览实时刷新,热敏按模板出签 |
| P3 收口(可延后) | PDF/Web 改「贴画布位图」→ 自定义模板四面一致 | 同模板热敏/PDF/Web 观感一致 |
按铁律:P1 原型过审前不写实现代码;前端改动原型与代码同提交。本方案即 P1 前评审。
| # | 决策 | 推荐 |
|---|---|---|
| 1 | 模板存哪 | shop.custom_fields(源码证实零后端改动、店级同步) |
| 2 | 渲染统一 | 画布为唯一权威 + PDF/Web 贴位图;热敏保留原生条码/独立 QR,仅坐标受模板驱动 |
| 3 | 字段自由度 | ② 区域+自动栈(可调位置又不丢自适应) |
| 4 | 字体 | MVP 只字号/粗体,多字体二期(增包体) |
| 5 | 颜色 | 热敏无效 → 建议 MVP 不暴露颜色(或仅作预览观感),你定 |
给个方向(认同/改哪条),我就做 P0 建模并出 P1 编辑器原型。
print_util_stub.dart(:158-617) / print_util_web.dart / label_data.dart / print_util.dart / label_preview_dialog.dart / device_management_screen.dart(:930-1035) / 后端 shop.go(:51-79)+shop_test.go · 本方案未改任何代码。