feat: 结算小票功能完善——分区分组、双二维码、支付类型、克重展示
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e83b0f0697
commit
d7ca18766e
@@ -312,7 +312,15 @@
|
||||
.receipt-table td:nth-child(2),
|
||||
.receipt-table td:nth-child(3),
|
||||
.receipt-table td:nth-child(4) { text-align: right; }
|
||||
.receipt-table td:nth-child(3) { color: #666; }
|
||||
.receipt-table td:nth-child(2) { color: #666; }
|
||||
.receipt-zone-header td {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 6px 0 2px;
|
||||
text-align: left;
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
/* 金额汇总 */
|
||||
.receipt-amount {
|
||||
@@ -328,7 +336,7 @@
|
||||
.receipt-amount__paid {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #0a7c64;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
/* 营养摄入摘要 */
|
||||
@@ -371,37 +379,50 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
background: linear-gradient(90deg, #f0f7ff, #e6f4ff);
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #cce4ff;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.receipt-score__label {
|
||||
font-size: 10px;
|
||||
color: #4a90d9;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
.receipt-score__value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #2570c0;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
/* 二维码 */
|
||||
.receipt-qr {
|
||||
/* 双二维码并排 */
|
||||
.receipt-qr--dual {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.receipt-qr--dual .receipt-qr {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.receipt-qr__img {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
.receipt-qr--dual .receipt-qr__img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
.receipt-qr__text {
|
||||
.receipt-qr--dual .receipt-qr__text {
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
.receipt-qr--dual .receipt-qr__sub {
|
||||
font-size: 8px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 底部提示 */
|
||||
@@ -558,14 +579,15 @@
|
||||
line: 'A 区 1 号餐线',
|
||||
cashier: '张三',
|
||||
time: '2026-08-03 12:15:30',
|
||||
payTime: '2026-08-03 12:15:35',
|
||||
items: [
|
||||
{ name: '宫保鸡丁', price: 18.00, qty: 1, subtotal: 18.00 },
|
||||
{ name: '番茄炒蛋', price: 8.00, qty: 1, subtotal: 8.00 },
|
||||
{ name: '米饭', price: 2.00, qty: 1, subtotal: 2.00 },
|
||||
{ name: '紫菜蛋花汤', price: 5.00, qty: 1, subtotal: 5.00 }
|
||||
{ name: '宫保鸡丁', zone: '档口秤', weight: '200g', price: 18.00, qty: 1, subtotal: 18.00 },
|
||||
{ name: '番茄炒蛋', zone: '营养秤', weight: '150g', price: 8.00, qty: 1, subtotal: 8.00 },
|
||||
{ name: '米饭', zone: '营养秤', weight: '250g', price: 2.00, qty: 1, subtotal: 2.00 },
|
||||
{ name: '紫菜蛋花汤', zone: '档口秤', weight: '300g', price: 5.00, qty: 1, subtotal: 5.00 }
|
||||
],
|
||||
total: 33.00, discount: 3.00, payable: 30.00, paid: 30.00,
|
||||
payMethod: '人脸支付', balance: 1280.50,
|
||||
payType: '会员支付', payMethod: '人脸支付', balance: 1280.50,
|
||||
nutrition: { calories: 685, protein: 28, fat: 18, carb: 95, score: 82 },
|
||||
printed: true
|
||||
},
|
||||
@@ -576,13 +598,14 @@
|
||||
line: 'A 区 1 号餐线',
|
||||
cashier: '张三',
|
||||
time: '2026-08-03 07:42:18',
|
||||
payTime: '2026-08-03 07:42:23',
|
||||
items: [
|
||||
{ name: '豆浆', price: 3.00, qty: 1, subtotal: 3.00 },
|
||||
{ name: '油条', price: 2.50, qty: 2, subtotal: 5.00 },
|
||||
{ name: '茶叶蛋', price: 4.00, qty: 1, subtotal: 4.00 }
|
||||
{ name: '豆浆', zone: '营养秤', weight: '300g', price: 3.00, qty: 1, subtotal: 3.00 },
|
||||
{ name: '油条', zone: '档口秤', weight: '120g', price: 2.50, qty: 2, subtotal: 5.00 },
|
||||
{ name: '茶叶蛋', zone: '营养秤', weight: '60g', price: 4.00, qty: 1, subtotal: 4.00 }
|
||||
],
|
||||
total: 12.00, discount: 0, payable: 12.00, paid: 12.00,
|
||||
payMethod: '餐卡支付', balance: 1292.50,
|
||||
payType: '会员支付', payMethod: '人脸支付', balance: 1292.50,
|
||||
nutrition: { calories: 425, protein: 15, fat: 12, carb: 60, score: 75 },
|
||||
printed: true
|
||||
},
|
||||
@@ -593,15 +616,16 @@
|
||||
line: 'A 区 2 号餐线',
|
||||
cashier: '李四',
|
||||
time: '2026-08-03 12:32:45',
|
||||
payTime: '2026-08-03 12:32:50',
|
||||
items: [
|
||||
{ name: '红烧肉', price: 22.00, qty: 1, subtotal: 22.00 },
|
||||
{ name: '清炒时蔬', price: 6.00, qty: 1, subtotal: 6.00 },
|
||||
{ name: '鱼香肉丝', price: 12.00, qty: 1, subtotal: 12.00 },
|
||||
{ name: '米饭', price: 2.00, qty: 1, subtotal: 2.00 },
|
||||
{ name: '冬瓜排骨汤', price: 3.00, qty: 1, subtotal: 3.00 }
|
||||
{ name: '红烧肉', zone: '档口秤', weight: '180g', price: 22.00, qty: 1, subtotal: 22.00 },
|
||||
{ name: '清炒时蔬', zone: '营养秤', weight: '150g', price: 6.00, qty: 1, subtotal: 6.00 },
|
||||
{ name: '鱼香肉丝', zone: '档口秤', weight: '160g', price: 12.00, qty: 1, subtotal: 12.00 },
|
||||
{ name: '米饭', zone: '营养秤', weight: '250g', price: 2.00, qty: 1, subtotal: 2.00 },
|
||||
{ name: '冬瓜排骨汤', zone: '档口秤', weight: '300g', price: 3.00, qty: 1, subtotal: 3.00 }
|
||||
],
|
||||
total: 45.00, discount: 5.00, payable: 40.00, paid: 40.00,
|
||||
payMethod: '人脸支付', balance: 1240.50,
|
||||
payType: '会员支付', payMethod: '人脸支付', balance: 1240.50,
|
||||
nutrition: { calories: 720, protein: 35, fat: 28, carb: 88, score: 88 },
|
||||
printed: false
|
||||
},
|
||||
@@ -612,13 +636,14 @@
|
||||
line: 'A 区 1 号餐线',
|
||||
cashier: '王五',
|
||||
time: '2026-08-03 18:15:20',
|
||||
payTime: '2026-08-03 18:15:25',
|
||||
items: [
|
||||
{ name: '清蒸鲈鱼', price: 25.00, qty: 1, subtotal: 25.00 },
|
||||
{ name: '蒜蓉西兰花', price: 6.00, qty: 1, subtotal: 6.00 },
|
||||
{ name: '紫米饭', price: 3.00, qty: 1, subtotal: 3.00 }
|
||||
{ name: '清蒸鲈鱼', zone: '档口秤', weight: '200g', price: 25.00, qty: 1, subtotal: 25.00 },
|
||||
{ name: '蒜蓉西兰花', zone: '营养秤', weight: '150g', price: 6.00, qty: 1, subtotal: 6.00 },
|
||||
{ name: '紫米饭', zone: '营养秤', weight: '200g', price: 3.00, qty: 1, subtotal: 3.00 }
|
||||
],
|
||||
total: 28.00, discount: 0, payable: 28.00, paid: 28.00,
|
||||
payMethod: '扫码支付', balance: 1212.50,
|
||||
payType: '扫码支付', payMethod: '微信支付', balance: 1212.50,
|
||||
nutrition: { calories: 510, protein: 32, fat: 14, carb: 65, score: 90 },
|
||||
printed: false
|
||||
},
|
||||
@@ -629,14 +654,15 @@
|
||||
line: 'A 区 3 号餐线',
|
||||
cashier: '张三',
|
||||
time: '2026-08-03 12:48:10',
|
||||
payTime: '2026-08-03 12:48:15',
|
||||
items: [
|
||||
{ name: '番茄炒蛋', price: 8.00, qty: 1, subtotal: 8.00 },
|
||||
{ name: '麻婆豆腐', price: 10.00, qty: 1, subtotal: 10.00 },
|
||||
{ name: '米饭', price: 2.00, qty: 1, subtotal: 2.00 },
|
||||
{ name: '番茄蛋汤', price: 5.00, qty: 1, subtotal: 5.00 }
|
||||
{ name: '番茄炒蛋', zone: '营养秤', weight: '150g', price: 8.00, qty: 1, subtotal: 8.00 },
|
||||
{ name: '麻婆豆腐', zone: '档口秤', weight: '180g', price: 10.00, qty: 1, subtotal: 10.00 },
|
||||
{ name: '米饭', zone: '营养秤', weight: '250g', price: 2.00, qty: 1, subtotal: 2.00 },
|
||||
{ name: '番茄蛋汤', zone: '档口秤', weight: '280g', price: 5.00, qty: 1, subtotal: 5.00 }
|
||||
],
|
||||
total: 25.00, discount: 0, payable: 25.00, paid: 25.00,
|
||||
payMethod: '人脸支付', balance: 1187.50,
|
||||
payType: '会员支付', payMethod: '人脸支付', balance: 1187.50,
|
||||
nutrition: { calories: 480, protein: 22, fat: 15, carb: 70, score: 78 },
|
||||
printed: false
|
||||
},
|
||||
@@ -647,12 +673,13 @@
|
||||
line: 'A 区 2 号餐线',
|
||||
cashier: '李四',
|
||||
time: '2026-08-03 08:15:30',
|
||||
payTime: '2026-08-03 08:15:33',
|
||||
items: [
|
||||
{ name: '皮蛋瘦肉粥', price: 5.00, qty: 1, subtotal: 5.00 },
|
||||
{ name: '鲜肉包子', price: 3.00, qty: 1, subtotal: 3.00 }
|
||||
{ name: '皮蛋瘦肉粥', zone: '档口秤', weight: '350g', price: 5.00, qty: 1, subtotal: 5.00 },
|
||||
{ name: '鲜肉包子', zone: '营养秤', weight: '150g', price: 3.00, qty: 1, subtotal: 3.00 }
|
||||
],
|
||||
total: 8.00, discount: 0, payable: 8.00, paid: 8.00,
|
||||
payMethod: '餐卡支付', balance: 1195.50,
|
||||
payType: '扫码支付', payMethod: '支付宝支付', balance: 1195.50,
|
||||
nutrition: { calories: 320, protein: 12, fat: 8, carb: 48, score: 70 },
|
||||
printed: false
|
||||
}
|
||||
@@ -717,17 +744,30 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var itemsHtml = order.items.map(function(it) {
|
||||
return '<tr>'
|
||||
+ '<td>' + it.name + '</td>'
|
||||
+ '<td>' + it.price.toFixed(2) + '</td>'
|
||||
+ '<td>' + it.qty + '</td>'
|
||||
+ '<td>' + it.subtotal.toFixed(2) + '</td>'
|
||||
+ '</tr>';
|
||||
}).join('');
|
||||
/* 按分区分组渲染 */
|
||||
var zoneOrder = ['营养秤', '档口秤'];
|
||||
var zones = {};
|
||||
order.items.forEach(function(it) {
|
||||
if (!zones[it.zone]) zones[it.zone] = [];
|
||||
zones[it.zone].push(it);
|
||||
});
|
||||
var itemsHtml = '';
|
||||
zoneOrder.forEach(function(zoneName) {
|
||||
var zoneItems = zones[zoneName];
|
||||
if (!zoneItems || zoneItems.length === 0) return;
|
||||
itemsHtml += '<tr class="receipt-zone-header"><td colspan="4">【' + zoneName + '】</td></tr>';
|
||||
zoneItems.forEach(function(it) {
|
||||
itemsHtml += '<tr>'
|
||||
+ '<td>' + it.name + '<span style="color:#999;font-size:10px;">(' + it.weight + ')</span></td>'
|
||||
+ '<td>' + it.price.toFixed(2) + '</td>'
|
||||
+ '<td>' + it.qty + '</td>'
|
||||
+ '<td>' + it.subtotal.toFixed(2) + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
});
|
||||
|
||||
var discountRow = order.discount > 0
|
||||
? '<div class="receipt-row"><span class="receipt-row__label">优惠</span><span class="receipt-row__value" style="color:#d4380d;">-' + order.discount.toFixed(2) + '</span></div>'
|
||||
? '<div class="receipt-row"><span class="receipt-row__label">优惠</span><span class="receipt-row__value">-' + order.discount.toFixed(2) + '</span></div>'
|
||||
: '';
|
||||
|
||||
var html = ''
|
||||
@@ -745,11 +785,12 @@
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">餐线</span><span class="receipt-row__value">' + order.line + '</span></div>'
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">收银员</span><span class="receipt-row__value">' + order.cashier + '</span></div>'
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">就餐时间</span><span class="receipt-row__value">' + order.time + '</span></div>'
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">付款时间</span><span class="receipt-row__value">' + order.payTime + '</span></div>'
|
||||
+ '</div>'
|
||||
+ '<div class="receipt-divider"></div>'
|
||||
/* 消费明细表 */
|
||||
+ '<table class="receipt-table">'
|
||||
+ '<thead><tr><th>餐品</th><th>单价</th><th>份数</th><th>小计</th></tr></thead>'
|
||||
+ '<thead><tr><th>餐品</th><th>单价(100g)</th><th>份数</th><th>小计</th></tr></thead>'
|
||||
+ '<tbody>' + itemsHtml + '</tbody>'
|
||||
+ '</table>'
|
||||
+ '<div class="receipt-divider"></div>'
|
||||
@@ -763,6 +804,7 @@
|
||||
+ '<div class="receipt-divider"></div>'
|
||||
/* 支付信息 */
|
||||
+ '<div class="receipt-info">'
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">支付类型</span><span class="receipt-row__value">' + order.payType + '</span></div>'
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">支付方式</span><span class="receipt-row__value">' + order.payMethod + '</span></div>'
|
||||
+ '<div class="receipt-row"><span class="receipt-row__label">餐卡余额</span><span class="receipt-row__value">¥' + order.balance.toFixed(2) + '</span></div>'
|
||||
+ '</div>'
|
||||
@@ -781,9 +823,17 @@
|
||||
+ '</div>'
|
||||
+ '<div class="receipt-divider"></div>'
|
||||
/* 二维码 */
|
||||
+ '<div class="receipt-qr">'
|
||||
+ getQRCodeSVG()
|
||||
+ '<div class="receipt-qr__text">扫码查看电子小票与营养详情</div>'
|
||||
+ '<div class="receipt-qr--dual">'
|
||||
+ '<div class="receipt-qr">'
|
||||
+ getQRCodeEnterpriseSVG()
|
||||
+ '<div class="receipt-qr__text">扫码加入企业微信群</div>'
|
||||
+ '<div class="receipt-qr__sub">获取营养知识与优惠活动</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="receipt-qr">'
|
||||
+ getQRCodeMiniProgramSVG()
|
||||
+ '<div class="receipt-qr__text">扫码进入小程序</div>'
|
||||
+ '<div class="receipt-qr__sub">查看详细营养分析报告</div>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="receipt-divider receipt-divider--double"></div>'
|
||||
/* 底部提示 */
|
||||
@@ -798,30 +848,7 @@
|
||||
}
|
||||
|
||||
/* 简化版二维码 SVG (21x21 模块,模拟外观) */
|
||||
function getQRCodeSVG() {
|
||||
var matrix = [
|
||||
'111111101010111111100',
|
||||
'100000101011100000110',
|
||||
'101110101110101110110',
|
||||
'101110100101101110010',
|
||||
'101110101101101110000',
|
||||
'100000101001100000110',
|
||||
'111111101010111111100',
|
||||
'000000001101000000000',
|
||||
'101011011001010110010',
|
||||
'011010001100011001010',
|
||||
'101101100110010100100',
|
||||
'011000101010011011010',
|
||||
'101011010011001011100',
|
||||
'000000011001010100010',
|
||||
'111111101100101101000',
|
||||
'100000101011011010110',
|
||||
'101110101010100100100',
|
||||
'101110101101011011010',
|
||||
'101110100110010100110',
|
||||
'100000101011010010010',
|
||||
'111111101101100101100'
|
||||
];
|
||||
function generateQRCodeSVG(matrix) {
|
||||
var size = 21;
|
||||
var unit = 4;
|
||||
var dim = size * unit;
|
||||
@@ -836,6 +863,62 @@
|
||||
return '<svg class="receipt-qr__img" viewBox="0 0 ' + dim + ' ' + dim + '" xmlns="http://www.w3.org/2000/svg">' + rects + '</svg>';
|
||||
}
|
||||
|
||||
/* 企业微信群二维码 */
|
||||
function getQRCodeEnterpriseSVG() {
|
||||
var matrix = [
|
||||
'111111101001010011100',
|
||||
'100000101100110000100',
|
||||
'101110100110101110100',
|
||||
'101110101110101110110',
|
||||
'101110100100001110100',
|
||||
'100000100011100000110',
|
||||
'111111101010111111100',
|
||||
'000000001011000000000',
|
||||
'101000110110010001010',
|
||||
'010111001101110011010',
|
||||
'101100100011100111000',
|
||||
'011010011010101011010',
|
||||
'100011111001101111010',
|
||||
'000000001100010010100',
|
||||
'111111101000011000110',
|
||||
'100000101110001001000',
|
||||
'101110101011110111010',
|
||||
'101110100110001100010',
|
||||
'101110100101111010010',
|
||||
'100000101100100100010',
|
||||
'111111101010011110100'
|
||||
];
|
||||
return generateQRCodeSVG(matrix);
|
||||
}
|
||||
|
||||
/* 小程序二维码 */
|
||||
function getQRCodeMiniProgramSVG() {
|
||||
var matrix = [
|
||||
'111111100111101110100',
|
||||
'100000101001100100100',
|
||||
'101110101110001110110',
|
||||
'101110100101001110110',
|
||||
'101110100011101110000',
|
||||
'100000100111100000100',
|
||||
'111111101010111111100',
|
||||
'000000000111000000000',
|
||||
'101000110010110110010',
|
||||
'010011011100000110110',
|
||||
'101110001010001010010',
|
||||
'011100101010111100100',
|
||||
'101011010000111001010',
|
||||
'000000001111010010010',
|
||||
'111111101101101101010',
|
||||
'100000101001000001110',
|
||||
'101110100001010100100',
|
||||
'101110101111111110010',
|
||||
'101110100110101000110',
|
||||
'100000101000001100100',
|
||||
'111111101110000110110'
|
||||
];
|
||||
return generateQRCodeSVG(matrix);
|
||||
}
|
||||
|
||||
/* 切换订单 */
|
||||
function selectOrder(orderId) {
|
||||
currentOrderId = orderId;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# 结算设备 CHANGELOG
|
||||
|
||||
## 2026-08-04 | 打印小票功能完善
|
||||
|
||||
### 新增
|
||||
- `02-print-receipt.html` 新增打印小票预览页面,左右分栏布局(订单列表 + 小票预览)
|
||||
- **订单信息**:订单号、餐线、收银员、就餐时间、付款时间
|
||||
- **支付信息**:支付类型(会员支付/扫码支付/现金支付)+ 支付方式(人脸支付/微信支付/支付宝支付/现金支付),含餐卡余额
|
||||
- **消费明细**:按分区(营养秤/档口秤)分组展示,餐品名称后带克重
|
||||
- **单价标注**:表头标注单价(100g)
|
||||
- **营养摘要**:热量、蛋白质、脂肪、碳水四维 + 营养得分
|
||||
- **双二维码**:左侧扫码加入企业微信群(获取营养知识与优惠活动),右侧扫码进入小程序(查看详细营养分析报告)
|
||||
- **黑白热敏纸风格**:全小票仅使用黑、白、灰三色,模拟热敏纸打印效果
|
||||
- 支持按餐次筛选、打印状态筛选、订单选择预览
|
||||
|
||||
### 变更
|
||||
- Mock 数据从 6 个订单增加到完整字段(zone、payTime、payType、weight)
|
||||
- 原单二维码改为双二维码并排布局
|
||||
|
||||
### 导航同步
|
||||
- `checkout-device/index.html` — 已有小票预览入口
|
||||
Reference in New Issue
Block a user