feat: 称重页支持多餐品包同时打包,任务列表按钮改为任务级

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhangyan
2026-06-16 16:42:13 +08:00
co-authored by Claude Opus 4.6
parent dc45b51430
commit dd0477f110
3 changed files with 93 additions and 57 deletions
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, initial-scale=1.0">
<title>净菜配比秤 - 称重任务 | 蚁熊智能餐养平台</title>
<title>净菜配比秤 - 打包任务 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -132,14 +132,14 @@
<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>
<span class="top-bar__user" id="topUser">--</span>
</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 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>
@@ -231,17 +231,17 @@
return ing.weighed >= ing.weightPerPack * pack.packSize;
});
if (allDone) return { text: '已完成', cls: 'status-badge--done' };
return { text: '待称重', cls: 'status-badge--pending' };
return { text: '待打包', cls: 'status-badge--pending' };
}
/* 判断餐品净菜包是否有待称重项 */
/* 判断餐品净菜包是否有待打包项 */
function hasPendingPack(pack) {
return pack.ingredients.some(function(ing) {
return ing.weighed < ing.weightPerPack * pack.packSize;
});
}
/* 判断任务是否有待称重项 */
/* 判断任务是否有待打包项 */
function hasPendingTask(task) {
return task.mealPacks.some(function(pack) {
return hasPendingPack(pack);
@@ -263,12 +263,18 @@
var taskCard = document.createElement('div');
taskCard.className = 'task-card';
/* 任务头部:任务号 + 状态 */
/* 任务头部:任务号 + 状态 + 去打包按钮 */
var firstPendingPack = null;
task.mealPacks.forEach(function(p) { if (!firstPendingPack && hasPendingPack(p)) firstPendingPack = p; });
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">' + task.taskNo + '</span></span>' +
'<span class="task-card__status"><span class="status-badge ' + (taskHasPending ? 'status-badge--pending' : 'status-badge--done') + '">' + (taskHasPending ? '待称重' : '已完成') + '</span></span>';
'<span class="task-card__status"><span class="status-badge ' + (taskHasPending ? 'status-badge--pending' : 'status-badge--done') + '">' + (taskHasPending ? '待打包' : '已完成') + '</span></span>' +
'<span style="flex:1"></span>' +
(taskHasPending
? '<button class="task-card__pack-btn" onclick="handleWeigh(\'' + task.taskNo + '\',\'' + firstPendingPack.packName + '\')">去打包</button>'
: '<button class="task-card__pack-btn" disabled>全部完成</button>');
taskCard.appendChild(header);
/* 每个餐品净菜包为独立区块 */
@@ -296,8 +302,7 @@
'<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>' +
'<button class="task-card__pack-btn" style="margin-left:6px"' + hasPendingBtn + ' onclick="handleWeigh(\'' + task.taskNo + '\',\'' + pack.packName + '\')">' + (packHasPending ? '去称重' : '已完成') + '</button></div>';
'<div style="text-align:center"><span class="status-tag ' + (packStatus.text === '已完成' ? 'status-tag--done' : 'status-tag--wait') + '">' + packStatus.text + '</span></div>';
taskCard.appendChild(packHeader);
/* 食材子行 */
@@ -314,7 +319,7 @@
'<div class="child-pack-size">×</div>' +
'<div class="child-weight" style="text-align:right">' + need + ' 克</div>' +
'<div class="child-weight ' + childWeightClass + '" style="text-align:right">' + ing.weighed + ' 克</div>' +
'<div style="text-align:center"><span class="status-tag ' + (isDone ? 'status-tag--done' : 'status-tag--wait') + '">' + (isDone ? '已完成' : '待称重') + '</span></div>';
'<div style="text-align:center"><span class="status-tag ' + (isDone ? 'status-tag--done' : 'status-tag--wait') + '">' + (isDone ? '已完成' : '待打包') + '</span></div>';
taskCard.appendChild(childRow);
});
});
@@ -343,7 +348,7 @@
});
}
/* 去称重 */
/* 去打包 */
function handleWeigh(taskNo, packName) {
var target = '02-weighing-packing.html?task=' + taskNo + '&pack=' + encodeURIComponent(packName);
if (userName) target += '&user=' + encodeURIComponent(userName);
@@ -363,7 +368,7 @@
/* 初始化 */
renderTaskList();
/* 初始过滤:待称重 Tab 默认不显示已完成订单 */
/* 初始过滤:待打包 Tab 默认不显示已完成订单 */
switchTab('pending', document.querySelector('.tab[data-filter="pending"]'));
/* 返回按钮 */
@@ -199,7 +199,7 @@
overflow: hidden; flex: 1; min-height: 0; display: flex; flex-direction: column;
}
.item-list__head {
display: grid; grid-template-columns: 1.2fr 0.7fr 0.6fr 0.7fr 0.7fr 50px 55px; gap: 6px;
display: grid; grid-template-columns: 0.9fr 1fr 0.6fr 0.5fr 0.6fr 0.6fr 45px 50px; gap: 6px;
padding: 8px 14px; background: rgba(255,255,255,0.03);
font-size: 11px; color: rgba(255,255,255,0.4); align-items: center; flex-shrink: 0;
}
@@ -209,11 +209,12 @@
.item-list__body::-webkit-scrollbar { width: 3px; }
.item-list__body::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 2px; }
.item-row {
display: grid; grid-template-columns: 1.2fr 0.7fr 0.6fr 0.7fr 0.7fr 50px 55px; gap: 6px;
display: grid; grid-template-columns: 0.9fr 1fr 0.6fr 0.5fr 0.6fr 0.6fr 45px 50px; gap: 6px;
padding: 9px 14px; font-size: 13px; align-items: center;
border-top: 1px solid rgba(255,255,255,0.04); transition: background 0.15s;
}
.item-row.current { background: rgba(74,144,217,0.08); }
.item-row__pack { font-size: 11px; color: rgba(255,255,255,0.4); }
.item-row__name { font-weight: 500; display: flex; align-items: center; gap: 6px; }
.item-row__name-icon { font-size: 14px; }
.item-row__per-pack { font-size: 12px; color: rgba(255,255,255,0.55); text-align: center; }
@@ -374,6 +375,7 @@
<!-- 净菜清单表格 -->
<div class="item-list">
<div class="item-list__head">
<span>餐品包</span>
<span>净菜名称</span>
<span style="text-align:center">每份用量(克)</span>
<span style="text-align:center">打包规格(份)</span>
@@ -458,22 +460,35 @@
/* ---- 状态变量 ---- */
var state = 'idle'; // idle | detecting
var currentPack = null;
var currentIngIdx = 0;
var allIngredients = []; // [{ pack, packName, ing, ingIdx }, ...] 跨所有餐品包
var currentIngIdx = 0; // 在 allIngredients 中的索引
/* ---- 构建跨包食材清单 ---- */
function rebuildAllIngredients() {
allIngredients = [];
var task = taskData[taskNo];
if (!task) return;
var packKeys = Object.keys(task.mealPacks);
packKeys.forEach(function(pk) {
var p = task.mealPacks[pk];
p.ingredients.forEach(function(ing, ingIdx) {
allIngredients.push({ pack: p, packName: p.packName, ing: ing, ingIdx: ingIdx });
});
});
}
/* ---- 初始化 ---- */
function init() {
var task = taskData[taskNo];
if (!task) { showToast('任务不存在', 2000, 'error'); return; }
currentPack = task.mealPacks[packName];
if (!currentPack) { showToast('餐品净菜包不存在', 2000, 'error'); return; }
rebuildAllIngredients();
/* 找到第一个待称重的食材 */
for (var i = 0; i < currentPack.ingredients.length; i++) {
var ing = currentPack.ingredients[i];
var need = ing.weightPerPack * currentPack.packSize;
if (ing.weighed < need) {
/* 找到第一个待称重的食材(跨所有包) */
for (var i = 0; i < allIngredients.length; i++) {
var item = allIngredients[i];
var need = item.ing.weightPerPack * item.pack.packSize;
if (item.ing.weighed < need) {
currentIngIdx = i;
break;
}
@@ -481,35 +496,36 @@
/* 设置页面信息 */
document.getElementById('taskNo').textContent = taskNo;
document.getElementById('packTag').textContent = currentPack.packName;
/* 渲染该任务下所有餐品净菜包标签 */
var packsHtml = '';
var packNames = Object.keys(task.mealPacks);
packNames.forEach(function(name) {
var packKeys = Object.keys(task.mealPacks);
packKeys.forEach(function(name) {
var p = task.mealPacks[name];
var isCurrent = (name === packName);
packsHtml += '<span class="task-pack-tag' + (isCurrent ? ' task-pack-tag--current' : '') + '">' + p.packName + ' ×' + p.packSize + ' 份</span>';
packsHtml += '<span class="task-pack-tag">' + p.packName + ' ×' + p.packSize + ' 份</span>';
});
document.getElementById('taskPacks').innerHTML = packsHtml;
document.getElementById('packTag').textContent = packKeys.length + ' 个包';
renderItemlist();
/* 初始化秤体重量显示为当前食材已有重量 */
var initIng = currentPack.ingredients[currentIngIdx];
document.getElementById('scaleWeightValue').textContent = initIng.weighed.toFixed(2);
/* 初始化秤体重量显示 */
var initItem = allIngredients[currentIngIdx];
document.getElementById('scaleWeightValue').textContent = initItem.ing.weighed.toFixed(2);
/* 显示相机覆盖层,等待用户手动点击识别 */
document.getElementById('cameraOverlay').classList.add('show');
}
/* ---- 渲染食材清单 ---- */
/* ---- 渲染食材清单(所有包的所有食材) ---- */
function renderItemlist() {
var body = document.getElementById('itemListBody');
body.innerHTML = '';
currentPack.ingredients.forEach(function(ing, idx) {
var need = Math.round(ing.weightPerPack * currentPack.packSize * 100) / 100;
allIngredients.forEach(function(item, idx) {
var ing = item.ing;
var pack = item.pack;
var need = Math.round(ing.weightPerPack * pack.packSize * 100) / 100;
var isDone = ing.weighed >= need;
var isCurrent = (idx === currentIngIdx);
@@ -525,9 +541,10 @@
: '<span class="status-tag status-tag--wait">待称重</span>';
row.innerHTML =
'<div class="item-row__pack">' + item.packName + '</div>' +
'<div class="item-row__name"><span class="item-row__name-icon">' + ing.icon + '</span>' + ing.name + '</div>' +
'<div class="item-row__per-pack">' + ing.weightPerPack + ' 克</div>' +
'<div class="item-row__pack-size">' + currentPack.packSize + ' 份</div>' +
'<div class="item-row__pack-size">' + pack.packSize + ' 份</div>' +
'<div class="item-row__need">' + need + '</div>' +
'<div class="item-row__actual ' + actualClass + '">' + ing.weighed + '</div>' +
'<div class="item-row__status">' + statusTag + '</div>' +
@@ -555,8 +572,10 @@
/* 模拟识别 + 称重完成(延迟后自动记录) */
setTimeout(function() {
var ing = currentPack.ingredients[currentIngIdx];
var need = ing.weightPerPack * currentPack.packSize;
var item = allIngredients[currentIngIdx];
var ing = item.ing;
var pack = item.pack;
var need = ing.weightPerPack * pack.packSize;
var remaining = Math.max(0, need - ing.weighed);
var simWeight = Math.round(Math.min(remaining * 0.88, remaining) * 100) / 100;
@@ -588,12 +607,12 @@
document.getElementById('candidateEmpty').classList.remove('hide');
document.getElementById('candidateTitle').textContent = '智能识别候选';
/* 自动跳转到下一个待称重食材 */
/* 自动跳转到下一个待称重食材(跨所有包) */
var found = false;
for (var i = 0; i < currentPack.ingredients.length; i++) {
var ing = currentPack.ingredients[i];
var need = ing.weightPerPack * currentPack.packSize;
if (ing.weighed < need) {
for (var i = 0; i < allIngredients.length; i++) {
var it = allIngredients[i];
var nd = it.ing.weightPerPack * it.pack.packSize;
if (it.ing.weighed < nd) {
currentIngIdx = i;
found = true;
break;
@@ -603,8 +622,8 @@
if (found) {
/* 更新秤体显示为下一食材当前重量 */
var nextIng = currentPack.ingredients[currentIngIdx];
document.getElementById('scaleWeightValue').textContent = nextIng.weighed.toFixed(2);
var nextItem = allIngredients[currentIngIdx];
document.getElementById('scaleWeightValue').textContent = nextItem.ing.weighed.toFixed(2);
document.getElementById('cameraOverlay').classList.add('show');
} else {
document.getElementById('scaleWeightValue').textContent = '0.00';
@@ -620,17 +639,17 @@
var empty = document.getElementById('candidateEmpty');
var title = document.getElementById('candidateTitle');
var packNames = {};
currentPack.ingredients.forEach(function(ing) { packNames[ing.name] = true; });
var allNames = {};
allIngredients.forEach(function(item) { allNames[item.ing.name] = true; });
var keys = Object.keys(aiResults);
var fromPack = keys.filter(function(k) { return packNames[aiResults[k].name]; });
var others = keys.filter(function(k) { return !packNames[aiResults[k].name]; });
var fromList = keys.filter(function(k) { return allNames[aiResults[k].name]; });
var others = keys.filter(function(k) { return !allNames[aiResults[k].name]; });
fromPack.sort(function() { return Math.random() - 0.5; });
fromList.sort(function() { return Math.random() - 0.5; });
others.sort(function() { return Math.random() - 0.5; });
var selected = fromPack.slice(0, 3).concat(others.slice(0, 1));
var selected = fromList.slice(0, 3).concat(others.slice(0, 1));
title.textContent = '智能识别候选 (' + selected.length + ')';
@@ -664,9 +683,9 @@
/* ---- 打包完成 ---- */
function handlePackDone() {
var allDone = currentPack.ingredients.every(function(ing) {
var need = ing.weightPerPack * currentPack.packSize;
return ing.weighed >= need;
var allDone = allIngredients.every(function(item) {
var need = item.ing.weightPerPack * item.pack.packSize;
return item.ing.weighed >= need;
});
if (!allDone) {
showToast('还有食材未完成称重', 2500, 'error');
@@ -680,14 +699,14 @@
/* ---- 清空重量 ---- */
function clearWeight(idx) {
var ing = currentPack.ingredients[idx];
ing.weighed = 0;
var item = allIngredients[idx];
item.ing.weighed = 0;
/* 如果清空的是当前食材,同步更新秤体显示 */
if (idx === currentIngIdx) {
document.getElementById('scaleWeightValue').textContent = '0.00';
}
renderItemlist();
showToast(ing.name + ' 重量已清空', 1500, 'success');
showToast(item.ing.name + ' 重量已清空', 1500, 'success');
}
/* ---- 重新识别 ---- */
@@ -1,5 +1,17 @@
# 净菜配比秤 CHANGELOG
## [1.4.0] - 2026-06-16
### 新增
- 称重页支持多餐品净菜包同时打包:清单表格新增「餐品包」列,全部包食材统一展示
- 食材识别自动跨包跳转,一轮流程覆盖所有餐品包的待称重食材
### 调整
- 任务列表「去打包」按钮移至任务头部,跟随任务而非单个餐品包
- 页面名称全局更新:「称重」→「打包」(待打包、去打包、打包任务、打包完成)
---
## [1.3.0] - 2026-06-16
### 重构