refactor: 净菜配比秤数据结构扁平化 & 配比确认弹窗改造
- 01/02 页:任务改为一对多(mealPacks数组),支持净菜包+餐品净菜包混合 - 01 页:表格6列→8列,新增净菜/餐品、包装规格列 - 02 页:食材清单表格→卡片布局,AI识别后弹窗确认替代自动称重 - 03 页:候选列表按置信度降序排列 - 营养秤PRD:返回路径改为三级优先级(referer→document.referrer→index.html) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1120bac840
commit
c94658f083
@@ -1,5 +1,16 @@
|
||||
# 餐线营养秤 - 变更记录
|
||||
|
||||
## [2026-06-17] refactor: 全局返回路径改为三级优先级机制
|
||||
|
||||
### 调整
|
||||
- **PRD 1.4 节**:返回按钮实现由 `history.back()` 改为三级优先级机制:
|
||||
1. URL 参数 `?referer=`(来源页跳转时主动携带)
|
||||
2. `document.referrer`(浏览器来源页,需同域)
|
||||
3. 兜底跳转 `index.html`(首页)
|
||||
- 多入口页面通过 URL 参数 `?referer=` 传递来源页,确保返回路径准确
|
||||
|
||||
---
|
||||
|
||||
## [2026-06-15] feat: 新增重新识别按钮
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -72,7 +72,11 @@
|
||||
> **核心原则**:从哪来回哪去。
|
||||
|
||||
- 所有页面的返回按钮必须指向进入该页面的来源页
|
||||
- 若页面有多个入口(如 06 可从 05 或 04 进入),返回按钮使用浏览器 `history.back()` 实现
|
||||
- 返回逻辑实现优先级:
|
||||
1. URL 参数 `?referer=xxx`(来源页跳转时主动携带)
|
||||
2. `document.referrer`(浏览器来源页,需同域)
|
||||
3. 兜底跳转 `index.html`(首页)
|
||||
- 若页面有多个入口(如 06 可从 05 或 04 进入),来源页通过 URL 参数 `?referer=` 传递,确保返回路径准确
|
||||
- 采样页面(05)的「取消」操作统一回到 02 待取餐界面(无论从何进入)
|
||||
- 采样页面(05)的「保存采集」操作统一跳转 02 待取餐界面(携带 dishId)
|
||||
|
||||
@@ -738,3 +742,4 @@ stateDiagram-v2
|
||||
| 2026-06-11 | v2.6 | 01 开餐界面流程补全:开餐成功自动提交 1 张图片到采样库并跳转至 02-待取餐界面(携带 dishId);修复采样页面返回时 sampleDishId 参数未处理导致不自动选中的 BUG;新增非员工点击设置按钮拦截(Toast 提示「非餐厅员工,无权操作系统」);明确自动选中规则(AI 识别选中识别率最高、搜索选中第一个结果、采样返回自动选中对应餐品);补全重量稳定判定(波动 < 5g 持续 3 秒)到开餐流程描述;明确营养秤开机默认进入 01-开餐界面 |
|
||||
| 2026-06-11 | v2.7 | 全局返回路径规则:从哪来回哪去;04 设置界面新增「开始开餐」按钮和「采样历史」入口,返回按钮改为回 01;05 采样页「取消」操作改为回到 02 待取餐界面,「保存采集」同样跳转 02;05/06 返回按钮改为 history.back()(遵循从哪来回哪去);02/03 页面餐品信息区域增加点击进入 01 开餐界面的交互;02 页面补全 dishId 参数处理 + 自动人脸识别触发(站在设备前自动识别)+ 识别成功跳转 03 + 新增设置按钮(非员工拦截);03 设置按钮修复跳转 04 + 非员工拦截;06 采样历史新增 04 设置页面入口;人脸识别明确为自动触发,无需手动摄像头入口 |
|
||||
| 2026-06-12 | v2.8 | 删除 8.2 图片存储上限与覆盖策略(单菜品 50 张上限 + 相似度覆盖逻辑),仅保留 8.1 反样本机制 |
|
||||
| 2026-06-15 | v2.9 | 1.4 全局返回路径规则更新:返回按钮实现由 `history.back()` 改为三级优先级机制(URL 参数 `?referer=` → `document.referrer` → `index.html` 兜底),确保多入口页面返回路径准确 |
|
||||
|
||||
@@ -59,16 +59,16 @@
|
||||
.task-card__pack-btn:hover { background: #5ba0e9; }
|
||||
.task-card__pack-btn:disabled { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); cursor: not-allowed; }
|
||||
|
||||
/* 表格头(6列) */
|
||||
/* 表格头(8列) */
|
||||
.item-table__head {
|
||||
display: grid; grid-template-columns: 1.6fr 0.8fr 0.7fr 0.9fr 0.9fr 70px; gap: 8px;
|
||||
display: grid; grid-template-columns: 1fr 0.6fr 0.55fr 0.6fr 0.5fr 0.65fr 0.6fr 50px; gap: 8px;
|
||||
padding: 6px 16px; background: rgba(0,0,0,0.2); font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500;
|
||||
}
|
||||
.item-table__body { display: flex; flex-direction: column; }
|
||||
|
||||
/* 餐品净菜包主行 */
|
||||
.item-parent-row {
|
||||
display: grid; grid-template-columns: 1.6fr 0.8fr 0.7fr 0.9fr 0.9fr 70px; gap: 8px;
|
||||
display: grid; grid-template-columns: 1fr 0.6fr 0.55fr 0.6fr 0.5fr 0.65fr 0.6fr 50px; gap: 8px;
|
||||
padding: 10px 16px; border-top: 1px solid rgba(255,255,255,0.04); align-items: center;
|
||||
background: rgba(74,144,217,0.06);
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
/* 食材子行 */
|
||||
.item-child-row {
|
||||
display: grid; grid-template-columns: 1.6fr 0.8fr 0.7fr 0.9fr 0.9fr 70px; gap: 8px;
|
||||
display: grid; grid-template-columns: 1fr 0.6fr 0.55fr 0.6fr 0.5fr 0.65fr 0.6fr 50px; gap: 8px;
|
||||
padding: 8px 16px 8px 48px; border-top: 1px solid rgba(255,255,255,0.03);
|
||||
align-items: center; transition: background 0.2s;
|
||||
}
|
||||
@@ -166,67 +166,56 @@
|
||||
/* 当前 Tab 筛选 */
|
||||
var currentFilter = 'pending';
|
||||
|
||||
/* 模拟数据:每任务只有1个餐品净菜包 */
|
||||
/* 模拟数据:净菜包=食材名,餐品净菜包=菜名 */
|
||||
var mockData = [
|
||||
{
|
||||
orderNo: 'PO-20260616-001',
|
||||
expectedArrival: '11:30',
|
||||
taskNo: 'T-001',
|
||||
mealPack: {
|
||||
packName: '宫保鸡丁套餐',
|
||||
packSize: 3,
|
||||
notes: '少辣',
|
||||
ingredients: [
|
||||
{ name: '土豆丝', icon: '🥔', iconClass: 'child-icon--potato', weightPerPack: 500, weighed: 750 },
|
||||
{ name: '胡萝卜丝', icon: '🥕', iconClass: 'child-icon--carrot', weightPerPack: 500, weighed: 1500 },
|
||||
{ name: '黄瓜片', icon: '🥒', iconClass: 'child-icon--cucumber', weightPerPack: 500, weighed: 0 },
|
||||
{ name: '大葱丝', icon: '🧅', iconClass: 'child-icon--green-onion', weightPerPack: 500, weighed: 0 },
|
||||
{ name: '紫洋葱丝', icon: '🧅', iconClass: 'child-icon--onion', weightPerPack: 500, weighed: 0 }
|
||||
]
|
||||
}
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '土豆丝', packSize: 3, packSpec: '500g', notes: '', ingredients: [
|
||||
{ name: '土豆丝', icon: '🥔', iconClass: 'child-icon--potato', weightPerPack: 500, weighed: 750 }
|
||||
]},
|
||||
{ type: '净菜包', packName: '胡萝卜丝', packSize: 3, packSpec: '500g', notes: '', ingredients: [
|
||||
{ name: '胡萝卜丝', icon: '🥕', iconClass: 'child-icon--carrot', weightPerPack: 500, weighed: 750 }
|
||||
]},
|
||||
{ type: '净菜包', packName: '黄瓜片', packSize: 3, packSpec: '500g', notes: '', ingredients: [
|
||||
{ name: '黄瓜片', icon: '🥒', iconClass: 'child-icon--cucumber', weightPerPack: 500, weighed: 0 }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
orderNo: 'PO-20260616-002',
|
||||
expectedArrival: '12:00',
|
||||
taskNo: 'T-002',
|
||||
mealPack: {
|
||||
packName: '鱼香肉丝套餐',
|
||||
packSize: 2,
|
||||
notes: '',
|
||||
ingredients: [
|
||||
{ name: '胡萝卜丝', icon: '🥕', iconClass: 'child-icon--carrot', weightPerPack: 750, weighed: 0 },
|
||||
{ name: '白菜丝', icon: '🥬', iconClass: 'child-icon--cabbage', weightPerPack: 1000, weighed: 0 },
|
||||
{ name: '土豆丝', icon: '🥔', iconClass: 'child-icon--potato', weightPerPack: 500, weighed: 0 }
|
||||
]
|
||||
}
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '白菜丝', packSize: 2, packSpec: '1000g', notes: '', ingredients: [
|
||||
{ name: '白菜丝', icon: '🥬', iconClass: 'child-icon--cabbage', weightPerPack: 1000, weighed: 0 }
|
||||
]},
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', notes: '', ingredients: [
|
||||
{ name: '鱼香肉丝', icon: '🥘', iconClass: 'child-icon--tomato', weightPerPack: 500, weighed: 0 }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
orderNo: 'PO-20260616-003',
|
||||
expectedArrival: '14:30',
|
||||
taskNo: 'T-003',
|
||||
mealPack: {
|
||||
packName: '番茄炒蛋套餐',
|
||||
packSize: 4,
|
||||
notes: '',
|
||||
ingredients: [
|
||||
{ name: '西红柿块', icon: '🍅', iconClass: 'child-icon--tomato', weightPerPack: 750, weighed: 3000 },
|
||||
{ name: '鸡蛋', icon: '🥚', iconClass: 'child-icon--egg', weightPerPack: 500, weighed: 2000 }
|
||||
]
|
||||
}
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', notes: '', ingredients: [
|
||||
{ name: '番茄炒蛋', icon: '🥘', iconClass: 'child-icon--tomato', weightPerPack: 500, weighed: 2000 }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
orderNo: 'PO-20260616-004',
|
||||
expectedArrival: '16:00',
|
||||
taskNo: 'T-004',
|
||||
mealPack: {
|
||||
packName: '凉拌黄瓜套餐',
|
||||
packSize: 5,
|
||||
notes: '多放蒜',
|
||||
ingredients: [
|
||||
{ name: '黄瓜条', icon: '🥒', iconClass: 'child-icon--cucumber', weightPerPack: 750, weighed: 3750 },
|
||||
{ name: '小番茄', icon: '🍅', iconClass: 'child-icon--tomato', weightPerPack: 250, weighed: 1250 }
|
||||
]
|
||||
}
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '盒装', notes: '', ingredients: [
|
||||
{ name: '凉拌黄瓜', icon: '🥘', iconClass: 'child-icon--cucumber', weightPerPack: 500, weighed: 2500 }
|
||||
]}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -255,9 +244,10 @@
|
||||
var doneCount = 0;
|
||||
|
||||
mockData.forEach(function(order) {
|
||||
var pack = order.mealPack;
|
||||
var hasPending = hasPendingPack(pack);
|
||||
if (hasPending) pendingCount++; else doneCount++;
|
||||
var packs = order.mealPacks;
|
||||
/* 订单整体状态:所有餐品包都已完成才算完成 */
|
||||
var orderAllDone = packs.every(function(p) { return !hasPendingPack(p); });
|
||||
if (orderAllDone) doneCount++; else pendingCount++;
|
||||
|
||||
var taskCard = document.createElement('div');
|
||||
taskCard.className = 'task-card';
|
||||
@@ -269,61 +259,48 @@
|
||||
'<span class="task-card__order-info"><span class="task-card__order-info__label">订单号:</span><span class="task-card__order-info__value">' + order.orderNo + '</span></span>' +
|
||||
'<span class="task-card__order-info"><span class="task-card__order-info__label">任务号:</span><span class="task-card__order-info__value">' + order.taskNo + '</span></span>' +
|
||||
'<span class="task-card__arrival">⏰ 期望到货:<span class="task-card__arrival__time">' + order.expectedArrival + '</span></span>' +
|
||||
'<span class="task-card__status"><span class="status-badge ' + (hasPending ? 'status-badge--pending' : 'status-badge--done') + '">' + (hasPending ? '待配比' : '已完成') + '</span></span>' +
|
||||
(hasPending
|
||||
? '<button class="task-card__pack-btn" onclick="handleWeigh(\'' + order.taskNo + '\',\'' + order.orderNo + '\',\'' + pack.packName + '\')">去配比</button>'
|
||||
: '<button class="task-card__pack-btn" disabled>全部完成</button>');
|
||||
'<span class="task-card__status"><span class="status-badge ' + (orderAllDone ? 'status-badge--done' : 'status-badge--pending') + '">' + (orderAllDone ? '已完成' : '待配比') + '</span></span>' +
|
||||
(orderAllDone
|
||||
? '<button class="task-card__pack-btn" disabled>全部完成</button>'
|
||||
: '<button class="task-card__pack-btn" onclick="handleWeigh(\'' + order.taskNo + '\',\'' + order.orderNo + '\')">去配比</button>');
|
||||
taskCard.appendChild(header);
|
||||
|
||||
/* 表格头(6列) */
|
||||
/* 表格头(8列) */
|
||||
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>';
|
||||
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: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';
|
||||
|
||||
var packStatus = getPackStatus(pack);
|
||||
var totalNeed = 0;
|
||||
var totalWeighed = 0;
|
||||
pack.ingredients.forEach(function(ing) {
|
||||
totalNeed += ing.weightPerPack * pack.packSize;
|
||||
totalWeighed += ing.weighed;
|
||||
});
|
||||
totalNeed = Math.round(totalNeed * 100) / 100;
|
||||
totalWeighed = Math.round(totalWeighed * 100) / 100;
|
||||
var weightClass = totalWeighed >= totalNeed ? 'parent-weight--done' : '';
|
||||
packs.forEach(function(pack) {
|
||||
var packStatus = getPackStatus(pack);
|
||||
var totalNeed = 0;
|
||||
var totalWeighed = 0;
|
||||
pack.ingredients.forEach(function(ing) {
|
||||
totalNeed += ing.weightPerPack * pack.packSize;
|
||||
totalWeighed += ing.weighed;
|
||||
});
|
||||
totalNeed = Math.round(totalNeed * 100) / 100;
|
||||
totalWeighed = Math.round(totalWeighed * 100) / 100;
|
||||
var weightClass = totalWeighed >= totalNeed ? 'parent-weight--done' : '';
|
||||
|
||||
/* 主行:餐品净菜包 */
|
||||
var parentRow = document.createElement('div');
|
||||
parentRow.className = 'item-parent-row';
|
||||
parentRow.innerHTML =
|
||||
'<div class="parent-name"><div class="parent-icon">📦</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
|
||||
'<div style="text-align:center;font-size:12px;color:rgba(255,255,255,0.5)">—</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>';
|
||||
tableBody.appendChild(parentRow);
|
||||
/* 主行:餐品净菜包 */
|
||||
var parentRow = document.createElement('div');
|
||||
parentRow.className = 'item-parent-row';
|
||||
parentRow.innerHTML =
|
||||
'<div class="parent-name"><div class="parent-icon">📦</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
|
||||
'<div style="text-align:center;font-size:12px;color:' + (pack.type === '餐品净菜包' ? '#faad16' : '#4a90d9') + '">' + pack.type + '</div>' +
|
||||
'<div style="text-align:center;font-size:12px;color:rgba(255,255,255,0.55)">' + (pack.packSpec || '—') + '</div>' +
|
||||
'<div class="parent-weight" style="text-align:center">' + (pack.type === '餐品净菜包' ? '—' : pack.ingredients[0].weightPerPack + ' 克') + '</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>';
|
||||
tableBody.appendChild(parentRow);
|
||||
|
||||
/* 子行:每种食材 */
|
||||
pack.ingredients.forEach(function(ing) {
|
||||
var need = Math.round(ing.weightPerPack * pack.packSize * 100) / 100;
|
||||
var isDone = ing.weighed >= need;
|
||||
var childWeightClass = isDone ? 'child-weight--done' : '';
|
||||
|
||||
var childRow = document.createElement('div');
|
||||
childRow.className = 'item-child-row';
|
||||
childRow.innerHTML =
|
||||
'<div class="child-name"><div class="child-icon ' + ing.iconClass + '">' + ing.icon + '</div><span class="child-name__text">' + ing.name + '</span></div>' +
|
||||
'<div class="child-weight" style="text-align:center">' + ing.weightPerPack + ' 克</div>' +
|
||||
'<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>';
|
||||
tableBody.appendChild(childRow);
|
||||
});
|
||||
|
||||
taskCard.appendChild(tableBody);
|
||||
@@ -353,8 +330,8 @@
|
||||
}
|
||||
|
||||
/* 去配比 */
|
||||
function handleWeigh(taskNo, orderNo, packName) {
|
||||
var target = '02-weighing-packing.html?task=' + taskNo + '&pack=' + encodeURIComponent(packName) + '&order=' + encodeURIComponent(orderNo);
|
||||
function handleWeigh(taskNo, orderNo) {
|
||||
var target = '02-weighing-packing.html?task=' + taskNo + '&order=' + encodeURIComponent(orderNo);
|
||||
if (userName) target += '&user=' + encodeURIComponent(userName);
|
||||
window.location.href = target;
|
||||
}
|
||||
|
||||
@@ -192,47 +192,60 @@
|
||||
.task-card__item-value--blue { color: #4a90d9; }
|
||||
.task-card__item-value--purple { color: #722ed1; }
|
||||
.task-card__item-value--green { color: #52c41a; }
|
||||
.task-card__item-value--yellow { color: #faad14; }
|
||||
|
||||
/* 净菜清单表格 */
|
||||
/* 净菜清单卡片 */
|
||||
.item-list {
|
||||
background: #1a2b4a; border-radius: 10px; border: 1px solid rgba(255,255,255,0.06);
|
||||
background: transparent; border-radius: 0; border: none;
|
||||
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;
|
||||
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;
|
||||
display: none;
|
||||
}
|
||||
.item-list__body {
|
||||
flex: 1; overflow-y: auto; min-height: 0;
|
||||
flex: 1; overflow-y: auto; min-height: 0; display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
.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;
|
||||
padding: 9px 14px; font-size: 13px; align-items: center;
|
||||
border-top: 1px solid rgba(255,255,255,0.04); transition: background 0.15s;
|
||||
background: #1a2b4a; border-radius: 10px; border: 1px solid rgba(255,255,255,0.06);
|
||||
padding: 12px 14px; font-size: 13px; transition: all 0.15s;
|
||||
}
|
||||
.item-row.current { background: rgba(74,144,217,0.08); }
|
||||
.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; }
|
||||
.item-row__pack-size { font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; }
|
||||
.item-row__need { font-size: 12px; color: rgba(255,255,255,0.6); text-align: right; }
|
||||
.item-row__actual { font-weight: 600; text-align: right; }
|
||||
.item-row__actual--done { color: #52c41a; }
|
||||
.item-row__actual--progress { color: #4a90d9; }
|
||||
.item-row__actual--zero { color: rgba(255,255,255,0.3); }
|
||||
.item-row__status { font-size: 11px; text-align: center; }
|
||||
.item-row__action { text-align: center; }
|
||||
.item-row.current { border-color: rgba(74,144,217,0.4); background: rgba(74,144,217,0.08); }
|
||||
/* 卡片头部:名称 + 状态 + 操作 */
|
||||
.item-card__header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
||||
.item-row__name { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 6px; flex: 1; }
|
||||
.item-row__name-icon { font-size: 18px; }
|
||||
.item-row__status { font-size: 11px; flex-shrink: 0; }
|
||||
.item-row__action { flex-shrink: 0; }
|
||||
.item-row__clear-btn {
|
||||
padding: 2px 8px; border: 1px solid rgba(255,77,79,0.3); background: rgba(255,77,79,0.08);
|
||||
color: rgba(255,77,79,0.7); border-radius: 4px; font-size: 10px; cursor: pointer;
|
||||
padding: 3px 10px; border: 1px solid rgba(255,77,79,0.3); background: rgba(255,77,79,0.08);
|
||||
color: rgba(255,77,79,0.7); border-radius: 4px; font-size: 11px; cursor: pointer;
|
||||
transition: all 0.2s; white-space: nowrap;
|
||||
}
|
||||
.item-row__clear-btn:hover { background: rgba(255,77,79,0.2); border-color: #ff4d4f; color: #ff4d4f; }
|
||||
.item-row__clear-btn:active { transform: scale(0.95); }
|
||||
.item-row__clear-btn:disabled { opacity: 0.25; cursor: not-allowed; }
|
||||
/* 卡片元信息行 */
|
||||
.item-card__meta {
|
||||
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
|
||||
font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 6px;
|
||||
}
|
||||
.item-card__meta-item { display: flex; align-items: center; gap: 3px; }
|
||||
.item-card__meta-label { color: rgba(255,255,255,0.3); }
|
||||
.item-card__meta-value { color: rgba(255,255,255,0.55); }
|
||||
.item-card__meta-value--meal { color: #faad16; }
|
||||
/* 卡片数据行:用量/份数/需/已 */
|
||||
.item-card__data {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.item-card__data-item { display: flex; align-items: center; gap: 4px; }
|
||||
.item-card__data-label { color: rgba(255,255,255,0.35); }
|
||||
.item-card__data-value { color: rgba(255,255,255,0.7); font-weight: 500; }
|
||||
.item-card__data-value--done { color: #52c41a; }
|
||||
.item-card__data-value--progress { color: #4a90d9; }
|
||||
.item-card__data-value--zero { color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* 秤体重量显示 */
|
||||
.scale-weight {
|
||||
@@ -266,51 +279,46 @@
|
||||
cursor: not-allowed; box-shadow: none;
|
||||
}
|
||||
|
||||
/* 弹窗遮罩 */
|
||||
/* 配比确认弹窗(与净菜加工确认弹窗一致) */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.65);
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
.modal-overlay.show { display: flex; }
|
||||
|
||||
/* 弹窗卡片 */
|
||||
.modal {
|
||||
background: #1a2b4a; border-radius: 14px; border: 1px solid rgba(255,255,255,0.08);
|
||||
width: 580px; padding: 24px 28px 20px;
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
|
||||
padding: 24px 28px; width: 360px; display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
.modal__title { font-size: 16px; font-weight: 600; margin-bottom: 18px; color: #fff; }
|
||||
.modal__form { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 20px; }
|
||||
.modal__form-group { display: flex; flex-direction: column; gap: 4px; }
|
||||
.modal__form-group--full { grid-column: 1 / -1; }
|
||||
.modal__label { font-size: 11px; color: rgba(255,255,255,0.5); }
|
||||
.modal__label .required { color: #ff4d4f; margin-left: 2px; }
|
||||
.modal__input, .modal__select {
|
||||
padding: 8px 12px; background: #121832;
|
||||
border: 1px solid rgba(255,255,255,0.1); border-radius: 6px;
|
||||
color: #fff; font-size: 13px; outline: none;
|
||||
transition: border-color 0.2s;
|
||||
.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__input:focus, .modal__select:focus { border-color: #4a90d9; }
|
||||
.modal__input::placeholder { color: rgba(255,255,255,0.2); }
|
||||
/* 自定义日期选择器(三列下拉) */
|
||||
.date-picker { display: flex; gap: 6px; }
|
||||
.date-picker .modal__select { flex: 1; }
|
||||
.modal__select option { background: #1a2b4a; color: #fff; }
|
||||
.modal__actions { display: flex; gap: 10px; margin-top: 20px; justify-content: flex-end; }
|
||||
.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 {
|
||||
padding: 8px 24px; border-radius: 8px; font-size: 13px; font-weight: 500;
|
||||
cursor: pointer; border: none; transition: all 0.2s;
|
||||
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.06); color: rgba(255,255,255,0.6);
|
||||
background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.modal__btn--cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.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: #3a7bc8; }
|
||||
.modal__btn--confirm:hover { background: #5ba0e9; }
|
||||
|
||||
/* Toast 提示 */
|
||||
.toast {
|
||||
@@ -330,10 +338,10 @@
|
||||
<a class="back-btn" id="backBtn" href="javascript:void(0)">◀ 返回</a>
|
||||
<span class="page-title">配比任务</span>
|
||||
<span class="top-bar__spacer"></span>
|
||||
<span class="top-bar__pack-tag" id="packTag">餐品净菜包</span>
|
||||
<div class="user-info">
|
||||
<div class="avatar" id="userAvatar">--</div>
|
||||
<span id="topUser">--</span>
|
||||
<span class="top-bar__logout" onclick="handleLogout()" style="margin-left:12px;font-size:12px;color:rgba(255,255,255,0.4);cursor:pointer;transition:color 0.2s" onmouseover="this.style.color='#ff4d4f'" onmouseout="this.style.color='rgba(255,255,255,0.4)'">退出登录</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -408,12 +416,12 @@
|
||||
<div class="task-card__item-value" id="taskNo">--</div>
|
||||
</div>
|
||||
<div class="task-card__item">
|
||||
<div class="task-card__item-label">餐品净菜包</div>
|
||||
<div class="task-card__item-value task-card__item-value--purple" id="taskPackName">--</div>
|
||||
<div class="task-card__item-label">打包份数</div>
|
||||
<div class="task-card__item-value task-card__item-value--green" id="taskTotalPacks">--</div>
|
||||
</div>
|
||||
<div class="task-card__item">
|
||||
<div class="task-card__item-label">打包规格</div>
|
||||
<div class="task-card__item-value task-card__item-value--green" id="taskPackSize">--</div>
|
||||
<div class="task-card__item-label">订单状态</div>
|
||||
<div class="task-card__item-value" id="taskStatus">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -422,8 +430,10 @@
|
||||
<div class="item-list">
|
||||
<div class="item-list__head">
|
||||
<span>净菜名称</span>
|
||||
<span style="text-align:center">餐品净菜包名称</span>
|
||||
<span style="text-align:center">包装规格</span>
|
||||
<span style="text-align:center">每份用量(克)</span>
|
||||
<span style="text-align:center">打包规格(份)</span>
|
||||
<span style="text-align:center">打包份数</span>
|
||||
<span style="text-align:right">所需总重(克)</span>
|
||||
<span style="text-align:right">已称(克)</span>
|
||||
<span style="text-align:center">状态</span>
|
||||
@@ -444,41 +454,20 @@
|
||||
<!-- Toast 提示 -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<!-- 包装信息弹窗 -->
|
||||
<!-- 配比确认弹窗 -->
|
||||
<div class="modal-overlay" id="modalOverlay">
|
||||
<div class="modal">
|
||||
<div class="modal__title">包装信息</div>
|
||||
<div class="modal__form">
|
||||
<div class="modal__form-group">
|
||||
<div class="modal__label">包装方式<span class="required">*</span></div>
|
||||
<select class="modal__select" id="formPackMethod">
|
||||
<option value="">请选择</option>
|
||||
<option value="vacuum">真空包装</option>
|
||||
<option value="tray">托盘包装</option>
|
||||
<option value="bag">袋装</option>
|
||||
<option value="box">盒装</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal__form-group">
|
||||
<div class="modal__label">保质期至<span class="required">*</span></div>
|
||||
<div class="date-picker">
|
||||
<select class="modal__select" id="formExpiryYear"><option value="">年</option></select>
|
||||
<select class="modal__select" id="formExpiryMonth"><option value="">月</option></select>
|
||||
<select class="modal__select" id="formExpiryDay"><option value="">日</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__form-group">
|
||||
<div class="modal__label">存放温度</div>
|
||||
<input type="text" class="modal__input" id="formTemp" placeholder="如:0-4℃">
|
||||
</div>
|
||||
<div class="modal__form-group">
|
||||
<div class="modal__label">终端设备</div>
|
||||
<select class="modal__select" id="formDevice">
|
||||
<option value="">请选择</option>
|
||||
<option value="vacuum-01">真空机-01</option>
|
||||
</select>
|
||||
</div>
|
||||
<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="重新获取秤上重量">🔄</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>
|
||||
@@ -498,7 +487,6 @@
|
||||
/* ---- 读取参数 ---- */
|
||||
var userName = getUrlParam('user') || '刘九';
|
||||
var taskNo = getUrlParam('task') || 'T-001';
|
||||
var packName = getUrlParam('pack') || '宫保鸡丁套餐';
|
||||
|
||||
/* 用户信息 */
|
||||
document.getElementById('topUser').textContent = userName;
|
||||
@@ -508,30 +496,34 @@
|
||||
var taskData = {
|
||||
'T-001': {
|
||||
orderNo: 'PO-20260616-001',
|
||||
mealPacks: {
|
||||
'宫保鸡丁套餐': {
|
||||
packName: '宫保鸡丁套餐',
|
||||
packSize: 3,
|
||||
notes: '少辣',
|
||||
ingredients: [
|
||||
{ name: '土豆丝', icon: '🥔', weightPerPack: 500, weighed: 1500 },
|
||||
{ name: '胡萝卜丝', icon: '🥕', weightPerPack: 500, weighed: 1500 },
|
||||
{ name: '黄瓜片', icon: '🥒', weightPerPack: 500, weighed: 1500 },
|
||||
{ name: '大葱丝', icon: '🧅', weightPerPack: 500, weighed: 1500 },
|
||||
{ name: '紫洋葱丝', icon: '🧅', weightPerPack: 500, weighed: 1500 }
|
||||
]
|
||||
},
|
||||
'鱼香肉丝套餐': {
|
||||
packName: '鱼香肉丝套餐',
|
||||
packSize: 2,
|
||||
notes: '',
|
||||
ingredients: [
|
||||
{ name: '胡萝卜丝', icon: '🥕', weightPerPack: 750, weighed: 0 },
|
||||
{ name: '白菜丝', icon: '🥬', weightPerPack: 1000, weighed: 0 },
|
||||
{ name: '土豆丝', icon: '🥔', weightPerPack: 500, weighed: 0 }
|
||||
]
|
||||
}
|
||||
}
|
||||
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: [
|
||||
{ name: '土豆丝', icon: '🥔', weightPerPack: 300, weighed: 0 },
|
||||
{ name: '胡萝卜丝', icon: '🥕', weightPerPack: 250, weighed: 0 },
|
||||
{ name: '黄瓜片', icon: '🥒', weightPerPack: 250, weighed: 0 }
|
||||
]}
|
||||
]
|
||||
},
|
||||
'T-002': {
|
||||
orderNo: 'PO-20260616-002',
|
||||
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 }
|
||||
]}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -548,7 +540,7 @@
|
||||
|
||||
/* ---- 状态变量 ---- */
|
||||
var state = 'idle'; // idle | detecting
|
||||
var currentPack = null;
|
||||
var allIngredients = []; // 扁平化:所有包的食材 [{name, icon, weightPerPack, packName, packType, packSize, weighed}]
|
||||
var currentIngIdx = 0;
|
||||
|
||||
/* ---- 初始化 ---- */
|
||||
@@ -556,13 +548,27 @@
|
||||
var task = taskData[taskNo];
|
||||
if (!task) { showToast('任务不存在', 2000, 'error'); return; }
|
||||
|
||||
currentPack = task.mealPacks[packName];
|
||||
if (!currentPack) { 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 < currentPack.ingredients.length; i++) {
|
||||
var ing = currentPack.ingredients[i];
|
||||
var need = ing.weightPerPack * currentPack.packSize;
|
||||
for (var i = 0; i < allIngredients.length; i++) {
|
||||
var ing = allIngredients[i];
|
||||
var need = ing.weightPerPack * ing.packSize;
|
||||
if (ing.weighed < need) {
|
||||
currentIngIdx = i;
|
||||
break;
|
||||
@@ -572,49 +578,74 @@
|
||||
/* 设置页面信息 */
|
||||
document.getElementById('taskOrder').textContent = task.orderNo;
|
||||
document.getElementById('taskNo').textContent = taskNo;
|
||||
document.getElementById('packTag').textContent = currentPack.packName;
|
||||
document.getElementById('taskPackName').textContent = currentPack.packName;
|
||||
document.getElementById('taskPackSize').textContent = currentPack.packSize + ' 份';
|
||||
/* 打包份数:每个食材项对应其所属包的 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) {
|
||||
return ing.weighed >= ing.weightPerPack * ing.packSize;
|
||||
});
|
||||
var statusEl = document.getElementById('taskStatus');
|
||||
statusEl.textContent = allDone ? '已完成' : '待配比';
|
||||
statusEl.className = 'task-card__item-value' + (allDone ? ' task-card__item-value--green' : ' task-card__item-value--yellow');
|
||||
|
||||
renderItemlist();
|
||||
|
||||
/* 初始化秤体重量显示为当前食材已有重量 */
|
||||
var initIng = currentPack.ingredients[currentIngIdx];
|
||||
var initIng = allIngredients[currentIngIdx];
|
||||
document.getElementById('scaleWeightValue').textContent = initIng.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(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' : '');
|
||||
|
||||
var actualClass = 'item-row__actual--zero';
|
||||
if (isDone) actualClass = 'item-row__actual--done';
|
||||
else if (ing.weighed > 0) actualClass = 'item-row__actual--progress';
|
||||
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
|
||||
? '<span class="status-tag status-tag--done">已完成</span>'
|
||||
: '<span class="status-tag status-tag--wait">待称重</span>';
|
||||
|
||||
/* 餐品净菜包名称:净菜包不显示此行 */
|
||||
var packNameDisplay = ing.packType === '餐品净菜包' ? ing.packName : '—';
|
||||
var packSpecDisplay = ing.packSpec || '—';
|
||||
|
||||
row.innerHTML =
|
||||
'<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__need">' + need + '</div>' +
|
||||
'<div class="item-row__actual ' + actualClass + '">' + ing.weighed + '</div>' +
|
||||
'<div class="item-row__status">' + statusTag + '</div>' +
|
||||
'<div class="item-row__action">' + (ing.weighed > 0 ? '<button class="item-row__clear-btn" onclick="clearWeight(' + idx + ')">清空</button>' : '') + '</div>';
|
||||
/* 卡片头部:名称 + 状态 + 清空按钮 */
|
||||
'<div class="item-card__header">' +
|
||||
'<div class="item-row__name"><span class="item-row__name-icon">' + ing.icon + '</span>' + ing.name + '</div>' +
|
||||
'<div class="item-row__status">' + statusTag + '</div>' +
|
||||
'<div class="item-row__action">' + (ing.weighed > 0 ? '<button class="item-row__clear-btn" onclick="clearWeight(' + idx + ')">清空</button>' : '') + '</div>' +
|
||||
'</div>' +
|
||||
/* 元信息行:餐品净菜包名 + 包装规格 */
|
||||
'<div class="item-card__meta">' +
|
||||
'<div class="item-card__meta-item"><span class="item-card__meta-label">餐品净菜包:</span><span class="item-card__meta-value' + (ing.packType === '餐品净菜包' ? ' item-card__meta-value--meal' : '') + '">' + packNameDisplay + '</span></div>' +
|
||||
'<div class="item-card__meta-item"><span class="item-card__meta-label">包装规格:</span><span class="item-card__meta-value">' + packSpecDisplay + '</span></div>' +
|
||||
'</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>';
|
||||
|
||||
body.appendChild(row);
|
||||
});
|
||||
@@ -633,85 +664,30 @@
|
||||
/* 隐藏相机覆盖层 */
|
||||
document.getElementById('cameraOverlay').classList.remove('show');
|
||||
|
||||
/* 生成候选列表 */
|
||||
updateCandidates();
|
||||
|
||||
/* 模拟识别 + 称重完成(延迟后自动记录) */
|
||||
/* 模拟识别延迟后生成候选列表,等待用户点选 */
|
||||
setTimeout(function() {
|
||||
var ing = currentPack.ingredients[currentIngIdx];
|
||||
var need = ing.weightPerPack * currentPack.packSize;
|
||||
var remaining = Math.max(0, need - ing.weighed);
|
||||
var simWeight = Math.round(Math.min(remaining * 0.88, remaining) * 100) / 100;
|
||||
|
||||
ing.weighed = Math.round((ing.weighed + simWeight) * 100) / 100;
|
||||
|
||||
/* 更新秤体重量显示 */
|
||||
document.getElementById('scaleWeightValue').textContent = ing.weighed.toFixed(2);
|
||||
|
||||
/* 更新识别状态 */
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--done';
|
||||
|
||||
/* 随机取一个 AI 结果 */
|
||||
var keys = Object.keys(aiResults);
|
||||
var randomKey = keys[Math.floor(Math.random() * keys.length)];
|
||||
var result = aiResults[randomKey];
|
||||
document.getElementById('statusText').textContent = '识别完成: ' + result.name + ' — 置信度 ' + result.confidence + '%';
|
||||
|
||||
showToast('称重完成: ' + result.name + ' ' + simWeight.toFixed(2) + '克', 2000, 'success');
|
||||
|
||||
/* 刷新清单 */
|
||||
renderItemlist();
|
||||
|
||||
/* 延迟回到就绪态 */
|
||||
setTimeout(function() {
|
||||
state = 'idle';
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
|
||||
document.getElementById('statusText').textContent = '就绪,等待食材放置';
|
||||
document.getElementById('candidateList').innerHTML = '';
|
||||
document.getElementById('candidateEmpty').classList.remove('hide');
|
||||
document.getElementById('candidateTitle').textContent = '智能识别候选';
|
||||
|
||||
/* 自动跳转到下一个待称重食材 */
|
||||
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) {
|
||||
currentIngIdx = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
renderItemlist();
|
||||
|
||||
if (found) {
|
||||
/* 更新秤体显示为下一食材当前重量 */
|
||||
var nextIng = currentPack.ingredients[currentIngIdx];
|
||||
document.getElementById('scaleWeightValue').textContent = nextIng.weighed.toFixed(2);
|
||||
document.getElementById('cameraOverlay').classList.add('show');
|
||||
} else {
|
||||
document.getElementById('scaleWeightValue').textContent = '0.00';
|
||||
showToast('所有食材已称重完成', 2500, 'success');
|
||||
}
|
||||
}, 2500);
|
||||
}, 1200);
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
|
||||
document.getElementById('statusText').textContent = '识别完成,请在候选区选择食材';
|
||||
updateCandidates();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
/* ---- 更新候选食材列表 ---- */
|
||||
/* ---- 更新候选食材列表(按置信度排序) ---- */
|
||||
function updateCandidates() {
|
||||
var list = document.getElementById('candidateList');
|
||||
var empty = document.getElementById('candidateEmpty');
|
||||
var title = document.getElementById('candidateTitle');
|
||||
|
||||
var packNames = {};
|
||||
currentPack.ingredients.forEach(function(ing) { packNames[ing.name] = true; });
|
||||
allIngredients.forEach(function(ing) { packNames[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]; });
|
||||
|
||||
fromPack.sort(function() { return Math.random() - 0.5; });
|
||||
others.sort(function() { return Math.random() - 0.5; });
|
||||
/* 按置信度降序排列 */
|
||||
fromPack.sort(function(a, b) { return aiResults[b].confidence - aiResults[a].confidence; });
|
||||
others.sort(function(a, b) { return aiResults[b].confidence - aiResults[a].confidence; });
|
||||
|
||||
var selected = fromPack.slice(0, 3).concat(others.slice(0, 1));
|
||||
|
||||
@@ -720,16 +696,15 @@
|
||||
list.innerHTML = '';
|
||||
selected.forEach(function(key, i) {
|
||||
var r = aiResults[key];
|
||||
var sim = Math.floor(Math.random() * 9) + 86;
|
||||
var card = document.createElement('div');
|
||||
card.className = 'candidate-card';
|
||||
card.onclick = function() {
|
||||
showToast('已选择: ' + r.name, 1500, 'success');
|
||||
openRatioModal(r);
|
||||
};
|
||||
card.innerHTML =
|
||||
'<div class="candidate-card__img">' + r.icon + '</div>' +
|
||||
'<div class="candidate-card__name">' + r.name + '</div>' +
|
||||
'<div class="candidate-card__sim">' + sim + '%</div>';
|
||||
'<div class="candidate-card__sim">' + r.confidence + '%</div>';
|
||||
list.appendChild(card);
|
||||
});
|
||||
|
||||
@@ -745,10 +720,95 @@
|
||||
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 ing = allIngredients[currentIngIdx];
|
||||
var modalWeight = parseFloat(document.getElementById('modalWeight').textContent) || 0;
|
||||
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 + '%';
|
||||
|
||||
showToast('称重完成: ' + pendingCandidate.name + ' ' + modalWeight.toFixed(2) + '克', 2000, 'success');
|
||||
pendingCandidate = null;
|
||||
|
||||
/* 刷新清单 */
|
||||
renderItemlist();
|
||||
|
||||
/* 同步更新订单状态 */
|
||||
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';
|
||||
document.getElementById('statusText').textContent = '就绪,等待食材放置';
|
||||
document.getElementById('candidateList').innerHTML = '';
|
||||
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;
|
||||
}
|
||||
}
|
||||
renderItemlist();
|
||||
|
||||
if (found) {
|
||||
var nextIng = allIngredients[currentIngIdx];
|
||||
document.getElementById('scaleWeightValue').textContent = nextIng.weighed.toFixed(2);
|
||||
document.getElementById('cameraOverlay').classList.add('show');
|
||||
} else {
|
||||
document.getElementById('scaleWeightValue').textContent = '0.00';
|
||||
showToast('所有食材已称重完成', 2500, 'success');
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
/* ---- 配比完成 ---- */
|
||||
function handlePackDone() {
|
||||
var allDone = currentPack.ingredients.every(function(ing) {
|
||||
var need = ing.weightPerPack * currentPack.packSize;
|
||||
var allDone = allIngredients.every(function(ing) {
|
||||
var need = ing.weightPerPack * ing.packSize;
|
||||
return ing.weighed >= need;
|
||||
});
|
||||
if (!allDone) {
|
||||
@@ -763,7 +823,7 @@
|
||||
|
||||
/* ---- 清空重量 ---- */
|
||||
function clearWeight(idx) {
|
||||
var ing = currentPack.ingredients[idx];
|
||||
var ing = allIngredients[idx];
|
||||
ing.weighed = 0;
|
||||
/* 如果清空的是当前食材,同步更新秤体显示 */
|
||||
if (idx === currentIngIdx) {
|
||||
@@ -773,6 +833,13 @@
|
||||
showToast(ing.name + ' 重量已清空', 1500, 'success');
|
||||
}
|
||||
|
||||
/* ---- 退出登录 ---- */
|
||||
function handleLogout() {
|
||||
if (confirm('确定退出登录?')) {
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 重新识别 ---- */
|
||||
function handleReidentify() {
|
||||
if (state === 'detecting') return;
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# 净菜配比秤 CHANGELOG
|
||||
|
||||
## [1.3.0] - 2026-06-17
|
||||
|
||||
### 重构
|
||||
- **数据结构扁平化**:任务由"一个订单一个餐品净菜包"改为"一个订单多个净菜包/餐品净菜包"(mealPacks 数组),支持混合打包模式
|
||||
- **任务列表页(01)**:表格从6列扩展为8列,新增「净菜/餐品」和「包装规格」列;去配比不再携带 packName 参数;订单状态由所有包完成才算完成
|
||||
- **称重打包页(02)**:食材清单从表格布局改为卡片布局,每张卡片展示名称、状态、餐品净菜包归属、包装规格、用量/份数/需/已称
|
||||
- **配比确认弹窗**:AI 识别后不再自动写入重量,改为弹窗展示食材图标、名称、当前秤重,支持刷新读取,需人工点选候选食材并确认
|
||||
- **候选列表排序**:AI 识别候选按置信度降序排列(不再随机)
|
||||
|
||||
### 新增
|
||||
- 02 页顶部栏新增「退出登录」按钮
|
||||
- 02 页任务信息卡新增「订单状态」字段(待配比/已完成)
|
||||
- 02 页候选识别区点选食材后弹出配比确认弹窗
|
||||
|
||||
### 删除
|
||||
- 02 页移除包装信息弹窗(包装方式/保质期/存放温度/终端设备),统一改为配比确认弹窗
|
||||
- 02 页顶部栏移除 packTag 展示
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] - 2026-06-16
|
||||
|
||||
### 新增
|
||||
|
||||
Reference in New Issue
Block a user