feat: 任务中心页重构(统计卡片/分段Tab/动态筛选) + 仓库页新增净菜包柜

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-08-24 11:09:19 +08:00
co-authored by Claude Opus 4.7
parent d13eb783ad
commit 564dd1c1c5
3 changed files with 439 additions and 137 deletions
+11
View File
@@ -1,5 +1,16 @@
# 营养用餐管理端 CHANGELOG
## [2026-08-24] feat: 任务中心页重构(统计卡片/分段Tab/动态筛选) + 仓库页新增净菜包柜
- `sup-task-center.html` 任务中心重构:
- 顶部新增统计卡片(今日任务总数/待办任务/已完成任务/任务完成率)
- Tab 改为分段式样式,筛选栏与表头随 Tab 动态渲染
- 毛菜加工任务改为按食材明细模式(需求数量/已加工/进度/截止时间),状态新增「进行中」
- 净菜加工任务改为按食材+净菜类型的单行任务模式
- `sup-warehouse.html` 仓库管理:
- 物品类型新增「净菜包」「净菜餐品包」,存储方式/货柜类型新增「净菜包柜」
- 新增 1号净菜包柜格口数据与存取记录,货柜/物品类型标签样式统一映射(tag-purple 新增)
## [2026-08-21] feat: 溯源链路详情卡片优化(去副标题/属性独占一行/时间并入详情/去重复溯源码)
- `trace-timeline-ingredient.html` / `trace-timeline-dish.html` 溯源节点详情卡片:
+377 -92
View File
@@ -41,11 +41,22 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* Tab 切换栏 */
.tabs{display:flex;gap:8px;margin-bottom:16px;background:#fff;border-radius:var(--radius-md);padding:8px 12px;box-shadow:var(--shadow-sm);}
.tab{padding:8px 20px;border-radius:var(--radius-sm);font-size:var(--font-sm);font-weight:500;cursor:pointer;color:var(--text-secondary);transition:all .2s;border:1px solid transparent;}
.tab:hover{color:var(--primary);}
.tab.active{color:var(--primary);background:var(--primary-light);border-color:var(--primary);}
/* 统计卡片 */
.stats-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);padding:20px 24px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;}
.stat-card__icon{width:48px;height:48px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:24px;}
.stat-card__icon.blue{background:var(--primary-light);}
.stat-card__icon.green{background:#F6FFED;}
.stat-card__icon.orange{background:#FFF7E6;}
.stat-card__icon.red{background:#FFF1F0;}
.stat-card__info{display:flex;flex-direction:column;gap:4px;}
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
/* Tab 切换栏(分段式) */
.tabs{display:flex;gap:0;margin-bottom:16px;background:#F0F2F5;border-radius:var(--radius-md);padding:4px;box-shadow:var(--shadow-sm);}
.tab{padding:8px 22px;border-radius:6px;font-size:var(--font-sm);font-weight:500;cursor:pointer;color:var(--text-secondary);transition:all .2s;white-space:nowrap;}
.tab:hover{color:var(--text-primary);}
.tab.active{color:var(--primary);background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.12);font-weight:600;}
.tab__count{margin-left:6px;font-size:var(--font-xs);color:var(--text-placeholder);}
.tab.active .tab__count{color:var(--primary);}
/* 筛选栏 */
@@ -117,6 +128,19 @@ tbody tr:hover{background:#F8FBFF;}
.detail-table th{background:#FAFAFA;padding:8px 12px;text-align:left;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
.detail-table td{padding:10px 12px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
.detail-table tr:last-child td{border-bottom:none;}
.detail-table tfoot td{background:#FAFAFA;font-weight:600;border-top:1px solid var(--border);border-bottom:none;}
/* 模态框 */
.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;animation:modalFadeIn .2s ease;}
.modal-overlay.closing{animation:modalFadeOut .2s ease forwards;}
.modal{background:#fff;border-radius:var(--radius-lg);box-shadow:var(--shadow-md);width:860px;max-width:92vw;max-height:86vh;display:flex;flex-direction:column;animation:modalZoomIn .2s ease;}
.modal-overlay.closing .modal{animation:modalZoomOut .2s ease forwards;}
@keyframes modalZoomIn{from{transform:scale(.96);opacity:0}to{transform:scale(1);opacity:1}}
@keyframes modalZoomOut{from{transform:scale(1);opacity:1}to{transform:scale(.96);opacity:0}}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
.modal__close{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:var(--radius-sm);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;}
.modal__close:hover{background:#f5f5f5;color:var(--text-primary);}
.modal__body{padding:20px 24px 24px;overflow-y:auto;flex:1;}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%) translateY(-20px);background:rgba(0,0,0,0.8);color:#fff;padding:12px 28px;border-radius:8px;font-size:13px;z-index:1200;opacity:0;visibility:hidden;transition:opacity .3s,transform .3s,visibility .3s;white-space:nowrap;pointer-events:none;}
.toast.show{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0);}
@@ -172,6 +196,14 @@ tbody tr:hover{background:#F8FBFF;}
<!-- 主内容区 -->
<main class="main">
<!-- 统计卡片 -->
<div class="stats-row">
<div class="stat-card"><div class="stat-card__icon blue">📋</div><div class="stat-card__info"><div class="stat-card__value" id="statTotal">0</div><div class="stat-card__label">今日任务总数</div></div></div>
<div class="stat-card"><div class="stat-card__icon orange"></div><div class="stat-card__info"><div class="stat-card__value" id="statPending">0</div><div class="stat-card__label">待办任务</div></div></div>
<div class="stat-card"><div class="stat-card__icon green"></div><div class="stat-card__info"><div class="stat-card__value" id="statDone">0</div><div class="stat-card__label">已完成任务</div></div></div>
<div class="stat-card"><div class="stat-card__icon red">📈</div><div class="stat-card__info"><div class="stat-card__value" id="statRate">0%</div><div class="stat-card__label">任务完成率</div></div></div>
</div>
<!-- Tab 切换 -->
<div class="tabs" id="tabs">
<div class="tab active" data-idx="0" onclick="switchTab(0, this)">毛菜加工任务 <span class="tab__count" id="tabCount-0"></span></div>
@@ -180,16 +212,8 @@ tbody tr:hover{background:#F8FBFF;}
<div class="tab" data-idx="3" onclick="switchTab(3, this)">净菜包装任务 <span class="tab__count" id="tabCount-3"></span></div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item"><label>任务编号</label><input class="filter-input" type="text" id="fTaskNo" placeholder="请输入任务编号"></div>
<div class="filter-item"><label>任务状态</label><select class="filter-select" id="fStatus"></select></div>
<div class="filter-item"><label>执行人</label><input class="filter-input" type="text" id="fExecutor" placeholder="请输入执行人"></div>
<div class="filter-item"><label>生成时间</label><input class="filter-input" type="date" id="fCreatedStart"></div>
<div class="filter-item"><label></label><input class="filter-input" type="date" id="fCreatedEnd"></div>
<button class="btn btn-primary" onclick="doQuery()">查询</button>
<button class="btn btn-default" onclick="doReset()">重置</button>
</div>
<!-- 筛选栏(随 Tab 动态渲染) -->
<div class="filter-bar" id="filterBar"></div>
<!-- 操作栏 -->
<div class="action-bar">
@@ -204,9 +228,7 @@ tbody tr:hover{background:#F8FBFF;}
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th>任务编号</th><th>关联单号</th><th>任务内容摘要</th><th>任务状态</th><th>执行人</th><th>生成时间</th><th>截止时间</th><th>完成时间</th><th>操作</th>
</tr></thead>
<thead><tr id="taskThead"></tr></thead>
<tbody id="taskTbody"></tbody>
</table>
</div>
@@ -241,48 +263,47 @@ var TASK_TABS=[
{
label:'毛菜加工任务',
device:'毛菜加工秤',
statusOptions:['全部','待处理','已完成'],
mode:'raw',
statusOptions:['全部','待处理','进行中','已完成'],
tasks:[
{ taskNo:'RW-20260613-001', refNo:'', content:'黄瓜、土豆、西红柿 等3种食材', status:'待处理', statusType:'pending', executor:'', createdAt:'2026-06-13 08:00', deadline:'2026-06-13 11:00', finishedAt:'—',
{ taskNo:'RW-20260613-001', status:'已完成', statusType:'done', operator:'张三丰', device:'毛菜加工秤-01', taskDate:'2026-06-13', createdAt:'2026-06-13 08:00', finishedAt:'2026-06-13 09:45',
detail:[
{ name:'黄瓜', spec:'需 2000g', state:'已录 2000g · 截止 10:00' },
{ name:'土豆', spec:'需 2500g', state:'已录 2500g · 截止 10:30' },
{ name:'西红柿', spec:'需 1750g', state:'已录 1750g · 截止 11:00' }
{ itemNo:'MC-001', name:'黄瓜', required:20, actual:20, progress:'100%', deadline:'2026-06-13 10:00', finishedAt:'2026-06-13 09:30' },
{ itemNo:'MC-002', name:'土豆', required:25, actual:25, progress:'100%', deadline:'2026-06-13 10:30', finishedAt:'2026-06-13 09:45' },
{ itemNo:'MC-003', name:'西红柿', required:17.5, actual:17.5, progress:'100%', deadline:'2026-06-13 11:00', finishedAt:'2026-06-13 09:45' }
] },
{ taskNo:'RW-20260613-002', refNo:'—', content:'胡萝卜、小番茄 等2种食材', status:'待处理', statusType:'pending', executor:'', createdAt:'2026-06-13 08:30', deadline:'2026-06-13 14:00', finishedAt:'—',
{ taskNo:'RW-20260613-002', status:'进行中', statusType:'processing', operator:'李四', device:'毛菜加工秤-02', taskDate:'2026-06-13', createdAt:'2026-06-13 08:30', finishedAt:'—',
detail:[
{ name:'胡萝卜', spec:'需 1000g', state:'已录 1000g · 截止 11:30' },
{ name:'小番茄', spec:'需 750g', state:'已录 750g · 截止 14:00' }
{ itemNo:'MC-004', name:'胡萝卜', required:15, actual:12, progress:'80%', deadline:'2026-06-13 11:30', finishedAt:'—' },
{ itemNo:'MC-005', name:'小番茄', required:8, actual:8, progress:'100%', deadline:'2026-06-13 14:00', finishedAt:'2026-06-13 10:15' }
] },
{ taskNo:'RW-20260613-003', refNo:'—', content:'大白菜、娃娃菜、土豆 等3种食材', status:'已完成', statusType:'done', executor:'张三丰', createdAt:'2026-06-13 07:30', deadline:'2026-06-13 09:30', finishedAt:'2026-06-13 09:45',
{ taskNo:'RW-20260613-003', status:'待处理', statusType:'pending', operator:'', device:'', taskDate:'2026-06-13', createdAt:'2026-06-13 07:30', finishedAt:'',
detail:[
{ name:'大白菜', spec:'需 4000g', state:'已录 4000g' },
{ name:'娃娃菜', spec:'需 1250g', state:'已录 1250g' },
{ name:'土豆', spec:'需 1500g', state:'已录 1500g' }
{ itemNo:'MC-006', name:'大白菜', required:40, actual:0, progress:'0%', deadline:'2026-06-13 09:30', finishedAt:'' },
{ itemNo:'MC-007', name:'娃娃菜', required:12.5, actual:0, progress:'0%', deadline:'2026-06-13 10:00', finishedAt:'—' },
{ itemNo:'MC-008', name:'土豆', required:15, actual:0, progress:'0%', deadline:'2026-06-13 10:30', finishedAt:'—' }
] },
{ taskNo:'RW-20260613-004', status:'待处理', statusType:'pending', operator:'—', device:'—', taskDate:'2026-06-13', createdAt:'2026-06-13 09:00', finishedAt:'—',
detail:[
{ itemNo:'MC-009', name:'西兰花', required:20, actual:0, progress:'0%', deadline:'2026-06-13 15:00', finishedAt:'—' },
{ itemNo:'MC-010', name:'荷兰豆', required:6, actual:0, progress:'0%', deadline:'2026-06-13 15:30', finishedAt:'—' }
] }
]
},
{
label:'净菜加工任务',
device:'净菜加工秤',
mode:'clean',
statusOptions:['全部','待加工','进行中','已完成'],
tasks:[
{ taskNo:'CW-20260617-001', refNo:'—', content:'土豆、胡萝卜、黄瓜 等3种食材', status:'进行中', statusType:'processing', executor:'赵六', createdAt:'2026-06-17 08:00', deadline:'2026-06-17 11:30', finishedAt:'—',
detail:[
{ name:'土豆', spec:'35×1×1mm · 长条 · 平刀', state:'已加工 3.2/5.0斤' },
{ name:'胡萝卜', spec:'20×1×1mm · 细丝 · 平刀', state:'已加工 1.5/2.0斤' },
{ name:'黄瓜', spec:'30×30×3mm · 圆片 · 平刀', state:'已加工 2.8/4.0斤' }
] },
{ taskNo:'CW-20260617-002', refNo:'', content:'大白菜、西红柿 等2种食材', status:'待加工', statusType:'pending', executor:'', createdAt:'2026-06-17 10:00', deadline:'2026-06-17 14:00', finishedAt:'—',
detail:[
{ name:'大白菜', spec:'50×30×30mm · 宽段 · 平刀', state:'未加工' },
{ name:'西红柿', spec:'25×25×20mm · 楔形块 · 平刀', state:'未加工' }
] },
{ taskNo:'CW-20260617-003', refNo:'—', content:'芹菜、洋葱 等2种食材', status:'已完成', statusType:'done', executor:'赵六', createdAt:'2026-06-17 07:30', deadline:'2026-06-17 09:00', finishedAt:'2026-06-17 09:15',
detail:[
{ name:'芹菜', spec:'40×10×10mm · 宽段 · 平刀', state:'已加工 3.0/3.0斤' },
{ name:'洋葱', spec:'30×2×2mm · 细丝 · 平刀', state:'已加工 2.0/2.0斤' }
] }
{ taskNo:'CW-20260617-001', status:'进行中', statusType:'processing', name:'土豆', cleanType:'土豆丝.细丝', processDate:'2026-06-17', required:5, actual:3.2, progress:'64%', device:'净菜加工秤-01', updatedAt:'2026-06-17 10:30', createdAt:'2026-06-17 08:00' },
{ taskNo:'CW-20260617-002', status:'进行中', statusType:'processing', name:'胡萝卜', cleanType:'胡萝卜丝.细丝', processDate:'2026-06-17', required:2, actual:1.5, progress:'75%', device:'净菜加工秤-01', updatedAt:'2026-06-17 10:45', createdAt:'2026-06-17 08:00' },
{ taskNo:'CW-20260617-003', status:'已完成', statusType:'done', name:'黄瓜', cleanType:'黄瓜片.圆片', processDate:'2026-06-17', required:4, actual:4, progress:'100%', device:'净菜加工秤-01', updatedAt:'2026-06-17 09:30', createdAt:'2026-06-17 07:30' },
{ taskNo:'CW-20260617-004', status:'待加工', statusType:'pending', name:'大白菜', cleanType:'白菜段.宽段', processDate:'2026-06-17', required:6, actual:0, progress:'0%', device:'—', updatedAt:'—', createdAt:'2026-06-17 10:00' },
{ taskNo:'CW-20260617-005', status:'待加工', statusType:'pending', name:'西红柿', cleanType:'番茄块.楔形块', processDate:'2026-06-17', required:3, actual:0, progress:'0%', device:'—', updatedAt:'—', createdAt:'2026-06-17 10:00' },
{ taskNo:'CW-20260617-006', status:'已完成', statusType:'done', name:'芹菜', cleanType:'芹菜段.宽段', processDate:'2026-06-17', required:3, actual:3, progress:'100%', device:'净菜加工秤-01', updatedAt:'2026-06-17 09:15', createdAt:'2026-06-17 07:30' },
{ taskNo:'CW-20260617-007', status:'已完成', statusType:'done', name:'洋葱', cleanType:'洋葱丝.细丝', processDate:'2026-06-17', required:2, actual:2, progress:'100%', device:'净菜加工秤-01', updatedAt:'2026-06-17 09:15', createdAt:'2026-06-17 07:30' },
{ taskNo:'CW-20260617-008', status:'待加工', statusType:'pending', name:'土豆', cleanType:'土豆片.圆片', processDate:'2026-06-17', required:3, actual:0, progress:'0%', device:'—', updatedAt:'—', createdAt:'2026-06-17 10:30' }
]
},
{
@@ -353,36 +374,188 @@ var currentTabIdx=0;
/* ===== 渲染状态下拉选项(随 Tab 切换) ===== */
function renderStatusOptions(){
var sel=document.getElementById('fStatus');
if(!sel) return;
var opts=TASK_TABS[currentTabIdx].statusOptions;
sel.innerHTML=opts.map(function(o){return '<option>'+o+'</option>';}).join('');
}
/* ===== 各 Tab 搜索字段配置 ===== */
var FILTER_DEFS={
raw:['no','status','device','operator','date'],
clean:['no','status','date','device','name','cleanType'],
common:['no','status','operator','date']
};
/* ===== 单个搜索项 HTML ===== */
function filterFieldHtml(key){
switch(key){
case 'no': return '<div class="filter-item"><label>任务编号</label><input class="filter-input" type="text" id="fTaskNo" placeholder="请输入任务编号"></div>';
case 'status': return '<div class="filter-item"><label>任务状态</label><select class="filter-select" id="fStatus"></select></div>';
case 'device': return '<div class="filter-item"><label>加工设备</label><select class="filter-select" id="fDevice"></select></div>';
case 'date': return '<div class="filter-item"><label>任务日期</label><input class="filter-input" type="date" id="fDateStart"></div><div class="filter-item"><label>至</label><input class="filter-input" type="date" id="fDateEnd"></div>';
case 'name': return '<div class="filter-item"><label>食材名称</label><select class="filter-select" id="fName" onchange="onNameChange()"></select></div>';
case 'cleanType': return '<div class="filter-item"><label>净菜类型</label><select class="filter-select" id="fCleanType"></select></div>';
case 'operator': return '<div class="filter-item"><label>操作人</label><input class="filter-input" type="text" id="fOperator" placeholder="请输入操作人"></div>';
}
return '';
}
/* ===== 渲染搜索区(随 Tab 切换) ===== */
function renderFilterBar(){
var tab=TASK_TABS[currentTabIdx];
var mode = tab.mode==='raw' ? 'raw' : (tab.mode==='clean' ? 'clean' : 'common');
var keys=FILTER_DEFS[mode];
var html=keys.map(filterFieldHtml).join('')
+'<button class="btn btn-primary" onclick="doQuery()">查询</button>'
+'<button class="btn btn-default" onclick="doReset()">重置</button>';
document.getElementById('filterBar').innerHTML=html;
renderStatusOptions();
renderFilterSelects();
}
/* ===== 从当前 Tab 数据提取去重值 ===== */
function distinctValues(field){
var tab=TASK_TABS[currentTabIdx];
var arr=[];
tab.tasks.forEach(function(t){
if(t[field] && t[field]!=='—' && arr.indexOf(t[field])===-1) arr.push(t[field]);
});
return arr;
}
/* ===== 填充下拉框选项(首项「全部」) ===== */
function fillSelect(id, options){
var sel=document.getElementById(id);
if(!sel) return;
sel.innerHTML=['全部'].concat(options).map(function(o){return '<option>'+o+'</option>';}).join('');
}
/* ===== 净菜类型下拉框联动:按选中食材过滤 ===== */
function updateCleanTypeOptions(){
var nameSel=document.getElementById('fName');
var typeSel=document.getElementById('fCleanType');
if(!typeSel) return;
var tab=TASK_TABS[currentTabIdx];
var selectedName = nameSel ? nameSel.value : '全部';
var opts=['全部'];
tab.tasks.forEach(function(t){
if(selectedName==='全部' || t.name===selectedName){
if(opts.indexOf(t.cleanType)===-1) opts.push(t.cleanType);
}
});
typeSel.innerHTML=opts.map(function(o){return '<option>'+o+'</option>';}).join('');
}
/* ===== 食材名称变化 → 联动净菜类型 ===== */
function onNameChange(){
updateCleanTypeOptions();
}
/* ===== 渲染搜索下拉框(设备/食材/净菜类型) ===== */
function renderFilterSelects(){
fillSelect('fDevice', distinctValues('device'));
fillSelect('fName', distinctValues('name'));
updateCleanTypeOptions();
}
/* ===== 安全读取输入值 ===== */
function val(id){
var el=document.getElementById(id);
return el ? el.value.trim() : '';
}
/* ===== 状态标签 HTML ===== */
function statusTag(statusType,text){
var cls=STATUS_TAG[statusType]||'tag-gray';
return '<span class="tag '+cls+'">'+text+'</span>';
}
/* ===== 重量格式化 ===== */
function fmtKg(kg){ return kg+'kg'; }
/* ===== 时间格式化(仅时分) ===== */
function fmtTime(s){ return (s && s!=='—') ? s.split(' ')[1] : '—'; }
/* ===== 渲染表头(随 Tab 切换) ===== */
function renderThead(){
var tab=TASK_TABS[currentTabIdx];
var th;
if(tab.mode==='raw'){
th=['序号','任务编号','任务状态','加工总量(kg)','任务明细','操作人','加工设备','任务日期','完成时间','创建时间','操作'];
}else if(tab.mode==='clean'){
th=['序号','任务编号','任务状态','食材名称','净菜类型','加工日期','需求量','已加工重量','进度','加工设备','最后更新时间','创建时间','操作'];
}else{
th=['任务编号','关联单号','任务内容摘要','任务状态','操作人','生成时间','截止时间','完成时间','操作'];
}
document.getElementById('taskThead').innerHTML=th.map(function(h){return '<th>'+h+'</th>';}).join('');
}
/* ===== 毛菜加工任务行 ===== */
function rawRow(t,i){
var vegCount=t.detail.length;
var totalKg=t.detail.reduce(function(s,d){return s+d.required;},0);
return '<tr>'
+'<td>'+(i+1)+'</td>'
+'<td>'+t.taskNo+'</td>'
+'<td>'+statusTag(t.statusType,t.status)+'</td>'
+'<td>'+fmtKg(totalKg)+'</td>'
+'<td><span class="link" onclick="showDetailList(\''+t.taskNo+'\')">'+vegCount+'项</span></td>'
+'<td>'+t.operator+'</td>'
+'<td>'+t.device+'</td>'
+'<td>'+t.taskDate+'</td>'
+'<td>'+t.finishedAt+'</td>'
+'<td>'+t.createdAt+'</td>'
+'<td class="td-actions"><span class="link" onclick="showDetail(\''+t.taskNo+'\')">详情</span></td>'
+'</tr>';
}
/* ===== 净菜加工任务行 ===== */
function cleanRow(t,i){
return '<tr>'
+'<td>'+(i+1)+'</td>'
+'<td>'+t.taskNo+'</td>'
+'<td>'+statusTag(t.statusType,t.status)+'</td>'
+'<td>'+t.name+'</td>'
+'<td>'+t.cleanType+'</td>'
+'<td>'+t.processDate+'</td>'
+'<td>'+fmtKg(t.required)+'</td>'
+'<td>'+(t.actual>0?fmtKg(t.actual):'—')+'</td>'
+'<td>'+t.progress+'</td>'
+'<td>'+t.device+'</td>'
+'<td>'+(t.updatedAt||'—')+'</td>'
+'<td>'+t.createdAt+'</td>'
+'<td class="td-actions"><span class="link" onclick="showDetail(\''+t.taskNo+'\')">详情</span></td>'
+'</tr>';
}
/* ===== 通用任务行 ===== */
function commonRow(t){
return '<tr>'
+'<td>'+t.taskNo+'</td>'
+'<td>'+t.refNo+'</td>'
+'<td>'+t.content+'</td>'
+'<td>'+statusTag(t.statusType,t.status)+'</td>'
+'<td>'+t.executor+'</td>'
+'<td>'+t.createdAt+'</td>'
+'<td>'+t.deadline+'</td>'
+'<td>'+t.finishedAt+'</td>'
+'<td class="td-actions"><span class="link" onclick="showDetail(\''+t.taskNo+'\')">详情</span></td>'
+'</tr>';
}
/* ===== 渲染行(按模式分发) ===== */
function renderRow(t,i){
var tab=TASK_TABS[currentTabIdx];
if(tab.mode==='raw') return rawRow(t,i);
if(tab.mode==='clean') return cleanRow(t,i);
return commonRow(t);
}
/* ===== 渲染表格 ===== */
function renderTable(){
renderThead();
var tab=TASK_TABS[currentTabIdx];
var tbody=document.getElementById('taskTbody');
var html='';
tab.tasks.forEach(function(t){
html+='<tr>'
+'<td>'+t.taskNo+'</td>'
+'<td>'+t.refNo+'</td>'
+'<td>'+t.content+'</td>'
+'<td>'+statusTag(t.statusType,t.status)+'</td>'
+'<td>'+t.executor+'</td>'
+'<td>'+t.createdAt+'</td>'
+'<td>'+t.deadline+'</td>'
+'<td>'+t.finishedAt+'</td>'
+'<td class="td-actions"><span class="link" onclick="showDetail(\''+t.taskNo+'\')">详情</span></td>'
+'</tr>';
});
tbody.innerHTML=html;
tbody.innerHTML=tab.tasks.map(renderRow).join('');
document.getElementById('tableTitle').textContent=tab.label;
document.getElementById('pageInfo').textContent='显示 '+tab.tasks.length+' 条';
}
@@ -394,57 +567,83 @@ function renderTabCounts(){
});
}
/* ===== 渲染顶部统计卡片(全局汇总,不随 Tab 变化) ===== */
function renderStats(){
var total=0, done=0;
TASK_TABS.forEach(function(tab){
total+=tab.tasks.length;
tab.tasks.forEach(function(t){
if(t.statusType==='done') done++;
});
});
var pending=total-done;
var rate=total ? Math.round(done/total*100) : 0;
document.getElementById('statTotal').textContent=total;
document.getElementById('statPending').textContent=pending;
document.getElementById('statDone').textContent=done;
document.getElementById('statRate').textContent=rate+'%';
}
/* ===== 切换 Tab ===== */
function switchTab(idx,el){
currentTabIdx=idx;
document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active');});
el.classList.add('active');
renderStatusOptions();
renderFilterBar();
doReset();
renderTable();
}
/* ===== 查询(简单前端过滤:任务编号/执行人关键字 + 状态精确匹配 ===== */
/* ===== 查询(各 Tab 独立过滤字段 ===== */
function doQuery(){
var tab=TASK_TABS[currentTabIdx];
var kwNo=document.getElementById('fTaskNo').value.trim();
var kwExe=document.getElementById('fExecutor').value.trim();
var status=document.getElementById('fStatus').value;
var mode = tab.mode==='raw' ? 'raw' : (tab.mode==='clean' ? 'clean' : 'common');
var kwNo=val('fTaskNo');
var status=val('fStatus');
var kwDevice=val('fDevice');
var kwOperator=val('fOperator');
var kwName=val('fName');
var kwCleanType=val('fCleanType');
var dateStart=val('fDateStart');
var dateEnd=val('fDateEnd');
var list=tab.tasks.filter(function(t){
if(kwNo && t.taskNo.indexOf(kwNo)===-1) return false;
if(kwExe && t.executor.indexOf(kwExe)===-1) return false;
if(status && status!=='全部' && t.status!==status) return false;
if(kwDevice && kwDevice!=='全部' && t.device!==kwDevice) return false;
if(mode==='raw'){
if(kwOperator && (t.operator||'').indexOf(kwOperator)===-1) return false;
if(dateStart && t.taskDate < dateStart) return false;
if(dateEnd && t.taskDate > dateEnd) return false;
}else if(mode==='clean'){
if(kwName && kwName!=='全部' && t.name!==kwName) return false;
if(kwCleanType && kwCleanType!=='全部' && t.cleanType!==kwCleanType) return false;
if(dateStart && t.processDate < dateStart) return false;
if(dateEnd && t.processDate > dateEnd) return false;
}else{
if(kwOperator && (t.executor||'').indexOf(kwOperator)===-1) return false;
if(dateStart && t.createdAt.slice(0,10) < dateStart) return false;
if(dateEnd && t.createdAt.slice(0,10) > dateEnd) return false;
}
return true;
});
var tbody=document.getElementById('taskTbody');
var html='';
list.forEach(function(t){
html+='<tr>'
+'<td>'+t.taskNo+'</td>'
+'<td>'+t.refNo+'</td>'
+'<td>'+t.content+'</td>'
+'<td>'+statusTag(t.statusType,t.status)+'</td>'
+'<td>'+t.executor+'</td>'
+'<td>'+t.createdAt+'</td>'
+'<td>'+t.deadline+'</td>'
+'<td>'+t.finishedAt+'</td>'
+'<td class="td-actions"><span class="link" onclick="showDetail(\''+t.taskNo+'\')">详情</span></td>'
+'</tr>';
});
tbody.innerHTML=html;
tbody.innerHTML=list.map(renderRow).join('');
document.getElementById('pageInfo').textContent='显示 '+list.length+' 条';
}
/* ===== 重置 ===== */
function doReset(){
document.getElementById('fTaskNo').value='';
document.getElementById('fExecutor').value='';
document.getElementById('fCreatedStart').value='';
document.getElementById('fCreatedEnd').value='';
var sel=document.getElementById('fStatus');
sel.selectedIndex=0;
['fTaskNo','fOperator','fDateStart','fDateEnd'].forEach(function(id){
var el=document.getElementById(id);
if(el) el.value='';
});
['fStatus','fDevice','fName','fCleanType'].forEach(function(id){
var el=document.getElementById(id);
if(el) el.selectedIndex=0;
});
updateCleanTypeOptions();
renderTable();
}
@@ -485,18 +684,77 @@ function findTask(taskNo){
return null;
}
/* ===== 毛菜加工食材明细表(任务明细弹窗与详情抽屉共用) ===== */
function renderRawDetailTable(t){
var rows='';var totalReq=0,totalActual=0;
t.detail.forEach(function(d){
totalReq+=d.required; totalActual+=d.actual||0;
rows+='<tr>'
+'<td>'+d.itemNo+'</td>'
+'<td>'+d.name+'</td>'
+'<td>'+fmtKg(d.required)+'</td>'
+'<td>'+(d.actual>0?fmtKg(d.actual):'—')+'</td>'
+'<td>'+d.progress+'</td>'
+'<td>'+fmtTime(d.deadline)+'</td>'
+'<td>'+fmtTime(d.finishedAt)+'</td>'
+'</tr>';
});
return '<table class="detail-table">'
+'<thead><tr><th>食材编号</th><th>食材名称</th><th>需求量</th><th>实际加工量</th><th>进度</th><th>截止时间</th><th>完成时间</th></tr></thead>'
+'<tbody>'+rows+'</tbody>'
+'<tfoot><tr><td>总计</td><td>'+t.detail.length+'种</td><td>'+fmtKg(totalReq)+'</td><td>'+(totalActual>0?fmtKg(totalActual):'—')+'</td><td>—</td><td>—</td><td>—</td></tr></tfoot>'
+'</table>';
}
function showDetail(taskNo){
var r=findTask(taskNo);
if(!r) return;
var tab=r.tab, t=r.task;
/* 毛菜加工任务:任务信息 + 食材列表 */
if(tab.mode==='raw'){
var rawFields=
'<div class="drawer-field"><div class="drawer-field__label">任务编号</div><div class="drawer-field__value">'+t.taskNo+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">任务状态</div><div class="drawer-field__value">'+statusTag(t.statusType,t.status)+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">操作人</div><div class="drawer-field__value">'+t.operator+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">加工设备</div><div class="drawer-field__value">'+t.device+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">任务日期</div><div class="drawer-field__value">'+t.taskDate+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">创建时间</div><div class="drawer-field__value">'+t.createdAt+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">完成时间</div><div class="drawer-field__value">'+t.finishedAt+'</div></div>';
var rawHtml='<div class="drawer-section"><div class="drawer-section__title">任务信息</div><div class="drawer-fields">'+rawFields+'</div></div>'
+'<div class="drawer-section"><div class="drawer-section__title">食材列表</div>'+renderRawDetailTable(t)+'</div>';
Drawer.open('任务详情 — '+t.taskNo, rawHtml);
return;
}
/* 净菜加工任务:任务信息 */
if(tab.mode==='clean'){
var cleanFields=
'<div class="drawer-field"><div class="drawer-field__label">任务编号</div><div class="drawer-field__value">'+t.taskNo+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">任务状态</div><div class="drawer-field__value">'+statusTag(t.statusType,t.status)+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">食材名称</div><div class="drawer-field__value">'+t.name+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">净菜类型</div><div class="drawer-field__value">'+t.cleanType+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">加工日期</div><div class="drawer-field__value">'+t.processDate+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">需求量</div><div class="drawer-field__value">'+fmtKg(t.required)+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">已加工重量</div><div class="drawer-field__value">'+(t.actual>0?fmtKg(t.actual):'—')+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">进度</div><div class="drawer-field__value">'+t.progress+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">加工设备</div><div class="drawer-field__value">'+t.device+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">最后更新时间</div><div class="drawer-field__value">'+(t.updatedAt||'—')+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">创建时间</div><div class="drawer-field__value">'+t.createdAt+'</div></div>';
Drawer.open('任务详情 — '+t.taskNo, '<div class="drawer-section"><div class="drawer-section__title">任务信息</div><div class="drawer-fields">'+cleanFields+'</div></div>');
return;
}
/* 任务信息区 */
var fields=
'<div class="drawer-field"><div class="drawer-field__label">任务编号</div><div class="drawer-field__value">'+t.taskNo+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">终端设备</div><div class="drawer-field__value">'+tab.device+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">关联单号</div><div class="drawer-field__value">'+t.refNo+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">任务状态</div><div class="drawer-field__value">'+statusTag(t.statusType,t.status)+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">执行人</div><div class="drawer-field__value">'+t.executor+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">操作人</div><div class="drawer-field__value">'+t.executor+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">生成时间</div><div class="drawer-field__value">'+t.createdAt+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">截止时间</div><div class="drawer-field__value">'+t.deadline+'</div></div>'
+'<div class="drawer-field"><div class="drawer-field__label">完成时间</div><div class="drawer-field__value">'+t.finishedAt+'</div></div>';
@@ -514,6 +772,32 @@ function showDetail(taskNo){
Drawer.open('任务详情 — '+t.taskNo, html);
}
/* ===== 任务明细弹窗(毛菜加工) ===== */
function showDetailList(taskNo){
var r=findTask(taskNo);
if(!r) return;
var t=r.task;
Modal.open('任务明细 — '+t.taskNo, renderRawDetailTable(t));
}
/* ===== 模态框 ===== */
var Modal={
open:function(title,bodyHtml){
this.close();
var overlay=document.createElement('div');
overlay.className='modal-overlay';
overlay.innerHTML='<div class="modal"><div class="modal__header"><div class="modal__title">'+title+'</div><button class="modal__close" onclick="Modal.close()">&times;</button></div><div class="modal__body">'+bodyHtml+'</div></div>';
overlay.addEventListener('click',function(e){if(e.target===overlay)Modal.close();});
document.body.appendChild(overlay);
},
close:function(){
var o=document.querySelector('.modal-overlay');
if(!o)return;
o.classList.add('closing');
setTimeout(function(){o.remove();},200);
}
};
/* ===== Toast ===== */
function showToast(msg){
var el=document.getElementById('toast');
@@ -524,7 +808,8 @@ function showToast(msg){
}
/* ===== 初始化 ===== */
renderStatusOptions();
renderStats();
renderFilterBar();
renderTabCounts();
renderTable();
</script>
+51 -45
View File
@@ -79,6 +79,7 @@ tbody tr:hover{background:#F8FBFF;}
.tag-orange{background:#FFF7E6;color:var(--warning);}
.tag-red{background:#FFF1F0;color:var(--danger);}
.tag-gray{background:#F5F5F5;color:#999;}
.tag-purple{background:#F9F0FF;color:#722ED1;}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;}
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
@@ -268,10 +269,10 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<div class="filter-bar">
<div class="filter-item"><label>食材名称</label><input class="filter-input" placeholder="请输入食材名称"></div>
<div class="filter-item"><label>物品类型</label>
<select class="filter-select"><option>全部</option><option>毛菜</option><option>净菜</option></select>
<select class="filter-select"><option>全部</option><option>毛菜</option><option>净菜</option><option>净菜包</option><option>净菜餐品包</option></select>
</div>
<div class="filter-item"><label>存储方式</label>
<select class="filter-select" id="storageTypeFilter" onchange="updateAreaOptions()"><option>全部</option><option>毛菜柜</option><option>净菜柜</option><option>仓库</option></select>
<select class="filter-select" id="storageTypeFilter" onchange="updateAreaOptions()"><option>全部</option><option>毛菜柜</option><option>净菜柜</option><option>净菜包柜</option><option>仓库</option></select>
</div>
<div class="filter-item"><label>所在区域</label>
<select class="filter-select" id="areaFilter"></select>
@@ -302,11 +303,13 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<tr><td>猪里脊</td><td><span class="tag tag-blue">净菜</span></td><td>IN-20260415-003</td><td><span class="tag tag-blue">净菜柜</span></td><td>3号净菜柜-C02格口</td><td>80</td><td>40</td><td>40</td><td>2026-04-15 09:15</td><td>2026-04-19</td><td><span class="tag tag-orange">临期</span></td><td><span class="tag tag-orange">不足</span></td><td class="td-actions"><span class="link">编辑</span> <span class="link" onclick="showStockDetail(this)">详情</span></td></tr>
<tr><td>草鱼</td><td><span class="tag tag-green">毛菜</span></td><td>IN-20260415-004</td><td><span class="tag tag-gray">仓库</span></td><td>C仓-水产区-01</td><td>60</td><td>0</td><td>60</td><td>2026-04-15 09:45</td><td>2026-04-15</td><td><span class="tag tag-red">已过期</span></td><td><span class="tag tag-orange">不足</span></td><td class="td-actions"><span class="link">编辑</span> <span class="link" onclick="showStockDetail(this)">详情</span></td></tr>
<tr><td>胡萝卜</td><td><span class="tag tag-green">毛菜</span></td><td>IN-20260416-005</td><td><span class="tag tag-gray">仓库</span></td><td>A仓-蔬菜区-03</td><td>420</td><td>0</td><td>420</td><td>2026-04-16 11:00</td><td>2026-04-23</td><td><span class="tag tag-green">正常</span></td><td><span class="tag tag-green">充足</span></td><td class="td-actions"><span class="link">编辑</span> <span class="link" onclick="showStockDetail(this)">详情</span></td></tr>
<tr><td>净土豆丝净菜包</td><td><span class="tag tag-orange">净菜包</span></td><td>IN-20260422-031</td><td><span class="tag tag-orange">净菜包柜</span></td><td>1号净菜包柜-A02格口</td><td>25</td><td>10</td><td>15</td><td>2026-04-22 09:15</td><td>2026-04-25</td><td><span class="tag tag-green">正常</span></td><td><span class="tag tag-green">充足</span></td><td class="td-actions"><span class="link">编辑</span> <span class="link" onclick="showStockDetail(this)">详情</span></td></tr>
<tr><td>土豆烧肉餐品包</td><td><span class="tag tag-purple">净菜餐品包</span></td><td>IN-20260422-032</td><td><span class="tag tag-orange">净菜包柜</span></td><td>1号净菜包柜-B01格口</td><td>18</td><td>6</td><td>12</td><td>2026-04-22 10:30</td><td>2026-04-24</td><td><span class="tag tag-orange">临期</span></td><td><span class="tag tag-green">充足</span></td><td class="td-actions"><span class="link">编辑</span> <span class="link" onclick="showStockDetail(this)">详情</span></td></tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>显示 5 条,共 18</span>
<span>显示 7 条,共 20</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
@@ -414,7 +417,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<div class="filter-bar">
<div class="filter-item"><label>设备编号/名称</label><input class="filter-input" placeholder="请输入设备编号或名称"></div>
<div class="filter-item"><label>货柜类型</label>
<select class="filter-select"><option>全部</option><option>净菜柜</option><option>毛菜柜</option></select>
<select class="filter-select"><option>全部</option><option>净菜柜</option><option>毛菜柜</option><option>净菜包柜</option></select>
</div>
<div class="filter-item"><label>设备状态</label>
<select class="filter-select"><option>全部</option><option>在线</option><option>离线</option><option>故障</option></select>
@@ -489,6 +492,7 @@ function switchTab(idx){
var storageAreaOptions={
'毛菜柜':['8号柜','9号柜','10号柜'],
'净菜柜':['3号净菜柜','5号净菜柜','6号净菜柜'],
'净菜包柜':['1号净菜包柜'],
'仓库':['A仓-蔬菜区-01','A仓-蔬菜区-02','A仓-蔬菜区-03','A仓-蔬菜区-04','B仓-肉类区-01','B仓-肉类区-02','B仓-肉类区-03','C仓-水产区-01','C仓-水产区-02']
};
function updateAreaOptions(){
@@ -497,7 +501,7 @@ function updateAreaOptions(){
if(!storageSelect||!areaSelect)return;
var storageType=storageSelect.value;
var options=storageType==='全部'
? storageAreaOptions['毛菜柜'].concat(storageAreaOptions['净菜柜'],storageAreaOptions['仓库'])
? storageAreaOptions['毛菜柜'].concat(storageAreaOptions['净菜柜'],storageAreaOptions['净菜包柜'],storageAreaOptions['仓库'])
: storageAreaOptions[storageType]||[];
areaSelect.innerHTML='<option>全部</option>'+options.map(function(item){return '<option>'+item+'</option>';}).join('');
}
@@ -662,21 +666,6 @@ var CABINETS=[
{id:'NC3-10',status:'idle'}
]
},
{
code:'CAB-NC-002',name:'5号净菜柜',cabinetType:'净菜柜',status:'在线',totalSlots:10,
slots:[
{id:'NC5-01',status:'occupied',itemName:'净黄瓜片',itemType:'净菜',qty:7.4,op:'李主管',inTime:'2026-04-22 10:05'},
{id:'NC5-02',status:'occupied',itemName:'净西红柿',itemType:'净菜',qty:12.0,op:'张厨师',inTime:'2026-04-22 10:15'},
{id:'NC5-03',status:'occupied',itemName:'净洋葱',itemType:'净菜',qty:3.2,op:'王厨师',inTime:'2026-04-22 08:40'},
{id:'NC5-04',status:'occupied',itemName:'净青椒丝',itemType:'净菜',qty:6.6,op:'张厨师',inTime:'2026-04-22 11:00'},
{id:'NC5-05',status:'occupied',itemName:'净豆角',itemType:'净菜',qty:9.0,op:'李主管',inTime:'2026-04-22 11:10'},
{id:'NC5-06',status:'occupied',itemName:'净藕片',itemType:'净菜',qty:4.8,op:'王厨师',inTime:'2026-04-22 11:20'},
{id:'NC5-07',status:'idle'},
{id:'NC5-08',status:'idle'},
{id:'NC5-09',status:'idle'},
{id:'NC5-10',status:'idle'}
]
},
{
code:'CAB-RW-008',name:'8号毛菜柜',cabinetType:'毛菜柜',status:'在线',totalSlots:8,
slots:[
@@ -691,16 +680,16 @@ var CABINETS=[
]
},
{
code:'CAB-RW-009',name:'9号毛菜柜',cabinetType:'毛菜柜',status:'线',totalSlots:8,
code:'CAB-NP-001',name:'1号净菜包柜',cabinetType:'净菜包柜',status:'线',totalSlots:8,
slots:[
{id:'RW9-A1',status:'offline',itemName:'蒜薹',itemType:'毛菜',qty:55.0,op:'张仓管',inTime:'2026-04-15 14:00'},
{id:'RW9-A2',status:'offline',itemName:'韭菜',itemType:'毛菜',qty:38.0,op:'张仓管',inTime:'2026-04-15 14:20'},
{id:'RW9-B1',status:'offline'},
{id:'RW9-B2',status:'offline'},
{id:'RW9-C1',status:'offline'},
{id:'RW9-C2',status:'offline'},
{id:'RW9-C3',status:'offline'},
{id:'RW9-D1',status:'offline'}
{id:'NP1-A1',status:'occupied',itemName:'净土豆丝净菜包',itemType:'净菜包',qty:15.0,op:'张厨师',inTime:'2026-04-22 09:15'},
{id:'NP1-A2',status:'occupied',itemName:'净青椒丝净菜包',itemType:'净菜包',qty:12.5,op:'李主管',inTime:'2026-04-22 09:30'},
{id:'NP1-B1',status:'occupied',itemName:'土豆烧肉餐品包',itemType:'净菜餐品包',qty:12.0,op:'王厨师',inTime:'2026-04-22 10:30'},
{id:'NP1-B2',status:'occupied',itemName:'香菇滑鸡餐品包',itemType:'净菜餐品包',qty:9.6,op:'张厨师',inTime:'2026-04-22 10:45'},
{id:'NP1-C1',status:'occupied',itemName:'净西兰花净菜包',itemType:'净菜包',qty:8.0,op:'王厨师',inTime:'2026-04-22 11:00'},
{id:'NP1-C2',status:'idle'},
{id:'NP1-C3',status:'idle'},
{id:'NP1-D1',status:'idle'}
]
}
];
@@ -709,6 +698,15 @@ var CABINETS=[
function slotStatusText(s){
return {occupied:'储物中',idle:'空闲',offline:'离线'}[s]||s;
}
/* 货柜类型 / 物品类型 → 标签样式映射 */
function cabinetTypeTag(t){
var map={'净菜柜':'tag-blue','毛菜柜':'tag-green','净菜包柜':'tag-orange'};
return '<span class="tag '+(map[t]||'tag-gray')+'">'+t+'</span>';
}
function itemTypeTag(t){
var map={'净菜':'tag-blue','毛菜':'tag-green','净菜包':'tag-orange','净菜餐品包':'tag-purple'};
return t?'<span class="tag '+(map[t]||'tag-gray')+'">'+t+'</span>':'—';
}
function slotStatusTag(s){
var map={occupied:'tag-green',idle:'tag-gray',offline:'tag-gray'};
return '<span class="tag '+(map[s]||'tag-gray')+'">'+slotStatusText(s)+'</span>';
@@ -723,7 +721,7 @@ function renderCabinets(){
var statusTag=cab.status==='在线'?'<span class="tag tag-green">● 在线</span>'
:cab.status==='离线'?'<span class="tag tag-gray">● 离线</span>'
:'<span class="tag tag-red">● 故障</span>';
var typeTag=cab.cabinetType==='净菜柜'?'<span class="tag tag-blue">净菜柜</span>':'<span class="tag tag-green">毛菜柜</span>';
var typeTag=cabinetTypeTag(cab.cabinetType);
/* 统计:空闲 / 储物中 / 离线 格口数 */
var idleCount=cab.slots.filter(function(s){return s.status==='idle';}).length;
var occupiedCount=cab.slots.filter(function(s){return s.status==='occupied';}).length;
@@ -803,24 +801,32 @@ var SLOT_HISTORY={
{time:'2026-04-20 09:30',itemType:'净菜',itemName:'净白菜',type:'取出',before:22.5,after:20.5,op:'张厨师',inOrder:'IN-20260415-006',remark:'午餐备菜'},
{time:'2026-04-18 14:30',itemType:'净菜',itemName:'净白菜',type:'存入',before:0.0,after:23.0,op:'王厨师',inOrder:'IN-20260415-006',remark:'首次入柜'}
],
'CAB-NC-002|NC5-01':[
{time:'2026-04-22 13:20',itemType:'净菜',itemName:'净黄瓜片',type:'取出',before:8.0,after:7.4,op:'李主管',inOrder:'IN-20260416-031',remark:'凉拌黄瓜'},
{time:'2026-04-22 10:05',itemType:'净菜',itemName:'净黄瓜片',type:'存入',before:0.0,after:9.5,op:'李主管',inOrder:'IN-20260416-031',remark:'首次入柜'}
],
'CAB-NC-002|NC5-02':[
{time:'2026-04-22 12:00',itemType:'净菜',itemName:'净西红柿',type:'取出',before:13.5,after:12.0,op:'张厨师',inOrder:'IN-20260416-032',remark:'西红柿炒蛋'},
{time:'2026-04-22 10:15',itemType:'净菜',itemName:'净西红柿',type:'存入',before:0.0,after:15.0,op:'张厨师',inOrder:'IN-20260416-032',remark:'首次入柜'}
],
'CAB-NC-002|NC5-03':[
{time:'2026-04-22 15:00',itemType:'净菜',itemName:'净洋葱',type:'取出',before:3.8,after:3.2,op:'王厨师',inOrder:'IN-20260416-033',remark:'洋葱炒蛋'},
{time:'2026-04-22 08:40',itemType:'净菜',itemName:'净洋葱',type:'存入',before:0.0,after:5.0,op:'王厨师',inOrder:'IN-20260416-033',remark:'首次入柜'}
],
'CAB-RW-008|RW8-A1':[
{time:'2026-04-16 09:00',itemType:'毛菜',itemName:'菠菜',type:'取出',before:248.0,after:225.0,op:'张仓管',inOrder:'IN-20260415-001',remark:'净菜加工领用'},
{time:'2026-04-15 10:30',itemType:'毛菜',itemName:'菠菜',type:'存入',before:0.0,after:286.0,op:'张仓管',inOrder:'IN-20260415-001',remark:'毛菜入库'}
],
'CAB-RW-008|RW8-C1':[
{time:'2026-04-16 08:00',itemType:'毛菜',itemName:'芹菜',type:'取出',before:18.0,after:12.0,op:'张仓管',inOrder:'IN-20260414-009',remark:'领用加工'}
],
'CAB-NP-001|NP1-A1':[
{time:'2026-04-22 14:00',itemType:'净菜包',itemName:'净土豆丝净菜包',type:'取出',before:17.5,after:15.0,op:'王厨师',inOrder:'IN-20260422-031',remark:'净菜包领用'},
{time:'2026-04-22 09:15',itemType:'净菜包',itemName:'净土豆丝净菜包',type:'存入',before:0.0,after:25.0,op:'张厨师',inOrder:'IN-20260422-031',remark:'包装完成入柜'}
],
'CAB-NP-001|NP1-A2':[
{time:'2026-04-22 15:20',itemType:'净菜包',itemName:'净青椒丝净菜包',type:'取出',before:14.0,after:12.5,op:'李主管',inOrder:'IN-20260422-033',remark:'青椒肉丝备菜'},
{time:'2026-04-22 09:30',itemType:'净菜包',itemName:'净青椒丝净菜包',type:'存入',before:0.0,after:18.0,op:'李主管',inOrder:'IN-20260422-033',remark:'包装完成入柜'}
],
'CAB-NP-001|NP1-B1':[
{time:'2026-04-22 12:30',itemType:'净菜餐品包',itemName:'土豆烧肉餐品包',type:'取出',before:14.0,after:12.0,op:'张厨师',inOrder:'IN-20260422-032',remark:'午餐档口领用'},
{time:'2026-04-22 10:30',itemType:'净菜餐品包',itemName:'土豆烧肉餐品包',type:'存入',before:0.0,after:18.0,op:'王厨师',inOrder:'IN-20260422-032',remark:'餐品包装完成入柜'}
],
'CAB-NP-001|NP1-B2':[
{time:'2026-04-22 13:10',itemType:'净菜餐品包',itemName:'香菇滑鸡餐品包',type:'取出',before:10.8,after:9.6,op:'王厨师',inOrder:'IN-20260422-034',remark:'晚餐备餐'},
{time:'2026-04-22 10:45',itemType:'净菜餐品包',itemName:'香菇滑鸡餐品包',type:'存入',before:0.0,after:12.0,op:'张厨师',inOrder:'IN-20260422-034',remark:'餐品包装完成入柜'}
],
'CAB-NP-001|NP1-C1':[
{time:'2026-04-22 11:40',itemType:'净菜包',itemName:'净西兰花净菜包',type:'取出',before:8.8,after:8.0,op:'李主管',inOrder:'IN-20260422-035',remark:'取样检测'},
{time:'2026-04-22 11:00',itemType:'净菜包',itemName:'净西兰花净菜包',type:'存入',before:0.0,after:10.0,op:'王厨师',inOrder:'IN-20260422-035',remark:'包装完成入柜'}
]
};
@@ -866,9 +872,9 @@ function showSlotHistory(cabIdx,slotIdx){
}
SLOT_HIST_CURRENT={cabIdx:cabIdx,slotIdx:slotIdx,key:cab.code+'|'+slot.id,page:1};
/* 上半区:格口信息 + 当前存储详情 */
var typeTag=cab.cabinetType==='净菜柜'?'<span class="tag tag-blue">净菜柜</span>':'<span class="tag tag-green">毛菜柜</span>';
var typeTag=cabinetTypeTag(cab.cabinetType);
var statusTag=cab.status==='在线'?'<span class="tag tag-green">在线</span>':cab.status==='离线'?'<span class="tag tag-gray">离线</span>':'<span class="tag tag-red">故障</span>';
var slotTypeTag=slot.itemType?(slot.itemType==='净菜'?'<span class="tag tag-blue">净菜</span>':'<span class="tag tag-green">毛菜</span>'):'—';
var slotTypeTag=itemTypeTag(slot.itemType);
var infoHtml='<div class="history-summary" style="grid-template-columns:none;">'
+'<div class="history-summary__item"><span class="label">所属柜:</span><strong>'+cab.name+'</strong></div>'
+'<div class="history-summary__item"><span class="label">设备编号:</span><strong>'+cab.code+'</strong></div>'
@@ -919,7 +925,7 @@ function renderSlotHistoryPage(){
var isPos=r.type==='存入';
var deltaHtml='<span class="history-delta '+(isPos?'pos':'neg')+'">'+(isPos?'+':'-')+delta+' '+(isPos?'↑':'↓')+'</span>';
var typeHtml=r.type==='存入'?'<span class="tag tag-blue">存入</span>':'<span class="tag tag-orange">取出</span>';
var itemTypeHtml=r.itemType==='净菜'?'<span class="tag tag-blue">净菜</span>':'<span class="tag tag-green">毛菜</span>';
var itemTypeHtml=itemTypeTag(r.itemType);
html+='<tr><td>'+r.time+'</td><td>'+itemTypeHtml+'</td><td>'+r.itemName+'</td><td>'+typeHtml+'</td><td>'+r.before.toFixed(1)+'</td><td>'+r.after.toFixed(1)+'</td><td>'+deltaHtml+'</td><td>'+r.op+'</td><td style="color:var(--primary);">'+r.inOrder+'</td><td>'+(r.remark||'—')+'</td></tr>';
});
}