feat: 营养用餐模块交互增强——点餐烹制任务、用户管理级联选择与十维档案完善

- din-order-meal: 个人/团体点餐增加烹制任务生成流程(复选框模式切换、二次确认弹窗)
- din-dish-add/all: 餐品档案增加称重基础价和称重会员价字段
- din-user-mgmt: 个人用户Tab增加性别列、级联单位部门选择器、完整十维档案弹窗、人脸采集弹窗(摄像头/上传双模式)
- din-order-pickup: 弹窗标题统一调整
- din-order-line: 交互细节优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 17:28:16 +08:00
co-authored by Claude Opus 4.6
parent ef68a8e1fb
commit e0514af9cf
6 changed files with 926 additions and 122 deletions
+7 -5
View File
@@ -285,12 +285,14 @@ var STEP_HTML = [
<div class="form-grid">
<div class="form-group"><label class="form-label">餐品名称<span class="required">*</span></label><input class="form-input" placeholder="请输入餐品名称"></div>
<div class="form-group"><label class="form-label">餐品分类<span class="required">*</span></label><select class="form-select"><option>请选择</option><option>主食</option><option>荤菜</option><option>素菜</option><option>汤品</option><option>小吃</option></select></div>
<div class="form-group"><label class="form-label">烹饪方法</label><select class="form-select"><option>请选择</option><option>炒</option><option>蒸</option><option>炖</option><option>煮</option><option>凉拌</option><option>炸</option><option>烤</option></select></div>
<div class="form-group"><label class="form-label">适用餐次</label><select class="form-select"><option>请选择</option><option>早餐</option><option>午餐</option><option>晚餐</option><option>早/午</option><option>午/晚</option><option>早/午/晚</option></select></div>
<div class="form-group"><label class="form-label">膳食宝塔分类</label><select class="form-select"><option>请选择</option><option>谷薯杂豆</option><option>蔬菜</option><option>水果</option><option>畜禽肉蛋水产</option><option>奶及奶制品</option><option>大豆坚果</option><option>油脂</option><option>盐</option></select></div>
<div class="form-group"><label class="form-label">烹饪方法<span class="required">*</span></label><select class="form-select"><option>请选择</option><option>炒</option><option>蒸</option><option>炖</option><option>煮</option><option>凉拌</option><option>炸</option><option>烤</option></select></div>
<div class="form-group"><label class="form-label">适用餐次<span class="required">*</span></label><select class="form-select"><option>请选择</option><option>早餐</option><option>午餐</option><option>晚餐</option><option>早/午</option><option>午/晚</option><option>早/午/晚</option></select></div>
<div class="form-group"><label class="form-label">膳食宝塔分类<span class="required">*</span></label><select class="form-select"><option>请选择</option><option>谷薯杂豆</option><option>蔬菜</option><option>水果</option><option>畜禽肉蛋水产</option><option>奶及奶制品</option><option>大豆坚果</option><option>油脂</option><option>盐</option></select></div>
<div class="form-group"><label class="form-label">过敏原</label><input class="form-input" placeholder="无则填无"></div>
<div class="form-group"><label class="form-label">餐品标签</label><select class="form-select"><option>请选择</option><option>低脂</option><option>高蛋白</option><option>素食</option><option>低卡</option><option>高纤维</option><option>低GI</option><option>高糖</option><option>无麸质</option></select></div>
<div class="form-group"><label class="form-label">上架状态</label><select class="form-select"><option>上架</option><option>下架</option></select></div>
<div class="form-group"><label class="form-label">称重基础价(元/100g)<span class="required">*</span></label><input class="form-input" type="number" step="0.01" placeholder="请输入称重基础价"></div>
<div class="form-group"><label class="form-label">称重会员价(元/100g)<span class="required">*</span></label><input class="form-input" type="number" step="0.01" placeholder="请输入称重会员价"></div>
<div class="form-group"><label class="form-label">上架状态<span class="required">*</span></label><select class="form-select"><option>上架</option><option>下架</option></select></div>
</div>
<div class="form-subtitle">👥 适配人群</div>
<div class="dynamic-rows" id="crowdRows"></div>
@@ -326,7 +328,7 @@ var STEP_HTML = [
// Step 1: 餐品构成
`<div class="form-card__title">🧩 餐品构成</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<span style="font-size:var(--font-sm);font-weight:600;">食材清单</span>
<span style="font-size:var(--font-sm);font-weight:600;">食材清单<span class="required">*</span></span>
<button class="btn btn-primary" style="height:30px;font-size:var(--font-xs);" onclick="openIngredientDialog()"> 新增食材</button>
</div>
<table class="ingredient-table">
+7 -5
View File
@@ -274,7 +274,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<script>
// 餐品数据
const DISHES = [
{id:'DISH001',name:'红烧肉',icon:'🥩',category:'荤菜',tags:['高蛋白'],specs:[{name:'标准份',weight:200,price:12,isDefault:true},{name:'小份',weight:120,price:8},{name:'大份',weight:300,price:18}],method:'炖',meal:'午/晚',allergen:'无',status:'上架',crowd:[{name:'成人',level:'推荐'},{name:'高血脂人群',level:'慎食'},{name:'高血压人群',level:'适量'}],crowdNote:'高血脂人群控制摄入量',
{id:'DISH001',name:'红烧肉',icon:'🥩',category:'荤菜',tags:['高蛋白'],specs:[{name:'标准份',weight:200,price:12,isDefault:true},{name:'小份',weight:120,price:8},{name:'大份',weight:300,price:18}],method:'炖',meal:'午/晚',allergen:'无',status:'上架',weightBasePrice:'6.00',weightVipPrice:'5.40',crowd:[{name:'成人',level:'推荐'},{name:'高血脂人群',level:'慎食'},{name:'高血压人群',level:'适量'}],crowdNote:'高血脂人群控制摄入量',
composition:[{name:'猪五花肉',cat:'肉禽类/猪肉',amount:160,pct:'80%',state:'净菜',main:true,allergen:'无',note:'—'},{name:'生姜',cat:'调味品/香辛料',amount:5,pct:'2.5%',state:'净菜',main:false,allergen:'无',note:'—'},{name:'生抽',cat:'调味品/酱油',amount:15,pct:'7.5%',state:'原料',main:false,allergen:'含麸质',note:'酱油'},{name:'冰糖',cat:'调味品/糖类',amount:10,pct:'5%',state:'原料',main:false,allergen:'无',note:'上色提味'},{name:'料酒',cat:'调味品/酒类',amount:10,pct:'5%',state:'原料',main:false,allergen:'无',note:'去腥'}],
nutri:{calorie:385,protein:18.2,carb:12.4,fat:28.6,vitC:0.8,mineral:42,fiber:0.3,sodium:486,note:'铁2.4mg、锌1.8mg'},
cook:{mainMethod:'炖',subMethod:'煎',heat:'中火→小火',duration:45,oil:15,salt:3,temp:'100→80',output:200,steps:6},
@@ -282,7 +282,7 @@ const DISHES = [
color:{mainColor:'红褐色',uniformity:4,gloss:4,freshness:4,appearance:4,colorScore:4.0,colorGrade:'良好'},
texture:{hardness:2,sticky:3,crispy:1,smooth:4,elastic:3,textureScore:3.6,textureGrade:'良好'}
},
{id:'DISH002',name:'清炒时蔬',icon:'🥬',category:'素菜',tags:['低脂','素食'],specs:[{name:'标准份',weight:150,price:6,isDefault:true},{name:'小份',weight:100,price:4}],method:'炒',meal:'早/午/晚',allergen:'无',status:'上架',crowd:[{name:'全人群',level:'推荐'},{name:'糖尿病人群',level:'推荐'},{name:'肾结石人群',level:'慎食'}],crowdNote:'肾结石人群慎食(草酸含量高)',
{id:'DISH002',name:'清炒时蔬',icon:'🥬',category:'素菜',tags:['低脂','素食'],specs:[{name:'标准份',weight:150,price:6,isDefault:true},{name:'小份',weight:100,price:4}],method:'炒',meal:'早/午/晚',allergen:'无',status:'上架',weightBasePrice:'3.00',weightVipPrice:'2.70',crowd:[{name:'全人群',level:'推荐'},{name:'糖尿病人群',level:'推荐'},{name:'肾结石人群',level:'慎食'}],crowdNote:'肾结石人群慎食(草酸含量高)',
composition:[{name:'时令青菜',cat:'蔬菜类/叶菜',amount:120,pct:'80%',state:'净菜',main:true,allergen:'无',note:'—'},{name:'蒜末',cat:'调味品/香辛料',amount:5,pct:'3.3%',state:'净菜',main:false,allergen:'无',note:'—'},{name:'食用油',cat:'油脂类/植物油',amount:8,pct:'5.3%',state:'原料',main:false,allergen:'无',note:'—'},{name:'食盐',cat:'调味品/盐类',amount:2,pct:'1.3%',state:'原料',main:false,allergen:'无',note:'—'}],
nutri:{calorie:68,protein:2.1,carb:8.6,fat:3.2,vitC:28.5,mineral:86,fiber:2.4,sodium:186,note:'铁1.2mg、钙45mg'},
cook:{mainMethod:'炒',subMethod:'—',heat:'大火',duration:5,oil:8,salt:2,temp:'200',output:150,steps:4},
@@ -290,7 +290,7 @@ const DISHES = [
color:{mainColor:'翠绿色',uniformity:5,gloss:4,freshness:5,appearance:4,colorScore:4.5,colorGrade:'优秀'},
texture:{hardness:3,sticky:1,crispy:4,smooth:3,elastic:2,textureScore:3.4,textureGrade:'良好'}
},
{id:'DISH003',name:'番茄鸡蛋汤',icon:'🍅',category:'汤品',tags:['低卡'],specs:[{name:'标准份',weight:300,price:5,isDefault:true}],method:'煮',meal:'午/晚',allergen:'含蛋类',status:'上架',crowd:[{name:'全人群',level:'推荐'},{name:'儿童',level:'推荐'},{name:'老年人',level:'推荐'}],crowdNote:'',
{id:'DISH003',name:'番茄鸡蛋汤',icon:'🍅',category:'汤品',tags:['低卡'],specs:[{name:'标准份',weight:300,price:5,isDefault:true}],method:'煮',meal:'午/晚',allergen:'含蛋类',status:'上架',weightBasePrice:'1.67',weightVipPrice:'1.50',crowd:[{name:'全人群',level:'推荐'},{name:'儿童',level:'推荐'},{name:'老年人',level:'推荐'}],crowdNote:'',
composition:[{name:'番茄',cat:'蔬菜类/茄果',amount:150,pct:'50%',state:'净菜',main:true,allergen:'无',note:'—'},{name:'鸡蛋',cat:'蛋类/禽蛋',amount:60,pct:'20%',state:'原料',main:true,allergen:'含蛋类',note:'—'},{name:'食用油',cat:'油脂类/植物油',amount:5,pct:'1.7%',state:'原料',main:false,allergen:'无',note:'—'},{name:'食盐',cat:'调味品/盐类',amount:2,pct:'0.7%',state:'原料',main:false,allergen:'无',note:'—'}],
nutri:{calorie:45,protein:3.8,carb:4.2,fat:2.1,vitC:18.2,mineral:32,fiber:1.1,sodium:280,note:'铁0.8mg、钾210mg'},
cook:{mainMethod:'煮',subMethod:'炒',heat:'中火',duration:12,oil:5,salt:2,temp:'100',output:300,steps:5},
@@ -298,7 +298,7 @@ const DISHES = [
color:{mainColor:'橙红色',uniformity:4,gloss:3,freshness:5,appearance:4,colorScore:4.0,colorGrade:'良好'},
texture:{hardness:1,sticky:2,crispy:1,smooth:5,elastic:2,textureScore:3.8,textureGrade:'良好'}
},
{id:'DISH004',name:'米饭',icon:'🍚',category:'主食',tags:['主食'],specs:[{name:'标准份',weight:200,price:3,isDefault:true},{name:'小份',weight:100,price:2},{name:'大份',weight:300,price:4}],method:'蒸',meal:'早/午/晚',allergen:'无',status:'上架',crowd:[{name:'全人群',level:'推荐'},{name:'糖尿病人群',level:'适量'}],crowdNote:'糖尿病人群注意控制量',
{id:'DISH004',name:'米饭',icon:'🍚',category:'主食',tags:['主食'],specs:[{name:'标准份',weight:200,price:3,isDefault:true},{name:'小份',weight:100,price:2},{name:'大份',weight:300,price:4}],method:'蒸',meal:'早/午/晚',allergen:'无',status:'上架',weightBasePrice:'1.50',weightVipPrice:'1.35',crowd:[{name:'全人群',level:'推荐'},{name:'糖尿病人群',level:'适量'}],crowdNote:'糖尿病人群注意控制量',
composition:[{name:'大米',cat:'谷薯类/稻米',amount:100,pct:'100%',state:'原料',main:true,allergen:'无',note:'—'}],
nutri:{calorie:232,protein:4.8,carb:51.2,fat:0.6,vitC:0,mineral:12,fiber:0.4,sodium:3,note:'—'},
cook:{mainMethod:'蒸',subMethod:'—',heat:'大火→中火',duration:30,oil:0,salt:0,temp:'100',output:200,steps:2},
@@ -306,7 +306,7 @@ const DISHES = [
color:{mainColor:'乳白色',uniformity:5,gloss:4,freshness:4,appearance:4,colorScore:4.2,colorGrade:'良好'},
texture:{hardness:2,sticky:3,crispy:1,smooth:3,elastic:2,textureScore:3.2,textureGrade:'一般'}
},
{id:'DISH005',name:'糖醋里脊',icon:'🍖',category:'荤菜',tags:['高糖'],specs:[{name:'标准份',weight:180,price:14,isDefault:true},{name:'大份',weight:280,price:20}],method:'炸',meal:'午',allergen:'无',status:'下架',crowd:[{name:'成人',level:'推荐'},{name:'糖尿病人群',level:'慎食'},{name:'高血脂人群',level:'适量'}],crowdNote:'糖尿病人群慎食,含糖量高',
{id:'DISH005',name:'糖醋里脊',icon:'🍖',category:'荤菜',tags:['高糖'],specs:[{name:'标准份',weight:180,price:14,isDefault:true},{name:'大份',weight:280,price:20}],method:'炸',meal:'午',allergen:'无',status:'下架',weightBasePrice:'7.78',weightVipPrice:'7.00',crowd:[{name:'成人',level:'推荐'},{name:'糖尿病人群',level:'慎食'},{name:'高血脂人群',level:'适量'}],crowdNote:'糖尿病人群慎食,含糖量高',
composition:[{name:'猪里脊肉',cat:'肉禽类/猪肉',amount:120,pct:'66.7%',state:'净菜',main:true,allergen:'无',note:'—'},{name:'淀粉',cat:'谷薯类/淀粉',amount:20,pct:'11.1%',state:'原料',main:false,allergen:'无',note:'挂糊'},{name:'白糖',cat:'调味品/糖类',amount:25,pct:'13.9%',state:'原料',main:false,allergen:'无',note:'糖醋汁'},{name:'食醋',cat:'调味品/醋类',amount:15,pct:'8.3%',state:'原料',main:false,allergen:'无',note:'糖醋汁'}],
nutri:{calorie:420,protein:22.4,carb:38.2,fat:18.6,vitC:1.2,mineral:28,fiber:0.6,sodium:620,note:'铁1.8mg'},
cook:{mainMethod:'炸',subMethod:'炒',heat:'大火',duration:15,oil:50,salt:1,temp:'180',output:180,steps:7},
@@ -363,6 +363,8 @@ function renderBasic(d){
<div class="info-cell"><div class="info-cell__label">适用餐次</div><div class="info-cell__value">${d.meal}</div></div>
<div class="info-cell"><div class="info-cell__label">过敏原</div><div class="info-cell__value">${d.allergen}</div></div>
<div class="info-cell"><div class="info-cell__label">餐品标签</div><div class="info-cell__value">${d.tags.map(t=>'<span class="tag tag-blue">'+t+'</span>').join('')}</div></div>
<div class="info-cell"><div class="info-cell__label">称重基础价(元/100g)</div><div class="info-cell__value">${d.weightBasePrice||'—'}</div></div>
<div class="info-cell"><div class="info-cell__label">称重会员价(元/100g)</div><div class="info-cell__value">${d.weightVipPrice||'—'}</div></div>
<div class="info-cell"><div class="info-cell__label">上架状态</div><div class="info-cell__value"><span class="tag tag-${d.status==='上架'?'green':'gray'}">${d.status}</span></div></div>
</div></div>
<div class="info-section"><div class="info-section__title">👥 适配人群</div>
+30 -42
View File
@@ -96,6 +96,15 @@ tbody tr:hover{background:#F8FBFF;}
.tab-panel{display:none;}
.tab-panel.active{display:block;}
/* 页面信息栏 */
.page-header-bar{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header-bar__left{display:flex;align-items:center;gap:12px;}
.page-header-bar__title{font-size:var(--font-lg);font-weight:700;color:var(--text-primary);}
.page-header-bar__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
.page-header-bar__right{display:flex;align-items:center;gap:12px;}
.page-header-bar__right label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.page-header-bar__right .canteen-select{height:36px;border:2px solid var(--primary);border-radius:var(--radius-sm);padding:0 32px 0 14px;font-size:var(--font-sm);color:var(--primary);font-weight:500;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='%231890FF'/%3E%3C/svg%3E") no-repeat right 12px center;appearance:none;cursor:pointer;min-width:140px;}
/* 汇总行 */
.summary-row td{background:#FAFAFA;font-weight:600;}
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
@@ -213,6 +222,22 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- 主内容区 -->
<div class="main">
<!-- 页面信息栏 -->
<div class="page-header-bar">
<div class="page-header-bar__left">
<span class="page-header-bar__title">餐线管理</span>
<span class="page-header-bar__meta">2026-05-20 · 数据实时更新</span>
</div>
<div class="page-header-bar__right">
<label>当前食堂</label>
<select class="canteen-select" id="globalCanteen">
<option value="1">一食堂(东区)</option>
<option value="2">二食堂(西区)</option>
<option value="3">三食堂(北区)</option>
</select>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-row">
<div class="stat-card">
@@ -255,10 +280,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- Tab1: 实时监控 -->
<div class="tab-panel active">
<div class="filter-bar">
<div class="filter-item"><label>食堂</label><select class="filter-select" id="filterCanteen" onchange="updateLineOptions()"><option value="">全部</option><option value="1">一号食堂</option><option value="2">二号食堂</option><option value="3">三号食堂</option></select></div>
<div class="filter-item"><label>餐线</label><select class="filter-select" id="filterLine"><option value="">全部</option><option data-canteen="1">A区早餐线</option><option data-canteen="1">A区中餐线</option><option data-canteen="1">B区中餐线</option><option data-canteen="2">B区早餐线</option><option data-canteen="2">C区快餐线</option><option data-canteen="3">D区晚餐线</option></select></div>
<div class="filter-item"><label>日期</label><input class="filter-input" type="date" value="2026-05-19" style="min-width:140px;"></div>
<div class="filter-item"><label>餐次</label><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="filterLine"><option value="">全部</option><option>A区早餐线</option><option>A区中餐线</option><option>B区中餐线</option></select></div>
<div class="filter-item"><label>餐次</label><select class="filter-select"><option>全部</option><option>早餐</option><option>中餐</option><option>晚餐</option><option>加餐</option></select></div>
<div class="filter-item"><label>称台编号</label><input class="filter-input" type="text" placeholder="请输入称台编号" style="min-width:140px;"></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
@@ -302,8 +327,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<div class="tab-panel">
<div class="filter-bar">
<div class="filter-item"><label>统计日期</label><input class="filter-input" type="date" value="2026-05-19" style="min-width:140px;"></div>
<div class="filter-item"><label>食堂</label><select class="filter-select" id="filterCanteen2" onchange="updateLineOptions2()"><option value="">全部</option><option value="1">一号食堂</option><option value="2">二号食堂</option><option value="3">三号食堂</option></select></div>
<div class="filter-item"><label>餐线</label><select class="filter-select" id="filterLine2"><option value="">全部</option><option data-canteen="1">A区早餐线</option><option data-canteen="1">A区中餐线</option><option data-canteen="1">B区中餐线</option><option data-canteen="2">B区早餐线</option><option data-canteen="2">C区快餐线</option><option data-canteen="3">D区晚餐线</option></select></div>
<div class="filter-item"><label>餐线</label><select class="filter-select" id="filterLine2"><option value="">全部</option><option>A区早餐线</option><option>A区中餐线</option><option>B区中餐线</option></select></div>
<div class="filter-item"><label>餐次</label><select class="filter-select"><option>全部</option><option>早餐</option><option>中餐</option><option>晚餐</option><option>加餐</option></select></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
@@ -344,8 +368,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<div class="tab-panel">
<div class="filter-bar">
<div class="filter-item"><label>统计日期</label><input class="filter-input" type="date" value="2026-05-19" style="min-width:140px;"></div>
<div class="filter-item"><label>食堂</label><select class="filter-select" id="filterCanteen3" onchange="updateLineOptions3()"><option value="">全部</option><option value="1">一号食堂</option><option value="2">二号食堂</option><option value="3">三号食堂</option></select></div>
<div class="filter-item"><label>餐线</label><select class="filter-select" id="filterLine3"><option value="">全部</option><option data-canteen="1">A区早餐线</option><option data-canteen="1">A区中餐线</option><option data-canteen="1">B区中餐线</option><option data-canteen="2">B区早餐线</option><option data-canteen="2">C区快餐线</option><option data-canteen="3">D区晚餐线</option></select></div>
<div class="filter-item"><label>餐线</label><select class="filter-select" id="filterLine3"><option value="">全部</option><option>A区早餐线</option><option>A区中餐线</option><option>B区中餐线</option></select></div>
<div class="filter-item"><label>餐次</label><select class="filter-select"><option>全部</option><option>早餐</option><option>中餐</option><option>晚餐</option><option>加餐</option></select></div>
<div class="filter-item"><label>称台编号</label><input class="filter-input" type="text" placeholder="请输入称台编号" style="min-width:140px;"></div>
<button class="btn btn-primary">查询</button>
@@ -397,41 +420,6 @@ function switchTab(idx){
document.querySelectorAll('.tab-panel').forEach(function(p,i){p.classList.toggle('active',i===idx);});
}
/* ========== 食堂→餐线联动 ========== */
function updateLineOptions(){
var canteenVal=document.getElementById('filterCanteen').value;
var lineSelect=document.getElementById('filterLine');
var opts=lineSelect.querySelectorAll('option');
opts.forEach(function(opt){
if(!opt.value&&!opt.getAttribute('data-canteen')){opt.style.display='';return;}
if(!canteenVal){opt.style.display='';return;}
opt.style.display=opt.getAttribute('data-canteen')===canteenVal?'':'none';
});
lineSelect.value='';
}
function updateLineOptions2(){
var canteenVal=document.getElementById('filterCanteen2').value;
var lineSelect=document.getElementById('filterLine2');
var opts=lineSelect.querySelectorAll('option');
opts.forEach(function(opt){
if(!opt.value&&!opt.getAttribute('data-canteen')){opt.style.display='';return;}
if(!canteenVal){opt.style.display='';return;}
opt.style.display=opt.getAttribute('data-canteen')===canteenVal?'':'none';
});
lineSelect.value='';
}
function updateLineOptions3(){
var canteenVal=document.getElementById('filterCanteen3').value;
var lineSelect=document.getElementById('filterLine3');
var opts=lineSelect.querySelectorAll('option');
opts.forEach(function(opt){
if(!opt.value&&!opt.getAttribute('data-canteen')){opt.style.display='';return;}
if(!canteenVal){opt.style.display='';return;}
opt.style.display=opt.getAttribute('data-canteen')===canteenVal?'':'none';
});
lineSelect.value='';
}
/* ========== 弹窗基础 ========== */
const Modal = {
open(title, bodyHtml) {
+388 -21
View File
@@ -89,6 +89,16 @@ tbody tr:hover{background:#F8FBFF;}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%) translateY(-20px);background:rgba(0,0,0,0.75);color:#fff;padding:10px 24px;border-radius:var(--radius-md);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .3s;z-index:2000;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
/* 表单样式 */
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;}
.form-item{display:flex;flex-direction:column;gap:6px;}
.form-item label{font-size:var(--font-sm);color:var(--text-secondary);}
.form-item input,.form-item select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;}
.form-item input:focus,.form-item select:focus{border-color:var(--primary);}
.form-item.full{grid-column:1 / -1;}
/* 弹窗样式 */
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;}
.modal{background:#fff;border-radius:8px;width:780px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);}
@@ -109,6 +119,17 @@ tbody tr:hover{background:#F8FBFF;}
thead th:last-child,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:2;white-space:nowrap;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
/* 页面信息栏 */
.page-header-bar{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header-bar__left{display:flex;align-items:center;gap:12px;}
.page-header-bar__title{font-size:var(--font-lg);font-weight:700;color:var(--text-primary);}
.page-header-bar__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
.page-header-bar__right{display:flex;align-items:center;gap:12px;}
.page-header-bar__right label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.page-header-bar__right .canteen-select{height:36px;border:2px solid var(--primary);border-radius:var(--radius-sm);padding:0 32px 0 14px;font-size:var(--font-sm);color:var(--primary);font-weight:500;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='%231890FF'/%3E%3C/svg%3E") no-repeat right 12px center;appearance:none;cursor:pointer;min-width:140px;}
/* 复选框列 */
.th-checkbox,.td-checkbox{width:40px;text-align:center;}
.td-checkbox input[disabled]{opacity:0.35;cursor:not-allowed;}
</style>
</head>
<body>
@@ -176,6 +197,22 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- 主内容 -->
<main class="main">
<!-- 页面信息栏 -->
<div class="page-header-bar">
<div class="page-header-bar__left">
<span class="page-header-bar__title">营养点餐</span>
<span class="page-header-bar__meta">2026-05-20 · 数据实时更新</span>
</div>
<div class="page-header-bar__right">
<label>当前餐厅</label>
<select class="canteen-select" id="globalCanteen">
<option value="1">一食堂(东区)</option>
<option value="2">二食堂(西区)</option>
<option value="3">三食堂(北区)</option>
</select>
</div>
</div>
<!-- 统计卡片 -->
<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">486单</div><div class="stat-card__label">今日个人订单</div></div></div>
@@ -193,25 +230,32 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- Tab1: 个人点餐 -->
<div class="tab-panel active">
<div class="filter-bar">
<div class="filter-item"><label>日期范围</label><input class="filter-input" type="date"></div>
<div class="filter-item"><label>日期</label><input class="filter-input" type="date" value="2026-05-20"></div>
<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><option>晚餐</option></select></div>
<div class="filter-item"><label>部门</label><select class="filter-select"><option>全部</option></select></div>
<div class="filter-item"><label>营养匹配度</label><select class="filter-select"><option>全部</option><option>≥90%</option><option>70%-89%</option><option><70%</option></select></div>
<div class="filter-item"><label>烹制任务生成</label><select class="filter-select"><option>全部</option><option>已生成</option><option>未生成</option></select></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<div class="action-bar"><button class="btn btn-default">🍳 生成烹制任务</button><button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button><button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button></div>
<div class="action-bar" id="personalActionBar">
<button class="btn btn-default" id="btnGenCook">🍳 生成烹制任务</button>
<button class="btn btn-primary btn-export-personal"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default btn-view-export"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
<!-- 生成模式按钮(默认隐藏) -->
<button class="btn btn-primary" id="btnConfirmGen" style="display:none;">✅ 确认生成</button>
<button class="btn btn-default" id="btnCancelGen" style="display:none;">取消</button>
</div>
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">个人点餐</div></div>
<div class="table-card__content"><table><thead><tr><th>订单号</th><th>用户</th><th>餐次</th><th>目标热量(kcal)</th><th>点餐内容</th><th>营养匹配度</th><th>禁忌过滤</th><th>营养评分</th><th>日期</th><th>操作</th></tr></thead>
<div class="table-card__content"><table id="personalTable"><thead><tr><th class="th-checkbox" style="display:none;"><input type="checkbox" id="checkAll"></th><th>订单号</th><th>用户</th><th>餐次</th><th>目标热量(kcal)</th><th>点餐内容</th><th>营养匹配度</th><th>禁忌过滤</th><th>营养评分</th><th>烹制任务生成</th><th>日期</th><th>操作</th></tr></thead>
<tbody>
<tr><td>NM-001</td><td>张伟(EMP001)</td><td>午餐</td><td>2,250</td><td>米饭+清蒸鱼+炒西兰花+番茄蛋花汤</td><td><span class="tag tag-green">92%</span></td><td><span class="tag tag-orange">已过滤</span></td><td><span class="tag tag-green">92分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>NM-002</td><td>李娜(EMP002)</td><td>午餐</td><td>1,800</td><td>杂粮饭+清蒸鱼+清炒时蔬+冬瓜排骨汤</td><td><span class="tag tag-green">95%</span></td><td></td><td><span class="tag tag-green">95分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>NM-003</td><td>王强(EMP003)</td><td>午餐</td><td>2,250</td><td>米饭+红烧肉+番茄炒蛋+番茄蛋花汤</td><td><span class="tag tag-orange">72%</span></td><td></td><td><span class="tag tag-orange">72分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>NM-004</td><td>赵敏(EMP004)</td><td>午餐</td><td>1,800</td><td>杂粮饭+宫保鸡丁+炒西兰花+番茄蛋花汤</td><td><span class="tag tag-green">88%</span></td><td><span class="tag tag-orange">已过滤</span></td><td><span class="tag tag-green">88分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>NM-005</td><td>陈磊(EMP005)</td><td>午餐</td><td>2,250</td><td>米饭+红烧肉+宫保鸡丁+炒西兰花</td><td><span class="tag tag-orange">68%</span></td><td></td><td><span class="tag tag-orange">68分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>NM-006</td><td>刘洋(EMP006)</td><td>午餐</td><td>1,600</td><td>杂粮饭+蒜蓉菠菜+炒时蔬+紫菜蛋花汤</td><td><span class="tag tag-green">90%</span></td><td></td><td><span class="tag tag-green">90分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>NM-007</td><td>吴芳(EMP007)</td><td>午餐</td><td>1,900</td><td>米饭+清蒸鱼+番茄炒蛋+豆腐+冬瓜排骨汤</td><td><span class="tag tag-green">85%</span></td><td><span class="tag tag-orange">已过滤</span></td><td><span class="tag tag-green">85分</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check"></td><td>NM-001</td><td>张伟(EMP001)</td><td>午餐</td><td>2,250</td><td>米饭+清蒸鱼+炒西兰花+番茄蛋花汤</td><td><span class="tag tag-green">92%</span></td><td><span class="tag tag-orange">已过滤</span></td><td><span class="tag tag-green">92分</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="1"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check" disabled></td><td>NM-002</td><td>李娜(EMP002)</td><td>午餐</td><td>1,800</td><td>杂粮饭+清蒸鱼+清炒时蔬+冬瓜排骨汤</td><td><span class="tag tag-green">95%</span></td><td></td><td><span class="tag tag-green">95分</span></td><td><span class="tag tag-green">已生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check"></td><td>NM-003</td><td>王强(EMP003)</td><td>午餐</td><td>2,250</td><td>米饭+红烧肉+番茄炒蛋+番茄蛋花汤</td><td><span class="tag tag-orange">72%</span></td><td></td><td><span class="tag tag-orange">72分</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="1"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check" disabled></td><td>NM-004</td><td>赵敏(EMP004)</td><td>午餐</td><td>1,800</td><td>杂粮饭+宫保鸡丁+炒西兰花+番茄蛋花汤</td><td><span class="tag tag-green">88%</span></td><td><span class="tag tag-orange">已过滤</span></td><td><span class="tag tag-green">88分</span></td><td><span class="tag tag-green">已生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check"></td><td>NM-005</td><td>陈磊(EMP005)</td><td>午餐</td><td>2,250</td><td>米饭+红烧肉+宫保鸡丁+炒西兰花</td><td><span class="tag tag-orange">68%</span></td><td></td><td><span class="tag tag-orange">68分</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check"></td><td>NM-006</td><td>刘洋(EMP006)</td><td>午餐</td><td>1,600</td><td>杂粮饭+蒜蓉菠菜+炒时蔬+紫菜蛋花汤</td><td><span class="tag tag-green">90%</span></td><td></td><td><span class="tag tag-green">90分</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="1"><td class="td-checkbox" style="display:none;"><input type="checkbox" class="row-check" disabled></td><td>NM-007</td><td>吴芳(EMP007)</td><td>午餐</td><td>1,900</td><td>米饭+清蒸鱼+番茄炒蛋+豆腐+冬瓜排骨汤</td><td><span class="tag tag-green">85%</span></td><td><span class="tag tag-orange">已过滤</span></td><td><span class="tag tag-green">85分</span></td><td><span class="tag tag-green">已生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
</tbody></table></div>
<div class="pagination">
<span>显示 7 条,共 486 条</span>
@@ -229,22 +273,31 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- Tab2: 团体点餐 -->
<div class="tab-panel">
<div class="filter-bar">
<div class="filter-item"><label>日期</label><input class="filter-input" type="date" value="2026-05-20"></div>
<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><option>专项</option></select></div>
<div class="filter-item"><label>日期</label><input class="filter-input" type="date"></div>
<div class="filter-item"><label>烹制任务生成</label><select class="filter-select"><option>全部</option><option>已生成</option><option>未生成</option></select></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<div class="action-bar"><button class="btn btn-primary"> 新增团体订单</button><button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button><button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button></div>
<div class="action-bar" id="groupActionBar">
<button class="btn btn-primary" id="btnAddGroup"> 新增团体订单</button>
<button class="btn btn-default" id="btnGenCookGroup">🍳 生成烹制任务</button>
<button class="btn btn-primary btn-export-group"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default btn-view-export-group"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
<!-- 生成模式按钮(默认隐藏) -->
<button class="btn btn-primary" id="btnConfirmGenGroup" style="display:none;">✅ 确认生成</button>
<button class="btn btn-default" id="btnCancelGenGroup" style="display:none;">取消</button>
</div>
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">团体点餐</div></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>
<div class="table-card__content"><table id="groupTable"><thead><tr><th class="th-checkbox-group" style="display:none;"><input type="checkbox" id="checkAllGroup"></th><th>团体名称</th><th>类型</th><th>人数</th><th>人群特征</th><th>人均预算(元)</th><th>总金额(元)</th><th>营养覆盖率</th><th>烹制任务生成</th><th>日期</th><th>操作</th></tr></thead>
<tbody>
<tr><td>研发中心午餐团</td><td>部门</td><td>86</td><td>年轻成人为主</td><td>¥28</td><td>¥2,408</td><td><span class="tag tag-green">88%</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>行政楼早餐团</td><td>部门</td><td>42</td><td>中年混合</td><td>¥15</td><td>¥630</td><td><span class="tag tag-green">82%</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>养老院午餐团</td><td>班级</td><td>28</td><td>老年人(≥60岁)</td><td>¥35</td><td>¥980</td><td><span class="tag tag-green">91%</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>幼儿园午餐团</td><td>班级</td><td>56</td><td>青少年</td><td>¥22</td><td>¥1,232</td><td><span class="tag tag-green">86%</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr><td>康复中心自定义团</td><td>专项</td><td>18</td><td>术后康复人群</td><td>¥42</td><td>¥756</td><td><span class="tag tag-green">94%</span></td><td>2026-04-23</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox-group" style="display:none;"><input type="checkbox" class="row-check-group"></td><td>研发中心午餐团</td><td>部门</td><td>86</td><td>年轻成人为主</td><td>¥28</td><td>¥2,408</td><td><span class="tag tag-green">88%</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="1"><td class="td-checkbox-group" style="display:none;"><input type="checkbox" class="row-check-group" disabled></td><td>行政楼早餐团</td><td>部门</td><td>42</td><td>中年混合</td><td>¥15</td><td>¥630</td><td><span class="tag tag-green">82%</span></td><td><span class="tag tag-green">已生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox-group" style="display:none;"><input type="checkbox" class="row-check-group"></td><td>养老院午餐团</td><td>班级</td><td>28</td><td>老年人(≥60岁)</td><td>¥35</td><td>¥980</td><td><span class="tag tag-green">91%</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="1"><td class="td-checkbox-group" style="display:none;"><input type="checkbox" class="row-check-group" disabled></td><td>幼儿园午餐团</td><td>班级</td><td>56</td><td>青少年</td><td>¥22</td><td>¥1,232</td><td><span class="tag tag-green">86%</span></td><td><span class="tag tag-green">已生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
<tr data-cook="0"><td class="td-checkbox-group" style="display:none;"><input type="checkbox" class="row-check-group"></td><td>康复中心自定义团</td><td>专项</td><td>18</td><td>术后康复人群</td><td>¥42</td><td>¥756</td><td><span class="tag tag-green">94%</span></td><td><span class="tag tag-gray">未生成</span></td><td>2026-05-20</td><td class="td-actions"><a class="link">餐品明细</a></td></tr>
</tbody></table></div>
<div class="pagination">
<span>显示 5 条,共 12 条</span>
@@ -267,7 +320,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<div class="action-bar"><button class="btn btn-default">✅ 确认发货</button><button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button><button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button></div>
<div class="action-bar"><button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button><button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button></div>
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">净菜订单</div></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>
@@ -292,6 +345,48 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</div>
</main>
<!-- 生成烹制任务确认弹窗 -->
<div class="modal-overlay" id="modalCookConfirm" style="display:none;">
<div class="modal" style="width:640px;">
<div class="modal-header">
<span class="modal-title">确认生成烹制任务</span>
<span class="modal-close" onclick="closeCookConfirm()">&times;</span>
</div>
<div class="modal-body">
<p style="margin-bottom:12px;font-size:var(--font-sm);color:var(--text-secondary);">以下订单将生成烹制任务,请确认:</p>
<table class="modal-table" id="cookConfirmTable">
<thead><tr><th>订单号</th><th>用户</th><th>餐次</th><th>点餐内容</th><th>日期</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeCookConfirm()">取消</button>
<button class="btn btn-primary" onclick="confirmCookGen()">确认</button>
</div>
</div>
</div>
<!-- 生成烹制任务确认弹窗(团体) -->
<div class="modal-overlay" id="modalCookConfirmGroup" style="display:none;">
<div class="modal" style="width:640px;">
<div class="modal-header">
<span class="modal-title">确认生成烹制任务</span>
<span class="modal-close" onclick="closeCookConfirmGroup()">&times;</span>
</div>
<div class="modal-body">
<p style="margin-bottom:12px;font-size:var(--font-sm);color:var(--text-secondary);">以下团体订单将生成烹制任务,请确认:</p>
<table class="modal-table" id="cookConfirmTableGroup">
<thead><tr><th>团体名称</th><th>类型</th><th>人数</th><th>人群特征</th><th>日期</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeCookConfirmGroup()">取消</button>
<button class="btn btn-primary" onclick="confirmCookGenGroup()">确认</button>
</div>
</div>
</div>
<!-- 餐品明细弹窗(个人/净菜) -->
<div class="modal-overlay" id="modalOverlay" style="display:none;">
<div class="modal">
@@ -359,23 +454,295 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</div>
</div>
<!-- Toast 容器 -->
<div class="toast" id="toast"></div>
<!-- 导出任务弹窗 -->
<div class="modal-overlay" id="modalExportOverlay" style="display:none;">
<div class="modal" style="width:640px;">
<div class="modal-header">
<span class="modal-title">导出任务列表</span>
<span class="modal-close" onclick="closeExportModal()">&times;</span>
</div>
<div class="modal-body">
<table class="modal-table">
<thead><tr><th>任务ID</th><th>导出内容</th><th>创建时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>EXP-20260423-001</td><td>个人点餐列表</td><td>2026-04-23 09:12</td><td><span class="tag tag-green">已完成</span></td><td><span class="link">下载</span></td></tr>
<tr><td>EXP-20260423-002</td><td>团体点餐列表</td><td>2026-04-23 10:35</td><td><span class="tag tag-orange">处理中</span></td><td><span class="link" style="color:#999;cursor:default;">下载</span></td></tr>
<tr><td>EXP-20260422-003</td><td>净菜订单列表</td><td>2026-04-22 16:48</td><td><span class="tag tag-green">已完成</span></td><td><span class="link">下载</span></td></tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeExportModal()">关闭</button>
</div>
</div>
</div>
<!-- 新增团体订单弹窗 -->
<div class="modal-overlay" id="modalAddGroupOverlay" style="display:none;">
<div class="modal" style="width:560px;">
<div class="modal-header">
<span class="modal-title">新增团体订单</span>
<span class="modal-close" onclick="closeAddGroupModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-row">
<div class="form-item"><label>团体名称</label><input placeholder="请输入团体名称"></div>
<div class="form-item"><label>团体类型</label><select><option>部门</option><option>班级</option><option>专项</option></select></div>
</div>
<div class="form-row">
<div class="form-item"><label>人数</label><input type="number" placeholder="请输入人数" min="1"></div>
<div class="form-item"><label>人均预算(元)</label><input type="number" placeholder="请输入人均预算" min="0"></div>
</div>
<div class="form-row">
<div class="form-item"><label>人群特征</label><input placeholder="如:年轻成人为主"></div>
<div class="form-item"><label>日期</label><input type="date"></div>
</div>
<div class="form-row">
<div class="form-item full"><label>备注说明</label><input placeholder="请输入备注(可选)"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeAddGroupModal()">取消</button>
<button class="btn btn-primary" onclick="saveGroupOrder()">保存</button>
</div>
</div>
</div>
<script>
/* ========== Tab 切换 ========== */
function switchTab(idx){
document.querySelectorAll('.page-tab').forEach((t,i)=>t.classList.toggle('active',i===idx));
document.querySelectorAll('.tab-panel').forEach((p,i)=>p.classList.toggle('active',i===idx));
}
/* ========== Toast ========== */
function showToast(msg){
var el=document.getElementById('toast');
el.textContent=msg;
el.classList.add('show');
setTimeout(function(){el.classList.remove('show');},2000);
}
/* ========== 弹窗控制 ========== */
function openModal(){document.getElementById('modalOverlay').style.display='flex';}
function closeModal(){document.getElementById('modalOverlay').style.display='none';}
function openGroupModal(){document.getElementById('modalGroupOverlay').style.display='flex';}
function closeGroupModal(){document.getElementById('modalGroupOverlay').style.display='none';}
function openExportModal(){document.getElementById('modalExportOverlay').style.display='flex';}
function closeExportModal(){document.getElementById('modalExportOverlay').style.display='none';}
function openAddGroupModal(){document.getElementById('modalAddGroupOverlay').style.display='flex';}
function closeAddGroupModal(){document.getElementById('modalAddGroupOverlay').style.display='none';}
// 点击遮罩关闭
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
document.getElementById('modalGroupOverlay').addEventListener('click',function(e){if(e.target===this)closeGroupModal();});
document.getElementById('modalExportOverlay').addEventListener('click',function(e){if(e.target===this)closeExportModal();});
document.getElementById('modalAddGroupOverlay').addEventListener('click',function(e){if(e.target===this)closeAddGroupModal();});
// 个人点餐(tab0)和净菜订单(tab2)用个人弹窗,团体点餐(tab1)用团体弹窗
document.querySelectorAll('.tab-panel').forEach(function(panel,idx){
panel.querySelectorAll('.td-actions .link').forEach(function(el){
el.addEventListener('click', idx===1 ? openGroupModal : openModal);
});
});
/* ========== 筛选区交互 ========== */
// 查询按钮
document.querySelectorAll('.filter-bar .btn-primary').forEach(function(btn){
if(btn.textContent.trim()==='查询'){
btn.addEventListener('click',function(){showToast('查询成功');});
}
});
// 重置按钮
document.querySelectorAll('.filter-bar .btn-default').forEach(function(btn){
if(btn.textContent.trim()==='重置'){
btn.addEventListener('click',function(){
var bar=btn.closest('.filter-bar');
bar.querySelectorAll('input').forEach(function(i){i.value='';});
bar.querySelectorAll('select').forEach(function(s){s.selectedIndex=0;});
showToast('筛选条件已重置');
});
}
});
/* ========== 操作按钮交互 ========== */
// 生成烹制任务模式切换
var isGenMode=false;
var btnGenCook=document.getElementById('btnGenCook');
var btnConfirmGen=document.getElementById('btnConfirmGen');
var btnCancelGen=document.getElementById('btnCancelGen');
var personalActionBar=document.getElementById('personalActionBar');
function enterGenMode(){
isGenMode=true;
// 隐藏原操作按钮
personalActionBar.querySelectorAll('.btn').forEach(function(b){b.style.display='none';});
btnConfirmGen.style.display='inline-flex';
btnCancelGen.style.display='inline-flex';
// 显示复选框列
document.querySelectorAll('#personalTable .th-checkbox, #personalTable .td-checkbox').forEach(function(el){el.style.display='';});
document.getElementById('checkAll').checked=false;
}
function exitGenMode(){
isGenMode=false;
// 恢复原操作按钮
personalActionBar.querySelectorAll('.btn').forEach(function(b){b.style.display='inline-flex';});
btnConfirmGen.style.display='none';
btnCancelGen.style.display='none';
// 隐藏复选框列并取消勾选
document.querySelectorAll('#personalTable .th-checkbox, #personalTable .td-checkbox').forEach(function(el){el.style.display='none';});
document.querySelectorAll('#personalTable .row-check').forEach(function(cb){cb.checked=false;});
document.getElementById('checkAll').checked=false;
}
btnGenCook.addEventListener('click', enterGenMode);
btnCancelGen.addEventListener('click', exitGenMode);
// 全选逻辑(仅选未禁用的)
document.getElementById('checkAll').addEventListener('change',function(){
var checked=this.checked;
document.querySelectorAll('#personalTable .row-check').forEach(function(cb){
if(!cb.disabled) cb.checked=checked;
});
});
// 确认生成 → 弹窗
btnConfirmGen.addEventListener('click',function(){
var rows=document.querySelectorAll('#personalTable tbody tr');
var selected=[];
rows.forEach(function(tr){
var cb=tr.querySelector('.row-check');
if(cb&&cb.checked){
var tds=tr.querySelectorAll('td');
selected.push({order:tds[1].textContent,user:tds[2].textContent,meal:tds[3].textContent,content:tds[5].textContent,date:tds[10].textContent});
}
});
if(selected.length===0){showToast('请至少选择一条未生成的订单');return;}
// 填充确认弹窗表格
var tbody=document.querySelector('#cookConfirmTable tbody');
tbody.innerHTML='';
selected.forEach(function(item){
tbody.innerHTML+='<tr><td>'+item.order+'</td><td>'+item.user+'</td><td>'+item.meal+'</td><td>'+item.content+'</td><td>'+item.date+'</td></tr>';
});
document.getElementById('modalCookConfirm').style.display='flex';
});
function closeCookConfirm(){document.getElementById('modalCookConfirm').style.display='none';}
function confirmCookGen(){
closeCookConfirm();
exitGenMode();
showToast('烹制任务生成成功');
}
document.getElementById('modalCookConfirm').addEventListener('click',function(e){if(e.target===this)closeCookConfirm();});
/* ========== 团体点餐 - 生成烹制任务模式 ========== */
var groupActionBar=document.getElementById('groupActionBar');
var btnGenCookGroup=document.getElementById('btnGenCookGroup');
var btnConfirmGenGroup=document.getElementById('btnConfirmGenGroup');
var btnCancelGenGroup=document.getElementById('btnCancelGenGroup');
function enterGenModeGroup(){
groupActionBar.querySelectorAll('.btn').forEach(function(b){b.style.display='none';});
btnConfirmGenGroup.style.display='inline-flex';
btnCancelGenGroup.style.display='inline-flex';
document.querySelectorAll('#groupTable .th-checkbox-group, #groupTable .td-checkbox-group').forEach(function(el){el.style.display='';});
document.getElementById('checkAllGroup').checked=false;
}
function exitGenModeGroup(){
groupActionBar.querySelectorAll('.btn').forEach(function(b){b.style.display='inline-flex';});
btnConfirmGenGroup.style.display='none';
btnCancelGenGroup.style.display='none';
document.querySelectorAll('#groupTable .th-checkbox-group, #groupTable .td-checkbox-group').forEach(function(el){el.style.display='none';});
document.querySelectorAll('#groupTable .row-check-group').forEach(function(cb){cb.checked=false;});
document.getElementById('checkAllGroup').checked=false;
}
btnGenCookGroup.addEventListener('click', enterGenModeGroup);
btnCancelGenGroup.addEventListener('click', exitGenModeGroup);
document.getElementById('checkAllGroup').addEventListener('change',function(){
var checked=this.checked;
document.querySelectorAll('#groupTable .row-check-group').forEach(function(cb){
if(!cb.disabled) cb.checked=checked;
});
});
btnConfirmGenGroup.addEventListener('click',function(){
var rows=document.querySelectorAll('#groupTable tbody tr');
var selected=[];
rows.forEach(function(tr){
var cb=tr.querySelector('.row-check-group');
if(cb&&cb.checked){
var tds=tr.querySelectorAll('td');
selected.push({name:tds[1].textContent,type:tds[2].textContent,count:tds[3].textContent,feature:tds[4].textContent,date:tds[9].textContent});
}
});
if(selected.length===0){showToast('请至少选择一条未生成的订单');return;}
var tbody=document.querySelector('#cookConfirmTableGroup tbody');
tbody.innerHTML='';
selected.forEach(function(item){
tbody.innerHTML+='<tr><td>'+item.name+'</td><td>'+item.type+'</td><td>'+item.count+'</td><td>'+item.feature+'</td><td>'+item.date+'</td></tr>';
});
document.getElementById('modalCookConfirmGroup').style.display='flex';
});
function closeCookConfirmGroup(){document.getElementById('modalCookConfirmGroup').style.display='none';}
function confirmCookGenGroup(){
closeCookConfirmGroup();
exitGenModeGroup();
showToast('烹制任务生成成功');
}
document.getElementById('modalCookConfirmGroup').addEventListener('click',function(e){if(e.target===this)closeCookConfirmGroup();});
// 新增团体订单按钮
document.getElementById('btnAddGroup').addEventListener('click', openAddGroupModal);
// 导出列表数据
document.querySelectorAll('.action-bar .btn-primary').forEach(function(btn){
var txt=btn.textContent.trim();
if(txt.indexOf('导出列表数据')>-1){
btn.addEventListener('click',function(){showToast('导出任务已创建,请至「查看导出任务」下载');});
}
});
/* ========== 保存团体订单 ========== */
function saveGroupOrder(){
closeAddGroupModal();
showToast('团体订单保存成功');
}
/* ========== 分页交互 ========== */
document.querySelectorAll('.pagination').forEach(function(pag){
pag.querySelectorAll('.page-btn').forEach(function(btn){
btn.addEventListener('click',function(){
if(this.textContent===''||this.textContent===''||this.textContent==='...')return;
pag.querySelectorAll('.page-btn').forEach(function(b){b.classList.remove('active');});
this.classList.add('active');
showToast('已切换至第 '+this.textContent+' 页');
});
});
});
/* ========== 食材溯源链路点击提示 ========== */
document.querySelectorAll('.modal-table .link').forEach(function(link){
if(link.textContent.trim()==='食材溯源链路'){
link.addEventListener('click',function(){showToast('跳转至食材溯源链路详情页');});
}
if(link.textContent.trim()==='下载'){
link.addEventListener('click',function(){showToast('开始下载文件');});
}
});
/* ========== 侧边栏 ========== */
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
(function(){var active=document.querySelector('.sidebar-item.active');if(!active)return;var sub=active.closest('.sidebar-subgroup');if(sub)sub.classList.add('open');})();
</script>
+2 -2
View File
@@ -328,7 +328,7 @@ const Modal = {
/* 模拟用户十维档案数据 */
const userProfiles = {
'张伟': { dept:'研发部', age:32, gender:'男', height:'175cm', weight:'72kg', bmi:'23.5', activity:'中等',
'张伟': { dept:'公司机关/研发部', age:32, gender:'男', height:'175cm', weight:'72kg', bmi:'23.5', activity:'中等',
disease:'无', chronic:'无', medicine:'无', family:'高血压(父)',
examDate:'2026-03-15', bp:'125/80', glucose:'5.2', lipid:'正常', uric:'380', liver:'正常', kidney:'正常',
taste:'偏辣', region:'川菜、湘菜',
@@ -513,7 +513,7 @@ function switchTab(el, key) {
function showUserProfile(name) {
const pureName = name.replace(/\(.*\)/, '').trim();
const p = userProfiles[pureName] || defaultProfile;
Modal.open('用户十维档案 — ' + pureName, buildProfileHTML(pureName, p));
Modal.open('用户信息 — ' + pureName, buildProfileHTML(pureName, p));
}
/* 绑定表格中的"查看用户信息"链接 */
+492 -47
View File
@@ -102,8 +102,10 @@ tbody tr:hover{background:#F8FBFF;}
.empty-hint{padding:60px 24px;text-align:center;color:var(--text-placeholder);font-size:var(--font-base);background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
/* 操作列固定右侧 */
thead th:last-child,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:2;white-space:nowrap;}
thead th:last-child{background:#FAFAFA;}
thead th:last-child{background:#FAFAFA;z-index:3;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
tbody td.td-actions{overflow:visible;}
.more-wrap.open{z-index:100;}
/* 弹窗样式 */
.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;}
@@ -121,6 +123,16 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
.modal-body{padding:24px;overflow-y:auto;flex:1;}
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border);}
/* 页面信息栏 */
.page-header-bar{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header-bar__left{display:flex;align-items:center;gap:12px;}
.page-header-bar__title{font-size:var(--font-lg);font-weight:700;color:var(--text-primary);}
.page-header-bar__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
.page-header-bar__right{display:flex;align-items:center;gap:12px;}
.page-header-bar__right label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.page-header-bar__right .canteen-select{height:36px;border:2px solid var(--primary);border-radius:var(--radius-sm);padding:0 32px 0 14px;font-size:var(--font-sm);color:var(--primary);font-weight:500;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='%231890FF'/%3E%3C/svg%3E") no-repeat right 12px center;appearance:none;cursor:pointer;min-width:140px;}
/* 十维档案 Tab */
.profile-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:20px;overflow-x:auto;}
.profile-tab{padding:10px 16px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all .2s;}
@@ -137,6 +149,51 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
.profile-summary__info{display:flex;flex-direction:column;gap:4px;}
.profile-summary__name{font-size:var(--font-base);font-weight:600;color:var(--text-primary);}
.profile-summary__meta{font-size:var(--font-sm);color:var(--text-secondary);}
.profile-field__value .tag{margin-left:4px;}
/* 历史用餐明细表格 */
.meal-history-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.meal-history-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
.meal-history-table tbody td{padding:12px;color:var(--text-primary);border-bottom:1px solid #f0f0f0;}
.meal-history-table tbody tr:last-child td{border-bottom:none;}
.meal-history-table tbody tr:hover{background:#F8FBFF;}
.score-tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:var(--font-sm);font-weight:600;}
.score-green{color:#52C41A;background:#F6FFED;}
.score-orange{color:#FA8C16;background:#FFF7E6;}
/* 菜品偏好 */
.dish-pref-section{display:flex;flex-direction:column;gap:12px;}
.dish-pref-item__label{font-size:var(--font-xs);color:var(--text-secondary);margin-bottom:6px;}
.dish-pref-item__tags{display:flex;gap:6px;flex-wrap:wrap;}
.dish-tag{display:inline-block;padding:2px 10px;border-radius:4px;font-size:var(--font-xs);color:#52C41A;background:#F6FFED;border:1px solid #B7EB8F;}
.dish-pref-item__value{font-size:var(--font-sm);color:var(--text-primary);}
.dish-pref-item__big{font-size:var(--font-base);font-weight:600;color:var(--text-primary);}
/* 妈妈味道 */
.mom-section{background:#FFF8F0;border-radius:var(--radius-md);padding:12px 16px;}
.mom-header{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
.mom-header__icon{font-size:22px;}
.mom-header__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);}
.mom-header__sub{font-size:var(--font-xs);color:#FA8C16;margin-top:2px;}
.mom-header__tags{display:flex;gap:6px;flex-wrap:wrap;}
.mom-tag{display:inline-block;padding:2px 10px;border-radius:4px;font-size:var(--font-xs);color:#FA8C16;background:#FFF7E6;border:1px solid #FFD591;}
/* 个性模型 */
.model-section{background:#E6F7FF;border-radius:var(--radius-md);padding:12px 16px;margin-bottom:16px;}
.model-header{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
.model-header__icon{font-size:22px;}
.model-header__title{font-size:var(--font-sm);font-weight:600;color:var(--primary);}
.model-header__sub{font-size:var(--font-xs);color:var(--text-secondary);margin-top:2px;}
.model-header__tags{display:flex;gap:6px;flex-wrap:wrap;}
.model-tag{display:inline-block;padding:2px 10px;border-radius:4px;font-size:var(--font-xs);color:var(--primary);background:#fff;border:1px solid #91D5FF;}
.model-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 20px;margin-bottom:16px;}
.model-item__label{font-size:var(--font-xs);color:var(--text-secondary);margin-bottom:4px;}
.model-item__big{font-size:var(--font-base);font-weight:600;color:var(--text-primary);}
.model-item__value{font-size:var(--font-sm);}
.risk-tag{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:4px;font-size:var(--font-xs);font-weight:500;}
.risk-safe{background:#F6FFED;color:#52C41A;border:1px solid #B7EB8F;}
.risk-warn{background:#FFF7E6;color:#FA8C16;border:1px solid #FFD591;}
.model-footer{font-size:var(--font-xs);color:var(--text-secondary);padding:10px 12px;background:#F5F7FA;border-radius:var(--radius-sm);}
/* 推荐食谱弹窗 */
.recipe-modal{width:720px;}
@@ -153,6 +210,55 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
.recipe-summary__item{display:flex;align-items:center;gap:4px;}
.recipe-summary__label{color:var(--text-secondary);}
.recipe-summary__value{font-weight:600;color:var(--text-primary);}
/* 更多操作 Tip 弹窗 */
.more-wrap{position:relative;display:inline-block;}
.cascader{position:relative;display:inline-block;}
.cascader__trigger{display:flex;align-items:center;gap:4px;padding:6px 12px;border:1px solid var(--border);border-radius:var(--radius-md);background:#fff;cursor:pointer;font-size:var(--font-sm);min-width:140px;justify-content:space-between;}
.cascader__trigger:hover{border-color:var(--primary);}
.cascader__arrow{font-size:10px;color:var(--text-3);transition:transform .2s;}
.cascader.open .cascader__arrow{transform:rotate(180deg);}
.cascader__panel{display:none;position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);border:1px solid var(--border);z-index:100;white-space:nowrap;}
.cascader.open .cascader__panel{display:flex;}
.cascader__col{min-width:120px;max-height:240px;overflow-y:auto;padding:4px 0;border-right:1px solid var(--border);}
.cascader__col:last-child{border-right:none;}
.cascader__col-item{padding:6px 16px;cursor:pointer;font-size:var(--font-sm);color:var(--text-1);}
.cascader__col-item:hover{background:var(--bg-hover);}
.cascader__col-item.active{color:var(--primary);background:var(--primary-light,#f0f7ff);}
.more-tip{display:none;position:fixed;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);border:1px solid var(--border);padding:4px 0;z-index:9999;min-width:120px;white-space:nowrap;}
.more-tip__item{display:block;padding:8px 16px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;transition:background .15s;text-decoration:none;}
.more-tip__item:hover{background:var(--primary-light);color:var(--primary);}
.more-tip__item--danger{color:var(--danger);}
.more-tip__item--danger:hover{background:#FFF1F0;color:var(--danger);}
/* 人脸采集弹窗 */
.face-current{display:flex;flex-direction:column;align-items:center;gap:12px;padding:20px 0 24px;border-bottom:1px solid var(--border);margin-bottom:20px;}
.face-current__img{width:120px;height:120px;border-radius:50%;border:3px solid var(--border);display:flex;align-items:center;justify-content:center;font-size:48px;color:var(--text-placeholder);background:#FAFAFA;overflow:hidden;}
.face-current__img.has-face{border-color:var(--primary);}
.face-current__img img{width:100%;height:100%;object-fit:cover;}
.face-current__status{font-size:var(--font-sm);color:var(--text-secondary);}
.face-current__status.ok{color:var(--success);}
.face-methods{display:flex;gap:16px;margin-bottom:20px;}
.face-method-card{flex:1;border:2px solid var(--border);border-radius:var(--radius-md);padding:24px 16px;text-align:center;cursor:pointer;transition:all .2s;}
.face-method-card:hover{border-color:var(--primary);background:#F0F7FF;}
.face-method-card.active{border-color:var(--primary);background:#F0F7FF;}
.face-method-card__icon{font-size:36px;margin-bottom:10px;}
.face-method-card__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:4px;}
.face-method-card__desc{font-size:var(--font-xs);color:var(--text-secondary);}
.face-panel{display:none;}
.face-panel.active{display:block;}
.face-camera{display:flex;flex-direction:column;align-items:center;gap:16px;padding:20px 0;}
.face-camera__view{width:280px;height:280px;border-radius:12px;background:#000;display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;position:relative;overflow:hidden;}
.face-camera__mask{position:absolute;width:200px;height:200px;border:2px dashed rgba(255,255,255,.6);border-radius:50%;}
.face-upload{display:flex;flex-direction:column;align-items:center;gap:16px;padding:20px 0;}
.face-upload__drop{width:280px;height:200px;border:2px dashed var(--border);border-radius:12px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer;transition:all .2s;color:var(--text-secondary);}
.face-upload__drop:hover{border-color:var(--primary);background:#F0F7FF;}
.face-upload__drop-icon{font-size:36px;}
.face-upload__drop-text{font-size:var(--font-sm);}
.face-upload__drop-hint{font-size:var(--font-xs);color:var(--text-placeholder);}
.face-upload__preview{display:none;width:280px;text-align:center;}
.face-upload__preview img{width:160px;height:160px;border-radius:50%;object-fit:cover;border:3px solid var(--primary);margin-bottom:12px;}
.face-upload__preview-actions{display:flex;gap:8px;justify-content:center;}
</style>
</head>
<body>
@@ -222,6 +328,22 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- 主内容区 -->
<div class="main">
<!-- 页面信息栏 -->
<div class="page-header-bar">
<div class="page-header-bar__left">
<span class="page-header-bar__title">用户管理</span>
<span class="page-header-bar__meta">2026-05-20 · 数据实时更新</span>
</div>
<div class="page-header-bar__right">
<label>当前餐厅</label>
<select class="canteen-select" id="globalCanteen">
<option value="1">一餐厅(东区)</option>
<option value="2">二餐厅(西区)</option>
<option value="3">三餐厅(北区)</option>
</select>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-row">
<div class="stat-card">
@@ -256,8 +378,8 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- Tab 切换栏 -->
<div class="page-tabs">
<div class="page-tab active" onclick="switchTab(0)">🧑 个人</div>
<div class="page-tab" onclick="switchTab(1)">👥 群体</div>
<div class="page-tab active" onclick="switchTab(0)">🧑 个人</div>
<div class="page-tab" onclick="switchTab(1)">👥 群体</div>
<div class="page-tab" onclick="switchTab(2)">💎 会员账户</div>
<div class="page-tab" onclick="switchTab(3)">🏆 等级配置</div>
</div>
@@ -265,8 +387,9 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- Tab1: 个人用户 -->
<div class="tab-panel active">
<div class="filter-bar">
<div class="filter-item"><label>姓名/电话</label><input class="filter-input" type="text" placeholder="请输入姓名或电话"></div>
<div class="filter-item"><label>部门</label><select class="filter-select"><option>全部</option><option>研发部</option><option>市场部</option><option>运营部</option><option>财务部</option><option>人事部</option><option>行政部</option><option>销售部</option></select></div>
<div class="filter-item"><label>姓名</label><input class="filter-input" type="text" placeholder="请输入姓名" style="min-width:140px;"></div>
<div class="filter-item"><label>员工/用户编号</label><input class="filter-input" type="text" placeholder="请输入编号" style="min-width:140px;"></div>
<div class="filter-item"><label>单位部门</label><div class="cascader" id="cascaderDept"><div class="cascader__trigger" onclick="toggleCascader()"><span class="cascader__text">全部</span><span class="cascader__arrow"></span></div><div class="cascader__panel"><div class="cascader__col cascader__col--left" id="cascaderLeft"></div><div class="cascader__col cascader__col--right" id="cascaderRight"></div></div></div></div>
<div class="filter-item"><label>活动水平</label><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"><option>全部</option><option>会员</option><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></select></div>
@@ -280,27 +403,27 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</div>
<div class="table-card">
<div class="table-card__header">
<div class="table-card__title">个人</div>
<div class="table-card__title">个人</div>
</div>
<div class="table-card__content">
<table>
<thead>
<tr><th>姓名</th><th>部门</th><th>年龄</th><th>BMI</th><th>活动水平</th><th>人员类别</th><th>饮食禁忌</th><th>状态</th><th>操作</th></tr>
<tr><th>姓名</th><th>员工/用户编号</th><th>性别</th><th>年龄</th><th>单位部门</th><th>BMI</th><th>活动水平</th><th>人员类别</th><th>饮食禁忌</th><th>状态</th><th>操作</th></tr>
</thead>
<tbody>
<tr><td>张伟</td><td>研发部</td><td>38</td><td>22.9</td><td>中度活动</td><td><span class="tag tag-orange">会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>李娜</td><td>市场部</td><td>34</td><td>19.8</td><td>轻度活动</td><td><span class="tag tag-orange">会员</span></td><td><span class="tag tag-red">高钠</span></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>王磊</td><td>运营部</td><td>41</td><td>26.8</td><td>久坐</td><td><span class="tag tag-gray">非会员</span></td><td><span class="tag tag-red">高脂</span></td><td><span class="tag tag-orange">预警</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>赵敏</td><td>财务部</td><td>36</td><td>18.8</td><td>轻度活动</td><td><span class="tag tag-orange">会员</span></td><td><span class="tag tag-red">高糖</span></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>陈刚</td><td>人事部</td><td>29</td><td>21.4</td><td>重度活动</td><td><span class="tag tag-gray">非会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>刘芳</td><td>市场部</td><td>32</td><td>20.6</td><td>中度活动</td><td><span class="tag tag-orange">会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>孙磊</td><td>行政部</td><td>45</td><td>24.3</td><td>久坐</td><td><span class="tag tag-gray">非会员</span></td><td><span class="tag tag-red">高钠</span></td><td><span class="tag tag-orange">预警</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>钱芳</td><td>销售部</td><td>27</td><td>17.9</td><td>轻度活动</td><td><span class="tag tag-orange">会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>张运维</td><td>运维部</td><td>35</td><td>23.5</td><td>中度活动</td><td><span class="tag tag-blue">员工</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>李后勤</td><td>后勤部</td><td>42</td><td>25.1</td><td>中度活动</td><td><span class="tag tag-blue">员工</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>王保安</td><td>安保部</td><td>48</td><td>24.8</td><td>重度活动</td><td><span class="tag tag-blue">工作人员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>赵清洁</td><td>保洁部</td><td>50</td><td>22.0</td><td>重度活动</td><td><span class="tag tag-blue">工作人员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>林教授</td><td>外部访客</td><td>55</td><td>23.8</td><td>轻度活动</td><td><span class="tag tag-purple">外部人员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link">详情</a> <a class="link">推荐食谱</a></td></tr>
<tr><td>张伟</td><td>EMP003</td><td></td><td>38</td><td></td><td>22.9</td><td>中度活动</td><td><span class="tag tag-orange">会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>李娜</td><td>EMP001</td><td></td><td>34</td><td></td><td>19.8</td><td>轻度活动</td><td><span class="tag tag-orange">会员</span></td><td><span class="tag tag-red">高钠</span></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>王磊</td><td>EMP002</td><td></td><td>41</td><td></td><td>26.8</td><td>久坐</td><td><span class="tag tag-gray">非会员</span></td><td><span class="tag tag-red">高脂</span></td><td><span class="tag tag-orange">预警</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>赵敏</td><td>EMP004</td><td></td><td>36</td><td></td><td>18.8</td><td>轻度活动</td><td><span class="tag tag-orange">会员</span></td><td><span class="tag tag-red">高糖</span></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>陈刚</td><td>EMP005</td><td></td><td>29</td><td></td><td>21.4</td><td>重度活动</td><td><span class="tag tag-gray">非会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>刘芳</td><td>EMP006</td><td></td><td>32</td><td></td><td>20.6</td><td>中度活动</td><td><span class="tag tag-orange">会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>孙磊</td><td>EMP007</td><td></td><td>45</td><td></td><td>24.3</td><td>久坐</td><td><span class="tag tag-gray">非会员</span></td><td><span class="tag tag-red">高钠</span></td><td><span class="tag tag-orange">预警</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>钱芳</td><td>EMP008</td><td></td><td>27</td><td></td><td>17.9</td><td>轻度活动</td><td><span class="tag tag-orange">会员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>张运维</td><td>EMP009</td><td></td><td>35</td><td>总部机关/运维部</td><td>23.5</td><td>中度活动</td><td><span class="tag tag-blue">员工</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>李后勤</td><td>EMP010</td><td></td><td>42</td><td>总部机关/后勤部</td><td>25.1</td><td>中度活动</td><td><span class="tag tag-blue">员工</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>王保安</td><td>WK003</td><td></td><td>48</td><td></td><td>24.8</td><td>重度活动</td><td><span class="tag tag-blue">工作人员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>赵清洁</td><td>WK004</td><td></td><td>50</td><td></td><td>22.0</td><td>重度活动</td><td><span class="tag tag-blue">工作人员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
<tr><td>林教授</td><td>EXT001</td><td></td><td>55</td><td>外部单位/访客组</td><td>23.8</td><td>轻度活动</td><td><span class="tag tag-purple">外部人员</span></td><td></td><td><span class="tag tag-green">正常</span></td><td class="td-actions"><a class="link" data-action="detail">详情</a> <a class="link" data-action="edit">编辑</a> <span class="more-wrap"><a class="link" data-action="more">更多 ▾</a><div class="more-tip"><a class="more-tip__item" data-action="recipe">推荐食谱</a><a class="more-tip__item" data-action="face">人脸采集</a><a class="more-tip__item more-tip__item--danger" data-action="delete">删除</a></div></span></td></tr>
</tbody>
</table>
</div>
@@ -408,6 +531,61 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<script>
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
(function(){var active=document.querySelector('.sidebar-item.active');if(!active)return;var sub=active.closest('.sidebar-subgroup');if(sub)sub.classList.add('open');})();
/* 级联选择器 */
var CASCADER_DATA={
'总部机关':['运维部','后勤部','研发部','市场部','运营部','财务部','人事部','行政部','销售部'],
'物业中心':['安保部','保洁部','绿化部','维修部'],
'外部单位':['访客组','合作方']
};
var cascaderEl=document.getElementById('cascaderDept');
var cascaderLeft=document.getElementById('cascaderLeft');
var cascaderRight=document.getElementById('cascaderRight');
var selectedUnit='',selectedDept='';
function renderCascaderLeft(){
var html='<div class="cascader__col-item'+(selectedUnit===''?' active':'')+'" data-val="">全部</div>';
Object.keys(CASCADER_DATA).forEach(function(u){
html+='<div class="cascader__col-item'+(selectedUnit===u?' active':'')+'" data-val="'+u+'">'+u+'</div>';
});
cascaderLeft.innerHTML=html;
cascaderLeft.querySelectorAll('.cascader__col-item').forEach(function(item){
item.onclick=function(e){e.stopPropagation();selectUnit(this.dataset.val);};
});
}
function renderCascaderRight(){
if(!selectedUnit){cascaderRight.innerHTML='';return;}
var depts=CASCADER_DATA[selectedUnit]||[];
var html='<div class="cascader__col-item'+(selectedDept===''?' active':'')+'" data-val="">全部</div>';
depts.forEach(function(d){
html+='<div class="cascader__col-item'+(selectedDept===d?' active':'')+'" data-val="'+d+'">'+d+'</div>';
});
cascaderRight.innerHTML=html;
cascaderRight.querySelectorAll('.cascader__col-item').forEach(function(item){
item.onclick=function(e){e.stopPropagation();selectDept(this.dataset.val);};
});
}
function selectUnit(val){
selectedUnit=val;selectedDept='';
renderCascaderLeft();renderCascaderRight();
if(!val){updateCascaderText();closeCascader();}
}
function selectDept(val){
selectedDept=val;renderCascaderRight();updateCascaderText();closeCascader();
}
function updateCascaderText(){
var txt='全部';
if(selectedUnit&&selectedDept)txt=selectedUnit+'/'+selectedDept;
else if(selectedUnit)txt=selectedUnit;
cascaderEl.querySelector('.cascader__text').textContent=txt;
}
function toggleCascader(){
var isOpen=cascaderEl.classList.contains('open');
if(isOpen){closeCascader();}else{renderCascaderLeft();renderCascaderRight();cascaderEl.classList.add('open');}
}
function closeCascader(){cascaderEl.classList.remove('open');}
document.addEventListener('click',function(e){if(!cascaderEl.contains(e.target))closeCascader();});
function switchTab(idx){
document.querySelectorAll('.page-tab').forEach(function(t,i){t.classList.toggle('active',i===idx);});
document.querySelectorAll('.tab-panel').forEach(function(p,i){p.classList.toggle('active',i===idx);});
@@ -437,34 +615,64 @@ const Modal = {
/* ========== 十维档案 ========== */
const userProfiles = {
'张伟': { dept:'研发部', age:38, gender:'男', height:'175cm', weight:'72kg', bmi:'22.9', activity:'中度活动',
'张伟': { dept:'公司机关/研发部', age:38, gender:'男', height:'175cm', weight:'72kg', bmi:'22.9', activity:'中度活动',
disease:'无', chronic:'无', medicine:'无', family:'高血压(父)',
examDate:'2026-03-15', bp:'125/80', glucose:'5.2', lipid:'正常', uric:'380', liver:'正常', kidney:'正常',
taste:'偏辣', region:'川菜、湘菜', taboo:'无', allergy:'花生',
exercise:'6000步/日', weightTrend:'稳定', bodyFat:'18%', heartRate:'68', sleep:'7.5h', stress:'低', source:'智能手环',
meals30:'58次', hotDish:'宫保鸡丁、番茄牛腩', avgCal:'680kcal', nutriRate:'88%',
favDish:'宫保鸡丁、清炒西兰花', recDish:'三文鱼沙拉', avoidDish:'油炸花生', topRate:'番茄牛腩(4.8)', repurchase:'宫保鸡丁',
hometown:'四川成都', momTag:'麻辣鲜香', momList:'回锅肉、麻婆豆腐', memory:'妈妈的回锅肉', recTry:'水煮鱼',
modelVer:'v2.3', modelTime:'2026-04-20', recCal:'2100kcal', nutriFocus:'蛋白质+膳食纤维', risk:'无', tags:'运动达人、川菜爱好者', confidence:'92%' },
meals30:'58次', hotDish:'宫保鸡丁、番茄牛腩', avgCal:'2,186 kcal/天', nutriRate:'88%',
proteinRate:'92%', highFatRate:'8%', preferMeal:'午餐', lastMeal:'2026-04-28 午餐',
mealHistory:[
{date:'2026-04-28',meal:'午餐',dishes:'清蒸鱼、炒西兰花、米饭、紫菜蛋花汤',cal:'832 kcal',score:95},
{date:'2026-04-28',meal:'早餐',dishes:'小米粥、卤蛋、凉拌黄瓜',cal:'380 kcal',score:92},
{date:'2026-04-27',meal:'午餐',dishes:'杂粮饭、清蒸鱼、蒜蓉菠菜',cal:'798 kcal',score:94},
{date:'2026-04-26',meal:'午餐',dishes:'米饭、宫保鸡丁、炒西兰花、汤',cal:'906 kcal',score:86},
{date:'2026-04-25',meal:'晚餐',dishes:'杂粮饭、清蒸鱼、炒时蔬',cal:'716 kcal',score:96},
{date:'2026-04-25',meal:'午餐',dishes:'米饭、宫保鸡丁、番茄炒蛋',cal:'928 kcal',score:88},
{date:'2026-04-24',meal:'午餐',dishes:'米饭、清蒸鱼、蒜蓉菠菜、番茄蛋花汤',cal:'768 kcal',score:95}
],
favDish:'宫保鸡丁、清炒西兰花、杂粮饭', recDish:'三文鱼沙拉、蒜蓉菠菜', avoidDish:'油炸花生', topRate:'宫保鸡丁(94分)', repurchase:'宫保鸡丁(近30日18次)',
hometown:'四川成都', momTag:'麻辣、红油、鲜香', momList:'回锅肉、麻婆豆腐、水煮鱼', memory:'从小常吃妈妈做的回锅肉,奠定了麻辣口味偏好。', recTry:'川味水煮鱼套餐(已上架一号食堂)',
modelVer:'v2.3', modelTime:'2026-04-20', recCal:'2,250 kcal', nutriFocus:'蛋白质+膳食纤维', risk:'无风险', tags:'清淡偏好、中度活动、无慢病', confidence:'94%', nextUpdate:'2026-05-28' },
'李娜': { dept:'市场部', age:34, gender:'女', height:'163cm', weight:'52kg', bmi:'19.8', activity:'轻度活动',
disease:'无', chronic:'无', medicine:'无', family:'糖尿病(母)',
examDate:'2026-02-20', bp:'110/70', glucose:'4.8', lipid:'正常', uric:'290', liver:'正常', kidney:'正常',
taste:'清淡', region:'粤菜、日料', taboo:'无', allergy:'海鲜(虾蟹)',
exercise:'4000步/日', weightTrend:'稳定', bodyFat:'22%', heartRate:'72', sleep:'7h', stress:'中', source:'手机计步',
meals30:'52次', hotDish:'蒸鱼、蔬菜沙拉', avgCal:'520kcal', nutriRate:'92%',
favDish:'清蒸鲈鱼、藜麦沙拉', recDish:'牛油果吐司', avoidDish:'麻辣火锅', topRate:'藜麦沙拉(4.9)', repurchase:'清蒸鲈鱼',
hometown:'广东广州', momTag:'鲜甜清润', momList:'老火靓汤、白切鸡', memory:'周末的煲汤', recTry:'椰子鸡',
modelVer:'v2.3', modelTime:'2026-04-18', recCal:'1650kcal', nutriFocus:'钙+铁+维生素D', risk:'注意糖摄入', tags:'轻食主义、养生达人', confidence:'95%' }
meals30:'52次', hotDish:'清蒸鲈鱼、藜麦沙拉', avgCal:'1,650 kcal/天', nutriRate:'92%',
proteinRate:'95%', highFatRate:'5%', preferMeal:'午餐', lastMeal:'2026-04-28 午餐',
mealHistory:[
{date:'2026-04-28',meal:'午餐',dishes:'藜麦沙拉、清蒸鲈鱼、蒜蓉西兰花',cal:'680 kcal',score:96},
{date:'2026-04-28',meal:'早餐',dishes:'牛油果吐司、酸奶、水果',cal:'420 kcal',score:94},
{date:'2026-04-27',meal:'午餐',dishes:'三文鱼沙拉、杂粮饭、蔬菜汤',cal:'720 kcal',score:97},
{date:'2026-04-26',meal:'午餐',dishes:'清蒸鲈鱼、藜麦饭、凉拌木耳',cal:'650 kcal',score:95},
{date:'2026-04-25',meal:'晚餐',dishes:'蔬菜沙拉、鸡胸肉、全麦面包',cal:'580 kcal',score:93},
{date:'2026-04-25',meal:'午餐',dishes:'番茄鱼片、杂粮饭、炒时蔬',cal:'750 kcal',score:91},
{date:'2026-04-24',meal:'午餐',dishes:'清蒸鲈鱼、藜麦沙拉、紫菜汤',cal:'690 kcal',score:96}
],
favDish:'清蒸鲈鱼、藜麦沙拉、杂粮饭', recDish:'蒜蓉菠菜、紫菜蛋花汤', avoidDish:'暂无', topRate:'清蒸鱼(96分)', repurchase:'清蒸鱼(近30日21次)',
hometown:'广东广州', momTag:'清炖、甜咸适中、河鲜', momList:'松鼠桂鱼、响油鳝糊、腌笃鲜', memory:'从小常吃妈妈做的清炖鱼,奠定了清淡口味偏好。', recTry:'苏式清蒸鱼套餐(已上架二号食堂)',
modelVer:'v2.3', modelTime:'2026-04-18', recCal:'1,650 kcal', nutriFocus:'钙+铁+维生素D', risk:'注意糖摄入', tags:'轻食主义、养生达人、低糖饮食', confidence:'95%', nextUpdate:'2026-05-28' }
};
const defaultProfile = { dept:'—', age:30, gender:'—', height:'170cm', weight:'65kg', bmi:'22.5', activity:'中等',
disease:'无', chronic:'无', medicine:'无', family:'无',
examDate:'2026-01-10', bp:'120/78', glucose:'5.0', lipid:'正常', uric:'350', liver:'正常', kidney:'正常',
taste:'适中', region:'不限', taboo:'无', allergy:'无',
exercise:'5000步/日', weightTrend:'稳定', bodyFat:'20%', heartRate:'70', sleep:'7h', stress:'低', source:'手动录入',
meals30:'45次', hotDish:'家常菜', avgCal:'600kcal', nutriRate:'78%',
favDish:'红烧肉、西红柿炒蛋', recDish:'杂粮饭套餐', avoidDish:'', topRate:'红烧肉(4.5)', repurchase:'西红柿炒蛋',
hometown:'—', momTag:'家常味', momList:'红烧肉', memory:'—', recTry:'—',
modelVer:'v2.3', modelTime:'2026-04-15', recCal:'1900kcal', nutriFocus:'均衡', risk:'无', tags:'普通用户', confidence:'80%' };
meals30:'45次', hotDish:'家常菜', avgCal:'1,900 kcal/天', nutriRate:'78%',
proteinRate:'80%', highFatRate:'12%', preferMeal:'午餐', lastMeal:'2026-04-28 午餐',
mealHistory:[
{date:'2026-04-28',meal:'午餐',dishes:'红烧肉、米饭、炒青菜、蛋花汤',cal:'856 kcal',score:82},
{date:'2026-04-27',meal:'午餐',dishes:'西红柿炒蛋、米饭、紫菜汤',cal:'720 kcal',score:85},
{date:'2026-04-26',meal:'午餐',dishes:'糖醋里脊、米饭、炒时蔬',cal:'880 kcal',score:80},
{date:'2026-04-25',meal:'午餐',dishes:'红烧肉、杂粮饭、凉拌黄瓜',cal:'810 kcal',score:84},
{date:'2026-04-25',meal:'早餐',dishes:'豆浆、油条、茶叶蛋',cal:'450 kcal',score:76},
{date:'2026-04-24',meal:'午餐',dishes:'宫保鸡丁、米饭、番茄蛋汤',cal:'790 kcal',score:86},
{date:'2026-04-23',meal:'午餐',dishes:'西红柿炒蛋、米饭、炒青菜',cal:'680 kcal',score:88}
],
favDish:'红烧肉、西红柿炒蛋、炒青菜', recDish:'杂粮饭套餐、蒸南瓜', avoidDish:'暂无', topRate:'红烧肉(90分)', repurchase:'西红柿炒蛋(近30日15次)',
hometown:'—', momTag:'家常、咸鲜、炖煮', momList:'红烧肉、炖排骨、西红柿炒蛋', memory:'小时候最爱妈妈做的红烧肉,是家的味道。', recTry:'家常红烧肉套餐(已上架一号食堂)',
modelVer:'v2.3', modelTime:'2026-04-15', recCal:'1,900 kcal', nutriFocus:'均衡', risk:'无风险', tags:'家常口味、中度活动、无慢病', confidence:'80%', nextUpdate:'2026-05-28' };
const tabDefs = [
{ key:'basic', label:'静态基础' },{ key:'disease', label:'疾病信息' },{ key:'physical', label:'体检信息' },
@@ -474,16 +682,95 @@ const tabDefs = [
function buildProfileHTML(name, p) {
const panels = {
basic: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">姓名</span><span class="profile-field__value">${name}</span></div><div class="profile-field"><span class="profile-field__label">部门</span><span class="profile-field__value">${p.dept}</span></div><div class="profile-field"><span class="profile-field__label">年龄</span><span class="profile-field__value">${p.age}岁</span></div><div class="profile-field"><span class="profile-field__label">性别</span><span class="profile-field__value">${p.gender}</span></div><div class="profile-field"><span class="profile-field__label">身高</span><span class="profile-field__value">${p.height}</span></div><div class="profile-field"><span class="profile-field__label">体重</span><span class="profile-field__value">${p.weight}</span></div><div class="profile-field"><span class="profile-field__label">BMI</span><span class="profile-field__value">${p.bmi}</span></div><div class="profile-field"><span class="profile-field__label">活动水平</span><span class="profile-field__value">${p.activity}</span></div></div>`,
disease: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">疾病史</span><span class="profile-field__value">${p.disease}</span></div><div class="profile-field"><span class="profile-field__label">慢病分级</span><span class="profile-field__value">${p.chronic}</span></div><div class="profile-field"><span class="profile-field__label">用药情况</span><span class="profile-field__value">${p.medicine}</span></div><div class="profile-field"><span class="profile-field__label">家族病史</span><span class="profile-field__value">${p.family}</span></div></div>`,
physical: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">体检日期</span><span class="profile-field__value">${p.examDate}</span></div><div class="profile-field"><span class="profile-field__label">血压</span><span class="profile-field__value">${p.bp} mmHg</span></div><div class="profile-field"><span class="profile-field__label">空腹血糖</span><span class="profile-field__value">${p.glucose} mmol/L</span></div><div class="profile-field"><span class="profile-field__label">血脂</span><span class="profile-field__value">${p.lipid}</span></div><div class="profile-field"><span class="profile-field__label">尿酸</span><span class="profile-field__value">${p.uric} μmol/L</span></div><div class="profile-field"><span class="profile-field__label">肝功能</span><span class="profile-field__value">${p.liver}</span></div><div class="profile-field"><span class="profile-field__label">肾功能</span><span class="profile-field__value">${p.kidney}</span></div></div>`,
taste: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">口味偏好</span><span class="profile-field__value">${p.taste}</span></div><div class="profile-field"><span class="profile-field__label">地域菜系</span><span class="profile-field__value">${p.region}</span></div></div>`,
taboo: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">饮食禁忌</span><span class="profile-field__value">${p.taboo}</span></div><div class="profile-field"><span class="profile-field__label">过敏原</span><span class="profile-field__value">${p.allergy}</span></div></div>`,
dynamic: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">日均运动量</span><span class="profile-field__value">${p.exercise}</span></div><div class="profile-field"><span class="profile-field__label">体重趋势</span><span class="profile-field__value">${p.weightTrend}</span></div><div class="profile-field"><span class="profile-field__label">体脂率</span><span class="profile-field__value">${p.bodyFat}</span></div><div class="profile-field"><span class="profile-field__label">静息心率</span><span class="profile-field__value">${p.heartRate} bpm</span></div><div class="profile-field"><span class="profile-field__label">平均睡眠</span><span class="profile-field__value">${p.sleep}</span></div><div class="profile-field"><span class="profile-field__label">压力指数</span><span class="profile-field__value">${p.stress}</span></div><div class="profile-field"><span class="profile-field__label">数据来源</span><span class="profile-field__value">${p.source}</span></div></div>`,
meal: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">近30日就餐</span><span class="profile-field__value">${p.meals30}</span></div><div class="profile-field"><span class="profile-field__label">常点菜品</span><span class="profile-field__value">${p.hotDish}</span></div><div class="profile-field"><span class="profile-field__label">平均热量</span><span class="profile-field__value">${p.avgCal}</span></div><div class="profile-field"><span class="profile-field__label">营养达标率</span><span class="profile-field__value">${p.nutriRate}</span></div></div>`,
dish: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">常点菜品</span><span class="profile-field__value">${p.favDish}</span></div><div class="profile-field"><span class="profile-field__label">系统推荐</span><span class="profile-field__value">${p.recDish}</span></div><div class="profile-field"><span class="profile-field__label">忌口菜品</span><span class="profile-field__value">${p.avoidDish}</span></div><div class="profile-field"><span class="profile-field__label">评分最高</span><span class="profile-field__value">${p.topRate}</span></div><div class="profile-field"><span class="profile-field__label">复购冠军</span><span class="profile-field__value">${p.repurchase}</span></div></div>`,
mom: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">家乡</span><span class="profile-field__value">${p.hometown}</span></div><div class="profile-field"><span class="profile-field__label">妈妈味道标签</span><span class="profile-field__value">${p.momTag}</span></div><div class="profile-field"><span class="profile-field__label">思乡菜清单</span><span class="profile-field__value">${p.momList}</span></div><div class="profile-field"><span class="profile-field__label">关联记忆</span><span class="profile-field__value">${p.memory}</span></div><div class="profile-field"><span class="profile-field__label">推荐尝试</span><span class="profile-field__value">${p.recTry}</span></div></div>`,
model: `<div class="profile-grid"><div class="profile-field"><span class="profile-field__label">模型版本</span><span class="profile-field__value">${p.modelVer}</span></div><div class="profile-field"><span class="profile-field__label">生成时间</span><span class="profile-field__value">${p.modelTime}</span></div><div class="profile-field"><span class="profile-field__label">推荐热量</span><span class="profile-field__value">${p.recCal}</span></div><div class="profile-field"><span class="profile-field__label">营养侧重</span><span class="profile-field__value">${p.nutriFocus}</span></div><div class="profile-field"><span class="profile-field__label">风险预警</span><span class="profile-field__value">${p.risk}</span></div><div class="profile-field"><span class="profile-field__label">个性标签</span><span class="profile-field__value">${p.tags}</span></div><div class="profile-field"><span class="profile-field__label">模型置信度</span><span class="profile-field__value">${p.confidence}</span></div></div>`
basic: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">姓名</span><span class="profile-field__value">${name}</span></div>
<div class="profile-field"><span class="profile-field__label">部门</span><span class="profile-field__value">${p.dept}</span></div>
<div class="profile-field"><span class="profile-field__label">年龄</span><span class="profile-field__value">${p.age}</span></div>
<div class="profile-field"><span class="profile-field__label">性别</span><span class="profile-field__value">${p.gender}</span></div>
<div class="profile-field"><span class="profile-field__label">身高</span><span class="profile-field__value">${p.height}</span></div>
<div class="profile-field"><span class="profile-field__label">体重</span><span class="profile-field__value">${p.weight}</span></div>
<div class="profile-field"><span class="profile-field__label">BMI</span><span class="profile-field__value">${p.bmi}</span></div>
<div class="profile-field"><span class="profile-field__label">活动水平</span><span class="profile-field__value">${p.activity}</span></div>
</div>`,
disease: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">疾病史</span><span class="profile-field__value">${p.disease}</span></div>
<div class="profile-field"><span class="profile-field__label">慢病分级</span><span class="profile-field__value">${p.chronic}</span></div>
<div class="profile-field"><span class="profile-field__label">用药情况</span><span class="profile-field__value">${p.medicine}</span></div>
<div class="profile-field"><span class="profile-field__label">家族病史</span><span class="profile-field__value">${p.family}</span></div>
</div>`,
physical: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">体检日期</span><span class="profile-field__value">${p.examDate}</span></div>
<div class="profile-field"><span class="profile-field__label">血压</span><span class="profile-field__value">${p.bp} mmHg</span></div>
<div class="profile-field"><span class="profile-field__label">空腹血糖</span><span class="profile-field__value">${p.glucose} mmol/L</span></div>
<div class="profile-field"><span class="profile-field__label">血脂</span><span class="profile-field__value">${p.lipid}</span></div>
<div class="profile-field"><span class="profile-field__label">尿酸</span><span class="profile-field__value">${p.uric} μmol/L</span></div>
<div class="profile-field"><span class="profile-field__label">肝功能</span><span class="profile-field__value">${p.liver}</span></div>
<div class="profile-field"><span class="profile-field__label">肾功能</span><span class="profile-field__value">${p.kidney}</span></div>
</div>`,
taste: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">口味偏好</span><span class="profile-field__value">${p.taste}</span></div>
<div class="profile-field"><span class="profile-field__label">地域菜系</span><span class="profile-field__value">${p.region}</span></div>
</div>`,
taboo: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">饮食禁忌</span><span class="profile-field__value">${p.taboo}</span></div>
<div class="profile-field"><span class="profile-field__label">过敏原</span><span class="profile-field__value">${p.allergy}</span></div>
</div>`,
dynamic: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">日均运动量</span><span class="profile-field__value">${p.exercise}</span></div>
<div class="profile-field"><span class="profile-field__label">体重趋势</span><span class="profile-field__value">${p.weightTrend}</span></div>
<div class="profile-field"><span class="profile-field__label">体脂率</span><span class="profile-field__value">${p.bodyFat}</span></div>
<div class="profile-field"><span class="profile-field__label">静息心率</span><span class="profile-field__value">${p.heartRate} bpm</span></div>
<div class="profile-field"><span class="profile-field__label">平均睡眠</span><span class="profile-field__value">${p.sleep}</span></div>
<div class="profile-field"><span class="profile-field__label">压力指数</span><span class="profile-field__value">${p.stress}</span></div>
<div class="profile-field"><span class="profile-field__label">数据来源</span><span class="profile-field__value">${p.source}</span></div>
</div>`,
meal: `<div class="profile-grid">
<div class="profile-field"><span class="profile-field__label">近30日就餐次数</span><span class="profile-field__value">${p.meals30}</span></div>
<div class="profile-field"><span class="profile-field__label">最常点菜品</span><span class="profile-field__value">${p.hotDish}</span></div>
<div class="profile-field"><span class="profile-field__label">平均热量摄入</span><span class="profile-field__value">${p.avgCal}</span></div>
<div class="profile-field"><span class="profile-field__label">蛋白质达标率</span><span class="profile-field__value">${p.proteinRate}</span></div>
<div class="profile-field"><span class="profile-field__label">高脂餐占比</span><span class="profile-field__value">${p.highFatRate}</span></div>
<div class="profile-field"><span class="profile-field__label">偏好餐次</span><span class="profile-field__value">${p.preferMeal}</span></div>
</div>
<div class="profile-field" style="border-bottom:none;padding:12px 0 4px;"><span class="profile-field__label">最近一次就餐</span><span class="profile-field__value">${p.lastMeal}</span></div>
<div style="margin-top:16px;font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:10px;">📋 最近用餐明细(${p.mealHistory.length}条)</div>
<table class="meal-history-table">
<thead><tr><th>日期</th><th>餐次</th><th>菜品</th><th>热量</th><th>营养评分</th></tr></thead>
<tbody>${p.mealHistory.map(h => '<tr><td>'+h.date+'</td><td><span class="tag tag-blue">'+h.meal+'</span></td><td>'+h.dishes+'</td><td>'+h.cal+'</td><td><span class="score-tag'+(h.score>=90?' score-green':h.score>=85?' score-green':' score-orange')+'">'+h.score+'分</span></td></tr>').join('')}</tbody>
</table>`,
dish: `<div class="dish-pref-section">
<div class="dish-pref-item"><div class="dish-pref-item__label">常点菜品</div><div class="dish-pref-item__tags">${p.favDish.split('、').map(d=>'<span class="dish-tag">'+d+'</span>').join('')}</div></div>
<div class="dish-pref-item"><div class="dish-pref-item__label">系统推荐</div><div class="dish-pref-item__tags">${p.recDish.split('、').map(d=>'<span class="dish-tag">'+d+'</span>').join('')}</div></div>
<div class="dish-pref-item"><div class="dish-pref-item__label">忌口菜品</div><div class="dish-pref-item__value">${p.avoidDish||'暂无'}</div></div>
<div class="dish-pref-item"><div class="dish-pref-item__label">评分最高</div><div class="dish-pref-item__big">${p.topRate}</div></div>
<div class="dish-pref-item"><div class="dish-pref-item__label">复购冠军</div><div class="dish-pref-item__big">${p.repurchase}</div></div>
</div>`,
mom: `<div class="mom-section">
<div class="mom-header">
<div class="mom-header__icon">🐱</div>
<div class="mom-header__info"><div class="mom-header__title">家乡:${p.hometown}</div><div class="mom-header__sub">从童年记忆中沉淀的味觉偏好与情感连接</div></div>
</div>
<div class="mom-header__tags">${p.momTag.split('、').map(t=>'<span class="mom-tag">'+t+'</span>').join('')}</div>
</div>
<div class="dish-pref-section" style="margin-top:20px;">
<div class="dish-pref-item"><div class="dish-pref-item__label">思乡菜清单</div><div class="dish-pref-item__tags">${p.momList.split('、').map(d=>'<span class="dish-tag" style="color:#FA8C16;background:#FFF7E6;border-color:#FFD591;">'+d+'</span>').join('')}</div></div>
<div class="dish-pref-item"><div class="dish-pref-item__label">关联记忆</div><div class="dish-pref-item__value" style="margin-top:4px;">${p.memory}</div></div>
<div class="dish-pref-item"><div class="dish-pref-item__label">推荐尝试</div><div class="dish-pref-item__value" style="color:#52C41A;font-weight:600;margin-top:4px;">${p.recTry}</div></div>
</div>`,
model: `<div class="model-section">
<div class="model-header">
<div class="model-header__icon">🤖</div>
<div class="model-header__info"><div class="model-header__title">${name} 的个性化营养模型</div><div class="model-header__sub">${p.modelVer} · 生成于 ${p.modelTime} · 置信度 ${p.confidence}</div></div>
</div>
<div class="model-header__tags">${p.tags.split('、').map(t=>'<span class="model-tag">'+t+'</span>').join('')}</div>
</div>
<div class="model-grid">
<div class="model-item"><div class="model-item__label">推荐每日热量</div><div class="model-item__big">${p.recCal}</div></div>
<div class="model-item"><div class="model-item__label">营养侧重</div><div class="model-item__big">${p.nutriFocus}</div></div>
<div class="model-item"><div class="model-item__label">风险预警</div><div class="model-item__value"><span class="risk-tag${p.risk==='无风险'?' risk-safe':' risk-warn'}">${p.risk==='无风险'?'✅ ':''} ${p.risk}</span></div></div>
<div class="model-item"><div class="model-item__label">下次模型更新</div><div class="model-item__big">${p.nextUpdate}</div></div>
</div>
<div class="model-footer">️ 个性模型由前9维档案数据综合计算生成(含菜品偏好、妈妈味道等情感维度),每月自动更新一次</div>`
};
const tabs = tabDefs.map((t, i) => `<div class="profile-tab${i===0?' active':''}" onclick="switchProfileTab(this,'${t.key}')">${t.label}</div>`).join('');
const panelsHtml = tabDefs.map((t, i) => `<div class="profile-panel${i===0?' active':''}" data-panel="${t.key}">${panels[t.key]}</div>`).join('');
@@ -500,7 +787,7 @@ function switchProfileTab(el, key) {
function showUserProfile(name) {
const p = userProfiles[name] || defaultProfile;
Modal.open('用户十维档案 — ' + name, buildProfileHTML(name, p));
Modal.open('用户信息 — ' + name, buildProfileHTML(name, p));
}
/* ========== 推荐食谱弹窗 ========== */
@@ -551,11 +838,169 @@ function showRecipeModal(name) {
/* ========== 事件绑定 ========== */
document.querySelectorAll('.tab-panel')[0].querySelectorAll('tbody tr').forEach(row => {
const links = row.querySelectorAll('.td-actions .link');
const nameCell = row.querySelector('td:first-child');
if (links[0] && nameCell) links[0].addEventListener('click', () => showUserProfile(nameCell.textContent));
if (links[1] && nameCell) links[1].addEventListener('click', () => showRecipeModal(nameCell.textContent));
const name = nameCell ? nameCell.textContent : '';
// 详情
const detailLink = row.querySelector('[data-action="detail"]');
if (detailLink) detailLink.addEventListener('click', () => showUserProfile(name));
// 编辑
const editLink = row.querySelector('[data-action="edit"]');
if (editLink) editLink.addEventListener('click', () => { Modal.open('编辑用户 — '+name, '<div style="padding:20px;text-align:center;color:var(--text-placeholder);">编辑表单(原型占位)</div>', '', '<button class="btn btn-default" onclick="Modal.close()">取消</button><button class="btn btn-primary" onclick="Modal.close()">保存</button>'); });
// 更多按钮
const moreLink = row.querySelector('[data-action="more"]');
if (moreLink) {
moreLink.addEventListener('click', function(e) {
e.stopPropagation();
var wrap = this.closest('.more-wrap');
var tip = wrap.querySelector('.more-tip');
// 关闭其他已打开的
document.querySelectorAll('.more-wrap.open').forEach(w => { if(w!==wrap) w.classList.remove('open'); });
var isOpen = wrap.classList.toggle('open');
if (isOpen) {
// 将 tip 移到 body 以脱离 sticky 层叠上下文
document.body.appendChild(tip);
var rect = this.getBoundingClientRect();
tip.style.top = (rect.bottom + 4) + 'px';
tip.style.left = (rect.right - tip.offsetWidth) + 'px';
tip.style.display = 'block';
wrap._tip = tip;
} else {
if (wrap._tip) { wrap._tip.style.display = 'none'; wrap.appendChild(wrap._tip); wrap._tip = null; }
}
});
}
// 推荐食谱
const recipeLink = row.querySelector('[data-action="recipe"]');
if (recipeLink) recipeLink.addEventListener('click', () => { closeAllMoreTips(); showRecipeModal(name); });
// 人脸采集
const faceLink = row.querySelector('[data-action="face"]');
if (faceLink) faceLink.addEventListener('click', () => { closeAllMoreTips(); showFaceModal(name, row); });
// 删除
const deleteLink = row.querySelector('[data-action="delete"]');
if (deleteLink) deleteLink.addEventListener('click', () => { closeAllMoreTips(); if(confirm('确定要删除用户「'+name+'」吗?此操作不可恢复。')){ row.remove(); } });
});
/* ========== 人脸采集弹窗 ========== */
// 模拟:张伟、李娜有人脸数据,其他人无
const faceDataMap = { '张伟': true, '李娜': true };
function showFaceModal(name) {
const hasFace = !!faceDataMap[name];
const currentFaceHtml = hasFace
? `<div class="face-current__img has-face"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120'%3E%3Crect fill='%23E6F7FF' width='120' height='120'/%3E%3Ctext x='60' y='68' text-anchor='middle' font-size='42'%3E🧑%3C/text%3E%3C/svg%3E" alt="人脸"></div>
<div class="face-current__status ok">✅ 已采集人脸数据(可重新采集替换)</div>`
: `<div class="face-current__img">🧑</div>
<div class="face-current__status">⚠️ 暂无人脸数据,请选择采集方式</div>`;
const html = `
<div class="face-current">${currentFaceHtml}</div>
<div style="font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:12px;">${hasFace ? '重新采集' : '选择采集方式'}</div>
<div class="face-methods">
<div class="face-method-card" onclick="switchFaceMethod('camera')">
<div class="face-method-card__icon">📷</div>
<div class="face-method-card__title">摄像头采集</div>
<div class="face-method-card__desc">使用摄像头实时拍摄人脸</div>
</div>
<div class="face-method-card" onclick="switchFaceMethod('upload')">
<div class="face-method-card__icon">📤</div>
<div class="face-method-card__title">上传图片</div>
<div class="face-method-card__desc">上传本地人脸照片</div>
</div>
</div>
<div class="face-panel" data-face="camera">
<div class="face-camera">
<div class="face-camera__view">
<div class="face-camera__mask"></div>
模拟摄像头画面
</div>
<div style="font-size:var(--font-xs);color:var(--text-secondary);">请将面部置于圆形区域内,保持正面</div>
<button class="btn btn-primary" onclick="captureFace()">📸 拍摄</button>
</div>
</div>
<div class="face-panel" data-face="upload">
<div class="face-upload">
<div class="face-upload__drop" id="faceDropZone" onclick="document.getElementById('faceFileInput').click()">
<div class="face-upload__drop-icon">📁</div>
<div class="face-upload__drop-text">点击选择图片或拖拽到此处</div>
<div class="face-upload__drop-hint">支持 JPG、PNG 格式,文件不超过 5MB</div>
</div>
<input type="file" id="faceFileInput" accept="image/*" style="display:none;" onchange="previewFaceUpload(this)">
<div class="face-upload__preview" id="facePreview">
<img id="facePreviewImg" src="" alt="预览">
<div class="face-upload__preview-actions">
<button class="btn btn-default" onclick="resetFaceUpload()">重新选择</button>
<button class="btn btn-primary" onclick="confirmFaceUpload()">确认上传</button>
</div>
</div>
</div>
</div>`;
Modal.open('人脸采集 — ' + name, html, '', '<button class="btn btn-default" onclick="Modal.close()">关闭</button>');
}
function switchFaceMethod(method) {
document.querySelectorAll('.face-method-card').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.face-panel').forEach(p => p.classList.remove('active'));
event.currentTarget.classList.add('active');
var panel = document.querySelector('[data-face="'+method+'"]');
if (panel) panel.classList.add('active');
}
function captureFace() {
// 模拟拍摄成功
var view = document.querySelector('.face-camera__view');
if (view) {
view.innerHTML = '<div class="face-camera__mask" style="border-color:#52C41A;"></div><div style="position:absolute;bottom:12px;background:rgba(82,196,26,.9);color:#fff;padding:4px 16px;border-radius:4px;font-size:13px;">✅ 采集成功</div>模拟摄像头画面';
}
// 更新顶部状态
var status = document.querySelector('.face-current__status');
if (status) { status.className = 'face-current__status ok'; status.textContent = '✅ 人脸数据已更新'; }
var img = document.querySelector('.face-current__img');
if (img && !img.classList.contains('has-face')) { img.classList.add('has-face'); }
}
function previewFaceUpload(input) {
if (!input.files || !input.files[0]) return;
var drop = document.getElementById('faceDropZone');
var preview = document.getElementById('facePreview');
var previewImg = document.getElementById('facePreviewImg');
// 模拟预览(原型用默认头像)
previewImg.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160'%3E%3Crect fill='%23E6F7FF' width='160' height='160'/%3E%3Ctext x='80' y='90' text-anchor='middle' font-size='56'%3E🧑%3C/text%3E%3C/svg%3E";
drop.style.display = 'none';
preview.style.display = 'block';
}
function resetFaceUpload() {
var drop = document.getElementById('faceDropZone');
var preview = document.getElementById('facePreview');
var input = document.getElementById('faceFileInput');
drop.style.display = 'flex';
preview.style.display = 'none';
if (input) input.value = '';
}
function confirmFaceUpload() {
// 模拟上传成功
var status = document.querySelector('.face-current__status');
if (status) { status.className = 'face-current__status ok'; status.textContent = '✅ 人脸数据已更新'; }
var img = document.querySelector('.face-current__img');
if (img && !img.classList.contains('has-face')) { img.classList.add('has-face'); }
resetFaceUpload();
// 隐藏上传面板
document.querySelectorAll('.face-method-card').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.face-panel').forEach(p => p.classList.remove('active'));
}
// 点击页面其他区域关闭 more-tip
function closeAllMoreTips() {
document.querySelectorAll('.more-wrap.open').forEach(w => {
w.classList.remove('open');
if (w._tip) { w._tip.style.display = 'none'; w.appendChild(w._tip); w._tip = null; }
});
// 清理可能残留在 body 上的 tip
document.querySelectorAll('body > .more-tip').forEach(t => { t.style.display = 'none'; });
}
document.addEventListener('click', closeAllMoreTips);
</script>
</body>
</html>