feat: 收银设备新增用户类型徽标体系 + 补齐明细页重构
- 主屏(03/04/05/06/07)与副屏(user-02/03/04)统一用户类型徽标:会员/临时/员工三种配色 - 订单列表(05)与订单详情(06)增加用户类型展示,新增顾客信息卡片 - 订单详情(06)移除"补齐明细"入口(支付后不再适用) - 补齐明细页(08)重构为表格化结构,新增"未识别取餐记录"作为少记加入来源 - 导航页"结算设备"改名为"结算台" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
fdeb8a2d09
commit
deb475222c
File diff suppressed because it is too large
Load Diff
@@ -123,6 +123,97 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 账户信息条(余额/混合/找零场景显示) */
|
||||
.account-info {
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(90deg, rgba(74, 144, 217, 0.18), rgba(10, 196, 168, 0.1));
|
||||
border: 1px solid rgba(74, 144, 217, 0.35);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
animation: slideUp 0.5s ease;
|
||||
}
|
||||
@keyframes slideUp {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.account-info__col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.account-info__label {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.account-info__value {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.account-info__value--expense { color: #fb923c; }
|
||||
.account-info__value--balance { color: #4ade80; }
|
||||
.account-info__value--change { color: #fbbf24; }
|
||||
.account-info__divider {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.account-info.account-info--wide { max-width: 460px; }
|
||||
.account-info.account-info--cash {
|
||||
background: linear-gradient(90deg, rgba(82, 196, 26, 0.18), rgba(251, 191, 36, 0.1));
|
||||
border-color: rgba(82, 196, 26, 0.4);
|
||||
}
|
||||
|
||||
/* 演示切换器(原型展示用,实际部署可移除) */
|
||||
.demo-switcher {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 6px;
|
||||
max-width: 540px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 22px;
|
||||
backdrop-filter: blur(8px);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.demo-switcher__btn {
|
||||
padding: 5px 14px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
transition: all 0.25s;
|
||||
font-family: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.demo-switcher__btn:hover {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.demo-switcher__btn.active {
|
||||
background: rgba(82, 196, 26, 0.35);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
/* 订单基础信息 */
|
||||
.success-meta {
|
||||
width: 100%;
|
||||
@@ -300,6 +391,16 @@
|
||||
|
||||
<div class="device-frame">
|
||||
|
||||
<!-- 演示切换按钮(原型展示用,实际部署可移除) -->
|
||||
<div class="demo-switcher">
|
||||
<button class="demo-switcher__btn active" data-method="online">在线支付</button>
|
||||
<button class="demo-switcher__btn" data-method="balance">余额支付</button>
|
||||
<button class="demo-switcher__btn" data-method="mixed-bl-online">余额+在线</button>
|
||||
<button class="demo-switcher__btn" data-method="mixed-bl-cash">余额+现金</button>
|
||||
<button class="demo-switcher__btn" data-method="mixed-cash-online">现金+在线</button>
|
||||
<button class="demo-switcher__btn" data-method="cash-change">现金找零</button>
|
||||
</div>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<div class="top-bar">
|
||||
<div class="top-bar__left"></div>
|
||||
@@ -314,14 +415,17 @@
|
||||
<div class="success-col">
|
||||
<div class="success-icon">✔</div>
|
||||
|
||||
<div class="success-title">支付成功</div>
|
||||
<div class="success-title" id="successTitle">支付成功</div>
|
||||
|
||||
<div class="success-amount">
|
||||
<div class="success-amount__label">实付金额</div>
|
||||
<div class="success-amount__value">¥41.60</div>
|
||||
<div class="success-pay-method">📱 在线支付 · 微信支付</div>
|
||||
<div class="success-amount__value" id="successAmount">¥41.60</div>
|
||||
<div class="success-pay-method" id="payMethodBadge">📱 在线支付 · 微信支付</div>
|
||||
</div>
|
||||
|
||||
<!-- 账户信息条(余额/混合/找零场景显示;由 JS 按支付方式动态注入) -->
|
||||
<div class="account-info" id="accountInfo" style="display:none;"></div>
|
||||
|
||||
<div class="success-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-row__label">订单号</span>
|
||||
@@ -400,12 +504,143 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* 8 秒后提示开始下一单 */
|
||||
setTimeout(function() {
|
||||
if (confirm('订单已完成,是否开始下一单?')) {
|
||||
window.location.href = '02-standby.html';
|
||||
/* ==================== 订单数据(与 03-checkout 金额一致) ==================== */
|
||||
var ORDER_TOTAL = 46.60;
|
||||
var ORDER_DISCOUNT = 5.00; /* 企业补贴 */
|
||||
var ORDER_PAYABLE = ORDER_TOTAL - ORDER_DISCOUNT; /* 41.60 */
|
||||
var USER_BALANCE = 128.50;
|
||||
var DEMO_BALANCE_FOR_MIXED = 20.00; /* 混合支付演示余额 */
|
||||
var DEMO_CASH_RATIO = 0.6; /* 现金+在线的现金比例 */
|
||||
var DEMO_CASH_RECEIVED = 50.00; /* 现金找零演示实收 */
|
||||
|
||||
/* ==================== 支付方式成功态配置 ==================== */
|
||||
/* title: 成功大标题 */
|
||||
/* badge: 金额下方的支付方式徽章 */
|
||||
/* accountHtml: 账户信息条 HTML(空字符串=隐藏) */
|
||||
var methodConfig = {
|
||||
/* 在线支付 */
|
||||
online: {
|
||||
title: '支付成功',
|
||||
badge: '📱 在线支付 · 微信 · 已到账',
|
||||
accountHtml: ''
|
||||
},
|
||||
/* 余额支付 */
|
||||
balance: {
|
||||
title: '扣款成功',
|
||||
badge: '💰 余额支付 · 已从会员账户扣除',
|
||||
accountHtml: accountRow('账户余额 · 原余额', '¥' + USER_BALANCE.toFixed(2), '')
|
||||
+ accountDivider()
|
||||
+ accountRow('本单消费', '-¥' + ORDER_PAYABLE.toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('扣款后余额', '¥' + (USER_BALANCE - ORDER_PAYABLE).toFixed(2), 'balance')
|
||||
},
|
||||
/* 余额 + 在线 */
|
||||
'mixed-bl-online': {
|
||||
title: '支付成功',
|
||||
badge: '💰 余额 + 📱 在线 · 混合支付完成',
|
||||
accountHtml: accountRow('余额扣款', '-¥' + DEMO_BALANCE_FOR_MIXED.toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('在线支付', '-¥' + (ORDER_PAYABLE - DEMO_BALANCE_FOR_MIXED).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('实付合计', '¥' + ORDER_PAYABLE.toFixed(2), 'balance'),
|
||||
wide: true
|
||||
},
|
||||
/* 余额 + 现金 */
|
||||
'mixed-bl-cash': {
|
||||
title: '支付成功',
|
||||
badge: '💰 余额 + 💶 现金 · 混合支付完成',
|
||||
accountHtml: accountRow('余额扣款', '-¥' + DEMO_BALANCE_FOR_MIXED.toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('现金支付', '-¥' + (ORDER_PAYABLE - DEMO_BALANCE_FOR_MIXED).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('实付合计', '¥' + ORDER_PAYABLE.toFixed(2), 'balance'),
|
||||
wide: true,
|
||||
cash: true
|
||||
},
|
||||
/* 现金 + 在线 */
|
||||
'mixed-cash-online': {
|
||||
title: '支付成功',
|
||||
badge: '💶 现金 + 📱 在线 · 混合支付完成',
|
||||
accountHtml: accountRow('现金支付', '-¥' + (ORDER_PAYABLE * DEMO_CASH_RATIO).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('在线支付', '-¥' + (ORDER_PAYABLE * (1 - DEMO_CASH_RATIO)).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('实付合计', '¥' + ORDER_PAYABLE.toFixed(2), 'balance'),
|
||||
wide: true,
|
||||
cash: true
|
||||
},
|
||||
/* 现金找零 */
|
||||
'cash-change': {
|
||||
title: '支付成功',
|
||||
badge: '💶 现金支付 · 收银员已确认',
|
||||
accountHtml: accountRow('应付金额', '¥' + ORDER_PAYABLE.toFixed(2), '')
|
||||
+ accountDivider()
|
||||
+ accountRow('实收现金', '¥' + DEMO_CASH_RECEIVED.toFixed(2), '')
|
||||
+ accountDivider()
|
||||
+ accountRow('找零', '¥' + (DEMO_CASH_RECEIVED - ORDER_PAYABLE).toFixed(2), 'change'),
|
||||
wide: true,
|
||||
cash: true
|
||||
}
|
||||
}, 8000);
|
||||
};
|
||||
|
||||
/* 账户信息条单元格构造器 */
|
||||
function accountRow(label, value, modifier) {
|
||||
var cls = modifier ? ' account-info__value--' + modifier : '';
|
||||
return '<div class="account-info__col">'
|
||||
+ '<span class="account-info__label">' + label + '</span>'
|
||||
+ '<span class="account-info__value' + cls + '">' + value + '</span>'
|
||||
+ '</div>';
|
||||
}
|
||||
function accountDivider() {
|
||||
return '<div class="account-info__divider"></div>';
|
||||
}
|
||||
|
||||
/* 按支付方式应用配置 */
|
||||
function switchDemo(method) {
|
||||
var cfg = methodConfig[method];
|
||||
if (!cfg) return;
|
||||
document.getElementById('successTitle').textContent = cfg.title;
|
||||
document.getElementById('successAmount').innerHTML = '¥' + ORDER_PAYABLE.toFixed(2);
|
||||
document.getElementById('payMethodBadge').innerHTML = cfg.badge;
|
||||
var accountEl = document.getElementById('accountInfo');
|
||||
if (cfg.accountHtml) {
|
||||
accountEl.innerHTML = cfg.accountHtml;
|
||||
accountEl.style.display = 'flex';
|
||||
accountEl.classList.toggle('account-info--wide', !!cfg.wide);
|
||||
accountEl.classList.toggle('account-info--cash', !!cfg.cash);
|
||||
} else {
|
||||
accountEl.style.display = 'none';
|
||||
accountEl.innerHTML = '';
|
||||
}
|
||||
document.querySelectorAll('.demo-switcher__btn').forEach(function(btn) {
|
||||
btn.classList.toggle('active', btn.dataset.method === method);
|
||||
});
|
||||
}
|
||||
|
||||
/* 演示切换器事件 + URL 同步 */
|
||||
document.querySelectorAll('.demo-switcher__btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
switchDemo(btn.dataset.method);
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('method', btn.dataset.method);
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
});
|
||||
});
|
||||
|
||||
/* URL 参数驱动:支持 ?method=xxx 从收银员屏 03 跳转携带支付方式 */
|
||||
function loadFromUrl() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var method = params.get('method');
|
||||
var valid = ['online', 'balance', 'mixed-bl-online', 'mixed-bl-cash', 'mixed-cash-online', 'cash-change'];
|
||||
if (method && valid.indexOf(method) !== -1) {
|
||||
switchDemo(method);
|
||||
} else {
|
||||
switchDemo('online');
|
||||
}
|
||||
}
|
||||
|
||||
/* 初始化(优先 URL 参数,无参数时默认展示在线支付) */
|
||||
loadFromUrl();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -148,8 +148,8 @@
|
||||
|
||||
.order-row {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1.3fr 70px 0.9fr 1.1fr 110px 95px 95px 130px;
|
||||
gap: 10px;
|
||||
grid-template-columns: 36px 1.2fr 92px 64px 64px 0.85fr 1fr 100px 86px 80px 116px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
background: rgba(26, 43, 74, 0.4);
|
||||
@@ -162,8 +162,8 @@
|
||||
/* 表头 */
|
||||
.order-head {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1.3fr 70px 0.9fr 1.1fr 110px 95px 95px 130px;
|
||||
gap: 10px;
|
||||
grid-template-columns: 36px 1.2fr 92px 64px 64px 0.85fr 1fr 100px 86px 80px 116px;
|
||||
gap: 8px;
|
||||
padding: 8px 14px;
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
@@ -194,6 +194,18 @@
|
||||
font-weight: 600;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
}
|
||||
.order-row__user {
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.order-row__user-code {
|
||||
font-size: 10px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-top: 2px;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
}
|
||||
.order-row__sub {
|
||||
font-size: 10px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
@@ -223,6 +235,34 @@
|
||||
color: #4ade80;
|
||||
border: 1px solid rgba(34, 197, 94, 0.35);
|
||||
}
|
||||
/* 用户类型徽标(会员/临时/内部员工) */
|
||||
.user-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px 8px;
|
||||
border-radius: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3px;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.user-type--member {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.22), rgba(251, 191, 36, 0.22));
|
||||
border: 1px solid rgba(251, 191, 36, 0.45);
|
||||
color: #fbbf24;
|
||||
}
|
||||
.user-type--temp {
|
||||
background: rgba(148, 163, 184, 0.18);
|
||||
border: 1px solid rgba(148, 163, 184, 0.45);
|
||||
color: #cbd5e1;
|
||||
}
|
||||
.user-type--staff {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.18), rgba(74, 144, 217, 0.12));
|
||||
border: 1px solid rgba(96, 165, 250, 0.45);
|
||||
color: #60a5fa;
|
||||
}
|
||||
.order-row__items {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
@@ -318,6 +358,11 @@
|
||||
color: #ff6b6b;
|
||||
border: 1px solid rgba(245, 34, 45, 0.3);
|
||||
}
|
||||
.status-pill--partial {
|
||||
background: rgba(251, 191, 36, 0.15);
|
||||
color: #fbbf24;
|
||||
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty {
|
||||
@@ -419,6 +464,7 @@
|
||||
<select class="filter-select">
|
||||
<option>全部状态</option>
|
||||
<option>正常</option>
|
||||
<option>部分退款</option>
|
||||
<option>已退款</option>
|
||||
</select>
|
||||
<select class="filter-select">
|
||||
@@ -430,7 +476,7 @@
|
||||
</select>
|
||||
<div class="filter-bar__search">
|
||||
<span>🔍</span>
|
||||
<input type="text" placeholder="搜索订单号 / 顾客姓名">
|
||||
<input type="text" placeholder="用户名/编码/支付流水号">
|
||||
</div>
|
||||
<div class="filter-bar__stat">
|
||||
午餐单数:<strong>5</strong>
|
||||
@@ -443,7 +489,9 @@
|
||||
<!-- 表头(静态保留,不被 JS 覆盖) -->
|
||||
<div class="order-head" id="orderHead">
|
||||
<div>#</div>
|
||||
<div>订单号 / 状态</div>
|
||||
<div>用户信息</div>
|
||||
<div>用户类型</div>
|
||||
<div>状态</div>
|
||||
<div>餐次</div>
|
||||
<div>商品</div>
|
||||
<div>支付流水号</div>
|
||||
@@ -475,10 +523,12 @@
|
||||
|
||||
<script>
|
||||
/* ==================== Mock 订单数据(当前餐次:午餐) ==================== */
|
||||
/* userType: member(会员) | temp(临时) | staff(内部员工) */
|
||||
var mockOrders = [
|
||||
{
|
||||
id: 'CO-20260806-031', mealType: 'lunch', mealLabel: '午餐',
|
||||
cashier: '张三',
|
||||
userName: '雷二', userCode: 'ER-566212', userType: 'member',
|
||||
cashier: '雷二',
|
||||
itemCount: 5, itemsSummary: '红烧肉/清炒时蔬/鱼香肉丝/米饭/...',
|
||||
payable: 41.60,
|
||||
txnNo: 'WX2608061232509987',
|
||||
@@ -488,6 +538,7 @@
|
||||
},
|
||||
{
|
||||
id: 'CO-20260806-030', mealType: 'lunch', mealLabel: '午餐',
|
||||
userName: '张三', userCode: 'ER-566198', userType: 'staff',
|
||||
cashier: '张三',
|
||||
itemCount: 4, itemsSummary: '宫保鸡丁/番茄炒蛋/米饭/...',
|
||||
payable: 32.50,
|
||||
@@ -498,17 +549,20 @@
|
||||
},
|
||||
{
|
||||
id: 'CO-20260806-029', mealType: 'lunch', mealLabel: '午餐',
|
||||
cashier: '张三',
|
||||
userName: '李四', userCode: 'ER-566145', userType: 'member',
|
||||
cashier: '李四',
|
||||
itemCount: 3, itemsSummary: '麻婆豆腐/番茄炒蛋/米饭',
|
||||
payable: 18.50,
|
||||
refundedAmount: 10.00,
|
||||
txnNo: 'MQ26080612180056',
|
||||
payMethod: '餐券/补贴码', payIcon: '🎫',
|
||||
payTime: '12:18:42',
|
||||
printed: true, status: 'normal'
|
||||
printed: true, status: 'partial'
|
||||
},
|
||||
{
|
||||
id: 'CO-20260806-028', mealType: 'lunch', mealLabel: '午餐',
|
||||
cashier: '张三',
|
||||
userName: '王五', userCode: 'TMP-0088', userType: 'temp',
|
||||
cashier: '王五',
|
||||
itemCount: 3, itemsSummary: '清蒸鲈鱼/蒜蓉西兰花/紫米饭',
|
||||
payable: 34.00,
|
||||
txnNo: 'WX2608061210304452',
|
||||
@@ -518,7 +572,8 @@
|
||||
},
|
||||
{
|
||||
id: 'CO-20260806-027', mealType: 'lunch', mealLabel: '午餐',
|
||||
cashier: '张三',
|
||||
userName: '赵六', userCode: 'ER-565820', userType: 'staff',
|
||||
cashier: '赵六',
|
||||
itemCount: 4, itemsSummary: '鱼香肉丝/麻婆豆腐/米饭/...',
|
||||
payable: 24.70,
|
||||
txnNo: 'CASH26080612021899',
|
||||
@@ -528,6 +583,13 @@
|
||||
}
|
||||
];
|
||||
|
||||
/* 用户类型配置(徽标文案 + 配色) */
|
||||
var USER_TYPE_CONFIG = {
|
||||
member: { label: '会员用户', cls: 'user-type--member' },
|
||||
temp: { label: '临时用户', cls: 'user-type--temp' },
|
||||
staff: { label: '内部员工', cls: 'user-type--staff' }
|
||||
};
|
||||
|
||||
function renderOrders() {
|
||||
var listEl = document.getElementById('orderRows');
|
||||
if (mockOrders.length === 0) {
|
||||
@@ -538,9 +600,22 @@
|
||||
var html = mockOrders.map(function(o, idx) {
|
||||
var mealTagClass = 'meal-tag--' + o.mealType;
|
||||
var refundedClass = o.status === 'refunded' ? ' refunded' : '';
|
||||
var statusPill = o.status === 'refunded'
|
||||
? '<span class="status-pill status-pill--refunded">已退款</span>'
|
||||
: '<span class="status-pill status-pill--normal">正常</span>';
|
||||
/* 状态徽章:正常 / 部分退款 / 已退款 */
|
||||
var statusPill;
|
||||
if (o.status === 'refunded') {
|
||||
statusPill = '<span class="status-pill status-pill--refunded">已退款</span>';
|
||||
} else if (o.status === 'partial') {
|
||||
statusPill = '<span class="status-pill status-pill--partial">部分退款</span>';
|
||||
} else {
|
||||
statusPill = '<span class="status-pill status-pill--normal">正常</span>';
|
||||
}
|
||||
|
||||
/* 用户信息:姓名 | 编号 */
|
||||
var userHtml = '<div class="order-row__user">' + o.userName + ' | ' + o.userCode + '</div>';
|
||||
|
||||
/* 用户类型徽标 */
|
||||
var typeCfg = USER_TYPE_CONFIG[o.userType] || USER_TYPE_CONFIG.temp;
|
||||
var userTypeHtml = '<span class="user-type ' + typeCfg.cls + '">' + typeCfg.label + '</span>';
|
||||
|
||||
/* 商品汇总显示:餐品 x N */
|
||||
var itemsHtml = '<div class="order-row__items">🍽️ 餐品 x' + o.itemCount + '</div>'
|
||||
@@ -553,20 +628,27 @@
|
||||
/* 支付时间(独立一列) */
|
||||
var payTimeHtml = '<div class="order-row__txn">' + o.payTime + '</div>';
|
||||
|
||||
/* 操作按钮 - 退款按钮携带 action=refund 参数跳转详情页 */
|
||||
var detailHref = '06-order-detail.html?id=' + o.id;
|
||||
var refundHref = '06-order-detail.html?id=' + o.id + '&action=refund';
|
||||
/* 操作按钮 - 已退款不再支持退款;部分退款支持「继续退款」(二次退款) */
|
||||
var detailParams = 'id=' + o.id
|
||||
+ (o.status ? '&status=' + o.status : '')
|
||||
+ (o.refundedAmount ? '&refunded=' + o.refundedAmount : '')
|
||||
+ '&payable=' + o.payable.toFixed(2)
|
||||
+ '&userType=' + o.userType
|
||||
+ '&userName=' + encodeURIComponent(o.userName)
|
||||
+ '&userCode=' + encodeURIComponent(o.userCode);
|
||||
var detailHref = '06-order-detail.html?' + detailParams;
|
||||
var refundHref = '06-order-detail.html?' + detailParams + '&action=refund';
|
||||
var actions = o.status === 'refunded'
|
||||
? '<a class="action-btn" href="' + detailHref + '">详情</a>'
|
||||
: '<a class="action-btn" href="' + detailHref + '">详情</a>'
|
||||
+ '<a class="action-btn action-btn--gold" href="09-receipt-preview.html?id=' + o.id + '">打印</a>'
|
||||
+ '<a class="action-btn action-btn--gold" href="09-receipt-preview.html?id=' + o.id + '">打印小票</a>'
|
||||
+ '<a class="action-btn action-btn--danger" href="' + refundHref + '">去退款</a>';
|
||||
|
||||
return '<div class="order-row' + refundedClass + '" onclick="window.location.href=\'' + detailHref + '\'">'
|
||||
+ '<div class="order-row__index">#' + (idx + 1) + '</div>'
|
||||
+ '<div>'
|
||||
+ '<div class="order-row__id">' + o.id + statusPill + '</div>'
|
||||
+ '</div>'
|
||||
+ '<div>' + userHtml + '</div>'
|
||||
+ '<div>' + userTypeHtml + '</div>'
|
||||
+ '<div>' + statusPill + '</div>'
|
||||
+ '<div><span class="meal-tag ' + mealTagClass + '">' + o.mealLabel + '</span></div>'
|
||||
+ '<div>' + itemsHtml + '</div>'
|
||||
+ '<div>' + txnHtml + '</div>'
|
||||
|
||||
@@ -141,6 +141,11 @@
|
||||
color: #ff6b6b;
|
||||
border: 1px solid rgba(245, 34, 45, 0.3);
|
||||
}
|
||||
.status-pill--partial {
|
||||
background: rgba(251, 191, 36, 0.15);
|
||||
color: #fbbf24;
|
||||
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||
}
|
||||
|
||||
/* 信息网格 */
|
||||
.info-grid {
|
||||
@@ -172,6 +177,67 @@
|
||||
border: 1px solid rgba(37, 99, 235, 0.35);
|
||||
}
|
||||
|
||||
/* 用户类型徽标(与 05-orders 同款样式) */
|
||||
.user-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3px;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.user-type--member {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.22), rgba(251, 191, 36, 0.22));
|
||||
border: 1px solid rgba(251, 191, 36, 0.45);
|
||||
color: #fbbf24;
|
||||
}
|
||||
.user-type--temp {
|
||||
background: rgba(148, 163, 184, 0.18);
|
||||
border: 1px solid rgba(148, 163, 184, 0.45);
|
||||
color: #cbd5e1;
|
||||
}
|
||||
.user-type--staff {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.18), rgba(74, 144, 217, 0.12));
|
||||
border: 1px solid rgba(96, 165, 250, 0.45);
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
/* 顾客信息卡(订单头下方独立卡片,展示用户类型/姓名/编号) */
|
||||
.customer-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.customer-card__field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.customer-card__label {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.customer-card__value {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.customer-card__value--code {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.customer-card__divider {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 商品明细表 */
|
||||
.item-table {
|
||||
width: 100%;
|
||||
@@ -523,6 +589,39 @@
|
||||
.refund-record.show { display: block; }
|
||||
.refund-record strong { color: #ff6b6b; }
|
||||
|
||||
/* 部分退款金额提示(可二次退款时显示) */
|
||||
.refund-progress {
|
||||
padding: 12px;
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.refund-progress__title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #fbbf24;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.refund-progress__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 3px 0;
|
||||
}
|
||||
.refund-progress__row strong {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.refund-progress__row--remain {
|
||||
margin-top: 4px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px dashed rgba(251, 191, 36, 0.3);
|
||||
}
|
||||
.refund-progress__row--remain strong { font-size: 15px; }
|
||||
|
||||
/* 关联信息 */
|
||||
.related-info {
|
||||
padding: 10px 12px;
|
||||
@@ -561,12 +660,12 @@
|
||||
<div class="card">
|
||||
<div class="order-head">
|
||||
<div>
|
||||
<div class="order-head__id">CO-20260806-031</div>
|
||||
<div class="order-head__id" id="orderIdLabel" data-status="normal">CO-20260806-031</div>
|
||||
<div style="font-size:11px;color:rgba(255,255,255,0.4);margin-top:4px;">
|
||||
下单:2026-08-06 12:15:30 · 支付:12:32:50
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-head__status">
|
||||
<div class="order-head__status" id="orderStatus">
|
||||
<span class="status-pill status-pill--normal">正常</span>
|
||||
<span class="status-pill status-pill--printed">已打印</span>
|
||||
</div>
|
||||
@@ -585,10 +684,6 @@
|
||||
<div class="info-item__label">收银员</div>
|
||||
<div class="info-item__value">张三 (CS001)</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">顾客类型</div>
|
||||
<div class="info-item__value">企业员工</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">订单件数</div>
|
||||
<div class="info-item__value">5 件</div>
|
||||
@@ -597,6 +692,31 @@
|
||||
<div class="info-item__label">订单总重</div>
|
||||
<div class="info-item__value">1040g</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">商品合计</div>
|
||||
<div class="info-item__value">¥46.60</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 顾客信息卡(展示用户类型/姓名/编号三项) -->
|
||||
<div class="card">
|
||||
<div class="card__title">顾客信息</div>
|
||||
<div class="customer-card" id="customerCard">
|
||||
<div class="customer-card__field">
|
||||
<span class="customer-card__label">用户类型</span>
|
||||
<span class="user-type user-type--member" id="customerUserType">会员用户</span>
|
||||
</div>
|
||||
<div class="customer-card__divider"></div>
|
||||
<div class="customer-card__field">
|
||||
<span class="customer-card__label">姓名</span>
|
||||
<span class="customer-card__value" id="customerUserName">雷二</span>
|
||||
</div>
|
||||
<div class="customer-card__divider"></div>
|
||||
<div class="customer-card__field">
|
||||
<span class="customer-card__label">编号</span>
|
||||
<span class="customer-card__value customer-card__value--code" id="customerUserCode">ER-566212</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -683,6 +803,23 @@
|
||||
<!-- 右列:操作面板 -->
|
||||
<div class="right-col">
|
||||
|
||||
<!-- 部分退款金额提示(部分退款订单显示) -->
|
||||
<div class="refund-progress" id="refundProgress" style="display:none;">
|
||||
<div class="refund-progress__title">⚠️ 已部分退款</div>
|
||||
<div class="refund-progress__row">
|
||||
<span>订单总额</span>
|
||||
<strong id="rpTotal">¥0.00</strong>
|
||||
</div>
|
||||
<div class="refund-progress__row">
|
||||
<span>已退金额</span>
|
||||
<strong id="rpRefunded" style="color:#ff6b6b;">¥0.00</strong>
|
||||
</div>
|
||||
<div class="refund-progress__row refund-progress__row--remain">
|
||||
<span>可退金额</span>
|
||||
<strong id="rpRemain" style="color:#fbbf24;">¥0.00</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 退款记录(已退款时显示) -->
|
||||
<div class="refund-record" id="refundRecord" style="display:none;">
|
||||
<div style="margin-bottom:6px;color:#ff6b6b;font-weight:600;">⚠️ 已退款</div>
|
||||
@@ -700,12 +837,6 @@
|
||||
<button class="btn btn--gold" onclick="window.location.href='09-receipt-preview.html'">
|
||||
🖨️ 重新打印小票
|
||||
</button>
|
||||
<button class="btn btn--ghost" onclick="alert('已发起补齐明细流程')">
|
||||
✏️ 补齐明细(漏记/多记)
|
||||
</button>
|
||||
<button class="btn btn--ghost" onclick="alert('订单已加入今日异常单列表')">
|
||||
⚠️ 标记为异常订单
|
||||
</button>
|
||||
<button class="btn btn--danger" onclick="toggleRefundPanel()">
|
||||
💸 申请退款
|
||||
</button>
|
||||
@@ -831,15 +962,21 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* 订单原金额 */
|
||||
/* 订单总额(原始应付) */
|
||||
var ORDER_PAYABLE = 41.60;
|
||||
/* 已退金额(部分退款场景下 > 0) */
|
||||
var ORDER_REFUNDED = 0;
|
||||
/* 可退金额 = 订单总额 - 已退金额 */
|
||||
function refundableAmount() {
|
||||
return Math.max(0, ORDER_PAYABLE - ORDER_REFUNDED);
|
||||
}
|
||||
/* 当前退款方式:full / partial */
|
||||
var currentRefundType = 'full';
|
||||
|
||||
function toggleRefundPanel() {
|
||||
var panel = document.getElementById('refundPanel');
|
||||
panel.classList.toggle('show');
|
||||
/* 默认重置为全额退款 */
|
||||
/* 默认重置为全额退款(全额 = 本次退完剩余可退金额) */
|
||||
if (panel.classList.contains('show')) {
|
||||
switchRefundType('full');
|
||||
}
|
||||
@@ -853,11 +990,27 @@
|
||||
});
|
||||
var wrap = document.getElementById('partialAmountWrap');
|
||||
wrap.classList.toggle('show', type === 'partial');
|
||||
/* 二次退款场景:更新全额退款的提示文案为「可退金额」 */
|
||||
var fullHint = document.querySelector('.refund-type__item[data-type="full"] .refund-type__hint');
|
||||
if (fullHint) {
|
||||
fullHint.textContent = '一键退回 ' + money(refundableAmount()) + ' 到原支付账户';
|
||||
}
|
||||
/* 全额退款的含义改为「退完剩余可退金额」 */
|
||||
var fullName = document.querySelector('.refund-type__item[data-type="full"] .refund-type__name');
|
||||
if (fullName) {
|
||||
fullName.textContent = ORDER_REFUNDED > 0 ? '退剩余全部' : '全额退款';
|
||||
}
|
||||
var partialHint = document.querySelector('.refund-type__item[data-type="partial"] .refund-type__hint');
|
||||
if (partialHint) {
|
||||
partialHint.textContent = '手动输入退款金额(最多 ' + money(refundableAmount()) + ')';
|
||||
}
|
||||
updateRefundDisplay();
|
||||
}
|
||||
|
||||
/* 部分退款:快捷金额 */
|
||||
/* 部分退款:快捷金额(基于可退金额) */
|
||||
function setPartial(v) {
|
||||
var remain = refundableAmount();
|
||||
if (v > remain) v = remain;
|
||||
document.getElementById('partialAmount').value = v.toFixed(2);
|
||||
updatePartialAmount();
|
||||
}
|
||||
@@ -865,11 +1018,12 @@
|
||||
function updatePartialAmount() {
|
||||
var input = document.getElementById('partialAmount');
|
||||
var v = parseFloat(input.value) || 0;
|
||||
/* 不超过订单应付 */
|
||||
if (v > ORDER_PAYABLE) {
|
||||
v = ORDER_PAYABLE;
|
||||
var remain = refundableAmount();
|
||||
/* 不超过可退金额 */
|
||||
if (v > remain) {
|
||||
v = remain;
|
||||
input.value = v.toFixed(2);
|
||||
showToast('退款金额不能超过应付金额 ¥' + ORDER_PAYABLE.toFixed(2), 'warn');
|
||||
showToast('本次退款不能超过可退金额 ' + money(remain), 'warn');
|
||||
}
|
||||
if (v < 0) {
|
||||
v = 0;
|
||||
@@ -880,8 +1034,48 @@
|
||||
|
||||
/* 更新退款金额显示 */
|
||||
function updateRefundDisplay() {
|
||||
var amount = currentRefundType === 'full' ? ORDER_PAYABLE : (parseFloat(document.getElementById('partialAmount').value) || 0);
|
||||
document.getElementById('refundAmountDisplay').innerHTML = '¥' + amount.toFixed(2);
|
||||
var amount = currentRefundType === 'full' ? refundableAmount() : (parseFloat(document.getElementById('partialAmount').value) || 0);
|
||||
document.getElementById('refundAmountDisplay').innerHTML = money(amount);
|
||||
}
|
||||
|
||||
/* 格式化金额为 ¥xx.xx
|
||||
* 注意:统一返回真实 ¥ 字符(¥),不要返回 HTML 实体字符串
|
||||
* 因为通过 textContent(如 toast)、原生 alert/confirm 弹窗、输入框 value 设置时,
|
||||
* HTML 实体会被当作字面量显示成 "¥" */
|
||||
function money(v) {
|
||||
return '¥' + Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
/* 渲染已退/可退金额提示块(部分退款场景) */
|
||||
function renderRefundProgress() {
|
||||
var box = document.getElementById('refundProgress');
|
||||
if (ORDER_REFUNDED > 0 && ORDER_REFUNDED < ORDER_PAYABLE) {
|
||||
document.getElementById('rpTotal').innerHTML = money(ORDER_PAYABLE);
|
||||
document.getElementById('rpRefunded').innerHTML = money(ORDER_REFUNDED);
|
||||
document.getElementById('rpRemain').innerHTML = money(refundableAmount());
|
||||
box.style.display = 'block';
|
||||
} else {
|
||||
box.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/* 渲染订单头状态徽章 */
|
||||
function renderOrderStatus(status) {
|
||||
var wrap = document.getElementById('orderStatus');
|
||||
if (!wrap) return;
|
||||
var pills = '';
|
||||
if (status === 'refunded') {
|
||||
pills = '<span class="status-pill status-pill--refunded">已退款</span>';
|
||||
} else if (status === 'partial') {
|
||||
pills = '<span class="status-pill status-pill--partial">部分退款</span>';
|
||||
if (typeof ORDER_PRINTED !== 'undefined' && ORDER_PRINTED) {
|
||||
pills += '<span class="status-pill status-pill--printed">已打印</span>';
|
||||
}
|
||||
} else {
|
||||
pills = '<span class="status-pill status-pill--normal">正常</span>'
|
||||
+ '<span class="status-pill status-pill--printed">已打印</span>';
|
||||
}
|
||||
wrap.innerHTML = pills;
|
||||
}
|
||||
|
||||
/* 退款原因选择 */
|
||||
@@ -907,20 +1101,27 @@
|
||||
}
|
||||
var reason = reasonEl.textContent.trim();
|
||||
var note = document.getElementById('refundNote').value.trim();
|
||||
var amount = currentRefundType === 'full' ? ORDER_PAYABLE : (parseFloat(document.getElementById('partialAmount').value) || 0);
|
||||
var remain = refundableAmount();
|
||||
var amount = currentRefundType === 'full' ? remain : (parseFloat(document.getElementById('partialAmount').value) || 0);
|
||||
|
||||
if (amount <= 0) {
|
||||
showToast('退款金额必须大于 0', 'warn');
|
||||
return;
|
||||
}
|
||||
if (amount > ORDER_PAYABLE) {
|
||||
showToast('退款金额不能超过应付金额', 'warn');
|
||||
if (amount > remain) {
|
||||
showToast('本次退款不能超过可退金额 ' + money(remain), 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
var typeText = currentRefundType === 'full' ? '全额退款' : '部分退款';
|
||||
/* 是否为二次退款(本次退款后是否还有剩余) */
|
||||
var afterRemain = remain - amount;
|
||||
var isSecond = ORDER_REFUNDED > 0;
|
||||
var typeText = currentRefundType === 'full' ? (ORDER_REFUNDED > 0 ? '退剩余全部' : '全额退款') : '部分退款';
|
||||
|
||||
var msg = '确认' + typeText + '?\n\n'
|
||||
+ '退款金额:¥' + amount.toFixed(2) + '\n'
|
||||
+ '本次退款金额:¥' + amount.toFixed(2) + '\n'
|
||||
+ (isSecond ? '已退金额:¥' + ORDER_REFUNDED.toFixed(2) + '\n' : '')
|
||||
+ '退款后剩余可退:¥' + afterRemain.toFixed(2) + '\n'
|
||||
+ '退款原因:' + reason + '\n'
|
||||
+ (note ? '备注:' + note + '\n' : '')
|
||||
+ '此操作不可撤销。';
|
||||
@@ -932,19 +1133,43 @@
|
||||
document.getElementById('normalActions').style.display = 'none';
|
||||
var record = document.getElementById('refundRecord');
|
||||
record.style.display = 'block';
|
||||
/* 累计已退金额 */
|
||||
var totalRefunded = ORDER_REFUNDED + amount;
|
||||
var stillPartial = totalRefunded < ORDER_PAYABLE;
|
||||
/* 渲染退款记录内容 */
|
||||
record.innerHTML =
|
||||
'<div style="margin-bottom:6px;color:#ff6b6b;font-weight:600;">⚠️ ' + typeText + '已发起</div>'
|
||||
+ '<div>退款金额:<strong>¥' + amount.toFixed(2) + '</strong> / ' + (currentRefundType === 'full' ? '原订单全额' : ('剩余 ¥' + (ORDER_PAYABLE - amount).toFixed(2) + ' 已结算')) + '</div>'
|
||||
'<div style="margin-bottom:6px;color:#ff6b6b;font-weight:600;">⚠️ ' + (stillPartial ? '部分退款' : '全额退款') + '已发起</div>'
|
||||
+ '<div>本次退款:<strong>¥' + amount.toFixed(2) + '</strong> · 累计已退:<strong>¥' + totalRefunded.toFixed(2) + '</strong></div>'
|
||||
+ '<div>订单总额:¥' + ORDER_PAYABLE.toFixed(2) + ' / 剩余可退:¥' + afterRemain.toFixed(2) + '</div>'
|
||||
+ '<div>退款方式:原路退回(微信)</div>'
|
||||
+ '<div>退款时间:2026-08-06 14:30:25</div>'
|
||||
+ '<div>操作人:张三 (CS001)</div>'
|
||||
+ '<div>退款原因:' + reason + '</div>'
|
||||
+ (note ? '<div>备注:' + note + '</div>' : '');
|
||||
/* 若仍可二次退款,展示「继续退款」入口 */
|
||||
if (stillPartial) {
|
||||
record.innerHTML += '<div style="margin-top:8px;text-align:center;">'
|
||||
+ '<button class="btn btn--danger" style="padding:8px 16px;font-size:12px;" onclick="resetForSecondRefund(' + afterRemain.toFixed(2) + ',' + totalRefunded.toFixed(2) + ')">继续退款剩余 ¥' + afterRemain.toFixed(2) + '</button>'
|
||||
+ '</div>';
|
||||
}
|
||||
}, 1200);
|
||||
}
|
||||
}
|
||||
|
||||
/* 二次退款:重置面板状态 */
|
||||
function resetForSecondRefund(newRemain, totalRefunded) {
|
||||
ORDER_REFUNDED = totalRefunded;
|
||||
document.getElementById('normalActions').style.display = 'block';
|
||||
var record = document.getElementById('refundRecord');
|
||||
record.style.display = 'none';
|
||||
/* 状态徽章更新为部分退款 */
|
||||
renderOrderStatus('partial');
|
||||
/* 已退/可退金额提示刷新 */
|
||||
renderRefundProgress();
|
||||
/* 重新打开退款面板 */
|
||||
toggleRefundPanel();
|
||||
}
|
||||
|
||||
function showToast(msg, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
@@ -967,6 +1192,28 @@
|
||||
return match ? decodeURIComponent(match) : null;
|
||||
}
|
||||
|
||||
/* 用户类型配置(与 05-orders 同款) */
|
||||
var USER_TYPE_CONFIG = {
|
||||
member: { label: '会员用户', cls: 'user-type--member' },
|
||||
temp: { label: '临时用户', cls: 'user-type--temp' },
|
||||
staff: { label: '内部员工', cls: 'user-type--staff' }
|
||||
};
|
||||
|
||||
/* 渲染顾客信息卡(由 URL 参数驱动,缺失则保留默认值) */
|
||||
function renderCustomerCard() {
|
||||
var userType = getQueryParam('userType');
|
||||
var userName = getQueryParam('userName');
|
||||
var userCode = getQueryParam('userCode');
|
||||
if (userName) document.getElementById('customerUserName').textContent = decodeURIComponent(userName);
|
||||
if (userCode) document.getElementById('customerUserCode').textContent = decodeURIComponent(userCode);
|
||||
if (userType && USER_TYPE_CONFIG[userType]) {
|
||||
var cfg = USER_TYPE_CONFIG[userType];
|
||||
var el = document.getElementById('customerUserType');
|
||||
el.textContent = cfg.label;
|
||||
el.className = 'user-type ' + cfg.cls;
|
||||
}
|
||||
}
|
||||
|
||||
(function autoInitFromUrl() {
|
||||
/* 同步订单号(若 URL 带了 id 参数,显示在订单头) */
|
||||
var orderId = getQueryParam('id');
|
||||
@@ -974,6 +1221,42 @@
|
||||
var idEl = document.querySelector('.order-head__id');
|
||||
if (idEl) idEl.textContent = orderId;
|
||||
}
|
||||
/* 渲染顾客信息卡 */
|
||||
renderCustomerCard();
|
||||
/* 订单状态 / 已退金额(用于演示部分退款场景)
|
||||
* status=partial → 显示「部分退款」徽章
|
||||
* refunded=<number> → 已退金额,影响可退金额计算
|
||||
* payable=<number> → 订单总额(可选,默认 41.60)
|
||||
* 例如:06-order-detail.html?id=CO-20260806-029&status=partial&refunded=10
|
||||
*/
|
||||
var status = getQueryParam('status') || 'normal';
|
||||
var payableParam = getQueryParam('payable');
|
||||
var refundedParam = getQueryParam('refunded');
|
||||
if (payableParam !== null) ORDER_PAYABLE = parseFloat(payableParam) || ORDER_PAYABLE;
|
||||
if (refundedParam !== null) ORDER_REFUNDED = Math.max(0, parseFloat(refundedParam) || 0);
|
||||
|
||||
/* 已退款:隐藏操作面板,显示退款记录 */
|
||||
if (status === 'refunded') {
|
||||
document.getElementById('normalActions').style.display = 'none';
|
||||
var record = document.getElementById('refundRecord');
|
||||
record.style.display = 'block';
|
||||
record.innerHTML =
|
||||
'<div style="margin-bottom:6px;color:#ff6b6b;font-weight:600;">⚠️ 已退款</div>'
|
||||
+ '<div>退款金额:<strong>' + money(ORDER_PAYABLE) + '</strong></div>'
|
||||
+ '<div>退款方式:原路退回(微信)</div>'
|
||||
+ '<div>退款时间:2026-08-06 13:05:12</div>'
|
||||
+ '<div>操作人:张三 (CS001)</div>'
|
||||
+ '<div>退款原因:顾客反馈菜品口味问题</div>';
|
||||
renderOrderStatus('refunded');
|
||||
return;
|
||||
}
|
||||
|
||||
/* 部分退款:更新状态徽章 + 显示已退/可退金额 */
|
||||
if (status === 'partial' && ORDER_REFUNDED > 0) {
|
||||
renderOrderStatus('partial');
|
||||
renderRefundProgress();
|
||||
}
|
||||
|
||||
var action = getQueryParam('action');
|
||||
/* 退款操作:自动展开退款面板并滚动到右侧 */
|
||||
if (action === 'refund') {
|
||||
@@ -989,7 +1272,7 @@
|
||||
/* 高亮提示一下退款面板 */
|
||||
panel.style.boxShadow = '0 0 0 3px rgba(245, 34, 45, 0.3)';
|
||||
setTimeout(function() { panel.style.boxShadow = ''; }, 1800);
|
||||
showToast('请在下方填写退款信息', 'warn');
|
||||
showToast(ORDER_REFUNDED > 0 ? '请继续填写二次退款信息' : '请在下方填写退款信息', 'warn');
|
||||
}, 200);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -29,14 +29,22 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
/* 顶部导航:标题 + Tab 切换 + 时间 */
|
||||
.top-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
height: 48px;
|
||||
background: #1a2b4a;
|
||||
padding: 0 20px;
|
||||
flex-shrink: 0;
|
||||
gap: 20px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.top-bar__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.back-btn {
|
||||
font-size: 13px;
|
||||
@@ -47,13 +55,37 @@
|
||||
}
|
||||
.back-btn:hover { color: #fff; }
|
||||
.top-bar__title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
/* 顶部内嵌 Tab(营收/餐线切换) */
|
||||
.top-tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.top-tab {
|
||||
padding: 6px 16px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
transition: all 0.15s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.top-tab:hover { color: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.04); }
|
||||
.top-tab.active {
|
||||
color: #fff;
|
||||
background: rgba(74, 144, 217, 0.18);
|
||||
font-weight: 600;
|
||||
}
|
||||
.top-bar__meta {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
@@ -108,6 +140,26 @@
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
}
|
||||
.meal-select option { background: #1a2b4a; color: #fff; }
|
||||
/* 自定义日期输入 */
|
||||
.date-input {
|
||||
padding: 6px 10px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
cursor: pointer;
|
||||
color-scheme: dark;
|
||||
}
|
||||
.date-input:focus { outline: none; border-color: #4a90d9; }
|
||||
.filter-bar__divider {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.filter-bar__right {
|
||||
display: flex;
|
||||
@@ -116,46 +168,6 @@
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
.export-btn {
|
||||
padding: 6px 12px;
|
||||
background: rgba(82, 196, 26, 0.15);
|
||||
border: 1px solid rgba(82, 196, 26, 0.3);
|
||||
border-radius: 6px;
|
||||
color: #4ade80;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.export-btn:hover { background: rgba(82, 196, 26, 0.25); }
|
||||
|
||||
/* 二级 Tab */
|
||||
.sub-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.sub-tab {
|
||||
padding: 8px 18px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.15s;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.sub-tab.active {
|
||||
color: #fff;
|
||||
border-bottom-color: #4a90d9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Tab 面板 */
|
||||
.tab-panel {
|
||||
@@ -496,16 +508,15 @@
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* 餐线主区:左明细表 + 右排行 */
|
||||
/* 餐线主区:明细表占满整行 */
|
||||
.line-main {
|
||||
display: grid;
|
||||
grid-template-columns: 1.6fr 1fr;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.line-table-card,
|
||||
.rank-card {
|
||||
.line-table-card {
|
||||
background: rgba(26, 43, 74, 0.4);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 12px;
|
||||
@@ -513,6 +524,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 表头 + 表体共用 table-layout,保证列对齐 */
|
||||
@@ -528,22 +541,38 @@
|
||||
table-layout: fixed;
|
||||
}
|
||||
.line-table thead th {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
text-align: right;
|
||||
padding: 6px 6px;
|
||||
padding: 8px 6px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #1a2b4a;
|
||||
z-index: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.line-table thead th:first-child,
|
||||
.line-table thead th:nth-child(2),
|
||||
.line-table thead th:nth-child(3) { text-align: left; }
|
||||
/* 第一列(营养秤编号)和第二列(菜品)左对齐 */
|
||||
.line-table thead th:nth-child(1),
|
||||
.line-table thead th:nth-child(2) { text-align: left; }
|
||||
/* 可排序列:鼠标手型 + 排序图标 */
|
||||
.line-table thead th.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.line-table thead th.sortable:hover { color: #fff; }
|
||||
.line-table thead th.sortable .sort-ico {
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
font-size: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.line-table thead th.sortable.asc .sort-ico { color: #4a90d9; }
|
||||
.line-table thead th.sortable.desc .sort-ico { color: #4a90d9; }
|
||||
.line-table tbody td {
|
||||
padding: 8px 6px;
|
||||
padding: 7px 6px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
text-align: right;
|
||||
color: #fff;
|
||||
@@ -552,43 +581,26 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
.line-table tbody td:first-child,
|
||||
.line-table tbody td:nth-child(2),
|
||||
.line-table tbody td:nth-child(3) { text-align: left; font-family: -apple-system, "Microsoft YaHei", sans-serif; }
|
||||
.line-table tbody td:nth-child(2) { text-align: left; }
|
||||
.line-table tbody td:nth-child(2) { font-family: -apple-system, "Microsoft YaHei", sans-serif; }
|
||||
.line-table tbody tr:hover { background: rgba(255, 255, 255, 0.02); }
|
||||
.line-table__rank {
|
||||
width: 30px;
|
||||
color: rgba(255, 255, 255, 0.6) !important;
|
||||
font-weight: 700;
|
||||
font-family: Menlo, Consolas, monospace !important;
|
||||
}
|
||||
.line-table__dish {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.line-table__thumb {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(135deg, rgba(74, 144, 217, 0.2), rgba(10, 196, 168, 0.2));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.line-table__name { font-size: 12px; color: #fff; }
|
||||
.line-table__type {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
font-size: 10px;
|
||||
border-radius: 4px;
|
||||
font-family: -apple-system, "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.type--staple { background: rgba(251, 191, 36, 0.18); color: #fbbf24; }
|
||||
.type--meat { background: rgba(255, 107, 107, 0.18); color: #ff8888; }
|
||||
.type--veg { background: rgba(82, 196, 26, 0.18); color: #4ade80; }
|
||||
.type--soup { background: rgba(74, 144, 217, 0.18); color: #60a5fa; }
|
||||
.diff {
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -596,120 +608,30 @@
|
||||
.diff--ok { color: #4ade80; }
|
||||
.diff--mid { color: #fbbf24; }
|
||||
.diff--zero { color: rgba(255, 255, 255, 0.45); }
|
||||
.progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.progress__track {
|
||||
width: 60px;
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress__fill {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(90deg, #52c41a, #0AC4A8);
|
||||
}
|
||||
.progress__fill--low { background: linear-gradient(90deg, #ff6b6b, #f87171); }
|
||||
.progress__fill--mid { background: linear-gradient(90deg, #fbbf24, #fb923c); }
|
||||
.progress__text {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
/* 销售进度:仅百分比,无进度条 */
|
||||
.progress-text-only {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
.progress-text-only--low { color: #ff6b6b; }
|
||||
.progress-text-only--mid { color: #fbbf24; }
|
||||
.progress-text-only--ok { color: #4ade80; }
|
||||
|
||||
/* 滚动条美化 */
|
||||
.line-table-wrap::-webkit-scrollbar { width: 6px; }
|
||||
.line-table-wrap::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.12); border-radius: 3px; }
|
||||
|
||||
/* 销量排行 */
|
||||
.rank-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.rank-item {
|
||||
display: grid;
|
||||
grid-template-columns: 22px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.04);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.rank-item__rank {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
}
|
||||
.rank-item__rank--1 { color: #fbbf24; font-size: 14px; }
|
||||
.rank-item__rank--2 { color: #cbd5e1; font-size: 14px; }
|
||||
.rank-item__rank--3 { color: #fb923c; font-size: 14px; }
|
||||
.rank-item__main { min-width: 0; }
|
||||
.rank-item__name {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.rank-item__bar-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 10px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
}
|
||||
.rank-item__bar {
|
||||
flex: 1;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rank-item__bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4a90d9, #60a5fa);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.rank-item__bar-fill--gold { background: linear-gradient(90deg, #fbbf24, #fb923c); }
|
||||
.rank-item__count {
|
||||
font-size: 13px;
|
||||
color: #4ade80;
|
||||
font-weight: 700;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
}
|
||||
.rank-item__count-unit {
|
||||
font-size: 10px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 人员类型横条 */
|
||||
.ppl-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 4px 4px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.ppl-row {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr 80px;
|
||||
grid-template-columns: 72px 1fr 110px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
@@ -732,48 +654,76 @@
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 维度切换 pill */
|
||||
.dim-switch {
|
||||
display: inline-flex;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 6px;
|
||||
padding: 2px;
|
||||
gap: 2px;
|
||||
}
|
||||
.dim-switch__btn {
|
||||
padding: 3px 10px;
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.dim-switch__btn.active {
|
||||
background: rgba(74, 144, 217, 0.3);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="device-frame">
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<!-- 顶部导航:返回 + 标题 + Tab + 时间 -->
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="index.html">◀ 返回主页</a>
|
||||
<span class="top-bar__title">运营数据统计</span>
|
||||
<div class="top-bar__left">
|
||||
<a class="back-btn" href="index.html">◀ 返回主页</a>
|
||||
<span class="top-bar__title">运营数据统计</span>
|
||||
<div class="top-tabs">
|
||||
<button class="top-tab active" data-tab="revenue">📊 营收统计</button>
|
||||
<button class="top-tab" data-tab="line">🍲 餐线统计</button>
|
||||
</div>
|
||||
</div>
|
||||
<span class="top-bar__meta">2026-08-06 14:25</span>
|
||||
</div>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<div class="main-area">
|
||||
|
||||
<!-- 筛选条:日期 + 餐次 + 导出 -->
|
||||
<!-- 筛选条:日期快捷 + 自定义日期 + 餐次 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-bar__left">
|
||||
<div class="date-tabs">
|
||||
<button class="date-tab active">当前餐次</button>
|
||||
<button class="date-tab">今日全部</button>
|
||||
<button class="date-tab">昨日</button>
|
||||
<button class="date-tab">本周</button>
|
||||
<button class="date-tab">自定义</button>
|
||||
<button class="date-tab active" data-range="current">当前餐次</button>
|
||||
<button class="date-tab" data-range="today">今日全部</button>
|
||||
<button class="date-tab" data-range="yesterday">昨日</button>
|
||||
</div>
|
||||
<button class="meal-select">🍽 午餐 ▼</button>
|
||||
<div class="filter-bar__divider"></div>
|
||||
<input type="date" class="date-input" id="customDate" value="2026-08-06" />
|
||||
<select class="meal-select" id="mealSelect">
|
||||
<option value="breakfast">🌅 早餐</option>
|
||||
<option value="lunch" selected>🍽 午餐</option>
|
||||
<option value="dinner">🌙 晚餐</option>
|
||||
<option value="all">全天全部</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-bar__right">
|
||||
<span>数据更新:14:25:30</span>
|
||||
<a class="export-btn" href="javascript:void(0)" onclick="alert('已导出午餐餐次运营数据报表')">
|
||||
📥 导出报表
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二级 Tab -->
|
||||
<div class="sub-tabs">
|
||||
<button class="sub-tab active" data-tab="revenue">📊 营收统计</button>
|
||||
<button class="sub-tab" data-tab="line">🍲 餐线统计</button>
|
||||
</div>
|
||||
|
||||
<!-- ===== Tab 1: 营收统计 ===== -->
|
||||
<div class="tab-panel active" data-panel="revenue">
|
||||
|
||||
@@ -957,31 +907,14 @@
|
||||
<div class="chart-card">
|
||||
<div class="chart-card__title">
|
||||
<span class="chart-card__title-text">就餐人员类型分布</span>
|
||||
<span class="chart-card__hint">按订单数</span>
|
||||
<div class="dim-switch" id="pplDimSwitch">
|
||||
<button class="dim-switch__btn active" data-dim="order">按订单</button>
|
||||
<button class="dim-switch__btn" data-dim="amount">按金额</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card__body">
|
||||
<div class="ppl-bars">
|
||||
<div class="ppl-row">
|
||||
<span class="ppl-row__label">企业员工</span>
|
||||
<div class="ppl-row__track">
|
||||
<div class="ppl-row__fill" style="width:60%; background:linear-gradient(90deg,#4a90d9,#60a5fa);"></div>
|
||||
</div>
|
||||
<span class="ppl-row__value">58 单 · 60%</span>
|
||||
</div>
|
||||
<div class="ppl-row">
|
||||
<span class="ppl-row__label">外部访客</span>
|
||||
<div class="ppl-row__track">
|
||||
<div class="ppl-row__fill" style="width:30%; background:linear-gradient(90deg,#fbbf24,#fb923c);"></div>
|
||||
</div>
|
||||
<span class="ppl-row__value">29 单 · 30%</span>
|
||||
</div>
|
||||
<div class="ppl-row">
|
||||
<span class="ppl-row__label">临时人员</span>
|
||||
<div class="ppl-row__track">
|
||||
<div class="ppl-row__fill" style="width:10%; background:linear-gradient(90deg,#52c41a,#0AC4A8);"></div>
|
||||
</div>
|
||||
<span class="ppl-row__value">9 单 · 10%</span>
|
||||
</div>
|
||||
<div class="ppl-bars" id="pplBars">
|
||||
<!-- 由 JS 动态渲染 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1017,247 +950,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 异常提示条 -->
|
||||
<div class="alert-bar">
|
||||
<span class="alert-bar__icon">⚠️</span>
|
||||
<span>检测到 <span class="alert-bar__count">4</span> 道菜品产销差异 > 0.3 kg(疑似损耗/丢失):</span>
|
||||
<span class="alert-bar__dishes">宫保鸡丁、清蒸鲈鱼、香菇鸡汤、凉拌黄瓜</span>
|
||||
</div>
|
||||
|
||||
<!-- 主区:左明细表 + 右排行 -->
|
||||
<!-- 主区:明细表占满整行 -->
|
||||
<div class="line-main">
|
||||
|
||||
<!-- 餐线明细表 -->
|
||||
<div class="line-table-card">
|
||||
<div class="chart-card__title">
|
||||
<span class="chart-card__title-text">餐线明细(制作 / 销售 / 剩余)</span>
|
||||
<span class="chart-card__hint">午餐餐次 · 按销量降序 · 单位 kg</span>
|
||||
<span class="chart-card__title-text">餐线明细(制作 / 销售 / 取餐 / 产销差额)</span>
|
||||
<span class="chart-card__hint">午餐餐次 · 点击列头排序</span>
|
||||
</div>
|
||||
<div class="line-table-wrap">
|
||||
<table class="line-table">
|
||||
<table class="line-table" id="lineTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30px;">#</th>
|
||||
<th style="width:28%;">菜品</th>
|
||||
<th style="width:13%;">类型</th>
|
||||
<th>制作量</th>
|
||||
<th>已售</th>
|
||||
<th>剩余</th>
|
||||
<th>产销差值</th>
|
||||
<th style="width:130px;">销售进度</th>
|
||||
</tr>
|
||||
<!--
|
||||
产销差值 = 制作量 − 已售 − 剩余
|
||||
正数:疑似损耗/丢失(红色)
|
||||
0 :账实一致(灰色)
|
||||
负数:数据异常(黄色)
|
||||
-->
|
||||
<th class="sortable" data-key="scaleNo">营养秤编号<span class="sort-ico">▲▼</span></th>
|
||||
<th style="width:17%;">菜品</th>
|
||||
<th class="sortable" data-key="salesAmount">销售额<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable" data-key="made">制作量<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable" data-key="sold">已售<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable" data-key="left">剩余<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable" data-key="pickupTimes">取餐次数<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable" data-key="pickupPeople">取餐人数<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable" data-key="diffKg">产销差值<span class="sort-ico">▲▼</span></th>
|
||||
<th class="sortable desc" data-key="diff">产销差额<span class="sort-ico">▼</span></th>
|
||||
<th class="sortable" data-key="progress">销售进度<span class="sort-ico">▲▼</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="line-table__rank">1</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍚</div><span class="line-table__name">米饭</span></div></td>
|
||||
<td><span class="line-table__type type--staple">主食</span></td>
|
||||
<td>12.0</td><td>9.8</td><td>2.0</td>
|
||||
<td><span class="diff diff--warn">+0.2</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill" style="width:82%;"></div></div>
|
||||
<span class="progress__text">82%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">2</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍖</div><span class="line-table__name">红烧肉</span></div></td>
|
||||
<td><span class="line-table__type type--meat">荤菜</span></td>
|
||||
<td>8.0</td><td>6.2</td><td>1.8</td>
|
||||
<td><span class="diff diff--zero">0.0</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill" style="width:78%;"></div></div>
|
||||
<span class="progress__text">78%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">3</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🥦</div><span class="line-table__name">清炒时蔬</span></div></td>
|
||||
<td><span class="line-table__type type--veg">素菜</span></td>
|
||||
<td>7.0</td><td>5.4</td><td>1.4</td>
|
||||
<td><span class="diff diff--warn">+0.2</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill" style="width:77%;"></div></div>
|
||||
<span class="progress__text">77%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">4</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍟</div><span class="line-table__name">鱼香肉丝</span></div></td>
|
||||
<td><span class="line-table__type type--meat">荤菜</span></td>
|
||||
<td>6.0</td><td>4.8</td><td>1.2</td>
|
||||
<td><span class="diff diff--zero">0.0</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill" style="width:80%;"></div></div>
|
||||
<span class="progress__text">80%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">5</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍲</div><span class="line-table__name">冬瓜排骨汤</span></div></td>
|
||||
<td><span class="line-table__type type--soup">汤品</span></td>
|
||||
<td>5.0</td><td>4.2</td><td>0.9</td>
|
||||
<td><span class="diff diff--mid">-0.1</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill" style="width:84%;"></div></div>
|
||||
<span class="progress__text">84%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">6</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍛</div><span class="line-table__name">宫保鸡丁</span></div></td>
|
||||
<td><span class="line-table__type type--meat">荤菜</span></td>
|
||||
<td>9.0</td><td>3.6</td><td>4.8</td>
|
||||
<td><span class="diff diff--warn">+0.6</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill progress__fill--low" style="width:40%;"></div></div>
|
||||
<span class="progress__text">40%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">7</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍤</div><span class="line-table__name">清蒸鲈鱼</span></div></td>
|
||||
<td><span class="line-table__type type--meat">荤菜</span></td>
|
||||
<td>6.0</td><td>1.6</td><td>3.8</td>
|
||||
<td><span class="diff diff--warn">+0.6</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill progress__fill--low" style="width:27%;"></div></div>
|
||||
<span class="progress__text">27%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">8</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🥝</div><span class="line-table__name">香菇鸡汤</span></div></td>
|
||||
<td><span class="line-table__type type--soup">汤品</span></td>
|
||||
<td>8.0</td><td>2.8</td><td>4.8</td>
|
||||
<td><span class="diff diff--warn">+0.4</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill progress__fill--low" style="width:35%;"></div></div>
|
||||
<span class="progress__text">35%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">9</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🍠</div><span class="line-table__name">麻婆豆腐</span></div></td>
|
||||
<td><span class="line-table__type type--meat">荤菜</span></td>
|
||||
<td>7.0</td><td>4.4</td><td>2.6</td>
|
||||
<td><span class="diff diff--zero">0.0</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill progress__fill--mid" style="width:63%;"></div></div>
|
||||
<span class="progress__text">63%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="line-table__rank">10</td>
|
||||
<td><div class="line-table__dish"><div class="line-table__thumb">🥒</div><span class="line-table__name">凉拌黄瓜</span></div></td>
|
||||
<td><span class="line-table__type type--veg">素菜</span></td>
|
||||
<td>3.0</td><td>0.8</td><td>1.8</td>
|
||||
<td><span class="diff diff--warn">+0.4</span></td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress__track"><div class="progress__fill progress__fill--low" style="width:27%;"></div></div>
|
||||
<span class="progress__text">27%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody id="lineTableBody">
|
||||
<!-- 由 JS 动态渲染 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 销量排行 Top 5 -->
|
||||
<div class="rank-card">
|
||||
<div class="chart-card__title">
|
||||
<span class="chart-card__title-text">菜品销量排行 Top 5</span>
|
||||
<span class="chart-card__hint">按已售 kg</span>
|
||||
</div>
|
||||
<div class="rank-list">
|
||||
<div class="rank-item">
|
||||
<span class="rank-item__rank rank-item__rank--1">1</span>
|
||||
<div class="rank-item__main">
|
||||
<div class="rank-item__name">米饭</div>
|
||||
<div class="rank-item__bar-wrap">
|
||||
<span>制作 12.0</span>
|
||||
<div class="rank-item__bar"><div class="rank-item__bar-fill rank-item__bar-fill--gold" style="width:100%;"></div></div>
|
||||
<span>82%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rank-item__count">9.8<span class="rank-item__count-unit"> kg</span></span>
|
||||
</div>
|
||||
<div class="rank-item">
|
||||
<span class="rank-item__rank rank-item__rank--2">2</span>
|
||||
<div class="rank-item__main">
|
||||
<div class="rank-item__name">红烧肉</div>
|
||||
<div class="rank-item__bar-wrap">
|
||||
<span>制作 8.0</span>
|
||||
<div class="rank-item__bar"><div class="rank-item__bar-fill" style="width:63%;"></div></div>
|
||||
<span>78%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rank-item__count">6.2<span class="rank-item__count-unit"> kg</span></span>
|
||||
</div>
|
||||
<div class="rank-item">
|
||||
<span class="rank-item__rank rank-item__rank--3">3</span>
|
||||
<div class="rank-item__main">
|
||||
<div class="rank-item__name">清炒时蔬</div>
|
||||
<div class="rank-item__bar-wrap">
|
||||
<span>制作 7.0</span>
|
||||
<div class="rank-item__bar"><div class="rank-item__bar-fill" style="width:55%;"></div></div>
|
||||
<span>77%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rank-item__count">5.4<span class="rank-item__count-unit"> kg</span></span>
|
||||
</div>
|
||||
<div class="rank-item">
|
||||
<span class="rank-item__rank">4</span>
|
||||
<div class="rank-item__main">
|
||||
<div class="rank-item__name">鱼香肉丝</div>
|
||||
<div class="rank-item__bar-wrap">
|
||||
<span>制作 6.0</span>
|
||||
<div class="rank-item__bar"><div class="rank-item__bar-fill" style="width:49%;"></div></div>
|
||||
<span>80%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rank-item__count">4.8<span class="rank-item__count-unit"> kg</span></span>
|
||||
</div>
|
||||
<div class="rank-item">
|
||||
<span class="rank-item__rank">5</span>
|
||||
<div class="rank-item__main">
|
||||
<div class="rank-item__name">冬瓜排骨汤</div>
|
||||
<div class="rank-item__bar-wrap">
|
||||
<span>制作 5.0</span>
|
||||
<div class="rank-item__bar"><div class="rank-item__bar-fill" style="width:43%;"></div></div>
|
||||
<span>84%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rank-item__count">4.2<span class="rank-item__count-unit"> kg</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 异常提示条(移到列表下方) -->
|
||||
<div class="alert-bar" id="lineAlertBar">
|
||||
<span class="alert-bar__icon">⚠️</span>
|
||||
<span>检测到 <span class="alert-bar__count" id="alertCount">4</span> 道菜品产销差额 > 10元(疑似损耗/丢失):</span>
|
||||
<span class="alert-bar__dishes" id="alertDishes">宫保鸡丁、清蒸鲈鱼、香菇鸡汤、凉拌黄瓜</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1268,20 +998,203 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 二级 Tab 切换
|
||||
document.querySelectorAll('.sub-tab').forEach(btn => {
|
||||
// 顶部 Tab 切换(营收/餐线)
|
||||
document.querySelectorAll('.top-tab').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tab = btn.dataset.tab;
|
||||
document.querySelectorAll('.sub-tab').forEach(b => b.classList.toggle('active', b === btn));
|
||||
document.querySelectorAll('.top-tab').forEach(b => b.classList.toggle('active', b === btn));
|
||||
document.querySelectorAll('.tab-panel').forEach(p => {
|
||||
p.classList.toggle('active', p.dataset.panel === tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
// 日期 Tab 切换(单选样式)
|
||||
// 日期快捷 Tab 切换(单选样式 + 同步日期输入)
|
||||
document.querySelectorAll('.date-tab').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.date-tab').forEach(b => b.classList.toggle('active', b === btn));
|
||||
const range = btn.dataset.range;
|
||||
const today = '2026-08-06';
|
||||
const yesterday = '2026-08-05';
|
||||
const input = document.getElementById('customDate');
|
||||
if (range === 'today' || range === 'current') input.value = today;
|
||||
else if (range === 'yesterday') input.value = yesterday;
|
||||
});
|
||||
});
|
||||
// 自定义日期变化时,取消快捷 Tab 选中
|
||||
document.getElementById('customDate').addEventListener('change', e => {
|
||||
const val = e.target.value;
|
||||
const today = '2026-08-06';
|
||||
const yesterday = '2026-08-05';
|
||||
let matched = null;
|
||||
if (val === today) matched = document.querySelector('[data-range="today"]');
|
||||
else if (val === yesterday) matched = document.querySelector('[data-range="yesterday"]');
|
||||
document.querySelectorAll('.date-tab').forEach(b => b.classList.toggle('active', b === matched));
|
||||
});
|
||||
// 餐次下拉变化提示
|
||||
document.getElementById('mealSelect').addEventListener('change', e => {
|
||||
const label = e.target.options[e.target.selectedIndex].textContent.trim();
|
||||
// 原型演示:仅做提示
|
||||
});
|
||||
|
||||
// ===== 就餐人员类型分布:四分类 + 订单/金额维度切换 =====
|
||||
const PPL_DATA = {
|
||||
order: [
|
||||
{ label: '会员用户', value: 42, unit: '单', percent: 44, color: 'linear-gradient(90deg,#fbbf24,#fb923c)' },
|
||||
{ label: '内部员工', value: 30, unit: '单', percent: 31, color: 'linear-gradient(90deg,#4a90d9,#60a5fa)' },
|
||||
{ label: '普通用户', value: 15, unit: '单', percent: 16, color: 'linear-gradient(90deg,#a855f7,#c084fc)' },
|
||||
{ label: '临时用户', value: 9, unit: '单', percent: 9, color: 'linear-gradient(90deg,#52c41a,#0AC4A8)' }
|
||||
],
|
||||
amount: [
|
||||
{ label: '会员用户', value: 1158.50, unit: '元', percent: 44, color: 'linear-gradient(90deg,#fbbf24,#fb923c)' },
|
||||
{ label: '内部员工', value: 815.40, unit: '元', percent: 31, color: 'linear-gradient(90deg,#4a90d9,#60a5fa)' },
|
||||
{ label: '普通用户', value: 418.20, unit: '元', percent: 16, color: 'linear-gradient(90deg,#a855f7,#c084fc)' },
|
||||
{ label: '临时用户', value: 222.40, unit: '元', percent: 9, color: 'linear-gradient(90deg,#52c41a,#0AC4A8)' }
|
||||
]
|
||||
};
|
||||
function formatPplValue(val, unit) {
|
||||
if (unit === '元') {
|
||||
return '¥' + val.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' · ';
|
||||
}
|
||||
return val + ' ' + unit + ' · ';
|
||||
}
|
||||
function renderPplBars(dim) {
|
||||
const list = PPL_DATA[dim];
|
||||
const wrap = document.getElementById('pplBars');
|
||||
wrap.innerHTML = list.map(it => `
|
||||
<div class="ppl-row">
|
||||
<span class="ppl-row__label">${it.label}</span>
|
||||
<div class="ppl-row__track">
|
||||
<div class="ppl-row__fill" style="width:${it.percent}%; background:${it.color};"></div>
|
||||
</div>
|
||||
<span class="ppl-row__value">${formatPplValue(it.value, it.unit)}${it.percent}%</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
renderPplBars('order');
|
||||
document.querySelectorAll('#pplDimSwitch .dim-switch__btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('#pplDimSwitch .dim-switch__btn').forEach(b => b.classList.toggle('active', b === btn));
|
||||
renderPplBars(btn.dataset.dim);
|
||||
});
|
||||
});
|
||||
|
||||
// ===== 餐线明细表:动态渲染 + 列头排序 =====
|
||||
// 字段说明:
|
||||
// made(制作量 kg) / sold(已售 kg) / left(剩余 kg)
|
||||
// salesAmount(销售额 元)= sold × 单价(简化为 mock)
|
||||
// pickupTimes(取餐次数) / pickupPeople(取餐人数)
|
||||
// scaleNo(营养秤编号) / diff(产销差额 元)
|
||||
// progress(销售进度 %) = sold / made
|
||||
// 产销差额:由原 kg 改为元,负数=数据异常,正数=损耗丢失
|
||||
const LINE_DATA = [
|
||||
{ name:'米饭', emoji:'🍚', made:12.0, sold:9.8, left:2.0, price:3.0, pickupTimes:42, pickupPeople:38, scaleNo:'SC-01' },
|
||||
{ name:'红烧肉', emoji:'🍖', made:8.0, sold:6.2, left:1.8, price:38.0, pickupTimes:24, pickupPeople:22, scaleNo:'SC-02' },
|
||||
{ name:'清炒时蔬', emoji:'🥦', made:7.0, sold:5.4, left:1.4, price:12.0, pickupTimes:22, pickupPeople:20, scaleNo:'SC-03' },
|
||||
{ name:'鱼香肉丝', emoji:'🍚', made:6.0, sold:4.8, left:1.2, price:28.0, pickupTimes:18, pickupPeople:16, scaleNo:'SC-02' },
|
||||
{ name:'冬瓜排骨汤', emoji:'🍲', made:5.0, sold:4.2, left:0.9, price:15.0, pickupTimes:16, pickupPeople:15, scaleNo:'SC-04' },
|
||||
{ name:'宫保鸡丁', emoji:'🍗', made:9.0, sold:3.6, left:4.8, price:32.0, pickupTimes:14, pickupPeople:12, scaleNo:'SC-05' },
|
||||
{ name:'清蒸鲈鱼', emoji:'🐟', made:6.0, sold:1.6, left:3.8, price:68.0, pickupTimes:6, pickupPeople:6, scaleNo:'SC-05' },
|
||||
{ name:'香菇鸡汤', emoji:'🥣', made:8.0, sold:2.8, left:4.8, price:25.0, pickupTimes:10, pickupPeople:9, scaleNo:'SC-04' },
|
||||
{ name:'麻婆豆腐', emoji:'🌶️', made:7.0, sold:4.4, left:2.6, price:18.0, pickupTimes:17, pickupPeople:15, scaleNo:'SC-03' },
|
||||
{ name:'凉拌黄瓜', emoji:'🥒', made:3.0, sold:0.8, left:1.8, price:8.0, pickupTimes:4, pickupPeople:4, scaleNo:'SC-06' }
|
||||
];
|
||||
// 计算销售额 + 产销差值(kg) + 产销差额(元) + 销售进度
|
||||
LINE_DATA.forEach(it => {
|
||||
it.salesAmount = +(it.sold * it.price).toFixed(2);
|
||||
// 产销差值(kg)= 制作量 − 已售 − 剩余
|
||||
it.diffKg = +(it.made - it.sold - it.left).toFixed(1);
|
||||
// 产销差额(元)= 产销差值 × 单价
|
||||
it.diff = +(it.diffKg * it.price).toFixed(2);
|
||||
it.progress = Math.round((it.sold / it.made) * 100);
|
||||
});
|
||||
let sortKey = 'diff';
|
||||
let sortDir = 'desc';
|
||||
function fmtMoney(n) {
|
||||
return '¥' + n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
function diffClass(v) {
|
||||
if (v > 0) return 'diff--warn';
|
||||
if (v < 0) return 'diff--mid';
|
||||
return 'diff--zero';
|
||||
}
|
||||
function diffSign(v) {
|
||||
if (v > 0) return '+';
|
||||
if (v < 0) return '-';
|
||||
return '';
|
||||
}
|
||||
function progressClass(p) {
|
||||
if (p < 50) return 'progress-text-only--low';
|
||||
if (p < 75) return 'progress-text-only--mid';
|
||||
return 'progress-text-only--ok';
|
||||
}
|
||||
function renderLineTable() {
|
||||
// 排序
|
||||
const sorted = [...LINE_DATA].sort((a, b) => {
|
||||
const av = a[sortKey];
|
||||
const bv = b[sortKey];
|
||||
// 字符串字段(scaleNo)按字母序
|
||||
if (typeof av === 'string' || typeof bv === 'string') {
|
||||
return sortDir === 'asc'
|
||||
? String(av).localeCompare(String(bv))
|
||||
: String(bv).localeCompare(String(av));
|
||||
}
|
||||
return sortDir === 'asc' ? av - bv : bv - av;
|
||||
});
|
||||
const tbody = document.getElementById('lineTableBody');
|
||||
tbody.innerHTML = sorted.map(it => {
|
||||
const dCls = diffClass(it.diff);
|
||||
const dSign = diffSign(it.diff);
|
||||
const dkCls = diffClass(it.diffKg);
|
||||
const dkSign = diffSign(it.diffKg);
|
||||
const pCls = progressClass(it.progress);
|
||||
return `
|
||||
<tr>
|
||||
<td>${it.scaleNo}</td>
|
||||
<td>
|
||||
<div class="line-table__dish">
|
||||
<div class="line-table__thumb">${it.emoji}</div>
|
||||
<span class="line-table__name">${it.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>${fmtMoney(it.salesAmount)}</td>
|
||||
<td>${it.made.toFixed(1)}</td>
|
||||
<td>${it.sold.toFixed(1)}</td>
|
||||
<td>${it.left.toFixed(1)}</td>
|
||||
<td>${it.pickupTimes}</td>
|
||||
<td>${it.pickupPeople}</td>
|
||||
<td><span class="diff ${dkCls}">${dkSign}${Math.abs(it.diffKg).toFixed(1)} kg</span></td>
|
||||
<td><span class="diff ${dCls}">${dSign}${fmtMoney(Math.abs(it.diff))}</span></td>
|
||||
<td><span class="progress-text-only ${pCls}">${it.progress}%</span></td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
// 更新表头排序状态
|
||||
document.querySelectorAll('#lineTable thead th.sortable').forEach(th => {
|
||||
th.classList.remove('asc', 'desc');
|
||||
const ico = th.querySelector('.sort-ico');
|
||||
if (th.dataset.key === sortKey) {
|
||||
th.classList.add(sortDir);
|
||||
ico.textContent = sortDir === 'asc' ? '▲' : '▼';
|
||||
} else {
|
||||
ico.textContent = '▲▼';
|
||||
}
|
||||
});
|
||||
// 更新异常提示条:产销差额 > 10元
|
||||
const warnItems = LINE_DATA.filter(it => it.diff >= 10).sort((a, b) => b.diff - a.diff);
|
||||
document.getElementById('alertCount').textContent = warnItems.length;
|
||||
document.getElementById('alertDishes').textContent = warnItems.map(it => it.name).join('、') || '无';
|
||||
}
|
||||
renderLineTable();
|
||||
document.querySelectorAll('#lineTable thead th.sortable').forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
const key = th.dataset.key;
|
||||
if (sortKey === key) {
|
||||
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortKey = key;
|
||||
sortDir = 'desc';
|
||||
}
|
||||
renderLineTable();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,309 @@
|
||||
# 结算设备 CHANGELOG
|
||||
|
||||
## 2026-08-10 | 订单列表(05)+订单详情(06)增加用户类型展示
|
||||
|
||||
### 业务背景
|
||||
原订单列表(`05-orders.html`)仅展示「用户姓名|编号」一列,收银员无法在订单列表层面快速识别订单的顾客构成(会员 / 临时 / 内部员工),不便于做营收细分查询。订单详情页(`06-order-detail.html`)也没有独立的顾客信息区块,需要从订单头信息网格里翻找,且原信息网格里的「顾客类型=企业员工」字段语义与新分类体系(会员/临时/内部员工)不一致,数据陈旧。
|
||||
|
||||
### 变更
|
||||
- **`05-orders.html`**:
|
||||
- **新增「用户类型」列**:位于「用户信息」列右侧,展示紧凑型徽标(与 03-checkout 的 user-type 同款配色)
|
||||
- `member` 会员用户 → 金蓝渐变金边
|
||||
- `temp` 临时用户 → 灰底
|
||||
- `staff` 内部员工 → 蓝底
|
||||
- mock 数据每条订单新增 `userType` 字段,5 条订单覆盖三种类型(2 会员 / 2 内部员工 / 1 临时)
|
||||
- **列宽重新分配**(10 列 → 11 列,总宽不变):从原部分列(用户信息/状态/餐次/流水号/支付时间/操作)各压缩 2-15px,腾出 92px 给用户类型列
|
||||
- 跳转 URL 新增 `userType` / `userName` / `userCode` 三个参数,详情页据此渲染顾客信息卡
|
||||
- **`06-order-detail.html`**:
|
||||
- **新增「顾客信息」卡片**:位于订单头卡片下方、商品明细上方,仅展示三项:
|
||||
- 用户类型(徽标样式与列表一致)
|
||||
- 姓名
|
||||
- 编号(Menlo 等宽字体)
|
||||
- 三项以竖线分隔符横向排列,卡片简洁不冗余
|
||||
- 新增 `renderCustomerCard()` 函数,读取 URL 参数驱动填充(无参数时显示默认值,兼容旧链接)
|
||||
- **清理冗余**:删除订单头信息网格中语义陈旧的「顾客类型=企业员工」项(与新顾客信息卡的"用户类型"语义重叠且数据不准确),替换为更有用的「商品合计 ¥46.60」
|
||||
- 新增 `USER_TYPE_CONFIG` 配置表,与 05-orders 同款,保证两页徽标样式一致
|
||||
|
||||
### 技术实现
|
||||
- 两页共用同一套 `user-type` CSS 与 `USER_TYPE_CONFIG` 配置(文案 + class),确保跨页视觉与语义统一
|
||||
- URL 参数采用 `encodeURIComponent` 编码用户名(支持中文),详情页 `decodeURIComponent` 解码
|
||||
- 列表→详情跳转链路完整:点击行或点「详情/去退款」按钮均携带用户类型参数,顾客信息卡可正确渲染
|
||||
|
||||
### 不变更
|
||||
- 05-orders 的筛选栏、分页、状态徽标、餐次标签等其他列保持不变
|
||||
- 06-order-detail 的退款流程、URL 驱动的 status/refunded 参数、关联信息等保持不变
|
||||
- 03-checkout 等其他页面的 user-type 配色风格保持不变(本次只是复用,未改动原页)
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-10 | 订单详情页(06)移除「补齐明细」操作项
|
||||
|
||||
### 业务背景
|
||||
`06-order-detail.html` 是订单支付完成后的详情页,右列「订单操作」原包含三个按钮:重新打印小票 / 补齐明细(漏记/多记) / 申请退款。
|
||||
|
||||
补齐明细(`08-reconcile.html`)本质是**支付前**的修正入口,返回按钮指向 `03-checkout.html`。订单已支付完成进入详情页后,补齐明细已不适用——此时如需调整金额,只能走退款流程(全额/部分退款)。保留该入口会让收银员误以为可在支付后继续修改订单,产生操作歧义。
|
||||
|
||||
### 变更
|
||||
- **`06-order-detail.html`**:删除「订单操作」按钮组中的「补齐明细(漏记/多记)」按钮,保留:
|
||||
- 🖨️ 重新打印小票
|
||||
- 💸 申请退款
|
||||
|
||||
### 不变更
|
||||
- 左列订单头、商品明细、金额汇总保持不变
|
||||
- 退款流程面板、URL 参数驱动(action=refund)、部分退款/二次退款逻辑保持不变
|
||||
- `08-reconcile.html` 自身及其入口(`03-checkout.html` 取餐明细头部的「补齐明细」按钮)保持不变 —— 补齐明细仅在支付前可用
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-10 | 补齐明细页(08-reconcile)重构:表格化 + 未识别取餐记录加入
|
||||
|
||||
### 业务背景
|
||||
原 `08-reconcile.html` 补齐明细页存在两个核心问题:
|
||||
1. **左侧订单明细样式**采用「分组 + 卡片堆叠」形式,与 `03-checkout.html` 表格化风格不一致,且分组标题占用了大量纵向空间,无法在 720px 内呈现完整明细。
|
||||
2. **添加商品的来源错误**:原方案弹出一个**菜品列表**(菜品库),由收银员手填重量加入。但实际业务路径是 —— 当前餐次中存在**未匹配到人脸/账户的取餐记录**(营养秤/档口秤有数据但识别不到人),这些就是「少记」的可信来源,直接从这里挑记录加入即可,不需要也不应该让收银员凭空选菜品。
|
||||
|
||||
补齐明细的核心业务路径明确为:**多记→手动删除** + **少记→从未识别取餐记录中直接加入**。
|
||||
|
||||
### 变更
|
||||
- **左侧当前订单(表格化,对齐 03-checkout)**:
|
||||
- 由 `<div>` 卡片堆叠改为 `<table>` 表格化结构,列:序号 / 类别 / 餐品名称 / 单价 / 取餐量 / 金额 / 操作
|
||||
- **同一菜品取两次会作为两条独立行显示**(不合并),满足"补齐明细中同名菜品多条记录"的场景
|
||||
- 操作列三种按钮:`-` 移除(正常→已移除)、`↺` 恢复(已移除→正常)、`✕` 直接删除(用于新加项)
|
||||
- 状态样式:已移除行删除线+半透明红底,新加行绿底+「新加」徽标
|
||||
- 底部合计行同步表格化:件数 / 总重 / 总金额
|
||||
- **「添加商品」Tab 重构为「补齐未识别」**:
|
||||
- 由原"菜品库网格 + 搜索"改为**当前餐次中未识别到人的取餐记录列表**
|
||||
- 顶部黄色提示条说明用途:"未匹配到顾客的取餐记录,属漏记则点 + 加入"
|
||||
- 按来源分组(营养秤 · 餐线取餐 / 档口秤 · 档口取餐),每组带条数计数
|
||||
- 每条记录展示:时间 / 类别 / 餐品 / 单价 / 取餐量 / 金额 / 操作
|
||||
- 点击 `+ 加入` → 直接加入左侧订单(标记「新加」),并从列表中标记「✓ 已加」(不可重复加入)
|
||||
- 在左侧订单中删除该新加项,自动恢复未识别列表的 `+` 按钮
|
||||
- **数据模型调整**:
|
||||
- `currentItems`:每条独立记录,新增 `origin`(recognized/unmatched)、`recordId`(关联未识别记录)、`status`(normal/removed/added)
|
||||
- `unmatchedRecords`:未识别取餐记录池,每条带 `recordId`、`time`、`source`(scale/stall)
|
||||
- `addedRecordIds`:Set 集合,跟踪已加入的未识别记录,避免重复加入
|
||||
- **右列差额汇总**:删除原"原订单合计/本次修正/修正后合计"中的 demo 写死数字,改为 ID 绑定,金额随操作实时变化;变更日志支持新加(绿)与移除(红)两种颜色区分
|
||||
|
||||
### 技术实现
|
||||
- 仿照 `03-checkout.html` 的 `items-table` + `items-footer` + 列宽 `colgroup` 模式,确保两页视觉一致
|
||||
- 未识别记录列表独立 `um-table` 样式(时间列、橙色配色区分),与当前订单的绿色配色形成视觉对照,暗示"待确认"
|
||||
- 加入操作不弹窗输入重量(因为未识别记录的重量已经由秤具给出,无需收银员重新输入)
|
||||
|
||||
### 不变更
|
||||
- 顶部导航(返回结算 + 标题 + 订单号)、右列差额汇总整体布局、变更日志样式、底部操作按钮(放弃修正 / 应用修正)保持不变
|
||||
- `ORIGINAL_TOTAL = 46.60`、`DISCOUNT = 5.00` 等金额常量与原版一致(对应 03-checkout 同一订单)
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-07 | 收银员屏支付成功页按支付方式动态展示 + 移除自动弹窗
|
||||
|
||||
### 业务背景
|
||||
原 `03-checkout.html` 收银员屏在 `confirmPay()` 中跳转到 `04-pay-success.html`(收银员屏成功页)时未携带任何参数,导致成功页无论收银员选了哪种支付方式(在线 / 余额 / 现金找零 / 三种混合支付),都固定展示"在线支付 · 微信支付",与实际收款方式不符。同时,旧版成功页有"8 秒后自动弹出『订单已完成,是否开始下一单?』"的逻辑,产品确认要移除。
|
||||
|
||||
### 变更
|
||||
- **`03-checkout.html`**:`confirmPay()` 跳转 URL 改为携带 `?method=xxx` 参数,映射规则:
|
||||
- `currentPay=online` → `method=online`
|
||||
- `currentPay=balance`(余额充足) → `method=balance`
|
||||
- `currentPay=cash`(实收充足找零) → `method=cash-change`
|
||||
- `mixedMode=bl-online` → `method=mixed-bl-online`
|
||||
- `mixedMode=bl-cash` → `method=mixed-bl-cash`
|
||||
- `mixedMode=cash-online` → `method=mixed-cash-online`
|
||||
- 若已核销代金券,追加 `&coupon=1`(预留)
|
||||
- **`04-pay-success.html`**(收银员屏):
|
||||
- 新增 `methodConfig` 字典,6 种支付方式差异化展示:
|
||||
- 标题(支付成功 / 扣款成功)
|
||||
- 徽章文案(支付方式 + 渠道 + 状态)
|
||||
- 账户信息条(余额/混合/找零场景显示对应明细;在线支付隐藏)
|
||||
- 新增 `loadFromUrl()` 读取 `?method=xxx`,调用 `switchDemo()` 应用配置
|
||||
- 新增演示切换器(右上角悬浮,6 个按钮,与 user-04 风格一致),便于原型评审
|
||||
- **删除**原 8 秒后弹出的 `confirm('订单已完成,是否开始下一单?')` 弹窗逻辑
|
||||
- **`user-04-success.html`**(用户副屏):同步加入 `loadFromUrl()` URL 参数驱动(此前已具备 `methodConfig`,仅缺参数入口)
|
||||
|
||||
### 技术实现
|
||||
- 收银员屏与用户副屏的 `methodConfig` 键名统一(`online` / `balance` / `mixed-bl-online` / `mixed-bl-cash` / `mixed-cash-online` / `cash-change`),与 `user-03-paying.html` 完全对齐,保证跨屏参数语义一致
|
||||
- 收银员屏成功页保留原有的左动画+右操作布局,账户信息条新增在 `success-meta` 上方,与 user-04 同样的样式 token
|
||||
|
||||
### 不变更
|
||||
- 营养摘要卡、操作按钮(开始下一单/重打小票/订单详情)、小票打印状态等成功页既有内容保持不变
|
||||
- user-04-success 的烟花动画、营养卡、二维码引导保持不变
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-07 | 收银屏结算页:取餐明细表格化 + 去除结算 Tab 提示信息
|
||||
|
||||
### 业务背景
|
||||
原 `03-checkout.html` 左栏取餐明细采用"卡片堆叠 + 分组标题(餐线菜品/档口菜品)"形式,与用户副屏 `user-02-checkout-sync.html` 的表格化风格不统一,且热量列对收银员无实际意义(收银员关心的是金额与取餐量,而非营养数据)。同时三种结算 Tab(在线/现金/余额)下的「支付详情区」(pay-detail)展示了 hint+status 文案,占用空间但对收银员操作无指导价值,违反"结算台操作要简单"的核心约束。
|
||||
|
||||
### 两项优化
|
||||
1. **取餐明细表格化(参考 user-02 风格)**:
|
||||
- 由卡片堆叠改为 `<table>` + `<colgroup>` 列宽控制,与用户副屏视觉统一
|
||||
- **删除热量列**(收银员场景下无意义),保留 6 列:序号 / 类别 / 餐品名称 / 单价 / 取餐量 / 金额
|
||||
- **不做自动滚动**(收银员需要稳定查看完整明细,不能像用户屏那样循环滚动)
|
||||
- 底部合计行(与表头列对齐):合计 / 5项 / 1040g / ¥46.60
|
||||
- 删除「餐线菜品/档口菜品」分组标题(改为「类别」列徽标,信息密度更高)
|
||||
- 类别徽标样式:餐线(蓝色)、档口(橙色),视觉与 user-02 完全一致
|
||||
|
||||
2. **去除三种结算 Tab 下的提示信息**:
|
||||
- 删除 `.pay-detail` 整块(CSS + DOM + JS 全部清理)
|
||||
- 删除 `payDetailMap` 数据结构、`switchPay` 中的赋值逻辑
|
||||
- 收银员切换 Tab 后直接看到对应操作区(在线扫码 / 现金输入 / 余额信息),不再有多余 hint/status 文案干扰
|
||||
|
||||
### 视觉与交互细节
|
||||
- 表头与表体 6 列完全对齐(经浏览器验证 left 坐标一致)
|
||||
- 取餐量保持青色 pill 样式、金额青色加粗、序号圆形蓝边,延续 user-02 设计语言
|
||||
- 合计行金色顶部分隔 + 蓝绿渐变背景,与 user-02 合计行视觉统一
|
||||
- 移除 pay-detail 后,右栏腾出 ~42px 高度,反而修复了之前现金补差场景的 1px 溢出
|
||||
|
||||
### 技术实现
|
||||
- DOM 重构:`.items-list` 内部由 `.table-head` + 多个 `.item-card` + `.zone-header` + `.subtotal-bar` 改为「表头 table + 表体 table + `.items-footer` 合计行」三段式结构
|
||||
- CSS 重写:`.items-table` 系列(列宽、表头、行 hover、cell 样式)+ `.items-footer` 系列(底部合计行)
|
||||
- JS 清理:删除 `payDetailMap` 全局对象、`switchPay` 中的 innerHTML 赋值、DOM 中的 `payDetail` 元素
|
||||
|
||||
### 验证结果
|
||||
| 检查项 | 结果 |
|
||||
|---|---|
|
||||
| 表格列数 | 6(序号/类别/名称/单价/取餐量/金额) |
|
||||
| 表格行数 | 5(原 5 道菜) |
|
||||
| 热量列存在 | 否 |
|
||||
| 表头/表体 6 列对齐 | 全部对齐(left 坐标差 <2px) |
|
||||
| 合计行金额 | ¥46.60(与商品合计一致) |
|
||||
| payDetail 元素 | 已彻底移除 |
|
||||
| 5 个支付场景右栏溢出 | 全部 0(在线/现金找零/现金补差/代金券+余额/余额不足+混合) |
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-07 | 收银屏结算页:右栏一屏化 + 代金券独立入口 + 演示场景控制
|
||||
|
||||
### 业务背景
|
||||
原 `03-checkout.html` 右侧收银操作区在多个支付场景下需要滚动才能完成操作,且代金券作为支付 Tab 与"在线/现金/余额"并列在语义上不合理(代金券为抵扣关系而非支付方式)。此外,原型演示"余额不足走混合支付"场景时缺少一键切换入口,需手改代码。
|
||||
|
||||
### 三项主要优化
|
||||
1. **右栏一屏可见**:压缩所有 padding/gap/字号,5 个核心场景(在线 / 现金找零 / 现金补差 / 代金券+余额充足 / 余额不足+混合支付)在 1280×720 PAD 内全部 ≤1px 溢出
|
||||
2. **代金券独立入口**:
|
||||
- 支付方式 Tab 从 4 个(在线/现金/余额/代金券)精简为 3 个(在线/现金/余额)
|
||||
- 代金券作为优惠抵扣入口独立放在金额汇总区下方,与支付方式形成"抵扣 + 支付"的并行关系
|
||||
- 三态切换:默认态(添加按钮)→ 输入态(券码输入框)→ 已核销态(显示面值 + 取消按钮)
|
||||
3. **原型演示控制徽标**:顶部右侧嵌入演示工具条,提供 4 个按钮
|
||||
- 「余额充足」`USER_BALANCE=128.50` 走单支付
|
||||
- 「余额不足」`USER_BALANCE=20.00` 走混合支付(自动弹混合建议弹窗)
|
||||
- 「代金券预载」开关,可叠加在任一场景上(直接进入已核销态)
|
||||
- 「↻」一键重置(清空代金券 + 现金 + 混合态,回到默认场景)
|
||||
|
||||
### 视觉与交互细节
|
||||
- **演示徽标设计**:紫罗兰边框 + 半透明黑底 + 模糊背景,与 top-bar 深蓝主色形成弱对比;选中场景按钮高亮(紫罗兰填充)
|
||||
- **代金券入口视觉**:金色虚线边框(默认)→ 金色实线(已应用),带 `🎫` 图标和面值金额,清晰传达"抵扣"语义
|
||||
- **混合支付进度条压缩**:margin-bottom 12→8,padding 10×12→7×10,内部 step-card 字号统一,适配一屏
|
||||
- **现金找零/补差结果区精简**:从 3 行(实收/应付/找零或差额)压缩为单行(找零金额或差额 + 按钮),保留核心信息
|
||||
- **余额信息卡精简**:从 5 行(等级/余额/应付/扣款后/tip)压缩为 2 行 + tip(等级与余额并列、应付与扣款后并列)
|
||||
- **混合支付态隐藏余额信息卡**:进度条已展示余额扣款信息,信息卡不再重复
|
||||
|
||||
### 技术实现
|
||||
- 新增 JS 函数:`showCouponInput()` / `hideCouponInput()` / `removeCoupon()` 驱动 coupon-entry 三态状态机
|
||||
- 新增 JS 函数:`switchScene(scene)` / `toggleCouponPreset()` / `resetDemo()` 控制演示场景切换
|
||||
- `refreshBalanceInfo()` 在 `mixedMode` 为真时直接 `remove('show')`,避免与进度条重复
|
||||
- `refreshBalanceInfo()` 由 switchPay 统一调用,移除 switchPay 内的 `classList.toggle('show', type === 'balance')` 避免冲突
|
||||
- `chooseMixed()` / `startCashOnlineMixed()` 内补调 `refreshBalanceInfo()`,确保混合态实时隐藏余额卡
|
||||
- 移除冗余代码:删除 `payDetailMap.coupon` / `confirmPay` 中的 `currentPay === 'coupon'` 分支 / `refreshBtnAmount` 中的 `couponAfterAmount` ID 引用
|
||||
- DOM 精简:删除现金找零/补差结果区的 `crChangeReceived`/`crChangePayable`/`crDiffReceived`/`crDiffPayable` 4 个冗余 ID
|
||||
|
||||
### 验证结果
|
||||
| 场景 | pay-col__body 溢出 |
|
||||
|---|---|
|
||||
| 默认(余额充足,在线支付) | 0px |
|
||||
| 现金找零(实收 ¥50) | 0px |
|
||||
| 现金补差(实收 ¥20,差额在线收) | 1px(无可见差异) |
|
||||
| 代金券预载 + 余额支付 | 0px |
|
||||
| 余额不足 + 混合支付(余额+在线) | 0px |
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-07 | 用户屏支付引导页:在线支付双途径(B扫C / C扫B)
|
||||
|
||||
### 业务背景
|
||||
在线支付实际支持两种扫码方向:
|
||||
- **B 扫 C(被扫)**:收银设备(B端)扫码器扫顾客付款码 → 顾客"出示付款码"
|
||||
- **C 扫 B(主扫)**:顾客手机扫收银设备屏幕上的收款二维码 → 顾客"扫码支付"
|
||||
|
||||
原模板 A 仅展示 B 扫 C 单一路径,缺少 C 扫 B 的二维码引导。本次升级在所有涉及"在线支付"的模板中同步加入双途径展示,并突出 B 扫 C 为推荐路径。
|
||||
|
||||
### 改造范围(3 处统一支持双途径)
|
||||
| 模板 | 在线支付用途 | 改造方式 |
|
||||
|---|---|---|
|
||||
| **模板 A 在线支付** | 全额在线 | 左右双栏并列展示 |
|
||||
| **模板 C1 余额+在线** | 余额扣尽后差额在线补 | pending 卡片内嵌紧凑版 |
|
||||
| **模板 C3 现金+在线** | 现金收讫后差额在线补 | pending 卡片内嵌紧凑版 |
|
||||
|
||||
### 视觉与交互细节
|
||||
- **左侧"出示付款码"(高亮推荐,业务上为 B 扫 C)**:
|
||||
- 青蓝主色 + 加粗边框 + 阴影发光,视觉权重明显大于右侧
|
||||
- 标题旁加 `🔥 推荐` 金色徽章
|
||||
- 底部加 `⚡ 出示付款码更快,推荐使用` 绿色提示条
|
||||
- 渠道徽章:微信 / 支付宝 / 银联
|
||||
- **右侧"扫码支付"(普通弱化,业务上为 C 扫 B)**:
|
||||
- 普通背景 + 细边框,视觉权重低
|
||||
- 静态占位二维码 SVG(内联,与 `user-04-success` 小程序引导二维码风格一致)
|
||||
- 渠道徽章:微信 / 支付宝(银联无主扫场景故略去)
|
||||
- 二维码下方文案动态显示金额:`扫描二维码支付 ¥XX.XX`(代金券叠加时自动重算)
|
||||
- **用户文案**:界面中只显示"出示付款码"/"扫码支付"等业务术语,不向用户暴露"B 扫 C / C 扫 B"等内部技术标签
|
||||
|
||||
### 技术实现
|
||||
- 抽取可复用组件 `renderScanMethods(amount, isInline)`:
|
||||
- `amount` 决定 C 扫 B 二维码下方显示的金额
|
||||
- `isInline=true` 时使用紧凑变体(`.scan-card--inline`),嵌入混合支付 pending 卡片内
|
||||
- 内联静态二维码 SVG 函数 `renderQRCodeSVG()`,无外部依赖(满足墨刀导入约束)
|
||||
- 混合支付整体压缩(原 22px gap → 12px,padding 22→14,图标 80→60),为内嵌扫码组件腾出空间
|
||||
- 6 套模板在 1280×720 PAD 内全部无溢出
|
||||
|
||||
### URL 参数
|
||||
- `?method=online` 模板 A 展示双途径扫码(默认形态)
|
||||
- `?method=mixed-bl-online` / `mixed-cash-online` 自动渲染内嵌紧凑版
|
||||
|
||||
## 2026-08-07 | 收银员屏结算页 + 用户屏支付引导页重构(支持混合支付/找零/代金券)
|
||||
|
||||
### 业务背景
|
||||
原结算页与支付引导页只支持单一支付方式,无法覆盖实际餐厅运营中的多种场景:
|
||||
- 余额不足时需要混合支付
|
||||
- 现金支付时需要找零或差额补付
|
||||
- 代金券(原"餐券/补贴码")作为通用优惠凭证
|
||||
|
||||
本次重构把支付方式体系升级为 **6 类支付形态 + 代金券叠加** 的统一模型,收银员屏与用户屏业务语义完全对齐。
|
||||
|
||||
### 收银员屏 `03-checkout.html` 改造
|
||||
- **金额汇总区扩展**:新增「代金券抵扣 / 余额抵扣 / 待付补差」三行动态显示,资金构成一目了然
|
||||
- **现金 Tab 增强**:
|
||||
- 新增「实收现金」输入框 + 6 个快捷金额按钮(¥20/50/100/200/500/Exact)
|
||||
- 实收 ≥ 应付 → 显示**找零**金额,按钮文案 `确认收款 ¥应付 · 找零 ¥X`
|
||||
- 实收 < 应付 → 显示**差额**金额 + 「差额用在线支付收取」按钮,进入现金+在线混合流程
|
||||
- **余额不足自动弹窗**:选「余额支付」时若余额不足,自动弹出「混合支付建议」对话框,提供「在线补差」「现金补差」二选一
|
||||
- **混合支付进度条**:进入混合态时金额汇总区顶部显示进度条,分两步展示「✅ 主支付已完成 + ⏳ 待补差」
|
||||
- **确认按钮动态切换**:单支付/找零/混合三种态切换文案与禁用状态,混合未完成时按钮禁用
|
||||
- **代金券语义统一**:原"餐券/补贴码"Tab 改名为"代金券",文案与图标统一为 🎫 代金券
|
||||
|
||||
### 用户屏 `user-03-paying.html` 改造
|
||||
- **顶部金额条升级**:从单一应付金额扩展为分项 chip 展示(代金券/余额/现金/待付/找零),按形态动态显示
|
||||
- **6 套支付引导模板**:
|
||||
- 模板 A `?method=online` 在线支付:大圆扫码图标 + 呼吸动效 + 支付渠道徽章
|
||||
- 模板 B `?method=balance` 余额支付:会员卡视觉 + 余额/消费/扣款后余额展示
|
||||
- 模板 C1 `?method=mixed-bl-online` 余额+在线:左右两栏「✅ 余额已扣 + ⏳ 请扫码补差」
|
||||
- 模板 C2 `?method=mixed-bl-cash` 余额+现金:左右两栏「✅ 余额已扣 + ⏳ 请现金补差」
|
||||
- 模板 C3 `?method=mixed-cash-online` 现金+在线:左右两栏「✅ 现金已收 + ⏳ 请扫码补差」
|
||||
- 模板 D `?method=cash-change` 现金找零:大圆现金图标 + 「请等待收银员找零」+ 应付/实收/找零明细卡
|
||||
- **代金券提示条**:核销代金券后顶部金额条下方显示金色 banner,提示券号与抵扣金额
|
||||
- **演示切换器**:底部居中悬浮 7 个按钮(6 类形态 + 代金券开关),与 `user-04-success` 风格一致
|
||||
- **URL 参数双驱动**:`?method=xxx` 切换形态、`?coupon=1` 开启代金券,便于原型评审分享
|
||||
|
||||
### 两屏业务串联
|
||||
收银员屏的支付方式状态通过业务后端同步到用户屏(原型中通过 URL 参数模拟):
|
||||
- 收银员选「余额」+ 不足弹窗选「在线补差」→ 用户屏切到 `?method=mixed-bl-online`
|
||||
- 收银员选「现金」+ 实收 100 → 用户屏切到 `?method=cash-change`,展示找零 ¥58.40
|
||||
- 收银员核销代金券 → 用户屏顶部 chip 同步显示 `🎫 券 -¥10.00`,应付金额重算
|
||||
|
||||
### 视觉与技术细节
|
||||
- 用户屏 6 套模板在 1280×720 PAD 内全部无溢出(满足 PAD 终端禁止可见滚动条约束)
|
||||
- 含会员卡的模板(B、D)使用 `pay-panel--with-card` 紧凑变体,图标圆 120px、字号下调
|
||||
- 混合支付左右两栏卡片用 CSS 动画 `glow` 实现待付侧呼吸高亮
|
||||
- 顶部 chip 按资金类型分色:代金券金色 / 余额青色 / 现金绿色 / 待付橙色
|
||||
|
||||
## 2026-08-07 | U02 副屏取餐明细三栏重构
|
||||
|
||||
### 变更
|
||||
|
||||
@@ -80,28 +80,45 @@
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
}
|
||||
.user-card__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
.user-card__tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.user-card__level {
|
||||
padding: 4px 10px;
|
||||
background: linear-gradient(135deg, rgba(251, 191, 36, 0.25), rgba(251, 146, 60, 0.18));
|
||||
border: 1px solid rgba(251, 191, 36, 0.5);
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
color: #fbbf24;
|
||||
/* 用户身份标签:类型 + 会员等级(单一渐变标签)
|
||||
- 会员用户:蓝→金渐变,后跟等级
|
||||
- 临时用户:灰色单色
|
||||
- 内部员工:蓝色单色 */
|
||||
.user-card__type {
|
||||
padding: 5px 12px;
|
||||
border-radius: 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
flex-shrink: 0;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.user-card__level small {
|
||||
display: block;
|
||||
font-size: 9px;
|
||||
color: rgba(251, 191, 36, 0.85);
|
||||
font-weight: 500;
|
||||
margin-top: 1px;
|
||||
.user-card__type--member {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.28), rgba(251, 191, 36, 0.32));
|
||||
border: 1px solid rgba(251, 191, 36, 0.55);
|
||||
color: #fbbf24;
|
||||
box-shadow: 0 2px 8px rgba(251, 191, 36, 0.18);
|
||||
}
|
||||
.user-card__type--member .level {
|
||||
color: #fde68a;
|
||||
font-weight: 800;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.user-card__type--temp {
|
||||
background: rgba(148, 163, 184, 0.18);
|
||||
border: 1px solid rgba(148, 163, 184, 0.45);
|
||||
color: #cbd5e1;
|
||||
}
|
||||
.user-card__type--staff {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.22), rgba(74, 144, 217, 0.14));
|
||||
border: 1px solid rgba(96, 165, 250, 0.5);
|
||||
color: #60a5fa;
|
||||
}
|
||||
.header__time {
|
||||
font-size: 13px;
|
||||
@@ -158,11 +175,13 @@
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* 列宽:序号 / 餐品名称+单价 / 取餐量 / 热量 / 金额 */
|
||||
/* 列宽:序号 / 类别 / 餐品名称 / 单价 / 取餐量 / 热量 / 金额 */
|
||||
.items-table col.col-seq { width: 40px; }
|
||||
.items-table col.col-name { width: auto; }
|
||||
.items-table col.col-weight { width: 100px; }
|
||||
.items-table col.col-cal { width: 110px; }
|
||||
.items-table col.col-cat { width: 64px; }
|
||||
.items-table col.col-name { width: 180px; }
|
||||
.items-table col.col-unit { width: 100px; }
|
||||
.items-table col.col-weight { width: 90px; }
|
||||
.items-table col.col-cal { width: 100px; }
|
||||
.items-table col.col-price { width: 92px; }
|
||||
|
||||
.items-table thead th {
|
||||
@@ -178,6 +197,7 @@
|
||||
}
|
||||
.items-table thead th.col-weight-h,
|
||||
.items-table thead th.col-cal-h { text-align: center; }
|
||||
.items-table thead th.col-unit-h,
|
||||
.items-table thead th.col-price-h { text-align: right; }
|
||||
|
||||
.items-table tbody td { border-bottom: 1px dashed rgba(255, 255, 255, 0.05); }
|
||||
@@ -205,13 +225,35 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.cell-name small {
|
||||
display: block;
|
||||
/* 类别徽标:餐线 / 档口 */
|
||||
.cell-cat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 0;
|
||||
width: 48px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.cell-cat--line {
|
||||
background: rgba(96, 165, 250, 0.18);
|
||||
border: 1px solid rgba(96, 165, 250, 0.45);
|
||||
color: #60a5fa;
|
||||
}
|
||||
.cell-cat--booth {
|
||||
background: rgba(251, 146, 60, 0.18);
|
||||
border: 1px solid rgba(251, 146, 60, 0.45);
|
||||
color: #fb923c;
|
||||
}
|
||||
.cell-unit {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-weight: 400;
|
||||
margin-top: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cell-weight, .cell-cal {
|
||||
text-align: center;
|
||||
@@ -286,9 +328,11 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
.items-footer td.col-seq-f { width: 40px; text-align: left; font-size: 12px; color: rgba(255,255,255,0.55); }
|
||||
.items-footer td.col-name-f { width: auto; }
|
||||
.items-footer td.col-weight-f { width: 100px; text-align: center; }
|
||||
.items-footer td.col-cal-f { width: 110px; text-align: center; }
|
||||
.items-footer td.col-cat-f { width: 64px; }
|
||||
.items-footer td.col-name-f { width: 180px; }
|
||||
.items-footer td.col-unit-f { width: 100px; }
|
||||
.items-footer td.col-weight-f { width: 90px; text-align: center; }
|
||||
.items-footer td.col-cal-f { width: 100px; text-align: center; }
|
||||
.items-footer td.col-price-f { width: 92px; text-align: right; }
|
||||
.footer-label {
|
||||
font-size: 13px;
|
||||
@@ -312,12 +356,12 @@
|
||||
|
||||
/* 右:营养 + 金额 + 二维码 */
|
||||
.side-area {
|
||||
padding: 14px 18px;
|
||||
padding: 12px 14px;
|
||||
background: rgba(26, 43, 74, 0.3);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -471,34 +515,39 @@
|
||||
|
||||
/* 订单金额(中间,非常醒目) */
|
||||
.amount-card {
|
||||
background: linear-gradient(135deg, rgba(10, 196, 168, 0.22), rgba(0, 0, 0, 0.4));
|
||||
border: 2px solid rgba(10, 196, 168, 0.65);
|
||||
background: linear-gradient(135deg, rgba(10, 196, 168, 0.28), rgba(0, 0, 0, 0.4));
|
||||
border: 2px solid rgba(10, 196, 168, 0.7);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
padding: 14px 16px 12px;
|
||||
text-align: center;
|
||||
box-shadow: 0 6px 24px rgba(10, 196, 168, 0.25), inset 0 0 0 1px rgba(10, 196, 168, 0.15);
|
||||
box-shadow: 0 8px 28px rgba(10, 196, 168, 0.32), inset 0 0 0 1px rgba(10, 196, 168, 0.2);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
.amount-card__label {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
letter-spacing: 4px;
|
||||
margin-bottom: 2px;
|
||||
text-shadow: 0 0 8px rgba(10, 196, 168, 0.4);
|
||||
}
|
||||
.amount-card__value {
|
||||
font-size: 60px;
|
||||
font-weight: 800;
|
||||
font-size: 64px;
|
||||
font-weight: 900;
|
||||
color: #0AC4A8;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
line-height: 1.05;
|
||||
text-shadow: 0 0 32px rgba(10, 196, 168, 0.55), 0 0 8px rgba(10, 196, 168, 0.8);
|
||||
text-shadow:
|
||||
0 0 8px rgba(10, 196, 168, 0.9),
|
||||
0 0 24px rgba(10, 196, 168, 0.7),
|
||||
0 0 48px rgba(10, 196, 168, 0.45);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.amount-card__sub {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
margin-top: 6px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.amount-card__sub strong { color: #4ade80; }
|
||||
|
||||
@@ -507,19 +556,19 @@
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.qr-card__code {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 6px;
|
||||
padding: 5px;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
@@ -529,7 +578,7 @@
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 5px;
|
||||
}
|
||||
.qr-card__title {
|
||||
font-size: 14px;
|
||||
@@ -570,11 +619,11 @@
|
||||
<span class="user-card__name">张*明</span>
|
||||
<span class="user-card__empno">EMP002346</span>
|
||||
</div>
|
||||
<span class="user-card__sub">研发中心 · 二荤餐线</span>
|
||||
</div>
|
||||
<div class="user-card__level">
|
||||
金牌会员
|
||||
<small>LV5</small>
|
||||
<div class="user-card__tags">
|
||||
<div class="user-card__type user-card__type--member">
|
||||
会员用户<span class="level">· 金牌 LV5</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header__time" id="nowTime">2026-08-06 12:30:45</div>
|
||||
@@ -587,14 +636,16 @@
|
||||
<div class="items-area">
|
||||
<div class="items-area__title">
|
||||
🛍️ 餐品明细
|
||||
<span class="items-area__title-count">共 5 件</span>
|
||||
<span class="items-area__title-count">共 13 件</span>
|
||||
</div>
|
||||
|
||||
<!-- 表头(固定) -->
|
||||
<table class="items-table">
|
||||
<colgroup>
|
||||
<col class="col-seq">
|
||||
<col class="col-cat">
|
||||
<col class="col-name">
|
||||
<col class="col-unit">
|
||||
<col class="col-weight">
|
||||
<col class="col-cal">
|
||||
<col class="col-price">
|
||||
@@ -602,7 +653,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>餐品名称 / 单价</th>
|
||||
<th>类别</th>
|
||||
<th>餐品名称</th>
|
||||
<th class="col-unit-h">单价</th>
|
||||
<th class="col-weight-h">取餐量</th>
|
||||
<th class="col-cal-h">热量</th>
|
||||
<th class="col-price-h">金额</th>
|
||||
@@ -618,7 +671,9 @@
|
||||
<table class="items-table">
|
||||
<colgroup>
|
||||
<col class="col-seq">
|
||||
<col class="col-cat">
|
||||
<col class="col-name">
|
||||
<col class="col-unit">
|
||||
<col class="col-weight">
|
||||
<col class="col-cal">
|
||||
<col class="col-price">
|
||||
@@ -626,46 +681,130 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="cell-seq">1</span></td>
|
||||
<td><div class="cell-name">清炒时蔬<small>¥4.00/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">清炒时蔬</div></td>
|
||||
<td class="cell-unit">¥4.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">150g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">45 kcal</span></td>
|
||||
<td class="cell-price">¥6.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">2</span></td>
|
||||
<td><div class="cell-name">米饭<small>¥1.60/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">米饭</div></td>
|
||||
<td class="cell-unit">¥1.60/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">250g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">360 kcal</span></td>
|
||||
<td class="cell-price">¥4.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">3</span></td>
|
||||
<td><div class="cell-name">红烧肉<small>¥12.00/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">红烧肉</div></td>
|
||||
<td class="cell-unit">¥12.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">180g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">486 kcal</span></td>
|
||||
<td class="cell-price">¥21.60</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">4</span></td>
|
||||
<td><div class="cell-name">鱼香肉丝<small>¥7.50/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">鱼香肉丝</div></td>
|
||||
<td class="cell-unit">¥7.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">160g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">336 kcal</span></td>
|
||||
<td class="cell-price">¥12.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">5</span></td>
|
||||
<td><div class="cell-name">冬瓜排骨汤<small>¥1.00/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">冬瓜排骨汤</div></td>
|
||||
<td class="cell-unit">¥1.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">300g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">336 kcal</span></td>
|
||||
<td class="cell-price">¥3.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">6</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">番茄炒蛋</div></td>
|
||||
<td class="cell-unit">¥5.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">140g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">196 kcal</span></td>
|
||||
<td class="cell-price">¥7.70</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">7</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">蒸南瓜</div></td>
|
||||
<td class="cell-unit">¥2.80/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">120g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">48 kcal</span></td>
|
||||
<td class="cell-price">¥3.36</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">8</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">凉拌黄瓜</div></td>
|
||||
<td class="cell-unit">¥3.20/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">100g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">32 kcal</span></td>
|
||||
<td class="cell-price">¥3.20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">9</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">麻婆豆腐</div></td>
|
||||
<td class="cell-unit">¥3.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">80g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">80 kcal</span></td>
|
||||
<td class="cell-price">¥2.80</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">10</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">拌三丝</div></td>
|
||||
<td class="cell-unit">¥2.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">70g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">35 kcal</span></td>
|
||||
<td class="cell-price">¥1.75</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">11</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">蒸蛋羹</div></td>
|
||||
<td class="cell-unit">¥3.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">80g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">60 kcal</span></td>
|
||||
<td class="cell-price">¥2.40</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">12</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">卤豆干</div></td>
|
||||
<td class="cell-unit">¥2.80/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">60g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">50 kcal</span></td>
|
||||
<td class="cell-price">¥1.68</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">13</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">紫菜蛋花汤</div></td>
|
||||
<td class="cell-unit">¥1.20/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">90g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">25 kcal</span></td>
|
||||
<td class="cell-price">¥1.08</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 表格2(无缝衔接:重复一份数据) -->
|
||||
<table class="items-table">
|
||||
<colgroup>
|
||||
<col class="col-seq">
|
||||
<col class="col-cat">
|
||||
<col class="col-name">
|
||||
<col class="col-unit">
|
||||
<col class="col-weight">
|
||||
<col class="col-cal">
|
||||
<col class="col-price">
|
||||
@@ -673,39 +812,121 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="cell-seq">1</span></td>
|
||||
<td><div class="cell-name">清炒时蔬<small>¥4.00/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">清炒时蔬</div></td>
|
||||
<td class="cell-unit">¥4.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">150g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">45 kcal</span></td>
|
||||
<td class="cell-price">¥6.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">2</span></td>
|
||||
<td><div class="cell-name">米饭<small>¥1.60/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">米饭</div></td>
|
||||
<td class="cell-unit">¥1.60/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">250g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">360 kcal</span></td>
|
||||
<td class="cell-price">¥4.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">3</span></td>
|
||||
<td><div class="cell-name">红烧肉<small>¥12.00/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">红烧肉</div></td>
|
||||
<td class="cell-unit">¥12.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">180g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">486 kcal</span></td>
|
||||
<td class="cell-price">¥21.60</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">4</span></td>
|
||||
<td><div class="cell-name">鱼香肉丝<small>¥7.50/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">鱼香肉丝</div></td>
|
||||
<td class="cell-unit">¥7.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">160g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">336 kcal</span></td>
|
||||
<td class="cell-price">¥12.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">5</span></td>
|
||||
<td><div class="cell-name">冬瓜排骨汤<small>¥1.00/100g</small></div></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">冬瓜排骨汤</div></td>
|
||||
<td class="cell-unit">¥1.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">300g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">336 kcal</span></td>
|
||||
<td class="cell-price">¥3.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">6</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">番茄炒蛋</div></td>
|
||||
<td class="cell-unit">¥5.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">140g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">196 kcal</span></td>
|
||||
<td class="cell-price">¥7.70</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">7</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">蒸南瓜</div></td>
|
||||
<td class="cell-unit">¥2.80/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">120g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">48 kcal</span></td>
|
||||
<td class="cell-price">¥3.36</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">8</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">凉拌黄瓜</div></td>
|
||||
<td class="cell-unit">¥3.20/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">100g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">32 kcal</span></td>
|
||||
<td class="cell-price">¥3.20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">9</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">麻婆豆腐</div></td>
|
||||
<td class="cell-unit">¥3.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">80g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">80 kcal</span></td>
|
||||
<td class="cell-price">¥2.80</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">10</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">拌三丝</div></td>
|
||||
<td class="cell-unit">¥2.50/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">70g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">35 kcal</span></td>
|
||||
<td class="cell-price">¥1.75</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">11</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">蒸蛋羹</div></td>
|
||||
<td class="cell-unit">¥3.00/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">80g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">60 kcal</span></td>
|
||||
<td class="cell-price">¥2.40</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">12</span></td>
|
||||
<td><span class="cell-cat cell-cat--booth">档口</span></td>
|
||||
<td><div class="cell-name">卤豆干</div></td>
|
||||
<td class="cell-unit">¥2.80/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">60g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">50 kcal</span></td>
|
||||
<td class="cell-price">¥1.68</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="cell-seq">13</span></td>
|
||||
<td><span class="cell-cat cell-cat--line">餐线</span></td>
|
||||
<td><div class="cell-name">紫菜蛋花汤</div></td>
|
||||
<td class="cell-unit">¥1.20/100g</td>
|
||||
<td class="cell-weight"><span class="cell-weight__pill">90g</span></td>
|
||||
<td class="cell-cal"><span class="cell-cal__pill">25 kcal</span></td>
|
||||
<td class="cell-price">¥1.08</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -717,25 +938,29 @@
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:40px">
|
||||
<col>
|
||||
<col style="width:64px">
|
||||
<col style="width:180px">
|
||||
<col style="width:100px">
|
||||
<col style="width:90px">
|
||||
<col style="width:100px">
|
||||
<col style="width:110px">
|
||||
<col style="width:92px">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-seq-f"></td>
|
||||
<td class="col-cat-f"></td>
|
||||
<td class="col-name-f">
|
||||
<span class="footer-label">合计</span>
|
||||
</td>
|
||||
<td class="col-unit-f"></td>
|
||||
<td class="col-weight-f">
|
||||
<span class="footer-value">1040g</span>
|
||||
<span class="footer-value">1780g</span>
|
||||
</td>
|
||||
<td class="col-cal-f">
|
||||
<span class="footer-value footer-value--cal">1563 kcal</span>
|
||||
<span class="footer-value footer-value--cal">2089 kcal</span>
|
||||
</td>
|
||||
<td class="col-price-f">
|
||||
<span class="footer-amount">¥46.60</span>
|
||||
<span class="footer-amount">¥70.57</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -818,8 +1043,8 @@
|
||||
<!-- 订单金额(醒目) -->
|
||||
<div class="amount-card">
|
||||
<div class="amount-card__label">应付金额</div>
|
||||
<div class="amount-card__value">¥41.60</div>
|
||||
<div class="amount-card__sub">原价 <strong>¥46.60</strong> · 优惠金额 ¥5.00</div>
|
||||
<div class="amount-card__value">¥65.57</div>
|
||||
<div class="amount-card__sub">原价 <strong>¥70.57</strong> · 优惠金额 ¥5.00</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付二维码 -->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -117,7 +117,7 @@
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
/* 账户信息条(仅刷脸支付显示) */
|
||||
/* 账户信息条(余额/混合/找零场景显示) */
|
||||
.account-info {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
@@ -154,11 +154,23 @@
|
||||
}
|
||||
.account-info__value--expense { color: #fb923c; }
|
||||
.account-info__value--balance { color: #4ade80; }
|
||||
.account-info__value--change { color: #fbbf24; }
|
||||
.account-info__value--highlight {
|
||||
font-size: 22px;
|
||||
color: #0AC4A8;
|
||||
text-shadow: 0 0 12px rgba(10, 196, 168, 0.4);
|
||||
}
|
||||
.account-info__divider {
|
||||
width: 1px;
|
||||
height: 32px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
/* 3 列以上的混合支付明细卡片,最大宽度放宽 */
|
||||
.account-info.account-info--wide { max-width: 460px; }
|
||||
.account-info.account-info--cash {
|
||||
background: linear-gradient(90deg, rgba(82, 196, 26, 0.18), rgba(251, 191, 36, 0.1));
|
||||
border-color: rgba(82, 196, 26, 0.4);
|
||||
}
|
||||
|
||||
/* 演示切换按钮(原型展示用) */
|
||||
.demo-switcher {
|
||||
@@ -284,36 +296,90 @@
|
||||
.health-tip__icon { font-size: 18px; }
|
||||
.health-tip__text strong { color: #4ade80; }
|
||||
|
||||
/* 二维码引导 */
|
||||
/* 二维码引导(放大版:二维码居上、文案居下) */
|
||||
.qr-guide {
|
||||
padding: 14px 18px;
|
||||
background: rgba(26, 43, 74, 0.5);
|
||||
border: 1px solid rgba(74, 144, 217, 0.3);
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px 16px;
|
||||
background: linear-gradient(135deg, rgba(26, 43, 74, 0.6), rgba(10, 196, 168, 0.1));
|
||||
border: 1px solid rgba(74, 144, 217, 0.4);
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3), 0 0 0 4px rgba(74, 144, 217, 0.08);
|
||||
}
|
||||
.qr-guide__qr-wrap {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
flex-shrink: 0;
|
||||
box-shadow:
|
||||
0 4px 16px rgba(74, 144, 217, 0.4),
|
||||
0 0 0 2px rgba(74, 144, 217, 0.35),
|
||||
0 0 32px rgba(74, 144, 217, 0.25);
|
||||
position: relative;
|
||||
}
|
||||
/* 装饰角标:扫码提示 */
|
||||
.qr-guide__qr-wrap::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-top: 2px solid #60a5fa;
|
||||
border-left: 2px solid #60a5fa;
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
.qr-guide__qr-wrap::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -6px;
|
||||
right: -6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-bottom: 2px solid #60a5fa;
|
||||
border-right: 2px solid #60a5fa;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.qr-guide__img {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
padding: 4px;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.qr-guide__text {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.qr-guide__text { flex: 1; }
|
||||
.qr-guide__title {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.qr-guide__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.qr-guide__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: 2px auto 0;
|
||||
padding: 3px 10px;
|
||||
background: rgba(82, 196, 26, 0.15);
|
||||
border: 1px solid rgba(82, 196, 26, 0.35);
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
color: #4ade80;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
@@ -341,8 +407,12 @@
|
||||
|
||||
<!-- 演示切换按钮(原型展示用,实际部署可移除) -->
|
||||
<div class="demo-switcher">
|
||||
<button class="demo-switcher__btn" data-method="online">在线支付演示</button>
|
||||
<button class="demo-switcher__btn active" data-method="face">刷脸支付演示</button>
|
||||
<button class="demo-switcher__btn active" data-method="online">在线支付</button>
|
||||
<button class="demo-switcher__btn" data-method="balance">余额支付</button>
|
||||
<button class="demo-switcher__btn" data-method="mixed-bl-online">余额+在线</button>
|
||||
<button class="demo-switcher__btn" data-method="mixed-bl-cash">余额+现金</button>
|
||||
<button class="demo-switcher__btn" data-method="mixed-cash-online">现金+在线</button>
|
||||
<button class="demo-switcher__btn" data-method="cash-change">现金找零</button>
|
||||
</div>
|
||||
|
||||
<!-- 烟花动画背景 -->
|
||||
@@ -353,25 +423,15 @@
|
||||
|
||||
<!-- 左:成功动画 + 金额 -->
|
||||
<div class="success-block">
|
||||
<div class="success-icon">✔</div>
|
||||
<div class="success-title">支付成功</div>
|
||||
<div class="success-icon" id="successIcon">✔</div>
|
||||
<div class="success-title" id="successTitle">支付成功</div>
|
||||
|
||||
<!-- 账户信息条(仅刷脸支付显示) -->
|
||||
<div class="account-info" id="accountInfo">
|
||||
<div class="account-info__col">
|
||||
<span class="account-info__label">本次消费</span>
|
||||
<span class="account-info__value account-info__value--expense">-¥41.60</span>
|
||||
</div>
|
||||
<div class="account-info__divider"></div>
|
||||
<div class="account-info__col">
|
||||
<span class="account-info__label">账户余额 · 张明</span>
|
||||
<span class="account-info__value account-info__value--balance">¥128.40</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 账户信息条(由 JS 按支付方式动态注入;余额/混合/找零场景显示) -->
|
||||
<div class="account-info" id="accountInfo"></div>
|
||||
|
||||
<div class="success-amount">
|
||||
<div class="success-amount__value">¥41.60</div>
|
||||
<div class="success-amount__method" id="payMethodBadge">👤 刷脸支付 · 已扣款</div>
|
||||
<div class="success-amount__value" id="successAmount">¥41.60</div>
|
||||
<div class="success-amount__method" id="payMethodBadge">📱 在线支付 · 微信 · 已到账</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -415,88 +475,91 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 小程序引导 -->
|
||||
<!-- 小程序引导(放大版二维码) -->
|
||||
<div class="qr-guide">
|
||||
<svg class="qr-guide__img" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="64" height="64" fill="#fff"/>
|
||||
<g fill="#0e1326">
|
||||
<!-- 三个定位角 -->
|
||||
<rect x="4" y="4" width="14" height="14"/>
|
||||
<rect x="6" y="6" width="10" height="10" fill="#fff"/>
|
||||
<rect x="8" y="8" width="6" height="6" fill="#0e1326"/>
|
||||
<rect x="46" y="4" width="14" height="14"/>
|
||||
<rect x="48" y="6" width="10" height="10" fill="#fff"/>
|
||||
<rect x="50" y="8" width="6" height="6" fill="#0e1326"/>
|
||||
<rect x="4" y="46" width="14" height="14"/>
|
||||
<rect x="6" y="48" width="10" height="10" fill="#fff"/>
|
||||
<rect x="8" y="50" width="6" height="6" fill="#0e1326"/>
|
||||
<!-- 数据点(模拟) -->
|
||||
<rect x="22" y="6" width="2" height="2"/>
|
||||
<rect x="26" y="6" width="2" height="2"/>
|
||||
<rect x="30" y="6" width="2" height="2"/>
|
||||
<rect x="38" y="6" width="2" height="2"/>
|
||||
<rect x="22" y="10" width="2" height="2"/>
|
||||
<rect x="34" y="10" width="2" height="2"/>
|
||||
<rect x="22" y="14" width="6" height="2"/>
|
||||
<rect x="32" y="14" width="2" height="2"/>
|
||||
<rect x="38" y="14" width="2" height="2"/>
|
||||
<rect x="6" y="22" width="2" height="2"/>
|
||||
<rect x="10" y="22" width="2" height="2"/>
|
||||
<rect x="14" y="22" width="2" height="2"/>
|
||||
<rect x="22" y="22" width="2" height="2"/>
|
||||
<rect x="26" y="22" width="6" height="2"/>
|
||||
<rect x="34" y="22" width="2" height="2"/>
|
||||
<rect x="42" y="22" width="2" height="2"/>
|
||||
<rect x="50" y="22" width="2" height="2"/>
|
||||
<rect x="54" y="22" width="2" height="2"/>
|
||||
<rect x="6" y="26" width="2" height="2"/>
|
||||
<rect x="14" y="26" width="2" height="2"/>
|
||||
<rect x="22" y="26" width="2" height="2"/>
|
||||
<rect x="30" y="26" width="2" height="2"/>
|
||||
<rect x="38" y="26" width="2" height="2"/>
|
||||
<rect x="46" y="26" width="2" height="2"/>
|
||||
<rect x="54" y="26" width="2" height="2"/>
|
||||
<rect x="10" y="30" width="2" height="2"/>
|
||||
<rect x="18" y="30" width="2" height="2"/>
|
||||
<rect x="26" y="30" width="2" height="2"/>
|
||||
<rect x="34" y="30" width="2" height="2"/>
|
||||
<rect x="42" y="30" width="2" height="2"/>
|
||||
<rect x="50" y="30" width="2" height="2"/>
|
||||
<rect x="58" y="30" width="2" height="2"/>
|
||||
<rect x="6" y="34" width="2" height="2"/>
|
||||
<rect x="14" y="34" width="2" height="2"/>
|
||||
<rect x="22" y="34" width="2" height="2"/>
|
||||
<rect x="30" y="34" width="6" height="2"/>
|
||||
<rect x="42" y="34" width="2" height="2"/>
|
||||
<rect x="50" y="34" width="2" height="2"/>
|
||||
<rect x="22" y="38" width="2" height="2"/>
|
||||
<rect x="26" y="38" width="2" height="2"/>
|
||||
<rect x="34" y="38" width="2" height="2"/>
|
||||
<rect x="42" y="38" width="2" height="2"/>
|
||||
<rect x="46" y="38" width="2" height="2"/>
|
||||
<rect x="54" y="38" width="2" height="2"/>
|
||||
<rect x="22" y="42" width="6" height="2"/>
|
||||
<rect x="34" y="42" width="2" height="2"/>
|
||||
<rect x="42" y="42" width="2" height="2"/>
|
||||
<rect x="50" y="42" width="6" height="2"/>
|
||||
<rect x="22" y="46" width="2" height="2"/>
|
||||
<rect x="30" y="46" width="2" height="2"/>
|
||||
<rect x="22" y="50" width="2" height="2"/>
|
||||
<rect x="26" y="50" width="2" height="2"/>
|
||||
<rect x="34" y="50" width="2" height="2"/>
|
||||
<rect x="42" y="50" width="2" height="2"/>
|
||||
<rect x="46" y="50" width="2" height="2"/>
|
||||
<rect x="54" y="50" width="2" height="2"/>
|
||||
<rect x="22" y="54" width="2" height="2"/>
|
||||
<rect x="30" y="54" width="2" height="2"/>
|
||||
<rect x="38" y="54" width="2" height="2"/>
|
||||
<rect x="46" y="54" width="2" height="2"/>
|
||||
<rect x="50" y="54" width="6" height="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="qr-guide__qr-wrap">
|
||||
<svg class="qr-guide__img" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="64" height="64" fill="#fff"/>
|
||||
<g fill="#0e1326">
|
||||
<!-- 三个定位角 -->
|
||||
<rect x="4" y="4" width="14" height="14"/>
|
||||
<rect x="6" y="6" width="10" height="10" fill="#fff"/>
|
||||
<rect x="8" y="8" width="6" height="6" fill="#0e1326"/>
|
||||
<rect x="46" y="4" width="14" height="14"/>
|
||||
<rect x="48" y="6" width="10" height="10" fill="#fff"/>
|
||||
<rect x="50" y="8" width="6" height="6" fill="#0e1326"/>
|
||||
<rect x="4" y="46" width="14" height="14"/>
|
||||
<rect x="6" y="48" width="10" height="10" fill="#fff"/>
|
||||
<rect x="8" y="50" width="6" height="6" fill="#0e1326"/>
|
||||
<!-- 数据点(模拟) -->
|
||||
<rect x="22" y="6" width="2" height="2"/>
|
||||
<rect x="26" y="6" width="2" height="2"/>
|
||||
<rect x="30" y="6" width="2" height="2"/>
|
||||
<rect x="38" y="6" width="2" height="2"/>
|
||||
<rect x="22" y="10" width="2" height="2"/>
|
||||
<rect x="34" y="10" width="2" height="2"/>
|
||||
<rect x="22" y="14" width="6" height="2"/>
|
||||
<rect x="32" y="14" width="2" height="2"/>
|
||||
<rect x="38" y="14" width="2" height="2"/>
|
||||
<rect x="6" y="22" width="2" height="2"/>
|
||||
<rect x="10" y="22" width="2" height="2"/>
|
||||
<rect x="14" y="22" width="2" height="2"/>
|
||||
<rect x="22" y="22" width="2" height="2"/>
|
||||
<rect x="26" y="22" width="6" height="2"/>
|
||||
<rect x="34" y="22" width="2" height="2"/>
|
||||
<rect x="42" y="22" width="2" height="2"/>
|
||||
<rect x="50" y="22" width="2" height="2"/>
|
||||
<rect x="54" y="22" width="2" height="2"/>
|
||||
<rect x="6" y="26" width="2" height="2"/>
|
||||
<rect x="14" y="26" width="2" height="2"/>
|
||||
<rect x="22" y="26" width="2" height="2"/>
|
||||
<rect x="30" y="26" width="2" height="2"/>
|
||||
<rect x="38" y="26" width="2" height="2"/>
|
||||
<rect x="46" y="26" width="2" height="2"/>
|
||||
<rect x="54" y="26" width="2" height="2"/>
|
||||
<rect x="10" y="30" width="2" height="2"/>
|
||||
<rect x="18" y="30" width="2" height="2"/>
|
||||
<rect x="26" y="30" width="2" height="2"/>
|
||||
<rect x="34" y="30" width="2" height="2"/>
|
||||
<rect x="42" y="30" width="2" height="2"/>
|
||||
<rect x="50" y="30" width="2" height="2"/>
|
||||
<rect x="58" y="30" width="2" height="2"/>
|
||||
<rect x="6" y="34" width="2" height="2"/>
|
||||
<rect x="14" y="34" width="2" height="2"/>
|
||||
<rect x="22" y="34" width="2" height="2"/>
|
||||
<rect x="30" y="34" width="6" height="2"/>
|
||||
<rect x="42" y="34" width="2" height="2"/>
|
||||
<rect x="50" y="34" width="2" height="2"/>
|
||||
<rect x="22" y="38" width="2" height="2"/>
|
||||
<rect x="26" y="38" width="2" height="2"/>
|
||||
<rect x="34" y="38" width="2" height="2"/>
|
||||
<rect x="42" y="38" width="2" height="2"/>
|
||||
<rect x="46" y="38" width="2" height="2"/>
|
||||
<rect x="54" y="38" width="2" height="2"/>
|
||||
<rect x="22" y="42" width="6" height="2"/>
|
||||
<rect x="34" y="42" width="2" height="2"/>
|
||||
<rect x="42" y="42" width="2" height="2"/>
|
||||
<rect x="50" y="42" width="6" height="2"/>
|
||||
<rect x="22" y="46" width="2" height="2"/>
|
||||
<rect x="30" y="46" width="2" height="2"/>
|
||||
<rect x="22" y="50" width="2" height="2"/>
|
||||
<rect x="26" y="50" width="2" height="2"/>
|
||||
<rect x="34" y="50" width="2" height="2"/>
|
||||
<rect x="42" y="50" width="2" height="2"/>
|
||||
<rect x="46" y="50" width="2" height="2"/>
|
||||
<rect x="54" y="50" width="2" height="2"/>
|
||||
<rect x="22" y="54" width="2" height="2"/>
|
||||
<rect x="30" y="54" width="2" height="2"/>
|
||||
<rect x="38" y="54" width="2" height="2"/>
|
||||
<rect x="46" y="54" width="2" height="2"/>
|
||||
<rect x="50" y="54" width="6" height="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="qr-guide__text">
|
||||
<div class="qr-guide__title">📱 扫码查看详细营养报告</div>
|
||||
<div class="qr-guide__sub">小程序内可查看每日营养摄入趋势、健康建议、餐品推荐</div>
|
||||
<span class="qr-guide__tag">🔥 今日已有 1,284 人查看</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -532,23 +595,115 @@
|
||||
}
|
||||
createConfetti();
|
||||
|
||||
/* 支付方式演示切换 */
|
||||
/* ==================== 订单数据(与 user-03 / user-04 金额一致) ==================== */
|
||||
var ORDER_TOTAL = 46.60;
|
||||
var ORDER_DISCOUNT = 5.00; /* 企业补贴 */
|
||||
var ORDER_PAYABLE = ORDER_TOTAL - ORDER_DISCOUNT; /* 41.60 */
|
||||
var USER_BALANCE = 128.50;
|
||||
var USER_NAME = '张明';
|
||||
var DEMO_BALANCE_FOR_MIXED = 20.00; /* 混合支付演示余额 */
|
||||
var DEMO_CASH_RATIO = 0.6; /* 现金+在线的现金比例 */
|
||||
var DEMO_CASH_RECEIVED = 50.00; /* 现金找零演示实收 */
|
||||
|
||||
/* ==================== 支付方式成功态配置 ==================== */
|
||||
/* icon: 成功对勾图标的覆盖(默认 ✔,部分场景替换为对应支付 icon) */
|
||||
/* title: 成功大标题 */
|
||||
/* badge: 金额下方的支付方式徽章 */
|
||||
/* accountHtml: 账户信息条 HTML(空字符串=隐藏) */
|
||||
var methodConfig = {
|
||||
/* 在线支付 */
|
||||
online: {
|
||||
badge: '📱 在线支付 · 微信',
|
||||
showAccount: false
|
||||
title: '支付成功',
|
||||
badge: '📱 在线支付 · 微信 · 已到账',
|
||||
accountHtml: ''
|
||||
},
|
||||
face: {
|
||||
badge: '👤 刷脸支付 · 已扣款',
|
||||
showAccount: true
|
||||
/* 余额支付 */
|
||||
balance: {
|
||||
title: '扣款成功',
|
||||
badge: '💰 余额支付 · 已从会员账户扣除',
|
||||
accountHtml: accountRow('账户余额 · 原余额', '¥' + USER_BALANCE.toFixed(2), '')
|
||||
+ accountDivider()
|
||||
+ accountRow('本单消费', '-¥' + ORDER_PAYABLE.toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('扣款后余额', '¥' + (USER_BALANCE - ORDER_PAYABLE).toFixed(2), 'balance')
|
||||
},
|
||||
/* 余额 + 在线 */
|
||||
'mixed-bl-online': {
|
||||
title: '支付成功',
|
||||
badge: '💰 余额 + 📱 在线 · 混合支付完成',
|
||||
accountHtml: accountRow('余额扣款', '-¥' + DEMO_BALANCE_FOR_MIXED.toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('在线支付', '-¥' + (ORDER_PAYABLE - DEMO_BALANCE_FOR_MIXED).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('实付合计', '¥' + ORDER_PAYABLE.toFixed(2), 'balance'),
|
||||
wide: true
|
||||
},
|
||||
/* 余额 + 现金 */
|
||||
'mixed-bl-cash': {
|
||||
title: '支付成功',
|
||||
badge: '💰 余额 + 💶 现金 · 混合支付完成',
|
||||
accountHtml: accountRow('余额扣款', '-¥' + DEMO_BALANCE_FOR_MIXED.toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('现金支付', '-¥' + (ORDER_PAYABLE - DEMO_BALANCE_FOR_MIXED).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('实付合计', '¥' + ORDER_PAYABLE.toFixed(2), 'balance'),
|
||||
wide: true,
|
||||
cash: true
|
||||
},
|
||||
/* 现金 + 在线 */
|
||||
'mixed-cash-online': {
|
||||
title: '支付成功',
|
||||
badge: '💶 现金 + 📱 在线 · 混合支付完成',
|
||||
accountHtml: accountRow('现金支付', '-¥' + (ORDER_PAYABLE * DEMO_CASH_RATIO).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('在线支付', '-¥' + (ORDER_PAYABLE * (1 - DEMO_CASH_RATIO)).toFixed(2), 'expense')
|
||||
+ accountDivider()
|
||||
+ accountRow('实付合计', '¥' + ORDER_PAYABLE.toFixed(2), 'balance'),
|
||||
wide: true,
|
||||
cash: true
|
||||
},
|
||||
/* 现金找零 */
|
||||
'cash-change': {
|
||||
title: '支付成功',
|
||||
badge: '💶 现金支付 · 收银员已确认',
|
||||
accountHtml: accountRow('应付金额', '¥' + ORDER_PAYABLE.toFixed(2), '')
|
||||
+ accountDivider()
|
||||
+ accountRow('实收现金', '¥' + DEMO_CASH_RECEIVED.toFixed(2), '')
|
||||
+ accountDivider()
|
||||
+ accountRow('找零', '¥' + (DEMO_CASH_RECEIVED - ORDER_PAYABLE).toFixed(2), 'change'),
|
||||
wide: true,
|
||||
cash: true
|
||||
}
|
||||
};
|
||||
|
||||
/* 账户信息条单元格构造器 */
|
||||
function accountRow(label, value, modifier) {
|
||||
var cls = modifier ? ' account-info__value--' + modifier : '';
|
||||
return '<div class="account-info__col">'
|
||||
+ '<span class="account-info__label">' + label + '</span>'
|
||||
+ '<span class="account-info__value' + cls + '">' + value + '</span>'
|
||||
+ '</div>';
|
||||
}
|
||||
function accountDivider() {
|
||||
return '<div class="account-info__divider"></div>';
|
||||
}
|
||||
|
||||
function switchDemo(method) {
|
||||
var cfg = methodConfig[method];
|
||||
if (!cfg) return;
|
||||
document.getElementById('successTitle').textContent = cfg.title;
|
||||
document.getElementById('successAmount').innerHTML = '¥' + ORDER_PAYABLE.toFixed(2);
|
||||
document.getElementById('payMethodBadge').innerHTML = cfg.badge;
|
||||
document.getElementById('accountInfo').style.display = cfg.showAccount ? 'flex' : 'none';
|
||||
var accountEl = document.getElementById('accountInfo');
|
||||
if (cfg.accountHtml) {
|
||||
accountEl.innerHTML = cfg.accountHtml;
|
||||
accountEl.style.display = 'flex';
|
||||
accountEl.classList.toggle('account-info--wide', !!cfg.wide);
|
||||
accountEl.classList.toggle('account-info--cash', !!cfg.cash);
|
||||
} else {
|
||||
accountEl.style.display = 'none';
|
||||
accountEl.innerHTML = '';
|
||||
}
|
||||
document.querySelectorAll('.demo-switcher__btn').forEach(function(btn) {
|
||||
btn.classList.toggle('active', btn.dataset.method === method);
|
||||
});
|
||||
@@ -557,8 +712,27 @@
|
||||
document.querySelectorAll('.demo-switcher__btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
switchDemo(btn.dataset.method);
|
||||
/* 同步 URL 参数,便于复制链接分享(与 user-03-paying 风格一致) */
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('method', btn.dataset.method);
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
});
|
||||
});
|
||||
|
||||
/* URL 参数驱动:支持 ?method=xxx 从收银员屏 03 跳转携带支付方式 */
|
||||
function loadFromUrl() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var method = params.get('method');
|
||||
var valid = ['online', 'balance', 'mixed-bl-online', 'mixed-bl-cash', 'mixed-cash-online', 'cash-change'];
|
||||
if (method && valid.indexOf(method) !== -1) {
|
||||
switchDemo(method);
|
||||
} else {
|
||||
switchDemo('online');
|
||||
}
|
||||
}
|
||||
|
||||
/* 初始化(优先 URL 参数,无参数时默认展示在线支付) */
|
||||
loadFromUrl();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
<a class="card" href="checkout-device/index.html">
|
||||
<span class="card__status card__status--done">已设计</span>
|
||||
<div class="card__icon">🧾</div>
|
||||
<div class="card__name">结算设备</div>
|
||||
<div class="card__name">结算台</div>
|
||||
<div class="card__desc">智能结算与支付</div>
|
||||
</a>
|
||||
<a class="card" href="nutrition-score-screen/index.html">
|
||||
|
||||
Reference in New Issue
Block a user