feat(client): 标签模板编辑器头部加「打印」按钮(重置与保存之间)

打印当前预览:复用出签弹窗 LabelPreviewDialog(选打印机/份数),传入正在
编辑的模板 _t 与示例数据。预览/打印共用 _sampleLabel() 抽出示例 LabelData。

L1 图标单源:新登记 i-printer(Lucide printer)到原型 icons.js + 官网
icons.njk 两端(check-l1-sync 同集要求),原型头部 use #i-printer。

design-first:原型 label-template-editor.html 同步加「打印」按钮 + printTpl()
占位,与真实屏同提交。smoke test 增头部「打印」按钮断言。

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-30 12:08:17 +08:00
parent 7b3a0ecdeb
commit 195b4d22e7
5 changed files with 33 additions and 6 deletions
@@ -18,6 +18,7 @@ import '../../providers/shop_provider.dart';
import '../../widgets/ds/ds_atoms.dart';
import '../../widgets/ds/ds_switch.dart';
import '../../widgets/ds/ds_toast.dart';
import '../../widgets/label_preview_dialog.dart';
/// 标签价签模板编辑器(原型 `design/prototype/screens/label-template-editor.html`)。
///
@@ -177,12 +178,13 @@ class _LabelTemplateEditorScreenState
Timer(const Duration(milliseconds: 150), _renderPreview);
}
Future<void> _renderPreview() async {
/// 预览/打印共用的示例数据(QR 已在 bootstrap 生成)。
LabelData? _sampleLabel() {
final qr = _qrBytes;
if (qr == null) return;
if (qr == null) return null;
final shopName =
ref.read(shopInfoProvider).valueOrNull?.name ?? '鼎晟酒行';
final sample = LabelData(
return LabelData(
qrBytes: qr,
name: '贵州茅台酒',
code: 'P1001',
@@ -193,10 +195,28 @@ class _LabelTemplateEditorScreenState
remark: '整箱现货',
shopName: shopName,
);
}
Future<void> _renderPreview() async {
final sample = _sampleLabel();
if (sample == null) return;
final png = await renderLabelPreview(sample, template: _t);
if (mounted && png != null) setState(() => _previewPng = png);
}
/// 打印当前预览:复用出签弹窗(选打印机/份数),传入正在编辑的模板 [_t]。
void _print() {
final sample = _sampleLabel();
if (sample == null) {
_toast('预览尚未就绪,请稍候', err: true);
return;
}
showDialog(
context: context,
builder: (_) => LabelPreviewDialog(labels: [sample], template: _t),
);
}
void _mutate(VoidCallback fn) {
setState(fn);
_scheduleRender();
@@ -513,6 +533,8 @@ class _LabelTemplateEditorScreenState
const Spacer(),
DsButton('重置', icon: LucideIcons.refreshCw, onPressed: _reset),
const SizedBox(width: 10),
DsButton('打印', icon: LucideIcons.printer, onPressed: _print),
const SizedBox(width: 10),
DsButton(_saving ? '保存中…' : '保存',
icon: LucideIcons.check,
variant: DsBtnVariant.primary,
@@ -38,6 +38,8 @@ void main() {
expect(find.text('标签模板编辑器'), findsOneWidget);
expect(find.text('显示字段'), findsOneWidget);
expect(find.text('打印参数'), findsOneWidget);
// 头部「打印」按钮(打印当前预览)
expect(find.text('打印'), findsOneWidget);
expect(find.text('模板'), findsWidgets);
// 字段行齐全(品名 + 二维码等)
expect(find.text('品名'), findsOneWidget);
+2 -2
View File
File diff suppressed because one or more lines are too long
@@ -117,6 +117,7 @@
<h1>标签模板编辑器</h1><span class="sub">左侧实时预览 · 右侧属性面板</span>
<div class="actions">
<div class="btn ghost" onclick="resetTpl()"><svg viewBox="0 0 24 24"><use href="#i-refresh"/></svg>重置</div>
<div class="btn ghost" onclick="printTpl()"><svg viewBox="0 0 24 24"><use href="#i-printer"/></svg>打印</div>
<div class="btn primary" onclick="toast('模板已保存 ✓')"><svg viewBox="0 0 24 24"><use href="#i-check"/></svg>保存</div>
</div>
</div>
@@ -377,6 +378,8 @@
document.getElementById('label').style.transform = 'scale('+z+')';
}
function resetTpl(){ toast('已重置为默认版式'); }
/* 打印当前预览:真实端弹出签打印弹窗(选打印机/份数),此处仅提示 */
function printTpl(){ toast('打开打印预览…'); }
/* 模板:下拉选=正在编辑;「设默认」=打印实际使用的那个(二者可不同) */
const TPLS = ['默认版式', '大字号简约', '双语抬头'];
let tplEditing = '默认版式'; // 正在编辑
File diff suppressed because one or more lines are too long