diff --git a/web-admin/nutrition/din-terminal.html b/web-admin/nutrition/din-terminal.html
index c13e55d..8b62db8 100644
--- a/web-admin/nutrition/din-terminal.html
+++ b/web-admin/nutrition/din-terminal.html
@@ -333,6 +333,33 @@ tbody tr:hover{background:#F8FBFF;}
.scale-sub-tab{padding:8px 16px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;user-select:none;transition:all .15s;}
.scale-sub-tab:hover{color:var(--primary);}
.scale-sub-tab.active{color:var(--primary);font-weight:600;border-bottom-color:var(--primary);}
+/* ===== 餐线营养秤:标题下方筛选栏 ===== */
+.scale-filter-bar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:0 0 14px;padding:10px 14px;background:#FAFBFC;border:1px solid var(--border);border-radius:var(--radius-sm);}
+.scale-filter-bar__item{display:flex;align-items:center;gap:8px;}
+.scale-filter-bar__item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
+.scale-filter-bar__date{display:flex;align-items:center;gap:6px;}
+.scale-filter-bar__date .sep{color:var(--text-placeholder);font-size:var(--font-sm);}
+.scale-filter-bar__input{height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;}
+.scale-filter-bar__input:focus{border-color:var(--primary);}
+.scale-filter-bar__select{height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 26px 0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;appearance:none;cursor:pointer;min-width:120px;}
+.scale-filter-bar__actions{display:flex;align-items:center;gap:8px;margin-left:auto;}
+.scale-filter-bar .btn{height:30px;padding:0 14px;font-size:var(--font-xs);}
+/* 表格/折线图切换 */
+.scale-view-toggle{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;user-select:none;}
+.scale-view-toggle__btn{height:30px;line-height:30px;padding:0 14px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;background:#fff;border:none;transition:all .15s;}
+.scale-view-toggle__btn+.scale-view-toggle__btn{border-left:1px solid var(--border);}
+.scale-view-toggle__btn.active{background:var(--primary);color:#fff;}
+/* 重量变化折线图 */
+.scale-chart-legend{display:flex;gap:16px;flex-wrap:wrap;margin-bottom:10px;}
+.scale-chart-legend__item{display:flex;align-items:center;gap:6px;font-size:var(--font-xs);color:var(--text-secondary);}
+.scale-chart-legend__dot{width:10px;height:10px;border-radius:50%;}
+.scale-chart-svg{width:100%;height:auto;display:block;}
+.scale-chart-dot{cursor:pointer;}
+.scale-chart-tooltip{position:fixed;z-index:1800;display:none;min-width:200px;max-width:280px;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:0 6px 20px rgba(0,0,0,.15);padding:10px 12px;pointer-events:none;}
+.scale-chart-tooltip__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid #f0f0f0;}
+.scale-chart-tooltip__row{display:flex;justify-content:space-between;gap:12px;font-size:var(--font-xs);color:var(--text-secondary);line-height:1.9;}
+.scale-chart-tooltip__row span{color:var(--text-placeholder);white-space:nowrap;}
+.scale-chart-tooltip__row strong{color:var(--text-primary);font-weight:500;text-align:right;word-break:break-all;}
/* ===== 人脸识别视频回放弹窗 ===== */
@keyframes ovScan{0%{top:10%}50%{top:88%}100%{top:10%}}
.face-video-mask{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1600;display:flex;align-items:center;justify-content:center;animation:ovFadeIn .2s ease;}
@@ -794,6 +821,14 @@ function renderOvTabBody(key){
/* ===== 表头列筛选(餐线营养秤四个 Tab 复用) ===== */
var SCALE_FILTERS={status:{},face:{},weight:{},log:{}};
+/* 日期范围筛选(标题下方筛选栏,status/face/weight 三个 Tab 使用) */
+var SCALE_DATE_RANGE={status:{from:'',to:''},face:{from:'',to:''},weight:{from:'',to:''}};
+/* 各 Tab 用于日期范围筛选的时间字段 */
+var SCALE_TIME_FIELD={status:'time',face:'start',weight:'start'};
+/* 重量变化数据展示样式:table 表格 / chart 折线图 */
+var SCALE_WEIGHT_VIEW='table';
+/* 识别点状态配色(折线图图例与数据点) */
+var SCALE_WEIGHT_STATES={'刷脸':'#1890FF','离开':'#722ED1','陌生人':'#FAAD14','重量稳定变化':'#52C41A'};
/* 每个 Tab 的列定义:key 列标识 / label 表头文字 / field 数据字段 */
var SCALE_COLS={
status:[
@@ -835,6 +870,12 @@ function scaleThead(tabKey,actionLabel){
if(actionLabel)ths+='
'+actionLabel+' | ';
return ''+ths+'
';
}
+/* 生成纯文字表头(无筛选图标,用于 status/face/weight 三个 Tab) */
+function scalePlainThead(tabKey,actionLabel){
+ var ths=SCALE_COLS[tabKey].map(function(c){return ''+c.label+' | ';}).join('');
+ if(actionLabel)ths+=''+actionLabel+' | ';
+ return ''+ths+'
';
+}
/* 取某 Tab 原始数据 */
function getScaleRows(tabKey){
if(tabKey==='status')return SCALE_STATUS_EVENTS||[];
@@ -848,6 +889,8 @@ function getFilteredScaleRows(tabKey){
var rows=getScaleRows(tabKey);
var f=SCALE_FILTERS[tabKey]||{};
var cols=SCALE_COLS[tabKey]||[];
+ var dr=SCALE_DATE_RANGE[tabKey]||{from:'',to:''};
+ var tf=SCALE_TIME_FIELD[tabKey];
return rows.filter(function(r){
for(var i=0;idr.to)return false;
+ }
return true;
});
}
@@ -972,6 +1021,150 @@ function renderScaleSubBody(key){
return '';
}
+/* ===== 标题下方筛选栏(status/face/weight) ===== */
+function renderScaleFilterBar(tabKey){
+ var dr=SCALE_DATE_RANGE[tabKey]||{from:'',to:''};
+ var dateHtml='';
+ var extraHtml='';
+ if(tabKey==='status'){
+ var cur=(SCALE_FILTERS.status&&SCALE_FILTERS.status.item)||'';
+ var opts='';
+ getScaleColValues('status','item').forEach(function(v){
+ opts+='';
+ });
+ extraHtml='';
+ }
+ var toggleHtml='';
+ if(tabKey==='weight'){
+ toggleHtml=''
+ +'表格'
+ +'折线图'
+ +'
';
+ }
+ return ''+dateHtml+extraHtml
+ +'
'
+ +''
+ +''
+ +toggleHtml
+ +'
';
+}
+function applyScaleFilter(tabKey,reset){
+ if(!SCALE_DATE_RANGE[tabKey])SCALE_DATE_RANGE[tabKey]={from:'',to:''};
+ if(reset){
+ SCALE_DATE_RANGE[tabKey].from='';
+ SCALE_DATE_RANGE[tabKey].to='';
+ if(tabKey==='status'){
+ if(!SCALE_FILTERS.status)SCALE_FILTERS.status={};
+ SCALE_FILTERS.status.item='';
+ }
+ }else{
+ var f=document.getElementById('scaleDateFrom_'+tabKey);
+ var t=document.getElementById('scaleDateTo_'+tabKey);
+ SCALE_DATE_RANGE[tabKey].from=f?f.value:'';
+ SCALE_DATE_RANGE[tabKey].to=t?t.value:'';
+ if(tabKey==='status'){
+ if(!SCALE_FILTERS.status)SCALE_FILTERS.status={};
+ var sel=document.getElementById('scaleItemFilter');
+ SCALE_FILTERS.status.item=sel?sel.value:'';
+ }
+ }
+ var body=document.getElementById('scaleSubBody');
+ if(body)body.innerHTML=renderScaleSubBody(tabKey);
+}
+function switchWeightView(view){
+ SCALE_WEIGHT_VIEW=view;
+ var body=document.getElementById('scaleSubBody');
+ if(body)body.innerHTML=renderScaleSubBody('weight');
+}
+
+/* ===== 重量变化折线图 ===== */
+function renderScaleWeightChart(){
+ var arr=getFilteredScaleRows('weight').slice().reverse();
+ var legend=Object.keys(SCALE_WEIGHT_STATES).map(function(s){
+ return ''+s+'
';
+ }).join('');
+ if(!arr.length){
+ return ''+legend+'
';
+ }
+ return ''+legend+'
'+buildWeightChartSvg(arr);
+}
+function buildWeightChartSvg(arr){
+ var w=760,h=260,pl=56,pr=16,pt=20,pb=40;
+ var cw=w-pl-pr,ch=h-pt-pb;
+ var weights=arr.map(function(r){return parseFloat(r.total)||0;});
+ var min=Math.min.apply(null,weights),max=Math.max.apply(null,weights);
+ if(max-min<1){var pad=Math.max(1,max*0.1);max+=pad;min-=pad;}
+ var range=max-min||1;
+ var step=arr.length>1?cw/(arr.length-1):0;
+ var pts=arr.map(function(r,i){
+ var x=pl+i*step;
+ var y=pt+ch-((parseFloat(r.total)||0)-min)/range*ch;
+ return {x:x,y:y,r:r};
+ });
+ var grid='';
+ for(var g=0;g<=4;g++){
+ var y=pt+ch-g/4*ch;
+ var val=min+range*g/4;
+ grid+=''
+ +''+val.toFixed(1)+'';
+ }
+ var xLabels=arr.map(function(r,i){
+ return ''+formatChartTime(r.start)+'';
+ }).join('');
+ var line=pts.map(function(p){return p.x+','+p.y;}).join(' ');
+ var polyline='';
+ var dots=pts.map(function(p){
+ var color=SCALE_WEIGHT_STATES[p.r.state]||'#999';
+ return '';
+ }).join('');
+ return '';
+}
+function formatChartTime(ts){
+ var s=String(ts);
+ var t=s.indexOf(' ')!==-1?s.split(' ')[1]:s;
+ return t.slice(0,8);
+}
+function showWeightPointTip(e,start){
+ var rec=SCALE_WEIGHT_RECORDS.filter(function(r){return r.start===start;})[0];
+ if(!rec)return;
+ var num=parseFloat(rec.delta);
+ var dir=num>0?'增':(num<0?'减':'平');
+ var tip=document.getElementById('scaleChartTip');
+ if(!tip){
+ tip=document.createElement('div');
+ tip.id='scaleChartTip';
+ tip.className='scale-chart-tooltip';
+ document.body.appendChild(tip);
+ }
+ tip.innerHTML='识别点详情
'
+ +'方向'+dir+'
'
+ +'变化量'+escapeHtml(rec.delta)+'克
'
+ +'起始→结束'+escapeHtml(rec.startWeight)+'→'+escapeHtml(rec.total)+'克
'
+ +'归属用户'+escapeHtml(rec.userId||rec.target)+'
'
+ +'起始时间'+escapeHtml(rec.start)+'
'
+ +'结束时间'+escapeHtml(rec.end)+'
';
+ tip.style.display='block';
+ positionWeightPointTip(e,tip);
+}
+function positionWeightPointTip(e,tip){
+ var x=e.clientX,y=e.clientY;
+ var w=tip.offsetWidth,h=tip.offsetHeight;
+ var left=x+14,top=y+14;
+ if(left+w>window.innerWidth)left=x-w-14;
+ if(top+h>window.innerHeight)top=y-h-14;
+ tip.style.left=left+'px';
+ tip.style.top=top+'px';
+}
+function hideWeightPointTip(){
+ var tip=document.getElementById('scaleChartTip');
+ if(tip)tip.style.display='none';
+}
+
/* 维度1:设备状态监控 — 状态事件流水 */
SCALE_STATUS_EVENTS=[
{time:'2026-08-25 08:15:32.456',item:'网络连接',event:'重新连接成功'},
@@ -986,7 +1179,8 @@ SCALE_STATUS_EVENTS=[
function renderScaleStatus(){
const rows=getFilteredScaleRows('status').map(s=>`| ${s.time} | ${s.item} | ${s.event} |
`).join('');
return `设备状态监控
- ${scaleThead('status')}${rows}
`;
+ ${renderScaleFilterBar('status')}
+ ${scalePlainThead('status')}${rows}
`;
}
/* 维度2:人脸识别数据 — 开始/结束时间 / 状态 + 视频回放 */
@@ -1008,7 +1202,8 @@ function renderScaleFace(){
return `| ${f.start} | ${f.end} | ${statusTag} | 回放视频 |
`;
}).join('');
return `人脸识别数据
- ${scaleThead('face','操作')}${rows}
`;
+ ${renderScaleFilterBar('face')}
+ ${scalePlainThead('face','操作')}${rows}
`;
}
/* 录像回放弹窗:自动播放 + 快进/后退 + 进度条跳转 */
@@ -1042,12 +1237,6 @@ function showFaceVideo(start){
00:00 / 00:30
-
-
识别时间${f.start}
-
状态${f.status}
-
用户类型${f.userType}
-
重量变化${f.weight}
-
`;
mask.addEventListener('click',e=>{if(e.target===mask)closeFaceVideo();});
@@ -1113,21 +1302,25 @@ function formatFaceVideoTime(t){
return (m<10?'0'+m:m)+':'+(s<10?'0'+s:s);
}
-/* 维度3:重量变化数据 — 日期/时间 / 用户 / 较上次变化 / 当前余量 */
+/* 维度3:重量变化数据 — 日期/时间 / 用户 / 较上次变化 / 当前余量(含折线图所需字段) */
SCALE_WEIGHT_RECORDS=[
- {start:'2026-08-25 08:15:22.701',target:'EMP0234 · 张伟',delta:'+50.0',total:'486.5'},
- {start:'2026-08-25 08:14:50.512',target:'无用户',delta:'+412.0',total:'462.0'},
- {start:'2026-08-25 08:13:46.098',target:'EMP0357 · 王强',delta:'-62.0',total:'501.2'},
- {start:'2026-08-25 08:12:08.876',target:'无用户',delta:'0.0',total:'0.0'}
+ {start:'2026-08-31 08:15:22.701',end:'2026-08-31 08:15:31.208',target:'EMP0234 · 张伟',userId:'2090646647302909953',delta:'+50.0',total:'486.5',startWeight:'436.5',state:'刷脸'},
+ {start:'2026-08-31 08:14:50.512',end:'2026-08-31 08:15:02.118',target:'无用户',userId:'—',delta:'+412.0',total:'462.0',startWeight:'50.0',state:'陌生人'},
+ {start:'2026-08-31 08:13:46.098',end:'2026-08-31 08:14:10.540',target:'EMP0357 · 王强',userId:'2090646647302909954',delta:'-62.0',total:'501.2',startWeight:'563.2',state:'离开'},
+ {start:'2026-08-31 08:12:08.876',end:'2026-08-31 08:12:30.775',target:'无用户',userId:'—',delta:'0.0',total:'0.0',startWeight:'0.0',state:'重量稳定变化'}
];
function renderScaleWeight(){
+ return `重量变化数据
+ ${renderScaleFilterBar('weight')}
+ ${SCALE_WEIGHT_VIEW==='chart'?renderScaleWeightChart():renderScaleWeightTable()}`;
+}
+function renderScaleWeightTable(){
const rows=getFilteredScaleRows('weight').map(w=>{
const num=parseFloat(w.delta);
const color=num>0?'var(--success)':(num<0?'var(--danger)':'var(--text-secondary)');
return `| ${w.start} | ${w.target} | ${w.delta} | ${w.total} |
`;
}).join('');
- return `重量变化数据
- ${scaleThead('weight')}${rows}
`;
+ return `${scalePlainThead('weight')}${rows}
`;
}
/* 维度4:运行日志 — 日期/时间 / 报错类型 / 报错内容(报错代码,20字符) / 操作 */
diff --git a/web-admin/nutrition/doc/CHANGELOG.md b/web-admin/nutrition/doc/CHANGELOG.md
index 14fb8db..22a483e 100644
--- a/web-admin/nutrition/doc/CHANGELOG.md
+++ b/web-admin/nutrition/doc/CHANGELOG.md
@@ -1,5 +1,12 @@
# 营养用餐管理端 CHANGELOG
+## [2026-08-31] feat: 餐线营养秤概览筛选栏下沉 + 重量变化折线图
+
+- `din-terminal.html` 设备概览「餐线营养秤」:
+ - 设备状态监控/重量变化数据/人脸识别数据三个 Tab 的筛选由表头图标改为标题下方筛选栏(日期时间段,设备状态监控额外含监控项下拉)
+ - 重量变化数据 Tab 新增「表格/折线图」切换:折线图纵轴为重量、横轴为当天时间,识别点按 4 种状态着色(刷脸/离开/陌生人/重量稳定变化),鼠标移入展示当天详情浮层
+ - 人脸识别视频回放弹窗删除识别时间/状态/用户类型/重量变化信息区
+
## [2026-08-27] fix: 健康生产菜单「净配」统一改回「净菜组配」
- 「健康生产」模块侧边栏菜单项「净配」→「净菜组配」,10 个页面同步修正: