feat: 结算设备复秤对账(03收银页对账条+08补齐明细智能推荐) + 营养管理端报表中心两页面补齐(场所经营分析/场所目标考核)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
ee6041257f
commit
dd0331bb98
@@ -1107,6 +1107,54 @@
|
||||
.toast--success { background: rgba(82, 196, 26, 0.92); }
|
||||
.toast--warn { background: rgba(250, 173, 22, 0.92); }
|
||||
.toast--error { background: rgba(245, 34, 45, 0.92); }
|
||||
|
||||
/* ============ 复秤对账条 ============ */
|
||||
.weigh-check {
|
||||
padding: 9px 24px;
|
||||
background: rgba(82, 196, 26, 0.06);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
flex-shrink: 0;
|
||||
transition: background 0.25s, border-color 0.25s;
|
||||
}
|
||||
.weigh-check__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
.weigh-check__item strong {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.weigh-check__diff strong { color: #4ade80; }
|
||||
.weigh-check__status {
|
||||
margin-left: auto;
|
||||
font-weight: 700;
|
||||
color: #4ade80;
|
||||
}
|
||||
.weigh-check--warn {
|
||||
background: rgba(251, 146, 60, 0.1);
|
||||
border-bottom-color: rgba(251, 146, 60, 0.45);
|
||||
}
|
||||
.weigh-check--warn .weigh-check__diff strong { color: #fb923c; }
|
||||
.weigh-check--warn .weigh-check__status {
|
||||
color: #fb923c;
|
||||
animation: weighBlink 1.2s infinite;
|
||||
}
|
||||
@keyframes weighBlink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.45; }
|
||||
}
|
||||
/* 补齐明细按钮警示呼吸态 */
|
||||
.header-btn--warn {
|
||||
background: rgba(251, 146, 60, 0.2) !important;
|
||||
border-color: rgba(251, 146, 60, 0.6) !important;
|
||||
color: #fb923c !important;
|
||||
animation: weighBlink 1.2s infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1124,6 +1172,7 @@
|
||||
<button class="demo-control__btn active" data-scene="normal" onclick="switchScene('normal')">余额充足</button>
|
||||
<button class="demo-control__btn" data-scene="low-balance" onclick="switchScene('low-balance')">余额不足</button>
|
||||
<button class="demo-control__btn demo-control__btn--toggle" id="couponToggle" onclick="toggleCouponPreset()">代金券预载</button>
|
||||
<button class="demo-control__btn demo-control__btn--toggle" id="weighToggle" onclick="toggleWeighMode()">复秤正常</button>
|
||||
<button class="demo-control__btn" onclick="resetDemo()">↻</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1160,12 +1209,22 @@
|
||||
<span class="items-col__count" id="itemCount">7 件</span>
|
||||
</div>
|
||||
<div class="items-col__actions">
|
||||
<a class="header-btn header-btn--primary" href="08-reconcile.html">
|
||||
<a class="header-btn header-btn--primary" id="reconcileBtn" href="08-reconcile.html">
|
||||
✏️ 补齐明细
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 复秤对账条(自动读秤,对比记录总重与实际复秤总净重) -->
|
||||
<div class="weigh-check" id="weighCheck">
|
||||
<div class="weigh-check__row">
|
||||
<span class="weigh-check__item">📝 记录取餐 <strong id="wcRecord">1240g</strong></span>
|
||||
<span class="weigh-check__item">⚖️ 实际复秤 <strong id="wcActual">1240g</strong></span>
|
||||
<span class="weigh-check__item weigh-check__diff">差额 <strong id="wcDiff">0g</strong></span>
|
||||
<span class="weigh-check__status" id="wcStatus">✅ 核对通过</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items-list" id="itemsList">
|
||||
<!-- 表头(独立表格,固定在列表顶部) -->
|
||||
<table class="items-table">
|
||||
@@ -2031,8 +2090,59 @@
|
||||
showToast('演示已重置', 'success');
|
||||
}
|
||||
|
||||
/* ==================== 复秤对账 ==================== */
|
||||
/* 记录取餐合计(与明细静态数据一致);复秤三态:正常/偏多(漏记)/偏少(多记) */
|
||||
var RECORD_WEIGHT = 1240;
|
||||
var WEIGH_MODES = [
|
||||
{ key: 'normal', label: '复秤正常', actual: 1240 },
|
||||
{ key: 'over', label: '漏记2条', actual: 1490 },
|
||||
{ key: 'under', label: '多记2条', actual: 1040 }
|
||||
];
|
||||
var weighModeIdx = 0;
|
||||
|
||||
/* 循环切换复秤场景(演示自动读秤) */
|
||||
function toggleWeighMode() {
|
||||
weighModeIdx = (weighModeIdx + 1) % WEIGH_MODES.length;
|
||||
var m = WEIGH_MODES[weighModeIdx];
|
||||
var toggle = document.getElementById('weighToggle');
|
||||
toggle.textContent = m.label;
|
||||
toggle.classList.toggle('on', weighModeIdx !== 0);
|
||||
refreshWeighCheck();
|
||||
showToast('已切换复秤:' + m.label, weighModeIdx === 0 ? 'success' : 'warn');
|
||||
}
|
||||
|
||||
/* 刷新复秤对账条:计算差额、判定阈值、联动警示与跳转 */
|
||||
function refreshWeighCheck() {
|
||||
var m = WEIGH_MODES[weighModeIdx];
|
||||
var actual = m.actual;
|
||||
var diff = actual - RECORD_WEIGHT;
|
||||
var threshold = Math.max(50, RECORD_WEIGHT * 0.05);
|
||||
var over = Math.abs(diff) > threshold;
|
||||
|
||||
document.getElementById('wcRecord').textContent = RECORD_WEIGHT + 'g';
|
||||
document.getElementById('wcActual').textContent = actual + 'g';
|
||||
document.getElementById('wcDiff').textContent = (diff > 0 ? '+' : '') + diff + 'g';
|
||||
|
||||
var box = document.getElementById('weighCheck');
|
||||
var status = document.getElementById('wcStatus');
|
||||
var btn = document.getElementById('reconcileBtn');
|
||||
|
||||
if (over) {
|
||||
box.classList.add('weigh-check--warn');
|
||||
status.innerHTML = (diff > 0) ? '⚠️ 疑似漏记,请补齐' : '⚠️ 疑似多记,请核对';
|
||||
btn.classList.add('header-btn--warn');
|
||||
} else {
|
||||
box.classList.remove('weigh-check--warn');
|
||||
status.innerHTML = '✅ 核对通过';
|
||||
btn.classList.remove('header-btn--warn');
|
||||
}
|
||||
/* 跳转补齐明细时携带当前复秤值 */
|
||||
btn.href = '08-reconcile.html?actual=' + actual;
|
||||
}
|
||||
|
||||
/* 初始化 */
|
||||
switchPay('online');
|
||||
refreshWeighCheck();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -714,6 +714,105 @@
|
||||
.toast--success { background: rgba(82, 196, 26, 0.92); }
|
||||
.toast--warn { background: rgba(250, 173, 22, 0.92); }
|
||||
.toast--error { background: rgba(245, 34, 45, 0.92); }
|
||||
|
||||
/* ============ 复秤对账横条 ============ */
|
||||
.weigh-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22px;
|
||||
padding: 9px 24px;
|
||||
background: rgba(26, 43, 74, 0.4);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
transition: background 0.25s, border-color 0.25s;
|
||||
}
|
||||
.weigh-bar__title { font-weight: 700; color: #fff; letter-spacing: 1px; }
|
||||
.weigh-bar__item strong {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.weigh-bar__diff strong { color: #4ade80; }
|
||||
.weigh-bar__status { margin-left: auto; font-weight: 700; }
|
||||
.weigh-bar--ok .weigh-bar__status { color: #4ade80; }
|
||||
.weigh-bar--warn {
|
||||
background: rgba(251, 146, 60, 0.1);
|
||||
border-bottom-color: rgba(251, 146, 60, 0.45);
|
||||
}
|
||||
.weigh-bar--warn .weigh-bar__diff strong { color: #fb923c; }
|
||||
.weigh-bar--warn .weigh-bar__status {
|
||||
color: #fb923c;
|
||||
animation: weighBlink 1.2s infinite;
|
||||
}
|
||||
@keyframes weighBlink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.45; }
|
||||
}
|
||||
|
||||
/* ============ 智能推荐卡片 ============ */
|
||||
.rec-card {
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
border: 1px solid rgba(251, 191, 36, 0.28);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.rec-head {
|
||||
color: #fbbf24;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.rec-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
border-top: 1px dashed rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.rec-item__rank {
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
background: rgba(251, 191, 36, 0.18);
|
||||
color: #fbbf24; font-size: 10px; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rec-item__body { flex: 1; min-width: 0; }
|
||||
.rec-item__names {
|
||||
color: #fff; font-weight: 600; line-height: 1.45;
|
||||
overflow: hidden; display: -webkit-box;
|
||||
-webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||||
}
|
||||
.rec-item__meta {
|
||||
font-size: 10px; color: rgba(255, 255, 255, 0.5);
|
||||
font-family: Menlo, Consolas, monospace; margin-top: 2px;
|
||||
}
|
||||
.rec-item__btn {
|
||||
padding: 3px 10px; border-radius: 6px; font-size: 11px; font-weight: 700;
|
||||
cursor: pointer; border: 1px solid; font-family: inherit; flex-shrink: 0;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.rec-item__btn--add {
|
||||
background: rgba(82, 196, 26, 0.14);
|
||||
border-color: rgba(82, 196, 26, 0.4); color: #4ade80;
|
||||
}
|
||||
.rec-item__btn--add:hover { background: rgba(82, 196, 26, 0.3); color: #fff; }
|
||||
.rec-item__btn--remove {
|
||||
background: rgba(245, 34, 45, 0.12);
|
||||
border-color: rgba(245, 34, 45, 0.35); color: #ff6b6b;
|
||||
}
|
||||
.rec-item__btn--remove:hover { background: rgba(245, 34, 45, 0.28); color: #fff; }
|
||||
.rec-empty { color: rgba(255, 255, 255, 0.4); text-align: center; padding: 8px 0; }
|
||||
/* 推荐徽标(左列列表内) */
|
||||
.rec-badge {
|
||||
display: inline-block; padding: 1px 6px; border-radius: 7px;
|
||||
background: rgba(251, 191, 36, 0.18); color: #fbbf24;
|
||||
border: 1px solid rgba(251, 191, 36, 0.4);
|
||||
font-size: 9px; font-weight: 700; letter-spacing: 0.5px; flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -727,6 +826,15 @@
|
||||
<span class="top-bar__meta">订单 CO-20260806-031</span>
|
||||
</div>
|
||||
|
||||
<!-- 复秤对账横条(实际称重 vs 记录合计,读取结算台传入的复秤值) -->
|
||||
<div class="weigh-bar" id="weighBar">
|
||||
<span class="weigh-bar__title">⚖️ 复秤对账</span>
|
||||
<span class="weigh-bar__item">实际称重 <strong id="wbActual">--</strong></span>
|
||||
<span class="weigh-bar__item">记录合计 <strong id="wbRecord">--</strong></span>
|
||||
<span class="weigh-bar__item weigh-bar__diff">差额 <strong id="wbDiff">--</strong></span>
|
||||
<span class="weigh-bar__status" id="wbStatus"></span>
|
||||
</div>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<div class="main-area">
|
||||
|
||||
@@ -759,12 +867,18 @@
|
||||
<!-- 右列:差额汇总 -->
|
||||
<div class="summary-col">
|
||||
|
||||
<!-- 智能推荐(根据复秤差额推荐删/补) -->
|
||||
<div id="recBlock" style="display:none;">
|
||||
<div class="section-label">💡 智能推荐</div>
|
||||
<div class="rec-card" id="recCard" style="margin-top:8px;"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="section-label">金额变化对比</div>
|
||||
<div class="compare-block" style="margin-top:8px;">
|
||||
<div class="compare-row compare-row--old">
|
||||
<span class="compare-row__label">原订单合计</span>
|
||||
<span class="compare-row__value" id="originalAmount">¥46.60</span>
|
||||
<span class="compare-row__value" id="originalAmount">¥69.40</span>
|
||||
</div>
|
||||
<div class="compare-row">
|
||||
<span class="compare-row__label">本次修正</span>
|
||||
@@ -778,7 +892,7 @@
|
||||
</div>
|
||||
<div class="compare-row compare-row--delta" id="deltaRow">
|
||||
<span class="compare-row__label">应付金额</span>
|
||||
<span class="compare-row__value" id="newPayable">¥41.60</span>
|
||||
<span class="compare-row__value" id="newPayable">¥64.40</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -810,20 +924,23 @@
|
||||
<script>
|
||||
/* ==================== Mock 数据 ==================== */
|
||||
/* 原始订单金额(从 03-checkout 同步,演示用) */
|
||||
var ORIGINAL_TOTAL = 46.60;
|
||||
var ORIGINAL_TOTAL = 69.40;
|
||||
var DISCOUNT = 5.00;
|
||||
|
||||
/*
|
||||
* 当前订单明细(每条独立记录,允许同名菜品多条)
|
||||
* status: normal | removed | added
|
||||
* origin: recognized(已识别,原订单) | unmatched(从未识别取餐记录加入)
|
||||
* 与 03-checkout 取餐明细保持一致(7 项,合计 1240g / ¥69.40)
|
||||
*/
|
||||
var currentItems = [
|
||||
{ id: 1001, name: '清炒时蔬', cat: 'line', weight: 150, unitPrice: 4.00, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1002, name: '米饭', cat: 'line', weight: 250, unitPrice: 1.60, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1003, name: '红烧肉', cat: 'booth', weight: 180, unitPrice: 12.00, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1004, name: '鱼香肉丝', cat: 'booth', weight: 160, unitPrice: 7.50, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1005, name: '冬瓜排骨汤', cat: 'booth', weight: 300, unitPrice: 1.00, status: 'normal', origin: 'recognized' }
|
||||
{ id: 1001, name: '清炒时蔬', cat: 'line', weight: 150, unitPrice: 4.00, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1002, name: '米饭', cat: 'line', weight: 250, unitPrice: 1.60, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1003, name: '红烧肉', cat: 'booth', weight: 180, unitPrice: 12.00, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1004, name: '鱼香肉丝', cat: 'booth', weight: 160, unitPrice: 7.50, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1005, name: '冬瓜排骨汤', cat: 'booth', weight: 300, unitPrice: 1.00, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1006, name: '卤鸡腿', cat: 'booth', weight: 120, unitPrice: 15.00, status: 'normal', origin: 'recognized' },
|
||||
{ id: 1007, name: '凉拌木耳', cat: 'booth', weight: 80, unitPrice: 6.00, status: 'normal', origin: 'recognized' }
|
||||
];
|
||||
|
||||
/*
|
||||
@@ -847,6 +964,181 @@
|
||||
/* 已加入订单的未识别 recordId 集合(用于在补齐列表标记「已加入」) */
|
||||
var addedRecordIds = new Set();
|
||||
|
||||
/* ==================== 复秤对账 ==================== */
|
||||
/* 实际复秤总净重:优先读取结算台跳转传入的 actual 参数,无则默认 1240g(正常) */
|
||||
function parseActualWeight() {
|
||||
var m = location.search.match(/[?&]actual=(\d+(?:\.\d+)?)/);
|
||||
return m ? parseFloat(m[1]) : 1240;
|
||||
}
|
||||
var ACTUAL_WEIGHT = parseActualWeight();
|
||||
var THRESHOLD_RATIO = 0.05;
|
||||
|
||||
/* 当前订单有效记录总重(排除已移除) */
|
||||
function getRecordWeight() {
|
||||
return currentItems.reduce(function(s, it) {
|
||||
return s + (it.status === 'removed' ? 0 : it.weight);
|
||||
}, 0);
|
||||
}
|
||||
/* 出入判定阈值:固定 50g 与记录总重 5% 取大者 */
|
||||
function getThreshold() {
|
||||
return Math.max(50, getRecordWeight() * THRESHOLD_RATIO);
|
||||
}
|
||||
/* 差额 = 实际复秤 - 记录总重;正=漏记,负=多记 */
|
||||
function getDiff() {
|
||||
return ACTUAL_WEIGHT - getRecordWeight();
|
||||
}
|
||||
/* 最高推荐方案内条目集合(用于左列列表多条目高亮) */
|
||||
var topAddKeys = new Set();
|
||||
var topRemoveKeys = new Set();
|
||||
/* 当前渲染的方案列表(供一键应用按索引取值) */
|
||||
var currentPlans = [];
|
||||
|
||||
/* 收集候选条目(漏记→未识别池,多记→当前订单有效项) */
|
||||
function getCandidates() {
|
||||
var diff = getDiff();
|
||||
var cands = [];
|
||||
if (diff > getThreshold()) {
|
||||
unmatchedRecords.forEach(function(r) {
|
||||
if (addedRecordIds.has(r.recordId)) return;
|
||||
cands.push({ key: r.recordId, name: r.name, weight: r.weight, action: 'add' });
|
||||
});
|
||||
} else if (diff < -getThreshold()) {
|
||||
currentItems.forEach(function(it) {
|
||||
if (it.status === 'removed') return;
|
||||
cands.push({ key: it.id, name: it.name, weight: it.weight, action: 'remove' });
|
||||
});
|
||||
}
|
||||
return cands;
|
||||
}
|
||||
|
||||
/* 组合推荐:枚举 1~MAX_K 条组合,按匹配度优先、条目数次优排序 */
|
||||
var MAX_COMBO_K = 2;
|
||||
function getPlans() {
|
||||
var diff = getDiff();
|
||||
var target = Math.abs(diff);
|
||||
var cands = getCandidates();
|
||||
var plans = [];
|
||||
var n = cands.length;
|
||||
for (var i = 0; i < n; i++) {
|
||||
/* 单条方案 */
|
||||
plans.push({
|
||||
items: [cands[i]],
|
||||
sum: cands[i].weight,
|
||||
delta: Math.abs(cands[i].weight - target),
|
||||
count: 1
|
||||
});
|
||||
/* 两条组合方案 */
|
||||
for (var j = i + 1; j < n; j++) {
|
||||
var sum2 = cands[i].weight + cands[j].weight;
|
||||
plans.push({
|
||||
items: [cands[i], cands[j]],
|
||||
sum: sum2,
|
||||
delta: Math.abs(sum2 - target),
|
||||
count: 2
|
||||
});
|
||||
}
|
||||
}
|
||||
plans.sort(function(a, b) {
|
||||
if (a.delta !== b.delta) return a.delta - b.delta;
|
||||
return a.count - b.count;
|
||||
});
|
||||
return plans;
|
||||
}
|
||||
|
||||
/* 更新最高推荐方案内全部条目(供左列列表多条目高亮) */
|
||||
function computeTopRecommend() {
|
||||
var plans = getPlans();
|
||||
topAddKeys.clear();
|
||||
topRemoveKeys.clear();
|
||||
if (plans.length > 0) {
|
||||
plans[0].items.forEach(function(c) {
|
||||
if (c.action === 'add') topAddKeys.add(c.key);
|
||||
else topRemoveKeys.add(c.key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* 渲染顶部复秤对账横条 */
|
||||
function renderWeighBar() {
|
||||
var record = getRecordWeight();
|
||||
var diff = getDiff();
|
||||
var over = Math.abs(diff) > getThreshold();
|
||||
|
||||
document.getElementById('wbActual').textContent = ACTUAL_WEIGHT + 'g';
|
||||
document.getElementById('wbRecord').textContent = record + 'g';
|
||||
var diffEl = document.getElementById('wbDiff');
|
||||
diffEl.textContent = (diff > 0 ? '+' : '') + diff + 'g';
|
||||
diffEl.style.color = over ? '#fb923c' : '#4ade80';
|
||||
|
||||
var bar = document.getElementById('weighBar');
|
||||
var status = document.getElementById('wbStatus');
|
||||
bar.classList.toggle('weigh-bar--warn', over);
|
||||
bar.classList.toggle('weigh-bar--ok', !over);
|
||||
if (!over) {
|
||||
status.innerHTML = '✅ 重量已对齐,可直接结算';
|
||||
} else if (diff > 0) {
|
||||
status.innerHTML = '⚠️ 疑似漏记 ' + diff + 'g,请补录';
|
||||
} else {
|
||||
status.innerHTML = '⚠️ 疑似多记 ' + (-diff) + 'g,请核对';
|
||||
}
|
||||
}
|
||||
|
||||
/* 渲染右列智能推荐卡片(组合方案列表) */
|
||||
function renderRecommend() {
|
||||
var diff = getDiff();
|
||||
var over = Math.abs(diff) > getThreshold();
|
||||
var block = document.getElementById('recBlock');
|
||||
var card = document.getElementById('recCard');
|
||||
if (!over) {
|
||||
block.style.display = 'none';
|
||||
currentPlans = [];
|
||||
return;
|
||||
}
|
||||
var plans = getPlans();
|
||||
if (plans.length === 0) {
|
||||
block.style.display = 'none';
|
||||
currentPlans = [];
|
||||
return;
|
||||
}
|
||||
block.style.display = '';
|
||||
var head = diff > 0
|
||||
? '差额 +' + diff + 'g,疑似漏记,推荐补录'
|
||||
: '差额 ' + diff + 'g,疑似多记,推荐移除';
|
||||
var html = '<div class="rec-head">' + head + '</div>';
|
||||
var top = plans.slice(0, 4);
|
||||
currentPlans = top;
|
||||
top.forEach(function(p, i) {
|
||||
var isAdd = p.items[0].action === 'add';
|
||||
var itemStr = p.items.map(function(c) { return c.name + ' ' + c.weight + 'g'; }).join(' + ');
|
||||
var deltaStr = p.delta === 0 ? '精确匹配' : '差 ' + p.delta + 'g';
|
||||
var btn = isAdd
|
||||
? '<button class="rec-item__btn rec-item__btn--add" onclick="applyPlan(' + i + ')">+ 一键加入</button>'
|
||||
: '<button class="rec-item__btn rec-item__btn--remove" onclick="applyPlan(' + i + ')">− 一键移除</button>';
|
||||
html += '<div class="rec-item">'
|
||||
+ '<span class="rec-item__rank">' + (i + 1) + '</span>'
|
||||
+ '<div class="rec-item__body">'
|
||||
+ '<div class="rec-item__names">' + itemStr + '</div>'
|
||||
+ '<div class="rec-item__meta">' + p.count + ' 条 · ' + deltaStr + '</div>'
|
||||
+ '</div>'
|
||||
+ btn
|
||||
+ '</div>';
|
||||
});
|
||||
card.innerHTML = html;
|
||||
}
|
||||
|
||||
/* 一键应用组合方案:连续加入/移除方案内全部条目 */
|
||||
function applyPlan(idx) {
|
||||
var plan = currentPlans[idx];
|
||||
if (!plan) return;
|
||||
plan.items.forEach(function(c) {
|
||||
if (c.action === 'add') {
|
||||
addFromUnmatched(c.key);
|
||||
} else {
|
||||
removeItem(c.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* 单条小计金额 */
|
||||
function subtotal(it) {
|
||||
return it.weight ? it.weight / 100 * it.unitPrice : it.unitPrice;
|
||||
@@ -859,6 +1151,7 @@
|
||||
|
||||
/* ================ 渲染:当前订单(表格化,对齐 03-checkout) ================ */
|
||||
function renderCurrent() {
|
||||
computeTopRecommend();
|
||||
var html = ''
|
||||
+ '<table class="items-table">'
|
||||
+ ' <colgroup>'
|
||||
@@ -898,6 +1191,9 @@
|
||||
} else if (it.status === 'removed') {
|
||||
badge = '<span class="change-badge change-badge--remove">已移除</span>';
|
||||
}
|
||||
if (topRemoveKeys.has(it.id)) {
|
||||
badge += '<span class="rec-badge">🔥 推荐删除</span>';
|
||||
}
|
||||
|
||||
/* 操作按钮:已移除→恢复,新加→直接删除,正常→移除 */
|
||||
var opBtn = '';
|
||||
@@ -950,6 +1246,7 @@
|
||||
|
||||
/* ================ 渲染:未识别取餐记录列表(添加商品 Tab) ================ */
|
||||
function renderUnmatched() {
|
||||
computeTopRecommend();
|
||||
var html = ''
|
||||
+ '<div class="unmatched-tip">'
|
||||
+ ' <span class="unmatched-tip__icon">⚠️</span>'
|
||||
@@ -1001,6 +1298,7 @@
|
||||
var catLabel = r.cat === 'line' ? '餐线' : '档口';
|
||||
var catCls = r.cat === 'line' ? 'cell-cat--line' : 'cell-cat--booth';
|
||||
var isAdded = addedRecordIds.has(r.recordId);
|
||||
var recBadge = topAddKeys.has(r.recordId) ? '<span class="rec-badge">🔥 推荐补录</span>' : '';
|
||||
var opBtn;
|
||||
if (isAdded) {
|
||||
opBtn = '<span class="op-btn op-btn--added">✓ 已加</span>';
|
||||
@@ -1011,7 +1309,7 @@
|
||||
+ '<tr>'
|
||||
+ ' <td class="cell-time">' + r.time + '</td>'
|
||||
+ ' <td><span class="cell-cat ' + catCls + '">' + catLabel + '</span></td>'
|
||||
+ ' <td><div class="um-name">' + r.name + '</div></td>'
|
||||
+ ' <td><div class="um-name">' + r.name + recBadge + '</div></td>'
|
||||
+ ' <td class="um-unit">¥' + r.unitPrice.toFixed(2) + '/100g</td>'
|
||||
+ ' <td class="um-weight"><span class="um-weight__pill">' + r.weight + 'g</span></td>'
|
||||
+ ' <td class="um-price">¥' + subtotal(r).toFixed(2) + '</td>'
|
||||
@@ -1155,6 +1453,10 @@
|
||||
|
||||
var deltaRow = document.getElementById('deltaRow');
|
||||
deltaRow.className = change >= 0 ? 'compare-row compare-row--delta' : 'compare-row compare-row--delta compare-row--delta-minus';
|
||||
|
||||
/* 复秤对账横条与智能推荐实时刷新 */
|
||||
renderWeighBar();
|
||||
renderRecommend();
|
||||
}
|
||||
|
||||
/* 应用变更 */
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# 结算设备 CHANGELOG
|
||||
|
||||
## 2026-08-20 | 收银页复秤对账 + 补齐明细智能推荐(03 / 08)
|
||||
|
||||
### 业务背景
|
||||
结算收银流程中,顾客实际取餐总重与系统记录总重可能存在偏差——漏记未识别到人的取餐记录,或多记了应移除的项,收银员需人工核对。本次在收银页(`03-checkout`)与补齐明细页(`08-reconcile`)引入「复秤对账」能力:秤具自动回传实际总净重,系统对比记录总重,差额超阈值时主动警示并智能推荐补录/移除方案。
|
||||
|
||||
### 变更清单
|
||||
- **`03-checkout.html` 收银页**:
|
||||
- 取餐明细头部下方新增「复秤对账条」,自动读秤对比「记录取餐总重」与「实际复秤净重」,展示差额
|
||||
- 顶部演示工具条新增「复秤正常」切换按钮,三态循环:复秤正常(1240g)/ 漏记 2 条(1490g)/ 多记 2 条(1040g)
|
||||
- 差额超阈值(固定 50g 与记录总重 5% 取大)时:对账条橙色警示 + 状态文案(疑似漏记/多记)+「补齐明细」按钮呼吸警示态
|
||||
- 跳转 `08-reconcile.html` 时携带 `?actual=xxx` 复秤值参数
|
||||
- **`08-reconcile.html` 补齐明细页**:
|
||||
- 顶部新增「复秤对账横条」:记录总重(按当前订单实时计算)/ 实际复秤 / 差额;重量对齐显示 ✅,超阈值警示
|
||||
- 差额算法:实际复秤 − 记录总重,正=漏记(从未识别池补录),负=多记(从当前订单移除)
|
||||
- 右列新增「智能推荐卡片」:枚举候选条目(漏记→未识别池,多记→当前订单有效项)的 1~2 条组合,按「匹配度优先、条目数次优」排序,展示 Top4 方案,每方案带「+ 一键加入 / − 一键移除」
|
||||
- 最高推荐方案内条目在左列列表高亮「🔥 推荐补录 / 推荐删除」徽标
|
||||
- 一键应用组合方案:连续加入/移除方案内全部条目
|
||||
|
||||
### 技术实现
|
||||
- 复秤对账条/横条共用以 `Math.max(50, 记录总重 * 0.05)` 为出入判定阈值,`weighBlink` 呼吸动画驱动警示态
|
||||
- 智能推荐采用组合枚举(单条 + 两条),`delta` 越接近差额目标、条目数越少越靠前
|
||||
- 03 与 08 通过 URL 参数 `?actual=xxx` 串联复秤值,保证跨页语义一致
|
||||
|
||||
### 不变更
|
||||
- 03-checkout 的支付方式体系、取餐明细表格化、代金券/混合支付逻辑保持不变
|
||||
- 08-reconcile 的表格化结构、未识别取餐记录加入/移除流程、差额汇总与变更日志保持不变
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-10 | 餐次管理模块新增(10-13 四页联动)
|
||||
|
||||
### 业务背景
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>营养管理 - 场所经营分析 | 健康CQ原型</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
@@ -40,10 +41,128 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
|
||||
/* 主区域 */
|
||||
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
|
||||
/* 页面占位 */
|
||||
.page-placeholder{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:48px 24px;text-align:center;}
|
||||
.page-placeholder__title{font-size:var(--font-xl);font-weight:600;color:var(--text-primary);margin-bottom:8px;}
|
||||
.page-placeholder__hint{font-size:var(--font-sm);color:var(--text-placeholder);}
|
||||
/* 页面信息栏(食堂切换区) */
|
||||
.page-header-bar{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
|
||||
.page-header-bar__left{display:flex;align-items:baseline;gap:12px;}
|
||||
.page-header-bar__title{font-size:var(--font-lg);font-weight:700;color:var(--text-primary);}
|
||||
.page-header-bar__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
|
||||
.page-header-bar__right{display:flex;align-items:center;gap:12px;}
|
||||
.page-header-bar__right label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
|
||||
.canteen-select{height:36px;border:2px solid var(--primary);border-radius:var(--radius-sm);padding:0 32px 0 14px;font-size:var(--font-sm);color:var(--primary);font-weight:500;outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%231890FF'/%3E%3C/svg%3E") no-repeat right 12px center;appearance:none;cursor:pointer;min-width:150px;}
|
||||
/* 筛选栏 */
|
||||
.filter-bar{display:flex;align-items:center;gap:14px;background:#fff;padding:16px 20px;border-radius:var(--radius-md);margin-bottom:16px;box-shadow:var(--shadow-sm);flex-wrap:wrap;}
|
||||
.filter-item{display:flex;align-items:center;gap:8px;}
|
||||
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
|
||||
.filter-input,.filter-select{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:140px;}
|
||||
.filter-select{padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;appearance:none;cursor:pointer;}
|
||||
.filter-input:focus,.filter-select:focus{border-color:var(--primary);}
|
||||
.filter-search{position:relative;}
|
||||
.filter-search input{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px 0 30px;font-size:var(--font-sm);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none'%3E%3Ccircle cx='6' cy='6' r='4.5' stroke='%23999' stroke-width='1.2'/%3E%3Cpath d='M9.5 9.5L12.5 12.5' stroke='%23999' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat 10px center;width:220px;}
|
||||
.filter-search input:focus{border-color:var(--primary);}
|
||||
/* 范围筛选 */
|
||||
.range-box{display:flex;align-items:center;gap:4px;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;background:#fff;}
|
||||
.range-box:focus-within{border-color:var(--primary);}
|
||||
.range-input{border:none;outline:none;width:64px;height:100%;font-size:var(--font-sm);color:var(--text-primary);background:transparent;}
|
||||
.range-input::-webkit-inner-spin-button,.range-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0;}
|
||||
.range-input{-moz-appearance:textfield;}
|
||||
.range-sep{color:var(--text-placeholder);font-size:var(--font-sm);}
|
||||
.range-unit{color:var(--text-placeholder);font-size:var(--font-xs);white-space:nowrap;}
|
||||
.btn{height:34px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
|
||||
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
|
||||
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
|
||||
/* 操作栏 */
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
/* 表格卡片 */
|
||||
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
|
||||
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.table-card__title{font-size:var(--font-base);font-weight:600;}
|
||||
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
|
||||
.table-card__content table{min-width:100%;white-space:nowrap;border-collapse:collapse;width:100%;}
|
||||
/* 双表头 */
|
||||
thead th{font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;padding:11px 14px;text-align:center;}
|
||||
thead th.group-th{background:#EDF4FF;color:#1d39c4;font-weight:600;border-bottom:1px solid var(--border);border-right:1px solid #E0EAFA;padding:10px 14px;}
|
||||
thead th.group-th:last-of-type{border-right:none;}
|
||||
thead tr.sub-row th{background:#FAFAFA;border-right:1px solid #F0F0F0;padding:10px 14px;}
|
||||
tbody td{padding:13px 14px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;text-align:center;}
|
||||
tbody tr:last-child td{border-bottom:none;}
|
||||
tbody tr:hover td{background:#F8FBFF;}
|
||||
td.td-left{text-align:left;}
|
||||
/* 数值强调 */
|
||||
.num{font-variant-numeric:tabular-nums;font-weight:500;}
|
||||
.num--money{color:#CF1322;}
|
||||
.num--rate{color:#389E0D;}
|
||||
/* 备注列:固定列宽 + 省略号 + 悬浮查看 */
|
||||
.note-cell{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;cursor:help;}
|
||||
/* 状态标签 */
|
||||
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
|
||||
.tag-green{background:#F6FFED;color:var(--success);}
|
||||
.tag-gray{background:#F5F5F5;color:#999;}
|
||||
.tag-orange{background:#FFF7E6;color:#D46B08;}
|
||||
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;margin:0 6px;}.link:hover{color:#40a9ff;}
|
||||
/* 操作列固定右侧 */
|
||||
thead th.th-actions,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:3;white-space:nowrap;}
|
||||
thead th.th-actions{background:#FAFAFA;z-index:4;}
|
||||
tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
/* 分页 */
|
||||
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
|
||||
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
|
||||
/* 弹窗通用 */
|
||||
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;}
|
||||
.modal{background:#fff;border-radius:8px;max-height:88vh;overflow-y:auto;box-shadow:0 8px 24px rgba(0,0,0,0.16);}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
|
||||
.modal-title{font-size:16px;font-weight:600;color:#262626;}
|
||||
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}.modal-close:hover{color:#262626;}
|
||||
.modal-body{padding:24px;}
|
||||
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
|
||||
.detail-info{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px 24px;margin-bottom:20px;padding:16px;background:#FAFAFA;border-radius:var(--radius-md);}
|
||||
.detail-info__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);}
|
||||
.detail-info__label{color:var(--text-secondary);}
|
||||
.detail-info__value{color:var(--text-primary);font-weight:500;}
|
||||
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:16px;}
|
||||
.form-item label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.form-item input,.form-item textarea{border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;width:100%;}
|
||||
.form-item input{height:36px;}
|
||||
.form-item textarea{height:72px;padding:10px 12px;resize:none;font-family:inherit;line-height:1.6;}
|
||||
.form-item input:focus,.form-item textarea:focus{border-color:var(--primary);}
|
||||
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);}
|
||||
/* ===== 详情报表(正式报表样式) ===== */
|
||||
.report-modal{width:900px;}
|
||||
.report-toolbar{display:flex;align-items:center;gap:10px;}
|
||||
.report-toolbar .btn{height:32px;}
|
||||
.report-body{background:#fff;}
|
||||
.report-title{text-align:center;font-size:22px;font-weight:700;color:#1d1d1f;letter-spacing:3px;margin-bottom:6px;}
|
||||
.report-subtitle{text-align:center;font-size:var(--font-sm);color:var(--text-placeholder);letter-spacing:1px;margin-bottom:6px;}
|
||||
.report-divider{height:2px;background:linear-gradient(90deg,transparent,#1890FF,transparent);margin-bottom:20px;}
|
||||
.report-meta{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;padding:14px 16px;background:#F7F9FC;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:20px;font-size:var(--font-sm);}
|
||||
.report-meta__item{display:flex;align-items:center;gap:6px;}
|
||||
.report-meta__item label{color:var(--text-secondary);white-space:nowrap;}
|
||||
.report-meta__item span{color:var(--text-primary);font-weight:500;}
|
||||
.report-section{margin-bottom:18px;}
|
||||
.report-section__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);padding:8px 12px;background:#EDF4FF;border-left:3px solid var(--primary);border-radius:2px;margin-bottom:10px;}
|
||||
.report-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);border:1px solid var(--border);}
|
||||
.report-table th{background:#FAFAFA;padding:9px 12px;text-align:center;color:var(--text-secondary);font-weight:500;border:1px solid var(--border);white-space:nowrap;}
|
||||
.report-table td{padding:9px 12px;text-align:center;color:var(--text-primary);border:1px solid var(--border);}
|
||||
.report-table td.report-table__label{background:#FAFAFA;color:var(--text-secondary);text-align:left;width:160px;white-space:nowrap;}
|
||||
.report-table td.report-note-cell{text-align:left;white-space:normal;word-break:break-word;line-height:1.7;vertical-align:top;min-height:42px;}
|
||||
.report-sign{margin-top:26px;display:flex;justify-content:space-between;gap:24px;font-size:var(--font-sm);color:var(--text-secondary);padding-top:16px;border-top:1px dashed #ccc;}
|
||||
.report-sign__item{display:flex;flex-direction:column;gap:6px;}
|
||||
.report-sign__line{width:140px;border-bottom:1px solid #999;height:24px;}
|
||||
.report-note{margin-top:16px;padding:12px 16px;background:#FFFBE6;border:1px solid #FFE58F;border-radius:var(--radius-sm);font-size:var(--font-sm);color:#874D00;line-height:1.6;}
|
||||
.report-note b{color:#D46B08;}
|
||||
/* Toast */
|
||||
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%) translateY(-20px);background:rgba(0,0,0,0.75);color:#fff;padding:10px 24px;border-radius:var(--radius-md);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .3s;z-index:2000;}
|
||||
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
|
||||
/* 打印:仅输出详情报表 */
|
||||
@media print{
|
||||
.top-bar,.sidebar,.main,.modal-overlay:not(#detailOverlay){display:none !important;}
|
||||
#detailOverlay{position:static;background:#fff;display:block !important;}
|
||||
#detailOverlay .modal{max-height:none;overflow:visible;box-shadow:none;width:100%;border-radius:0;}
|
||||
#detailOverlay .report-toolbar,#detailOverlay .modal-close{display:none !important;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -142,17 +261,490 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
|
||||
<!-- 主内容 -->
|
||||
<main class="main">
|
||||
<div class="page-placeholder">
|
||||
<div class="page-placeholder__title">场所经营分析</div>
|
||||
<div class="page-placeholder__hint">页面功能待补充</div>
|
||||
<!-- ① 食堂切换区 -->
|
||||
<div class="page-header-bar">
|
||||
<div class="page-header-bar__left">
|
||||
<span class="page-header-bar__title">场所经营分析</span>
|
||||
<span class="page-header-bar__meta">按日统计各场所营收数据 · 数据实时更新</span>
|
||||
</div>
|
||||
<div class="page-header-bar__right">
|
||||
<label>当前场所</label>
|
||||
<select class="canteen-select" id="globalPlace">
|
||||
<option value="全部">全部场所</option>
|
||||
<option value="一场所(东区)">一场所(东区)</option>
|
||||
<option value="二场所(西区)">二场所(西区)</option>
|
||||
<option value="三场所(北区)">三场所(北区)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ② 搜索区 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item"><label>统计日期</label><input class="filter-input" type="date" value="2026-08-20"></div>
|
||||
<div class="filter-item">
|
||||
<label>取餐总额</label>
|
||||
<div class="range-box">
|
||||
<input class="range-input" type="number" placeholder="最小">
|
||||
<span class="range-sep">—</span>
|
||||
<input class="range-input" type="number" placeholder="最大">
|
||||
<span class="range-unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>取餐笔数</label>
|
||||
<div class="range-box">
|
||||
<input class="range-input" type="number" placeholder="最小">
|
||||
<span class="range-sep">—</span>
|
||||
<input class="range-input" type="number" placeholder="最大">
|
||||
<span class="range-unit">笔</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>实收金额</label>
|
||||
<div class="range-box">
|
||||
<input class="range-input" type="number" placeholder="最小">
|
||||
<span class="range-sep">—</span>
|
||||
<input class="range-input" type="number" placeholder="最大">
|
||||
<span class="range-unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>毛利额</label>
|
||||
<div class="range-box">
|
||||
<input class="range-input" type="number" placeholder="最小">
|
||||
<span class="range-sep">—</span>
|
||||
<input class="range-input" type="number" placeholder="最大">
|
||||
<span class="range-unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="showToast('查询成功')">🔍 查询</button>
|
||||
<button class="btn btn-default" onclick="resetFilter(this)">⟳ 重置</button>
|
||||
</div>
|
||||
|
||||
<!-- ③ 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary" onclick="showToast('导出任务已创建,请至「查看导出任务」下载')">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据
|
||||
</button>
|
||||
<button class="btn btn-default" onclick="openExportModal()">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ④ 列表区(双表头) -->
|
||||
<div class="table-card">
|
||||
<div class="table-card__header"><div class="table-card__title">场所营收明细</div></div>
|
||||
<div class="table-card__content">
|
||||
<table id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="group-th" colspan="3">基础门店信息</th>
|
||||
<th class="group-th" colspan="5">客流底盘指标</th>
|
||||
<th class="group-th" colspan="4">营收单价指标</th>
|
||||
<th class="group-th" colspan="4">资金结算风控</th>
|
||||
<th class="group-th" colspan="4">盈利成本核心</th>
|
||||
<th class="group-th" colspan="2">运营管理字段</th>
|
||||
<th class="th-actions" rowspan="2">操作</th>
|
||||
</tr>
|
||||
<tr class="sub-row">
|
||||
<th>场所名称</th><th>统计日期</th><th>营业时段</th>
|
||||
<th>取餐笔数</th><th>就餐人数</th><th>取餐总份数</th><th>单人平均取餐份数</th><th>翻台率</th>
|
||||
<th>取餐总额</th><th>笔单价</th><th>人均消费</th><th>优惠减免金额</th>
|
||||
<th>实收金额</th><th>未结算差额</th><th>结算完成率</th><th>差额成因备注</th>
|
||||
<th>当日食材耗用总成本</th><th>食材成本率</th><th>毛利额</th><th>毛利率</th>
|
||||
<th>审核人</th><th>经营异常备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-place="一场所(东区)">
|
||||
<td class="td-left">一场所(东区)</td><td>2026-08-20</td><td>午餐</td>
|
||||
<td class="num">1,286</td><td class="num">892</td><td class="num">3,340</td><td class="num">3.74</td><td class="num">2.6</td>
|
||||
<td class="num num--money">¥30,580.00</td><td class="num">¥23.78</td><td class="num">¥34.28</td><td class="num">¥1,380.00</td>
|
||||
<td class="num num--money">¥29,200.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell">—</div></td>
|
||||
<td class="num">¥19,500.00</td><td class="num">63.8%</td><td class="num num--rate">¥9,700.00</td><td class="num num--rate">31.7%</td>
|
||||
<td class="td-auditor">王丽华</td><td class="td-abnormal td-left"><div class="note-cell">—</div></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
|
||||
</tr>
|
||||
<tr data-place="一场所(东区)">
|
||||
<td class="td-left">一场所(东区)</td><td>2026-08-20</td><td>晚餐</td>
|
||||
<td class="num">762</td><td class="num">521</td><td class="num">1,980</td><td class="num">3.80</td><td class="num">1.5</td>
|
||||
<td class="num num--money">¥18,120.00</td><td class="num">¥23.78</td><td class="num">¥34.78</td><td class="num">¥820.00</td>
|
||||
<td class="num num--money">¥17,300.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell">—</div></td>
|
||||
<td class="num">¥11,600.00</td><td class="num">64.0%</td><td class="num num--rate">¥5,700.00</td><td class="num num--rate">31.5%</td>
|
||||
<td class="td-auditor"><span class="tag tag-gray">未审核</span></td><td class="td-abnormal td-left"><div class="note-cell">—</div></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
|
||||
</tr>
|
||||
<tr data-place="二场所(西区)">
|
||||
<td class="td-left">二场所(西区)</td><td>2026-08-20</td><td>早餐,午餐</td>
|
||||
<td class="num">1,543</td><td class="num">1,042</td><td class="num">4,050</td><td class="num">3.89</td><td class="num">2.9</td>
|
||||
<td class="num num--money">¥36,780.00</td><td class="num">¥23.84</td><td class="num">¥35.30</td><td class="num">¥1,680.00</td>
|
||||
<td class="num num--money">¥35,100.00</td><td class="num">¥120.00</td><td class="num num--rate">99.7%</td><td class="td-left td-reason"><div class="note-cell">2笔餐券补录延迟,已通知收银员次日补录</div></td>
|
||||
<td class="num">¥23,600.00</td><td class="num">64.2%</td><td class="num num--rate">¥11,500.00</td><td class="num num--rate">31.3%</td>
|
||||
<td class="td-auditor">李建军</td><td class="td-abnormal td-left"><div class="note-cell">晚餐时段客流超预期,已临时加派两个打餐窗口,食材供应充足</div></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
|
||||
</tr>
|
||||
<tr data-place="二场所(西区)">
|
||||
<td class="td-left">二场所(西区)</td><td>2026-08-20</td><td>晚餐</td>
|
||||
<td class="num">918</td><td class="num">610</td><td class="num">2,380</td><td class="num">3.90</td><td class="num">1.8</td>
|
||||
<td class="num num--money">¥22,050.00</td><td class="num">¥24.02</td><td class="num">¥36.15</td><td class="num">¥960.00</td>
|
||||
<td class="num num--money">¥21,090.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell">—</div></td>
|
||||
<td class="num">¥14,300.00</td><td class="num">64.9%</td><td class="num num--rate">¥6,790.00</td><td class="num num--rate">30.8%</td>
|
||||
<td class="td-auditor"><span class="tag tag-gray">未审核</span></td><td class="td-abnormal td-left"><div class="note-cell">—</div></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
|
||||
</tr>
|
||||
<tr data-place="三场所(北区)">
|
||||
<td class="td-left">三场所(北区)</td><td>2026-08-20</td><td>午餐</td>
|
||||
<td class="num">1,102</td><td class="num">758</td><td class="num">2,860</td><td class="num">3.77</td><td class="num">2.3</td>
|
||||
<td class="num num--money">¥26,340.00</td><td class="num">¥23.90</td><td class="num">¥34.75</td><td class="num">¥1,150.00</td>
|
||||
<td class="num num--money">¥25,190.00</td><td class="num">¥200.00</td><td class="num num--rate">99.2%</td><td class="td-left td-reason"><div class="note-cell">现金盘点差异200元,待财务核实后调整入账</div></td>
|
||||
<td class="num">¥16,900.00</td><td class="num">64.2%</td><td class="num num--rate">¥8,290.00</td><td class="num num--rate">31.5%</td>
|
||||
<td class="td-auditor">陈晓东</td><td class="td-abnormal td-left"><div class="note-cell">现金盘点差异200元,已提交财务核实,暂不影响当日结算</div></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
|
||||
</tr>
|
||||
<tr data-place="三场所(北区)">
|
||||
<td class="td-left">三场所(北区)</td><td>2026-08-20</td><td>午餐,晚餐</td>
|
||||
<td class="num">635</td><td class="num">428</td><td class="num">1,620</td><td class="num">3.79</td><td class="num">1.3</td>
|
||||
<td class="num num--money">¥14,980.00</td><td class="num">¥23.59</td><td class="num">¥35.00</td><td class="num">¥680.00</td>
|
||||
<td class="num num--money">¥14,300.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell">—</div></td>
|
||||
<td class="num">¥9,650.00</td><td class="num">64.4%</td><td class="num num--rate">¥4,650.00</td><td class="num num--rate">31.0%</td>
|
||||
<td class="td-auditor"><span class="tag tag-gray">未审核</span></td><td class="td-abnormal td-left"><div class="note-cell">—</div></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span>显示 6 条,共 6 条</span>
|
||||
<button class="page-btn">‹</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 报表审计弹窗 -->
|
||||
<div class="modal-overlay" id="auditOverlay" style="display:none;">
|
||||
<div class="modal" style="width:560px;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">报表审计</span>
|
||||
<span class="modal-close" onclick="closeAudit()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="detail-info">
|
||||
<div class="detail-info__item"><span class="detail-info__label">场所名称:</span><span class="detail-info__value" id="auditPlace">—</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">统计日期:</span><span class="detail-info__value" id="auditDate">—</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">营业时段:</span><span class="detail-info__value" id="auditMeal">—</span></div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>审核人</label>
|
||||
<input type="text" id="auditName" placeholder="请输入审核人姓名">
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>差额成因备注</label>
|
||||
<textarea id="auditReason" placeholder="请输入未结算差额的成因说明(可选)"></textarea>
|
||||
</div>
|
||||
<div class="form-item" style="margin-bottom:0;">
|
||||
<label>经营异常备注</label>
|
||||
<textarea id="auditNote" placeholder="请输入经营异常情况说明(可选)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeAudit()">取消</button>
|
||||
<button class="btn btn-primary" onclick="confirmAudit()">确认审计</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情报表弹窗 -->
|
||||
<div class="modal-overlay" id="detailOverlay" style="display:none;">
|
||||
<div class="modal report-modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">经营分析报表</span>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<div class="report-toolbar">
|
||||
<button class="btn btn-primary" onclick="showToast('正在导出 PDF...')">📄 导出 PDF</button>
|
||||
<button class="btn btn-default" onclick="window.print()">🖨️ 打印</button>
|
||||
</div>
|
||||
<span class="modal-close" onclick="closeDetail()">×</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body report-body">
|
||||
<div class="report-title">场所经营分析报表</div>
|
||||
<div class="report-subtitle">DAILY PLACE BUSINESS ANALYSIS REPORT</div>
|
||||
<div class="report-divider"></div>
|
||||
<div class="report-meta">
|
||||
<div class="report-meta__item"><label>报表编号:</label><span>RPT-20260820-001</span></div>
|
||||
<div class="report-meta__item"><label>统计周期:</label><span id="rptDate">2026-08-20</span></div>
|
||||
<div class="report-meta__item"><label>营业时段:</label><span id="rptMeal">午餐</span></div>
|
||||
<div class="report-meta__item"><label>生成时间:</label><span>2026-08-20 18:30</span></div>
|
||||
</div>
|
||||
|
||||
<div class="report-section">
|
||||
<div class="report-section__title">基础门店信息</div>
|
||||
<table class="report-table">
|
||||
<tr>
|
||||
<td class="report-table__label">场所名称</td><td id="rptPlace">一场所(东区)</td>
|
||||
<td class="report-table__label">统计日期</td><td id="rptDate2">2026-08-20</td>
|
||||
<td class="report-table__label">营业时段</td><td id="rptMeal2">午餐</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="report-section">
|
||||
<div class="report-section__title">客流底盘指标</div>
|
||||
<table class="report-table">
|
||||
<tr>
|
||||
<th>取餐笔数</th><th>就餐人数</th><th>取餐总份数</th><th>单人平均取餐份数</th><th>翻台率</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="rptC1">—</td><td id="rptC2">—</td><td id="rptC3">—</td><td id="rptC4">—</td><td id="rptC5">—</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="report-section">
|
||||
<div class="report-section__title">营收单价指标</div>
|
||||
<table class="report-table">
|
||||
<tr>
|
||||
<th>取餐总额</th><th>笔单价</th><th>人均消费</th><th>优惠减免金额</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="rptC6">—</td><td id="rptC7">—</td><td id="rptC8">—</td><td id="rptC9">—</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="report-section">
|
||||
<div class="report-section__title">资金结算风控</div>
|
||||
<table class="report-table">
|
||||
<tr>
|
||||
<th>实收金额</th><th>未结算差额</th><th>结算完成率</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="rptC10">—</td><td id="rptC11">—</td><td id="rptC12">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="report-table__label">差额成因备注</td>
|
||||
<td id="rptC13" colspan="2" class="report-note-cell">—</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="report-section">
|
||||
<div class="report-section__title">盈利成本核心</div>
|
||||
<table class="report-table">
|
||||
<tr>
|
||||
<th>当日食材耗用总成本</th><th>食材成本率</th><th>毛利额</th><th>毛利率</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="rptC14">—</td><td id="rptC15">—</td><td id="rptC16">—</td><td id="rptC17">—</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="report-section">
|
||||
<div class="report-section__title">运营管理字段</div>
|
||||
<table class="report-table">
|
||||
<tr>
|
||||
<td class="report-table__label">审核人</td>
|
||||
<td id="rptAuditor" colspan="3">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="report-table__label">经营异常备注</td>
|
||||
<td id="rptAbnormal" colspan="3" class="report-note-cell">—</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="report-note" id="rptNote" style="display:none;"><b>经营异常:</b><span id="rptNoteText"></span></div>
|
||||
|
||||
<div class="report-sign">
|
||||
<div class="report-sign__item"><span>审核人签字:</span><span class="report-sign__line"></span></div>
|
||||
<div class="report-sign__item"><span>经营负责人签字:</span><span class="report-sign__line"></span></div>
|
||||
<div class="report-sign__item"><span>打印日期:2026-08-20</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导出任务弹窗 -->
|
||||
<div class="modal-overlay" id="exportOverlay" style="display:none;">
|
||||
<div class="modal" style="width:640px;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">导出任务列表</span>
|
||||
<span class="modal-close" onclick="closeExportModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="modal-table">
|
||||
<thead><tr><th>任务ID</th><th>导出内容</th><th>创建时间</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>EXP-20260820-001</td><td>场所经营分析列表</td><td>2026-08-20 09:12</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
|
||||
<tr><td>EXP-20260820-002</td><td>场所经营分析列表</td><td>2026-08-20 10:35</td><td><span class="tag tag-orange">处理中</span></td><td><span class="link" style="color:#999;cursor:default;">下载</span></td></tr>
|
||||
<tr><td>EXP-20260819-003</td><td>场所经营分析列表</td><td>2026-08-19 16:48</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeExportModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* 侧边栏子组折叠 */
|
||||
/* ========== 侧边栏子组折叠 ========== */
|
||||
function toggleSubgroup(header) {
|
||||
header.parentElement.classList.toggle('open');
|
||||
}
|
||||
/* 自动展开当前激活项所在子组 */
|
||||
(function(){
|
||||
var active = document.querySelector('.sidebar-item.active');
|
||||
if (active) { var sub = active.closest('.sidebar-subgroup'); if (sub) sub.classList.add('open'); }
|
||||
})();
|
||||
|
||||
/* ========== Toast ========== */
|
||||
function showToast(msg){
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._t);
|
||||
el._t = setTimeout(function(){ el.classList.remove('show'); }, 2000);
|
||||
}
|
||||
|
||||
/* ========== 备注列 title 初始化(悬浮查看全部) ========== */
|
||||
function initNoteTitles(){
|
||||
document.querySelectorAll('#dataTable .note-cell').forEach(function(cell){
|
||||
var t = cell.textContent.trim();
|
||||
cell.title = (t === '—') ? '' : t;
|
||||
});
|
||||
}
|
||||
|
||||
/* ========== 食堂切换区 ========== */
|
||||
document.getElementById('globalPlace').addEventListener('change', function(){
|
||||
var val = this.value;
|
||||
var rows = document.querySelectorAll('#dataTable tbody tr');
|
||||
var visible = 0;
|
||||
rows.forEach(function(tr){
|
||||
var show = (val === '全部') || (tr.getAttribute('data-place') === val);
|
||||
tr.style.display = show ? '' : 'none';
|
||||
if (show) visible++;
|
||||
});
|
||||
showToast(val === '全部' ? '已显示全部场所数据' : ('已切换至「' + val + '」,共 ' + visible + ' 条记录'));
|
||||
});
|
||||
|
||||
/* ========== 筛选重置 ========== */
|
||||
function resetFilter(btn){
|
||||
var bar = btn.closest('.filter-bar');
|
||||
bar.querySelectorAll('input').forEach(function(i){ i.value = ''; });
|
||||
bar.querySelectorAll('select').forEach(function(s){ s.selectedIndex = 0; });
|
||||
showToast('筛选条件已重置');
|
||||
}
|
||||
|
||||
/* ========== 备注单元格写入(同步省略号 title) ========== */
|
||||
function setNoteCell(td, val){
|
||||
var text = val && val.trim() ? val.trim() : '—';
|
||||
var cell = td.querySelector('.note-cell');
|
||||
if (cell) {
|
||||
cell.textContent = text;
|
||||
cell.title = (text === '—') ? '' : text;
|
||||
} else {
|
||||
td.textContent = text;
|
||||
td.title = (text === '—') ? '' : text;
|
||||
}
|
||||
}
|
||||
function getNoteText(td){
|
||||
var cell = td.querySelector('.note-cell');
|
||||
return cell ? cell.textContent.trim() : td.textContent.trim();
|
||||
}
|
||||
|
||||
/* ========== 报表审计 ========== */
|
||||
var auditTargetRow = null;
|
||||
function openAudit(link){
|
||||
auditTargetRow = link.closest('tr');
|
||||
var tds = auditTargetRow.querySelectorAll('td');
|
||||
document.getElementById('auditPlace').textContent = tds[0].textContent;
|
||||
document.getElementById('auditDate').textContent = tds[1].textContent;
|
||||
document.getElementById('auditMeal').textContent = tds[2].textContent;
|
||||
// 预填差额成因备注(第15列)
|
||||
var reason = getNoteText(tds[15]);
|
||||
document.getElementById('auditReason').value = (reason === '—') ? '' : reason;
|
||||
// 预填审核人(第20列)
|
||||
var auditor = tds[20].textContent;
|
||||
document.getElementById('auditName').value = (auditor.indexOf('未审核') > -1) ? '' : auditor;
|
||||
// 预填经营异常备注(第21列)
|
||||
var abnormal = getNoteText(tds[21]);
|
||||
document.getElementById('auditNote').value = (abnormal === '—') ? '' : abnormal;
|
||||
document.getElementById('auditOverlay').style.display = 'flex';
|
||||
}
|
||||
function closeAudit(){ document.getElementById('auditOverlay').style.display = 'none'; }
|
||||
function confirmAudit(){
|
||||
var name = document.getElementById('auditName').value.trim();
|
||||
if (!name) { showToast('请填写审核人姓名'); return; }
|
||||
var reason = document.getElementById('auditReason').value;
|
||||
var note = document.getElementById('auditNote').value;
|
||||
if (auditTargetRow) {
|
||||
var tds = auditTargetRow.querySelectorAll('td');
|
||||
setNoteCell(tds[15], reason); // 差额成因备注
|
||||
tds[20].textContent = name; // 审核人
|
||||
setNoteCell(tds[21], note); // 经营异常备注
|
||||
}
|
||||
closeAudit();
|
||||
showToast('审核完成,已回填审核人、差额成因备注与经营异常备注');
|
||||
}
|
||||
document.getElementById('auditOverlay').addEventListener('click', function(e){ if (e.target === this) closeAudit(); });
|
||||
|
||||
/* ========== 详情报表 ========== */
|
||||
function openDetail(link){
|
||||
var tr = link.closest('tr');
|
||||
var tds = tr.querySelectorAll('td');
|
||||
var place = tds[0].textContent, date = tds[1].textContent, meal = tds[2].textContent;
|
||||
var v = [];
|
||||
for (var i = 3; i <= 21; i++) v.push(tds[i].textContent.trim());
|
||||
// v[0..4]=客流(5) v[5..8]=营收(4) v[9..12]=结算(4) v[13..16]=成本(4) v[17]=审核人 v[18]=经营异常备注
|
||||
document.getElementById('rptPlace').textContent = place;
|
||||
document.getElementById('rptDate').textContent = date;
|
||||
document.getElementById('rptDate2').textContent = date;
|
||||
document.getElementById('rptMeal').textContent = meal;
|
||||
document.getElementById('rptMeal2').textContent = meal;
|
||||
for (var i = 0; i < 17; i++) {
|
||||
document.getElementById('rptC' + (i + 1)).textContent = v[i];
|
||||
}
|
||||
var auditor = v[17], abnormal = v[18];
|
||||
document.getElementById('rptAuditor').textContent = (auditor.indexOf('未审核') > -1) ? '—' : auditor;
|
||||
document.getElementById('rptAbnormal').textContent = (abnormal === '—') ? '—' : abnormal;
|
||||
var note = document.getElementById('rptNote');
|
||||
if (abnormal && abnormal !== '—') {
|
||||
note.style.display = '';
|
||||
document.getElementById('rptNoteText').textContent = abnormal;
|
||||
} else {
|
||||
note.style.display = 'none';
|
||||
}
|
||||
document.getElementById('detailOverlay').style.display = 'flex';
|
||||
}
|
||||
function closeDetail(){ document.getElementById('detailOverlay').style.display = 'none'; }
|
||||
document.getElementById('detailOverlay').addEventListener('click', function(e){ if (e.target === this) closeDetail(); });
|
||||
|
||||
/* ========== 导出任务弹窗 ========== */
|
||||
function openExportModal(){ document.getElementById('exportOverlay').style.display = 'flex'; }
|
||||
function closeExportModal(){ document.getElementById('exportOverlay').style.display = 'none'; }
|
||||
document.getElementById('exportOverlay').addEventListener('click', function(e){ if (e.target === this) closeExportModal(); });
|
||||
|
||||
/* ========== 分页交互 ========== */
|
||||
document.querySelectorAll('.pagination').forEach(function(pag){
|
||||
pag.querySelectorAll('.page-btn').forEach(function(btn){
|
||||
btn.addEventListener('click', function(){
|
||||
if (this.textContent === '‹' || this.textContent === '›' || this.textContent === '...') return;
|
||||
pag.querySelectorAll('.page-btn').forEach(function(b){ b.classList.remove('active'); });
|
||||
this.classList.add('active');
|
||||
showToast('已切换至第 ' + this.textContent + ' 页');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* ========== 初始化 ========== */
|
||||
initNoteTitles();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>营养管理 - 场所目标考核 | 健康CQ原型</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
@@ -40,10 +41,96 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
|
||||
/* 主区域 */
|
||||
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
|
||||
/* 页面占位 */
|
||||
.page-placeholder{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:48px 24px;text-align:center;}
|
||||
.page-placeholder__title{font-size:var(--font-xl);font-weight:600;color:var(--text-primary);margin-bottom:8px;}
|
||||
.page-placeholder__hint{font-size:var(--font-sm);color:var(--text-placeholder);}
|
||||
/* 统计总览卡片 */
|
||||
.overview-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);margin-bottom:16px;overflow:hidden;}
|
||||
.overview-card__title{font-size:var(--font-base);font-weight:600;padding:14px 20px;border-bottom:1px solid #f0f0f0;}
|
||||
.overview-card__meta{font-size:var(--font-xs);color:var(--text-placeholder);font-weight:400;margin-left:10px;}
|
||||
.overview-card__body{display:grid;grid-template-columns:repeat(4,1fr);padding:28px 20px 32px;}
|
||||
.kpi{text-align:center;}
|
||||
.kpi__label{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:14px;}
|
||||
.kpi__value{font-size:22px;font-weight:700;color:var(--text-primary);font-variant-numeric:tabular-nums;}
|
||||
.kpi__value--rate{color:#D46B08;}
|
||||
/* 筛选栏 */
|
||||
.filter-bar{display:flex;align-items:center;gap:14px;background:#fff;padding:16px 20px;border-radius:var(--radius-md);margin-bottom:16px;box-shadow:var(--shadow-sm);flex-wrap:wrap;}
|
||||
.filter-item{display:flex;align-items:center;gap:8px;}
|
||||
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
|
||||
.filter-input,.filter-select{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:140px;}
|
||||
.filter-select{padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;appearance:none;cursor:pointer;}
|
||||
.filter-input:focus,.filter-select:focus{border-color:var(--primary);}
|
||||
.filter-search{position:relative;}
|
||||
.filter-search input{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px 0 30px;font-size:var(--font-sm);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none'%3E%3Ccircle cx='6' cy='6' r='4.5' stroke='%23999' stroke-width='1.2'/%3E%3Cpath d='M9.5 9.5L12.5 12.5' stroke='%23999' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat 10px center;width:240px;}
|
||||
.filter-search input:focus{border-color:var(--primary);}
|
||||
.filter-input--range{width:88px;min-width:0;}
|
||||
.range-sep{color:var(--text-placeholder);font-size:var(--font-sm);}
|
||||
.btn{height:34px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
|
||||
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
|
||||
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
|
||||
/* 操作栏 */
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
/* 表格卡片 */
|
||||
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
|
||||
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.table-card__title{font-size:var(--font-base);font-weight:600;}
|
||||
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
|
||||
.table-card__content table{min-width:100%;white-space:nowrap;border-collapse:collapse;width:100%;}
|
||||
thead th{font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;padding:11px 14px;text-align:center;}
|
||||
tbody td{padding:13px 14px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;text-align:center;}
|
||||
tbody tr:last-child td{border-bottom:none;}
|
||||
tbody tr:hover td{background:#F8FBFF;}
|
||||
td.td-left{text-align:left;}
|
||||
/* 数值强调 */
|
||||
.num{font-variant-numeric:tabular-nums;font-weight:500;}
|
||||
.num--money{color:#CF1322;}
|
||||
.num--rate{color:#389E0D;}
|
||||
.num--rate-warn{color:#D46B08;}
|
||||
.num--up{color:#CF1322;font-weight:600;}
|
||||
.num--down{color:#389E0D;font-weight:600;}
|
||||
.muted{color:#C0C4CC;}
|
||||
/* 经营小结列省略号 + 点击查看 */
|
||||
.td-summary{max-width:180px;}
|
||||
.summary-text{display:inline-block;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;cursor:pointer;}
|
||||
.summary-text:hover{color:var(--primary);}
|
||||
.summary-full{padding:16px;background:#FAFAFA;border-radius:var(--radius-md);font-size:var(--font-sm);line-height:1.8;color:var(--text-primary);white-space:normal;word-break:break-all;}
|
||||
/* 操作列固定右侧 */
|
||||
thead th.th-actions,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:3;white-space:nowrap;}
|
||||
thead th.th-actions{background:#FAFAFA;z-index:4;}
|
||||
tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;margin:0 6px;}.link:hover{color:#40a9ff;}
|
||||
/* 分页 */
|
||||
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
|
||||
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
|
||||
/* 弹窗通用 */
|
||||
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;}
|
||||
.modal{background:#fff;border-radius:8px;max-height:88vh;overflow-y:auto;box-shadow:0 8px 24px rgba(0,0,0,0.16);}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
|
||||
.modal-title{font-size:16px;font-weight:600;color:#262626;}
|
||||
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}.modal-close:hover{color:#262626;}
|
||||
.modal-body{padding:24px;}
|
||||
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
|
||||
.detail-info{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px 24px;margin-bottom:20px;padding:16px;background:#FAFAFA;border-radius:var(--radius-md);}
|
||||
.detail-info__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);}
|
||||
.detail-info__label{color:var(--text-secondary);}
|
||||
.detail-info__value{color:var(--text-primary);font-weight:500;}
|
||||
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:16px;}
|
||||
.form-item label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.form-item input,.form-item textarea{border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;width:100%;}
|
||||
.form-item input{height:36px;}
|
||||
.form-item textarea{height:88px;padding:10px 12px;resize:none;font-family:inherit;line-height:1.6;}
|
||||
.form-item input:focus,.form-item textarea:focus{border-color:var(--primary);}
|
||||
.form-item .input-prefix{position:relative;}
|
||||
.form-item .input-prefix input{padding-left:26px;}
|
||||
.form-item .input-prefix::before{content:'¥';position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--text-placeholder);font-size:var(--font-sm);}
|
||||
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);text-align:left;}
|
||||
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
|
||||
.tag-green{background:#F6FFED;color:var(--success);}
|
||||
.tag-orange{background:#FFF7E6;color:#D46B08;}
|
||||
/* Toast */
|
||||
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%) translateY(-20px);background:rgba(0,0,0,0.75);color:#fff;padding:10px 24px;border-radius:var(--radius-md);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .3s;z-index:2000;}
|
||||
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -142,17 +229,406 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
|
||||
<!-- 主内容 -->
|
||||
<main class="main">
|
||||
<div class="page-placeholder">
|
||||
<div class="page-placeholder__title">场所目标考核</div>
|
||||
<div class="page-placeholder__hint">页面功能待补充</div>
|
||||
<!-- ① 本周目标考核总览 -->
|
||||
<div class="overview-card">
|
||||
<div class="overview-card__title">本周目标考核总览<span class="overview-card__meta">按周统计各场所目标完成率 · 数据每周更新</span></div>
|
||||
<div class="overview-card__body">
|
||||
<div class="kpi">
|
||||
<div class="kpi__label">本周就餐人数(人)</div>
|
||||
<div class="kpi__value">15,924</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi__label">本周应付营收(元)</div>
|
||||
<div class="kpi__value">¥547,530.00</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi__label">本周目标营收(元)</div>
|
||||
<div class="kpi__value">¥400,000.00</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi__label">平均营收完成率</div>
|
||||
<div class="kpi__value kpi__value--rate">98.2%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ② 搜索区 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item">
|
||||
<label>统计周期</label>
|
||||
<select class="filter-select" id="filterWeek">
|
||||
<option value="w34" selected>2026年第34周(08-17~08-23)</option>
|
||||
<option value="w33">2026年第33周(08-10~08-16)</option>
|
||||
<option value="w32">2026年第32周(08-03~08-09)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>场所名称</label>
|
||||
<select class="filter-select" id="filterPlace">
|
||||
<option value="全部" selected>全部</option>
|
||||
<option value="一场所(东区)">一场所(东区)</option>
|
||||
<option value="二场所(西区)">二场所(西区)</option>
|
||||
<option value="三场所(北区)">三场所(北区)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>应付营收</label>
|
||||
<input class="filter-input filter-input--range" id="filterRevMin" type="text" placeholder="最低">
|
||||
<span class="range-sep">~</span>
|
||||
<input class="filter-input filter-input--range" id="filterRevMax" type="text" placeholder="最高">
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>目标营收</label>
|
||||
<input class="filter-input filter-input--range" id="filterGoalMin" type="text" placeholder="最低">
|
||||
<span class="range-sep">~</span>
|
||||
<input class="filter-input filter-input--range" id="filterGoalMax" type="text" placeholder="最高">
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>营收完成率</label>
|
||||
<input class="filter-input filter-input--range" id="filterRateMin" type="text" placeholder="最低%">
|
||||
<span class="range-sep">~</span>
|
||||
<input class="filter-input filter-input--range" id="filterRateMax" type="text" placeholder="最高%">
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="applyFilter()">🔍 查询</button>
|
||||
<button class="btn btn-default" onclick="resetFilter(this)">⟳ 重置</button>
|
||||
</div>
|
||||
|
||||
<!-- ③ 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary" onclick="showToast('导出任务已创建,请至「查看导出任务」下载')">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据
|
||||
</button>
|
||||
<button class="btn btn-default" onclick="openExportModal()">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ④ 列表区 -->
|
||||
<div class="table-card">
|
||||
<div class="table-card__header"><div class="table-card__title">场所目标考核明细</div></div>
|
||||
<div class="table-card__content">
|
||||
<table id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>统计周期</th>
|
||||
<th>场所名称</th>
|
||||
<th>就餐人数</th>
|
||||
<th>环比人数变动</th>
|
||||
<th>应付营收</th>
|
||||
<th>目标营收</th>
|
||||
<th>营收完成率</th>
|
||||
<th>结算率</th>
|
||||
<th>食材成本率</th>
|
||||
<th>毛利率</th>
|
||||
<th>翻台率</th>
|
||||
<th>经营小结</th>
|
||||
<th class="th-actions">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-place="一场所(东区)" data-rate="up">
|
||||
<td>第34周(08-17~08-23)</td>
|
||||
<td class="td-left">一场所(东区)</td>
|
||||
<td class="num">5,214</td>
|
||||
<td class="num num--up">↑ +6.2%</td>
|
||||
<td class="td-revenue num num--money">¥182,450.00</td>
|
||||
<td class="td-goal num">¥180,000.00</td>
|
||||
<td class="td-rate num num--rate">101.4%</td>
|
||||
<td class="num">99.2%</td>
|
||||
<td class="num">63.5%</td>
|
||||
<td class="num num--rate">30.8%</td>
|
||||
<td class="num">2.6</td>
|
||||
<td class="td-summary td-left"><span class="summary-text" title="午晚高峰翻台良好,营收超额达成" onclick="viewSummary(this)">午晚高峰翻台良好,营收超额达成</span></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">考核审计</a></td>
|
||||
</tr>
|
||||
<tr data-place="二场所(西区)" data-rate="down">
|
||||
<td>第34周(08-17~08-23)</td>
|
||||
<td class="td-left">二场所(西区)</td>
|
||||
<td class="num">6,128</td>
|
||||
<td class="num num--down">↓ -3.1%</td>
|
||||
<td class="td-revenue num num--money">¥208,760.00</td>
|
||||
<td class="td-goal num">¥220,000.00</td>
|
||||
<td class="td-rate num num--rate-warn">94.9%</td>
|
||||
<td class="num">98.7%</td>
|
||||
<td class="num">64.9%</td>
|
||||
<td class="num num--rate">30.4%</td>
|
||||
<td class="num">2.4</td>
|
||||
<td class="td-summary td-left"><span class="summary-text" title="受高温影响客流略降,营收缺口集中在晚餐" onclick="viewSummary(this)">受高温影响客流略降,营收缺口集中在晚餐</span></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">考核审计</a></td>
|
||||
</tr>
|
||||
<tr data-place="三场所(北区)" data-rate="pending">
|
||||
<td>第34周(08-17~08-23)</td>
|
||||
<td class="td-left">三场所(北区)</td>
|
||||
<td class="num">4,582</td>
|
||||
<td class="num num--up">↑ +1.8%</td>
|
||||
<td class="td-revenue num num--money">¥156,320.00</td>
|
||||
<td class="td-goal num muted">—</td>
|
||||
<td class="td-rate num muted">—</td>
|
||||
<td class="num">99.5%</td>
|
||||
<td class="num">62.8%</td>
|
||||
<td class="num num--rate">31.5%</td>
|
||||
<td class="num">2.1</td>
|
||||
<td class="td-summary td-left"><span class="summary-text muted" onclick="viewSummary(this)">—</span></td>
|
||||
<td class="td-actions"><a class="link" onclick="openAudit(this)">考核审计</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span id="pageInfo">显示 3 条,共 3 条</span>
|
||||
<button class="page-btn">‹</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 考核审计弹窗 -->
|
||||
<div class="modal-overlay" id="auditOverlay" style="display:none;">
|
||||
<div class="modal" style="width:560px;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">考核审计</span>
|
||||
<span class="modal-close" onclick="closeAudit()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="detail-info">
|
||||
<div class="detail-info__item"><span class="detail-info__label">场所名称:</span><span class="detail-info__value" id="auditPlace">—</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">统计周期:</span><span class="detail-info__value" id="auditWeek">—</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">本周应付营收:</span><span class="detail-info__value" id="auditRevenue">—</span></div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>目标营收(元)<span style="color:var(--danger);">*</span></label>
|
||||
<div class="input-prefix">
|
||||
<input type="text" id="auditGoal" placeholder="请输入本周目标营收,用于核算完成率">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item" style="margin-bottom:0;">
|
||||
<label>经营小结</label>
|
||||
<textarea id="auditSummary" placeholder="请输入本周经营小结(可选)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeAudit()">取消</button>
|
||||
<button class="btn btn-primary" onclick="confirmAudit()">确认审计</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 经营小结查看弹窗 -->
|
||||
<div class="modal-overlay" id="summaryOverlay" style="display:none;">
|
||||
<div class="modal" style="width:480px;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">经营小结</span>
|
||||
<span class="modal-close" onclick="closeSummary()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="detail-info">
|
||||
<div class="detail-info__item"><span class="detail-info__label">场所名称:</span><span class="detail-info__value" id="summaryPlace">—</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">统计周期:</span><span class="detail-info__value" id="summaryWeek">—</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">营收完成率:</span><span class="detail-info__value" id="summaryRate">—</span></div>
|
||||
</div>
|
||||
<div class="summary-full" id="summaryFull">—</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeSummary()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导出任务弹窗 -->
|
||||
<div class="modal-overlay" id="exportOverlay" style="display:none;">
|
||||
<div class="modal" style="width:640px;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">导出任务列表</span>
|
||||
<span class="modal-close" onclick="closeExportModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="modal-table">
|
||||
<thead><tr><th>任务ID</th><th>导出内容</th><th>创建时间</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>EXP-20260820-001</td><td>场所目标考核列表</td><td>2026-08-20 09:12</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
|
||||
<tr><td>EXP-20260820-002</td><td>场所目标考核列表</td><td>2026-08-20 10:35</td><td><span class="tag tag-orange">处理中</span></td><td><span class="link" style="color:#999;cursor:default;">下载</span></td></tr>
|
||||
<tr><td>EXP-20260819-003</td><td>场所目标考核列表</td><td>2026-08-19 16:48</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeExportModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* 侧边栏子组折叠 */
|
||||
/* ========== 侧边栏子组折叠 ========== */
|
||||
function toggleSubgroup(header) {
|
||||
header.parentElement.classList.toggle('open');
|
||||
}
|
||||
/* 自动展开当前激活项所在子组 */
|
||||
(function(){
|
||||
var active = document.querySelector('.sidebar-item.active');
|
||||
if (active) { var sub = active.closest('.sidebar-subgroup'); if (sub) sub.classList.add('open'); }
|
||||
})();
|
||||
|
||||
/* ========== Toast ========== */
|
||||
function showToast(msg){
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._t);
|
||||
el._t = setTimeout(function(){ el.classList.remove('show'); }, 2000);
|
||||
}
|
||||
|
||||
/* ========== 金额格式化(千分位 + 两位小数) ========== */
|
||||
function formatMoney(n){
|
||||
var s = n.toFixed(2);
|
||||
var parts = s.split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return '¥' + parts.join('.');
|
||||
}
|
||||
|
||||
/* ========== 统计周期切换 ========== */
|
||||
document.getElementById('filterWeek').addEventListener('change', function(){
|
||||
var val = this.value;
|
||||
if (val === 'w34') {
|
||||
showToast('已查看本周(第34周)数据');
|
||||
} else {
|
||||
showToast('已切换统计周期,该周暂无考核数据');
|
||||
}
|
||||
});
|
||||
|
||||
/* ========== 数值解析(去掉 ¥ % 千分位逗号) ========== */
|
||||
function parseNum(str){
|
||||
if (!str || str === '—') return null;
|
||||
var n = parseFloat(str.replace(/[¥,%]/g, '').replace(/,/g, ''));
|
||||
return isNaN(n) ? null : n;
|
||||
}
|
||||
/* 范围判断:未设范围不过滤;设了范围但该行无值则不匹配 */
|
||||
function inRange(val, minStr, maxStr){
|
||||
var hasMin = minStr !== '' && minStr !== null;
|
||||
var hasMax = maxStr !== '' && maxStr !== null;
|
||||
if (!hasMin && !hasMax) return true;
|
||||
if (val === null) return false;
|
||||
var min = hasMin ? parseFloat(minStr) : -Infinity;
|
||||
var max = hasMax ? parseFloat(maxStr) : Infinity;
|
||||
return val >= min && val <= max;
|
||||
}
|
||||
/* ========== 查询(场所 + 三项范围 + 关键字) ========== */
|
||||
function applyFilter(){
|
||||
var place = document.getElementById('filterPlace').value;
|
||||
var kw = document.getElementById('filterKeyword').value.trim().toLowerCase();
|
||||
var revMin = document.getElementById('filterRevMin').value.trim();
|
||||
var revMax = document.getElementById('filterRevMax').value.trim();
|
||||
var goalMin = document.getElementById('filterGoalMin').value.trim();
|
||||
var goalMax = document.getElementById('filterGoalMax').value.trim();
|
||||
var rateMin = document.getElementById('filterRateMin').value.trim();
|
||||
var rateMax = document.getElementById('filterRateMax').value.trim();
|
||||
var rows = document.querySelectorAll('#dataTable tbody tr');
|
||||
var count = 0;
|
||||
rows.forEach(function(tr){
|
||||
var p = tr.getAttribute('data-place');
|
||||
var revenue = parseNum(tr.querySelector('.td-revenue').textContent);
|
||||
var goal = parseNum(tr.querySelector('.td-goal').textContent);
|
||||
var rate = parseNum(tr.querySelector('.td-rate').textContent);
|
||||
var text = tr.textContent.toLowerCase();
|
||||
var okPlace = place === '全部' || p === place;
|
||||
var okRev = inRange(revenue, revMin, revMax);
|
||||
var okGoal = inRange(goal, goalMin, goalMax);
|
||||
var okRate = inRange(rate, rateMin, rateMax);
|
||||
var okKw = !kw || text.indexOf(kw) > -1;
|
||||
var show = okPlace && okRev && okGoal && okRate && okKw;
|
||||
tr.style.display = show ? '' : 'none';
|
||||
if (show) count++;
|
||||
});
|
||||
document.getElementById('pageInfo').textContent = '显示 ' + count + ' 条,共 ' + count + ' 条';
|
||||
showToast('查询完成,共 ' + count + ' 条记录');
|
||||
}
|
||||
|
||||
/* ========== 筛选重置 ========== */
|
||||
function resetFilter(btn){
|
||||
var bar = btn.closest('.filter-bar');
|
||||
bar.querySelectorAll('select').forEach(function(s){ s.selectedIndex = 0; });
|
||||
bar.querySelectorAll('input').forEach(function(i){ i.value = ''; });
|
||||
applyFilter();
|
||||
showToast('筛选条件已重置');
|
||||
}
|
||||
|
||||
/* ========== 考核审计 ========== */
|
||||
var auditTargetRow = null;
|
||||
function openAudit(link){
|
||||
auditTargetRow = link.closest('tr');
|
||||
var tds = auditTargetRow.querySelectorAll('td');
|
||||
// 列顺序:0统计周期 1门店 2就餐人数 3环比 4应付营收 5目标营收 6完成率 7结算率 8成本率 9毛利率 10翻台率 11小结 12操作
|
||||
document.getElementById('auditPlace').textContent = tds[1].textContent;
|
||||
document.getElementById('auditWeek').textContent = tds[0].textContent;
|
||||
document.getElementById('auditRevenue').textContent = tds[4].textContent;
|
||||
var goalText = tds[5].textContent;
|
||||
var summaryText = tds[11].textContent;
|
||||
document.getElementById('auditGoal').value = (goalText === '—') ? '' : goalText.replace(/[¥,]/g, '');
|
||||
document.getElementById('auditSummary').value = (summaryText === '—') ? '' : summaryText;
|
||||
document.getElementById('auditOverlay').style.display = 'flex';
|
||||
}
|
||||
function closeAudit(){ document.getElementById('auditOverlay').style.display = 'none'; }
|
||||
function confirmAudit(){
|
||||
var goalInput = document.getElementById('auditGoal').value.trim();
|
||||
if (!goalInput) { showToast('请填写目标营收'); return; }
|
||||
var goal = parseFloat(goalInput.replace(/[¥,]/g, ''));
|
||||
if (isNaN(goal) || goal <= 0) { showToast('目标营收格式不正确'); return; }
|
||||
var summary = document.getElementById('auditSummary').value.trim();
|
||||
if (auditTargetRow) {
|
||||
// 回填目标营收与经营小结
|
||||
auditTargetRow.querySelector('.td-goal').textContent = formatMoney(goal);
|
||||
auditTargetRow.querySelector('.td-goal').classList.remove('muted');
|
||||
var summarySpan = auditTargetRow.querySelector('.summary-text');
|
||||
summarySpan.textContent = summary || '—';
|
||||
summarySpan.classList.toggle('muted', !summary);
|
||||
summarySpan.title = summary || '';
|
||||
// 重算营收完成率 = 应付营收 ÷ 目标营收
|
||||
var revenue = parseFloat(auditTargetRow.querySelector('.td-revenue').textContent.replace(/[¥,]/g, ''));
|
||||
var rate = revenue / goal * 100;
|
||||
var rateCell = auditTargetRow.querySelector('.td-rate');
|
||||
rateCell.textContent = rate.toFixed(1) + '%';
|
||||
rateCell.className = 'td-rate num ' + (rate >= 100 ? 'num--rate' : 'num--rate-warn');
|
||||
auditTargetRow.setAttribute('data-rate', rate >= 100 ? 'up' : 'down');
|
||||
}
|
||||
closeAudit();
|
||||
showToast('考核审计完成,已回填目标营收与经营小结并重算完成率');
|
||||
}
|
||||
document.getElementById('auditOverlay').addEventListener('click', function(e){ if (e.target === this) closeAudit(); });
|
||||
|
||||
/* ========== 经营小结查看 ========== */
|
||||
function viewSummary(span){
|
||||
var text = span.textContent.trim();
|
||||
if (!text || text === '—') return;
|
||||
var tr = span.closest('tr');
|
||||
var tds = tr.querySelectorAll('td');
|
||||
document.getElementById('summaryPlace').textContent = tds[1].textContent;
|
||||
document.getElementById('summaryWeek').textContent = tds[0].textContent;
|
||||
document.getElementById('summaryRate').textContent = tds[6].textContent;
|
||||
document.getElementById('summaryFull').textContent = text;
|
||||
document.getElementById('summaryOverlay').style.display = 'flex';
|
||||
}
|
||||
function closeSummary(){ document.getElementById('summaryOverlay').style.display = 'none'; }
|
||||
document.getElementById('summaryOverlay').addEventListener('click', function(e){ if (e.target === this) closeSummary(); });
|
||||
|
||||
/* ========== 导出任务弹窗 ========== */
|
||||
function openExportModal(){ document.getElementById('exportOverlay').style.display = 'flex'; }
|
||||
function closeExportModal(){ document.getElementById('exportOverlay').style.display = 'none'; }
|
||||
document.getElementById('exportOverlay').addEventListener('click', function(e){ if (e.target === this) closeExportModal(); });
|
||||
|
||||
/* ========== 分页交互 ========== */
|
||||
document.querySelectorAll('.pagination').forEach(function(pag){
|
||||
pag.querySelectorAll('.page-btn').forEach(function(btn){
|
||||
btn.addEventListener('click', function(){
|
||||
if (this.textContent === '‹' || this.textContent === '›' || this.textContent === '...') return;
|
||||
pag.querySelectorAll('.page-btn').forEach(function(b){ b.classList.remove('active'); });
|
||||
this.classList.add('active');
|
||||
showToast('已切换至第 ' + this.textContent + ' 页');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# 营养用餐管理端 CHANGELOG
|
||||
|
||||
## [2026-08-20] feat: 报表中心两页面补齐 — 场所经营分析 + 场所目标考核(`din-place-biz-analysis.html` / `din-place-goal.html`)
|
||||
|
||||
- 原「场所经营分析」「场所目标考核」为占位页,本次补齐为完整报表页
|
||||
- `din-place-biz-analysis.html` 场所经营分析:
|
||||
- 食堂切换区(当前场所下拉:全部 / 一场所东区 / 二场所西区 / 三场所北区)
|
||||
- 搜索筛选区(统计日期 + 取餐总额 / 取餐笔数 / 实收金额范围筛选)
|
||||
- 双表头表格(分组表头 + 子表头)+ 分页
|
||||
- 详情报表弹窗:正式报表样式(标题 / 元信息 / 分段表格 / 签字栏),支持打印(`@media print` 仅输出报表)
|
||||
- `din-place-goal.html` 场所目标考核:
|
||||
- 本周目标考核总览(4 个 KPI:就餐人数 / 应付营收 / 目标营收 / 平均完成率)
|
||||
- 搜索筛选区(统计周期 + 场所名称)
|
||||
- 明细表格(经营小结列省略号 + 点击查看全文、操作列固定右侧)+ 分页
|
||||
- 目标编辑弹窗(金额输入带 ¥ 前缀)
|
||||
|
||||
## [2026-08-19] feat: 餐线管理重构 — 收费模式周设置矩阵 + 设备概况/详情弹窗(`din-canteen-line.html`)
|
||||
|
||||
- 餐线列表新增结算方式(自动/手动)·状态(胶囊开关,点击启用/停用切换)两字段;收费模式因每天餐次可能不同,不在列表展示
|
||||
|
||||
Reference in New Issue
Block a user