refactor(client): 出入库单打印改用「紧凑报表」版式
- 左侧竖线点缀 + 抬头左对齐内联(公司名 + 单据类型同行) - 抬头信息改为灰底信息条;明细表收紧字号/行距、灰底表头 - 数量/单价/金额/总计改等宽数字(PDF 内置 Courier,Web monospace) - 单据总计改为右对齐灰底条 - 仅排版调整,字段/数据/售价口径/温馨提示逻辑均不变 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -709,174 +709,186 @@ pw.Widget _buildOrderDoc({
|
||||
required double totalQty,
|
||||
required double totalAmt,
|
||||
}) {
|
||||
final cellStyle = pw.TextStyle(font: font, fontSize: 9);
|
||||
// 样式三「紧凑报表」:左侧竖线点缀 + 抬头左对齐内联 + 灰底信息条 + 密集明细表
|
||||
// + 数字等宽(内置 Courier,ASCII 数字适用)+ 灰底总计条。
|
||||
final mono = pw.Font.courier();
|
||||
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: 9.5, fontWeight: pw.FontWeight.bold);
|
||||
final metaStyle = pw.TextStyle(font: font, fontSize: 10);
|
||||
final boldStyle =
|
||||
pw.TextStyle(font: bold, fontSize: 10, fontWeight: pw.FontWeight.bold);
|
||||
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);
|
||||
|
||||
pw.Widget metaText(String label, String value, pw.TextAlign align) =>
|
||||
pw.Text('$label:$value', style: metaStyle, textAlign: align);
|
||||
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// ── 公司名称 ──────────────────────────────────────────────────────────
|
||||
pw.Center(
|
||||
child: pw.Text(shopName,
|
||||
style: pw.TextStyle(
|
||||
font: bold, fontSize: 18, fontWeight: pw.FontWeight.bold)),
|
||||
),
|
||||
pw.SizedBox(height: 4),
|
||||
// ── 单据类型 ──────────────────────────────────────────────────────────
|
||||
pw.Center(
|
||||
child: pw.Text(title,
|
||||
style: pw.TextStyle(
|
||||
font: bold,
|
||||
fontSize: 15,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
letterSpacing: 2)),
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
// ── 抬头信息(两行三列)──────────────────────────────────────────────
|
||||
pw.Container(
|
||||
decoration: const pw.BoxDecoration(
|
||||
border: pw.Border(bottom: pw.BorderSide(color: PdfColors.grey700)),
|
||||
),
|
||||
padding: const pw.EdgeInsets.only(bottom: 5),
|
||||
child: pw.Column(
|
||||
return pw.Container(
|
||||
// 左侧竖线点缀
|
||||
decoration: const pw.BoxDecoration(
|
||||
border: pw.Border(left: pw.BorderSide(color: PdfColors.black, width: 3)),
|
||||
),
|
||||
padding: const pw.EdgeInsets.only(left: 14),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// ── 抬头:公司名 + 单据类型,左对齐内联 ──────────────────────────
|
||||
pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||
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: boldStyle, 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)),
|
||||
],
|
||||
pw.Text(shopName,
|
||||
style: pw.TextStyle(
|
||||
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.Table(
|
||||
border: pw.TableBorder.all(color: PdfColors.grey600, width: 0.5),
|
||||
columnWidths: {
|
||||
for (var i = 0; i < _orderColFlex.length; i++)
|
||||
i: pw.FlexColumnWidth(_orderColFlex[i])
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
decoration: const pw.BoxDecoration(color: PdfColors.grey200),
|
||||
pw.SizedBox(height: 8),
|
||||
// ── 抬头信息:灰底信息条(两行三列)────────────────────────────
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: PdfColors.grey100,
|
||||
border: pw.Border.all(color: PdfColors.grey400, width: 0.5),
|
||||
),
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: pw.Column(
|
||||
children: [
|
||||
for (final h in _orderHeaders)
|
||||
pw.Padding(
|
||||
padding:
|
||||
const pw.EdgeInsets.symmetric(horizontal: 2, vertical: 3),
|
||||
child: pw.Text(h,
|
||||
textAlign: pw.TextAlign.center, style: headStyle),
|
||||
),
|
||||
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)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
for (final row in rows)
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
// ── 明细表(密集、灰底表头、数字等宽)──────────────────────────
|
||||
pw.Table(
|
||||
border: pw.TableBorder.all(color: PdfColors.grey500, width: 0.5),
|
||||
columnWidths: {
|
||||
for (var i = 0; i < _orderColFlex.length; i++)
|
||||
i: pw.FlexColumnWidth(_orderColFlex[i])
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
decoration: const pw.BoxDecoration(color: PdfColors.grey300),
|
||||
children: [
|
||||
for (var i = 0; i < row.length; i++)
|
||||
for (final h in _orderHeaders)
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.symmetric(
|
||||
horizontal: 2, vertical: 2.5),
|
||||
child: pw.Text(row[i],
|
||||
textAlign: _colAlign(i), style: cellStyle),
|
||||
child: pw.Text(h,
|
||||
textAlign: pw.TextAlign.center, style: headStyle),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// ── 单据总计:独立一行,仅数量 + 金额,右对齐高亮 ──────────────────
|
||||
pw.SizedBox(height: 6),
|
||||
pw.Container(
|
||||
width: double.infinity,
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.grey600, width: 0.5),
|
||||
color: PdfColors.grey100,
|
||||
),
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
pw.Text('单据总计',
|
||||
style: pw.TextStyle(
|
||||
font: bold, fontSize: 10.5, fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(width: 30),
|
||||
pw.Text('数量', style: metaStyle),
|
||||
pw.SizedBox(width: 6),
|
||||
pw.Text(_qtyStr(totalQty),
|
||||
style: pw.TextStyle(
|
||||
font: bold, fontSize: 11, fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(width: 30),
|
||||
pw.Text('金额', style: metaStyle),
|
||||
pw.SizedBox(width: 6),
|
||||
pw.Text(totalAmt.toStringAsFixed(2),
|
||||
style: pw.TextStyle(
|
||||
font: bold, fontSize: 12, fontWeight: pw.FontWeight.bold)),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// ── 温馨提示(仅出库单)────────────────────────────────────────────
|
||||
if (showTips) ...[
|
||||
pw.SizedBox(height: 8),
|
||||
// ── 单据总计:灰底条,右对齐,仅数量 + 金额(等宽数字)──────────
|
||||
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),
|
||||
color: PdfColors.grey50,
|
||||
),
|
||||
padding: const pw.EdgeInsets.all(5),
|
||||
child: pw.Text(_kTipsText,
|
||||
style: pw.TextStyle(
|
||||
font: font, fontSize: 8.5, color: PdfColors.grey800)),
|
||||
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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
pw.SizedBox(height: 26),
|
||||
// ── 签字行:签收人 / 经办人 / 制单人 ────────────────────────────────
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text('$signerLabel:', style: metaStyle),
|
||||
pw.Text('$operatorRoleLabel:${operatorName ?? ''}', style: metaStyle),
|
||||
pw.Text('制单人:${makerName ?? ''}', style: metaStyle),
|
||||
// ── 温馨提示(仅出库单)──────────────────────────────────────────
|
||||
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.Text('$signerLabel:', style: metaStyle),
|
||||
pw.Text('$operatorRoleLabel:${operatorName ?? ''}',
|
||||
style: metaStyle),
|
||||
pw.Text('制单人:${makerName ?? ''}', style: metaStyle),
|
||||
],
|
||||
),
|
||||
if ((remark ?? '').isNotEmpty) ...[
|
||||
pw.SizedBox(height: 10),
|
||||
pw.Text('备注:$remark', style: metaStyle),
|
||||
],
|
||||
),
|
||||
if ((remark ?? '').isNotEmpty) ...[
|
||||
pw.SizedBox(height: 10),
|
||||
pw.Text('备注:$remark', style: metaStyle),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -181,27 +181,29 @@ Future<Uint8List> buildStockOutOrderPdfImpl(
|
||||
StockOutOrder order, OrderPrintMeta meta) async =>
|
||||
throw UnsupportedError('Web 端出库单走 HTML 打印,不生成 PDF');
|
||||
|
||||
/// 单据打印共用样式(入库/出库一致)。
|
||||
/// 单据打印共用样式(样式三「紧凑报表」:左侧竖线 + 抬头左对齐内联 +
|
||||
/// 灰底信息条 + 密集明细表 + 数字等宽 + 灰底总计条;入库/出库一致)。
|
||||
const String _orderPrintCss = '''
|
||||
@page { size: A4; margin: 12mm 15mm; }
|
||||
@page { size: A4; margin: 12mm 14mm; }
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: "PingFang SC", "Microsoft YaHei", "SimSun", sans-serif; font-size: 11px; color: #000; }
|
||||
.shop { text-align: center; font-size: 20px; font-weight: bold; }
|
||||
.title { text-align: center; font-size: 16px; font-weight: bold; letter-spacing: 4px; margin: 4px 0 8px; }
|
||||
.head-block { border-bottom: 1px solid #555; padding-bottom: 5px; margin-bottom: 8px; }
|
||||
.meta-row { display: flex; font-size: 11px; }
|
||||
.meta-row + .meta-row { margin-top: 3px; }
|
||||
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: bold; font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { border: 1px solid #777; padding: 4px 4px; font-size: 10.5px; }
|
||||
th { background: #eee; font-weight: 600; text-align: center; }
|
||||
.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; }
|
||||
.subtotal-bar { display: flex; justify-content: flex-end; align-items: baseline; gap: 30px; border: 1px solid #777; background: #f3f3f3; padding: 7px 10px; margin-top: 6px; font-size: 11px; }
|
||||
.subtotal-bar .lbl-total { font-weight: bold; font-size: 12px; margin-right: auto; }
|
||||
.subtotal-bar b { font-size: 13px; }
|
||||
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: 26px; font-size: 11px; }
|
||||
.sigs { display: flex; justify-content: space-between; margin-top: 30px; font-size: 12px; }
|
||||
.remark { margin-top: 10px; font-size: 11px; color: #333; }
|
||||
''';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user