Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd064dd36 | ||
|
|
c33e0c52d1 |
@@ -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)">◀ 返回</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>
|
||||
|
||||
@@ -166,62 +166,67 @@
|
||||
/* 当前 Tab 筛选 */
|
||||
var currentFilter = 'pending';
|
||||
|
||||
/* 模拟数据:一个任务包含多个餐品净菜包 */
|
||||
/* 模拟数据:每任务只有1个餐品净菜包 */
|
||||
var mockData = [
|
||||
{
|
||||
orderNo: 'PO-20260616-001',
|
||||
expectedArrival: '11:30',
|
||||
taskNo: 'T-001',
|
||||
mealPacks: [
|
||||
{
|
||||
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 }
|
||||
]
|
||||
},
|
||||
{
|
||||
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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
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 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
orderNo: 'PO-20260616-002',
|
||||
expectedArrival: '12:00',
|
||||
taskNo: 'T-002',
|
||||
mealPacks: [
|
||||
{
|
||||
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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
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 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
orderNo: 'PO-20260616-003',
|
||||
expectedArrival: '14:30',
|
||||
taskNo: 'T-003',
|
||||
mealPacks: [
|
||||
{
|
||||
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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
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 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
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 }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -231,23 +236,16 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/* 渲染任务列表 */
|
||||
function renderTaskList() {
|
||||
var container = document.getElementById('taskListContainer');
|
||||
@@ -256,74 +254,79 @@
|
||||
var pendingCount = 0;
|
||||
var doneCount = 0;
|
||||
|
||||
mockData.forEach(function(task) {
|
||||
var taskHasPending = hasPendingTask(task);
|
||||
if (taskHasPending) pendingCount++; else doneCount++;
|
||||
mockData.forEach(function(order) {
|
||||
var pack = order.mealPack;
|
||||
var hasPending = hasPendingPack(pack);
|
||||
if (hasPending) pendingCount++; else doneCount++;
|
||||
|
||||
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 style="flex:1"></span>' +
|
||||
(taskHasPending
|
||||
? '<button class="task-card__pack-btn" onclick="handleWeigh(\'' + task.taskNo + '\',\'' + firstPendingPack.packName + '\')">去打包</button>'
|
||||
'<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>');
|
||||
taskCard.appendChild(header);
|
||||
|
||||
/* 每个餐品净菜包为独立区块 */
|
||||
task.mealPacks.forEach(function(pack) {
|
||||
var packHasPending = hasPendingPack(pack);
|
||||
var packStatus = getPackStatus(pack);
|
||||
/* 表格头(6列) */
|
||||
var tableHead = document.createElement('div');
|
||||
tableHead.className = 'item-table__head';
|
||||
tableHead.innerHTML = '<div>名称</div><div style="text-align:center">每份用量</div><div style="text-align:center">打包规格</div><div style="text-align:right">所需总重</div><div style="text-align:right">已称</div><div style="text-align:center">状态</div>';
|
||||
taskCard.appendChild(tableHead);
|
||||
|
||||
/* 包标题行 */
|
||||
var packHeader = document.createElement('div');
|
||||
packHeader.className = 'item-parent-row';
|
||||
packHeader.style.borderTop = '1px solid rgba(255,255,255,0.06)';
|
||||
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 hasPendingBtn = packHasPending ? '' : ' disabled';
|
||||
packHeader.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>';
|
||||
taskCard.appendChild(packHeader);
|
||||
/* 表格体 */
|
||||
var tableBody = document.createElement('div');
|
||||
tableBody.className = 'item-table__body';
|
||||
|
||||
/* 食材子行 */
|
||||
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 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 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>';
|
||||
taskCard.appendChild(childRow);
|
||||
});
|
||||
/* 主行:餐品净菜包 */
|
||||
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);
|
||||
|
||||
/* 子行:每种食材 */
|
||||
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);
|
||||
container.appendChild(taskCard);
|
||||
});
|
||||
|
||||
@@ -339,18 +342,19 @@
|
||||
el.classList.add('active');
|
||||
|
||||
document.querySelectorAll('.task-card').forEach(function(card) {
|
||||
var anyEnabled = card.querySelector('.task-card__pack-btn:not([disabled])');
|
||||
var packBtn = card.querySelector('.task-card__pack-btn');
|
||||
var hasPending = packBtn && !packBtn.disabled;
|
||||
if (filter === 'pending') {
|
||||
card.style.display = anyEnabled ? 'block' : 'none';
|
||||
card.style.display = hasPending ? 'block' : 'none';
|
||||
} else {
|
||||
card.style.display = anyEnabled ? 'none' : 'block';
|
||||
card.style.display = hasPending ? 'none' : 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* 去打包 */
|
||||
function handleWeigh(taskNo, packName) {
|
||||
var target = '02-weighing-packing.html?task=' + taskNo + '&pack=' + encodeURIComponent(packName);
|
||||
/* 去称重 */
|
||||
function handleWeigh(taskNo, orderNo, packName) {
|
||||
var target = '02-weighing-packing.html?task=' + taskNo + '&pack=' + encodeURIComponent(packName) + '&order=' + encodeURIComponent(orderNo);
|
||||
if (userName) target += '&user=' + encodeURIComponent(userName);
|
||||
window.location.href = target;
|
||||
}
|
||||
@@ -368,7 +372,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: 0.9fr 1fr 0.6fr 0.5fr 0.6fr 0.6fr 45px 50px; gap: 6px;
|
||||
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;
|
||||
}
|
||||
@@ -209,12 +209,11 @@
|
||||
.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: 0.9fr 1fr 0.6fr 0.5fr 0.6fr 0.6fr 45px 50px; gap: 6px;
|
||||
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;
|
||||
}
|
||||
.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; }
|
||||
@@ -267,14 +266,53 @@
|
||||
cursor: not-allowed; box-shadow: none;
|
||||
}
|
||||
|
||||
/* 任务下餐品净菜包列表 */
|
||||
.task-packs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
|
||||
.task-pack-tag {
|
||||
font-size: 11px; padding: 3px 10px; border-radius: 4px;
|
||||
background: rgba(114,46,209,0.1); color: rgba(255,255,255,0.6);
|
||||
border: 1px solid rgba(114,46,209,0.2);
|
||||
/* 弹窗遮罩 */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.65);
|
||||
display: none; align-items: center; justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
.task-pack-tag--current { color: #722ed1; background: rgba(114,46,209,0.2); border-color: rgba(114,46,209,0.4); }
|
||||
.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);
|
||||
}
|
||||
.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__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__btn {
|
||||
padding: 8px 24px; border-radius: 8px; font-size: 13px; font-weight: 500;
|
||||
cursor: pointer; border: none; transition: all 0.2s;
|
||||
}
|
||||
.modal__btn--cancel {
|
||||
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.modal__btn--cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.modal__btn--confirm {
|
||||
background: #4a90d9; color: #fff;
|
||||
}
|
||||
.modal__btn--confirm:hover { background: #3a7bc8; }
|
||||
|
||||
/* 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;
|
||||
@@ -361,13 +399,21 @@
|
||||
<!-- 任务信息卡片 -->
|
||||
<div class="task-card">
|
||||
<div class="task-card__row">
|
||||
<div class="task-card__item">
|
||||
<div class="task-card__item-label">订单号</div>
|
||||
<div class="task-card__item-value task-card__item-value--blue" id="taskOrder">--</div>
|
||||
</div>
|
||||
<div class="task-card__item">
|
||||
<div class="task-card__item-label">任务号</div>
|
||||
<div class="task-card__item-value" id="taskNo">--</div>
|
||||
</div>
|
||||
<div class="task-card__item" style="flex:1">
|
||||
<div class="task-card__item">
|
||||
<div class="task-card__item-label">餐品净菜包</div>
|
||||
<div class="task-packs" id="taskPacks"></div>
|
||||
<div class="task-card__item-value task-card__item-value--purple" id="taskPackName">--</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -375,7 +421,6 @@
|
||||
<!-- 净菜清单表格 -->
|
||||
<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>
|
||||
@@ -391,7 +436,7 @@
|
||||
|
||||
<!-- 下一步按钮 -->
|
||||
<div class="next-btn-wrap">
|
||||
<button class="next-btn" id="btnNext" onclick="handlePackDone()">打包完成</button>
|
||||
<button class="next-btn" id="btnNext" onclick="handleNext()">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -399,6 +444,48 @@
|
||||
<!-- 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>
|
||||
<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) {
|
||||
@@ -420,6 +507,7 @@
|
||||
/* ---- 模拟数据 ---- */
|
||||
var taskData = {
|
||||
'T-001': {
|
||||
orderNo: 'PO-20260616-001',
|
||||
mealPacks: {
|
||||
'宫保鸡丁套餐': {
|
||||
packName: '宫保鸡丁套餐',
|
||||
@@ -460,72 +548,51 @@
|
||||
|
||||
/* ---- 状态变量 ---- */
|
||||
var state = 'idle'; // idle | detecting
|
||||
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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
var currentPack = null;
|
||||
var currentIngIdx = 0;
|
||||
|
||||
/* ---- 初始化 ---- */
|
||||
function init() {
|
||||
var task = taskData[taskNo];
|
||||
if (!task) { showToast('任务不存在', 2000, 'error'); return; }
|
||||
|
||||
rebuildAllIngredients();
|
||||
currentPack = task.mealPacks[packName];
|
||||
if (!currentPack) { showToast('餐品净菜包不存在', 2000, 'error'); return; }
|
||||
|
||||
/* 找到第一个待称重的食材(跨所有包) */
|
||||
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) {
|
||||
/* 找到第一个待称重的食材 */
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 设置页面信息 */
|
||||
document.getElementById('taskOrder').textContent = task.orderNo;
|
||||
document.getElementById('taskNo').textContent = taskNo;
|
||||
|
||||
/* 渲染该任务下所有餐品净菜包标签 */
|
||||
var packsHtml = '';
|
||||
var packKeys = Object.keys(task.mealPacks);
|
||||
packKeys.forEach(function(name) {
|
||||
var p = task.mealPacks[name];
|
||||
packsHtml += '<span class="task-pack-tag">' + p.packName + ' ×' + p.packSize + ' 份</span>';
|
||||
});
|
||||
document.getElementById('taskPacks').innerHTML = packsHtml;
|
||||
document.getElementById('packTag').textContent = packKeys.length + ' 个包';
|
||||
document.getElementById('packTag').textContent = currentPack.packName;
|
||||
document.getElementById('taskPackName').textContent = currentPack.packName;
|
||||
document.getElementById('taskPackSize').textContent = currentPack.packSize + ' 份';
|
||||
|
||||
renderItemlist();
|
||||
|
||||
/* 初始化秤体重量显示 */
|
||||
var initItem = allIngredients[currentIngIdx];
|
||||
document.getElementById('scaleWeightValue').textContent = initItem.ing.weighed.toFixed(2);
|
||||
/* 初始化秤体重量显示为当前食材已有重量 */
|
||||
var initIng = currentPack.ingredients[currentIngIdx];
|
||||
document.getElementById('scaleWeightValue').textContent = initIng.weighed.toFixed(2);
|
||||
|
||||
/* 显示相机覆盖层,等待用户手动点击识别 */
|
||||
document.getElementById('cameraOverlay').classList.add('show');
|
||||
}
|
||||
|
||||
/* ---- 渲染食材清单(所有包的所有食材) ---- */
|
||||
/* ---- 渲染食材清单 ---- */
|
||||
function renderItemlist() {
|
||||
var body = document.getElementById('itemListBody');
|
||||
body.innerHTML = '';
|
||||
|
||||
allIngredients.forEach(function(item, idx) {
|
||||
var ing = item.ing;
|
||||
var pack = item.pack;
|
||||
var need = Math.round(ing.weightPerPack * pack.packSize * 100) / 100;
|
||||
currentPack.ingredients.forEach(function(ing, idx) {
|
||||
var need = Math.round(ing.weightPerPack * currentPack.packSize * 100) / 100;
|
||||
var isDone = ing.weighed >= need;
|
||||
var isCurrent = (idx === currentIngIdx);
|
||||
|
||||
@@ -541,10 +608,9 @@
|
||||
: '<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">' + pack.packSize + ' 份</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>' +
|
||||
@@ -572,10 +638,8 @@
|
||||
|
||||
/* 模拟识别 + 称重完成(延迟后自动记录) */
|
||||
setTimeout(function() {
|
||||
var item = allIngredients[currentIngIdx];
|
||||
var ing = item.ing;
|
||||
var pack = item.pack;
|
||||
var need = ing.weightPerPack * pack.packSize;
|
||||
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;
|
||||
|
||||
@@ -607,12 +671,12 @@
|
||||
document.getElementById('candidateEmpty').classList.remove('hide');
|
||||
document.getElementById('candidateTitle').textContent = '智能识别候选';
|
||||
|
||||
/* 自动跳转到下一个待称重食材(跨所有包) */
|
||||
/* 自动跳转到下一个待称重食材 */
|
||||
var found = false;
|
||||
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) {
|
||||
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;
|
||||
@@ -622,8 +686,8 @@
|
||||
|
||||
if (found) {
|
||||
/* 更新秤体显示为下一食材当前重量 */
|
||||
var nextItem = allIngredients[currentIngIdx];
|
||||
document.getElementById('scaleWeightValue').textContent = nextItem.ing.weighed.toFixed(2);
|
||||
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';
|
||||
@@ -639,17 +703,17 @@
|
||||
var empty = document.getElementById('candidateEmpty');
|
||||
var title = document.getElementById('candidateTitle');
|
||||
|
||||
var allNames = {};
|
||||
allIngredients.forEach(function(item) { allNames[item.ing.name] = true; });
|
||||
var packNames = {};
|
||||
currentPack.ingredients.forEach(function(ing) { packNames[ing.name] = true; });
|
||||
|
||||
var keys = Object.keys(aiResults);
|
||||
var fromList = keys.filter(function(k) { return allNames[aiResults[k].name]; });
|
||||
var others = keys.filter(function(k) { return !allNames[aiResults[k].name]; });
|
||||
var fromPack = keys.filter(function(k) { return packNames[aiResults[k].name]; });
|
||||
var others = keys.filter(function(k) { return !packNames[aiResults[k].name]; });
|
||||
|
||||
fromList.sort(function() { return Math.random() - 0.5; });
|
||||
fromPack.sort(function() { return Math.random() - 0.5; });
|
||||
others.sort(function() { return Math.random() - 0.5; });
|
||||
|
||||
var selected = fromList.slice(0, 3).concat(others.slice(0, 1));
|
||||
var selected = fromPack.slice(0, 3).concat(others.slice(0, 1));
|
||||
|
||||
title.textContent = '智能识别候选 (' + selected.length + ')';
|
||||
|
||||
@@ -681,32 +745,101 @@
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
/* ---- 打包完成 ---- */
|
||||
function handlePackDone() {
|
||||
var allDone = allIngredients.every(function(item) {
|
||||
var need = item.ing.weightPerPack * item.pack.packSize;
|
||||
return item.ing.weighed >= need;
|
||||
/* ---- 日期选择器 ---- */
|
||||
function initDatePicker() {
|
||||
var yearSel = document.getElementById('formExpiryYear');
|
||||
var monthSel = document.getElementById('formExpiryMonth');
|
||||
var daySel = document.getElementById('formExpiryDay');
|
||||
var now = new Date();
|
||||
var thisYear = now.getFullYear();
|
||||
/* 年:今年起往后 3 年 */
|
||||
for (var y = thisYear; y <= thisYear + 3; y++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = y; opt.textContent = y + ' 年';
|
||||
yearSel.appendChild(opt);
|
||||
}
|
||||
/* 月:1-12 */
|
||||
for (var m = 1; m <= 12; m++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = m; opt.textContent = m + ' 月';
|
||||
monthSel.appendChild(opt);
|
||||
}
|
||||
/* 日:根据年月动态更新 */
|
||||
monthSel.onchange = function() { updateDays(); };
|
||||
yearSel.onchange = function() { updateDays(); };
|
||||
}
|
||||
function updateDays() {
|
||||
var year = parseInt(document.getElementById('formExpiryYear').value);
|
||||
var month = parseInt(document.getElementById('formExpiryMonth').value);
|
||||
var daySel = document.getElementById('formExpiryDay');
|
||||
var curVal = daySel.value;
|
||||
daySel.innerHTML = '<option value="">日</option>';
|
||||
if (!year || !month) return;
|
||||
var daysInMonth = new Date(year, month, 0).getDate();
|
||||
for (var d = 1; d <= daysInMonth; d++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = d; opt.textContent = d + ' 日';
|
||||
daySel.appendChild(opt);
|
||||
}
|
||||
if (parseInt(curVal) <= daysInMonth) daySel.value = curVal;
|
||||
}
|
||||
function resetDatePicker() {
|
||||
document.getElementById('formExpiryYear').value = '';
|
||||
document.getElementById('formExpiryMonth').value = '';
|
||||
document.getElementById('formExpiryDay').innerHTML = '<option value="">日</option>';
|
||||
}
|
||||
|
||||
/* ---- 下一步:打开包装弹窗 ---- */
|
||||
function handleNext() {
|
||||
var allDone = currentPack.ingredients.every(function(ing) {
|
||||
var need = ing.weightPerPack * currentPack.packSize;
|
||||
return ing.weighed >= need;
|
||||
});
|
||||
if (!allDone) {
|
||||
showToast('还有食材未完成称重', 2500, 'error');
|
||||
return;
|
||||
}
|
||||
showToast('打包完成', 1500, 'success');
|
||||
/* 重置弹窗字段 */
|
||||
document.getElementById('formPackMethod').value = '';
|
||||
resetDatePicker();
|
||||
document.getElementById('formTemp').value = '';
|
||||
document.getElementById('formDevice').value = '';
|
||||
document.getElementById('modalOverlay').classList.add('show');
|
||||
}
|
||||
|
||||
/* ---- 关闭弹窗 ---- */
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').classList.remove('show');
|
||||
}
|
||||
|
||||
/* ---- 确认包装 ---- */
|
||||
function confirmPack() {
|
||||
var packMethod = document.getElementById('formPackMethod').value;
|
||||
var expYear = document.getElementById('formExpiryYear').value;
|
||||
var expMonth = document.getElementById('formExpiryMonth').value;
|
||||
var expDay = document.getElementById('formExpiryDay').value;
|
||||
|
||||
/* 必填校验 */
|
||||
if (!packMethod) { showToast('请选择包装方式', 2000, 'error'); return; }
|
||||
if (!expYear || !expMonth || !expDay) { showToast('请选择保质期至', 2000, 'error'); return; }
|
||||
|
||||
closeModal();
|
||||
showToast('包装信息已确认,返回任务列表', 2500, 'success');
|
||||
setTimeout(function() {
|
||||
window.location.href = '01-packing-tasks.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
|
||||
}, 1000);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
/* ---- 清空重量 ---- */
|
||||
function clearWeight(idx) {
|
||||
var item = allIngredients[idx];
|
||||
item.ing.weighed = 0;
|
||||
var ing = currentPack.ingredients[idx];
|
||||
ing.weighed = 0;
|
||||
/* 如果清空的是当前食材,同步更新秤体显示 */
|
||||
if (idx === currentIngIdx) {
|
||||
document.getElementById('scaleWeightValue').textContent = '0.00';
|
||||
}
|
||||
renderItemlist();
|
||||
showToast(item.ing.name + ' 重量已清空', 1500, 'success');
|
||||
showToast(ing.name + ' 重量已清空', 1500, 'success');
|
||||
}
|
||||
|
||||
/* ---- 重新识别 ---- */
|
||||
@@ -750,6 +883,7 @@
|
||||
})();
|
||||
|
||||
/* ---- 启动 ---- */
|
||||
initDatePicker();
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
# 净菜配比秤 CHANGELOG
|
||||
|
||||
## [1.4.0] - 2026-06-16
|
||||
|
||||
### 新增
|
||||
- 称重页支持多餐品净菜包同时打包:清单表格新增「餐品包」列,全部包食材统一展示
|
||||
- 食材识别自动跨包跳转,一轮流程覆盖所有餐品包的待称重食材
|
||||
|
||||
### 调整
|
||||
- 任务列表「去打包」按钮移至任务头部,跟随任务而非单个餐品包
|
||||
- 页面名称全局更新:「称重」→「打包」(待打包、去打包、打包任务、打包完成)
|
||||
|
||||
---
|
||||
|
||||
## [1.3.0] - 2026-06-16
|
||||
|
||||
### 重构
|
||||
- 去除订单维度:任务列表和称重页不再包含订单号/期望到货等订单信息
|
||||
- 任务与餐品净菜包一对多:一个任务可包含多个餐品净菜包,列表页按任务+多包展示
|
||||
- 称重页任务信息区改为显示任务号 + 该任务下所有餐品包列表(名称×份数)
|
||||
- 称重页「下一步」按钮改为「打包完成」,点击直接返回列表(移除包装信息弹窗)
|
||||
|
||||
### 删除
|
||||
- 称重页包装信息弹窗及其相关样式/JS(弹窗表单、日期选择器、校验逻辑)
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] - 2026-06-16
|
||||
|
||||
### 新增
|
||||
|
||||
Reference in New Issue
Block a user