fix(client): 删除价签底部米色页脚,二维码/条码放大填满纸面

原型与热敏/PDF/Web 三路渲染同步去掉标签底部那条印着「40×20mm」的
米色横条(打印时深色字会印到纸上,属冗余),主体直抵纸底:

- 原型 devices.html:删 .lbl-ftr CSS+DOM,.lbl-mid flex:1 自动填充
- Canvas 预览:删米色页脚绘制块+cFoot 常量,bodyBot 145→160
- 热敏 TSPL 原生:qrW/qrH 104→118、qrX 208→194、qrY 28→33,
  条码 kBarH 28→34、kBarY 102→118
- PDF:删 footerH 常量+页脚 Container,Expanded 主体撑满
- Web:删 .ftr CSS+div,.middle flex:1 填充

门禁:analyze 0 error / test 356 通过 / DS 原型 12 道 / L1 同源 / 颜色单源 全绿

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bupi8Kdqkfx2N5acFsHTx5
This commit is contained in:
wangjia
2026-08-28 00:15:21 +08:00
parent 848a9ee2c6
commit d299d4b0b0
3 changed files with 17 additions and 57 deletions
+15 -43
View File
@@ -47,8 +47,8 @@ String _fileTs() {
// ── 设计色彩 token ──────────────────────────────────────────────────────────
const _navy = PdfColor(0.122, 0.165, 0.227); // #1F2A3A header/chip
const _cream = PdfColor(0.957, 0.925, 0.847); // #F4ECD8 footer/reversed text
const _footnote = PdfColor(0.353, 0.306, 0.208); // #5A4E35 footer text
const _cream = PdfColor(0.957, 0.925, 0.847); // #F4ECD8 抬头反白店名
const _footnote = PdfColor(0.353, 0.306, 0.208); // #5A4E35 编号 / 三字段小字
const _ink = PdfColor(0.067, 0.067, 0.067); // #111 body text
// ── 热敏标签 TSPL 裸发(桌面端:dart:ui 画扁平标签位图 → BITMAP → lp -o raw)────────
@@ -253,10 +253,9 @@ Future<ui.Image> _renderLabelBitmap({
// 热敏机为单色介质,实际打印时头/脚颜色会退化为黑白(TSPL 路径另行处理)。
const cWhite = ui.Color(0xFFFFFFFF); // ds-ignore
const cNavy = ui.Color(0xFF1F2A3A); // ds-ignore 深蓝店名头
const cCream = ui.Color(0xFFF4ECD8); // ds-ignore 米色页脚 / 头部字色
const cCream = ui.Color(0xFFF4ECD8); // ds-ignore 抬头店名字色
const cInk = ui.Color(0xFF111111); // ds-ignore 正文
const cCode = ui.Color(0xFF333333); // ds-ignore 编号 / 三字段
const cFoot = ui.Color(0xFF5A4E35); // ds-ignore 页脚字
canvas.drawRect(ui.Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble()),
ui.Paint()..color = cWhite);
@@ -267,19 +266,9 @@ Future<ui.Image> _renderLabelBitmap({
ui.Rect.fromLTWH(0, 0, w.toDouble(), headerH), ui.Paint()..color = cNavy);
_drawText(canvas, shop, 10, 5, 12, w - 20, bold: true, color: cCream);
// ── 米色页脚 ────────────────────────────────────────────────
const footerH = 15.0;
const footerTop = h - footerH; // 145
canvas.drawRect(ui.Rect.fromLTWH(0, footerTop, w.toDouble(), footerH),
ui.Paint()..color = cCream);
// 页脚只留右侧尺寸标注:店名已在顶部抬头,页脚不再重复(2026-08-27 去冗余)
final szW = _measureWidth('40×20mm', 8, false);
_drawText(canvas, '40×20mm', w - 10 - szW, footerTop + 3.5, 8, szW,
color: cFoot);
// ── 主体两列布局 ────────────────────────────────────────────
// ── 主体两列布局(无页脚:主体直抵纸底,2026-08-27 删掉底部米色尺寸条)────────
const bodyTop = headerH; // 24
const bodyBot = footerTop; // 145
const double bodyBot = 160.0; // 直抵纸底 h(原到页脚顶 145),二维码/条码顺势放大
const padV = 6.0, padH = 8.0; // 收紧留白,给二维码腾空间
const double colGap = 6.0; // 左列与二维码列间距(原 10,收小)
@@ -384,7 +373,7 @@ Future<ui.Image> _renderLabelBitmap({
/// 桌面端把整张标签「光栅化」成单色位图裸发到热敏机(TSPL BITMAP),令热敏输出与
/// 预览画布 100% 一致——彻底消除内置点阵字(TSS16/24)与预览 TrueType 度量不一致
/// 导致的字体重叠 / 失真。文字/抬头/页脚走光栅;条码(Code128)与二维码仍走原生
/// 导致的字体重叠 / 失真。文字/抬头走光栅;条码(Code128)与二维码仍走原生
/// BARCODE / BITMAP 命令保证扫码可靠(光栅化后细条/密码易糊)。
/// 检测不到热敏机 -> 返回 false(交系统打印);检测到则强制 TSPL,失败抛异常。
/// [printerName] 非空时跳过自动检测直接使用,为空则走 _findThermalPrinter()。
@@ -404,7 +393,7 @@ Future<bool> _printFlatLabelThermal({
const w = 320, h = 160; // 40×20mm @203dpi(逻辑尺寸)
const wBytes = (w + 7) >> 3; // 40 bytes/row
// ── 文字/抬头/页脚整幅光栅:复用预览画布(跳过条码+QR,二者改原生命令)─────────
// ── 文字/抬头整幅光栅:复用预览画布(跳过条码+QR,二者改原生命令)─────────
// 画布内部 2× 超采样(640×320),此处 2×2 盒式降采样 + 亮度 128 阈值二值化,边缘更干净。
final img = await _renderLabelBitmap(
shop: shop,
@@ -425,7 +414,7 @@ Future<bool> _printFlatLabelThermal({
'REFERENCE 0,0\r\nDENSITY 10\r\nSPEED 2\r\nCLS\r\n',
));
// 整幅文字位图(mode 0 覆写整版):深蓝抬头→黑底反白、米色页脚→白底黑字,
// 整幅文字位图(mode 0 覆写整版):深蓝抬头→黑底反白,其余白底黑字,
// 均由亮度阈值自然还原,无需 REVERSE;bit 0=黑点,默认 0xFF=白。
buf.add(ascii.encode('BITMAP 0,0,$wBytes,$h,0,'));
for (int y = 0; y < h; y++) {
@@ -449,10 +438,10 @@ Future<bool> _printFlatLabelThermal({
buf.add(ascii.encode('\r\n'));
// ── 右列二维码(原生 BITMAP,避免光栅降采样糊码):占主体整高、右对齐 ──
const qrW = 104, qrH = 104;
const qrX = 320 - 8 - qrW; // 208
const bodyTop = 26, bodyBot = 134;
const qrY = bodyTop + ((bodyBot - bodyTop) - qrH) ~/ 2; // 28
const qrW = 118, qrH = 118;
const qrX = 320 - 8 - qrW; // 194
const bodyTop = 24, bodyBot = 160; // 无页脚:主体直抵纸底
const qrY = bodyTop + ((bodyBot - bodyTop) - qrH) ~/ 2; // 33
const qrWBytes = (qrW + 7) >> 3; // 13
final qrCodec = await ui.instantiateImageCodec(qrBytes,
targetWidth: qrW, targetHeight: qrH);
@@ -475,9 +464,9 @@ Future<bool> _printFlatLabelThermal({
buf.add(ascii.encode('\r\n'));
// ── 左列底部整幅一维条码(原生 BARCODE,Code128,内容=商品编号),narrow 自适应铺满居中 ──
const leftX = 8, leftRight = qrX - 6, leftW = leftRight - leftX; // 8..202, 194
const kBarH = 28;
const kBarY = bodyBot - 4 - kBarH; // 102
const leftX = 8, leftRight = qrX - 6, leftW = leftRight - leftX; // 8..188, 180
const kBarH = 34;
const kBarY = bodyBot - 8 - kBarH; // 118
if (code.isNotEmpty) {
final estModules = 11 * code.length + 35; // Code128B 估算模块数(含起止校验)
final narrow = (leftW / estModules).floor().clamp(2, 4);
@@ -518,7 +507,6 @@ Future<void> printProductLabelImpl({
const labelW = 40.0 * PdfPageFormat.mm;
const labelH = 20.0 * PdfPageFormat.mm;
const headerH = labelH * 0.20;
const footerH = labelH * 0.11;
// 底部平铺三字段:度数(型号) / 规格(版本) / 日期(口径与预览、TSPL 一致)
final subs = _labelSubFields(series, spec, productionDate);
@@ -653,22 +641,6 @@ Future<void> printProductLabelImpl({
),
),
),
// ── Footer:米色页脚(仅尺寸;店名已在抬头,不再重复)─────────────────
pw.Container(
height: footerH,
color: _cream,
padding: const pw.EdgeInsets.symmetric(horizontal: 5),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
pw.Text('40×20mm',
style: pw.TextStyle(
font: font, fontSize: 3.5, color: _footnote)),
],
),
),
],
),
));
-10
View File
@@ -116,14 +116,6 @@ body { width: 40mm; height: 20mm; overflow: hidden; background: #fff; }
/* 右:二维码占主体整高(正方形)*/
.qr { flex-shrink: 0; height: 100%; aspect-ratio: 1; }
.qr img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* ── Footer:米色页脚(仅尺寸;店名已在抬头,不再重复)── */
.ftr {
background: #f4ecd8; height: 2.4mm; flex-shrink: 0;
display: flex; align-items: center; justify-content: space-between;
padding: 0 1.2mm;
font-size: 3.4pt; color: #5a4e35; overflow: hidden; white-space: nowrap;
}
</style>
</head>
<body>
@@ -146,8 +138,6 @@ body { width: 40mm; height: 20mm; overflow: hidden; background: #fff; }
</div>
</div>
<div class="ftr"><span></span><span>40×20mm</span></div>
</div>
<script>
if (document.fonts && document.fonts.ready) {
+2 -4
View File
@@ -61,8 +61,8 @@
.tpl-desc{font-size:var(--fs-xs); color:var(--muted); margin-top:3px; line-height:1.4;}
.tpl-acts{display:flex; gap:8px; margin-top:10px;}
/* ---- 标签预览(40×20mm 热敏价签:抬头 + 商品信息/二维码 + 底部整幅一维条码 + 页脚)----
打印纸面为固定专色(navy 抬头 / cream 页脚 / 墨黑正文),不随 App 主题切换,故按 ds-allow 豁免。 */
/* ---- 标签预览(40×20mm 热敏价签:抬头 + 商品信息/二维码 + 底部整幅一维条码,无页脚)----
打印纸面为固定专色(navy 抬头 / 墨黑正文),不随 App 主题切换,故按 ds-allow 豁免。 */
.lbl-wrap{display:flex; flex-direction:column; align-items:center; gap:14px;}
.lbl-preview{width:340px; height:170px; border:1px solid var(--border); border-radius:var(--r-md); overflow:hidden; display:flex; flex-direction:column; box-shadow:var(--sh-2); background:#fff; color:#111;} /* ds-allow 打印纸面固定色 */
.lbl-hdr{height:26px; flex:none; display:flex; align-items:center; padding:0 10px; font-size:13px; font-weight:700; letter-spacing:.05em; background:#1f2a3a; color:#f4ecd8;} /* ds-allow 打印纸面固定尺寸 */
@@ -76,7 +76,6 @@
.lbl-bc img{width:100%; height:100%; display:block; object-fit:fill;}
.lbl-qr{flex:none; height:100%; aspect-ratio:1;}
.lbl-qr img{width:100%; height:100%; display:block; object-fit:contain;}
.lbl-ftr{height:16px; flex:none; display:flex; align-items:center; justify-content:space-between; padding:0 10px; font-size:9px; background:#f4ecd8; color:#5a4e35;} /* ds-allow 打印纸面固定尺寸 */
.lbl-note{max-width:340px; font-size:var(--fs-sm); color:var(--muted); line-height:1.6; text-align:center;}
/* 设计期返回悬浮球(右下角,非侵入) */
.ds-fab{position:fixed; right:22px; bottom:22px; width:44px; height:44px; border-radius:50%; background:var(--surface); color:var(--muted); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; box-shadow:var(--sh-2); z-index:var(--z-fab); opacity:.6; transition:opacity .18s,color .18s,border-color .18s,transform .18s,box-shadow .18s; text-decoration:none;}
@@ -172,7 +171,6 @@
</div>
<div class="lbl-qr"><img alt="二维码" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAABHNCSVQICAgIfAhkiAAAAAFzUkdCAK7OHOkAAAk0SURBVHic7d2xbtNXHMXxk8oREqqUSJHiR6hASHFHJpzdOy/ATp4AZWZiI6/QuQ9AOnXJYCYvDB3JhAMtqJEQXf4sAan3uP0599jfj5Tt6urm+n/aP9Ivx5L0hZ+Vf85VZ2qeZVq497vCOznv4HOM/fnBvGwAHSHAQDACDAQjwEAwAgwEI8BAMAIMBCPAQDACDAQbGWuXkl4XnqUXR5L2i/aeGHs/NPf+sWitJH00Pvu5ubeDZ/A7ehgb7Mmr0LHBmXGOmbn3E/P3rLoTnkFGKTfWn0VrJemNuR5rQoCBYAQYCEaAgWAEGAhGgIFgBBgIRoCBYAQYCEaAgWDOLLTrxTD7e9vmkk5u+xCDvWGet8WupB1J143rx8Y55pKOjfWPJZ0ae/dy3xv/DFYGeCLpUeH+ia5W+EOJu43rLo09l+Zc8WnoZ7nxzyCv0EAwAgwEI8BAMAIMBCPAQDACDAQjwEAwAgwEI8BAsMpJLHzLqVw9kPTAWO9Uxe6bI4YLY21lrSxuIMDr5czDziT9WnSOyVBd2up4iypdo/AK3S+3+tVdjw1AgIFgBBgIRoCBYAQYCEaAgWAEGAhGgIFgBBgIRoCBYIxSrpdTc7owq1+dGeRtqZXdeAR4vdya06r5422pld14vEIDwQgwEIwAA8EIMBCMAAPBCDAQjAADwQgwEIwAA8EqJ7F6qRft5Rwyq1+dtS63VvbQWFt5blcvn33ZOSoDzDxsv9xa2Q+FZ6m08c8gr9Dr5VS/9lQT+9lY29O5Nx4BBoIRYCAYAQaCEWAgGAEGghFgIBgBBoIRYCAYAQaCOaOUR+b4XSpnRti1NzRCtq51VFbWnhlr3XM7eAZvcAK8L2m62nkwuBrusXWto7Ky9q2knxrXuud28AzewCs0EIwAA8EIMBCMAAPBCDAQjAADwQgwEIwAA8EIMBBsJOm32z5EMLcu1KlcPZA0M0rinOrXyr3dWtleql+Bf3Uh6UvRz/tO9r647UveJrxCr1dl5apT/Vq5N7Wya0SAgWAEGAhGgIFgBBgIRoCBYAQYCEaAgWAEGAhGgIFgO0Y74dcZ19ZJmxNjznUy1KK2cvZ2OfWs7p28lHTZuPZY0tNhPLHFHUl/N679JOmZpDeN68fGuZfmZ1N533tGS+Z8eK5aVZ7ber6r5med+s9p4d6u89A7eWesdSplq1Xed+WddPGc8AoNBCPAQDACDAQjwEAwAgwEI8BAMAIMBCPAQDACDARzvuBbkl4b3zDfuu7rWqfedmxMq7ijfc7aA0kPjPVO5apbz7pbuHcl9yzOM+iMUi7MSbmFsdYdpXSe7+bxrrnx7fLVnDG2yrHBmTkiNyvc+y9jbU/Vr07VbuUzGDnO67xCL83/q24Dt0LVWe/ufV24dyXnLDyDN/BvYCAYAQaCEWAgGAEGghFgIBgBBoIRYCAYAQaCEWAg2MiYbLk3jL31UCv7UtJp41p3csepCz2U9MH4AuyxcY75UC3byq1+dVRX7VZ9lo6xuf+ZpLeNa907ue+c5darMVPnUIvvpCfbcCeVNb7uT/PevEIDwQgwEIwAA8EIMBCMAAPBCDAQjAADwQgwEIwAA8Eqa2WdasyH5jkqa1F7qZXdN8YXZY5SuqN9lRWqvZTUudXGTmWt+5w4FcHN411upWfl+J1Tz1qpsla2p9G+XsYdU5VVBKfWyvZSi1pZK4vNUVYRzL+BgWAEGAhGgIFgBBgIRoCBYAQYCEaAgWAEGAhGgIFgO8MIXos/JP1s7D0xRi+PJT0dxsNaXBZWejozrgtJvzSu1TCO2jrN1tMs9OOhVrjFfKgUbuVU1lbu7d6JU5vsfpZnTgVx63zmuXEAV09zv87elXfSE2eu3b2TXvbuaT68+dy8QgPBCDAQjAADwQgwEIwAA8EIMBCMAAPBCDAQjAADwUZGlaZTt+qqrPSsHKV078QZL10W3rk72ufUyrpndtYvzAmoQ/MsjsqKYOe+sUaVY4MOd3S1l1pZ99zvC0cpKyuCm++bV2hsss+Fe3dREUyAgWAEGAhGgIFgBBgIRoCBYAQYCEaAgWAEGAhGgIFgoy1qVqzg1pzuGbWy9yRdFNbhHjeulTmvPBnqXFs59axj80vmf5f0vHGtW23cXPu6wrmb9x5JemRsjP/myvhjBpnD+Etz76r/cO+bz5Rz5ktz/XPz93xmnsVZ6/6eTXiFBoIRYCAYAQaCEWAgGAEGghFgIBgBBoIRYCAYAQaCjYy1S0mvC8/SiyNzasbhjCS6dbj3jL0PhlbF1r3HxnTQQ+McMutZnbUyx0Urz+3WJjtjl13UnPbkVeidXBRWqL7rpJ51Zu697OTcZXiF3hxd1JyuwDm3+zu2/mHCKrq4bwIMBCPAQDACDAQjwEAwAgwEI8BAMAIMBCPAQDACDARzZqFdL4aK0dvmVr+mOjFmuN0KVaee1Z3hfizptHHtoaQPxhd33zHmincl7Ui6blzv1Mq6rOxUzf2eF86hOj/uuVNnoR1T8w6nhWepfE4qZ7i7uBNeoYFgBBgIRoCBYAQYCEaAgWAEGAhGgIFgBBgIRoCBYJWjlPjWxBh3XJo1tM7elRWqLud3PJD0wFj/0ahCdkdAx8Y0lru388XujFLeUDlK6dxJT/fdRYXqCrWyTwrPUnnf7xmlxP+piwrVFc7xpugc1Vr/WIN/AwPJCDAQjAADwQgwEIwAA8EIMBCMAAPBCDAQjAADwZiFXi+n+nVsjlO+VHs9a2X1a2WN73yoxG3lnNu9k/tGZe0nSc+MybCzxnVS6Gxu6iy0I7X6taeq3V4qa8uywys0EIwAA8EIMBCMAAPBCDAQjAADwQgwEIwAA8EIMBCscpTSqQut1Ms5tCXVrwtzKmws6bJxrVu120tlrfsMWusTR+Qq9VIr6/70Uv3qjoBWjiQ6eqqsbcYr9Obopfo1VWRlLQEGghFgIBgBBoIRYCAYAQaCEWAgGAEGghFgIBgBBoI5s9BHw5jhppsU7r1nVJHuStqRdN24fmycYyLphbF+T9JV49qFWf3qzEK7VbuV53bmld37Pmnd3wnwfnF16Ta4Mv6Y4au7jetaQ6DhDI+M9Uvz3FUzy9PQc7v33XxmXqGBYAQYCEaAgWAEGAhGgIFgBBgIRoCBYAQYCEaAgWD/ANlOLkkLzBmKAAAAAElFTkSuQmCC" /></div>
</div>
<div class="lbl-ftr"><span></span><span>40×20mm</span></div>
</div>
<div class="lbl-note">40×20mm 热敏价签:二维码溯源 + 底部整幅一维条码(Code128,内容=商品编号,供扫码枪扫码出库)。原型为示意布局,实际由客户端热敏/PDF/Web 三路渲染。</div>
</div>