feat: 营养管理溯源总览菜单重构——食材溯源/餐品溯源双菜单分离

- 侧边栏菜单"溯源查询/链路可视化"重构为"食材溯源/餐品溯源"
- trace-query.html 沿用作食材溯源主页,新增"入库单号"列
- 新增 trace-dish.html 餐品溯源主页:餐品名称(可搜索下拉)+烹制时间区间搜索;
  表头餐品名称/烹制单号/烹制时间/烹制份数/烹制厨师/食材数量/检测结论/操作
- 新增 trace-timeline-ingredient.html 与 trace-timeline-dish.html 两个链路详情页,
  分别承载原 trace-timeline.html 食材/餐品维度内容,去除维度Tab与搜索区,添加面包屑
- 删除旧 trace-timeline.html
- prod-proc-cabinet.html 修正死链与 typo
- index.html SITE_MAP 同步更新

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-06-10 15:50:11 +08:00
co-authored by Claude Opus 4.7
parent 5fcaea4bd5
commit 51ff005fc6
7 changed files with 660 additions and 172 deletions
+20
View File
@@ -1,5 +1,25 @@
# 营养用餐管理端 CHANGELOG
## [2026-06-10] feat: 溯源总览菜单重构——食材溯源 / 餐品溯源 双菜单分离
- **侧边栏菜单**:原"溯源查询 / 链路可视化"重构为"食材溯源 / 餐品溯源"两个一级菜单。
- **trace-query.html**(食材溯源主页,沿用原溯源查询):
- 侧边栏菜单文案调整、操作列文案与链接更新(指向 `trace-timeline-ingredient.html`)。
- 表头新增「入库单号」列(位于来源类型与入库时间之间),8 条样例数据补充入库单号字段。
- **trace-dish.html**(新增 · 餐品溯源主页):
- 搜索区:餐品名称(datalist 可搜索下拉)+ 烹制时间(日期区间)。
- 表头:餐品名称 / 烹制单号 / 烹制时间 / 烹制份数 / 烹制厨师 / 食材数量 / 检测结论 / 操作。
- 8 条样例数据,7 绿 1 红(鱼香肉丝不合格)展示双状态样式。
- **trace-timeline-ingredient.html**(新增 · 食材链路详情):
- 内容拆自原 `trace-timeline.html` 的食材维度 tab,去掉维度切换 Tab 与上方搜索区。
- 顶部增加面包屑(食材溯源 / 食材链路),可返回主页。
- **trace-timeline-dish.html**(新增 · 餐品链路详情):
- 内容拆自原 `trace-timeline.html` 的餐品维度 tab,去掉维度切换 Tab 与上方搜索区。
- 保留食材标签页 + 横向管道时间线 + 节点详情 + 视频弹窗 + 面包屑。
- **trace-timeline.html**:删除(被上述两个新链路详情页替代)。
- **prod-proc-cabinet.html**:净菜柜表格中 6 条「溯源」链接由 `trace-timeline.html` 改为 `trace-timeline-ingredient.html`,避免死链;同时修正一处 typo`ttrace-timeline-ingredient.html``trace-timeline-ingredient.html`)。
- **index.html**SITE_MAP 营养管理模块条目同步更新,新增 4 条(食材溯源 / 食材链路 / 餐品溯源 / 餐品链路),移除旧"溯源查询 / 链路可视化"。
## [2026-06-08] feat: 溯源模块重构——餐品/食材双维度分离
- **trace-query.html**:表格表头、筛选条件、数据行从餐品维度切换为食材维度(溯源码、食材名称、来源类型、入库时间、产地/供应商、种养殖批次、检测结论、操作)
+6 -6
View File
@@ -332,12 +332,12 @@ tbody tr.row-active td.td-actions{background:var(--primary-light);}
<table>
<thead><tr><th>格口</th><th>关联净菜订单</th><th>存放净菜</th><th>类型</th><th>初始重量(kg)</th><th>当前余量(kg)</th><th>操作人</th><th>温度(℃)</th><th>保质剩余</th><th>存入时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody id="detailTable">
<tr class="row-active" data-slot="0" data-type="净菜" data-order="JC-2026-0422-001" data-trace="NC-CAB-BOCA-0422-01"><td>A1-01</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-001</a></td><td>土豆(土豆丝)</td><td><span class="tag tag-green">🥬 净菜</span></td><td>12.5</td><td>10.2</td><td>张厨师</td><td>4.2</td><td>剩余32h</td><td>04-22 09:15</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline.html">溯源</a> <span class="link" onclick="showVideo('A1-01','土豆')">视频</span> <span class="link" onclick="showDetail(this)">详情</span> <span class="link">编辑</span></td></tr>
<tr data-slot="1" data-type="净菜" data-order="JC-2026-0422-002" data-trace="NC-CAB-HLUB-0422-01"><td>A1-02</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-002</a></td><td>净胡萝卜</td><td><span class="tag tag-green">🥬 净菜</span></td><td>18.0</td><td>15.6</td><td>李主管</td><td>4.1</td><td>剩余46h</td><td>04-22 09:20</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline.html">溯源</a> <span class="link" onclick="showVideo('A1-02','净胡萝卜')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="2" data-type="净菜" data-order="JC-2026-0422-003" data-trace="NC-CAB-XGUO-0422-01"><td>A1-03</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-003</a></td><td>净香菇</td><td><span class="tag tag-green">🥬 净菜</span></td><td>9.0</td><td>6.8</td><td>王厨师</td><td>4.3</td><td>剩余6h</td><td>04-22 09:30</td><td><span class="tag tag-orange">临期预警</span></td><td class="td-actions"><a class="link" href="trace-timeline.html">溯源</a> <span class="link" onclick="showVideo('A1-03','净香菇')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="3" data-type="净菜" data-order="JC-2026-0422-004" data-trace="NC-CAB-XLHU-0422-01"><td>A1-04</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-004</a></td><td>净西兰花</td><td><span class="tag tag-green">🥬 净菜</span></td><td>10.0</td><td>8.5</td><td>张厨师</td><td>4.0</td><td>剩余50h</td><td>04-22 09:40</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline.html">溯源</a> <span class="link" onclick="showVideo('A1-04','净西兰花')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="4" data-type="餐品包" data-order="JC-2026-0422-005" data-trace="PKG-DH-001"><td>A1-05</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-005</a></td><td>宫保鸡丁</td><td><span class="tag tag-orange">🍱 餐品包</span></td><td>6.0</td><td>5.4</td><td>李主管</td><td>3.8</td><td>剩余44h</td><td>04-22 10:20</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline.html">溯源</a> <span class="link" onclick="showVideo('A1-05','宫保鸡丁')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="5" data-type="净菜" data-order="JC-2026-0422-006" data-trace="NC-CAB-BCAI-0422-01"><td>A1-06</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-006</a></td><td>净白菜</td><td><span class="tag tag-green">🥬 净菜</span></td><td>22.0</td><td>18.0</td><td>王厨师</td><td>7.8</td><td>剩余40h</td><td>04-22 08:50</td><td><span class="tag tag-red">已过期</span></td><td class="td-actions"><a class="link" href="trace-timeline.html">溯源</a> <span class="link" onclick="showVideo('A1-06','净白菜')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr class="row-active" data-slot="0" data-type="净菜" data-order="JC-2026-0422-001" data-trace="NC-CAB-BOCA-0422-01"><td>A1-01</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-001</a></td><td>土豆(土豆丝)</td><td><span class="tag tag-green">🥬 净菜</span></td><td>12.5</td><td>10.2</td><td>张厨师</td><td>4.2</td><td>剩余32h</td><td>04-22 09:15</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">溯源</a> <span class="link" onclick="showVideo('A1-01','土豆')">视频</span> <span class="link" onclick="showDetail(this)">详情</span> <span class="link">编辑</span></td></tr>
<tr data-slot="1" data-type="净菜" data-order="JC-2026-0422-002" data-trace="NC-CAB-HLUB-0422-01"><td>A1-02</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-002</a></td><td>净胡萝卜</td><td><span class="tag tag-green">🥬 净菜</span></td><td>18.0</td><td>15.6</td><td>李主管</td><td>4.1</td><td>剩余46h</td><td>04-22 09:20</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">溯源</a> <span class="link" onclick="showVideo('A1-02','净胡萝卜')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="2" data-type="净菜" data-order="JC-2026-0422-003" data-trace="NC-CAB-XGUO-0422-01"><td>A1-03</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-003</a></td><td>净香菇</td><td><span class="tag tag-green">🥬 净菜</span></td><td>9.0</td><td>6.8</td><td>王厨师</td><td>4.3</td><td>剩余6h</td><td>04-22 09:30</td><td><span class="tag tag-orange">临期预警</span></td><td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">溯源</a> <span class="link" onclick="showVideo('A1-03','净香菇')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="3" data-type="净菜" data-order="JC-2026-0422-004" data-trace="NC-CAB-XLHU-0422-01"><td>A1-04</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-004</a></td><td>净西兰花</td><td><span class="tag tag-green">🥬 净菜</span></td><td>10.0</td><td>8.5</td><td>张厨师</td><td>4.0</td><td>剩余50h</td><td>04-22 09:40</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">溯源</a> <span class="link" onclick="showVideo('A1-04','净西兰花')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="4" data-type="餐品包" data-order="JC-2026-0422-005" data-trace="PKG-DH-001"><td>A1-05</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-005</a></td><td>宫保鸡丁</td><td><span class="tag tag-orange">🍱 餐品包</span></td><td>6.0</td><td>5.4</td><td>李主管</td><td>3.8</td><td>剩余44h</td><td>04-22 10:20</td><td><span class="tag tag-green">存放中</span></td><td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">溯源</a> <span class="link" onclick="showVideo('A1-05','宫保鸡丁')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="5" data-type="净菜" data-order="JC-2026-0422-006" data-trace="NC-CAB-BCAI-0422-01"><td>A1-06</td><td><a class="link" href="prod-clean-order.html">JC-2026-0422-006</a></td><td>净白菜</td><td><span class="tag tag-green">🥬 净菜</span></td><td>22.0</td><td>18.0</td><td>王厨师</td><td>7.8</td><td>剩余40h</td><td>04-22 08:50</td><td><span class="tag tag-red">已过期</span></td><td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">溯源</a> <span class="link" onclick="showVideo('A1-06','净白菜')">视频</span> <span class="link" onclick="showDetail(this)">详情</span><span class="link">编辑</span></td></tr>
<tr data-slot="6"><td>A1-07</td><td></td><td></td><td></td><td></td><td></td><td></td><td>4.1</td><td></td><td></td><td><span class="tag tag-gray">空闲</span></td><td class="td-actions"></td></tr>
<tr data-slot="7"><td>A1-08</td><td></td><td></td><td></td><td></td><td></td><td></td><td>4.1</td><td></td><td></td><td><span class="tag tag-gray">空闲</span></td><td class="td-actions"></td></tr>
<tr data-slot="8"><td>A1-09</td><td></td><td></td><td></td><td></td><td></td><td></td><td>4.2</td><td></td><td></td><td><span class="tag tag-gray">空闲</span></td><td class="td-actions"></td></tr>
+267
View File
@@ -0,0 +1,267 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>营养管理 - 餐品溯源 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;}
:root{
--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;
--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;--background:#F5F7FA;
--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;
--radius-sm:4px;--radius-md:8px;--radius-lg:12px;
--shadow-sm:0 1px 4px rgba(0,0,0,0.08);--shadow-md:0 4px 12px rgba(0,0,0,0.12);
}
/* 顶部导航 */
.top-bar{display:flex;align-items:center;height:56px;background:#001529;padding:0 24px;position:fixed;top:0;left:0;right:0;z-index:100;}
.top-bar__logo{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
.top-bar__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.top-bar__right .btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.top-bar__right .btn-back:hover{color:#fff;}
.avatar{width:32px;height:32px;border-radius:50%;background:#1890FF;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;text-transform:uppercase;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,0.06);}
.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;}
/* 主内容 */
.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-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:200px;}
.filter-input:focus{border-color:var(--primary);}
.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);}
.btn-danger{background:#fff;color:var(--danger);border:1px solid var(--danger);}.btn-danger:hover{background:#fff1f0;}
/* 表格 */
.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;}
thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
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-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);text-decoration:none;}.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);}
/* 操作列固定右侧 */
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;}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="top-bar">
<div class="top-bar__logo">
<svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
营养管理
</div>
<nav class="top-bar__nav">
<a href="trace-query.html" class="active">溯源总览</a>
<a href="arc-employee.html">档案</a>
<a href="farm-land.html">绿色种采</a>
<a href="sup-hygiene-check.html">卫生供应</a>
<a href="prod-qc-daily.html">健康生产</a>
<a href="din-dict-mgmt.html">营养用餐</a>
<a href="ano-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right">
<a class="btn-back" href="../../index.html">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
返回首页
</a>
<div class="avatar"></div>
<span style="color:rgba(255,255,255,0.65);">系统管理员 ▾</span>
</div>
</div>
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">溯源总览</div>
<a href="trace-query.html" class="sidebar-item"><span class="mi">🥬</span>食材溯源</a>
<a href="trace-dish.html" class="sidebar-item active"><span class="mi">🍳</span>餐品溯源</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>餐品名称</label>
<input class="filter-input" type="text" list="dishNameOptions" placeholder="请输入或选择餐品名称" style="min-width:200px;">
<datalist id="dishNameOptions">
<option value="宫保鸡丁"></option>
<option value="辣子鸡"></option>
<option value="青椒鸡丁"></option>
<option value="白切鸡"></option>
<option value="鱼香肉丝"></option>
<option value="红烧肉"></option>
<option value="番茄炒蛋"></option>
<option value="麻婆豆腐"></option>
<option value="清蒸鲈鱼"></option>
<option value="干锅花菜"></option>
<option value="酸辣土豆丝"></option>
<option value="蒜蓉菠菜"></option>
</datalist>
</div>
<div class="filter-item">
<label>烹制时间</label>
<input class="filter-input" type="date" value="2026-04-22" style="min-width:140px;">
<span style="font-size:var(--font-sm);color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" value="2026-04-22" style="min-width:140px;">
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</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>
</tr>
</thead>
<tbody>
<tr>
<td>宫保鸡丁</td>
<td>COOK-20260422-001</td>
<td>2026-04-22 11:00~11:35</td>
<td>120 份</td>
<td>张厨师</td>
<td>4 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>辣子鸡</td>
<td>COOK-20260422-009</td>
<td>2026-04-22 11:05~11:38</td>
<td>80 份</td>
<td>王厨师</td>
<td>5 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>青椒鸡丁</td>
<td>COOK-20260422-015</td>
<td>2026-04-22 11:10~11:40</td>
<td>60 份</td>
<td>李厨师</td>
<td>4 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>白切鸡</td>
<td>COOK-20260422-021</td>
<td>2026-04-22 10:30~11:20</td>
<td>40 份</td>
<td>陈厨师</td>
<td>3 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>鱼香肉丝</td>
<td>COOK-20260422-028</td>
<td>2026-04-22 11:15~11:50</td>
<td>100 份</td>
<td>赵厨师</td>
<td>6 种</td>
<td><span class="tag tag-red">不合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>番茄炒蛋</td>
<td>COOK-20260422-035</td>
<td>2026-04-22 11:18~11:45</td>
<td>150 份</td>
<td>刘厨师</td>
<td>3 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>麻婆豆腐</td>
<td>COOK-20260422-042</td>
<td>2026-04-22 11:20~11:48</td>
<td>90 份</td>
<td>孙厨师</td>
<td>5 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
<tr>
<td>清蒸鲈鱼</td>
<td>COOK-20260422-050</td>
<td>2026-04-22 11:25~12:00</td>
<td>50 份</td>
<td>张厨师</td>
<td>4 种</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline-dish.html">查看餐品链路</a></td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span>显示 8 条,共 86 条</span>
<button class="page-btn">&#8249;</button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<span>...</span>
<button class="page-btn">&#8250;</button>
</div>
</div>
</main>
</body>
</html>
+20 -11
View File
@@ -117,8 +117,8 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">溯源总览</div>
<a href="trace-query.html" class="sidebar-item active"><span class="mi">🔎</span>溯源查询</a>
<a href="trace-timeline.html" class="sidebar-item"><span class="mi">➡️</span>链路可视化</a>
<a href="trace-query.html" class="sidebar-item active"><span class="mi">🥬</span>食材溯源</a>
<a href="trace-dish.html" class="sidebar-item"><span class="mi">🍳</span>餐品溯源</a>
</aside>
<!-- 主内容 -->
@@ -157,7 +157,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<!-- 数据表格 -->
<div class="table-card">
<div class="table-card__header">
<div class="table-card__title">溯源查询</div>
<div class="table-card__title">食材溯源</div>
</div>
<div class="table-card__content">
<table>
@@ -166,6 +166,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<th>溯源码</th>
<th>食材名称</th>
<th>来源类型</th>
<th>入库单号</th>
<th>入库时间</th>
<th>产地/供应商</th>
<th>种养殖批次</th>
@@ -178,81 +179,89 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<td>ST-FH-FL02-MT-HWJJ-1200-20260315-001</td>
<td>鸡胸肉</td>
<td><span class="tag tag-green">自产养殖</span></td>
<td>MC-IN-20260315-001</td>
<td>2026-03-15 12:00:00</td>
<td>B3-鸡舍</td>
<td>FH-FL02-1200-20260315-001</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-FH-FA01-VG-BOCA-286K-20260415-001</td>
<td>菠菜</td>
<td><span class="tag tag-green">自产种植</span></td>
<td>MC-IN-20260415-001</td>
<td>2026-04-15 12:00:00</td>
<td>A区蔬菜基地</td>
<td>FH-FA01-286K-20260415-001</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-EX-LY01-VG-CGSR-030K-20260415-001</td>
<td>番茄</td>
<td><span class="tag tag-blue">外采</span></td>
<td>MC-IN-20260415-003</td>
<td>2026-04-15 12:00:00</td>
<td>绿源蔬菜配送</td>
<td>LY01-030K-20260415-001</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-EX-LY02-DG-DAMI-1000-20260413-001</td>
<td>大米</td>
<td><span class="tag tag-blue">外采</span></td>
<td>DG-IN-20260413-001</td>
<td>2026-04-13 12:00:00</td>
<td>粮油批发商行</td>
<td>LY02-1000-20260413-001</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-EX-XR01-MT-ZLRJ-080K-20260415-005</td>
<td>猪肉</td>
<td><span class="tag tag-blue">外采</span></td>
<td>MC-IN-20260415-005</td>
<td>2026-04-15 12:00:00</td>
<td>鲜肉直供公司</td>
<td>XR01-080K-20260415-005</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-EX-HS01-SF-CAYO-060K-20260414-001</td>
<td>草鱼</td>
<td><span class="tag tag-blue">外采</span></td>
<td>MC-IN-20260414-001</td>
<td>2026-04-14 12:00:00</td>
<td>海鲜水产配送</td>
<td>HS01-060K-20260414-001</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-FH-FA01-VG-BOCA-286K-20260415-002</td>
<td>时蔬</td>
<td><span class="tag tag-green">自产种植</span></td>
<td>MC-IN-20260415-002</td>
<td>2026-04-15 12:00:00</td>
<td>A区蔬菜基地</td>
<td>FH-FA01-286K-20260415-002</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
<tr>
<td>ST-FH-FL02-MT-HWJJ-1200-20260315-002</td>
<td>鸡胸肉</td>
<td><span class="tag tag-green">自产养殖</span></td>
<td>MC-IN-20260315-002</td>
<td>2026-03-15 12:00:00</td>
<td>B3-鸡舍</td>
<td>FH-FL02-1200-20260315-002</td>
<td><span class="tag tag-green">合格</span></td>
<td class="td-actions"><a class="link" href="trace-timeline.html">查看食材链路</a></td>
<td class="td-actions"><a class="link" href="trace-timeline-ingredient.html">查看食材链路</a></td>
</tr>
</tbody>
</table>
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>营养管理 - 链路可视化 | 健康CQ原型</title>
<title>营养管理 - 餐品链路 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;}
@@ -37,26 +37,11 @@
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 维度TAB */
.dimension-tabs{display:flex;gap:0;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;border-bottom:2px solid var(--border);padding:0 24px;box-shadow:var(--shadow-sm);}
.dimension-tab{padding:14px 24px;font-size:var(--font-sm);cursor:pointer;color:var(--text-secondary);border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .2s;font-weight:500;}
.dimension-tab:hover{color:var(--primary);}
.dimension-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
/* 筛选栏 */
.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-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:200px;}
.filter-input:focus{border-color:var(--primary);}
.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);}
/* 维度面板 */
.dimension-panel{display:none;}
.dimension-panel.active{display:block;}
/* 面包屑 */
.breadcrumb{display:flex;align-items:center;gap:8px;margin-bottom:16px;font-size:var(--font-sm);color:var(--text-secondary);}
.breadcrumb a{color:var(--primary);text-decoration:none;}
.breadcrumb a:hover{text-decoration:underline;}
.breadcrumb .sep{color:var(--text-placeholder);}
/* 餐品信息卡 */
.trace-header{background:#fff;border-radius:var(--radius-md);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
@@ -122,10 +107,6 @@
.node-card__video{display:inline-flex;align-items:center;gap:4px;margin-top:10px;padding:4px 12px;border-radius:var(--radius-sm);background:#F0F5FF;color:var(--primary);font-size:var(--font-xs);cursor:pointer;border:1px solid #ADC6FF;transition:all .15s;}
.node-card__video:hover{background:var(--primary-light);border-color:var(--primary);}
/* 隐藏非活跃食材面板 */
.ingredient-panel{display:none;}
.ingredient-panel.active{display:block;}
/* 视频弹窗 */
.modal-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.55);z-index:1000;align-items:center;justify-content:center;}
.modal-overlay.active{display:flex;}
@@ -168,98 +149,43 @@
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">溯源总览</div>
<a href="trace-query.html" class="sidebar-item"><span class="mi">🔎</span>溯源查询</a>
<a href="trace-timeline.html" class="sidebar-item active"><span class="mi">➡️</span>链路可视化</a>
<a href="trace-query.html" class="sidebar-item"><span class="mi">🥬</span>食材溯源</a>
<a href="trace-dish.html" class="sidebar-item active"><span class="mi">🍳</span>餐品溯源</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- 维度TAB切换 -->
<div class="dimension-tabs">
<div class="dimension-tab active" onclick="switchDimension(0)">餐品维度</div>
<div class="dimension-tab" onclick="switchDimension(1)">食材维度</div>
</div>
<!-- ===== 餐品维度面板 ===== -->
<div class="dimension-panel active" id="dim-0">
<!-- 餐品维度查询栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>餐品名称</label>
<select class="filter-select" style="min-width:150px;">
<option>全部</option><option>宫保鸡丁</option><option>辣子鸡</option><option>青椒鸡丁</option><option>白切鸡</option>
</select>
</div>
<div class="filter-item">
<label>烹制时间</label>
<input class="filter-input" type="date" value="2026-04-22" style="min-width:140px;">
<span style="font-size:var(--font-sm);color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" value="2026-04-22" style="min-width:140px;">
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</button>
</div>
<!-- 餐品溯源信息卡 -->
<div class="trace-header">
<div class="trace-header__top">
<div class="trace-header__title">🍳 宫保鸡丁(后厨生产批次)</div>
<div class="trace-header__status"><span class="trace-header__status-dot"></span>全程合格</div>
</div>
<div class="trace-header__grid">
<div class="trace-header__item"><div class="trace-header__label">餐品名称</div><div class="trace-header__value">宫保鸡丁</div></div>
<div class="trace-header__item"><div class="trace-header__label">烹制单号</div><div class="trace-header__value">COOK-20260422-001</div></div>
<div class="trace-header__item"><div class="trace-header__label">烹制时间</div><div class="trace-header__value">2026-04-22 11:00~11:35</div></div>
<div class="trace-header__item"><div class="trace-header__label">烹制厨师</div><div class="trace-header__value">张厨师</div></div>
<div class="trace-header__item"><div class="trace-header__label">食材数量</div><div class="trace-header__value">4 种</div></div>
</div>
</div>
<!-- 食材标签页 -->
<div class="ingredient-tabs" id="ingredientTabs"></div>
<!-- 食材信息卡 -->
<div class="ingredient-info" id="ingredientInfo"></div>
<!-- 共享管道+详情 -->
<div class="timeline-container">
<div class="pipeline" id="pipeline"></div>
<div class="node-detail-panel active" id="nodeDetail"></div>
</div>
<!-- 面包屑 -->
<div class="breadcrumb">
<a href="trace-dish.html">餐品溯源</a>
<span class="sep">/</span>
<span>餐品链路</span>
</div>
<!-- ===== 食材维度面板 ===== -->
<div class="dimension-panel" id="dim-1">
<!-- 食材维度查询栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>食材名称</label>
<select class="filter-select" style="min-width:140px;">
<option>全部</option><option>鸡胸肉</option><option>花生</option><option>辣椒</option><option>葱姜蒜</option>
</select>
</div>
<div class="filter-item">
<label>溯源码</label>
<input class="filter-input" type="text" placeholder="请输入溯源码" style="min-width:180px;">
</div>
<div class="filter-item">
<label>入库时间</label>
<input class="filter-input" type="date" value="2026-04-22" style="min-width:140px;">
<span style="font-size:var(--font-sm);color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" value="2026-04-22" style="min-width:140px;">
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</button>
<!-- 餐品溯源信息卡 -->
<div class="trace-header">
<div class="trace-header__top">
<div class="trace-header__title">🍳 宫保鸡丁(后厨生产批次)</div>
<div class="trace-header__status"><span class="trace-header__status-dot"></span>全程合格</div>
</div>
<div class="trace-header" id="dim1Header"></div>
<div class="timeline-container">
<div class="pipeline" id="pipelineDim1"></div>
<div class="node-detail-panel active" id="nodeDetailDim1"></div>
<div class="trace-header__grid">
<div class="trace-header__item"><div class="trace-header__label">餐品名称</div><div class="trace-header__value">宫保鸡丁</div></div>
<div class="trace-header__item"><div class="trace-header__label">烹制单号</div><div class="trace-header__value">COOK-20260422-001</div></div>
<div class="trace-header__item"><div class="trace-header__label">烹制时间</div><div class="trace-header__value">2026-04-22 11:00~11:35</div></div>
<div class="trace-header__item"><div class="trace-header__label">烹制厨师</div><div class="trace-header__value">张厨师</div></div>
<div class="trace-header__item"><div class="trace-header__label">食材数量</div><div class="trace-header__value">4 种</div></div>
</div>
</div>
<!-- 食材标签页 -->
<div class="ingredient-tabs" id="ingredientTabs"></div>
<!-- 食材信息卡 -->
<div class="ingredient-info" id="ingredientInfo"></div>
<!-- 共享管道+详情 -->
<div class="timeline-container">
<div class="pipeline" id="pipeline"></div>
<div class="node-detail-panel active" id="nodeDetail"></div>
</div>
</main>
@@ -367,19 +293,17 @@ var INGREDIENT_EMOJIS = ['🥩','🥜','🌶️','🧄'];
/* 当前状态 */
var currentIngIdx = 0;
var currentNodeIdx = 0;
var currentDim = 0;
var currentDim1NodeIdx = 0;
/* ========== 渲染函数 ========== */
/* 渲染管道节点 */
function renderPipeline(containerId, nodes, clickFn) {
var el = document.getElementById(containerId);
function renderPipeline() {
var el = document.getElementById('pipeline');
var nodes = INGREDIENTS[currentIngIdx].nodes;
var html = '';
nodes.forEach(function(n, i) {
var isFirst = (i === 0);
var isLast = (i === nodes.length - 1);
html += '<div class="pipeline-node' + (isFirst ? ' active' : '') + '" onclick="' + clickFn + '(' + i + ')">';
html += '<div class="pipeline-node' + (i === currentNodeIdx ? ' active' : '') + '" onclick="selectNode(' + i + ')">';
html += '<div class="pipeline-node__dot" style="background:' + (NODE_COLORS[n.stage]||'#999') + '">' + (NODE_ICONS[n.stage]||'●') + '</div>';
if (n.badge) html += '<span class="pipeline-node__badge">' + n.badge + '</span>';
html += '<span class="pipeline-node__status pipeline-node__status--' + (n.status||'ok') + '"></span>';
@@ -391,8 +315,8 @@ function renderPipeline(containerId, nodes, clickFn) {
}
/* 渲染节点详情卡片 */
function renderNodeDetail(containerId, node) {
var el = document.getElementById(containerId);
function renderNodeDetail(node) {
var el = document.getElementById('nodeDetail');
var stage = node.stage;
var iconBg = NODE_ICON_BG[stage] || '#F5F5F5';
var iconColor = NODE_COLORS[stage] || '#999';
@@ -434,51 +358,19 @@ function renderIngredientInfo() {
/* ========== 交互函数 ========== */
function switchDimension(idx) {
currentDim = idx;
document.querySelectorAll('.dimension-tab').forEach(function(t, i) { t.classList.toggle('active', i === idx); });
document.querySelectorAll('.dimension-panel').forEach(function(p, i) { p.classList.toggle('active', i === idx); });
if (idx === 1) renderDim1();
}
function switchIngredient(idx) {
currentIngIdx = idx;
currentNodeIdx = 0;
renderIngredientTabs();
renderIngredientInfo();
renderPipeline('pipeline', INGREDIENTS[idx].nodes, 'selectNode');
renderNodeDetail('nodeDetail', INGREDIENTS[idx].nodes[0]);
renderPipeline();
renderNodeDetail(INGREDIENTS[idx].nodes[0]);
}
function selectNode(idx) {
currentNodeIdx = idx;
var pipeline = document.getElementById('pipeline');
pipeline.querySelectorAll('.pipeline-node').forEach(function(n, i) { n.classList.toggle('active', i === idx); });
renderNodeDetail('nodeDetail', INGREDIENTS[currentIngIdx].nodes[idx]);
}
/* 食材维度 - 使用第一个食材(鸡胸肉)的完整链路 */
function renderDim1() {
var ing = INGREDIENTS[0];
var headerEl = document.getElementById('dim1Header');
headerEl.innerHTML = '<div class="trace-header__top"><div class="trace-header__title">🥩 ' + ing.name + '(食材溯源)</div>' +
'<div class="trace-header__status"><span class="trace-header__status-dot"></span>全程合格</div></div>' +
'<div class="trace-header__grid">' +
'<div class="trace-header__item"><div class="trace-header__label">食材名称</div><div class="trace-header__value">' + ing.name + '</div></div>' +
'<div class="trace-header__item"><div class="trace-header__label">溯源码</div><div class="trace-header__value">' + ing.traceCode + '</div></div>' +
'<div class="trace-header__item"><div class="trace-header__label">入库单号</div><div class="trace-header__value">MC-IN-20260415-002</div></div>' +
'<div class="trace-header__item"><div class="trace-header__label">来源类型</div><div class="trace-header__value">' + ing.sourceType + '</div></div>' +
'<div class="trace-header__item"><div class="trace-header__label">供应商</div><div class="trace-header__value">鲜肉直供公司</div></div>' +
'<div class="trace-header__item"><div class="trace-header__label">入库量</div><div class="trace-header__value">80kg</div></div></div>';
renderPipeline('pipelineDim1', ing.nodes, 'selectNodeDim1');
renderNodeDetail('nodeDetailDim1', ing.nodes[0]);
}
function selectNodeDim1(idx) {
currentDim1NodeIdx = idx;
var pipeline = document.getElementById('pipelineDim1');
pipeline.querySelectorAll('.pipeline-node').forEach(function(n, i) { n.classList.toggle('active', i === idx); });
renderNodeDetail('nodeDetailDim1', INGREDIENTS[0].nodes[idx]);
renderPipeline();
renderNodeDetail(INGREDIENTS[currentIngIdx].nodes[idx]);
}
/* 视频弹窗 */
@@ -498,8 +390,8 @@ document.getElementById('videoModal').addEventListener('click', function(e) {
/* 初始化渲染 */
renderIngredientTabs();
renderIngredientInfo();
renderPipeline('pipeline', INGREDIENTS[0].nodes, 'selectNode');
renderNodeDetail('nodeDetail', INGREDIENTS[0].nodes[0]);
renderPipeline();
renderNodeDetail(INGREDIENTS[0].nodes[0]);
</script>
</body>
</html>
@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>营养管理 - 食材链路 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;}
:root{
--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;
--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;--background:#F5F7FA;
--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;
--radius-sm:4px;--radius-md:8px;--radius-lg:12px;
--shadow-sm:0 1px 4px rgba(0,0,0,0.08);--shadow-md:0 4px 12px rgba(0,0,0,0.12);
}
/* 顶部导航 */
.top-bar{display:flex;align-items:center;height:56px;background:#001529;padding:0 24px;position:fixed;top:0;left:0;right:0;z-index:100;}
.top-bar__logo{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
.top-bar__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.top-bar__right .btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.top-bar__right .btn-back:hover{color:#fff;}
.avatar{width:28px;height:28px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;text-transform:uppercase;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.mi{font-size:14px;opacity:0.8;}
.sidebar-item.active .mi{opacity:0.9;}
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 面包屑 */
.breadcrumb{display:flex;align-items:center;gap:8px;margin-bottom:16px;font-size:var(--font-sm);color:var(--text-secondary);}
.breadcrumb a{color:var(--primary);text-decoration:none;}
.breadcrumb a:hover{text-decoration:underline;}
.breadcrumb .sep{color:var(--text-placeholder);}
/* 食材信息卡 */
.ingredient-info{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:24px;flex-wrap:wrap;}
.ingredient-info__name{font-size:var(--font-base);font-weight:600;}
.ingredient-info__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);color:var(--text-secondary);}
.ingredient-info__item span{color:var(--text-primary);}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.tag-green{background:#F6FFED;color:var(--success);}
.ingredient-info__status{margin-left:auto;display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border-radius:12px;font-size:var(--font-xs);font-weight:600;background:#F6FFED;color:var(--success);border:1px solid #B7EB8F;}
.ingredient-info__status-dot{width:6px;height:6px;border-radius:50%;background:var(--success);}
/* 时间线容器 */
.timeline-container{background:#fff;border-radius:var(--radius-md);padding:24px;box-shadow:var(--shadow-sm);}
/* 横向管道概览 */
.pipeline{display:flex;align-items:flex-start;gap:0;overflow-x:auto;padding:16px 0 24px;margin-bottom:24px;position:relative;}
.pipeline-node{display:flex;flex-direction:column;align-items:center;min-width:80px;position:relative;cursor:pointer;}
.pipeline-node__dot{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:15px;color:#fff;position:relative;z-index:2;transition:all .2s;border:2px solid transparent;}
.pipeline-node:hover .pipeline-node__dot{transform:scale(1.15);}
.pipeline-node.active .pipeline-node__dot{border-color:var(--primary);box-shadow:0 0 0 4px rgba(24,144,255,0.15);}
.pipeline-node__line{position:absolute;top:18px;left:calc(50% + 18px);right:calc(-50% + 18px);height:2px;background:var(--border);z-index:1;}
.pipeline-node:last-child .pipeline-node__line{display:none;}
.pipeline-node__label{font-size:10px;color:var(--text-secondary);margin-top:8px;text-align:center;max-width:70px;line-height:1.3;}
.pipeline-node.active .pipeline-node__label{color:var(--primary);font-weight:600;}
/* 分支角标 */
.pipeline-node__badge{position:absolute;top:-4px;right:-4px;background:#FF4D4F;color:#fff;font-size:9px;font-weight:700;padding:1px 4px;border-radius:8px;line-height:1.2;z-index:3;min-width:16px;text-align:center;}
/* 状态小圆点 */
.pipeline-node__status{position:absolute;bottom:-2px;right:0;width:10px;height:10px;border-radius:50%;border:2px solid #fff;z-index:3;}
.pipeline-node__status--ok{background:var(--success);}
.pipeline-node__status--warn{background:var(--warning);}
.pipeline-node__status--error{background:var(--danger);}
/* 节点详情 */
.node-detail-panel{display:none;animation:fadeIn .25s ease;}
.node-detail-panel.active{display:block;}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:translateY(0);}}
.node-card{display:flex;gap:16px;padding:16px;border:1px solid var(--border);border-radius:var(--radius-md);transition:all .2s;margin-bottom:12px;}
.node-card:hover{border-color:var(--primary);box-shadow:0 2px 8px rgba(24,144,255,0.1);}
.node-card__icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}
.node-card__body{flex:1;min-width:0;}
.node-card__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;}
.node-card__stage{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);}
.node-card__time{font-size:var(--font-xs);color:var(--text-placeholder);}
.node-card__title{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:6px;}
.node-card__trace{font-size:var(--font-xs);color:var(--text-placeholder);margin-bottom:8px;}
.node-card__detail{font-size:var(--font-xs);color:var(--text-secondary);line-height:1.8;background:#FAFAFA;border-radius:var(--radius-sm);padding:10px 12px;}
.node-card__video{display:inline-flex;align-items:center;gap:4px;margin-top:10px;padding:4px 12px;border-radius:var(--radius-sm);background:#F0F5FF;color:var(--primary);font-size:var(--font-xs);cursor:pointer;border:1px solid #ADC6FF;transition:all .15s;}
.node-card__video:hover{background:var(--primary-light);border-color:var(--primary);}
/* 视频弹窗 */
.modal-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.55);z-index:1000;align-items:center;justify-content:center;}
.modal-overlay.active{display:flex;}
.modal{background:#fff;border-radius:var(--radius-lg);width:640px;max-width:90vw;box-shadow:0 12px 40px rgba(0,0,0,0.2);overflow:hidden;}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);}
.modal__title{font-size:var(--font-base);font-weight:600;}
.modal__close{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-placeholder);transition:all .15s;}
.modal__close:hover{background:#f5f5f5;color:var(--text-primary);}
.modal__body{padding:20px;text-align:center;}
.modal__video-placeholder{width:100%;height:320px;background:#000;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;color:#fff;font-size:48px;}
.modal__info{margin-top:12px;font-size:var(--font-sm);color:var(--text-secondary);text-align:left;}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="top-bar">
<div class="top-bar__logo">
<svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
营养管理
</div>
<nav class="top-bar__nav">
<a href="trace-query.html" class="active">溯源总览</a>
<a href="arc-employee.html">档案</a>
<a href="farm-land.html">绿色种采</a>
<a href="sup-hygiene-check.html">卫生供应</a>
<a href="prod-qc-daily.html">健康生产</a>
<a href="din-dict-mgmt.html">营养用餐</a>
<a href="ano-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right">
<a class="btn-back" href="../../index.html">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
返回首页
</a>
<div class="avatar"></div>
<span style="color:rgba(255,255,255,0.65);">系统管理员 ▾</span>
</div>
</div>
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">溯源总览</div>
<a href="trace-query.html" class="sidebar-item active"><span class="mi">🥬</span>食材溯源</a>
<a href="trace-dish.html" class="sidebar-item"><span class="mi">🍳</span>餐品溯源</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- 面包屑 -->
<div class="breadcrumb">
<a href="trace-query.html">食材溯源</a>
<span class="sep">/</span>
<span>食材链路</span>
</div>
<!-- 食材信息卡 -->
<div class="ingredient-info">
<div class="ingredient-info__name">🥩 鸡胸肉</div>
<div class="ingredient-info__item">溯源码:<span>ST-EX-XR01-MT-JXRJ-080K-20260415-002</span></div>
<div class="ingredient-info__item">来源类型:<span class="tag tag-blue">外采</span></div>
<div class="ingredient-info__item">供应商:<span>鲜肉直供公司</span></div>
<div class="ingredient-info__item">入库单号:<span>MC-IN-20260415-002</span></div>
<div class="ingredient-info__item">入库量:<span>80kg</span></div>
<div class="ingredient-info__status"><span class="ingredient-info__status-dot"></span>全程合格</div>
</div>
<!-- 时间线容器 -->
<div class="timeline-container">
<div class="pipeline" id="pipeline"></div>
<div class="node-detail-panel active" id="nodeDetail"></div>
</div>
</main>
<!-- 视频弹窗 -->
<div class="modal-overlay" id="videoModal">
<div class="modal">
<div class="modal__header">
<span class="modal__title" id="videoModalTitle">溯源视频</span>
<div class="modal__close" onclick="closeVideoModal()"></div>
</div>
<div class="modal__body">
<div class="modal__video-placeholder"></div>
<div class="modal__info" id="videoModalInfo"></div>
</div>
</div>
</div>
<script>
/* ========== 数据定义 ========== */
/* 节点颜色映射 */
var NODE_COLORS = {
'采供入库':'#52C41A','卫生检测':'#1890FF','毛菜入库':'#13C2C2','毛菜存储':'#FA8C16',
'净菜加工':'#52C41A','净菜入库':'#2F54EB','净菜内供':'#FA541C','净菜进柜':'#722ED1',
'净菜组配':'#EB2F96','按单烹制':'#FAAD14','上餐监控':'#F5222D','余量处置':'#8C8C8C',
'营养检验':'#13C2C2','用户取餐':'#1890FF','种植采收':'#52C41A','农残检测':'#1890FF',
'干货入库':'#13C2C2','干货存储':'#FA8C16','预处理':'#52C41A'
};
var NODE_ICONS = {
'采供入库':'🛒','卫生检测':'🔬','毛菜入库':'📥','毛菜存储':'🏪',
'净菜加工':'🥬','净菜入库':'📦','净菜内供':'🚚','净菜进柜':'🗄️',
'净菜组配':'🔪','按单烹制':'🍳','上餐监控':'🍽️','余量处置':'♻️',
'营养检验':'🧪','用户取餐':'👤','种植采收':'🌱','农残检测':'🔬',
'干货入库':'📥','干货存储':'🏪','预处理':'🥬'
};
var NODE_ICON_BG = {
'采供入库':'#F6FFED','卫生检测':'#E6F7FF','毛菜入库':'#E6FFFB','毛菜存储':'#FFF7E6',
'净菜加工':'#F6FFED','净菜入库':'#F0F5FF','净菜内供':'#FFF2E8','净菜进柜':'#F9F0FF',
'净菜组配':'#FFF0F6','按单烹制':'#FFF7E6','上餐监控':'#FFF1F0','余量处置':'#F5F5F5',
'营养检验':'#E6FFFB','用户取餐':'#E6F7FF','种植采收':'#F6FFED','农残检测':'#E6F7FF',
'干货入库':'#E6FFFB','干货存储':'#FFF7E6','预处理':'#F6FFED'
};
/* 食材完整链路节点(鸡胸肉示例,含多分支) */
var NODES = [
{stage:'采供入库',time:'2026-04-15 09:15',title:'采购入库 · 鲜肉直供公司',trace:'ST-EX-XR01-MT-JXRJ-080K-20260415-002',detail:'品名:鸡胸肉 · 规格:10kg/箱<br>入库量:80kg · 批次号:JXRJ-20260415-002<br>供应商资质:食品经营许可证 有效期至2027-06<br>经办人:赵采购',badge:'',status:'ok',video:['采供入库','2026-04-15 09:15','仓库入口-CAM01','00:02:35']},
{stage:'卫生检测',time:'2026-04-15 10:00',title:'入库检测 · 兽药残留+微生物',trace:'QC-MT-20260415-002',detail:'兽药残留:未检出<br>沙门氏菌:未检出 · 大肠杆菌:<10CFU/g<br>感官检查:色泽正常、弹性良好、无异味<br>检测结论:<strong style="color:#52c41a">合格</strong>',badge:'',status:'ok',video:['卫生检测','2026-04-15 10:00','检测室-CAM02','00:03:10']},
{stage:'毛菜入库',time:'2026-04-15 11:00',title:'毛菜冷库入库',trace:'MC-IN-20260415-002',detail:'入库量:80kg · 库位:MC-A01-03<br>存储温度:0~4℃ · 经办人:王仓管',badge:'',status:'ok',video:['毛菜入库','2026-04-15 11:00','冷库-CAM04','00:01:45']},
{stage:'毛菜存储',time:'2026-04-15 ~ 2026-04-22',title:'冷库存储 · 温度监控',trace:'库位:MC-A01-03',detail:'存储天数:7天 · 温度范围:0~4℃<br>湿度范围:85~90% · 无异常告警<br>剩余库存:32kg(已领用48kg',badge:'',status:'ok',video:['毛菜存储','2026-04-20 08:00','冷库-CAM04','00:01:00']},
{stage:'净菜加工',time:'2026-04-22 07:30',title:'鸡胸肉切丁加工(4条分支)',trace:'NC-JXRJ-20260422-001',detail:'分支A:切丁5kg→宫保鸡丁<br>分支B:切片3kg→辣子鸡<br>分支C:切条2kg→青椒鸡丁<br>分支D:整块1.5kg→白切鸡',badge:'x4',status:'ok',video:['净菜加工','2026-04-22 07:30','净菜间-CAM06','00:03:20']},
{stage:'净菜入库',time:'2026-04-22 08:30',title:'净菜冷柜暂存(3条分支)',trace:'NC-ST-20260422-001',detail:'分支A:切丁5kg → 净菜柜NC-01<br>分支B:切片3kg → 净菜柜NC-02<br>分支C:切条2kg → 净菜柜NC-03',badge:'x3',status:'ok',video:['净菜入库','2026-04-22 08:30','净菜柜-CAM07','00:01:50']},
{stage:'净菜内供',time:'2026-04-22 09:30',title:'净菜领用出库(3条分支)',trace:'NC-OUT-20260422-001',detail:'分支A:切丁5kg → 后厨A组<br>分支B:切片3kg → 后厨B组<br>分支C:切条2kg → 后厨C组',badge:'x3',status:'ok',video:['净菜内供','2026-04-22 09:30','出库口-CAM08','00:02:00']},
{stage:'净菜进柜',time:'2026-04-22 09:45',title:'后厨冷柜暂存(2条分支)',trace:'KC-IN-20260422-001',detail:'分支A:切丁5kg → 后厨冷柜KC-01<br>分支B:切片3kg → 后厨冷柜KC-02',badge:'x2',status:'ok',video:['净菜进柜','2026-04-22 09:45','后厨柜-CAM08','00:01:20']},
{stage:'净菜组配',time:'2026-04-22 10:20',title:'菜品组配(2条分支)',trace:'CMB-20260422-001',detail:'分支ACMB-001 宫保鸡丁 5.0kg · 李组配<br>分支BCMB-006 辣子鸡 3.5kg · 张组配',badge:'x2',status:'ok',video:['净菜组配','2026-04-22 10:20','组配间-CAM09','00:02:40']},
{stage:'按单烹制',time:'2026-04-22 11:00 ~ 11:35',title:'烹制任务 · 多锅次烹制(3条分支)',trace:'COOK-20260422-001',detail:'分支ACOOK-001(宫保鸡丁)张厨师 2锅<br>分支BCOOK-009(辣子鸡)王厨师 1锅<br>分支CCOOK-015(青椒鸡丁)李厨师 1锅',badge:'x3',status:'ok',video:['按单烹制','2026-04-22 11:00','后厨-CAM10','00:04:20']},
{stage:'上餐监控',time:'2026-04-22 11:30 ~ 12:00',title:'上餐批次 · 多餐线分发(3条分支)',trace:'SERVE-20260422-001',detail:'分支ASERVE-001 → A区中餐线 6.8kg<br>分支BSERVE-007 → B区中餐线 1.4kg<br>分支CSERVE-015 → C区中餐线 2.1kg',badge:'x3',status:'ok',video:['上餐监控','2026-04-22 11:30','餐线-CAM11','00:03:05']},
{stage:'余量处置',time:'2026-04-22 13:00 ~ 13:20',title:'余量处置 · 多餐线余量登记(3条分支)',trace:'DISP-20260422-001',detail:'分支A:余量0.3kg → 冷藏留用<br>分支B:余量0.1kg → 废弃销毁<br>分支C:余量0.2kg → 废弃销毁<br>经办人:李处置',badge:'x3',status:'ok',video:['余量处置','2026-04-22 13:00','余量区-CAM12','00:01:30']},
{stage:'营养检验',time:'2026-04-22 13:30',title:'营养检测 · 成品抽检',trace:'NUT-20260422-001',detail:'COOK-001(宫保鸡丁)检测单号:NUT-20260422-001<br>热量:186kcal/100g · 蛋白质:18.2g · 脂肪:9.4g · 碳水:8.1g<br>检测结论:<strong style="color:#52c41a">达标</strong>',badge:'',status:'ok',video:['营养检验','2026-04-22 13:30','检测室-CAM13','00:02:15']},
{stage:'用户取餐',time:'2026-04-22 11:40 ~ 12:30',title:'关联订单食材映射',trace:'ORD-20260422-021',detail:'关联订单:SO20260422021#1、SO20260422087#2、SO20260422103#1<br>用于召回时可直接反查受影响订单范围',badge:'',status:'ok',video:['用户取餐','2026-04-22 11:40','取餐口-CAM14','00:01:50']}
];
/* 当前节点索引 */
var currentNodeIdx = 0;
/* ========== 渲染函数 ========== */
/* 渲染管道节点 */
function renderPipeline() {
var el = document.getElementById('pipeline');
var html = '';
NODES.forEach(function(n, i) {
var isLast = (i === NODES.length - 1);
html += '<div class="pipeline-node' + (i === currentNodeIdx ? ' active' : '') + '" onclick="selectNode(' + i + ')">';
html += '<div class="pipeline-node__dot" style="background:' + (NODE_COLORS[n.stage]||'#999') + '">' + (NODE_ICONS[n.stage]||'●') + '</div>';
if (n.badge) html += '<span class="pipeline-node__badge">' + n.badge + '</span>';
html += '<span class="pipeline-node__status pipeline-node__status--' + (n.status||'ok') + '"></span>';
if (!isLast) html += '<div class="pipeline-node__line"></div>';
html += '<div class="pipeline-node__label">' + n.stage + '</div>';
html += '</div>';
});
el.innerHTML = html;
}
/* 渲染节点详情卡片 */
function renderNodeDetail(node) {
var el = document.getElementById('nodeDetail');
var stage = node.stage;
var iconBg = NODE_ICON_BG[stage] || '#F5F5F5';
var iconColor = NODE_COLORS[stage] || '#999';
var icon = NODE_ICONS[stage] || '●';
var html = '<div class="node-card">';
html += '<div class="node-card__icon" style="background:' + iconBg + ';color:' + iconColor + ';">' + icon + '</div>';
html += '<div class="node-card__body">';
html += '<div class="node-card__header"><span class="node-card__stage">' + icon + ' ' + stage + '</span><span class="node-card__time">' + node.time + '</span></div>';
html += '<div class="node-card__title">' + node.title + '</div>';
html += '<div class="node-card__trace">溯源码:' + node.trace + '</div>';
html += '<div class="node-card__detail">' + node.detail + '</div>';
if (node.video) {
html += '<div class="node-card__video" onclick="openVideoModal(\'' + node.video[0] + '\',\'' + node.video[1] + '\',\'' + node.video[2] + '\',\'' + node.video[3] + '\')">🎬 查看视频</div>';
}
html += '</div></div>';
el.innerHTML = html;
}
/* ========== 交互函数 ========== */
function selectNode(idx) {
currentNodeIdx = idx;
renderPipeline();
renderNodeDetail(NODES[idx]);
}
/* 视频弹窗 */
function openVideoModal(stage, time, camera, duration) {
document.getElementById('videoModalTitle').textContent = stage + ' - 溯源视频';
document.getElementById('videoModalInfo').innerHTML =
'<p>环节:' + stage + ' · 时间:' + time + '</p><p>摄像头:' + camera + ' · 时长:' + duration + '</p>';
document.getElementById('videoModal').classList.add('active');
}
function closeVideoModal() {
document.getElementById('videoModal').classList.remove('active');
}
document.getElementById('videoModal').addEventListener('click', function(e) {
if (e.target === this) closeVideoModal();
});
/* 初始化渲染 */
renderPipeline();
renderNodeDetail(NODES[0]);
</script>
</body>
</html>