Compare commits

...

7 Commits

Author SHA1 Message Date
wangjia 47cf0ae547 chore: release client-v1.0.84
Deploy Client / build-client-web (push) Successful in 45s
Deploy Client / build-windows (push) Successful in 2m43s
Deploy Client / build-macos (push) Successful in 2m15s
Deploy Client / build-android (push) Successful in 1m20s
Deploy Client / build-ios (push) Successful in 2m52s
Deploy Client / release-deploy-client (push) Successful in 1m51s
修复出库单/入库单打印时单据明细超过一页导致 MultiPage 分页死循环卡死的回归
(_buildOrderDoc 改返回可拆分子组件列表,左侧竖线点缀改由 pageTheme 背景每页绘制)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:28:00 +08:00
wangjia 68fce1970c chore: release client-v1.0.83
Deploy Client / build-client-web (push) Successful in 46s
Deploy Client / build-windows (push) Successful in 1m54s
Deploy Client / build-macos (push) Successful in 2m22s
Deploy Client / build-android (push) Successful in 1m28s
Deploy Client / build-ios (push) Successful in 3m19s
Deploy Client / release-deploy-client (push) Successful in 1m53s
2026-06-26 18:11:40 +08:00
wangjia 2ad893fb40 docs: 出入库单打印排版方案(6 种黑白样式 HTML + PDF)
参考真实发票/送货单产出 6 种黑白排版供选型,已登记 docs 索引。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 18:10:19 +08:00
wangjia 1558ce1e94 refactor(client): 出入库单打印改用「紧凑报表」版式
- 左侧竖线点缀 + 抬头左对齐内联(公司名 + 单据类型同行)
- 抬头信息改为灰底信息条;明细表收紧字号/行距、灰底表头
- 数量/单价/金额/总计改等宽数字(PDF 内置 Courier,Web monospace)
- 单据总计改为右对齐灰底条
- 仅排版调整,字段/数据/售价口径/温馨提示逻辑均不变

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 18:10:12 +08:00
wangjia a355a3a2a2 feat(client): 出入库单打印按新版式重做 + 单价口径修正
- 版式参照单据样板:公司抬头 + 两行三列表头 + 统一明细列
  (商品编号/名称/系列/规格/批次号/生产日期/数量/单价/金额/备注)
- 单据总计独立成行(仅数量+金额,右对齐高亮)
- 出库单保留温馨提示,入库单去掉
- 签字行:制单人=当前登录用户(OrderPrintMeta),采购员/销售员=经办人;
  公司名取门店(shopInfoProvider)
- 出库单单价=售价(sale_price),金额=售价×数量;历史单 sale_price=0
  时回退 unit_price/total_price。入库单单价=进价(unit_price)
- 桌面 PDF 与 Web HTML 两端同构

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:03:23 +08:00
wangjia 5187702fc5 chore: release client-v1.0.82
Deploy Client / build-client-web (push) Successful in 47s
Deploy Client / build-windows (push) Successful in 1m56s
Deploy Client / build-macos (push) Successful in 2m15s
Deploy Client / build-android (push) Successful in 1m20s
Deploy Client / build-ios (push) Successful in 2m47s
Deploy Client / release-deploy-client (push) Successful in 1m51s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 01:52:04 +08:00
wangjia 3d83f94f24 chore: release client-v1.0.81
Deploy Client / build-client-web (push) Successful in 42s
Deploy Client / build-windows (push) Successful in 1m55s
Deploy Client / build-macos (push) Successful in 2m19s
Deploy Client / build-android (push) Successful in 1m7s
Deploy Client / build-ios (push) Successful in 2m37s
Deploy Client / release-deploy-client (push) Successful in 1m53s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:40:23 +08:00
13 changed files with 1151 additions and 325 deletions
+24
View File
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.84] - 2026-06-26
### 修复
- 修复出库单 / 入库单打印时,单据明细超过一页会导致程序卡死无响应的问题(新版打印版式引入的回归)。
## [1.0.83] - 2026-06-26
### 改进
- 出库单 / 入库单打印版式全面优化:公司抬头、批次号与生产日期、数字等宽对齐、单据总计独立高亮条,整体更紧凑规整,打印更清晰。
### 修复
- 修正出库单打印的「单价 / 金额」:现按售价显示(金额 = 售价 × 数量),不再误用成本价;历史单据自动兼容。
## [1.0.82] - 2026-06-26
### 新功能
- 出库单 / 入库单打印新增「打印预览」:点击打印先弹出整页预览(所见即所得),确认无误后再调用系统打印,解决部分 Windows 电脑打印对话框无法预览的问题。
## [1.0.81] - 2026-06-24
### 改进
- 出库 / 入库单详情全屏弹窗只覆盖内容区,不再遮挡左侧菜单栏、顶栏与状态栏。
- 出库 / 入库单详情标题栏改为浅色配色(浅蓝底 + 深色文字),观感更清爽。
## [1.0.80] - 2026-06-24 ## [1.0.80] - 2026-06-24
### 新功能 ### 新功能
@@ -0,0 +1,20 @@
/// 单据打印的「门店抬头 + 制单人」元信息。
///
/// - 门店信息(公司名称/地址/电话)来自 `shopInfoProvider`,作为单据顶部抬头。
/// - [makerName] = 制单人 = **当前登录、点击打印的人**(非单据经办人/审核人)。
/// 入库单的「采购员」「出库单的「销售员」才是单据经办人(operator)。
class OrderPrintMeta {
final String shopName;
final String shopAddress;
final String shopPhone;
/// 制单人 = 当前登录用户(打印此单的人)。
final String makerName;
const OrderPrintMeta({
this.shopName = '',
this.shopAddress = '',
this.shopPhone = '',
this.makerName = '',
});
}
+15 -4
View File
@@ -5,6 +5,8 @@ import '../../models/stock_out.dart';
import '../errors/error_reporter.dart'; import '../errors/error_reporter.dart';
import 'label_data.dart'; import 'label_data.dart';
export 'label_data.dart'; export 'label_data.dart';
import 'order_print_meta.dart';
export 'order_print_meta.dart';
import 'print_util_stub.dart' import 'print_util_stub.dart'
if (dart.library.js_interop) 'print_util_web.dart'; if (dart.library.js_interop) 'print_util_web.dart';
@@ -47,11 +49,20 @@ Future<List<String>> listLabelPrinters() => listLabelPrintersImpl();
/// 自动检测默认热敏打印机(名字含 deli/dl-888);找不到或 Web 返回 null。 /// 自动检测默认热敏打印机(名字含 deli/dl-888);找不到或 Web 返回 null。
Future<String?> detectDefaultPrinter() => detectDefaultPrinterImpl(); Future<String?> detectDefaultPrinter() => detectDefaultPrinterImpl();
Future<void> printStockInOrder(StockInOrder order) => Future<void> printStockInOrder(StockInOrder order, OrderPrintMeta meta) =>
printStockInOrderImpl(order); printStockInOrderImpl(order, meta);
Future<void> printStockOutOrder(StockOutOrder order) => Future<void> printStockOutOrder(StockOutOrder order, OrderPrintMeta meta) =>
printStockOutOrderImpl(order); printStockOutOrderImpl(order, meta);
/// 生成入库单 PDF 字节(桌面端真实排版,供打印预览光栅化用);Web 端不支持。
Future<Uint8List> buildStockInOrderPdf(StockInOrder order, OrderPrintMeta meta) =>
buildStockInOrderPdfImpl(order, meta);
/// 生成出库单 PDF 字节(桌面端真实排版,供打印预览光栅化用);Web 端不支持。
Future<Uint8List> buildStockOutOrderPdf(
StockOutOrder order, OrderPrintMeta meta) =>
buildStockOutOrderPdfImpl(order, meta);
/// 统一打印入口:catch 任意异常,上报并弹 SnackBar 给用户。 /// 统一打印入口:catch 任意异常,上报并弹 SnackBar 给用户。
/// 用法:await safePrint(context, () => printStockInOrder(order)); /// 用法:await safePrint(context, () => printStockInOrder(order));
+310 -170
View File
@@ -25,6 +25,7 @@ import 'package:printing/printing.dart';
import '../../models/stock_in.dart'; import '../../models/stock_in.dart';
import '../../models/stock_out.dart'; import '../../models/stock_out.dart';
import 'label_data.dart'; import 'label_data.dart';
import 'order_print_meta.dart';
Future<pw.Font> _loadFont() async { Future<pw.Font> _loadFont() async {
final data = await rootBundle.load('assets/fonts/NotoSansSC-Regular.ttf'); final data = await rootBundle.load('assets/fonts/NotoSansSC-Regular.ttf');
@@ -644,211 +645,337 @@ Future<List<String>> listLabelPrintersImpl() async {
/// 自动检测默认热敏打印机(名字含 deli/dl-888);找不到返回 null。 /// 自动检测默认热敏打印机(名字含 deli/dl-888);找不到返回 null。
Future<String?> detectDefaultPrinterImpl() => _findThermalPrinter(); Future<String?> detectDefaultPrinterImpl() => _findThermalPrinter();
pw.Widget _buildOrderDoc({ /// 截取日期前 10 位(yyyy-MM-dd),空值返回空串。
String _d10(String? s) {
if (s == null || s.isEmpty) return '';
return s.length > 10 ? s.substring(0, 10) : s;
}
/// 数量格式化:整数不带小数,否则保留 3 位。
String _qtyStr(double q) =>
q % 1 == 0 ? q.toStringAsFixed(0) : q.toStringAsFixed(3);
/// 单据明细列(入库/出库统一):商品编号→备注。
const _orderHeaders = [
'商品编号', '商品名称', '系列', '规格', '批次号',
'生产日期', '数量', '单价', '金额', '备注',
];
const _orderColFlex = <double>[1.5, 2.0, 1.1, 1.3, 1.6, 1.3, 0.7, 1.1, 1.2, 0.9];
/// 温馨提示文案(入库/出库统一)。
const _kTipsText =
'温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,'
'如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。';
/// 各明细列对齐:系列/规格/批次号/生产日期居中,数量/单价/金额右对齐,其余左对齐。
pw.TextAlign _colAlign(int i) {
switch (i) {
case 2:
case 3:
case 4:
case 5:
return pw.TextAlign.center;
case 6:
case 7:
case 8:
return pw.TextAlign.right;
default:
return pw.TextAlign.left;
}
}
/// 构建入库单/出库单 A4 版式(参照「锐浪报表」单据:公司抬头 + 两行三列表头
/// + 明细表 + 单据总计 + 温馨提示 + 签字行)。入库/出库仅称谓不同。
List<pw.Widget> _buildOrderDoc({
required pw.Font font, required pw.Font font,
required pw.Font bold, required pw.Font bold,
required String title, required String shopName, // 公司名称(顶部抬头)
required String title, // 入 库 单 / 出 库 单
required String orderNo, required String orderNo,
required String? orderDate, required String noPrefix, // 'NO' / 'NO.'
required String? partnerLabel, required String partnerLabel, // 单位名称 / 客户名称
required String? partnerName, required String? partnerName,
required String dateLabel, // 入库日期 / 出库日期
required String? orderDate,
required String addressLabel, // 单位地址 / 送货地址
required String? warehouseName, required String? warehouseName,
required String signerLabel, // 出货人(签字)/ 收货人(签字)
required String operatorRoleLabel, // 采购员 / 销售员(= 经办人)
required String? operatorName, required String? operatorName,
required String? reviewerName, required String? makerName, // 制单人 = 打印人
required String? remark, required String? remark,
required List<String> headers, required bool showTips,
required List<List<String>> rows, required List<List<String>> rows,
required double totalQty, required double totalQty,
required double totalAmt, required double totalAmt,
String? tipsText,
}) { }) {
final headerStyle = pw.TextStyle(font: bold, fontSize: 10, fontWeight: pw.FontWeight.bold); // 样式三「紧凑报表」:左侧竖线点缀 + 抬头左对齐内联 + 灰底信息条 + 密集明细表
final cellStyle = pw.TextStyle(font: font, fontSize: 9.5); // + 数字等宽(内置 Courier,ASCII 数字适用)+ 灰底总计条。
final metaStyle = pw.TextStyle(font: font, fontSize: 10); final mono = pw.Font.courier();
final boldStyle = pw.TextStyle(font: bold, fontSize: 10, fontWeight: pw.FontWeight.bold); final monoBold = pw.Font.courierBold();
final cellStyle = pw.TextStyle(font: font, fontSize: 8.5);
final numStyle = pw.TextStyle(font: mono, fontSize: 8.5);
final headStyle =
pw.TextStyle(font: bold, fontSize: 8.5, fontWeight: pw.FontWeight.bold);
final metaStyle = pw.TextStyle(font: font, fontSize: 9);
final metaBold =
pw.TextStyle(font: bold, fontSize: 9, fontWeight: pw.FontWeight.bold);
final colWidths = headers.map((h) { pw.Widget metaText(String label, String value, pw.TextAlign align) =>
if (h == '商品名称') return const pw.FlexColumnWidth(2.2); pw.Text('$label$value', style: metaStyle, textAlign: align);
if (h == '系列' || h == '规格') return const pw.FlexColumnWidth(1.2);
if (h == '商品编号') return const pw.FlexColumnWidth(1.2);
return const pw.FlexColumnWidth(0.9);
}).toList();
return pw.Column( // 返回明细块的「子组件列表」而非单个 Container:作为 MultiPage 的顶层
crossAxisAlignment: pw.CrossAxisAlignment.start, // children,明细表可跨页拆分。左侧竖线点缀改由 pageTheme 背景每页绘制
children: [ // (见 _orderPageTheme)——原先用非可拆分的 pw.Container 包裹整块内容,
pw.Center( // 当内容超过一页时 MultiPage 无法拆分,分页算法不前进→无限加页、内存暴涨
child: pw.Text(title, // →打印卡死(根因)。
style: pw.TextStyle(font: bold, fontSize: 20, fontWeight: pw.FontWeight.bold)), return [
), // ── 抬头:公司名 + 单据类型,左对齐内联 ──────────────────────────
pw.SizedBox(height: 8), pw.Row(
pw.Container( crossAxisAlignment: pw.CrossAxisAlignment.end,
decoration: const pw.BoxDecoration(
border: pw.Border(bottom: pw.BorderSide(color: PdfColors.grey600)),
),
padding: const pw.EdgeInsets.only(bottom: 5),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [ children: [
pw.Text('$partnerLabel${partnerName ?? ''}', style: metaStyle), pw.Text(shopName,
pw.Text('日期:${orderDate ?? ''}', style: metaStyle), style: pw.TextStyle(
pw.Text('NO: $orderNo', style: boldStyle), font: bold, fontSize: 16, fontWeight: pw.FontWeight.bold)),
pw.SizedBox(width: 12),
pw.Padding(
padding: const pw.EdgeInsets.only(bottom: 1.5),
child: pw.Text(title,
style: pw.TextStyle(
font: font,
fontSize: 11,
letterSpacing: 3,
color: PdfColors.grey700)),
),
], ],
), ),
), pw.SizedBox(height: 8),
pw.SizedBox(height: 4), // ── 抬头信息:灰底信息条(两行三列)────────────────────────────
pw.Row(
children: [
pw.Text('仓库:${warehouseName ?? ''}', style: metaStyle),
pw.SizedBox(width: 24),
pw.Text('经办人:${operatorName ?? ''}', style: metaStyle),
],
),
pw.SizedBox(height: 8),
pw.Table(
border: pw.TableBorder.all(color: PdfColors.grey500),
columnWidths: { for (var i = 0; i < colWidths.length; i++) i: colWidths[i] },
children: [
pw.TableRow(
decoration: const pw.BoxDecoration(color: PdfColors.grey200),
children: headers.map((h) => pw.Padding(
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 3),
child: pw.Text(h, textAlign: pw.TextAlign.center, style: headerStyle),
)).toList(),
),
...rows.map((row) => pw.TableRow(
children: row.map((cell) => pw.Padding(
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 2),
child: pw.Text(cell, style: cellStyle),
)).toList(),
)),
pw.TableRow(
decoration: const pw.BoxDecoration(color: PdfColors.grey100),
children: List.generate(headers.length, (i) {
if (i == headers.length - 3) {
return pw.Padding(
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 2),
child: pw.Text('单据总计', textAlign: pw.TextAlign.right, style: boldStyle),
);
}
if (i == headers.length - 2) {
return pw.Padding(
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 2),
child: pw.Text(
totalQty % 1 == 0 ? totalQty.toStringAsFixed(0) : totalQty.toStringAsFixed(3),
textAlign: pw.TextAlign.right, style: boldStyle,
),
);
}
if (i == headers.length - 1) {
return pw.Padding(
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 2),
child: pw.Text(totalAmt.toStringAsFixed(2),
textAlign: pw.TextAlign.right, style: boldStyle),
);
}
return pw.SizedBox();
}),
),
],
),
if ((remark ?? '').isNotEmpty) ...[
pw.SizedBox(height: 6),
pw.Text('备注:$remark', style: metaStyle),
],
if (tipsText != null) ...[
pw.SizedBox(height: 6),
pw.Container( pw.Container(
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
border: pw.Border.all(color: PdfColors.grey400), color: PdfColors.grey100,
color: PdfColors.grey50, border: pw.Border.all(color: PdfColors.grey400, width: 0.5),
),
padding: const pw.EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: pw.Column(
children: [
pw.Row(
children: [
pw.Expanded(
flex: 5,
child: metaText(
partnerLabel, partnerName ?? '', pw.TextAlign.left)),
pw.Expanded(
flex: 4,
child: metaText(
dateLabel, _d10(orderDate), pw.TextAlign.left)),
pw.Expanded(
flex: 3,
child: pw.Text('$noPrefix$orderNo',
style: metaBold, textAlign: pw.TextAlign.right)),
],
),
pw.SizedBox(height: 3),
pw.Row(
children: [
pw.Expanded(
flex: 5,
child: metaText('联系电话', '', pw.TextAlign.left)),
pw.Expanded(
flex: 4,
child: metaText(addressLabel, '', pw.TextAlign.left)),
pw.Expanded(
flex: 3,
child: metaText(
'仓库', warehouseName ?? '', pw.TextAlign.right)),
],
),
],
), ),
padding: const pw.EdgeInsets.all(5),
child: pw.Text(tipsText,
style: pw.TextStyle(font: font, fontSize: 9, color: PdfColors.grey700)),
), ),
], pw.SizedBox(height: 8),
pw.SizedBox(height: 36), // ── 明细表(密集、灰底表头、数字等宽)──────────────────────────
pw.Row( pw.Table(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, border: pw.TableBorder.all(color: PdfColors.grey500, width: 0.5),
children: [ columnWidths: {
_sig(font, bold, '收/出货人(签字)'), for (var i = 0; i < _orderColFlex.length; i++)
_sig(font, bold, '经办人:${operatorName ?? ''}'), i: pw.FlexColumnWidth(_orderColFlex[i])
_sig(font, bold, '制单人:${reviewerName ?? operatorName ?? ''}'), },
children: [
pw.TableRow(
decoration: const pw.BoxDecoration(color: PdfColors.grey300),
children: [
for (final h in _orderHeaders)
pw.Padding(
padding: const pw.EdgeInsets.symmetric(
horizontal: 2, vertical: 2.5),
child: pw.Text(h,
textAlign: pw.TextAlign.center, style: headStyle),
),
],
),
for (final row in rows)
pw.TableRow(
children: [
for (var i = 0; i < row.length; i++)
pw.Padding(
padding: const pw.EdgeInsets.symmetric(
horizontal: 3, vertical: 2),
child: pw.Text(row[i],
textAlign: _colAlign(i),
style: (i >= 6 && i <= 8) ? numStyle : cellStyle),
),
],
),
],
),
// ── 单据总计:灰底条,右对齐,仅数量 + 金额(等宽数字)──────────
pw.SizedBox(height: 6),
pw.Container(
width: double.infinity,
decoration: pw.BoxDecoration(
color: PdfColors.grey100,
border: pw.Border.all(color: PdfColors.grey400, width: 0.5),
),
padding: const pw.EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
pw.Text('单据总计', style: metaBold),
pw.SizedBox(width: 26),
pw.Text('数量 ', style: metaStyle),
pw.Text(_qtyStr(totalQty),
style: pw.TextStyle(font: monoBold, fontSize: 11)),
pw.SizedBox(width: 26),
pw.Text('金额 ', style: metaStyle),
pw.Text(totalAmt.toStringAsFixed(2),
style: pw.TextStyle(font: monoBold, fontSize: 12)),
],
),
),
// ── 温馨提示(仅出库单)──────────────────────────────────────────
if (showTips) ...[
pw.SizedBox(height: 8),
pw.Container(
width: double.infinity,
decoration: pw.BoxDecoration(
border: pw.Border.all(color: PdfColors.grey400, width: 0.5),
color: PdfColors.grey50,
),
padding: const pw.EdgeInsets.all(5),
child: pw.Text(_kTipsText,
style: pw.TextStyle(
font: font, fontSize: 8.5, color: PdfColors.grey800)),
),
], ],
), pw.SizedBox(height: 24),
], // ── 签字行:签收人 / 经办人 / 制单人 ──────────────────────────────
); pw.Row(
} mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Widget _sig(pw.Font font, pw.Font bold, String label) { pw.Text('$signerLabel', style: metaStyle),
return pw.Column( pw.Text('$operatorRoleLabel${operatorName ?? ''}',
children: [ style: metaStyle),
pw.Container(width: 120, height: 28), pw.Text('制单人:${makerName ?? ''}', style: metaStyle),
pw.Container( ],
width: 120,
decoration: const pw.BoxDecoration(
border: pw.Border(top: pw.BorderSide(color: PdfColors.grey700)),
), ),
padding: const pw.EdgeInsets.only(top: 3), if ((remark ?? '').isNotEmpty) ...[
child: pw.Text(label, pw.SizedBox(height: 10),
textAlign: pw.TextAlign.center, pw.Text('备注:$remark', style: metaStyle),
style: pw.TextStyle(font: font, fontSize: 10)), ],
), ];
],
);
} }
Future<void> printStockInOrderImpl(StockInOrder order) async { /// 出/入库单 MultiPage 页面主题:内容区左缩进 + 全页高左侧竖线点缀(每页都画)。
try { /// 用 pageTheme 背景绘制竖线,让明细内容(含表格)可跨页拆分,避免非可拆分
/// Container 在内容超一页时触发 MultiPage 分页死循环。
pw.PageTheme _orderPageTheme() => pw.PageTheme(
pageFormat: PdfPageFormat.a4,
// 左边距 = 原始外边距 15mm + (竖线宽 3 + 内缩 14),与旧版视觉缩进一致。
margin: const pw.EdgeInsets.fromLTRB(
15 * PdfPageFormat.mm + 17,
12 * PdfPageFormat.mm,
15 * PdfPageFormat.mm,
12 * PdfPageFormat.mm,
),
buildBackground: (ctx) => pw.FullPage(
ignoreMargins: true,
child: pw.Padding(
padding: const pw.EdgeInsets.fromLTRB(
15 * PdfPageFormat.mm,
12 * PdfPageFormat.mm,
0,
12 * PdfPageFormat.mm,
),
child: pw.Container(
decoration: const pw.BoxDecoration(
border: pw.Border(
left: pw.BorderSide(color: PdfColors.black, width: 3)),
),
),
),
),
);
/// 生成入库单 A4 PDF 字节(与实际打印同一套排版,供预览光栅化 + 打印共用)。
Future<Uint8List> buildStockInOrderPdfImpl(
StockInOrder order, OrderPrintMeta meta) async {
final font = await _loadFont(); final font = await _loadFont();
final bold = await _loadBoldFont(); final bold = await _loadBoldFont();
double totalQty = 0, totalAmt = 0; double totalQty = 0, totalAmt = 0;
final rows = <List<String>>[]; final rows = <List<String>>[];
for (int i = 0; i < order.items.length; i++) { for (final it in order.items) {
final it = order.items[i];
totalQty += it.quantity; totalQty += it.quantity;
totalAmt += it.totalPrice; totalAmt += it.totalPrice;
rows.add([ rows.add([
'${i + 1}',
it.productCode ?? '', it.productCode ?? '',
it.productName ?? '', it.productName ?? '',
it.productSeries ?? '', it.productSeries ?? '',
it.productSpec ?? '', it.productSpec ?? '',
it.quantity % 1 == 0 ? it.quantity.toStringAsFixed(0) : it.quantity.toStringAsFixed(3), it.batchNo ?? '',
it.productUnit ?? '', _d10(it.productionDate),
_qtyStr(it.quantity),
it.unitPrice.toStringAsFixed(2), it.unitPrice.toStringAsFixed(2),
it.totalPrice.toStringAsFixed(2), it.totalPrice.toStringAsFixed(2),
it.productionDate ?? '', '',
it.batchNo ?? '',
]); ]);
} }
final doc = pw.Document(); final doc = pw.Document();
doc.addPage(pw.MultiPage( doc.addPage(pw.MultiPage(
pageFormat: PdfPageFormat.a4, pageTheme: _orderPageTheme(),
margin: const pw.EdgeInsets.symmetric(horizontal: 15 * PdfPageFormat.mm, vertical: 12 * PdfPageFormat.mm), build: (_) => _buildOrderDoc(
build: (_) => [
_buildOrderDoc(
font: font, bold: bold, font: font, bold: bold,
shopName: meta.shopName,
title: '入 库 单', title: '入 库 单',
orderNo: order.orderNo, orderNo: order.orderNo,
orderDate: order.orderDate, noPrefix: 'NO',
partnerLabel: '供应商', partnerLabel: '单位名称',
partnerName: order.partnerName, partnerName: order.partnerName,
dateLabel: '入库日期',
orderDate: order.orderDate,
addressLabel: '单位地址',
warehouseName: order.warehouseName, warehouseName: order.warehouseName,
signerLabel: '出货人(签字)',
operatorRoleLabel: '采购员',
operatorName: order.operatorName, operatorName: order.operatorName,
reviewerName: order.reviewerName, makerName: meta.makerName,
remark: order.remark, remark: order.remark,
headers: ['序号', '商品编号', '商品名称', '系列', '规格', '数量', '单位', '单价', '金额', '生产日期', '批次'], showTips: false,
rows: rows, rows: rows,
totalQty: totalQty, totalQty: totalQty,
totalAmt: totalAmt, totalAmt: totalAmt,
), ),
],
)); ));
final bytes = await doc.save(); return doc.save();
}
Future<void> printStockInOrderImpl(
StockInOrder order, OrderPrintMeta meta) async {
try {
final bytes = await buildStockInOrderPdfImpl(order, meta);
if (!kIsWeb && Platform.isMacOS) { if (!kIsWeb && Platform.isMacOS) {
await Future<void>.delayed(Duration.zero); await Future<void>.delayed(Duration.zero);
} }
@@ -862,55 +989,68 @@ Future<void> printStockInOrderImpl(StockInOrder order) async {
} }
} }
Future<void> printStockOutOrderImpl(StockOutOrder order) async { /// 生成出库单 A4 PDF 字节(与实际打印同一套排版,供预览光栅化 + 打印共用)。
Future<Uint8List> buildStockOutOrderPdfImpl(
StockOutOrder order, OrderPrintMeta meta) async {
final font = await _loadFont(); final font = await _loadFont();
final bold = await _loadBoldFont(); final bold = await _loadBoldFont();
double totalQty = 0, totalAmt = 0; double totalQty = 0, totalAmt = 0;
final rows = <List<String>>[]; final rows = <List<String>>[];
for (int i = 0; i < order.items.length; i++) { for (final it in order.items) {
final it = order.items[i]; // 出库单「单价」= 售价:App 单据存 sale_price;历史导入单 sale_price=0、
// 实际售价存在 unit_price/total_price,故 sale_price 为空时回退到 unit_price。
final unit = it.salePrice > 0 ? it.salePrice : it.unitPrice;
final amt = it.salePrice > 0 ? it.salePrice * it.quantity : it.totalPrice;
totalQty += it.quantity; totalQty += it.quantity;
totalAmt += it.totalPrice; totalAmt += amt;
rows.add([ rows.add([
'${i + 1}',
it.productCode ?? '', it.productCode ?? '',
it.productName ?? '', it.productName ?? '',
it.productSeries ?? '', it.productSeries ?? '',
it.productSpec ?? '', it.productSpec ?? '',
it.productUnit ?? '', it.batchNo ?? '',
it.quantity % 1 == 0 ? it.quantity.toStringAsFixed(0) : it.quantity.toStringAsFixed(3), _d10(it.productionDate),
it.unitPrice.toStringAsFixed(2), _qtyStr(it.quantity),
it.totalPrice.toStringAsFixed(2), unit.toStringAsFixed(2),
amt.toStringAsFixed(2),
'',
]); ]);
} }
final doc = pw.Document(); final doc = pw.Document();
doc.addPage(pw.MultiPage( doc.addPage(pw.MultiPage(
pageFormat: PdfPageFormat.a4, pageTheme: _orderPageTheme(),
margin: const pw.EdgeInsets.symmetric(horizontal: 15 * PdfPageFormat.mm, vertical: 12 * PdfPageFormat.mm), build: (_) => _buildOrderDoc(
build: (_) => [
_buildOrderDoc(
font: font, bold: bold, font: font, bold: bold,
shopName: meta.shopName,
title: '出 库 单', title: '出 库 单',
orderNo: order.orderNo, orderNo: order.orderNo,
orderDate: order.orderDate, noPrefix: 'NO.',
partnerLabel: '客户', partnerLabel: '客户名称',
partnerName: order.partnerName, partnerName: order.partnerName,
dateLabel: '出库日期',
orderDate: order.orderDate,
addressLabel: '送货地址',
warehouseName: order.warehouseName, warehouseName: order.warehouseName,
signerLabel: '收货人(签字)',
operatorRoleLabel: '销售员',
operatorName: order.operatorName, operatorName: order.operatorName,
reviewerName: order.reviewerName, makerName: meta.makerName,
remark: order.remark, remark: order.remark,
headers: ['序号', '商品编号', '商品名称', '系列', '规格', '单位', '数量', '单价', '金额'], showTips: true,
rows: rows, rows: rows,
totalQty: totalQty, totalQty: totalQty,
totalAmt: totalAmt, totalAmt: totalAmt,
tipsText: '温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。',
), ),
],
)); ));
return doc.save();
}
Future<void> printStockOutOrderImpl(
StockOutOrder order, OrderPrintMeta meta) async {
try { try {
final bytes = await doc.save(); final bytes = await buildStockOutOrderPdfImpl(order, meta);
if (!kIsWeb && Platform.isMacOS) { if (!kIsWeb && Platform.isMacOS) {
await Future<void>.delayed(Duration.zero); await Future<void>.delayed(Duration.zero);
} }
+130 -132
View File
@@ -6,6 +6,7 @@ import '../../models/stock_in.dart';
import '../../models/stock_out.dart'; import '../../models/stock_out.dart';
import '../errors/error_reporter.dart'; import '../errors/error_reporter.dart';
import 'label_data.dart'; import 'label_data.dart';
import 'order_print_meta.dart';
void _openPrintWindow(String html) { void _openPrintWindow(String html) {
final win = web.window.open('', '_blank'); final win = web.window.open('', '_blank');
@@ -170,28 +171,85 @@ body { width: 38mm; height: 20mm; overflow: hidden; background: #fff; }
} }
} }
// ── 单据 PDF 字节(Web 走 HTML 打印路径,不生成 PDF;预览弹窗仅桌面端调用)──────
Future<Uint8List> buildStockInOrderPdfImpl(
StockInOrder order, OrderPrintMeta meta) async =>
throw UnsupportedError('Web 端入库单走 HTML 打印,不生成 PDF');
Future<Uint8List> buildStockOutOrderPdfImpl(
StockOutOrder order, OrderPrintMeta meta) async =>
throw UnsupportedError('Web 端出库单走 HTML 打印,不生成 PDF');
/// 单据打印共用样式(样式三「紧凑报表」:左侧竖线 + 抬头左对齐内联 +
/// 灰底信息条 + 密集明细表 + 数字等宽 + 灰底总计条;入库/出库一致)。
const String _orderPrintCss = '''
@page { size: A4; margin: 12mm 14mm; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: "PingFang SC", "Microsoft YaHei", "SimSun", sans-serif; color: #111; border-left: 3px solid #111; padding-left: 14px; }
.shop { display: inline; font-size: 19px; font-weight: 700; }
.title { display: inline; margin-left: 12px; font-size: 13px; letter-spacing: 4px; color: #444; }
.head-block { background: #f3f3f3; border: 1px solid #ccc; padding: 7px 10px; margin: 10px 0; }
.meta-row { display: flex; font-size: 11.5px; line-height: 1.9; }
.meta-row + .meta-row { margin-top: 2px; }
.meta-row .c1 { flex: 5; } .meta-row .c2 { flex: 4; } .meta-row .c3 { flex: 3; text-align: right; }
.meta-row .no { font-weight: 700; }
table { width: 100%; border-collapse: collapse; border: 1px solid #bbb; }
th { background: #e5e5e5; border: 1px solid #bbb; padding: 5px 5px; font-size: 11px; font-weight: 700; text-align: center; white-space: nowrap; }
td { border: 1px solid #ccc; padding: 4px 6px; font-size: 11px; }
.c { text-align: center; } .r { text-align: right; }
td.r { font-family: "SF Mono", "Roboto Mono", Menlo, Consolas, monospace; }
.subtotal-bar { display: flex; justify-content: flex-end; align-items: baseline; gap: 28px; border: 1px solid #bbb; background: #f3f3f3; padding: 7px 12px; margin-top: 8px; font-size: 12px; font-weight: 700; }
.subtotal-bar .lbl-total { margin-right: auto; }
.subtotal-bar b { font-size: 14px; font-family: "SF Mono", Menlo, Consolas, monospace; }
.tips { border: 1px solid #ddd; background: #fafafa; padding: 6px 8px; margin: 8px 0; font-size: 10px; color: #555; line-height: 1.6; }
.sigs { display: flex; justify-content: space-between; margin-top: 30px; font-size: 12px; }
.remark { margin-top: 10px; font-size: 11px; color: #333; }
''';
const String _orderTipsHtml =
'<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,'
'如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div>';
/// 截取日期前 10 位(yyyy-MM-dd)。
String _d10(String? s) =>
(s == null || s.isEmpty) ? '' : (s.length > 10 ? s.substring(0, 10) : s);
/// 单据明细表头(入库/出库一致)。
const String _orderTableHead = '''<thead><tr>
<th style="width:12%">商品编号</th>
<th style="width:16%">商品名称</th>
<th style="width:9%">系列</th>
<th style="width:10%">规格</th>
<th style="width:14%">批次号</th>
<th style="width:11%">生产日期</th>
<th style="width:5%">数量</th>
<th style="width:8%">单价</th>
<th style="width:9%">金额</th>
<th style="width:6%">备注</th>
</tr></thead>''';
// ── 入库单 ────────────────────────────────────────────────────────────────── // ── 入库单 ──────────────────────────────────────────────────────────────────
Future<void> printStockInOrderImpl(StockInOrder order) async { Future<void> printStockInOrderImpl(
StockInOrder order, OrderPrintMeta meta) async {
final rows = StringBuffer(); final rows = StringBuffer();
double totalQty = 0; double totalQty = 0;
double totalAmt = 0; double totalAmt = 0;
for (int i = 0; i < order.items.length; i++) { for (final item in order.items) {
final item = order.items[i];
totalQty += item.quantity; totalQty += item.quantity;
totalAmt += item.totalPrice; totalAmt += item.totalPrice;
rows.write('''<tr> rows.write('''<tr>
<td class="c">${i + 1}</td>
<td>${item.productCode ?? ''}</td> <td>${item.productCode ?? ''}</td>
<td>${item.productName ?? ''}</td> <td>${item.productName ?? ''}</td>
<td class="c">${item.productSeries ?? ''}</td> <td class="c">${item.productSeries ?? ''}</td>
<td class="c">${item.productSpec ?? ''}</td> <td class="c">${item.productSpec ?? ''}</td>
<td class="c">${item.batchNo ?? ''}</td>
<td class="c">${_d10(item.productionDate)}</td>
<td class="r">${item.quantity % 1 == 0 ? item.quantity.toStringAsFixed(0) : item.quantity.toStringAsFixed(3)}</td> <td class="r">${item.quantity % 1 == 0 ? item.quantity.toStringAsFixed(0) : item.quantity.toStringAsFixed(3)}</td>
<td class="c">${item.productUnit ?? ''}</td>
<td class="r">${item.unitPrice.toStringAsFixed(2)}</td> <td class="r">${item.unitPrice.toStringAsFixed(2)}</td>
<td class="r">${item.totalPrice.toStringAsFixed(2)}</td> <td class="r">${item.totalPrice.toStringAsFixed(2)}</td>
<td class="c">${item.productionDate ?? ''}</td> <td></td>
<td class="c">${item.batchNo ?? ''}</td>
</tr>'''); </tr>''');
} }
@@ -203,72 +261,38 @@ Future<void> printStockInOrderImpl(StockInOrder order) async {
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>$_orderPrintCss</style>
@page { size: A4; margin: 12mm 15mm; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: "PingFang SC", "Microsoft YaHei", "SimSun", sans-serif; font-size: 11px; color: #000; }
.hdr { text-align: center; margin-bottom: 10px; }
.hdr .title { font-size: 22px; font-weight: bold; margin-bottom: 4px; }
.meta { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 6px; border-bottom: 1px solid #666; padding-bottom: 5px; }
.meta .left, .meta .center, .meta .right { flex: 1; }
.meta .center { text-align: center; }
.meta .right { text-align: right; font-weight: bold; font-size: 12px; }
.meta2 { display: flex; gap: 24px; margin-bottom: 8px; font-size: 11px; }
table { width: 100%; border-collapse: collapse; margin-bottom: 4px; }
th, td { border: 1px solid #999; padding: 4px 5px; }
th { background: #f0f0f0; font-weight: 600; text-align: center; font-size: 11px; }
td { font-size: 10.5px; }
.c { text-align: center; }
.r { text-align: right; }
.subtotal { font-weight: bold; background: #f7f7f7; }
.remark { margin: 6px 0; font-size: 11px; color: #333; }
.sigs { display: flex; justify-content: space-between; margin-top: 28px; }
.sig { text-align: center; min-width: 120px; }
.sig-line { border-top: 1px solid #555; padding-top: 4px; margin-top: 28px; font-size: 11px; }
.footer-note { text-align: right; font-size: 9px; color: #aaa; margin-top: 6px; }
</style>
</head> </head>
<body> <body>
<div class="hdr"><div class="title">入 库 单</div></div> <div class="shop">${meta.shopName}</div>
<div class="meta"> <div class="title">入 库 单</div>
<div class="left">供应商:${order.partnerName ?? ''}</div> <div class="head-block">
<div class="center">入库日期:${order.orderDate ?? ''}</div> <div class="meta-row">
<div class="right">NO: ${order.orderNo}</div> <div class="c1">单位名称:${order.partnerName ?? ''}</div>
</div> <div class="c2">入库日期:${_d10(order.orderDate)}</div>
<div class="meta2"> <div class="c3 no">NO${order.orderNo}</div>
<span>仓库:${order.warehouseName ?? ''}</span> </div>
<span>采购人:${order.operatorName ?? ''}</span> <div class="meta-row">
<div class="c1">联系电话:</div>
<div class="c2">单位地址:</div>
<div class="c3">仓库:${order.warehouseName ?? ''}</div>
</div>
</div> </div>
<table> <table>
<thead><tr> $_orderTableHead
<th style="width:3%">序号</th>
<th style="width:9%">商品编号</th>
<th style="width:18%">商品名称</th>
<th style="width:10%">系列</th>
<th style="width:9%">规格</th>
<th style="width:5%">数量</th>
<th style="width:5%">单位</th>
<th style="width:8%">单价</th>
<th style="width:8%">金额</th>
<th style="width:10%">生产日期</th>
<th style="width:10%">批次号</th>
</tr></thead>
<tbody>$rows</tbody> <tbody>$rows</tbody>
<tfoot><tr class="subtotal">
<td colspan="5" class="r">单据总计</td>
<td class="r">${totalQty % 1 == 0 ? totalQty.toStringAsFixed(0) : totalQty.toStringAsFixed(3)}</td>
<td></td><td></td>
<td class="r">${totalAmt.toStringAsFixed(2)}</td>
<td colspan="2"></td>
</tr></tfoot>
</table> </table>
$remarkLine <div class="subtotal-bar">
<div class="sigs"> <span class="lbl-total">单据总计</span>
<div class="sig"><div class="sig-line">出货人(签字)</div></div> <span>数量 <b>${totalQty % 1 == 0 ? totalQty.toStringAsFixed(0) : totalQty.toStringAsFixed(3)}</b></span>
<div class="sig"><div class="sig-line">采购员:${order.operatorName ?? ''}</div></div> <span>金额 <b>${totalAmt.toStringAsFixed(2)}</b></span>
<div class="sig"><div class="sig-line">制单人:${order.reviewerName ?? order.operatorName ?? ''}</div></div>
</div> </div>
<div class="footer-note">酒库管理系统</div> <div class="sigs">
<span>出货人(签字):</span>
<span>采购员:${order.operatorName ?? ''}</span>
<span>制单人:${meta.makerName}</span>
</div>
$remarkLine
<script>window.onload = function(){ window.print(); };</script> <script>window.onload = function(){ window.print(); };</script>
</body> </body>
</html>'''; </html>''';
@@ -291,24 +315,30 @@ Future<String?> detectDefaultPrinterImpl() async => null;
// ── 出库单 ────────────────────────────────────────────────────────────────── // ── 出库单 ──────────────────────────────────────────────────────────────────
Future<void> printStockOutOrderImpl(StockOutOrder order) async { Future<void> printStockOutOrderImpl(
StockOutOrder order, OrderPrintMeta meta) async {
final rows = StringBuffer(); final rows = StringBuffer();
double totalQty = 0; double totalQty = 0;
double totalAmt = 0; double totalAmt = 0;
for (int i = 0; i < order.items.length; i++) { for (final item in order.items) {
final item = order.items[i]; // 出库单「单价」= 售价:App 单据存 sale_price;历史导入单 sale_price=0、
// 实际售价存在 unit_price/total_price,故 sale_price 为空时回退到 unit_price。
final unit = item.salePrice > 0 ? item.salePrice : item.unitPrice;
final amt =
item.salePrice > 0 ? item.salePrice * item.quantity : item.totalPrice;
totalQty += item.quantity; totalQty += item.quantity;
totalAmt += item.totalPrice; totalAmt += amt;
rows.write('''<tr> rows.write('''<tr>
<td class="c">${i + 1}</td>
<td>${item.productCode ?? ''}</td> <td>${item.productCode ?? ''}</td>
<td>${item.productName ?? ''}</td> <td>${item.productName ?? ''}</td>
<td class="c">${item.productSeries ?? ''}</td> <td class="c">${item.productSeries ?? ''}</td>
<td class="c">${item.productSpec ?? ''}</td> <td class="c">${item.productSpec ?? ''}</td>
<td class="c">${item.productUnit ?? ''}</td> <td class="c">${item.batchNo ?? ''}</td>
<td class="c">${_d10(item.productionDate)}</td>
<td class="r">${item.quantity % 1 == 0 ? item.quantity.toStringAsFixed(0) : item.quantity.toStringAsFixed(3)}</td> <td class="r">${item.quantity % 1 == 0 ? item.quantity.toStringAsFixed(0) : item.quantity.toStringAsFixed(3)}</td>
<td class="r">${item.unitPrice.toStringAsFixed(2)}</td> <td class="r">${unit.toStringAsFixed(2)}</td>
<td class="r">${item.totalPrice.toStringAsFixed(2)}</td> <td class="r">${amt.toStringAsFixed(2)}</td>
<td></td>
</tr>'''); </tr>''');
} }
@@ -320,71 +350,39 @@ Future<void> printStockOutOrderImpl(StockOutOrder order) async {
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>$_orderPrintCss</style>
@page { size: A4; margin: 12mm 15mm; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: "PingFang SC", "Microsoft YaHei", "SimSun", sans-serif; font-size: 11px; color: #000; }
.hdr { text-align: center; margin-bottom: 10px; }
.hdr .title { font-size: 22px; font-weight: bold; margin-bottom: 4px; }
.meta { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 6px; border-bottom: 1px solid #666; padding-bottom: 5px; }
.meta .left, .meta .center, .meta .right { flex: 1; }
.meta .center { text-align: center; }
.meta .right { text-align: right; font-weight: bold; font-size: 12px; }
.meta2 { display: flex; gap: 24px; margin-bottom: 8px; font-size: 11px; }
table { width: 100%; border-collapse: collapse; margin-bottom: 4px; }
th, td { border: 1px solid #999; padding: 4px 5px; }
th { background: #f0f0f0; font-weight: 600; text-align: center; font-size: 11px; }
td { font-size: 10.5px; }
.c { text-align: center; }
.r { text-align: right; }
.subtotal { font-weight: bold; background: #f7f7f7; }
.remark { margin: 6px 0; font-size: 11px; color: #333; }
.tips { border: 1px solid #ddd; background: #fafafa; padding: 6px 8px; margin: 8px 0; font-size: 10px; color: #555; line-height: 1.6; }
.sigs { display: flex; justify-content: space-between; margin-top: 28px; }
.sig { text-align: center; min-width: 120px; }
.sig-line { border-top: 1px solid #555; padding-top: 4px; margin-top: 28px; font-size: 11px; }
.footer-note { text-align: right; font-size: 9px; color: #aaa; margin-top: 6px; }
</style>
</head> </head>
<body> <body>
<div class="hdr"><div class="title">出 库 单</div></div> <div class="shop">${meta.shopName}</div>
<div class="meta"> <div class="title">出 库 单</div>
<div class="left">客户:${order.partnerName ?? ''}</div> <div class="head-block">
<div class="center">出库日期:${order.orderDate ?? ''}</div> <div class="meta-row">
<div class="right">NO.${order.orderNo}</div> <div class="c1">客户名称:${order.partnerName ?? ''}</div>
</div> <div class="c2">出库日期:${_d10(order.orderDate)}</div>
<div class="meta2"> <div class="c3 no">NO.${order.orderNo}</div>
<span>仓库:${order.warehouseName ?? ''}</span> </div>
<span>经办人:${order.operatorName ?? ''}</span> <div class="meta-row">
<div class="c1">联系电话:</div>
<div class="c2">送货地址:</div>
<div class="c3">仓库:${order.warehouseName ?? ''}</div>
</div>
</div> </div>
<table> <table>
<thead><tr> $_orderTableHead
<th style="width:3%">序号</th>
<th style="width:9%">商品编号</th>
<th style="width:20%">商品名称</th>
<th style="width:11%">系列</th>
<th style="width:10%">规格</th>
<th style="width:5%">单位</th>
<th style="width:5%">数量</th>
<th style="width:9%">单价</th>
<th style="width:9%">金额</th>
</tr></thead>
<tbody>$rows</tbody> <tbody>$rows</tbody>
<tfoot><tr class="subtotal">
<td colspan="6" class="r">单据总计</td>
<td class="r">${totalQty % 1 == 0 ? totalQty.toStringAsFixed(0) : totalQty.toStringAsFixed(3)}</td>
<td></td>
<td class="r">${totalAmt.toStringAsFixed(2)}</td>
</tr></tfoot>
</table> </table>
$remarkLine <div class="subtotal-bar">
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div> <span class="lbl-total">单据总计</span>
<div class="sigs"> <span>数量 <b>${totalQty % 1 == 0 ? totalQty.toStringAsFixed(0) : totalQty.toStringAsFixed(3)}</b></span>
<div class="sig"><div class="sig-line">收货人(签字)</div></div> <span>金额 <b>${totalAmt.toStringAsFixed(2)}</b></span>
<div class="sig"><div class="sig-line">销售员:${order.operatorName ?? ''}</div></div>
<div class="sig"><div class="sig-line">制单人:${order.reviewerName ?? order.operatorName ?? ''}</div></div>
</div> </div>
<div class="footer-note">酒库管理系统</div> $_orderTipsHtml
<div class="sigs">
<span>收货人(签字):</span>
<span>销售员:${order.operatorName ?? ''}</span>
<span>制单人:${meta.makerName}</span>
</div>
$remarkLine
<script>window.onload = function(){ window.print(); };</script> <script>window.onload = function(){ window.print(); };</script>
</body> </body>
</html>'''; </html>''';
@@ -5,8 +5,8 @@ import '../../core/responsive/responsive.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../core/theme/app_theme.dart'; import '../../core/theme/app_theme.dart';
import '../../core/auth/auth_state.dart'; import '../../core/auth/auth_state.dart';
import '../../core/utils/print_util.dart';
import '../../core/utils/date_util.dart'; import '../../core/utils/date_util.dart';
import '../../widgets/order_print_preview_dialog.dart';
import '../../models/stock_in.dart'; import '../../models/stock_in.dart';
import '../../core/config/app_constants.dart'; import '../../core/config/app_constants.dart';
import '../../providers/partner_provider.dart'; import '../../providers/partner_provider.dart';
@@ -227,7 +227,7 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
Future<void> _printOrder() async { Future<void> _printOrder() async {
if (_loadedOrder == null) return; if (_loadedOrder == null) return;
await safePrint(context, () => printStockInOrder(_loadedOrder!)); await showStockInOrderPrint(context, ref, _loadedOrder!);
} }
Future<void> _submit(bool asDraft) async { Future<void> _submit(bool asDraft) async {
@@ -1,8 +1,9 @@
import '../../core/utils/dialog_util.dart'; import '../../core/utils/dialog_util.dart';
import '../../core/errors/error_reporter.dart'; import '../../core/errors/error_reporter.dart';
import '../../core/exceptions.dart'; import '../../core/exceptions.dart';
import '../../core/utils/print_util.dart' show safePrint, printStockInOrder, LabelData; import '../../core/utils/print_util.dart' show LabelData;
import '../../widgets/label_preview_dialog.dart'; import '../../widgets/label_preview_dialog.dart';
import '../../widgets/order_print_preview_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -529,7 +530,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
onPrint: () async { onPrint: () async {
final order = await ref.read(stockInRepositoryProvider).get(o.id); final order = await ref.read(stockInRepositoryProvider).get(o.id);
if (context.mounted) { if (context.mounted) {
await safePrint(context, () => printStockInOrder(order)); await showStockInOrderPrint(context, ref, order);
} }
}, },
afterPrint: [ afterPrint: [
@@ -607,6 +608,8 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
Future<void> _showDetail(BuildContext context, int orderId) async { Future<void> _showDetail(BuildContext context, int orderId) async {
showAppDialog( showAppDialog(
context: context, context: context,
// 用分支 Navigator 承载,全屏弹窗只覆盖内容区,不盖住左侧栏/顶栏/状态栏
useRootNavigator: false,
builder: (ctx) => _StockInDetailDialog( builder: (ctx) => _StockInDetailDialog(
orderId: orderId, orderId: orderId,
repository: ref.read(stockInRepositoryProvider), repository: ref.read(stockInRepositoryProvider),
@@ -1051,11 +1054,14 @@ class _StockInDetailDialogState extends ConsumerState<_StockInDetailDialog> {
children: [ children: [
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: const BoxDecoration(color: AppTheme.primary), decoration: const BoxDecoration(
color: AppTheme.brand50,
border: Border(bottom: BorderSide(color: AppTheme.border)),
),
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: const Icon(Icons.arrow_back, color: AppTheme.primaryDark),
tooltip: '返回', tooltip: '返回',
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
@@ -1063,13 +1069,13 @@ class _StockInDetailDialogState extends ConsumerState<_StockInDetailDialog> {
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white)), color: AppTheme.primaryDark)),
const Spacer(), const Spacer(),
if (_loadedOrder != null) if (_loadedOrder != null)
IconButton( IconButton(
icon: const Icon(Icons.print_outlined, color: Colors.white), icon: const Icon(Icons.print_outlined, color: AppTheme.primaryDark),
tooltip: '打印', tooltip: '打印',
onPressed: () => safePrint(context, () => printStockInOrder(_loadedOrder!)), onPressed: () => showStockInOrderPrint(context, ref, _loadedOrder!),
), ),
], ],
), ),
@@ -7,8 +7,8 @@ import 'package:go_router/go_router.dart';
import '../../core/responsive/responsive.dart'; import '../../core/responsive/responsive.dart';
import '../../core/theme/app_theme.dart'; import '../../core/theme/app_theme.dart';
import '../../core/auth/auth_state.dart'; import '../../core/auth/auth_state.dart';
import '../../core/utils/print_util.dart';
import '../../core/utils/date_util.dart'; import '../../core/utils/date_util.dart';
import '../../widgets/order_print_preview_dialog.dart';
import '../../models/inventory.dart'; import '../../models/inventory.dart';
import '../../models/stock_out.dart'; import '../../models/stock_out.dart';
import '../../widgets/date_picker_field.dart'; import '../../widgets/date_picker_field.dart';
@@ -258,7 +258,7 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
Future<void> _printOrder() async { Future<void> _printOrder() async {
if (_loadedOrder == null) return; if (_loadedOrder == null) return;
await safePrint(context, () => printStockOutOrder(_loadedOrder!)); await showStockOutOrderPrint(context, ref, _loadedOrder!);
} }
Future<void> _submit(bool asDraft) async { Future<void> _submit(bool asDraft) async {
@@ -1,6 +1,6 @@
import '../../core/utils/dialog_util.dart'; import '../../core/utils/dialog_util.dart';
import '../../core/errors/error_reporter.dart'; import '../../core/errors/error_reporter.dart';
import '../../core/utils/print_util.dart' show safePrint, printStockOutOrder; import '../../widgets/order_print_preview_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -17,7 +17,6 @@ import '../../widgets/page_scaffold.dart';
import '../../widgets/status_badge.dart'; import '../../widgets/status_badge.dart';
import '../../widgets/search_chip.dart'; import '../../widgets/search_chip.dart';
import '../../core/utils/export_util.dart'; import '../../core/utils/export_util.dart';
import '../../core/utils/print_util.dart';
import '../../providers/inventory_provider.dart'; import '../../providers/inventory_provider.dart';
import '../../providers/tab_state_provider.dart'; import '../../providers/tab_state_provider.dart';
import '../../providers/product_provider.dart'; import '../../providers/product_provider.dart';
@@ -559,7 +558,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
onPrint: () async { onPrint: () async {
final order = await ref.read(stockOutRepositoryProvider).get(o.id); final order = await ref.read(stockOutRepositoryProvider).get(o.id);
if (context.mounted) { if (context.mounted) {
await safePrint(context, () => printStockOutOrder(order)); await showStockOutOrderPrint(context, ref, order);
} }
}, },
onSettle: () => _confirmSettle(context, o.id, 'stock_out'), onSettle: () => _confirmSettle(context, o.id, 'stock_out'),
@@ -607,6 +606,8 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
Future<void> _showDetail(BuildContext context, int orderId) async { Future<void> _showDetail(BuildContext context, int orderId) async {
showAppDialog( showAppDialog(
context: context, context: context,
// 用分支 Navigator 承载,全屏弹窗只覆盖内容区,不盖住左侧栏/顶栏/状态栏
useRootNavigator: false,
builder: (ctx) => _StockOutDetailDialog( builder: (ctx) => _StockOutDetailDialog(
orderId: orderId, orderId: orderId,
repository: ref.read(stockOutRepositoryProvider), repository: ref.read(stockOutRepositoryProvider),
@@ -964,11 +965,14 @@ class _StockOutDetailDialogState extends ConsumerState<_StockOutDetailDialog> {
children: [ children: [
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: const BoxDecoration(color: AppTheme.primary), decoration: const BoxDecoration(
color: AppTheme.brand50,
border: Border(bottom: BorderSide(color: AppTheme.border)),
),
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: const Icon(Icons.arrow_back, color: AppTheme.primaryDark),
tooltip: '返回', tooltip: '返回',
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
@@ -976,13 +980,13 @@ class _StockOutDetailDialogState extends ConsumerState<_StockOutDetailDialog> {
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white)), color: AppTheme.primaryDark)),
const Spacer(), const Spacer(),
if (_loadedOrder != null) if (_loadedOrder != null)
IconButton( IconButton(
icon: const Icon(Icons.print_outlined, color: Colors.white), icon: const Icon(Icons.print_outlined, color: AppTheme.primaryDark),
tooltip: '打印', tooltip: '打印',
onPressed: () => safePrint(context, () => printStockOutOrder(_loadedOrder!)), onPressed: () => showStockOutOrderPrint(context, ref, _loadedOrder!),
), ),
], ],
), ),
@@ -0,0 +1,282 @@
import 'dart:typed_data';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:printing/printing.dart';
import '../core/auth/auth_state.dart';
import '../core/errors/error_reporter.dart';
import '../core/theme/app_theme.dart';
import '../core/responsive/responsive.dart';
import '../core/utils/print_util.dart';
import '../models/shop.dart';
import '../models/stock_in.dart';
import '../models/stock_out.dart';
import '../providers/shop_provider.dart';
/// 单据打印预览弹窗:把待打印的 A4 PDF 光栅化成图片(所见即所得)逐页展示,
/// 页内点「打印」再调系统打印。解决部分 Windows 系统打印对话框无预览的问题。
///
/// 仅桌面端使用;Web 端走浏览器自带的打印预览(见 [showStockInOrderPrint])。
class OrderPrintPreviewDialog extends StatefulWidget {
/// 标题,如「入库单打印预览」。
final String title;
/// 系统打印任务名(也作另存默认文件名前缀)。
final String printName;
/// 惰性生成 PDF 字节。
final Future<Uint8List> Function() pdfBuilder;
const OrderPrintPreviewDialog({
super.key,
required this.title,
required this.printName,
required this.pdfBuilder,
});
@override
State<OrderPrintPreviewDialog> createState() =>
_OrderPrintPreviewDialogState();
}
class _OrderPrintPreviewDialogState extends State<OrderPrintPreviewDialog> {
Uint8List? _pdfBytes;
final List<Uint8List> _pages = [];
bool _rendering = true;
bool _printing = false;
String _error = '';
@override
void initState() {
super.initState();
_render();
}
Future<void> _render() async {
try {
final bytes = await widget.pdfBuilder();
if (!mounted) return;
setState(() => _pdfBytes = bytes);
// 逐页光栅化(progressive),DPI 取 110 兼顾清晰与速度。
await for (final raster in Printing.raster(bytes, dpi: 110)) {
final png = await raster.toPng();
if (!mounted) return;
setState(() => _pages.add(png));
}
} catch (e, st) {
reportError(e, st);
if (mounted) setState(() => _error = '预览生成失败:$e');
} finally {
if (mounted) setState(() => _rendering = false);
}
}
Future<void> _doPrint() async {
final bytes = _pdfBytes;
if (bytes == null || _printing) return;
setState(() => _printing = true);
try {
await Printing.layoutPdf(
name: '${widget.printName}_${_ts()}',
dynamicLayout: false,
onLayout: (_) async => bytes,
);
if (mounted) Navigator.of(context).pop();
} catch (e, st) {
reportError(e, st);
if (mounted) {
setState(() {
_printing = false;
_error = '打印失败,请检查打印机连接和驱动是否正常。\n详情:$e';
});
}
}
}
String _ts() {
final n = DateTime.now();
String p(int v) => v.toString().padLeft(2, '0');
return '${n.year}${p(n.month)}${p(n.day)}_${p(n.hour)}${p(n.minute)}${p(n.second)}';
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container(
width: context.dialogWidth(720),
constraints: const BoxConstraints(maxHeight: 720),
child: Column(
children: [
// ── 顶栏 ──────────────────────────────────────────────────────────
Container(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: const BoxDecoration(
color: AppTheme.primary,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
child: Row(
children: [
Text(widget.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white)),
const Spacer(),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed:
_printing ? null : () => Navigator.of(context).pop(),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
// ── 预览区 ─────────────────────────────────────────────────────────
Expanded(
child: Container(
width: double.infinity,
color: const Color(0xFFE9ECF1),
child: _buildBody(),
),
),
// ── 底栏 ──────────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 14),
child: Row(
children: [
Expanded(
child: Text(
_error.isNotEmpty
? _error
: (_rendering
? '正在生成预览…'
: '${_pages.length}'),
style: TextStyle(
fontSize: 12,
color: _error.isNotEmpty
? Colors.red
: AppTheme.textSecondary),
),
),
TextButton(
onPressed:
_printing ? null : () => Navigator.of(context).pop(),
child: const Text('取消'),
),
const SizedBox(width: 8),
FilledButton.icon(
icon: const Icon(Icons.print, size: 18),
onPressed: (_pdfBytes == null || _printing)
? null
: _doPrint,
label: Text(_printing ? '打印中…' : '打印'),
),
],
),
),
],
),
),
);
}
Widget _buildBody() {
if (_pages.isEmpty) {
if (_error.isNotEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(_error,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red, fontSize: 13)),
),
);
}
return const Center(child: CircularProgressIndicator());
}
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
itemCount: _pages.length,
separatorBuilder: (_, __) => const SizedBox(height: 16),
itemBuilder: (_, i) => Center(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.18),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Image.memory(_pages[i], fit: BoxFit.contain),
),
),
);
}
}
/// 组装单据打印抬头信息:公司名称(门店)+ 制单人(当前登录、点击打印的人)。
Future<OrderPrintMeta> _buildPrintMeta(WidgetRef ref) async {
ShopInfo? shop;
try {
shop = await ref.read(shopInfoProvider.future);
} catch (_) {
// 门店信息拉取失败(离线等)时抬头留空,不阻塞打印。
}
return OrderPrintMeta(
shopName: shop?.name ?? '',
shopAddress: shop?.address ?? '',
shopPhone: shop?.phone ?? '',
makerName: ref.read(authStateProvider).user?.realName ?? '',
);
}
/// 打印入库单:桌面端先弹应用内预览,Web 端走浏览器自带打印预览。
Future<void> showStockInOrderPrint(
BuildContext context, WidgetRef ref, StockInOrder order) async {
final meta = await _buildPrintMeta(ref);
if (!context.mounted) return;
if (kIsWeb) {
await safePrint(context, () => printStockInOrder(order, meta));
return;
}
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => OrderPrintPreviewDialog(
title: '入库单打印预览',
printName: '入库单',
pdfBuilder: () => buildStockInOrderPdf(order, meta),
),
);
}
/// 打印出库单:桌面端先弹应用内预览,Web 端走浏览器自带打印预览。
Future<void> showStockOutOrderPrint(
BuildContext context, WidgetRef ref, StockOutOrder order) async {
final meta = await _buildPrintMeta(ref);
if (!context.mounted) return;
if (kIsWeb) {
await safePrint(context, () => printStockOutOrder(order, meta));
return;
}
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => OrderPrintPreviewDialog(
title: '出库单打印预览',
printName: '出库单',
pdfBuilder: () => buildStockOutOrderPdf(order, meta),
),
);
}
+281
View File
@@ -0,0 +1,281 @@
<!DOCTYPE html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>出入库单排版方案 · 6 种黑白样式</title>
<style>
* { box-sizing:border-box; margin:0; padding:0; }
body { background:#e9ebee; font-family:"PingFang SC","Microsoft YaHei",sans-serif; color:#111; padding-bottom:60px; }
.page-head { position:sticky; top:0; z-index:10; background:#fff; border-bottom:1px solid #ccc; padding:14px 24px; display:flex; align-items:center; gap:24px; flex-wrap:wrap; }
.page-head h1 { font-size:17px; font-weight:700; }
.page-head nav a { color:#111; text-decoration:none; font-size:13px; margin-right:14px; border-bottom:1px dashed #999; padding-bottom:1px; }
.style-block { max-width:900px; margin:34px auto; }
.style-block > h2 { font-size:16px; margin:0 12px 4px; }
.style-block > .desc { font-size:12.5px; color:#555; margin:0 12px 16px; line-height:1.6; }
.sheet { background:#fff; box-shadow:0 2px 12px rgba(0,0,0,.16); padding:46px 40px; margin-bottom:26px; }
.doc table { width:100%; border-collapse:collapse; }
.doc th { text-align:center; white-space:nowrap; }
.doc td.num { text-align:right; }
.doc td.ctr { text-align:center; }
.doc .meta-row { display:flex; }
.doc .total { display:flex; }
.doc .sign { display:flex; justify-content:space-between; margin-top:40px; font-size:13px; }
.doc .tips { font-size:11px; color:#555; line-height:1.6; border:1px solid #ddd; background:#fafafa; padding:7px 10px; margin-top:12px; }
/* 一 极简线框 */
.s1 .doc { color:#1a1a1a; }
.s1 .hd { display:flex; align-items:baseline; gap:14px; border-bottom:2px solid #111; padding-bottom:12px; margin-bottom:16px; }
.s1 .co { font-size:23px; font-weight:700; letter-spacing:1px; }
.s1 .ty { font-size:14px; color:#666; letter-spacing:6px; }
.s1 .meta { margin-bottom:16px; }
.s1 .meta-row { justify-content:flex-start; gap:52px; font-size:12.5px; color:#333; line-height:2.1; }
.s1 .m-no { font-weight:700; color:#111; }
.s1 table { border:none; }
.s1 th { border:none; border-bottom:2px solid #111; padding:8px 6px; font-size:12px; font-weight:700; letter-spacing:.5px; }
.s1 td { border:none; border-bottom:1px solid #e2e2e2; padding:9px 6px; font-size:12px; }
.s1 .total { border-top:2px solid #111; margin-top:-1px; justify-content:flex-end; gap:44px; padding:12px 4px; font-size:13px; }
.s1 .t-label { margin-right:auto; color:#666; font-weight:700; }
.s1 .total b { font-size:15px; font-weight:700; }
.s1 .sign { margin-top:48px; }
.s1 .tips { border:none; border-left:3px solid #111; background:none; padding:2px 0 2px 10px; }
/* 二 现代分区 */
.s2 .hd { text-align:center; margin-bottom:14px; }
.s2 .co { font-size:24px; font-weight:800; }
.s2 .ty { display:inline-block; margin-top:8px; font-size:14px; font-weight:700; letter-spacing:6px; border:1.5px solid #111; border-radius:2px; padding:3px 18px; }
.s2 .meta { display:grid; grid-template-columns:repeat(3,1fr); border:1px solid #bbb; margin-bottom:14px; }
.s2 .meta-row { display:contents; }
.s2 .m { border:.5px solid #ccc; padding:8px 10px; font-size:12.5px; }
.s2 table { border:1px solid #ccc; }
.s2 th { background:#ececec; border:1px solid #ccc; padding:8px 6px; font-size:12px; font-weight:700; }
.s2 td { border:1px solid #ddd; padding:7px 8px; font-size:12px; }
.s2 tbody tr:nth-child(even) { background:#f7f7f7; }
.s2 .total { margin-top:12px; justify-content:flex-end; align-items:center; gap:36px; background:#efefef; border:1px solid #bbb; border-radius:2px; padding:12px 18px; }
.s2 .t-label { margin-right:auto; font-weight:800; font-size:14px; }
.s2 .total b { font-size:17px; font-weight:800; }
/* 三 紧凑报表 */
.s3 .doc { border-left:4px solid #111; padding-left:18px; }
.s3 .hd { display:flex; align-items:baseline; gap:12px; margin-bottom:10px; }
.s3 .co { font-size:19px; font-weight:700; }
.s3 .ty { font-size:13px; letter-spacing:4px; color:#444; }
.s3 .meta { background:#f3f3f3; border:1px solid #ccc; padding:7px 10px; margin-bottom:10px; }
.s3 .meta-row { justify-content:space-between; font-size:11.5px; line-height:1.9; }
.s3 .m-no { font-weight:700; }
.s3 table { border:1px solid #bbb; }
.s3 th { background:#e5e5e5; border:1px solid #bbb; padding:5px 5px; font-size:11px; font-weight:700; }
.s3 td { border:1px solid #ccc; padding:4px 6px; font-size:11px; }
.s3 td.num { font-family:"SF Mono","Roboto Mono",Menlo,Consolas,monospace; }
.s3 .total { justify-content:flex-end; gap:28px; margin-top:8px; border:1px solid #bbb; background:#f3f3f3; padding:7px 12px; font-size:12px; font-weight:700; }
.s3 .t-label { margin-right:auto; }
.s3 .total b { font-size:14px; font-family:"SF Mono",Menlo,Consolas,monospace; }
.s3 .sign { margin-top:30px; font-size:12px; }
/* 四 西式发票 */
.s4 .topbar { display:flex; justify-content:space-between; align-items:flex-start; border-bottom:3px solid #111; padding-bottom:14px; margin-bottom:16px; }
.s4 .co { font-size:30px; font-weight:800; letter-spacing:1px; }
.s4 .docmeta { text-align:right; }
.s4 .doctype { font-size:18px; font-weight:700; letter-spacing:6px; margin-bottom:8px; }
.s4 .mbox { font-size:12px; line-height:1.8; color:#333; }
.s4 .party { margin-bottom:14px; }
.s4 .plabel { font-size:10.5px; letter-spacing:3px; color:#888; text-transform:uppercase; margin-bottom:5px; }
.s4 .pgrid { display:flex; gap:40px; font-size:13px; }
.s4 .seclabel { font-size:11px; letter-spacing:3px; color:#111; font-weight:700; border-bottom:1px solid #111; padding-bottom:5px; text-transform:uppercase; }
.s4 th { border-bottom:1.5px solid #111; padding:8px 6px; font-size:11.5px; font-weight:700; }
.s4 td { border-bottom:1px solid #e0e0e0; padding:8px 6px; font-size:12px; }
.s4 .grand { display:flex; flex-direction:column; align-items:flex-end; gap:4px; margin-top:14px; }
.s4 .grand > div { display:flex; gap:18px; min-width:230px; justify-content:space-between; font-size:13px; }
.s4 .grand .g1 { justify-content:flex-end; color:#888; letter-spacing:2px; }
.s4 .grand .g3 { border-top:2px solid #111; padding-top:6px; margin-top:2px; font-weight:800; }
.s4 .grand b { font-size:15px; } .s4 .grand .g3 b { font-size:19px; }
.s4 .sign { border-top:1px solid #ccc; padding-top:14px; }
/* 五 瑞士网格 */
.s5 .kicker { font-size:12px; letter-spacing:4px; text-transform:uppercase; color:#888; }
.s5 .headline { font-size:46px; font-weight:800; letter-spacing:4px; line-height:1; margin:4px 0 16px; }
.s5 .metaline { display:flex; gap:42px; flex-wrap:wrap; border-top:1px solid #111; border-bottom:1px solid #111; padding:10px 0; font-size:12.5px; margin-bottom:22px; }
.s5 .metaline i { display:block; font-style:normal; font-size:10px; letter-spacing:2px; text-transform:uppercase; color:#999; margin-bottom:3px; }
.s5 .sec { font-size:10.5px; letter-spacing:3px; text-transform:uppercase; color:#666; margin-bottom:6px; }
.s5 th { border-top:2px solid #111; border-bottom:2px solid #111; padding:8px 6px; font-size:11px; letter-spacing:.5px; font-weight:700; }
.s5 td { border-bottom:1px solid #e4e4e4; padding:9px 6px; font-size:12px; }
.s5 td.num { font-variant-numeric:tabular-nums; font-feature-settings:"tnum"; }
.s5 .total8 { display:flex; justify-content:flex-end; align-items:baseline; gap:14px; border-bottom:3px double #111; padding:12px 2px; margin-top:-1px; }
.s5 .total8 > span { margin-right:auto; font-size:11px; letter-spacing:3px; text-transform:uppercase; color:#666; }
.s5 .total8 em { font-style:normal; font-size:11px; color:#999; letter-spacing:2px; }
.s5 .total8 b { font-size:18px; font-weight:800; }
.s5 .sign { margin-top:46px; }
/* 六 填空表单 */
.s6 .frame { border:1px solid #111; outline:3px solid #111; outline-offset:3px; padding:18px 18px 16px; }
.s6 .co9 { text-align:center; font-size:14px; letter-spacing:2px; color:#333; }
.s6 .title9 { text-align:center; font-size:22px; font-weight:800; letter-spacing:10px; margin:2px 0 14px; }
.s6 .meta9 { display:grid; grid-template-columns:repeat(3,1fr); gap:6px 24px; margin-bottom:12px; }
.s6 .f { display:flex; align-items:baseline; font-size:12.5px; }
.s6 .f label { color:#333; white-space:nowrap; } .s6 .f label::after { content:""; }
.s6 .f .fill { flex:1; border-bottom:1px solid #111; min-height:18px; padding:0 4px; font-weight:600; }
.s6 table, .s6 th, .s6 td { border:1px solid #111; }
.s6 th { background:#eee; padding:6px; font-size:11.5px; font-weight:700; }
.s6 td { padding:6px 7px; font-size:11.5px; }
.s6 .total9 { display:flex; border:1px solid #111; border-top:none; padding:8px 12px; font-size:13px; font-weight:700; }
.s6 .total9 span:first-child { margin-right:auto; }
.s6 .total9 span+span { margin-left:36px; }
.s6 .sign9 { display:flex; margin-top:16px; gap:10px; }
.s6 .sign9 div { flex:1; border:1px solid #111; padding:14px 10px 8px; font-size:12.5px; }
@media print {
@page { size:A4; margin:10mm; }
body { background:#fff; padding:0; }
.page-head { display:none; }
.style-block { max-width:none; margin:0; break-before:page; }
.style-block:first-of-type { break-before:auto; }
.style-block > h2 { font-size:13px; margin:0 0 2px; }
.style-block > .desc { font-size:10px; color:#555; margin:0 0 8px; }
.sheet { box-shadow:none; border:1px solid #e3e3e3; padding:18px 20px; margin-bottom:10px; break-inside:avoid; }
}
</style></head><body>
<header class="page-head"><h1>出入库单排版方案 · 6 种黑白样式</h1><nav><a href="#s1">极简线框</a><a href="#s2">现代分区</a><a href="#s3">紧凑报表</a><a href="#s4">西式发票</a><a href="#s5">瑞士网格</a><a href="#s6">填空表单</a></nav></header>
<section id="s1" class="style-block">
<h2>样式一 · 极简线框</h2><p class="desc">无竖线、仅细横线分隔,大留白、左对齐抬头 —— 清爽现代,激光打印省墨。</p>
<div class="sheet s1"><div class="doc">
<div class="hd"><div class="co">鼎晟酒行</div><div class="ty">入 库 单</div></div>
<div class="meta">
<div class="meta-row r1"><span class="m m-partner">单位名称:丁小峰</span><span class="m m-date">入库日期:2026-06-23</span><span class="m m-no">NORK20260623000013</span></div>
<div class="meta-row r2"><span class="m m-phone">联系电话:</span><span class="m m-addr">单位地址:</span><span class="m m-wh">仓库:鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1328</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719005920</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1329</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719007000</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr></tbody></table>
<div class="total"><span class="t-label">单据总计</span><span class="t-qty">数量 <b>24</b></span><span class="t-amt">金额 <b>44160.00</b></span></div>
<div class="sign"><span>出货人(签字):</span><span>采购员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
<div class="sheet s1"><div class="doc">
<div class="hd"><div class="co">鼎晟酒行</div><div class="ty">出 库 单</div></div>
<div class="meta">
<div class="meta-row r1"><span class="m m-partner">客户名称:刘总</span><span class="m m-date">出库日期:2026-06-23</span><span class="m m-no">NO.CK20260623000020</span></div>
<div class="meta-row r2"><span class="m m-phone">联系电话:</span><span class="m m-addr">送货地址:</span><span class="m m-wh">仓库:鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr></tbody></table>
<div class="total"><span class="t-label">单据总计</span><span class="t-qty">数量 <b>2</b></span><span class="t-amt">金额 <b>6160.00</b></span></div>
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div><div class="sign"><span>收货人(签字):</span><span>销售员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
</section>
<section id="s2" class="style-block">
<h2>样式二 · 现代分区</h2><p class="desc">抬头字段做成带框表单格、明细斑马纹、总计高亮条 —— 信息分区清晰。</p>
<div class="sheet s2"><div class="doc">
<div class="hd"><div class="co">鼎晟酒行</div><div class="ty">入 库 单</div></div>
<div class="meta">
<div class="meta-row r1"><span class="m m-partner">单位名称:丁小峰</span><span class="m m-date">入库日期:2026-06-23</span><span class="m m-no">NORK20260623000013</span></div>
<div class="meta-row r2"><span class="m m-phone">联系电话:</span><span class="m m-addr">单位地址:</span><span class="m m-wh">仓库:鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1328</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719005920</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1329</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719007000</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr></tbody></table>
<div class="total"><span class="t-label">单据总计</span><span class="t-qty">数量 <b>24</b></span><span class="t-amt">金额 <b>44160.00</b></span></div>
<div class="sign"><span>出货人(签字):</span><span>采购员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
<div class="sheet s2"><div class="doc">
<div class="hd"><div class="co">鼎晟酒行</div><div class="ty">出 库 单</div></div>
<div class="meta">
<div class="meta-row r1"><span class="m m-partner">客户名称:刘总</span><span class="m m-date">出库日期:2026-06-23</span><span class="m m-no">NO.CK20260623000020</span></div>
<div class="meta-row r2"><span class="m m-phone">联系电话:</span><span class="m m-addr">送货地址:</span><span class="m m-wh">仓库:鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr></tbody></table>
<div class="total"><span class="t-label">单据总计</span><span class="t-qty">数量 <b>2</b></span><span class="t-amt">金额 <b>6160.00</b></span></div>
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div><div class="sign"><span>收货人(签字):</span><span>销售员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
</section>
<section id="s3" class="style-block">
<h2>样式三 · 紧凑报表</h2><p class="desc">小字号、数字等宽对齐、左侧竖线点缀 —— 单页能塞更多行,密集报表风。</p>
<div class="sheet s3"><div class="doc">
<div class="hd"><div class="co">鼎晟酒行</div><div class="ty">入 库 单</div></div>
<div class="meta">
<div class="meta-row r1"><span class="m m-partner">单位名称:丁小峰</span><span class="m m-date">入库日期:2026-06-23</span><span class="m m-no">NORK20260623000013</span></div>
<div class="meta-row r2"><span class="m m-phone">联系电话:</span><span class="m m-addr">单位地址:</span><span class="m m-wh">仓库:鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1328</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719005920</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1329</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719007000</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr></tbody></table>
<div class="total"><span class="t-label">单据总计</span><span class="t-qty">数量 <b>24</b></span><span class="t-amt">金额 <b>44160.00</b></span></div>
<div class="sign"><span>出货人(签字):</span><span>采购员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
<div class="sheet s3"><div class="doc">
<div class="hd"><div class="co">鼎晟酒行</div><div class="ty">出 库 单</div></div>
<div class="meta">
<div class="meta-row r1"><span class="m m-partner">客户名称:刘总</span><span class="m m-date">出库日期:2026-06-23</span><span class="m m-no">NO.CK20260623000020</span></div>
<div class="meta-row r2"><span class="m m-phone">联系电话:</span><span class="m m-addr">送货地址:</span><span class="m m-wh">仓库:鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr></tbody></table>
<div class="total"><span class="t-label">单据总计</span><span class="t-qty">数量 <b>2</b></span><span class="t-amt">金额 <b>6160.00</b></span></div>
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div><div class="sign"><span>收货人(签字):</span><span>销售员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
</section>
<section id="s4" class="style-block">
<h2>样式四 · 西式发票</h2><p class="desc">左上大字号商号 + 右上单号块、底部「单据总计」金额栈、字母大写小标题 —— 借鉴欧美 INVOICE。</p>
<div class="sheet s4"><div class="doc">
<div class="topbar"><div class="co">鼎晟酒行</div>
<div class="docmeta"><div class="doctype">入 库 单</div>
<div class="mbox"><div>NORK20260623000013</div><div>入库日期:2026-06-23</div><div>仓库:鼎晟酒行</div></div></div></div>
<div class="party"><div class="plabel">往来信息</div>
<div class="pgrid"><span>单位名称:丁小峰</span><span>联系电话:</span><span>单位地址:</span></div></div>
<div class="seclabel">商品明细 / ITEMS</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1328</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719005920</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1329</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719007000</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr></tbody></table>
<div class="grand"><div class="g1">单据总计</div>
<div class="g2"><span>数量</span><b>24</b></div>
<div class="g3"><span>金额</span><b>44160.00</b></div></div>
<div class="sign"><span>出货人(签字):</span><span>采购员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
<div class="sheet s4"><div class="doc">
<div class="topbar"><div class="co">鼎晟酒行</div>
<div class="docmeta"><div class="doctype">出 库 单</div>
<div class="mbox"><div>NO.CK20260623000020</div><div>出库日期:2026-06-23</div><div>仓库:鼎晟酒行</div></div></div></div>
<div class="party"><div class="plabel">往来信息</div>
<div class="pgrid"><span>客户名称:刘总</span><span>联系电话:</span><span>送货地址:</span></div></div>
<div class="seclabel">商品明细 / ITEMS</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr></tbody></table>
<div class="grand"><div class="g1">单据总计</div>
<div class="g2"><span>数量</span><b>2</b></div>
<div class="g3"><span>金额</span><b>6160.00</b></div></div>
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div><div class="sign"><span>收货人(签字):</span><span>销售员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
</section>
<section id="s5" class="style-block">
<h2>样式五 · 瑞士网格</h2><p class="desc">超大标题 + 细基线网格、小标签等宽数字、表格仅上下粗线 —— 杂志/瑞士排版。</p>
<div class="sheet s5"><div class="doc">
<div class="kicker">鼎晟酒行</div>
<h1 class="headline">入 库 单</h1>
<div class="metaline"><span><i>单号</i>RK20260623000013</span><span><i>入库日期</i>2026-06-23</span><span><i>单位名称</i>丁小峰</span><span><i>仓库</i>鼎晟酒行</span></div>
<div class="sec">商品明细</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1328</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719005920</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1329</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719007000</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr></tbody></table>
<div class="total8"><span>单据总计</span><em>数量</em><b>24</b><em>金额</em><b>44160.00</b></div>
<div class="sign"><span>出货人(签字):</span><span>采购员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
<div class="sheet s5"><div class="doc">
<div class="kicker">鼎晟酒行</div>
<h1 class="headline">出 库 单</h1>
<div class="metaline"><span><i>单号</i>CK20260623000020</span><span><i>出库日期</i>2026-06-23</span><span><i>客户名称</i>刘总</span><span><i>仓库</i>鼎晟酒行</span></div>
<div class="sec">商品明细</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr></tbody></table>
<div class="total8"><span>单据总计</span><em>数量</em><b>2</b><em>金额</em><b>6160.00</b></div>
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div><div class="sign"><span>收货人(签字):</span><span>销售员:汤宇飞</span><span>制单人:汤宇飞</span></div></div></div>
</section>
<section id="s6" class="style-block">
<h2>样式六 · 填空表单</h2><p class="desc">双线外框、字段下划线填空、底部多签字格 —— 仿手工三联单/预印单据,正式可手填。</p>
<div class="sheet s6"><div class="doc form9"><div class="frame">
<div class="co9">鼎晟酒行</div>
<div class="title9">入 库 单</div>
<div class="meta9">
<div class="f"><label>单位名称</label><span class="fill">丁小峰</span></div>
<div class="f"><label>入库日期</label><span class="fill">2026-06-23</span></div>
<div class="f"><label>单据编号</label><span class="fill">RK20260623000013</span></div>
<div class="f"><label>联系电话</label><span class="fill"></span></div>
<div class="f"><label>单位地址</label><span class="fill"></span></div>
<div class="f"><label>仓库</label><span class="fill">鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1328</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719005920</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1329</td><td class="">茅台1935原箱2026</td><td class="ctr">普通/53度</td><td class="ctr">500ml*6/件</td><td class="ctr">8719007000</td><td class="ctr">2026-02-28</td><td class="num">6</td><td class="num">600.00</td><td class="num">3600.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">6</td><td class="num">3080.00</td><td class="num">18480.00</td><td class=""></td></tr></tbody></table>
<div class="total9"><span>单据总计</span><span>数量 24</span><span>金额 44160.00</span></div>
<div class="sign9"><div>出货人(签字):</div><div>采购员:汤宇飞</div><div>制单人:汤宇飞</div></div>
</div></div></div>
<div class="sheet s6"><div class="doc form9"><div class="frame">
<div class="co9">鼎晟酒行</div>
<div class="title9">出 库 单</div>
<div class="meta9">
<div class="f"><label>客户名称</label><span class="fill">刘总</span></div>
<div class="f"><label>出库日期</label><span class="fill">2026-06-23</span></div>
<div class="f"><label>单据编号</label><span class="fill">CK20260623000020</span></div>
<div class="f"><label>联系电话</label><span class="fill"></span></div>
<div class="f"><label>送货地址</label><span class="fill"></span></div>
<div class="f"><label>仓库</label><span class="fill">鼎晟酒行</span></div>
</div>
<table><thead><tr><th>商品编号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次号</th><th>生产日期</th><th>数量</th><th>单价</th><th>金额</th><th>备注</th></tr></thead><tbody><tr><td class="">P1331</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-126/009192582386</td><td class="ctr">2026-03-13</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr><tr><td class="">P1330</td><td class="">茅台公斤2026原箱</td><td class="ctr">普通/53度</td><td class="ctr">1.00L*6/件</td><td class="ctr">2025-184/248896472708</td><td class="ctr">2026-06-01</td><td class="num">1</td><td class="num">3080.00</td><td class="num">3080.00</td><td class=""></td></tr></tbody></table>
<div class="total9"><span>单据总计</span><span>数量 2</span><span>金额 6160.00</span></div>
<div class="tips">温馨提示:签收时,请务必核对好酒品数量、年份和日期、批次及物流码,如有问题及时反馈,酒品无质量问题一经售出概不退换,谢谢合作。</div>
<div class="sign9"><div>收货人(签字):</div><div>销售员:汤宇飞</div><div>制单人:汤宇飞</div></div>
</div></div></div>
</section>
</body></html>
Binary file not shown.
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文档索引 — 酒库管理系统</title>
<style>
:root{
--primary:#2563AC; --primary-dark:#154072; --accent:#8B2331;
--ink:#232934; --muted:#6E7888; --border:#DCE2EB; --paper:#F5F7FA; --head:#F0F4FF;
}
*{box-sizing:border-box;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}
body{margin:0;background:var(--paper);color:var(--ink);padding:28px;line-height:1.6;}
h1{font-size:20px;margin:0 0 4px;}
.sub{color:var(--muted);font-size:13px;margin-bottom:20px;}
h2{font-size:15px;margin:24px 0 8px;color:var(--primary-dark);border-bottom:2px solid var(--head);padding-bottom:6px;}
ul{margin:6px 0;padding-left:20px;}
li{margin:5px 0;font-size:13.5px;}
a{color:var(--primary);text-decoration:none;}
a:hover{text-decoration:underline;}
.hint{color:var(--muted);font-size:12px;}
.tag{display:inline-block;font-size:10px;padding:1px 7px;border-radius:9px;background:#EEF2F8;color:var(--muted);margin-left:6px;}
.tag.html{background:#E5EEF9;color:var(--primary);}
</style>
</head>
<body>
<h1>酒库管理系统 · 文档索引</h1>
<div class="sub">项目所有文档的单一入口。新增文档须同时登记到此处。</div>
<h2>设计方案 / 原型</h2>
<ul>
<li><a href="design/order-return-prototype.html">退单原型(已审核单据)</a><span class="tag html">HTML</span></li>
<li><a href="design/order-print-layouts.html">出入库单打印排版方案(6 种黑白样式精选)</a><span class="tag html">HTML</span> · <a href="design/order-print-layouts.pdf">PDF</a></li>
</ul>
<h2>实现计划</h2>
<ul>
<li class="hint">(暂无)</li>
</ul>
<h2>排障 / 运维 Runbook</h2>
<ul>
<li><a href="runbooks/ec2-to-ali-migration.html">EC2 → 阿里云 迁移计划</a><span class="tag html">HTML</span> <span class="hint">— jiu 服务/数据/DB/文件/域名/CI 整体迁移</span></li>
</ul>
<h2>知识库 / 调研 / 测试</h2>
<ul>
<li><a href="testing/license-test-flow.html">授权兑换券测试流程</a><span class="tag html">HTML</span></li>
</ul>
<h2>历史 Markdown 文档(逐步迁移)</h2>
<ul>
<li><a href="deployment-nas-gitea.md">部署:NAS Gitea/Forgejo</a><span class="tag">MD</span></li>
<li><a href="dev-setup.md">开发环境搭建</a><span class="tag">MD</span></li>
<li><a href="android-signing.md">Android 签名</a> · <a href="ios-signing.md">iOS 签名</a> · <a href="macos-signing.md">macOS 签名</a><span class="tag">MD</span></li>
<li><a href="user-manual.md">用户手册</a><span class="tag">MD</span></li>
<li class="hint">另有 api/ architecture/ requirements/ review/ security/ runbooks/ context/ 等目录下的过程文档</li>
</ul>
</body>
</html>