-
📷 食材识别
-
-
-
- 食材识别区域
-
-
-
-
-
- 当前秤重
- 0.00
- 克
+
-
+
+
+ 📷 食材识别
+
+ 食材识别区域
+
+
+
+
+
-
+ 当前秤重
+ 0.00
+ 克
+
+
+
📷
+ 点击继续识别
+
-
-
-
- 📷
- 点击继续识别
+
+
+ 就绪,等待食材放置
+
-
- 就绪,等待食材放置
-
-
-
-
-
-
-
-
- 🔍
-
+
-
+
+
+
-
-
-
+
-
+ 🔍
+
+
-
-
+
- 智能识别候选
-
-
-
-
-
- 📷
- 等待食材放置后自动识别
+
+
+
+ 智能识别候选
+
+
+
+
📷
+ 等待食材放置后自动识别
+ 场景监控后台静默录制中
场景监控后台静默录制中
@@ -420,52 +508,57 @@
--
-
+ 打包份数
+ 餐品名称
+ --
+
+
组配份数
--
-
- 订单状态
+ 餐品组配状态
--
-
当前规格
- 30mm*30mm*3mm · 圆片 · 平刀
-
+ 🥜主辅材组配
+
+
-
+
+
- 净菜名称
- 餐品净菜包名称
- 包装规格
- 每份用量(克)
- 打包份数
- 所需总重(克)
- 已称(克)
- 状态
- 操作
-
+
+
+ 🧂调料组配
+
+
+
+
+
+
-
+
-
配比确认
+ 组配确认
--
@@ -495,6 +588,7 @@
/* ---- 读取参数 ---- */
var userName = getUrlParam('user') || '刘九';
var taskNo = getUrlParam('task') || 'T-001';
+ var packName = getUrlParam('pack') ? decodeURIComponent(getUrlParam('pack')) : '';
/* 用户信息 */
document.getElementById('topUser').textContent = userName;
@@ -506,19 +600,18 @@
orderNo: 'PO-20260616-001',
orderType: 'internal',
mealPacks: [
- { type: '净菜包', packName: '土豆丝', packSize: 3, packSpec: '500g', ingredients: [
- { name: '土豆丝', icon: '🥔', weightPerPack: 500, weighed: 750 }
- ]},
- { type: '净菜包', packName: '胡萝卜丝', packSize: 3, packSpec: '500g', ingredients: [
- { name: '胡萝卜丝', icon: '🥕', weightPerPack: 500, weighed: 750 }
- ]},
- { type: '净菜包', packName: '黄瓜片', packSize: 3, packSpec: '500g', ingredients: [
- { name: '黄瓜片', icon: '🥒', weightPerPack: 500, weighed: 0 }
- ]},
- { type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', ingredients: [
+ { type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
{ name: '土豆丝', icon: '🥔', weightPerPack: 300, weighed: 0 },
{ name: '胡萝卜丝', icon: '🥕', weightPerPack: 250, weighed: 0 },
{ name: '黄瓜片', icon: '🥒', weightPerPack: 250, weighed: 0 }
+ ], seasonings: [
+ { name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 50, weighed: 0 },
+ { name: '盐', icon: '🧂', cellIdx: 1, weightPerPack: 5, weighed: 0 },
+ { name: '白糖', icon: '🍬', cellIdx: 2, weightPerPack: 10, weighed: 0 },
+ { name: '酱油', icon: '🍲', cellIdx: 3, weightPerPack: 15, weighed: 0 },
+ { name: '醋', icon: '🍹', cellIdx: 4, weightPerPack: 8, weighed: 0 },
+ { name: '料酒', icon: '🍷', cellIdx: 5, weightPerPack: 12, weighed: 0 },
+ { name: '味精', icon: '❕', cellIdx: 6, weightPerPack: 2, weighed: 0 }
]}
]
},
@@ -526,17 +619,82 @@
orderNo: 'PO-20260616-002',
orderType: 'external',
mealPacks: [
- { type: '净菜包', packName: '白菜丝', packSize: 2, packSpec: '1000g', ingredients: [
- { name: '白菜丝', icon: '🥬', weightPerPack: 1000, weighed: 0 }
- ]},
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
{ name: '白菜丝', icon: '🥬', weightPerPack: 400, weighed: 0 },
{ name: '胡萝卜丝', icon: '🥕', weightPerPack: 400, weighed: 0 }
+ ], seasonings: [
+ { name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 40, weighed: 0 },
+ { name: '盐', icon: '🧂', cellIdx: 1, weightPerPack: 4, weighed: 0 },
+ { name: '醋', icon: '🍹', cellIdx: 2, weightPerPack: 10, weighed: 0 },
+ { name: '酱油', icon: '🍲', cellIdx: 3, weightPerPack: 12, weighed: 0 },
+ { name: '白糖', icon: '🍬', cellIdx: 4, weightPerPack: 8, weighed: 0 },
+ { name: '料酒', icon: '🍷', cellIdx: 5, weightPerPack: 10, weighed: 0 },
+ { name: '豆瓣酱', icon: '🍡', cellIdx: 6, weightPerPack: 15, weighed: 0 }
+ ]}
+ ]
+ },
+ 'T-003': {
+ orderNo: 'PO-20260616-003',
+ orderType: 'internal',
+ mealPacks: [
+ { type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', ingredients: [
+ { name: '番茄炒蛋', icon: '🥘', weightPerPack: 500, weighed: 2000 }
+ ], seasonings: [
+ { name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 30, weighed: 120 },
+ { name: '盐', icon: '🧂', cellIdx: 2, weightPerPack: 5, weighed: 20 },
+ { name: '白糖', icon: '🍬', cellIdx: 3, weightPerPack: 8, weighed: 32 }
+ ]}
+ ]
+ },
+ 'T-004': {
+ orderNo: 'PO-20260616-004',
+ orderType: 'external',
+ mealPacks: [
+ { type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '盒装', ingredients: [
+ { name: '凉拌黄瓜', icon: '🥘', weightPerPack: 500, weighed: 2500 }
+ ], seasonings: [
+ { name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 20, weighed: 100 },
+ { name: '醋', icon: '🍹', cellIdx: 2, weightPerPack: 6, weighed: 30 },
+ { name: '酱油', icon: '🍲', cellIdx: 3, weightPerPack: 10, weighed: 50 }
]}
]
}
};
+ /* 调料名称→图标映射(与 04 设置页一致) */
+ var SEASONING_ICONS = {
+ '油': '🛢', '盐': '🧂', '白糖': '🍬', '酱油': '🥩',
+ '醋': '🥩', '味精': '❕', '鸡精': '🧂', '料酒': '🍷',
+ '胡椒粉': '🧂', '豆瓣酱': '🍡', '甜面酱': '🥬', '蚝油': '🍯',
+ '花椒': '🌿', '八角': '🌿', '桂皮': '🌿', '干辣椒': '🌶',
+ '蒜蓉': '🧄', '姜末': '🥜'
+ };
+
+ /* 从 localStorage 加载调料位置配置 */
+ var SEASONING_CONFIG_KEY = 'seasoning_position_config_v2';
+ function loadSeasoningConfig() {
+ var raw = localStorage.getItem(SEASONING_CONFIG_KEY);
+ if (raw) {
+ try { return JSON.parse(raw); } catch(e) {}
+ }
+ /* 默认配置(与 04 设置页一致,扩展 7-11 位) */
+ return {
+ 0: { name: '油', weight: 1000 },
+ 1: { name: '盐', weight: 500 },
+ 2: { name: '白糖', weight: 500 },
+ 3: { name: '酱油', weight: 1000 },
+ 4: { name: '醋', weight: 500 },
+ 5: { name: '味精', weight: 250 },
+ 6: { name: '料酒', weight: 500 },
+ 7: { name: '豆瓣酱', weight: 500 },
+ 8: { name: '蚝油', weight: 500 },
+ 9: { name: '花椒', weight: 200 },
+ 10: { name: '干辣椒', weight: 200 },
+ 11: { name: '蒜蓉', weight: 300 }
+ };
+ }
+ var seasoningConfig = loadSeasoningConfig();
+
var aiResults = {
'potato': { name: '土豆丝', icon: '🥔', confidence: 97 },
'carrot': { name: '胡萝卜丝', icon: '🥕', confidence: 96 },
@@ -550,73 +708,98 @@
/* ---- 状态变量 ---- */
var state = 'idle'; // idle | detecting
- var allIngredients = []; // 扁平化:所有包的食材 [{name, icon, weightPerPack, packName, packType, packSize, weighed}]
- var currentIngIdx = 0;
+ var currentPack = null; // 当前选中的餐品净菜包数据
+ var allIngredients = []; // 主辅材扁平化列表
+ var allSeasonings = []; // 调料列表
+ var currentIngIdx = 0; // 当前主辅材索引
+ var currentSeaIdx = 0; // 当前调料索引
/* ---- 初始化 ---- */
function init() {
var task = taskData[taskNo];
if (!task) { showToast('任务不存在', 2000, 'error'); return; }
- /* 扁平化:将所有包的食材展开到统一列表 */
- allIngredients = [];
- task.mealPacks.forEach(function(pack) {
- pack.ingredients.forEach(function(ing) {
- allIngredients.push({
- name: ing.name,
- icon: ing.icon,
- weightPerPack: ing.weightPerPack,
- weighed: ing.weighed,
- packName: pack.packName,
- packType: pack.type,
- packSize: pack.packSize,
- packSpec: pack.packSpec || ''
- });
- });
- });
-
- /* 找到第一个待称重的食材 */
- for (var i = 0; i < allIngredients.length; i++) {
- var ing = allIngredients[i];
- var need = ing.weightPerPack * ing.packSize;
- if (ing.weighed < need) {
- currentIngIdx = i;
+ /* 按 packName 筛选对应餐品净菜包 */
+ currentPack = null;
+ for (var i = 0; i < task.mealPacks.length; i++) {
+ if (task.mealPacks[i].packName === packName) {
+ currentPack = task.mealPacks[i];
break;
}
}
+ if (!currentPack) { currentPack = task.mealPacks[0]; packName = currentPack.packName; }
+
+ /* 扁平化主辅材 */
+ allIngredients = currentPack.ingredients.map(function(ing) {
+ return {
+ name: ing.name,
+ icon: ing.icon,
+ weightPerPack: ing.weightPerPack,
+ weighed: ing.weighed,
+ packSize: currentPack.packSize
+ };
+ });
+
+ /* 调料列表 */
+ allSeasonings = (currentPack.seasonings || []).map(function(sea) {
+ return {
+ name: sea.name,
+ cellIdx: sea.cellIdx,
+ weightPerPack: sea.weightPerPack,
+ weighed: sea.weighed,
+ packSize: currentPack.packSize
+ };
+ });
+
+ /* 找到第一个待称重的主辅材 */
+ currentIngIdx = 0;
+ for (var j = 0; j < allIngredients.length; j++) {
+ var need = allIngredients[j].weightPerPack * allIngredients[j].packSize;
+ if (allIngredients[j].weighed < need) { currentIngIdx = j; break; }
+ }
+ /* 找到第一个待称重的调料 */
+ currentSeaIdx = -1;
+ for (var k = 0; k < allSeasonings.length; k++) {
+ var seaNeed = allSeasonings[k].weightPerPack * allSeasonings[k].packSize;
+ if (allSeasonings[k].weighed < seaNeed) { currentSeaIdx = k; break; }
+ }
/* 设置页面信息 */
document.getElementById('taskOrder').textContent = task.orderNo;
- /* 订单类型标签 */
var orderTypeEl = document.getElementById('taskOrderType');
var orderTypeLabel = task.orderType === 'internal' ? '内供订单' : '外售订单';
var orderTypeCls = task.orderType === 'internal' ? 'order-type-tag--internal' : 'order-type-tag--external';
orderTypeEl.innerHTML = '' + orderTypeLabel + '';
- /* 打包份数:每个食材项对应其所属包的 packSize,累加所有食材 */
- var totalPacks = 0;
- task.mealPacks.forEach(function(pack) {
- pack.ingredients.forEach(function() { totalPacks += pack.packSize; });
- });
- document.getElementById('taskTotalPacks').textContent = totalPacks;
- /* 订单状态:全部食材已完成 → 已完成,否则 → 待配比 */
- var allDone = allIngredients.every(function(ing) {
+ document.getElementById('taskPackName').textContent = currentPack.packName + ' · ' + (currentPack.packSpec || '—');
+ document.getElementById('taskTotalPacks').textContent = currentPack.packSize;
+
+ /* 订单状态:主辅材+调料全部完成才算完成 */
+ var ingAllDone = allIngredients.every(function(ing) {
return ing.weighed >= ing.weightPerPack * ing.packSize;
});
+ var seaAllDone = allSeasonings.every(function(sea) {
+ return sea.weighed >= sea.weightPerPack * sea.packSize;
+ });
+ var allDone = ingAllDone && seaAllDone;
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');
renderItemlist();
+ renderSeasoningGrid();
- /* 初始化秤体重量显示为当前食材已有重量 */
- var initIng = allIngredients[currentIngIdx];
- document.getElementById('scaleWeightValue').textContent = initIng.weighed.toFixed(2);
+ /* 初始化秤体重量显示 */
+ var initWeight = 0;
+ if (currentIngIdx < allIngredients.length) {
+ initWeight = allIngredients[currentIngIdx].weighed;
+ }
+ document.getElementById('scaleWeightValue').textContent = initWeight.toFixed(2);
/* 显示相机覆盖层,等待用户手动点击识别 */
document.getElementById('cameraOverlay').classList.add('show');
}
- /* ---- 渲染食材清单(卡片形式) ---- */
+ /* ---- 渲染主辅材清单(卡片形式,不显示餐品净菜包名称) ---- */
function renderItemlist() {
var body = document.getElementById('itemListBody');
body.innerHTML = '';
@@ -624,47 +807,166 @@
allIngredients.forEach(function(ing, idx) {
var need = Math.round(ing.weightPerPack * ing.packSize * 100) / 100;
var isDone = ing.weighed >= need;
- var isCurrent = (idx === currentIngIdx);
var row = document.createElement('div');
- row.className = 'item-row' + (isCurrent ? ' current' : '');
+ row.className = 'item-row';
var actualClass = 'item-card__data-value--zero';
if (isDone) actualClass = 'item-card__data-value--done';
else if (ing.weighed > 0) actualClass = 'item-card__data-value--progress';
var statusTag = isDone
- ? '已完成'
- : '待称重';
-
- /* 餐品净菜包名称:净菜包不显示此行 */
- var packNameDisplay = ing.packType === '餐品净菜包' ? ing.packName : '—';
- var packSpecDisplay = ing.packSpec || '—';
+ ? '已组配'
+ : '待组配';
row.innerHTML =
- /* 卡片头部:名称 + 状态 + 清空按钮 */
'净菜配比秤 - 配比记录 | 蚁熊智能餐养平台
+
+ 净菜组配秤 - 组配记录 | 蚁熊智能餐养平台
+
+
+ 净菜配比秤 - 蚁熊智能餐养平台
+
+ 净菜组配秤 - 蚁熊智能餐养平台
' +
'
' +
- /* 元信息行:餐品净菜包名 + 包装规格 */
- '' + ing.icon + '' + ing.name + '
' +
'' + statusTag + '
' +
- '' + (ing.weighed > 0 ? '' : '') + '
' +
'' +
- '
' +
- /* 数据行:每份用量 / 份数 / 所需总重 / 已称 */
'餐品净菜包:' + packNameDisplay + '
' +
- '包装规格:' + packSpecDisplay + '
' +
- '' +
- '
';
body.appendChild(row);
});
}
+ /* ---- 渲染调料组配网格(3行×5列=15格,左上4格合并为信息格固定显示油,11格为调料位) ---- */
+ function renderSeasoningGrid() {
+ var grid = document.getElementById('seasoningGrid');
+ grid.innerHTML = '';
+
+ /* 信息格(合并格):固定显示"油"的完整字段(油始终参与组配) */
+ var oilSea = allSeasonings.find(function(s) { return s.cellIdx === 0; });
+ var infoCell = document.createElement('div');
+ infoCell.className = 'seasoning-cell seasoning-cell--info';
+ if (oilSea) {
+ var oilNeed = Math.round(oilSea.weightPerPack * oilSea.packSize * 100) / 100;
+ var oilDone = oilSea.weighed >= oilNeed;
+ /* 待组配→脉冲高亮,已组配→绿色 */
+ if (oilDone) {
+ infoCell.classList.add('seasoning-cell--done');
+ } else {
+ infoCell.classList.add('seasoning-cell--pending');
+ }
+ var oilStatusCls = oilDone ? 'sea-slot__status--done' : 'sea-slot__status--wait';
+ var oilStatusText = oilDone ? '已组配' : '待组配';
+ var oilActualCls = oilDone ? 'sea-field__value--done' : '';
+ infoCell.innerHTML =
+ '' + oilStatusText + '' +
+ '每份用量:' + ing.weightPerPack + 'g
' +
- '份数:' + ing.packSize + '
' +
- '需:' + need + 'g
' +
- '已称:' + ing.weighed + 'g
' +
+ '每份' + ing.weightPerPack + 'g
' +
+ '份数' + ing.packSize + '
' +
+ '应称' + need + 'g
' +
+ '已称' + ing.weighed + 'g
' +
'' + (oilSea.icon || '🛢') + '' + oilSea.name + '
' +
+ '' +
+ '
';
+ } else {
+ infoCell.innerHTML = '每份用量' + oilSea.weightPerPack + 'g
' +
+ '份数' + oilSea.packSize + '
' +
+ '应称' + oilNeed + 'g
' +
+ '已称' + oilSea.weighed + 'g
' +
+ '全部完成
';
+ }
+ /* 信息格也可点击称重 */
+ if (oilSea) {
+ var oilIdx = allSeasonings.indexOf(oilSea);
+ infoCell.style.cursor = 'pointer';
+ infoCell.onclick = function() { openSeasoningModal(oilIdx); };
+ }
+ grid.appendChild(infoCell);
+
+ /* 构建当前餐品需要的调料 cellIdx 集合 */
+ var neededSeaMap = {};
+ allSeasonings.forEach(function(sea) { if (sea.cellIdx > 0) neededSeaMap[sea.cellIdx] = sea; });
+
+ /* 11 个调料位置格(cellIdx 1-11) */
+ for (var pos = 1; pos <= 11; pos++) {
+ var cell = document.createElement('div');
+ cell.className = 'seasoning-cell seasoning-cell--slot';
+
+ /* 先从位置配置获取该格放置的调料 */
+ var cfg = seasoningConfig[pos];
+ var neededSea = neededSeaMap[pos];
+
+ if (neededSea) {
+ /* 状态1/2:该格调料在本餐品组配清单中 — 待组配或已组配 */
+ var seaNeed2 = Math.round(neededSea.weightPerPack * neededSea.packSize * 100) / 100;
+ var seaDone2 = neededSea.weighed >= seaNeed2;
+
+ cell.classList.add('seasoning-cell--occupied');
+ if (seaDone2) {
+ /* 已组配:绿色,取消高亮 */
+ cell.classList.add('seasoning-cell--done');
+ } else {
+ /* 待组配:脉冲高亮 */
+ cell.classList.add('seasoning-cell--pending');
+ }
+
+ var actualCls2 = seaDone2 ? 'sea-slot__field-value--done' : '';
+ var statusCls = seaDone2 ? 'sea-slot__status--done' : 'sea-slot__status--wait';
+ var statusText = seaDone2 ? '已组配' : '待组配';
+
+ cell.innerHTML =
+ '' + pos + '' +
+ '' + statusText + '' +
+ '' + (neededSea.icon || '🧂') + '' + neededSea.name + '
' +
+ '' +
+ '
';
+
+ cell.onclick = (function(idx) { return function() { openSeasoningModal(idx); }; })(findSeasoningIdxByPos(pos));
+ } else if (cfg && cfg.name) {
+ /* 状态3:该格放置了调料但本餐品不需要组配 — 字段一致,数据用--占位 */
+ cell.classList.add('seasoning-cell--occupied');
+ var cfgIcon = SEASONING_ICONS[cfg.name] || '🧂';
+ cell.innerHTML =
+ '' + pos + '' +
+ '每份' + neededSea.weightPerPack + 'g
' +
+ '份数' + neededSea.packSize + '
' +
+ '应称' + seaNeed2 + 'g
' +
+ '已称' + neededSea.weighed + 'g
' +
+ '' + cfgIcon + '' + cfg.name + '
' +
+ '' +
+ '
';
+ } else {
+ cell.innerHTML = '' + pos + '每份--
' +
+ '份数--
' +
+ '应称--
' +
+ '已称--
' +
+ '空位
';
+ }
+ grid.appendChild(cell);
+ }
+ }
+
+ /* 根据 cellIdx 查找 allSeasonings 中的索引 */
+ function findSeasoningIdxByPos(pos) {
+ for (var i = 0; i < allSeasonings.length; i++) {
+ if (allSeasonings[i].cellIdx === pos) return i;
+ }
+ return -1;
+ }
+
+ /* ---- 调料组配确认弹窗(不走 AI 识别,直接弹窗读秤) ---- */
+ function openSeasoningModal(idx) {
+ if (idx < 0 || idx >= allSeasonings.length) return;
+ var sea = allSeasonings[idx];
+ var seaNeed = Math.round(sea.weightPerPack * sea.packSize * 100) / 100;
+ if (sea.weighed >= seaNeed) {
+ showToast(sea.name + ' 已组配', 1500, 'success');
+ return;
+ }
+ /* 设为当前调料 */
+ currentSeaIdx = idx;
+ renderSeasoningGrid();
+ /* 填入弹窗 */
+ document.getElementById('modalIcon').innerHTML = sea.icon || '🧂';
+ 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 || '🧂', confidence: 100, isSeasoning: true, idx: idx };
+ }
+
/* ---- 触发识别(点击相机覆盖层) ---- */
function triggerRecognition() {
if (state === 'detecting') return;
@@ -734,7 +1036,7 @@
event.target.value = '';
}
- /* ---- 配比确认弹窗 ---- */
+ /* ---- 组配确认弹窗 ---- */
var pendingCandidate = null; // 待确认的识别结果
function openRatioModal(result) {
@@ -759,34 +1061,41 @@
function confirmPack() {
if (!pendingCandidate) return;
document.getElementById('modalOverlay').classList.remove('show');
-
- /* 将弹窗中显示的称重结果写入当前食材 */
- var ing = allIngredients[currentIngIdx];
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 识别后确认 */
+ var ing = allIngredients[currentIngIdx];
ing.weighed = modalWeight;
-
- /* 更新秤体重量 */
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 + '%';
-
+ document.getElementById('statusText').textContent = '组配确认: ' + pendingCandidate.name + ' — 置信度 ' + pendingCandidate.confidence + '%';
showToast('称重完成: ' + pendingCandidate.name + ' ' + modalWeight.toFixed(2) + '克', 2000, 'success');
pendingCandidate = null;
-
- /* 刷新清单 */
renderItemlist();
+ updateOverallStatus();
- /* 同步更新订单状态 */
- var allDoneNow = allIngredients.every(function(ing) {
- return ing.weighed >= ing.weightPerPack * ing.packSize;
- });
- var taskStatusEl = document.getElementById('taskStatus');
- taskStatusEl.textContent = allDoneNow ? '已完成' : '待配比';
- taskStatusEl.className = 'task-card__item-value' + (allDoneNow ? ' task-card__item-value--green' : ' task-card__item-value--yellow');
-
- /* 延迟回到就绪态 */
+ /* 延迟回到就绪态,自动跳转下一个待称主辅材 */
setTimeout(function() {
state = 'idle';
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
@@ -795,41 +1104,58 @@
document.getElementById('candidateEmpty').classList.remove('hide');
document.getElementById('candidateTitle').textContent = '智能识别候选';
- /* 自动跳转到下一个待称重食材 */
var found = false;
for (var i = 0; i < allIngredients.length; i++) {
- var ing = allIngredients[i];
- var need = ing.weightPerPack * ing.packSize;
- if (ing.weighed < need) {
- currentIngIdx = i;
- found = true;
- break;
+ if (allIngredients[i].weighed < allIngredients[i].weightPerPack * allIngredients[i].packSize) {
+ currentIngIdx = i; found = true; break;
}
}
renderItemlist();
-
if (found) {
- var nextIng = allIngredients[currentIngIdx];
- document.getElementById('scaleWeightValue').textContent = nextIng.weighed.toFixed(2);
+ document.getElementById('scaleWeightValue').textContent = allIngredients[currentIngIdx].weighed.toFixed(2);
document.getElementById('cameraOverlay').classList.add('show');
} else {
document.getElementById('scaleWeightValue').textContent = '0.00';
- showToast('所有食材已称重完成', 2500, 'success');
+ /* 主辅材全完成,提示进行调料组配 */
+ if (currentSeaIdx >= 0) {
+ showToast('主辅材已组配,请点击高亮调料格进行调料组配', 3000, 'success');
+ } else {
+ showToast('所有食材已称重完成', 2500, 'success');
+ }
}
}, 2500);
}
- /* ---- 配比完成 ---- */
- function handlePackDone() {
- var allDone = allIngredients.every(function(ing) {
- var need = ing.weightPerPack * ing.packSize;
- return ing.weighed >= need;
+ /* 更新订单整体状态 */
+ function updateOverallStatus() {
+ var ingDone = allIngredients.every(function(ing) {
+ return ing.weighed >= ing.weightPerPack * ing.packSize;
});
- if (!allDone) {
- showToast('还有食材未完成称重', 2500, 'error');
+ var seaDone = allSeasonings.every(function(sea) {
+ return sea.weighed >= sea.weightPerPack * sea.packSize;
+ });
+ var allDoneNow = ingDone && seaDone;
+ var taskStatusEl = document.getElementById('taskStatus');
+ taskStatusEl.textContent = allDoneNow ? '已组配' : '待组配';
+ taskStatusEl.className = 'task-card__item-value' + (allDoneNow ? ' task-card__item-value--green' : ' task-card__item-value--yellow');
+ }
+
+ /* ---- 组配完成 ---- */
+ function handlePackDone() {
+ 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) {
+ var msg = [];
+ 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);
diff --git a/other-module/canteen-device/ratio-scale/03-verify-history.html b/other-module/canteen-device/ratio-scale/03-verify-history.html
index 5288392..fb8eb68 100644
--- a/other-module/canteen-device/ratio-scale/03-verify-history.html
+++ b/other-module/canteen-device/ratio-scale/03-verify-history.html
@@ -2,13 +2,25 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/other-module/canteen-device/ratio-scale/doc/CHANGELOG.md b/other-module/canteen-device/ratio-scale/doc/CHANGELOG.md
index 1cfa5d3..4784479 100644
--- a/other-module/canteen-device/ratio-scale/doc/CHANGELOG.md
+++ b/other-module/canteen-device/ratio-scale/doc/CHANGELOG.md
@@ -1,4 +1,28 @@
-# 净菜配比秤 CHANGELOG
+# 净菜组配秤 CHANGELOG
+
+## [2.0.0] - 2026-06-25
+
+### 屏幕比例改造
+- **全页面纵向 3:4**:所有 7 个页面从横向 1280×720(16:9)改为纵向 720×960(3:4),viewport 同步更新
+- **02 组配详情页布局重构**:从横向两列(左识别+右清单)改为纵向上下(上区识别+候选左右排列,下区任务+清单+调料+按钮);body 统一为 PAD 外壳模式
+- **03 组配记录页**:统一为 PAD 外壳模式(深色画布+居中 device-frame)
+
+### 重构
+- **全局命名**:硬件名称由「净菜配比秤」改为「净菜组配秤」,所有「配比」文案统一改为「组配」(组配任务/组配记录/去组配/组配完成/组配确认/待组配)
+- **任务列表页(01)**:移除「净菜包」订单,仅展示「餐品净菜包」订单;移除订单级「去组配」按钮,改为每个餐品净菜包主行独立「组配」按钮;表头精简(去掉「净菜/餐品」列,新增「操作」列)
+- **组配详情页(02)**:任务信息卡新增「餐品名称」字段;移除清单中「餐品净菜包」名称元信息行;清单拆为「主辅材组配」+「调料组配」两区
+- **调料组配区(02)**:3行×5列=15格网格,左上角第一列+第二列的第1、2行4格合并为信息格(固定显示「油」的完整字段:名称/每份用量/份数/应称/已称),其余11格为调料位置格,每个调料格字段与主辅材一致(名称/每份用量/份数/应称/已称)
+- **调料多高亮状态(02)**:active(当前要称,蓝色脉冲)、next(下一个要称,淡蓝虚线)、done(已完成,绿色),称重确认后自动从 active 切换为 done
+- **调料组配交互(02)**:不走 AI 识别,点击高亮调料格 → 弹出组配确认弹窗 → 读取秤值 → 确认写入,信息格(油)也可点击称重
+- **状态判断(02)**:主辅材+调料全部完成才算该餐品净菜包组配完成
+
+### 新增
+- **调料位置设置页(04-seasoning-settings.html)**:3×5 网格可视化配置调料秤 11 个位置各自放置的调料,点击格子 → 下拉选择调料名称(17种常用调料候选),支持清空/重置/保存(localStorage 持久化)
+- **功能菜单(00-welcome.html)**:新增「调料位置设置」入口
+- **导航页(index.html)**:新增「调料位置设置」卡片入口
+- **canteen-device/index.html**:卡片名称改为「净菜组配秤」,描述更新为「餐品净菜包独立组配,主辅材+调料双区称重」
+
+---
## [1.3.2] - 2026-06-17
diff --git a/other-module/canteen-device/ratio-scale/index.html b/other-module/canteen-device/ratio-scale/index.html
index 09694fc..7648f64 100644
--- a/other-module/canteen-device/ratio-scale/index.html
+++ b/other-module/canteen-device/ratio-scale/index.html
@@ -2,20 +2,20 @@
-
-
+
+
+ 选择调料
+ 位置 --
+
+
+
+ ▼
+
+
+ 放置重量:
+
+ 克
+
+
+
+
+
+
+