feat:食堂中转缓存服务器需求初稿设计,同时设计四个后台监控界面

This commit is contained in:
fengpu
2026-06-23 18:56:53 +08:00
parent c62a1b1a31
commit 750543ee14
6 changed files with 2278 additions and 0 deletions
+242
View File
@@ -0,0 +1,242 @@
<!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;--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;--radius-sm:4px;--radius-md:8px;--shadow-sm:0 1px 4px rgba(0,0,0,.08);--shadow-md:0 4px 12px rgba(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,.65);text-decoration:none;padding:6px 14px;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,.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,.65);font-size:var(--font-sm);}
.btn-back{color:rgba(255,255,255,.65);text-decoration:none;}
.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);}
.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,.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,.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:.75;margin-right:2px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:18px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header__title{font-size:var(--font-xl);font-weight:700;}
.page-header__desc{margin-top:6px;font-size:var(--font-sm);color:var(--text-secondary);}
.page-header__actions{display:flex;gap:8px;align-items:center;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}
.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.stats-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 18px;}
.stat-card__value{font-size:var(--font-xxl);font-weight:700;line-height:1;}
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);margin-top:8px;}
.layout-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:16px;margin-bottom:16px;}
.card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);}
.card__title{font-size:var(--font-base);font-weight:600;}
.card__body{padding:16px 20px;}
.category-list{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
.category-card{border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;background:#FAFCFF;}
.category-card__name{font-size:var(--font-sm);font-weight:600;}
.category-card__count{font-size:var(--font-xl);font-weight:700;margin-top:8px;}
.process-list{display:flex;flex-direction:column;gap:12px;}
.process-item{display:grid;grid-template-columns:100px 1fr 80px;gap:12px;align-items:center;}
.process-label{font-size:var(--font-sm);color:var(--text-secondary);}
.process-bar{height:8px;background:#F0F2F5;border-radius:4px;overflow:hidden;}
.process-bar span{display:block;height:100%;background:var(--primary);border-radius:4px;}
.process-num{font-size:var(--font-sm);font-weight:600;text-align:right;}
.filter-bar{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:16px;}
.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);outline:none;min-width:160px;}
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;min-width:120px;background:#fff;}
.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 20px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-base);font-weight:600;}
.table-wrap{overflow-x:auto;}
.table-wrap table{min-width:1500px;}
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;vertical-align:middle;}
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,.04);z-index:2;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.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;}
.drawer-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:none;}
.drawer-mask.show{display:block;}
.drawer{position:absolute;right:0;top:0;bottom:0;width:560px;background:#fff;box-shadow:-6px 0 24px rgba(0,0,0,.12);display:flex;flex-direction:column;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--border);}
.drawer__title{font-size:var(--font-lg);font-weight:600;}
.drawer__close{border:none;background:none;font-size:22px;cursor:pointer;color:var(--text-placeholder);}
.drawer__body{padding:20px 24px;overflow-y:auto;flex:1;}
.drawer-section{margin-bottom:20px;}
.drawer-section__title{font-size:var(--font-sm);font-weight:600;margin-bottom:10px;padding-left:8px;border-left:3px solid var(--primary);}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 18px;}
.detail-item{display:flex;flex-direction:column;gap:3px;}
.detail-label{font-size:var(--font-xs);color:var(--text-placeholder);}
.detail-value{font-size:var(--font-sm);color:var(--text-primary);}
.timeline{display:flex;flex-direction:column;gap:12px;}
.timeline-item{border-left:2px solid var(--primary-light);padding-left:12px;font-size:var(--font-sm);}
.timeline-time{color:var(--text-placeholder);font-size:var(--font-xs);margin-bottom:4px;}
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-12px);background:#fff;border-left:4px solid var(--primary);box-shadow:var(--shadow-md);padding:10px 18px;border-radius:var(--radius-sm);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .25s;z-index:1200;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
</style>
</head>
<body>
<header 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">溯源总览</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><a href="edge-node-list.html" class="active">边缘服务器</a></nav>
<div class="top-bar__right"><a href="../../index.html" class="btn-back">← 返回首页</a><div class="avatar"></div><span>系统管理员 ▾</span></div>
</header>
<aside class="sidebar">
<div class="sidebar-menu__title">边缘服务器</div>
<a href="edge-alarm-center.html" class="sidebar-item active"><span class="mi"></span>告警中心</a>
<a href="edge-sync-monitor.html" class="sidebar-item"><span class="mi">🔄</span>同步监控</a>
<a href="edge-dispatch-compensation.html" class="sidebar-item"><span class="mi">📦</span>下发与补偿</a>
<a href="edge-node-list.html" class="sidebar-item"><span class="mi">🧭</span>边缘服务器列表</a>
</aside>
<main class="main">
<section class="page-header">
<div><div class="page-header__title">上报故障告警中心</div><div class="page-header__desc">默认故障入口,集中识别食堂上报故障、服务器离线、数据断流和恢复异常,形成分派、跟踪、恢复闭环。</div></div>
<div class="page-header__actions"><button class="btn btn-default" onclick="showToast('正在导出告警')">导出告警</button><button class="btn btn-primary" onclick="showToast('已刷新告警状态')">刷新状态</button></div>
</section>
<section class="stats-grid">
<div class="stat-card"><div class="stat-card__value">6</div><div class="stat-card__label">故障食堂数</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--danger)">3</div><div class="stat-card__label">数据断流食堂</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--danger)">8</div><div class="stat-card__label">严重故障</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--warning)">19</div><div class="stat-card__label">待处理故障</div></div>
<div class="stat-card"><div class="stat-card__value">7</div><div class="stat-card__label">待指派</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--success)">36</div><div class="stat-card__label">今日已恢复</div></div>
</section>
<section class="layout-grid">
<div class="card">
<div class="card__header"><div class="card__title">故障分类</div><span class="tag tag-orange">未恢复</span></div>
<div class="card__body">
<div class="category-list">
<div class="category-card"><div class="category-card__name">服务器类</div><div class="category-card__count" style="color:var(--danger)">7</div></div>
<div class="category-card"><div class="category-card__name">上报连接类</div><div class="category-card__count" style="color:var(--warning)">5</div></div>
<div class="category-card"><div class="category-card__name">设备类</div><div class="category-card__count">9</div></div>
<div class="category-card"><div class="category-card__name">上报同步类</div><div class="category-card__count" style="color:var(--warning)">13</div></div>
<div class="category-card"><div class="category-card__name">上报数据类</div><div class="category-card__count">4</div></div>
<div class="category-card"><div class="category-card__name">升级类</div><div class="category-card__count" style="color:var(--danger)">4</div></div>
</div>
</div>
</div>
<div class="card">
<div class="card__header"><div class="card__title">处理状态分布</div><span class="tag tag-blue">当前周期</span></div>
<div class="card__body">
<div class="process-list">
<div class="process-item"><div class="process-label">待处理</div><div class="process-bar"><span style="width:45%;background:var(--warning)"></span></div><div class="process-num">19</div></div>
<div class="process-item"><div class="process-label">处理中</div><div class="process-bar"><span style="width:33%;background:var(--primary)"></span></div><div class="process-num">14</div></div>
<div class="process-item"><div class="process-label">已恢复</div><div class="process-bar"><span style="width:86%;background:var(--success)"></span></div><div class="process-num">36</div></div>
<div class="process-item"><div class="process-label">已忽略</div><div class="process-bar"><span style="width:12%;background:#999"></span></div><div class="process-num">5</div></div>
</div>
</div>
</div>
</section>
<section class="filter-bar">
<div class="filter-item"><label>告警类型</label><select class="filter-select"><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></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><option>已忽略</option></select></div>
<div class="filter-item"><label>关键字</label><input class="filter-input" placeholder="告警编号 / 服务器 / 对象"></div>
<button class="btn btn-primary" onclick="showToast('已查询告警')">查询</button>
<button class="btn btn-default" onclick="showToast('已重置筛选条件')">重置</button>
</section>
<section class="table-card">
<div class="table-card__header"><div class="table-card__title">告警列表</div><button class="btn btn-default" onclick="showToast('已批量标记处理中')">批量标记处理中</button></div>
<div class="table-wrap">
<table>
<thead><tr><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>ALM-20260623-081</td><td>服务器类</td><td>服务器离线</td><td><span class="tag tag-red">严重</span></td><td>炼化中心食堂</td><td>炼化中心餐线服务器</td><td>全部现场设备</td><td>2026-06-23 13:54:09</td><td>2026-06-23 14:32:00</td><td>38 分钟</td><td><span class="tag tag-orange">待处理</span></td><td>未指派</td><td class="td-actions"><a class="link" onclick="openDrawer('ALM-20260623-081')">详情</a> <a class="link" href="edge-sync-monitor.html">查看链路</a> <a class="link" href="edge-node-detail.html">服务器详情</a></td></tr>
<tr><td>ALM-20260623-078</td><td>同步类</td><td>上行积压超 30 分钟</td><td><span class="tag tag-orange">警告</span></td><td>采气一厂食堂</td><td>采气一厂后厨服务器</td><td>就餐流水 / 称重记录</td><td>2026-06-23 13:45:00</td><td>2026-06-23 14:28:00</td><td>47 分钟</td><td><span class="tag tag-blue">处理中</span></td><td>运维张工</td><td class="td-actions"><a class="link" onclick="openDrawer('ALM-20260623-078')">详情</a> <a class="link" href="edge-sync-monitor.html">查看链路</a> <a class="link" onclick="showToast('已标记恢复')">标记恢复</a></td></tr>
<tr><td>ALM-20260623-072</td><td>数据类</td><td>人脸版本落后</td><td><span class="tag tag-orange">警告</span></td><td>采气一厂食堂</td><td>采气一厂后厨服务器</td><td>FACE-BATCH-0623-04</td><td>2026-06-23 10:12:00</td><td>2026-06-23 14:20:00</td><td>4 小时</td><td><span class="tag tag-orange">待处理</span></td><td>未指派</td><td class="td-actions"><a class="link" onclick="openDrawer('ALM-20260623-072')">详情</a> <a class="link" href="edge-dispatch-compensation.html">发起补偿</a> <a class="link" onclick="ignoreAlarm()">忽略</a></td></tr>
<tr><td>ALM-20260623-066</td><td>升级类</td><td>设备未执行升级</td><td><span class="tag tag-red">严重</span></td><td>炼化中心食堂</td><td>炼化中心餐线服务器</td><td>餐线营养秤 02</td><td>2026-06-23 11:10:00</td><td>2026-06-23 14:00:00</td><td>2 小时 50 分钟</td><td><span class="tag tag-orange">待处理</span></td><td>未指派</td><td class="td-actions"><a class="link" onclick="openDrawer('ALM-20260623-066')">详情</a> <a class="link" href="edge-dispatch-compensation.html">关联补偿</a></td></tr>
<tr><td>ALM-20260623-049</td><td>设备类</td><td>设备离线</td><td><span class="tag tag-orange">警告</span></td><td>输气站小食堂</td><td>输气站小食堂服务器</td><td>结算终端 01</td><td>2026-06-23 09:28:00</td><td>2026-06-23 09:48:00</td><td>20 分钟</td><td><span class="tag tag-green">已恢复</span></td><td>运维李工</td><td class="td-actions"><a class="link" onclick="openDrawer('ALM-20260623-049')">详情</a></td></tr>
</tbody>
</table>
</div>
</section>
</main>
<div class="drawer-mask" id="drawerMask">
<div class="drawer">
<div class="drawer__header"><div class="drawer__title">告警详情</div><button class="drawer__close" onclick="closeDrawer()">×</button></div>
<div class="drawer__body">
<div class="drawer-section">
<div class="drawer-section__title">基础信息</div>
<div class="detail-grid">
<div class="detail-item"><div class="detail-label">告警编号</div><div class="detail-value" id="alarmNo">ALM-20260623-081</div></div>
<div class="detail-item"><div class="detail-label">告警等级</div><div class="detail-value"><span class="tag tag-red">严重</span></div></div>
<div class="detail-item"><div class="detail-label">所属食堂</div><div class="detail-value">炼化中心食堂</div></div>
<div class="detail-item"><div class="detail-label">所属服务器</div><div class="detail-value">炼化中心餐线服务器</div></div>
<div class="detail-item"><div class="detail-label">受影响对象</div><div class="detail-value">全部现场设备</div></div>
<div class="detail-item"><div class="detail-label">当前状态</div><div class="detail-value"><span class="tag tag-orange">待处理</span></div></div>
</div>
</div>
<div class="drawer-section">
<div class="drawer-section__title">处理轨迹</div>
<div class="timeline">
<div class="timeline-item"><div class="timeline-time">2026-06-23 14:32:00</div><div>系统合并同类告警,持续时间更新为 38 分钟。</div></div>
<div class="timeline-item"><div class="timeline-time">2026-06-23 14:10:00</div><div>自动重连检测失败,等待人工介入。</div></div>
<div class="timeline-item"><div class="timeline-time">2026-06-23 13:54:09</div><div>服务器心跳超阈值未上报,触发严重告警。</div></div>
</div>
</div>
<div class="drawer-section">
<div class="drawer-section__title">处理操作</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-primary" onclick="showToast('已标记处理中')">标记处理中</button>
<button class="btn btn-default" onclick="showToast('已指派处理人')">指派处理人</button>
<button class="btn btn-default" onclick="showToast('已标记恢复')">标记已恢复</button>
<button class="btn btn-default" onclick="location.href='edge-dispatch-compensation.html'">发起关联补偿</button>
</div>
</div>
</div>
</div>
</div>
<div class="toast" id="toast">操作已完成</div>
<script>
function showToast(text){
var toast=document.getElementById('toast');
toast.textContent=text;
toast.classList.add('show');
setTimeout(function(){toast.classList.remove('show');},1800);
}
function openDrawer(no){
document.getElementById('alarmNo').textContent=no;
document.getElementById('drawerMask').classList.add('show');
}
function closeDrawer(){
document.getElementById('drawerMask').classList.remove('show');
}
function ignoreAlarm(){
if(window.confirm('确认忽略该告警?忽略不代表问题已解决,需要记录原因。')){showToast('告警已标记忽略');}
}
</script>
</body>
</html>
@@ -0,0 +1,199 @@
<!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;--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;--radius-sm:4px;--radius-md:8px;--shadow-sm:0 1px 4px rgba(0,0,0,.08);--shadow-md:0 4px 12px rgba(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,.65);text-decoration:none;padding:6px 14px;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,.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,.65);font-size:var(--font-sm);}
.btn-back{color:rgba(255,255,255,.65);text-decoration:none;}
.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);}
.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,.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,.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:.75;margin-right:2px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:18px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header__title{font-size:var(--font-xl);font-weight:700;}
.page-header__desc{margin-top:6px;font-size:var(--font-sm);color:var(--text-secondary);}
.page-header__actions{display:flex;gap:8px;align-items:center;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}
.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);}
.stats-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 18px;}
.stat-card__value{font-size:var(--font-xxl);font-weight:700;line-height:1;}
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);margin-top:8px;}
.tabs{display:flex;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);overflow:hidden;}
.tab{padding:14px 22px;font-size:var(--font-sm);cursor:pointer;border-bottom:2px solid transparent;color:var(--text-secondary);}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-panel{display:none;background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;}
.tab-panel.active{display:block;}
.filter-bar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:16px;}
.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);outline:none;min-width:160px;}
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;min-width:120px;background:#fff;}
.table-wrap{overflow-x:auto;}
.table-wrap table{min-width:1360px;}
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;vertical-align:middle;}
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,.04);z-index:2;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.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;}
.split-layout{display:grid;grid-template-columns:1.3fr .7fr;gap:16px;margin-top:16px;}
.card{background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;}
.card__header{padding:14px 16px;border-bottom:1px solid var(--border);font-size:var(--font-base);font-weight:600;display:flex;justify-content:space-between;align-items:center;}
.card__body{padding:16px;}
.step-list{display:flex;flex-direction:column;gap:12px;}
.step-item{display:grid;grid-template-columns:28px 1fr;gap:10px;align-items:start;}
.step-num{width:24px;height:24px;border-radius:50%;background:var(--primary-light);color:var(--primary);display:flex;align-items:center;justify-content:center;font-size:var(--font-xs);font-weight:700;}
.step-title{font-size:var(--font-sm);font-weight:600;}
.step-desc{font-size:var(--font-xs);color:var(--text-secondary);margin-top:4px;line-height:1.6;}
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-12px);background:#fff;border-left:4px solid var(--primary);box-shadow:var(--shadow-md);padding:10px 18px;border-radius:var(--radius-sm);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .25s;z-index:1200;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
</style>
</head>
<body>
<header 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">溯源总览</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><a href="edge-node-list.html" class="active">边缘服务器</a></nav>
<div class="top-bar__right"><a href="../../index.html" class="btn-back">← 返回首页</a><div class="avatar"></div><span>系统管理员 ▾</span></div>
</header>
<aside class="sidebar">
<div class="sidebar-menu__title">边缘服务器</div>
<a href="edge-alarm-center.html" class="sidebar-item"><span class="mi"></span>告警中心</a>
<a href="edge-sync-monitor.html" class="sidebar-item"><span class="mi">🔄</span>同步监控</a>
<a href="edge-dispatch-compensation.html" class="sidebar-item active"><span class="mi">📦</span>下发与补偿</a>
<a href="edge-node-list.html" class="sidebar-item"><span class="mi">🧭</span>边缘服务器列表</a>
</aside>
<main class="main">
<section class="page-header">
<div><div class="page-header__title">故障恢复与补偿</div><div class="page-header__desc">针对食堂上报故障后的恢复动作,跟踪补发、重试、补偿和最终恢复结果。</div></div>
<div class="page-header__actions"><button class="btn btn-default" onclick="showToast('正在导出下发记录')">导出记录</button><button class="btn btn-primary" onclick="showToast('已创建补偿批次草稿')">新建补偿批次</button></div>
</section>
<section class="stats-grid">
<div class="stat-card"><div class="stat-card__value">126</div><div class="stat-card__label">今日恢复任务</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--success)">118</div><div class="stat-card__label">恢复成功批次</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--warning)">6</div><div class="stat-card__label">部分恢复成功</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--danger)">2</div><div class="stat-card__label">恢复失败批次</div></div>
<div class="stat-card"><div class="stat-card__value">14</div><div class="stat-card__label">补偿处理中</div></div>
</section>
<section class="tabs">
<div class="tab active" onclick="switchTab('dispatch',this)">恢复下发记录</div>
<div class="tab" onclick="switchTab('compensation',this)">补偿处理记录</div>
</section>
<section id="dispatch" class="tab-panel active">
<div class="filter-bar">
<div class="filter-item"><label>下发类型</label><select class="filter-select"><option>全部</option><option>基础资料</option><option>人脸批次</option><option>生产任务</option><option>升级任务</option><option>文件缓存</option></select></div>
<div class="filter-item"><label>目标食堂</label><select class="filter-select"><option>全部</option><option>采气一厂食堂</option><option>炼化中心食堂</option></select></div>
<div class="filter-item"><label>当前状态</label><select class="filter-select"><option>全部</option><option>已完成</option><option>部分失败</option><option>下发失败</option><option>下发中</option></select></div>
<div class="filter-item"><label>批次号</label><input class="filter-input" placeholder="请输入批次号"></div>
<button class="btn btn-primary" onclick="showToast('已查询下发记录')">查询</button>
<button class="btn btn-default" onclick="showToast('已重置筛选条件')">重置</button>
</div>
<div class="table-wrap">
<table>
<thead><tr><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>FACE-BATCH-0623-04</td><td>人脸批次</td><td>采气一厂食堂</td><td>采气一厂后厨服务器</td><td>人脸增量 326 人</td><td>系统自动</td><td>2026-06-23 10:00:00</td><td><span class="tag tag-red">下发失败</span></td><td>1</td><td>0</td><td class="td-actions"><a class="link" href="edge-alarm-center.html">查看故障</a> <a class="link" onclick="showToast('已发起单服务器重新下发')">单服务器重发</a></td></tr>
<tr><td>UPG-20260623-002</td><td>升级任务</td><td>炼化中心食堂</td><td>炼化中心餐线服务器</td><td>餐线营养秤 v1.8.2</td><td>运维李工</td><td>2026-06-23 11:00:00</td><td><span class="tag tag-orange">部分失败</span></td><td>2</td><td>14</td><td class="td-actions"><a class="link" href="edge-alarm-center.html">查看故障</a> <a class="link" onclick="showToast('已重新通知离线设备')">重新通知设备</a></td></tr>
<tr><td>TASK-20260623-021</td><td>生产任务</td><td>总部一号食堂</td><td>总部一号食堂服务器</td><td>净菜组配任务 18 条</td><td>营养运营</td><td>2026-06-23 09:12:00</td><td><span class="tag tag-green">已完成</span></td><td>0</td><td>1</td><td class="td-actions"><a class="link">查看回执</a></td></tr>
<tr><td>BASE-20260623-003</td><td>基础资料</td><td>全部食堂</td><td>28 个服务器</td><td>菜品/食材/物料更新</td><td>系统自动</td><td>2026-06-23 09:30:00</td><td><span class="tag tag-green">已完成</span></td><td>0</td><td>28</td><td class="td-actions"><a class="link">查看回执</a></td></tr>
</tbody>
</table>
</div>
<div class="split-layout">
<div class="card">
<div class="card__header">恢复失败明细 <span class="tag tag-orange">示例选中:UPG-20260623-002</span></div>
<div class="card__body table-wrap">
<table style="min-width:760px"><thead><tr><th>服务器</th><th>设备</th><th>失败原因</th><th>最近处理</th><th>操作</th></tr></thead><tbody>
<tr><td>炼化中心餐线服务器</td><td>餐线营养秤 02</td><td>设备离线</td><td>2026-06-23 14:00:00</td><td class="td-actions"><a class="link" onclick="showToast('已重新通知设备')">重新通知</a></td></tr>
<tr><td>炼化中心餐线服务器</td><td>结算终端 01</td><td>升级包校验失败</td><td>2026-06-23 13:58:21</td><td class="td-actions"><a class="link" onclick="showToast('已重新分发升级包')">重新分发包</a></td></tr>
</tbody></table>
</div>
</div>
<div class="card">
<div class="card__header">恢复补偿规则</div>
<div class="card__body">
<div class="step-list">
<div class="step-item"><div class="step-num">1</div><div><div class="step-title">避免覆盖成功服务器</div><div class="step-desc">默认只对失败服务器重新下发,强制覆盖需单独确认。</div></div></div>
<div class="step-item"><div class="step-num">2</div><div><div class="step-title">校验任务状态</div><div class="step-desc">生产任务已开始或已完成时,进入异常纠错而非直接覆盖。</div></div></div>
<div class="step-item"><div class="step-num">3</div><div><div class="step-title">区分升级补偿</div><div class="step-desc">升级失败可选择重新通知设备或重新分发升级包。</div></div></div>
</div>
</div>
</div>
</div>
</section>
<section id="compensation" class="tab-panel">
<div class="filter-bar">
<div class="filter-item"><label>补偿方式</label><select class="filter-select"><option>全部</option><option>单服务器重新下发</option><option>批量重新下发</option><option>跳过失败服务器</option></select></div>
<div class="filter-item"><label>补偿结果</label><select class="filter-select"><option>全部</option><option>成功</option><option>失败</option><option>处理中</option><option>已终止</option></select></div>
<div class="filter-item"><label>原始批次</label><input class="filter-input" placeholder="请输入原始批次号"></div>
<button class="btn btn-primary" onclick="showToast('已查询补偿记录')">查询</button>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>补偿单号</th><th>原始失败记录</th><th>补偿方式</th><th>补偿发起人</th><th>补偿时间</th><th>补偿结果</th><th>备注</th><th>操作</th></tr></thead>
<tbody>
<tr><td>CMP-20260623-009</td><td>FACE-BATCH-0623-04</td><td>单服务器重新下发</td><td>运维张工</td><td>2026-06-23 14:20:00</td><td><span class="tag tag-orange">处理中</span></td><td>等待服务器恢复 MQ 连接</td><td class="td-actions"><a class="link" href="edge-sync-monitor.html">查看链路</a> <a class="link" onclick="confirmStop()">终止补偿</a></td></tr>
<tr><td>CMP-20260623-006</td><td>UPG-20260623-002</td><td>重新通知设备</td><td>运维李工</td><td>2026-06-23 13:10:00</td><td><span class="tag tag-red">失败</span></td><td>设备仍离线</td><td class="td-actions"><a class="link" onclick="showToast('已重新生成补偿批次')">重新生成批次</a></td></tr>
<tr><td>CMP-20260622-018</td><td>BASE-20260622-011</td><td>批量重新下发</td><td>系统管理员</td><td>2026-06-22 18:20:00</td><td><span class="tag tag-green">成功</span></td><td>3 个服务器补偿完成</td><td class="td-actions"><a class="link">查看回执</a></td></tr>
</tbody>
</table>
</div>
</section>
</main>
<div class="toast" id="toast">操作已完成</div>
<script>
function switchTab(id,el){
document.querySelectorAll('.tab').forEach(function(tab){tab.classList.remove('active');});
document.querySelectorAll('.tab-panel').forEach(function(panel){panel.classList.remove('active');});
el.classList.add('active');
document.getElementById(id).classList.add('active');
}
function showToast(text){
var toast=document.getElementById('toast');
toast.textContent=text;
toast.classList.add('show');
setTimeout(function(){toast.classList.remove('show');},1800);
}
function confirmStop(){
if(window.confirm('确认终止该补偿单?终止后需记录处理原因。')){showToast('补偿单已终止');}
}
</script>
</body>
</html>
+243
View File
@@ -0,0 +1,243 @@
<!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;--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 14px;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);}
.btn-back{color:rgba(255,255,255,0.65);text-decoration:none;}
.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);}
.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:.75;margin-right:2px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.breadcrumb{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:12px;}
.breadcrumb a{color:var(--primary);text-decoration:none;}
.page-header{display:flex;align-items:flex-start;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header__title{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
.page-header__desc{margin-top:6px;font-size:var(--font-sm);color:var(--text-secondary);}
.page-header__actions{display:flex;align-items:center;gap:8px;}
.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;white-space:nowrap;}
.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);}
.overview-grid{display:grid;grid-template-columns:2fr 1fr;gap:16px;margin-bottom:16px;}
.card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);}
.card__title{font-size:var(--font-base);font-weight:600;}
.card__body{padding:18px 20px;}
.info-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px 24px;}
.info-item{display:flex;flex-direction:column;gap:4px;}
.info-label{font-size:var(--font-xs);color:var(--text-placeholder);}
.info-value{font-size:var(--font-sm);color:var(--text-primary);font-weight:500;min-height:22px;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);width:max-content;}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.tag-orange{background:#FFF7E6;color:var(--warning);}
.tag-red{background:#FFF1F0;color:var(--danger);}
.tag-gray{background:#F5F5F5;color:#999;}
.resource-list{display:flex;flex-direction:column;gap:14px;}
.resource-row{display:grid;grid-template-columns:60px 1fr 48px;align-items:center;gap:10px;font-size:var(--font-sm);}
.bar{height:8px;background:#F0F2F5;border-radius:4px;overflow:hidden;}
.bar span{display:block;height:100%;border-radius:4px;background:var(--primary);}
.bar.warning span{background:var(--warning);}
.bar.danger span{background:var(--danger);}
.tabs{display:flex;align-items:center;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);overflow:hidden;}
.tab{padding:14px 22px;font-size:var(--font-sm);cursor:pointer;border-bottom:2px solid transparent;color:var(--text-secondary);}
.tab:hover{color:var(--primary);}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-panel{display:none;background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;}
.tab-panel.active{display:block;}
.table-wrap{overflow-x:auto;}
.table-wrap table{min-width:1100px;}
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;vertical-align:middle;}
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;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;}
.link:hover{color:#40a9ff;}
.timeline{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:18px;}
.mini-stat{border:1px solid var(--border);border-radius:var(--radius-md);padding:14px 16px;background:#FAFCFF;}
.mini-stat__value{font-size:var(--font-xl);font-weight:700;}
.mini-stat__label{font-size:var(--font-sm);color:var(--text-secondary);margin-top:5px;}
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-12px);background:#fff;border-left:4px solid var(--primary);box-shadow:var(--shadow-md);padding:10px 18px;border-radius:var(--radius-sm);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .25s;z-index:1200;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
</style>
</head>
<body>
<header 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">溯源总览</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><a href="edge-node-list.html" class="active">边缘服务器</a>
</nav>
<div class="top-bar__right"><a href="../../index.html" class="btn-back">← 返回首页</a><div class="avatar"></div><span>系统管理员 ▾</span></div>
</header>
<aside class="sidebar">
<div class="sidebar-menu__title">边缘服务器</div>
<a href="edge-alarm-center.html" class="sidebar-item"><span class="mi"></span>告警中心</a>
<a href="edge-sync-monitor.html" class="sidebar-item"><span class="mi">🔄</span>同步监控</a>
<a href="edge-dispatch-compensation.html" class="sidebar-item"><span class="mi">📦</span>下发与补偿</a>
<a href="edge-node-list.html" class="sidebar-item active"><span class="mi">🧭</span>边缘服务器列表</a>
</aside>
<main class="main">
<div class="breadcrumb"><a href="edge-alarm-center.html">告警中心</a> / <a href="edge-node-list.html">边缘服务器列表</a> / 服务器故障详情</div>
<section class="page-header">
<div>
<div class="page-header__title">采气一厂后厨服务器</div>
<div class="page-header__desc">EDGE-CQ-017 · 采气一厂食堂 · 后厨机房 · 当前处于上报故障排查中 · 最近心跳 2026-06-23 14:31:58</div>
</div>
<div class="page-header__actions">
<button class="btn btn-default" onclick="location.href='edge-alarm-center.html'">返回告警中心</button>
<button class="btn btn-default" onclick="location.href='edge-sync-monitor.html'">查看同步链路</button>
<button class="btn btn-primary" onclick="location.href='edge-dispatch-compensation.html'">发起恢复补偿</button>
</div>
</section>
<section class="overview-grid">
<div class="card">
<div class="card__header"><div class="card__title">基本信息</div><span class="tag tag-orange">健康告警</span></div>
<div class="card__body">
<div class="info-grid">
<div class="info-item"><div class="info-label">服务器名称</div><div class="info-value">采气一厂后厨服务器</div></div>
<div class="info-item"><div class="info-label">服务器编号</div><div class="info-value">EDGE-CQ-017</div></div>
<div class="info-item"><div class="info-label">服务器 IP</div><div class="info-value">10.18.32.17</div></div>
<div class="info-item"><div class="info-label">部署位置</div><div class="info-value">后厨机房</div></div>
<div class="info-item"><div class="info-label">服务版本</div><div class="info-value">v2.3.0</div></div>
<div class="info-item"><div class="info-label">配置版本</div><div class="info-value">CFG-20260623-04</div></div>
<div class="info-item"><div class="info-label">安装时间</div><div class="info-value">2026-05-12 09:20</div></div>
<div class="info-item"><div class="info-label">最近启动</div><div class="info-value">2026-06-23 08:11</div></div>
<div class="info-item"><div class="info-label">本地数据库</div><div class="info-value"><span class="tag tag-green">正常</span></div></div>
<div class="info-item"><div class="info-label">MQ 连接</div><div class="info-value"><span class="tag tag-orange">间歇断连</span></div></div>
<div class="info-item"><div class="info-label">未同步数据</div><div class="info-value" style="color:var(--warning)">486 条</div></div>
<div class="info-item"><div class="info-label">最后上报成功</div><div class="info-value">2026-06-23 13:42:11</div></div>
</div>
</div>
</div>
<div class="card">
<div class="card__header"><div class="card__title">资源状态</div><span class="tag tag-orange">磁盘预警</span></div>
<div class="card__body">
<div class="resource-list">
<div class="resource-row"><span>CPU</span><div class="bar"><span style="width:46%"></span></div><strong>46%</strong></div>
<div class="resource-row"><span>内存</span><div class="bar"><span style="width:68%"></span></div><strong>68%</strong></div>
<div class="resource-row"><span>磁盘</span><div class="bar warning"><span style="width:82%"></span></div><strong>82%</strong></div>
<div class="resource-row"><span>缓存</span><div class="bar"><span style="width:61%"></span></div><strong>61%</strong></div>
</div>
</div>
</div>
</section>
<section class="tabs">
<div class="tab active" onclick="switchTab('base',this)">基本信息</div>
<div class="tab" onclick="switchTab('devices',this)">设备列表</div>
<div class="tab" onclick="switchTab('sync',this)">同步记录</div>
<div class="tab" onclick="switchTab('dispatch',this)">下发记录</div>
<div class="tab" onclick="switchTab('files',this)">文件缓存</div>
<div class="tab" onclick="switchTab('upgrade',this)">升级记录</div>
<div class="tab" onclick="switchTab('alarm',this)">告警记录</div>
</section>
<section id="base" class="tab-panel active">
<div class="timeline">
<div class="mini-stat"><div class="mini-stat__value">12 / 18</div><div class="mini-stat__label">在线设备 / 绑定设备</div></div>
<div class="mini-stat"><div class="mini-stat__value" style="color:var(--warning)">486</div><div class="mini-stat__label">上行待同步数据</div></div>
<div class="mini-stat"><div class="mini-stat__value" style="color:var(--warning)">6</div><div class="mini-stat__label">下发失败记录</div></div>
<div class="mini-stat"><div class="mini-stat__value" style="color:var(--danger)">3</div><div class="mini-stat__label">当前未恢复告警</div></div>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>检查项</th><th>当前状态</th><th>最近更新时间</th><th>说明</th><th>操作</th></tr></thead>
<tbody>
<tr><td>设备接入服务</td><td><span class="tag tag-green">可用</span></td><td>2026-06-23 14:31:58</td><td>营养秤、组配秤、结算终端可正常访问本地 API</td><td class="td-actions"><a class="link">查看日志</a></td></tr>
<tr><td>同步引擎</td><td><span class="tag tag-orange">重试中</span></td><td>2026-06-23 14:30:21</td><td>MQ 连接抖动,上行数据存在积压</td><td class="td-actions"><a class="link" href="edge-sync-monitor.html">查看同步</a></td></tr>
<tr><td>文件缓存</td><td><span class="tag tag-green">可用</span></td><td>2026-06-23 14:20:12</td><td>升级包、人脸图、过程图片缓存正常</td><td class="td-actions"><a class="link">清理策略</a></td></tr>
</tbody>
</table>
</div>
</section>
<section id="devices" class="tab-panel">
<div class="table-wrap"><table><thead><tr><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>智能组配秤 01</td><td>CMB-SCALE-001</td><td>净菜组配秤</td><td>净菜组配</td><td>后厨组配区</td><td><span class="tag tag-green">在线</span></td><td>2026-06-23 14:31:42</td><td>v1.8.2</td><td><span class="tag tag-green">成功</span></td><td class="td-actions"><a class="link">设备详情</a> <a class="link">设备日志</a></td></tr>
<tr><td>餐线营养秤 02</td><td>LINE-SCALE-002</td><td>餐线营养秤</td><td>上餐监控</td><td>一号餐线</td><td><span class="tag tag-orange">离线</span></td><td>2026-06-23 14:02:18</td><td>v1.7.6</td><td><span class="tag tag-gray">未升级</span></td><td class="td-actions"><a class="link">重新拉取配置</a> <a class="link">下发升级</a></td></tr>
<tr><td>人脸识别终端 03</td><td>FACE-TERM-003</td><td>人脸识别设备</td><td>就餐识别</td><td>入口闸机</td><td><span class="tag tag-green">在线</span></td><td>2026-06-23 14:31:55</td><td>v3.2.0</td><td><span class="tag tag-green">成功</span></td><td class="td-actions"><a class="link">设备详情</a> <a class="link">设备日志</a></td></tr>
</tbody></table></div>
</section>
<section id="sync" class="tab-panel">
<div class="table-wrap"><table><thead><tr><th>方向</th><th>业务类型</th><th>业务编号</th><th>发生/下发时间</th><th>当前状态</th><th>失败次数</th><th>失败原因</th><th>最近处理时间</th><th>操作</th></tr></thead><tbody>
<tr><td>上行</td><td>就餐流水</td><td>MEAL-20260623-0178</td><td>2026-06-23 12:11:04</td><td><span class="tag tag-orange">待重试</span></td><td>3</td><td>MQ 连接超时</td><td>2026-06-23 14:28:33</td><td class="td-actions"><a class="link">查看详情</a> <a class="link">手动重试</a></td></tr>
<tr><td>下行</td><td>人脸增量</td><td>FACE-BATCH-0623-04</td><td>2026-06-23 10:00:00</td><td><span class="tag tag-red">应用失败</span></td><td>2</td><td>本地特征库写入失败</td><td>2026-06-23 13:57:22</td><td class="td-actions"><a class="link" href="edge-dispatch-compensation.html">发起补偿</a></td></tr>
</tbody></table></div>
</section>
<section id="dispatch" class="tab-panel">
<div class="table-wrap"><table><thead><tr><th>下发类型</th><th>批次号</th><th>下发时间</th><th>应用状态</th><th>失败原因</th><th>处理完成时间</th><th>操作</th></tr></thead><tbody>
<tr><td>基础资料</td><td>BASE-20260623-003</td><td>2026-06-23 09:30:00</td><td><span class="tag tag-green">已应用</span></td><td></td><td>2026-06-23 09:31:28</td><td class="td-actions"><a class="link">查看回执</a></td></tr>
<tr><td>升级任务</td><td>UPG-20260623-002</td><td>2026-06-23 11:00:00</td><td><span class="tag tag-orange">部分失败</span></td><td>设备离线 2 台</td><td></td><td class="td-actions"><a class="link" href="edge-dispatch-compensation.html">补偿处理</a></td></tr>
</tbody></table></div>
</section>
<section id="files" class="tab-panel">
<div class="table-wrap"><table><thead><tr><th>文件类型</th><th>文件名称</th><th>文件版本</th><th>文件大小</th><th>下载状态</th><th>校验状态</th><th>最近下载时间</th><th>来源任务</th><th>操作</th></tr></thead><tbody>
<tr><td>升级包</td><td>line-scale-v1.8.2.bin</td><td>v1.8.2</td><td>86 MB</td><td><span class="tag tag-green">已下载</span></td><td><span class="tag tag-green">通过</span></td><td>2026-06-23 10:42:00</td><td>UPG-20260623-002</td><td class="td-actions"><a class="link">重新校验</a></td></tr>
<tr><td>人脸图片</td><td>face-batch-0623-04.zip</td><td>20260623.04</td><td>312 MB</td><td><span class="tag tag-orange">下载中</span></td><td><span class="tag tag-gray">待校验</span></td><td>2026-06-23 14:20:00</td><td>FACE-BATCH-0623-04</td><td class="td-actions"><a class="link">查看任务</a></td></tr>
</tbody></table></div>
</section>
<section id="upgrade" class="tab-panel">
<div class="table-wrap"><table><thead><tr><th>升级任务编号</th><th>升级对象</th><th>目标版本</th><th>下发时间</th><th>当前状态</th><th>完成时间</th><th>失败原因</th><th>操作</th></tr></thead><tbody>
<tr><td>UPG-20260623-002</td><td>餐线营养秤 02</td><td>v1.8.2</td><td>2026-06-23 11:00:00</td><td><span class="tag tag-red">失败</span></td><td></td><td>设备离线,未接收升级通知</td><td class="td-actions"><a class="link">重新通知设备</a></td></tr>
<tr><td>UPG-20260621-009</td><td>智能组配秤 01</td><td>v1.8.2</td><td>2026-06-21 20:00:00</td><td><span class="tag tag-green">成功</span></td><td>2026-06-21 20:16:28</td><td></td><td class="td-actions"><a class="link">查看日志</a></td></tr>
</tbody></table></div>
</section>
<section id="alarm" class="tab-panel">
<div class="table-wrap"><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><span class="tag tag-orange">警告</span></td><td>2026-06-23 13:45:00</td><td>2026-06-23 14:28:00</td><td><span class="tag tag-orange">处理中</span></td><td>运维张工</td><td>正在排查 MQ 连接抖动</td><td class="td-actions"><a class="link" href="edge-alarm-center.html">告警详情</a></td></tr>
<tr><td>升级类 · 设备未执行升级</td><td><span class="tag tag-red">严重</span></td><td>2026-06-23 11:10:00</td><td>2026-06-23 14:00:00</td><td><span class="tag tag-orange">待处理</span></td><td>未指派</td><td>餐线营养秤离线</td><td class="td-actions"><a class="link" href="edge-dispatch-compensation.html">发起补偿</a></td></tr>
</tbody></table></div>
</section>
</main>
<div class="toast" id="toast">操作已完成</div>
<script>
function switchTab(id,el){
document.querySelectorAll('.tab').forEach(function(tab){tab.classList.remove('active');});
document.querySelectorAll('.tab-panel').forEach(function(panel){panel.classList.remove('active');});
el.classList.add('active');
document.getElementById(id).classList.add('active');
}
function showToast(text){
var toast=document.getElementById('toast');
toast.textContent=text;
toast.classList.add('show');
setTimeout(function(){toast.classList.remove('show');},1800);
}
</script>
</body>
</html>
+210
View File
@@ -0,0 +1,210 @@
<!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 14px;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);}
.btn-back{color:rgba(255,255,255,0.65);text-decoration:none;}
.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);}
.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:.75;margin-right:2px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:18px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header__title{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
.page-header__desc{margin-top:6px;font-size:var(--font-sm);color:var(--text-secondary);}
.page-header__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
.stats-row{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 18px;cursor:pointer;border:1px solid transparent;transition:all .2s;}
.stat-card:hover{border-color:var(--primary);transform:translateY(-1px);}
.stat-card__value{font-size:var(--font-xxl);font-weight:700;line-height:1;color:var(--text-primary);}
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);margin-top:8px;}
.stat-card__hint{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:8px;}
.filter-bar{background:#fff;border-radius:var(--radius-md);padding:18px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:14px;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);outline:none;background:#fff;min-width:170px;}
.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);outline:none;background:#fff;appearance:auto;min-width:128px;}
.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;white-space:nowrap;}
.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);}
.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;align-items:center;gap:8px;}
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
.table-card__content table{min-width:1560px;}
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;vertical-align:middle;}
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;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.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;}
.node-name{font-weight:600;color:var(--primary);cursor:pointer;}
.node-sub{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:3px;}
.metric-danger{color:var(--danger);font-weight:600;}
.metric-warning{color:var(--warning);font-weight:600;}
.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);}
.page-btn.active,.page-btn:hover{background:var(--primary);border-color:var(--primary);color:#fff;}
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-12px);background:#fff;border-left:4px solid var(--primary);box-shadow:var(--shadow-md);padding:10px 18px;border-radius:var(--radius-sm);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .25s;z-index:1200;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
</style>
</head>
<body>
<header 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">溯源总览</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>
<a href="edge-node-list.html" class="active">边缘服务器</a>
</nav>
<div class="top-bar__right">
<a href="../../index.html" class="btn-back">← 返回首页</a>
<div class="avatar"></div>
<span>系统管理员 ▾</span>
</div>
</header>
<aside class="sidebar">
<div class="sidebar-menu__title">边缘服务器</div>
<a href="edge-alarm-center.html" class="sidebar-item"><span class="mi"></span>告警中心</a>
<a href="edge-sync-monitor.html" class="sidebar-item"><span class="mi">🔄</span>同步监控</a>
<a href="edge-dispatch-compensation.html" class="sidebar-item"><span class="mi">📦</span>下发与补偿</a>
<a href="edge-node-list.html" class="sidebar-item active"><span class="mi">🧭</span>边缘服务器列表</a>
</aside>
<main class="main">
<section class="page-header">
<div>
<div class="page-header__title">食堂上报风险总览</div>
<div class="page-header__desc">按食堂维度快速定位服务器离线、上报中断、数据积压和严重故障,作为故障排查总览入口。</div>
</div>
<div class="page-header__meta">数据更新时间:2026-06-23 14:32:18</div>
</section>
<section class="stats-row">
<!-- [交互] 点击统计卡自动带入对应筛选条件 -->
<div class="stat-card" onclick="quickFilter('全部服务器')"><div class="stat-card__value">28</div><div class="stat-card__label">接入服务器数</div><div class="stat-card__hint">覆盖 16 个食堂</div></div>
<div class="stat-card" onclick="quickFilter('正常上报服务器')"><div class="stat-card__value" style="color:var(--success)">23</div><div class="stat-card__label">正常上报服务器</div><div class="stat-card__hint">最近 3 分钟有心跳且有上报</div></div>
<div class="stat-card" onclick="quickFilter('上报中断服务器')"><div class="stat-card__value" style="color:var(--danger)">2</div><div class="stat-card__label">上报中断服务器</div><div class="stat-card__hint">超过 10 分钟未心跳或无上报</div></div>
<div class="stat-card" onclick="quickFilter('异常食堂服务器')"><div class="stat-card__value" style="color:var(--warning)">3</div><div class="stat-card__label">异常食堂服务器</div><div class="stat-card__hint">在线但已出现故障告警</div></div>
<div class="stat-card" onclick="quickFilter('上报积压服务器')"><div class="stat-card__value" style="color:var(--warning)">5</div><div class="stat-card__label">上报积压服务器</div><div class="stat-card__hint">存在待上报数据积压</div></div>
<div class="stat-card" onclick="quickFilter('严重故障服务器')"><div class="stat-card__value" style="color:var(--danger)">4</div><div class="stat-card__label">严重故障服务器</div><div class="stat-card__hint">需运维立即处理</div></div>
</section>
<section class="filter-bar">
<div class="filter-item"><label>所属区域</label><select class="filter-select"><option>全部</option><option>CQ一区</option><option>CQ二区</option><option>CQ三厂</option></select></div>
<div class="filter-item"><label>所属食堂</label><select class="filter-select"><option>全部</option><option>总部一号食堂</option><option>采气一厂食堂</option><option>炼化中心食堂</option></select></div>
<div class="filter-item"><label>服务器状态</label><select class="filter-select"><option>全部</option><option>在线</option><option>离线</option><option>异常</option></select></div>
<div class="filter-item"><label>上报状态</label><select class="filter-select"><option>全部</option><option>正常</option><option>积压</option><option>失败</option><option>中断</option></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>v2.3.1</option><option>v2.3.0</option><option>v2.2.8</option></select></div>
<div class="filter-item"><label>最近心跳</label><input class="filter-input" type="date" style="min-width:150px"></div>
<div class="filter-item"><label>关键字</label><input class="filter-input" type="text" placeholder="服务器名称 / 编号 / 食堂"></div>
<button class="btn btn-primary" onclick="showToast('已按筛选条件刷新服务器列表')">查询</button>
<button class="btn btn-default" onclick="showToast('已重置筛选条件')">重置</button>
</section>
<section class="table-card">
<div class="table-card__header">
<div class="table-card__title">食堂风险清单</div>
<div class="table-card__actions">
<button class="btn btn-default" onclick="showToast('正在导出服务器状态')">导出服务器状态</button>
</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><th>最后上报成功</th><th>当前故障告警</th><th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><div class="node-name" onclick="location.href='edge-node-detail.html'">总部一号食堂服务器</div><div class="node-sub">EDGE-CQ-001</div></td>
<td>总部一号食堂</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">正常</span></td><td>v2.3.1</td><td>18 / 20</td><td>0</td><td>0</td><td>2026-06-23 14:32:10</td><td>2026-06-23 14:30:02</td><td>0</td>
<td class="td-actions"><a class="link" href="edge-node-detail.html">查看详情</a> <a class="link" href="edge-sync-monitor.html">查看同步</a></td>
</tr>
<tr>
<td><div class="node-name" onclick="location.href='edge-node-detail.html'">采气一厂后厨服务器</div><div class="node-sub">EDGE-CQ-017</div></td>
<td>采气一厂食堂</td><td><span class="tag tag-orange">异常</span></td><td><span class="tag tag-orange">告警</span></td><td>v2.3.0</td><td>12 / 18</td><td><a class="link metric-warning" href="edge-sync-monitor.html">486</a></td><td><a class="link metric-warning" href="edge-dispatch-compensation.html">6</a></td><td>2026-06-23 14:31:58</td><td>2026-06-23 13:42:11</td><td><a class="link metric-warning" href="edge-alarm-center.html">3</a></td>
<td class="td-actions"><a class="link" href="edge-alarm-center.html">查看告警</a> <a class="link" href="edge-sync-monitor.html">查看同步</a> <a class="link" href="edge-node-detail.html">查看详情</a></td>
</tr>
<tr>
<td><div class="node-name" onclick="location.href='edge-node-detail.html'">炼化中心餐线服务器</div><div class="node-sub">EDGE-CQ-024</div></td>
<td>炼化中心食堂</td><td><span class="tag tag-red">离线</span></td><td><span class="tag tag-red">严重</span></td><td>v2.2.8</td><td>0 / 16</td><td><a class="link metric-danger" href="edge-sync-monitor.html">1,284</a></td><td><a class="link metric-danger" href="edge-dispatch-compensation.html">21</a></td><td>2026-06-23 13:54:09</td><td>2026-06-23 12:40:18</td><td><a class="link metric-danger" href="edge-alarm-center.html">8</a></td>
<td class="td-actions"><a class="link" href="edge-alarm-center.html">查看告警</a> <a class="link" href="edge-sync-monitor.html">查看同步</a> <a class="link" href="edge-node-detail.html">查看详情</a></td>
</tr>
<tr>
<td><div class="node-name" onclick="location.href='edge-node-detail.html'">输气站小食堂服务器</div><div class="node-sub">EDGE-CQ-032</div></td>
<td>输气站小食堂</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-orange">告警</span></td><td>v2.3.1</td><td>7 / 8</td><td><a class="link metric-warning" href="edge-sync-monitor.html">94</a></td><td>0</td><td>2026-06-23 14:32:02</td><td>2026-06-23 14:10:33</td><td><a class="link metric-warning" href="edge-alarm-center.html">1</a></td>
<td class="td-actions"><a class="link" href="edge-alarm-center.html">查看告警</a> <a class="link" href="edge-sync-monitor.html">查看同步</a> <a class="link" href="edge-dispatch-compensation.html">恢复补偿</a></td>
</tr>
<tr>
<td><div class="node-name" onclick="location.href='edge-node-detail.html'">培训中心食堂服务器</div><div class="node-sub">EDGE-CQ-041</div></td>
<td>培训中心食堂</td><td><span class="tag tag-green">在线</span></td><td><span class="tag tag-green">正常</span></td><td>v2.3.1</td><td>10 / 10</td><td>0</td><td>0</td><td>2026-06-23 14:32:14</td><td>2026-06-23 14:28:41</td><td>0</td>
<td class="td-actions"><a class="link" href="edge-node-detail.html">查看详情</a> <a class="link" href="edge-sync-monitor.html">查看同步</a></td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>显示 5 条,共 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>
</section>
</main>
<div class="toast" id="toast">操作已完成</div>
<script>
function showToast(text){
var toast=document.getElementById('toast');
toast.textContent=text;
toast.classList.add('show');
setTimeout(function(){toast.classList.remove('show');},1800);
}
function quickFilter(type){showToast('已筛选:'+type);}
function confirmAction(text){
if(window.confirm('确认执行该操作?')){showToast(text);}
}
</script>
</body>
</html>
+210
View File
@@ -0,0 +1,210 @@
<!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;--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;--radius-sm:4px;--radius-md:8px;--shadow-sm:0 1px 4px rgba(0,0,0,.08);--shadow-md:0 4px 12px rgba(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,.65);text-decoration:none;padding:6px 14px;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,.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,.65);font-size:var(--font-sm);}
.btn-back{color:rgba(255,255,255,.65);text-decoration:none;}
.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);}
.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,.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,.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:.75;margin-right:2px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:18px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header__title{font-size:var(--font-xl);font-weight:700;}
.page-header__desc{margin-top:6px;font-size:var(--font-sm);color:var(--text-secondary);}
.page-header__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
.stats-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 18px;}
.stat-card__value{font-size:var(--font-xxl);font-weight:700;line-height:1;color:var(--text-primary);}
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);margin-top:8px;}
.stat-card__trend{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:8px;}
.layout-grid{display:grid;grid-template-columns:2fr 1fr;gap:16px;margin-bottom:16px;}
.card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);}
.card__title{font-size:var(--font-base);font-weight:600;}
.card__body{padding:18px 20px;}
.trend-chart{height:220px;display:flex;align-items:flex-end;gap:12px;border-bottom:1px solid var(--border);padding:18px 8px 0;}
.trend-bar{flex:1;background:var(--primary-light);border-radius:4px 4px 0 0;position:relative;min-width:20px;}
.trend-bar::after{content:attr(data-label);position:absolute;left:50%;bottom:-24px;transform:translateX(-50%);font-size:var(--font-xs);color:var(--text-placeholder);}
.trend-bar.warning{background:#FFF7E6;}
.trend-bar.danger{background:#FFF1F0;}
.node-risk-list{display:flex;flex-direction:column;gap:12px;}
.node-risk-item{display:grid;grid-template-columns:1fr auto;gap:12px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;}
.node-risk-item__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);}
.node-risk-item__desc{font-size:var(--font-xs);color:var(--text-secondary);margin-top:4px;}
.tabs{display:flex;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);overflow:hidden;}
.tab{padding:14px 22px;font-size:var(--font-sm);cursor:pointer;border-bottom:2px solid transparent;color:var(--text-secondary);}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-panel{display:none;background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;}
.tab-panel.active{display:block;}
.filter-bar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:16px;}
.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);outline:none;min-width:160px;}
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;min-width:120px;background:#fff;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}
.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.table-wrap{overflow-x:auto;}
.table-wrap table{min-width:1320px;}
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;vertical-align:middle;}
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,.04);z-index:2;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.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;}
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-12px);background:#fff;border-left:4px solid var(--primary);box-shadow:var(--shadow-md);padding:10px 18px;border-radius:var(--radius-sm);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .25s;z-index:1200;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
</style>
</head>
<body>
<header 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">溯源总览</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><a href="edge-node-list.html" class="active">边缘服务器</a></nav>
<div class="top-bar__right"><a href="../../index.html" class="btn-back">← 返回首页</a><div class="avatar"></div><span>系统管理员 ▾</span></div>
</header>
<aside class="sidebar">
<div class="sidebar-menu__title">边缘服务器</div>
<a href="edge-alarm-center.html" class="sidebar-item"><span class="mi"></span>告警中心</a>
<a href="edge-sync-monitor.html" class="sidebar-item active"><span class="mi">🔄</span>同步监控</a>
<a href="edge-dispatch-compensation.html" class="sidebar-item"><span class="mi">📦</span>下发与补偿</a>
<a href="edge-node-list.html" class="sidebar-item"><span class="mi">🧭</span>边缘服务器列表</a>
</aside>
<main class="main">
<section class="page-header">
<div><div class="page-header__title">食堂上报同步监控</div><div class="page-header__desc">聚焦食堂现场数据上报链路,查看上报中断、延迟、积压和恢复处理情况。</div></div>
<div class="page-header__meta">最近刷新:2026-06-23 14:35:00</div>
</section>
<section class="stats-grid">
<div class="stat-card"><div class="stat-card__value">23 / 2</div><div class="stat-card__label">正常上报 / 中断服务器</div><div class="stat-card__trend">5 分钟心跳与上报口径</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--warning)">5</div><div class="stat-card__label">上报异常食堂</div><div class="stat-card__trend">较昨日 +2</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--warning)">1,864</div><div class="stat-card__label">待上报数据条数</div><div class="stat-card__trend">主要来自就餐流水</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--danger)">38</div><div class="stat-card__label">恢复下发失败</div><div class="stat-card__trend">含人脸/升级包</div></div>
<div class="stat-card"><div class="stat-card__value" style="color:var(--danger)">4</div><div class="stat-card__label">积压超阈值服务器</div><div class="stat-card__trend">积压超过 30 分钟</div></div>
<div class="stat-card"><div class="stat-card__value">96.8%</div><div class="stat-card__label">24小时上报成功率</div><div class="stat-card__trend">午间链路波动明显</div></div>
</section>
<section class="layout-grid">
<div class="card">
<div class="card__header"><div class="card__title">最近 24 小时上报失败趋势</div><span class="tag tag-orange">按小时统计</span></div>
<div class="card__body">
<div class="trend-chart">
<div class="trend-bar" style="height:30%" data-label="00"></div>
<div class="trend-bar" style="height:42%" data-label="04"></div>
<div class="trend-bar warning" style="height:58%" data-label="08"></div>
<div class="trend-bar warning" style="height:72%" data-label="12"></div>
<div class="trend-bar danger" style="height:88%" data-label="14"></div>
<div class="trend-bar warning" style="height:60%" data-label="16"></div>
<div class="trend-bar" style="height:36%" data-label="20"></div>
</div>
</div>
</div>
<div class="card">
<div class="card__header"><div class="card__title">上报高风险服务器</div><a class="link" href="edge-alarm-center.html">查看故障</a></div>
<div class="card__body">
<div class="node-risk-list">
<div class="node-risk-item"><div><div class="node-risk-item__title">炼化中心餐线服务器</div><div class="node-risk-item__desc">上行积压 1,284 条,下发失败 21 条</div></div><span class="tag tag-red">严重</span></div>
<div class="node-risk-item"><div><div class="node-risk-item__title">采气一厂后厨服务器</div><div class="node-risk-item__desc">MQ 间歇断连,失败重试中</div></div><span class="tag tag-orange">告警</span></div>
<div class="node-risk-item"><div><div class="node-risk-item__title">输气站小食堂服务器</div><div class="node-risk-item__desc">上行待同步 94 条</div></div><span class="tag tag-orange">积压</span></div>
</div>
</div>
</div>
</section>
<section class="tabs">
<div class="tab active" onclick="switchTab('up',this)">食堂上报记录</div>
<div class="tab" onclick="switchTab('down',this)">恢复下发记录</div>
</section>
<section id="up" class="tab-panel active">
<div class="filter-bar">
<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></select></div>
<div class="filter-item"><label>业务类型</label><select class="filter-select"><option>全部</option><option>就餐流水</option><option>称重记录</option><option>设备日志</option></select></div>
<div class="filter-item"><label>同步状态</label><select class="filter-select"><option>全部</option><option>待同步</option><option>同步失败</option><option>已同步</option></select></div>
<div class="filter-item"><label>发生时间</label><input class="filter-input" type="date"></div>
<button class="btn btn-primary" onclick="showToast('已刷新食堂上报记录')">查询</button>
<button class="btn btn-default" onclick="showToast('正在导出食堂上报记录')">导出记录</button>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>食堂</th><th>服务器</th><th>业务类型</th><th>业务编号</th><th>上报产生时间</th><th>上报状态</th><th>失败次数</th><th>最近处理时间</th><th>失败原因</th><th>操作</th></tr></thead>
<tbody>
<tr><td>炼化中心食堂</td><td>炼化中心餐线服务器</td><td>就餐流水</td><td>MEAL-20260623-0781</td><td>2026-06-23 12:21:18</td><td><span class="tag tag-red">同步失败</span></td><td>8</td><td>2026-06-23 14:31:20</td><td>服务器离线,等待恢复</td><td class="td-actions"><a class="link" href="edge-alarm-center.html">查看故障</a> <a class="link" href="edge-dispatch-compensation.html">恢复补偿</a></td></tr>
<tr><td>采气一厂食堂</td><td>采气一厂后厨服务器</td><td>称重记录</td><td>WEIGH-20260623-0148</td><td>2026-06-23 11:42:03</td><td><span class="tag tag-orange">待重试</span></td><td>3</td><td>2026-06-23 14:28:33</td><td>MQ 连接超时</td><td class="td-actions"><a class="link" href="edge-alarm-center.html">查看故障</a> <a class="link" onclick="showToast('已加入重试队列')">手动重试</a></td></tr>
<tr><td>总部一号食堂</td><td>总部一号食堂服务器</td><td>设备日志</td><td>LOG-20260623-0311</td><td>2026-06-23 13:05:48</td><td><span class="tag tag-green">已同步</span></td><td>0</td><td>2026-06-23 13:06:03</td><td></td><td class="td-actions"><a class="link" href="edge-node-detail.html">服务器详情</a></td></tr>
</tbody>
</table>
</div>
</section>
<section id="down" class="tab-panel">
<div class="filter-bar">
<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></select></div>
<div class="filter-item"><label>下发类型</label><select class="filter-select"><option>全部</option><option>基础资料</option><option>人脸批次</option><option>生产任务</option><option>升级包</option></select></div>
<div class="filter-item"><label>应用状态</label><select class="filter-select"><option>全部</option><option>已应用</option><option>应用失败</option><option>待处理</option></select></div>
<div class="filter-item"><label>下发时间</label><input class="filter-input" type="date"></div>
<button class="btn btn-primary" onclick="showToast('已刷新恢复下发记录')">查询</button>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>食堂</th><th>服务器</th><th>下发类型</th><th>批次号</th><th>下发时间</th><th>当前状态</th><th>完成时间</th><th>失败原因</th><th>操作</th></tr></thead>
<tbody>
<tr><td>采气一厂食堂</td><td>采气一厂后厨服务器</td><td>人脸批次</td><td>FACE-BATCH-0623-04</td><td>2026-06-23 10:00:00</td><td><span class="tag tag-red">应用失败</span></td><td></td><td>本地特征库写入失败</td><td class="td-actions"><a class="link" href="edge-dispatch-compensation.html">恢复补偿</a> <a class="link" href="edge-alarm-center.html">查看故障</a></td></tr>
<tr><td>炼化中心食堂</td><td>炼化中心餐线服务器</td><td>升级包</td><td>UPG-20260623-002</td><td>2026-06-23 11:00:00</td><td><span class="tag tag-orange">部分失败</span></td><td></td><td>2 台设备未执行升级</td><td class="td-actions"><a class="link" href="edge-dispatch-compensation.html">恢复补偿</a> <a class="link" href="edge-alarm-center.html">查看故障</a></td></tr>
<tr><td>总部一号食堂</td><td>总部一号食堂服务器</td><td>基础资料</td><td>BASE-20260623-003</td><td>2026-06-23 09:30:00</td><td><span class="tag tag-green">已应用</span></td><td>2026-06-23 09:31:28</td><td></td><td class="td-actions"><a class="link" href="edge-node-detail.html">服务器详情</a></td></tr>
</tbody>
</table>
</div>
</section>
</main>
<div class="toast" id="toast">操作已完成</div>
<script>
function switchTab(id,el){
document.querySelectorAll('.tab').forEach(function(tab){tab.classList.remove('active');});
document.querySelectorAll('.tab-panel').forEach(function(panel){panel.classList.remove('active');});
el.classList.add('active');
document.getElementById(id).classList.add('active');
}
function showToast(text){
var toast=document.getElementById('toast');
toast.textContent=text;
toast.classList.add('show');
setTimeout(function(){toast.classList.remove('show');},1800);
}
</script>
</body>
</html>