feat: 净菜验收页改为净菜加工,卡片布局+累计数据+重量确认弹窗

- 菜单页按钮「净菜验收」改名「净菜加工」
- 页面标题改为「净菜加工」,顶部栏新增「刷新」按钮
- 右列表格改为两列卡片布局,展示处理进度(斤)和进度条
- 数据模型改为累计模式:processed/target
- 新增重量确认弹窗:识别/搜索/刷新后弹窗确认本次加工重量
- 确认后重量累加到卡片 processed,支持手动修改

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhangyan
2026-06-17 09:14:56 +08:00
co-authored by Claude Opus 4.6
parent 4dd064dd36
commit 23388a8e9a
2 changed files with 223 additions and 82 deletions
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, initial-scale=1.0">
<title>净菜加工秤 - 净菜验收 | 蚁熊智能餐养平台</title>
<title>净菜加工秤 - 净菜加工 | 蚁熊智能餐养平台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -24,6 +24,13 @@
.page-title {
font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
}
.refresh-btn {
margin-left: 16px; padding: 5px 12px; font-size: 11px;
background: rgba(74,144,217,0.1); border: 1px solid rgba(74,144,217,0.25);
border-radius: 6px; color: #4a90d9; cursor: pointer;
transition: all 0.2s; font-family: inherit; display: flex; align-items: center; gap: 4px;
}
.refresh-btn:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
.user-info {
margin-left: auto; font-size: 12px; color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 6px;
}
@@ -145,36 +152,39 @@
display: flex; flex-direction: column; gap: 10px; overflow: hidden;
}
/* 净菜列表 */
.food-list {
/* 净菜卡片列表 */
.food-cards {
flex: 1; min-height: 0; overflow-y: auto; display: grid;
grid-template-columns: 1fr 1fr; gap: 10px; align-content: start;
}
.food-cards::-webkit-scrollbar { width: 3px; }
.food-cards::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 2px; }
.food-card {
background: #1a2b4a; border-radius: 10px; border: 1px solid rgba(255,255,255,0.06);
overflow: hidden; flex: 1; min-height: 0; display: flex; flex-direction: column;
padding: 14px 16px; display: flex; flex-direction: column; gap: 10px;
transition: border-color 0.2s; position: relative;
}
.food-list__head {
display: grid; grid-template-columns: 1fr 180px 80px 40px; gap: 6px;
padding: 8px 12px; background: rgba(255,255,255,0.03);
font-size: 11px; color: rgba(255,255,255,0.4); align-items: center; flex-shrink: 0;
}
.food-list__body {
flex: 1; overflow-y: auto; min-height: 0;
}
.food-list__body::-webkit-scrollbar { width: 3px; }
.food-list__body::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 2px; }
.food-row {
display: grid; grid-template-columns: 1fr 180px 80px 40px; gap: 6px;
padding: 8px 12px; font-size: 13px; align-items: center;
border-top: 1px solid rgba(255,255,255,0.04);
}
.food-row__name { font-weight: 600; font-size: 13px; }
.food-row__type { font-size: 11px; color: #40a9ff; background: rgba(24,144,255,0.12); padding: 2px 6px; border-radius: 4px; text-align: center; display: inline-block; }
.food-row__actual { color: #4a90d9; font-weight: 600; }
.food-row__delete {
width: 24px; height: 24px; border-radius: 4px; border: none;
background: rgba(255,77,79,0.1); color: rgba(255,77,79,0.7); font-size: 12px;
.food-card:hover { border-color: rgba(74,144,217,0.2); }
.food-card__header { display: flex; align-items: center; gap: 8px; }
.food-card__icon { font-size: 22px; }
.food-card__name { font-size: 14px; font-weight: 600; flex: 1; }
.food-card__delete {
width: 22px; height: 22px; border-radius: 4px; border: none;
background: rgba(255,77,79,0.1); color: rgba(255,77,79,0.7); font-size: 11px;
cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center;
margin-left: auto;
}
.food-row__delete:hover { background: rgba(255,77,79,0.25); color: #ff6b6d; }
.food-card__delete:hover { background: rgba(255,77,79,0.25); color: #ff6b6d; }
.food-card__spec { font-size: 11px; color: #40a9ff; background: rgba(24,144,255,0.12); padding: 3px 8px; border-radius: 4px; display: inline-block; align-self: flex-start; }
.food-card__progress { display: flex; flex-direction: column; gap: 4px; }
.food-card__progress-label { font-size: 11px; color: rgba(255,255,255,0.4); }
.food-card__progress-text { font-size: 12px; color: #fff; font-weight: 600; }
.food-card__progress-wrap {
height: 6px; background: rgba(255,255,255,0.08); border-radius: 3px; overflow: hidden;
}
.food-card__progress-bar {
height: 100%; background: linear-gradient(90deg, #4a90d9, #52c41a); border-radius: 3px;
transition: width 0.4s ease;
}
/* 底部操作栏 */
.bottom-bar {
@@ -229,6 +239,42 @@
}
.search-item__add:hover { background: rgba(82,196,26,0.3); }
/* 重量确认弹窗 */
.modal-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
.modal-overlay.show { display: flex; }
.modal {
background: #1a2b4a; border-radius: 14px; border: 1px solid rgba(255,255,255,0.08);
padding: 24px 28px; width: 360px; display: flex; flex-direction: column; gap: 16px;
}
.modal__title { font-size: 15px; font-weight: 600; text-align: center; }
.modal__food { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; }
.modal__food-icon { font-size: 24px; }
.modal__label { font-size: 12px; color: rgba(255,255,255,0.5); text-align: center; }
.modal__weight-input {
width: 120px; margin: 0 auto; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.15);
border-radius: 8px; padding: 10px 12px; font-size: 20px; font-weight: 700;
color: #4a90d9; text-align: center; outline: none; font-family: inherit;
}
.modal__weight-input:focus { border-color: #4a90d9; }
.modal__weight-unit { text-align: center; font-size: 12px; color: rgba(255,255,255,0.4); margin-top: -8px; }
.modal__hint { font-size: 11px; color: rgba(255,255,255,0.35); text-align: center; }
.modal__actions { display: flex; gap: 10px; }
.modal__btn {
flex: 1; padding: 10px 0; border-radius: 8px; font-size: 13px; font-weight: 600;
cursor: pointer; transition: all 0.2s; font-family: inherit; border: none;
}
.modal__btn--cancel {
background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.6);
}
.modal__btn--cancel:hover { background: rgba(255,255,255,0.12); color: #fff; }
.modal__btn--confirm {
background: #4a90d9; color: #fff;
}
.modal__btn--confirm:hover { background: #5ba0e9; }
/* Toast */
.toast {
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
@@ -248,7 +294,8 @@
<!-- 顶部导航 -->
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<span class="page-title">净菜验收识别屏</span>
<span class="page-title">净菜加工</span>
<button class="refresh-btn" onclick="handleRefresh()">&#x1F504; 刷新</button>
<div class="user-info">
<div class="avatar" id="userAvatar"></div>
<span id="userName">赵六</span>
@@ -298,13 +345,8 @@
<!-- 右列:净菜列表 -->
<div class="col-right">
<!-- 净菜验收列表 -->
<div class="food-list">
<div class="food-list__head">
<span>净菜</span><span>净菜类型</span><span>实称量</span><span>操作</span>
</div>
<div class="food-list__body" id="foodList"></div>
</div>
<!-- 净菜加工卡片列表 -->
<div class="food-cards" id="foodCards"></div>
<!-- 底部操作栏 -->
<div class="bottom-bar">
@@ -316,13 +358,31 @@
<div class="toast" id="toast"></div>
<!-- 重量确认弹窗 -->
<div class="modal-overlay" id="weightModal">
<div class="modal">
<div class="modal__title">本次加工确认</div>
<div class="modal__food">
<span class="modal__food-icon" id="modalIcon"></span>
<span id="modalFoodName">--</span>
</div>
<div class="modal__label">本次加工重量</div>
<input type="number" class="modal__weight-input" id="modalWeight" value="0.0" step="0.1" min="0">
<div class="modal__weight-unit"></div>
<div class="modal__hint" id="modalHint"></div>
<div class="modal__actions">
<button class="modal__btn modal__btn--cancel" onclick="closeWeightModal()">取消</button>
<button class="modal__btn modal__btn--confirm" onclick="confirmWeight()">确认</button>
</div>
</div>
</div>
<script>
// ---- 模拟数据 ----
// 当前任务净菜列表
// ---- 模拟数据(累计模式) ----
var mockFoods = [
{ name: '土豆丝', size: '35mm*1mm*1mm', shape: '长条', blade: '平刀', target: '5.0斤', icon: '\u{1F954}' },
{ name: '胡萝卜丝', size: '20mm*1mm*1mm', shape: '细丝', blade: '平刀', target: '2.0斤', icon: '\u{1F955}' },
{ name: '黄瓜片', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', target: '4.0斤', icon: '\u{1F952}' }
{ name: '土豆丝', size: '35mm*1mm*1mm', shape: '长条', blade: '平刀', target: 5.0, processed: 3.2, icon: '\u{1F954}' },
{ name: '胡萝卜丝', size: '20mm*1mm*1mm', shape: '细丝', blade: '平刀', target: 2.0, processed: 1.5, icon: '\u{1F955}' },
{ name: '黄瓜片', size: '30mm*30mm*3mm', shape: '圆片', blade: '平刀', target: 4.0, processed: 2.8, icon: '\u{1F952}' }
];
// 净菜食材数据库(供搜索用)
@@ -351,6 +411,7 @@
var currentUser = null;
var acceptedFoods = [];
var pendingWeightFood = null; // 待确认重量的净菜引用
// ---- URL 参数 ----
function getUrlParam(name) {
@@ -367,36 +428,70 @@
document.getElementById('userName').textContent = userName;
document.getElementById('userAvatar').textContent = userName.charAt(0);
// 初始化净菜列表(添加即带实称重量)
// 初始化净菜列表(带累计已加工重量)
acceptedFoods = mockFoods.map(function(f) {
return { name: f.name, size: f.size, shape: f.shape, blade: f.blade, target: f.target, icon: f.icon, actual: f.target, status: 'pass' };
return { name: f.name, size: f.size, shape: f.shape, blade: f.blade, target: f.target, processed: f.processed, icon: f.icon };
});
renderFoodList();
renderFoodCards();
})();
// ---- 渲染净菜列表 ----
function renderFoodList() {
var list = document.getElementById('foodList');
// ---- 渲染净菜卡片列表 ----
function renderFoodCards() {
var container = document.getElementById('foodCards');
var html = '';
acceptedFoods.forEach(function(f, idx) {
html += '<div class="food-row">'
+ '<div class="food-row__name">' + f.icon + ' ' + f.name + '</div>'
+ '<div><span class="food-row__type">' + f.size + ' ' + f.shape + ' ' + f.blade + '</span></div>'
+ '<div class="food-row__actual">' + f.actual + '</div>'
+ '<div><button class="food-row__delete" onclick="removeFood(' + idx + ')" title="删除">&#x2715;</button></div>'
var pct = f.target > 0 ? Math.min(100, (f.processed / f.target) * 100) : 0;
html += '<div class="food-card">'
+ '<div class="food-card__header">'
+ '<span class="food-card__icon">' + f.icon + '</span>'
+ '<span class="food-card__name">' + f.name + '</span>'
+ '<button class="food-card__delete" onclick="removeFood(' + idx + ')" title="删除">&#x2715;</button>'
+ '</div>'
+ '<span class="food-card__spec">' + f.size + ' · ' + f.shape + ' · ' + f.blade + '</span>'
+ '<div class="food-card__progress">'
+ '<div style="display:flex;justify-content:space-between;">'
+ '<span class="food-card__progress-label">处理进度(斤)</span>'
+ '<span class="food-card__progress-text">' + f.processed.toFixed(1) + ' / ' + f.target.toFixed(1) + '</span>'
+ '</div>'
+ '<div class="food-card__progress-wrap">'
+ '<div class="food-card__progress-bar" style="width:' + pct + '%;"></div>'
+ '</div>'
+ '</div>'
+ '</div>';
});
list.innerHTML = html;
if (html === '') {
html = '<div style="grid-column:1/-1;text-align:center;padding:40px;color:rgba(255,255,255,0.2);font-size:13px;">暂无加工净菜,点击右上角「刷新」获取</div>';
}
container.innerHTML = html;
}
// ---- 删除净菜 ----
function removeFood(idx) {
acceptedFoods.splice(idx, 1);
renderFoodList();
renderFoodCards();
showToast('已删除', 1000);
}
// ---- 模拟识别(演示用,自动逐个验收) ----
// ---- 刷新:模拟获取加工任务最新重量 ----
function handleRefresh() {
showToast('正在获取加工数据...', 800);
setTimeout(function() {
if (acceptedFoods.length === 0) {
// 无数据时重新初始化
acceptedFoods = mockFoods.map(function(f) {
return { name: f.name, size: f.size, shape: f.shape, blade: f.blade, target: f.target, processed: f.processed, icon: f.icon };
});
renderFoodCards();
showToast('加工任务已刷新', 1200);
return;
}
var f = acceptedFoods[Math.floor(Math.random() * acceptedFoods.length)];
var detectedWeight = parseFloat((Math.random() * 2 + 0.3).toFixed(1));
showWeightModal(f, detectedWeight);
}, 800);
}
// ---- 模拟识别(AI识别 → 弹窗确认重量) ----
function simulateRecognize() {
var statusDot = document.querySelector('.status-dot');
var statusText = document.getElementById('foodStatusText');
@@ -406,30 +501,49 @@
setTimeout(function() {
var f = mockFoods[Math.floor(Math.random() * mockFoods.length)];
var detectedWeight = parseFloat((Math.random() * 2 + 0.3).toFixed(1));
statusDot.className = 'status-dot status-dot--ready';
statusText.textContent = '识别完成:' + f.name;
statusText.textContent = '识别完成:' + f.name + ' (' + detectedWeight.toFixed(1) + '斤)';
showCandidate(f);
showToast(f.name + ' 识别成功', 1500);
showCandidate(f, detectedWeight);
}, 2000);
}
// ---- 显示AI识别候选 ----
function showCandidate(food) {
function showCandidate(food, detectedWeight) {
var list = document.getElementById('candidateList');
var empty = document.getElementById('candidateEmpty');
empty.style.display = 'none';
list.innerHTML = '<div class="candidate-item" onclick="addFromCandidate(\'' + food.name.replace(/'/g, "\\'") + '\')">'
list.innerHTML = '<div class="candidate-item" onclick="addFromCandidate(\'' + food.name.replace(/'/g, "\\'") + '\',' + detectedWeight + ')">'
+ '<div class="candidate-item__icon">' + food.icon + '</div>'
+ '<div class="candidate-item__info">'
+ '<div class="candidate-item__name">' + food.name + '</div>'
+ '<div class="candidate-item__conf">置信度 96% · ' + food.size + ' · ' + food.shape + ' · ' + food.blade + '</div>'
+ '</div>'
+ '<button class="candidate-item__add" onclick="event.stopPropagation();addFromCandidate(\'' + food.name.replace(/'/g, "\\'") + '\')">+</button>'
+ '<button class="candidate-item__add" onclick="event.stopPropagation();addFromCandidate(\'' + food.name.replace(/'/g, "\\'") + '\',' + detectedWeight + ')">+</button>'
+ '</div>';
}
// ---- 从AI候选添加(弹窗确认重量) ----
function addFromCandidate(name, detectedWeight) {
// 检查是否已在卡片列表中存在
var existingIdx = -1;
for (var i = 0; i < acceptedFoods.length; i++) {
if (acceptedFoods[i].name === name) { existingIdx = i; break; }
}
if (existingIdx >= 0) {
// 已存在,弹窗累加重量
showWeightModal(acceptedFoods[existingIdx], detectedWeight);
} else {
// 新净菜,添加到列表并弹窗
var food = foodDatabase.find(function(f) { return f.name === name; });
var newFood = { name: food.name, size: food.size, shape: food.shape, blade: food.blade, target: 3.0, processed: 0, icon: food.icon };
acceptedFoods.push(newFood);
renderFoodCards();
showWeightModal(newFood, detectedWeight);
}
}
// ---- 搜索处理 ----
var searchTimer = null;
function handleSearch() {
@@ -443,7 +557,6 @@
var results = foodDatabase.filter(function(f) {
return f.name.indexOf(keyword) !== -1;
});
// 排除已在列表中的净菜
var existingNames = acceptedFoods.map(function(f) { return f.name; });
results = results.filter(function(f) { return existingNames.indexOf(f.name) === -1; });
@@ -467,26 +580,17 @@
}, 200);
}
// ---- 从搜索结果添加净菜 ----
// ---- 从搜索结果添加(弹窗确认重量) ----
function addFromSearch(name) {
var food = foodDatabase.find(function(f) { return f.name === name; });
if (!food) return;
// 检查是否已存在
var exists = acceptedFoods.find(function(f) { return f.name === name; });
if (exists) { showToast(name + ' 已在列表中', 1200, 'warn'); return; }
var randomWeight = (Math.random() * 4 + 1).toFixed(1) + '斤';
acceptedFoods.push({ name: food.name, size: food.size, shape: food.shape, blade: food.blade, target: '--', icon: food.icon, actual: randomWeight, status: 'pass' });
renderFoodList();
var detectedWeight = parseFloat((Math.random() * 2 + 0.3).toFixed(1));
var newFood = { name: food.name, size: food.size, shape: food.shape, blade: food.blade, target: 3.0, processed: 0, icon: food.icon };
acceptedFoods.push(newFood);
renderFoodCards();
document.getElementById('searchInput').value = '';
document.getElementById('searchResults').innerHTML = '';
showToast(name + ' 已添加到列表', 1200);
}
// ---- 从AI候选添加净菜 ----
function addFromCandidate(name) {
var exists = acceptedFoods.find(function(f) { return f.name === name; });
if (exists) { showToast(name + ' 已在列表中', 1200, 'warn'); return; }
simulateRecognize();
showWeightModal(newFood, detectedWeight);
}
// ---- 重新识别 ----
@@ -495,18 +599,55 @@
simulateRecognize();
}
// ===== 重量确认弹窗 =====
// ---- 显示弹窗 ----
function showWeightModal(food, detectedWeight) {
pendingWeightFood = food;
document.getElementById('modalIcon').textContent = food.icon;
document.getElementById('modalFoodName').textContent = food.name;
document.getElementById('modalWeight').value = detectedWeight;
document.getElementById('modalHint').textContent = '已加工 ' + food.processed.toFixed(1) + '斤 / 应加工 ' + food.target.toFixed(1) + '斤';
document.getElementById('weightModal').classList.add('show');
setTimeout(function() {
document.getElementById('modalWeight').focus();
document.getElementById('modalWeight').select();
}, 100);
}
// ---- 关闭弹窗 ----
function closeWeightModal() {
document.getElementById('weightModal').classList.remove('show');
pendingWeightFood = null;
}
// ---- 确认重量(累加) ----
function confirmWeight() {
var weight = parseFloat(document.getElementById('modalWeight').value);
if (isNaN(weight) || weight <= 0) {
showToast('请输入有效重量', 1200, 'warn');
return;
}
if (pendingWeightFood) {
pendingWeightFood.processed += weight;
showToast(pendingWeightFood.name + ' +' + weight.toFixed(1) + '斤(累计 ' + pendingWeightFood.processed.toFixed(1) + '斤)', 2000);
}
renderFoodCards();
closeWeightModal();
}
// ---- 保存(跳转菜单页) ----
function handleSave() {
window.location.href = '01-menu.html?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
}
// ---- 保存并继续(重置页面 ----
// ---- 保存并继续(重置为初始数据 ----
function handleSaveContinue() {
showToast('保存成功', 1500);
acceptedFoods = mockFoods.map(function(f) {
return { name: f.name, size: f.size, shape: f.shape, blade: f.blade, target: f.target, icon: f.icon, actual: f.target, status: 'pass' };
return { name: f.name, size: f.size, shape: f.shape, blade: f.blade, target: f.target, processed: f.processed, icon: f.icon };
});
renderFoodList();
renderFoodCards();
document.getElementById('candidateEmpty').style.display = 'flex';
document.getElementById('candidateList').innerHTML = '';
document.getElementById('searchInput').value = '';
@@ -83,8 +83,8 @@
<a class="menu-btn" id="btnAccept" href="javascript:void(0)" onclick="handleMenu('01-accept.html')">
<div class="menu-btn__icon menu-btn__icon--blue">&#x2705;</div>
<div class="menu-btn__info">
<div class="menu-btn__title">净菜验收</div>
<div class="menu-btn__desc">净菜上秤识别、验收确认</div>
<div class="menu-btn__title">净菜加工</div>
<div class="menu-btn__desc">净菜上秤识别、加工确认</div>
</div>
<div class="menu-btn__arrow">&#x203A;</div>
</a>