feat: 营养管理异常事件页面重构及多页面交互优化
- 重构系统脏数据页面(ano-dirty):去掉统计卡片,筛选项改为业务模块/异常类型/年份,表格列重新设计 - 重构结果异常页面(ano-result):去掉统计卡片,筛选项改为年份/异常类型/时间范围,操作列仅保留详情 - 营养用餐模块多页面交互优化(数据、模型、终端、订单等) - 删除 din-user-mgmt-dialog.html,用户管理弹窗合并至主页面 - 同步更新 index.html SITE_MAP(移除已删除条目,新增 arc-employee-detail) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-3
@@ -230,6 +230,7 @@ const SITE_MAP = [
|
||||
{ file:'trace-query.html', label:'溯源查询' },
|
||||
{ file:'trace-timeline.html', label:'链路可视化' },
|
||||
{ file:'arc-employee.html', label:'员工营养数据' },
|
||||
{ file:'arc-employee-detail.html', label:'员工营养详情' },
|
||||
{ file:'arc-unit.html', label:'单位报表数据' },
|
||||
{ file:'arc-algo.html', label:'算法模型' },
|
||||
{ file:'farm-land.html', label:'土地环境' },
|
||||
@@ -247,11 +248,9 @@ const SITE_MAP = [
|
||||
{ file:'sup-clean-internal.html', label:'净菜内供' },
|
||||
{ file:'sup-clean-external.html', label:'净菜外售' },
|
||||
{ file:'din-user-mgmt.html', label:'用户管理' },
|
||||
{ file:'din-user-mgmt-dialog.html', label:'用户管理弹窗', dialog:true },
|
||||
{ file:'din-dish-all.html', label:'菜品总库' },
|
||||
{ file:'din-dish-all-dialog.html', label:'菜品总库弹窗', dialog:true },
|
||||
{ file:'din-dish-add.html', label:'新增菜品' },
|
||||
{ file:'din-dish-composition.html', label:'菜品成分' },
|
||||
{ file:'din-ingredient.html', label:'食材管理' },
|
||||
{ file:'din-ingredient-dialog.html', label:'食材管理弹窗', dialog:true },
|
||||
{ file:'din-ingredient-detail.html', label:'食材详情' },
|
||||
@@ -271,7 +270,6 @@ const SITE_MAP = [
|
||||
{ file:'din-data-consume.html', label:'消费数据' },
|
||||
{ file:'din-data-benefit.html', label:'效益数据' },
|
||||
{ file:'prod-recipe-plan.html', label:'食谱排期' },
|
||||
{ file:'prod-recipe-plan-dialog.html', label:'食谱排期弹窗', dialog:true },
|
||||
{ file:'prod-clean-order.html', label:'净菜下单' },
|
||||
{ file:'prod-proc-combo.html', label:'配菜组合' },
|
||||
{ file:'prod-proc-cook.html', label:'烹饪加工' },
|
||||
|
||||
+176
-101
@@ -35,20 +35,27 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
|
||||
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar{background:#fff;border-radius:var(--radius-md);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;flex-wrap:wrap;}
|
||||
.filter-item{display:flex;align-items:center;gap:8px;}
|
||||
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
|
||||
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;cursor:pointer;min-width:120px;}
|
||||
|
||||
/* 按钮 */
|
||||
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
|
||||
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
|
||||
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
|
||||
|
||||
/* 操作栏 */
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
.action-bar__right{margin-left:auto;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.action-bar__right strong{color:var(--primary);font-weight:600;}
|
||||
|
||||
/* 表格卡片 */
|
||||
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
|
||||
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.table-card__title{font-size:var(--font-base);font-weight:600;}
|
||||
.table-card__actions{display:flex;gap:8px;align-items:center;}
|
||||
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
|
||||
.table-card__content table{min-width:100%;white-space:nowrap;}
|
||||
table{width:100%;border-collapse:collapse;}
|
||||
@@ -56,33 +63,26 @@ thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--fo
|
||||
tbody td{padding:14px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;}
|
||||
tbody tr:last-child td{border-bottom:none;}
|
||||
tbody tr:hover{background:#F8FBFF;}
|
||||
|
||||
/* 标签 */
|
||||
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
|
||||
.tag-red{background:#FFF1F0;color:var(--danger);}
|
||||
.tag-orange{background:#FFF7E6;color:var(--warning);}
|
||||
.tag-blue{background:var(--primary-light);color:var(--primary);}
|
||||
.tag-green{background:#F6FFED;color:var(--success);}
|
||||
.tag-orange{background:#FFF7E6;color:var(--warning);}
|
||||
.tag-red{background:#FFF1F0;color:var(--danger);}
|
||||
.tag-gray{background:#F5F5F5;color:#999;}
|
||||
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;}
|
||||
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
|
||||
/* 分页 */
|
||||
.pagination{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-top:1px solid var(--border);}
|
||||
.pagination__left{display:flex;align-items:center;gap:8px;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.pagination__left .filter-select{min-width:80px;height:32px;}
|
||||
.pagination__right{display:flex;align-items:center;gap:6px;}
|
||||
.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);}
|
||||
.page-ellipsis{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-row{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:16px;}
|
||||
.stat-card{background:#fff;border-radius:var(--radius-md);padding:20px 24px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;}
|
||||
.stat-card__icon{width:48px;height:48px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:24px;}
|
||||
.stat-card__icon.blue{background:var(--primary-light);}
|
||||
.stat-card__icon.green{background:#F6FFED;}
|
||||
.stat-card__icon.orange{background:#FFF7E6;}
|
||||
.stat-card__icon.red{background:#FFF1F0;}
|
||||
.stat-card__info{display:flex;flex-direction:column;gap:4px;}
|
||||
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
|
||||
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
/* 异常值高亮 */
|
||||
.val-error{color:var(--danger);font-weight:500;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -119,129 +119,204 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 主内容区 -->
|
||||
<div class="main">
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__icon orange">🗑️</div>
|
||||
<div class="stat-card__info">
|
||||
<div class="stat-card__value">28</div>
|
||||
<div class="stat-card__label">今日脏数据</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__icon green">✅</div>
|
||||
<div class="stat-card__info">
|
||||
<div class="stat-card__value">20</div>
|
||||
<div class="stat-card__label">已修正</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__icon orange">⏳</div>
|
||||
<div class="stat-card__info">
|
||||
<div class="stat-card__value">8</div>
|
||||
<div class="stat-card__label">待处理</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item">
|
||||
<label>来源模块</label>
|
||||
<label>业务模块</label>
|
||||
<select class="filter-select">
|
||||
<option>全部</option>
|
||||
<option>营养用餐</option>
|
||||
<option>食谱管理</option>
|
||||
<option>订餐管理</option>
|
||||
<option>用户档案</option>
|
||||
<option>营养分析</option>
|
||||
<option>食材库存</option>
|
||||
<option>健康生产</option>
|
||||
<option>卫生供应</option>
|
||||
<option>人员健康</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>数据类型</label>
|
||||
<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>
|
||||
<select class="filter-select">
|
||||
<option>全部</option>
|
||||
<option>待处理</option>
|
||||
<option>已修正</option>
|
||||
<option>已忽略</option>
|
||||
<label>年份</label>
|
||||
<select class="filter-select" style="min-width:90px;">
|
||||
<option>2026</option>
|
||||
<option>2025</option>
|
||||
<option>2024</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"><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 class="action-bar__right">共 <strong>156</strong> 条脏数据</div>
|
||||
</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>记录ID</th>
|
||||
<th>来源模块</th>
|
||||
<th>数据类型</th>
|
||||
<th>问题描述</th>
|
||||
<th>影响范围</th>
|
||||
<th>发现时间</th>
|
||||
<th>处理状态</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>DIRTY-001</td>
|
||||
<td>营养用餐</td>
|
||||
<td>格式错误</td>
|
||||
<td>员工EMP0234营养摄入值为负数</td>
|
||||
<td>1条记录</td>
|
||||
<td>2026-04-15 08:30</td>
|
||||
<td><span class="tag tag-orange">待处理</span></td>
|
||||
<td class="td-actions"><a class="link">查看</a> <a class="link">修正</a></td>
|
||||
<td>食谱管理</td>
|
||||
<td><span class="tag tag-red">数据缺失</span></td>
|
||||
<td>热量值字段为空,无法参与营养计算</td>
|
||||
<td>2026-05-14 09:23</td>
|
||||
<td>t_recipe_nutrition</td>
|
||||
<td>calorie</td>
|
||||
<td>RCP-20260514-0087</td>
|
||||
<td class="val-error">NULL</td>
|
||||
<td>2026-05-14 08:10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>订餐管理</td>
|
||||
<td><span class="tag tag-orange">格式错误</span></td>
|
||||
<td>订餐日期格式非法,无法解析</td>
|
||||
<td>2026-05-14 09:15</td>
|
||||
<td>t_order_meal</td>
|
||||
<td>order_date</td>
|
||||
<td>ORD-20260514-0342</td>
|
||||
<td class="val-error">2026/13/45</td>
|
||||
<td>2026-05-14 07:55</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>用户档案</td>
|
||||
<td><span class="tag tag-red">逻辑矛盾</span></td>
|
||||
<td>员工年龄为负数,BMI值超出合理范围</td>
|
||||
<td>2026-05-14 08:50</td>
|
||||
<td>t_employee_health</td>
|
||||
<td>age / bmi</td>
|
||||
<td>EMP-00156</td>
|
||||
<td class="val-error">age=-5, bmi=999</td>
|
||||
<td>2026-05-13 18:30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>营养分析</td>
|
||||
<td><span class="tag tag-blue">重复数据</span></td>
|
||||
<td>同一用户同日存在3条重复营养分析记录</td>
|
||||
<td>2026-05-13 17:30</td>
|
||||
<td>t_nutri_analysis</td>
|
||||
<td>user_id</td>
|
||||
<td>NA-20260513-0218</td>
|
||||
<td class="val-error">3条重复</td>
|
||||
<td>2026-05-13 12:00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>食材库存</td>
|
||||
<td><span class="tag tag-red">范围越界</span></td>
|
||||
<td>库存数量为负值,疑似出库数据异常</td>
|
||||
<td>2026-05-13 16:45</td>
|
||||
<td>t_material_stock</td>
|
||||
<td>stock_qty</td>
|
||||
<td>MAT-00523</td>
|
||||
<td class="val-error">-500 kg</td>
|
||||
<td>2026-05-13 15:20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>健康生产</td>
|
||||
<td><span class="tag tag-orange">格式错误</span></td>
|
||||
<td>生产批次号含非法字符,无法关联溯源</td>
|
||||
<td>2026-05-13 15:10</td>
|
||||
<td>t_prod_batch</td>
|
||||
<td>batch_no</td>
|
||||
<td>PRD-20260513-0091</td>
|
||||
<td class="val-error">B@TCH#001!</td>
|
||||
<td>2026-05-13 14:00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DIRTY-002</td>
|
||||
<td>卫生供应</td>
|
||||
<td>缺失值</td>
|
||||
<td>采购单食材重量字段为空</td>
|
||||
<td>1条记录</td>
|
||||
<td>2026-04-14 16:20</td>
|
||||
<td><span class="tag tag-green">已修正</span></td>
|
||||
<td class="td-actions"><a class="link">查看</a> <a class="link">修正</a></td>
|
||||
<td><span class="tag tag-red">数据缺失</span></td>
|
||||
<td>供应商资质证书编号为空</td>
|
||||
<td>2026-05-13 14:20</td>
|
||||
<td>t_supplier_cert</td>
|
||||
<td>cert_no</td>
|
||||
<td>SUP-00078</td>
|
||||
<td class="val-error">NULL</td>
|
||||
<td>2026-05-13 10:30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DIRTY-003</td>
|
||||
<td>人员健康</td>
|
||||
<td>重复数据</td>
|
||||
<td>员工EMP0156今日就餐记录重复录入</td>
|
||||
<td>2条记录</td>
|
||||
<td>2026-04-15 12:05</td>
|
||||
<td><span class="tag tag-gray">已忽略</span></td>
|
||||
<td class="td-actions"><a class="link">查看</a> <a class="link">修正</a></td>
|
||||
<td>订餐管理</td>
|
||||
<td><span class="tag tag-red">范围越界</span></td>
|
||||
<td>单次订餐份数超出系统上限</td>
|
||||
<td>2026-05-13 11:05</td>
|
||||
<td>t_order_meal</td>
|
||||
<td>quantity</td>
|
||||
<td>ORD-20260513-0198</td>
|
||||
<td class="val-error">99999</td>
|
||||
<td>2026-05-13 10:45</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>食谱管理</td>
|
||||
<td><span class="tag tag-orange">关联缺失</span></td>
|
||||
<td>食谱引用的食材ID在食材库中不存在</td>
|
||||
<td>2026-05-12 17:30</td>
|
||||
<td>t_recipe_item</td>
|
||||
<td>material_id</td>
|
||||
<td>RCP-20260512-0045</td>
|
||||
<td class="val-error">MAT-99999</td>
|
||||
<td>2026-05-12 16:00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>用户档案</td>
|
||||
<td><span class="tag tag-blue">重复数据</span></td>
|
||||
<td>员工健康档案存在重复建档记录</td>
|
||||
<td>2026-05-12 15:40</td>
|
||||
<td>t_employee_health</td>
|
||||
<td>emp_id</td>
|
||||
<td>EMP-00234</td>
|
||||
<td class="val-error">2条重复</td>
|
||||
<td>2026-05-12 09:15</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination">
|
||||
<span>显示 3 条,共 28 条</span>
|
||||
<button class="page-btn">‹</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">2</button>
|
||||
<button class="page-btn">3</button>
|
||||
<button class="page-btn">›</button>
|
||||
<div class="pagination__left">
|
||||
<span>共 156 条</span>
|
||||
<span>每页</span>
|
||||
<select class="filter-select" style="min-width:70px;">
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>50</option>
|
||||
</select>
|
||||
<span>条</span>
|
||||
</div>
|
||||
<div class="pagination__right">
|
||||
<button class="page-btn">‹</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">2</button>
|
||||
<button class="page-btn">3</button>
|
||||
<span class="page-ellipsis">…</span>
|
||||
<button class="page-btn">16</button>
|
||||
<button class="page-btn">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -35,20 +35,30 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
|
||||
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar{background:#fff;border-radius:var(--radius-md);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;flex-wrap:wrap;}
|
||||
.filter-item{display:flex;align-items:center;gap:8px;}
|
||||
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
|
||||
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;cursor:pointer;min-width:120px;}
|
||||
.filter-input{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:140px;}
|
||||
.filter-input:focus{border-color:var(--primary);}
|
||||
.filter-range-sep{font-size:var(--font-sm);color:var(--text-placeholder);}
|
||||
|
||||
/* 按钮 */
|
||||
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
|
||||
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
|
||||
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
|
||||
|
||||
/* 操作栏 */
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
.action-bar__right{margin-left:auto;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.action-bar__right strong{color:var(--primary);font-weight:600;}
|
||||
|
||||
/* 表格卡片 */
|
||||
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
|
||||
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.table-card__title{font-size:var(--font-base);font-weight:600;}
|
||||
.table-card__actions{display:flex;gap:8px;align-items:center;}
|
||||
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
|
||||
.table-card__content table{min-width:100%;white-space:nowrap;}
|
||||
table{width:100%;border-collapse:collapse;}
|
||||
@@ -56,33 +66,31 @@ thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--fo
|
||||
tbody td{padding:14px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;}
|
||||
tbody tr:last-child td{border-bottom:none;}
|
||||
tbody tr:hover{background:#F8FBFF;}
|
||||
|
||||
/* 标签 */
|
||||
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
|
||||
.tag-red{background:#FFF1F0;color:var(--danger);}
|
||||
.tag-orange{background:#FFF7E6;color:var(--warning);}
|
||||
.tag-blue{background:var(--primary-light);color:var(--primary);}
|
||||
.tag-green{background:#F6FFED;color:var(--success);}
|
||||
.tag-orange{background:#FFF7E6;color:var(--warning);}
|
||||
.tag-red{background:#FFF1F0;color:var(--danger);}
|
||||
.tag-gray{background:#F5F5F5;color:#999;}
|
||||
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;}
|
||||
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.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);}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-row{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:16px;}
|
||||
.stat-card{background:#fff;border-radius:var(--radius-md);padding:20px 24px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;}
|
||||
.stat-card__icon{width:48px;height:48px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:24px;}
|
||||
.stat-card__icon.blue{background:var(--primary-light);}
|
||||
.stat-card__icon.green{background:#F6FFED;}
|
||||
.stat-card__icon.orange{background:#FFF7E6;}
|
||||
.stat-card__icon.red{background:#FFF1F0;}
|
||||
.stat-card__info{display:flex;flex-direction:column;gap:4px;}
|
||||
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
|
||||
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
/* 操作链接 */
|
||||
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;}
|
||||
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
|
||||
/* 分页 */
|
||||
.pagination{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-top:1px solid var(--border);}
|
||||
.pagination__left{display:flex;align-items:center;gap:8px;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.pagination__left .filter-select{min-width:70px;height:32px;}
|
||||
.pagination__right{display:flex;align-items:center;gap:6px;}
|
||||
.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);}
|
||||
.page-ellipsis{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -119,26 +127,16 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 主内容区 -->
|
||||
<div class="main">
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__icon red">❌</div>
|
||||
<div class="stat-card__info">
|
||||
<div class="stat-card__value">12</div>
|
||||
<div class="stat-card__label">今日结果异常</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__icon green">✅</div>
|
||||
<div class="stat-card__info">
|
||||
<div class="stat-card__value">8</div>
|
||||
<div class="stat-card__label">已溯源</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item">
|
||||
<label>年份</label>
|
||||
<select class="filter-select" style="min-width:90px;">
|
||||
<option>2026</option>
|
||||
<option>2025</option>
|
||||
<option>2024</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>异常类型</label>
|
||||
<select class="filter-select">
|
||||
@@ -146,38 +144,29 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<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>
|
||||
<div class="filter-item">
|
||||
<label>严重程度</label>
|
||||
<select class="filter-select">
|
||||
<option>全部</option>
|
||||
<option>高</option>
|
||||
<option>中</option>
|
||||
<option>低</option>
|
||||
</select>
|
||||
<label>时间范围</label>
|
||||
<input type="date" class="filter-input" value="2026-05-01">
|
||||
<span class="filter-range-sep">至</span>
|
||||
<input type="date" class="filter-input" value="2026-05-14">
|
||||
</div>
|
||||
<button class="btn btn-primary">查询</button>
|
||||
<button class="btn btn-default">重置</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 class="action-bar__right">共 <strong>89</strong> 条异常记录</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-card">
|
||||
<div class="table-card__header">
|
||||
<div class="table-card__title">结果异常</div>
|
||||
</div>
|
||||
<div class="table-card__content">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -194,46 +183,129 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>ERR-001</td>
|
||||
<td>营养值异常</td>
|
||||
<td>ERR-2026-001</td>
|
||||
<td><span class="tag tag-red">营养值异常</span></td>
|
||||
<td>营养计算</td>
|
||||
<td>员工EMP0234午餐热量计算结果为0kcal</td>
|
||||
<td><span class="tag tag-red">高</span></td>
|
||||
<td>v2.3.1</td>
|
||||
<td>2026-04-15 12:30</td>
|
||||
<td class="td-actions"><a class="link">查看</a> <a class="link">溯源</a></td>
|
||||
<td>2026-05-14 12:30</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-002</td>
|
||||
<td>推荐结果为空</td>
|
||||
<td>ERR-2026-002</td>
|
||||
<td><span class="tag tag-orange">推荐结果为空</span></td>
|
||||
<td>菜品推荐</td>
|
||||
<td>员工EMP0456个性化推荐返回空列表</td>
|
||||
<td><span class="tag tag-orange">中</span></td>
|
||||
<td>v1.8.0</td>
|
||||
<td>2026-04-15 11:45</td>
|
||||
<td class="td-actions"><a class="link">查看</a> <a class="link">溯源</a></td>
|
||||
<td>2026-05-14 11:45</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-003</td>
|
||||
<td>营养值超范围</td>
|
||||
<td>ERR-2026-003</td>
|
||||
<td><span class="tag tag-red">营养值超范围</span></td>
|
||||
<td>营养计算</td>
|
||||
<td>某菜品钠含量计算结果为99999mg</td>
|
||||
<td><span class="tag tag-red">高</span></td>
|
||||
<td>v2.3.1</td>
|
||||
<td>2026-04-15 10:20</td>
|
||||
<td class="td-actions"><a class="link">查看</a> <a class="link">溯源</a></td>
|
||||
<td>2026-05-14 10:20</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-004</td>
|
||||
<td><span class="tag tag-red">算法超时</span></td>
|
||||
<td>营养计算</td>
|
||||
<td>批量营养分析任务执行超时(>30s)</td>
|
||||
<td><span class="tag tag-orange">中</span></td>
|
||||
<td>v2.3.1</td>
|
||||
<td>2026-05-13 16:40</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-005</td>
|
||||
<td><span class="tag tag-orange">推荐结果为空</span></td>
|
||||
<td>菜品推荐</td>
|
||||
<td>糖尿病患者推荐食谱未返回低GI菜品</td>
|
||||
<td><span class="tag tag-red">高</span></td>
|
||||
<td>v1.8.0</td>
|
||||
<td>2026-05-13 14:15</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-006</td>
|
||||
<td><span class="tag tag-blue">模型输出异常</span></td>
|
||||
<td>智能推荐</td>
|
||||
<td>LLM返回JSON格式解析失败</td>
|
||||
<td><span class="tag tag-orange">中</span></td>
|
||||
<td>v3.0.2</td>
|
||||
<td>2026-05-13 11:30</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-007</td>
|
||||
<td><span class="tag tag-red">营养值异常</span></td>
|
||||
<td>营养计算</td>
|
||||
<td>维生素A摄入量计算结果为负值</td>
|
||||
<td><span class="tag tag-red">高</span></td>
|
||||
<td>v2.3.1</td>
|
||||
<td>2026-05-12 15:50</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-008</td>
|
||||
<td><span class="tag tag-red">营养值超范围</span></td>
|
||||
<td>营养计算</td>
|
||||
<td>单餐蛋白质含量超出日推荐量300%</td>
|
||||
<td><span class="tag tag-orange">中</span></td>
|
||||
<td>v2.3.1</td>
|
||||
<td>2026-05-12 12:10</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-009</td>
|
||||
<td><span class="tag tag-blue">模型输出异常</span></td>
|
||||
<td>智能推荐</td>
|
||||
<td>小模型推理服务返回500错误</td>
|
||||
<td><span class="tag tag-red">高</span></td>
|
||||
<td>v3.0.2</td>
|
||||
<td>2026-05-11 09:25</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ERR-2026-010</td>
|
||||
<td><span class="tag tag-orange">推荐结果为空</span></td>
|
||||
<td>菜品推荐</td>
|
||||
<td>过敏源过滤后可推荐菜品为零</td>
|
||||
<td><span class="tag tag-orange">中</span></td>
|
||||
<td>v1.8.0</td>
|
||||
<td>2026-05-11 08:40</td>
|
||||
<td class="td-actions"><a class="link">详情</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination">
|
||||
<span>显示 3 条,共 12 条</span>
|
||||
<button class="page-btn">‹</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">2</button>
|
||||
<button class="page-btn">3</button>
|
||||
<button class="page-btn">›</button>
|
||||
<div class="pagination__left">
|
||||
<span>共 89 条</span>
|
||||
<span>每页</span>
|
||||
<select class="filter-select" style="min-width:70px;">
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>50</option>
|
||||
</select>
|
||||
<span>条</span>
|
||||
</div>
|
||||
<div class="pagination__right">
|
||||
<button class="page-btn">‹</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">2</button>
|
||||
<button class="page-btn">3</button>
|
||||
<span class="page-ellipsis">…</span>
|
||||
<button class="page-btn">9</button>
|
||||
<button class="page-btn">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.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);}
|
||||
${CSS_RULE}
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
|
||||
@@ -31,7 +31,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -183,9 +182,6 @@ 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-primary">✏️ 编辑标准</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>热量范围(kcal)</th><th>蛋白质比例(%)</th><th>脂肪比例(%)</th><th>碳水比例(%)</th><th>钙(mg)</th><th>铁(mg)</th><th>维生素C(mg)</th><th>膳食纤维(g)</th><th>每日饮水量(mL)</th><th>操作</th></tr></thead>
|
||||
|
||||
@@ -31,7 +31,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -154,14 +153,6 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
|
||||
<!-- 主内容 -->
|
||||
<main class="main">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card"><div class="stat-card__icon blue">💎</div><div class="stat-card__info"><div class="stat-card__value">186</div><div class="stat-card__label">VIP定制人数</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon green">🎯</div><div class="stat-card__info"><div class="stat-card__value">82</div><div class="stat-card__label">减重目标</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon orange">💪</div><div class="stat-card__info"><div class="stat-card__value">54</div><div class="stat-card__label">增肌目标</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon red">🩺</div><div class="stat-card__info"><div class="stat-card__value">50</div><div class="stat-card__label">慢病管理目标</div></div></div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item"><label>用户姓名</label><input class="filter-input" placeholder="请输入用户姓名"></div>
|
||||
@@ -184,11 +175,11 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</div>
|
||||
<div class="table-card__content">
|
||||
<table>
|
||||
<thead><tr><th>用户编号/姓名</th><th>定制目标类型</th><th>每日热量(kcal)</th><th>蛋白质目标(g)</th><th>脂肪目标(g)</th><th>碳水目标(g)</th><th>特殊限制项</th><th>制定营养师</th><th>生效日期</th><th>复评日期</th><th>操作</th></tr></thead>
|
||||
<thead><tr><th>姓名</th><th>用户编号</th><th>所属单位</th><th>所属部门</th><th>定制目标类型</th><th>每日热量(kcal)</th><th>蛋白质目标(g)</th><th>脂肪目标(g)</th><th>碳水目标(g)</th><th>特殊限制项</th><th>制定营养师</th><th>生效日期</th><th>复评日期</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>VIP001/张明</td><td>减重</td><td>1650</td><td>95</td><td>45</td><td>170</td><td>禁高糖饮料</td><td>营养师李明</td><td>2026-04-01</td><td>2026-05-01</td><td class="td-actions"><span class="link">编辑</span></td></tr>
|
||||
<tr><td>VIP002/刘芳</td><td>控糖(糖尿病)</td><td>1750</td><td>88</td><td>50</td><td>160</td><td>低GI优先</td><td>营养师王芳</td><td>2026-03-20</td><td>2026-04-25</td><td class="td-actions"><span class="link">编辑</span></td></tr>
|
||||
<tr><td>VIP003/陈伟</td><td>增肌</td><td>2450</td><td>140</td><td>68</td><td>280</td><td>增加优质蛋白</td><td>营养师李明</td><td>2026-04-10</td><td>2026-05-10</td><td class="td-actions"><span class="link">编辑</span></td></tr>
|
||||
<tr><td>张明</td><td>VIP001</td><td>长庆油田</td><td>采油一厂</td><td>减重</td><td>1650</td><td>95</td><td>45</td><td>170</td><td>禁高糖饮料</td><td>营养师李明</td><td>2026-04-01</td><td>2026-05-01</td><td class="td-actions"><span class="link">详情</span> <span class="link">编辑</span> <span class="link" style="color:var(--danger)">删除</span></td></tr>
|
||||
<tr><td>刘芳</td><td>VIP002</td><td>长庆油田</td><td>采气二厂</td><td>控糖(糖尿病)</td><td>1750</td><td>88</td><td>50</td><td>160</td><td>低GI优先</td><td>营养师王芳</td><td>2026-03-20</td><td>2026-04-25</td><td class="td-actions"><span class="link">详情</span> <span class="link">编辑</span> <span class="link" style="color:var(--danger)">删除</span></td></tr>
|
||||
<tr><td>陈伟</td><td>VIP003</td><td>长庆油田</td><td>机械制造公司</td><td>增肌</td><td>2450</td><td>140</td><td>68</td><td>280</td><td>增加优质蛋白</td><td>营养师李明</td><td>2026-04-10</td><td>2026-05-10</td><td class="td-actions"><span class="link">详情</span> <span class="link">编辑</span> <span class="link" style="color:var(--danger)">删除</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -42,16 +41,16 @@ ${CSS_RULE}
|
||||
.sidebar-subgroup.open>.sidebar-subgroup__body{display:block;}
|
||||
.sidebar-subgroup__body .sidebar-item{padding-left:36px;font-size:12px;}
|
||||
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:16px;}
|
||||
.stat-card{background:#fff;border-radius:var(--radius-md);padding:20px 24px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;}
|
||||
.stat-card__icon{width:48px;height:48px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:24px;}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px;}
|
||||
.stat-card{background:#fff;border-radius:var(--radius-md);padding:12px 14px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:10px;}
|
||||
.stat-card__icon{width:36px;height:36px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}
|
||||
.stat-card__icon.blue{background:var(--primary-light);}
|
||||
.stat-card__icon.green{background:#F6FFED;}
|
||||
.stat-card__icon.orange{background:#FFF7E6;}
|
||||
.stat-card__icon.red{background:#FFF1F0;}
|
||||
.stat-card__info{display:flex;flex-direction:column;gap:4px;}
|
||||
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
|
||||
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.stat-card__info{display:flex;flex-direction:column;gap:2px;min-width:0;}
|
||||
.stat-card__value{font-size:var(--font-base);font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.stat-card__label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
|
||||
.page-tabs{display:flex;gap:0;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;padding:0 24px;box-shadow:var(--shadow-sm);border-bottom:2px solid var(--border);}
|
||||
.page-tab{padding:14px 20px;font-size:var(--font-sm);cursor:pointer;color:var(--text-secondary);border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .2s;display:flex;align-items:center;gap:6px;}
|
||||
.page-tab:hover{color:var(--primary);}
|
||||
@@ -94,6 +93,42 @@ 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;}
|
||||
/* 弹窗基础样式 */
|
||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s;}
|
||||
.modal-overlay.show{opacity:1;}
|
||||
.modal{background:#fff;border-radius:var(--radius-lg);width:680px;max-height:80vh;display:flex;flex-direction:column;transform:translateY(30px);transition:transform .25s;box-shadow:0 12px 40px rgba(0,0,0,0.18);}
|
||||
.modal-overlay.show .modal{transform:translateY(0);}
|
||||
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--border);}
|
||||
.modal__header h3{font-size:var(--font-lg);font-weight:600;margin:0;}
|
||||
.modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;font-size:20px;color:#999;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);}
|
||||
.modal__close:hover{background:#f5f5f5;color:#333;}
|
||||
.modal__body{padding:20px 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);}
|
||||
/* 损耗明细弹窗 */
|
||||
.loss-info{display:grid;grid-template-columns:1fr 1fr;gap:12px 24px;margin-bottom:20px;}
|
||||
.loss-info__item{display:flex;align-items:center;gap:8px;font-size:var(--font-sm);}
|
||||
.loss-info__label{color:var(--text-secondary);min-width:80px;}
|
||||
.loss-info__value{color:var(--text-primary);font-weight:500;}
|
||||
.loss-section{margin-bottom:16px;}
|
||||
.loss-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid var(--border);}
|
||||
.loss-bar{display:flex;align-items:center;gap:10px;margin-bottom:8px;font-size:var(--font-sm);}
|
||||
.loss-bar__label{width:80px;color:var(--text-secondary);text-align:right;}
|
||||
.loss-bar__track{flex:1;height:18px;background:#f0f0f0;border-radius:9px;overflow:hidden;position:relative;}
|
||||
.loss-bar__fill{height:100%;border-radius:9px;transition:width .4s;}
|
||||
.loss-bar__val{width:60px;font-weight:500;}
|
||||
.loss-tip{background:#FFF7E6;border:1px solid #FFE58F;border-radius:var(--radius-sm);padding:10px 14px;font-size:var(--font-sm);color:#D48806;margin-top:12px;display:flex;align-items:flex-start;gap:6px;}
|
||||
/* 食材成本明细弹窗 */
|
||||
.cost-header{display:flex;gap:24px;margin-bottom:16px;font-size:var(--font-sm);}
|
||||
.cost-header span{color:var(--text-secondary);}
|
||||
.cost-header strong{color:var(--text-primary);margin-left:4px;}
|
||||
.cost-table{width:100%;border-collapse:collapse;}
|
||||
.cost-table thead th{background:#FAFAFA;padding:10px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.cost-table tbody td{padding:12px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
|
||||
.cost-table tbody tr:last-child td{border-bottom:none;}
|
||||
.cost-table tbody tr:hover{background:#F8FBFF;}
|
||||
.cost-summary{display:flex;justify-content:flex-end;gap:24px;padding:12px 16px;background:#FAFAFA;border-radius:0 0 var(--radius-md) var(--radius-md);font-size:var(--font-sm);}
|
||||
.cost-summary span{color:var(--text-secondary);}
|
||||
.cost-summary strong{color:var(--primary);margin-left:4px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -122,40 +157,40 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-menu__title">营养用餐</div>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item">字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item">营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item">食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item">餐品总库</a>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item"><span class="mi">📖</span>字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item"><span class="mi">🔬</span>营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item"><span class="mi">🥦</span>食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item"><span class="mi">🍲</span>餐品总库</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
算法标准
|
||||
📐 算法标准
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-algo-population.html" class="sidebar-item">人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item">VIP定制标准</a>
|
||||
<a href="din-algo-population.html" class="sidebar-item"><span class="mi">👥</span>人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item"><span class="mi">💎</span>VIP定制标准</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item">食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item">营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item">用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item">餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item">取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item">消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item">营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item active">餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item">终端管理</a>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item"><span class="mi">📋</span>食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item"><span class="mi">🛒</span>营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item"><span class="mi">🧑</span>用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item"><span class="mi">⚖️</span>餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item"><span class="mi">🍽️</span>取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item"><span class="mi">💰</span>消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item"><span class="mi">📊</span>营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item active"><span class="mi">📈</span>餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item"><span class="mi">🖥️</span>终端管理</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
模型管理
|
||||
🤖 模型管理
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-model-llm.html" class="sidebar-item">大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item">小模型管理</a>
|
||||
<a href="din-model-llm.html" class="sidebar-item"><span class="mi">🧠</span>大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item"><span class="mi">⚙️</span>小模型管理</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-innovation.html" class="sidebar-item">味养创新</a>
|
||||
<a href="din-innovation.html" class="sidebar-item"><span class="mi">🌟</span>味养创新</a>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容 -->
|
||||
@@ -251,6 +286,157 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
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));}
|
||||
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');})();
|
||||
|
||||
/* ===== 弹窗组件 ===== */
|
||||
const Modal={
|
||||
open(title,bodyHtml,extraClass){
|
||||
const overlay=document.createElement('div');
|
||||
overlay.className='modal-overlay';
|
||||
overlay.innerHTML=`<div class="modal ${extraClass||''}">
|
||||
<div class="modal__header"><h3>${title}</h3><button class="modal__close" onclick="Modal.close()">×</button></div>
|
||||
<div class="modal__body">${bodyHtml}</div>
|
||||
<div class="modal__footer"><button class="btn btn-default" onclick="Modal.close()">关闭</button></div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
overlay.addEventListener('click',e=>{if(e.target===overlay)Modal.close();});
|
||||
requestAnimationFrame(()=>overlay.classList.add('show'));
|
||||
},
|
||||
close(){
|
||||
const o=document.querySelector('.modal-overlay');
|
||||
if(!o)return;
|
||||
o.classList.remove('show');
|
||||
setTimeout(()=>o.remove(),250);
|
||||
}
|
||||
};
|
||||
|
||||
/* ===== 损耗明细弹窗 ===== */
|
||||
function showLossDetail(row){
|
||||
/* 从表格行中读取数据 */
|
||||
const cells=row.querySelectorAll('td');
|
||||
const date=cells[0].textContent.trim();
|
||||
const code=cells[1].textContent.trim();
|
||||
const name=cells[2].textContent.trim();
|
||||
const category=cells[3].textContent.trim();
|
||||
const supplier=cells[4].textContent.trim();
|
||||
const buyQty=cells[5].textContent.trim();
|
||||
const price=cells[6].textContent.trim();
|
||||
const total=cells[7].textContent.trim();
|
||||
const usedQty=cells[8].textContent.trim();
|
||||
const lossQty=cells[9].textContent.trim();
|
||||
const lossAmt=cells[10].textContent.trim();
|
||||
const lossRate=cells[11].textContent.trim();
|
||||
const level=cells[12].textContent.trim();
|
||||
const reason=cells[13].textContent.trim();
|
||||
|
||||
const html=`
|
||||
<div class="loss-info">
|
||||
<div class="loss-info__item"><span class="loss-info__label">食材编号</span><span class="loss-info__value">${code}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">食材名称</span><span class="loss-info__value">${name}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">所属分类</span><span class="loss-info__value">${category}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">供应商</span><span class="loss-info__value">${supplier}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">统计日期</span><span class="loss-info__value">${date}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">采购单价</span><span class="loss-info__value">${price}</span></div>
|
||||
</div>
|
||||
<div class="loss-section">
|
||||
<div class="loss-section__title">用量与损耗</div>
|
||||
<div class="loss-bar"><span class="loss-bar__label">采购量</span><div class="loss-bar__track"><div class="loss-bar__fill" style="width:100%;background:var(--primary);"></div></div><span class="loss-bar__val">${buyQty}kg</span></div>
|
||||
<div class="loss-bar"><span class="loss-bar__label">实际使用</span><div class="loss-bar__track"><div class="loss-bar__fill" style="width:${(parseFloat(usedQty)/parseFloat(buyQty)*100).toFixed(0)}%;background:var(--success);"></div></div><span class="loss-bar__val">${usedQty}kg</span></div>
|
||||
<div class="loss-bar"><span class="loss-bar__label">损耗量</span><div class="loss-bar__track"><div class="loss-bar__fill" style="width:${(parseFloat(lossQty)/parseFloat(buyQty)*100).toFixed(0)}%;background:var(--danger);"></div></div><span class="loss-bar__val">${lossQty}kg</span></div>
|
||||
</div>
|
||||
<div class="loss-section">
|
||||
<div class="loss-section__title">损耗分析</div>
|
||||
<div class="loss-info">
|
||||
<div class="loss-info__item"><span class="loss-info__label">采购总额</span><span class="loss-info__value">${total}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">损耗金额</span><span class="loss-info__value" style="color:var(--danger);font-weight:600;">${lossAmt}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">损耗率</span><span class="loss-info__value">${lossRate}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">预警等级</span><span class="loss-info__value">${level}</span></div>
|
||||
<div class="loss-info__item"><span class="loss-info__label">损耗原因</span><span class="loss-info__value">${reason}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loss-tip">⚠️ 建议:${reason==='过期未用'?'加强库存先进先出管理,缩短采购周期,减少积压。':reason==='储存变质'?'检查冷链温控设备,优化储存条件,缩短库存周转天数。':'优化加工切配流程,加强厨师技能培训,减少边角料浪费。'}</div>`;
|
||||
|
||||
Modal.open('损耗明细 — '+name,html);
|
||||
}
|
||||
|
||||
/* ===== 食材成本明细弹窗 ===== */
|
||||
function showCostDetail(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const dishCode=cells[0].textContent.trim();
|
||||
const dishName=cells[1].textContent.trim();
|
||||
const mealType=cells[2].textContent.trim();
|
||||
const costPerServe=cells[8].textContent.trim();
|
||||
const sellPrice=cells[9].textContent.trim();
|
||||
|
||||
/* 模拟食材成本数据 */
|
||||
const costData={
|
||||
'糖醋里脊':[
|
||||
{name:'猪里脊肉',weight:'150g',unitPrice:'28.0',cost:'4.20'},
|
||||
{name:'淀粉',weight:'20g',unitPrice:'6.0',cost:'0.12'},
|
||||
{name:'番茄酱',weight:'30g',unitPrice:'12.0',cost:'0.36'},
|
||||
{name:'白糖',weight:'15g',unitPrice:'8.0',cost:'0.12'},
|
||||
{name:'食用油',weight:'40ml',unitPrice:'10.0',cost:'0.40'},
|
||||
{name:'醋',weight:'20ml',unitPrice:'5.0',cost:'0.10'}
|
||||
],
|
||||
'蒜蓉西兰花':[
|
||||
{name:'西兰花',weight:'200g',unitPrice:'5.0',cost:'1.00'},
|
||||
{name:'大蒜',weight:'15g',unitPrice:'12.0',cost:'0.18'},
|
||||
{name:'食用油',weight:'15ml',unitPrice:'10.0',cost:'0.15'},
|
||||
{name:'蚝油',weight:'10ml',unitPrice:'15.0',cost:'0.15'},
|
||||
{name:'盐',weight:'3g',unitPrice:'3.0',cost:'0.01'}
|
||||
],
|
||||
'鱼香肉丝':[
|
||||
{name:'猪瘦肉',weight:'120g',unitPrice:'24.0',cost:'2.88'},
|
||||
{name:'胡萝卜',weight:'50g',unitPrice:'3.0',cost:'0.15'},
|
||||
{name:'黑木耳',weight:'30g',unitPrice:'40.0',cost:'1.20'},
|
||||
{name:'笋丝',weight:'40g',unitPrice:'8.0',cost:'0.32'},
|
||||
{name:'豆瓣酱',weight:'15g',unitPrice:'10.0',cost:'0.15'},
|
||||
{name:'食用油',weight:'30ml',unitPrice:'10.0',cost:'0.30'}
|
||||
],
|
||||
'红烧肉':[
|
||||
{name:'五花肉',weight:'180g',unitPrice:'26.0',cost:'4.68'},
|
||||
{name:'生姜',weight:'10g',unitPrice:'8.0',cost:'0.08'},
|
||||
{name:'八角',weight:'3g',unitPrice:'30.0',cost:'0.09'},
|
||||
{name:'冰糖',weight:'15g',unitPrice:'10.0',cost:'0.15'},
|
||||
{name:'酱油',weight:'20ml',unitPrice:'8.0',cost:'0.16'},
|
||||
{name:'料酒',weight:'15ml',unitPrice:'5.0',cost:'0.08'}
|
||||
]
|
||||
};
|
||||
const items=costData[dishName]||costData['糖醋里脊'];
|
||||
const totalCost=items.reduce((s,i)=>s+parseFloat(i.cost),0).toFixed(2);
|
||||
|
||||
let tableRows=items.map(i=>`<tr><td>${i.name}</td><td>${i.weight}</td><td>¥${i.unitPrice}/kg</td><td>¥${i.cost}</td></tr>`).join('');
|
||||
|
||||
const html=`
|
||||
<div class="cost-header">
|
||||
<span>餐品编号:<strong>${dishCode}</strong></span>
|
||||
<span>所属餐次:<strong>${mealType}</strong></span>
|
||||
<span>销售单价:<strong>¥${sellPrice}</strong></span>
|
||||
</div>
|
||||
<table class="cost-table">
|
||||
<thead><tr><th>食材名称</th><th>用量/份</th><th>采购单价</th><th>成本(元/份)</th></tr></thead>
|
||||
<tbody>${tableRows}</tbody>
|
||||
</table>
|
||||
<div class="cost-summary">
|
||||
<span>食材成本合计:<strong>¥${totalCost}</strong></span>
|
||||
<span>毛利/份:<strong>¥${(parseFloat(sellPrice)-parseFloat(totalCost)).toFixed(2)}</strong></span>
|
||||
</div>`;
|
||||
|
||||
Modal.open('食材成本明细 — '+dishName,html);
|
||||
}
|
||||
|
||||
/* ===== 事件绑定 ===== */
|
||||
document.querySelectorAll('.tab-panel').forEach((panel,tabIdx)=>{
|
||||
panel.querySelectorAll('tbody tr').forEach(row=>{
|
||||
row.querySelectorAll('.td-actions .link').forEach(link=>{
|
||||
if(tabIdx===0 && link.textContent.includes('损耗明细')){
|
||||
link.addEventListener('click',()=>showLossDetail(row));
|
||||
}
|
||||
if(tabIdx===1 && link.textContent.includes('食材成本明细')){
|
||||
link.addEventListener('click',()=>showCostDetail(row));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -35,7 +35,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -93,6 +92,33 @@ 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;}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;animation:modalFadeIn .2s ease;}
|
||||
.modal-overlay.closing{animation:modalFadeOut .2s ease forwards;}
|
||||
@keyframes modalFadeIn{from{opacity:0}to{opacity:1}}
|
||||
@keyframes modalFadeOut{from{opacity:1}to{opacity:0}}
|
||||
@keyframes modalSlideIn{from{transform:translateY(-20px);opacity:0}to{transform:translateY(0);opacity:1}}
|
||||
@keyframes modalSlideOut{from{transform:translateY(0);opacity:1}to{transform:translateY(-20px);opacity:0}}
|
||||
.modal{background:#fff;border-radius:8px;width:860px;max-width:90vw;max-height:85vh;display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.15);animation:modalSlideIn .25s ease;}
|
||||
.modal-overlay.closing .modal{animation:modalSlideOut .2s ease forwards;}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.modal-title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
|
||||
.modal-close{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:var(--radius-sm);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;}
|
||||
.modal-close:hover{background:#f5f5f5;color:var(--text-primary);}
|
||||
.modal-body{padding:24px;overflow-y:auto;flex:1;}
|
||||
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);}
|
||||
.modal-table tbody tr:last-child td{border-bottom:none;}
|
||||
.modal-table tbody tr:hover{background:#F8FBFF;}
|
||||
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 24px;margin-bottom:20px;padding:16px;background:#FAFAFA;border-radius:var(--radius-md);}
|
||||
.detail-grid__item{display:flex;align-items:center;gap:8px;font-size:var(--font-sm);}
|
||||
.detail-grid__label{color:var(--text-secondary);min-width:80px;}
|
||||
.detail-grid__value{color:var(--text-primary);font-weight:500;}
|
||||
.detail-info{display:flex;gap:24px;padding:12px 16px;background:#FAFAFA;border-radius:var(--radius-sm);margin-bottom:16px;font-size:var(--font-sm);}
|
||||
.detail-info span{color:var(--text-secondary);}
|
||||
.detail-info b{color:var(--text-primary);font-weight:500;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -252,6 +278,119 @@ 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');})();
|
||||
|
||||
/* ========== 弹窗基础 ========== */
|
||||
const Modal = {
|
||||
open(title, bodyHtml) {
|
||||
this.close();
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
overlay.innerHTML = `<div class="modal">
|
||||
<div class="modal-header"><div class="modal-title">${title}</div><button class="modal-close" onclick="Modal.close()">×</button></div>
|
||||
<div class="modal-body">${bodyHtml}</div>
|
||||
</div>`;
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) Modal.close(); });
|
||||
document.body.appendChild(overlay);
|
||||
},
|
||||
close() {
|
||||
const o = document.querySelector('.modal-overlay');
|
||||
if (!o) return;
|
||||
o.classList.add('closing');
|
||||
setTimeout(() => o.remove(), 200);
|
||||
}
|
||||
};
|
||||
|
||||
/* ========== 消费数据 ========== */
|
||||
const consumeData = {
|
||||
'TX-20260421-001': { txId:'TX-20260421-001', user:'张伟(EMP001)', dept:'研发部', type:'午餐消费', pay:'余额支付', amount:'¥22.00', time:'2026-04-21 11:02:35', status:'成功', preBal:'¥256.50', postBal:'¥234.50', orderId:'ORD-20260421-001', mealId:'MEAL-20260421-001', operator:'系统自动', remark:'—',
|
||||
dishes:[
|
||||
{ code:'DISH-001', name:'宫保鸡丁', weight:'0.18kg', price:'¥14/份', total:'¥14.00' },
|
||||
{ code:'DISH-012', name:'米饭', weight:'0.25kg', price:'¥2/份', total:'¥2.00' },
|
||||
{ code:'DISH-008', name:'紫菜蛋花汤', weight:'0.30kg', price:'¥6/份', total:'¥6.00' },
|
||||
]},
|
||||
'TX-20260421-002': { txId:'TX-20260421-002', user:'李娜(EMP002)', dept:'市场部', type:'午餐消费', pay:'余额支付', amount:'¥18.00', time:'2026-04-21 11:08:12', status:'成功', preBal:'¥104.20', postBal:'¥86.20', orderId:'ORD-20260421-002', mealId:'MEAL-20260421-002', operator:'系统自动', remark:'—',
|
||||
dishes:[
|
||||
{ code:'DISH-003', name:'清蒸鱼', weight:'0.15kg', price:'¥12/份', total:'¥12.00' },
|
||||
{ code:'DISH-012', name:'米饭', weight:'0.25kg', price:'¥2/份', total:'¥2.00' },
|
||||
{ code:'DISH-015', name:'炒时蔬', weight:'0.20kg', price:'¥4/份', total:'¥4.00' },
|
||||
]},
|
||||
'TX-20260421-003': { txId:'TX-20260421-003', user:'王磊(EMP003)', dept:'运营部', type:'午餐消费', pay:'微信支付', amount:'¥28.00', time:'2026-04-21 11:15:48', status:'成功', preBal:'—', postBal:'—', orderId:'ORD-20260421-003', mealId:'MEAL-20260421-003', operator:'系统自动', remark:'—',
|
||||
dishes:[
|
||||
{ code:'DISH-005', name:'红烧肉', weight:'0.22kg', price:'¥16/份', total:'¥16.00' },
|
||||
{ code:'DISH-012', name:'米饭', weight:'0.25kg', price:'¥2/份', total:'¥2.00' },
|
||||
{ code:'DISH-009', name:'番茄炒蛋', weight:'0.18kg', price:'¥8/份', total:'¥8.00' },
|
||||
{ code:'DISH-012', name:'酸辣汤', weight:'0.30kg', price:'¥2/份', total:'¥2.00' },
|
||||
]},
|
||||
'TX-20260421-004': { txId:'TX-20260421-004', user:'赵敏(EMP004)', dept:'财务部', type:'账户充值', pay:'微信支付', amount:'+¥100.00', time:'2026-04-21 09:30:22', status:'成功', preBal:'¥256.80', postBal:'¥356.80', orderId:'—', mealId:'—', operator:'系统自动', remark:'—', dishes:[] },
|
||||
'TX-20260421-005': { txId:'TX-20260421-005', user:'陈刚(EMP005)', dept:'人事部', type:'早餐消费', pay:'余额支付', amount:'¥12.00', time:'2026-04-21 07:45:10', status:'成功', preBal:'¥154.60', postBal:'¥142.60', orderId:'ORD-20260421-005', mealId:'MEAL-20260421-005', operator:'系统自动', remark:'—',
|
||||
dishes:[
|
||||
{ code:'DISH-020', name:'小米粥', weight:'0.35kg', price:'¥5/份', total:'¥5.00' },
|
||||
{ code:'DISH-022', name:'煎蛋', weight:'0.08kg', price:'¥3/份', total:'¥3.00' },
|
||||
{ code:'DISH-025', name:'凉拌黄瓜', weight:'0.15kg', price:'¥4/份', total:'¥4.00' },
|
||||
]},
|
||||
'TX-20260421-006': { txId:'TX-20260421-006', user:'刘芳(EMP006)', dept:'市场部', type:'退款', pay:'原路退回', amount:'+¥18.00', time:'2026-04-21 10:20:05', status:'已退款', preBal:'¥50.40', postBal:'¥68.40', orderId:'ORD-20260421-006', mealId:'MEAL-20260421-006', operator:'客服张丽', remark:'用户申请退款',
|
||||
dishes:[
|
||||
{ code:'DISH-003', name:'清蒸鱼', weight:'0.15kg', price:'¥12/份', total:'¥12.00' },
|
||||
{ code:'DISH-015', name:'炒时蔬', weight:'0.20kg', price:'¥4/份', total:'¥4.00' },
|
||||
{ code:'DISH-012', name:'米饭', weight:'0.25kg', price:'¥2/份', total:'¥2.00' },
|
||||
]},
|
||||
};
|
||||
|
||||
/* ========== 餐品明细弹窗 ========== */
|
||||
function showDishDetail(txId) {
|
||||
const d = consumeData[txId];
|
||||
if (!d) return;
|
||||
const infoHtml = `<div class="detail-info">
|
||||
<span>交易流水号:<b>${d.txId}</b></span>
|
||||
<span>就餐记录号:<b>${d.mealId}</b></span>
|
||||
<span>用户:<b>${d.user}</b></span>
|
||||
<span>时间:<b>${d.time}</b></span>
|
||||
</div>`;
|
||||
const tbody = d.dishes.map(r => `<tr><td>${r.code}</td><td>${r.name}</td><td>${r.weight}</td><td>${r.price}</td><td>${r.total}</td><td><a class="link">食材溯源链路</a></td></tr>`).join('');
|
||||
const tableHtml = d.dishes.length ? `<table class="modal-table">
|
||||
<thead><tr><th>餐品编码</th><th>餐品</th><th>重量</th><th>单价</th><th>合计金额</th><th>食材溯源</th></tr></thead>
|
||||
<tbody>${tbody}</tbody>
|
||||
</table>` : '<div style="padding:24px;text-align:center;color:#999;">无餐品数据</div>';
|
||||
Modal.open('餐品明细 — ' + txId, infoHtml + tableHtml);
|
||||
}
|
||||
|
||||
/* ========== 交易明细弹窗 ========== */
|
||||
function showTransDetail(txId) {
|
||||
const d = consumeData[txId];
|
||||
if (!d) return;
|
||||
const statusTag = d.status === '成功' ? '<span class="tag tag-green">成功</span>' : '<span class="tag tag-gray">已退款</span>';
|
||||
const html = `<div class="detail-grid">
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易流水号</span><span class="detail-grid__value">${d.txId}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易类型</span><span class="detail-grid__value">${d.type}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">支付方式</span><span class="detail-grid__value">${d.pay}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易金额</span><span class="detail-grid__value">${d.amount}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易时间</span><span class="detail-grid__value">${d.time}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易状态</span><span class="detail-grid__value">${statusTag}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易前余额</span><span class="detail-grid__value">${d.preBal}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">交易后余额</span><span class="detail-grid__value">${d.postBal}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">关联订单</span><span class="detail-grid__value">${d.orderId}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">关联就餐记录</span><span class="detail-grid__value">${d.mealId}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">操作员</span><span class="detail-grid__value">${d.operator}</span></div>
|
||||
<div class="detail-grid__item"><span class="detail-grid__label">备注</span><span class="detail-grid__value">${d.remark}</span></div>
|
||||
</div>`;
|
||||
Modal.open('交易明细 — ' + txId, html);
|
||||
}
|
||||
|
||||
/* ========== 事件绑定 ========== */
|
||||
(function bindEvents() {
|
||||
document.querySelectorAll('tbody tr').forEach(row => {
|
||||
const cells = row.querySelectorAll('td');
|
||||
if (cells.length < 16) return;
|
||||
const txId = cells[0].textContent.trim();
|
||||
// 消费金额列(index 6)点击 → 餐品明细
|
||||
const amountLink = cells[6] && cells[6].querySelector('.link');
|
||||
if (amountLink) amountLink.addEventListener('click', () => showDishDetail(txId));
|
||||
// 操作列:交易明细、餐品明细
|
||||
const actionLinks = cells[15] && cells[15].querySelectorAll('.link');
|
||||
if (actionLinks && actionLinks[0]) actionLinks[0].addEventListener('click', () => showTransDetail(txId));
|
||||
if (actionLinks && actionLinks[1]) actionLinks[1].addEventListener('click', () => showDishDetail(txId));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,7 +31,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -94,6 +93,206 @@ 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;}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;animation:modalFadeIn .2s ease;}
|
||||
.modal-overlay.closing{animation:modalFadeOut .2s ease forwards;}
|
||||
@keyframes modalFadeIn{from{opacity:0}to{opacity:1}}
|
||||
@keyframes modalFadeOut{from{opacity:1}to{opacity:0}}
|
||||
@keyframes modalSlideIn{from{transform:translateY(-20px);opacity:0}to{transform:translateY(0);opacity:1}}
|
||||
@keyframes modalSlideOut{from{transform:translateY(0);opacity:1}to{transform:translateY(-20px);opacity:0}}
|
||||
.modal{background:#fff;border-radius:8px;width:900px;max-width:90vw;max-height:85vh;display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.15);animation:modalSlideIn .25s ease;}
|
||||
.modal-overlay.closing .modal{animation:modalSlideOut .2s ease forwards;}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.modal-title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
|
||||
.modal-close{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:var(--radius-sm);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;}
|
||||
.modal-close:hover{background:#f5f5f5;color:var(--text-primary);}
|
||||
.modal-body{padding: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);}
|
||||
|
||||
/* 十维档案 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;}
|
||||
.profile-tab:hover{color:var(--primary);}
|
||||
.profile-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500;}
|
||||
.profile-panel{display:none;}
|
||||
.profile-panel.active{display:block;}
|
||||
.profile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 24px;}
|
||||
.profile-field{display:flex;align-items:baseline;gap:8px;font-size:var(--font-sm);padding:8px 0;border-bottom:1px dashed #f0f0f0;}
|
||||
.profile-field__label{color:var(--text-secondary);min-width:80px;flex-shrink:0;}
|
||||
.profile-field__value{color:var(--text-primary);font-weight:500;}
|
||||
.profile-summary{display:flex;align-items:center;gap:16px;padding:16px;background:#f8fafc;border-radius:var(--radius-md);margin-bottom:20px;}
|
||||
.profile-summary__avatar{width:56px;height:56px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;color:#fff;font-size:22px;font-weight:600;}
|
||||
.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);}
|
||||
|
||||
/* 营养明细弹窗 */
|
||||
.nutri-modal{width:960px;}
|
||||
.nutri-header-info{display:flex;align-items:center;gap:24px;padding:14px 20px;background:#f8fafc;border-radius:var(--radius-md);margin-bottom:20px;font-size:var(--font-sm);flex-wrap:wrap;}
|
||||
.nutri-header-info__item{display:flex;align-items:center;gap:6px;}
|
||||
.nutri-header-info__label{color:var(--text-secondary);}
|
||||
.nutri-header-info__value{color:var(--text-primary);font-weight:500;}
|
||||
.nutri-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:20px;}
|
||||
.nutri-tab{padding:10px 20px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all .2s;}
|
||||
.nutri-tab:hover{color:var(--primary);}
|
||||
.nutri-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500;}
|
||||
.nutri-panel{display:none;}
|
||||
.nutri-panel.active{display:block;}
|
||||
.nutri-section{margin-bottom:24px;}
|
||||
.nutri-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:14px;padding-left:8px;border-left:3px solid var(--primary);}
|
||||
/* 进度条 */
|
||||
.nutri-bar-list{display:flex;flex-direction:column;gap:14px;}
|
||||
.nutri-bar-item{display:flex;align-items:center;gap:12px;font-size:var(--font-sm);}
|
||||
.nutri-bar-item__label{min-width:50px;color:var(--text-secondary);text-align:right;flex-shrink:0;}
|
||||
.nutri-bar-item__track{flex:1;height:20px;background:#f0f0f0;border-radius:10px;position:relative;overflow:hidden;}
|
||||
.nutri-bar-item__fill{height:100%;border-radius:10px;transition:width .6s ease;}
|
||||
.nutri-bar-item__fill.green{background:linear-gradient(90deg,#95de64,#52C41A);}
|
||||
.nutri-bar-item__fill.orange{background:linear-gradient(90deg,#ffd666,#FAAD14);}
|
||||
.nutri-bar-item__fill.red{background:linear-gradient(90deg,#ff9c9c,#FF4D4F);}
|
||||
.nutri-bar-item__info{min-width:200px;color:var(--text-secondary);flex-shrink:0;font-size:12px;}
|
||||
.nutri-bar-item__info strong{color:var(--text-primary);}
|
||||
/* 群体对比 */
|
||||
.nutri-compare{display:flex;gap:32px;justify-content:center;padding:16px 0;}
|
||||
.nutri-compare__item{display:flex;flex-direction:column;align-items:center;gap:6px;min-width:120px;}
|
||||
.nutri-compare__score{font-size:40px;font-weight:700;line-height:1;}
|
||||
.nutri-compare__score.green{color:var(--success);}
|
||||
.nutri-compare__score.orange{color:var(--warning);}
|
||||
.nutri-compare__score.gray{color:#999;}
|
||||
.nutri-compare__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
/* 趋势图 */
|
||||
.nutri-charts{display:grid;grid-template-columns:1fr 1fr;gap:20px;}
|
||||
.nutri-chart-box{background:#fafafa;border-radius:var(--radius-md);padding:16px;}
|
||||
.nutri-chart-box__title{font-size:var(--font-sm);font-weight:500;color:var(--text-primary);margin-bottom:12px;}
|
||||
.nutri-chart-box svg{width:100%;display:block;}
|
||||
/* 风险评估 */
|
||||
.nutri-risk{display:flex;align-items:center;gap:12px;padding:14px 20px;border-radius:var(--radius-md);font-size:var(--font-sm);}
|
||||
.nutri-risk.low{background:#F6FFED;color:var(--success);border:1px solid #b7eb8f;}
|
||||
.nutri-risk.mid{background:#FFF7E6;color:var(--warning);border:1px solid #ffe58f;}
|
||||
.nutri-risk.high{background:#FFF1F0;color:var(--danger);border:1px solid #ffa39e;}
|
||||
.nutri-risk__icon{font-size:20px;flex-shrink:0;}
|
||||
.nutri-risk__text{display:flex;flex-direction:column;gap:2px;}
|
||||
.nutri-risk__title{font-weight:600;}
|
||||
/* 干预建议 */
|
||||
.nutri-advice{background:#f8fafc;border-radius:var(--radius-md);padding:16px 20px;}
|
||||
.nutri-advice__item{display:flex;align-items:flex-start;gap:8px;font-size:var(--font-sm);color:var(--text-primary);padding:6px 0;}
|
||||
.nutri-advice__item::before{content:'💡';flex-shrink:0;}
|
||||
/* 膳食宝塔 */
|
||||
.pyramid{display:flex;flex-direction:column;align-items:center;gap:0;padding:20px 0;}
|
||||
.pyramid__level{display:flex;align-items:center;justify-content:center;text-align:center;font-size:var(--font-sm);color:#fff;font-weight:500;border-radius:4px 4px 0 0;padding:10px 8px;line-height:1.4;}
|
||||
.pyramid__level:nth-child(1){width:20%;background:#FF4D4F;}
|
||||
.pyramid__level:nth-child(2){width:35%;background:#FAAD14;}
|
||||
.pyramid__level:nth-child(3){width:50%;background:#1890FF;}
|
||||
.pyramid__level:nth-child(4){width:65%;background:#13C2C2;}
|
||||
.pyramid__level:nth-child(5){width:80%;background:#52C41A;}
|
||||
.pyramid__legend{margin-top:20px;width:100%;}
|
||||
.pyramid__legend-row{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px dashed #f0f0f0;font-size:var(--font-sm);}
|
||||
.pyramid__legend-name{display:flex;align-items:center;gap:8px;}
|
||||
.pyramid__legend-dot{width:10px;height:10px;border-radius:2px;flex-shrink:0;}
|
||||
.pyramid__legend-values{display:flex;gap:24px;color:var(--text-secondary);}
|
||||
.pyramid__legend-values strong{color:var(--text-primary);}
|
||||
|
||||
/* 餐品明细(溯源关联)弹窗 */
|
||||
.dish-modal{width:980px;}
|
||||
.dish-header-info{display:flex;align-items:center;gap:24px;padding:14px 20px;background:#f8fafc;border-radius:var(--radius-md);margin-bottom:16px;font-size:var(--font-sm);flex-wrap:wrap;}
|
||||
.dish-header-info__item{display:flex;align-items:center;gap:6px;}
|
||||
.dish-header-info__label{color:var(--text-secondary);}
|
||||
.dish-header-info__value{color:var(--text-primary);font-weight:500;}
|
||||
.dish-tip{display:flex;align-items:center;gap:8px;padding:10px 16px;background:var(--primary-light);border-radius:var(--radius-sm);margin-bottom:16px;font-size:var(--font-sm);color:var(--primary);}
|
||||
.dish-tip svg{flex-shrink:0;}
|
||||
.dish-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);margin-bottom:16px;}
|
||||
.dish-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
|
||||
.dish-table tbody td{padding:12px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);white-space:nowrap;}
|
||||
.dish-table tbody tr:last-child td{border-bottom:none;}
|
||||
.dish-table tbody tr:hover{background:#F8FBFF;}
|
||||
.dish-table .td-num{font-variant-numeric:tabular-nums;}
|
||||
.dish-table .td-code{color:var(--text-secondary);font-family:monospace;font-size:12px;}
|
||||
.dish-trace-link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;gap:4px;}.dish-trace-link:hover{color:#40a9ff;}
|
||||
/* 底部汇总 */
|
||||
.dish-summary{display:flex;align-items:center;gap:32px;padding:16px 20px;background:#f8fafc;border-radius:var(--radius-md);font-size:var(--font-sm);}
|
||||
.dish-summary__item{display:flex;align-items:baseline;gap:6px;}
|
||||
.dish-summary__label{color:var(--text-secondary);}
|
||||
.dish-summary__value{font-size:var(--font-lg);font-weight:700;color:var(--text-primary);}
|
||||
.dish-summary__value.blue{color:var(--primary);}
|
||||
.dish-summary__value.green{color:var(--success);}
|
||||
.dish-summary__value.orange{color:var(--warning);}
|
||||
|
||||
/* 营养师工作台弹窗 */
|
||||
.wb-modal{width:1060px;}
|
||||
.wb-layout{display:flex;gap:16px;align-items:flex-start;}
|
||||
/* 左侧用户摘要 */
|
||||
.wb-left{width:240px;flex-shrink:0;background:#f8fafc;border-radius:var(--radius-md);padding:16px;}
|
||||
.wb-avatar-row{display:flex;align-items:center;gap:10px;margin-bottom:14px;}
|
||||
.wb-avatar{width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;}
|
||||
.wb-user-name{font-size:14px;font-weight:700;color:var(--text-primary);}
|
||||
.wb-user-meta{font-size:11px;color:var(--text-secondary);margin-top:2px;}
|
||||
.wb-score-row{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px;}
|
||||
.wb-score-card{background:#fff;border-radius:6px;padding:8px;text-align:center;}
|
||||
.wb-score-card__val{font-size:18px;font-weight:800;line-height:1.2;}
|
||||
.wb-score-card__label{font-size:10px;color:var(--text-secondary);margin-top:2px;}
|
||||
.wb-spark{background:#fff;border-radius:6px;padding:10px;margin-bottom:12px;}
|
||||
.wb-spark__title{font-size:11px;color:var(--text-secondary);margin-bottom:6px;}
|
||||
.wb-spark svg{display:block;width:100%;}
|
||||
.wb-info-list{display:flex;flex-direction:column;gap:8px;}
|
||||
.wb-info-item__label{font-size:11px;color:var(--text-secondary);}
|
||||
.wb-info-item__value{font-size:13px;font-weight:600;color:var(--text-primary);}
|
||||
/* 右侧 Tab */
|
||||
.wb-right{flex:1;min-width:0;}
|
||||
.wb-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:14px;}
|
||||
.wb-tab{padding:8px 16px;font-size:13px;color:var(--text-secondary);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;margin-bottom:-1px;transition:all .2s;white-space:nowrap;}
|
||||
.wb-tab:hover{color:var(--primary);}
|
||||
.wb-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
|
||||
.wb-tab-body{max-height:520px;overflow-y:auto;padding-right:6px;}
|
||||
.wb-panel{display:none;}
|
||||
.wb-panel.active{display:block;}
|
||||
/* 预警卡片 */
|
||||
.wb-warn{border-left:4px solid;border-radius:6px;padding:12px 14px;margin-bottom:10px;background:#fff;}
|
||||
.wb-warn.high{border-color:var(--danger);border-top:1px solid #ffa39e;border-right:1px solid #ffa39e;border-bottom:1px solid #ffa39e;}
|
||||
.wb-warn.mid{border-color:var(--warning);border-top:1px solid #ffe58f;border-right:1px solid #ffe58f;border-bottom:1px solid #ffe58f;}
|
||||
.wb-warn.low{border-color:var(--primary);border-top:1px solid #91d5ff;border-right:1px solid #91d5ff;border-bottom:1px solid #91d5ff;}
|
||||
.wb-warn__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;}
|
||||
.wb-warn__title{font-size:14px;font-weight:600;}
|
||||
.wb-warn__body{font-size:12px;color:var(--text-secondary);line-height:1.7;}
|
||||
.wb-warn__foot{font-size:11px;color:var(--text-secondary);margin-top:6px;}
|
||||
/* 规划卡片 */
|
||||
.wb-plan{background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);padding:14px;margin-bottom:12px;}
|
||||
.wb-plan__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;}
|
||||
.wb-plan__goal{font-size:14px;font-weight:600;color:var(--text-primary);}
|
||||
.wb-plan__period{font-size:11px;color:var(--text-secondary);margin-bottom:8px;}
|
||||
.wb-plan__bar{background:#f0f0f0;height:6px;border-radius:3px;margin-bottom:8px;overflow:hidden;}
|
||||
.wb-plan__bar-fill{height:100%;border-radius:3px;background:linear-gradient(90deg,#1890FF,#36cfc9);}
|
||||
.wb-plan__pct{font-size:11px;color:var(--text-secondary);margin-bottom:8px;}
|
||||
.wb-plan__items{font-size:12px;color:var(--text-secondary);line-height:1.9;}
|
||||
.wb-plan__actions{display:flex;gap:6px;margin-top:10px;}
|
||||
/* 建议卡片 */
|
||||
.wb-adv{border:1px solid var(--border);border-radius:6px;padding:10px 12px;margin-bottom:8px;}
|
||||
.wb-adv.ai{background:#f0f7ff;}
|
||||
.wb-adv__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;}
|
||||
.wb-adv__author{font-size:12px;font-weight:600;display:flex;align-items:center;gap:6px;}
|
||||
.wb-adv__time{font-size:11px;color:var(--text-secondary);}
|
||||
.wb-adv__content{font-size:13px;color:var(--text-secondary);line-height:1.7;}
|
||||
/* 跟进时间轴 */
|
||||
.wb-timeline{position:relative;padding-left:18px;}
|
||||
.wb-timeline::before{content:'';position:absolute;left:5px;top:6px;bottom:6px;width:2px;background:var(--border);}
|
||||
.wb-tl-item{position:relative;margin-bottom:14px;}
|
||||
.wb-tl-dot{position:absolute;left:-15px;top:6px;width:10px;height:10px;border-radius:50%;background:var(--primary);border:2px solid #fff;box-shadow:0 0 0 1px var(--primary);}
|
||||
.wb-tl-card{background:#fff;border:1px solid var(--border);border-radius:6px;padding:10px 12px;}
|
||||
.wb-tl-card__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;}
|
||||
.wb-tl-card__topic{font-size:13px;font-weight:600;}
|
||||
.wb-tl-card__time{font-size:11px;color:var(--text-secondary);}
|
||||
.wb-tl-card__who{font-size:12px;color:var(--text-secondary);margin-bottom:6px;}
|
||||
.wb-tl-card__body{font-size:12px;color:var(--text-secondary);line-height:1.7;}
|
||||
.wb-tl-card__fb{font-size:11px;color:var(--text-secondary);margin-top:6px;}
|
||||
/* 空状态 */
|
||||
.wb-empty{text-align:center;padding:40px 20px;color:var(--text-placeholder);}
|
||||
.wb-empty__icon{font-size:32px;margin-bottom:8px;}
|
||||
/* 面板头部工具条 */
|
||||
.wb-panel-bar{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;}
|
||||
.wb-panel-bar__title{font-size:13px;font-weight:600;}
|
||||
/* 建议输入区 */
|
||||
.wb-adv-input{width:100%;min-height:70px;padding:8px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-sm);resize:vertical;outline:none;font-family:inherit;}
|
||||
.wb-adv-input:focus{border-color:var(--primary);}
|
||||
.wb-adv-btns{display:flex;gap:6px;margin-top:8px;margin-bottom:12px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -122,40 +321,40 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-menu__title">营养用餐</div>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item">字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item">营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item">食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item">餐品总库</a>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item"><span class="mi">📖</span>字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item"><span class="mi">🔬</span>营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item"><span class="mi">🥦</span>食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item"><span class="mi">🍲</span>餐品总库</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
算法标准
|
||||
📐 算法标准
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-algo-population.html" class="sidebar-item">人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item">VIP定制标准</a>
|
||||
<a href="din-algo-population.html" class="sidebar-item"><span class="mi">👥</span>人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item"><span class="mi">💎</span>VIP定制标准</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item">食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item">营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item">用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item">餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item">取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item">消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item active">营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item">餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item">终端管理</a>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item"><span class="mi">📋</span>食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item"><span class="mi">🛒</span>营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item"><span class="mi">🧑</span>用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item"><span class="mi">⚖️</span>餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item"><span class="mi">🍽️</span>取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item"><span class="mi">💰</span>消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item active"><span class="mi">📊</span>营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item"><span class="mi">📈</span>餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item"><span class="mi">🖥️</span>终端管理</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
模型管理
|
||||
🤖 模型管理
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-model-llm.html" class="sidebar-item">大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item">小模型管理</a>
|
||||
<a href="din-model-llm.html" class="sidebar-item"><span class="mi">🧠</span>大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item"><span class="mi">⚙️</span>小模型管理</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-innovation.html" class="sidebar-item">味养创新</a>
|
||||
<a href="din-innovation.html" class="sidebar-item"><span class="mi">🌟</span>味养创新</a>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容 -->
|
||||
@@ -192,10 +391,10 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<button class="btn btn-default">重置</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>
|
||||
<button class="btn btn-default">📊 群体分析</button>
|
||||
<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="table-card">
|
||||
<div class="table-card__header">
|
||||
@@ -233,10 +432,10 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<button class="btn btn-default">重置</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>
|
||||
<button class="btn btn-default">📊 群体分析</button>
|
||||
<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="table-card">
|
||||
<div class="table-card__header">
|
||||
@@ -272,10 +471,10 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<button class="btn btn-default">重置</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>
|
||||
<button class="btn btn-default">📊 群体分析</button>
|
||||
<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="table-card">
|
||||
<div class="table-card__header">
|
||||
@@ -310,10 +509,10 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<button class="btn btn-default">重置</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>
|
||||
<button class="btn btn-primary">➕ 新建跟进</button>
|
||||
<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="table-card">
|
||||
<div class="table-card__header">
|
||||
@@ -344,6 +543,444 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
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));}
|
||||
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');})();
|
||||
|
||||
/* ========== 弹窗基础 ========== */
|
||||
const Modal = {
|
||||
open(title, bodyHtml, extraClass, footerHtml) {
|
||||
this.close();
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
overlay.innerHTML = `<div class="modal ${extraClass||''}">
|
||||
<div class="modal-header"><div class="modal-title">${title}</div><button class="modal-close" onclick="Modal.close()">×</button></div>
|
||||
<div class="modal-body">${bodyHtml}</div>
|
||||
${footerHtml ? '<div class="modal-footer">'+footerHtml+'</div>' : ''}
|
||||
</div>`;
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) Modal.close(); });
|
||||
document.body.appendChild(overlay);
|
||||
},
|
||||
close() {
|
||||
const o = document.querySelector('.modal-overlay');
|
||||
if (!o) return;
|
||||
o.classList.add('closing');
|
||||
setTimeout(() => o.remove(), 200);
|
||||
}
|
||||
};
|
||||
|
||||
/* ========== 十维档案 ========== */
|
||||
const userProfiles = {
|
||||
'张伟': { 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%' },
|
||||
'李娜': { 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%' }
|
||||
};
|
||||
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%' };
|
||||
|
||||
const tabDefs = [
|
||||
{ key:'basic', label:'静态基础' },{ key:'disease', label:'疾病信息' },{ key:'physical', label:'体检信息' },
|
||||
{ key:'taste', label:'口味偏好' },{ key:'taboo', label:'禁忌关联' },{ key:'dynamic', label:'动态生理' },
|
||||
{ key:'meal', label:'历史用餐' },{ key:'dish', label:'菜品偏好' },{ key:'mom', label:'妈妈味道' },{ key:'model', label:'个性模型' }
|
||||
];
|
||||
|
||||
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>`
|
||||
};
|
||||
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('');
|
||||
const summary = `<div class="profile-summary"><div class="profile-summary__avatar">${name[0]}</div><div class="profile-summary__info"><div class="profile-summary__name">${name}</div><div class="profile-summary__meta">${p.dept} · ${p.gender} · ${p.age}岁 · BMI ${p.bmi}</div></div></div>`;
|
||||
return summary + `<div class="profile-tabs">${tabs}</div>${panelsHtml}`;
|
||||
}
|
||||
|
||||
function switchProfileTab(el, key) {
|
||||
el.parentElement.querySelectorAll('.profile-tab').forEach(t => t.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
el.closest('.modal-body').querySelectorAll('.profile-panel').forEach(p => p.classList.remove('active'));
|
||||
el.closest('.modal-body').querySelector(`[data-panel="${key}"]`).classList.add('active');
|
||||
}
|
||||
|
||||
function showUserProfile(name) {
|
||||
const p = userProfiles[name] || defaultProfile;
|
||||
Modal.open('用户十维档案 — ' + name, buildProfileHTML(name, p));
|
||||
}
|
||||
|
||||
/* ========== 营养明细弹窗 ========== */
|
||||
function buildNutriDetailHTML() {
|
||||
/* SVG 折线图:热量趋势 */
|
||||
const calPoints = [2080,2150,2020,2200,2100,2180,2156];
|
||||
const naPoints = [1750,1900,1680,1850,1920,1780,1820];
|
||||
const dates = ['4/22','4/23','4/24','4/25','4/26','4/27','4/28'];
|
||||
function buildSvgLine(data, color, unit, minY, maxY) {
|
||||
const w = 340, h = 120, px = 36, pr = 10, pt = 10, pb = 24;
|
||||
const cw = w - px - pr, ch = h - pt - pb;
|
||||
const step = cw / (data.length - 1);
|
||||
const range = maxY - minY || 1;
|
||||
const pts = data.map((v, i) => [px + i * step, pt + ch - (v - minY) / range * ch]);
|
||||
const line = pts.map(p => p.join(',')).join(' ');
|
||||
const gridLines = [0, 0.5, 1].map(r => {
|
||||
const y = pt + ch - r * ch;
|
||||
const val = Math.round(minY + r * range);
|
||||
return `<line x1="${px}" y1="${y}" x2="${w - pr}" y2="${y}" stroke="#f0f0f0" stroke-width="1"/>
|
||||
<text x="${px - 4}" y="${y + 4}" text-anchor="end" fill="#999" font-size="10">${val}</text>`;
|
||||
}).join('');
|
||||
const xLabels = dates.map((d, i) => `<text x="${px + i * step}" y="${h - 4}" text-anchor="middle" fill="#999" font-size="10">${d}</text>`).join('');
|
||||
const dots = pts.map((p, i) => `<circle cx="${p[0]}" cy="${p[1]}" r="3" fill="${color}" stroke="#fff" stroke-width="1.5"/>`).join('');
|
||||
return `<svg viewBox="0 0 ${w} ${h}" xmlns="http://www.w3.org/2000/svg">
|
||||
${gridLines}${xLabels}
|
||||
<polyline points="${line}" fill="none" stroke="${color}" stroke-width="2" stroke-linejoin="round"/>
|
||||
${dots}
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
const calChart = buildSvgLine(calPoints, '#1890FF', 'kcal', 1900, 2300);
|
||||
const naChart = buildSvgLine(naPoints, '#FAAD14', 'mg', 1500, 2100);
|
||||
|
||||
/* 营养维度面板 */
|
||||
const dimensionPanel = `
|
||||
<div class="nutri-section">
|
||||
<div class="nutri-section__title">当日营养摄入 vs 日推荐量</div>
|
||||
<div class="nutri-bar-list">
|
||||
<div class="nutri-bar-item">
|
||||
<span class="nutri-bar-item__label">热量</span>
|
||||
<div class="nutri-bar-item__track"><div class="nutri-bar-item__fill green" style="width:96%"></div></div>
|
||||
<span class="nutri-bar-item__info"><strong>2,156 kcal</strong> / 推荐 2,250 kcal(96%)</span>
|
||||
</div>
|
||||
<div class="nutri-bar-item">
|
||||
<span class="nutri-bar-item__label">蛋白质</span>
|
||||
<div class="nutri-bar-item__track"><div class="nutri-bar-item__fill green" style="width:100%"></div></div>
|
||||
<span class="nutri-bar-item__info"><strong>78.3 g</strong> / 推荐 75 g(104%)</span>
|
||||
</div>
|
||||
<div class="nutri-bar-item">
|
||||
<span class="nutri-bar-item__label">碳水</span>
|
||||
<div class="nutri-bar-item__track"><div class="nutri-bar-item__fill green" style="width:95%"></div></div>
|
||||
<span class="nutri-bar-item__info"><strong>285.4 g</strong> / 推荐 300 g(95%)</span>
|
||||
</div>
|
||||
<div class="nutri-bar-item">
|
||||
<span class="nutri-bar-item__label">脂肪</span>
|
||||
<div class="nutri-bar-item__track"><div class="nutri-bar-item__fill orange" style="width:90%"></div></div>
|
||||
<span class="nutri-bar-item__info"><strong>72.1 g</strong> / 推荐 80 g(90%)</span>
|
||||
</div>
|
||||
<div class="nutri-bar-item">
|
||||
<span class="nutri-bar-item__label">钠</span>
|
||||
<div class="nutri-bar-item__track"><div class="nutri-bar-item__fill green" style="width:91%"></div></div>
|
||||
<span class="nutri-bar-item__info"><strong>1,820 mg</strong> / 推荐 2,000 mg(91%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutri-section">
|
||||
<div class="nutri-section__title">与群体均值对比(综合评分)</div>
|
||||
<div class="nutri-compare">
|
||||
<div class="nutri-compare__item"><div class="nutri-compare__score green">88</div><div class="nutri-compare__label">本人</div></div>
|
||||
<div class="nutri-compare__item"><div class="nutri-compare__score orange">84</div><div class="nutri-compare__label">研发部均值</div></div>
|
||||
<div class="nutri-compare__item"><div class="nutri-compare__score gray">81</div><div class="nutri-compare__label">全公司均值</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutri-section">
|
||||
<div class="nutri-section__title">近7日营养素趋势</div>
|
||||
<div class="nutri-charts">
|
||||
<div class="nutri-chart-box"><div class="nutri-chart-box__title">热量(kcal)</div>${calChart}</div>
|
||||
<div class="nutri-chart-box"><div class="nutri-chart-box__title">钠(mg)</div>${naChart}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutri-section">
|
||||
<div class="nutri-section__title">慢病风险评估</div>
|
||||
<div class="nutri-risk low">
|
||||
<span class="nutri-risk__icon">✅</span>
|
||||
<div class="nutri-risk__text"><span class="nutri-risk__title">低风险</span><span>暂无慢病风险项,继续保持良好饮食习惯</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nutri-section">
|
||||
<div class="nutri-section__title">专业干预建议</div>
|
||||
<div class="nutri-advice">
|
||||
<div class="nutri-advice__item">本周营养摄入稳定,各项指标均在合理范围</div>
|
||||
<div class="nutri-advice__item">脂肪摄入连续偏高,建议减少红肉频次,每周不超过3次</div>
|
||||
<div class="nutri-advice__item">建议增加深色蔬菜摄入,补充膳食纤维</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
/* 膳食宝塔面板 */
|
||||
const pyramidPanel = `
|
||||
<div class="pyramid">
|
||||
<div class="pyramid__level" style="background:#FF4D4F;">油盐<br>油 25g · 盐 5g</div>
|
||||
<div class="pyramid__level" style="background:#FAAD14;">奶豆<br>奶 300g · 大豆 25g</div>
|
||||
<div class="pyramid__level" style="background:#1890FF;">鱼肉蛋<br>鱼虾 75g · 畜禽肉 50g · 蛋 50g</div>
|
||||
<div class="pyramid__level" style="background:#13C2C2;">蔬果<br>蔬菜 400g · 水果 250g</div>
|
||||
<div class="pyramid__level" style="background:#52C41A;">谷薯<br>谷薯类 300g</div>
|
||||
</div>
|
||||
<div class="pyramid__legend">
|
||||
<div class="pyramid__legend-row"><span class="pyramid__legend-name"><span class="pyramid__legend-dot" style="background:#52C41A"></span>谷薯类</span><div class="pyramid__legend-values"><span>推荐 <strong>300g</strong></span><span>实际 <strong>285g</strong></span><span class="tag tag-green">达标</span></div></div>
|
||||
<div class="pyramid__legend-row"><span class="pyramid__legend-name"><span class="pyramid__legend-dot" style="background:#13C2C2"></span>蔬菜水果</span><div class="pyramid__legend-values"><span>推荐 <strong>650g</strong></span><span>实际 <strong>580g</strong></span><span class="tag tag-orange">偏低</span></div></div>
|
||||
<div class="pyramid__legend-row"><span class="pyramid__legend-name"><span class="pyramid__legend-dot" style="background:#1890FF"></span>鱼肉蛋</span><div class="pyramid__legend-values"><span>推荐 <strong>175g</strong></span><span>实际 <strong>190g</strong></span><span class="tag tag-green">达标</span></div></div>
|
||||
<div class="pyramid__legend-row"><span class="pyramid__legend-name"><span class="pyramid__legend-dot" style="background:#FAAD14"></span>奶豆类</span><div class="pyramid__legend-values"><span>推荐 <strong>325g</strong></span><span>实际 <strong>260g</strong></span><span class="tag tag-orange">偏低</span></div></div>
|
||||
<div class="pyramid__legend-row"><span class="pyramid__legend-name"><span class="pyramid__legend-dot" style="background:#FF4D4F"></span>油盐</span><div class="pyramid__legend-values"><span>推荐 <strong>油25g 盐5g</strong></span><span>实际 <strong>油22g 盐4.6g</strong></span><span class="tag tag-green">达标</span></div></div>
|
||||
</div>`;
|
||||
|
||||
/* 顶部信息 + Tab 组装 */
|
||||
const headerInfo = `<div class="nutri-header-info">
|
||||
<div class="nutri-header-info__item"><span class="nutri-header-info__label">用户:</span><span class="nutri-header-info__value">张伟(EMP001)</span></div>
|
||||
<div class="nutri-header-info__item"><span class="nutri-header-info__label">统计日期:</span><span class="nutri-header-info__value">2026-04-28</span></div>
|
||||
<div class="nutri-header-info__item"><span class="nutri-header-info__label">统计范围:</span><span class="nutri-header-info__value">午餐聚合</span></div>
|
||||
<div class="nutri-header-info__item"><span class="nutri-header-info__label">综合评分:</span><span class="tag tag-green">88分</span></div>
|
||||
<div class="nutri-header-info__item"><span class="nutri-header-info__label">慢病风险:</span><span class="tag tag-green">低风险</span></div>
|
||||
</div>`;
|
||||
|
||||
const tabs = `<div class="nutri-tabs">
|
||||
<div class="nutri-tab active" onclick="switchNutriTab(this,0)">📊 营养维度</div>
|
||||
<div class="nutri-tab" onclick="switchNutriTab(this,1)">🔺 膳食宝塔</div>
|
||||
</div>`;
|
||||
|
||||
return headerInfo + tabs +
|
||||
`<div class="nutri-panel active">${dimensionPanel}</div>` +
|
||||
`<div class="nutri-panel">${pyramidPanel}</div>`;
|
||||
}
|
||||
|
||||
function switchNutriTab(el, idx) {
|
||||
el.parentElement.querySelectorAll('.nutri-tab').forEach((t, i) => t.classList.toggle('active', i === idx));
|
||||
el.closest('.modal-body').querySelectorAll('.nutri-panel').forEach((p, i) => p.classList.toggle('active', i === idx));
|
||||
}
|
||||
|
||||
function showNutriDetail() {
|
||||
Modal.open('营养明细 — 张伟(EMP001) · 2026-04-28 午餐聚合', buildNutriDetailHTML(), 'nutri-modal');
|
||||
}
|
||||
|
||||
/* ========== 餐品明细弹窗 ========== */
|
||||
function buildDishDetailHTML() {
|
||||
const headerInfo = `<div class="dish-header-info">
|
||||
<div class="dish-header-info__item"><span class="dish-header-info__label">用户:</span><span class="dish-header-info__value">张伟(EMP001)</span></div>
|
||||
<div class="dish-header-info__item"><span class="dish-header-info__label">统计范围:</span><span class="dish-header-info__value">2026-04-28 午餐</span></div>
|
||||
<div class="dish-header-info__item"><span class="dish-header-info__label">消费合计:</span><span class="dish-header-info__value" style="color:var(--primary);font-weight:700;">¥22.00</span></div>
|
||||
<div class="dish-header-info__item"><span class="dish-header-info__label">营养评分:</span><span class="tag tag-green">88分</span></div>
|
||||
</div>`;
|
||||
|
||||
const tip = `<div class="dish-tip">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5"/><path d="M8 4.5v4M8 10.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
点击「食材溯源链路」按钮可在链路可视化中查看该餐品对应的食材溯源链路
|
||||
</div>`;
|
||||
|
||||
const table = `<table class="dish-table">
|
||||
<thead><tr><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 class="td-code">DM-MIFA</td><td>米饭</td><td>180g</td><td>232</td><td>4.3g</td><td>50.8g</td><td>0.5g</td><td>2mg</td><td>¥2.00</td><td><span class="tag tag-green">正常</span></td><td><a class="dish-trace-link">🔗 食材溯源链路</a></td></tr>
|
||||
<tr><td class="td-code">DM-HSRO</td><td>红烧肉</td><td>120g</td><td>395</td><td>14.2g</td><td>8.6g</td><td>28g</td><td>680mg</td><td>¥12.00</td><td><span class="tag tag-red">⚠️ 脂肪偏高</span></td><td><a class="dish-trace-link">🔗 食材溯源链路</a></td></tr>
|
||||
<tr><td class="td-code">DM-CSHU</td><td>炒时蔬</td><td>80g</td><td>38</td><td>2.1g</td><td>5.2g</td><td>1.2g</td><td>180mg</td><td>¥8.00</td><td><span class="tag tag-green">正常</span></td><td><a class="dish-trace-link">🔗 食材溯源链路</a></td></tr>
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
||||
const summary = `<div class="dish-summary">
|
||||
<div class="dish-summary__item"><span class="dish-summary__label">总热量:</span><span class="dish-summary__value blue">856 kcal</span></div>
|
||||
<div class="dish-summary__item"><span class="dish-summary__label">热量达成率:</span><span class="dish-summary__value orange">43%</span></div>
|
||||
<div class="dish-summary__item"><span class="dish-summary__label">消费合计:</span><span class="dish-summary__value">¥22.00</span></div>
|
||||
<div class="dish-summary__item"><span class="dish-summary__label">营养评分:</span><span class="dish-summary__value green">88分</span></div>
|
||||
</div>`;
|
||||
|
||||
return headerInfo + tip + table + summary;
|
||||
}
|
||||
|
||||
function showDishDetail() {
|
||||
Modal.open('溯源关联 — 张伟(EMP001) · 2026-04-28 午餐', buildDishDetailHTML(), 'dish-modal');
|
||||
}
|
||||
|
||||
/* ========== 营养师工作台弹窗 ========== */
|
||||
const wbData = {
|
||||
user:'张伟(EMP001)', dept:'研发部', personType:'会员', age:38, gender:'男', bmi:'22.9', diseaseLabel:'无',
|
||||
avgScore30:86, riskLevel:'低', interventionStatus:'已稳定', nutritionist:'林营养师', lastFollowUp:'2026-04-26',
|
||||
scoreTrend:[82,84,85,86,88,87,86],
|
||||
warnings:[],
|
||||
plans:[
|
||||
{ goal:'维持当前营养均衡水平', period:'2026-04-01 ~ 2026-09-30', progress:65, items:['每日蛋白质保持 ≥75g','深色蔬菜每日 ≥250g','红肉每周 ≤3 次'], status:'进行中' }
|
||||
],
|
||||
advices:[
|
||||
{ time:'2026-04-26', author:'林营养师', content:'本月各项指标稳定,继续保持当前饮食结构。下次复评 2026-05-26。', isAi:false },
|
||||
{ time:'2026-04-15', author:'AI 助手', content:'检测到脂肪摄入连续 3 日偏高,建议减少红肉频次。', isAi:true }
|
||||
],
|
||||
followUps:[
|
||||
{ time:'2026-04-26 14:30', who:'林营养师', topic:'月度复评', content:'电话沟通,用户反馈饮食习惯无变化,BMI 维持 22.9。', feedback:'满意' },
|
||||
{ time:'2026-03-26 10:00', who:'林营养师', topic:'月度复评', content:'首次建立营养档案,目标维持稳定。', feedback:'认可' }
|
||||
]
|
||||
};
|
||||
|
||||
function buildWorkbenchHTML() {
|
||||
const a = wbData;
|
||||
const scoreColor = a.avgScore30 >= 85 ? '#52C41A' : a.avgScore30 >= 75 ? '#1890FF' : a.avgScore30 >= 60 ? '#FAAD14' : '#FF4D4F';
|
||||
const riskColor = a.riskLevel === '高' ? '#FF4D4F' : a.riskLevel === '中' ? '#FAAD14' : '#52C41A';
|
||||
|
||||
/* 迷你折线图 */
|
||||
const trend = a.scoreTrend;
|
||||
const maxS = Math.max(...trend), minS = Math.min(...trend), spkRange = (maxS - minS) || 1;
|
||||
const spkPts = trend.map((v, i) => {
|
||||
const x = (i / (trend.length - 1)) * 200;
|
||||
const y = 40 - ((v - minS) / spkRange) * 34 - 3;
|
||||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||||
}).join(' ');
|
||||
const spkDots = trend.map((v, i) => {
|
||||
const x = (i / (trend.length - 1)) * 200;
|
||||
const y = 40 - ((v - minS) / spkRange) * 34 - 3;
|
||||
return `<circle cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="2" fill="${scoreColor}"/>`;
|
||||
}).join('');
|
||||
|
||||
/* 左侧面板 */
|
||||
const left = `<div class="wb-left">
|
||||
<div class="wb-avatar-row">
|
||||
<div class="wb-avatar" style="background:${scoreColor}22">👤</div>
|
||||
<div><div class="wb-user-name">${a.user}</div><div class="wb-user-meta">${a.dept} · ${a.gender} · ${a.age}岁</div></div>
|
||||
</div>
|
||||
<div class="wb-score-row">
|
||||
<div class="wb-score-card"><div class="wb-score-card__val" style="color:${scoreColor}">${a.avgScore30}</div><div class="wb-score-card__label">近30日均分</div></div>
|
||||
<div class="wb-score-card"><div class="wb-score-card__val" style="color:${riskColor}">${a.riskLevel}</div><div class="wb-score-card__label">风险等级</div></div>
|
||||
</div>
|
||||
<div class="wb-spark">
|
||||
<div class="wb-spark__title">近7日评分趋势</div>
|
||||
<svg viewBox="0 0 200 40"><polyline points="${spkPts}" fill="none" stroke="${scoreColor}" stroke-width="2"/>${spkDots}</svg>
|
||||
</div>
|
||||
<div class="wb-info-list">
|
||||
<div><div class="wb-info-item__label">BMI</div><div class="wb-info-item__value">${a.bmi}</div></div>
|
||||
<div><div class="wb-info-item__label">慢病标签</div><div class="wb-info-item__value" style="color:${a.diseaseLabel === '无' ? '#52C41A' : '#FF4D4F'}">${a.diseaseLabel}</div></div>
|
||||
<div><div class="wb-info-item__label">人员类别</div><div class="wb-info-item__value">${a.personType}</div></div>
|
||||
<div><div class="wb-info-item__label">负责营养师</div><div class="wb-info-item__value" style="color:var(--primary)">${a.nutritionist}</div></div>
|
||||
<div><div class="wb-info-item__label">最近跟进</div><div class="wb-info-item__value">${a.lastFollowUp}</div></div>
|
||||
<div><div class="wb-info-item__label">干预状态</div><div class="wb-info-item__value">${a.interventionStatus}</div></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
/* ① 疾病预警 */
|
||||
const warnList = a.warnings.length
|
||||
? a.warnings.map(w => {
|
||||
const cls = w.severity === '高' ? 'high' : w.severity === '中' ? 'mid' : 'low';
|
||||
const sevColor = w.severity === '高' ? 'var(--danger)' : w.severity === '中' ? 'var(--warning)' : 'var(--primary)';
|
||||
return `<div class="wb-warn ${cls}">
|
||||
<div class="wb-warn__head"><span class="wb-warn__title" style="color:${sevColor}">⚠️ ${w.type}</span><span class="tag" style="background:${sevColor}20;color:${sevColor}">${w.severity}风险</span></div>
|
||||
<div class="wb-warn__body">${w.evidence}</div>
|
||||
<div class="wb-warn__foot">触发时间:${w.triggeredAt} · ${w.notified ? '<span style="color:#52C41A">✓ 已通知</span>' : '<span style="color:#FAAD14">⏳ 待通知</span>'}</div>
|
||||
</div>`;
|
||||
}).join('')
|
||||
: '<div class="wb-empty"><div class="wb-empty__icon">✅</div>当前无疾病预警</div>';
|
||||
const warnHtml = `<div class="wb-panel-bar"><div class="wb-panel-bar__title">预警列表(${a.warnings.length})</div><button class="btn btn-primary" style="height:28px;font-size:12px;padding:0 10px;" onclick="alert('已添加预警(原型演示)')">+ 新增预警</button></div>${warnList}`;
|
||||
|
||||
/* ② 营养规划 */
|
||||
const planList = a.plans.length
|
||||
? a.plans.map(p => `<div class="wb-plan">
|
||||
<div class="wb-plan__head"><span class="wb-plan__goal">🎯 ${p.goal}</span><span class="tag tag-blue">${p.status}</span></div>
|
||||
<div class="wb-plan__period">⏱ ${p.period}</div>
|
||||
<div class="wb-plan__bar"><div class="wb-plan__bar-fill" style="width:${p.progress}%"></div></div>
|
||||
<div class="wb-plan__pct">完成进度 <strong style="color:var(--primary)">${p.progress}%</strong></div>
|
||||
<div class="wb-plan__items">${p.items.map(i => `<div>· ${i}</div>`).join('')}</div>
|
||||
<div class="wb-plan__actions"><button class="btn btn-default" style="height:26px;font-size:11px;padding:0 8px;" onclick="alert('编辑规划(原型演示)')">编辑</button><button class="btn btn-default" style="height:26px;font-size:11px;padding:0 8px;" onclick="alert('已结案(原型演示)')">结案</button></div>
|
||||
</div>`).join('')
|
||||
: '<div class="wb-empty"><div class="wb-empty__icon">📋</div>暂无营养规划</div>';
|
||||
const planHtml = `<div class="wb-panel-bar"><div class="wb-panel-bar__title">营养规划(${a.plans.length})</div><button class="btn btn-primary" style="height:28px;font-size:12px;padding:0 10px;" onclick="alert('已创建规划(原型演示)')">+ 新建规划</button></div>${planList}`;
|
||||
|
||||
/* ③ 营养建议 */
|
||||
const advList = a.advices.map(ad => {
|
||||
const tag = ad.isAi ? '<span class="tag tag-blue" style="font-size:10px">🤖 AI</span>' : '<span class="tag tag-green" style="font-size:10px">👤 营养师</span>';
|
||||
return `<div class="wb-adv ${ad.isAi ? 'ai' : ''}">
|
||||
<div class="wb-adv__head"><div class="wb-adv__author">${ad.author} ${tag}</div><span class="wb-adv__time">${ad.time}</span></div>
|
||||
<div class="wb-adv__content">${ad.content}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
const advHtml = `<textarea class="wb-adv-input" placeholder="输入新的营养建议..."></textarea>
|
||||
<div class="wb-adv-btns"><button class="btn btn-primary" style="height:28px;font-size:12px;padding:0 10px;" onclick="alert('已保存建议并推送(原型演示)')">💾 保存并推送</button><button class="btn btn-default" style="height:28px;font-size:12px;padding:0 10px;" onclick="alert('AI 已生成建议草稿(原型演示)')">🤖 AI 生成草稿</button></div>
|
||||
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:8px">历史建议(${a.advices.length})</div>${advList}`;
|
||||
|
||||
/* ④ 跟进记录 */
|
||||
const followList = a.followUps.length
|
||||
? `<div class="wb-timeline">${a.followUps.map(f => `<div class="wb-tl-item">
|
||||
<div class="wb-tl-dot"></div>
|
||||
<div class="wb-tl-card">
|
||||
<div class="wb-tl-card__head"><span class="wb-tl-card__topic">${f.topic}</span><span class="wb-tl-card__time">${f.time}</span></div>
|
||||
<div class="wb-tl-card__who">👤 ${f.who}</div>
|
||||
<div class="wb-tl-card__body">${f.content}</div>
|
||||
<div class="wb-tl-card__fb">用户反馈:<span class="tag tag-green" style="font-size:11px">${f.feedback}</span></div>
|
||||
</div>
|
||||
</div>`).join('')}</div>`
|
||||
: '<div class="wb-empty">暂无跟进记录</div>';
|
||||
const followHtml = `<div class="wb-panel-bar"><div class="wb-panel-bar__title">跟进时间轴(${a.followUps.length})</div><button class="btn btn-primary" style="height:28px;font-size:12px;padding:0 10px;" onclick="alert('已添加跟进记录(原型演示)')">+ 新建跟进</button></div>${followList}`;
|
||||
|
||||
/* 右侧面板 */
|
||||
const right = `<div class="wb-right">
|
||||
<div class="wb-tabs">
|
||||
<button class="wb-tab active" onclick="switchWbTab(this,0)">🚨 疾病预警</button>
|
||||
<button class="wb-tab" onclick="switchWbTab(this,1)">📋 营养规划</button>
|
||||
<button class="wb-tab" onclick="switchWbTab(this,2)">💡 营养建议</button>
|
||||
<button class="wb-tab" onclick="switchWbTab(this,3)">📊 跟进记录</button>
|
||||
</div>
|
||||
<div class="wb-tab-body">
|
||||
<div class="wb-panel active">${warnHtml}</div>
|
||||
<div class="wb-panel">${planHtml}</div>
|
||||
<div class="wb-panel">${advHtml}</div>
|
||||
<div class="wb-panel">${followHtml}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
return `<div class="wb-layout">${left}${right}</div>`;
|
||||
}
|
||||
|
||||
function switchWbTab(el, idx) {
|
||||
el.parentElement.querySelectorAll('.wb-tab').forEach((t, i) => t.classList.toggle('active', i === idx));
|
||||
el.closest('.wb-right').querySelectorAll('.wb-panel').forEach((p, i) => p.classList.toggle('active', i === idx));
|
||||
}
|
||||
|
||||
function showWorkbench() {
|
||||
const footer = `<button class="btn btn-default" onclick="Modal.close()">取消</button><button class="btn btn-primary" onclick="Modal.close()">确定</button>`;
|
||||
Modal.open('🍽️ 营养师工作台 — ' + wbData.user, buildWorkbenchHTML(), 'wb-modal', footer);
|
||||
}
|
||||
|
||||
/* ========== 四个 Tab 列表事件绑定 ========== */
|
||||
document.querySelectorAll('.tab-panel').forEach((panel, tabIdx) => {
|
||||
panel.querySelectorAll('tbody tr').forEach(row => {
|
||||
const links = row.querySelectorAll('.td-actions .link');
|
||||
const nameCell = row.querySelector('td:first-child');
|
||||
if (!nameCell) return;
|
||||
const rawName = nameCell.textContent.trim();
|
||||
const userName = rawName.replace(/\(.*\)/, '').trim();
|
||||
links.forEach(link => {
|
||||
if (link.textContent.includes('查看用户信息')) {
|
||||
link.addEventListener('click', () => showUserProfile(userName));
|
||||
}
|
||||
/* 前三个 Tab(餐次/按日/按周)的营养明细按钮 */
|
||||
if (tabIdx < 3 && link.textContent.includes('营养明细')) {
|
||||
link.addEventListener('click', () => showNutriDetail());
|
||||
}
|
||||
/* 前三个 Tab(餐次/按日/按周)的餐品明细按钮 */
|
||||
if (tabIdx < 3 && link.textContent.includes('餐品明细')) {
|
||||
link.addEventListener('click', () => showDishDetail());
|
||||
}
|
||||
/* 第四个 Tab(营养档案)的营养管理按钮 */
|
||||
if (tabIdx === 3 && link.textContent.includes('营养管理')) {
|
||||
link.addEventListener('click', () => showWorkbench());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -115,22 +115,22 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</header>
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-menu__title">营养用餐</div>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item">字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item">营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item">食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item">餐品总库</a>
|
||||
<div class="sidebar-subgroup"><div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>算法标准</div><div class="sidebar-subgroup__body"><a href="din-algo-population.html" class="sidebar-item">人群基础标准</a><a href="din-algo-vip.html" class="sidebar-item">VIP定制标准</a></div></div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item">食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item">营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item">用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item">餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item">取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item">消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item">营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item">餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item">终端管理</a>
|
||||
<div class="sidebar-subgroup"><div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>模型管理</div><div class="sidebar-subgroup__body"><a href="din-model-llm.html" class="sidebar-item">大模型管理</a><a href="din-model-small.html" class="sidebar-item">小模型管理</a></div></div>
|
||||
<a href="din-innovation.html" class="sidebar-item active">味养创新</a>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item"><span class="mi">📖</span>字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item"><span class="mi">🔬</span>营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item"><span class="mi">🥦</span>食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item"><span class="mi">🍲</span>餐品总库</a>
|
||||
<div class="sidebar-subgroup"><div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>📐 算法标准</div><div class="sidebar-subgroup__body"><a href="din-algo-population.html" class="sidebar-item"><span class="mi">👥</span>人群基础标准</a><a href="din-algo-vip.html" class="sidebar-item"><span class="mi">💎</span>VIP定制标准</a></div></div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item"><span class="mi">📋</span>食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item"><span class="mi">🛒</span>营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item"><span class="mi">🧑</span>用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item"><span class="mi">⚖️</span>餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item"><span class="mi">🍽️</span>取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item"><span class="mi">💰</span>消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item"><span class="mi">📊</span>营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item"><span class="mi">📈</span>餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item"><span class="mi">🖥️</span>终端管理</a>
|
||||
<div class="sidebar-subgroup"><div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>🤖 模型管理</div><div class="sidebar-subgroup__body"><a href="din-model-llm.html" class="sidebar-item"><span class="mi">🧠</span>大模型管理</a><a href="din-model-small.html" class="sidebar-item"><span class="mi">⚙️</span>小模型管理</a></div></div>
|
||||
<a href="din-innovation.html" class="sidebar-item active"><span class="mi">🌟</span>味养创新</a>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="stats-row">
|
||||
|
||||
@@ -32,7 +32,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -74,37 +73,31 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.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);}
|
||||
.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:680px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
|
||||
.modal-title{font-size:16px;font-weight:600;color:#262626;}
|
||||
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}
|
||||
.modal-close:hover{color:#262626;}
|
||||
.modal-body{padding:24px;}
|
||||
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
|
||||
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
|
||||
.form-item{display:flex;flex-direction:column;gap:4px;}
|
||||
.form-item.full{grid-column:1/-1;}
|
||||
.form-item label{font-size:13px;color:#8c8c8c;}
|
||||
.form-item label .required{color:#ff4d4f;margin-left:2px;}
|
||||
.form-item input,.form-item select,.form-item textarea{height:36px;padding:0 12px;border:1px solid #e8e8e8;border-radius:4px;font-size:13px;outline:none;}
|
||||
.form-item input:focus,.form-item select:focus,.form-item textarea:focus{border-color:#1890FF;}
|
||||
.form-item textarea{height:80px;padding:8px 12px;resize:vertical;}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
|
||||
.stat-card{background:#fff;border-radius:var(--radius-md);padding:20px 24px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;}
|
||||
.stat-card__icon{width:48px;height:48px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:24px;}
|
||||
.stat-card__icon.blue{background:var(--primary-light);}
|
||||
.stat-card__icon.green{background:#F6FFED;}
|
||||
.stat-card__icon.orange{background:#FFF7E6;}
|
||||
.stat-card__icon.red{background:#FFF1F0;}
|
||||
.stat-card__info{display:flex;flex-direction:column;gap:4px;}
|
||||
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
|
||||
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
/* 动态弹窗 */
|
||||
.dyn-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s;}
|
||||
.dyn-overlay.show{opacity:1;}
|
||||
.dyn-modal{background:#fff;border-radius:var(--radius-lg);max-height:80vh;display:flex;flex-direction:column;transform:translateY(30px);transition:transform .25s;box-shadow:0 12px 40px rgba(0,0,0,0.18);}
|
||||
.dyn-overlay.show .dyn-modal{transform:translateY(0);}
|
||||
.dyn-modal__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--border);}
|
||||
.dyn-modal__header h3{font-size:var(--font-lg);font-weight:600;margin:0;}
|
||||
.dyn-modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;font-size:20px;color:#999;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);}
|
||||
.dyn-modal__close:hover{background:#f5f5f5;color:#333;}
|
||||
.dyn-modal__body{padding:20px 24px;overflow-y:auto;flex:1;}
|
||||
.dyn-modal__footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border);}
|
||||
/* 调试日志弹窗 */
|
||||
.log-modal{width:820px;}
|
||||
.log-dev-info{display:flex;gap:24px;margin-bottom:16px;font-size:var(--font-sm);color:var(--text-secondary);flex-wrap:wrap;}
|
||||
.log-dev-info strong{color:var(--text-primary);margin-left:4px;}
|
||||
.log-table{width:100%;border-collapse:collapse;}
|
||||
.log-table thead th{background:#FAFAFA;padding:10px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.log-table tbody td{padding:12px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
|
||||
.log-table tbody tr:last-child td{border-bottom:none;}
|
||||
.log-table tbody tr:hover{background:#F8FBFF;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -133,52 +126,44 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-menu__title">营养用餐</div>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item">字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item">营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item">食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item">餐品总库</a>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item"><span class="mi">📖</span>字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item"><span class="mi">🔬</span>营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item"><span class="mi">🥦</span>食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item"><span class="mi">🍲</span>餐品总库</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
算法标准
|
||||
📐 算法标准
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-algo-population.html" class="sidebar-item">人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item">VIP定制标准</a>
|
||||
<a href="din-algo-population.html" class="sidebar-item"><span class="mi">👥</span>人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item"><span class="mi">💎</span>VIP定制标准</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item">食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item">营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item">用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item">餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item">取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item">消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item">营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item">餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item">终端管理</a>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item"><span class="mi">📋</span>食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item"><span class="mi">🛒</span>营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item"><span class="mi">🧑</span>用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item"><span class="mi">⚖️</span>餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item"><span class="mi">🍽️</span>取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item"><span class="mi">💰</span>消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item"><span class="mi">📊</span>营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item"><span class="mi">📈</span>餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item"><span class="mi">🖥️</span>终端管理</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
模型管理
|
||||
🤖 模型管理
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-model-llm.html" class="sidebar-item active">大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item">小模型管理</a>
|
||||
<a href="din-model-llm.html" class="sidebar-item active"><span class="mi">🧠</span>大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item"><span class="mi">⚙️</span>小模型管理</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-innovation.html" class="sidebar-item">味养创新</a>
|
||||
<a href="din-innovation.html" class="sidebar-item"><span class="mi">🌟</span>味养创新</a>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="main">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card"><div class="stat-card__icon blue">🧠</div><div class="stat-card__info"><div class="stat-card__value">3</div><div class="stat-card__label">接入大模型数</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon green">✅</div><div class="stat-card__info"><div class="stat-card__value">2</div><div class="stat-card__label">在用</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon orange">📊</div><div class="stat-card__info"><div class="stat-card__value">12.4万次</div><div class="stat-card__label">本月调用量</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon blue">💰</div><div class="stat-card__info"><div class="stat-card__value">¥3,860</div><div class="stat-card__label">本月费用</div></div></div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item"><label>模型名称</label><input class="filter-input" type="text" placeholder="请输入模型名称"></div>
|
||||
@@ -190,7 +175,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary" onclick="openModal()">➕ 接入新模型</button>
|
||||
<button class="btn btn-primary">➕ 接入新模型</button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
@@ -202,9 +187,9 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<table>
|
||||
<thead><tr><th>模型编号</th><th>模型名称</th><th>提供商</th><th>版本</th><th>接入方式</th><th>使用场景</th><th>月调用量(次)</th><th>月消耗Token</th><th>月费用</th><th>平均响应(ms)</th><th>错误率</th><th>超时率</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>LLM-001</td><td>豆包</td><td>字节跳动</td><td>Doubao-pro-32k</td><td>API调用</td><td>小程序AI营养顾问</td><td>68,240</td><td>42.6万</td><td>¥2,140</td><td>320</td><td>0.12%</td><td>0.08%</td><td><span class="tag tag-green">在用</span></td><td class="td-actions"><a class="link">调用日志</a></td></tr>
|
||||
<tr><td>LLM-002</td><td>DeepSeek-V3</td><td>深度求索</td><td>v3-20250324</td><td>API调用</td><td>管理端智能助手</td><td>54,820</td><td>38.2万</td><td>¥1,720</td><td>280</td><td>0.08%</td><td>0.05%</td><td><span class="tag tag-green">在用</span></td><td class="td-actions"><a class="link">调用日志</a></td></tr>
|
||||
<tr><td>LLM-003</td><td>文心一言</td><td>百度</td><td>ERNIE-4.0</td><td>API调用</td><td>小程序AI营养顾问</td><td>0</td><td>0</td><td>¥0</td><td>—</td><td>—</td><td>—</td><td><span class="tag tag-orange">备用</span></td><td class="td-actions"><a class="link">调用日志</a></td></tr>
|
||||
<tr><td>LLM-001</td><td>豆包</td><td>字节跳动</td><td>Doubao-pro-32k</td><td>API调用</td><td>小程序AI营养顾问</td><td>68,240</td><td>42.6万</td><td>¥2,140</td><td>320</td><td>0.12%</td><td>0.08%</td><td><span class="tag tag-green">在用</span></td><td class="td-actions"><a class="link">调试日志</a></td></tr>
|
||||
<tr><td>LLM-002</td><td>DeepSeek-V3</td><td>深度求索</td><td>v3-20250324</td><td>API调用</td><td>管理端智能助手</td><td>54,820</td><td>38.2万</td><td>¥1,720</td><td>280</td><td>0.08%</td><td>0.05%</td><td><span class="tag tag-green">在用</span></td><td class="td-actions"><a class="link">调试日志</a></td></tr>
|
||||
<tr><td>LLM-003</td><td>文心一言</td><td>百度</td><td>ERNIE-4.0</td><td>API调用</td><td>小程序AI营养顾问</td><td>0</td><td>0</td><td>¥0</td><td>—</td><td>—</td><td>—</td><td><span class="tag tag-orange">备用</span></td><td class="td-actions"><a class="link">调试日志</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -212,36 +197,103 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 接入新模型弹窗 -->
|
||||
<div class="modal-overlay" id="modalOverlay" style="display:none;">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">接入新模型</span>
|
||||
<span class="modal-close" onclick="closeModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>模型名称<span class="required">*</span></label><input type="text" placeholder="请输入模型名称"></div>
|
||||
<div class="form-item"><label>模型提供商<span class="required">*</span></label><input type="text" placeholder="请输入模型提供商"></div>
|
||||
<div class="form-item"><label>模型版本<span class="required">*</span></label><input type="text" placeholder="请输入模型版本"></div>
|
||||
<div class="form-item"><label>接入方式</label><select><option>请选择</option><option>API调用</option><option>私有化部署</option></select></div>
|
||||
<div class="form-item"><label>使用场景<span class="required">*</span></label><input type="text" placeholder="请输入使用场景"></div>
|
||||
<div class="form-item full"><label>API Key / 接入配置</label><textarea placeholder="请输入API Key或接入配置信息"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openModal(){document.getElementById('modalOverlay').style.display='flex';}
|
||||
function closeModal(){document.getElementById('modalOverlay').style.display='none';}
|
||||
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
|
||||
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');})();
|
||||
|
||||
/* ===== 动态弹窗工具 ===== */
|
||||
const DynModal={
|
||||
open(title,bodyHtml,footerHtml,extraClass){
|
||||
const overlay=document.createElement('div');
|
||||
overlay.className='dyn-overlay';
|
||||
overlay.innerHTML=`<div class="dyn-modal ${extraClass||''}">
|
||||
<div class="dyn-modal__header"><h3>${title}</h3><button class="dyn-modal__close" onclick="DynModal.close()">×</button></div>
|
||||
<div class="dyn-modal__body">${bodyHtml}</div>
|
||||
<div class="dyn-modal__footer">${footerHtml}</div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
overlay.addEventListener('click',e=>{if(e.target===overlay)DynModal.close();});
|
||||
requestAnimationFrame(()=>overlay.classList.add('show'));
|
||||
},
|
||||
close(){
|
||||
const o=document.querySelector('.dyn-overlay');
|
||||
if(!o)return;
|
||||
o.classList.remove('show');
|
||||
setTimeout(()=>o.remove(),250);
|
||||
}
|
||||
};
|
||||
|
||||
/* ===== 调试日志数据(模拟) ===== */
|
||||
const debugLogData={
|
||||
'LLM-001':[
|
||||
{time:'2026-04-22 11:28:15',scene:'营养方案推荐',token:'1,248',latency:'312ms',status:'成功'},
|
||||
{time:'2026-04-22 11:25:03',scene:'饮食分析',token:'986',latency:'285ms',status:'成功'},
|
||||
{time:'2026-04-22 11:18:40',scene:'健康问答',token:'1,520',latency:'398ms',status:'成功'},
|
||||
{time:'2026-04-22 10:55:12',scene:'营养方案推荐',token:'2,104',latency:'520ms',status:'超时'},
|
||||
{time:'2026-04-22 10:42:08',scene:'食谱生成',token:'1,860',latency:'445ms',status:'成功'},
|
||||
{time:'2026-04-22 10:30:55',scene:'饮食分析',token:'768',latency:'210ms',status:'成功'},
|
||||
{time:'2026-04-22 09:58:33',scene:'健康问答',token:'1,102',latency:'295ms',status:'成功'},
|
||||
{time:'2026-04-22 09:45:18',scene:'营养方案推荐',token:'1,450',latency:'340ms',status:'成功'}
|
||||
],
|
||||
'LLM-002':[
|
||||
{time:'2026-04-22 11:30:02',scene:'智能报告生成',token:'3,240',latency:'680ms',status:'成功'},
|
||||
{time:'2026-04-22 11:22:45',scene:'数据分析摘要',token:'2,180',latency:'425ms',status:'成功'},
|
||||
{time:'2026-04-22 11:10:18',scene:'异常预警分析',token:'1,560',latency:'310ms',status:'成功'},
|
||||
{time:'2026-04-22 10:48:30',scene:'智能报告生成',token:'2,890',latency:'590ms',status:'成功'},
|
||||
{time:'2026-04-22 10:35:12',scene:'数据分析摘要',token:'1,780',latency:'350ms',status:'成功'},
|
||||
{time:'2026-04-22 10:20:05',scene:'异常预警分析',token:'0',latency:'—',status:'失败'},
|
||||
{time:'2026-04-22 09:55:40',scene:'智能报告生成',token:'2,650',latency:'510ms',status:'成功'}
|
||||
],
|
||||
'LLM-003':[]
|
||||
};
|
||||
|
||||
/* ===== 调试日志弹窗 ===== */
|
||||
function showDebugLog(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const modelCode=cells[0].textContent.trim();
|
||||
const modelName=cells[1].textContent.trim();
|
||||
const provider=cells[2].textContent.trim();
|
||||
const version=cells[3].textContent.trim();
|
||||
const records=debugLogData[modelCode]||[];
|
||||
|
||||
const statusTag=s=>s==='成功'?'tag-green':s==='超时'?'tag-orange':'tag-red';
|
||||
let tableHtml='';
|
||||
if(records.length===0){
|
||||
tableHtml='<div style="text-align:center;padding:40px 0;color:var(--text-placeholder);font-size:var(--font-sm);">暂无调试日志</div>';
|
||||
}else{
|
||||
const rows=records.map(r=>`<tr>
|
||||
<td>${r.time}</td>
|
||||
<td>${r.scene}</td>
|
||||
<td>${r.token}</td>
|
||||
<td>${r.latency}</td>
|
||||
<td><span class="tag ${statusTag(r.status)}">${r.status}</span></td>
|
||||
</tr>`).join('');
|
||||
tableHtml=`<table class="log-table">
|
||||
<thead><tr><th>时间</th><th>场景</th><th>Token</th><th>延迟</th><th>状态</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
const html=`
|
||||
<div class="log-dev-info">
|
||||
<span>模型编号:<strong>${modelCode}</strong></span>
|
||||
<span>模型名称:<strong>${modelName}</strong></span>
|
||||
<span>提供商:<strong>${provider}</strong></span>
|
||||
<span>版本:<strong>${version}</strong></span>
|
||||
</div>
|
||||
${tableHtml}`;
|
||||
|
||||
DynModal.open('调试日志 — '+modelName,html,'<button class="btn btn-default" onclick="DynModal.close()">关闭</button>','log-modal');
|
||||
}
|
||||
|
||||
/* ===== 事件绑定 ===== */
|
||||
document.querySelectorAll('tbody tr').forEach(row=>{
|
||||
row.querySelectorAll('.td-actions .link').forEach(link=>{
|
||||
if(link.textContent.includes('调试日志')){
|
||||
link.addEventListener('click',()=>showDebugLog(row));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -32,7 +32,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -74,37 +73,52 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.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);}
|
||||
.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:680px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
|
||||
.modal-title{font-size:16px;font-weight:600;color:#262626;}
|
||||
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}
|
||||
.modal-close:hover{color:#262626;}
|
||||
.modal-body{padding:24px;}
|
||||
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
|
||||
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
|
||||
.form-item{display:flex;flex-direction:column;gap:4px;}
|
||||
.form-item.full{grid-column:1/-1;}
|
||||
.form-item label{font-size:13px;color:#8c8c8c;}
|
||||
.form-item label .required{color:#ff4d4f;margin-left:2px;}
|
||||
.form-item input,.form-item select,.form-item textarea{height:36px;padding:0 12px;border:1px solid #e8e8e8;border-radius:4px;font-size:13px;outline:none;}
|
||||
.form-item input:focus,.form-item select:focus,.form-item textarea:focus{border-color:#1890FF;}
|
||||
.form-item textarea{height:80px;padding:8px 12px;resize:vertical;}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
|
||||
.stat-card{background:#fff;border-radius:var(--radius-md);padding:20px 24px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;}
|
||||
.stat-card__icon{width:48px;height:48px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:24px;}
|
||||
.stat-card__icon.blue{background:var(--primary-light);}
|
||||
.stat-card__icon.green{background:#F6FFED;}
|
||||
.stat-card__icon.orange{background:#FFF7E6;}
|
||||
.stat-card__icon.red{background:#FFF1F0;}
|
||||
.stat-card__info{display:flex;flex-direction:column;gap:4px;}
|
||||
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
|
||||
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
/* 动态弹窗 */
|
||||
.dyn-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s;}
|
||||
.dyn-overlay.show{opacity:1;}
|
||||
.dyn-modal{background:#fff;border-radius:var(--radius-lg);max-height:80vh;display:flex;flex-direction:column;transform:translateY(30px);transition:transform .25s;box-shadow:0 12px 40px rgba(0,0,0,0.18);}
|
||||
.dyn-overlay.show .dyn-modal{transform:translateY(0);}
|
||||
.dyn-modal__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--border);}
|
||||
.dyn-modal__header h3{font-size:var(--font-lg);font-weight:600;margin:0;}
|
||||
.dyn-modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;font-size:20px;color:#999;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);}
|
||||
.dyn-modal__close:hover{background:#f5f5f5;color:#333;}
|
||||
.dyn-modal__body{padding:20px 24px;overflow-y:auto;flex:1;}
|
||||
.dyn-modal__footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border);}
|
||||
/* 上传版本表单 */
|
||||
.upload-modal{width:600px;}
|
||||
.upload-form{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
|
||||
.upload-form .form-item{display:flex;flex-direction:column;gap:4px;}
|
||||
.upload-form .form-item.full{grid-column:1/-1;}
|
||||
.upload-form .form-item label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.upload-form .form-item label .req{color:var(--danger);margin-left:2px;}
|
||||
.upload-form .form-item input,.upload-form .form-item select{height:36px;padding:0 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-sm);outline:none;}
|
||||
.upload-form .form-item input:focus,.upload-form .form-item select:focus,.upload-form .form-item textarea:focus{border-color:var(--primary);}
|
||||
.upload-form .form-item textarea{height:72px;padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-sm);outline:none;resize:vertical;}
|
||||
/* 版本历史 */
|
||||
.ver-modal{width:780px;}
|
||||
.ver-info{display:flex;gap:24px;margin-bottom:16px;font-size:var(--font-sm);color:var(--text-secondary);flex-wrap:wrap;}
|
||||
.ver-info strong{color:var(--text-primary);margin-left:4px;}
|
||||
.ver-table{width:100%;border-collapse:collapse;}
|
||||
.ver-table thead th{background:#FAFAFA;padding:10px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.ver-table tbody td{padding:12px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
|
||||
.ver-table tbody tr:last-child td{border-bottom:none;}
|
||||
.ver-table tbody tr:hover{background:#F8FBFF;}
|
||||
/* 在线测试 */
|
||||
.test-modal{width:560px;}
|
||||
.test-input-area{margin-bottom:16px;}
|
||||
.test-input-area label{display:block;font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:6px;}
|
||||
.test-input-area textarea{width:100%;height:80px;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-sm);outline:none;resize:vertical;}
|
||||
.test-input-area textarea:focus{border-color:var(--primary);}
|
||||
.test-run-bar{display:flex;justify-content:flex-end;margin-bottom:16px;}
|
||||
.test-result{background:#F6FFED;border:1px solid #B7EB8F;border-radius:var(--radius-md);padding:16px 20px;}
|
||||
.test-result__title{font-size:var(--font-sm);color:var(--success);font-weight:600;margin-bottom:8px;}
|
||||
.test-result__content{font-size:var(--font-base);color:var(--text-primary);line-height:1.6;}
|
||||
.test-result__score{font-size:var(--font-lg);font-weight:700;color:var(--success);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -133,52 +147,44 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-menu__title">营养用餐</div>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item">字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item">营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item">食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item">餐品总库</a>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item"><span class="mi">📖</span>字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item"><span class="mi">🔬</span>营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item"><span class="mi">🥦</span>食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item"><span class="mi">🍲</span>餐品总库</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
算法标准
|
||||
📐 算法标准
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-algo-population.html" class="sidebar-item">人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item">VIP定制标准</a>
|
||||
<a href="din-algo-population.html" class="sidebar-item"><span class="mi">👥</span>人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item"><span class="mi">💎</span>VIP定制标准</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item">食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item">营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item">用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item">餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item">取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item">消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item">营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item">餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item">终端管理</a>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item"><span class="mi">📋</span>食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item"><span class="mi">🛒</span>营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item"><span class="mi">🧑</span>用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item"><span class="mi">⚖️</span>餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item"><span class="mi">🍽️</span>取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item"><span class="mi">💰</span>消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item"><span class="mi">📊</span>营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item"><span class="mi">📈</span>餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item"><span class="mi">🖥️</span>终端管理</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
模型管理
|
||||
🤖 模型管理
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-model-llm.html" class="sidebar-item">大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item active">小模型管理</a>
|
||||
<a href="din-model-llm.html" class="sidebar-item"><span class="mi">🧠</span>大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item active"><span class="mi">⚙️</span>小模型管理</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-innovation.html" class="sidebar-item">味养创新</a>
|
||||
<a href="din-innovation.html" class="sidebar-item"><span class="mi">🌟</span>味养创新</a>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="main">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card"><div class="stat-card__icon blue">🔬</div><div class="stat-card__info"><div class="stat-card__value">6</div><div class="stat-card__label">小模型总数</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon green">✅</div><div class="stat-card__info"><div class="stat-card__value">5</div><div class="stat-card__label">在线</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon orange">⏸️</div><div class="stat-card__info"><div class="stat-card__value">1</div><div class="stat-card__label">离线</div></div></div>
|
||||
<div class="stat-card"><div class="stat-card__icon green">🎯</div><div class="stat-card__info"><div class="stat-card__value">91.2%</div><div class="stat-card__label">平均准确率</div></div></div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item"><label>模型名称</label><input class="filter-input" type="text" placeholder="请输入模型名称"></div>
|
||||
@@ -190,7 +196,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary" onclick="openModal()">➕ 新增模型</button>
|
||||
<button class="btn btn-primary">➕ 新增模型</button>
|
||||
<button class="btn btn-default">📊 评估报告</button>
|
||||
</div>
|
||||
|
||||
@@ -216,36 +222,155 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增模型弹窗 -->
|
||||
<div class="modal-overlay" id="modalOverlay" style="display:none;">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">新增模型</span>
|
||||
<span class="modal-close" onclick="closeModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-item"><label>模型名称<span class="required">*</span></label><input type="text" placeholder="请输入模型名称"></div>
|
||||
<div class="form-item"><label>模型类型</label><select><option>请选择</option><option>营养计算</option><option>疾病预测</option><option>智能配餐</option><option>消费预测</option><option>食材损耗预测</option><option>过敏原识别</option></select></div>
|
||||
<div class="form-item"><label>版本号<span class="required">*</span></label><input type="text" placeholder="如 v2.4.0"></div>
|
||||
<div class="form-item"><label>版本文件<span class="required">*</span></label><input type="file"></div>
|
||||
<div class="form-item full"><label>训练数据集说明</label><textarea placeholder="请输入训练数据集说明"></textarea></div>
|
||||
<div class="form-item full"><label>变更说明</label><textarea placeholder="请输入变更说明"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openModal(){document.getElementById('modalOverlay').style.display='flex';}
|
||||
function closeModal(){document.getElementById('modalOverlay').style.display='none';}
|
||||
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
|
||||
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');})();
|
||||
|
||||
/* ===== 动态弹窗工具 ===== */
|
||||
const DynModal={
|
||||
open(title,bodyHtml,footerHtml,extraClass){
|
||||
const overlay=document.createElement('div');
|
||||
overlay.className='dyn-overlay';
|
||||
overlay.innerHTML=`<div class="dyn-modal ${extraClass||''}">
|
||||
<div class="dyn-modal__header"><h3>${title}</h3><button class="dyn-modal__close" onclick="DynModal.close()">×</button></div>
|
||||
<div class="dyn-modal__body">${bodyHtml}</div>
|
||||
<div class="dyn-modal__footer">${footerHtml}</div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
overlay.addEventListener('click',e=>{if(e.target===overlay)DynModal.close();});
|
||||
requestAnimationFrame(()=>overlay.classList.add('show'));
|
||||
},
|
||||
close(){
|
||||
const o=document.querySelector('.dyn-overlay');
|
||||
if(!o)return;
|
||||
o.classList.remove('show');
|
||||
setTimeout(()=>o.remove(),250);
|
||||
}
|
||||
};
|
||||
|
||||
/* ===== 上传版本弹窗 ===== */
|
||||
function showUploadVersion(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const modelName=cells[1].textContent.trim();
|
||||
const modelType=cells[2].textContent.trim();
|
||||
|
||||
const html=`<div class="upload-form">
|
||||
<div class="form-item"><label>模型名称<span class="req">*</span></label><input type="text" value="${modelName}" readonly style="background:#f5f5f5;"></div>
|
||||
<div class="form-item"><label>模型类型</label><input type="text" value="${modelType}" readonly style="background:#f5f5f5;"></div>
|
||||
<div class="form-item"><label>版本号<span class="req">*</span></label><input type="text" placeholder="如 v2.4.0"></div>
|
||||
<div class="form-item"><label>版本文件<span class="req">*</span></label><input type="file"></div>
|
||||
<div class="form-item full"><label>训练数据集说明</label><textarea placeholder="请输入训练数据集说明"></textarea></div>
|
||||
<div class="form-item full"><label>变更说明</label><textarea placeholder="请输入本次版本变更说明"></textarea></div>
|
||||
</div>`;
|
||||
|
||||
const footer=`<button class="btn btn-default" onclick="DynModal.close()">取消</button><button class="btn btn-primary" onclick="DynModal.close()">确定上传</button>`;
|
||||
DynModal.open('上传版本 — '+modelName,html,footer,'upload-modal');
|
||||
}
|
||||
|
||||
/* ===== 版本历史数据(模拟) ===== */
|
||||
const versionData={
|
||||
'SM-001':[
|
||||
{ver:'v2.3.1',file:'nutrition_score_v2.3.1.pkl',size:'128MB',date:'2026-04-01',accuracy:'93.5%',status:'当前版本',note:'优化蛋白质评分权重'},
|
||||
{ver:'v2.2.0',file:'nutrition_score_v2.2.0.pkl',size:'125MB',date:'2026-02-15',accuracy:'91.8%',status:'历史版本',note:'新增膳食纤维评分维度'},
|
||||
{ver:'v2.1.0',file:'nutrition_score_v2.1.0.pkl',size:'120MB',date:'2025-12-10',accuracy:'90.2%',status:'历史版本',note:'修复微量元素计算偏差'}
|
||||
],
|
||||
'SM-002':[
|
||||
{ver:'v1.8.0',file:'recommend_engine_v1.8.0.pkl',size:'156MB',date:'2026-03-15',accuracy:'88.7%',status:'当前版本',note:'引入协同过滤优化'},
|
||||
{ver:'v1.7.2',file:'recommend_engine_v1.7.2.pkl',size:'152MB',date:'2026-01-20',accuracy:'86.4%',status:'历史版本',note:'修复冷启动推荐问题'}
|
||||
],
|
||||
'SM-003':[
|
||||
{ver:'v1.2.1',file:'consume_predict_v1.2.1.pkl',size:'96MB',date:'2026-03-01',accuracy:'85.2%',status:'当前版本',note:'增加节假日消费因子'},
|
||||
{ver:'v1.1.0',file:'consume_predict_v1.1.0.pkl',size:'92MB',date:'2025-11-10',accuracy:'82.6%',status:'历史版本',note:'初始训练版本'}
|
||||
],
|
||||
'SM-004':[
|
||||
{ver:'v1.0.4',file:'loss_predict_v1.0.4.pkl',size:'110MB',date:'2026-02-20',accuracy:'82.6%',status:'当前版本',note:'修复季节性预测偏差'},
|
||||
{ver:'v1.0.2',file:'loss_predict_v1.0.2.pkl',size:'108MB',date:'2025-12-05',accuracy:'80.1%',status:'历史版本',note:'增加温度湿度特征'}
|
||||
],
|
||||
'SM-005':[
|
||||
{ver:'v2.0.0',file:'allergen_detect_v2.0.0.pkl',size:'88MB',date:'2026-04-10',accuracy:'96.8%',status:'当前版本',note:'升级检测算法至BERT架构'},
|
||||
{ver:'v1.5.0',file:'allergen_detect_v1.5.0.pkl',size:'76MB',date:'2026-01-15',accuracy:'94.2%',status:'历史版本',note:'新增8种过敏原类型'},
|
||||
{ver:'v1.0.0',file:'allergen_detect_v1.0.0.pkl',size:'64MB',date:'2025-08-20',accuracy:'91.5%',status:'历史版本',note:'初始发布版本'}
|
||||
],
|
||||
'SM-006':[
|
||||
{ver:'v1.1.0',file:'disease_risk_v1.1.0.pkl',size:'142MB',date:'2026-01-15',accuracy:'79.4%',status:'当前版本',note:'增加糖尿病风险评估'},
|
||||
{ver:'v1.0.0',file:'disease_risk_v1.0.0.pkl',size:'136MB',date:'2025-09-01',accuracy:'76.8%',status:'历史版本',note:'初始发布版本'}
|
||||
]
|
||||
};
|
||||
|
||||
/* ===== 版本历史弹窗 ===== */
|
||||
function showVersionHistory(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const modelCode=cells[0].textContent.trim();
|
||||
const modelName=cells[1].textContent.trim();
|
||||
const modelType=cells[2].textContent.trim();
|
||||
const records=versionData[modelCode]||[];
|
||||
|
||||
const statusTag=s=>s==='当前版本'?'tag-green':'tag-gray';
|
||||
const rows=records.map(r=>`<tr>
|
||||
<td>${r.ver}</td>
|
||||
<td>${r.file}</td>
|
||||
<td>${r.size}</td>
|
||||
<td>${r.date}</td>
|
||||
<td>${r.accuracy}</td>
|
||||
<td><span class="tag ${statusTag(r.status)}">${r.status}</span></td>
|
||||
<td>${r.note}</td>
|
||||
</tr>`).join('');
|
||||
|
||||
const html=`
|
||||
<div class="ver-info">
|
||||
<span>模型编号:<strong>${modelCode}</strong></span>
|
||||
<span>模型名称:<strong>${modelName}</strong></span>
|
||||
<span>模型类型:<strong>${modelType}</strong></span>
|
||||
<span>版本数:<strong>${records.length}</strong></span>
|
||||
</div>
|
||||
<table class="ver-table">
|
||||
<thead><tr><th>版本号</th><th>版本文件</th><th>大小</th><th>发布日期</th><th>准确率</th><th>状态</th><th>变更说明</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>`;
|
||||
|
||||
DynModal.open('版本历史 — '+modelName,html,'<button class="btn btn-default" onclick="DynModal.close()">关闭</button>','ver-modal');
|
||||
}
|
||||
|
||||
/* ===== 在线测试弹窗 ===== */
|
||||
function showOnlineTest(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const modelName=cells[1].textContent.trim();
|
||||
const modelType=cells[2].textContent.trim();
|
||||
const curVer=cells[3].textContent.trim();
|
||||
|
||||
const html=`
|
||||
<div class="ver-info" style="margin-bottom:16px;">
|
||||
<span>模型:<strong>${modelName}</strong></span>
|
||||
<span>版本:<strong>${curVer}</strong></span>
|
||||
<span>类型:<strong>${modelType}</strong></span>
|
||||
</div>
|
||||
<div class="test-input-area">
|
||||
<label>输入测试数据</label>
|
||||
<textarea placeholder="请输入测试数据,如: 性别:男, 年龄:45, 身高:175, 体重:78, 热量摄入:2200kcal, 蛋白质:85g, 碳水:280g, 脂肪:72g"></textarea>
|
||||
</div>
|
||||
<div class="test-run-bar"><button class="btn btn-primary">运行测试</button></div>
|
||||
<div class="test-result">
|
||||
<div class="test-result__title">测试输出</div>
|
||||
<div class="test-result__content">预测结果:正常<br>置信度:<span class="test-result__score">93.2%</span></div>
|
||||
</div>`;
|
||||
|
||||
DynModal.open('在线测试 — '+modelName,html,'<button class="btn btn-default" onclick="DynModal.close()">关闭</button>','test-modal');
|
||||
}
|
||||
|
||||
/* ===== 事件绑定 ===== */
|
||||
document.querySelectorAll('tbody tr').forEach(row=>{
|
||||
row.querySelectorAll('.td-actions .link').forEach(link=>{
|
||||
if(link.textContent.includes('上传版本')){
|
||||
link.addEventListener('click',()=>showUploadVersion(row));
|
||||
}
|
||||
if(link.textContent.includes('版本历史')){
|
||||
link.addEventListener('click',()=>showVersionHistory(row));
|
||||
}
|
||||
if(link.textContent.includes('在线测试')){
|
||||
link.addEventListener('click',()=>showOnlineTest(row));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -35,7 +35,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -104,6 +103,26 @@ 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;}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;animation:modalFadeIn .2s ease;}
|
||||
.modal-overlay.closing{animation:modalFadeOut .2s ease forwards;}
|
||||
@keyframes modalFadeIn{from{opacity:0}to{opacity:1}}
|
||||
@keyframes modalFadeOut{from{opacity:1}to{opacity:0}}
|
||||
@keyframes modalSlideIn{from{transform:translateY(-20px);opacity:0}to{transform:translateY(0);opacity:1}}
|
||||
@keyframes modalSlideOut{from{transform:translateY(0);opacity:1}to{transform:translateY(-20px);opacity:0}}
|
||||
.modal{background:#fff;border-radius:8px;width:860px;max-width:90vw;max-height:85vh;display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.15);animation:modalSlideIn .25s ease;}
|
||||
.modal-overlay.closing .modal{animation:modalSlideOut .2s ease forwards;}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.modal-title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
|
||||
.modal-close{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:var(--radius-sm);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;}
|
||||
.modal-close:hover{background:#f5f5f5;color:var(--text-primary);}
|
||||
.modal-body{padding:24px;overflow-y:auto;flex:1;}
|
||||
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);}
|
||||
.modal-table tbody tr:last-child td{border-bottom:none;}
|
||||
.modal-table tbody tr:hover{background:#F8FBFF;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -353,6 +372,146 @@ 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);});
|
||||
}
|
||||
|
||||
/* ========== 弹窗基础 ========== */
|
||||
const Modal = {
|
||||
open(title, bodyHtml) {
|
||||
this.close();
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
overlay.innerHTML = `<div class="modal">
|
||||
<div class="modal-header"><div class="modal-title">${title}</div><button class="modal-close" onclick="Modal.close()">×</button></div>
|
||||
<div class="modal-body">${bodyHtml}</div>
|
||||
</div>`;
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) Modal.close(); });
|
||||
document.body.appendChild(overlay);
|
||||
},
|
||||
close() {
|
||||
const o = document.querySelector('.modal-overlay');
|
||||
if (!o) return;
|
||||
o.classList.add('closing');
|
||||
setTimeout(() => o.remove(), 200);
|
||||
}
|
||||
};
|
||||
|
||||
/* ========== 取餐明细弹窗 ========== */
|
||||
const pickupData = {
|
||||
'00022号秤': [
|
||||
{ time:'07:32', person:'张伟', dept:'研发部', dish:'小米粥', weight:'0.35kg', price:'¥5/份', amount:'¥5' },
|
||||
{ time:'07:35', person:'李娜', dept:'市场部', dish:'小米粥', weight:'0.28kg', price:'¥5/份', amount:'¥5' },
|
||||
{ time:'07:38', person:'王磊', dept:'运营部', dish:'小米粥', weight:'0.32kg', price:'¥5/份', amount:'¥5' },
|
||||
{ time:'07:42', person:'赵敏', dept:'财务部', dish:'小米粥', weight:'0.30kg', price:'¥5/份', amount:'¥5' },
|
||||
{ time:'07:45', person:'陈刚', dept:'人事部', dish:'小米粥', weight:'0.26kg', price:'¥5/份', amount:'¥5' },
|
||||
],
|
||||
'00023号秤': [
|
||||
{ time:'07:36', person:'刘芳', dept:'市场部', dish:'燕麦粥', weight:'0.30kg', price:'¥4/份', amount:'¥4' },
|
||||
{ time:'07:40', person:'孙磊', dept:'行政部', dish:'燕麦粥', weight:'0.28kg', price:'¥4/份', amount:'¥4' },
|
||||
{ time:'07:44', person:'钱芳', dept:'销售部', dish:'燕麦粥', weight:'0.25kg', price:'¥4/份', amount:'¥4' },
|
||||
],
|
||||
'00025号秤': [
|
||||
{ time:'11:35', person:'张伟', dept:'研发部', dish:'宫保鸡丁', weight:'0.18kg', price:'¥14/份', amount:'¥14' },
|
||||
{ time:'11:38', person:'李娜', dept:'市场部', dish:'宫保鸡丁', weight:'0.15kg', price:'¥14/份', amount:'¥14' },
|
||||
{ time:'11:42', person:'赵敏', dept:'财务部', dish:'宫保鸡丁', weight:'0.20kg', price:'¥14/份', amount:'¥14' },
|
||||
{ time:'11:45', person:'陈刚', dept:'人事部', dish:'宫保鸡丁', weight:'0.16kg', price:'¥14/份', amount:'¥14' },
|
||||
],
|
||||
'00026号秤': [
|
||||
{ time:'11:36', person:'王磊', dept:'运营部', dish:'红烧肉', weight:'0.22kg', price:'¥16/份', amount:'¥16' },
|
||||
{ time:'11:40', person:'刘芳', dept:'市场部', dish:'红烧肉', weight:'0.18kg', price:'¥16/份', amount:'¥16' },
|
||||
{ time:'11:44', person:'孙磊', dept:'行政部', dish:'红烧肉', weight:'0.20kg', price:'¥16/份', amount:'¥16' },
|
||||
],
|
||||
'00027号秤': [
|
||||
{ time:'12:15', person:'钱芳', dept:'销售部', dish:'番茄炒蛋', weight:'0.16kg', price:'¥8/份', amount:'¥8' },
|
||||
{ time:'12:20', person:'张伟', dept:'研发部', dish:'番茄炒蛋', weight:'0.14kg', price:'¥8/份', amount:'¥8' },
|
||||
]
|
||||
};
|
||||
|
||||
function showPickupDetail(scaleId) {
|
||||
const rows = pickupData[scaleId] || [];
|
||||
const totalWeight = rows.reduce((s, r) => s + parseFloat(r.weight), 0).toFixed(2);
|
||||
const totalAmount = rows.reduce((s, r) => s + parseFloat(r.amount.replace('¥','')), 0);
|
||||
const totalCount = rows.length;
|
||||
const summaryHtml = `<div style="display:flex;gap:24px;padding:12px 16px;background:#FAFAFA;border-radius:4px;margin-bottom:16px;font-size:13px;">
|
||||
<span style="color:#666;">取餐人次:<b style="color:#333;">${totalCount}人</b></span>
|
||||
<span style="color:#666;">累计重量:<b style="color:#333;">${totalWeight}kg</b></span>
|
||||
<span style="color:#666;">累计金额:<b style="color:#333;">¥${totalAmount}</b></span>
|
||||
</div>`;
|
||||
const pageSize = 5;
|
||||
const totalPages = Math.ceil(rows.length / pageSize) || 1;
|
||||
const buildPage = (page) => {
|
||||
const start = (page - 1) * pageSize;
|
||||
const pageRows = rows.slice(start, start + pageSize);
|
||||
const tbody = pageRows.map(r => `<tr><td>${r.time}</td><td>${r.person}</td><td>${r.dept}</td><td>${r.dish}</td><td>${r.weight}</td><td>${r.price}</td><td>${r.amount}</td></tr>`).join('');
|
||||
let pageBtns = '';
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pageBtns += `<button class="page-btn${i===page?' active':''}" onclick="pickupGoPage(${i})">${i}</button>`;
|
||||
}
|
||||
return `${summaryHtml}<table class="modal-table">
|
||||
<thead><tr><th>时间</th><th>取餐人</th><th>部门</th><th>菜品</th><th>重量</th><th>单价</th><th>金额</th></tr></thead>
|
||||
<tbody>${tbody}</tbody>
|
||||
</table>
|
||||
<div style="display:flex;align-items:center;justify-content:flex-end;padding:12px 0;gap:8px;">
|
||||
<span style="font-size:13px;color:#666;">显示 ${pageRows.length} 条,共 ${rows.length} 条</span>
|
||||
${pageBtns}
|
||||
</div>`;
|
||||
};
|
||||
window._pickupScaleId = scaleId;
|
||||
window.pickupGoPage = function(page) {
|
||||
const body = document.querySelector('.modal-overlay .modal-body');
|
||||
if (body) body.innerHTML = buildPage(page);
|
||||
};
|
||||
Modal.open('取餐明细 — ' + scaleId, buildPage(1));
|
||||
}
|
||||
|
||||
/* ========== 上餐明细弹窗 ========== */
|
||||
const servingData = {
|
||||
'00022号秤': [
|
||||
{ time:'06:55', batch:'SC-0428-001', type:'首次上餐', dish:'小米粥', rawWeight:'8.0kg', servWeight:'7.2kg', chef:'王师傅' },
|
||||
{ time:'08:10', batch:'SC-0428-002', type:'补餐', dish:'小米粥', rawWeight:'6.2kg', servWeight:'5.4kg', chef:'王师傅' },
|
||||
],
|
||||
'00023号秤': [
|
||||
{ time:'07:00', batch:'SC-0428-003', type:'首次上餐', dish:'燕麦粥', rawWeight:'5.8kg', servWeight:'5.2kg', chef:'李师傅' },
|
||||
{ time:'08:20', batch:'SC-0428-004', type:'补餐', dish:'燕麦粥', rawWeight:'3.8kg', servWeight:'3.2kg', chef:'李师傅' },
|
||||
],
|
||||
'00025号秤': [
|
||||
{ time:'11:30', batch:'SC-0428-005', type:'首次上餐', dish:'宫保鸡丁', rawWeight:'26.0kg', servWeight:'22.8kg', chef:'张师傅' },
|
||||
{ time:'12:15', batch:'SC-0428-006', type:'补餐', dish:'宫保鸡丁', rawWeight:'18.0kg', servWeight:'15.4kg', chef:'张师傅' },
|
||||
],
|
||||
'00026号秤': [
|
||||
{ time:'11:38', batch:'SC-0428-007', type:'首次上餐', dish:'红烧肉', rawWeight:'28.5kg', servWeight:'24.6kg', chef:'赵师傅' },
|
||||
{ time:'12:20', batch:'SC-0428-008', type:'补餐', dish:'红烧肉', rawWeight:'20.0kg', servWeight:'18.0kg', chef:'赵师傅' },
|
||||
],
|
||||
'00027号秤': [
|
||||
{ time:'12:12', batch:'SC-0428-009', type:'首次上餐', dish:'番茄炒蛋', rawWeight:'12.6kg', servWeight:'11.2kg', chef:'刘师傅' },
|
||||
{ time:'12:50', batch:'SC-0428-010', type:'补餐', dish:'番茄炒蛋', rawWeight:'6.0kg', servWeight:'5.6kg', chef:'刘师傅' },
|
||||
]
|
||||
};
|
||||
|
||||
function showServingDetail(scaleId) {
|
||||
const rows = servingData[scaleId] || [];
|
||||
const tbody = rows.map(r => `<tr><td>${r.time}</td><td>${r.batch}</td><td>${r.type}</td><td>${r.dish}</td><td>${r.rawWeight}</td><td>${r.servWeight}</td><td>${r.chef}</td></tr>`).join('');
|
||||
const html = `<table class="modal-table">
|
||||
<thead><tr><th>时间</th><th>批次号</th><th>类型</th><th>菜品</th><th>生重</th><th>上餐重量</th><th>厨师</th></tr></thead>
|
||||
<tbody>${tbody}</tbody>
|
||||
</table>`;
|
||||
Modal.open('上餐明细 — ' + scaleId, html);
|
||||
}
|
||||
|
||||
/* ========== 事件绑定 ========== */
|
||||
(function bindDetailLinks() {
|
||||
const panel = document.querySelectorAll('.tab-panel')[0];
|
||||
if (!panel) return;
|
||||
panel.querySelectorAll('tbody tr').forEach(row => {
|
||||
const cells = row.querySelectorAll('td');
|
||||
if (cells.length < 15) return;
|
||||
const scaleId = cells[0].textContent.trim();
|
||||
// 累计取餐重量 — 第13列(index 12)
|
||||
const pickupLink = cells[12] && cells[12].querySelector('.link');
|
||||
if (pickupLink) pickupLink.addEventListener('click', () => showPickupDetail(scaleId));
|
||||
// 累计上餐重量 — 第15列(index 14)
|
||||
const servingLink = cells[14] && cells[14].querySelector('.link');
|
||||
if (servingLink) servingLink.addEventListener('click', () => showServingDetail(scaleId));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,7 +31,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -90,6 +89,22 @@ 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;}
|
||||
/* 弹窗样式 */
|
||||
.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);}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
|
||||
.modal-title{font-size:16px;font-weight:600;color:#262626;}
|
||||
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}.modal-close:hover{color:#262626;}
|
||||
.modal-body{padding:24px;}
|
||||
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
|
||||
.detail-info{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px 24px;margin-bottom:20px;padding:16px;background:#FAFAFA;border-radius:var(--radius-md);}
|
||||
.detail-info__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);}
|
||||
.detail-info__label{color:var(--text-secondary);}
|
||||
.detail-info__value{color:var(--text-primary);font-weight:500;}
|
||||
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);}
|
||||
.modal-table tfoot td{padding:10px 12px;font-weight:600;background:#FAFAFA;border-top:2px solid var(--border);}
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
@@ -184,7 +199,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-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><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="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>
|
||||
@@ -251,7 +266,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-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><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="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>
|
||||
@@ -276,11 +291,90 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 餐品明细弹窗(个人/净菜) -->
|
||||
<div class="modal-overlay" id="modalOverlay" style="display:none;">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">餐品明细</span>
|
||||
<span class="modal-close" onclick="closeModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="detail-info">
|
||||
<div class="detail-info__item"><span class="detail-info__label">订单号:</span><span class="detail-info__value">NM-001</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">用户:</span><span class="detail-info__value">张伟(EMP001)</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">餐次:</span><span class="detail-info__value">午餐</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">日期:</span><span class="detail-info__value">2026-04-23</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">目标热量:</span><span class="detail-info__value">2,250 kcal</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">营养匹配度:</span><span class="detail-info__value">92%</span></div>
|
||||
</div>
|
||||
<table class="modal-table">
|
||||
<thead><tr><th>餐品名称</th><th>分类</th><th>规格/份量</th><th>热量(kcal)</th><th>蛋白质(g)</th><th>脂肪(g)</th><th>碳水(g)</th><th>价格(元)</th><th>餐品溯源</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>米饭</td><td>主食</td><td>200g/份</td><td>232</td><td>4.6</td><td>0.6</td><td>51.2</td><td>2.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>清蒸鱼</td><td>荤菜</td><td>150g/份</td><td>165</td><td>28.5</td><td>5.2</td><td>0.8</td><td>12.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>炒西兰花</td><td>素菜</td><td>120g/份</td><td>68</td><td>4.2</td><td>3.8</td><td>5.6</td><td>6.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>番茄蛋花汤</td><td>汤品</td><td>250ml/份</td><td>85</td><td>5.8</td><td>3.2</td><td>8.4</td><td>4.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
</tbody>
|
||||
<tfoot><tr><td>合计</td><td>4道</td><td>—</td><td>550</td><td>43.1</td><td>12.8</td><td>66.0</td><td>24.0</td><td>—</td></tr></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 餐品明细弹窗(团体) -->
|
||||
<div class="modal-overlay" id="modalGroupOverlay" style="display:none;">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">餐品明细</span>
|
||||
<span class="modal-close" onclick="closeGroupModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="detail-info">
|
||||
<div class="detail-info__item"><span class="detail-info__label">团体名称:</span><span class="detail-info__value">研发中心午餐团</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">类型:</span><span class="detail-info__value">部门</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">人数:</span><span class="detail-info__value">86人</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">人群特征:</span><span class="detail-info__value">年轻成人为主</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">人均预算:</span><span class="detail-info__value">¥28</span></div>
|
||||
<div class="detail-info__item"><span class="detail-info__label">营养覆盖率:</span><span class="detail-info__value">88%</span></div>
|
||||
</div>
|
||||
<table class="modal-table">
|
||||
<thead><tr><th>餐品名称</th><th>分类</th><th>规格/份量</th><th>热量(kcal)</th><th>蛋白质(g)</th><th>脂肪(g)</th><th>碳水(g)</th><th>价格(元)</th><th>餐品溯源</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>杂粮饭</td><td>主食</td><td>200g/份</td><td>218</td><td>5.2</td><td>1.0</td><td>46.8</td><td>3.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>红烧排骨</td><td>荤菜</td><td>150g/份</td><td>285</td><td>22.0</td><td>18.5</td><td>6.2</td><td>15.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>清炒时蔬</td><td>素菜</td><td>120g/份</td><td>52</td><td>2.8</td><td>2.6</td><td>4.8</td><td>5.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>紫菜蛋花汤</td><td>汤品</td><td>250ml/份</td><td>62</td><td>4.5</td><td>2.8</td><td>5.2</td><td>3.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
<tr><td>凉拌黄瓜</td><td>凉菜</td><td>100g/份</td><td>35</td><td>1.2</td><td>1.5</td><td>3.8</td><td>4.0</td><td><span class="link">食材溯源链路</span></td></tr>
|
||||
</tbody>
|
||||
<tfoot><tr><td>合计</td><td>5道</td><td>—</td><td>652</td><td>35.7</td><td>26.4</td><td>66.8</td><td>30.0</td><td>—</td></tr></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeGroupModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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));
|
||||
}
|
||||
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';}
|
||||
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
|
||||
document.getElementById('modalGroupOverlay').addEventListener('click',function(e){if(e.target===this)closeGroupModal();});
|
||||
// 个人点餐(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);
|
||||
});
|
||||
});
|
||||
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>
|
||||
|
||||
@@ -35,7 +35,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
|
||||
@@ -31,7 +31,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -83,22 +82,6 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.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);}
|
||||
.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:680px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
|
||||
.modal-title{font-size:16px;font-weight:600;color:#262626;}
|
||||
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}
|
||||
.modal-close:hover{color:#262626;}
|
||||
.modal-body{padding:24px;}
|
||||
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
|
||||
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
|
||||
.form-item{display:flex;flex-direction:column;gap:4px;}
|
||||
.form-item.full{grid-column:1/-1;}
|
||||
.form-item label{font-size:13px;color:#8c8c8c;}
|
||||
.form-item label .required{color:#ff4d4f;margin-left:2px;}
|
||||
.form-item input,.form-item select,.form-item textarea{height:36px;padding:0 12px;border:1px solid #e8e8e8;border-radius:4px;font-size:13px;outline:none;}
|
||||
.form-item input:focus,.form-item select:focus,.form-item textarea:focus{border-color:#1890FF;}
|
||||
.form-item textarea{height:80px;padding:8px 12px;resize:vertical;}
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
/* 操作列固定右侧 */
|
||||
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;}
|
||||
@@ -182,7 +165,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</div>
|
||||
|
||||
<!-- 表格卡片 -->
|
||||
<div class="action-bar"><button class="btn btn-primary" onclick="openModal()">➕ 新建食谱</button> <button class="btn btn-default">📋 生成净菜订单</button> <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">➕ 新建食谱</button> <button class="btn btn-default">📋 生成净菜订单</button> <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="table-card">
|
||||
<div class="table-card__header">
|
||||
<div class="table-card__title">食谱管理</div>
|
||||
@@ -209,36 +192,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 新建食谱弹窗 -->
|
||||
<div class="modal-overlay" id="modalOverlay" style="display:none;">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">新建食谱</span>
|
||||
<span class="modal-close" onclick="closeModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-item full"><label>食谱名称<span class="required">*</span></label><input type="text" placeholder="请输入食谱名称,如:第16周营养食谱"></div>
|
||||
<div class="form-item"><label>生效周期开始</label><input type="date"></div>
|
||||
<div class="form-item"><label>生效周期结束</label><input type="date"></div>
|
||||
<div class="form-item"><label>适用食堂</label><input type="text" placeholder="如:一号食堂/二号食堂"></div>
|
||||
<div class="form-item"><label>适用终端</label><select><option>请选择</option><option>食堂</option><option>小程序</option><option>食堂+小程序</option></select></div>
|
||||
<div class="form-item"><label>发布状态</label><select><option>请选择</option><option>待发布</option><option>已发布</option><option>已停用</option></select></div>
|
||||
<div class="form-item"><label>成本评估结论</label><select><option>请选择</option><option>合理</option><option>偏高</option><option>超标</option><option>待评估</option></select></div>
|
||||
<div class="form-item full"><label>备注</label><textarea placeholder="请输入备注信息"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openModal(){document.getElementById('modalOverlay').style.display='flex';}
|
||||
function closeModal(){document.getElementById('modalOverlay').style.display='none';}
|
||||
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
|
||||
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>
|
||||
|
||||
@@ -32,7 +32,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -109,6 +108,33 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.stat-card__value{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
|
||||
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
|
||||
/* 动态弹窗基础 */
|
||||
.dyn-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1010;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s;}
|
||||
.dyn-overlay.show{opacity:1;}
|
||||
.dyn-modal{background:#fff;border-radius:var(--radius-lg);max-height:80vh;display:flex;flex-direction:column;transform:translateY(30px);transition:transform .25s;box-shadow:0 12px 40px rgba(0,0,0,0.18);}
|
||||
.dyn-overlay.show .dyn-modal{transform:translateY(0);}
|
||||
.dyn-modal__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--border);}
|
||||
.dyn-modal__header h3{font-size:var(--font-lg);font-weight:600;margin:0;}
|
||||
.dyn-modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;font-size:20px;color:#999;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);}
|
||||
.dyn-modal__close:hover{background:#f5f5f5;color:#333;}
|
||||
.dyn-modal__body{padding:20px 24px;overflow-y:auto;flex:1;}
|
||||
.dyn-modal__footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border);}
|
||||
/* 故障记录弹窗 */
|
||||
.fault-modal{width:760px;}
|
||||
.fault-dev-info{display:flex;gap:24px;margin-bottom:16px;font-size:var(--font-sm);color:var(--text-secondary);flex-wrap:wrap;}
|
||||
.fault-dev-info strong{color:var(--text-primary);margin-left:4px;}
|
||||
.fault-table{width:100%;border-collapse:collapse;}
|
||||
.fault-table thead th{background:#FAFAFA;padding:10px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.fault-table tbody td{padding:12px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
|
||||
.fault-table tbody tr:last-child td{border-bottom:none;}
|
||||
.fault-table tbody tr:hover{background:#F8FBFF;}
|
||||
.fault-empty{text-align:center;padding:40px 0;color:var(--text-placeholder);font-size:var(--font-sm);}
|
||||
/* 确认弹窗 */
|
||||
.confirm-modal{width:420px;}
|
||||
.confirm-icon{display:flex;align-items:center;justify-content:center;margin-bottom:16px;}
|
||||
.confirm-icon svg{width:48px;height:48px;}
|
||||
.confirm-msg{text-align:center;font-size:var(--font-base);color:var(--text-primary);margin-bottom:8px;font-weight:500;}
|
||||
.confirm-sub{text-align:center;font-size:var(--font-sm);color:var(--text-secondary);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -137,40 +163,40 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-menu__title">营养用餐</div>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item">字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item">营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item">食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item">餐品总库</a>
|
||||
<a href="din-dict-mgmt.html" class="sidebar-item"><span class="mi">📖</span>字典管理</a>
|
||||
<a href="prod-qc-nutrition.html" class="sidebar-item"><span class="mi">🔬</span>营养检验</a>
|
||||
<a href="din-ingredient.html" class="sidebar-item"><span class="mi">🥦</span>食物成分</a>
|
||||
<a href="din-dish-all.html" class="sidebar-item"><span class="mi">🍲</span>餐品总库</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
算法标准
|
||||
📐 算法标准
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-algo-population.html" class="sidebar-item">人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item">VIP定制标准</a>
|
||||
<a href="din-algo-population.html" class="sidebar-item"><span class="mi">👥</span>人群基础标准</a>
|
||||
<a href="din-algo-vip.html" class="sidebar-item"><span class="mi">💎</span>VIP定制标准</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item">食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item">营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item">用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item">餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item">取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item">消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item">营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item">餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item active">终端管理</a>
|
||||
<a href="din-recipe-mgmt.html" class="sidebar-item"><span class="mi">📋</span>食谱管理</a>
|
||||
<a href="din-order-meal.html" class="sidebar-item"><span class="mi">🛒</span>营养点餐</a>
|
||||
<a href="din-user-mgmt.html" class="sidebar-item"><span class="mi">🧑</span>用户管理</a>
|
||||
<a href="din-order-line.html" class="sidebar-item"><span class="mi">⚖️</span>餐线管理</a>
|
||||
<a href="din-order-pickup.html" class="sidebar-item"><span class="mi">🍽️</span>取餐管理</a>
|
||||
<a href="din-data-consume.html" class="sidebar-item"><span class="mi">💰</span>消费管理</a>
|
||||
<a href="din-data-nutri.html" class="sidebar-item"><span class="mi">📊</span>营养管理</a>
|
||||
<a href="din-data-benefit.html" class="sidebar-item"><span class="mi">📈</span>餐材效益</a>
|
||||
<a href="din-terminal.html" class="sidebar-item active"><span class="mi">🖥️</span>终端管理</a>
|
||||
<div class="sidebar-subgroup">
|
||||
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
|
||||
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
|
||||
模型管理
|
||||
🤖 模型管理
|
||||
</div>
|
||||
<div class="sidebar-subgroup__body">
|
||||
<a href="din-model-llm.html" class="sidebar-item">大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item">小模型管理</a>
|
||||
<a href="din-model-llm.html" class="sidebar-item"><span class="mi">🧠</span>大模型管理</a>
|
||||
<a href="din-model-small.html" class="sidebar-item"><span class="mi">⚙️</span>小模型管理</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="din-innovation.html" class="sidebar-item">味养创新</a>
|
||||
<a href="din-innovation.html" class="sidebar-item"><span class="mi">🌟</span>味养创新</a>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
@@ -258,6 +284,117 @@ function closeModal(){document.getElementById('modalOverlay').style.display='non
|
||||
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
|
||||
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');})();
|
||||
|
||||
/* ===== 动态弹窗工具 ===== */
|
||||
const DynModal={
|
||||
open(title,bodyHtml,footerHtml,extraClass){
|
||||
const overlay=document.createElement('div');
|
||||
overlay.className='dyn-overlay';
|
||||
overlay.innerHTML=`<div class="dyn-modal ${extraClass||''}">
|
||||
<div class="dyn-modal__header"><h3>${title}</h3><button class="dyn-modal__close" onclick="DynModal.close()">×</button></div>
|
||||
<div class="dyn-modal__body">${bodyHtml}</div>
|
||||
<div class="dyn-modal__footer">${footerHtml}</div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
overlay.addEventListener('click',e=>{if(e.target===overlay)DynModal.close();});
|
||||
requestAnimationFrame(()=>overlay.classList.add('show'));
|
||||
},
|
||||
close(){
|
||||
const o=document.querySelector('.dyn-overlay');
|
||||
if(!o)return;
|
||||
o.classList.remove('show');
|
||||
setTimeout(()=>o.remove(),250);
|
||||
}
|
||||
};
|
||||
|
||||
/* ===== 故障记录数据(模拟) ===== */
|
||||
const faultData={
|
||||
'DEV-001':[
|
||||
{time:'2026-03-18 14:22',type:'触屏无响应',level:'中',status:'已修复',handler:'张运维'},
|
||||
{time:'2025-12-05 09:10',type:'网络断连',level:'低',status:'已修复',handler:'李运维'}
|
||||
],
|
||||
'DEV-002':[],
|
||||
'DEV-003':[
|
||||
{time:'2026-04-08 16:35',type:'显示花屏',level:'高',status:'已修复',handler:'张运维'}
|
||||
],
|
||||
'DEV-004':[
|
||||
{time:'2026-04-22 09:18',type:'主板故障',level:'高',status:'维修中',handler:'李运维'},
|
||||
{time:'2026-02-14 11:00',type:'电源异常',level:'中',status:'已修复',handler:'王运维'},
|
||||
{time:'2025-11-20 08:45',type:'网络断连',level:'低',status:'已修复',handler:'李运维'}
|
||||
],
|
||||
'DEV-005':[],
|
||||
'DEV-006':[
|
||||
{time:'2026-04-20 10:30',type:'传感器异常',level:'高',status:'维修中',handler:'张运维'}
|
||||
]
|
||||
};
|
||||
|
||||
/* ===== 故障记录弹窗 ===== */
|
||||
function showFaultLog(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const devCode=cells[0].textContent.trim();
|
||||
const devName=cells[2].textContent.trim();
|
||||
const devType=cells[3].textContent.trim();
|
||||
const records=faultData[devCode]||[];
|
||||
|
||||
let tableHtml='';
|
||||
if(records.length===0){
|
||||
tableHtml='<div class="fault-empty">暂无故障记录</div>';
|
||||
}else{
|
||||
const levelTag=l=>l==='高'?'tag-red':l==='中'?'tag-orange':'tag-green';
|
||||
const statusTag=s=>s==='维修中'?'tag-orange':s==='已修复'?'tag-green':'tag-gray';
|
||||
const rows=records.map(r=>`<tr>
|
||||
<td>${r.time}</td>
|
||||
<td>${r.type}</td>
|
||||
<td><span class="tag ${levelTag(r.level)}">${r.level}</span></td>
|
||||
<td><span class="tag ${statusTag(r.status)}">${r.status}</span></td>
|
||||
<td>${r.handler}</td>
|
||||
</tr>`).join('');
|
||||
tableHtml=`<table class="fault-table">
|
||||
<thead><tr><th>时间</th><th>故障类型</th><th>等级</th><th>状态</th><th>处理人</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
const html=`
|
||||
<div class="fault-dev-info">
|
||||
<span>设备编号:<strong>${devCode}</strong></span>
|
||||
<span>设备名称:<strong>${devName}</strong></span>
|
||||
<span>设备类型:<strong>${devType}</strong></span>
|
||||
<span>故障记录数:<strong>${records.length}</strong></span>
|
||||
</div>
|
||||
${tableHtml}`;
|
||||
|
||||
DynModal.open('故障记录 — '+devName,html,'<button class="btn btn-default" onclick="DynModal.close()">关闭</button>','fault-modal');
|
||||
}
|
||||
|
||||
/* ===== 远程重启确认弹窗 ===== */
|
||||
function showRebootConfirm(row){
|
||||
const cells=row.querySelectorAll('td');
|
||||
const devName=cells[2].textContent.trim();
|
||||
const devCode=cells[0].textContent.trim();
|
||||
|
||||
const html=`
|
||||
<div class="confirm-icon">
|
||||
<svg viewBox="0 0 48 48" fill="none"><circle cx="24" cy="24" r="22" fill="#FFF7E6" stroke="#FAAD14" stroke-width="2"/><path d="M24 14v14" stroke="#FAAD14" stroke-width="3" stroke-linecap="round"/><circle cx="24" cy="34" r="2" fill="#FAAD14"/></svg>
|
||||
</div>
|
||||
<div class="confirm-msg">是否要强制重启该设备?</div>
|
||||
<div class="confirm-sub">${devCode} · ${devName}</div>`;
|
||||
|
||||
const footer=`<button class="btn btn-default" onclick="DynModal.close()">取消</button><button class="btn btn-primary" onclick="DynModal.close()">确定重启</button>`;
|
||||
DynModal.open('远程重启确认',html,footer,'confirm-modal');
|
||||
}
|
||||
|
||||
/* ===== 事件绑定 ===== */
|
||||
document.querySelectorAll('tbody tr').forEach(row=>{
|
||||
row.querySelectorAll('.td-actions .link').forEach(link=>{
|
||||
if(link.textContent.includes('故障记录')){
|
||||
link.addEventListener('click',()=>showFaultLog(row));
|
||||
}
|
||||
if(link.textContent.includes('远程重启')){
|
||||
link.addEventListener('click',()=>showRebootConfirm(row));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,172 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>营养管理 - 用户管理弹窗 | 健康长庆原型</title>
|
||||
<style>
|
||||
/* 设计令牌 */
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:rgba(0,0,0,0.45);margin:0;display:flex;align-items:center;justify-content:center;min-height:100vh;}
|
||||
:root{
|
||||
--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;
|
||||
--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;
|
||||
--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;
|
||||
--radius-sm:4px;--radius-md:8px;--radius-lg:12px;
|
||||
--shadow-md:0 4px 12px rgba(0,0,0,0.12);
|
||||
}
|
||||
/* 弹窗容器 */
|
||||
.dialog{background:#fff;border-radius:var(--radius-lg);width:680px;max-width:90vw;box-shadow:var(--shadow-md);}
|
||||
.dialog__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--border);}
|
||||
.dialog__title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
|
||||
.dialog__close{width:32px;height:32px;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);color:var(--text-placeholder);}
|
||||
.dialog__close:hover{background:#F5F5F5;color:var(--text-primary);}
|
||||
.dialog__body{padding:24px;max-height:70vh;overflow-y:auto;}
|
||||
/* 表单元素 */
|
||||
.form-group{margin-bottom:20px;}
|
||||
.form-group label{display:block;font-size:var(--font-sm);color:var(--text-primary);margin-bottom:8px;font-weight:500;}
|
||||
.form-group label .required{color:var(--danger);margin-left:2px;}
|
||||
.form-input{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;}
|
||||
.form-input:focus{border-color:var(--primary);}
|
||||
.form-select{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;appearance:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 12px center;}
|
||||
.form-textarea{width:100%;height:80px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;font-size:var(--font-sm);outline:none;resize:vertical;font-family:inherit;}
|
||||
.form-textarea:focus{border-color:var(--primary);}
|
||||
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
|
||||
.dialog__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 24px;border-top:1px solid var(--border);}
|
||||
.btn{height:36px;padding:0 20px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;}
|
||||
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
|
||||
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
|
||||
/* Tab 切换 */
|
||||
.dialog-tabs{display:flex;gap:0;padding:0 24px;border-bottom:1px solid var(--border);}
|
||||
.dialog-tab{padding:12px 16px;font-size:var(--font-sm);cursor:pointer;color:var(--text-secondary);border-bottom:2px solid transparent;margin-bottom:-1px;transition:all .2s;}
|
||||
.dialog-tab:hover{color:var(--primary);}
|
||||
.dialog-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500;}
|
||||
.dialog-panel{display:none;}
|
||||
.dialog-panel.active{display:block;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 用户管理弹窗 -->
|
||||
<div class="dialog">
|
||||
<div class="dialog__header">
|
||||
<div class="dialog__title">用户管理</div>
|
||||
<button class="dialog__close" onclick="window.close()">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗内 Tab -->
|
||||
<div class="dialog-tabs">
|
||||
<div class="dialog-tab active" onclick="switchDialogTab(0)">开通会员</div>
|
||||
<div class="dialog-tab" onclick="switchDialogTab(1)">新增等级</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab1: 开通会员 -->
|
||||
<div class="dialog-panel active">
|
||||
<div class="dialog__body">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<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>初始等级<span class="required">*</span></label>
|
||||
<select class="form-select">
|
||||
<option>请选择</option>
|
||||
<option>普通</option>
|
||||
<option>银卡</option>
|
||||
<option>金卡</option>
|
||||
<option>钻石</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>有效期(年)<span class="required">*</span></label>
|
||||
<select class="form-select">
|
||||
<option>请选择</option>
|
||||
<option>1年</option>
|
||||
<option>2年</option>
|
||||
<option>3年</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>初始积分</label>
|
||||
<input class="form-input" type="number" placeholder="请输入初始积分">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>备注</label>
|
||||
<textarea class="form-textarea" placeholder="请输入备注信息"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog__footer">
|
||||
<button class="btn btn-default" onclick="window.close()">取消</button>
|
||||
<button class="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab2: 新增等级 -->
|
||||
<div class="dialog-panel">
|
||||
<div class="dialog__body">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>等级名称<span class="required">*</span></label>
|
||||
<input class="form-input" type="text" placeholder="请输入等级名称">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>升级条件(累计消费元)<span class="required">*</span></label>
|
||||
<input class="form-input" type="number" placeholder="请输入累计消费金额">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>积分倍数</label>
|
||||
<input class="form-input" type="number" placeholder="如 1.5 表示每消费1元返1.5积分">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>点餐折扣</label>
|
||||
<input class="form-input" type="text" placeholder="如 9.5折">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>取餐特权</label>
|
||||
<select class="form-select">
|
||||
<option>请选择</option>
|
||||
<option>常规</option>
|
||||
<option>优先取餐</option>
|
||||
<option>专属窗口</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>专属权益</label>
|
||||
<textarea class="form-textarea" placeholder="请输入专属权益描述"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog__footer">
|
||||
<button class="btn btn-default" onclick="window.close()">取消</button>
|
||||
<button class="btn btn-primary">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchDialogTab(idx){
|
||||
document.querySelectorAll('.dialog-tab').forEach(function(t,i){t.classList.toggle('active',i===idx);});
|
||||
document.querySelectorAll('.dialog-panel').forEach(function(p,i){p.classList.toggle('active',i===idx);});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -35,7 +35,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
|
||||
.sidebar-item.active{color:#fff;background:var(--primary);}
|
||||
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
|
||||
.sidebar-item.active .mi{opacity:0.9;}
|
||||
${CSS_RULE}
|
||||
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
|
||||
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
|
||||
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
|
||||
@@ -105,6 +104,55 @@ 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;}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;animation:modalFadeIn .2s ease;}
|
||||
.modal-overlay.closing{animation:modalFadeOut .2s ease forwards;}
|
||||
@keyframes modalFadeIn{from{opacity:0}to{opacity:1}}
|
||||
@keyframes modalFadeOut{from{opacity:1}to{opacity:0}}
|
||||
@keyframes modalSlideIn{from{transform:translateY(-20px);opacity:0}to{transform:translateY(0);opacity:1}}
|
||||
@keyframes modalSlideOut{from{transform:translateY(0);opacity:1}to{transform:translateY(-20px);opacity:0}}
|
||||
.modal{background:#fff;border-radius:8px;width:900px;max-width:90vw;max-height:85vh;display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.15);animation:modalSlideIn .25s ease;}
|
||||
.modal-overlay.closing .modal{animation:modalSlideOut .2s ease forwards;}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.modal-title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
|
||||
.modal-close{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:var(--radius-sm);color:var(--text-secondary);display:flex;align-items:center;justify-content:center;}
|
||||
.modal-close:hover{background:#f5f5f5;color:var(--text-primary);}
|
||||
.modal-body{padding: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);}
|
||||
|
||||
/* 十维档案 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;}
|
||||
.profile-tab:hover{color:var(--primary);}
|
||||
.profile-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500;}
|
||||
.profile-panel{display:none;}
|
||||
.profile-panel.active{display:block;}
|
||||
.profile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 24px;}
|
||||
.profile-field{display:flex;align-items:baseline;gap:8px;font-size:var(--font-sm);padding:8px 0;border-bottom:1px dashed #f0f0f0;}
|
||||
.profile-field__label{color:var(--text-secondary);min-width:80px;flex-shrink:0;}
|
||||
.profile-field__value{color:var(--text-primary);font-weight:500;}
|
||||
.profile-summary{display:flex;align-items:center;gap:16px;padding:16px;background:#f8fafc;border-radius:var(--radius-md);margin-bottom:20px;}
|
||||
.profile-summary__avatar{width:56px;height:56px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;color:#fff;font-size:22px;font-weight:600;}
|
||||
.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);}
|
||||
|
||||
/* 推荐食谱弹窗 */
|
||||
.recipe-modal{width:720px;}
|
||||
.recipe-banner{display:flex;align-items:center;gap:8px;padding:10px 16px;background:var(--primary-light);border-radius:var(--radius-sm);margin-bottom:20px;font-size:var(--font-sm);color:var(--primary);}
|
||||
.recipe-banner svg{flex-shrink:0;}
|
||||
.recipe-section{margin-bottom:16px;}
|
||||
.recipe-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:8px;padding-left:4px;}
|
||||
.recipe-section table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.recipe-section thead th{background:#FAFAFA;padding:8px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
|
||||
.recipe-section tbody td{padding:8px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);}
|
||||
.recipe-section tbody td.td-cal{color:var(--primary);font-weight:500;}
|
||||
.recipe-section tbody td.td-safe{color:var(--success);}
|
||||
.recipe-summary{display:flex;align-items:center;gap:24px;padding:12px 16px;background:#FAFAFA;border-radius:var(--radius-sm);margin-top:16px;font-size:var(--font-sm);}
|
||||
.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);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -282,7 +330,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<button class="btn btn-default">重置</button>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<a class="btn btn-primary" href="din-user-mgmt-dialog.html">➕ 开通会员</a>
|
||||
<!-- 开通会员:后续用独立二级页面实现 --><button class="btn btn-primary">➕ 开通会员</button>
|
||||
<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>
|
||||
@@ -326,7 +374,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
|
||||
<button class="btn btn-default">重置</button>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<a class="btn btn-primary" href="din-user-mgmt-dialog.html">➕ 新增等级</a>
|
||||
<!-- 新增等级:后续用独立二级页面实现 --><button class="btn btn-primary">➕ 新增等级</button>
|
||||
<button class="btn btn-default">✏️ 调整权益</button>
|
||||
</div>
|
||||
<div class="table-card">
|
||||
@@ -363,6 +411,150 @@ 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);});
|
||||
}
|
||||
|
||||
/* ========== 弹窗基础 ========== */
|
||||
const Modal = {
|
||||
open(title, bodyHtml, extraClass, footerHtml) {
|
||||
this.close();
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
overlay.innerHTML = `<div class="modal ${extraClass||''}">
|
||||
<div class="modal-header"><div class="modal-title">${title}</div><button class="modal-close" onclick="Modal.close()">×</button></div>
|
||||
<div class="modal-body">${bodyHtml}</div>
|
||||
${footerHtml ? '<div class="modal-footer">'+footerHtml+'</div>' : ''}
|
||||
</div>`;
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) Modal.close(); });
|
||||
document.body.appendChild(overlay);
|
||||
},
|
||||
close() {
|
||||
const o = document.querySelector('.modal-overlay');
|
||||
if (!o) return;
|
||||
o.classList.add('closing');
|
||||
setTimeout(() => o.remove(), 200);
|
||||
}
|
||||
};
|
||||
|
||||
/* ========== 十维档案 ========== */
|
||||
const userProfiles = {
|
||||
'张伟': { 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%' },
|
||||
'李娜': { 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%' }
|
||||
};
|
||||
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%' };
|
||||
|
||||
const tabDefs = [
|
||||
{ key:'basic', label:'静态基础' },{ key:'disease', label:'疾病信息' },{ key:'physical', label:'体检信息' },
|
||||
{ key:'taste', label:'口味偏好' },{ key:'taboo', label:'禁忌关联' },{ key:'dynamic', label:'动态生理' },
|
||||
{ key:'meal', label:'历史用餐' },{ key:'dish', label:'菜品偏好' },{ key:'mom', label:'妈妈味道' },{ key:'model', label:'个性模型' }
|
||||
];
|
||||
|
||||
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>`
|
||||
};
|
||||
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('');
|
||||
const summary = `<div class="profile-summary"><div class="profile-summary__avatar">${name[0]}</div><div class="profile-summary__info"><div class="profile-summary__name">${name}</div><div class="profile-summary__meta">${p.dept} · ${p.gender} · ${p.age}岁 · BMI ${p.bmi}</div></div></div>`;
|
||||
return summary + `<div class="profile-tabs">${tabs}</div>${panelsHtml}`;
|
||||
}
|
||||
|
||||
function switchProfileTab(el, key) {
|
||||
el.parentElement.querySelectorAll('.profile-tab').forEach(t => t.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
el.closest('.modal-body').querySelectorAll('.profile-panel').forEach(p => p.classList.remove('active'));
|
||||
el.closest('.modal-body').querySelector(`[data-panel="${key}"]`).classList.add('active');
|
||||
}
|
||||
|
||||
function showUserProfile(name) {
|
||||
const p = userProfiles[name] || defaultProfile;
|
||||
Modal.open('用户十维档案 — ' + name, buildProfileHTML(name, p));
|
||||
}
|
||||
|
||||
/* ========== 推荐食谱弹窗 ========== */
|
||||
function showRecipeModal(name) {
|
||||
const p = userProfiles[name] || defaultProfile;
|
||||
const html = `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
||||
<div></div>
|
||||
<button class="btn btn-primary" style="height:32px;font-size:12px;">🤖 生成食谱</button>
|
||||
</div>
|
||||
<div class="recipe-banner">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5"/><path d="M8 4.5v4M8 10.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
基于十维档案数据自动生成:BMI ${p.bmi} / 活动水平 ${p.activity} / 每日目标 ${p.recCal}
|
||||
</div>
|
||||
<div class="recipe-section">
|
||||
<div class="recipe-section__title">早餐</div>
|
||||
<table><thead><tr><th>菜品</th><th>热量(kcal)</th><th>口味匹配</th><th>禁忌过滤</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>小米粥</td><td class="td-cal">186</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
<tr><td>凉拌黄瓜</td><td class="td-cal">48</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<div class="recipe-section">
|
||||
<div class="recipe-section__title">午餐</div>
|
||||
<table><thead><tr><th>菜品</th><th>热量(kcal)</th><th>口味匹配</th><th>禁忌过滤</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>米饭</td><td class="td-cal">232</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
<tr><td>清蒸鱼</td><td class="td-cal">136</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
<tr><td>炒西兰花</td><td class="td-cal">50</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<div class="recipe-section">
|
||||
<div class="recipe-section__title">晚餐</div>
|
||||
<table><thead><tr><th>菜品</th><th>热量(kcal)</th><th>口味匹配</th><th>禁忌过滤</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>杂粮饭</td><td class="td-cal">162</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
<tr><td>番茄炒蛋</td><td class="td-cal">142</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
<tr><td>紫菜汤</td><td class="td-cal">38</td><td>匹配</td><td class="td-safe">安全</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<div class="recipe-summary">
|
||||
<div class="recipe-summary__item"><span class="recipe-summary__label">总热量:</span><span class="recipe-summary__value">994 kcal</span></div>
|
||||
<div class="recipe-summary__item"><span class="recipe-summary__label">目标热量:</span><span class="recipe-summary__value">${p.recCal}</span></div>
|
||||
<div class="recipe-summary__item"><span class="recipe-summary__label">匹配度:</span><span class="tag tag-orange">44%</span></div>
|
||||
</div>`;
|
||||
const footer = `<button class="btn btn-default" onclick="Modal.close()">取消</button><button class="btn btn-primary" onclick="Modal.close()">确定</button>`;
|
||||
Modal.open(name + ' — 个人推荐食谱', html, 'recipe-modal', footer);
|
||||
}
|
||||
|
||||
/* ========== 事件绑定 ========== */
|
||||
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));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user