feat: 净菜贴标秤模块初版 + 净菜组配秤组配记录页2.1.0升级 + 毛菜验检秤PRD更新

净菜贴标秤:
- 新增 00-login 员工登录、01-orders 贴标订单、02-labeling 贴标作业、03-history 贴标历史
- 重构 index 模块导航页(占位页 → 实际功能菜单)
- 导航页 SITE_MAP 同步添加贴标秤4个条目

净菜组配秤 v2.1.0:
- 组配记录页(03)表头6列对齐任务页(01)
- 数据结构与状态计算一致,所需总重含调料
- 统计行组配总重量包含调料已称重量

毛菜验检秤 PRD v1.1:
- 采样历史页删除采样备注字段
- 全局返回按钮改为"从哪来回哪去"逻辑(URL参数referer → document.referrer → 默认页)
- 补充变更日志章节

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhangyan
2026-06-25 16:49:49 +08:00
co-authored by Claude Opus 4.6
parent c3cca6127a
commit da06b4fa94
13 changed files with 2356 additions and 79 deletions
@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 登录 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
background: #05080f; color: #fff;
min-height: 100vh; min-width: 1024px;
display: flex; align-items: center; justify-content: center;
padding: 20px;
}
/* PAD 设备外壳 — 4:3 横屏 */
.device-frame {
width: 1024px; height: 768px;
background: #0e1326;
border-radius: 24px;
box-shadow: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04);
overflow: hidden;
position: relative;
display: flex; flex-direction: column;
}
/* 顶部导航栏 */
.top-bar {
display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px;
}
.top-bar__title {
flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
}
/* 主内容:左右两列布局 */
.login-main {
display: flex; height: calc(100% - 44px);
}
/* 左侧品牌区 */
.login-brand {
width: 400px; flex-shrink: 0;
background: linear-gradient(135deg, rgba(74,144,217,0.06), rgba(26,43,74,0.5));
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 20px; padding: 40px;
border-right: 1px solid rgba(255,255,255,0.06);
}
.login-brand__icon {
width: 88px; height: 88px; border-radius: 22px;
background: rgba(74,144,217,0.15);
display: flex; align-items: center; justify-content: center;
font-size: 44px;
}
.login-brand__name { font-size: 26px; font-weight: 700; color: rgba(255,255,255,0.9); }
.login-brand__desc { font-size: 13px; color: rgba(255,255,255,0.4); text-align: center; line-height: 1.7; }
/* 右侧登录面板 */
.login-panel-wrap {
flex: 1; display: flex; align-items: center; justify-content: center; padding: 32px;
}
.login-panel {
width: 420px; display: flex; flex-direction: column; gap: 20px;
}
.login-panel__title {
font-size: 20px; font-weight: 600; color: rgba(255,255,255,0.85); text-align: center;
}
.login-panel__subtitle {
font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; margin-top: -12px;
}
/* 登录方式切换 Tab */
.login-tabs {
display: flex; gap: 0; border-bottom: 1px solid rgba(255,255,255,0.1);
}
.login-tab {
flex: 1; padding: 12px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.5);
cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s;
}
.login-tab:hover { color: rgba(255,255,255,0.8); }
.login-tab.active { color: #4a90d9; border-bottom-color: #4a90d9; }
/* 人脸识别区 */
.face-section {
display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 20px 0;
}
.face-section.hidden { display: none; }
.face-frame {
width: 180px; height: 180px; border-radius: 50%;
border: 3px dashed rgba(74,144,217,0.4);
display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px;
background: rgba(26,43,74,0.3); cursor: pointer; transition: all 0.3s;
}
.face-frame:hover { border-color: #4a90d9; background: rgba(74,144,217,0.1); }
.face-frame__icon { font-size: 40px; color: rgba(255,255,255,0.3); }
.face-frame__text { font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; }
@keyframes ringPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.3); }
50% { box-shadow: 0 0 0 12px rgba(74,144,217,0); }
}
.face-frame.detecting {
border-color: #4a90d9; border-style: solid;
animation: ringPulse 1.5s infinite;
}
.face-frame.success {
border-color: #52c41a; border-style: solid; background: rgba(82,196,26,0.08);
}
.face-info {
text-align: center; min-height: 48px;
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 10px 16px; width: 100%;
}
.face-info__status { font-size: 12px; color: rgba(255,255,255,0.35); display: block; }
.face-info__name { font-size: 15px; font-weight: 700; color: #52c41a; display: none; }
.face-info__name.show { display: block; }
.face-info__detail { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; display: none; }
.face-info__detail.show { display: block; }
/* 账号密码表单 */
.password-section { display: flex; flex-direction: column; gap: 14px; padding: 16px 0; }
.password-section.hidden { display: none; }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-label { font-size: 12px; color: rgba(255,255,255,0.6); }
.form-input {
background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px; padding: 12px 14px; font-size: 14px; color: #fff;
outline: none; transition: border-color 0.2s; font-family: inherit;
}
.form-input:focus { border-color: #4a90d9; }
.form-input::placeholder { color: rgba(255,255,255,0.3); }
.login-error { font-size: 12px; color: #ff4d4f; min-height: 16px; }
.login-btn {
background: #4a90d9; border: none; border-radius: 8px;
padding: 12px; font-size: 14px; font-weight: 600; color: #fff;
cursor: pointer; transition: all 0.2s; margin-top: 8px; font-family: inherit;
}
.login-btn:hover { background: #5ba0e9; transform: translateY(-1px); }
.login-btn:active { transform: translateY(0); }
.login-hint { font-size: 11px; color: rgba(255,255,255,0.3); text-align: center; }
/* Toast */
.toast {
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px;
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
white-space: nowrap; pointer-events: none; max-width: 90%;
text-align: center; line-height: 1.5;
}
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.toast--warn { background: rgba(250,173,22,0.92); }
.toast--error { background: rgba(255,77,79,0.92); }
</style>
</head>
<body>
<div class="device-frame">
<div class="top-bar">
<span class="top-bar__title">净菜贴标秤 - 员工登录</span>
</div>
<div class="login-main">
<!-- 左侧品牌区 -->
<div class="login-brand">
<div class="login-brand__icon">&#x1F3F7;&#xFE0F;</div>
<div class="login-brand__name">净菜贴标秤</div>
<div class="login-brand__desc">接收配比完成订单<br>核对实物 · 贴标塑封 · 全程可溯</div>
</div>
<!-- 右侧登录面板 -->
<div class="login-panel-wrap">
<div class="login-panel">
<div class="login-panel__title">员工登录</div>
<div class="login-panel__subtitle">请选择登录方式</div>
<div class="login-tabs">
<div class="login-tab active" data-tab="face" onclick="switchTab('face')">人脸识别</div>
<div class="login-tab" data-tab="password" onclick="switchTab('password')">账号密码</div>
</div>
<!-- 人脸识别区 -->
<div class="face-section" id="faceSection">
<div class="face-frame" id="faceFrame" onclick="startFaceDetect()">
<div class="face-frame__icon" id="faceIcon">&#x1F464;</div>
<div class="face-frame__text" id="faceText">点击激活<br>人脸识别</div>
</div>
<div class="face-info">
<span class="face-info__status" id="faceStatus">识别成功后即可进入订单页</span>
<div class="face-info__name" id="faceName"></div>
<div class="face-info__detail" id="faceDetail"></div>
</div>
</div>
<!-- 账号密码表单 -->
<div class="password-section hidden" id="passwordSection">
<div class="form-group">
<label class="form-label">员工工号</label>
<input type="text" class="form-input" id="empId" placeholder="请输入员工工号">
</div>
<div class="form-group">
<label class="form-label">登录密码</label>
<input type="password" class="form-input" id="empPassword" placeholder="请输入登录密码">
</div>
<div class="login-error" id="loginError"></div>
<button class="login-btn" onclick="passwordLogin()">登 录</button>
<div class="login-hint">演示账号:40001 / 123456</div>
</div>
</div>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 登录状态:idle | detecting | success */
var loginState = 'idle';
var loginTab = 'face';
/* 模拟用户数据 */
var mockUsers = [
{ name: '刘九', id: 'EMP-20260050', dept: '贴标车间', role: '贴标员', empId: '40001', password: '123456' },
{ name: '陈十', id: 'EMP-20260055', dept: '贴标车间', role: '贴标员', empId: '40002', password: '123456' },
{ name: '杨一', id: 'EMP-20260060', dept: '质检部', role: '质检员', empId: '40003', password: '123456' }
];
/* 切换登录方式 */
function switchTab(tab) {
loginTab = tab;
document.querySelectorAll('.login-tab').forEach(function(el) {
el.classList.toggle('active', el.dataset.tab === tab);
});
document.getElementById('faceSection').classList.toggle('hidden', tab !== 'face');
document.getElementById('passwordSection').classList.toggle('hidden', tab !== 'password');
document.getElementById('loginError').textContent = '';
}
/* 人脸识别 */
function startFaceDetect() {
if (loginState !== 'idle') return;
loginState = 'detecting';
var frame = document.getElementById('faceFrame');
var icon = document.getElementById('faceIcon');
var text = document.getElementById('faceText');
frame.classList.add('detecting');
icon.innerHTML = '&#x1F50D;';
text.textContent = '识别中...';
showToast('正在识别人脸...', 1500);
setTimeout(function() {
var user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
loginSuccess(user);
}, 2000);
}
/* 账号密码登录 */
function passwordLogin() {
var empId = document.getElementById('empId').value.trim();
var password = document.getElementById('empPassword').value.trim();
var errorEl = document.getElementById('loginError');
if (!empId || !password) { errorEl.textContent = '请输入工号和密码'; return; }
var user = null;
for (var i = 0; i < mockUsers.length; i++) {
var u = mockUsers[i];
if (u.empId === empId && u.password === password) { user = u; break; }
}
if (!user) { errorEl.textContent = '工号或密码错误,请重试'; return; }
loginSuccess(user);
}
/* 登录成功 */
function loginSuccess(user) {
loginState = 'success';
if (loginTab === 'face') {
var frame = document.getElementById('faceFrame');
var icon = document.getElementById('faceIcon');
var text = document.getElementById('faceText');
frame.classList.remove('detecting');
frame.classList.add('success');
icon.innerHTML = '&#x2705;';
text.innerHTML = user.name;
document.getElementById('faceStatus').style.display = 'none';
var nameEl = document.getElementById('faceName');
nameEl.textContent = user.name + ' · ' + user.role;
nameEl.classList.add('show');
var detailEl = document.getElementById('faceDetail');
detailEl.textContent = user.dept + ' · ' + user.id;
detailEl.classList.add('show');
}
showToast('欢迎,' + user.name + ',正在进入菜单...', 1500);
setTimeout(function() {
window.location.href = 'index.html?user=' + encodeURIComponent(user.name) + '&uid=' + user.id;
}, 1500);
}
/* Toast 提示 */
function showToast(msg, duration, type) {
var el = document.getElementById('toast');
el.textContent = msg;
el.className = 'toast';
if (type === 'warn') el.classList.add('toast--warn');
if (type === 'error') el.classList.add('toast--error');
el.classList.add('show');
clearTimeout(el._timer);
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
}
</script>
</body>
</html>
@@ -0,0 +1,358 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 订单列表 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
background: #05080f; color: #fff;
min-height: 100vh; min-width: 1024px;
display: flex; align-items: center; justify-content: center;
padding: 20px;
}
/* PAD 设备外壳 — 4:3 横屏 */
.device-frame {
width: 1024px; height: 768px;
background: #0e1326;
border-radius: 24px;
box-shadow: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04);
overflow: hidden;
position: relative;
display: flex; flex-direction: column;
}
/* 顶部栏 */
.top-bar { display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px; }
.back-btn { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; transition: color 0.2s; }
.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); }
.top-bar__user { font-size: 12px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 6px; }
.top-bar__avatar { width: 26px; height: 26px; border-radius: 50%; background: #4a90d9; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #fff; }
.logout-btn { margin-left: 10px; padding: 4px 10px; font-size: 11px; background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2); border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer; transition: all 0.2s; font-family: inherit; }
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
/* 主内容区 */
.main-content { height: calc(100% - 44px); padding: 14px 20px; display: flex; flex-direction: column; gap: 10px; }
/* Tab 切换栏 */
.tabs { display: flex; gap: 8px; }
.tab { padding: 7px 18px; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; border: 1px solid rgba(255,255,255,0.08); background: transparent; color: rgba(255,255,255,0.6); }
.tab:hover { background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.9); }
.tab.active { background: #4a90d9; border-color: #4a90d9; color: #fff; }
.tab__count { margin-left: 6px; font-size: 11px; opacity: 0.8; }
/* 任务列表容器 */
.task-list-container { flex: 1; overflow-y: auto; scrollbar-width: none; }
.task-list-container::-webkit-scrollbar { display: none; }
/* 任务卡片 */
.task-card { background: #1a2b4a; border-radius: 10px; margin-bottom: 8px; border: 1px solid rgba(255,255,255,0.06); transition: all 0.2s; overflow: hidden; }
.task-card:hover { border-color: rgba(74,144,217,0.3); }
.task-card__header { display: flex; align-items: center; padding: 8px 14px; gap: 12px; background: rgba(26,43,74,0.6); border-bottom: 1px solid rgba(255,255,255,0.06); }
.task-card__order-info { font-size: 12px; color: rgba(255,255,255,0.7); display: flex; align-items: center; gap: 4px; }
.task-card__order-info__label { color: rgba(255,255,255,0.4); }
.task-card__order-info__value { color: #4a90d9; font-weight: 600; }
.task-card__arrival { font-size: 11px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 4px; }
.task-card__arrival__time { color: #faad16; font-weight: 500; }
.task-card__operator { font-size: 11px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 4px; }
.task-card__operator__name { color: #52c41a; font-weight: 500; }
.task-card__status { margin-left: auto; }
.task-card__label-btn { padding: 5px 14px; border-radius: 5px; font-size: 12px; font-weight: 500; border: none; cursor: pointer; transition: all 0.2s; background: #4a90d9; color: #fff; font-family: inherit; }
.task-card__label-btn:hover { background: #5ba0e9; }
/* 表格头 */
.item-table__head {
display: grid; grid-template-columns: 1fr 0.7fr 0.55fr 0.5fr 0.6fr 0.55fr 50px; gap: 6px;
padding: 5px 14px; background: rgba(0,0,0,0.2); font-size: 10px; color: rgba(255,255,255,0.4); font-weight: 500;
}
.item-table__body { display: flex; flex-direction: column; }
/* 餐品净菜包主行 */
.item-parent-row {
display: grid; grid-template-columns: 1fr 0.7fr 0.55fr 0.5fr 0.6fr 0.55fr 50px; gap: 6px;
padding: 8px 14px; border-top: 1px solid rgba(255,255,255,0.04); align-items: center;
background: rgba(74,144,217,0.06);
}
.parent-name { display: flex; align-items: center; gap: 8px; }
.parent-icon { width: 28px; height: 28px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; background: rgba(114,46,209,0.15); flex-shrink: 0; }
.parent-name__text { font-size: 12px; font-weight: 600; color: #fff; }
.pack-size-tag { display: inline-block; font-size: 10px; padding: 2px 7px; border-radius: 4px; background: rgba(74,144,217,0.15); color: #4a90d9; font-weight: 500; }
.parent-weight { font-size: 11px; color: rgba(255,255,255,0.6); text-align: right; }
.parent-weight--done { color: #52c41a; font-weight: 600; }
/* 状态徽章 */
.status-badge { font-size: 10px; padding: 2px 7px; border-radius: 4px; font-weight: 500; display: inline-block; }
.status-badge--pending { background: rgba(250,173,22,0.15); color: #faad16; }
.status-badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
.order-type-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; white-space: nowrap; }
.order-type-tag--internal { background: rgba(74,144,217,0.15); color: #4a90d9; }
.order-type-tag--external { background: rgba(250,140,22,0.15); color: #fa8c16; }
.status-tag { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 10px; }
.status-tag--done { background: rgba(82,196,26,0.15); color: #52c41a; }
.status-tag--wait { background: rgba(250,173,22,0.15); color: #faad16; }
/* Toast */
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.toast--warn { background: rgba(250,173,22,0.92); }
</style>
</head>
<body>
<div class="device-frame">
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<span class="top-bar__title">贴标任务</span>
<div class="top-bar__user">
<div class="top-bar__avatar" id="topAvatar">--</div>
<span id="topUser">--</span>
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
</div>
</div>
<div class="main-content">
<!-- Tab 切换 -->
<div class="tabs">
<div class="tab active" data-filter="pending" onclick="switchTab('pending', this)">待吐标 <span class="tab__count" id="pendingCount">0</span></div>
<div class="tab" data-filter="done" onclick="switchTab('done', this)">已贴标 <span class="tab__count" id="doneCount">0</span></div>
</div>
<!-- 任务列表 -->
<div class="task-list-container" id="taskListContainer"></div>
</div>
<div class="toast" id="toast"></div>
</div>
<script>
/* 获取 URL 参数 */
function getUrlParam(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r !== null) return decodeURIComponent(r[2]);
return null;
}
var userName = getUrlParam('user');
var userId = getUrlParam('uid');
if (userName) {
document.getElementById('topUser').textContent = userName;
document.getElementById('topAvatar').textContent = userName.charAt(0);
}
/* 当前 Tab */
var currentFilter = 'pending';
/* 模拟数据:待吐标订单(来自配比秤已完成配比的订单) */
var mockData = [
{
orderNo: 'PO-20260625-001',
orderType: 'internal',
expectedArrival: '2026-06-25 11:30',
taskNo: 'L-001',
labeled: false,
mealPacks: [
{ type: '净菜包', packName: '土豆丝', packSize: 3, packSpec: '500g', ingredients: [{ name: '土豆丝', weightPerPack: 500, weighed: 1500 }] },
{ type: '净菜包', packName: '胡萝卜丝', packSize: 3, packSpec: '500g', ingredients: [{ name: '胡萝卜丝', weightPerPack: 500, weighed: 1500 }] },
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '宫保鸡丁', weightPerPack: 800, weighed: 1600 }] }
]
},
{
orderNo: 'PO-20260625-002',
orderType: 'external',
expectedArrival: '2026-06-25 12:00',
taskNo: 'L-002',
labeled: false,
mealPacks: [
{ type: '净菜包', packName: '白菜丝', packSize: 2, packSpec: '1000g', ingredients: [{ name: '白菜丝', weightPerPack: 1000, weighed: 2000 }] },
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '鱼香肉丝', weightPerPack: 800, weighed: 1600 }] }
]
},
{
orderNo: 'PO-20260625-003',
orderType: 'internal',
expectedArrival: '2026-06-25 14:30',
taskNo: 'L-003',
labeled: false,
mealPacks: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', ingredients: [{ name: '番茄炒蛋', weightPerPack: 500, weighed: 2000 }] }
]
}
];
/* 已贴标订单(贴标历史) */
var mockDoneData = [
{
orderNo: 'PO-20260624-001',
orderType: 'internal',
expectedArrival: '2026-06-24 10:30',
taskNo: 'L-D01',
labeled: true,
labeledTime: '2026-06-24 10:15',
operator: '刘九',
mealPacks: [
{ type: '净菜包', packName: '黄瓜片', packSize: 3, packSpec: '500g', ingredients: [{ name: '黄瓜片', weightPerPack: 500, weighed: 1500 }] },
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 2, packSpec: '盒装', ingredients: [{ name: '凉拌黄瓜', weightPerPack: 600, weighed: 1200 }] }
]
},
{
orderNo: 'PO-20260624-002',
orderType: 'external',
expectedArrival: '2026-06-24 14:00',
taskNo: 'L-D02',
labeled: true,
labeledTime: '2026-06-24 13:45',
operator: '陈十',
mealPacks: [
{ type: '净菜包', packName: '芹菜段', packSize: 2, packSpec: '500g', ingredients: [{ name: '芹菜段', weightPerPack: 500, weighed: 1000 }] },
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
]
}
];
/* 判断订单是否所有包都已完成 */
function isOrderDone(order) {
return order.labeled === true;
}
/* 获取包状态 */
function getPackStatus(pack) {
var allDone = pack.ingredients.every(function(ing) {
return ing.weighed >= ing.weightPerPack * pack.packSize;
});
return allDone ? { text: '已贴标', cls: 'status-tag--done' } : { text: '待吐标', cls: 'status-tag--wait' };
}
/* 渲染任务列表 */
function renderTaskList() {
var container = document.getElementById('taskListContainer');
container.innerHTML = '';
var pendingCount = 0;
var doneCount = 0;
var dataList = currentFilter === 'pending' ? mockData : mockDoneData;
dataList.forEach(function(order) {
if (currentFilter === 'pending') pendingCount++;
else doneCount++;
var taskCard = document.createElement('div');
taskCard.className = 'task-card';
/* 头部:订单号 + 类型 + 时间 + 状态 + 操作 */
var header = document.createElement('div');
header.className = 'task-card__header';
var orderTypeLabel = order.orderType === 'internal' ? '内供订单' : '外售订单';
var orderTypeCls = order.orderType === 'internal' ? 'order-type-tag--internal' : 'order-type-tag--external';
var headerHtml =
'<span class="task-card__order-info"><span class="task-card__order-info__label">订单号:</span><span class="task-card__order-info__value">' + order.orderNo + '</span></span>' +
'<span class="order-type-tag ' + orderTypeCls + '">' + orderTypeLabel + '</span>';
if (currentFilter === 'pending') {
headerHtml += '<span class="task-card__arrival">&#x23F0; 任务截止时间:<span class="task-card__arrival__time">' + order.expectedArrival + '</span></span>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--pending">待贴标</span></span>';
headerHtml += '<button class="task-card__label-btn" onclick="handleLabel(\'' + order.taskNo + '\',\'' + order.orderNo + '\')">去贴标</button>';
} else {
headerHtml += '<span class="task-card__arrival">&#x23F0; 贴标完成时间:<span class="task-card__arrival__time">' + order.labeledTime + '</span></span>';
headerHtml += '<span class="task-card__operator">&#x1F464; 操作人:<span class="task-card__operator__name">' + order.operator + '</span></span>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--done">已贴标</span></span>';
}
header.innerHTML = headerHtml;
taskCard.appendChild(header);
/* 表格头 */
var tableHead = document.createElement('div');
tableHead.className = 'item-table__head';
tableHead.innerHTML = '<div>名称</div><div style="text-align:center">净菜/餐品</div><div style="text-align:center">包装规格</div><div style="text-align:center">打包份数</div><div style="text-align:right">所需总重</div><div style="text-align:right">已称</div><div style="text-align:center">状态</div>';
taskCard.appendChild(tableHead);
/* 表格体 */
var tableBody = document.createElement('div');
tableBody.className = 'item-table__body';
order.mealPacks.forEach(function(pack) {
var packStatus = currentFilter === 'pending'
? { text: '待吐标', cls: 'status-tag--wait' }
: { text: '已贴标', cls: 'status-tag--done' };
var totalNeed = 0;
var totalWeighed = 0;
pack.ingredients.forEach(function(ing) {
totalNeed += ing.weightPerPack * pack.packSize;
totalWeighed += ing.weighed;
});
var weightClass = totalWeighed >= totalNeed ? 'parent-weight--done' : '';
var parentRow = document.createElement('div');
parentRow.className = 'item-parent-row';
parentRow.innerHTML =
'<div class="parent-name"><div class="parent-icon">&#x1F4E6;</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
'<div style="text-align:center;font-size:11px;color:' + (pack.type === '餐品净菜包' ? '#faad16' : '#4a90d9') + '">' + pack.type + '</div>' +
'<div style="text-align:center;font-size:11px;color:rgba(255,255,255,0.55)">' + (pack.packSpec || '—') + '</div>' +
'<div style="text-align:center"><span class="pack-size-tag">' + pack.packSize + '</span></div>' +
'<div class="parent-weight ' + weightClass + '">' + totalNeed + ' g</div>' +
'<div class="parent-weight ' + weightClass + '">' + totalWeighed + ' g</div>' +
'<div style="text-align:center"><span class="status-tag ' + packStatus.cls + '">' + packStatus.text + '</span></div>';
tableBody.appendChild(parentRow);
});
taskCard.appendChild(tableBody);
container.appendChild(taskCard);
});
/* 更新 Tab 计数 */
document.getElementById('pendingCount').textContent = mockData.length;
document.getElementById('doneCount').textContent = mockDoneData.length;
}
/* 切换 Tab */
function switchTab(filter, el) {
currentFilter = filter;
document.querySelectorAll('.tab').forEach(function(tab) { tab.classList.remove('active'); });
el.classList.add('active');
renderTaskList();
}
/* 去贴标 */
function handleLabel(taskNo, orderNo) {
var target = '02-labeling.html?task=' + taskNo + '&order=' + encodeURIComponent(orderNo);
if (userName) target += '&user=' + encodeURIComponent(userName) + '&uid=' + (userId || '');
window.location.href = target;
}
/* 退出登录 */
function handleLogout() {
window.location.href = '00-login.html';
}
/* Toast */
function showToast(msg, duration, type) {
var el = document.getElementById('toast');
el.textContent = msg;
el.className = 'toast';
if (type === 'warn') el.classList.add('toast--warn');
el.classList.add('show');
clearTimeout(el._timer);
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
}
/* 返回按钮 */
(function() {
var backBtn = document.getElementById('backBtn');
if (backBtn) {
var params = new URLSearchParams(window.location.search);
var referer = params.get('referer');
var userParam = userName ? '?user=' + encodeURIComponent(userName) + (userId ? '&uid=' + userId : '') : '';
if (referer) {
backBtn.href = referer;
} else {
backBtn.href = 'index.html' + userParam;
}
}
})();
/* 初始化 */
renderTaskList();
</script>
</body>
</html>
@@ -0,0 +1,761 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 贴标任务 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
background: #05080f; color: #fff;
min-height: 100vh; min-width: 1024px;
display: flex; align-items: center; justify-content: center;
padding: 20px; user-select: none;
}
/* PAD 设备外壳 — 4:3 横屏 */
.device-frame {
width: 1024px; height: 768px;
background: #0e1326;
border-radius: 24px;
box-shadow: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04);
overflow: hidden; position: relative;
display: flex; flex-direction: column;
}
/* 顶部导航栏 */
.top-bar { display: flex; align-items: center; height: 48px; background: #1a2b4a; padding: 0 20px; }
.back-btn { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; transition: color 0.2s; margin-right: 12px; }
.back-btn:hover { color: #fff; }
.page-title { font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); }
.top-bar__spacer { flex: 1; }
.user-info { font-size: 12px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 6px; }
.user-info .avatar { width: 28px; height: 28px; border-radius: 50%; background: #4a90d9; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #fff; }
.logout-btn { margin-left: 10px; padding: 4px 10px; font-size: 11px; background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2); border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer; transition: all 0.2s; font-family: inherit; }
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
/* 两列布局 */
.main-layout { display: flex; height: calc(100% - 48px); }
/* 左列:识别 + 候选 */
.col-left {
width: 360px; flex-shrink: 0; padding: 10px 10px 10px 16px;
display: flex; flex-direction: column; gap: 8px; overflow-y: auto;
scrollbar-width: none;
}
.col-left::-webkit-scrollbar { display: none; }
.col-label { font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500; letter-spacing: 1px; }
/* 摄像头识别画面 */
.camera-frame {
width: 100%; height: 180px; flex-shrink: 0; background: #0D1117; border-radius: 12px;
border: 1px solid rgba(255,255,255,0.08); display: flex; align-items: center;
justify-content: center; position: relative; overflow: hidden;
}
.camera-frame__text { color: rgba(255,255,255,0.2); font-size: 12px; z-index: 1; }
.camera-frame::before, .camera-frame::after { content: ""; position: absolute; pointer-events: none; z-index: 2; }
.camera-frame::before { top: 0; bottom: 0; left: 50%; width: 1px; border-left: 1px dashed rgba(74,144,217,0.25); }
.camera-frame::after { left: 0; right: 0; top: 50%; height: 1px; border-top: 1px dashed rgba(74,144,217,0.25); }
.corner { position: absolute; width: 18px; height: 18px; z-index: 3; }
.corner--tl { top: 10px; left: 10px; border-top: 2px solid #4a90d9; border-left: 2px solid #4a90d9; }
.corner--tr { top: 10px; right: 10px; border-top: 2px solid #4a90d9; border-right: 2px solid #4a90d9; }
.corner--bl { bottom: 10px; left: 10px; border-bottom: 2px solid #4a90d9; border-left: 2px solid #4a90d9; }
.corner--br { bottom: 10px; right: 10px; border-bottom: 2px solid #4a90d9; border-right: 2px solid #4a90d9; }
.camera-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,0.55);
display: none; align-items: center; justify-content: center; flex-direction: column; gap: 6px;
z-index: 5; cursor: pointer; border-radius: 12px; transition: background 0.2s;
}
.camera-overlay.show { display: flex; }
.camera-overlay:hover { background: rgba(0,0,0,0.4); }
.camera-overlay__icon { font-size: 26px; }
.camera-overlay__text { font-size: 12px; color: rgba(255,255,255,0.8); font-weight: 500; }
/* 秤体重量显示 */
.scale-weight {
position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,0.7); border: 1px solid rgba(74,144,217,0.3);
border-radius: 8px; padding: 5px 14px; z-index: 4;
display: flex; align-items: center; gap: 8px;
}
.scale-weight__label { font-size: 10px; color: rgba(255,255,255,0.5); }
.scale-weight__value { font-size: 18px; font-weight: 700; color: #4a90d9; font-variant-numeric: tabular-nums; }
.scale-weight__unit { font-size: 10px; color: rgba(255,255,255,0.4); }
/* 识别状态 */
.recognize-status {
display: flex; align-items: center; justify-content: center; gap: 8px;
padding: 6px; background: #1a2b4a; border-radius: 8px; font-size: 11px; color: rgba(255,255,255,0.6);
}
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.status-dot--ready { background: #52c41a; box-shadow: 0 0 6px #52c41a; }
.status-dot--detecting { background: #faad14; box-shadow: 0 0 6px #faad14; animation: pulse 1s infinite; }
.status-dot--done { background: #4a90d9; box-shadow: 0 0 6px #4a90d9; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
/* 重新识别按钮 */
.reidentify-btn {
width: 100%; padding: 7px 0; background: rgba(74,144,217,0.1);
border: 1px solid rgba(74,144,217,0.25); border-radius: 8px;
color: #4a90d9; font-size: 12px; font-weight: 500; cursor: pointer;
transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 6px;
font-family: inherit;
}
.reidentify-btn:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
.reidentify-btn:active { transform: scale(0.98); }
/* 候选食材区 */
.candidate-section { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.candidate-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.candidate-title { font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500; }
.candidate-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; flex-shrink: 0; }
.candidate-card {
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06);
border-radius: 10px; overflow: hidden; cursor: pointer; transition: all 0.2s;
display: flex; flex-direction: row; align-items: center; height: 44px; position: relative;
}
.candidate-card:hover { border-color: rgba(74,144,217,0.3); background: rgba(74,144,217,0.08); transform: scale(1.03); }
.candidate-card__img { width: 38px; height: 38px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 20px; background: rgba(255,255,255,0.02); border-radius: 8px; margin: 3px; }
.candidate-card__name { flex: 1; font-size: 12px; font-weight: 500; color: rgba(255,255,255,0.75); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 6px; }
.candidate-card__sim { position: absolute; top: 3px; right: 3px; font-size: 9px; font-weight: 700; color: #52c41a; background: rgba(82,196,26,0.15); border-radius: 4px; padding: 1px 4px; line-height: 1.3; }
.candidate-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 16px 0; gap: 6px; }
.candidate-empty.hide { display: none; }
.candidate-empty__icon { font-size: 24px; opacity: 0.3; }
.candidate-empty__text { font-size: 10px; color: rgba(255,255,255,0.25); }
.scene-note { font-size: 9px; color: rgba(255,255,255,0.2); text-align: center; padding-top: 2px; }
/* 右列 */
.col-right {
flex: 1; min-width: 0; padding: 10px 16px 10px 10px;
display: flex; flex-direction: column; gap: 8px; overflow: hidden;
}
/* 任务信息卡片 */
.task-card {
background: #1a2b4a; border-radius: 10px; padding: 10px 14px; flex-shrink: 0;
border: 1px solid rgba(255,255,255,0.06);
}
.task-card__row { display: flex; align-items: center; gap: 16px; }
.task-card__item { display: flex; flex-direction: column; gap: 2px; }
.task-card__item-label { font-size: 10px; color: rgba(255,255,255,0.35); }
.task-card__item-value { font-size: 12px; font-weight: 500; }
.task-card__item-value--blue { color: #4a90d9; }
.task-card__item-value--green { color: #52c41a; }
.task-card__item-value--yellow { color: #faad14; }
.order-type-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; white-space: nowrap; flex-shrink: 0; }
.order-type-tag--internal { background: rgba(74,144,217,0.15); color: #4a90d9; }
.order-type-tag--external { background: rgba(250,140,22,0.15); color: #fa8c16; }
/* 贴标清单 */
.item-list { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.item-list__body { flex: 1; overflow-y: auto; min-height: 0; display: flex; flex-direction: column; gap: 6px; scrollbar-width: none; }
.item-list__body::-webkit-scrollbar { display: none; }
.item-row {
background: #1a2b4a; border-radius: 10px; border: 1px solid rgba(255,255,255,0.06);
padding: 10px 12px; transition: all 0.15s;
}
.item-row--meal { border-left: 3px solid rgba(250,173,22,0.4); }
/* 卡片头部 */
.item-card__header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.item-row__name { font-weight: 600; font-size: 13px; display: flex; align-items: center; gap: 6px; flex: 1; }
.item-row__name-icon { font-size: 16px; }
.item-row__type-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; white-space: nowrap; flex-shrink: 0; }
.item-row__type-tag--veg { background: rgba(74,144,217,0.15); color: #4a90d9; }
.item-row__type-tag--meal { background: rgba(250,173,22,0.15); color: #faad16; }
.item-row__status { font-size: 10px; flex-shrink: 0; }
.item-row__action { flex-shrink: 0; display: flex; gap: 6px; }
/* 吐标按钮 */
.label-btn {
padding: 4px 12px; border: 1px solid rgba(82,196,26,0.3); background: rgba(82,196,26,0.1);
color: #52c41a; border-radius: 5px; font-size: 11px; font-weight: 500; cursor: pointer;
transition: all 0.2s; white-space: nowrap; font-family: inherit;
}
.label-btn:hover { background: rgba(82,196,26,0.2); border-color: #52c41a; }
.label-btn:active { transform: scale(0.95); }
.label-btn--done { border-color: rgba(82,196,26,0.5); background: rgba(82,196,26,0.15); }
/* 清空按钮 */
.clear-btn {
padding: 3px 10px; border: 1px solid rgba(255,77,79,0.3); background: rgba(255,77,79,0.08);
color: rgba(255,77,79,0.7); border-radius: 4px; font-size: 10px; cursor: pointer;
transition: all 0.2s; white-space: nowrap; font-family: inherit;
}
.clear-btn:hover { background: rgba(255,77,79,0.2); border-color: #ff4d4f; color: #ff4d4f; }
/* 卡片元信息行 */
.item-card__meta {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
font-size: 10px; color: rgba(255,255,255,0.4); margin-bottom: 4px;
}
.item-card__meta-item { display: flex; align-items: center; gap: 3px; }
.item-card__meta-label { color: rgba(255,255,255,0.3); }
.item-card__meta-value { color: rgba(255,255,255,0.55); }
/* 卡片数据行 */
.item-card__data {
display: flex; align-items: center; gap: 14px; font-size: 11px; color: rgba(255,255,255,0.5);
}
.item-card__data-item { display: flex; align-items: center; gap: 4px; }
.item-card__data-label { color: rgba(255,255,255,0.35); }
.item-card__data-value { color: rgba(255,255,255,0.7); font-weight: 500; }
.item-card__data-value--done { color: #52c41a; }
.item-card__data-value--progress { color: #4a90d9; }
.item-card__data-value--zero { color: rgba(255,255,255,0.3); }
.item-card__data-value--dash { color: rgba(255,255,255,0.25); }
.status-tag { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 10px; }
.status-tag--wait { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.35); }
.status-tag--labeled { background: rgba(82,196,26,0.15); color: #52c41a; }
/* 贴标完成按钮 */
.next-btn-wrap { flex-shrink: 0; }
.next-btn {
width: 100%; padding: 11px 0; border: none; border-radius: 10px;
font-size: 14px; font-weight: 600; cursor: pointer;
transition: all 0.25s; text-align: center; font-family: inherit;
background: #4a90d9; color: #fff;
}
.next-btn:hover { background: #3a7bc8; box-shadow: 0 4px 14px rgba(74,144,217,0.35); }
.next-btn:active { transform: scale(0.97); }
.next-btn:disabled { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.2); cursor: not-allowed; box-shadow: none; }
/* 秤重确认弹窗 */
.modal-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
.modal-overlay.show { display: flex; }
.modal {
background: #1a2b4a; border-radius: 14px; border: 1px solid rgba(255,255,255,0.08);
padding: 24px 28px; width: 340px; display: flex; flex-direction: column; gap: 16px;
}
.modal__title { font-size: 15px; font-weight: 600; text-align: center; }
.modal__food { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; }
.modal__food-icon { font-size: 24px; }
.modal__label { font-size: 12px; color: rgba(255,255,255,0.5); text-align: center; }
.modal__weight-row { display: flex; align-items: center; justify-content: center; gap: 10px; }
.modal__weight-value { font-size: 28px; font-weight: 700; color: #4a90d9; line-height: 1; }
.modal__weight-refresh {
width: 28px; height: 28px; border-radius: 6px; border: 1px solid rgba(74,144,217,0.25);
background: rgba(74,144,217,0.1); color: #4a90d9; font-size: 14px;
cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center;
}
.modal__weight-refresh:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
.modal__weight-unit { text-align: center; font-size: 12px; color: rgba(255,255,255,0.4); margin-top: -8px; }
.modal__actions { display: flex; gap: 10px; }
.modal__btn { flex: 1; padding: 10px 0; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.2s; font-family: inherit; border: none; }
.modal__btn--cancel { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.6); }
.modal__btn--cancel:hover { background: rgba(255,255,255,0.12); color: #fff; }
.modal__btn--confirm { background: #4a90d9; color: #fff; }
.modal__btn--confirm:hover { background: #5ba0e9; }
/* Toast */
.toast {
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px;
font-size: 13px; z-index: 1100; opacity: 0; visibility: hidden;
transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none;
}
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.toast--error { background: rgba(255,77,79,0.92); }
.toast--success { background: rgba(82,196,26,0.92); }
.toast--warn { background: rgba(250,173,22,0.92); }
</style>
</head>
<body>
<div class="device-frame">
<!-- 顶部导航 -->
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<span class="page-title">贴标任务</span>
<span class="top-bar__spacer"></span>
<div class="user-info">
<div class="avatar" id="userAvatar">--</div>
<span id="topUser">--</span>
<button class="logout-btn" onclick="handleLogout()">退出</button>
</div>
</div>
<!-- 两列布局 -->
<div class="main-layout">
<!-- 左列:识别 + 候选 -->
<div class="col-left" id="colLeft">
<div class="col-label">&#x1F4F7; 净菜识别</div>
<div class="camera-frame" id="cameraFrame">
<span class="camera-frame__text">净菜识别区域</span>
<div class="corner corner--tl"></div>
<div class="corner corner--tr"></div>
<div class="corner corner--bl"></div>
<div class="corner corner--br"></div>
<div class="scale-weight" id="scaleWeight">
<span class="scale-weight__label">当前秤重</span>
<span class="scale-weight__value" id="scaleWeightValue">0.00</span>
<span class="scale-weight__unit"></span>
</div>
<div class="camera-overlay" id="cameraOverlay" onclick="triggerRecognition()">
<div class="camera-overlay__icon">&#x1F4F7;</div>
<div class="camera-overlay__text">点击继续识别</div>
</div>
</div>
<div class="recognize-status" id="recognizeStatus">
<span class="status-dot status-dot--ready" id="statusDot"></span>
<span id="statusText">就绪,等待净菜放置</span>
</div>
<button class="reidentify-btn" onclick="handleReidentify()">&#x1F504; 重新识别</button>
<div class="candidate-section" id="candidateSection">
<div class="candidate-header">
<span class="candidate-title" id="candidateTitle">智能识别候选</span>
</div>
<div class="candidate-list" id="candidateList"></div>
<div class="candidate-empty" id="candidateEmpty">
<div class="candidate-empty__icon">&#x1F4F7;</div>
<div class="candidate-empty__text">等待净菜放置后自动识别</div>
</div>
</div>
<div class="scene-note">场景监控后台静默录制中</div>
</div>
<!-- 右列:订单信息 + 贴标清单 + 完成按钮 -->
<div class="col-right">
<!-- 订单信息卡片 -->
<div class="task-card">
<div class="task-card__row">
<div class="task-card__item">
<div class="task-card__item-label">订单号</div>
<div class="task-card__item-value task-card__item-value--blue" id="taskOrder">--</div>
</div>
<div class="task-card__item">
<div class="task-card__item-label">订单类型</div>
<div class="task-card__item-value" id="taskOrderType">--</div>
</div>
<div class="task-card__item">
<div class="task-card__item-label">总项数</div>
<div class="task-card__item-value task-card__item-value--green" id="taskTotalItems">--</div>
</div>
<div class="task-card__item">
<div class="task-card__item-label">订单状态</div>
<div class="task-card__item-value" id="taskStatus">--</div>
</div>
</div>
</div>
<!-- 贴标清单 -->
<div class="item-list">
<div class="item-list__body" id="itemListBody"></div>
</div>
<!-- 贴标完成按钮 -->
<div class="next-btn-wrap">
<button class="next-btn" id="btnNext" onclick="handleLabelDone()">贴标完成</button>
</div>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<!-- 秤重确认弹窗 -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal">
<div class="modal__title">秤重确认</div>
<div class="modal__food">
<span class="modal__food-icon" id="modalIcon"></span>
<span id="modalFoodName">--</span>
</div>
<div class="modal__label">本次秤重重量</div>
<div class="modal__weight-row">
<span class="modal__weight-value" id="modalWeight">0.0</span>
<button class="modal__weight-refresh" onclick="refreshWeight()" title="重新获取秤上重量">&#x1F504;</button>
</div>
<div class="modal__weight-unit"></div>
<div class="modal__actions">
<button class="modal__btn modal__btn--cancel" onclick="closeModal()">取消</button>
<button class="modal__btn modal__btn--confirm" onclick="confirmWeight()">确认</button>
</div>
</div>
</div>
<script>
/* ---- 工具函数 ---- */
function getUrlParam(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r !== null) return decodeURIComponent(r[2]);
return null;
}
var userName = getUrlParam('user') || '刘九';
var userId = getUrlParam('uid') || '';
var taskNo = getUrlParam('task') || 'L-001';
document.getElementById('topUser').textContent = userName;
document.getElementById('userAvatar').textContent = userName.charAt(0);
/* ---- 模拟订单数据(来自配比秤已完成配比的订单) ---- */
var taskData = {
'L-001': {
orderNo: 'PO-20260625-001',
orderType: 'internal',
items: [
{ type: '净菜包', packName: '土豆丝', packSize: 3, packSpec: '500g', weightPerPack: 500, weighed: 1500, icon: '🥔' },
{ type: '净菜包', packName: '胡萝卜丝', packSize: 3, packSpec: '500g', weightPerPack: 500, weighed: 1500, icon: '🥕' },
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', weightPerPack: 800, weighed: 1600, icon: '🍱' }
]
},
'L-002': {
orderNo: 'PO-20260625-002',
orderType: 'external',
items: [
{ type: '净菜包', packName: '白菜丝', packSize: 2, packSpec: '1000g', weightPerPack: 1000, weighed: 2000, icon: '🥬' },
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', weightPerPack: 800, weighed: 1600, icon: '🍱' }
]
},
'L-003': {
orderNo: 'PO-20260625-003',
orderType: 'internal',
items: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', weightPerPack: 500, weighed: 2000, icon: '🍱' }
]
}
};
/* AI 识别候选数据 */
var aiResults = {
'potato': { name: '土豆丝', icon: '🥔', confidence: 97 },
'carrot': { name: '胡萝卜丝', icon: '🥕', confidence: 96 },
'cabbage': { name: '白菜丝', icon: '🥬', confidence: 93 }
};
/* ---- 状态变量 ---- */
var state = 'idle';
var allItems = [];
var currentVegIdx = 0;
var pendingCandidate = null;
/* ---- 初始化 ---- */
function init() {
var task = taskData[taskNo];
if (!task) { showToast('任务不存在', 2000, 'error'); return; }
allItems = task.items.map(function(item) {
return {
type: item.type,
packName: item.packName,
packSize: item.packSize,
packSpec: item.packSpec,
weightPerPack: item.weightPerPack,
weighed: item.weighed,
icon: item.icon,
labeled: false
};
});
currentVegIdx = -1;
for (var i = 0; i < allItems.length; i++) {
if (allItems[i].type === '净菜包') {
currentVegIdx = i;
break;
}
}
document.getElementById('taskOrder').textContent = task.orderNo;
var orderTypeEl = document.getElementById('taskOrderType');
var orderTypeLabel = task.orderType === 'internal' ? '内供订单' : '外售订单';
var orderTypeCls = task.orderType === 'internal' ? 'order-type-tag--internal' : 'order-type-tag--external';
orderTypeEl.innerHTML = '<span class="order-type-tag ' + orderTypeCls + '">' + orderTypeLabel + '</span>';
document.getElementById('taskTotalItems').textContent = allItems.length;
updateTaskStatus();
renderItemList();
if (currentVegIdx >= 0) {
document.getElementById('scaleWeightValue').textContent = allItems[currentVegIdx].weighed.toFixed(2);
document.getElementById('cameraOverlay').classList.add('show');
} else {
document.getElementById('scaleWeightValue').textContent = '0.00';
var hasVeg = allItems.some(function(item) { return item.type === '净菜包'; });
if (!hasVeg) {
document.getElementById('statusText').textContent = '本订单无净菜包';
document.getElementById('cameraOverlay').classList.remove('show');
}
}
}
/* ---- 更新订单状态 ---- */
function updateTaskStatus() {
var allDone = checkAllDone();
var statusEl = document.getElementById('taskStatus');
statusEl.textContent = allDone ? '已完成' : '待贴标';
statusEl.className = 'task-card__item-value' + (allDone ? ' task-card__item-value--green' : ' task-card__item-value--yellow');
document.getElementById('btnNext').disabled = !allDone;
}
/* ---- 检查是否全部完成(所有项都已吐标) ---- */
function checkAllDone() {
return allItems.every(function(item) { return item.labeled; });
}
/* ---- 渲染贴标清单(净菜包和餐品净菜包统一用待吐标/已吐标) ---- */
function renderItemList() {
var body = document.getElementById('itemListBody');
body.innerHTML = '';
allItems.forEach(function(item, idx) {
var need = Math.round(item.weightPerPack * item.packSize * 100) / 100;
var row = document.createElement('div');
var isMeal = item.type === '餐品净菜包';
row.className = 'item-row' + (isMeal ? ' item-row--meal' : '');
/* 状态:统一用待吐标/已吐标 */
var labeledTag = item.labeled
? '<span class="status-tag status-tag--labeled">已吐标</span>'
: '<span class="status-tag status-tag--wait">待吐标</span>';
/* 按钮:统一用吐标/重新吐标 */
var labelBtnText = item.labeled ? '重新吐标' : '吐标';
var labelBtnCls = item.labeled ? 'label-btn label-btn--done' : 'label-btn';
var typeTagCls = isMeal ? 'item-row__type-tag--meal' : 'item-row__type-tag--veg';
/* 每份用量:净菜包显示实际值,餐品净菜包显示-- */
var perPackDisplay = isMeal
? '<span class="item-card__data-value item-card__data-value--dash">--</span>'
: '<span class="item-card__data-value">' + item.weightPerPack + 'g</span>';
/* 餐品字段:餐品净菜包显示餐品名称,净菜包显示-- */
var mealDisplay = isMeal
? '<span class="item-card__data-value">' + item.packName + '</span>'
: '<span class="item-card__data-value item-card__data-value--dash">--</span>';
/* 按钮逻辑:净菜包无手动吐标按钮(秤重确认时自动吐标),餐品净菜包手动吐标 */
var actionHtml;
if (isMeal) {
actionHtml = '<button class="' + labelBtnCls + '" onclick="handleSpitLabel(' + idx + ')">' + labelBtnText + '</button>';
} else {
if (item.labeled) {
actionHtml = '<button class="' + labelBtnCls + '" onclick="handleSpitLabel(' + idx + ')">重新吐标</button>';
} else {
actionHtml = '';
}
}
row.innerHTML =
'<div class="item-card__header">' +
'<div class="item-row__name"><span class="item-row__name-icon">' + item.icon + '</span>' + item.packName + '</div>' +
'<span class="item-row__type-tag ' + typeTagCls + '">' + item.type + '</span>' +
'<div class="item-row__status">' + labeledTag + '</div>' +
'<div class="item-row__action">' + actionHtml + '</div>' +
'</div>' +
'<div class="item-card__meta">' +
'<div class="item-card__meta-item"><span class="item-card__meta-label">包装规格:</span><span class="item-card__meta-value">' + (item.packSpec || '—') + '</span></div>' +
'</div>' +
'<div class="item-card__data">' +
'<div class="item-card__data-item"><span class="item-card__data-label">餐品:</span>' + mealDisplay + '</div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">每份用量:</span>' + perPackDisplay + '</div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">份数:</span><span class="item-card__data-value">' + item.packSize + '</span></div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">需:</span><span class="item-card__data-value">' + need + 'g</span></div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">已称:</span><span class="item-card__data-value">' + item.weighed + 'g</span></div>' +
'</div>';
body.appendChild(row);
});
}
/* ---- 吐标(餐品净菜包手动触发 / 净菜包重新吐标) ---- */
function handleSpitLabel(idx) {
var item = allItems[idx];
var wasLabeled = item.labeled;
item.labeled = true;
renderItemList();
updateTaskStatus();
showToast(item.packName + (wasLabeled ? ' 已重新吐标' : ' 已吐标'), 1500, 'success');
}
/* ---- 触发识别(点击相机覆盖层) ---- */
function triggerRecognition() {
if (state === 'detecting') return;
if (currentVegIdx < 0) {
showToast('无净菜包可识别', 1500, 'warn');
return;
}
state = 'detecting';
document.getElementById('statusDot').className = 'status-dot status-dot--detecting';
document.getElementById('statusText').textContent = '识别中...';
document.getElementById('cameraOverlay').classList.remove('show');
setTimeout(function() {
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
document.getElementById('statusText').textContent = '识别完成,请在候选区选择净菜';
updateCandidates();
}, 800);
}
/* ---- 更新候选食材列表 ---- */
function updateCandidates() {
var list = document.getElementById('candidateList');
var empty = document.getElementById('candidateEmpty');
var title = document.getElementById('candidateTitle');
var vegNames = {};
allItems.forEach(function(item) {
if (item.type === '净菜包') vegNames[item.packName] = true;
});
var keys = Object.keys(aiResults);
var fromOrder = keys.filter(function(k) { return vegNames[aiResults[k].name]; });
var others = keys.filter(function(k) { return !vegNames[aiResults[k].name]; });
fromOrder.sort(function(a, b) { return aiResults[b].confidence - aiResults[a].confidence; });
others.sort(function(a, b) { return aiResults[b].confidence - aiResults[a].confidence; });
var selected = fromOrder.slice(0, 3).concat(others.slice(0, 1));
title.textContent = '智能识别候选 (' + selected.length + ')';
list.innerHTML = '';
empty.classList.add('hide');
selected.forEach(function(key) {
var r = aiResults[key];
var card = document.createElement('div');
card.className = 'candidate-card';
card.onclick = function() { openWeightModal(r); };
card.innerHTML =
'<div class="candidate-card__img">' + r.icon + '</div>' +
'<div class="candidate-card__name">' + r.name + '</div>' +
'<div class="candidate-card__sim">' + r.confidence + '%</div>';
list.appendChild(card);
});
}
/* ---- 秤重确认弹窗 ---- */
function openWeightModal(result) {
pendingCandidate = result;
document.getElementById('modalIcon').textContent = result.icon;
document.getElementById('modalFoodName').textContent = result.name;
document.getElementById('modalWeight').textContent = parseFloat(document.getElementById('scaleWeightValue').textContent).toFixed(1);
document.getElementById('modalOverlay').classList.add('show');
}
function refreshWeight() {
var newWeight = parseFloat((Math.random() * 2000 + 200).toFixed(1));
document.getElementById('modalWeight').textContent = newWeight.toFixed(1);
document.getElementById('scaleWeightValue').textContent = newWeight.toFixed(2);
showToast('重量已刷新:' + newWeight.toFixed(1) + 'g', 800);
}
function closeModal() {
pendingCandidate = null;
document.getElementById('modalOverlay').classList.remove('show');
}
function confirmWeight() {
if (!pendingCandidate) return;
document.getElementById('modalOverlay').classList.remove('show');
var matchedIdx = -1;
for (var i = 0; i < allItems.length; i++) {
if (allItems[i].type === '净菜包' && allItems[i].packName === pendingCandidate.name) {
matchedIdx = i;
break;
}
}
if (matchedIdx < 0) {
showToast('未找到匹配的净菜包项', 1500, 'error');
pendingCandidate = null;
return;
}
var modalWeight = parseFloat(document.getElementById('modalWeight').textContent) || 0;
allItems[matchedIdx].weighed = modalWeight;
/* 秤重确认后自动吐标 */
allItems[matchedIdx].labeled = true;
document.getElementById('scaleWeightValue').textContent = allItems[matchedIdx].weighed.toFixed(2);
document.getElementById('statusDot').className = 'status-dot status-dot--done';
document.getElementById('statusText').textContent = '秤重确认: ' + pendingCandidate.name + ' — ' + modalWeight.toFixed(1) + 'g';
showToast('秤重完成: ' + pendingCandidate.name + ' ' + modalWeight.toFixed(1) + 'g', 2000, 'success');
pendingCandidate = null;
renderItemList();
updateTaskStatus();
setTimeout(function() {
state = 'idle';
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
document.getElementById('statusText').textContent = '就绪,等待净菜放置';
document.getElementById('candidateList').innerHTML = '';
document.getElementById('candidateEmpty').classList.remove('hide');
document.getElementById('candidateTitle').textContent = '智能识别候选';
document.getElementById('cameraOverlay').classList.add('show');
}, 2000);
}
/* ---- 贴标完成 ---- */
function handleLabelDone() {
if (!checkAllDone()) {
showToast('还有项目未完成吐标', 2000, 'error');
return;
}
showToast('贴标完成,正在返回订单列表...', 1500, 'success');
setTimeout(function() {
window.location.href = '01-orders.html?user=' + encodeURIComponent(userName) + (userId ? '&uid=' + userId : '');
}, 1000);
}
/* ---- 重新识别 ---- */
function handleReidentify() {
if (state === 'detecting') return;
document.getElementById('cameraOverlay').classList.add('show');
document.getElementById('candidateList').innerHTML = '';
document.getElementById('candidateEmpty').classList.remove('hide');
document.getElementById('candidateTitle').textContent = '智能识别候选';
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
document.getElementById('statusText').textContent = '就绪,等待净菜放置';
showToast('已重新发起识别', 1000, 'success');
}
/* ---- 退出登录 ---- */
function handleLogout() {
window.location.href = '00-login.html';
}
/* ---- Toast ---- */
function showToast(msg, duration, type) {
var el = document.getElementById('toast');
el.textContent = msg;
el.className = 'toast';
if (type === 'error') el.classList.add('toast--error');
if (type === 'success') el.classList.add('toast--success');
if (type === 'warn') el.classList.add('toast--warn');
el.classList.add('show');
clearTimeout(el._timer);
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
}
/* ---- 返回按钮 ---- */
(function() {
var backBtn = document.getElementById('backBtn');
if (backBtn) {
var params = new URLSearchParams(window.location.search);
var referer = params.get('referer');
if (referer) {
backBtn.href = referer;
} else {
backBtn.href = '01-orders.html?user=' + encodeURIComponent(userName) + (userId ? '&uid=' + userId : '');
}
}
})();
/* ---- 启动 ---- */
init();
</script>
</body>
</html>
@@ -0,0 +1,576 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 贴标历史 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
background: #05080f; color: #fff;
min-height: 100vh; min-width: 1024px;
display: flex; align-items: center; justify-content: center;
padding: 20px;
}
/* PAD 设备外壳 — 4:3 横屏 */
.device-frame {
width: 1024px; height: 768px;
background: #0e1326;
border-radius: 24px;
box-shadow: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04);
overflow: hidden; position: relative;
display: flex; flex-direction: column;
}
/* 顶部导航栏 */
.top-bar { display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px; flex-shrink: 0; }
.back-btn { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; transition: color 0.2s; }
.back-btn:hover { color: #fff; }
.page-title { flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); }
.top-bar__user { font-size: 12px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 6px; }
.top-bar__avatar { width: 26px; height: 26px; border-radius: 50%; background: #4a90d9; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #fff; }
.logout-btn { margin-left: 10px; padding: 4px 10px; font-size: 11px; background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2); border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer; transition: all 0.2s; font-family: inherit; }
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
/* 触控筛选栏 */
.touch-filter {
display: flex; align-items: center; gap: 10px;
padding: 10px 20px; background: #1a2b4a; margin: 12px 20px 0; border-radius: 10px;
flex-shrink: 0;
}
/* 模式切换 */
.mode-seg { flex-shrink: 0; display: flex; height: 44px; border-radius: 8px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); }
.mode-btn { min-width: 64px; padding: 0 14px; border: none; background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.5); font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; font-family: inherit; }
.mode-btn:hover { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
.mode-btn.active { background: #4a90d9; color: #fff; }
.filter-divider { width: 1px; height: 32px; background: rgba(255,255,255,0.1); flex-shrink: 0; }
/* 横向日期滚动条 */
.date-strip { flex: 1; display: flex; align-items: stretch; gap: 6px; overflow-x: auto; overflow-y: hidden; padding: 4px 0; scrollbar-width: none; transition: opacity 0.2s; }
.date-strip::-webkit-scrollbar { display: none; }
.date-strip.disabled { opacity: 0.25; pointer-events: none; }
.date-cell { flex-shrink: 0; width: 64px; height: 56px; border-radius: 8px; border: 2px solid rgba(255,255,255,0.08); background: rgba(255,255,255,0.03); cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; transition: all 0.2s; }
.date-cell:hover { background: rgba(74,144,217,0.1); border-color: rgba(74,144,217,0.3); }
.date-cell.active { background: rgba(74,144,217,0.25); border-color: #4a90d9; }
.date-cell__weekday { font-size: 11px; color: rgba(255,255,255,0.45); }
.date-cell__day { font-size: 18px; font-weight: 700; color: rgba(255,255,255,0.9); }
.date-cell.active .date-cell__weekday { color: #7ab8f5; }
.date-cell.active .date-cell__day { color: #fff; }
.today-btn { flex-shrink: 0; height: 56px; padding: 0 14px; border-radius: 8px; border: 2px solid rgba(74,144,217,0.4); background: rgba(74,144,217,0.1); color: #7ab8f5; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.2s; white-space: nowrap; font-family: inherit; }
.today-btn:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
.today-btn.active { background: #4a90d9; color: #fff; border-color: #4a90d9; }
.today-btn.disabled { opacity: 0.25; pointer-events: none; }
.calendar-btn { flex-shrink: 0; width: 56px; height: 56px; border-radius: 8px; border: 2px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.04); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; position: relative; }
.calendar-btn:hover { background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.25); }
.calendar-btn svg { width: 24px; height: 24px; }
.calendar-btn.disabled { opacity: 0.25; pointer-events: none; }
/* 搜索框 */
.search-box { flex-shrink: 0; display: flex; align-items: center; gap: 6px; height: 36px; padding: 0 12px; border-radius: 8px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); transition: border-color 0.2s; }
.search-box:focus-within { border-color: #4a90d9; }
.search-box__icon { font-size: 13px; color: rgba(255,255,255,0.35); flex-shrink: 0; }
.search-box__input { width: 140px; border: none; outline: none; background: transparent; font-size: 12px; color: rgba(255,255,255,0.85); font-family: inherit; }
.search-box__input::placeholder { color: rgba(255,255,255,0.25); }
.search-box__clear { flex-shrink: 0; width: 18px; height: 18px; border-radius: 50%; background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.5); font-size: 11px; cursor: pointer; display: none; align-items: center; justify-content: center; border: none; }
.search-box__clear.show { display: flex; }
.search-box__clear:hover { background: rgba(255,255,255,0.18); color: #fff; }
/* 统计行 */
.stats-row { display: flex; gap: 10px; padding: 10px 20px; flex-shrink: 0; }
.stat-chip { flex: 1; background: #1a2b4a; border-radius: 8px; padding: 10px 14px; display: flex; align-items: center; gap: 10px; }
.stat-chip__icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.stat-chip__icon--blue { background: rgba(74,144,217,0.2); }
.stat-chip__icon--green { background: rgba(82,196,26,0.2); }
.stat-chip__icon--orange { background: rgba(250,173,22,0.2); }
.stat-chip__icon--red { background: rgba(255,77,79,0.2); }
.stat-chip__value { font-size: 18px; font-weight: 700; }
.stat-chip__label { font-size: 10px; color: rgba(255,255,255,0.4); }
/* 表格容器 */
.table-wrap { margin: 10px 20px; background: #1a2b4a; border-radius: 10px; overflow: hidden; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.table-scroll { flex: 1; overflow-y: auto; padding: 6px; scrollbar-width: none; }
.table-scroll::-webkit-scrollbar { display: none; }
/* 任务卡片 */
.task-card { background: #1a2b4a; border-radius: 10px; margin-bottom: 6px; border: 1px solid rgba(255,255,255,0.06); overflow: hidden; }
.task-card__header { display: flex; align-items: center; padding: 8px 14px; gap: 12px; background: rgba(26,43,74,0.6); border-bottom: 1px solid rgba(255,255,255,0.06); }
.task-card__order-info { font-size: 12px; color: rgba(255,255,255,0.7); display: flex; align-items: center; gap: 4px; }
.task-card__order-info__label { color: rgba(255,255,255,0.4); }
.task-card__order-info__value { color: #4a90d9; font-weight: 600; }
.task-card__arrival { font-size: 11px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 4px; }
.task-card__arrival__time { color: #faad16; font-weight: 500; }
.task-card__operator { font-size: 11px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 4px; }
.task-card__operator__name { color: #52c41a; font-weight: 500; }
.task-card__status { margin-left: auto; }
/* 表格头(7列) */
.item-table__head { display: grid; grid-template-columns: 1fr 0.7fr 0.55fr 0.5fr 0.6fr 0.55fr 50px; gap: 6px; padding: 5px 14px; background: rgba(0,0,0,0.2); font-size: 10px; color: rgba(255,255,255,0.4); font-weight: 500; }
.item-table__body { display: flex; flex-direction: column; }
/* 行 */
.item-parent-row { display: grid; grid-template-columns: 1fr 0.7fr 0.55fr 0.5fr 0.6fr 0.55fr 50px; gap: 6px; padding: 8px 14px; border-top: 1px solid rgba(255,255,255,0.04); align-items: center; background: rgba(74,144,217,0.06); }
.parent-name { display: flex; align-items: center; gap: 8px; }
.parent-icon { width: 28px; height: 28px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; background: rgba(114,46,209,0.15); flex-shrink: 0; }
.parent-name__text { font-size: 12px; font-weight: 600; color: #fff; }
.pack-size-tag { display: inline-block; font-size: 10px; padding: 2px 7px; border-radius: 4px; background: rgba(74,144,217,0.15); color: #4a90d9; font-weight: 500; }
.parent-weight { font-size: 11px; color: rgba(255,255,255,0.6); text-align: right; }
.parent-weight--done { color: #52c41a; font-weight: 600; }
/* 状态徽章 */
.status-badge { font-size: 10px; padding: 2px 7px; border-radius: 4px; font-weight: 500; display: inline-block; }
.status-badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
.order-type-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; white-space: nowrap; }
.order-type-tag--internal { background: rgba(74,144,217,0.15); color: #4a90d9; }
.order-type-tag--external { background: rgba(250,140,22,0.15); color: #fa8c16; }
.status-tag { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 10px; }
.status-tag--done { background: rgba(82,196,26,0.15); color: #52c41a; }
/* 底栏 */
.table-footer { display: flex; align-items: center; justify-content: center; padding: 8px 20px; font-size: 11px; color: rgba(255,255,255,0.35); border-top: 1px solid rgba(255,255,255,0.04); flex-shrink: 0; }
/* 空状态 */
.empty-state { display: none; flex: 1; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: rgba(255,255,255,0.3); font-size: 14px; }
.empty-state.show { display: flex; }
.empty-state__icon { font-size: 40px; opacity: 0.4; }
/* Toast */
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
</style>
</head>
<body>
<div class="device-frame">
<!-- 顶部导航 -->
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<span class="page-title">贴标历史</span>
<div class="top-bar__user">
<div class="top-bar__avatar" id="topAvatar">--</div>
<span id="topUser">--</span>
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
</div>
</div>
<!-- 触控筛选栏 -->
<div class="touch-filter">
<!-- 模式切换:日视图 / 本周 -->
<div class="mode-seg">
<button class="mode-btn active" id="modeDayBtn">日视图</button>
<button class="mode-btn" id="modeWeekBtn">本周</button>
</div>
<div class="filter-divider"></div>
<!-- 横向日期滚动条 -->
<div class="date-strip" id="dateStrip"></div>
<button class="today-btn active" id="todayBtn">今天</button>
<label class="calendar-btn" id="calendarBtn" title="选择任意日期">
<svg viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.6)" stroke-width="1.8">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
<rect x="7" y="14" width="3" height="3" rx="0.5" fill="rgba(255,255,255,0.6)" stroke="none"/>
</svg>
<input type="date" id="calendarInput" style="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;">
</label>
<div class="filter-divider"></div>
<!-- 搜索框 -->
<div class="search-box">
<span class="search-box__icon">&#x1F50D;</span>
<input class="search-box__input" id="searchInput" type="text" placeholder="搜索订单号 / 名称" oninput="onSearchInput()">
<button class="search-box__clear" id="searchClear" onclick="clearSearch()">&times;</button>
</div>
</div>
<!-- 统计行 -->
<div class="stats-row">
<div class="stat-chip">
<div class="stat-chip__icon stat-chip__icon--blue">&#x1F4CB;</div>
<div><div class="stat-chip__value" id="statTotal">0</div><div class="stat-chip__label" id="statTotalLabel">今日贴标订单数</div></div>
</div>
<div class="stat-chip">
<div class="stat-chip__icon stat-chip__icon--green">&#x1F4E6;</div>
<div><div class="stat-chip__value" id="statPacks">0</div><div class="stat-chip__label">总份数</div></div>
</div>
<div class="stat-chip">
<div class="stat-chip__icon stat-chip__icon--orange">&#x1F96C;</div>
<div><div class="stat-chip__value" id="statFoods">0</div><div class="stat-chip__label">净菜种类</div></div>
</div>
<div class="stat-chip">
<div class="stat-chip__icon stat-chip__icon--red">&#x1F4E6;</div>
<div><div class="stat-chip__value" id="statWeight">0</div><div class="stat-chip__label">贴标总重量(g)</div></div>
</div>
</div>
<!-- 任务列表 -->
<div class="table-wrap">
<div class="table-scroll" id="taskListContainer"></div>
<div class="empty-state" id="emptyState">
<div class="empty-state__icon">&#x1F4ED;</div>
<div>暂无贴标记录</div>
</div>
<div class="table-footer" id="tableFooter">共 0 条记录</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 获取 URL 参数 */
function getUrlParam(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r !== null) return decodeURIComponent(r[2]);
return null;
}
var userName = getUrlParam('user');
var userId = getUrlParam('uid');
if (userName) {
document.getElementById('topUser').textContent = userName;
document.getElementById('topAvatar').textContent = userName.charAt(0);
}
/* 退出登录 */
function handleLogout() {
window.location.href = '00-login.html';
}
/* 当天日期(原型基准日期) */
var TODAY = '2026-06-25';
var WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
/* 贴标历史数据(已贴标完成的订单) */
var RECORDS = [
{
orderNo: 'PO-20260625-001', orderType: 'internal', completeTime: '2026-06-25 11:15', operator: '刘九',
mealPacks: [
{ type: '净菜包', packName: '土豆丝', packSize: 3, packSpec: '500g', ingredients: [{ name: '土豆丝', weightPerPack: 500, weighed: 1500 }] },
{ type: '净菜包', packName: '胡萝卜丝', packSize: 3, packSpec: '500g', ingredients: [{ name: '胡萝卜丝', weightPerPack: 500, weighed: 1500 }] },
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '宫保鸡丁', weightPerPack: 800, weighed: 1600 }] }
]
},
{
orderNo: 'PO-20260625-002', orderType: 'external', completeTime: '2026-06-25 12:30', operator: '陈十',
mealPacks: [
{ type: '净菜包', packName: '白菜丝', packSize: 2, packSpec: '1000g', ingredients: [{ name: '白菜丝', weightPerPack: 1000, weighed: 2000 }] },
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '鱼香肉丝', weightPerPack: 800, weighed: 1600 }] }
]
},
{
orderNo: 'PO-20260624-001', orderType: 'internal', completeTime: '2026-06-24 10:15', operator: '刘九',
mealPacks: [
{ type: '净菜包', packName: '黄瓜片', packSize: 3, packSpec: '500g', ingredients: [{ name: '黄瓜片', weightPerPack: 500, weighed: 1500 }] },
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 2, packSpec: '盒装', ingredients: [{ name: '凉拌黄瓜', weightPerPack: 600, weighed: 1200 }] }
]
},
{
orderNo: 'PO-20260624-002', orderType: 'external', completeTime: '2026-06-24 13:45', operator: '陈十',
mealPacks: [
{ type: '净菜包', packName: '芹菜段', packSize: 2, packSpec: '500g', ingredients: [{ name: '芹菜段', weightPerPack: 500, weighed: 1000 }] },
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
]
},
{
orderNo: 'PO-20260623-001', orderType: 'internal', completeTime: '2026-06-23 09:30', operator: '王八',
mealPacks: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', ingredients: [{ name: '番茄炒蛋', weightPerPack: 500, weighed: 2000 }] }
]
}
];
var selectedDate = TODAY;
var isWeeklyMode = false;
var searchKeyword = '';
/* 获取本周一日期 */
function getMonday() {
var today = new Date(TODAY + 'T00:00:00');
var day = today.getDay();
var diff = today.getDate() - day + (day === 0 ? -6 : 1);
var monday = new Date(today);
monday.setDate(diff);
return monday;
}
/* 生成日期字符串 */
function dateStr(d) {
return d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0');
}
/* 生成日期条(最近7天) */
function generateDateStrip() {
var strip = document.getElementById('dateStrip');
var today = new Date(TODAY + 'T00:00:00');
var html = '';
for (var i = 6; i >= 0; i--) {
var d = new Date(today);
d.setDate(d.getDate() - i);
var ds = dateStr(d);
var dayNum = d.getDate();
var weekday = WEEKDAYS[d.getDay()];
var label = (i === 0) ? '今天' : (i === 1 ? '昨天' : weekday);
var isActive = (ds === selectedDate) ? ' active' : '';
html += '<button class="date-cell' + isActive + '" data-date="' + ds + '">'
+ '<span class="date-cell__weekday">' + label + '</span>'
+ '<span class="date-cell__day">' + dayNum + '</span>'
+ '</button>';
}
strip.innerHTML = html;
strip.querySelectorAll('.date-cell').forEach(function(cell) {
cell.addEventListener('click', function() {
selectDate(cell.getAttribute('data-date'));
});
});
strip.scrollLeft = strip.scrollWidth;
}
/* 选中日期 */
function selectDate(ds) {
if (isWeeklyMode) return;
selectedDate = ds;
document.querySelectorAll('.date-cell').forEach(function(c) {
c.classList.toggle('active', c.getAttribute('data-date') === ds);
});
document.getElementById('todayBtn').classList.toggle('active', ds === TODAY);
var inRange = Array.from(document.querySelectorAll('.date-cell'))
.some(function(c) { return c.getAttribute('data-date') === ds; });
if (!inRange) appendDateToStrip(ds);
var activeCell = document.querySelector('.date-cell[data-date="' + ds + '"]');
if (activeCell) activeCell.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
updateStats();
renderTable();
}
/* 扩展日期条 */
function appendDateToStrip(ds) {
var strip = document.getElementById('dateStrip');
var d = new Date(ds + 'T00:00:00');
var cell = document.createElement('button');
cell.className = 'date-cell active';
cell.setAttribute('data-date', ds);
cell.innerHTML = '<span class="date-cell__weekday">' + WEEKDAYS[d.getDay()] + '</span>'
+ '<span class="date-cell__day">' + d.getDate() + '</span>';
cell.addEventListener('click', function() { selectDate(ds); });
strip.insertBefore(cell, strip.firstChild);
}
/* 切换模式 */
function setMode(weekly) {
isWeeklyMode = weekly;
document.getElementById('modeDayBtn').classList.toggle('active', !weekly);
document.getElementById('modeWeekBtn').classList.toggle('active', weekly);
var disableClass = weekly ? 'add' : 'remove';
document.getElementById('dateStrip').classList[disableClass]('disabled');
document.getElementById('todayBtn').classList[disableClass]('disabled');
document.getElementById('calendarBtn').classList[disableClass]('disabled');
updateStats();
renderTable();
}
/* 更新统计行 */
function updateStats() {
var records;
var totalLabel;
if (searchKeyword) {
records = RECORDS;
totalLabel = '搜索结果';
} else if (isWeeklyMode) {
var monday = getMonday();
var mondayStr = dateStr(monday);
records = RECORDS.filter(function(r) { return r.completeTime.substring(0, 10) >= mondayStr; });
totalLabel = '本周贴标订单数';
} else {
records = RECORDS.filter(function(r) { return r.completeTime.substring(0, 10) === selectedDate; });
totalLabel = (selectedDate === TODAY) ? '今日贴标订单数' : (selectedDate.substring(5).replace('-', '/') + ' 贴标订单数');
}
var total = records.length;
var totalWeight = 0;
var foodSet = {};
var totalPacks = 0;
records.forEach(function(order) {
order.mealPacks.forEach(function(pack) {
pack.ingredients.forEach(function(ing) {
totalWeight += ing.weighed;
foodSet[ing.name] = true;
});
totalPacks += pack.packSize;
});
});
document.getElementById('statTotal').textContent = total;
document.getElementById('statTotalLabel').textContent = totalLabel;
document.getElementById('statWeight').textContent = totalWeight;
document.getElementById('statFoods').textContent = Object.keys(foodSet).length;
document.getElementById('statPacks').textContent = totalPacks;
}
/* 搜索输入处理 */
function onSearchInput() {
var val = document.getElementById('searchInput').value.trim();
searchKeyword = val;
document.getElementById('searchClear').classList.toggle('show', val.length > 0);
updateStats();
renderTable();
}
function clearSearch() {
document.getElementById('searchInput').value = '';
searchKeyword = '';
document.getElementById('searchClear').classList.remove('show');
updateStats();
renderTable();
}
/* 过滤记录 */
function getFilteredRecords() {
var monday = getMonday();
var mondayStr = dateStr(monday);
return RECORDS.filter(function(r) {
if (!searchKeyword) {
if (isWeeklyMode) {
if (r.completeTime.substring(0, 10) < mondayStr) return false;
} else {
if (r.completeTime.substring(0, 10) !== selectedDate) return false;
}
}
if (searchKeyword) {
var kw = searchKeyword;
var matchOrder = (r.orderNo || '').indexOf(kw) !== -1;
var matchFood = r.mealPacks.some(function(pack) {
return pack.packName.indexOf(kw) !== -1 || pack.ingredients.some(function(ing) {
return ing.name.indexOf(kw) !== -1;
});
});
if (!matchOrder && !matchFood) return false;
}
return true;
}).sort(function(a, b) {
return b.completeTime.localeCompare(a.completeTime);
});
}
/* 渲染卡片列表 */
function renderTable() {
var records = getFilteredRecords();
var container = document.getElementById('taskListContainer');
var emptyEl = document.getElementById('emptyState');
if (records.length === 0) {
container.innerHTML = '';
emptyEl.classList.add('show');
document.getElementById('tableFooter').textContent = '共 0 条记录';
return;
}
emptyEl.classList.remove('show');
container.innerHTML = '';
records.forEach(function(order) {
var taskCard = document.createElement('div');
taskCard.className = 'task-card';
var orderTypeLabel = order.orderType === 'internal' ? '内供订单' : '外售订单';
var orderTypeCls = order.orderType === 'internal' ? 'order-type-tag--internal' : 'order-type-tag--external';
/* 卡片头部:订单号 + 类型 + 完成时间 + 操作人 + 状态 */
var header = document.createElement('div');
header.className = 'task-card__header';
header.innerHTML =
'<span class="task-card__order-info"><span class="task-card__order-info__label">订单号:</span><span class="task-card__order-info__value">' + order.orderNo + '</span></span>' +
'<span class="order-type-tag ' + orderTypeCls + '">' + orderTypeLabel + '</span>' +
'<span class="task-card__arrival">&#x23F0; 贴标完成时间:<span class="task-card__arrival__time">' + order.completeTime + '</span></span>' +
'<span class="task-card__operator">&#x1F464; 操作人:<span class="task-card__operator__name">' + order.operator + '</span></span>' +
'<span class="task-card__status"><span class="status-badge status-badge--done">已贴标</span></span>';
taskCard.appendChild(header);
/* 表格头 */
var tableHead = document.createElement('div');
tableHead.className = 'item-table__head';
tableHead.innerHTML = '<div>名称</div><div style="text-align:center">净菜/餐品</div><div style="text-align:center">包装规格</div><div style="text-align:center">打包份数</div><div style="text-align:right">所需总重</div><div style="text-align:right">已称</div><div style="text-align:center">状态</div>';
taskCard.appendChild(tableHead);
/* 表格体 */
var tableBody = document.createElement('div');
tableBody.className = 'item-table__body';
order.mealPacks.forEach(function(pack) {
var totalNeed = 0;
var totalWeighed = 0;
pack.ingredients.forEach(function(ing) {
totalNeed += ing.weightPerPack * pack.packSize;
totalWeighed += ing.weighed;
});
var parentRow = document.createElement('div');
parentRow.className = 'item-parent-row';
parentRow.innerHTML =
'<div class="parent-name"><div class="parent-icon">&#x1F4E6;</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
'<div style="text-align:center;font-size:11px;color:' + (pack.type === '餐品净菜包' ? '#faad16' : '#4a90d9') + '">' + pack.type + '</div>' +
'<div style="text-align:center;font-size:11px;color:rgba(255,255,255,0.55)">' + (pack.packSpec || '—') + '</div>' +
'<div style="text-align:center"><span class="pack-size-tag">' + pack.packSize + '</span></div>' +
'<div class="parent-weight parent-weight--done">' + totalNeed + ' g</div>' +
'<div class="parent-weight parent-weight--done">' + totalWeighed + ' g</div>' +
'<div style="text-align:center"><span class="status-tag status-tag--done">已吐标</span></div>';
tableBody.appendChild(parentRow);
});
taskCard.appendChild(tableBody);
container.appendChild(taskCard);
});
var footerText;
if (isWeeklyMode) {
var monday = getMonday();
var mondayStr = dateStr(monday).substring(5).replace('-', '/');
var todayStr = TODAY.substring(5).replace('-', '/');
footerText = '本周 ' + mondayStr + ' - ' + todayStr + ' 共 ' + records.length + ' 条记录';
} else {
footerText = '共 ' + records.length + ' 条记录';
}
document.getElementById('tableFooter').textContent = footerText;
}
/* Toast */
function showToast(msg, duration) {
var el = document.getElementById('toast');
el.textContent = msg;
el.className = 'toast';
el.classList.add('show');
clearTimeout(el._timer);
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
}
/* 返回按钮 */
(function() {
var backBtn = document.getElementById('backBtn');
if (backBtn) {
var params = new URLSearchParams(window.location.search);
var referer = params.get('referer');
var userParam = userName ? '?user=' + encodeURIComponent(userName) + (userId ? '&uid=' + userId : '') : '';
if (referer) {
backBtn.href = referer;
} else {
backBtn.href = 'index.html' + userParam;
}
}
})();
/* 初始化 */
function init() {
generateDateStrip();
document.getElementById('modeDayBtn').addEventListener('click', function() { setMode(false); });
document.getElementById('modeWeekBtn').addEventListener('click', function() { setMode(true); });
document.getElementById('todayBtn').addEventListener('click', function() { selectDate(TODAY); });
document.getElementById('calendarInput').addEventListener('change', function(e) {
if (e.target.value) selectDate(e.target.value);
});
updateStats();
renderTable();
}
init();
</script>
</body>
</html>
@@ -0,0 +1,39 @@
# 净菜贴标秤 CHANGELOG
## 2026-06-25 | 初版创建
### 新增页面
- `00-login.html` — 员工登录页(4:3横屏 PAD外壳,左右布局:左侧品牌区 + 右侧登录面板,支持人脸识别/账号密码双Tab)
- `01-orders.html` — 贴标订单列表页(双Tab:待贴标/已贴标,已贴标Tab含操作人+贴标完成时间,支持"去贴标"跳转)
- `02-labeling.html` — 贴标作业页(两列布局:左列净菜识别+秤重,右列订单信息+贴标清单+贴标完成按钮)
- `index.html` — 模块导航页(从占位页改为实际功能菜单入口)
### 功能说明
- 接收配比秤已完成配比的订单(含净菜包和餐品净菜包)
- 净菜包:通过摄像头识别+候选选择+秤重确认弹窗完成秤重
- 餐品净菜包:每份用量显示"--",后方有"吐标"按钮,吐完标状态显示"已吐标",按钮可重复点击
- 贴标清单:餐品净菜包按餐品维度展示(不按食材展开),份数/需/已称按实际显示
- 贴标完成总按钮:所有净菜包秤重完成且所有餐品净菜包已吐标后可点击,点击后返回订单列表
- 已贴标Tab可查看历史记录及操作人信息
### 设计规范
- 4:3横屏 1024×768 PAD外壳(深色画布居中)
- 遵循 PAD 终端无可见滚动条约束
- 统一设计令牌(#0e1326 背景、#1a2b4a 卡片、#4a90d9 主色)
### 导航同步
- 更新 `canteen-device/index.html` 贴标机条目状态为"已设计"
- 更新根 `index.html` SITE_MAP 添加贴标秤4个页面条目
## 2026-06-25 | 状态统一与贴标历史页
### 修改
- `01-orders.html` — 左上角"退出"改为"返回",点击跳转菜单页;页面标题"贴标订单"改为"贴标任务"
- `02-labeling.html` — 所有秤重状态统一为待吐标/已吐标;净菜包吐标由秤重弹窗确认自动触发(无手动吐标按钮);餐品净菜包保持手动吐标/重新吐标按钮;列表新增"餐品"字段(餐品净菜包显示餐品名称,净菜包显示--);所有"重新吐标"按钮可重复点击
- `index.html` — "贴标订单"改为"贴标任务",新增"贴标历史"菜单按钮
### 新增页面
- `03-history.html` — 贴标历史页(4:3横屏,参考组配秤组配历史的显示逻辑:日视图/本周切换、日期条、搜索框、统计行;参考贴标秤任务列表的表单结构:订单号+类型+完成时间+操作人+表格行)
### 导航同步
- 更新根 `index.html` SITE_MAP 新增贴标历史页面条目,贴标订单更名为贴标任务
@@ -2,33 +2,168 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, initial-scale=1.0">
<title>净菜包装贴标 - 蚁熊智能餐养平台</title>
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标 - 模块导航 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
background: #05080f; color: #fff;
min-height: 100vh; min-width: 1024px;
display: flex; align-items: center; justify-content: center;
padding: 20px;
}
/* PAD 设备外壳 — 4:3 横屏 */
.device-frame {
width: 1024px; height: 768px;
background: #0e1326;
border-radius: 24px;
box-shadow: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04);
overflow: hidden; position: relative;
display: flex; flex-direction: column;
}
/* 顶部导航栏 */
.top-bar { display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px; }
.back-btn { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; transition: color 0.2s; }
.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); }
.main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); }
.placeholder { text-align: center; color: rgba(255,255,255,0.3); }
.placeholder__icon { font-size: 48px; margin-bottom: 16px; }
.placeholder__text { font-size: 14px; }
.top-bar__user { font-size: 12px; color: rgba(255,255,255,0.5); }
.logout-btn { font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer; padding: 4px 12px; border-radius: 4px; transition: all 0.2s; border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px; font-family: inherit; }
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
/* 主内容 */
.menu-main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); padding: 24px 32px; }
.menu-panel { width: 480px; display: flex; flex-direction: column; gap: 14px; }
.menu-panel__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.menu-panel__title { font-size: 14px; color: rgba(255,255,255,0.4); letter-spacing: 1px; }
.menu-panel__user { font-size: 12px; color: rgba(255,255,255,0.5); }
.menu-btn {
display: flex; align-items: center; gap: 14px;
padding: 16px 20px; border-radius: 12px;
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
cursor: pointer; transition: all 0.2s; text-decoration: none; color: #fff;
position: relative;
}
.menu-btn:hover { transform: translateX(4px); border-color: rgba(74,144,217,0.3); box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
.menu-btn:active { transform: translateX(2px) scale(0.99); }
.menu-btn__icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 22px; flex-shrink: 0; }
.menu-btn__icon--blue { background: rgba(74,144,217,0.15); }
.menu-btn__icon--green { background: rgba(82,196,26,0.15); }
.menu-btn__info { flex: 1; }
.menu-btn__title { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
.menu-btn__desc { font-size: 12px; color: rgba(255,255,255,0.4); }
.menu-btn__arrow { font-size: 16px; color: rgba(255,255,255,0.25); flex-shrink: 0; }
.menu-btn--has-tasks { border-color: rgba(74,144,217,0.5) !important; animation: menuPulse 2s ease-in-out infinite; }
@keyframes menuPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.4); } 50% { box-shadow: 0 0 0 6px rgba(74,144,217,0); } }
.menu-btn__badge { position: absolute; top: -6px; right: -6px; background: #ff4d4f; color: #fff; font-size: 10px; font-weight: 700; min-width: 18px; height: 18px; border-radius: 9px; display: none; align-items: center; justify-content: center; padding: 0 4px; line-height: 1; }
/* Toast */
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; max-width: 90%; text-align: center; line-height: 1.5; }
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.toast--warn { background: rgba(250,173,22,0.92); }
.toast--error { background: rgba(255,77,79,0.92); }
</style>
</head>
<body>
<div class="top-bar">
<span class="top-bar__title">净菜包装贴标机</span>
</div>
<div class="main">
<div class="placeholder">
<div class="placeholder__icon">&#x1F6A7;</div>
<div class="placeholder__text">净菜包装贴标机 &#x9875;&#x9762;&#x5F85;&#x5F00;&#x53D1;</div>
<div class="device-frame">
<div class="top-bar">
<a class="back-btn" href="../index.html">&#x25C0; 返回餐厅终端</a>
<span class="top-bar__title">净菜贴标秤</span>
<span class="top-bar__user" id="topUser">未登录</span>
<button class="logout-btn" onclick="handleLogout()" style="display:none" id="logoutBtn">退出登录</button>
</div>
<div class="menu-main">
<div class="menu-panel">
<div class="menu-panel__header">
<div class="menu-panel__title">&#x1F4CC; 功能菜单</div>
<div class="menu-panel__user" id="menuUser"></div>
</div>
<a class="menu-btn" id="btnOrders" onclick="handleMenu('01-orders.html')">
<div class="menu-btn__icon menu-btn__icon--blue">&#x1F4CB;</div>
<div class="menu-btn__info">
<div class="menu-btn__title">贴标任务</div>
<div class="menu-btn__desc">查看待贴标/已贴标订单</div>
</div>
<span class="menu-btn__badge" id="taskBadge">0</span>
<div class="menu-btn__arrow">&#x203A;</div>
</a>
<a class="menu-btn" onclick="handleMenu('03-history.html')">
<div class="menu-btn__icon menu-btn__icon--blue">&#x1F4C4;</div>
<div class="menu-btn__info">
<div class="menu-btn__title">贴标历史</div>
<div class="menu-btn__desc">查看历史贴标记录</div>
</div>
<div class="menu-btn__arrow">&#x203A;</div>
</a>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var currentUser = null;
/* 获取 URL 参数 */
function getUrlParam(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r !== null) return decodeURIComponent(r[2]);
return null;
}
/* 页面加载时检查登录状态 */
(function() {
var userName = getUrlParam('user');
var userId = getUrlParam('uid');
if (userName) {
currentUser = { name: userName, id: userId || '--' };
document.getElementById('topUser').textContent = '操作员:' + userName;
document.getElementById('menuUser').textContent = '当前用户:' + userName;
document.getElementById('logoutBtn').style.display = 'inline-block';
/* 模拟新任务到达 */
var btn = document.getElementById('btnOrders');
var badge = document.getElementById('taskBadge');
btn.classList.add('menu-btn--has-tasks');
badge.textContent = '3';
badge.style.display = 'flex';
}
})();
/* 菜单点击 */
function handleMenu(target) {
if (!currentUser && target !== '00-login.html') {
window.location.href = '00-login.html';
return;
}
var url = target;
if (currentUser) {
url += '?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
}
window.location.href = url;
}
/* 退出登录 */
function handleLogout() {
window.location.href = '00-login.html';
}
/* Toast */
function showToast(msg, duration, type) {
var el = document.getElementById('toast');
el.textContent = msg;
el.className = 'toast';
if (type === 'warn') el.classList.add('toast--warn');
if (type === 'error') el.classList.add('toast--error');
el.classList.add('show');
clearTimeout(el._timer);
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
}
</script>
</body>
</html>
</html>
@@ -85,6 +85,10 @@
display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 4px;
background: rgba(74,144,217,0.15); color: #4a90d9; font-weight: 500;
}
.pack-spec-tag {
display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 4px;
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.55); font-weight: 500;
}
.parent-weight { font-size: 12px; color: rgba(255,255,255,0.6); text-align: right; }
.parent-weight--done { color: #52c41a; font-weight: 600; }
@@ -203,7 +207,7 @@
{ name: '醋', cellIdx: 2, weightPerPack: 8, weighed: 0 },
{ name: '料酒', cellIdx: 4, weightPerPack: 12, weighed: 0 }
]},
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准500g', ingredients: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准500g', ingredients: [
{ name: '番茄块', icon: '&#x1F345;', weightPerPack: 300, weighed: 0 },
{ name: '鸡蛋液', icon: '&#x1F95A;', weightPerPack: 200, weighed: 0 }
], seasonings: [
@@ -218,7 +222,7 @@
expectedArrival: '2026-06-17 14:30',
taskNo: 'T-003',
mealPacks: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', ingredients: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '大份500g', ingredients: [
{ name: '番茄炒蛋', icon: '&#x1F958;', weightPerPack: 500, weighed: 2000 }
], seasonings: [
{ name: '盐', cellIdx: 2, weightPerPack: 5, weighed: 20 },
@@ -232,7 +236,7 @@
expectedArrival: '2026-06-17 16:00',
taskNo: 'T-004',
mealPacks: [
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '盒装', ingredients: [
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '标准份500g', ingredients: [
{ name: '凉拌黄瓜', icon: '&#x1F958;', weightPerPack: 500, weighed: 2500 }
], seasonings: [
{ name: '醋', cellIdx: 2, weightPerPack: 6, weighed: 30 },
@@ -330,7 +334,7 @@
parentRow.className = 'item-parent-row';
parentRow.innerHTML =
'<div class="parent-name"><div class="parent-icon">&#x1F4E6;</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
'<div style="text-align:center;font-size:12px;color:rgba(255,255,255,0.55)">' + (pack.packSpec || '—') + '</div>' +
'<div style="text-align:center"><span class="pack-spec-tag">' + (pack.packSpec || '—') + '</span></div>' +
'<div style="text-align:center"><span class="pack-size-tag">' + pack.packSize + '</span></div>' +
'<div class="parent-weight ' + weightClass + '">需 ' + totalNeed + ' 克</div>' +
'<div class="parent-weight ' + weightClass + '">已 ' + totalWeighed + ' 克</div>' +
@@ -637,7 +637,7 @@
orderNo: 'PO-20260616-003',
orderType: 'internal',
mealPacks: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', ingredients: [
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '大份500g', ingredients: [
{ name: '番茄炒蛋', icon: '&#x1F958;', weightPerPack: 500, weighed: 2000 }
], seasonings: [
{ name: '油', icon: '&#x1F6E2;', cellIdx: 0, weightPerPack: 30, weighed: 120 },
@@ -650,7 +650,7 @@
orderNo: 'PO-20260616-004',
orderType: 'external',
mealPacks: [
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '盒装', ingredients: [
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '标准份500g', ingredients: [
{ name: '凉拌黄瓜', icon: '&#x1F958;', weightPerPack: 500, weighed: 2500 }
], seasonings: [
{ name: '油', icon: '&#x1F6E2;', cellIdx: 0, weightPerPack: 20, weighed: 100 },
@@ -201,19 +201,20 @@
.task-card__arrival__time { color: #faad16; font-weight: 500; }
.task-card__status { margin-left: auto; }
/* 表格头(7列) */
/* 表格头(6列) */
.item-table__head {
display: grid; grid-template-columns: 1fr 0.65fr 0.6fr 0.5fr 0.65fr 0.6fr 50px; gap: 8px;
display: grid; grid-template-columns: 1fr 0.6fr 0.5fr 0.6fr 0.6fr 70px; gap: 8px;
padding: 6px 16px; background: rgba(0,0,0,0.2); font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500;
}
.item-table__body { display: flex; flex-direction: column; }
/* 餐品净菜包主行 */
.item-parent-row {
display: grid; grid-template-columns: 1fr 0.65fr 0.6fr 0.5fr 0.65fr 0.6fr 50px; gap: 8px;
display: grid; grid-template-columns: 1fr 0.6fr 0.5fr 0.6fr 0.6fr 70px; gap: 8px;
padding: 10px 16px; border-top: 1px solid rgba(255,255,255,0.04); align-items: center;
background: rgba(74,144,217,0.06);
background: rgba(74,144,217,0.06); margin-bottom: 4px; border-radius: 6px;
}
.item-parent-row:last-child { margin-bottom: 0; }
.parent-name { display: flex; align-items: center; gap: 8px; }
.parent-icon {
width: 32px; height: 32px; border-radius: 6px;
@@ -225,6 +226,10 @@
display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 4px;
background: rgba(74,144,217,0.15); color: #4a90d9; font-weight: 500;
}
.pack-spec-tag {
display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 4px;
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.55); font-weight: 500;
}
.parent-weight { font-size: 12px; color: rgba(255,255,255,0.6); text-align: right; }
/* 食材子行 */
@@ -377,52 +382,72 @@
var TODAY = '2026-06-16';
var WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
// 组配记录数据(按订单组织,对齐组配任务页字段结构
// 组配记录数据(与组配任务页01数据结构一致,全部为已组配状态
var RECORDS = [
{
orderNo: 'PO-20260616-001', orderType: 'internal', completeTime: '2026-06-16 10:20',
mealPacks: [
{ type: '净菜包', packName: '土豆丝', packSize: 3, packSpec: '500g', ingredients: [
{ name: '土豆丝', icon: '&#x1F954;', iconClass: 'child-icon--potato', weightPerPack: 500, weighed: 1500 }
]},
{ type: '净菜包', packName: '胡萝卜丝', packSize: 3, packSpec: '500g', ingredients: [
{ name: '胡萝卜丝', icon: '&#x1F955;', iconClass: 'child-icon--carrot', weightPerPack: 500, weighed: 1500 }
]},
{ type: '净菜包', packName: '黄瓜片', packSize: 3, packSpec: '500g', ingredients: [
{ name: '黄瓜片', icon: '&#x1F952;', iconClass: 'child-icon--cucumber', weightPerPack: 500, weighed: 1500 }
]},
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', ingredients: [
{ name: '宫保鸡丁', icon: '&#x1F958;', iconClass: 'child-icon--tomato', weightPerPack: 800, weighed: 1600 }
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
{ name: '土豆丝', icon: '&#x1F954;', weightPerPack: 300, weighed: 900 },
{ name: '胡萝卜丝', icon: '&#x1F955;', weightPerPack: 250, weighed: 750 },
{ name: '黄瓜片', icon: '&#x1F952;', weightPerPack: 250, weighed: 750 }
], seasonings: [
{ name: '油', cellIdx: 0, weightPerPack: 50, weighed: 150 },
{ name: '', cellIdx: 1, weightPerPack: 5, weighed: 15 },
{ name: '白糖', cellIdx: 2, weightPerPack: 10, weighed: 30 },
{ name: '酱油', cellIdx: 3, weightPerPack: 15, weighed: 45 },
{ name: '', cellIdx: 4, weightPerPack: 8, weighed: 24 },
{ name: '料酒', cellIdx: 5, weightPerPack: 12, weighed: 36 },
{ name: '味精', cellIdx: 6, weightPerPack: 2, weighed: 6 }
]}
]
},
{
orderNo: 'PO-20260616-002', orderType: 'external', completeTime: '2026-06-16 11:00',
mealPacks: [
{ type: '净菜包', packName: '白菜丝', packSize: 2, packSpec: '1000g', ingredients: [
{ name: '白菜丝', icon: '&#x1F96C;', iconClass: 'child-icon--cabbage', weightPerPack: 1000, weighed: 2000 }
]},
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
{ name: '鱼香肉丝', icon: '&#x1F958;', iconClass: 'child-icon--tomato', weightPerPack: 800, weighed: 1600 }
]}
]
},
{
orderNo: 'PO-20260615-003', orderType: 'internal', completeTime: '2026-06-15 14:50',
mealPacks: [
{ type: '净菜包', packName: '青椒丝', packSize: 3, packSpec: '400g', ingredients: [
{ name: '青椒丝', icon: '&#x1F97A;', iconClass: 'child-icon--green-onion', weightPerPack: 400, weighed: 1200 }
{ name: '白菜丝', icon: '&#x1F96C;', weightPerPack: 400, weighed: 800 },
{ name: '胡萝卜丝', icon: '&#x1F955;', weightPerPack: 400, weighed: 800 }
], seasonings: [
{ name: '油', cellIdx: 0, weightPerPack: 40, weighed: 80 },
{ name: '盐', cellIdx: 1, weightPerPack: 4, weighed: 8 },
{ name: '醋', cellIdx: 2, weightPerPack: 10, weighed: 20 },
{ name: '酱油', cellIdx: 3, weightPerPack: 12, weighed: 24 },
{ name: '白糖', cellIdx: 4, weightPerPack: 8, weighed: 16 },
{ name: '料酒', cellIdx: 5, weightPerPack: 10, weighed: 20 },
{ name: '豆瓣酱', cellIdx: 6, weightPerPack: 15, weighed: 30 }
]},
{ type: '净菜包', packName: '洋葱丝', packSize: 3, packSpec: '300g', ingredients: [
{ name: '洋葱丝', icon: '&#x1F9C5;', iconClass: 'child-icon--onion', weightPerPack: 300, weighed: 900 }
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准份500g', ingredients: [
{ name: '番茄块', icon: '&#x1F345;', weightPerPack: 300, weighed: 600 },
{ name: '鸡蛋液', icon: '&#x1F95A;', weightPerPack: 200, weighed: 400 }
], seasonings: [
{ name: '油', cellIdx: 0, weightPerPack: 30, weighed: 60 },
{ name: '盐', cellIdx: 1, weightPerPack: 4, weighed: 8 },
{ name: '白糖', cellIdx: 2, weightPerPack: 6, weighed: 12 }
]}
]
},
{
orderNo: 'PO-20260615-004', orderType: 'external', completeTime: '2026-06-15 15:20',
orderNo: 'PO-20260616-003', orderType: 'internal', completeTime: '2026-06-16 14:50',
mealPacks: [
{ type: '净菜包', packName: '芹菜段', packSize: 2, packSpec: '400g', ingredients: [
{ name: '芹菜段', icon: '&#x1F96C;', iconClass: 'child-icon--cabbage', weightPerPack: 400, weighed: 800 }
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '大份500g', ingredients: [
{ name: '番茄炒蛋', icon: '&#x1F958;', weightPerPack: 500, weighed: 2000 }
], seasonings: [
{ name: '油', cellIdx: 0, weightPerPack: 30, weighed: 120 },
{ name: '盐', cellIdx: 2, weightPerPack: 5, weighed: 20 },
{ name: '白糖', cellIdx: 3, weightPerPack: 8, weighed: 32 }
]}
]
},
{
orderNo: 'PO-20260616-004', orderType: 'external', completeTime: '2026-06-16 15:20',
mealPacks: [
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '标准份500g', ingredients: [
{ name: '凉拌黄瓜', icon: '&#x1F958;', weightPerPack: 500, weighed: 2500 }
], seasonings: [
{ name: '油', cellIdx: 0, weightPerPack: 20, weighed: 100 },
{ name: '醋', cellIdx: 2, weightPerPack: 6, weighed: 30 },
{ name: '酱油', cellIdx: 3, weightPerPack: 10, weighed: 50 }
]}
]
}
@@ -554,6 +579,11 @@
totalWeight += ing.weighed;
foodSet[ing.name] = true;
});
if (pack.seasonings) {
pack.seasonings.forEach(function(sea) {
totalWeight += sea.weighed;
});
}
totalPacks += pack.packSize;
});
});
@@ -611,12 +641,15 @@
});
}
/* 获取净菜包状态 */
/* 获取餐品净菜包状态:主辅材+调料全部完成才算已组配 */
function getPackStatus(pack) {
var allDone = pack.ingredients.every(function(ing) {
var ingDone = pack.ingredients.every(function(ing) {
return ing.weighed >= ing.weightPerPack * pack.packSize;
});
if (allDone) return { text: '已完成', cls: 'status-badge--done' };
var seaDone = !pack.seasonings || pack.seasonings.every(function(sea) {
return sea.weighed >= sea.weightPerPack * pack.packSize;
});
if (ingDone && seaDone) return { text: '已组配', cls: 'status-badge--done' };
return { text: '待组配', cls: 'status-badge--pending' };
}
@@ -638,7 +671,7 @@
records.forEach(function(order) {
var packs = order.mealPacks;
var orderAllDone = packs.every(function(p) { return !getPackStatus(p).text !== '组配'; });
var orderAllDone = packs.every(function(p) { return getPackStatus(p).text === '组配'; });
var taskCard = document.createElement('div');
taskCard.className = 'task-card';
@@ -653,13 +686,13 @@
'<span class="task-card__order-info"><span class="task-card__order-info__label">订单号:</span><span class="task-card__order-info__value">' + order.orderNo + '</span></span>' +
'<span class="order-type-tag ' + orderTypeCls + '">' + orderTypeLabel + '</span>' +
'<span class="task-card__arrival">&#x23F0; 任务完成时间:<span class="task-card__arrival__time">' + order.completeTime + '</span></span>' +
'<span class="task-card__status"><span class="status-badge status-badge--done">已完成</span></span>';
'<span class="task-card__status"><span class="status-badge ' + (orderAllDone ? 'status-badge--done' : 'status-badge--pending') + '">' + (orderAllDone ? '已组配' : '待组配') + '</span></span>';
taskCard.appendChild(header);
/* 表格头(7列 */
/* 表格头(6列:餐品净菜包名称 / 包装规格 / 打包份数 / 所需总重 / 已称 / 状态 */
var tableHead = document.createElement('div');
tableHead.className = 'item-table__head';
tableHead.innerHTML = '<div>名称</div><div style="text-align:center">净菜/餐品</div><div style="text-align:center">包装规格</div><div style="text-align:center">打包份数</div><div style="text-align:right">所需总重</div><div style="text-align:right">已称</div><div style="text-align:center">状态</div>';
tableHead.innerHTML = '<div>餐品净菜包名称</div><div style="text-align:center">包装规格</div><div style="text-align:center">打包份数</div><div style="text-align:right">所需总重</div><div style="text-align:right">已称</div><div style="text-align:center">状态</div>';
taskCard.appendChild(tableHead);
/* 表格体 */
@@ -668,26 +701,33 @@
packs.forEach(function(pack) {
var packStatus = getPackStatus(pack);
var totalNeed = 0;
var totalWeighed = 0;
/* 主辅材合计 */
var ingNeed = 0, ingWeighed = 0;
pack.ingredients.forEach(function(ing) {
totalNeed += ing.weightPerPack * pack.packSize;
totalWeighed += ing.weighed;
ingNeed += ing.weightPerPack * pack.packSize;
ingWeighed += ing.weighed;
});
totalNeed = Math.round(totalNeed * 100) / 100;
totalWeighed = Math.round(totalWeighed * 100) / 100;
/* 调料合计 */
var seaNeed = 0, seaWeighed = 0;
if (pack.seasonings) {
pack.seasonings.forEach(function(sea) {
seaNeed += sea.weightPerPack * pack.packSize;
seaWeighed += sea.weighed;
});
}
var totalNeed = Math.round((ingNeed + seaNeed) * 100) / 100;
var totalWeighed = Math.round((ingWeighed + seaWeighed) * 100) / 100;
/* 主行:净菜包/餐品净菜包 */
/* 主行:餐品净菜包 */
var parentRow = document.createElement('div');
parentRow.className = 'item-parent-row';
parentRow.innerHTML =
'<div class="parent-name"><div class="parent-icon">&#x1F4E6;</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
'<div style="text-align:center;font-size:12px;color:' + (pack.type === '餐品净菜包' ? '#faad16' : '#4a90d9') + '">' + pack.type + '</div>' +
'<div style="text-align:center;font-size:12px;color:rgba(255,255,255,0.55)">' + (pack.packSpec || '—') + '</div>' +
'<div style="text-align:center"><span class="pack-spec-tag">' + (pack.packSpec || '—') + '</span></div>' +
'<div style="text-align:center"><span class="pack-size-tag">' + pack.packSize + '</span></div>' +
'<div class="parent-weight">需 ' + totalNeed + ' 克</div>' +
'<div class="parent-weight">已 ' + totalWeighed + ' 克</div>' +
'<div style="text-align:center"><span class="status-tag ' + (packStatus.text === '已完成' ? 'status-tag--done' : 'status-tag--wait') + '">' + packStatus.text + '</span></div>';
'<div style="text-align:center"><span class="status-tag ' + (packStatus.text === '已组配' ? 'status-tag--done' : 'status-tag--wait') + '">' + packStatus.text + '</span></div>';
tableBody.appendChild(parentRow);
});
@@ -1,5 +1,14 @@
# 净菜组配秤 CHANGELOG
## [2.1.0] - 2026-06-25
### 组配记录页对齐组配任务页
- **表头6列**:餐品净菜包名称 / 包装规格 / 打包份数 / 所需总重 / 已称 / 状态(去掉"净菜/餐品"列和操作列)
- **数据结构一致**:只含餐品净菜包,每个包含 ingredients + seasonings,与01页镜像
- **所需总重含调料**:主辅材需求合计 + 调料需求合计
- **状态计算一致**:主辅材+调料全部完成才算"已组配",文案统一为待组配/已组配
- **统计行含调料重量**:组配总重量包含调料已称重量
## [2.0.0] - 2026-06-25
### 屏幕比例改造
@@ -539,7 +539,7 @@ flowchart TD
| 采样数量 | 该食材采样次数 |
| 采样时间 | 最近一次采样时间 |
点击卡片 → 弹窗展示采样详情(采样备注等)。
点击卡片 → 弹窗展示采样详情(图片网格、采样时间等)。
> 注:采样历史不涉及验收合格/不合格概念,不涉及供应商信息,是独立的食材样本库。
@@ -588,7 +588,6 @@ flowchart TD
|------|------|------|----------|
| 食材名称 | AI识别 + 可修正 | 是 | 必须为食材库中存在的食材 |
| 外观品级 | 单选 | 是 | 合格/不合格 |
| 采样备注 | 文本 | 否 | 最多 200 字 |
---
@@ -678,12 +677,19 @@ flowchart TD
### 6.4 返回按钮行为
> **核心原则**:从哪来回哪去。
| 页面 | 返回目标 | 特殊处理 |
|------|----------|----------|
| 00 欢迎页 | `../index.html`(根导航) | — |
| 01 Step1 | 来源页(动态检测) | |
| 01 Step1 | 来源页(动态检测) | 优先 URL 参数 referer → document.referrer → 00 欢迎页 |
| 01 Step2 | 有验收数据 → 弹出返回确认弹窗;无数据 → 回退到 Step1 | 确认弹窗防止误操作丢失数据 |
| 02 ~ 05 | `00-welcome.html` | — |
| 02 ~ 05 | 来源页(从哪来回哪去) | 优先 URL 参数 referer → document.referrer → 00 欢迎页 |
**返回逻辑优先级**
1. URL 参数 `?referer=xxx`
2. `document.referrer`(浏览器来源页)
3. 默认返回 `00-welcome.html`
---
@@ -708,3 +714,12 @@ flowchart TD
- 供应商类型映射
- 库存联动(入库成功后更新仓储)
- 验收/采样记录查询导出
---
## 八、变更日志
| 日期 | 版本 | 变更内容 |
|------|------|---------|
| 2026-06-13 | v1.0 | 初始创建,覆盖 6 个页面的基础需求描述 |
| 2026-06-15 | v1.1 | 采样历史页面删除采样备注字段;全局返回按钮改为「从哪来回哪去」逻辑(优先 URL 参数 referer → document.referrer → 默认页) |