feat(client): 新增入库单/出库单打印,增强商品标签打印布局

- 商品标签改为 4×2 英寸横向,二维码右置,左侧展示系列/规格/批次/生产日期
- 新增 printStockInOrder:A4 入库单,含单号/供应商/仓库/明细表/签字栏
- 新增 printStockOutOrder:A4 出库通知单(温馨提示),含客户/明细表/签字栏
- 入库单/出库单编辑页加载完成后显示「打印」按钮

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-05-19 00:50:54 +08:00
parent 7ac06a409d
commit 142e92f151
6 changed files with 291 additions and 35 deletions
@@ -533,6 +533,7 @@ class _QRCodeDialogState extends ConsumerState<_QRCodeDialog> {
name: p.name,
code: p.code,
spec: p.spec,
series: p.series,
);
} catch (e) {
if (mounted) {
@@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/theme/app_theme.dart';
import '../../core/auth/auth_state.dart';
import '../../core/utils/print_util.dart';
import '../../models/stock_in.dart';
import '../../providers/partner_provider.dart';
import '../../providers/product_option_provider.dart';
import '../../providers/product_provider.dart';
@@ -29,6 +31,7 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
bool _submitting = false;
bool _loadingEdit = false;
Map<int, double> _inventoryMap = {};
StockInOrder? _loadedOrder;
final List<_ItemRow> _items = [];
@@ -53,6 +56,7 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
final specOpts = await ref.read(productSpecListProvider.future);
setState(() {
_loadedOrder = order;
_warehouseId = order.warehouseId;
_partnerId = order.partnerId;
if (order.orderDate != null) {
@@ -124,6 +128,11 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
});
}
Future<void> _printOrder() async {
if (_loadedOrder == null) return;
await printStockInOrder(_loadedOrder!);
}
Future<void> _submit(bool asDraft) async {
if (!asDraft && !_formKey.currentState!.validate()) return;
if (_warehouseId == null) {
@@ -248,6 +257,14 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
Text(_isEdit ? '修改入库单' : '新建入库单',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const Spacer(),
if (_isEdit && _loadedOrder != null) ...[
OutlinedButton.icon(
onPressed: _printOrder,
icon: const Icon(Icons.print_outlined, size: 16),
label: const Text('打印'),
),
const SizedBox(width: 8),
],
OutlinedButton(
onPressed: _submitting ? null : () => _submit(true),
child: const Text('保存草稿'),
@@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/theme/app_theme.dart';
import '../../core/auth/auth_state.dart';
import '../../core/utils/print_util.dart';
import '../../models/stock_out.dart';
import '../../providers/inventory_provider.dart';
import '../../providers/partner_provider.dart';
import '../../providers/product_option_provider.dart';
@@ -29,6 +31,7 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
bool _submitting = false;
bool _loadingEdit = false;
Map<int, double> _inventoryMap = {};
StockOutOrder? _loadedOrder;
final List<_ItemRow> _items = [];
@@ -53,6 +56,7 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
final specOpts = await ref.read(productSpecListProvider.future);
setState(() {
_loadedOrder = order;
_warehouseId = order.warehouseId;
_partnerId = order.partnerId;
if (order.orderDate != null) {
@@ -125,6 +129,11 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
});
}
Future<void> _printOrder() async {
if (_loadedOrder == null) return;
await printStockOutOrder(_loadedOrder!);
}
Future<void> _submit(bool asDraft) async {
if (!asDraft && !_formKey.currentState!.validate()) return;
if (_warehouseId == null) {
@@ -248,6 +257,14 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
Text(_isEdit ? '修改出库单' : '新建出库单',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const Spacer(),
if (_isEdit && _loadedOrder != null) ...[
OutlinedButton.icon(
onPressed: _printOrder,
icon: const Icon(Icons.print_outlined, size: 16),
label: const Text('打印'),
),
const SizedBox(width: 8),
],
OutlinedButton(
onPressed: _submitting ? null : () => _submit(true),
child: const Text('保存草稿'),