feat: 健康体检新增前置机管理(常规体检前置服务)页面及PRD

参照老系统 hospitalAgent 复刻医院前置机设备运维管理,新增常规体检前置服务页面(设备列表+6弹窗),同步 SITE_MAP 与 PRD 文档。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-09-15 19:46:14 +08:00
co-authored by Claude Opus 4.7
parent c85d9c124e
commit 2dfc61bedf
5 changed files with 908 additions and 8 deletions
+841
View File
@@ -0,0 +1,841 @@
<!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);--shadow-lg:0 8px 24px rgba(0,0,0,0.16);
}
/* 顶部导航 */
.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);}
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 按钮行 */
.btn-row{display:flex;gap:12px;margin-bottom:16px;}
/* 筛选栏 */
.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);}
.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;}
.btn-link{background:none;border:none;color:var(--primary);cursor:pointer;font-size:var(--font-sm);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}.btn-link--danger:hover{color:#ff7875;}
.btn-sm{height:28px;padding:0 12px;font-size:var(--font-xs);}
/* 表格 */
.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__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
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-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;}
.tag-blue{background:var(--primary-light);color:var(--primary);}
/* 在线状态圆点 */
.status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;vertical-align:middle;}
.status-dot--online{background:var(--success);}
.status-dot--offline{background:#BFBFBF;}
/* 操作列固定 */
td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
th.td-actions{position:sticky;right:0;background:#FAFAFA;z-index:1;}
/* 分页 */
.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);}
/* 弹窗遮罩与容器 */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1000;display:none;align-items:center;justify-content:center;}
.modal-overlay.open{display:flex;}
.modal-overlay--top{z-index:1100;}
.modal{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;max-height:86vh;width:560px;}
.modal--lg{width:700px;}.modal--xl{width:900px;}.modal--xxl{width:950px;}.modal--sm{width:420px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__title{font-size:var(--font-lg);font-weight:600;}
.modal__close{width:32px;height:32px;border-radius:50%;border:none;background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);font-size:18px;}
.modal__close:hover{background:#F5F5F5;}
.modal__body{flex:1;overflow-y:auto;padding:24px;}
.modal__footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 描述列表 */
.descriptions{border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;margin-bottom:16px;}
.desc-row{display:flex;border-bottom:1px solid var(--border);}
.desc-row:last-child{border-bottom:none;}
.desc-item{flex:1;display:flex;padding:0;}
.desc-label{width:120px;background:#FAFAFA;padding:12px 16px;font-size:var(--font-sm);color:var(--text-secondary);border-right:1px solid var(--border);flex-shrink:0;}
.desc-value{flex:1;padding:12px 16px;font-size:var(--font-sm);color:var(--text-primary);}
.desc-item--span .desc-label{width:120px;}
/* Tab 切换 */
.tabs{display:flex;border-bottom:1px solid var(--border);margin-bottom:16px;}
.tab{flex:1;text-align:center;padding:12px 0;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:all .2s;}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500;}
.tab-pane{display:none;}
.tab-pane.active{display:block;}
/* 表单 */
.form-item{margin-bottom:16px;}
.form-label{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:8px;display:flex;align-items:center;gap:4px;}
.form-input{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;}
.form-input:focus{border-color:var(--primary);}
.form-input:disabled{background:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
.form-input--inline{width:auto;flex:1;}
.form-textarea{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;font-size:var(--font-sm);outline:none;resize:vertical;font-family:Consolas,Monaco,monospace;line-height:1.6;}
.form-textarea:focus{border-color:var(--primary);}
.input-row{display:flex;align-items:center;gap:8px;}
.tip{color:var(--text-placeholder);font-size:var(--font-xs);margin-top:4px;}
/* 提示条 alert */
.alert{display:flex;gap:8px;padding:12px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);margin-bottom:16px;align-items:flex-start;}
.alert-warning{background:#FFF7E6;border:1px solid #FFE7B3;color:#AD6800;}
.alert-info{background:#E6F7FF;border:1px solid #BAE7FF;color:#096DD9;}
.alert svg{flex-shrink:0;margin-top:1px;}
/* 卡片 */
.card{border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:16px;}
.card__title{padding:12px 16px;font-size:var(--font-sm);font-weight:600;border-bottom:1px solid var(--border);background:#FAFAFA;border-radius:var(--radius-sm) var(--radius-sm) 0 0;}
.card__body{padding:16px;}
/* 代码块 */
pre.code{background:#1e1e1e;color:#d4d4d4;padding:12px;border-radius:var(--radius-sm);font-family:Consolas,Monaco,monospace;font-size:12px;line-height:1.6;overflow:auto;max-height:320px;white-space:pre-wrap;word-break:break-all;}
pre.code--light{background:#F5F5F5;color:#333;}
/* 单选 */
.radio-group{display:flex;gap:20px;margin-bottom:12px;}
.radio{display:inline-flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{margin:0;cursor:pointer;}
/* 复选 */
.checkbox-list{display:flex;flex-direction:column;gap:10px;max-height:320px;overflow-y:auto;padding:4px 0;}
.checkbox{display:inline-flex;align-items:center;gap:8px;font-size:var(--font-sm);cursor:pointer;}
.checkbox input{margin:0;cursor:pointer;}
/* 动态参数 */
.param-row{display:flex;align-items:flex-start;margin-bottom:10px;}
.param-label{width:180px;flex-shrink:0;padding-top:8px;font-size:var(--font-sm);color:var(--text-secondary);}
.param-label .req{color:var(--danger);margin-right:2px;}
.param-label .in-tag{display:inline-block;background:#F0F0F0;color:#666;border-radius:2px;padding:0 4px;font-size:10px;margin-left:4px;}
.param-input{flex:1;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;}
.param-input:focus{border-color:var(--primary);}
.param-textarea{flex:1;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;font-size:var(--font-sm);outline:none;resize:vertical;font-family:Consolas,Monaco,monospace;}
.method-tag{display:inline-block;padding:1px 8px;border-radius:2px;font-size:11px;font-weight:600;}
.method-tag--get{background:#E6F7FF;color:#1890FF;}
.method-tag--post{background:#F6FFED;color:#52C41A;}
/* 响应状态码 */
.status-code{display:inline-block;padding:1px 10px;border-radius:10px;font-size:var(--font-xs);font-weight:600;}
.status-code--ok{background:#F6FFED;color:#52C41A;}
.status-code--err{background:#FFF1F0;color:#FF4D4F;}
/* 轻提示 toast */
.toast{position:fixed;top:72px;left:50%;transform:translateX(-50%);background:#fff;padding:10px 20px;border-radius:var(--radius-sm);box-shadow:var(--shadow-md);z-index:2000;display:none;font-size:var(--font-sm);color:var(--text-primary);}
.toast.show{display:block;}
.empty-tip{color:var(--text-placeholder);font-size:var(--font-sm);text-align:center;padding:24px 0;}
.flex-between{display:flex;align-items:center;justify-content:space-between;}
.mt-8{margin-top:8px;}.mt-16{margin-top:16px;}.mb-8{margin-bottom:8px;}.mb-16{margin-bottom:16px;}
</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="archive-personal.html">档案</a>
<a href="config-material-notice.html">基础配置</a>
<a href="pre-hospital-scope.html">检前</a>
<a href="mid-appointment-track.html">检中</a>
<a href="post-unit-count.html">检后</a>
<a href="agent-device.html" class="active">前置机管理</a>
<a href="exception-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="agent-device.html" class="sidebar-item active">常规体检前置服务</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- 顶部功能按钮 -->
<div class="btn-row">
<!-- [交互] 点击打开升级管理弹窗 -->
<button class="btn btn-primary" onclick="openModal('modal-upgrade')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 10V3M4.5 5.5L7 3l2.5 2.5M2 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
升级管理
</button>
<!-- [交互] 点击打开接口测试弹窗 -->
<button class="btn btn-default" onclick="openModal('modal-apitest')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="2" stroke="currentColor" stroke-width="1.2"/><path d="M5 9l4-4M5 5h4v4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
接口测试
</button>
<!-- [交互] 点击打开操作审计弹窗 -->
<button class="btn btn-default" onclick="openModal('modal-audit')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M4 2h6v10H4zM2 2h2v10H2zM10 2h2v10h-2z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
操作审计
</button>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>医院编码:</label>
<input class="filter-input" type="text" placeholder="请输入医院编码" style="min-width:200px;">
</div>
<div class="filter-item">
<label>设备编号:</label>
<input class="filter-input" type="text" placeholder="请输入设备编号" style="min-width:200px;">
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<!-- 设备列表 -->
<div class="table-card">
<div class="table-card__header">
<div class="table-card__title">医院前置机设备</div>
<div style="font-size:var(--font-xs);color:var(--text-placeholder);">30 秒自动刷新在线状态</div>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th>设备编号</th><th>医院</th><th>版本</th><th>在线状态</th><th>最后心跳</th><th class="td-actions">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>D-1001</td><td>CQ能源总医院</td><td>v1.0.2</td>
<td><span class="status-dot status-dot--online"></span>在线</td>
<td>2026-09-15 10:23:45</td>
<td class="td-actions">
<button class="btn-link" onclick="openDetail(this)">详情</button>
<button class="btn-link" onclick="openEdit(this)">编辑</button>
<button class="btn-link" onclick="openLog(this)">日志</button>
<button class="btn-link btn-link--danger" onclick="openRestart(this)">重启</button>
</td>
</tr>
<tr>
<td>D-1002</td><td>CQ西安医院</td><td>v1.0.1</td>
<td><span class="status-dot status-dot--online"></span>在线</td>
<td>2026-09-15 10:22:18</td>
<td class="td-actions">
<button class="btn-link" onclick="openDetail(this)">详情</button>
<button class="btn-link" onclick="openEdit(this)">编辑</button>
<button class="btn-link" onclick="openLog(this)">日志</button>
<button class="btn-link btn-link--danger" onclick="openRestart(this)">重启</button>
</td>
</tr>
<tr>
<td>D-1003</td><td>CQ延安医院</td><td>v1.0.2</td>
<td><span class="status-dot status-dot--offline"></span>离线</td>
<td>2026-09-15 08:56:02</td>
<td class="td-actions">
<button class="btn-link" onclick="openDetail(this)">详情</button>
<button class="btn-link" onclick="openEdit(this)">编辑</button>
<button class="btn-link" onclick="openLog(this)">日志</button>
<button class="btn-link btn-link--danger" onclick="openRestart(this)">重启</button>
</td>
</tr>
<tr>
<td>D-1004</td><td>CQ榆林医院</td><td>v1.0.0</td>
<td><span class="status-dot status-dot--online"></span>在线</td>
<td>2026-09-15 10:21:33</td>
<td class="td-actions">
<button class="btn-link" onclick="openDetail(this)">详情</button>
<button class="btn-link" onclick="openEdit(this)">编辑</button>
<button class="btn-link" onclick="openLog(this)">日志</button>
<button class="btn-link btn-link--danger" onclick="openRestart(this)">重启</button>
</td>
</tr>
<tr>
<td>D-1005</td><td>CQ庆阳医院</td><td>v1.0.1</td>
<td><span class="status-dot status-dot--online"></span>在线</td>
<td>2026-09-15 10:20:57</td>
<td class="td-actions">
<button class="btn-link" onclick="openDetail(this)">详情</button>
<button class="btn-link" onclick="openEdit(this)">编辑</button>
<button class="btn-link" onclick="openLog(this)">日志</button>
<button class="btn-link btn-link--danger" onclick="openRestart(this)">重启</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>共 5 条</span>
<span>显示 10 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn"></button>
</div>
</div>
</main>
<!-- 设备详情弹窗 -->
<div class="modal-overlay" id="modal-detail" onclick="overlayClose(event,'modal-detail')">
<div class="modal modal--lg">
<div class="modal__header">
<div class="modal__title" id="detail-title">设备详情</div>
<button class="modal__close" onclick="closeModal('modal-detail')"></button>
</div>
<div class="modal__body">
<div class="descriptions">
<div class="desc-row">
<div class="desc-item"><div class="desc-label">医院</div><div class="desc-value" id="detail-hospital">CQ能源总医院</div></div>
<div class="desc-item"><div class="desc-label">设备编号</div><div class="desc-value" id="detail-device">D-1001</div></div>
</div>
<div class="desc-row">
<div class="desc-item"><div class="desc-label">版本</div><div class="desc-value" id="detail-version">v1.0.2</div></div>
<div class="desc-item"><div class="desc-label">状态</div><div class="desc-value"><span class="status-dot status-dot--online"></span>在线</div></div>
</div>
<div class="desc-row">
<div class="desc-item desc-item--span" style="flex:1;"><div class="desc-label">最后心跳</div><div class="desc-value">2026-09-15 10:23:45</div></div>
</div>
</div>
<!-- [交互] Tab 切换:健康快照 / 配置文件 -->
<div class="tabs">
<div class="tab active" onclick="switchTab(this,'detail-pane-health')">健康快照</div>
<div class="tab" onclick="switchTab(this,'detail-pane-config')">配置文件</div>
</div>
<div class="tab-pane active" id="detail-pane-health">
<pre class="code">{
"cpu": "12%",
"memory": "342MB / 2048MB",
"disk": "12.4GB / 80GB",
"jvm": "OpenJDK 17.0.8",
"uptime": "18 天 06:42:11",
"activeConnections": 3,
"queueSize": 0
}</pre>
</div>
<div class="tab-pane" id="detail-pane-config">
<div class="input-row mb-8">
<span style="font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;">医院编码:</span>
<input class="form-input form-input--inline" type="text" id="config-hospital" value="cq-oilfield" placeholder="如 jinghe">
<button class="btn btn-default btn-sm" onclick="toast('已读取最新配置')">读取</button>
<button class="btn btn-primary btn-sm" onclick="toast('配置已保存(重启后生效)')">保存</button>
</div>
<textarea class="form-textarea" rows="12" placeholder="点击读取加载当前配置">server:
port: 8082
hospital:
code: cq-oilfield
name: CQ能源总医院
datasource:
url: jdbc:postgresql://10.10.10.30:5432/health_medical
username: agent
password: ******</textarea>
<div class="tip">保存后需重启主程序生效(列表页操作重启)</div>
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-detail')">关闭</button>
</div>
</div>
</div>
<!-- 编辑设备弹窗 -->
<div class="modal-overlay" id="modal-edit" onclick="overlayClose(event,'modal-edit')">
<div class="modal">
<div class="modal__header">
<div class="modal__title" id="edit-title">编辑设备</div>
<button class="modal__close" onclick="closeModal('modal-edit')"></button>
</div>
<div class="modal__body">
<div class="form-item">
<div class="form-label">医院</div>
<input class="form-input" type="text" value="CQ能源总医院" disabled>
</div>
<div class="form-item">
<div class="form-label">设备编号</div>
<input class="form-input" type="text" value="D-1001" disabled>
</div>
<div class="form-item">
<div class="form-label">设备 TokenAgent WS 鉴权用)</div>
<div class="input-row">
<input class="form-input form-input--inline" type="text" id="device-token" value="Xk9mQ2pL8vNw5rT3yBcF7hJd">
<button class="btn btn-default" onclick="genToken()">重置生成</button>
</div>
</div>
<div class="form-item">
<div class="form-label">main 外部地址(B 端直连下载日志用)</div>
<input class="form-input" type="text" value="http://10.10.10.50:8082">
</div>
<div class="form-item">
<div class="form-label">main 管理 Token/api/admin 鉴权用)</div>
<input class="form-input" type="text" value="admin-token-2026">
</div>
<div class="tip">仅更新登记值,不自动下发到前置机;前置机侧配置需人工对齐并重启生效。</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-edit')">取消</button>
<button class="btn btn-primary" onclick="closeModal('modal-edit');toast('保存成功(前置机侧配置请人工对齐)')">保存</button>
</div>
</div>
</div>
<!-- 日志查看弹窗 -->
<div class="modal-overlay" id="modal-log" onclick="overlayClose(event,'modal-log')">
<div class="modal modal--lg" style="width:780px;">
<div class="modal__header">
<div class="modal__title" id="log-title">日志查看</div>
<button class="modal__close" onclick="closeModal('modal-log')"></button>
</div>
<div class="modal__body">
<div class="flex-between mb-8">
<span class="tip">日志文件列表(点击下载获取完整日志)</span>
<button class="btn btn-default btn-sm" onclick="toast('日志列表已刷新')">刷新</button>
</div>
<table>
<thead>
<tr><th>文件名</th><th>大小</th><th>最后修改</th><th class="td-actions">操作</th></tr>
</thead>
<tbody>
<tr><td>agent.log</td><td>2.4 M</td><td>2026-09-15 10:20:18</td><td class="td-actions"><button class="btn-link" onclick="toast('开始下载 agent.log')">下载</button></td></tr>
<tr><td>main.log</td><td>3.1 M</td><td>2026-09-15 10:20:11</td><td class="td-actions"><button class="btn-link" onclick="toast('开始下载 main.log')">下载</button></td></tr>
<tr><td>error.log</td><td>156 K</td><td>2026-09-14 22:45:03</td><td class="td-actions"><button class="btn-link" onclick="toast('开始下载 error.log')">下载</button></td></tr>
<tr><td>upgrade.log</td><td>89 K</td><td>2026-09-12 14:35:00</td><td class="td-actions"><button class="btn-link" onclick="toast('开始下载 upgrade.log')">下载</button></td></tr>
</tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-log')">关闭</button>
</div>
</div>
</div>
<!-- 升级管理弹窗 -->
<div class="modal-overlay" id="modal-upgrade" onclick="overlayClose(event,'modal-upgrade')">
<div class="modal modal--xl">
<div class="modal__header">
<div class="modal__title">升级管理</div>
<button class="modal__close" onclick="closeModal('modal-upgrade')"></button>
</div>
<div class="modal__body">
<div class="alert alert-warning">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1.5l7 13H1l7-13z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M8 6v4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg>
<div>
<div>升级按医院逐台指定下发,不支持全量统一更新</div>
<div class="tip" style="color:#AD6800;margin-top:2px;">各医院 jar 版本与数据源配置可能不同。上传后点行内「下发」多选在线医院逐台操作;进度见行内「下发进度」列。</div>
</div>
</div>
<!-- 上传区 -->
<div class="card">
<div class="card__title">上传新版本</div>
<div class="card__body">
<div class="input-row" style="flex-wrap:wrap;">
<span style="font-size:var(--font-sm);color:var(--text-secondary);">版本号:</span>
<input class="form-input" type="text" id="upgrade-version" placeholder="如 1.0.3" style="width:140px;">
<!-- [交互] 点击模拟上传 jar,成功后提示并刷新版本列表 -->
<button class="btn btn-primary" onclick="uploadVersion()">选择 jar 并上传</button>
<span id="upload-tip" style="color:var(--success);font-size:var(--font-sm);margin-left:12px;"></span>
</div>
</div>
</div>
<!-- 版本列表 -->
<table>
<thead>
<tr><th>版本号</th><th>上传人</th><th>下发进度</th><th>上传时间</th><th class="td-actions">操作</th></tr>
</thead>
<tbody>
<tr>
<td>v1.0.2</td><td>系统管理员</td>
<td>3/5(成功 2、升级中 1</td><td>2026-09-12 14:30</td>
<td class="td-actions">
<button class="btn-link" onclick="openModal('modal-dispatch')">下发</button>
<button class="btn-link" onclick="openModal('modal-tasks')">明细</button>
<button class="btn-link btn-link--danger" onclick="toast('该版本存在进行中任务,暂不可删除')">删除</button>
</td>
</tr>
<tr>
<td>v1.0.1</td><td>系统管理员</td>
<td>5/5(成功 5</td><td>2026-09-01 09:15</td>
<td class="td-actions">
<button class="btn-link" onclick="openModal('modal-dispatch')">下发</button>
<button class="btn-link" onclick="openModal('modal-tasks')">明细</button>
<button class="btn-link btn-link--danger" onclick="toast('版本 v1.0.1 已删除')">删除</button>
</td>
</tr>
<tr>
<td>v1.0.0</td><td>系统管理员</td>
<td>5/5(成功 5</td><td>2026-08-15 16:00</td>
<td class="td-actions">
<button class="btn-link" onclick="openModal('modal-dispatch')">下发</button>
<button class="btn-link" onclick="openModal('modal-tasks')">明细</button>
<button class="btn-link btn-link--danger" onclick="toast('版本 v1.0.0 已删除')">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-upgrade')">关闭</button>
</div>
</div>
</div>
<!-- 下发升级子弹窗 -->
<div class="modal-overlay modal-overlay--top" id="modal-dispatch" onclick="overlayClose(event,'modal-dispatch')">
<div class="modal modal--sm">
<div class="modal__header">
<div class="modal__title">下发升级(多选在线医院)</div>
<button class="modal__close" onclick="closeModal('modal-dispatch')"></button>
</div>
<div class="modal__body">
<div style="font-size:var(--font-sm);margin-bottom:12px;">版本:<b>v1.0.2</b></div>
<div class="checkbox-list">
<label class="checkbox"><input type="checkbox" checked> CQ能源总医院(D-1001</label>
<label class="checkbox"><input type="checkbox"> CQ西安医院(D-1002</label>
<label class="checkbox"><input type="checkbox"> CQ榆林医院(D-1004</label>
<label class="checkbox"><input type="checkbox"> CQ庆阳医院(D-1005</label>
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-dispatch')">取消</button>
<button class="btn btn-primary" onclick="closeModal('modal-dispatch');toast('下发完成:成功 1 台')">确认下发</button>
</div>
</div>
</div>
<!-- 下发明细子弹窗 -->
<div class="modal-overlay modal-overlay--top" id="modal-tasks" onclick="overlayClose(event,'modal-tasks')">
<div class="modal" style="width:820px;">
<div class="modal__header">
<div class="modal__title">版本 v1.0.2 下发明细</div>
<button class="modal__close" onclick="closeModal('modal-tasks')"></button>
</div>
<div class="modal__body">
<div class="flex-between mb-8">
<span class="tip">各医院升级进度(进行中任务实时刷新看最新状态)</span>
<button class="btn btn-default btn-sm" onclick="toast('进度已刷新')">刷新</button>
</div>
<table>
<thead>
<tr><th>设备</th><th>原版本</th><th>目标版本</th><th>状态</th><th>进度阶段</th><th>结果信息</th><th>下发时间</th></tr>
</thead>
<tbody>
<tr><td>D-1001</td><td>v1.0.1</td><td>v1.0.2</td><td><span class="tag tag-green">成功</span></td><td>已完成</td><td>升级成功</td><td>2026-09-12 14:35</td></tr>
<tr><td>D-1002</td><td>v1.0.1</td><td>v1.0.2</td><td><span class="tag tag-green">成功</span></td><td>已完成</td><td>升级成功</td><td>2026-09-12 14:36</td></tr>
<tr><td>D-1004</td><td>v1.0.0</td><td>v1.0.2</td><td><span class="tag tag-orange">升级中</span></td><td>下载 jar</td><td>下载中 45%</td><td>2026-09-12 14:38</td></tr>
<tr><td>D-1005</td><td>v1.0.1</td><td>v1.0.2</td><td><span class="tag tag-red">失败</span></td><td>校验</td><td>jar 校验失败:md5 不一致</td><td>2026-09-12 14:39</td></tr>
</tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-tasks')">关闭</button>
</div>
</div>
</div>
<!-- 接口测试弹窗 -->
<div class="modal-overlay" id="modal-apitest" onclick="overlayClose(event,'modal-apitest')">
<div class="modal modal--xl">
<div class="modal__header">
<div class="modal__title">接口测试</div>
<button class="modal__close" onclick="closeModal('modal-apitest')"></button>
</div>
<div class="modal__body">
<!-- 选设备 + 加载接口 -->
<div class="input-row mb-16">
<span style="font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;">目标设备:</span>
<select class="form-input" id="apitest-device" style="width:260px;flex:none;">
<option value="">选择在线医院设备</option>
<option>CQ能源总医院(D-1001</option>
<option>CQ西安医院(D-1002</option>
<option>CQ榆林医院(D-1004</option>
<option>CQ庆阳医院(D-1005</option>
</select>
<button class="btn btn-primary" onclick="loadApis()">加载接口</button>
</div>
<div class="alert alert-info" id="apitest-alert">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.3"/><path d="M8 7v4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="8" cy="5" r="0.8" fill="currentColor"/></svg>
<div>
<div>先选择在线医院设备,加载接口清单后填参数测试</div>
<div class="tip" style="color:#096DD9;margin-top:2px;">因前置机白名单,B 端通过 Agent 通道代理调用前置机本机接口。Agent 自动处理鉴权(/order→Basic, /api/admin→X-Admin-Token)。</div>
</div>
</div>
<!-- 接口测试主体(默认隐藏,加载接口后显示) -->
<div id="apitest-body" style="display:none;">
<!-- 选接口 -->
<div class="card">
<div class="card__title">选择接口</div>
<div class="card__body">
<select class="form-input" id="apitest-api" onchange="onApiChange(this.value)">
<option value="">请选择接口</option>
<option value="health">GET /health/status - 健康检查</option>
<option value="orderList">GET /order/list - 订单列表</option>
<option value="orderCreate">POST /order/create - 创建订单</option>
<option value="deviceInfo">GET /api/admin/device/info - 设备信息</option>
<option value="configSave">POST /api/admin/config/save - 保存配置</option>
</select>
<div id="api-desc" style="margin-top:8px;font-size:var(--font-sm);color:var(--text-secondary);display:none;">
<span class="method-tag method-tag--get" id="api-method">GET</span>
<span id="api-path" style="margin-left:8px;">/health/status</span>
</div>
</div>
</div>
<!-- 认证 -->
<div class="card" id="auth-card" style="display:none;">
<div class="card__title">认证</div>
<div class="card__body">
<div class="radio-group" onchange="onAuthChange()">
<label class="radio"><input type="radio" name="auth" value="none" checked></label>
<label class="radio"><input type="radio" name="auth" value="basic"> Basic 认证</label>
<label class="radio"><input type="radio" name="auth" value="token"> X-Admin-Token</label>
</div>
<div id="auth-basic" style="display:none;">
<div class="input-row">
<span class="param-label" style="width:auto;padding-top:8px;"><span class="req">*</span>账号</span>
<input class="form-input" type="text" placeholder="账号(必填)" style="width:160px;">
<span class="param-label" style="width:auto;padding-top:8px;"><span class="req">*</span>密码</span>
<input class="form-input" type="password" placeholder="密码(必填)" style="width:200px;">
</div>
<div class="tip">/order/** 接口用 HTTP Basic</div>
</div>
<div id="auth-token" style="display:none;">
<div class="input-row">
<span class="param-label" style="width:auto;padding-top:8px;"><span class="req">*</span>Token</span>
<input class="form-input" type="text" placeholder="X-Admin-Token 值(必填)" style="width:320px;">
</div>
<div class="tip">/api/admin/** 接口用</div>
</div>
</div>
</div>
<!-- 请求参数 -->
<div class="card" id="param-card" style="display:none;">
<div class="card__title">请求参数</div>
<div class="card__body">
<div id="param-fields"></div>
<button class="btn btn-primary" onclick="toast('接口调用成功(模拟)')">执行</button>
</div>
</div>
<!-- 响应结果 -->
<div class="card" id="response-card" style="display:none;">
<div class="card__title">响应结果</div>
<div class="card__body">
<div style="margin-bottom:8px;">
<span class="status-code status-code--ok">200</span>
</div>
<pre class="code">{
"code": 0,
"message": "success",
"data": {
"status": "UP"
}
}</pre>
</div>
</div>
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-apitest')">关闭</button>
</div>
</div>
</div>
<!-- 操作审计弹窗 -->
<div class="modal-overlay" id="modal-audit" onclick="overlayClose(event,'modal-audit')">
<div class="modal modal--xxl">
<div class="modal__header">
<div class="modal__title">操作审计</div>
<button class="modal__close" onclick="closeModal('modal-audit')"></button>
</div>
<div class="modal__body">
<table>
<thead>
<tr><th>设备</th><th>操作</th><th>参数</th><th>结果</th><th>操作人</th><th>时间</th></tr>
</thead>
<tbody>
<tr><td>D-1001</td><td>重启</td><td>{"deviceId":"D-1001"}</td><td><span class="tag tag-green">成功</span></td><td>系统管理员</td><td>2026-09-15 09:10:22</td></tr>
<tr><td>D-1002</td><td>升级下发</td><td>{"upgradeId":"1002","toVersion":"v1.0.2"}</td><td><span class="tag tag-green">成功</span></td><td>系统管理员</td><td>2026-09-12 14:36:10</td></tr>
<tr><td>D-1003</td><td>读取配置</td><td>{"hospital":"cq-yanan"}</td><td><span class="tag tag-green">成功</span></td><td>系统管理员</td><td>2026-09-11 16:20:45</td></tr>
<tr><td>D-1001</td><td>保存配置</td><td>{"hospital":"cq-oilfield"}</td><td><span class="tag tag-green">成功</span></td><td>系统管理员</td><td>2026-09-10 11:02:33</td></tr>
<tr><td>D-1004</td><td>接口代理</td><td>{"path":"/health/status","method":"GET"}</td><td><span class="tag tag-red">失败</span></td><td>系统管理员</td><td>2026-09-09 15:44:08</td></tr>
</tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-audit')">关闭</button>
</div>
</div>
</div>
<!-- 重启确认弹窗 -->
<div class="modal-overlay" id="modal-restart" onclick="overlayClose(event,'modal-restart')">
<div class="modal modal--sm">
<div class="modal__header">
<div class="modal__title">确认重启</div>
<button class="modal__close" onclick="closeModal('modal-restart')"></button>
</div>
<div class="modal__body">
<div style="font-size:var(--font-base);" id="restart-content">确认远程重启「CQ能源总医院」主程序?</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-restart')">取消</button>
<button class="btn btn-primary" onclick="closeModal('modal-restart');toast('重启指令已下发')">确认</button>
</div>
</div>
</div>
<!-- 轻提示 -->
<div class="toast" id="toast"></div>
<script>
/* 弹窗开关 */
function openModal(id){ document.getElementById(id).classList.add('open'); }
function closeModal(id){ document.getElementById(id).classList.remove('open'); }
/* 点击遮罩空白处关闭弹窗 */
function overlayClose(evt,id){ if(evt.target === evt.currentTarget) closeModal(id); }
/* 从表格行读取医院名称(点击操作按钮时传 this 即按钮元素) */
function rowHospital(btn){
var row = btn.closest('tr');
return row.children[1].textContent.trim();
}
/* 设备详情:填充医院名到标题 */
function openDetail(btn){
document.getElementById('detail-title').textContent = '设备详情 - ' + rowHospital(btn);
document.getElementById('detail-hospital').textContent = rowHospital(btn);
openModal('modal-detail');
}
function openEdit(btn){
document.getElementById('edit-title').textContent = '编辑设备 - ' + rowHospital(btn);
openModal('modal-edit');
}
function openLog(btn){
document.getElementById('log-title').textContent = '日志查看 - ' + rowHospital(btn);
openModal('modal-log');
}
function openRestart(btn){
document.getElementById('restart-content').textContent = '确认远程重启「' + rowHospital(btn) + '」主程序?';
openModal('modal-restart');
}
/* 详情弹窗 Tab 切换 */
function switchTab(tab, paneId){
var tabs = tab.parentElement.querySelectorAll('.tab');
tabs.forEach(function(t){ t.classList.remove('active'); });
tab.classList.add('active');
var parent = tab.parentElement.parentElement;
parent.querySelectorAll('.tab-pane').forEach(function(p){ p.classList.remove('active'); });
document.getElementById(paneId).classList.add('active');
}
/* 编辑设备:随机生成 24 位 deviceToken */
function genToken(){
var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
var s = '';
for(var i=0;i<24;i++){ s += chars[Math.floor(Math.random()*chars.length)]; }
document.getElementById('device-token').value = s;
}
/* 升级管理:模拟上传 jar */
function uploadVersion(){
var v = document.getElementById('upgrade-version').value.trim();
if(!v){ toast('请先填写版本号'); return; }
document.getElementById('upload-tip').textContent = 'v' + v + ' 已上传';
document.getElementById('upgrade-version').value = '';
toast('上传成功');
}
/* 接口测试:加载接口清单 */
function loadApis(){
var dev = document.getElementById('apitest-device').value;
if(!dev){ toast('请先选择在线医院设备'); return; }
document.getElementById('apitest-alert').style.display = 'none';
document.getElementById('apitest-body').style.display = 'block';
toast('接口清单已加载');
}
/* 接口测试:选择接口后联动显示认证与参数 */
function onApiChange(val){
if(!val) return;
document.getElementById('api-desc').style.display = 'block';
document.getElementById('auth-card').style.display = 'block';
document.getElementById('param-card').style.display = 'block';
document.getElementById('response-card').style.display = 'block';
var pathText = val;
var isPost = val === 'orderCreate' || val === 'configSave';
var methodEl = document.getElementById('api-method');
methodEl.textContent = isPost ? 'POST' : 'GET';
methodEl.className = 'method-tag ' + (isPost ? 'method-tag--post' : 'method-tag--get');
document.getElementById('api-path').textContent = val;
// 根据接口渲染参数(简化:仅示意动态参数区)
renderParams(isPost);
}
/* 渲染请求参数(模拟) */
function renderParams(isPost){
var box = document.getElementById('param-fields');
if(isPost){
box.innerHTML = '<div class="param-row"><span class="param-label"><span class="req">*</span>body<span class="in-tag">body</span></span><textarea class="param-textarea" rows="5" placeholder=\'{"hospitalCode":"cq-oilfield"}\'></textarea></div>';
}else{
box.innerHTML = '<div class="param-row"><span class="param-label"><span class="req">*</span>hospitalCode<span class="in-tag">query</span></span><input class="param-input" type="text" placeholder="医院编码"></div>';
}
}
/* 接口测试:认证方式切换 */
function onAuthChange(){
var v = document.querySelector('input[name="auth"]:checked').value;
document.getElementById('auth-basic').style.display = v === 'basic' ? 'block' : 'none';
document.getElementById('auth-token').style.display = v === 'token' ? 'block' : 'none';
}
/* 轻提示 */
var toastTimer = null;
function toast(msg){
var el = document.getElementById('toast');
el.textContent = msg;
el.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(function(){ el.classList.remove('show'); }, 2000);
}
</script>
</body>
</html>
@@ -2,6 +2,12 @@
## [Unreleased] 2026-09-15
### Added
- 前置机管理:新增 `agent-device.html`(常规体检前置服务)页面,参照老系统 cqyt-admin hospitalAgent 复刻医院前置机设备统一运维能力。顶栏新增"前置机管理"一级菜单(位于检后与异常事件之间),侧边栏新增"前置机管理"分组(唯一二级菜单"常规体检前置服务");设备列表列:设备编号/医院/版本/在线状态(绿/灰圆点)/最后心跳/操作(详情/编辑/日志/重启,操作列右侧冻结);顶部按钮:升级管理/接口测试/操作审计;页内 7 个弹窗——设备详情(描述信息 + 健康快照/配置文件 Tab 切换)、编辑设备(设备 Token 重置生成 + main 外部地址 + main 管理 Token)、日志查看(日志文件列表 + 下载)、升级管理(上传版本 + 版本列表 + 下发子弹窗多选在线医院 + 明细子弹窗各医院进度)、接口测试(选设备加载接口 + 认证方式切换 + 动态参数 + 响应结果)、操作审计(审计记录表)、重启二次确认
- `index.html` SITE_MAP 健康体检模块新增"前置机管理 · 常规体检前置服务"入口(位于异常事件之前)
- `PRD.md`:新增"前置机管理"模块——模块概述补充、页面导航结构(顶部导航 + 侧边栏节点)、通用说明顶部导航分组、页面清单新增"六、前置机管理"分组(原异常事件改为"七、"并序号 66→67/68 后移)、逐页说明新增"六、前置机管理"章节(功能说明/筛选条件/表格列/业务规则/6 个弹窗说明/交互说明)
- 新增截图 `doc/screenshots/agent-device.jpg`
### Changed
- `PRD.md`:补充 6 处需求澄清/待确认说明——体检须知(短信内容配置与预览、多条短信拆分)、单位体检项目管理(项目仅可从医院同步数据中选择、不支持改名、逻辑删除与找回)、医院体检项目(指定医院/全部医院同步)、员工自选项目(关联项目需考虑体检额度)、组合模板/例外配置(增加默认费用显示、模板例外优先级与自选项目不重复展示)、单位体检项目审核("修改/编辑"文案改为"修改费用"
+60 -8
View File
@@ -2,7 +2,7 @@
## 模块概述
健康体检管理端为管理人员提供员工体检业务全流程的管理能力,按体检阶段划分为检前、检中、检后三大域,并配套档案查询、基础配置与异常事件。检前涵盖医院承检范围、单位体检计划、体检额度、体检项目费用与组合模板、特殊人员(防癌筛查/职业体检/胃肠镜/冠脉CTA)配置;检中涵盖员工预约跟踪、已选项目、已检员工与单位/医院/医生多维预约与实时统计;检后涵盖体检报告(常规/胃肠镜/冠脉CTA)、员工体检大表、大病人员、指标异常通知、多维体检统计与病种信息管理。除常规体检外,胃肠镜体检与冠脉CTA体检两条专项线在检前/检中/检后均设有独立页面组。
健康体检管理端为管理人员提供员工体检业务全流程的管理能力,按体检阶段划分为检前、检中、检后三大域,并配套档案查询、基础配置与异常事件。检前涵盖医院承检范围、单位体检计划、体检额度、体检项目费用与组合模板、特殊人员(防癌筛查/职业体检/胃肠镜/冠脉CTA)配置;检中涵盖员工预约跟踪、已选项目、已检员工与单位/医院/医生多维预约与实时统计;检后涵盖体检报告(常规/胃肠镜/冠脉CTA)、员工体检大表、大病人员、指标异常通知、多维体检统计与病种信息管理。除常规体检外,胃肠镜体检与冠脉CTA体检两条专项线在检前/检中/检后均设有独立页面组。此外,前置机管理为医院前置机设备提供统一运维能力(设备列表、升级下发、接口测试、操作审计)。
### 页面导航结构
@@ -14,6 +14,7 @@
│ ├── 检前 → pre-hospital-scope.html
│ ├── 检中 → mid-appointment-track.html
│ ├── 检后 → post-unit-count.html
│ ├── 前置机管理 → agent-device.html
│ └── 异常事件 → exception-dirty.html
├── 档案(侧边栏)
@@ -121,6 +122,10 @@
│ └── 冠脉CTA体检统计 → post-cta-stat.html
│ └── Tab:二级单位 / 三级单位
├── 前置机管理(侧边栏)
│ └── 常规体检前置服务 → agent-device.html
│ └── 弹窗:设备详情 / 编辑设备 / 日志查看 / 升级管理 / 接口测试 / 操作审计 / 重启确认
└── 异常事件(侧边栏)
├── 系统脏数据 → exception-dirty.html
└── 结果异常 → exception-result.html
@@ -130,7 +135,7 @@
## 通用说明
- **页面框架:** 所有页面共用顶部导航(健康体检 logo + 档案/基础配置/检前/检中/检后/异常事件 + 返回首页 + 系统管理员)与左侧深色侧边栏(240px,分组可折叠,页面加载时自动展开并高亮当前页所在分组)。
- **页面框架:** 所有页面共用顶部导航(健康体检 logo + 档案/基础配置/检前/检中/检后/前置机管理/异常事件 + 返回首页 + 系统管理员)与左侧深色侧边栏(240px,分组可折叠,页面加载时自动展开并高亮当前页所在分组)。
- **状态标签色规范:** 绿色(有效/审核通过/已生成/成功/已体检/是)、橙色(待审核/生成中/未体检/未预约/生成中)、红色(无效/已拒绝/已取消预约/失败/危急/否)、蓝色(体检中/进行中/同步中/部分勾选/蓝色链接)、灰色(未提交/不跳过)。
- **操作按钮惯例:** 列表页操作栏普遍提供"导出列表数据/查看导出任务";人员类页面另有"批量导入人员/查看导入任务";删除类操作均有确认提示。
- **体检年份筛选:** 各列表页默认定位当前年度。
@@ -230,12 +235,18 @@
| 64 | 冠脉CTA体检报告 | post-cta-report.html | 页面 |
| 65 | 冠脉CTA体检统计 | post-cta-stat.html | 页面 |
### 六、异常事件(侧边栏:异常事件
### 六、前置机管理(侧边栏:前置机管理
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 66 | 系统脏数据 | exception-dirty.html | 页面 |
| 67 | 结果异常 | exception-result.html | 页面 |
| 66 | 常规体检前置服务(含设备详情/编辑设备/日志查看/升级管理/接口测试/操作审计/重启确认弹窗) | agent-device.html | 页面 |
### 七、异常事件(侧边栏:异常事件)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 67 | 系统脏数据 | exception-dirty.html | 页面 |
| 68 | 结果异常 | exception-result.html | 页面 |
---
@@ -1570,9 +1581,50 @@
---
## 六、异常事件
## 六、前置机管理
### 66. 系统脏数据(exception-dirty.html
### 66. 常规体检前置服务(agent-device.html
![页面截图](screenshots/agent-device.jpg)
**功能说明:** 医院前置机设备统一运维管理,展示各医院前置机设备的在线状态、版本与心跳信息,并提供设备详情、编辑、日志查看、远程重启、升级管理、接口测试与操作审计能力。
**筛选条件:** 医院编码(输入框)、设备编号(输入框)+ 查询/重置按钮
**操作按钮:** 升级管理、接口测试、操作审计
**表格列:** 设备编号、医院、版本、在线状态(绿色圆点"在线"/灰色圆点"离线")、最后心跳、操作(详情/编辑/日志/重启)
**业务规则:**
- 在线状态由 Agent WebSocket 心跳实时维护,列表 30 秒自动刷新
- 重启操作需二次确认,确认后远程下发重启指令
**设备详情弹窗:** 标题"设备详情 - 医院",上半部描述信息(医院/设备编号/版本/状态/最后心跳),下半部两个 Tab:
- 健康快照:JSON 展示 CPU、内存、磁盘、JVM、运行时长、连接数等运行时指标
- 配置文件:医院编码(输入框)+ 读取/保存按钮 + 配置内容(多行文本域);保存后需重启主程序生效
**编辑设备弹窗:** 标题"编辑设备 - 医院",医院与设备编号只读;可编辑设备 Token(支持"重置生成"随机 24 位)、main 外部地址、main 管理 Token;仅更新登记值,不自动下发,前置机侧配置需人工对齐并重启生效。
**日志查看弹窗:** 标题"日志查看 - 医院",展示前置机日志文件列表(文件名/大小/最后修改),支持刷新与逐条下载。
**升级管理弹窗:** 标题"升级管理",顶部黄色警告(升级按医院逐台指定下发,不支持全量统一更新);含上传新版本(版本号输入框 + 选择 jar 上传)与版本列表(版本号/上传人/下发进度/上传时间),行内操作:
- 下发:弹出"下发升级(多选在线医院)"子弹窗,勾选在线医院后确认下发
- 明细:弹出"版本下发明细"子弹窗,展示各医院升级进度(设备/原版本/目标版本/状态/进度阶段/结果信息/下发时间)
- 删除:仅无进行中任务才可删除
**接口测试弹窗:** 标题"接口测试",选择在线医院设备并加载接口清单后,选择接口并填写认证方式(无/Basic 认证/X-Admin-Token)与动态参数,执行后展示响应结果(状态码 + 响应体)。
**操作审计弹窗:** 标题"操作审计",展示设备操作审计记录(设备/操作/参数/结果/操作人/时间)。
**交互说明:**
- 顶部"升级管理/接口测试/操作审计"三个按钮分别打开对应弹窗
- 表格行操作"详情/编辑/日志"打开对应弹窗,"重启"先弹二次确认
---
## 七、异常事件
### 67. 系统脏数据(exception-dirty.html
![页面截图](screenshots/exception-dirty.jpg)
@@ -1590,7 +1642,7 @@
---
### 67. 结果异常(exception-result.html
### 68. 结果异常(exception-result.html
![页面截图](screenshots/exception-result.jpg)
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB