From 3d1fc0d4ba586cacb3195d63136811c6b10c1d8c Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 25 May 2026 22:55:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E7=BB=9F=E4=B8=80=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=8C=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=97=B6=E4=B8=8A=E6=8A=A5=E5=B9=B6=E5=BC=B9=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 safePrint 封装,所有打印调用点统一 catch 任意异常(含字体加载失败), 自动上报 error_reporter 并展示 SnackBar,不再静默失败。 Co-Authored-By: Claude Sonnet 4.6 --- .claude/commands/release.md | 10 ++++++ CHANGELOG.md | 5 +-- client/lib/core/utils/print_util.dart | 17 ++++++++++ client/lib/core/utils/print_util_stub.dart | 2 -- .../inventory/inventory_list_screen.dart | 21 ++++-------- .../products/product_detail_screen.dart | 14 +++----- .../stock_in/stock_in_form_screen.dart | 2 +- .../stock_in/stock_in_list_screen.dart | 29 +++++----------- .../stock_out/stock_out_form_screen.dart | 2 +- .../stock_out/stock_out_list_screen.dart | 33 +++++-------------- 10 files changed, 58 insertions(+), 77 deletions(-) diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 1a318e4..5336b40 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -43,6 +43,16 @@ cd client && flutter analyze --no-fatal-infos --no-fatal-warnings 只保留有内容的分类(新功能/改进/修复),根据 commit message 的类型(feat/fix/refactor 等)归类。 +**CHANGELOG 撰写原则:** +- 站在**用户视角**总结,描述对使用体验的实际影响,而非技术实现 +- **忽略**以下类型的提交(产品感知弱): + - 发版工具、CI/CD、部署脚本相关(chore: release、ci:、devops:) + - 内部文档、开发规范、CLAUDE.md 更新(docs(claude):、docs(internal):) + - 代码格式、lint、重构(style:、refactor: 纯内部) + - 依赖升级、版本号 bump(chore: bump、chore: upgrade) +- **保留并重点描述**:feat、fix、perf 类,以及用户能感知到的 refactor(如界面改版、交互优化) +- 每条描述用一句话说清楚"用户能做什么"或"修了什么问题",不堆砌技术术语 + ## 5. 提交代码 将所有改动(包括 CHANGELOG.md)用以下格式提交到 main: diff --git a/CHANGELOG.md b/CHANGELOG.md index 85d2f12..cce49d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.2] - 2026-05-25 -### 改进 -- 添加 /release 本地发版 slash command,一键完成构建、测试、打 tag 和推送 - ### 修复 -- Web 版打印弹窗被浏览器拦截时,由静默失败改为给出明确提示,并自动上报错误 +- 打印入库单/出库单/商品标签时,若浏览器拦截了弹窗,现在会给出明确提示,不再静默失败 ## [1.0.1] - 2026-05-25 diff --git a/client/lib/core/utils/print_util.dart b/client/lib/core/utils/print_util.dart index 674cda0..36f0a2d 100644 --- a/client/lib/core/utils/print_util.dart +++ b/client/lib/core/utils/print_util.dart @@ -1,6 +1,8 @@ import 'dart:typed_data'; +import 'package:flutter/material.dart'; import '../../models/stock_in.dart'; import '../../models/stock_out.dart'; +import '../errors/error_reporter.dart'; import 'print_util_stub.dart' if (dart.library.js_interop) 'print_util_web.dart'; @@ -36,3 +38,18 @@ Future printStockInOrder(StockInOrder order) => Future printStockOutOrder(StockOutOrder order) => printStockOutOrderImpl(order); + +/// 统一打印入口:catch 任意异常,上报并弹 SnackBar 给用户。 +/// 用法:await safePrint(context, () => printStockInOrder(order)); +Future safePrint(BuildContext context, Future Function() fn) async { + try { + await fn(); + } catch (e, st) { + reportError(e, st); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('打印失败:$e'), backgroundColor: Colors.red), + ); + } + } +} diff --git a/client/lib/core/utils/print_util_stub.dart b/client/lib/core/utils/print_util_stub.dart index 8328f65..05779c6 100644 --- a/client/lib/core/utils/print_util_stub.dart +++ b/client/lib/core/utils/print_util_stub.dart @@ -190,7 +190,6 @@ Future printProductLabelImpl({ ), )); - try { await Printing.layoutPdf(onLayout: (_) async => doc.save()); } catch (e, st) { reportError(e, st); @@ -430,7 +429,6 @@ Future printStockInOrderImpl(StockInOrder order) async { ), ], )); - try { await Printing.layoutPdf(onLayout: (_) async => doc.save()); } catch (e, st) { reportError(e, st); diff --git a/client/lib/screens/inventory/inventory_list_screen.dart b/client/lib/screens/inventory/inventory_list_screen.dart index c6c82d6..5946a21 100644 --- a/client/lib/screens/inventory/inventory_list_screen.dart +++ b/client/lib/screens/inventory/inventory_list_screen.dart @@ -485,12 +485,12 @@ class _InventoryListScreenState extends ConsumerState { item.productId != null ? TextButton( onPressed: () async { - try { - final qrBytes = await ref - .read(productRepositoryProvider) - .getQRCodeBytes(item.productId!); - final shopInfo = ref.read(shopInfoProvider).valueOrNull; - await printProductLabel( + final qrBytes = await ref + .read(productRepositoryProvider) + .getQRCodeBytes(item.productId!); + final shopInfo = ref.read(shopInfoProvider).valueOrNull; + if (context.mounted) { + await safePrint(context, () => printProductLabel( qrBytes: qrBytes, name: item.productName, code: item.productCode, @@ -502,14 +502,7 @@ class _InventoryListScreenState extends ConsumerState { shopName: shopInfo?.name ?? '', shopAddress: shopInfo?.address ?? '', shopPhone: shopInfo?.phone ?? '', - ); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('打印失败:$e'), - backgroundColor: AppTheme.danger), - ); - } + )); } }, child: const Text('打标签', diff --git a/client/lib/screens/products/product_detail_screen.dart b/client/lib/screens/products/product_detail_screen.dart index 74e62e5..2c4aa85 100644 --- a/client/lib/screens/products/product_detail_screen.dart +++ b/client/lib/screens/products/product_detail_screen.dart @@ -528,10 +528,10 @@ class _QRCodeDialogState extends ConsumerState<_QRCodeDialog> { Future _print() async { if (_bytes == null || _printing) return; setState(() { _printing = true; _printStatus = '正在打印...'; }); + final p = widget.product; + final shopInfo = ref.read(shopInfoProvider).valueOrNull; try { - final p = widget.product; - final shopInfo = ref.read(shopInfoProvider).valueOrNull; - await printProductLabel( + await safePrint(context, () => printProductLabel( qrBytes: _bytes!, name: p.name, code: p.code, @@ -540,13 +540,7 @@ class _QRCodeDialogState extends ConsumerState<_QRCodeDialog> { shopName: shopInfo?.name ?? '', shopAddress: shopInfo?.address ?? '', shopPhone: shopInfo?.phone ?? '', - ); - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('打印失败:$e'), backgroundColor: Colors.red), - ); - } + )); } finally { if (mounted) setState(() { _printing = false; _printStatus = ''; }); } diff --git a/client/lib/screens/stock_in/stock_in_form_screen.dart b/client/lib/screens/stock_in/stock_in_form_screen.dart index 0c3c5f9..d3d3ef9 100644 --- a/client/lib/screens/stock_in/stock_in_form_screen.dart +++ b/client/lib/screens/stock_in/stock_in_form_screen.dart @@ -137,7 +137,7 @@ class _StockInFormScreenState extends ConsumerState { Future _printOrder() async { if (_loadedOrder == null) return; - await printStockInOrder(_loadedOrder!); + await safePrint(context, () => printStockInOrder(_loadedOrder!)); } Future _submit(bool asDraft) async { diff --git a/client/lib/screens/stock_in/stock_in_list_screen.dart b/client/lib/screens/stock_in/stock_in_list_screen.dart index 5c9a355..fab3e5a 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -1,4 +1,6 @@ import '../../core/utils/dialog_util.dart'; +import '../../core/errors/error_reporter.dart'; +import '../../core/utils/print_util.dart' show safePrint, printStockInOrder, printProductLabel; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -247,15 +249,9 @@ class _StockInListScreenState extends ConsumerState { ), TextButton( onPressed: () async { - try { - final order = await ref.read(stockInRepositoryProvider).get(o.id); - await printStockInOrder(order); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), - ); - } + final order = await ref.read(stockInRepositoryProvider).get(o.id); + if (context.mounted) { + await safePrint(context, () => printStockInOrder(order)); } }, child: const Text('打印', @@ -698,17 +694,7 @@ class _StockInDetailDialogState extends ConsumerState<_StockInDetailDialog> { IconButton( icon: const Icon(Icons.print_outlined, color: Colors.white), tooltip: '打印', - onPressed: () async { - try { - await printStockInOrder(_loadedOrder!); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), - ); - } - } - }, + onPressed: () => safePrint(context, () => printStockInOrder(_loadedOrder!)), ), IconButton( icon: const Icon(Icons.close, color: Colors.white), @@ -966,7 +952,8 @@ class _LabelPrintDialogState extends State<_LabelPrintDialog> { ); done++; if (mounted) setState(() => _status = '已打印 $done 张...'); - } catch (e) { + } catch (e, st) { + reportError(e, st); if (mounted) setState(() => _status = '第${i + 1}行打印失败:$e'); } } diff --git a/client/lib/screens/stock_out/stock_out_form_screen.dart b/client/lib/screens/stock_out/stock_out_form_screen.dart index b6ce590..f216007 100644 --- a/client/lib/screens/stock_out/stock_out_form_screen.dart +++ b/client/lib/screens/stock_out/stock_out_form_screen.dart @@ -222,7 +222,7 @@ class _StockOutFormScreenState extends ConsumerState { Future _printOrder() async { if (_loadedOrder == null) return; - await printStockOutOrder(_loadedOrder!); + await safePrint(context, () => printStockOutOrder(_loadedOrder!)); } Future _submit(bool asDraft) async { diff --git a/client/lib/screens/stock_out/stock_out_list_screen.dart b/client/lib/screens/stock_out/stock_out_list_screen.dart index 7b44e23..90d6311 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -1,5 +1,7 @@ import '../../repositories/product_repository.dart'; import '../../core/utils/dialog_util.dart'; +import '../../core/errors/error_reporter.dart'; +import '../../core/utils/print_util.dart' show safePrint, printStockOutOrder, printProductLabel; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -254,15 +256,9 @@ class _StockOutListScreenState extends ConsumerState { ), TextButton( onPressed: () async { - try { - final order = await ref.read(stockOutRepositoryProvider).get(o.id); - await printStockOutOrder(order); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), - ); - } + final order = await ref.read(stockOutRepositoryProvider).get(o.id); + if (context.mounted) { + await safePrint(context, () => printStockOutOrder(order)); } }, child: const Text('打印', @@ -690,17 +686,7 @@ class _StockOutDetailDialogState extends ConsumerState<_StockOutDetailDialog> { IconButton( icon: const Icon(Icons.print_outlined, color: Colors.white), tooltip: '打印', - onPressed: () async { - try { - await printStockOutOrder(_loadedOrder!); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: Colors.red), - ); - } - } - }, + onPressed: () => safePrint(context, () => printStockOutOrder(_loadedOrder!)), ), IconButton( icon: const Icon(Icons.close, color: Colors.white), @@ -960,10 +946,9 @@ class _LabelPrintDialogState extends State<_LabelPrintDialog> { ); done++; if (mounted) setState(() => _status = '已打印 $done 张...'); - } catch (e) { - if (mounted) { - setState(() => _status = '第${i + 1}行打印失败:$e'); - } + } catch (e, st) { + reportError(e, st); + if (mounted) setState(() => _status = '第${i + 1}行打印失败:$e'); } } if (mounted) {