feat: 净菜包装秤术语统一+包装作业交互升级 + 组配秤按单份拆分

净菜包装秤(原贴标秤):
- 全模块术语统一: 贴标→包装, 贴标秤→包装秤, 去丝/段/片后缀
- 包装作业页: 清单按packSize展开为单份, 移除弹窗改为直接读秤, 新增搜索框
- 包装任务/历史页: 表头对齐, 按单份拆分展示
- 新增label-preview.html标签预览页

净菜组配秤:
- 任务/历史页: 行展示按单份拆分, 食材名称简化(去丝/段/片/块/液后缀)
- 组配作业页: 移除确认弹窗, 底部按钮区改为flex布局

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhangyan
2026-06-26 10:11:51 +08:00
co-authored by Claude Opus 4.6
parent 63940f4c49
commit 723b5cef02
16 changed files with 1356 additions and 414 deletions
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 登录 | 蚁熊智能餐养平台</title>
<title>净菜包装秤 - 登录 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -146,15 +146,15 @@
<div class="device-frame">
<div class="top-bar">
<span class="top-bar__title">净菜贴标秤 - 员工登录</span>
<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 class="login-brand__name">净菜包装</div>
<div class="login-brand__desc">接收配比完成订单<br>核对实物 · 包装塑封 · 全程可溯</div>
</div>
<!-- 右侧登录面板 -->
@@ -209,8 +209,8 @@
/* 模拟用户数据 */
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-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' }
];
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 订单列表 | 蚁熊智能餐养平台</title>
<title>净菜包装秤 - 订单列表 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -64,15 +64,15 @@
/* 表格头 */
.item-table__head {
display: grid; grid-template-columns: 1fr 0.7fr 0.55fr 0.5fr 0.6fr 0.55fr 50px; gap: 6px;
display: grid; grid-template-columns: 1fr 0.5fr 0.45fr 0.25fr 0.4fr 0.4fr 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;
display: grid; grid-template-columns: 1fr 0.5fr 0.45fr 0.25fr 0.4fr 0.4fr 50px; gap: 6px;
padding: 6px 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; }
@@ -103,7 +103,7 @@
<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>
<span class="top-bar__title">包装任务</span>
<div class="top-bar__user">
<div class="top-bar__avatar" id="topAvatar">--</div>
<span id="topUser">--</span>
@@ -115,7 +115,7 @@
<!-- 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 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>
@@ -151,8 +151,8 @@
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: 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 }] }
]
},
@@ -163,7 +163,7 @@
taskNo: 'L-002',
labeled: false,
mealPacks: [
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: '1000g', ingredients: [{ name: '白菜', weightPerPack: 1000, weighed: 2000 }] },
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: '1000g', ingredients: [{ name: '白菜', weightPerPack: 1000, weighed: 2000 }] },
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '鱼香肉丝', weightPerPack: 800, weighed: 1600 }] }
]
},
@@ -179,7 +179,7 @@
}
];
/* 已贴标订单(贴标历史) */
/* 已包装订单(包装历史) */
var mockDoneData = [
{
orderNo: 'PO-20260624-001',
@@ -190,7 +190,7 @@
labeledTime: '2026-06-24 10: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: '盒装', ingredients: [{ name: '凉拌黄瓜', weightPerPack: 600, weighed: 1200 }] }
]
},
@@ -203,7 +203,7 @@
labeledTime: '2026-06-24 13:45',
operator: '陈十',
mealPacks: [
{ type: '净菜包', packName: '芹菜', packSize: 2, packSpec: '500g', ingredients: [{ name: '芹菜', weightPerPack: 500, weighed: 1000 }] },
{ type: '净菜包', packName: '芹菜', packSize: 2, packSpec: '500g', ingredients: [{ name: '芹菜', weightPerPack: 500, weighed: 1000 }] },
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
]
}
@@ -219,7 +219,7 @@
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' };
return allDone ? { text: '已包装', cls: 'status-tag--done' } : { text: '待打印', cls: 'status-tag--wait' };
}
/* 渲染任务列表 */
@@ -251,12 +251,12 @@
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>';
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__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>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--done">已包装</span></span>';
}
header.innerHTML = headerHtml;
taskCard.appendChild(header);
@@ -264,10 +264,10 @@
/* 表格头 */
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: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';
@@ -275,25 +275,32 @@
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 ingWeight = pack.ingredients[0] ? pack.ingredients[0].weightPerPack : 0;
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);
/* 按 packSize 拆分为单份行 */
/* 从包装规格中提取应称重量(如"500g"→500 */
var specMatch = pack.packSpec && pack.packSpec.match(/(\d+)g/);
var dueWeight = specMatch ? parseInt(specMatch[1]) : ingWeight;
var doneWeight = currentFilter === 'done' ? dueWeight : 0;
for (var p = 0; p < pack.packSize; p++) {
var parentRow = document.createElement('div');
parentRow.className = 'item-parent-row';
var statusCell = '<div style="text-align:center"><span class="status-tag ' + packStatus.cls + '">' + packStatus.text + '</span>';
if (currentFilter === 'done') {
statusCell += '<div style="margin-top:3px"><button style="font-size:10px;padding:2px 8px;background:rgba(74,144,217,0.15);border:1px solid rgba(74,144,217,0.3);border-radius:4px;color:#4a90d9;cursor:pointer;font-family:inherit;" onclick="event.stopPropagation();handleReprint(\'' + order.orderNo + '\',\'' + pack.packName + '\',' + (p + 1) + ')">重新打印</button></div>';
}
statusCell += '</div>';
parentRow.innerHTML =
'<div class="parent-name"><div class="parent-icon">&#x1F4E6;</div><span class="parent-name__text">' + pack.packName + ' <span style="font-size:10px;color:rgba(255,255,255,0.4)">第' + (p + 1) + '份/' + pack.packSize + '份</span></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;font-size:11px;color:rgba(255,255,255,0.55)">1</div>' +
'<div class="parent-weight parent-weight--done">' + dueWeight + ' g</div>' +
'<div class="parent-weight">' + doneWeight + ' g</div>' +
statusCell;
tableBody.appendChild(parentRow);
}
});
taskCard.appendChild(tableBody);
@@ -313,13 +320,18 @@
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 handleReprint(orderNo, packName, portion) {
showToast('重新打印: ' + orderNo + ' ' + packName + ' 第' + portion + '份', 2000, 'success');
}
/* 退出登录 */
function handleLogout() {
window.location.href = '00-login.html';
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 贴标任务 | 蚁熊智能餐养平台</title>
<title>净菜包装秤 - 包装任务 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -103,6 +103,21 @@
.reidentify-btn:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
.reidentify-btn:active { transform: scale(0.98); }
/* 搜索框 */
.food-search { flex-shrink: 0; }
.food-search__wrap { position: relative; }
.food-search__input {
width: 100%; padding: 8px 14px 8px 34px; background: #121832;
border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #fff;
font-size: 13px; outline: none; transition: border-color 0.2s;
}
.food-search__input:focus { border-color: #4a90d9; }
.food-search__input::placeholder { color: rgba(255,255,255,0.25); }
.food-search__icon {
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
font-size: 13px; color: rgba(255,255,255,0.3);
}
/* 候选食材区 */
.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; }
@@ -146,7 +161,7 @@
.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; }
@@ -210,7 +225,7 @@
.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;
@@ -222,36 +237,6 @@
.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);
@@ -271,7 +256,7 @@
<!-- 顶部导航 -->
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<span class="page-title">贴标任务</span>
<span class="page-title">包装任务</span>
<span class="top-bar__spacer"></span>
<div class="user-info">
<div class="avatar" id="userAvatar">--</div>
@@ -306,6 +291,14 @@
<span id="statusText">就绪,等待净菜放置</span>
</div>
<button class="reidentify-btn" onclick="handleReidentify()">&#x1F504; 重新识别</button>
<!-- 搜索框 -->
<div class="food-search">
<div class="food-search__wrap">
<span class="food-search__icon">&#x1F50D;</span>
<input type="text" class="food-search__input" id="foodSearchInput"
placeholder="搜索净菜名称,回车确认..." onkeydown="handleFoodSearchKey(event)">
</div>
</div>
<div class="candidate-section" id="candidateSection">
<div class="candidate-header">
<span class="candidate-title" id="candidateTitle">智能识别候选</span>
@@ -319,7 +312,7 @@
<div class="scene-note">场景监控后台静默录制中</div>
</div>
<!-- 右列:订单信息 + 贴标清单 + 完成按钮 -->
<!-- 右列:订单信息 + 包装清单 + 完成按钮 -->
<div class="col-right">
<!-- 订单信息卡片 -->
<div class="task-card">
@@ -343,14 +336,14 @@
</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>
<button class="next-btn" id="btnNext" onclick="handleLabelDone()">包装完成</button>
</div>
</div>
</div>
@@ -359,27 +352,6 @@
<!-- 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) {
@@ -402,8 +374,8 @@
orderNo: 'PO-20260625-001',
orderType: 'internal',
items: [
{ type: '净菜包', packName: '土豆', packSize: 3, packSpec: '500g', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 500, weighed: 1500, icon: '🥔' },
{ type: '净菜包', packName: '胡萝卜', packSize: 3, packSpec: '500g', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 500, weighed: 1500, icon: '🥕' },
{ type: '净菜包', packName: '土豆', packSize: 3, packSpec: '500g', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 500, weighed: 1500, icon: '🥔' },
{ type: '净菜包', packName: '胡萝卜', packSize: 3, packSpec: '500g', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 500, weighed: 1500, icon: '🥕' },
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', weightPerPack: 800, weighed: 1600, icon: '🍱' }
]
},
@@ -411,7 +383,7 @@
orderNo: 'PO-20260625-002',
orderType: 'external',
items: [
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: '1000g', cutSpec: '50mm*30mm*30mm · 宽段 · 平刀', weightPerPack: 1000, weighed: 2000, icon: '🥬' },
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: '1000g', cutSpec: '50mm*30mm*30mm · 宽段 · 平刀', weightPerPack: 1000, weighed: 2000, icon: '🥬' },
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', weightPerPack: 800, weighed: 1600, icon: '🍱' }
]
},
@@ -426,34 +398,38 @@
/* AI 识别候选数据 */
var aiResults = {
'potato': { name: '土豆', icon: '🥔', confidence: 97 },
'carrot': { name: '胡萝卜', icon: '🥕', confidence: 96 },
'cabbage': { name: '白菜', icon: '🥬', confidence: 93 }
'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;
/* ---- 初始化 ---- */
/* ---- 初始化:按 packSize 展开为单份 ---- */
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,
cutSpec: item.cutSpec,
weightPerPack: item.weightPerPack,
weighed: item.weighed,
icon: item.icon,
labeled: false
};
/* 将每个 item 按 packSize 展开为独立份数 */
allItems = [];
task.items.forEach(function(item) {
for (var p = 0; p < item.packSize; p++) {
allItems.push({
type: item.type,
packName: item.packName,
packSpec: item.packSpec,
cutSpec: item.cutSpec,
weightPerPack: item.weightPerPack,
portionIdx: p + 1,
portionTotal: item.packSize,
weighed: 0,
icon: item.icon,
labeled: false
});
}
});
currentVegIdx = -1;
@@ -475,7 +451,7 @@
renderItemList();
if (currentVegIdx >= 0) {
document.getElementById('scaleWeightValue').textContent = allItems[currentVegIdx].weighed.toFixed(2);
document.getElementById('scaleWeightValue').textContent = '0.00';
document.getElementById('cameraOverlay').classList.add('show');
} else {
document.getElementById('scaleWeightValue').textContent = '0.00';
@@ -491,7 +467,7 @@
function updateTaskStatus() {
var allDone = checkAllDone();
var statusEl = document.getElementById('taskStatus');
statusEl.textContent = allDone ? '已完成' : '待贴标';
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;
}
@@ -501,13 +477,12 @@
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 === '餐品净菜包';
@@ -523,16 +498,6 @@
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) {
@@ -547,7 +512,7 @@
row.innerHTML =
'<div class="item-card__header">' +
'<div class="item-row__name"><span class="item-row__name-icon">' + item.icon + '</span>' + item.packName + '</div>' +
'<div class="item-row__name"><span class="item-row__name-icon">' + item.icon + '</span>' + item.packName + ' <span style="font-size:10px;color:rgba(255,255,255,0.4)">第' + item.portionIdx + '份/' + item.portionTotal + '份</span></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>' +
@@ -557,11 +522,8 @@
'<div class="item-card__meta-item"><span class="item-card__meta-label">净菜类型:</span><span class="item-card__meta-value">' + (isMeal ? '—' : (item.cutSpec || '—')) + '</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 class="item-card__data-item"><span class="item-card__data-label">单份重量:</span><span class="item-card__data-value">' + item.weightPerPack + 'g</span></div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">已称:</span><span class="item-card__data-value ' + (item.labeled ? 'item-card__data-value--done' : 'item-card__data-value--zero') + '">' + item.weighed + 'g</span></div>' +
'</div>';
body.appendChild(row);
@@ -626,7 +588,7 @@
var r = aiResults[key];
var card = document.createElement('div');
card.className = 'candidate-card';
card.onclick = function() { openWeightModal(r); };
card.onclick = function() { confirmCandidateDirect(r); };
card.innerHTML =
'<div class="candidate-card__img">' + r.icon + '</div>' +
'<div class="candidate-card__name">' + r.name + '</div>' +
@@ -635,34 +597,12 @@
});
}
/* ---- 秤重确认弹窗 ---- */
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');
/* ---- 秤重直接确认(无弹窗,候选点击后直接读秤赋值) ---- */
function confirmCandidateDirect(result) {
/* 在待打印的净菜包中查找匹配项 */
var matchedIdx = -1;
for (var i = 0; i < allItems.length; i++) {
if (allItems[i].type === '净菜包' && allItems[i].packName === pendingCandidate.name) {
if (allItems[i].type === '净菜包' && !allItems[i].labeled && allItems[i].packName === result.name) {
matchedIdx = i;
break;
}
@@ -670,25 +610,25 @@
if (matchedIdx < 0) {
showToast('未找到匹配的净菜包项', 1500, 'error');
pendingCandidate = null;
return;
}
var modalWeight = parseFloat(document.getElementById('modalWeight').textContent) || 0;
allItems[matchedIdx].weighed = modalWeight;
/* 直接读取当前秤重赋值 */
var scaleWeight = parseFloat(document.getElementById('scaleWeightValue').textContent) || 0;
allItems[matchedIdx].weighed = scaleWeight;
/* 秤重确认后自动打印 */
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';
document.getElementById('statusText').textContent = '秤重确认: ' + result.name + ' 第' + allItems[matchedIdx].portionIdx + '份 — ' + scaleWeight.toFixed(1) + 'g';
showToast('秤重完成: ' + pendingCandidate.name + ' ' + modalWeight.toFixed(1) + 'g', 2000, 'success');
pendingCandidate = null;
showToast('秤重完成: ' + result.name + ' ' + allItems[matchedIdx].portionIdx + '份 ' + scaleWeight.toFixed(1) + 'g', 2000, 'success');
renderItemList();
updateTaskStatus();
/* 延迟回到就绪态,推进到下一个待打印净菜包 */
setTimeout(function() {
state = 'idle';
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
@@ -696,17 +636,38 @@
document.getElementById('candidateList').innerHTML = '';
document.getElementById('candidateEmpty').classList.remove('hide');
document.getElementById('candidateTitle').textContent = '智能识别候选';
document.getElementById('cameraOverlay').classList.add('show');
/* 查找下一个待打印的净菜包 */
currentVegIdx = -1;
for (var j = 0; j < allItems.length; j++) {
if (allItems[j].type === '净菜包' && !allItems[j].labeled) {
currentVegIdx = j;
break;
}
}
if (currentVegIdx >= 0) {
document.getElementById('scaleWeightValue').textContent = '0.00';
document.getElementById('cameraOverlay').classList.add('show');
} else {
document.getElementById('scaleWeightValue').textContent = '0.00';
var hasPendingMeal = allItems.some(function(item) { return item.type === '餐品净菜包' && !item.labeled; });
if (hasPendingMeal) {
showToast('净菜包已全部打印,请继续打印餐品净菜包', 2500, 'success');
} else {
showToast('所有项目已打印完成', 2000, 'success');
}
}
}, 2000);
}
/* ---- 贴标完成 ---- */
/* ---- 包装完成 ---- */
function handleLabelDone() {
if (!checkAllDone()) {
showToast('还有项目未完成打印', 2000, 'error');
return;
}
showToast('贴标完成,正在返回订单列表...', 1500, 'success');
showToast('包装完成,正在返回订单列表...', 1500, 'success');
setTimeout(function() {
window.location.href = '01-orders.html?user=' + encodeURIComponent(userName) + (userId ? '&uid=' + userId : '');
}, 1000);
@@ -724,6 +685,44 @@
showToast('已重新发起识别', 1000, 'success');
}
/* ---- 搜索食材 ---- */
function handleFoodSearchKey(event) {
if (event.key !== 'Enter') return;
var input = document.getElementById('foodSearchInput');
var kw = input.value.trim();
if (!kw) return;
document.getElementById('cameraOverlay').classList.remove('show');
var keys = Object.keys(aiResults);
var results = [];
for (var i = 0; i < keys.length; i++) {
var r = aiResults[keys[i]];
if (r.name.indexOf(kw) !== -1) {
results.push({ key: keys[i], name: r.name, icon: r.icon, confidence: r.confidence });
}
}
var list = document.getElementById('candidateList');
var empty = document.getElementById('candidateEmpty');
var title = document.getElementById('candidateTitle');
if (results.length === 0) {
list.innerHTML = '<div style="text-align:center;color:rgba(255,255,255,0.25);padding:12px 0;font-size:12px;grid-column:1/-1;">未找到匹配食材,请尝试其他关键词</div>';
title.textContent = '搜索结果';
empty.classList.add('hide');
} else {
title.textContent = '搜索结果 (' + results.length + ')';
list.innerHTML = '';
empty.classList.add('hide');
results.forEach(function(r) {
var card = document.createElement('div');
card.className = 'candidate-card';
card.onclick = function() { confirmCandidateDirect(r); };
card.innerHTML =
'<div class="candidate-card__img">' + r.icon + '</div>' +
'<div class="candidate-card__name">' + r.name + '</div>';
list.appendChild(card);
});
}
}
/* ---- 退出登录 ---- */
function handleLogout() {
window.location.href = '00-login.html';
@@ -248,29 +248,29 @@
{
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: 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: '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: 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: 2, packSpec: '500g', ingredients: [{ name: '芹菜', weightPerPack: 500, weighed: 1000 }] },
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
]
},
@@ -1,4 +1,28 @@
# 净菜贴标秤 CHANGELOG
# 净菜包装秤 CHANGELOG
## 2026-06-26 | 术语统一"贴标→包装" + 包装作业交互升级
### 术语统一
- 全模块"净菜贴标秤"→"净菜包装秤"(页面标题、标签、按钮、导航条目、数据、注释)
- "贴标"→"包装"(待贴标→待包装、已贴标→已包装、去贴标→去包装、贴标任务→包装任务、贴标清单→包装清单)
- 员工角色:贴标车间→包装车间、贴标员→包装员
- 净菜包名称简化:去丝/段/片后缀(土豆丝→土豆、白菜丝→白菜等)
-`index.html` SITE_MAP 同步更新
### 新增
- `label-preview.html` — 标签预览页(展示打印标签样式)
### 包装作业页交互升级 (`02-labeling.html`)
- 清单按 packSize 展开为单份("第N份/M份"),每份独立卡片
- 移除秤重确认弹窗,点击候选食材直接读秤赋值并自动打印
- 秤重完成一份后自动推进到下一个待打印净菜包
- 新增搜索框:手动输入食材名称回车搜索,支持模糊匹配
- 卡片展示简化:仅保留单份重量+已称,移除餐品/每份用量/份数/需字段
### 包装任务页 (`01-orders.html`)
- 表头优化:打包份数→份数、所需总重→应称重量、已称→已称重量
- 行展示按单份拆分("第N份/M份"),与包装作业页一致
- 已包装Tab新增"重新打印"按钮
## 2026-06-26 | 包装历史页字段与显示逻辑对齐
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, initial-scale=1.0">
<title>净菜贴标秤 - 模块导航 | 蚁熊智能餐养平台</title>
<title>净菜包装秤 - 模块导航 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -68,7 +68,7 @@
<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__title">净菜包装</span>
<span class="top-bar__user" id="topUser">未登录</span>
<button class="logout-btn" onclick="handleLogout()" style="display:none" id="logoutBtn">退出登录</button>
</div>
@@ -83,8 +83,8 @@
<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 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>
@@ -93,8 +93,17 @@
<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 class="menu-btn__title">包装历史</div>
<div class="menu-btn__desc">查看历史包装记录</div>
</div>
<div class="menu-btn__arrow">&#x203A;</div>
</a>
<a class="menu-btn" onclick="handleMenu('label-preview.html')">
<div class="menu-btn__icon menu-btn__icon--green">&#x1F3F7;&#xFE0F;</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>
@@ -0,0 +1,417 @@
<!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; flex-shrink: 0; }
.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); }
/* Tab 切换栏 */
.tab-bar { display: flex; gap: 0; padding: 12px 20px 0; flex-shrink: 0; }
.tab-btn { padding: 8px 20px; border: 1px solid rgba(255,255,255,0.08); background: transparent; color: rgba(255,255,255,0.5); font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; font-family: inherit; border-radius: 8px 8px 0 0; border-bottom: none; }
.tab-btn:hover { color: rgba(255,255,255,0.8); background: rgba(255,255,255,0.03); }
.tab-btn.active { color: #fff; border-color: rgba(255,255,255,0.12); }
.tab-btn--blue.active { background: #1a2b4a; }
.tab-btn--green.active { background: #1a2b4a; }
.tab-btn--orange.active { background: #1a2b4a; }
.tab-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; position: relative; top: -1px; }
.tab-dot--blue { background: #4a90d9; }
.tab-dot--green { background: #52c41a; }
.tab-dot--orange { background: #fa8c16; }
/* 主内容区 */
.main-content { flex: 1; padding: 16px 20px; overflow-y: auto; scrollbar-width: none; display: flex; flex-direction: column; gap: 16px; }
.main-content::-webkit-scrollbar { display: none; }
/* ====== 标签卡片(模拟热敏标签纸) ====== */
.label-panel { display: none; flex-direction: column; gap: 16px; }
.label-panel.active { display: flex; }
/* 单个标签 */
.label-sticker {
width: 100%; background: #fff; color: #1a1a2e; border-radius: 8px;
overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.3);
display: flex; flex-direction: row;
/* 模拟 80mm × 50mm 热敏标签比例 */
min-height: 140px;
}
/* 标签主信息区 */
.label-sticker__body { flex: 1; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
/* 标签标题栏 */
.label-sticker__title-bar {
font-size: 13px; font-weight: 700; color: #fff; padding: 3px 10px; border-radius: 4px;
display: inline-block; align-self: flex-start; letter-spacing: 2px;
}
.label-sticker__title-bar--blue { background: #4a90d9; }
.label-sticker__title-bar--green { background: #52c41a; }
.label-sticker__title-bar--orange { background: #fa8c16; }
/* 信息行 */
.label-info { display: flex; flex-wrap: wrap; gap: 2px 16px; font-size: 11px; }
.label-info__item { display: flex; align-items: center; gap: 3px; white-space: nowrap; }
.label-info__label { color: #888; }
.label-info__value { color: #1a1a2e; font-weight: 600; }
/* 订单标签 — 包装项明细表 */
.label-detail-table { width: 100%; border-collapse: collapse; margin-top: 2px; font-size: 10px; }
.label-detail-table th {
background: #f0f4f8; color: #666; font-weight: 500; padding: 3px 6px;
text-align: left; border-bottom: 1px solid #e0e4e8;
}
.label-detail-table td {
padding: 3px 6px; border-bottom: 1px solid #f0f0f0; color: #333;
}
.label-detail-table .type-tag { display: inline-block; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 500; }
.type-tag--veg { background: #e6f7ff; color: #4a90d9; }
.type-tag--meal { background: #fff7e6; color: #fa8c16; }
/* 总重量行 */
.label-total { font-size: 11px; font-weight: 700; color: #1a1a2e; margin-top: 2px; }
/* 保质期三字段 */
.label-shelf { display: flex; gap: 12px; margin-top: 2px; }
.label-shelf__item { display: flex; flex-direction: column; align-items: center; padding: 2px 8px; background: #f5f5f5; border-radius: 4px; }
.label-shelf__label { font-size: 9px; color: #999; }
.label-shelf__value { font-size: 11px; font-weight: 700; color: #333; }
/* 打印按钮 */
.action-bar { display: flex; gap: 10px; justify-content: center; flex-shrink: 0; padding: 0 20px 16px; }
.print-btn {
padding: 10px 32px; border: none; border-radius: 8px; font-size: 14px; font-weight: 600;
cursor: pointer; transition: all 0.2s; font-family: inherit; color: #fff;
}
.print-btn--blue { background: #4a90d9; }
.print-btn--blue:hover { background: #3a7bc8; box-shadow: 0 4px 14px rgba(74,144,217,0.35); }
.print-btn--green { background: #52c41a; }
.print-btn--green:hover { background: #45a818; box-shadow: 0 4px 14px rgba(82,196,26,0.35); }
.print-btn--orange { background: #fa8c16; }
.print-btn--orange:hover { background: #e07b10; box-shadow: 0 4px 14px rgba(250,140,22,0.35); }
.print-btn:active { transform: scale(0.97); }
.print-all-btn { padding: 10px 24px; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; font-family: inherit; background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.6); }
.print-all-btn:hover { background: rgba(255,255,255,0.08); color: #fff; border-color: rgba(255,255,255,0.3); }
/* 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--success { background: rgba(82,196,26,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>
<!-- Tab 切换 -->
<div class="tab-bar">
<button class="tab-btn tab-btn--blue active" data-tab="order" onclick="switchTab('order')">
<span class="tab-dot tab-dot--blue"></span>订单标签
</button>
<button class="tab-btn tab-btn--green" data-tab="veg" onclick="switchTab('veg')">
<span class="tab-dot tab-dot--green"></span>净菜标签
</button>
<button class="tab-btn tab-btn--orange" data-tab="meal" onclick="switchTab('meal')">
<span class="tab-dot tab-dot--orange"></span>餐品净菜包标签
</button>
</div>
<!-- 主内容 -->
<div class="main-content" id="mainContent">
<!-- 订单标签面板 -->
<div class="label-panel active" id="panelOrder"></div>
<!-- 净菜标签面板 -->
<div class="label-panel" id="panelVeg"></div>
<!-- 餐品净菜包标签面板 -->
<div class="label-panel" id="panelMeal"></div>
</div>
<!-- 操作栏 -->
<div class="action-bar" id="actionBar">
<button class="print-all-btn" onclick="handlePrintAll()">&#x1F5A8;&#xFE0F; 全部打印</button>
<button class="print-btn print-btn--blue" id="printBtn" onclick="handlePrintCurrent()">&#x1F5A8;&#xFE0F; 打印当前标签</button>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></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') || '';
document.getElementById('topUser').textContent = userName;
document.getElementById('userAvatar').textContent = userName.charAt(0);
/* ---- Mock 数据 ---- */
var mockData = {
orderNo: 'PO-20260625-001',
orderType: 'internal',
packDate: '2026-06-25',
/* 净菜包:食材名 + 净菜类型 */
vegPacks: [
{ type: '净菜包', name: '土豆', cutSpec: '20mm×1mm×1mm · 细丝 · 平刀', packSpec: '500g', portions: 3, shelfLife: '3天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-28' },
{ type: '净菜包', name: '胡萝卜', cutSpec: '20mm×1mm×1mm · 细丝 · 平刀', packSpec: '500g', portions: 3, shelfLife: '3天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-28' }
],
/* 餐品净菜包 */
mealPacks: [
{ type: '餐品净菜包', name: '宫保鸡丁套餐', packSpec: '大份800g', portions: 2, shelfLife: '2天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-27' }
]
};
/* 计算总重量 */
function calcTotalWeight() {
var total = 0;
mockData.vegPacks.forEach(function(v) {
var w = parseInt(v.packSpec) || 0;
total += w * v.portions;
});
mockData.mealPacks.forEach(function(m) {
var specMatch = m.packSpec.match(/(\d+)g/);
var w = specMatch ? parseInt(specMatch[1]) : 0;
total += w * m.portions;
});
return total;
}
/* ---- 渲染订单标签 ---- */
function renderOrderLabels() {
var panel = document.getElementById('panelOrder');
var orderTypeLabel = mockData.orderType === 'internal' ? '内供订单' : '外售订单';
var totalWeight = calcTotalWeight();
/* 包装项明细行 */
var detailRows = '';
mockData.vegPacks.forEach(function(v, vi) {
detailRows += '<tr>'
+ '<td>' + (vi + 1) + '</td>'
+ '<td><span class="type-tag type-tag--veg">净菜包</span></td>'
+ '<td>' + v.name + '</td>'
+ '<td>' + v.cutSpec + '</td>'
+ '<td>' + v.packSpec + '</td>'
+ '<td>' + v.portions + '份</td>'
+ '</tr>';
});
mockData.mealPacks.forEach(function(m, mi) {
detailRows += '<tr>'
+ '<td>' + (mockData.vegPacks.length + mi + 1) + '</td>'
+ '<td><span class="type-tag type-tag--meal">餐品净菜包</span></td>'
+ '<td>' + m.name + '</td>'
+ '<td>—</td>'
+ '<td>' + m.packSpec + '</td>'
+ '<td>' + m.portions + '份</td>'
+ '</tr>';
});
var html = ''
+ '<div class="label-sticker">'
+ '<div class="label-sticker__body">'
+ '<span class="label-sticker__title-bar label-sticker__title-bar--blue">订单标签</span>'
+ '<div class="label-info" style="margin-top:4px">'
+ '<div class="label-info__item"><span class="label-info__label">订单号:</span><span class="label-info__value">' + mockData.orderNo + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">订单类型:</span><span class="label-info__value">' + orderTypeLabel + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">包装日期:</span><span class="label-info__value">' + mockData.packDate + '</span></div>'
+ '</div>'
+ '<table class="label-detail-table">'
+ '<thead><tr><th>#</th><th>类型</th><th>名称</th><th>净菜类型</th><th>包装规格</th><th>份数</th></tr></thead>'
+ '<tbody>' + detailRows + '</tbody>'
+ '</table>'
+ '<div class="label-total">总重量:' + totalWeight + 'g</div>'
+ '</div>'
+ '</div>';
panel.innerHTML = html;
}
/* ---- 渲染净菜标签 ---- */
function renderVegLabels() {
var panel = document.getElementById('panelVeg');
var html = '';
mockData.vegPacks.forEach(function(v) {
for (var p = 0; p < v.portions; p++) {
var code = mockData.orderNo + '-V' + v.name.charAt(0) + (p + 1);
html += ''
+ '<div class="label-sticker">'
+ '<div class="label-sticker__body">'
+ '<span class="label-sticker__title-bar label-sticker__title-bar--green">净菜标签</span>'
+ '<div class="label-info" style="margin-top:4px">'
+ '<div class="label-info__item"><span class="label-info__label">净菜名称:</span><span class="label-info__value">' + v.name + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">净菜类型:</span><span class="label-info__value">' + v.cutSpec + '</span></div>'
+ '</div>'
+ '<div class="label-info">'
+ '<div class="label-info__item"><span class="label-info__label">包装规格:</span><span class="label-info__value">' + v.packSpec + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">包装日期:</span><span class="label-info__value">' + mockData.packDate + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">订单号:</span><span class="label-info__value">' + mockData.orderNo + '</span></div>'
+ '</div>'
+ '<div class="label-shelf">'
+ '<div class="label-shelf__item"><span class="label-shelf__label">保质期</span><span class="label-shelf__value">' + v.shelfLife + '</span></div>'
+ '<div class="label-shelf__item"><span class="label-shelf__label">存放温度</span><span class="label-shelf__value">' + v.storageTemp + '</span></div>'
+ '<div class="label-shelf__item"><span class="label-shelf__label">保质期至</span><span class="label-shelf__value">' + v.expiryDate + '</span></div>'
+ '</div>'
+ '</div>'
+ '</div>';
}
});
panel.innerHTML = html;
}
/* ---- 渲染餐品净菜包标签 ---- */
function renderMealLabels() {
var panel = document.getElementById('panelMeal');
var html = '';
mockData.mealPacks.forEach(function(m) {
for (var p = 0; p < m.portions; p++) {
var code = mockData.orderNo + '-M' + (p + 1);
html += ''
+ '<div class="label-sticker">'
+ '<div class="label-sticker__body">'
+ '<span class="label-sticker__title-bar label-sticker__title-bar--orange">餐品净菜包标签</span>'
+ '<div class="label-info" style="margin-top:4px">'
+ '<div class="label-info__item"><span class="label-info__label">餐品名称:</span><span class="label-info__value">' + m.name + '</span></div>'
+ '</div>'
+ '<div class="label-info">'
+ '<div class="label-info__item"><span class="label-info__label">包装规格:</span><span class="label-info__value">' + m.packSpec + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">包装日期:</span><span class="label-info__value">' + mockData.packDate + '</span></div>'
+ '<div class="label-info__item"><span class="label-info__label">订单号:</span><span class="label-info__value">' + mockData.orderNo + '</span></div>'
+ '</div>'
+ '<div class="label-shelf">'
+ '<div class="label-shelf__item"><span class="label-shelf__label">保质期</span><span class="label-shelf__value">' + m.shelfLife + '</span></div>'
+ '<div class="label-shelf__item"><span class="label-shelf__label">存放温度</span><span class="label-shelf__value">' + m.storageTemp + '</span></div>'
+ '<div class="label-shelf__item"><span class="label-shelf__label">保质期至</span><span class="label-shelf__value">' + m.expiryDate + '</span></div>'
+ '</div>'
+ '</div>'
+ '</div>';
}
});
panel.innerHTML = html;
}
/* ---- Tab 切换 ---- */
var currentTab = 'order';
function switchTab(tab) {
currentTab = tab;
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
document.querySelector('.tab-btn[data-tab="' + tab + '"]').classList.add('active');
document.querySelectorAll('.label-panel').forEach(function(p) { p.classList.remove('active'); });
if (tab === 'order') {
document.getElementById('panelOrder').classList.add('active');
} else if (tab === 'veg') {
document.getElementById('panelVeg').classList.add('active');
} else if (tab === 'meal') {
document.getElementById('panelMeal').classList.add('active');
}
/* 更新打印按钮颜色 */
var btn = document.getElementById('printBtn');
btn.className = 'print-btn';
if (tab === 'order') btn.classList.add('print-btn--blue');
else if (tab === 'veg') btn.classList.add('print-btn--green');
else if (tab === 'meal') btn.classList.add('print-btn--orange');
}
/* ---- 打印当前标签 ---- */
function handlePrintCurrent() {
var labels = [];
if (currentTab === 'order') labels.push('订单标签');
else if (currentTab === 'veg') {
mockData.vegPacks.forEach(function(v) {
for (var p = 0; p < v.portions; p++) { labels.push('净菜标签 — ' + v.name); }
});
} else if (currentTab === 'meal') {
mockData.mealPacks.forEach(function(m) {
for (var p = 0; p < m.portions; p++) { labels.push('餐品净菜包标签 — ' + m.name); }
});
}
showToast('已发送打印指令:' + labels.length + ' 张标签', 2000, 'success');
}
/* ---- 全部打印 ---- */
function handlePrintAll() {
var total = 1; /* 订单标签 1 张 */
mockData.vegPacks.forEach(function(v) { total += v.portions; });
mockData.mealPacks.forEach(function(m) { total += m.portions; });
showToast('已发送全部打印指令:共 ' + total + ' 张标签', 2500, '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) el.classList.add('toast--' + type);
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 = '02-labeling.html?user=' + encodeURIComponent(userName) + (userId ? '&uid=' + userId : '');
}
}
})();
/* ---- 初始化 ---- */
renderOrderLabels();
renderVegLabels();
renderMealLabels();
</script>
</body>
</html>
@@ -368,33 +368,33 @@
<script>
// ---- 模拟数据(累计模式) ----
var mockFoods = [
{ name: '土豆', size: '35mm*1mm*1mm', shape: '长条', blade: '平刀', target: 5.0, processed: 3.2, arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '胡萝卜', size: '20mm*1mm*1mm', shape: '细丝', blade: '平刀', target: 2.0, processed: 1.5, arrivalTime: '2026-06-17 09:15', icon: '\u{1F955}' },
{ name: '黄瓜', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', target: 4.0, processed: 2.8, arrivalTime: '2026-06-17 10:00', icon: '\u{1F952}' }
{ name: '土豆', size: '35mm*1mm*1mm', shape: '长条', blade: '平刀', target: 5.0, processed: 3.2, arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '胡萝卜', size: '20mm*1mm*1mm', shape: '细丝', blade: '平刀', target: 2.0, processed: 1.5, arrivalTime: '2026-06-17 09:15', icon: '\u{1F955}' },
{ name: '黄瓜', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', target: 4.0, processed: 2.8, arrivalTime: '2026-06-17 10:00', icon: '\u{1F952}' }
];
// 净菜食材数据库(供搜索用)
var foodDatabase = [
{ name: '土豆', size: '35mm*1mm*1mm', shape: '长条', blade: '平刀', arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '土豆', size: '20mm*20mm*20mm', shape: '方丁', blade: '平刀', arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '土豆', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '胡萝卜', size: '20mm*1mm*1mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 09:15', icon: '\u{1F955}' },
{ name: '胡萝卜', size: '25mm*25mm*2mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 09:15', icon: '\u{1F955}' },
{ name: '黄瓜', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 10:00', icon: '\u{1F952}' },
{ name: '黄瓜', size: '40mm*8mm*8mm', shape: '长条', blade: '平刀', arrivalTime: '2026-06-17 10:00', icon: '\u{1F952}' },
{ name: '大白菜', size: '50mm*30mm*30mm', shape: '宽段', blade: '平刀', arrivalTime: '2026-06-17 10:30', icon: '\u{1F96C}' },
{ name: '大白菜', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 10:30', icon: '\u{1F96C}' },
{ name: '西红柿', size: '25mm*25mm*20mm', shape: '楔形块', blade: '平刀', arrivalTime: '2026-06-17 11:00', icon: '\u{1F345}' },
{ name: '西红柿', size: '30mm*30mm*5mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 11:00', icon: '\u{1F345}' },
{ name: '青椒', size: '30mm*3mm*3mm', shape: '长条', blade: '平刀', arrivalTime: '2026-06-17 11:30', icon: '\u{1FAD1}' },
{ name: '青椒', size: '20mm*20mm*20mm', shape: '方丁', blade: '平刀', arrivalTime: '2026-06-17 11:30', icon: '\u{1FAD1}' },
{ name: '洋葱', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 13:00', icon: '\u{1F9C5}' },
{ name: '洋葱', size: '15mm*15mm*15mm', shape: '方丁', blade: '平刀', arrivalTime: '2026-06-17 13:00', icon: '\u{1F9C5}' },
{ name: '芹菜', size: '40mm*10mm*10mm', shape: '宽段', blade: '平刀', arrivalTime: '2026-06-17 13:30', icon: '\u{1F96C}' },
{ name: '芹菜', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 13:30', icon: '\u{1F96C}' },
{ name: '莲藕', size: '35mm*35mm*3mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 14:00', icon: '\u{1FAD1}' },
{ name: '莴笋', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 14:00', icon: '\u{1F96C}' },
{ name: '豆角', size: '40mm*10mm*10mm', shape: '宽段', blade: '平刀', arrivalTime: '2026-06-17 14:30', icon: '\u{1FAD8}' }
{ name: '土豆', size: '35mm*1mm*1mm', shape: '长条', blade: '平刀', arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '土豆', size: '20mm*20mm*20mm', shape: '方丁', blade: '平刀', arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '土豆', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 08:30', icon: '\u{1F954}' },
{ name: '胡萝卜', size: '20mm*1mm*1mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 09:15', icon: '\u{1F955}' },
{ name: '胡萝卜', size: '25mm*25mm*2mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 09:15', icon: '\u{1F955}' },
{ name: '黄瓜', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 10:00', icon: '\u{1F952}' },
{ name: '黄瓜', size: '40mm*8mm*8mm', shape: '长条', blade: '平刀', arrivalTime: '2026-06-17 10:00', icon: '\u{1F952}' },
{ name: '大白菜', size: '50mm*30mm*30mm', shape: '宽段', blade: '平刀', arrivalTime: '2026-06-17 10:30', icon: '\u{1F96C}' },
{ name: '大白菜', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 10:30', icon: '\u{1F96C}' },
{ name: '西红柿', size: '25mm*25mm*20mm', shape: '楔形块', blade: '平刀', arrivalTime: '2026-06-17 11:00', icon: '\u{1F345}' },
{ name: '西红柿', size: '30mm*30mm*5mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 11:00', icon: '\u{1F345}' },
{ name: '青椒', size: '30mm*3mm*3mm', shape: '长条', blade: '平刀', arrivalTime: '2026-06-17 11:30', icon: '\u{1FAD1}' },
{ name: '青椒', size: '20mm*20mm*20mm', shape: '方丁', blade: '平刀', arrivalTime: '2026-06-17 11:30', icon: '\u{1FAD1}' },
{ name: '洋葱', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 13:00', icon: '\u{1F9C5}' },
{ name: '洋葱', size: '15mm*15mm*15mm', shape: '方丁', blade: '平刀', arrivalTime: '2026-06-17 13:00', icon: '\u{1F9C5}' },
{ name: '芹菜', size: '40mm*10mm*10mm', shape: '宽段', blade: '平刀', arrivalTime: '2026-06-17 13:30', icon: '\u{1F96C}' },
{ name: '芹菜', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 13:30', icon: '\u{1F96C}' },
{ name: '莲藕', size: '35mm*35mm*3mm', shape: '圆片', blade: '平刀', arrivalTime: '2026-06-17 14:00', icon: '\u{1FAD1}' },
{ name: '莴笋', size: '30mm*2mm*2mm', shape: '细丝', blade: '平刀', arrivalTime: '2026-06-17 14:00', icon: '\u{1F96C}' },
{ name: '豆角', size: '40mm*10mm*10mm', shape: '宽段', blade: '平刀', arrivalTime: '2026-06-17 14:30', icon: '\u{1FAD8}' }
];
var currentUser = null;
+2 -2
View File
@@ -196,8 +196,8 @@
<a class="card" href="clean-veg-packaging-labeler/index.html">
<span class="card__status card__status--done">已设计</span>
<div class="card__icon">&#x1F3F7;&#xFE0F;</div>
<div class="card__name">净菜贴标</div>
<div class="card__desc">接收配比完成订单,核对实物、贴标塑封</div>
<div class="card__name">净菜包装</div>
<div class="card__desc">接收配比完成订单,核对实物、包装塑封</div>
</a>
</div>
@@ -61,14 +61,14 @@
/* 表格头(7列) */
.item-table__head {
display: grid; grid-template-columns: 1fr 0.6fr 0.5fr 0.65fr 0.6fr 50px 72px; gap: 8px;
display: grid; grid-template-columns: 1fr 0.55fr 0.35fr 0.55fr 0.55fr 46px 66px; gap: 6px;
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.6fr 0.5fr 0.65fr 0.6fr 50px 72px; gap: 8px;
display: grid; grid-template-columns: 1fr 0.55fr 0.35fr 0.55fr 0.55fr 46px 66px; gap: 6px;
padding: 10px 16px; border-top: 1px solid rgba(255,255,255,0.04); align-items: center;
background: rgba(74,144,217,0.06);
margin-bottom: 4px; border-radius: 6px;
@@ -94,7 +94,7 @@
/* 食材子行 */
.item-child-row {
display: grid; grid-template-columns: 1fr 0.6fr 0.5fr 0.65fr 0.6fr 50px 72px; gap: 8px;
display: grid; grid-template-columns: 1fr 0.55fr 0.45fr 0.35fr 0.55fr 0.55fr 46px 66px; gap: 6px;
padding: 8px 16px 8px 48px; border-top: 1px solid rgba(255,255,255,0.03);
align-items: center; transition: background 0.2s;
}
@@ -184,9 +184,9 @@
taskNo: 'T-001',
mealPacks: [
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
{ name: '土豆', icon: '&#x1F954;', weightPerPack: 300, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', weightPerPack: 250, weighed: 0 },
{ name: '黄瓜', icon: '&#x1F952;', weightPerPack: 250, weighed: 0 }
{ name: '土豆', icon: '&#x1F954;', weightPerPack: 300, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', weightPerPack: 250, weighed: 0 },
{ name: '黄瓜', icon: '&#x1F952;', weightPerPack: 250, weighed: 0 }
], seasonings: [
{ name: '盐', cellIdx: 2, weightPerPack: 5, weighed: 0 },
{ name: '白糖', cellIdx: 3, weightPerPack: 10, weighed: 0 },
@@ -201,15 +201,15 @@
taskNo: 'T-002',
mealPacks: [
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
{ name: '白菜', icon: '&#x1F96C;', weightPerPack: 400, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', weightPerPack: 400, weighed: 0 }
{ name: '白菜', icon: '&#x1F96C;', weightPerPack: 400, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', weightPerPack: 400, weighed: 0 }
], seasonings: [
{ name: '醋', cellIdx: 2, weightPerPack: 8, weighed: 0 },
{ name: '料酒', cellIdx: 4, weightPerPack: 12, weighed: 0 }
]},
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准份500g', ingredients: [
{ name: '番茄', icon: '&#x1F345;', weightPerPack: 300, weighed: 0 },
{ name: '鸡蛋', icon: '&#x1F95A;', weightPerPack: 200, weighed: 0 }
{ name: '番茄', icon: '&#x1F345;', weightPerPack: 300, weighed: 0 },
{ name: '鸡蛋', icon: '&#x1F95A;', weightPerPack: 200, weighed: 0 }
], seasonings: [
{ name: '盐', cellIdx: 2, weightPerPack: 4, weighed: 0 },
{ name: '白糖', cellIdx: 3, weightPerPack: 6, weighed: 0 }
@@ -298,10 +298,10 @@
'<span class="task-card__status"><span class="status-badge ' + (orderAllDone ? 'status-badge--done' : 'status-badge--pending') + '">' + (orderAllDone ? '已完成' : '待组配') + '</span></span>';
taskCard.appendChild(header);
/* 表格头(7列:名称/包装规格/打包份数/所需总重/已称/状态/操作) */
/* 表格头(7列:名称/包装规格/份数/应称重量/已称重量/状态/操作) */
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:right">所需总重</div><div style="text-align:right">已称</div><div style="text-align:center">状态</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><div style="text-align:center">操作</div>';
taskCard.appendChild(tableHead);
/* 表格体 */
@@ -326,21 +326,30 @@
}
var totalNeed = Math.round((ingNeed + seaNeed) * 100) / 100;
var totalWeighed = Math.round((ingWeighed + seaWeighed) * 100) / 100;
var weightClass = totalWeighed >= totalNeed ? 'parent-weight--done' : '';
var perPackWeight = Math.round((totalNeed / pack.packSize) * 100) / 100;
var perPackWeighed = Math.round((totalWeighed / pack.packSize) * 100) / 100;
var packDone = !hasPendingPack(pack);
/* 从包装规格中提取重量(如"大份800g"→800 */
var specMatch = pack.packSpec && pack.packSpec.match(/(\d+)g/);
var specWeight = specMatch ? parseInt(specMatch[1]) : perPackWeight;
/* 主行:餐品净菜包 + 独立组配按钮 */
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>' +
/* 按单份拆分,每份一行 */
for (var p = 0; p < pack.packSize; p++) {
var portionWeighed = packDone ? perPackWeighed : 0;
var weightClass = packDone ? '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 style="font-size:10px;color:rgba(255,255,255,0.4)">第' + (p + 1) + '份/' + pack.packSize + '份</span></span></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>' +
'<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">' + (packDone ? '' : '<button class="task-card__pack-btn" onclick="handlePackWeigh(\'' + order.taskNo + '\',\'' + order.orderNo + '\',\'' + encodeURIComponent(pack.packName) + '\')">组配</button>') + '</div>';
tableBody.appendChild(parentRow);
'<div style="text-align:center"><span class="pack-size-tag">1</span></div>' +
'<div class="parent-weight ' + weightClass + '">' + specWeight + ' 克</div>' +
'<div class="parent-weight ' + weightClass + '">' + portionWeighed + ' 克</div>' +
'<div style="text-align:center"><span class="status-tag ' + (packDone ? 'status-tag--done' : 'status-tag--wait') + '">' + (packDone ? '已完成' : '待组配') + '</span></div>' +
'<div style="text-align:center">' + (packDone ? '' : '<button class="task-card__pack-btn" onclick="handlePackWeigh(\'' + order.taskNo + '\',\'' + order.orderNo + '\',\'' + encodeURIComponent(pack.packName) + '\',' + (p + 1) + ')">组配</button>') + '</div>';
tableBody.appendChild(parentRow);
}
});
@@ -371,8 +380,9 @@
}
/* 进入餐品净菜包组配详情 */
function handlePackWeigh(taskNo, orderNo, packName) {
function handlePackWeigh(taskNo, orderNo, packName, portion) {
var target = '02-weighing-packing.html?task=' + taskNo + '&order=' + encodeURIComponent(orderNo) + '&pack=' + packName;
if (portion) target += '&portion=' + portion;
if (userName) target += '&user=' + encodeURIComponent(userName);
window.location.href = target;
}
@@ -262,12 +262,12 @@
.status-tag--done { background: rgba(82,196,26,0.15); color: #52c41a; }
.status-tag--wait { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.35); }
/* 下一步按钮 */
.next-btn-wrap { flex-shrink: 0; }
/* 底部按钮 */
.next-btn-wrap { flex-shrink: 0; display: flex; gap: 10px; }
.next-btn {
width: 100%; padding: 12px 0; border: none; border-radius: 10px;
flex: 1; padding: 12px 0; border: none; border-radius: 10px;
font-size: 14px; font-weight: 600; cursor: pointer;
transition: all 0.25s; text-align: center;
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); }
@@ -276,47 +276,14 @@
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;
.next-btn--secondary {
background: rgba(82,196,26,0.12); color: #52c41a; border: 1px solid rgba(82,196,26,0.4);
}
.modal-overlay.show { display: flex; }
.modal {
background: #1a2b4a; border-radius: 14px; border: 1px solid rgba(255,255,255,0.08);
padding: 24px 28px; width: 360px; display: flex; flex-direction: column; gap: 16px;
.next-btn--secondary:hover { background: rgba(82,196,26,0.22); box-shadow: 0 4px 14px rgba(82,196,26,0.25); }
.next-btn--secondary:disabled {
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.2);
border-color: transparent; cursor: not-allowed; box-shadow: none;
}
.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 {
@@ -544,9 +511,10 @@
</div>
</div>
<!-- 下一步按钮 -->
<!-- 底部按钮 -->
<div class="next-btn-wrap">
<button class="next-btn" id="btnNext" onclick="handlePackDone()">组配完成</button>
<button class="next-btn next-btn--secondary" id="btnExit" onclick="handlePackDoneExit()">完成并退出</button>
<button class="next-btn" id="btnContinue" onclick="handlePackDoneContinue()">完成并继续</button>
</div>
</div>
</div>
@@ -555,27 +523,6 @@
<!-- 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="confirmPack()">确认</button>
</div>
</div>
</div>
<script>
/* ---- 工具函数 ---- */
function getUrlParam(name) {
@@ -601,9 +548,9 @@
orderType: 'internal',
mealPacks: [
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
{ name: '土豆', icon: '&#x1F954;', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 300, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 250, weighed: 0 },
{ name: '黄瓜', icon: '&#x1F952;', cutSpec: '30mm*30mm*3mm · 圆片 · 平刀', weightPerPack: 250, weighed: 0 }
{ name: '土豆', icon: '&#x1F954;', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 300, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 250, weighed: 0 },
{ name: '黄瓜', icon: '&#x1F952;', cutSpec: '30mm*30mm*3mm · 圆片 · 平刀', weightPerPack: 250, weighed: 0 }
], seasonings: [
{ name: '油', icon: '&#x1F6E2;', cellIdx: 0, weightPerPack: 50, weighed: 0 },
{ name: '盐', icon: '&#x1F9C2;', cellIdx: 1, weightPerPack: 5, weighed: 0 },
@@ -620,8 +567,8 @@
orderType: 'external',
mealPacks: [
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
{ name: '白菜', icon: '&#x1F96C;', cutSpec: '50mm*30mm*30mm · 宽段 · 平刀', weightPerPack: 400, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 400, weighed: 0 }
{ name: '白菜', icon: '&#x1F96C;', cutSpec: '50mm*30mm*30mm · 宽段 · 平刀', weightPerPack: 400, weighed: 0 },
{ name: '胡萝卜', icon: '&#x1F955;', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 400, weighed: 0 }
], seasonings: [
{ name: '油', icon: '&#x1F6E2;', cellIdx: 0, weightPerPack: 40, weighed: 0 },
{ name: '盐', icon: '&#x1F9C2;', cellIdx: 1, weightPerPack: 4, weighed: 0 },
@@ -696,14 +643,14 @@
var seasoningConfig = loadSeasoningConfig();
var aiResults = {
'potato': { name: '土豆', icon: '&#x1F954;', confidence: 97 },
'carrot': { name: '胡萝卜', icon: '&#x1F955;', confidence: 96 },
'cucumber': { name: '黄瓜', icon: '&#x1F952;', confidence: 94 },
'cabbage': { name: '白菜', icon: '&#x1F96C;', confidence: 93 },
'tomato': { name: '西红柿', icon: '&#x1F345;', confidence: 95 },
'potato': { name: '土豆', icon: '&#x1F954;', confidence: 97 },
'carrot': { name: '胡萝卜', icon: '&#x1F955;', confidence: 96 },
'cucumber': { name: '黄瓜', icon: '&#x1F952;', confidence: 94 },
'cabbage': { name: '白菜', icon: '&#x1F96C;', confidence: 93 },
'tomato': { name: '西红柿', icon: '&#x1F345;', confidence: 95 },
'egg': { name: '鸡蛋', icon: '&#x1F95A;', confidence: 92 },
'onion': { name: '紫洋葱', icon: '&#x1F9C5;', confidence: 91 },
'green-onion': { name: '大葱', icon: '&#x1F9C5;', confidence: 90 }
'onion': { name: '紫洋葱', icon: '&#x1F9C5;', confidence: 91 },
'green-onion': { name: '大葱', icon: '&#x1F9C5;', confidence: 90 }
};
/* ---- 状态变量 ---- */
@@ -737,7 +684,7 @@
cutSpec: ing.cutSpec,
weightPerPack: ing.weightPerPack,
weighed: ing.weighed,
packSize: currentPack.packSize
packSize: 1
};
});
@@ -748,7 +695,7 @@
cellIdx: sea.cellIdx,
weightPerPack: sea.weightPerPack,
weighed: sea.weighed,
packSize: currentPack.packSize
packSize: 1
};
});
@@ -772,7 +719,7 @@
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('taskPackName').textContent = currentPack.packName + ' · ' + (currentPack.packSpec || '—');
document.getElementById('taskTotalPacks').textContent = currentPack.packSize;
document.getElementById('taskTotalPacks').textContent = '1';
/* 订单状态:主辅材+调料全部完成才算完成 */
var ingAllDone = allIngredients.every(function(ing) {
@@ -827,7 +774,6 @@
'</div>' +
'<div class="item-card__data">' +
'<div class="item-card__data-item"><span class="item-card__data-label">每份</span><span class="item-card__data-value">' + ing.weightPerPack + 'g</span></div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">份数</span><span class="item-card__data-value">' + ing.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 ' + actualClass + '">' + ing.weighed + 'g</span></div>' +
'<div class="item-card__data-item"><span class="item-card__data-label">净菜类型</span><span class="item-card__data-value">' + (ing.cutSpec || '—') + '</span></div>' +
@@ -863,7 +809,6 @@
'<div class="sea-name"><span class="sea-name-icon">' + (oilSea.icon || '&#x1F6E2;') + '</span>' + oilSea.name + '</div>' +
'<div class="sea-fields">' +
'<div class="sea-field"><span class="sea-field__label">每份用量</span><span class="sea-field__value">' + oilSea.weightPerPack + 'g</span></div>' +
'<div class="sea-field"><span class="sea-field__label">份数</span><span class="sea-field__value">' + oilSea.packSize + '</span></div>' +
'<div class="sea-field"><span class="sea-field__label">应称</span><span class="sea-field__value">' + oilNeed + 'g</span></div>' +
'<div class="sea-field"><span class="sea-field__label">已称</span><span class="sea-field__value ' + oilActualCls + '">' + oilSea.weighed + 'g</span></div>' +
'</div>';
@@ -874,7 +819,7 @@
if (oilSea) {
var oilIdx = allSeasonings.indexOf(oilSea);
infoCell.style.cursor = 'pointer';
infoCell.onclick = function() { openSeasoningModal(oilIdx); };
infoCell.onclick = function() { confirmSeasoningDirect(oilIdx); };
}
grid.appendChild(infoCell);
@@ -915,12 +860,11 @@
'<div class="sea-slot__name"><span class="sea-slot__name-icon">' + (neededSea.icon || '&#x1F9C2;') + '</span>' + neededSea.name + '</div>' +
'<div class="sea-slot__fields">' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">每份</span><span class="sea-slot__field-value">' + neededSea.weightPerPack + 'g</span></div>' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">份数</span><span class="sea-slot__field-value">' + neededSea.packSize + '</span></div>' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">应称</span><span class="sea-slot__field-value">' + seaNeed2 + 'g</span></div>' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">已称</span><span class="sea-slot__field-value ' + actualCls2 + '">' + neededSea.weighed + 'g</span></div>' +
'</div>';
cell.onclick = (function(idx) { return function() { openSeasoningModal(idx); }; })(findSeasoningIdxByPos(pos));
cell.onclick = (function(idx) { return function() { confirmSeasoningDirect(idx); }; })(findSeasoningIdxByPos(pos));
} else if (cfg && cfg.name) {
/* 状态3:该格放置了调料但本餐品不需要组配 — 字段一致,数据用--占位 */
cell.classList.add('seasoning-cell--occupied');
@@ -930,7 +874,6 @@
'<div class="sea-slot__name"><span class="sea-slot__name-icon">' + cfgIcon + '</span>' + cfg.name + '</div>' +
'<div class="sea-slot__fields">' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">每份</span><span class="sea-slot__field-value">--</span></div>' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">份数</span><span class="sea-slot__field-value">--</span></div>' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">应称</span><span class="sea-slot__field-value">--</span></div>' +
'<div class="sea-slot__field"><span class="sea-slot__field-label">已称</span><span class="sea-slot__field-value">--</span></div>' +
'</div>';
@@ -949,8 +892,8 @@
return -1;
}
/* ---- 调料组配确认弹窗(不走 AI 识别,直接弹窗读秤 ---- */
function openSeasoningModal(idx) {
/* ---- 调料组配:直接读秤确认(无弹窗 ---- */
function confirmSeasoningDirect(idx) {
if (idx < 0 || idx >= allSeasonings.length) return;
var sea = allSeasonings[idx];
var seaNeed = Math.round(sea.weightPerPack * sea.packSize * 100) / 100;
@@ -961,12 +904,22 @@
/* 设为当前调料 */
currentSeaIdx = idx;
renderSeasoningGrid();
/* 填入弹窗 */
document.getElementById('modalIcon').innerHTML = sea.icon || '&#x1F9C2;';
document.getElementById('modalFoodName').textContent = sea.name;
document.getElementById('modalWeight').textContent = parseFloat(document.getElementById('scaleWeightValue').textContent).toFixed(1);
document.getElementById('modalOverlay').classList.add('show');
pendingCandidate = { name: sea.name, icon: sea.icon || '&#x1F9C2;', confidence: 100, isSeasoning: true, idx: idx };
/* 直接读取当前秤重赋值 */
var scaleWeight = parseFloat(document.getElementById('scaleWeightValue').textContent) || 0;
sea.weighed = scaleWeight;
showToast('调料称重完成: ' + sea.name + ' ' + scaleWeight.toFixed(2) + '克', 2000, 'success');
renderSeasoningGrid();
updateOverallStatus();
/* 自动跳到下一个待称调料 */
currentSeaIdx = -1;
for (var s = 0; s < allSeasonings.length; s++) {
if (allSeasonings[s].weighed < allSeasonings[s].weightPerPack * allSeasonings[s].packSize) {
currentSeaIdx = s; break;
}
}
renderSeasoningGrid();
document.getElementById('scaleWeightValue').textContent = '0.00';
if (currentSeaIdx < 0) showToast('所有调料已称重完成', 2000, 'success');
}
/* ---- 触发识别(点击相机覆盖层) ---- */
@@ -1017,7 +970,7 @@
var card = document.createElement('div');
card.className = 'candidate-card';
card.onclick = function() {
openRatioModal(r);
confirmCandidateDirect(r);
};
card.innerHTML =
'<div class="candidate-card__img">' + r.icon + '</div>' +
@@ -1038,62 +991,16 @@
event.target.value = '';
}
/* ---- 组配确认弹窗 ---- */
var pendingCandidate = null; // 待确认的识别结果
function openRatioModal(result) {
pendingCandidate = result;
/* 填入弹窗:食材图标、名称、当前秤重 */
document.getElementById('modalIcon').innerHTML = 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() {
document.getElementById('modalWeight').textContent = parseFloat(document.getElementById('scaleWeightValue').textContent).toFixed(1);
}
function closeModal() {
pendingCandidate = null;
document.getElementById('modalOverlay').classList.remove('show');
}
function confirmPack() {
if (!pendingCandidate) return;
document.getElementById('modalOverlay').classList.remove('show');
var modalWeight = parseFloat(document.getElementById('modalWeight').textContent) || 0;
/* 调料组配:不走 AI 识别 */
if (pendingCandidate.isSeasoning) {
var sea = allSeasonings[pendingCandidate.idx];
sea.weighed = modalWeight;
showToast('调料称重完成: ' + sea.name + ' ' + modalWeight.toFixed(2) + '克', 2000, 'success');
pendingCandidate = null;
renderSeasoningGrid();
updateOverallStatus();
/* 自动跳到下一个待称调料 */
currentSeaIdx = -1;
for (var s = 0; s < allSeasonings.length; s++) {
if (allSeasonings[s].weighed < allSeasonings[s].weightPerPack * allSeasonings[s].packSize) {
currentSeaIdx = s; break;
}
}
renderSeasoningGrid();
document.getElementById('scaleWeightValue').textContent = '0.00';
if (currentSeaIdx < 0) showToast('所有调料已称重完成', 2000, 'success');
return;
}
/* 主辅材组配:AI 识别后确认 */
/* ---- 主辅材组配:直接读秤确认(无弹窗 ---- */
function confirmCandidateDirect(result) {
/* 直接读取当前秤重赋值 */
var scaleWeight = parseFloat(document.getElementById('scaleWeightValue').textContent) || 0;
var ing = allIngredients[currentIngIdx];
ing.weighed = modalWeight;
ing.weighed = scaleWeight;
document.getElementById('scaleWeightValue').textContent = ing.weighed.toFixed(2);
document.getElementById('statusDot').className = 'status-dot status-dot--done';
document.getElementById('statusText').textContent = '组配确认: ' + pendingCandidate.name + ' — 置信度 ' + pendingCandidate.confidence + '%';
showToast('称重完成: ' + pendingCandidate.name + ' ' + modalWeight.toFixed(2) + '克', 2000, 'success');
pendingCandidate = null;
document.getElementById('statusText').textContent = '组配确认: ' + result.name + ' — 置信度 ' + result.confidence + '%';
showToast('称重完成: ' + result.name + ' ' + scaleWeight.toFixed(2) + '克', 2000, 'success');
renderItemlist();
updateOverallStatus();
@@ -1142,27 +1049,74 @@
taskStatusEl.className = 'task-card__item-value' + (allDoneNow ? ' task-card__item-value--green' : ' task-card__item-value--yellow');
}
/* ---- 组配完成 ---- */
function handlePackDone() {
/* ---- 校验组配是否全部完成 ---- */
function checkPackAllDone() {
var ingDone = allIngredients.every(function(ing) {
return ing.weighed >= ing.weightPerPack * ing.packSize;
});
var seaDone = allSeasonings.every(function(sea) {
return sea.weighed >= sea.weightPerPack * sea.packSize;
});
if (!ingDone || !seaDone) {
return ingDone && seaDone;
}
/* ---- 完成并退出:返回组配任务列表 ---- */
function handlePackDoneExit() {
if (!checkPackAllDone()) {
var msg = [];
var ingDone = allIngredients.every(function(ing) { return ing.weighed >= ing.weightPerPack * ing.packSize; });
var seaDone = allSeasonings.every(function(sea) { return sea.weighed >= sea.weightPerPack * sea.packSize; });
if (!ingDone) msg.push('主辅材');
if (!seaDone) msg.push('调料');
showToast(msg.join('和') + '未完成称重', 2500, 'error');
return;
}
showToast('组配完成', 1500, 'success');
showToast('组配完成,正在返回任务列表...', 1500, 'success');
setTimeout(function() {
window.location.href = '01-packing-tasks.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
}, 1000);
}
/* ---- 完成并继续:跳转同订单下一个待组配餐品包 ---- */
function handlePackDoneContinue() {
if (!checkPackAllDone()) {
var msg = [];
var ingDone = allIngredients.every(function(ing) { return ing.weighed >= ing.weightPerPack * ing.packSize; });
var seaDone = allSeasonings.every(function(sea) { return sea.weighed >= sea.weightPerPack * sea.packSize; });
if (!ingDone) msg.push('主辅材');
if (!seaDone) msg.push('调料');
showToast(msg.join('和') + '未完成称重', 2500, 'error');
return;
}
/* 在同订单中查找下一个待组配餐品包 */
var task = taskData[taskNo];
var nextPack = null;
if (task) {
for (var i = 0; i < task.mealPacks.length; i++) {
var pk = task.mealPacks[i];
if (pk.packName === packName) continue;
/* 判断该包是否有待组配项 */
var ingPending = pk.ingredients.some(function(ing) { return ing.weighed < ing.weightPerPack * pk.packSize; });
var seaPending = pk.seasonings && pk.seasonings.some(function(sea) { return sea.weighed < sea.weightPerPack * pk.packSize; });
if (ingPending || seaPending) { nextPack = pk; break; }
}
}
if (nextPack) {
showToast('组配完成,继续下一餐品包: ' + nextPack.packName, 1500, 'success');
setTimeout(function() {
var target = '02-weighing-packing.html?task=' + taskNo + '&order=' + encodeURIComponent(task ? task.orderNo : '') + '&pack=' + encodeURIComponent(nextPack.packName);
if (userName) target += '&user=' + encodeURIComponent(userName);
window.location.href = target;
}, 1000);
} else {
/* 同订单无更多待组配包,返回任务列表 */
showToast('本订单所有餐品包已组配完成,正在返回任务列表...', 1500, 'success');
setTimeout(function() {
window.location.href = '01-packing-tasks.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
}, 1000);
}
}
/* ---- 清空重量 ---- */
function clearWeight(idx) {
var ing = allIngredients[idx];
@@ -1177,9 +1131,7 @@
/* ---- 退出登录 ---- */
function handleLogout() {
if (confirm('确定退出登录?')) {
window.location.href = 'index.html';
}
window.location.href = 'index.html';
}
/* ---- 重新识别 ---- */
@@ -389,9 +389,9 @@
orderNo: 'PO-20260616-001', orderType: 'internal', completeTime: '2026-06-16 10:20', taskNo: 'T-001',
mealPacks: [
{ 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 }
{ 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 },
@@ -407,8 +407,8 @@
orderNo: 'PO-20260616-002', orderType: 'external', completeTime: '2026-06-16 11:00', taskNo: 'T-002',
mealPacks: [
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
{ name: '白菜', icon: '&#x1F96C;', weightPerPack: 400, weighed: 800 },
{ name: '胡萝卜', icon: '&#x1F955;', weightPerPack: 400, weighed: 800 }
{ 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 },
@@ -419,8 +419,8 @@
{ name: '豆瓣酱', cellIdx: 6, weightPerPack: 15, weighed: 30 }
]},
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准份500g', ingredients: [
{ name: '番茄', icon: '&#x1F345;', weightPerPack: 300, weighed: 600 },
{ name: '鸡蛋', icon: '&#x1F95A;', weightPerPack: 200, weighed: 400 }
{ 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 },
@@ -1,5 +1,15 @@
# 净菜组配秤 CHANGELOG
## [2.2.2] - 2026-06-26
### 组配任务页按单份拆分 + 组配作业页移除弹窗
- `01-packing-tasks.html` — 行展示按单份拆分("第N份/M份"),组配按钮携带份数参数;grid 列宽调整(`1fr 0.55fr 0.35fr 0.55fr 0.55fr 46px 66px`
- `01-packing-tasks.html` — 食材名称简化:去丝/段/片/块/液后缀(土豆丝→土豆、黄瓜片→黄瓜、番茄块→番茄、鸡蛋液→鸡蛋等)
- `02-weighing-packing.html` — 移除组配确认弹窗(弹窗 CSS + HTML + JS 全部删除);底部按钮区改为 flex 布局
- `02-weighing-packing.html` — 新增 `next-btn--secondary` 样式(绿色边框按钮)
- `03-verify-history.html` — 食材名称同步简化
- `doc/PRD.md` — 食材名称同步简化
## [2.2.1] - 2026-06-26
### 组配记录页字段与显示逻辑对齐
@@ -153,12 +153,12 @@ flowchart TD
```
订单 #PO-20260616-001(期望到货 11:30
└── 任务 #T-001
├── 🥔 土豆(净菜包)需 2.5 斤 已包 1.0 斤 [进行中]
├── 🥕 胡萝卜(净菜包)需 1.8 斤 已包 1.8 斤 [已完成]
├── 🥔 土豆(净菜包)需 2.5 斤 已包 1.0 斤 [进行中]
├── 🥕 胡萝卜(净菜包)需 1.8 斤 已包 1.8 斤 [已完成]
└── 📦 宫保鸡丁套餐(餐品净菜包)需 3 份 已包 0 份
├── 土豆 1.5 斤
├── 胡萝卜 0.8 斤
└── 黄瓜 0.5 斤
├── 土豆 1.5 斤
├── 胡萝卜 0.8 斤
└── 黄瓜 0.5 斤
```
**交互说明**