feat(client): 价签默认模板品名默认展示 + 二维码放大留小边框
- 默认模板改为代码只读基准:readLabelTemplates 加载时强制用 builtinDefault() 覆盖 builtin-default 项,忽略存储里的旧脏几何 (旧默认品名被关/旧 QR 坐标等一律自愈回代码基准) - 二维码放大 x198/y44/尺寸96 → x192/y32/尺寸120,四周留 ~1mm 小边框 - 原型 label-template-editor.html 高级 QR 输入同步;golden 重生 - 新增存储测试钉死「内置默认永远回代码基准」 原型与代码同提交(design-first 双边一致)
This commit is contained in:
@@ -54,7 +54,8 @@ Future<ui.Image> renderLabelImage({
|
||||
lay.headerFontSize, w - 2 * lay.headerTextX,
|
||||
bold: lay.headerBold,
|
||||
textAlign: _uiAlign(lay.headerAlign),
|
||||
color: cCream);
|
||||
color: cCream,
|
||||
centerInH: lay.headerHeight); // 店名在深蓝带内上下居中(textY 作微调)
|
||||
}
|
||||
|
||||
// ── 右列二维码(正方形,绝对坐标)─────────────────────────────
|
||||
@@ -127,12 +128,15 @@ Future<Uint8List?> renderLabelPngBytes({
|
||||
return bd?.buffer.asUint8List();
|
||||
}
|
||||
|
||||
/// 在 canvas 上画一行文字(默认黑色,单行不换行,CJK 走系统字体回退)
|
||||
/// 在 canvas 上画一行文字(默认黑色,单行不换行,CJK 走系统字体回退)。
|
||||
/// [centerInH] > 0 时:在 [y, y+centerInH] 区间内竖直居中(用紧排行高 1.0 量高后
|
||||
/// 上下留白均分),供抬头店名在深蓝带内上下居中;[y] 作为居中基础上的手动微调量。
|
||||
void _drawText(
|
||||
ui.Canvas canvas, String s, double x, double y, double size, double maxW,
|
||||
{bool bold = false,
|
||||
ui.TextAlign textAlign = ui.TextAlign.left,
|
||||
ui.Color color = const ui.Color(0xFF000000)}) // ds-ignore: 纸面固定色(默认黑)
|
||||
ui.Color color = const ui.Color(0xFF000000), // ds-ignore: 纸面固定色(默认黑)
|
||||
double centerInH = 0})
|
||||
{
|
||||
if (s.isEmpty) return;
|
||||
final pb = ui.ParagraphBuilder(ui.ParagraphStyle(
|
||||
@@ -142,11 +146,13 @@ void _drawText(
|
||||
textAlign: textAlign,
|
||||
maxLines: 1,
|
||||
ellipsis: '',
|
||||
height: centerInH > 0 ? 1.0 : null, // 居中时用紧排行高,量出的高≈字号,居中更准
|
||||
))
|
||||
..pushStyle(ui.TextStyle(color: color)) // ds-ignore: 打印画布纸面固定色
|
||||
..addText(s);
|
||||
final p = pb.build()..layout(ui.ParagraphConstraints(width: maxW));
|
||||
canvas.drawParagraph(p, ui.Offset(x, y));
|
||||
final double dy = centerInH > 0 ? y + (centerInH - p.height) / 2 : y;
|
||||
canvas.drawParagraph(p, ui.Offset(x, dy));
|
||||
}
|
||||
|
||||
/// 版式对齐 → 画布 TextAlign。
|
||||
|
||||
@@ -147,7 +147,7 @@ class LabelHeader {
|
||||
this.show = true,
|
||||
this.height = 24,
|
||||
this.textX = 10,
|
||||
this.textY = 4,
|
||||
this.textY = 0, // 0=渲染器按 height 竖直居中;正负值作微调
|
||||
this.fontSize = 14,
|
||||
this.bold = true,
|
||||
this.align = LabelAlign.left,
|
||||
@@ -167,7 +167,7 @@ class LabelHeader {
|
||||
show: j['show'] as bool? ?? true,
|
||||
height: (j['height'] as num?)?.toDouble() ?? 24,
|
||||
textX: (j['textX'] as num?)?.toDouble() ?? 10,
|
||||
textY: (j['textY'] as num?)?.toDouble() ?? 4,
|
||||
textY: (j['textY'] as num?)?.toDouble() ?? 0,
|
||||
fontSize: (j['fontSize'] as num?)?.toDouble() ?? 14,
|
||||
bold: j['bold'] as bool? ?? true,
|
||||
align: _alignFrom(j['align'] as String?),
|
||||
@@ -534,7 +534,7 @@ class LabelTemplate {
|
||||
show: true,
|
||||
height: 24,
|
||||
textX: 10,
|
||||
textY: 4,
|
||||
textY: 0, // 0=纯居中(渲染器按 height 竖直居中),正负值作微调
|
||||
fontSize: 14,
|
||||
bold: true,
|
||||
align: LabelAlign.left),
|
||||
@@ -564,12 +564,41 @@ class LabelTemplate {
|
||||
align: LabelAlign.center),
|
||||
],
|
||||
),
|
||||
qr: LabelQr(show: true, x: 178, y: 25, size: 134),
|
||||
qr: LabelQr(show: true, x: 192, y: 32, size: 120), // 放大占满右列,四周留 ~8px(≈1mm) 小边框;左距条码/文字(x≤172) 20px
|
||||
barcode:
|
||||
LabelBarcode(show: true, x: 8, y: 114, width: 164, height: 40),
|
||||
);
|
||||
}
|
||||
|
||||
/// 历史内置默认的 QR 坐标签名(x/y/size)。加载老店铺模板时,凡 QR 恰好等于某个历史默认,
|
||||
/// 自动升级为当前 [LabelTemplate.builtinDefault] 的居中坐标——老模板无需手动「重置」即可拿到
|
||||
/// 二维码居中+四周留白的新版式。用户手动挪过的 QR 不匹配任一签名,保持不动。
|
||||
const List<List<double>> _legacyDefaultQrXYS = [
|
||||
[178, 25, 134], // 最初默认(贴头顶、无留白)
|
||||
[190, 34, 116], // 中间一版(缩小但仍偏上)
|
||||
];
|
||||
|
||||
/// 就地把匹配历史默认签名的 QR 升级到当前默认坐标;返回同一实例便于链式 map。
|
||||
LabelTemplate _migrateLegacyDefaults(LabelTemplate t) {
|
||||
final q = t.qr;
|
||||
for (final s in _legacyDefaultQrXYS) {
|
||||
if (q.x == s[0] && q.y == s[1] && q.size == s[2]) {
|
||||
final def = LabelTemplate.builtinDefault().qr;
|
||||
q.x = def.x;
|
||||
q.y = def.y;
|
||||
q.size = def.size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/// 内置默认模板是「代码真相 + 只读锁」:无论存储里存了什么旧版式(旧 QR、品名被关等),
|
||||
/// 加载时一律用当前 [LabelTemplate.builtinDefault] 覆盖,保证默认永远是最新基准。
|
||||
/// 用户的定制一律走「复制」出来的副本(另一 id),不受影响。
|
||||
LabelTemplate _forceBuiltinDefaultFromCode(LabelTemplate t) =>
|
||||
t.id == 'builtin-default' ? LabelTemplate.builtinDefault() : t;
|
||||
|
||||
/// 从 shop.custom_fields 读取店级模板列表;为空/损坏时回退单个 [LabelTemplate.builtinDefault]。
|
||||
List<LabelTemplate> readLabelTemplates(Map<String, dynamic> cf) {
|
||||
final raw = cf['label_templates'];
|
||||
@@ -577,6 +606,8 @@ List<LabelTemplate> readLabelTemplates(Map<String, dynamic> cf) {
|
||||
final list = raw
|
||||
.whereType<Map>()
|
||||
.map((m) => LabelTemplate.fromJson(m.cast<String, dynamic>()))
|
||||
.map(_migrateLegacyDefaults)
|
||||
.map(_forceBuiltinDefaultFromCode)
|
||||
.toList();
|
||||
if (list.isNotEmpty) return list;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user