feat: 一线医疗管理端首版原型(45页覆盖档案/基础配置/日常业务/床头呼叫/数据统计/健康小屋/异常事件七大分区:就医与单位报表数据、算法模型、医疗点/合作医院/医护人员/药品与库存/分配/救护车配置及统计双视图、员工与非员工就诊、出入库、随访与慢病干预、员工档案、解读任务、健康宣教、楼栋宿舍床位设备四级管理与分配/呼叫/超时告警/统计、医疗点统计双视图、小屋轮播/健康室/预约/排班/大屏/小屋管理与统计、脏数据与结果异常;首页SITE_MAP同步一线医疗全量页面并将医疗点PAD端入口调整为任务中心;新增模块CHANGELOG,移除目录占位.gitkeep)

This commit is contained in:
fengpu
2026-09-07 18:56:08 +08:00
parent d76285aa4a
commit b4f3d5cb8c
48 changed files with 18831 additions and 2 deletions
+48 -2
View File
@@ -586,7 +586,53 @@ const SITE_MAP = [
{ file:'professional.html', label:'正在应急' },
{ file:'chat-message.html', label:'对话消息' }
]},
{ name:'一线医疗', dir:'web-admin/frontline-medical', pages:[] }
{ name:'一线医疗', dir:'web-admin/frontline-medical', entry:'archive-visit-data.html', done:true, pages:[
{ file:'archive-visit-data.html', label:'档案 · 个人就医数据' },
{ file:'archive-unit-report.html', label:'档案 · 单位报表数据' },
{ file:'archive-algo-model.html', label:'档案 · 算法模型' },
{ file:'config-medical-point.html', label:'基础配置 · 医疗点配置' },
{ file:'config-partner-hospital.html', label:'基础配置 · 合作医院' },
{ file:'config-medical-staff.html', label:'基础配置 · 医护人员管理' },
{ file:'config-medicine.html', label:'基础配置 · 药品信息' },
{ file:'config-medicine-stock.html', label:'基础配置 · 实时库存' },
{ file:'config-allocate.html', label:'基础配置 · 分配管理' },
{ file:'config-allocate-stat.html', label:'基础配置 · 分配统计' },
{ file:'config-ambulance.html', label:'基础配置 · 救护车列表' },
{ file:'stat-ambulance-table.html', label:'基础配置 · 救护车统计(表格视图)' },
{ file:'stat-ambulance-chart.html', label:'基础配置 · 救护车统计(图表视图)' },
{ file:'daily-treatment-staff.html', label:'日常业务 · 员工就诊' },
{ file:'daily-treatment-nonstaff.html', label:'日常业务 · 非员工就诊' },
{ file:'daily-stock-in.html', label:'日常业务 · 入库记录' },
{ file:'daily-stock-out.html', label:'日常业务 · 出库记录' },
{ file:'daily-follow.html', label:'日常业务 · 随访管理' },
{ file:'daily-chronic.html', label:'日常业务 · 慢病管理' },
{ file:'daily-chronic-intervention.html', label:'日常业务 · 慢病干预(二级页)' },
{ file:'daily-employee-search.html', label:'日常业务 · 员工档案' },
{ file:'daily-follow-task.html', label:'日常业务 · 随访任务' },
{ file:'daily-interpret.html', label:'日常业务 · 解读任务' },
{ file:'daily-education.html', label:'日常业务 · 健康宣教管理' },
{ file:'daily-interpret-manage.html', label:'日常业务 · 报告解读管理' },
{ file:'bed-building.html', label:'床头呼叫 · 楼栋管理' },
{ file:'bed-dormitory.html', label:'床头呼叫 · 宿舍管理' },
{ file:'bed-bed.html', label:'床头呼叫 · 床位管理' },
{ file:'bed-device.html', label:'床头呼叫 · 设备列表' },
{ file:'bed-device-allocate.html', label:'床头呼叫 · 设备分配' },
{ file:'bed-allocate-record.html', label:'床头呼叫 · 分配记录' },
{ file:'bed-call-record.html', label:'床头呼叫 · 呼叫记录' },
{ file:'bed-timeout-alarm.html', label:'床头呼叫 · 超时未归告警' },
{ file:'bed-stat.html', label:'床头呼叫 · 统计汇总' },
{ file:'stat-clinic-table.html', label:'数据统计 · 医疗点统计-表格' },
{ file:'stat-clinic-chart.html', label:'数据统计 · 医疗点统计-图表' },
{ file:'hut-banner.html', label:'健康小屋 · 轮播图配置' },
{ file:'hut-room.html', label:'健康小屋 · 健康室管理' },
{ file:'hut-appointment.html', label:'健康小屋 · 预约管理' },
{ file:'hut-schedule.html', label:'健康小屋 · 默认排班' },
{ file:'hut-screen.html', label:'健康小屋 · 大屏数据配置' },
{ file:'hut-cabin.html', label:'健康小屋 · 小屋管理' },
{ file:'hut-cabin-stat.html', label:'健康小屋 · 小屋统计' },
{ file:'alert-dirty.html', label:'异常事件 · 系统脏数据' },
{ file:'alert-abnormal.html', label:'异常事件 · 结果异常' }
]}
]},
{ id:'employee-app', label:'员工端 APP', icon:'app', color:'#95de64', modules:[
{ name:'AI会话', dir:'employee-app/ai', pages:[] },
@@ -932,7 +978,7 @@ const SITE_MAP = [
] },
{ name:'标准版中心大屏', dir:'other-module/emergency-screen/standard-center', pages:[] },
{ name:'九厂应急中心大屏', dir:'other-module/emergency-screen/jiuchang-center', pages:[] },
{ name:'医疗点PAD端', dir:'other-module/medical-pad', entry:'login.html', pages:[
{ name:'医疗点PAD端', dir:'other-module/medical-pad', entry:'task.html', pages:[
{label:'登录页', file:'login.html'},
{label:'任务中心', file:'task.html'},
{label:'日常诊疗', file:'daily.html'},
@@ -0,0 +1,319 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>结果异常 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 22px;display:flex;align-items:center;gap:14px;}
.stat-card__ico{width:40px;height:40px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;flex-shrink:0;}
.stat-card__value{font-size:22px;font-weight:600;color:var(--text-primary);}
.stat-card__label{font-size:var(--font-xs);color:var(--text-secondary);margin-top:2px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1600px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:480px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
.modal--wide{width:680px;max-height:82vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:14px 0 10px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1.5;font-family:inherit;}
.timeline{position:relative;padding-left:20px;}
.timeline::before{content:'';position:absolute;left:5px;top:4px;bottom:4px;width:1px;background:var(--border);}
.tl-item{position:relative;padding-bottom:14px;}
.tl-item:last-child{padding-bottom:0;}
.tl-item::before{content:'';position:absolute;left:-19px;top:4px;width:9px;height:9px;border-radius:50%;background:#fff;border:2px solid var(--primary);}
.tl-item__head{font-size:var(--font-xs);color:var(--text-placeholder);}
.tl-item__body{font-size:var(--font-sm);color:var(--text-primary);margin-top:2px;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html" class="active">异常事件</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="alert-dirty.html" class="sidebar-item">系统脏数据</a>
<a href="alert-abnormal.html" class="sidebar-item active">结果异常</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>异常类型</label>
<select class="filter-select" id="filterType" onchange="renderTable()" style="min-width:140px;">
<option value="">全部类型</option><option>缺货预警</option><option>多重耐药菌检出</option><option>体征极端值</option>
</select>
</div>
<div class="filter-item">
<label>严重度</label>
<select class="filter-select" id="filterLevel" onchange="renderTable()" style="min-width:100px;">
<option value="">全部</option><option></option><option></option><option></option>
</select>
</div>
<div class="filter-item">
<label>处理状态</label>
<select class="filter-select" id="filterStatus" onchange="renderTable()" style="min-width:110px;">
<option value="">全部状态</option><option>待处理</option><option>处理中</option><option>已关闭</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
预警规则:急救类药品低于安全库存 7 天用量 → 高危实时推送;常规缺货与极端体征 → 每日 08:00 定时汇总;多重耐药菌检出 → 实时推送管理医生与合作医院院感科。缺货阈值见「档案 → 算法模型 → 缺货预警模型」。
</div>
<!-- [交互] 顶部统计卡 -->
<div class="stat-row" id="statRow"></div>
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="min-width:130px;">异常编号</th><th>异常类型</th><th style="min-width:190px;">预警对象</th><th style="min-width:280px;">预警内容</th>
<th>严重度</th><th>触发时间</th><th>推送方式</th><th>处理状态</th><th style="width:110px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 处理弹窗:生成待办并转派 -->
<div class="modal-mask" id="handleMask">
<div class="modal">
<div class="modal__title">处理预警 · <span id="handleNo"></span></div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 处理措施</span>
<select class="form-control" id="hWay">
<option value="">请选择处理措施</option>
<option>下采购调拨单</option><option>安排复诊复查</option><option>通知管理医生干预</option><option>联合合作医院处置</option><option>误报忽略</option>
</select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 转派处理人</span>
<select class="form-control" id="hWho">
<option value="">请选择处理人</option>
<option>数据专员 闫婷</option><option>运维专员 邓成</option><option>管理医生 曲志明</option><option>医疗点负责人(按单位)</option>
</select>
</div>
<div class="form-item">
<span class="form-label">处理意见</span>
<textarea class="form-control" id="hNote" placeholder="填写处理要求与完成时限"></textarea>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeHandle()">取消</button>
<button class="btn btn-primary" onclick="submitHandle()">生成待办并转派</button>
</div>
</div>
</div>
<!-- [交互] 详情弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">预警详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 预警对象与「实时库存」页缺口、随访/慢病页人员勾稽 */
var alerts=[
{no:'YC-20260907-01',type:'缺货预警',obj:'桐园作业区医疗点 · 藿香正气水',content:'现存 0 盒(安全库存 20),急救类药品断货',level:'高',time:'2026-09-07 06:30',push:'实时推送',status:'待处理',who:'数据专员 闫婷',logs:[{by:'中台引擎',time:'2026-09-07 06:30',note:'缺货预警模型 V2.0 判定:可支撑 0 天 < 7 天阈值,生成待办工单并推送运维。'}]},
{no:'YC-20260907-02',type:'缺货预警',obj:'姬圪台作业区医疗点 · 蒙脱石散',content:'现存 15 盒(安全库存 25),按近30天速率可支撑约 8 天',level:'中',time:'2026-09-07 08:00',push:'每日定时',status:'待处理',who:'运维专员 邓成',logs:[{by:'中台引擎',time:'2026-09-07 08:00',note:'每日定时汇总推送,待运维确认补货计划。'}]},
{no:'YC-20260906-03',type:'缺货预警',obj:'安塞采油队长住点医疗室 · 二甲双胍缓释片',content:'现存 12 盒(安全库存 20),慢病常用药偏低',level:'中',time:'2026-09-06 08:00',push:'每日定时',status:'处理中',who:'数据专员 闫婷',logs:[{by:'中台引擎',time:'2026-09-06 08:00',note:'纳入每日缺货汇总。'},{by:'运维专员 邓成',time:'2026-09-06 15:20',note:'已提交调拨申请(桐园→安塞 10 盒),在途预计 09-09 到货。'}]},
{no:'YC-20260906-04',type:'多重耐药菌检出',obj:'刘玉梅(CQ036441)· 靖边气田医疗点',content:'合作医院标本培养回报 MRSA(耐甲氧西林金黄色葡萄球菌)阳性',level:'高',time:'2026-09-06 11:47',push:'实时推送',status:'处理中',who:'管理医生 曲志明',logs:[{by:'中台引擎',time:'2026-09-06 11:47',note:'检验结果结构化解析命中耐药菌字典,实时推送管理医生与院感科。'},{by:'管理医生 曲志明',time:'2026-09-06 16:00',note:'已联系本人安排复诊,指导家庭防护与工位消毒。'}]},
{no:'YC-20260905-05',type:'体征极端值',obj:'赵万福(CQ033096)· 桐园作业区医疗点',content:'随访录入血压 182/110 mmHg,达高血压危象风险区间',level:'高',time:'2026-09-05 09:18',push:'实时推送',status:'已关闭',who:'管理医生 曲志明',logs:[{by:'中台引擎',time:'2026-09-05 09:18',note:'随访体征极端值规则命中,推送管理医生。'},{by:'管理医生 曲志明',time:'2026-09-05 10:30',note:'电话指导含服卡托普利,当日 loop 复测 148/92。'},{by:'管理医生 曲志明',time:'2026-09-06 08:40',note:'复诊后调整用药方案,预警闭环关闭。'}]},
{no:'YC-20260904-06',type:'缺货预警',obj:'靖边气田医疗点 · 碘伏棉签',content:'现存 18 袋(安全库存 20),医用耗材接近缺口',level:'低',time:'2026-09-04 08:00',push:'每日定时',status:'已关闭',who:'运维专员 邓成',logs:[{by:'中台引擎',time:'2026-09-04 08:00',note:'低严重度,仅定时提醒。'},{by:'运维专员 邓成',time:'2026-09-05 10:00',note:'随下次采购合并补货,预警关闭。'}]},
{no:'YC-20260903-07',type:'多重耐药菌检出',obj:'高建军(CQ010564)· 西峰基地医疗点',content:'痰培养回报 ESBL(产超广谱β内酰胺酶)大肠埃希菌',level:'中',time:'2026-09-03 08:00',push:'每日定时',status:'已关闭',who:'管理医生 曲志明',logs:[{by:'中台引擎',time:'2026-09-03 08:00',note:'定时汇总推送。'},{by:'管理医生 曲志明',time:'2026-09-04 09:30',note:'按药敏结果调整抗感染方案,复查转阴,关闭。'}]},
{no:'YC-20260902-08',type:'体征极端值',obj:'石维国(CQ021578)· 樊学作业区医疗点',content:'随访录入空腹血糖 15.2 mmol/L,显著高于控制目标',level:'中',time:'2026-09-02 08:00',push:'每日定时',status:'已关闭',who:'管理医生 曲志明',logs:[{by:'中台引擎',time:'2026-09-02 08:00',note:'定时汇总推送。'},{by:'管理医生 曲志明',time:'2026-09-03 11:00',note:'加强饮食干预与用药,随访已回落,关闭。'}]}
];
var typeBadge={'缺货预警':'badge--warning','多重耐药菌检出':'badge--danger','体征极端值':'badge--danger'};
var levelBadge={'高':'badge--danger','中':'badge--warning','低':'badge--info'};
var statusBadge={'待处理':'badge--warning','处理中':'badge--info','已关闭':'badge--muted'};
function renderStats(){
var open=alerts.filter(function(a){return a.status==='待处理';}).length;
var doing=alerts.filter(function(a){return a.status==='处理中';}).length;
var high=alerts.filter(function(a){return a.level==='高'&&a.status!=='已关闭';}).length;
var stock=alerts.filter(function(a){return a.type==='缺货预警';}).length;
document.getElementById('statRow').innerHTML=
'<div class="stat-card"><div class="stat-card__ico" style="background:#FFF1F0;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 2l8 14H2L10 2z" stroke="#FF4D4F" stroke-width="1.5" stroke-linejoin="round"/><path d="M10 8v4M10 14v.5" stroke="#FF4D4F" stroke-width="1.6" stroke-linecap="round"/></svg></div><div><div class="stat-card__value">'+open+'</div><div class="stat-card__label">待处理预警</div></div></div>'
+'<div class="stat-card"><div class="stat-card__ico" style="background:#E6F7FF;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="7.5" stroke="#1890FF" stroke-width="1.5"/><path d="M10 6v4l2.5 2" stroke="#1890FF" stroke-width="1.5" stroke-linecap="round"/></svg></div><div><div class="stat-card__value">'+doing+'</div><div class="stat-card__label">处理中(待办流转)</div></div></div>'
+'<div class="stat-card"><div class="stat-card__ico" style="background:#FFF1F0;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 17s-6.5-4-6.5-8.5A3.5 3.5 0 0 1 10 6a3.5 3.5 0 0 1 6.5 2.5C16.5 13 10 17 10 17z" stroke="#FF4D4F" stroke-width="1.5"/></svg></div><div><div class="stat-card__value">'+high+'</div><div class="stat-card__label">高危未闭环</div></div></div>'
+'<div class="stat-card"><div class="stat-card__ico" style="background:#FFFBE6;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="3" y="7" width="14" height="10" rx="1.5" stroke="#FAAD14" stroke-width="1.5"/><path d="M7 7V5a3 3 0 0 1 6 0v2" stroke="#FAAD14" stroke-width="1.5"/></svg></div><div><div class="stat-card__value">'+stock+'</div><div class="stat-card__label">缺货类预警(本月累计)</div></div></div>';
}
function renderTable(){
var t=document.getElementById('filterType').value,l=document.getElementById('filterLevel').value,s=document.getElementById('filterStatus').value;
var list=alerts.filter(function(a){return (!t||a.type===t)&&(!l||a.level===l)&&(!s||a.status===s);});
document.getElementById('tbody').innerHTML=list.map(function(a){
var i=alerts.indexOf(a);
var ops='<button class="btn btn-link" onclick="openDetail('+i+')">详情</button>';
if(a.status==='待处理')ops='<button class="btn btn-link" onclick="openHandle('+i+')">处理</button>'+ops;
if(a.status==='处理中')ops='<button class="btn btn-link" onclick="closeAlert('+i+')">关闭</button>'+ops;
return '<tr><td style="font-family:monospace;">'+a.no+'</td>'
+'<td><span class="badge '+(typeBadge[a.type])+'">'+a.type+'</span></td>'
+'<td style="text-align:left;">'+a.obj+'</td><td style="text-align:left;white-space:normal;min-width:280px;">'+a.content+'</td>'
+'<td><span class="badge '+(levelBadge[a.level])+'">'+a.level+'</span></td>'
+'<td>'+a.time+'</td><td>'+a.push+'</td><td><span class="badge '+(statusBadge[a.status])+'">'+a.status+'</span></td>'
+'<td>'+ops+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的预警记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 条预警';
renderStats();
}
function resetFilter(){['filterType','filterLevel','filterStatus'].forEach(function(id){document.getElementById(id).value='';});renderTable();}
var handleId=null;
function openHandle(i){
handleId=i;
document.getElementById('handleNo').textContent=alerts[i].no;
document.getElementById('hWay').value='';document.getElementById('hWho').value='';document.getElementById('hNote').value='';
document.getElementById('handleMask').classList.add('open');
}
function closeHandle(){document.getElementById('handleMask').classList.remove('open');}
function submitHandle(){
var way=document.getElementById('hWay').value,who=document.getElementById('hWho').value,note=document.getElementById('hNote').value.trim();
if(!way){showToast('请选择处理措施');return;}
if(!who){showToast('请选择转派处理人');return;}
var a=alerts[handleId];
if(way==='误报忽略'){
a.status='已关闭';
a.logs.push({by:'系统管理员',time:'2026-09-07 '+new Date().toTimeString().slice(0,5),note:'处理措施:误报忽略。'+(note||'')});
showToast('已按误报关闭');
}else{
a.status='处理中';a.who=who;
a.logs.push({by:'系统管理员',time:'2026-09-07 '+new Date().toTimeString().slice(0,5),note:'处理措施:'+way+';转派 '+who+'。'+(note||'')});
showToast('已生成待办并转派 '+who);
}
closeHandle();renderTable();
}
function closeAlert(i){
alerts[i].logs.push({by:'系统管理员',time:'2026-09-07 '+new Date().toTimeString().slice(0,5),note:'处理完成确认,预警闭环关闭。'});
alerts[i].status='已关闭';renderTable();showToast('预警已关闭并归档');
}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var a=alerts[i];
document.getElementById('detailTitle').textContent='预警详情 · '+a.no;
document.getElementById('detailBody').innerHTML=
'<div class="form-section">预警信息</div><div class="detail-grid">'
+di('异常编号',a.no)+di('异常类型',a.type)
+di('预警对象',a.obj)+di('严重度',a.level)
+di('触发时间',a.time)+di('推送方式',a.push)
+di('处理人',a.who)+di('处理状态',a.status)
+di('预警内容',a.content,true)
+'</div>'
+'<div class="form-section">处理记录(待办工单流转)</div><div class="timeline">'
+a.logs.map(function(l){return '<div class="tl-item"><div class="tl-item__head">'+l.by+' · '+l.time+'</div><div class="tl-item__body">'+l.note+'</div></div>';}).join('')
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
</script>
</body>
</html>
@@ -0,0 +1,297 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统脏数据 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
.modal--wide{width:680px;max-height:82vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:14px 0 10px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.radio-group{display:flex;gap:16px;min-height:32px;align-items:center;flex-wrap:wrap;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
textarea.form-control{height:64px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;resize:vertical;line-height:1.5;font-family:inherit;}
textarea.form-control:focus{border-color:var(--primary);}
/* 处理记录时间线 */
.timeline{position:relative;padding-left:20px;}
.timeline::before{content:'';position:absolute;left:5px;top:4px;bottom:4px;width:1px;background:var(--border);}
.tl-item{position:relative;padding-bottom:14px;}
.tl-item:last-child{padding-bottom:0;}
.tl-item::before{content:'';position:absolute;left:-19px;top:4px;width:9px;height:9px;border-radius:50%;background:#fff;border:2px solid var(--primary);}
.tl-item__head{font-size:var(--font-xs);color:var(--text-placeholder);}
.tl-item__body{font-size:var(--font-sm);color:var(--text-primary);margin-top:2px;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html" class="active">异常事件</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="alert-dirty.html" class="sidebar-item active">系统脏数据</a>
<a href="alert-abnormal.html" class="sidebar-item">结果异常</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>异常类型</label>
<select class="filter-select" id="filterType" onchange="renderTable()" style="min-width:130px;">
<option value="">全部类型</option><option>库存负数</option><option>处方剂量异常</option><option>时间倒挂</option><option>数量不一致</option>
</select>
</div>
<div class="filter-item">
<label>处理状态</label>
<select class="filter-select" id="filterStatus" onchange="renderTable()" style="min-width:110px;">
<option value="">全部状态</option><option>待处理</option><option>处理中</option><option>已处理</option><option>已忽略</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
防控口径:业务录入端已收紧控量规则(库存不允许为负、处方超出说明书极量弹窗拦截),本台账处理历史与在途脏数据;识别规则见「档案 → 算法模型 → 脏数据识别模型」。
</div>
<div class="action-bar">
<span class="action-bar__hint">脏数据实时推送运维值班群;超过 24 小时未处理自动升级提醒。</span>
</div>
<!-- [交互] 脏数据台账 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="min-width:130px;">异常编号</th><th>异常类型</th><th style="min-width:180px;">关联对象</th><th style="min-width:260px;">异常详情</th>
<th style="min-width:160px;">来源</th><th>发现时间</th><th>处理状态</th><th style="width:120px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 处理弹窗 -->
<div class="modal-mask" id="handleMask">
<div class="modal">
<div class="modal__title">处理脏数据 · <span id="handleNo"></span></div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 处理方式</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="hWay" value="人工修正后回传" checked>人工修正后回传</label>
<label class="radio"><input type="radio" name="hWay" value="退回业务端重录">退回业务端重录</label>
<label class="radio"><input type="radio" name="hWay" value="误报忽略">误报忽略</label>
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 处理说明(将写入处理记录并推送原录入人)</span>
<textarea class="form-control" id="hNote" placeholder="请说明异常原因与修正结果,不少于 5 个字"></textarea>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeHandle()">取消</button>
<button class="btn btn-primary" onclick="submitHandle()">提交处理</button>
</div>
</div>
</div>
<!-- [交互] 详情弹窗:异常信息 + 处理记录时间线 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">脏数据详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer" style="border-top:1px solid var(--border);"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 关联对象与「实时库存/出入库记录」页药品、单据勾稽 */
var dirty=[
{no:'DZ-20260907-01',type:'库存负数',obj:'乌审旗采气区医疗点 · 硝酸甘油片',detail:'发药同步后现存量为 -3(安全库存 10),判定为 PAD 离线发药重复上传所致',src:'脏数据识别模型 V1.4 · PAD终端-WSQ-02',time:'2026-09-07 07:42',status:'待处理',logs:[]},
{no:'DZ-20260906-02',type:'处方剂量异常',obj:'西峰基地医疗点 · 处方 CF20260906018',detail:'单张处方开具阿莫西林胶囊 120 粒,超过说明书极量比对阈值(72 粒/疗程)',src:'脏数据识别模型 V1.4 · 开方提交拦截',time:'2026-09-06 16:05',status:'待处理',logs:[]},
{no:'DZ-20260905-03',type:'时间倒挂',obj:'靖边气田医疗点 · 出库单 CK20260905007',detail:'出库时间 2026-09-05 08:10 早于最近入库时间 2026-09-02 08:30 所属批次登记时间,疑似终端时钟漂移',src:'脏数据识别模型 V1.4 · 每日补扫',time:'2026-09-05 10:00',status:'处理中',logs:[{by:'数据专员 闫婷',time:'2026-09-06 09:20',note:'已比对终端流水,确认为靖边 PAD 时钟未同步导致,等待中台按单据时间重排回传。'}]},
{no:'DZ-20260904-04',type:'库存负数',obj:'樊学作业区医疗点 · 蒙脱石散',detail:'盘点发现现存量记录为 -2,实际货架现存 15 盒,判定为巡诊发药未同步',src:'脏数据识别模型 V1.4 · 实时校验',time:'2026-09-04 18:26',status:'已处理',logs:[{by:'运维专员 邓成',time:'2026-09-04 19:10',note:'现场盘点确认为漏同步。'},{by:'数据专员 闫婷',time:'2026-09-05 09:00',note:'已人工补录发药出库 17 盒,库存回正为 15,与实物一致。'}]},
{no:'DZ-20260903-05',type:'数量不一致',obj:'安塞采油队长住点医疗室 · 二甲双胍缓释片',detail:'终端盘点 24 盒 与 中台账 30 盒 差 6 盒(差值率 20%)',src:'脏数据识别模型 V1.4 · 每日补扫',time:'2026-09-03 07:00',status:'已处理',logs:[{by:'运维专员 苏文斌',time:'2026-09-03 10:30',note:'调阅出入库流水,发现 8 月底退药 6 盒重复冲减。'},{by:'数据专员 闫婷',time:'2026-09-03 15:00',note:'中台冲销重复退药记录,两端数量已一致。'}]},
{no:'DZ-20260902-06',type:'处方剂量异常',obj:'姬圪台作业区医疗点 · 处方 CF20260902011',detail:'布洛芬缓释胶囊单次开具 120 粒,超阈值触发',src:'脏数据识别模型 V1.4 · 开方提交拦截',time:'2026-09-02 14:37',status:'已忽略',logs:[{by:'管理医生 曲志明',time:'2026-09-02 15:10',note:'慢病随访患者一次性开具三个月用量,符合规定,标记误报并豁免规则(同类不再提醒)。'}]},
{no:'DZ-20260831-07',type:'时间倒挂',obj:'桐园作业区医疗点 · 入库单 RK20260831002',detail:'入库登记 2026-08-29 早于采购单审批时间 2026-08-30',src:'脏数据识别模型 V1.3 · 每日补扫',time:'2026-08-31 09:15',status:'已忽略',logs:[{by:'运维专员 赵万福',time:'2026-08-31 11:00',note:'补录历史到货,时间口径可接受,忽略。'}]},
{no:'DZ-20260830-08',type:'库存负数',obj:'姬塬作业区医疗点 · 阿司匹林肠溶片',detail:'现存量记录为 -1,判定为两终端同时离线发药冲突',src:'脏数据识别模型 V1.3 · 实时校验',time:'2026-08-30 17:48',status:'待处理',logs:[]}
];
var typeBadge={'库存负数':'badge--danger','处方剂量异常':'badge--warning','时间倒挂':'badge--info','数量不一致':'badge--warning'};
var statusBadge={'待处理':'badge--warning','处理中':'badge--info','已处理':'badge--success','已忽略':'badge--muted'};
function renderTable(){
var t=document.getElementById('filterType').value,s=document.getElementById('filterStatus').value;
var list=dirty.filter(function(d){return (!t||d.type===t)&&(!s||d.status===s);});
document.getElementById('tbody').innerHTML=list.map(function(d){
var i=dirty.indexOf(d);
var ops='<button class="btn btn-link" onclick="openDetail('+i+')">详情</button>';
if(d.status==='待处理')ops='<button class="btn btn-link" onclick="openHandle('+i+')">处理</button>'+ops;
if(d.status==='处理中')ops='<button class="btn btn-link" onclick="markDone('+i+')">标记已处理</button>'+ops;
return '<tr><td style="font-family:monospace;">'+d.no+'</td>'
+'<td><span class="badge '+(typeBadge[d.type]||'badge--info')+'">'+d.type+'</span></td>'
+'<td style="text-align:left;">'+d.obj+'</td><td style="text-align:left;white-space:normal;min-width:260px;">'+d.detail+'</td>'
+'<td style="white-space:normal;text-align:left;font-size:var(--font-xs);color:var(--text-secondary);">'+d.src+'</td>'
+'<td>'+d.time+'</td><td><span class="badge '+(statusBadge[d.status])+'">'+d.status+'</span></td>'
+'<td>'+ops+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="8">没有符合条件的脏数据记录</td></tr>';
var pending=dirty.filter(function(d){return d.status==='待处理';}).length;
document.getElementById('totalTip').textContent='共 '+list.length+' 条 · 待处理 '+pending+' 条';
}
function resetFilter(){document.getElementById('filterType').value='';document.getElementById('filterStatus').value='';renderTable();}
var handleId=null;
function openHandle(i){
handleId=i;
document.getElementById('handleNo').textContent=dirty[i].no;
document.getElementById('hNote').value='';
var rs=document.getElementsByName('hWay');rs[0].checked=true;
document.getElementById('handleMask').classList.add('open');
}
function closeHandle(){document.getElementById('handleMask').classList.remove('open');}
function getRadio(name){
var rs=document.getElementsByName(name);
for(var i=0;i<rs.length;i++)if(rs[i].checked)return rs[i].value;
return '';
}
function submitHandle(){
var way=getRadio('hWay'),note=document.getElementById('hNote').value.trim();
if(note.length<5){showToast('请填写处理说明(不少于 5 个字)');return;}
var d=dirty[handleId];
d.logs.push({by:'系统管理员',time:'2026-09-07 '+new Date().toTimeString().slice(0,5),note:'处理方式:'+way+';'+note});
d.status=(way==='误报忽略')?'已忽略':'处理中';
closeHandle();renderTable();
showToast(way==='误报忽略'?'已忽略并豁免同类规则':'已提交,转「处理中」等待中台清洗回传');
}
function markDone(i){
dirty[i].logs.push({by:'系统管理员',time:'2026-09-07 09:30',note:'中台清洗回传完成,来源模块数据已修正,标记已处理。'});
dirty[i].status='已处理';renderTable();showToast('已标记处理完成');
}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var d=dirty[i];
document.getElementById('detailTitle').textContent='脏数据详情 · '+d.no;
document.getElementById('detailBody').innerHTML=
'<div class="form-section">异常信息</div><div class="detail-grid">'
+di('异常编号',d.no)+di('异常类型',d.type)
+di('关联对象',d.obj)+di('处理状态',d.status)
+di('发现时间',d.time)+di('来源',d.src)
+di('异常详情',d.detail,true)
+'</div>'
+'<div class="form-section">处理记录</div>'
+(d.logs.length?'<div class="timeline">'+d.logs.map(function(l){
return '<div class="tl-item"><div class="tl-item__head">'+l.by+' · '+l.time+'</div><div class="tl-item__body">'+l.note+'</div></div>';
}).join('')+'</div>'
:'<div style="font-size:var(--font-sm);color:var(--text-placeholder);">尚无处理记录。</div>');
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
</script>
</body>
</html>
@@ -0,0 +1,289 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>算法模型 | 一线医疗</title>
<style>
/* 通用重置与设计令牌 */
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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:200px;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);}
.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;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;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);}
/* 筛选栏 */
.filter-bar{background:#fff;border-radius:var(--radius-md);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;flex-wrap:wrap;}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:200px;}
.filter-input:focus{border-color:var(--primary);}
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;cursor:pointer;min-width:120px;}
/* 按钮 */
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
/* 表格卡片 */
.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-card__content table{min-width:100%;white-space:nowrap;}
table{width:100%;border-collapse:collapse;}
thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:14px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
/* 链接 */
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
/* 模态弹窗 */
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;}
.modal{background:#fff;border-radius:8px;width:680px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);}
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
.modal-title{font-size:16px;font-weight:600;color:#262626;}
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}
.modal-close:hover{color:#262626;}
.modal-body{padding:24px;}
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.form-item{display:flex;flex-direction:column;gap:4px;}
.form-item.full{grid-column:1/-1;}
.form-item label{font-size:13px;color:#8c8c8c;}
.form-item label .required{color:#ff4d4f;margin-left:2px;}
.form-item input,.form-item select,.form-item textarea{height:36px;padding:0 12px;border:1px solid #e8e8e8;border-radius:4px;font-size:13px;outline:none;}
.form-item input:focus,.form-item select:focus,.form-item textarea:focus{border-color:#1890FF;}
.form-item textarea{height:80px;padding:8px 12px;resize:vertical;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html" class="active">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="archive-visit-data.html" class="sidebar-item">个人就医数据</a>
<a href="archive-unit-report.html" class="sidebar-item">单位报表数据</a>
<a href="archive-algo-model.html" class="sidebar-item active">算法模型</a>
</aside>
<!-- 主内容区 -->
<div class="main">
<!-- [交互] 筛选栏:文档名称关键字 + 制作年份 -->
<div class="filter-bar">
<div class="filter-item">
<label>文档名称</label>
<input class="filter-input" type="text" placeholder="请输入文档名称">
</div>
<div class="filter-item">
<label>制作年份</label>
<select class="filter-select">
<option>全部</option>
<option selected>2026年</option>
<option>2025年</option>
<option>2024年</option>
</select>
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<!-- [交互] 操作栏:新增模型文档 / 异步导出 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openModal()">+ 新增</button>
<button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
</div>
<!-- [交互] 模型说明文档列表:附件支持预览/下载,操作支持编辑/删除 -->
<div class="table-card">
<div class="table-card__header">
<div class="table-card__title">算法模型</div>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th>文档名称</th>
<th>文档描述</th>
<th>制作机构</th>
<th>制作人</th>
<th>制作年份</th>
<th>上传日期</th>
<th>附件</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>慢病自动入组模型使用说明</td>
<td>基于体检指标、就诊诊断与随访体征滚动窗口判定,自动入组慢病管理台账</td>
<td>算法平台组</td>
<td>陈工</td>
<td>2026</td>
<td>2026-07-15</td>
<td><a class="link">预览</a> <a class="link">下载</a></td>
<td><a class="link" onclick="openModal()">编辑</a> <a class="link" style="color:var(--danger)">删除</a></td>
</tr>
<tr>
<td>健康风险评估模型技术文档</td>
<td>对全员进行五类人群分类,输出个人健康风险分与群体分布</td>
<td>健康管理部</td>
<td>刘主任</td>
<td>2026</td>
<td>2026-06-20</td>
<td><a class="link">预览</a> <a class="link">下载</a></td>
<td><a class="link" onclick="openModal()">编辑</a> <a class="link" style="color:var(--danger)">删除</a></td>
</tr>
<tr>
<td>报告解读辅助模型白皮书</td>
<td>结构化解析年度体检报告,预生成异常摘要与建议草稿供医护确认</td>
<td>医疗点支撑组</td>
<td>周医生</td>
<td>2026</td>
<td>2026-08-01</td>
<td><a class="link">预览</a> <a class="link">下载</a></td>
<td><a class="link" onclick="openModal()">编辑</a> <a class="link" style="color:var(--danger)">删除</a></td>
</tr>
<tr>
<td>脏数据识别模型规则说明</td>
<td>识别库存负数、处方剂量异常、时间倒挂等不合逻辑业务数据</td>
<td>数据治理组</td>
<td>杨工</td>
<td>2025</td>
<td>2025-12-08</td>
<td><a class="link">预览</a> <a class="link">下载</a></td>
<td><a class="link" onclick="openModal()">编辑</a> <a class="link" style="color:var(--danger)">删除</a></td>
</tr>
<tr>
<td>缺货预警模型参数说明</td>
<td>按医疗点消耗速率预测关键药品可支撑天数,低于安全线生成预警工单</td>
<td>药事管理组</td>
<td>吴药师</td>
<td>2025</td>
<td>2025-11-20</td>
<td><a class="link">预览</a> <a class="link">下载</a></td>
<td><a class="link" onclick="openModal()">编辑</a> <a class="link" style="color:var(--danger)">删除</a></td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span>显示 5 条,共 5 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn"></button>
</div>
</div>
</div>
<!-- [交互] 新增/编辑算法模型弹窗 -->
<div class="modal-overlay" id="modalOverlay" style="display:none;">
<div class="modal">
<div class="modal-header">
<span class="modal-title">新增算法模型</span>
<span class="modal-close" onclick="closeModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-grid">
<div class="form-item full">
<label>文档名称<span class="required">*</span></label>
<input type="text" placeholder="请输入文档名称">
</div>
<div class="form-item full">
<label>文档描述</label>
<textarea placeholder="请输入文档描述"></textarea>
</div>
<div class="form-item">
<label>制作机构<span class="required">*</span></label>
<input type="text" placeholder="请输入制作机构">
</div>
<div class="form-item">
<label>制作人<span class="required">*</span></label>
<input type="text" placeholder="请输入制作人">
</div>
<div class="form-item">
<label>制作年份<span class="required">*</span></label>
<select><option value="">请选择年份</option><option>2026</option><option>2025</option><option>2024</option></select>
</div>
<div class="form-item">
<label>上传附件</label>
<input type="file">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeModal()">取消</button>
<button class="btn btn-primary">确定</button>
</div>
</div>
</div>
<script>
/* 模态弹窗控制 */
function openModal(){document.getElementById('modalOverlay').style.display='flex';}
function closeModal(){document.getElementById('modalOverlay').style.display='none';}
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal();});
</script>
</body>
</html>
@@ -0,0 +1,225 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单位报表数据 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1400px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal--wide{width:820px;max-height:82vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding-top:12px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:8px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;padding:8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html" class="active">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="archive-visit-data.html" class="sidebar-item">个人就医数据</a>
<a href="archive-unit-report.html" class="sidebar-item active">单位报表数据</a>
<a href="archive-algo-model.html" class="sidebar-item">算法模型</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位 + 年度 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="renderTable()" style="min-width:130px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>年度</label>
<select class="filter-select" id="filterYear" onchange="renderTable()" style="min-width:100px;">
<option value="">全部年度</option><option>2026</option><option>2025</option><option>2024</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="document.getElementById('filterUnit').value='';document.getElementById('filterYear').value='';renderTable()">重置</button>
</div>
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('年报已导出,请在浏览器下载列表查看')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5v8M4 6.5L7 9.5l3-3M2 11.5h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>导出报表
</button>
<span class="action-bar__hint">2026 年度为年初至今动态口径,历史年度为全年归档口径。</span>
</div>
<!-- [交互] 年报列表:操作列固定,明细弹窗展示医疗点分解 -->
<div class="table-card">
<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 style="width:80px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 单位年报明细弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">单位年报明细</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDetail()">关闭</button>
<button class="btn btn-primary" onclick="showToast('该单位年报已导出 PDF(演示)')">导出该年报</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 医疗点年度基础数据(2026年初至今口径),与「医疗点统计」页同源;历史年度按系数折算 */
var base=[
{unit:'采油一厂',point:'樊学作业区医疗点',rad:2860,visit:1580,chronic:23,acts:23,interp:128},
{unit:'采油一厂',point:'安塞采油队长住点医疗室',rad:960,visit:260,chronic:6,acts:4,interp:21},
{unit:'采油一厂',point:'姬塬作业区医疗点',rad:1730,visit:890,chronic:9,acts:5,interp:66},
{unit:'采油二厂',point:'姬圪台作业区医疗点',rad:2140,visit:1240,chronic:31,acts:18,interp:102},
{unit:'采油二厂',point:'桐园作业区医疗点',rad:1420,visit:760,chronic:15,acts:13,interp:64},
{unit:'第四采油厂',point:'西峰基地医疗点',rad:4350,visit:2280,chronic:45,acts:30,interp:216},
{unit:'采气一厂',point:'靖边气田医疗点',rad:1680,visit:830,chronic:18,acts:15,interp:69},
{unit:'采气一厂',point:'乌审旗采气区医疗点',rad:1550,visit:900,chronic:14,acts:13,interp:78}
];
var yearFactor={'2026':1,'2025':0.9,'2024':0.72};
var genTime={'2026':'2026-09-07 02:00','2025':'2025-12-29 02:00','2024':'2024-12-30 02:00'};
function unitAgg(unit,year){
var f=yearFactor[year];
var list=base.filter(function(b){return b.unit===unit;});
var sum=function(k){return Math.round(list.reduce(function(s,b){return s+b[k];},0)*(k==='rad'?1:f));};
return {points:list.length,rad:sum('rad'),visit:sum('visit'),chronic:Math.round(sum('chronic')*1),acts:sum('acts'),interp:sum('interp'),list:list,f:f};
}
function renderTable(){
var u=document.getElementById('filterUnit').value,y=document.getElementById('filterYear').value;
var units=u?[u]:['采油一厂','采油二厂','第四采油厂','采气一厂'];
var years=y?[y]:['2026','2025','2024'];
var html='';
units.forEach(function(un){years.forEach(function(yr){
var a=unitAgg(un,yr);
var st=yr==='2024'&&un==='采油一厂'?'<span class="badge badge--warning">补录确认中</span>':'<span class="badge badge--success">已生成</span>';
html+='<tr><td>'+un+'</td><td>'+yr+'</td><td>'+a.points+'</td><td>'+a.rad+'</td><td>'+a.visit+'</td>'
+'<td>'+a.chronic+'</td><td>'+a.acts+'</td><td>'+a.interp+'</td><td>'+genTime[yr]+'</td><td>'+st+'</td>'
+'<td><button class="btn btn-link" onclick="openDetail(\''+un+'\','+yr+')">明细</button></td></tr>';
});});
document.getElementById('tbody').innerHTML=html;
document.getElementById('totalTip').textContent='共 '+(units.length*years.length)+' 份年报';
}
function openDetail(unit,year){
var a=unitAgg(unit,year),f=a.f;
document.getElementById('detailTitle').textContent='单位年报明细 · '+unit+' · '+year+'年度';
document.getElementById('detailBody').innerHTML=
'<div class="form-section">医疗点分解</div>'
+'<table class="mini-table"><thead><tr><th>医疗点</th><th>覆盖员工</th><th>就诊人次</th><th>慢病管理</th><th>培训宣教场次</th><th>解读人次</th></tr></thead><tbody>'
+a.list.map(function(b){
return '<tr><td style="text-align:left;">'+b.point+'</td><td>'+b.rad+'</td><td>'+Math.round(b.visit*f)+'</td><td>'+Math.round(b.chronic*f)+'</td><td>'+Math.round(b.acts*f)+'</td><td>'+Math.round(b.interp*f)+'</td></tr>';
}).join('')
+'<tr style="background:#FAFAFA;font-weight:600;"><td style="text-align:left;">合计</td><td>'+a.rad+'</td><td>'+a.visit+'</td><td>'+a.chronic+'</td><td>'+a.acts+'</td><td>'+a.interp+'</td></tr>'
+'</tbody></table>'
+'<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-top:10px;">口径说明:慢病管理人数为年度内入组过慢病管理的去重人数;培训宣教场次含急救培训与健康宣教;'+(year==='2026'?'2026 年度数据截至 2026-09-06。':'该年度数据已冻结归档。')+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
</script>
</body>
</html>
@@ -0,0 +1,310 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人就医数据 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.chip{display:inline-block;padding:1px 8px;border-radius:9px;font-size:var(--font-xs);background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;margin:1px 2px;}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.emp-card{display:grid;grid-template-columns:repeat(4,1fr);gap:6px 12px;background:#F8FBFF;border:1px solid #D6EAFB;border-radius:var(--radius-sm);padding:10px 14px;margin-bottom:14px;font-size:var(--font-xs);color:var(--text-secondary);}
.emp-card b{color:var(--text-primary);font-weight:600;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;padding:8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html" class="active">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="archive-visit-data.html" class="sidebar-item active">个人就医数据</a>
<a href="archive-unit-report.html" class="sidebar-item">单位报表数据</a>
<a href="archive-algo-model.html" class="sidebar-item">算法模型</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位→医疗点级联、员工关键字、年度 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:170px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>员工</label>
<input class="filter-input" id="filterName" placeholder="姓名或员工编号">
</div>
<div class="filter-item">
<label>年度</label>
<select class="filter-select" id="filterYear" onchange="applyFilter()" style="min-width:90px;">
<option>2026</option><option>2025</option><option>2024</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="action-bar">
<span class="action-bar__hint">档案为只读归档视图;修改业务数据请回到对应的日常业务模块。</span>
</div>
<!-- [交互] 档案列表:操作列固定,打开右侧档案抽屉 -->
<div class="table-card">
<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 style="width:90px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination" id="pager"></div>
</div>
</main>
<!-- [交互] 档案抽屉:基本信息 + 就诊/随访/解读记录 + 慢病入组 -->
<div class="drawer-mask" id="drawerMask" onclick="closeDrawer()"></div>
<div class="drawer" id="drawer">
<div class="drawer__header"><span id="drawerTitle">个人就医档案</span><button class="drawer__close" onclick="closeDrawer()">&times;</button></div>
<div class="drawer__body" id="drawerBody"></div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer()">关闭</button>
<button class="btn btn-primary" onclick="showToast('档案已导出为 PDF(演示)')">导出档案</button>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 员工名册与「员工分配/随访/慢病/解读」各页勾稽;year=按年度聚合的模拟归档 */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var archives=[
{code:'CQ021578',name:'石维国',unit:'采油一厂',point:'樊学作业区医疗点',sex:'男',age:52,chronic:['高血压'],last:'2026-08-30 随访'},
{code:'CQ032144',name:'李桂芳',unit:'采油一厂',point:'樊学作业区医疗点',sex:'女',age:46,chronic:[],last:'2026-08-21 就诊'},
{code:'CQ045130',name:'杨慧敏',unit:'采油一厂',point:'樊学作业区医疗点',sex:'女',age:38,chronic:[],last:'2026-07-16 就诊'},
{code:'CQ019203',name:'曲志明',unit:'采油二厂',point:'姬圪台作业区医疗点',sex:'男',age:57,chronic:['冠心病'],last:'2026-08-25 随访'},
{code:'CQ027710',name:'张秀兰',unit:'采油二厂',point:'姬圪台作业区医疗点',sex:'女',age:49,chronic:['肥胖','其他'],last:'2026-08-12 就诊'},
{code:'CQ033096',name:'赵万福',unit:'采油二厂',point:'桐园作业区医疗点',sex:'男',age:61,chronic:['高血压'],last:'2026-09-02 随访'},
{code:'CQ010564',name:'高建军',unit:'第四采油厂',point:'西峰基地医疗点',sex:'男',age:44,chronic:['糖尿病','肥胖'],last:'2026-08-28 解读'},
{code:'CQ041822',name:'闫婷',unit:'第四采油厂',point:'西峰基地医疗点',sex:'女',age:35,chronic:[],last:'2026-06-30 就诊'},
{code:'CQ036441',name:'刘玉梅',unit:'采气一厂',point:'靖边气田医疗点',sex:'女',age:50,chronic:['高血压'],last:'2026-08-19 随访'},
{code:'CQ029905',name:'呼建国',unit:'采气一厂',point:'靖边气田医疗点',sex:'男',age:55,chronic:['其他'],last:'2026-07-24 就诊'},
{code:'CQ044871',name:'白瑞',unit:'采油一厂',point:'安塞采油队长住点医疗室',sex:'男',age:41,chronic:[],last:'2026-05-09 就诊'},
{code:'CQ022317',name:'徐立成',unit:'采气一厂',point:'乌审旗采气区医疗点',sex:'男',age:47,chronic:['肥胖'],last:'2026-08-26 随访'}
];
/* 年度聚合计数(就诊/随访/解读),按年度切换展示不同口径 */
var yearly={
'2026':[[4,3,1],[3,1,0],[2,0,0],[5,4,1],[3,2,1],[6,5,1],[4,3,2],[2,1,0],[5,3,1],[3,1,0],[1,0,0],[4,3,1]],
'2025':[[3,2,1],[2,1,0],[1,0,0],[4,3,1],[2,1,0],[4,4,1],[3,2,1],[1,1,0],[4,2,1],[2,1,0],[0,0,0],[3,2,1]],
'2024':[[2,1,0],[1,0,0],[0,0,0],[3,2,1],[1,1,0],[3,3,0],[2,1,1],[1,0,0],[3,1,0],[1,1,0],[0,0,0],[2,1,0]]
};
/* 档案明细记录(以2026年为例演示;2024/2025 年度结构一致仅条数不同) */
var visitsMap={
'CQ021578':[['2026-07-18','慢病复诊','高血压(随访期)','石维国'],['2026-05-02','普通门诊','上呼吸道感染','李桂芳'],['2026-03-11','普通门诊','腰肌劳损','李桂芳']],
'CQ032144':[['2026-08-21','普通门诊','颈椎病','石维国'],['2026-04-07','普通门诊','急性胃肠炎','石维国']],
'CQ045130':[['2026-07-16','普通门诊','过敏性鼻炎','石维国']],
'CQ019203':[['2026-08-02','慢病复诊','冠心病稳定期','曲志明'],['2026-06-15','普通门诊','高血压','曲志明'],['2026-02-27','巡诊','井下作业后肌肉酸痛','张秀兰']],
'CQ027710':[['2026-08-12','普通门诊','体重管理门诊','曲志明'],['2026-05-20','慢病复诊','脂肪肝随访','曲志明']],
'CQ033096':[['2026-08-20','慢病复诊','高血压(药物调整)','赵万福'],['2026-06-03','普通门诊','失眠焦虑','赵万福'],['2026-01-19','巡诊','关节疼痛','赵万福']],
'CQ010564':[['2026-08-28','慢病复诊','2型糖尿病','高建军'],['2026-07-04','普通门诊','皮肤湿疹','闫婷']],
'CQ041822':[['2026-06-30','普通门诊','痛经调理','高建军']],
'CQ036441':[['2026-08-05','慢病复诊','高血压','刘玉梅'],['2026-04-22','普通门诊','咽炎','刘玉梅']],
'CQ029905':[['2026-07-24','普通门诊','痛风急性发作','刘玉梅']],
'CQ044871':[['2026-05-09','普通门诊','外伤换药','白瑞']],
'CQ022317':[['2026-08-11','慢病复诊','肥胖代谢管理','徐立成'],['2026-03-25','巡诊','沙尘天气呼吸道不适','徐立成']]
};
var followsMap={
'CQ021578':[['2026-08-30','门诊','138/85','6.1','血压平稳,继续用药'],['2026-07-28','电话','142/88','6.0','依从性良好'],['2026-06-25','门诊','145/92','6.2','建议低盐饮食']],
'CQ019203':[['2026-08-25','门诊','132/82','5.8','心率正常'],['2026-07-20','电话','136/84','5.9','无胸痛发作'],['2026-06-18','家庭','130/80','5.7','家庭血压计自测规范'],['2026-05-16','门诊','135/83','5.8','恢复巡诊周期']],
'CQ033096':[['2026-09-02','门诊','136/86','6.3','药物调整后复测'],['2026-08-08','电话','141/89','6.1','入组首次随访'],['2026-07-05','巡诊','144/90','6.0','井场巡诊顺访']],
'CQ010564':[['2026-08-28','门诊','128/80','7.8','血糖偏高,强化饮食'],['2026-07-26','电话','130/82','8.1','自测记录完整'],['2026-06-24','家庭','126/79','7.5','运动处方执行良好']],
'CQ036441':[['2026-08-19','门诊','139/87','6.2','季节换药'],['2026-07-15','电话','142/88','6.4','睡眠改善'],['2026-06-12','巡诊','145/91','6.3','气田巡诊']],
'CQ027710':[['2026-08-14','门诊','125/78','5.6','体重下降1.2kg'],['2026-07-10','电话','127/80','5.7','饮食记录达标']],
'CQ022317':[['2026-08-26','门诊','131/83','6.0','减重计划推进'],['2026-07-22','电话','133/84','6.1','高寒环境运动调整']],
'CQ029905':[['2026-07-26','门诊','129/81','5.9','痛风缓解期']]
};
var interpMap={
'CQ021578':[['2026-04-12','慢性病风险人群','血压持续偏高,建议入组管理']],
'CQ019203':[['2026-03-20','已确诊慢病人群','冠心病史,年度体检提示血脂异常']],
'CQ033096':[['2026-05-08','已确诊慢病人群','高血压2级,靶器官未见损害']],
'CQ010564':[['2026-08-28','已确诊慢病人群','糖化血红蛋白7.2%,需强化随访']],
'CQ036441':[['2026-03-28','已确诊慢病人群','高血压合并颈椎退变']],
'CQ027710':[['2026-06-11','慢性病风险人群','BMI31.6,脂肪肝,代谢风险升高']],
'CQ022317':[['2026-05-30','慢性病风险人群','腹型肥胖,血糖临界']],
'CQ029905':[['2026-04-03','已确诊慢病人群','高尿酸血症,痛风史']]
};
var curPage=1,pageSize=10,viewList=[];
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var all=[].concat.apply([],Object.keys(pointMap).map(function(k){return pointMap[k];}));
document.getElementById('filterPoint').innerHTML='<option value="">全部医疗点</option>'+(u?pointMap[u]:all).map(function(p){return '<option>'+p+'</option>';}).join('');
}
function yearCounts(i){var y=document.getElementById('filterYear').value;return yearly[y][i];}
function renderTable(){
var u=document.getElementById('filterUnit').value,p=document.getElementById('filterPoint').value,kw=document.getElementById('filterName').value.trim();
viewList=archives.map(function(a,i){return {a:a,i:i};}).filter(function(o){
return (!u||o.a.unit===u)&&(!p||o.a.point===p)&&(!kw||o.a.name.indexOf(kw)>=0||o.a.code.indexOf(kw)>=0);
});
var total=viewList.length,pages=Math.max(1,Math.ceil(total/pageSize));
if(curPage>pages)curPage=pages;
var pageItems=viewList.slice((curPage-1)*pageSize,curPage*pageSize);
document.getElementById('tbody').innerHTML=pageItems.map(function(o){
var a=o.a,c=yearCounts(o.i);
return '<tr><td>'+a.code+'</td><td style="font-weight:600;">'+a.name+'</td><td>'+a.unit+'</td><td>'+a.point+'</td>'
+'<td>'+a.sex+' / '+a.age+'</td>'
+'<td>'+(a.chronic.length?a.chronic.map(function(x){return '<span class="chip">'+x+'</span>';}).join(''):'<span style="color:var(--text-placeholder);">—</span>')+'</td>'
+'<td>'+c[0]+' 次</td><td>'+c[1]+' 次</td><td>'+c[2]+' 次</td><td>'+a.last+'</td>'
+'<td><button class="btn btn-link" onclick="openDrawer('+o.i+')">档案</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="11">没有符合条件的归档员工</td></tr>';
var btns='';
for(var i=1;i<=pages;i++)btns+='<button class="page-btn'+(i===curPage?' active':'')+'" onclick="goPage('+i+')">'+i+'</button>';
document.getElementById('pager').innerHTML='<span>共 '+total+' 条归档记录</span><button class="page-btn" onclick="goPage('+(curPage-1)+')"'+(curPage===1?' disabled':'')+'></button>'+btns+'<button class="page-btn" onclick="goPage('+(curPage+1)+')"'+(curPage===pages?' disabled':'')+'></button>';
}
function goPage(n){var pages=Math.max(1,Math.ceil(viewList.length/pageSize));if(n<1||n>pages)return;curPage=n;renderTable();}
function applyFilter(){curPage=1;renderTable();showToast('查询完成');}
function resetFilter(){document.getElementById('filterUnit').value='';document.getElementById('filterName').value='';document.getElementById('filterYear').value='2026';onFilterUnit();curPage=1;renderTable();}
function mini(title,heads,rows){
return '<div class="form-section">'+title+'</div><table class="mini-table"><thead><tr>'+heads.map(function(h){return '<th>'+h+'</th>';}).join('')+'</tr></thead><tbody>'
+(rows.length?rows.map(function(r){return '<tr>'+r.map(function(c){return '<td>'+c+'</td>';}).join('')+'</tr>';}).join(''):'<tr><td class="td-empty" colspan="'+heads.length+'" style="padding:18px 0;">本年度暂无记录</td></tr>')+'</tbody></table>';
}
function openDrawer(i){
var a=archives[i],c=yearCounts(i);
var y=document.getElementById('filterYear').value;
document.getElementById('drawerTitle').textContent='个人就医档案 · '+a.name+'('+y+'年度)';
document.getElementById('drawerBody').innerHTML=
'<div class="emp-card"><span>员工编号 <b>'+a.code+'</b></span><span>性别年龄 <b>'+a.sex+' / '+a.age+'</b></span><span>所属单位 <b>'+a.unit+'</b></span><span>分配医疗点 <b>'+a.point+'</b></span></div>'
+'<div class="form-section">健康标签</div><div class="detail-grid">'
+'<div class="detail-item"><span class="dk">慢病类型</span><span class="dv">'+(a.chronic.length?a.chronic.join('、'):'健康人群(未入组)')+'</span></div>'
+'<div class="detail-item"><span class="dk">年度行为</span><span class="dv">就诊 '+c[0]+' 次 · 随访 '+c[1]+' 次 · 解读 '+c[2]+' 次</span></div>'
+'</div>'
+mini('就诊记录',['日期','类型','诊断/处置','接诊医生'],(visitsMap[a.code]||[]).filter(function(r){return r[0].indexOf(y)===0;}).map(function(r){return [r[0],r[1],r[2],r[3]];}))
+mini('随访记录',['日期','形式','血压mmHg','血糖mmol/L','随访结论'],(followsMap[a.code]||[]).filter(function(r){return r[0].indexOf(y)===0;}))
+mini('报告解读',['日期','人群分类','解读结论'],(interpMap[a.code]||[]).filter(function(r){return r[0].indexOf(y)===0;}));
document.getElementById('drawer').classList.add('open');
document.getElementById('drawerMask').classList.add('open');
}
function closeDrawer(){document.getElementById('drawer').classList.remove('open');document.getElementById('drawerMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
onFilterUnit();renderTable();
</script>
</body>
</html>
@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分配记录 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.filter-date{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.tab-bar{display:flex;gap:4px;margin-top:16px;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);padding:6px 16px 0;}
.tab-item{padding:10px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;}
.tab-item.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1300px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item active">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="renderAll()"><option value="">全部单位</option></select>
</div>
<div class="filter-item">
<label>编号关键字</label>
<input class="filter-input" id="filterKw" placeholder="设备/床位/员工编号" oninput="renderAll()">
</div>
<div class="filter-item">
<label>分配形式</label>
<select class="filter-select" id="filterType" onchange="renderAll()" style="min-width:100px;">
<option value="">全部</option><option value="1">宿舍</option><option value="2">床位</option>
</select>
</div>
<div class="filter-item">
<label>绑定日期</label>
<input class="filter-date" type="date" id="filterFrom" value="2026-01-01" onchange="renderAll()">
<span style="color:var(--text-placeholder);font-size:var(--font-xs);"></span>
<input class="filter-date" type="date" id="filterTo" value="2026-09-07" onchange="renderAll()">
</div>
<button class="btn btn-primary" onclick="renderAll();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
「分配日期」为绑定生效日;解除日期为空表示当前仍生效。与「设备分配」「床位管理」页面当前状态互为快照与历史的关系。
</div>
<!-- [交互] TAB -->
<div class="tab-bar">
<div class="tab-item active" id="tabDev" onclick="switchTab('dev')">设备-空间分配记录</div>
<div class="tab-item" id="tabEmp" onclick="switchTab('emp')">床位-员工分配记录</div>
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title" id="cardTitle">设备-空间分配历史</span>
<button class="btn btn-default" onclick="showToast('分配记录已导出')">导出</button>
</div>
<div class="table-card__content">
<table>
<thead id="thead"></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
var unitDeparts=['采油一厂','采油二厂','第四采油厂','采气一厂'];
var curTab='dev';
/* 设备-空间:含换绑历史(未分配前的旧绑定) */
var devRecords=[
{unit:'采油一厂',code:'HC-FX-01',type:2,space:'樊学倒班宿舍1号楼 · 101-1',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',code:'HC-FX-02',type:2,space:'樊学倒班宿舍1号楼 · 101-2',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',code:'HC-FX-03',type:2,space:'樊学倒班宿舍1号楼 · 102-1',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',code:'HC-FX-03',type:2,space:'樊学倒班宿舍1号楼 · 201-1',bind:'2026-04-02',unbind:'2026-04-08'},
{unit:'采油一厂',code:'HC-FX-04',type:1,space:'樊学倒班宿舍1号楼 · 101室',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',code:'HC-MZ-01',type:1,space:'麻子山倒班宿舍楼 · 101室',bind:'2026-03-19',unbind:''},
{unit:'采油二厂',code:'HC-TY-01',type:2,space:'桐园倒班宿舍1号楼 · 301-1',bind:'2026-04-10',unbind:''},
{unit:'采油二厂',code:'HC-TY-02',type:1,space:'桐园倒班宿舍1号楼 · 201室',bind:'2026-04-10',unbind:''},
{unit:'第四采油厂',code:'HC-XF-01',type:2,space:'西峰基地综合宿舍楼 · 301-1',bind:'2026-05-12',unbind:''},
{unit:'第四采油厂',code:'HC-XF-02',type:2,space:'西峰基地综合宿舍楼 · 302-1',bind:'2026-05-12',unbind:''},
{unit:'第四采油厂',code:'HC-XF-03',type:1,space:'西峰基地综合宿舍楼 · 402室',bind:'2026-05-12',unbind:''},
{unit:'采气一厂',code:'HC-JB-01',type:2,space:'靖边气田倒班宿舍楼 · 401-1',bind:'2026-06-24',unbind:''}
];
/* 床位-员工:当前12条生效+3条历史换宿/退宿 */
var empRecords=[
{bed:'101-1',dorm:'101室',building:'樊学倒班宿舍1号楼',name:'石维国',no:'CQ021578',org:'采油一厂',depart:'樊学作业区',bind:'2026-03-05',unbind:''},
{bed:'101-2',dorm:'101室',building:'樊学倒班宿舍1号楼',name:'李桂芳',no:'CQ032144',org:'采油一厂',depart:'樊学作业区',bind:'2026-03-05',unbind:''},
{bed:'102-1',dorm:'102室',building:'樊学倒班宿舍1号楼',name:'曲志明',no:'CQ019203',org:'采油二厂',depart:'姬圪台作业区(借调)',bind:'2026-06-01',unbind:''},
{bed:'101-1',dorm:'101室',building:'麻子山倒班宿舍楼',name:'杨慧敏',no:'CQ045130',org:'采油一厂',depart:'麻子山作业区',bind:'2026-03-19',unbind:''},
{bed:'201-1',dorm:'201室',building:'桐园倒班宿舍1号楼',name:'赵万福',no:'CQ033096',org:'采油二厂',depart:'桐园作业区',bind:'2026-04-10',unbind:''},
{bed:'301-1',dorm:'301室',building:'桐园倒班宿舍1号楼',name:'徐立成',no:'CQ022317',org:'第四采油厂',depart:'西峰作业区(轮训)',bind:'2026-08-01',unbind:''},
{bed:'301-1',dorm:'301室',building:'西峰基地综合宿舍楼',name:'高建军',no:'CQ010564',org:'第四采油厂',depart:'西峰作业区',bind:'2026-05-12',unbind:''},
{bed:'301-2',dorm:'301室',building:'西峰基地综合宿舍楼',name:'闫婷',no:'CQ041822',org:'第四采油厂',depart:'西峰作业区',bind:'2026-05-12',unbind:''},
{bed:'302-1',dorm:'302室',building:'西峰基地综合宿舍楼',name:'白瑞',no:'CQ044871',org:'采气一厂',depart:'靖边气田(轮训)',bind:'2026-05-02',unbind:''},
{bed:'402-1',dorm:'402室',building:'西峰基地综合宿舍楼',name:'张秀兰',no:'CQ027710',org:'采油二厂',depart:'姬圪台作业区',bind:'2026-07-01',unbind:''},
{bed:'401-1',dorm:'401室',building:'靖边气田倒班宿舍楼',name:'刘玉梅',no:'CQ036441',org:'采气一厂',depart:'靖边气田',bind:'2026-06-24',unbind:''},
{bed:'401-2',dorm:'401室',building:'靖边气田倒班宿舍楼',name:'呼建国',no:'CQ029905',org:'采气一厂',depart:'靖边气田',bind:'2026-06-24',unbind:''},
{bed:'301-3',dorm:'301室',building:'西峰基地综合宿舍楼',name:'白瑞',no:'CQ044871',org:'采气一厂',depart:'靖边气田',bind:'2026-04-01',unbind:'2026-05-01'},
{bed:'402-1',dorm:'402室',building:'靖边气田倒班宿舍楼',name:'张秀兰',no:'CQ027710',org:'采油二厂',depart:'姬圪台作业区',bind:'2026-03-15',unbind:'2026-06-30'},
{bed:'201-2',dorm:'201室',building:'桐园倒班宿舍1号楼',name:'赵万福',no:'CQ033096',org:'采油二厂',depart:'桐园作业区',bind:'2026-03-01',unbind:'2026-04-09'}
];
function headDev(){return '<tr><th style="min-width:120px;">设备所属单位</th><th style="min-width:100px;">设备编号</th><th>分配形式</th><th style="min-width:230px;">宿舍/床位名称</th><th>分配日期</th><th>解除日期</th><th>当前状态</th></tr>';}
function headEmp(){return '<tr><th style="min-width:90px;">床位编号</th><th>所属宿舍</th><th style="min-width:180px;">所属楼栋</th><th>分配员工</th><th>员工编号</th><th style="min-width:120px;">员工单位</th><th style="min-width:150px;">员工部门</th><th>分配日期</th><th>解除日期</th><th>当前状态</th></tr>';}
function inRange(d){var f=document.getElementById('filterFrom').value,t=document.getElementById('filterTo').value;return (!f||d>=f)&&(!t||d<=t);}
function renderAll(){
var u=document.getElementById('filterUnit').value,k=document.getElementById('filterKw').value.trim().toUpperCase(),tp=document.getElementById('filterType').value;
var rows='';
if(curTab==='dev'){
var list=devRecords.filter(function(r){return (!u||r.unit===u)&&(!k||r.code.toUpperCase().indexOf(k)>=0)&&(!tp||String(r.type)===tp)&&inRange(r.bind);});
rows=list.map(function(r){
return '<tr><td>'+r.unit+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td>'
+'<td><span class="badge '+(r.type===1?'badge--info':'badge--success')+'">'+(r.type===1?'宿舍':'床位')+'</span></td>'
+'<td>'+r.space+'</td><td>'+r.bind+'</td><td>'+(r.unbind||'-')+'</td>'
+'<td>'+(r.unbind?'<span class="badge badge--muted">已解除</span>':'<span class="badge badge--success">生效中</span>')+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="7">没有符合条件的分配记录</td></tr>';
document.getElementById('thead').innerHTML=headDev();
document.getElementById('totalTip').textContent='共 '+list.length+' 条 · 生效中 '+list.filter(function(r){return !r.unbind;}).length+' 条';
document.getElementById('cardTitle').textContent='设备-空间分配历史';
}else{
var list2=empRecords.filter(function(r){return (!u||r.org===u)&&(!k||(r.no+r.name).toUpperCase().indexOf(k)>=0)&&(!tp||tp==='2')&&inRange(r.bind);});
rows=list2.map(function(r){
return '<tr><td style="font-family:monospace;font-weight:500;">'+r.bed+'</td><td>'+r.dorm+'</td><td>'+r.building+'</td>'
+'<td>'+r.name+'</td><td>'+r.no+'</td><td>'+r.org+'</td><td>'+r.depart+'</td><td>'+r.bind+'</td><td>'+(r.unbind||'-')+'</td>'
+'<td>'+(r.unbind?'<span class="badge badge--muted">已退住</span>':'<span class="badge badge--success">在住</span>')+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的分配记录</td></tr>';
document.getElementById('thead').innerHTML=headEmp();
document.getElementById('totalTip').textContent='共 '+list2.length+' 条 · 在住 '+list2.filter(function(r){return !r.unbind;}).length+' 人';
document.getElementById('cardTitle').textContent='床位-员工分配历史';
}
document.getElementById('tbody').innerHTML=rows;
}
function switchTab(t){
curTab=t;
document.getElementById('tabDev').classList.toggle('active',t==='dev');
document.getElementById('tabEmp').classList.toggle('active',t==='emp');
document.getElementById('filterType').style.display=t==='dev'?'':'none';
renderAll();
}
function resetFilter(){document.getElementById('filterUnit').value='';document.getElementById('filterKw').value='';document.getElementById('filterType').value='';document.getElementById('filterFrom').value='2026-01-01';document.getElementById('filterTo').value='2026-09-07';renderAll();}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+unitDeparts.map(function(u){return '<option>'+u+'</option>';}).join('');
renderAll();
</script>
</body>
</html>
+335
View File
@@ -0,0 +1,335 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>床位管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{background:none;color:var(--danger);padding:0 4px;height:auto;}.btn-link--danger:hover{color:#ff7875;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1200px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:14px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
.pg-btn{min-width:28px;height:28px;border:1px solid var(--border);background:#fff;border-radius:var(--radius-sm);font-size:var(--font-xs);cursor:pointer;color:var(--text-primary);}
.pg-btn.active{background:var(--primary);border-color:var(--primary);color:#fff;}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:560px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.6;margin-bottom:4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.emp-list{max-height:220px;overflow-y:auto;border:1px solid var(--border);border-radius:var(--radius-sm);}
.emp-row{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;border-bottom:1px solid #F0F0F0;cursor:pointer;font-size:var(--font-sm);}
.emp-row:last-child{border-bottom:none;}
.emp-row:hover{background:#F0F8FF;}
.emp-row.selected{background:var(--primary-light);}
.emp-row__meta{font-size:var(--font-xs);color:var(--text-secondary);}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item active">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:楼栋→宿舍级联 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属楼栋</label>
<select class="filter-select" id="filterBuilding" onchange="onFilterBuilding()" style="min-width:180px;">
<option value="">全部楼栋</option>
</select>
</div>
<div class="filter-item">
<label>所属宿舍</label>
<select class="filter-select" id="filterDorm" onchange="renderTable()" style="min-width:110px;">
<option value="">全部宿舍</option>
</select>
</div>
<div class="filter-item">
<label>床位编号</label>
<input class="filter-input" id="filterBed" placeholder="如:101" oninput="renderTable()" style="min-width:100px;">
</div>
<div class="filter-item">
<label>是否分配员工</label>
<select class="filter-select" id="filterAlloc" onchange="renderTable()" style="min-width:100px;">
<option value="">全部</option><option value="1">已分配</option><option value="0">未分配</option>
</select>
</div>
<div class="filter-item">
<label>员工姓名/编号</label>
<input class="filter-input" id="filterEmp" placeholder="关键字" oninput="renderTable()" style="min-width:120px;">
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
一名员工同一时间仅可分配一个床位;换宿请先「解除分配」再分配新床位,解除与分配均会写入「分配记录」的床位-员工历史。
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">床位清单</span>
<button class="btn btn-default" onclick="showToast('床位清单已导出')">导出</button>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="min-width:100px;">床位编号</th><th style="min-width:180px;">所属楼栋</th><th style="min-width:90px;">所属宿舍</th>
<th>是否分配员工</th><th>员工姓名</th><th>员工编号</th><th style="min-width:110px;">员工联系方式</th><th style="min-width:100px;">床头设备</th><th style="width:130px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span><button class="pg-btn" id="pgPrev" onclick="gotoPage(-1)">上一页</button><button class="pg-btn" id="pgNext" onclick="gotoPage(1)">下一页</button></div>
</div>
</main>
<!-- [交互] 分配员工弹窗 -->
<div class="modal-mask" id="allocMask">
<div class="modal">
<div class="modal__title">分配员工 · <span id="allocBed"></span></div>
<div class="form-item">
<span class="form-label">搜索员工(姓名/编号)</span>
<input class="form-control" id="empSearch" placeholder="输入关键字过滤" oninput="renderEmpList()">
</div>
<div class="emp-list" id="empList"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeAlloc()">取消</button>
<button class="btn btn-primary" onclick="confirmAlloc()">确认分配</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 宿舍清单与「宿舍管理」页勾稽;床位按床位数自动展开 */
var dormMeta=[
{name:'101室',building:'樊学倒班宿舍1号楼',beds:4},
{name:'102室',building:'樊学倒班宿舍1号楼',beds:4},
{name:'201室',building:'樊学倒班宿舍1号楼',beds:2},
{name:'101室',building:'麻子山倒班宿舍楼',beds:4},
{name:'102室',building:'麻子山倒班宿舍楼',beds:2},
{name:'201室',building:'桐园倒班宿舍1号楼',beds:2},
{name:'301室',building:'桐园倒班宿舍1号楼',beds:4},
{name:'302室',building:'桐园倒班宿舍1号楼',beds:2},
{name:'301室',building:'西峰基地综合宿舍楼',beds:4},
{name:'302室',building:'西峰基地综合宿舍楼',beds:4},
{name:'401室',building:'西峰基地综合宿舍楼',beds:2},
{name:'402室',building:'西峰基地综合宿舍楼',beds:4},
{name:'401室',building:'靖边气田倒班宿舍楼',beds:4},
{name:'402室',building:'靖边气田倒班宿舍楼',beds:2}
];
/* empPool:与全站演示数据共享的员工池(已脱敏) */
var empPool=[
{no:'CQ021578',name:'石维国',org:'采油一厂',depart:'樊学作业区',phone:'139****3456'},
{no:'CQ032144',name:'李桂芳',org:'采油一厂',depart:'樊学作业区',phone:'139****7788'},
{no:'CQ045130',name:'杨慧敏',org:'采油一厂',depart:'麻子山作业区',phone:'139****5012'},
{no:'CQ019203',name:'曲志明',org:'采油二厂',depart:'姬圪台作业区(借调)',phone:'139****7208'},
{no:'CQ033096',name:'赵万福',org:'采油二厂',depart:'桐园作业区',phone:'139****6630'},
{no:'CQ027710',name:'张秀兰',org:'采油二厂',depart:'姬圪台作业区',phone:'139****4415'},
{no:'CQ010564',name:'高建军',org:'第四采油厂',depart:'西峰作业区',phone:'139****4320'},
{no:'CQ041822',name:'闫婷',org:'第四采油厂',depart:'西峰作业区',phone:'139****4531'},
{no:'CQ036441',name:'刘玉梅',org:'采气一厂',depart:'靖边气田',phone:'139****3745'},
{no:'CQ029905',name:'呼建国',org:'采气一厂',depart:'靖边气田',phone:'139****3870'},
{no:'CQ044871',name:'白瑞',org:'采气一厂',depart:'靖边气田(轮训)',phone:'139****2317'},
{no:'CQ022317',name:'徐立成',org:'第四采油厂',depart:'西峰作业区(轮训)',phone:'139****4906'}
];
/* 床位→员工分配(key=楼栋|床位编号) */
var assigns={
'樊学倒班宿舍1号楼|101-1':'CQ021578','樊学倒班宿舍1号楼|101-2':'CQ032144','樊学倒班宿舍1号楼|102-1':'CQ019203',
'麻子山倒班宿舍楼|101-1':'CQ045130','桐园倒班宿舍1号楼|201-1':'CQ033096','桐园倒班宿舍1号楼|301-1':'CQ022317',
'西峰基地综合宿舍楼|301-1':'CQ010564','西峰基地综合宿舍楼|301-2':'CQ041822','西峰基地综合宿舍楼|302-1':'CQ044871','西峰基地综合宿舍楼|402-1':'CQ027710',
'靖边气田倒班宿舍楼|401-1':'CQ036441','靖边气田倒班宿舍楼|401-2':'CQ029905'
};
/* 床位→床头设备(key同上) */
var bedDevices={'樊学倒班宿舍1号楼|101-1':'HC-FX-01','樊学倒班宿舍1号楼|101-2':'HC-FX-02','樊学倒班宿舍1号楼|102-1':'HC-FX-03','桐园倒班宿舍1号楼|301-1':'HC-TY-01','西峰基地综合宿舍楼|301-1':'HC-XF-01','西峰基地综合宿舍楼|302-1':'HC-XF-02','靖边气田倒班宿舍楼|401-1':'HC-JB-01'};
var beds=[];
dormMeta.forEach(function(d){
for(var nn=1;nn<=d.beds;nn++){
var code=d.name.replace('室','')+'-'+nn;
beds.push({code:code,building:d.building,dorm:d.name});
}
});
var pageNo=1,pageSize=10;
function initFilters(){
var bs=[];dormMeta.forEach(function(d){if(bs.indexOf(d.building)<0)bs.push(d.building);});
document.getElementById('filterBuilding').innerHTML='<option value="">全部楼栋</option>'+bs.map(function(b){return '<option>'+b+'</option>';}).join('');
}
function onFilterBuilding(){
var b=document.getElementById('filterBuilding').value;
var ds=dormMeta.filter(function(d){return !b||d.building===b;}).map(function(d){return d.name;});
var uniq=[];ds.forEach(function(x){if(uniq.indexOf(x)<0)uniq.push(x);});
document.getElementById('filterDorm').innerHTML='<option value="">全部宿舍</option>'+uniq.map(function(d){return '<option>'+d+'</option>';}).join('');
renderTable();
}
function empOf(no){for(var i=0;i<empPool.length;i++){if(empPool[i].no===no)return empPool[i];}return null;}
function filtered(){
var b=document.getElementById('filterBuilding').value,d=document.getElementById('filterDorm').value,kc=document.getElementById('filterBed').value.trim();
var a=document.getElementById('filterAlloc').value,ke=document.getElementById('filterEmp').value.trim();
return beds.filter(function(r){
var key=r.building+'|'+r.code,e=assigns[key]?empOf(assigns[key]):null;
return (!b||r.building===b)&&(!d||r.dorm===d)&&(!kc||r.code.indexOf(kc)>=0)
&&(a===''||String(assigns[key]?1:0)===a)&&(!ke||(e&&(e.name.indexOf(ke)>=0||e.no.indexOf(ke.toUpperCase())>=0)));
});
}
function renderTable(){
var list=filtered();
var pages=Math.max(1,Math.ceil(list.length/pageSize));
if(pageNo>pages)pageNo=pages;
var slice=list.slice((pageNo-1)*pageSize,pageNo*pageSize);
document.getElementById('tbody').innerHTML=slice.map(function(r){
var key=r.building+'|'+r.code,no=assigns[key],e=no?empOf(no):null;
return '<tr><td style="font-family:monospace;font-weight:500;">'+r.code+'</td><td>'+r.building+'</td><td>'+r.dorm+'</td>'
+(e?'<td><span class="badge badge--success">是</span></td><td>'+e.name+'</td><td>'+e.no+'</td><td>'+e.phone+'</td>'
:'<td><span class="badge badge--muted">否</span></td><td>-</td><td>-</td><td>-</td>')
+'<td style="font-family:monospace;">'+(bedDevices[key]||'-')+'</td>'
+'<td>'+(e?'<button class="btn btn-link--danger" onclick="unassign(\''+key+'\')">解除</button>':'<button class="btn btn-link" onclick="openAlloc(\''+key+'\',\''+r.code+'\')">分配</button>')+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的床位</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 床 · 已分配 '+Object.keys(assigns).length+' · 第 '+pageNo+'/'+pages+' 页';
}
function gotoPage(d){
var pages=Math.max(1,Math.ceil(filtered().length/pageSize));
pageNo=Math.min(pages,Math.max(1,pageNo+d));renderTable();
}
function resetFilter(){['filterBuilding','filterDorm','filterAlloc'].forEach(function(id){document.getElementById(id).value='';});['filterBed','filterEmp'].forEach(function(id){document.getElementById(id).value='';});onFilterBuilding();}
/* [交互] 分配员工 */
var allocKey='',allocSel='';
function openAlloc(key,code){
allocKey=key;allocSel='';
document.getElementById('allocBed').textContent=code;
document.getElementById('empSearch').value='';
renderEmpList();
document.getElementById('allocMask').classList.add('open');
}
function renderEmpList(){
var k=document.getElementById('empSearch').value.trim();
document.getElementById('empList').innerHTML=empPool.map(function(e){
if(k&&e.name.indexOf(k)<0&&e.no.toUpperCase().indexOf(k.toUpperCase())<0)return '';
return '<div class="emp-row'+(allocSel===e.no?' selected':'')+'" onclick="pickEmp(\''+e.no+'\')"><span>'+e.name+' <span class="emp-row__meta">'+e.no+'</span></span><span class="emp-row__meta">'+e.org+' '+e.depart+'</span></div>';
}).join('')||'<div class="td-empty" style="padding:24px 0;">无匹配员工</div>';
}
function pickEmp(no){allocSel=no;renderEmpList();}
function closeAlloc(){document.getElementById('allocMask').classList.remove('open');}
function confirmAlloc(){
if(!allocSel){showToast('请选择一名员工');return;}
var e=empOf(allocSel);
/* 员工已有床位时先解除旧床位(换宿) */
Object.keys(assigns).forEach(function(k){if(assigns[k]===allocSel)delete assigns[k];});
assigns[allocKey]=allocSel;
closeAlloc();renderTable();showToast('已将「'+e.name+'」分配到该床位');
}
function unassign(key){
var e=empOf(assigns[key]);
delete assigns[key];
renderTable();showToast('已解除「'+(e?e.name:'')+'」的床位分配并记入分配记录');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
initFilters();onFilterBuilding();
</script>
</body>
</html>
@@ -0,0 +1,377 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>楼栋管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{background:none;color:var(--danger);padding:0 4px;height:auto;}.btn-link--danger:hover{color:#ff7875;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1300px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:560px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.6;margin-bottom:4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:14px 0 10px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-md);padding:28px 16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;transition:all .2s;background:#FAFAFA;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);background:#F0F8FF;}
.upload-box svg{display:block;margin:0 auto 8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item active">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位→部门→站点三级级联 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
</select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderTable()" style="min-width:130px;">
<option value="">全部部门</option>
</select>
</div>
<div class="filter-item">
<label>所属站点</label>
<select class="filter-select" id="filterStation" onchange="renderTable()" style="min-width:140px;">
<option value="">全部站点</option>
</select>
</div>
<div class="filter-item">
<label>楼栋名称</label>
<input class="filter-input" id="filterName" placeholder="请输入楼栋名称" oninput="renderTable()">
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
楼栋台账由各单位综合办维护;新建楼栋后请先到「宿舍管理」导入该楼栋的宿舍清单,再在「床位管理」维护床位与员工分配关系。
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">楼栋台账</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" onclick="openEdit(-1)">
<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M6.5 2v9M2 6.5h9" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增楼栋
</button>
<button class="btn btn-default" onclick="openImport()">批量导入</button>
<button class="btn btn-default" onclick="showToast('楼栋台账已导出')">导出</button>
</div>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th>所属单位</th><th>所属部门</th><th style="min-width:120px;">所属站点</th><th style="min-width:140px;">楼栋名称</th>
<th style="min-width:220px;">详细地址</th><th>宿舍数量</th><th>负责人</th><th style="min-width:110px;">负责人电话</th><th style="width:110px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 新增/编辑楼栋 -->
<div class="modal-mask" id="editMask">
<div class="modal--wide">
<div class="modal__title" id="editTitle">新增楼栋</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="eUnit" onchange="onEditUnit()"><option value="">请选择单位</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属部门</span>
<select class="form-control" id="eDepart"><option value="">请先选择单位</option></select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属站点</span>
<select class="form-control" id="eStation"><option value="">请先选择部门</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 楼栋名称</span>
<input class="form-control" id="eName" placeholder="如:樊学倒班宿舍1号楼">
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 详细地址</span>
<input class="form-control" id="eAddr" placeholder="楼栋详细地址">
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">负责人</span>
<input class="form-control" id="eManager" placeholder="楼栋综合负责人">
</div>
<div class="form-item">
<span class="form-label">负责人电话</span>
<input class="form-control" id="eMobile" placeholder="11位手机号">
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeEdit()">取消</button>
<button class="btn btn-primary" onclick="saveEdit()">保存</button>
</div>
</div>
</div>
<!-- [交互] 批量导入:选择站点后上传楼栋清单 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title">批量导入楼栋</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 选择站点(导入的楼栋将挂在该站点下)</span>
<select class="form-control" id="iStation"><option value="">请选择站点</option><option>樊学站点基地</option><option>麻子山站点</option><option>桐园站点基地</option><option>西峰基地</option><option>靖边气田站点</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 上传文件</span>
<div class="upload-box" onclick="showToast('已选择文件:楼栋清单-模板.xlsx')">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M13 17V6m0 0L8.5 10.5M13 6l4.5 4.5" stroke="#999" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 18v2.5A1.5 1.5 0 0 0 5.5 22h15a1.5 1.5 0 0 0 1.5-1.5V18" stroke="#999" stroke-width="1.6" stroke-linecap="round"/></svg>
点击上传 Excel 文件(.xlsx)
</div>
</div>
<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-bottom:4px;">
<a href="javascript:void(0)" style="color:var(--primary);text-decoration:none;" onclick="showToast('模板下载中…')">下载导入模板</a> · 楼栋名称在站点内需唯一
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeImport()">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 删除确认 -->
<div class="modal-mask" id="confirmMask">
<div class="modal">
<div class="modal__title">删除确认</div>
<div class="modal__text" id="confirmText">确认删除该楼栋?</div>
<div class="modal__text" style="color:var(--danger);">楼栋下若存在宿舍或已绑定设备,请先在「宿舍管理」「设备分配」中处理后再删除。</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeConfirm()">取消</button>
<button class="btn btn-danger" onclick="doDelete()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 单位→部门→站点三级映射,与基础配置页单位口径一致 */
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
var depStations={'樊学作业区':['樊学站点基地'],'麻子山作业区':['麻子山站点'],'桐园作业区':['桐园站点基地'],'姬圪台作业区':['姬圪台站点'],'西峰作业区':['西峰基地'],'靖边气田':['靖边气田站点']};
var buildings=[
{unit:'采油一厂',depart:'樊学作业区',station:'樊学站点基地',name:'樊学倒班宿舍1号楼',addr:'樊学作业区基地营地东侧',dorms:3,manager:'王守正',mobile:'13991203456'},
{unit:'采油一厂',depart:'麻子山作业区',station:'麻子山站点',name:'麻子山倒班宿舍楼',addr:'麻子山作业区站点北门',dorms:2,manager:'安伟',mobile:'13991345012'},
{unit:'采油二厂',depart:'桐园作业区',station:'桐园站点基地',name:'桐园倒班宿舍1号楼',addr:'桐园作业区生活区西区',dorms:3,manager:'陈国栋',mobile:'13991567208'},
{unit:'第四采油厂',depart:'西峰作业区',station:'西峰基地',name:'西峰基地综合宿舍楼',addr:'西峰基地食堂南侧',dorms:4,manager:'马文秀',mobile:'13991784320'},
{unit:'采气一厂',depart:'靖边气田',station:'靖边气田站点',name:'靖边气田倒班宿舍楼',addr:'靖边气田综合管理营地北侧',dorms:2,manager:'周铁祥',mobile:'13991923745'}
];
function fillOptions(sel,opts,keepVal){
var v=keepVal||'';
sel.innerHTML='<option value="">'+(sel.dataset.tip||'全部')+'</option>'+opts.map(function(o){return '<option'+(o===v?' selected':'')+'>'+o+'</option>';}).join('');
}
function initFilters(){
var su=document.getElementById('filterUnit');su.dataset.tip='全部单位';
su.innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
document.getElementById('filterDepart').dataset.tip='全部部门';
document.getElementById('filterStation').dataset.tip='全部站点';
document.getElementById('eUnit').innerHTML='<option value="">请选择单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
}
/* [交互] 单位变更时级联刷新部门与站点 */
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart'),staSel=document.getElementById('filterStation');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
depSel.dataset.tip='全部部门';fillOptions(depSel,deps);
var stas=deps.reduce(function(a,d){return a.concat(depStations[d]||[]);},[]);
staSel.dataset.tip='全部站点';fillOptions(staSel,stas);
renderTable();
}
function renderTable(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,s=document.getElementById('filterStation').value,k=document.getElementById('filterName').value.trim();
var list=buildings.filter(function(b){return (!u||b.unit===u)&&(!d||b.depart===d)&&(!s||b.station===s)&&(!k||b.name.indexOf(k)>=0);});
document.getElementById('tbody').innerHTML=list.map(function(b){
var i=buildings.indexOf(b);
return '<tr><td>'+b.unit+'</td><td>'+b.depart+'</td><td>'+b.station+'</td><td style="font-weight:500;">'+b.name+'</td>'
+'<td>'+b.addr+'</td><td>'+b.dorms+'</td><td>'+b.manager+'</td><td>'+b.mobile+'</td>'
+'<td><button class="btn btn-link" onclick="openEdit('+i+')">编辑</button><button class="btn btn-link--danger" onclick="askDelete('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的楼栋</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 栋';
}
function resetFilter(){['filterUnit','filterDepart','filterStation'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';onFilterUnit();}
/* [交互] 新增/编辑 */
var editIdx=-1;
function onEditUnit(){
var u=document.getElementById('eUnit').value;
var depSel=document.getElementById('eDepart'),staSel=document.getElementById('eStation');
depSel.innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
staSel.innerHTML='<option value="">请先选择部门</option>';
}
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=i<0?'新增楼栋':'编辑楼栋';
onEditUnit();
if(i>=0){
var b=buildings[i];
document.getElementById('eUnit').value=b.unit;onEditUnit();
document.getElementById('eDepart').innerHTML='<option>请选择部门</option>'+unitDeparts[b.unit].map(function(d){return '<option'+(d===b.depart?' selected':'')+'>'+d+'</option>';}).join('');
document.getElementById('eStation').innerHTML='<option>请选择站点</option>'+depStations[b.depart].map(function(s){return '<option'+(s===b.station?' selected':'')+'>'+s+'</option>';}).join('');
document.getElementById('eName').value=b.name;document.getElementById('eAddr').value=b.addr;
document.getElementById('eManager').value=b.manager;document.getElementById('eMobile').value=b.mobile;
}else{
['eName','eAddr','eManager','eMobile'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('eUnit').value='';onEditUnit();
}
document.getElementById('editMask').classList.add('open');
}
function closeEdit(){document.getElementById('editMask').classList.remove('open');}
function saveEdit(){
var u=document.getElementById('eUnit').value,d=document.getElementById('eDepart').value,s=document.getElementById('eStation').value;
var n=document.getElementById('eName').value.trim(),a=document.getElementById('eAddr').value.trim();
if(!u||!d||!s||!n||!a){showToast('请完整填写带 * 的必填项');return;}
var dup=buildings.some(function(b,i){return i!==editIdx&&b.station===s&&b.name===n;});
if(dup){showToast('该站点下已存在同名楼栋');return;}
var obj={unit:u,depart:d,station:s,name:n,addr:a,dorms:editIdx>=0?buildings[editIdx].dorms:0,manager:document.getElementById('eManager').value.trim()||'—',mobile:document.getElementById('eMobile').value.trim()||'—'};
if(editIdx<0)buildings.push(obj);else buildings[editIdx]=obj;
closeEdit();renderTable();showToast(editIdx<0?'楼栋已新增':'楼栋已更新');
}
/* [交互] 导入 */
function openImport(){document.getElementById('importMask').classList.add('open');}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){
if(!document.getElementById('iStation').value){showToast('请先选择导入目标站点');return;}
closeImport();showToast('导入成功:新增 2 栋、更新 1 栋');
}
/* [交互] 删除 */
var delIdx=-1;
function askDelete(i){
delIdx=i;
document.getElementById('confirmText').textContent='确认删除「'+buildings[i].name+'」?其下 '+buildings[i].dorms+' 间宿舍将一并失效。';
document.getElementById('confirmMask').classList.add('open');
}
function closeConfirm(){document.getElementById('confirmMask').classList.remove('open');}
function doDelete(){buildings.splice(delIdx,1);closeConfirm();renderTable();showToast('楼栋已删除');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
initFilters();onFilterUnit();
</script>
</body>
</html>
@@ -0,0 +1,301 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>呼叫记录 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.tab-bar{display:flex;gap:4px;margin-top:16px;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);padding:6px 16px 0;}
.tab-item{padding:10px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;}
.tab-item.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1350px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal--wide{width:680px;max-height:82vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:14px 0 10px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item active">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏(按 TAB 切换显示对应字段) -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名/编号</label>
<input class="filter-input" id="filterEmp" placeholder="关键字" oninput="renderAll()">
</div>
<div class="filter-item">
<label>主呼设备</label>
<input class="filter-input" id="filterDev" placeholder="如:HC-FX" oninput="renderAll()">
</div>
<div class="filter-item">
<label>呼叫形式</label>
<select class="filter-select" id="filterCallType" onchange="renderAll()" style="min-width:110px;">
<option value="">全部</option>
</select>
</div>
<div class="filter-item">
<label>呼叫状态</label>
<select class="filter-select" id="filterCallStatus" onchange="renderAll()" style="min-width:100px;">
<option value="">全部</option>
</select>
</div>
<div class="filter-item" id="rescueItem">
<label>救助状态</label>
<select class="filter-select" id="filterRescue" onchange="renderAll()" style="min-width:110px;">
<option value="">全部</option><option>等待处理</option><option>处理中</option><option>处理完成</option>
</select>
</div>
<div class="filter-item">
<label>呼叫日期</label>
<input class="filter-input" type="date" id="filterFrom" value="2026-09-01" onchange="renderAll()" style="min-width:130px;">
<span style="color:var(--text-placeholder);font-size:var(--font-xs);"></span>
<input class="filter-input" type="date" id="filterTo" value="2026-09-07" onchange="renderAll()" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="renderAll();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
员工呼入:呼叫形式=宿舍呼叫(房间面板)/床位呼叫(床头按钮);「等待处理」超过 10 分钟未接听将同步升级为「超时未归告警」联动值班考核。
</div>
<!-- [交互] TAB -->
<div class="tab-bar">
<div class="tab-item active" id="tabIn" onclick="switchTab('in')">员工呼入</div>
<div class="tab-item" id="tabOut" onclick="switchTab('out')">终端呼出</div>
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title" id="cardTitle">员工呼入记录</span>
<button class="btn btn-default" onclick="showToast('呼叫记录已导出')">导出</button>
</div>
<div class="table-card__content">
<table>
<thead id="thead"></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 呼入详情:呼叫信息+住宿员工+救助处置 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">呼叫详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var curTab='in';
/* 员工呼入:主呼设备/空间与分配记录勾稽;赵万福(182/110)与结果异常 YC-20260905-05 同源事件 */
var incoming=[
{dev:'HC-FX-01',time:'2026-09-07 02:14',ct:'床位呼叫',name:'石维国',no:'CQ021578',cs:'已接通',rs:'处理完成',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101-1',reason:'夜间胸闷,请求值班医生到场',action:'值班医生 2 分钟到场,心电监护无异常,处置后回床休息。'},
{dev:'HC-FX-04',time:'2026-09-06 23:41',ct:'宿舍呼叫',name:'李桂芳',no:'CQ032144',cs:'已接通',rs:'处理完成',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101室',reason:'同事突发头晕',action:'到 101 室处置,测血压 126/78,判定为体位性低血压,补液后缓解。'},
{dev:'HC-TY-02',time:'2026-09-05 21:07',ct:'宿舍呼叫',name:'赵万福',no:'CQ033096',cs:'已接通',rs:'处理完成',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 201室',reason:'自测血压 182/110 伴头痛',action:'值班医生到场复测确认,按高血压危象指引处置并通知管理医生——该事件进入结果异常台账(YC-20260905-05)。'},
{dev:'HC-JB-01',time:'2026-09-05 14:22',ct:'床位呼叫',name:'刘玉梅',no:'CQ036441',cs:'已接通',rs:'处理完成',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1',reason:'发热 38.6℃ 需退热处置',action:'靖边医疗点到宿舍处置,物理降温+口服退热,次随访已退。'},
{dev:'HC-XF-01',time:'2026-09-04 07:52',ct:'床位呼叫',name:'高建军',no:'CQ010564',cs:'未接通',rs:'处理完成',station:'西峰基地',space:'西峰基地综合宿舍楼 · 301-1',reason:'晨起手指麻木请求咨询',action:'首呼未接听,5 分钟后回拨接通,指导活动观察,当日无碍。'},
{dev:'HC-MZ-01',time:'2026-09-03 19:36',ct:'宿舍呼叫',name:'杨慧敏',no:'CQ045130',cs:'已接通',rs:'等待处理',station:'麻子山站点',space:'麻子山倒班宿舍楼 · 101室',reason:'踝扭伤冰敷后疼痛加重',action:'待夜班值班医生到场评估,已电话指导制动抬高。'},
{dev:'HC-XF-03',time:'2026-09-02 12:03',ct:'宿舍呼叫',name:'张秀兰',no:'CQ027710',cs:'已接通',rs:'处理中',station:'西峰基地',space:'西峰基地综合宿舍楼 · 402室',reason:'外伤换药到期提醒请求处理',action:'值班室已派单,14:00 医疗点护士到宿舍换药。'},
{dev:'HC-FX-02',time:'2026-09-01 22:48',ct:'床位呼叫',name:'李桂芳',no:'CQ032144',cs:'已接通',rs:'处理完成',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101-2',reason:'失眠需助眠指导',action:'健康指导并纳入随访关注,无需药物介入。'},
{dev:'HC-TY-01',time:'2026-09-07 06:02',ct:'床位呼叫',name:'徐立成',no:'CQ022317',cs:'未接通',rs:'等待处理',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 301-1',reason:'晨起呼叫后设备侧无声',action:'终端未接听,值班员正现场核实;超过 10 分钟将联动超时告警考核。'},
{dev:'HC-JB-01',time:'2026-09-06 05:36',ct:'床位呼叫',name:'呼建国',no:'CQ029905',cs:'已接通',rs:'处理完成',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1',reason:'哮喘吸入剂用完需应急配药',action:'值班医生送备用吸入剂并预约复诊。'}
];
/* 终端呼出:值班室查房呼叫 */
var outgoing=[
{from:'HD-FX-01',to:'HC-FX-01',time:'2026-09-07 07:10',ct:'语音呼叫',cs:'已接听',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101-1'},
{from:'HD-FX-01',to:'HC-FX-04',time:'2026-09-06 21:30',ct:'视频呼叫',cs:'已接听',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101室'},
{from:'HD-MZ-01',to:'HC-MZ-01',time:'2026-09-06 20:05',ct:'语音呼叫',cs:'未接听',station:'麻子山站点',space:'麻子山倒班宿舍楼 · 101室'},
{from:'HD-TY-01',to:'HC-TY-01',time:'2026-09-05 19:50',ct:'视频呼叫',cs:'已接听',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 301-1'},
{from:'HD-TY-01',to:'HC-TY-02',time:'2026-09-05 08:20',ct:'一键呼叫',cs:'已挂断',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 201室'},
{from:'HD-XF-01',to:'HC-XF-02',time:'2026-09-04 18:40',ct:'语音呼叫',cs:'未接听',station:'西峰基地',space:'西峰基地综合宿舍楼 · 302-1'},
{from:'HD-JB-01',to:'HC-JB-01',time:'2026-09-04 07:15',ct:'视频呼叫',cs:'已接听',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1'},
{from:'HD-JB-01',to:'HC-JB-01',time:'2026-09-01 20:30',ct:'语音呼叫',cs:'已接听',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1'}
];
var inBadge={'已接通':'badge--success','未接通':'badge--danger'};
var rsBadge={'等待处理':'badge--warning','处理中':'badge--info','处理完成':'badge--success'};
var outBadge={'已接听':'badge--success','未接听':'badge--danger','已挂断':'badge--muted'};
function headIn(){return '<tr><th style="min-width:100px;">主呼设备</th><th style="min-width:140px;">呼叫时间</th><th>呼叫形式</th><th>员工姓名</th><th>员工编号</th><th>呼叫状态</th><th>救助状态</th><th style="min-width:120px;">站点名称</th><th style="min-width:200px;">宿舍/床位信息</th><th style="width:80px;">操作</th></tr>';}
function headOut(){return '<tr><th style="min-width:100px;">主呼设备</th><th style="min-width:100px;">被呼设备</th><th style="min-width:140px;">呼叫时间</th><th>呼叫形式</th><th>呼叫状态</th><th style="min-width:120px;">站点名称</th><th style="min-width:200px;">宿舍/床位信息</th></tr>';}
function fillCallTypeFilters(){
var ts=curTab==='in'?['宿舍呼叫','床位呼叫']:['语音呼叫','视频呼叫','一键呼叫'];
document.getElementById('filterCallType').innerHTML='<option value="">全部</option>'+ts.map(function(t){return '<option>'+t+'</option>';}).join('');
var ss=curTab==='in'?['已接通','未接通']:['已接听','未接听','已挂断'];
document.getElementById('filterCallStatus').innerHTML='<option value="">全部</option>'+ss.map(function(s){return '<option>'+s+'</option>';}).join('');
document.getElementById('rescueItem').style.display=curTab==='in'?'':'none';
}
function renderAll(){
var ke=document.getElementById('filterEmp').value.trim(),kd=document.getElementById('filterDev').value.trim().toUpperCase();
var tc=document.getElementById('filterCallType').value,sc=document.getElementById('filterCallStatus').value,sr=document.getElementById('filterRescue').value;
var f=document.getElementById('filterFrom').value,t=document.getElementById('filterTo').value;
if(curTab==='in'){
var list=filter(incoming,function(r){return (r.name.indexOf(ke)>=0||r.no.toUpperCase().indexOf(ke.toUpperCase())>=0)&&r.dev.toUpperCase().indexOf(kd)>=0&&(!tc||r.ct===tc)&&(!sc||r.cs===sc)&&(!sr||r.rs===sr)&&day(r.time)>=f&&day(r.time)<=t;});
document.getElementById('thead').innerHTML=headIn();
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=incoming.indexOf(r);
return '<tr><td style="font-family:monospace;">'+r.dev+'</td><td>'+r.time+'</td><td>'+r.ct+'</td><td>'+r.name+'</td><td>'+r.no+'</td>'
+'<td><span class="badge '+inBadge[r.cs]+'">'+r.cs+'</span></td><td><span class="badge '+rsBadge[r.rs]+'">'+r.rs+'</span></td>'
+'<td>'+r.station+'</td><td>'+r.space+'</td><td><button class="btn btn-link" onclick="openDetail('+i+')">详情</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的呼叫记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 次 · 接通率 '+(list.length?Math.round(list.filter(function(r){return r.cs==='已接通';}).length/list.length*100):0)+'% · 处置完成 '+list.filter(function(r){return r.rs==='处理完成';}).length+' 次';
document.getElementById('cardTitle').textContent='员工呼入记录';
}else{
var list2=filter(outgoing,function(r){return (r.from.toUpperCase().indexOf(kd)>=0||r.to.toUpperCase().indexOf(kd)>=0)&&(!tc||r.ct===tc)&&(!sc||r.cs===sc)&&day(r.time)>=f&&day(r.time)<=t;});
document.getElementById('thead').innerHTML=headOut();
document.getElementById('tbody').innerHTML=list2.map(function(r){
return '<tr><td style="font-family:monospace;">'+r.from+'</td><td style="font-family:monospace;">'+r.to+'</td><td>'+r.time+'</td><td>'+r.ct+'</td>'
+'<td><span class="badge '+outBadge[r.cs]+'">'+r.cs+'</span></td><td>'+r.station+'</td><td>'+r.space+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="7">没有符合条件的呼叫记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list2.length+' 次 · 接听 '+list2.filter(function(r){return r.cs==='已接听';}).length+' 次';
document.getElementById('cardTitle').textContent='终端呼出记录';
}
}
function filter(arr,fn){return arr.filter(fn);}
function day(dt){return dt.slice(0,10);}
function switchTab(t){
curTab=t;
document.getElementById('tabIn').classList.toggle('active',t==='in');
document.getElementById('tabOut').classList.toggle('active',t==='out');
fillCallTypeFilters();renderAll();
}
function resetFilter(){['filterEmp','filterDev','filterCallType','filterCallStatus','filterRescue'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterFrom').value='2026-09-01';document.getElementById('filterTo').value='2026-09-07';renderAll();}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var r=incoming[i];
document.getElementById('detailTitle').textContent='呼叫详情 · '+r.dev+' · '+r.time;
document.getElementById('detailBody').innerHTML=
'<div class="form-section">呼叫信息</div><div class="detail-grid">'
+di('主呼设备',r.dev)+di('呼叫形式',r.ct)
+di('呼叫时间',r.time)+di('呼叫状态',r.cs)
+di('救助状态',r.rs)+di('站点名称',r.station)
+di('宿舍/床位信息',r.space,true)
+'</div><div class="form-section">住宿员工</div><div class="detail-grid">'
+di('员工姓名',r.name)+di('员工编号',r.no)
+di('呼叫事由',r.reason,true)
+'</div><div class="form-section">救助处置</div><div class="detail-grid">'
+di('处置过程',r.action,true)
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
fillCallTypeFilters();renderAll();
</script>
</body>
</html>
@@ -0,0 +1,302 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>设备分配 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{background:none;color:var(--danger);padding:0 4px;height:auto;}.btn-link--danger:hover{color:#ff7875;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1150px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.radio-group{display:flex;gap:20px;padding:6px 0;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{accent-color:var(--primary);width:15px;height:15px;cursor:pointer;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item active">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()"><option value="">全部单位</option></select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderTable()" style="min-width:130px;"><option value="">全部部门</option></select>
</div>
<div class="filter-item">
<label>设备编号</label>
<input class="filter-input" id="filterCode" placeholder="如:HC-" oninput="renderTable()">
</div>
<div class="filter-item">
<label>分配状态</label>
<select class="filter-select" id="filterStatus" onchange="renderTable()" style="min-width:100px;">
<option value="">全部</option><option value="1">已分配</option><option value="0">未分配</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
一台设备同一时间只能绑定一个空间;对已绑定设备再次分配会自动先解除原绑定,两次动作均写入「分配记录」(设备-空间历史)。
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">呼叫设备分配</span>
<button class="btn btn-default" onclick="showToast('分配清单已导出')">导出</button>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th>所属单位</th><th>所属部门</th><th style="min-width:100px;">设备编号</th><th>分配状态</th><th>分配类型</th>
<th style="min-width:230px;">分配宿舍/床位</th><th style="min-width:100px;">分配日期</th><th style="width:120px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 分配弹窗:类型+楼栋→宿舍→(床位)级联 -->
<div class="modal-mask" id="allocMask">
<div class="modal">
<div class="modal__title">分配设备 · <span id="aCode"></span></div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 分配类型</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="aType" value="1" checked onchange="onAllocType()"> 宿舍(房间级一键呼叫)</label>
<label class="radio"><input type="radio" name="aType" value="2" onchange="onAllocType()"> 床位(床头呼叫)</label>
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 楼栋</span>
<select class="form-control" id="aBuilding" onchange="onAllocBuilding()"><option value="">请选择楼栋</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 宿舍</span>
<select class="form-control" id="aDorm" onchange="onAllocDorm()"><option value="">请先选择楼栋</option></select>
</div>
</div>
<div class="form-item" id="bedItem" style="display:none;">
<span class="form-label"><span class="req">*</span> 床位</span>
<select class="form-control" id="aBed"><option value="">请先选择宿舍</option></select>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeAlloc()">取消</button>
<button class="btn btn-primary" onclick="confirmAlloc()">确认分配</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
/* 楼栋→宿舍→床位数,与「宿舍管理」「床位管理」勾稽 */
var dormTree={
'樊学倒班宿舍1号楼':{'101室':4,'102室':4,'201室':2},
'麻子山倒班宿舍楼':{'101室':4,'102室':2},
'桐园倒班宿舍1号楼':{'201室':2,'301室':4,'302室':2},
'西峰基地综合宿舍楼':{'301室':4,'302室':4,'401室':2,'402室':4},
'靖边气田倒班宿舍楼':{'401室':4,'402室':2}
};
/* 与「设备列表」呼叫设备一致;alloc:1宿舍/2床位 */
var devices=[
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 101-1',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 101-2',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 102-1',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',alloc:1,type:1,space:'樊学倒班宿舍1号楼 · 101室',time:'2026-03-05'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',alloc:1,type:1,space:'麻子山倒班宿舍楼 · 101室',time:'2026-03-19'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',alloc:1,type:2,space:'桐园倒班宿舍1号楼 · 301-1',time:'2026-04-10'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',alloc:1,type:1,space:'桐园倒班宿舍1号楼 · 201室',time:'2026-04-10'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',alloc:1,type:2,space:'西峰基地综合宿舍楼 · 301-1',time:'2026-05-12'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',alloc:1,type:2,space:'西峰基地综合宿舍楼 · 302-1',time:'2026-05-12'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',alloc:1,type:1,space:'西峰基地综合宿舍楼 · 402室',time:'2026-05-12'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-01',alloc:1,type:2,space:'靖边气田倒班宿舍楼 · 401-1',time:'2026-06-24'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-02',alloc:0,type:0,space:'',time:''}
];
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
var uniq=[];deps.forEach(function(x){if(uniq.indexOf(x)<0)uniq.push(x);});
depSel.innerHTML='<option value="">全部部门</option>'+uniq.map(function(d){return '<option>'+d+'</option>';}).join('');
renderTable();
}
function renderTable(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,k=document.getElementById('filterCode').value.trim().toUpperCase(),s=document.getElementById('filterStatus').value;
var list=devices.filter(function(r){return (!u||r.unit===u)&&(!d||r.depart===d)&&(!k||r.code.toUpperCase().indexOf(k)>=0)&&(s===''||String(r.alloc)===s);});
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=devices.indexOf(r);
var typeTxt=r.alloc?('<span class="badge '+(r.type===1?'badge--info':'badge--success')+'">'+(r.type===1?'宿舍':'床位')+'</span>'):'<span class="badge badge--muted">-</span>';
var ops=r.alloc
?'<button class="btn btn-link" onclick="openAlloc('+i+')">换绑</button><button class="btn btn-link--danger" onclick="unbind('+i+')">解除</button>'
:'<button class="btn btn-link" onclick="openAlloc('+i+')">分配</button>';
return '<tr><td>'+r.unit+'</td><td>'+r.depart+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td>'
+'<td>'+(r.alloc?'<span class="badge badge--success">已分配</span>':'<span class="badge badge--warning">未分配</span>')+'</td>'
+'<td>'+typeTxt+'</td><td>'+(r.space||'-')+'</td><td>'+(r.time||'-')+'</td><td>'+ops+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="8">没有符合条件的设备</td></tr>';
document.getElementById('totalTip').textContent='共 '+devices.length+' 台 · 已分配 '+devices.filter(function(r){return r.alloc;}).length+' 台';
}
function resetFilter(){['filterUnit','filterDepart','filterStatus'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterCode').value='';onFilterUnit();}
var allocIdx=-1;
function openAlloc(i){
allocIdx=i;
document.getElementById('aCode').textContent=devices[i].code;
document.getElementById('aBuilding').innerHTML='<option value="">请选择楼栋</option>'+Object.keys(dormTree).map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('aDorm').innerHTML='<option value="">请先选择楼栋</option>';
document.getElementById('aBed').innerHTML='<option value="">请先选择宿舍</option>';
document.querySelector('input[name="aType"][value="1"]').checked=true;
onAllocType();
document.getElementById('allocMask').classList.add('open');
}
function onAllocType(){document.getElementById('bedItem').style.display=document.querySelector('input[name="aType"]:checked').value==='2'?'':'none';}
function onAllocBuilding(){
var b=document.getElementById('aBuilding').value;
document.getElementById('aDorm').innerHTML='<option value="">请选择宿舍</option>'+(b?Object.keys(dormTree[b]).map(function(d){return '<option>'+d+'</option>';}).join(''):[]);
document.getElementById('aBed').innerHTML='<option value="">请先选择宿舍</option>';
}
function onAllocDorm(){
var b=document.getElementById('aBuilding').value,d=document.getElementById('aDorm').value;
var beds=d?dormTree[b][d]:0,arr=[];
for(var nn=1;nn<=beds;nn++)arr.push(d.replace('室','')+'-'+nn);
document.getElementById('aBed').innerHTML='<option value="">请选择床位</option>'+arr.map(function(x){return '<option>'+x+'</option>';}).join('');
}
function closeAlloc(){document.getElementById('allocMask').classList.remove('open');}
function confirmAlloc(){
var r=devices[allocIdx];
var t=parseInt(document.querySelector('input[name="aType"]:checked').value,10);
var b=document.getElementById('aBuilding').value,d=document.getElementById('aDorm').value;
if(!b||!d){showToast('请选择楼栋与宿舍');return;}
var bed='';
if(t===2){bed=document.getElementById('aBed').value;if(!bed){showToast('床位分配需选择具体床位');return;}}
var had=r.alloc;
r.alloc=1;r.type=t;r.space=b+' · '+(t===2?bed:d);r.time='2026-09-07';
closeAlloc();renderTable();showToast('已绑定至 '+r.space+(had?'(原绑定已解除并记入分配记录)':''));
}
function unbind(i){
var r=devices[i];r.alloc=0;r.type=0;r.space='';r.time='';
renderTable();showToast('「'+r.code+'」已解除绑定,历史见分配记录');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
onFilterUnit();
</script>
</body>
</html>
+311
View File
@@ -0,0 +1,311 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>设备列表 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{background:none;color:var(--danger);padding:0 4px;height:auto;}.btn-link--danger:hover{color:#ff7875;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.tab-bar{display:flex;gap:4px;margin-top:16px;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);padding:6px 16px 0;}
.tab-item{padding:10px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;}
.tab-item.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.6;margin-bottom:4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-md);padding:28px 16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;transition:all .2s;background:#FAFAFA;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);background:#F0F8FF;}
.upload-box svg{display:block;margin:0 auto 8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item active">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()"><option value="">全部单位</option></select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderAll()" style="min-width:130px;"><option value="">全部部门</option></select>
</div>
<div class="filter-item">
<label>设备编号</label>
<input class="filter-input" id="filterCode" placeholder="如:HC-FX" oninput="renderAll()">
</div>
<div class="filter-item">
<label>设备状态</label>
<select class="filter-select" id="filterStatus" onchange="renderAll()" style="min-width:100px;">
<option value="">全部</option><option value="0">正常</option><option value="1">故障</option><option value="2">停用</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderAll();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
呼叫设备编号前缀 HC(Huang-Call 床头呼叫),终端设备前缀 HD;编号规则 HC-站点拼音-序号。状态为「故障」的设备在「超时未归告警」与呼叫记录中会出现对应告警。
</div>
<!-- [交互] TAB:呼叫设备 / 终端设备 -->
<div class="tab-bar">
<div class="tab-item active" id="tabCall" onclick="switchTab('call')">呼叫设备</div>
<div class="tab-item" id="tabTerm" onclick="switchTab('term')">终端设备</div>
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title" id="cardTitle">呼叫设备台账</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" onclick="openImport()"><svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M6.5 2v9M2 6.5h9" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>批量导入</button>
<button class="btn btn-default" onclick="showToast('台账已导出')">导出</button>
</div>
</div>
<div class="table-card__content">
<table>
<thead id="thead"></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 设备台账导入:按单位+部门归属 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title" id="importTitle">批量导入呼叫设备</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="iUnit"><option value="">请选择单位</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属部门</span>
<select class="form-control" id="iDepart"><option value="">请选择部门</option></select>
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 上传文件</span>
<div class="upload-box" onclick="showToast('已选择文件:设备台账-模板.xlsx')">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M13 17V6m0 0L8.5 10.5M13 6l4.5 4.5" stroke="#999" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 18v2.5A1.5 1.5 0 0 0 5.5 22h15a1.5 1.5 0 0 0 1.5-1.5V18" stroke="#999" stroke-width="1.6" stroke-linecap="round"/></svg>
点击上传 Excel 文件(.xlsx)
</div>
</div>
<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-bottom:4px;">
<a href="javascript:void(0)" style="color:var(--primary);text-decoration:none;" onclick="showToast('模板下载中…')">下载导入模板</a> · 设备编号全局唯一
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeImport()">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
var statusBadge={'0':'badge--success','1':'badge--danger','2':'badge--muted'};
var statusText={'0':'正常','1':'故障','2':'停用'};
/* 呼叫设备:分配关系与「设备分配」「床位管理」勾稽(HC-FX-03 故障见超时告警演示) */
var callDevices=[
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-03-02',status:'1',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-03-18',status:'0',manager:'安伟',mphone:'13991345012',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-06-23',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-02',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-09-01',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'CQ中控医疗',mfrTel:'029-86521700'}
];
/* 终端设备:值班室固定部署,不参与宿舍/床位分配 */
var termDevices=[
{unit:'采油一厂',depart:'樊学作业区',code:'HD-FX-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HD-MZ-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-03-18',status:'0',manager:'安伟',mphone:'13991345012',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油二厂',depart:'桐园作业区',code:'HD-TY-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HD-XF-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-05-11',status:'1',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采气一厂',depart:'靖边气田',code:'HD-JB-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-06-23',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'华旭智能',mfrTel:'029-87334210'}
];
var curTab='call';
function headRow(){
var name=curTab==='call'?'设备名称':'终端名称';
return '<tr><th>所属单位</th><th>所属部门</th><th style="min-width:100px;">设备编号</th><th style="min-width:120px;">'+name+'</th><th>设备型号</th><th>入库日期</th><th>设备状态</th><th>管理人</th><th style="min-width:105px;">管理人电话</th><th style="min-width:110px;">设备厂家</th><th style="min-width:110px;">厂家电话</th><th style="width:90px;">操作</th></tr>';
}
function renderAll(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,k=document.getElementById('filterCode').value.trim().toUpperCase(),s=document.getElementById('filterStatus').value;
var src=curTab==='call'?callDevices:termDevices;
var list=src.filter(function(r){return (!u||r.unit===u)&&(!d||r.depart===d)&&(!k||r.code.toUpperCase().indexOf(k)>=0)&&(s===''||r.status===s);});
document.getElementById('thead').innerHTML=headRow();
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=src.indexOf(r);
return '<tr><td>'+r.unit+'</td><td>'+r.depart+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td><td>'+r.name+'</td><td>'+r.model+'</td><td>'+r.store+'</td>'
+'<td><span class="badge '+statusBadge[r.status]+'">'+statusText[r.status]+'</span></td>'
+'<td>'+r.manager+'</td><td>'+r.mphone+'</td><td>'+r.mfr+'</td><td>'+r.mfrTel+'</td>'
+'<td><button class="btn btn-link" onclick="openEditStatus('+i+')">报修/停用</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="12">没有符合条件的设备</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 台(正常 '+list.filter(function(r){return r.status==='0';}).length+' / 故障 '+list.filter(function(r){return r.status==='1';}).length+' / 停用 '+list.filter(function(r){return r.status==='2';}).length+')';
document.getElementById('cardTitle').textContent=curTab==='call'?'呼叫设备台账':'终端设备台账';
}
function switchTab(t){
curTab=t;
document.getElementById('tabCall').classList.toggle('active',t==='call');
document.getElementById('tabTerm').classList.toggle('active',t==='term');
renderAll();
}
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
var uniq=[];deps.forEach(function(x){if(uniq.indexOf(x)<0)uniq.push(x);});
depSel.innerHTML='<option value="">全部部门</option>'+uniq.map(function(d){return '<option>'+d+'</option>';}).join('');
renderAll();
}
function resetFilter(){['filterUnit','filterDepart','filterStatus'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterCode').value='';onFilterUnit();}
/* [交互] 状态变更:正常↔故障↔停用 轮转演示 */
function openEditStatus(i){
var src=curTab==='call'?callDevices:termDevices;
var r=src[i];
var next=r.status==='0'?'1':(r.status==='1'?'2':'0');
r.status=next;renderAll();
showToast('「'+r.code+'」状态已变更为:'+statusText[next]);
}
/* [交互] 导入 */
function openImport(){
document.getElementById('importTitle').textContent=curTab==='call'?'批量导入呼叫设备':'批量导入终端设备';
var us=Object.keys(unitDeparts);
document.getElementById('iUnit').innerHTML='<option value="">请选择单位</option>'+us.map(function(u){return '<option>'+u+'</option>';}).join('');
document.getElementById('iDepart').innerHTML='<option value="">请选择部门</option>';
document.getElementById('iUnit').onchange=function(){
var u=this.value;
document.getElementById('iDepart').innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
};
document.getElementById('importMask').classList.add('open');
}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){
if(!document.getElementById('iUnit').value||!document.getElementById('iDepart').value){showToast('请选择单位与部门');return;}
closeImport();showToast('导入成功:新增 '+(curTab==='call'?'4':'1')+' 台设备');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
onFilterUnit();
</script>
</body>
</html>
@@ -0,0 +1,340 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宿舍管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{background:none;color:var(--danger);padding:0 4px;height:auto;}.btn-link--danger:hover{color:#ff7875;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1200px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:560px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.6;margin-bottom:4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-md);padding:28px 16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;transition:all .2s;background:#FAFAFA;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);background:#F0F8FF;}
.upload-box svg{display:block;margin:0 auto 8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item active">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属楼栋</label>
<select class="filter-select" id="filterBuilding" onchange="renderTable()" style="min-width:180px;">
<option value="">全部楼栋</option>
</select>
</div>
<div class="filter-item">
<label>宿舍名称</label>
<input class="filter-input" id="filterName" placeholder="如:101" oninput="renderTable()">
</div>
<div class="filter-item">
<label>是否分配设备</label>
<select class="filter-select" id="filterAlloc" onchange="renderTable()" style="min-width:110px;">
<option value="">全部</option><option value="1">已分配</option><option value="0">未分配</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderTable();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
房间级设备适用于 4 人间集中呼叫;床头级设备请在「设备分配」中绑定到床位。床位清单自动按宿舍「床位数」生成,可在「床位管理」核对。
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">宿舍清单</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" onclick="openEdit(-1)">
<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M6.5 2v9M2 6.5h9" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增宿舍
</button>
<button class="btn btn-default" onclick="openImport()">批量导入</button>
<button class="btn btn-default" onclick="showToast('宿舍清单已导出')">导出</button>
</div>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="min-width:110px;">宿舍名称</th><th style="min-width:180px;">所属楼栋</th><th>床位数量</th><th>负责人</th>
<th style="min-width:110px;">负责人电话</th><th>是否分配设备</th><th style="min-width:110px;">设备编号</th><th style="width:110px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 新增/编辑宿舍 -->
<div class="modal-mask" id="editMask">
<div class="modal--wide">
<div class="modal__title" id="editTitle">新增宿舍</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属楼栋</span>
<select class="form-control" id="eBuilding"><option value="">请选择楼栋</option></select>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 宿舍名称</span>
<input class="form-control" id="eName" placeholder="如:101室">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 床位数量</span>
<input class="form-control" id="eBeds" type="number" min="1" max="8" placeholder="1-8">
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">负责人</span>
<input class="form-control" id="eManager" placeholder="宿舍长">
</div>
<div class="form-item">
<span class="form-label">负责人电话</span>
<input class="form-control" id="eMobile" placeholder="11位手机号">
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeEdit()">取消</button>
<button class="btn btn-primary" onclick="saveEdit()">保存</button>
</div>
</div>
</div>
<!-- [交互] 批量导入:选择楼栋后上传宿舍清单 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title">批量导入宿舍</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 选择楼栋(导入的宿舍将挂在该楼栋下)</span>
<select class="form-control" id="iBuilding"><option value="">请选择楼栋</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 上传文件</span>
<div class="upload-box" onclick="showToast('已选择文件:宿舍清单-模板.xlsx')">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M13 17V6m0 0L8.5 10.5M13 6l4.5 4.5" stroke="#999" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 18v2.5A1.5 1.5 0 0 0 5.5 22h15a1.5 1.5 0 0 0 1.5-1.5V18" stroke="#999" stroke-width="1.6" stroke-linecap="round"/></svg>
点击上传 Excel 文件(.xlsx)
</div>
</div>
<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-bottom:4px;">
<a href="javascript:void(0)" style="color:var(--primary);text-decoration:none;" onclick="showToast('模板下载中…')">下载导入模板</a> · 导入后将按「床位数」自动生成床位记录
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeImport()">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 删除确认 -->
<div class="modal-mask" id="confirmMask">
<div class="modal">
<div class="modal__title">删除确认</div>
<div class="modal__text" id="confirmText">确认删除该宿舍?</div>
<div class="modal__text" style="color:var(--danger);">宿舍下若存在床位分配或已绑定设备,请先在「床位管理」「设备分配」中解除后再删除。</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeConfirm()">取消</button>
<button class="btn btn-danger" onclick="doDelete()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 楼栋清单与「楼栋管理」页勾稽 */
var buildings=['樊学倒班宿舍1号楼','麻子山倒班宿舍楼','桐园倒班宿舍1号楼','西峰基地综合宿舍楼','靖边气田倒班宿舍楼'];
/* alloc:绑定到宿舍本身的设备(房间级呼叫);床位级绑定见「设备分配」 */
var dorms=[
{name:'101室',building:'樊学倒班宿舍1号楼',beds:4,manager:'石维国',mobile:'13991203456',alloc:1,device:'HC-FX-04'},
{name:'102室',building:'樊学倒班宿舍1号楼',beds:4,manager:'李桂芳',mobile:'13991207788',alloc:0,device:''},
{name:'201室',building:'樊学倒班宿舍1号楼',beds:2,manager:'—',mobile:'—',alloc:0,device:''},
{name:'101室',building:'麻子山倒班宿舍楼',beds:4,manager:'杨慧敏',mobile:'13991345012',alloc:1,device:'HC-MZ-01'},
{name:'102室',building:'麻子山倒班宿舍楼',beds:2,manager:'—',mobile:'—',alloc:0,device:''},
{name:'201室',building:'桐园倒班宿舍1号楼',beds:2,manager:'赵万福',mobile:'13991567208',alloc:1,device:'HC-TY-02'},
{name:'301室',building:'桐园倒班宿舍1号楼',beds:4,manager:'—',mobile:'—',alloc:0,device:''},
{name:'302室',building:'桐园倒班宿舍1号楼',beds:2,manager:'—',mobile:'—',alloc:0,device:''},
{name:'301室',building:'西峰基地综合宿舍楼',beds:4,manager:'高建军',mobile:'13991784320',alloc:0,device:''},
{name:'302室',building:'西峰基地综合宿舍楼',beds:4,manager:'闫婷',mobile:'13991784531',alloc:0,device:''},
{name:'401室',building:'西峰基地综合宿舍楼',beds:2,manager:'—',mobile:'—',alloc:0,device:''},
{name:'402室',building:'西峰基地综合宿舍楼',beds:4,manager:'徐立成',mobile:'13991784906',alloc:1,device:'HC-XF-03'},
{name:'401室',building:'靖边气田倒班宿舍楼',beds:4,manager:'刘玉梅',mobile:'13991923745',alloc:0,device:''},
{name:'402室',building:'靖边气田倒班宿舍楼',beds:2,manager:'呼建国',mobile:'13991923870',alloc:0,device:''}
];
function initFilters(){
var opts='<option value="">全部楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('filterBuilding').innerHTML=opts;
document.getElementById('eBuilding').innerHTML='<option value="">请选择楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('iBuilding').innerHTML='<option value="">请选择楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
}
function renderTable(){
var b=document.getElementById('filterBuilding').value,n=document.getElementById('filterName').value.trim(),a=document.getElementById('filterAlloc').value;
var list=dorms.filter(function(r){return (!b||r.building===b)&&(!n||r.name.indexOf(n)>=0)&&(a===''||String(r.alloc)===a);});
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=dorms.indexOf(r);
return '<tr><td style="font-weight:500;">'+r.name+'</td><td>'+r.building+'</td><td>'+r.beds+'</td><td>'+r.manager+'</td><td>'+r.mobile+'</td>'
+'<td>'+(r.alloc?'<span class="badge badge--success">已分配</span>':'<span class="badge badge--muted">未分配</span>')+'</td>'
+'<td style="font-family:monospace;">'+(r.device||'-')+'</td>'
+'<td><button class="btn btn-link" onclick="openEdit('+i+')">编辑</button><button class="btn btn-link--danger" onclick="askDelete('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="8">没有符合条件的宿舍</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 间 · 房间级设备 '+dorms.filter(function(r){return r.alloc;}).length+' 台';
}
function resetFilter(){['filterBuilding','filterAlloc'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';renderTable();}
var editIdx=-1;
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=i<0?'新增宿舍':'编辑宿舍';
if(i>=0){
var r=dorms[i];
document.getElementById('eBuilding').value=r.building;document.getElementById('eName').value=r.name;
document.getElementById('eBeds').value=r.beds;document.getElementById('eManager').value=r.manager==='—'?'':r.manager;
document.getElementById('eMobile').value=r.mobile==='—'?'':r.mobile;
}else{
document.getElementById('eBuilding').value='';['eName','eBeds','eManager','eMobile'].forEach(function(id){document.getElementById(id).value='';});
}
document.getElementById('editMask').classList.add('open');
}
function closeEdit(){document.getElementById('editMask').classList.remove('open');}
function saveEdit(){
var b=document.getElementById('eBuilding').value,n=document.getElementById('eName').value.trim(),beds=parseInt(document.getElementById('eBeds').value,10);
if(!b||!n||!beds||beds<1||beds>8){showToast('请完整填写,床位数须在 1-8 之间');return;}
var dup=dorms.some(function(r,i){return i!==editIdx&&r.building===b&&r.name===n;});
if(dup){showToast('该楼栋下已存在同名宿舍');return;}
var obj={name:n,building:b,beds:beds,manager:document.getElementById('eManager').value.trim()||'—',mobile:document.getElementById('eMobile').value.trim()||'—',alloc:editIdx>=0?dorms[editIdx].alloc:0,device:editIdx>=0?dorms[editIdx].device:''};
if(editIdx<0)dorms.push(obj);else dorms[editIdx]=obj;
closeEdit();renderTable();showToast(editIdx<0?'宿舍已新增(将生成 '+beds+' 条床位)':'宿舍已更新');
}
function openImport(){document.getElementById('importMask').classList.add('open');}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){
if(!document.getElementById('iBuilding').value){showToast('请先选择导入目标楼栋');return;}
closeImport();showToast('导入成功:新增 6 间宿舍、生成 22 条床位');
}
var delIdx=-1;
function askDelete(i){
delIdx=i;
document.getElementById('confirmText').textContent='确认删除「'+dorms[i].building+' · '+dorms[i].name+'」?其下 '+dorms[i].beds+' 张床位将一并失效。';
document.getElementById('confirmMask').classList.add('open');
}
function closeConfirm(){document.getElementById('confirmMask').classList.remove('open');}
function doDelete(){dorms.splice(delIdx,1);closeConfirm();renderTable();showToast('宿舍已删除');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
initFilters();renderTable();
</script>
</body>
</html>
+221
View File
@@ -0,0 +1,221 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>统计汇总 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;}
.stat-card__num{font-size:22px;font-weight:700;line-height:1.2;}
.stat-card__label{font-size:var(--font-xs);color:var(--text-secondary);margin-top:2px;}
.tab-bar{display:flex;gap:4px;margin-top:16px;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);padding:6px 16px 0;}
.tab-item{padding:10px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;}
.tab-item.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1350px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
td.td-name{text-align:left;padding-left:14px;font-weight:500;}
tfoot td{padding:10px 7px;text-align:center;font-size:var(--font-sm);font-weight:700;background:#FAFAFA;border-top:2px solid var(--border);white-space:nowrap;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item active">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>单位</label>
<select class="filter-select" id="filterUnit" onchange="render()">
<option value="">全部单位</option>
<option>采油一厂</option>
<option>采油二厂</option>
<option>第四采油厂</option>
<option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>统计周期</label>
<input class="filter-input" type="date" id="filterFrom" value="2026-09-01" onchange="render()" style="min-width:130px;">
<span style="color:var(--text-placeholder);font-size:var(--font-xs);"></span>
<input class="filter-input" type="date" id="filterTo" value="2026-09-07" onchange="render()" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="render();showToast('统计完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
仅统计已安装床头呼叫设备的作业区/站点;接听率 = 已接通 ÷ 呼叫总数,处置率 = 处理完成 ÷ 呼叫总数。
</div>
<!-- [交互] 概览卡片(随筛选联动) -->
<div class="stat-row">
<div class="stat-card"><div class="stat-card__num" id="cardDev">-</div><div class="stat-card__label">呼叫设备(已分配 <span id="cardAlloc">-</span>)</div></div>
<div class="stat-card"><div class="stat-card__num" id="cardCall">-</div><div class="stat-card__label">呼叫总次数</div></div>
<div class="stat-card"><div class="stat-card__num" style="color:var(--success);" id="cardRate">-</div><div class="stat-card__label">接听率</div></div>
<div class="stat-card"><div class="stat-card__num" style="color:var(--warning);" id="cardAlarm">-</div><div class="stat-card__label">超时告警次数</div></div>
</div>
<!-- [交互] 维度 TAB -->
<div class="tab-bar">
<div class="tab-item active" id="tabDep" onclick="switchDim('dep')">按部门统计</div>
<div class="tab-item" id="tabStation" onclick="switchDim('station')">按站点统计</div>
</div>
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title" id="cardTitle">部门呼叫统计</span>
<button class="btn btn-default" onclick="showToast('统计表已导出')">导出</button>
</div>
<div class="table-card__content">
<table>
<thead id="thead"></thead>
<tbody id="tbody"></tbody>
<tfoot id="tfoot"></tfoot>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
/* 统计数据与已建页面勾稽:设备/已分配=设备分配页(12/11);呼叫8接通2未接通=呼叫记录呼入TAB;告警6=超时告警页 */
var rows=[
{unit:'采油一厂',dep:'樊学作业区',station:'樊学站点基地',dev:4,alloc:4,calls:3,ok:3,miss:0,done:3,undone:0,alarm:1},
{unit:'采油一厂',dep:'麻子山作业区',station:'麻子山站点',dev:1,alloc:1,calls:1,ok:1,miss:0,done:0,undone:1,alarm:2},
{unit:'采油二厂',dep:'桐园作业区',station:'桐园站点基地',dev:2,alloc:2,calls:2,ok:1,miss:1,done:1,undone:1,alarm:1},
{unit:'第四采油厂',dep:'西峰作业区',station:'西峰基地',dev:3,alloc:3,calls:2,ok:1,miss:1,done:1,undone:1,alarm:2},
{unit:'采气一厂',dep:'靖边气田',station:'靖边气田站点',dev:2,alloc:1,calls:2,ok:2,miss:0,done:2,undone:0,alarm:0}
];
var dim='dep';
function head(){
var dimCol=dim==='dep'?'部门':'站点';
return '<tr><th style="min-width:100px;">单位</th><th style="min-width:120px;">'+dimCol+'</th>'
+'<th>呼叫设备数量</th><th>已分配数量</th><th>总呼叫次数</th><th>已接通次数</th><th>未接通次数</th>'
+'<th>已急救处理次数</th><th>未急救处理次数</th><th>告警次数</th></tr>';
}
function num(v,color){return '<td'+(color?' style="color:'+color+';"':'')+'>'+v+'</td>';}
function render(){
var u=document.getElementById('filterUnit').value;
var f=document.getElementById('filterFrom').value,t=document.getElementById('filterTo').value;
var inRange=!(t<'2026-09-01'||f>'2026-09-30');
var list=inRange?rows.filter(function(r){return !u||r.unit===u;}):[];
document.getElementById('thead').innerHTML=head();
document.getElementById('tbody').innerHTML=list.map(function(r){
var dimCell=dim==='dep'?r.dep:r.station;
return '<tr><td class="td-name">'+r.unit+'</td><td class="td-name">'+dimCell+'</td>'
+num(r.dev)+num(r.alloc,r.alloc<r.dev?'#D48806':'')+num(r.calls)+num(r.ok,'#389E0D')+num(r.miss,r.miss?'#CF1322':'')
+num(r.done,'#389E0D')+num(r.undone,r.undone?'#D48806':'')+num(r.alarm,r.alarm?'#CF1322':'')+'</tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">'+(inRange?'暂无符合条件的统计数据':'所选周期内无统计数据(演示数据仅覆盖 2026-09)')+'</td></tr>';
var sum=function(k){return list.reduce(function(s,r){return s+r[k];},0);};
var tv=list.length?'<tr><td class="td-name">合计</td><td class="td-name">'+list.length+' 个'+(dim==='dep'?'部门':'站点')+'</td>'
+num(sum('dev'))+num(sum('alloc'))+num(sum('calls'))+num(sum('ok'))+num(sum('miss'))+num(sum('done'))+num(sum('undone'))+num(sum('alarm'))+'</tr>':'';
document.getElementById('tfoot').innerHTML=tv;
var calls=sum('calls'),ok=sum('ok');
document.getElementById('cardDev').textContent=sum('dev');
document.getElementById('cardAlloc').textContent=sum('alloc');
document.getElementById('cardCall').textContent=calls;
document.getElementById('cardRate').textContent=calls?Math.round(ok/calls*100)+'%':'—';
document.getElementById('cardAlarm').textContent=sum('alarm');
document.getElementById('cardTitle').textContent=dim==='dep'?'部门呼叫统计':'站点呼叫统计';
document.getElementById('totalTip').textContent='共 '+list.length+' 行 · 统计周期 '+(f||'—')+' 至 '+(t||'—');
}
function switchDim(d){
dim=d;
document.getElementById('tabDep').classList.toggle('active',d==='dep');
document.getElementById('tabStation').classList.toggle('active',d==='station');
render();
}
function resetFilter(){document.getElementById('filterUnit').value='';document.getElementById('filterFrom').value='2026-09-01';document.getElementById('filterTo').value='2026-09-07';render();}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
@@ -0,0 +1,301 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超时未归告警 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;display:flex;align-items:center;gap:14px;}
.stat-card__icon{width:42px;height:42px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;flex-shrink:0;}
.stat-card__num{font-size:22px;font-weight:700;line-height:1.2;}
.stat-card__label{font-size:var(--font-xs);color:var(--text-secondary);margin-top:2px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1280px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:620px;max-height:82vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;font-size:var(--font-sm);color:var(--text-secondary);line-height:1.7;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:14px 0 10px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html" class="active">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="bed-building.html" class="sidebar-item">楼栋管理</a>
<a href="bed-dormitory.html" class="sidebar-item">宿舍管理</a>
<a href="bed-bed.html" class="sidebar-item">床位管理</a>
<a href="bed-device.html" class="sidebar-item">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item">设备分配</a>
<a href="bed-allocate-record.html" class="sidebar-item">分配记录</a>
<a href="bed-call-record.html" class="sidebar-item">呼叫记录</a>
<a href="bed-timeout-alarm.html" class="sidebar-item active">超时未归告警</a>
<a href="bed-stat.html" class="sidebar-item">统计汇总</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>告警设备</label>
<input class="filter-input" id="filterDev" placeholder="如:HC-FX-03" oninput="render()">
</div>
<div class="filter-item">
<label>宿舍名称</label>
<input class="filter-input" id="filterDorm" placeholder="关键字" oninput="render()">
</div>
<div class="filter-item">
<label>告警日期</label>
<input class="filter-input" type="date" id="filterFrom" value="2026-09-01" onchange="render()" style="min-width:130px;">
<span style="color:var(--text-placeholder);font-size:var(--font-xs);"></span>
<input class="filter-input" type="date" id="filterTo" value="2026-09-07" onchange="render()" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="render();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
升级规则:呼叫 10 分钟未接听、呼入已接通但 30 分钟未派单处置、设备心跳丢失超过 15 分钟,均生成本页告警;「待处理」告警按值班表推送当班人员。
</div>
<!-- [交互] 统计卡片 -->
<div class="stat-row">
<div class="stat-card">
<div class="stat-card__icon" style="background:#E6F7FF;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 2a5.5 5.5 0 00-5.5 5.5v3L3 14h14l-1.5-3.5v-3A5.5 5.5 0 0010 2z" stroke="#1890FF" stroke-width="1.4" fill="none"/><path d="M8 16a2 2 0 004 0" stroke="#1890FF" stroke-width="1.4" fill="none"/></svg></div>
<div><div class="stat-card__num" id="statTotal">-</div><div class="stat-card__label">本期告警总数</div></div>
</div>
<div class="stat-card">
<div class="stat-card__icon" style="background:#FFF1F0;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="7.5" stroke="#FF4D4F" stroke-width="1.4" fill="none"/><path d="M10 6.5V11" stroke="#FF4D4F" stroke-width="1.6" stroke-linecap="round"/><circle cx="10" cy="13.6" r="1" fill="#FF4D4F"/></svg></div>
<div><div class="stat-card__num" style="color:var(--danger);" id="statPending">-</div><div class="stat-card__label">待处理</div></div>
</div>
<div class="stat-card">
<div class="stat-card__icon" style="background:#F6FFED;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="7.5" stroke="#52C41A" stroke-width="1.4" fill="none"/><path d="M6.5 10.5l2.3 2.3 4.7-5" stroke="#52C41A" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg></div>
<div><div class="stat-card__num" style="color:var(--success);" id="statDone">-</div><div class="stat-card__label">已处理</div></div>
</div>
<div class="stat-card">
<div class="stat-card__icon" style="background:#FFFBE6;"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3.5l7 12H3l7-12z" stroke="#FAAD14" stroke-width="1.4" fill="none" stroke-linejoin="round"/><path d="M10 8.5v3" stroke="#FAAD14" stroke-width="1.6" stroke-linecap="round"/><circle cx="10" cy="13.6" r="1" fill="#FAAD14"/></svg></div>
<div><div class="stat-card__num" style="color:var(--warning);" id="statDev">-</div><div class="stat-card__label">设备类告警</div></div>
</div>
</div>
<!-- [交互] 告警列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">超时告警记录</span>
<button class="btn btn-default" onclick="showToast('告警记录已导出')">导出</button>
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th style="min-width:140px;">告警时间</th><th style="min-width:130px;">告警类型</th>
<th style="min-width:300px;">告警原因</th><th>告警设备</th>
<th style="min-width:220px;">宿舍名称</th><th>告警状态</th><th style="width:110px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 告警详情 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">告警详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<!-- [交互] 标记处理确认 -->
<div class="modal-mask" id="handleMask">
<div class="modal">
<div class="modal__header"><span>标记处理</span><button class="modal__close" onclick="closeHandle()">&times;</button></div>
<div class="modal__body" id="handleBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeHandle()">取消</button>
<button class="btn btn-primary" onclick="confirmHandle()">确认已处理</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 告警数据:与呼叫记录/设备列表跨页勾稽——徐立成未接通升级、杨慧敏超时未派单、HC-FX-03 与 HD-XF-01 故障离线 */
var alarms=[
{time:'2026-09-07 06:14',type:'呼叫超时未接听',dev:'HC-TY-01',dorm:'桐园倒班宿舍1号楼 · 301室',status:'待处理',
reason:'床位呼叫(HC-TY-01)发起后 10 分钟内值班终端未接听',rel:'关联呼叫:2026-09-07 06:02 徐立成(CQ022317)',
plan:'值班员现场核实中;计入当班坐席未接听考核。'},
{time:'2026-09-06 20:16',type:'呼叫超时未接听',dev:'HC-MZ-01',dorm:'麻子山倒班宿舍楼 · 101室',status:'已处理',
reason:'值班终端(HD-MZ-01)呼出语音呼叫 10 分钟内被呼侧未接听',rel:'关联呼叫:2026-09-06 20:05 HD-MZ-01 → HC-MZ-01',
plan:'值班员改走宿舍敲门查房,员工在巡检途中未携带房卡,已电话确认无异常。',handler:'麻子山医疗点 安伟',handleTime:'2026-09-06 21:02'},
{time:'2026-09-04 18:51',type:'呼叫超时未接听',dev:'HC-XF-02',dorm:'西峰基地综合宿舍楼 · 302室',status:'已处理',
reason:'值班终端(HD-XF-01)呼出语音呼叫 10 分钟内被呼侧未接听',rel:'关联呼叫:2026-09-04 18:40 HD-XF-01 → HC-XF-02',
plan:'被呼员工外出就餐,返回后终端回拨确认无需求;HD-XF-01 后续报修。',handler:'西峰医疗点 马文秀',handleTime:'2026-09-04 19:26'},
{time:'2026-09-04 09:20',type:'设备离线告警',dev:'HC-FX-03',dorm:'樊学倒班宿舍1号楼 · 102室',status:'已处理',
reason:'呼叫设备心跳丢失超过 15 分钟(设备状态已置故障)',rel:'关联设备:HC-FX-03(102-1 曲志明床位)',
plan:'厂家远程诊断判定线路老化,已派单维修,期间 102 室临时启用 HC-FX-04 房间面板代呼。',handler:'CQ中控医疗 工单 4127',handleTime:'2026-09-04 16:40'},
{time:'2026-09-02 23:47',type:'设备离线告警',dev:'HD-XF-01',dorm:'西峰基地值班室',status:'待处理',
reason:'值班终端离线超过 15 分钟,呼入转接至备用座机',rel:'关联设备:HD-XF-01(西峰基地值班室)',
plan:'疑似电源适配器故障,备件明日到站,期间值班室使用备用座机接管呼叫。'},
{time:'2026-09-03 20:08',type:'救助处置超时',dev:'HC-MZ-01',dorm:'麻子山倒班宿舍楼 · 101室',status:'已处理',
reason:'呼入已接通但 30 分钟内未派单到场处置(杨慧敏踝扭伤)',rel:'关联呼叫:2026-09-03 19:36 杨慧敏(CQ045130)',
plan:'电话指导制动抬高后,夜班医生 21:15 到场评估为轻度扭伤,开单随访。',handler:'麻子山医疗点 安伟',handleTime:'2026-09-03 21:30'}
];
var typeBadge={'呼叫超时未接听':'badge--danger','设备离线告警':'badge--warning','救助处置超时':'badge--info'};
var stBadge={'待处理':'badge--danger','已处理':'badge--success'};
function day(dt){return dt.slice(0,10);}
function render(){
var kd=document.getElementById('filterDev').value.trim().toUpperCase();
var km=document.getElementById('filterDorm').value.trim();
var f=document.getElementById('filterFrom').value,t=document.getElementById('filterTo').value;
var list=alarms.filter(function(a){return a.dev.toUpperCase().indexOf(kd)>=0&&a.dorm.indexOf(km)>=0&&day(a.time)>=f&&day(a.time)<=t;});
document.getElementById('tbody').innerHTML=list.map(function(a){
var i=alarms.indexOf(a);
return '<tr><td>'+a.time+'</td><td><span class="badge '+typeBadge[a.type]+'">'+a.type+'</span></td>'
+'<td style="text-align:left;white-space:normal;max-width:340px;">'+a.reason+'</td>'
+'<td style="font-family:monospace;">'+a.dev+'</td><td>'+a.dorm+'</td>'
+'<td><span class="badge '+stBadge[a.status]+'">'+a.status+'</span></td>'
+'<td><button class="btn btn-link" onclick="openDetail('+i+')">详情</button>'
+(a.status==='待处理'?'<button class="btn btn-link" onclick="openHandle('+i+')">标记处理</button>':'')+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="7">没有符合条件的告警记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 条告警 · 待处理 '+list.filter(function(a){return a.status==='待处理';}).length+' 条';
var all=alarms.filter(function(a){return day(a.time)>=f&&day(a.time)<=t;});
document.getElementById('statTotal').textContent=all.length;
document.getElementById('statPending').textContent=all.filter(function(a){return a.status==='待处理';}).length;
document.getElementById('statDone').textContent=all.filter(function(a){return a.status==='已处理';}).length;
document.getElementById('statDev').textContent=all.filter(function(a){return a.type==='设备离线告警';}).length;
}
function resetFilter(){['filterDev','filterDorm'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterFrom').value='2026-09-01';document.getElementById('filterTo').value='2026-09-07';render();}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var a=alarms[i];
document.getElementById('detailTitle').textContent='告警详情 · '+a.dev;
document.getElementById('detailBody').innerHTML=
'<div class="form-section">告警信息</div><div class="detail-grid">'
+di('告警时间',a.time)+di('告警类型',a.type)
+di('告警设备',a.dev)+di('宿舍名称',a.dorm)
+di('告警状态',a.status)+di('关联信息',a.rel)
+di('告警原因',a.reason,true)
+'</div><div class="form-section">处置情况</div><div class="detail-grid">'
+di('处置方案',a.plan,true)
+di('处理人',a.handler||'—')+di('处理时间',a.handleTime||'—')
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
var handleIdx=-1;
function openHandle(i){
handleIdx=i;var a=alarms[i];
document.getElementById('handleBody').innerHTML='确认将告警 <b style="color:var(--text-primary);">'+a.dev+' · '+a.type+'</b>('+a.time+')标记为已处理?标记后将不再计入值班考核,可在详情中补充处置记录。';
document.getElementById('handleMask').classList.add('open');
}
function closeHandle(){document.getElementById('handleMask').classList.remove('open');}
function confirmHandle(){
var a=alarms[handleIdx];
a.status='已处理';a.handler='当前值班坐席';a.handleTime='2026-09-07 09:30';
closeHandle();render();showToast('已标记处理,联动解除值班考核挂账');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
@@ -0,0 +1,396 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分配统计 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 概览卡 */
.stat-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;display:flex;align-items:center;gap:14px;}
.stat-card__icon{width:44px;height:44px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}
.stat-card__icon--blue{background:var(--primary-light);}
.stat-card__icon--green{background:#F6FFED;}
.stat-card__icon--orange{background:#FFFBE6;}
.stat-card__icon--purple{background:#F9F0FF;}
.stat-card__label{font-size:var(--font-xs);color:var(--text-secondary);}
.stat-card__value{font-size:24px;font-weight:600;color:var(--text-primary);line-height:1.3;font-variant-numeric:tabular-nums;}
.stat-card__extra{font-size:var(--font-xs);color:var(--text-placeholder);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 表格 */
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.td-num{font-variant-numeric:tabular-nums;}
/* 徽标 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 分页 */
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-680px;bottom:0;width:640px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.drawer__toolbar{display:flex;align-items:center;gap:8px;margin-bottom:14px;}
.drawer__toolbar .grow{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 内嵌小表格 */
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;}
.mini-table td{white-space:normal;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item active">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 概览卡:随筛选结果实时计算 -->
<div class="stat-cards" id="statCards"></div>
<!-- [交互] 筛选栏:医疗点所属单位→医疗点级联 -->
<div class="filter-bar">
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onUnit()" style="min-width:150px;">
<option value="">全部单位</option>
</select>
</div>
<div class="filter-item">
<label>医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:180px;">
<option value="">全部医疗点</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<span class="action-bar__hint">医护人员数量来自「系统管理 → 用户管理」的医护角色统计,本页只读汇总;点击「已分配员工数量」可下钻名册</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th>序号</th><th>医疗点所属单位</th><th>医疗点名称</th><th>医疗点地址</th><th>合作医院</th><th>设立时间</th><th>医护人员数量</th><th>已分配员工数量</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 已分配员工名册下钻抽屉 -->
<div class="drawer-mask" id="rosterMask" onclick="closeDrawer()"></div>
<div class="drawer" id="rosterDrawer">
<div class="drawer__header"><span id="rosterTitle">已分配员工名册</span><button class="drawer__close" onclick="closeDrawer()">×</button></div>
<div class="drawer__body">
<!-- [交互] 名册按姓名/工号即时过滤 -->
<div class="drawer__toolbar">
<div class="filter-item"><label>姓名</label>
<input class="filter-input" id="rsName" placeholder="请输入姓名" style="min-width:100px;" oninput="renderRoster()"></div>
<div class="filter-item"><label>工号</label>
<input class="filter-input" id="rsCode" placeholder="请输入工号" style="min-width:100px;" oninput="renderRoster()"></div>
<span class="grow" id="rosterHint"></span>
</div>
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>单位名称</th><th>部门名称</th><th>姓名</th><th>性别</th><th>年龄</th><th>员工工号</th></tr></thead>
<tbody id="rosterBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer()">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏,名册与「员工分配」页保持一致) ===== */
var statPoints=[
{unit:'采油一厂',name:'樊学作业区医疗点',addr:'CQ油田采油一厂樊学作业区基地东侧',hospital:'CQ油田总医院',found:'2021-06-18',staff:3,
roster:[{unit:'采油一厂',dept:'樊学作业区',name:'石维国',sex:'男',age:46,code:'CQ021578'},
{unit:'采油一厂',dept:'樊学作业区',name:'李桂芳',sex:'女',age:38,code:'CQ032144'}]},
{unit:'采油二厂',name:'姬圪台作业区医疗点',addr:'采油二厂姬圪台联合站生活区1号',hospital:'CQ油田总医院',found:'2020-09-02',staff:2,
roster:[{unit:'采油二厂',dept:'姬圪台联合站',name:'曲志明',sex:'男',age:42,code:'CQ019203'},
{unit:'采油二厂',dept:'姬圪台联合站',name:'张秀兰',sex:'女',age:45,code:'CQ027710'}]},
{unit:'第四采油厂',name:'西峰基地医疗点',addr:'第四采油厂西峰基地综合服务区2层',hospital:'第四采油厂总医院',found:'2019-04-26',staff:4,
roster:[{unit:'第四采油厂',dept:'基地管理处',name:'高建军',sex:'男',age:50,code:'CQ010564'},
{unit:'第四采油厂',dept:'健康管理部',name:'闫婷',sex:'女',age:34,code:'CQ041822'}]},
{unit:'采气一厂',name:'靖边气田医疗点',addr:'采气一厂靖边净化厂生活小区旁',hospital:'CQ油田总医院',found:'2022-03-15',staff:3,
roster:[{unit:'采气一厂',dept:'靖边净化厂',name:'刘玉梅',sex:'女',age:40,code:'CQ036441'},
{unit:'采气一厂',dept:'靖边净化厂',name:'呼建国',sex:'男',age:36,code:'CQ029905'}]},
{unit:'采油二厂',name:'桐园作业区医疗点',addr:'采油二厂桐园作业区营地西侧',hospital:'环县油田医院',found:'2021-11-08',staff:2,
roster:[{unit:'采油二厂',dept:'桐园作业区',name:'赵万福',sex:'男',age:39,code:'CQ033096'}]},
{unit:'采油一厂',name:'安塞采油队长住点医疗室',addr:'采油一厂安塞基地综合公寓1层',hospital:'吴起油田医院',found:'2023-05-20',staff:2,
roster:[{unit:'采油一厂',dept:'生产技术部',name:'白瑞',sex:'男',age:31,code:'CQ044871'}]},
{unit:'采油一厂',name:'姬塬作业区医疗点',addr:'采油三厂筹备组姬塬先导试验区板房区',hospital:null,found:'2024-02-27',staff:1,
roster:[{unit:'采油一厂',dept:'樊学作业区',name:'杨慧敏',sex:'女',age:33,code:'CQ045130'}]},
{unit:'采气一厂',name:'乌审旗采气区医疗点',addr:'采气一厂苏里格南作业区营地',hospital:'庆阳市人民医院',found:'2020-12-11',staff:2,
roster:[{unit:'采气一厂',dept:'苏里格南作业区',name:'徐立成',sex:'男',age:44,code:'CQ022317'}]}
];
/* ===== 单位→医疗点级联(由统计数据生成,保证单一数据源) ===== */
function initUnitSel(){
var sel=document.getElementById('filterUnit');
var units=[];
statPoints.forEach(function(p){if(units.indexOf(p.unit)===-1)units.push(p.unit);});
units.forEach(function(u){var o=document.createElement('option');o.textContent=u;sel.appendChild(o);});
}
function onUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){statPoints.forEach(function(p){if(p.unit===unit){var o=document.createElement('option');o.textContent=p.name;sel.appendChild(o);}});}
}
/* ===== 筛选与渲染 ===== */
var pageSize=10,currentPage=1;
function currentData(){
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
return statPoints.filter(function(p){return (!unit||p.unit===unit)&&(!point||p.name===point);});
}
function renderStats(list){
var staff=0,alloc=0;
list.forEach(function(p){staff+=p.staff;alloc+=p.roster.length;});
var avg=list.length?(alloc/list.length).toFixed(1):'0.0';
document.getElementById('statCards').innerHTML=
'<div class="stat-card"><div class="stat-card__icon stat-card__icon--blue">🏥</div><div><div class="stat-card__label">医疗点数量</div><div class="stat-card__value">'+list.length+'</div><div class="stat-card__extra">当前筛选范围内</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--green">👩‍⚕️</div><div><div class="stat-card__label">医护人员总数</div><div class="stat-card__value">'+staff+'</div><div class="stat-card__extra">来自系统管理医护角色统计</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--orange">👥</div><div><div class="stat-card__label">已分配员工总数</div><div class="stat-card__value">'+alloc+'</div><div class="stat-card__extra">在「员工分配」页维护</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--purple">📊</div><div><div class="stat-card__label">平均每点分配人数</div><div class="stat-card__value">'+avg+'</div><div class="stat-card__extra">已分配员工总数 ÷ 医疗点数</div></div></div>';
}
function renderTable(){
var list=currentData();
renderStats(list);
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="9" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(p,i){
var hosp=p.hospital?p.hospital:'<span class="badge badge--muted">未指定</span>';
var allocCls=p.roster.length?'btn-link':'btn-link';
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+p.unit+'</td>'
+'<td style="text-align:left;font-weight:600;">'+p.name+'</td>'
+'<td style="text-align:left;max-width:260px;overflow:hidden;text-overflow:ellipsis;">'+p.addr+'</td>'
+'<td>'+hosp+'</td>'
+'<td>'+p.found+'</td>'
+'<td class="td-num">'+p.staff+'</td>'
+'<td><button class="btn '+allocCls+'" onclick="openRoster(\''+p.name+'\')">'+p.roster.length+'</button></td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openRoster(\''+p.name+'\')">查看名册</button></td>'
+'</tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){document.getElementById('filterUnit').value='';onUnit();applyFilter();}
function turnPage(d){
var list=currentData();
var totalPages=Math.ceil(list.length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 名册下钻抽屉 ===== */
var curRoster=null;
function openRoster(pointName){
curRoster=statPoints.find(function(x){return x.name===pointName;});
document.getElementById('rosterTitle').textContent='已分配员工名册 · '+curRoster.name;
document.getElementById('rsName').value='';
document.getElementById('rsCode').value='';
renderRoster();
document.getElementById('rosterMask').classList.add('open');
document.getElementById('rosterDrawer').classList.add('open');
}
function renderRoster(){
if(!curRoster)return;
var name=document.getElementById('rsName').value.trim();
var code=document.getElementById('rsCode').value.trim();
var list=curRoster.roster.filter(function(e){
return (!name||e.name.indexOf(name)!==-1)&&(!code||e.code.toLowerCase().indexOf(code.toLowerCase())!==-1);
});
var filteredTip=(name||code)?(' · 按条件匹配 '+list.length+'/'+curRoster.roster.length+' 人'):'';
document.getElementById('rosterHint').textContent='共 '+curRoster.roster.length+' 人 · 分配至「'+curRoster.unit+'/'+curRoster.name+'」'+filteredTip;
document.getElementById('rosterBody').innerHTML=list.length?list.map(function(e){
return '<tr><td>'+e.unit+'</td><td>'+e.dept+'</td><td style="font-weight:600;">'+e.name+'</td><td>'+e.sex+'</td><td class="td-num">'+e.age+'</td><td>'+e.code+'</td></tr>';
}).join(''):'<tr><td colspan="6" class="td-empty">'+((name||code)?'无匹配员工':'该医疗点暂未分配员工')+'</td></tr>';
}
function closeDrawer(){
document.getElementById('rosterMask').classList.remove('open');
document.getElementById('rosterDrawer').classList.remove('open');
}
/* ===== 通用 ===== */
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
initUnitSel();
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,585 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分配管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 状态Tab */
.tabs{display:flex;gap:4px;background:#fff;padding:6px 12px 0;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.tab{padding:8px 20px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;user-select:none;}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab__count{display:inline-block;min-width:18px;padding:0 5px;height:18px;line-height:18px;border-radius:9px;background:#F0F0F0;color:var(--text-secondary);font-size:var(--font-xs);margin-left:4px;text-align:center;}
.tab.active .tab__count{background:var(--primary-light);color:var(--primary);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-top:1px solid #F0F0F0;}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
/* 单位部门级联面板 */
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:12px 24px;background:#fff;border-top:1px solid #F0F0F0;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 表格 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
/* 徽标 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 分页 */
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:20px;line-height:1.6;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.drop-zone{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
</style>
</head>
<body>
<!-- 顶部导航栏 -->
<div class="top-bar">
<div class="top-bar__logo"><svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item active">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 状态Tab:已分配/未分配 -->
<div class="tabs">
<div class="tab active" id="tabAlloc" onclick="switchTab(true)">已分配<span class="tab__count" id="cntAlloc">0</span></div>
<div class="tab" id="tabUnalloc" onclick="switchTab(false)">未分配<span class="tab__count" id="cntUnalloc">0</span></div>
</div>
<!-- [交互] 筛选栏:单位部门级联 + 姓名/工号 + 医疗点(仅已分配) -->
<div class="filter-bar">
<div class="filter-item">
<label>员工所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" data-unit="" onclick="selectUnit(this,'全部单位/部门','')">全部单位/部门</div>
<div class="org-item" data-unit="采油一厂" onclick="selectUnit(this,'采油一厂','采油一厂')">采油一厂</div>
<div class="org-item" data-unit="采油二厂" onclick="selectUnit(this,'采油二厂','采油二厂')">采油二厂</div>
<div class="org-item" data-unit="第四采油厂" onclick="selectUnit(this,'第四采油厂','第四采油厂')">第四采油厂</div>
<div class="org-item" data-unit="采气一厂" onclick="selectUnit(this,'采气一厂','采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" data-dept="" onclick="selectDept(this,'全部部门','')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>员工姓名</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>员工工号</label>
<input class="filter-input" id="filterCode" placeholder="请输入工号" style="min-width:100px;">
</div>
<div class="filter-item" id="mfUnitWrap">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterMedUnit" onchange="onMedUnit()" style="min-width:110px;">
<option value="">全部</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item" id="mfPointWrap">
<label>分配医疗点</label>
<select class="filter-select" id="filterMedPoint" style="min-width:130px;">
<option value="">全部医疗点</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏:按钮随Tab切换 -->
<div class="action-bar">
<span id="allocBtns">
<button class="btn btn-default" onclick="openImport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 10V2M4.5 4.5L7 2l2.5 2.5M2 11h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导入分配记录
</button>
</span>
<span id="unallocBtns" style="display:none;">
<button class="btn btn-primary" onclick="batchAllocate()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7h10M7 2v10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>分配医疗点
</button>
<button class="btn btn-default" onclick="openImport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 10V2M4.5 4.5L7 2l2.5 2.5M2 11h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入
</button>
</span>
<span class="action-bar__hint" id="tabHint">已分配员工在 PAD 端按所属医疗点接收任务</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 分配医疗点弹窗(单位→医疗点级联) -->
<div class="modal-mask" id="allocMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M4 10.5l4 4 8-9" stroke="#1890FF" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg> <span id="allocTitle">分配医疗点</span></div>
<div class="modal__text" style="margin-bottom:14px;" id="allocSub"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 医疗点所属单位</span>
<select class="form-control" id="aUnit" onchange="onAllocUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 分配医疗点</span>
<select class="form-control" id="aPoint"><option value="">请先选择医疗点所属单位</option></select></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('allocMask')">取消</button>
<button class="btn btn-primary" onclick="confirmAllocate()">确认分配</button>
</div>
</div>
</div>
<!-- [交互] 批量导入弹窗 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 14V3M6.5 6.5L10 3l3.5 3.5M3 16h14" stroke="#1890FF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg> 批量导入分配</div>
<div class="form-item" style="margin-top:14px;"><span class="form-label"><span class="req">*</span> 医疗点所属单位</span>
<select class="form-control" id="iUnit" onchange="onImportUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 分配医疗点</span>
<select class="form-control" id="iPoint"><option value="">请先选择单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 上传文件(员工工号清单)</span>
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>员工分配导入模板.xlsx</div></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('importMask')">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 取消分配确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 取消分配确认</div>
<div class="modal__text" id="delText">确认取消分配?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmCancel()">确认</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
/* 已分配:unit/dept=员工所属,mUnit/mPoint=分配去向 */
var alloc=[
{id:1,name:'石维国',sex:'男',age:46,code:'CQ021578',tel:'139****2051',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'樊学作业区医疗点',date:'2026-03-02'},
{id:2,name:'李桂芳',sex:'女',age:38,code:'CQ032144',tel:'138****7742',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'樊学作业区医疗点',date:'2026-03-02'},
{id:3,name:'白瑞',sex:'男',age:31,code:'CQ044871',tel:'155****3316',unit:'采油一厂',dept:'生产技术部',mUnit:'采油一厂',mPoint:'安塞采油队长住点医疗室',date:'2026-04-15'},
{id:4,name:'曲志明',sex:'男',age:42,code:'CQ019203',tel:'137****8890',unit:'采油二厂',dept:'姬圪台联合站',mUnit:'采油二厂',mPoint:'姬圪台作业区医疗点',date:'2026-01-08'},
{id:5,name:'张秀兰',sex:'女',age:45,code:'CQ027710',tel:'136****2248',unit:'采油二厂',dept:'姬圪台联合站',mUnit:'采油二厂',mPoint:'姬圪台作业区医疗点',date:'2026-01-08'},
{id:6,name:'赵万福',sex:'男',age:39,code:'CQ033096',tel:'150****6127',unit:'采油二厂',dept:'桐园作业区',mUnit:'采油二厂',mPoint:'桐园作业区医疗点',date:'2026-02-20'},
{id:7,name:'高建军',sex:'男',age:50,code:'CQ010564',tel:'139****0086',unit:'第四采油厂',dept:'基地管理处',mUnit:'第四采油厂',mPoint:'西峰基地医疗点',date:'2025-12-01'},
{id:8,name:'闫婷',sex:'女',age:34,code:'CQ041822',tel:'186****5527',unit:'第四采油厂',dept:'健康管理部',mUnit:'第四采油厂',mPoint:'西峰基地医疗点',date:'2026-01-12'},
{id:9,name:'刘玉梅',sex:'女',age:40,code:'CQ036441',tel:'135****7719',unit:'采气一厂',dept:'靖边净化厂',mUnit:'采气一厂',mPoint:'靖边气田医疗点',date:'2026-03-25'},
{id:10,name:'呼建国',sex:'男',age:36,code:'CQ029905',tel:'147****9003',unit:'采气一厂',dept:'靖边净化厂',mUnit:'采气一厂',mPoint:'靖边气田医疗点',date:'2026-03-25'},
{id:11,name:'徐立成',sex:'男',age:44,code:'CQ022317',tel:'133****4860',unit:'采气一厂',dept:'苏里格南作业区',mUnit:'采气一厂',mPoint:'乌审旗采气区医疗点',date:'2026-05-10'},
{id:12,name:'杨慧敏',sex:'女',age:33,code:'CQ045130',tel:'188****7214',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'姬塬作业区医疗点',date:'2026-06-18'}
];
/* 未分配:可被分配的驻点后备医护与协管人员 */
var unalloc=[
{id:101,name:'周瑞琪',sex:'女',age:29,code:'CQ049218',tel:'151****3072',unit:'采油一厂',dept:'生产技术部'},
{id:102,name:'田震',sex:'男',age:35,code:'CQ037744',tel:'139****5218',unit:'采油一厂',dept:'樊学作业区'},
{id:103,name:'韩梅',sex:'女',age:41,code:'CQ026109',tel:'137****0833',unit:'采油二厂',dept:'安全环保科'},
{id:104,name:'冯保山',sex:'男',age:48,code:'CQ015523',tel:'135****6174',unit:'采油二厂',dept:'桐园作业区'},
{id:105,name:'曹雪芹',sex:'女',age:30,code:'CQ047831',tel:'186****2940',unit:'第四采油厂',dept:'健康管理部'},
{id:106,name:'罗建平',sex:'男',age:37,code:'CQ033485',tel:'150****8357',unit:'第四采油厂',dept:'基地管理处'},
{id:107,name:'梁小燕',sex:'女',age:32,code:'CQ042296',tel:'188****1029',unit:'采气一厂',dept:'生产技术部'},
{id:108,name:'邓成',sex:'男',age:43,code:'CQ024778',tel:'133****9481',unit:'采气一厂',dept:'苏里格南作业区'},
{id:109,name:'苏文斌',sex:'男',age:27,code:'CQ051027',tel:'155****6703',unit:'采油一厂',dept:'工会办公室'}
];
var nextId=200,curAllocTab=true,allocTargets=[],allocRealloc=false,cancelId=null;
/* ===== Tab 切换 ===== */
function switchTab(toAlloc){
curAllocTab=toAlloc;
document.getElementById('tabAlloc').classList.toggle('active',toAlloc);
document.getElementById('tabUnalloc').classList.toggle('active',!toAlloc);
document.getElementById('mfUnitWrap').style.display=toAlloc?'':'none';
document.getElementById('mfPointWrap').style.display=toAlloc?'':'none';
document.getElementById('allocBtns').style.display=toAlloc?'':'none';
document.getElementById('unallocBtns').style.display=toAlloc?'none':'';
document.getElementById('tabHint').textContent=toAlloc?'已分配员工可「重新分配」调整驻点,或通过「导入分配记录」批量补录':'勾选未分配员工后点击「分配医疗点」,或批量导入工号清单';
currentPage=1;renderTable();
}
/* ===== 单位部门级联 ===== */
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName,unitCode){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitCode;
var html='<div class="org-col__title">部门</div><div class="org-item active" data-dept="" onclick="selectDept(this,\'全部部门\',\'\')">全部部门</div>';
(deptMap[unitCode]||[]).forEach(function(d){
html+='<div class="org-item" data-dept="'+d+'" onclick="selectDept(this,\''+d+'\',\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName,deptCode){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptCode;
var label=(selUnit||'全部单位')+(selDept?' / '+selDept:' / 全部部门');
document.getElementById('orgTrigger').textContent=label;
}
/* ===== 医疗点级联筛选 ===== */
function onMedUnit(){
var unit=document.getElementById('filterMedUnit').value;
var sel=document.getElementById('filterMedPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
/* ===== 列表渲染 ===== */
var pageSize=10,currentPage=1;
function currentData(){
var name=document.getElementById('filterName').value.trim();
var code=document.getElementById('filterCode').value.trim();
var mUnit=document.getElementById('filterMedUnit').value;
var mPoint=document.getElementById('filterMedPoint').value;
var list=(curAllocTab?alloc:unalloc).filter(function(p){
return (!name||p.name.indexOf(name)!==-1)
&&(!code||p.code.toLowerCase().indexOf(code.toLowerCase())!==-1)
&&(!selUnit||p.unit===selUnit)&&(!selDept||p.dept===selDept)
&&(!curAllocTab||((!mUnit||p.mUnit===mUnit)&&(!mPoint||p.mPoint===mPoint)));
});
return list;
}
function renderHead(){
var h='<tr><th style="width:40px"><input type="checkbox" onchange="toggleAll(this)"></th><th>序号</th><th>员工所属单位</th><th>员工所属部门</th><th>员工姓名</th><th>性别</th><th>年龄</th><th>员工工号</th><th>联系电话</th>';
h+=curAllocTab?'<th>医疗点所属单位</th><th>分配医疗点</th><th>分配日期</th><th>操作</th></tr>':'<th>操作</th></tr>';
document.getElementById('tableHead').innerHTML=h;
}
function renderTable(){
renderHead();
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
document.getElementById('cntAlloc').textContent=alloc.length;
document.getElementById('cntUnalloc').textContent=unalloc.length;
var colspan=curAllocTab?13:10;
if(!pageData.length){document.getElementById('tableBody').innerHTML='<tr><td colspan="'+colspan+'" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(p,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+p.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+p.unit+'</td><td>'+p.dept+'</td>'
+'<td style="font-weight:600;">'+p.name+'</td>'
+'<td><span class="badge '+(p.sex==='男'?'badge--muted':'badge--muted')+'">'+p.sex+'</span></td>'
+'<td>'+p.age+'</td><td>'+p.code+'</td><td>'+p.tel+'</td>';
if(curAllocTab){
html+='<td>'+p.mUnit+'</td><td>'+p.mPoint+'</td><td>'+p.date+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openAllocate(['+p.id+'],true)">重新分配</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askCancel('+p.id+')">取消分配</button></td>';
}else{
html+='<td class="td-actions"><button class="btn btn-link" onclick="openAllocate(['+p.id+'])">分配医疗点</button></td>';
}
html+='</tr>';
});
document.getElementById('tableBody').innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterCode').value='';
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.querySelectorAll('#unitCol .org-item').forEach(function(i,x){i.classList.toggle('active',x===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'全部部门\',\'\')">全部部门</div>';
document.getElementById('filterMedUnit').value='';
onMedUnit();
applyFilter();
}
function turnPage(d){
var list=currentData();
var totalPages=Math.ceil(list.length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
/* ===== 分配 ===== */
function checkedIds(){return [].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return +c.value;});}
function batchAllocate(){
var ids=checkedIds();
if(!ids.length){showToast('请先勾选未分配员工');return;}
openAllocate(ids);
}
function openAllocate(ids,realloc){
allocTargets=ids;allocRealloc=!!realloc;
var src=realloc?alloc:unalloc;
document.getElementById('allocTitle').textContent=realloc
?(ids.length>1?('批量重新分配('+ids.length+'人)'):'重新分配')
:(ids.length>1?('批量分配医疗点('+ids.length+'人)'):'分配医疗点');
var names=ids.map(function(id){return src.find(function(x){return x.id===id;}).name;});
document.getElementById('allocSub').textContent='为员工「'+names.slice(0,3).join('、')+(names.length>3?' 等 '+names.length+' 人':'')+'」选择'+(realloc?'新的':'')+'驻点医疗点';
document.getElementById('aUnit').value='';
document.getElementById('aPoint').innerHTML='<option value="">请先选择医疗点所属单位</option>';
document.getElementById('allocMask').classList.add('open');
}
function onAllocUnit(){
var unit=document.getElementById('aUnit').value;
var sel=document.getElementById('aPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function confirmAllocate(){
var unit=document.getElementById('aUnit').value;
var point=document.getElementById('aPoint').value;
if(!unit){showToast('请选择医疗点所属单位');return;}
if(!point){showToast('请选择分配医疗点');return;}
allocTargets.forEach(function(id){
if(allocRealloc){
var q=alloc.find(function(x){return x.id===id;});
q.mUnit=unit;q.mPoint=point;q.date='2026-09-07';
}else{
var idx=unalloc.findIndex(function(x){return x.id===id;});
var p=unalloc.splice(idx,1)[0];
p.mUnit=unit;p.mPoint=point;p.date='2026-09-07';
alloc.push(p);
}
});
closeModal('allocMask');renderTable();
showToast('已为 '+allocTargets.length+' 人'+(allocRealloc?'重新':'')+'分配「'+point+'」');
}
function askCancel(id){
cancelId=id;
var p=alloc.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认取消员工「'+p.name+'」在「'+p.mPoint+'」的分配?取消后其 PAD 端任务将转回医疗点驻点医护。';
document.getElementById('delMask').classList.add('open');
}
function confirmCancel(){
var idx=alloc.findIndex(function(x){return x.id===cancelId;});
var p=alloc.splice(idx,1)[0];
delete p.mUnit;delete p.mPoint;delete p.date;
unalloc.push(p);
closeModal('delMask');renderTable();
showToast('员工「'+p.name+'」已取消分配');
}
/* ===== 批量导入 ===== */
function openImport(){
document.getElementById('iUnit').value='';
document.getElementById('iPoint').innerHTML='<option value="">请先选择单位</option>';
document.getElementById('importMask').classList.add('open');
}
function onImportUnit(){
var unit=document.getElementById('iUnit').value;
var sel=document.getElementById('iPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function doImport(){
if(!document.getElementById('iUnit').value){showToast('请选择医疗点所属单位');return;}
if(!document.getElementById('iPoint').value){showToast('请选择分配医疗点');return;}
closeModal('importMask');
showToast('导入任务已提交,完成后将推送站内通知');
}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,940 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>救护车列表 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 表格 */
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1600px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.td-num{font-variant-numeric:tabular-nums;}
/* 徽标 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--info{background:var(--primary-light);color:#096DD9;border:1px solid #91D5FF;}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 分页 */
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 表单 */
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-item--full{grid-column:1/-1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.radio-group{display:flex;gap:20px;height:32px;align-items:center;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
/* 照片上传占位 */
.photo-row{display:flex;gap:8px;flex-wrap:wrap;}
.photo-tile{width:64px;height:64px;border-radius:var(--radius-sm);background:#F0F8FF;border:1px solid #91D5FF;display:flex;align-items:center;justify-content:center;font-size:22px;cursor:pointer;}
.photo-add{width:64px;height:64px;border-radius:var(--radius-sm);border:1px dashed var(--border);display:flex;align-items:center;justify-content:center;color:var(--text-placeholder);font-size:20px;cursor:pointer;background:#FAFAFA;}
.photo-add:hover{border-color:var(--primary);color:var(--primary);}
.photo-tip{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:4px;}
/* 详情描述列表 */
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:130px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
/* 弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:20px;line-height:1.6;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;}
.drop-zone{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
/* 数据管理(出勤记录)所需样式 */
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
.radio--disabled{color:var(--text-placeholder);}
.radio--disabled input{cursor:not-allowed;}
.radio .opt-tip{font-size:var(--font-xs);color:var(--text-placeholder);}
.radio-wrap{flex-wrap:wrap;height:auto;align-items:flex-start;gap:8px 20px;}
.chips{display:flex;gap:8px;flex-wrap:wrap;}
.chip{padding:5px 14px;border:1px solid var(--border);border-radius:16px;font-size:var(--font-xs);color:var(--text-secondary);cursor:pointer;user-select:none;background:#fff;}
.chip.active{border-color:var(--primary);color:var(--primary);background:var(--primary-light);}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.modal--wide{width:680px;max-height:80vh;display:flex;flex-direction:column;}
.form-item .with-btn{display:flex;gap:8px;}
.table-holder{overflow:auto;border:1px solid var(--border);border-radius:var(--radius-sm);}
.modal--wide .table-holder{flex:1;}
.table-holder table{min-width:0;}
.table-holder thead tr th:last-child,.table-holder tbody td:last-child{position:static;box-shadow:none;}
.mini-table td{white-space:normal;}
.duty-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:12px;}
.duty-toolbar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item active">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位→医疗点级联 + 救护类型 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:130px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:170px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>救护类型</label>
<select class="filter-select" id="filterType" style="min-width:110px;">
<option value="">全部类型</option>
<option>急救型</option><option>转运型</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增救护车
</button>
<button class="btn btn-default" onclick="openImport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 10V2M4.5 4.5L7 2l2.5 2.5M2 11h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入
</button>
<button class="btn btn-default" onclick="showToast('救护车导入模板下载功能演示')">下载模板</button>
<button class="btn btn-export" onclick="showToast('导出救护车台账功能演示')">导出</button>
<button class="btn btn-default" onclick="askDelete(checkedIds())">批量删除</button>
<span class="action-bar__hint">救护车与医疗点一对多,删除后出勤记录保留历史快照</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" onchange="toggleAll(this)"></th>
<th>序号</th><th>所属单位</th><th>所属医疗点</th><th>救护类型</th><th>车型</th><th>车牌号</th><th>覆盖区域</th><th>覆盖人数</th><th>驾驶员姓名</th><th>驾驶员联系电话</th><th>是否安装GPS</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑抽屉(分节表单) -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增救护车</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section">归属信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="fUnit" onchange="onFormUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="fPoint"><option value="">请先选择所属单位</option></select></div>
</div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 救护类型</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fType" value="急救型" checked>急救型</label>
<label class="radio"><input type="radio" name="fType" value="转运型">转运型</label>
</div></div>
<div class="form-section">车辆信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 车型</span><input class="form-control" id="fModel" placeholder="如:江铃轻型监护型救护车"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 车牌号</span><input class="form-control" id="fPlate" placeholder="如:CQ·0712A"></div>
<div class="form-item"><span class="form-label">年支付费用(万元)</span><input class="form-control" id="fFee" type="number" min="0" step="0.1" placeholder="0.0"></div>
<div class="form-item"><span class="form-label">救护车所属机构名称</span><input class="form-control" id="fOrg" placeholder="如:CQ油田总医院"></div>
<div class="form-item"><span class="form-label">开始服务年份</span><input class="form-control" id="fYear" type="number" min="2000" max="2026" placeholder="不晚于当年"></div>
<div class="form-item"><span class="form-label">是否链接公司车辆管理系统</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fLink" value="是"></label>
<label class="radio"><input type="radio" name="fLink" value="否" checked></label>
</div></div>
</div>
<div class="form-section">覆盖范围</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 覆盖区域</span><input class="form-control" id="fArea" placeholder="如:樊学作业区及沿线井站"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 覆盖人数</span><input class="form-control" id="fPeople" type="number" min="1" placeholder="正整数"></div>
</div>
<div class="form-section">急救能力</div>
<div class="form-item form-item--full"><span class="form-label">所配急救设备</span>
<textarea class="form-control" id="fEquip" placeholder="如:除颤仪、心电监护仪、氧气袋、创伤固定包"></textarea></div>
<div class="form-item form-item--full"><span class="form-label">救护能力</span>
<textarea class="form-control" id="fAbility" placeholder="描述救护车可承担的院前急救与转运能力"></textarea></div>
<div class="form-section">驾驶员信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 驾驶员姓名</span><input class="form-control" id="fDriver" placeholder="请输入驾驶员姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 联系电话</span><input class="form-control" id="fTel" placeholder="手机号或区号-座机号"></div>
</div>
<div class="form-item"><span class="form-label">是否安装GPS</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fGps" value="是" checked></label>
<label class="radio"><input type="radio" name="fGps" value="否"></label>
</div></div>
<div class="form-section">证照与照片</div>
<div class="form-item"><span class="form-label">救护车照片(最多5张)</span>
<div class="photo-row" id="phCar"></div>
<div class="photo-tip">点击 + 上传,支持 jpg/png,单张不超过 5MB</div></div>
<div class="form-grid">
<div class="form-item"><span class="form-label">驾驶证图片(最多5张)</span>
<div class="photo-row" id="phLicense"></div></div>
<div class="form-item"><span class="form-label">资格证图片(最多5张)</span>
<div class="photo-row" id="phCert"></div></div>
</div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 详情抽屉 -->
<div class="drawer-mask" id="detailMask" onclick="closeDrawer('detail')"></div>
<div class="drawer" id="detailDrawer">
<div class="drawer__header"><span id="detailTitle">救护车详情</span><button class="drawer__close" onclick="closeDrawer('detail')">×</button></div>
<div class="drawer__body" id="detailBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('detail')">关闭</button></div>
</div>
<!-- [交互] 数据管理抽屉(单车出勤记录台账,由列表行操作打开) -->
<div class="drawer-mask" id="dutyMask" onclick="closeDrawer('duty')"></div>
<div class="drawer" id="dutyDrawer">
<div class="drawer__header"><span id="dutyTitle">出勤记录</span><button class="drawer__close" onclick="closeDrawer('duty')">×</button></div>
<div class="drawer__body">
<div class="duty-toolbar">
<button class="btn btn-primary" onclick="openDutyForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>登记出勤
</button>
<button class="btn btn-export" onclick="showToast('导出该救护车出勤记录功能演示')">导出</button>
<span class="duty-toolbar__hint" id="dutyHint"></span>
</div>
<div class="table-holder" style="max-height:calc(100vh - 300px);">
<table class="mini-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 id="dutyBody"></tbody>
</table>
</div>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('duty')">关闭</button></div>
</div>
<!-- [交互] 出勤登记/编辑抽屉(固定为当前车辆) -->
<div class="drawer-mask" id="dutyFormMask" onclick="closeDrawer('dutyForm')"></div>
<div class="drawer" id="dutyFormDrawer">
<div class="drawer__header"><span id="dutyFormTitle">登记出勤</span><button class="drawer__close" onclick="closeDrawer('dutyForm')">×</button></div>
<div class="drawer__body">
<div class="form-section">救护车信息(自动带出)</div>
<div class="form-grid">
<div class="form-item"><span class="form-label">所属医疗点</span><input class="form-control" id="dfPoint" readonly></div>
<div class="form-item"><span class="form-label">救护车车牌</span><input class="form-control" id="dfPlate" readonly></div>
<div class="form-item"><span class="form-label">出勤司机</span><input class="form-control" id="dfDriver" readonly></div>
<div class="form-item"><span class="form-label">司机电话</span><input class="form-control" id="dfTel" readonly></div>
</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 出勤日期</span>
<input class="form-control" type="date" id="dfDate"></div>
</div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 出勤类型</span>
<div class="radio-group radio-wrap">
<label class="radio radio--disabled"><input type="radio" name="dfType" value="一键呼叫" disabled>一键呼叫<span class="opt-tip">(由 PAD 端床头/随身呼叫自动生成,不可手动选择)</span></label>
<label class="radio"><input type="radio" name="dfType" value="急救出车" checked>急救出车</label>
<label class="radio"><input type="radio" name="dfType" value="计划转运">计划转运</label>
<label class="radio"><input type="radio" name="dfType" value="日常巡诊">日常巡诊</label>
<label class="radio"><input type="radio" name="dfType" value="应急戒备">应急戒备</label>
</div></div>
<div class="form-section">救助信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 救助员工</span>
<div class="with-btn">
<input class="form-control" id="dfEmp" readonly placeholder="点击下方按钮选人" style="flex:1;">
<button class="btn btn-default" onclick="openEmpPick()">选择员工</button>
</div></div>
<div class="form-item"><span class="form-label">员工工号</span>
<input class="form-control" id="dfEmpCode" readonly placeholder="--"></div>
</div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 救助信息</span>
<textarea class="form-control" id="dfInfo" placeholder="简述伤情/病情与处置过程"></textarea></div>
<div class="form-section">随车医护与地点</div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 随车医护(可多选)</span>
<div class="chips" id="dfStaff"></div></div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 出勤地点</span>
<input class="form-control" id="dfFrom" placeholder="出车起点,如:樊学作业区3号井场"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 到达地点</span>
<input class="form-control" id="dfTo" placeholder="到达终点,如:樊学作业区医疗点"></div>
</div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('dutyForm')">取消</button>
<button class="btn btn-primary" onclick="saveDutyForm()">保存登记</button>
</div>
</div>
<!-- [交互] 救助员工选人弹窗(出勤登记表单内) -->
<div class="modal-mask" id="empMask">
<div class="modal modal--wide">
<div class="modal__title">选择救助员工</div>
<div class="filter-bar" style="box-shadow:none;padding:0;margin-bottom:14px;border-radius:var(--radius-sm);">
<div class="filter-item">
<label>姓名/工号</label>
<input class="filter-input" id="empQ" placeholder="输入姓名或工号过滤" style="min-width:150px;" oninput="renderEmpPick()">
</div>
</div>
<div class="table-holder" style="max-height:320px;">
<table>
<thead><tr><th style="width:40px"></th><th>姓名</th><th>员工工号</th><th>所属单位</th><th>所属部门</th></tr></thead>
<tbody id="empBody"></tbody>
</table>
</div>
<div class="modal__footer" style="margin-top:14px;">
<button class="btn btn-default" onclick="closeModal('empMask')">取消</button>
<button class="btn btn-primary" onclick="confirmEmp()">确认选择</button>
</div>
</div>
</div>
<!-- [交互] 出勤记录删除确认弹窗 -->
<div class="modal-mask" id="dutyDelMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="dutyDelText">确认删除该条出勤记录?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('dutyDelMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDutyDelete()">确认删除</button>
</div>
</div>
</div>
<!-- [交互] 批量导入弹窗 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 14V3M6.5 6.5L10 3l3.5 3.5M3 16h14" stroke="#1890FF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg> 批量导入救护车</div>
<div class="form-item" style="margin-top:14px;"><span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="iUnit" onchange="onImportUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="iPoint"><option value="">请先选择单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 上传文件</span>
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>救护车导入模板.xlsx</div></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('importMask')">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="delText">确认删除?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var ambulances=[
{id:1,unit:'采油一厂',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',plate:'CQ·0712A',area:'樊学作业区及沿线井站',people:860,driver:'田震',tel:'139****5218',gps:'是',fee:8.5,org:'CQ油田总医院',year:2021,link:'是',equip:'除颤仪、心电监护仪、氧气袋、创伤固定包',ability:'可承担院内心跳呼吸骤停复苏与创伤伤员的第一时间转运'},
{id:2,unit:'采油二厂',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',plate:'CQ·1366B',area:'姬圪台联合站及管线区',people:1240,driver:'冯保山',tel:'135****6174',gps:'是',fee:9.2,org:'CQ油田总医院',year:2020,link:'是',equip:'除颤仪、便携式彩超、呼吸机',ability:'具备危重伤员途中生命支持与心梗卒中协同转运能力'},
{id:3,unit:'第四采油厂',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',plate:'CQ·0208C',area:'西峰基地至第四采油厂机关沿线',people:2100,driver:'罗建平',tel:'150****8357',gps:'是',fee:12,org:'第四采油厂总医院',year:2019,link:'是',equip:'担架系统、输液泵、供氧系统',ability:'承担慢性病患者往返医院透析、复查等非急救转运'},
{id:4,unit:'采气一厂',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',plate:'CQ·1533D',area:'靖边净化厂及井场',people:760,driver:'邓成',tel:'133****9481',gps:'否',fee:7.8,org:'CQ油田总医院',year:2022,link:'否',equip:'除颤监护仪、氧气瓶、急救药箱',ability:'满足井场外伤、中暑、心源性猝倒的现场急救与送医'},
{id:5,unit:'采油一厂',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',plate:'CQ·0917E',area:'安塞基地及队部常驻区',people:540,driver:'苏文斌',tel:'155****6703',gps:'是',fee:6.5,org:'吴起油田医院',year:2023,link:'是',equip:'自动上车担架、颈胸腰固定支具',ability:'常驻点员工常规转运与陪同就医'},
{id:6,unit:'采气一厂',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',plate:'CQ·2201F',area:'苏里格南作业区营地',people:680,driver:'曹雪芹',tel:'186****2940',gps:'是',fee:8,org:'庆阳市人民医院',year:2024,link:'否',equip:'除颤仪、心电监护仪、简易呼吸器',ability:'高寒沙尘环境下的急救响应与危重员转运'}
];
var nextId=7,editingId=null,pendingDel=[];
/* ===== 筛选与渲染 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var type=document.getElementById('filterType').value;
return ambulances.filter(function(a){return (!unit||a.unit===unit)&&(!point||a.point===point)&&(!type||a.type===type);});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="13" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(a,i){
var typeBadge=a.type==='急救型'?'<span class="badge badge--danger">急救型</span>':'<span class="badge badge--info">转运型</span>';
var gpsBadge=a.gps==='是'?'<span class="badge badge--success">是</span>':'<span class="badge badge--muted">否</span>';
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+a.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+a.unit+'</td>'
+'<td style="text-align:left;font-weight:600;">'+a.point+'</td>'
+'<td>'+typeBadge+'</td>'
+'<td style="text-align:left;max-width:200px;overflow:hidden;text-overflow:ellipsis;">'+a.model+'</td>'
+'<td>'+a.plate+'</td>'
+'<td style="text-align:left;max-width:200px;overflow:hidden;text-overflow:ellipsis;">'+a.area+'</td>'
+'<td class="td-num">'+a.people.toLocaleString()+'</td>'
+'<td>'+a.driver+'</td>'
+'<td>'+a.tel+'</td>'
+'<td>'+gpsBadge+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openDetail('+a.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openDuty('+a.id+')">数据管理</button>'
+'<button class="btn btn-link" onclick="openForm('+a.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete(['+a.id+'])">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterUnit').value='';
onFilterUnit();
document.getElementById('filterType').value='';
applyFilter();
}
function turnPage(d){
var list=currentData();
var totalPages=Math.ceil(list.length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function checkedIds(){return [].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return +c.value;});}
/* ===== 照片占位 ===== */
function renderPhotos(boxId,count){
var html='';
for(var i=0;i<count;i++){html+='<div class="photo-tile" onclick="showToast(\'演示环境不支持预览原图\')">🚑</div>';}
if(count<5){html+='<div class="photo-add" onclick="showToast(\'演示环境请选择本地图片\')">+</div>';}
document.getElementById(boxId).innerHTML=html;
}
/* ===== 新增/编辑抽屉 ===== */
function fillPointSel(selId,unit,keep){
var sel=document.getElementById(selId);
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){
var o=document.createElement('option');o.textContent=p;
if(keep&&p===keep)o.selected=true;
sel.appendChild(o);
});}
}
function onFormUnit(){fillPointSel('fPoint',document.getElementById('fUnit').value);}
function setRadio(name,val){
document.querySelectorAll('input[name="'+name+'"]').forEach(function(r){r.checked=(r.value===val);});
}
function getRadio(name){
var r=document.querySelector('input[name="'+name+'"]:checked');
return r?r.value:'';
}
function openForm(id){
editingId=id||null;
document.getElementById('formTitle').textContent=id?'编辑救护车 · '+ambulances.find(function(a){return a.id===id;}).plate:'新增救护车';
var a=id?ambulances.find(function(x){return x.id===id;}):null;
['fModel','fPlate','fFee','fOrg','fYear','fArea','fPeople','fDriver','fTel','fEquip','fAbility'].forEach(function(k){document.getElementById(k).value='';});
document.getElementById('fUnit').value=a?a.unit:'';
fillPointSel('fPoint',a?a.unit:'',a?a.point:'');
if(!a)document.getElementById('fPoint').innerHTML='<option value="">请先选择所属单位</option>';
setRadio('fType',a?a.type:'急救型');
setRadio('fGps',a?a.gps:'是');
setRadio('fLink',a?a.link:'否');
if(a){
document.getElementById('fModel').value=a.model;
document.getElementById('fPlate').value=a.plate;
document.getElementById('fFee').value=a.fee||'';
document.getElementById('fOrg').value=a.org||'';
document.getElementById('fYear').value=a.year||'';
document.getElementById('fArea').value=a.area;
document.getElementById('fPeople').value=a.people;
document.getElementById('fDriver').value=a.driver;
document.getElementById('fTel').value=a.tel;
document.getElementById('fEquip').value=a.equip||'';
document.getElementById('fAbility').value=a.ability||'';
}
renderPhotos('phCar',a?2:0);
renderPhotos('phLicense',a?1:0);
renderPhotos('phCert',a?1:0);
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function saveForm(){
var unit=document.getElementById('fUnit').value;
var point=document.getElementById('fPoint').value;
var model=document.getElementById('fModel').value.trim();
var plate=document.getElementById('fPlate').value.trim();
var area=document.getElementById('fArea').value.trim();
var people=document.getElementById('fPeople').value;
var driver=document.getElementById('fDriver').value.trim();
var tel=document.getElementById('fTel').value.trim();
var year=document.getElementById('fYear').value;
if(!unit){showToast('请选择所属单位');return;}
if(!point){showToast('请选择所属医疗点');return;}
if(!model){showToast('请填写车型');return;}
if(!plate){showToast('请填写车牌号');return;}
if(!area){showToast('请填写覆盖区域');return;}
if(!people||+people<1||people.indexOf('.')!==-1){showToast('覆盖人数须为正整数');return;}
if(year&&+year>2026){showToast('开始服务年份不能晚于当年');return;}
if(!driver){showToast('请填写驾驶员姓名');return;}
if(!tel){showToast('请填写联系电话');return;}
if(!/^[\d\-\*\+ ]{7,}$/.test(tel)){showToast('联系电话格式不正确(手机号或区号-座机号)');return;}
var obj={
id:editingId||nextId++,
unit:unit,point:point,type:getRadio('fType'),model:model,plate:plate,
area:area,people:+people,driver:driver,tel:tel,
gps:getRadio('fGps'),fee:+document.getElementById('fFee').value||0,
org:document.getElementById('fOrg').value.trim(),year:year?+year:'',
link:getRadio('fLink'),
equip:document.getElementById('fEquip').value.trim(),
ability:document.getElementById('fAbility').value.trim()
};
if(editingId){
ambulances.splice(ambulances.findIndex(function(a){return a.id===editingId;}),1,obj);
}else{
ambulances.push(obj);
}
closeDrawer('form');renderTable();
showToast(editingId?('救护车「'+plate+'」信息已更新'):('救护车「'+plate+'」已新增'));
}
/* ===== 详情抽屉 ===== */
function openDetail(id){
var a=ambulances.find(function(x){return x.id===id;});
document.getElementById('detailTitle').textContent='救护车详情 · '+a.plate;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v===''||v==null?'—':v)+'</div></div>';}
document.getElementById('detailBody').innerHTML=
'<div class="form-section" style="margin-top:0;">归属与类型</div>'
+'<div class="detail-grid">'+d('所属单位',a.unit)+d('所属医疗点',a.point)+d('救护类型',a.type)+d('车型',a.model)+'</div>'
+'<div class="form-section">车辆信息</div>'
+'<div class="detail-grid">'+d('车牌号',a.plate)+d('年支付费用(万元)',a.fee)+d('所属机构',a.org)+d('开始服务年份',a.year)+d('链接车辆管理系统',a.link)+d('是否安装GPS',a.gps)+'</div>'
+'<div class="form-section">覆盖与急救能力</div>'
+'<div class="detail-grid">'+d('覆盖区域',a.area)+d('覆盖人数',a.people.toLocaleString()+' 人')+d('所配急救设备',a.equip,true)+d('救护能力',a.ability,true)+'</div>'
+'<div class="form-section">驾驶员</div>'
+'<div class="detail-grid">'+d('驾驶员姓名',a.driver)+d('联系电话',a.tel)+'</div>'
+'<div class="form-section">证照与照片</div>'
+'<div class="photo-row"><div class="photo-tile">🚑</div><div class="photo-tile">🚑</div><div class="photo-tile">🪪</div><div class="photo-tile">📄</div></div>';
document.getElementById('detailMask').classList.add('open');
document.getElementById('detailDrawer').classList.add('open');
}
/* ===== 删除 ===== */
function askDelete(ids){
if(!ids.length){showToast('请先勾选要删除的救护车');return;}
pendingDel=ids;
var names=ids.map(function(id){return ambulances.find(function(a){return a.id===id;}).plate;});
document.getElementById('delText').textContent='确认删除救护车「'+names.slice(0,3).join('、')+(names.length>3?' 等 '+names.length+' 辆':'')+'」?其历史出勤记录保留快照,对应车辆的「数据管理」入口将随之移除。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
ambulances=ambulances.filter(function(a){return pendingDel.indexOf(a.id)===-1;});
closeModal('delMask');renderTable();
showToast('已删除 '+pendingDel.length+' 辆救护车');
pendingDel=[];
}
/* ===== 批量导入 ===== */
function openImport(){
document.getElementById('iUnit').value='';
document.getElementById('iPoint').innerHTML='<option value="">请先选择单位</option>';
document.getElementById('importMask').classList.add('open');
}
function onImportUnit(){
var unit=document.getElementById('iUnit').value;
var sel=document.getElementById('iPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function doImport(){
if(!document.getElementById('iUnit').value){showToast('请选择所属单位');return;}
if(!document.getElementById('iPoint').value){showToast('请选择所属医疗点');return;}
closeModal('importMask');
showToast('导入任务已提交,完成后将推送站内通知');
}
/* ===== 数据管理(单车出勤记录,原「救护车值班」页功能并入) ===== */
var staffPool=['石维国','李桂芳','曲志明','张秀兰','高建军','闫婷','刘玉梅','徐立成'];
/* 可选的救助员工(与员工分配页已分配名单一致) */
var empPool=[
{name:'石维国',code:'CQ021578',unit:'采油一厂',dept:'樊学作业区'},
{name:'李桂芳',code:'CQ032144',unit:'采油一厂',dept:'樊学作业区'},
{name:'白瑞',code:'CQ044871',unit:'采油一厂',dept:'生产技术部'},
{name:'曲志明',code:'CQ019203',unit:'采油二厂',dept:'姬圪台联合站'},
{name:'张秀兰',code:'CQ027710',unit:'采油二厂',dept:'姬圪台联合站'},
{name:'赵万福',code:'CQ033096',unit:'采油二厂',dept:'桐园作业区'},
{name:'高建军',code:'CQ010564',unit:'第四采油厂',dept:'基地管理处'},
{name:'闫婷',code:'CQ041822',unit:'第四采油厂',dept:'健康管理部'},
{name:'刘玉梅',code:'CQ036441',unit:'采气一厂',dept:'靖边净化厂'},
{name:'呼建国',code:'CQ029905',unit:'采气一厂',dept:'靖边净化厂'},
{name:'徐立成',code:'CQ022317',unit:'采气一厂',dept:'苏里格南作业区'},
{name:'杨慧敏',code:'CQ045130',unit:'采油一厂',dept:'樊学作业区'}
];
var duties=[
{id:1,date:'2026-09-06',point:'樊学作业区医疗点',plate:'CQ·0712A',type:'急救出车',driver:'田震',tel:'139****5218',staff:['石维国','李桂芳'],emp:'白瑞',empCode:'CQ044871',info:'井场触电致左前臂灼伤,现场处置后接回医疗点换药',from:'樊学作业区3号井场',to:'樊学作业区医疗点'},
{id:2,date:'2026-09-05',point:'西峰基地医疗点',plate:'CQ·0208C',type:'计划转运',driver:'罗建平',tel:'150****8357',staff:['闫婷'],emp:'高建军',empCode:'CQ010564',info:'每周常规血透转运,途中生命体征平稳',from:'西峰基地医疗点',to:'第四采油厂总医院'},
{id:3,date:'2026-09-03',point:'靖边气田医疗点',plate:'CQ·1533D',type:'一键呼叫',driver:'邓成',tel:'133****9481',staff:['刘玉梅'],emp:'呼建国',empCode:'CQ029905',info:'净化厂装置区气体报警疏散,一名员工头晕请求救护(PAD端一键呼叫自动生成)',from:'靖边净化厂中控区',to:'靖边气田医疗点',auto:true},
{id:4,date:'2026-08-29',point:'姬圪台作业区医疗点',plate:'CQ·1366B',type:'急救出车',driver:'冯保山',tel:'135****6174',staff:['曲志明','张秀兰'],emp:'张秀兰',empCode:'CQ027710',info:'泵房高温中暑晕厥,物理降温后补液观察',from:'姬圪台联合站泵房',to:'姬圪台作业区医疗点'},
{id:5,date:'2026-08-26',point:'西峰基地医疗点',plate:'CQ·0208C',type:'日常巡诊',driver:'罗建平',tel:'150****8357',staff:['高建军','闫婷'],emp:'高建军',empCode:'CQ010564',info:'基地夏季巡诊送药,无异常处置',from:'西峰基地综合服务区',to:'西峰基地机关办公楼'},
{id:6,date:'2026-08-22',point:'安塞采油队长住点医疗室',plate:'CQ·0917E',type:'计划转运',driver:'苏文斌',tel:'155****6703',staff:['石维国'],emp:'白瑞',empCode:'CQ044871',info:'骨折术后拆线复查陪同转运',from:'安塞常驻点医疗室',to:'吴起油田医院'},
{id:7,date:'2026-08-18',point:'乌审旗采气区医疗点',plate:'CQ·2201F',type:'应急戒备',driver:'曹雪芹',tel:'186****2940',staff:['徐立成'],emp:'徐立成',empCode:'CQ022317',info:'汛期营地应急戒备值守,车辆人员全时待命',from:'乌审旗医疗点',to:'苏里格南作业区营地'},
{id:8,date:'2026-08-15',point:'靖边气田医疗点',plate:'CQ·1533D',type:'急救出车',driver:'邓成',tel:'133****9481',staff:['刘玉梅'],emp:'刘玉梅',empCode:'CQ036441',info:'高血压危象,协同降压后转送县级医院',from:'靖边气田医疗点',to:'靖边县人民医院'},
{id:9,date:'2026-09-01',point:'樊学作业区医疗点',plate:'CQ·0712A',type:'急救出车',driver:'田震',tel:'139****5218',staff:['李桂芳'],emp:'杨慧敏',empCode:'CQ045130',info:'高处坠落致踝关节扭伤,固定后接回',from:'姬塬先导试验区12号井场',to:'樊学作业区医疗点'}
];
var curAmb=null,dutyNextId=10,dutyEditingId=null,pendingDutyDel=null,curStaff=[],curEmp=null;
function typeBadge(t){
var cls={'一键呼叫':'badge--danger','急救出车':'badge--warning','计划转运':'badge--info','日常巡诊':'badge--success','应急戒备':'badge--muted'}[t]||'badge--muted';
return '<span class="badge '+cls+'">'+t+'</span>';
}
function dutyList(){
if(!curAmb)return [];
return duties.filter(function(d){return d.plate===curAmb.plate;})
.sort(function(a,b){return a.date<b.date?1:(a.date>b.date?-1:b.id-a.id);});
}
function openDuty(id){
curAmb=ambulances.find(function(a){return a.id===id;});
if(!curAmb)return;
document.getElementById('dutyTitle').textContent='出勤记录 · '+curAmb.plate+'('+curAmb.point+')';
renderDutyTable();
document.getElementById('dutyMask').classList.add('open');
document.getElementById('dutyDrawer').classList.add('open');
}
function renderDutyTable(){
var list=dutyList();
document.getElementById('dutyHint').textContent='本车共 '+list.length+' 条出勤记录';
var tbody=document.getElementById('dutyBody');
if(!list.length){tbody.innerHTML='<tr><td colspan="12" class="td-empty">该救护车暂无出勤记录,点击「登记出勤」补录</td></tr>';return;}
var html='';
list.forEach(function(d,i){
var ops=d.auto
?'<span class="badge badge--muted">系统生成</span>'
:('<button class="btn btn-link" onclick="openDutyForm('+d.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDutyDelete('+d.id+')">删除</button>');
html+='<tr>'
+'<td>'+(i+1)+'</td>'
+'<td>'+d.date+'</td>'
+'<td>'+typeBadge(d.type)+'</td>'
+'<td>'+d.driver+'</td>'
+'<td>'+d.tel+'</td>'
+'<td>'+d.staff.join('、')+'</td>'
+'<td>'+d.emp+'</td>'
+'<td>'+d.empCode+'</td>'
+'<td style="min-width:200px;">'+d.info+'</td>'
+'<td style="min-width:140px;">'+d.from+'</td>'
+'<td style="min-width:140px;">'+d.to+'</td>'
+'<td>'+ops+'</td>'
+'</tr>';
});
tbody.innerHTML=html;
}
function renderStaffChips(){
document.getElementById('dfStaff').innerHTML=staffPool.map(function(n){
return '<span class="chip'+(curStaff.indexOf(n)!==-1?' active':'')+'" onclick="toggleStaff(\''+n+'\')">'+n+'</span>';
}).join('');
}
function toggleStaff(n){
var i=curStaff.indexOf(n);
if(i===-1)curStaff.push(n);else curStaff.splice(i,1);
renderStaffChips();
}
function openDutyForm(id){
dutyEditingId=id||null;
var d=id?duties.find(function(x){return x.id===id;}):null;
document.getElementById('dutyFormTitle').textContent=id?('编辑出勤记录 · '+d.date):('登记出勤 · '+curAmb.plate);
document.getElementById('dfPoint').value=curAmb.point;
document.getElementById('dfPlate').value=curAmb.plate;
document.getElementById('dfDriver').value=curAmb.driver;
document.getElementById('dfTel').value=curAmb.tel;
document.getElementById('dfDate').value=d?d.date:'2026-09-07';
setRadio('dfType',d?d.type:'急救出车');
document.getElementById('dfEmp').value=d?d.emp:'';
document.getElementById('dfEmpCode').value=d?d.empCode:'';
document.getElementById('dfInfo').value=d?d.info:'';
document.getElementById('dfFrom').value=d?d.from:'';
document.getElementById('dfTo').value=d?d.to:'';
curStaff=d?d.staff.slice():[];
curEmp=d?{name:d.emp,code:d.empCode}:null;
renderStaffChips();
document.getElementById('dutyFormMask').classList.add('open');
document.getElementById('dutyFormDrawer').classList.add('open');
}
function saveDutyForm(){
var date=document.getElementById('dfDate').value;
var emp=document.getElementById('dfEmp').value;
var info=document.getElementById('dfInfo').value.trim();
var from=document.getElementById('dfFrom').value.trim();
var to=document.getElementById('dfTo').value.trim();
if(!date){showToast('请选择出勤日期');return;}
if(date>'2026-09-07'){showToast('出勤日期不能晚于今天');return;}
if(!emp){showToast('请选择救助员工');return;}
if(!info){showToast('请填写救助信息');return;}
if(!curStaff.length){showToast('请至少选择一名随车医护');return;}
if(!from){showToast('请填写出勤地点');return;}
if(!to){showToast('请填写到达地点');return;}
var obj={
id:dutyEditingId||dutyNextId++,date:date,point:curAmb.point,plate:curAmb.plate,
type:getRadio('dfType'),driver:curAmb.driver,tel:curAmb.tel,
staff:curStaff.slice(),emp:emp,empCode:document.getElementById('dfEmpCode').value,
info:info,from:from,to:to
};
if(dutyEditingId){
duties.splice(duties.findIndex(function(x){return x.id===dutyEditingId;}),1,obj);
}else{
duties.push(obj);
}
closeDrawer('dutyForm');renderDutyTable();
showToast(dutyEditingId?'出勤记录已更新':'出勤记录已登记');
}
function openEmpPick(){
document.getElementById('empQ').value='';
renderEmpPick();
document.getElementById('empMask').classList.add('open');
}
function renderEmpPick(){
var q=document.getElementById('empQ').value.trim();
var list=empPool.filter(function(e){return !q||e.name.indexOf(q)!==-1||e.code.toLowerCase().indexOf(q.toLowerCase())!==-1;});
document.getElementById('empBody').innerHTML=list.length?list.map(function(e){
return '<tr><td><input type="radio" name="empSel" value="'+e.code+'"'+(curEmp&&curEmp.code===e.code?' checked':'')+'></td>'
+'<td style="font-weight:600;">'+e.name+'</td><td>'+e.code+'</td><td>'+e.unit+'</td><td>'+e.dept+'</td></tr>';
}).join(''):'<tr><td colspan="5" class="td-empty">未找到匹配员工</td></tr>';
}
function confirmEmp(){
var r=document.querySelector('input[name="empSel"]:checked');
if(!r){showToast('请选择一名员工');return;}
var e=empPool.find(function(x){return x.code===r.value;});
curEmp=e;
document.getElementById('dfEmp').value=e.name;
document.getElementById('dfEmpCode').value=e.code;
closeModal('empMask');
}
function askDutyDelete(id){
pendingDutyDel=id;
var d=duties.find(function(x){return x.id===id;});
document.getElementById('dutyDelText').textContent='确认删除「'+d.date+' '+d.plate+'」的出勤记录?删除后「数据统计」模块的救护车出勤口径将同步回落。';
document.getElementById('dutyDelMask').classList.add('open');
}
function confirmDutyDelete(){
duties=duties.filter(function(d){return d.id!==pendingDutyDel;});
closeModal('dutyDelMask');renderDutyTable();
showToast('出勤记录已删除');
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,619 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>医疗点配置 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:140px;}
.filter-input:focus{border-color:var(--primary);}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-danger{background:#fff;color:var(--danger);border:1px solid #ffccc7;}.btn-danger:hover{background:#fff1f0;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
/* 视图切换 + 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.view-switch{margin-left:auto;display:inline-flex;background:#F5F7FA;border:1px solid var(--border);border-radius:var(--radius-sm);padding:2px;}
.view-switch button{border:none;background:transparent;padding:4px 14px;font-size:var(--font-xs);color:var(--text-secondary);cursor:pointer;border-radius:3px;}
.view-switch button.active{background:#fff;color:var(--primary);box-shadow:var(--shadow-sm);}
/* 表格 */
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:2100px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
/* 徽标/开关 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:var(--primary-light);color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.switch{position:relative;display:inline-block;width:36px;height:20px;cursor:pointer;}
.switch input{display:none;}
.switch__track{position:absolute;inset:0;background:#BFBFBF;border-radius:10px;transition:.2s;}
.switch__track::after{content:'';position:absolute;left:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:.2s;}
.switch input:checked + .switch__track{background:var(--primary);}
.switch input:checked + .switch__track::after{transform:translateX(16px);}
/* 分页 */
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 表单 */
.form-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);padding:8px 0;border-left:3px solid var(--primary);padding-left:8px;margin-bottom:12px;}
.form-section__title:not(:first-child){margin-top:20px;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 24px;}
.form-item{display:flex;flex-direction:column;gap:6px;}
.form-item--full{grid-column:1 / -1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-textarea{height:72px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.radio-group{display:flex;align-items:center;gap:16px;height:32px;font-size:var(--font-sm);}
.radio-group label{display:flex;align-items:center;gap:5px;cursor:pointer;}
.check-tags{display:flex;flex-wrap:wrap;gap:8px;padding:4px 0;}
.check-tag{border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 12px;font-size:var(--font-xs);cursor:pointer;color:var(--text-secondary);}
.check-tag.checked{border-color:var(--primary);color:var(--primary);background:var(--primary-light);}
.upload-row{display:flex;gap:8px;align-items:center;}
.upload-box{width:80px;height:80px;border:1px dashed var(--border);border-radius:var(--radius-sm);display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;gap:2px;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);}
.img-ph{width:80px;height:80px;border-radius:var(--radius-sm);background:linear-gradient(135deg,#E6F7FF,#BAE7FF);display:flex;align-items:center;justify-content:center;font-size:22px;}
/* 只读详情 */
.desc-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 24px;}
.desc-item{display:flex;padding:10px 0;border-bottom:1px solid #F0F0F0;font-size:var(--font-sm);}
.desc-item--full{grid-column:1 / -1;}
.desc-item__label{width:110px;color:var(--text-secondary);flex-shrink:0;}
.desc-item__value{color:var(--text-primary);}
/* 内嵌小表格(导出记录) */
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;}
.mini-table td{white-space:normal;}
/* 确认弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:400px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:20px;line-height:1.6;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;}
/* 地图视图 */
.map-wrap{display:none;background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.map-wrap.open{display:flex;}
.map-canvas{flex:1;min-width:0;background:#EEF3F8;position:relative;}
.map-canvas svg{display:block;width:100%;height:560px;}
.map-pin__label{font-size:11px;fill:#0b4d8c;font-weight:600;}
.map-side{width:300px;flex-shrink:0;border-left:1px solid var(--border);padding:16px;overflow-y:auto;max-height:560px;}
.map-side__title{font-size:var(--font-sm);font-weight:600;margin-bottom:12px;}
.map-point{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:10px;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:8px;font-size:var(--font-xs);cursor:pointer;}
.map-point:hover{border-color:var(--primary);background:#F8FBFF;}
.map-point__name{font-weight:600;color:var(--text-primary);font-size:var(--font-sm);}
.map-point__meta{color:var(--text-placeholder);margin-top:2px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item active">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>医疗点名称</label>
<input class="filter-input" id="filterName" placeholder="请输入医疗点名称">
</div>
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit">
<option value="">全部单位</option>
<option>采油一厂</option>
<option>采油二厂</option>
<option>第四采油厂</option>
<option>采气一厂</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏:新增/批量删除/异步导出+导出记录/视图切换 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm('新增医疗点')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增
</button>
<button class="btn btn-default" onclick="showToast('请先勾选要删除的医疗点')">批量删除</button>
<button class="btn btn-export" onclick="doExport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出
</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<div class="view-switch">
<button id="btnList" class="active" onclick="switchView('list')">资源列表</button>
<button id="btnMap" onclick="switchView('map')">地图列表</button>
</div>
</div>
<!-- 列表视图 -->
<div class="table-card" id="listView">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th>
<th>序号</th><th>医疗点名称</th><th>医疗点地址</th><th>所属医院</th><th>联系电话</th><th>所属单位</th><th>负责人</th><th>合同签订金额(万元)</th><th>设立时间</th><th>医生人数</th><th>护士人数</th><th>辐射人数</th><th>救护车数量</th><th>开启状态</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
<!-- [状态] 地图视图:医疗点打点分布(示意) -->
<div class="map-wrap" id="mapView">
<div class="map-canvas">
<svg viewBox="0 0 900 560" xmlns="http://www.w3.org/2000/svg">
<!-- 示意底图:路网与矿区色块 -->
<rect x="0" y="0" width="900" height="560" fill="#EEF3F8"/>
<path d="M0 380 Q220 300 420 340 T900 300" stroke="#D4E2EE" stroke-width="26" fill="none"/>
<path d="M120 0 L180 260 L150 560" stroke="#D4E2EE" stroke-width="16" fill="none"/>
<path d="M620 0 L580 240 L660 560" stroke="#D4E2EE" stroke-width="16" fill="none"/>
<ellipse cx="250" cy="170" rx="90" ry="52" fill="#E1EFE3"/>
<ellipse cx="680" cy="150" rx="110" ry="60" fill="#F3EEE1"/>
<ellipse cx="430" cy="430" rx="120" ry="62" fill="#E6EAF3"/>
<text x="250" y="174" text-anchor="middle" font-size="12" fill="#9AAE9F">姬塬作业区</text>
<text x="680" y="154" text-anchor="middle" font-size="12" fill="#B0A488">安塞作业区</text>
<text x="430" y="434" text-anchor="middle" font-size="12" fill="#8F9BB3">西峰作业区</text>
<!-- 医疗点打点(带数量角标) -->
<g id="mapPins"></g>
</svg>
</div>
<div class="map-side">
<div class="map-side__title">医疗点分布(<span id="mapCount">0</span>)</div>
<div id="mapPointList"></div>
</div>
</div>
</main>
<!-- [交互] 新增/编辑抽屉(页内 overlay 实现) -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增医疗点</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section__title">基本信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 医疗点名称</span><input class="form-control" id="fName" placeholder="请输入医疗点名称"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="fUnit"><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医院</span>
<select class="form-control" id="fHospital"><option>CQ油田总医院</option><option>第四采油厂总医院</option><option>环县油田医院</option><option>吴起油田医院</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 负责人</span><input class="form-control" id="fLeader" placeholder="请输入负责人姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 联系电话</span><input class="form-control" id="fPhone" placeholder="请输入联系电话"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 合同签订金额(万元)</span><input class="form-control" id="fAmount" type="number" placeholder="请输入金额"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 设立时间</span><input class="form-control" id="fFound" type="date"></div>
<div class="form-item"><span class="form-label">状态</span>
<div class="radio-group"><label><input type="radio" name="fStatus" checked> 开启</label><label><input type="radio" name="fStatus"> 关闭</label></div></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 医疗点地址</span>
<div class="upload-row"><input class="form-control" id="fAddr" readonly placeholder="点击右侧地图选点后自动回填"><button class="btn btn-default" onclick="showToast('演示:打开地图选点')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5c2.5 0 4.5 2 4.5 4.4 0 3.2-4.5 7.1-4.5 7.1S2.5 9.1 2.5 5.9C2.5 3.5 4.5 1.5 7 1.5z" stroke="currentColor" stroke-width="1.2"/><circle cx="7" cy="5.9" r="1.6" stroke="currentColor" stroke-width="1.2"/></svg>地图选点</button></div>
<span style="font-size:var(--font-xs);color:var(--text-placeholder);">经度 107.6379 / 纬度 36.9426(选点后回填)</span></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 医疗点图片</span>
<div class="upload-row"><div class="img-ph">🏥</div><div class="upload-box" onclick="showToast('演示环境暂不支持上传')"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 4v10M4 9h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>上传</div></div></div>
</div>
<div class="form-section__title">辐射范围</div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 辐射单位(可多选)</span>
<div class="check-tags" id="fRadiate">
<span class="check-tag checked" onclick="toggleTag(this)">采油一厂 · 樊学作业区</span>
<span class="check-tag" onclick="toggleTag(this)">采油一厂 · 麻子山作业区</span>
<span class="check-tag" onclick="toggleTag(this)">采油二厂 · 姬圪台作业区</span>
<span class="check-tag checked" onclick="toggleTag(this)">采油二厂 · 桐园作业区</span>
<span class="check-tag" onclick="toggleTag(this)">第四采油厂 · 西谷峁作业区</span>
<span class="check-tag" onclick="toggleTag(this)">采气一厂 · 靖边气田</span>
</div></div>
<div class="form-section__title">医疗点简介</div>
<div class="form-item form-item--full"><textarea class="form-control form-textarea" id="fIntro" placeholder="请输入医疗点简介"></textarea></div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 医疗点详情抽屉(只读) -->
<div class="drawer-mask" id="detailMask" onclick="closeDrawer('detail')"></div>
<div class="drawer drawer--narrow" id="detailDrawer">
<div class="drawer__header"><span>医疗点详情</span><button class="drawer__close" onclick="closeDrawer('detail')">×</button></div>
<div class="drawer__body" id="detailBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('detail')">关闭</button></div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="delText">确认删除该医疗点?删除后其下药品、员工分配、就诊记录将不可访问。</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var points=[
{id:1,name:'樊学作业区医疗点',addr:'CQ油田采油一厂樊学作业区基地东侧',hospital:'环县油田医院',phone:'0934-5621-086',unit:'采油一厂',leader:'石维国',amount:86,found:'2021-06-18',doctors:3,nurses:4,radPeople:2860,ambulance:2,status:true,mx:230,my:150,radiate:'樊学作业区、麻子山作业区',intro:'覆盖樊学、麻子山两个作业区的综合医疗点,设全科诊室、治疗室与药房。'},
{id:2,name:'姬圪台作业区医疗点',addr:'采油二厂姬圪台联合站生活区1号',hospital:'CQ油田总医院',phone:'0934-8842-175',unit:'采油二厂',leader:'曲志明',amount:72,found:'2020-09-02',doctors:2,nurses:3,radPeople:2140,ambulance:1,status:true,mx:320,my:210,radiate:'姬圪台作业区、桐园作业区',intro:'以井站巡诊与慢病随访为主的医疗点。'},
{id:3,name:'西峰基地医疗点',addr:'第四采油厂西峰基地综合服务区2层',hospital:'第四采油厂总医院',phone:'0934-8263-002',unit:'第四采油厂',leader:'高建军',amount:128,found:'2019-04-26',doctors:5,nurses:6,radPeople:4350,ambulance:3,status:true,mx:440,my:420,radiate:'西峰基地、西谷峁注水作业区',intro:'厂级中心医疗点,承担基地日常诊疗、体检报告解读与健康宣教。'},
{id:4,name:'靖边气田医疗点',addr:'采气一厂靖边净化厂生活小区旁',hospital:'CQ油田总医院',phone:'0912-5774-268',unit:'采气一厂',leader:'刘玉梅',amount:64,found:'2022-03-15',doctors:2,nurses:2,radPeople:1680,ambulance:1,status:true,mx:660,my:160,radiate:'靖边气田、乌审旗采气区',intro:'气田一线医疗点,配备车载急救与远程会诊终端。'},
{id:5,name:'桐园作业区医疗点',addr:'采油二厂桐园作业区营地西侧',hospital:'环县油田医院',phone:'0934-8847-330',unit:'采油二厂',leader:'赵万福',amount:58,found:'2021-11-08',doctors:2,nurses:2,radPeople:1420,ambulance:1,status:true,mx:390,my:300,radiate:'桐园作业区',intro:'由中心医疗点派驻医护值守,夜间依托一键呼叫联动。'},
{id:6,name:'安塞采油队长住点医疗室',addr:'采油一厂安塞基地综合公寓1层',hospital:'吴起油田医院',phone:'0911-2386-941',unit:'采油一厂',leader:'马进良',amount:45,found:'2023-05-20',doctors:1,nurses:2,radPeople:960,ambulance:1,status:false,mx:700,my:120,radiate:'安塞基地',intro:'规模较小的常驻医疗室,正在升级设备后重新开放。'},
{id:7,name:'姬塬作业区医疗点',addr:'采油三厂筹备组姬塬先导试验区板房区',hospital:'第四采油厂总医院',phone:'0934-6658-213',unit:'采油一厂',leader:'杨慧敏',amount:60,found:'2024-02-27',doctors:2,nurses:3,radPeople:1730,ambulance:2,status:true,mx:180,my:190,radiate:'姬塬试验区、定边采油区',intro:'新设医疗点,承担试验区产能建设人员医疗保障。'},
{id:8,name:'乌审旗采气区医疗点',addr:'采气一厂苏里格南作业区营地',hospital:'CQ油田总医院',phone:'0477-6263-180',unit:'采气一厂',leader:'徐立成',amount:66,found:'2020-12-11',doctors:2,nurses:3,radPeople:1550,ambulance:1,status:true,mx:760,my:220,radiate:'苏里格南作业区',intro:'蒙地气田医疗点,冬季巡诊频次加密。'}
];
var editId=null,delId=null;
/* ===== 列表渲染 ===== */
var pageSize=10,currentPage=1,filtered=points.slice();
function renderTable(){
var tbody=document.getElementById('tableBody');
var start=(currentPage-1)*pageSize,pageData=filtered.slice(start,start+pageSize);
var totalPages=Math.ceil(filtered.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+filtered.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
if(!filtered.length){tbody.innerHTML='<tr><td colspan="16" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(p,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+p.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="text-align:left;font-weight:600;">'+p.name+'</td>'
+'<td style="text-align:left;max-width:260px;overflow:hidden;text-overflow:ellipsis;">'+p.addr+'</td>'
+'<td>'+p.hospital+'</td><td>'+p.phone+'</td><td>'+p.unit+'</td><td>'+p.leader+'</td>'
+'<td>'+p.amount.toFixed(1)+'</td><td>'+p.found+'</td>'
+'<td>'+p.doctors+'</td><td>'+p.nurses+'</td><td>'+p.radPeople.toLocaleString()+'</td><td>'+p.ambulance+'</td>'
+'<td><label class="switch"><input type="checkbox" '+(p.status?'checked':'')+' onchange="toggleStatus('+p.id+',this.checked)"><span class="switch__track"></span></label></td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openEdit('+p.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete('+p.id+')">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
filtered=points.filter(function(p){
return (!name||p.name.indexOf(name)!==-1)&&(!unit||p.unit===unit);
});
currentPage=1;renderTable();renderMap();
}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(filtered.length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function toggleStatus(id,on){
var p=points.find(function(x){return x.id===id;});
p.status=on;showToast(on?('已开启「'+p.name+'」'):('已关闭「'+p.name+'」,APP端不再展示该医疗点'));
}
/* ===== 视图切换 ===== */
function switchView(v){
document.getElementById('btnList').classList.toggle('active',v==='list');
document.getElementById('btnMap').classList.toggle('active',v==='map');
document.getElementById('listView').style.display=v==='list'?'':'none';
document.getElementById('mapView').classList.toggle('open',v==='map');
}
/* 地图打点 */
function renderMap(){
var pins=document.getElementById('mapPins'),list=document.getElementById('mapPointList');
var pn='',ps='';
filtered.forEach(function(p){
pn+='<g style="cursor:pointer" onclick="openDetail('+p.id+')">'
+'<circle cx="'+p.mx+'" cy="'+p.my+'" r="12" fill="#1890FF" opacity=".18"><animate attributeName="r" values="10;18;10" dur="2.4s" repeatCount="indefinite"/></circle>'
+'<circle cx="'+p.mx+'" cy="'+p.my+'" r="6" fill="'+(p.status?'#1890FF':'#BFBFBF')+'" stroke="#fff" stroke-width="1.6"/>'
+'<text x="'+p.mx+'" y="'+(p.my-14)+'" text-anchor="middle" class="map-pin__label">'+p.name.slice(0,7)+'</text></g>';
ps+='<div class="map-point" onclick="openDetail('+p.id+')"><div><div class="map-point__name">'+p.name+'</div><div class="map-point__meta">'+p.unit+' · 医生'+p.doctors+'人 · 辐射'+p.radPeople.toLocaleString()+'人</div></div>'
+'<span class="badge '+(p.status?'badge--success':'badge--muted')+'">'+(p.status?'开启':'关闭')+'</span></div>';
});
pins.innerHTML=pn;list.innerHTML=ps;
document.getElementById('mapCount').textContent=filtered.length;
}
/* ===== 新增/编辑抽屉 ===== */
function openForm(title){
document.getElementById('formTitle').textContent=title;
editId=null;
['fName','fLeader','fPhone','fAmount','fFound','fAddr','fIntro'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function openEdit(id){
var p=points.find(function(x){return x.id===id;});
openForm('编辑医疗点');editId=id;
document.getElementById('fName').value=p.name;
document.getElementById('fUnit').value=p.unit;
document.getElementById('fHospital').value=p.hospital;
document.getElementById('fLeader').value=p.leader;
document.getElementById('fPhone').value=p.phone;
document.getElementById('fAmount').value=p.amount;
document.getElementById('fFound').value=p.found;
document.getElementById('fAddr').value=p.addr;
document.getElementById('fIntro').value=p.intro;
}
function saveForm(){
var name=document.getElementById('fName').value.trim();
if(!name){showToast('请填写医疗点名称');return;}
showToast(editId?('医疗点「'+name+'」已保存'):('医疗点「'+name+'」已创建'));
closeDrawer('form');
}
function toggleTag(el){el.classList.toggle('checked');}
/* ===== 详情抽屉 ===== */
function openDetail(id){
var p=points.find(function(x){return x.id===id;});
document.getElementById('detailBody').innerHTML=
'<div class="upload-row" style="margin-bottom:16px;"><div class="img-ph" style="width:120px;height:80px;">🏥</div><div class="img-ph" style="width:120px;height:80px;background:linear-gradient(135deg,#F0F5FF,#D6E4FF);">🚑</div></div>'
+'<div class="desc-grid">'
+row('医疗点名称',p.name)+row('所属单位',p.unit)+row('所属医院',p.hospital)+row('负责人',p.leader)
+row('联系电话',p.phone)+row('合同签订金额',p.amount.toFixed(1)+' 万元')+row('设立时间',p.found)
+row('医护人员',p.doctors+' 名医生 / '+p.nurses+' 名护士')+row('救护车',p.ambulance+' 辆')+row('辐射人数',p.radPeople.toLocaleString()+' 人')
+row('开启状态',p.status?'<span class="badge badge--success">开启</span>':'<span class="badge badge--muted">关闭</span>')
+'<div class="desc-item desc-item--full"><span class="desc-item__label">辐射范围</span><span class="desc-item__value">'+p.radiate+'</span></div>'
+'<div class="desc-item desc-item--full"><span class="desc-item__label">医疗点地址</span><span class="desc-item__value">'+p.addr+'</span></div>'
+'<div class="desc-item desc-item--full"><span class="desc-item__label">简介</span><span class="desc-item__value">'+p.intro+'</span></div>'
+'</div>';
document.getElementById('detailMask').classList.add('open');
document.getElementById('detailDrawer').classList.add('open');
}
function row(l,v){return '<div class="desc-item"><span class="desc-item__label">'+l+'</span><span class="desc-item__value">'+v+'</span></div>';}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'医疗点列表导出_2026-09-01 09:36.xlsx',status:'done',time:'2026-09-01 09:36'},
{file:'医疗点列表导出_2026-08-15 14:02.xlsx',status:'done',time:'2026-08-15 14:02'},
{file:'医疗点列表导出_2026-07-30 16:45.xlsx',status:'fail',time:'2026-07-30 16:45'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);">—</span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
/* 导出为异步任务:提交后生成「生成中」记录,稍后自动转为「已生成」 */
function doExport(){
exportLogs.unshift({file:'医疗点列表导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 删除确认 ===== */
function askDelete(id){
delId=id;
var p=points.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认删除「'+p.name+'」?删除后其下药品、员工分配、就诊记录将不可访问。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
points=points.filter(function(x){return x.id!==delId;});
closeModal();applyFilter();showToast('医疗点已删除');
}
function closeModal(){document.getElementById('delMask').classList.remove('open');}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();renderMap();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,614 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>医护人员管理 | 一线医疗</title>
<style>
:root{--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;--text-primary:rgba(0,0,0,0.85);--text-secondary:rgba(0,0,0,0.65);--text-placeholder:rgba(0,0,0,0.25);--border:#F0F0F0;--bg:#F5F7FA;--radius-md:8px;--radius-sm:4px;--shadow-sm:0 1px 2px rgba(0,0,0,0.06);--font-xs:12px;--font-sm:13px;--font-base:14px;--font-lg:16px;}
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:"Microsoft YaHei","PingFang SC",sans-serif;font-size:var(--font-base);color:var(--text-primary);background:var(--bg);min-width:1280px;}
/* 顶部导航栏 */
.top-bar{position:fixed;top:0;left:0;right:0;height:56px;background:#001529;display:flex;align-items:center;padding:0 24px;z-index:100;}
.top-bar__logo{color:#fff;font-size:var(--font-lg);font-weight:600;display:flex;align-items:center;gap:10px;margin-right:40px;}
.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;}
.btn-back{color:#1890FF;text-decoration:none;font-size:var(--font-sm);background:rgba(255,255,255,0.9);padding:5px 12px;border-radius:var(--radius-sm);}
.avatar{width:32px;height:32px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;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;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-input,.filter-select{height:32px;border:1px solid #D9D9D9;border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);background:#fff;outline:none;}
.filter-input:focus,.filter-select:focus{border-color:var(--primary);}
/* 按钮 */
.btn{height:32px;padding:0 14px;border-radius:var(--radius-sm);border:1px solid transparent;font-size:var(--font-sm);cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all .15s;}
.btn-primary{background:var(--primary);color:#fff;}
.btn-primary:hover{background:#40A9FF;}
.btn-default{background:#fff;border-color:#D9D9D9;color:var(--text-primary);}
.btn-default:hover{border-color:var(--primary);color:var(--primary);}
.btn-export{background:#fff;border-color:#D9D9D9;color:var(--primary);display:inline-flex;align-items:center;gap:6px;}
.btn-export:hover{border-color:var(--primary);}
.btn-link{background:none;border:none;color:var(--primary);cursor:pointer;font-size:var(--font-sm);padding:0 6px;}
.btn-link:hover{opacity:.75;}
.btn-link--danger{color:var(--danger);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:12px 24px;background:#fff;border-top:1px solid #F0F0F0;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);margin-bottom:16px;}
.action-bar__sep{width:1px;height:18px;background:#E8E8E8;margin:0 4px;}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 表格 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1680px;}
thead th{position:sticky;top:0;background:#FAFAFA;font-weight:600;font-size:var(--font-sm);color:var(--text-primary);text-align:left;padding:11px 12px;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:11px 12px;font-size:var(--font-sm);color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
tbody tr:hover{background:#FAFCFF;}
.td-actions{white-space:nowrap;}
.avatar-cell{width:42px;height:42px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-base);flex-shrink:0;}
.idcell{color:var(--text-secondary);}
/* 状态开关 */
.switch{position:relative;display:inline-block;width:40px;height:20px;cursor:pointer;}
.switch input{opacity:0;width:0;height:0;}
.switch__track{position:absolute;inset:0;background:#BFBFBF;border-radius:10px;transition:.2s;}
.switch__track:before{content:"";position:absolute;width:16px;height:16px;left:2px;top:2px;background:#fff;border-radius:50%;transition:.2s;}
.switch input:checked+.switch__track{background:var(--primary);}
.switch input:checked+.switch__track:before{transform:translateX(20px);}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 16px;font-size:var(--font-sm);color:var(--text-secondary);}
.page-btn{height:28px;min-width:28px;padding:0 8px;border:1px solid #D9D9D9;background:#fff;border-radius:var(--radius-sm);cursor:pointer;font-size:var(--font-sm);color:var(--text-primary);}
.page-btn:hover:not(:disabled){border-color:var(--primary);color:var(--primary);}
.page-btn:disabled{color:var(--text-placeholder);cursor:not-allowed;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:200;}
.drawer-mask.show{display:block;}
.drawer{position:fixed;top:0;right:-640px;width:640px;bottom:0;background:#fff;z-index:210;box-shadow:-6px 0 16px rgba(0,0,0,0.12);transition:right .25s;display:flex;flex-direction:column;}
.drawer.show{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{border:none;background:none;font-size:18px;color:var(--text-secondary);cursor:pointer;line-height:1;}
.drawer__close:hover{color:var(--danger);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:12px;padding:14px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 抽屉表单 */
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 20px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-sm);color:var(--text-secondary);}
.form-label .req{color:var(--danger);margin-right:2px;}
.form-control{height:32px;border:1px solid #D9D9D9;border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;background:#fff;}
.form-control:focus{border-color:var(--primary);}
.form-control[disabled]{background:#F5F5F5;color:var(--text-secondary);cursor:not-allowed;}
textarea.form-control{height:72px;padding:8px 10px;resize:vertical;}
.form-item--full{grid-column:1 / -1;}
.radio-group{display:flex;gap:18px;align-items:center;height:32px;}
.radio{display:inline-flex;align-items:center;gap:5px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;}
/* 弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:300;align-items:center;justify-content:center;}
.modal-mask.show{display:flex;}
.modal{width:420px;background:#fff;border-radius:var(--radius-md);box-shadow:0 6px 16px rgba(0,0,0,0.18);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__body{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.7;}
.modal__footer{display:flex;justify-content:flex-end;gap:12px;margin-top:20px;}
/* Toast */
.toast{position:fixed;top:72px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.78);color:#fff;padding:9px 18px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:400;display:none;}
.toast.show{display:block;animation:toastIn .2s;}
@keyframes toastIn{from{opacity:0;transform:translate(-50%,-6px);}to{opacity:1;transform:translate(-50%,0);}}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item active">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:员工姓名 + 单位名称 + 所属医疗点(先选单位,医疗点随单位级联) -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名关键字" style="min-width:130px;">
</div>
<div class="filter-item">
<label>单位名称</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:130px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:170px;">
<option value="">全部医疗点</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏:新增/批量删除;导出为异步任务,进度在「导出记录」查看 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增
</button>
<button class="btn btn-default" onclick="batchDelete()" style="color:var(--danger);border-color:#FFCCC7;">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 4h8M5.5 4V2.5h3V4M4 4l.5 8h5L10 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>批量删除
</button>
<span class="action-bar__sep"></span>
<button class="btn btn-export" onclick="doExport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 10V2M4.5 4.5L7 2l2.5 2.5M2 11h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出
</button>
<button class="btn btn-default" onclick="openExportRecords()">导出记录</button>
<span class="action-bar__hint">医护人员账号可登录医疗点 PAD 端接收任务</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" onchange="toggleAll(this)"></th>
<th style="width:52px;">序号</th><th style="width:56px;">图片</th><th>员工姓名</th><th style="min-width:100px;">单位名称</th><th style="min-width:150px;">所属医疗点</th><th style="width:56px;">性别</th><th style="width:56px;">年龄</th><th style="min-width:160px;">身份证</th><th style="min-width:110px;">联系电话</th><th style="min-width:90px;">职称</th><th style="width:80px;">岗位</th><th style="min-width:96px;">驻点时间</th><th style="min-width:120px;">毕业院校</th><th style="min-width:90px;">工作时间</th><th style="width:84px;">开启状态</th><th style="width:150px;">操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- [交互] 分页:每页 10 条,上一页/下一页切换 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑抽屉:新增显示账号与密码区,编辑时账号只读、密码区隐藏 -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增医护人员</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div id="accountSection">
<div class="form-section">账号信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 用户账号</span>
<input class="form-control" id="fUsername" placeholder="字母/数字/下划线" oninput="this.value=this.value.replace(/[^a-zA-Z0-9_]/g,'')"></div>
<div class="form-item"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 登录密码</span>
<input class="form-control" type="password" id="fPwd" placeholder="至少8位,含字母和数字"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 确认密码</span>
<input class="form-control" type="password" id="fPwd2" placeholder="再次输入登录密码"></div>
</div>
</div>
<div class="form-section">基本信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 姓名</span><input class="form-control" id="fName" placeholder="请输入姓名"></div>
<div class="form-item"><span class="form-label">性别</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fSex" value="男" checked></label>
<label class="radio"><input type="radio" name="fSex" value="女"></label>
</div></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 身份证</span><input class="form-control" id="fIdCard" placeholder="18位身份证号" maxlength="18"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 联系电话</span><input class="form-control" id="fTel" placeholder="11位手机号" maxlength="11"></div>
<div class="form-item"><span class="form-label">生日</span><input class="form-control" type="date" id="fBirthday"></div>
<div class="form-item"><span class="form-label">头像</span>
<button class="btn btn-default" style="width:fit-content;" onclick="showToast('演示:上传图片(仅支持 jpg/png,≤2MB)')">上传头像</button></div>
</div>
<div class="form-section">归属信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="fUnit" onchange="onFormUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="fPoint" onfocus="onPointFocus2()"><option value="">请先选择所属单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 驻点时间</span><input class="form-control" type="date" id="fJoinTime"></div>
</div>
<div class="form-section">专业信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 职称</span>
<select class="form-control" id="fJob"><option value="">请选择职称</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 岗位</span>
<select class="form-control" id="fPost"><option value="">请选择岗位</option></select></div>
<div class="form-item"><span class="form-label">学历</span>
<select class="form-control" id="fEdu"><option value="">请选择学历</option><option>中专</option><option>大专</option><option>本科</option><option>硕士</option><option>博士</option></select></div>
<div class="form-item"><span class="form-label">毕业院校</span><input class="form-control" id="fSchool" placeholder="请输入毕业院校"></div>
<div class="form-item"><span class="form-label">毕业时间</span><input class="form-control" type="date" id="fGradTime"></div>
<div class="form-item"><span class="form-label">工作时间</span><input class="form-control" type="date" id="fWorkTime"></div>
</div>
<div class="form-item form-item--full"><span class="form-label">简介</span>
<textarea class="form-control" id="fDesc" placeholder="个人专长、从业经历等(选填)"></textarea></div>
<div class="form-item form-item--full"><span class="form-label">开启状态</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fStatus" value="1" checked>开启</label>
<label class="radio"><input type="radio" name="fStatus" value="0">关闭</label>
</div></div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 重置密码弹窗:保存后提示重置成功(演示) -->
<div class="modal-mask" id="pwdMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6 9V6.5a4 4 0 118 0V9" stroke="#1890FF" stroke-width="1.6"/><rect x="4.5" y="9" width="11" height="7.5" rx="1.5" stroke="#1890FF" stroke-width="1.6"/></svg> 重置密码</div>
<div class="modal__body">为「<span id="pwdName" style="color:var(--text-primary);font-weight:600;"></span>」重置 PAD 端登录密码。</div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 新密码</span><input class="form-control" type="password" id="newPwd" placeholder="至少8位,含字母和数字"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 确认密码</span><input class="form-control" type="password" id="newPwd2" placeholder="再次输入新密码"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('pwdMask')">取消</button>
<button class="btn btn-primary" onclick="savePwd()">确认重置</button>
</div>
</div>
</div>
<!-- [交互] 删除确认弹窗:单条与批量共用 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__body" id="delText">确认删除所选医护人员?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" style="background:var(--danger);" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉:异步导出任务列表 -->
<div class="drawer-mask" id="recMask" onclick="closeDrawer('rec')"></div>
<div class="drawer" id="recDrawer" style="width:560px;right:-560px;">
<div class="drawer__header"><span>医护人员导出记录</span><button class="drawer__close" onclick="closeDrawer('rec')">×</button></div>
<div class="drawer__body">
<table style="min-width:0;width:100%;">
<thead><tr><th style="min-width:170px;">文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="recBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('rec')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 字典与级联数据(与救护车/医疗点页 mock 口径一致) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var jobOptions=['住院医师','主治医师','副主任医师','主任医师','护师','主管护师','副主任护师','技师'];
var postOptions=['医生','护士','检验人员','放射人员','药师','公卫人员'];
/* ===== 医护人员 mock 数据(演示数据已脱敏) ===== */
var staff=[
{id:1,username:'fanyx_doc01',name:'韩东学',sex:'男',unit:'采油一厂',point:'樊学作业区医疗点',idCard:'622101********1537',tel:'139****5218',job:'主治医师',post:'医生',joinTime:'2021-03-01',edu:'本科',school:'西安医学院',gradTime:'2012-06-30',workTime:'2012-07-01',birthday:'1988-04-12',desc:'从事油田一线全科医疗十年,擅长外伤处置与慢性病管理。',status:1},
{id:2,username:'fanyx_nur02',name:'兰小娟',sex:'女',unit:'采油一厂',point:'樊学作业区医疗点',idCard:'610621********3246',tel:'135****6174',job:'主管护师',post:'护士',joinTime:'2021-03-01',edu:'大专',school:'延安大学医护学院',gradTime:'2014-06-30',workTime:'2014-07-01',birthday:'1991-09-23',desc:'负责医疗点护理与药品管理。',status:1},
{id:3,username:'anse_doc03',name:'白宇峰',sex:'男',unit:'采油一厂',point:'安塞采油队长住点医疗室',idCard:'612524********0817',tel:'150****8357',job:'副主任医师',post:'医生',joinTime:'2020-06-15',edu:'硕士',school:'陕西中医药大学',gradTime:'2010-06-30',workTime:'2010-07-01',birthday:'1985-11-02',desc:'常驻点医疗室负责人,心血管方向。',status:1},
{id:4,username:'jyuan_doc04',name:'温亚丽',sex:'女',unit:'采油一厂',point:'姬塬作业区医疗点',idCard:'640321********6624',tel:'133****9481',job:'主治医师',post:'医生',joinTime:'2022-08-01',edu:'本科',school:'宁夏医科大学',gradTime:'2015-06-30',workTime:'2015-07-01',birthday:'1992-02-17',desc:'擅长高原作业区多发病防治。',status:1},
{id:5,username:'jgt_doc05',name:'窦志远',sex:'男',unit:'采油二厂',point:'姬圪台作业区医疗点',idCard:'612721********0933',tel:'155****6703',job:'主任医师',post:'医生',joinTime:'2019-04-01',edu:'硕士',school:'兰州大学医学院',gradTime:'2008-06-30',workTime:'2008-07-01',birthday:'1983-06-28',desc:'医疗点学科带头人。',status:1},
{id:6,username:'jgt_nur06',name:'苗慧敏',sex:'女',unit:'采油二厂',point:'姬圪台作业区医疗点',idCard:'610421********4528',tel:'186****2940',job:'护师',post:'护士',joinTime:'2023-05-10',edu:'本科',school:'咸阳职业技术学院',gradTime:'2019-06-30',workTime:'2019-07-01',birthday:'1996-12-05',desc:'',status:1},
{id:7,username:'tongyuan_doc07',name:'屈二娃',sex:'男',unit:'采油二厂',point:'桐园作业区医疗点',idCard:'612121********3612',tel:'137****8826',job:'住院医师',post:'医生',joinTime:'2024-07-01',edu:'本科',school:'西安医学院',gradTime:'2022-06-30',workTime:'2022-07-01',birthday:'1998-03-20',desc:'新入职,跟诊培养中。',status:1},
{id:8,username:'xf_lab08',name:'霍静怡',sex:'女',unit:'第四采油厂',point:'西峰基地医疗点',idCard:'621021********7745',tel:'158****3319',job:'技师',post:'检验人员',joinTime:'2020-09-01',edu:'本科',school:'甘肃医学院',gradTime:'2016-06-30',workTime:'2016-07-01',birthday:'1993-08-14',desc:'负责血液、尿液常规检验。',status:1},
{id:9,username:'jb_rad09',name:'康永强',sex:'男',unit:'采气一厂',point:'靖边气田医疗点',idCard:'612722********4218',tel:'131****7652',job:'主治医师',post:'放射人员',joinTime:'2021-11-15',edu:'本科',school:'榆林学院医学院',gradTime:'2013-06-30',workTime:'2013-07-01',birthday:'1990-05-30',desc:'兼任DR拍片与读片工作。',status:0},
{id:10,username:'wsq_nur10',name:'花灵芝',sex:'女',unit:'采气一厂',point:'乌审旗采气区医疗点',idCard:'152722********2136',tel:'147****0285',job:'主管护师',post:'护士',joinTime:'2019-03-01',edu:'大专',school:'蒙东卫生学校',gradTime:'2011-06-30',workTime:'2011-07-01',birthday:'1989-10-09',desc:'蒙汉双语医护。',status:1},
{id:11,username:'wsq_doc11',name:'荣大海',sex:'男',unit:'采气一厂',point:'乌审旗采气区医疗点',idCard:'150212********3351',tel:'133****5917',job:'副主任医师',post:'医生',joinTime:'2018-06-01',edu:'硕士',school:'内蒙古医科大学',gradTime:'2006-06-30',workTime:'2006-07-01',birthday:'1981-01-26',desc:'高寒沙尘环境急救经验丰富。',status:1},
{id:12,username:'jyuan_doc12',name:'牛玉洁',sex:'女',unit:'采油一厂',point:'姬塬作业区医疗点',idCard:'610121********9064',tel:'159****4471',job:'护师',post:'药师',joinTime:'2022-08-01',edu:'本科',school:'陕西能源职业技术学院',gradTime:'2018-06-30',workTime:'2018-07-01',birthday:'1995-07-11',desc:'兼职医疗点药品与库存管理。',status:1}
];
var nextId=13,editingId=null,pendingDel=[];
var pageSize=10,currentPage=1,checked={};
function showToast(msg){var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');clearTimeout(t._h);t._h=setTimeout(function(){t.classList.remove('show');},2200);}
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function openDrawer(n){document.getElementById(n+'Mask').classList.add('show');document.getElementById(n+'Drawer').classList.add('show');}
function closeDrawer(n){document.getElementById(n+'Mask').classList.remove('show');document.getElementById(n+'Drawer').classList.remove('show');}
function closeModal(id){document.getElementById(id).classList.remove('show');}
function fillOptions(sel,list,placeholder){var v=sel.value;sel.innerHTML='<option value="">'+placeholder+'</option>';list.forEach(function(x){var o=document.createElement('option');o.textContent=x;sel.appendChild(o);});sel.value=v;}
/* ===== 筛选级联 ===== */
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
/* [交互] 未选单位时聚焦医疗点提示先选单位 */
function onPointFocus(){
if(!document.getElementById('filterUnit').value){showToast('请先选择单位');}
}
/* ===== 列表渲染 ===== */
function currentData(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
return staff.filter(function(s){
if(name&&s.name.indexOf(name)<0)return false;
if(unit&&s.unit!==unit)return false;
if(point&&s.point!==point)return false;
return true;
});
}
function renderTable(){
var list=currentData();
var total=list.length,pages=Math.max(1,Math.ceil(total/pageSize));
if(currentPage>pages)currentPage=pages;
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var html=pageData.map(function(s,i){
var sexColor=s.sex==='女'?'#EB2F96':'#1890FF';
return '<tr>'
+'<td><input type="checkbox" '+(checked[s.id]?'checked':'')+' onchange="toggleRow('+s.id+',this.checked)"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td><div class="avatar-cell" style="background:'+sexColor+';">'+s.name.charAt(0)+'</div></td>'
+'<td style="color:var(--text-primary);font-weight:600;">'+s.name+'</td>'
+'<td>'+s.unit+'</td><td>'+s.point+'</td>'
+'<td>'+s.sex+'</td>'
+'<td>'+(calcAge(s.birthday)||'--')+'</td>'
+'<td class="idcell">'+s.idCard+'</td><td>'+s.tel+'</td>'
+'<td>'+s.job+'</td><td>'+s.post+'</td>'
+'<td>'+(s.joinTime||'--')+'</td><td>'+(s.school||'--')+'</td><td>'+(s.workTime||'--')+'</td>'
+'<td><label class="switch"><input type="checkbox" '+(s.status?'checked':'')+' onchange="toggleStatus('+s.id+',this.checked)"><span class="switch__track"></span></label></td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openForm('+s.id+')">编辑</button>'
+'<button class="btn btn-link" onclick="openResetPwd('+s.id+')">重置密码</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete(['+s.id+'])">删除</button>'
+'</td></tr>';
}).join('');
document.getElementById('tableBody').innerHTML=html||'<tr><td colspan="17" style="text-align:center;padding:32px;color:var(--text-placeholder);">暂无符合条件的医护人员</td></tr>';
document.getElementById('pageInfo').textContent='共 '+total+' 条';
document.getElementById('pageNum').textContent=currentPage+' / '+pages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=pages;
}
function calcAge(bd){if(!bd)return '';var b=new Date(bd),n=new Date('2026-09-07');var a=n.getFullYear()-b.getFullYear();if(n.getMonth()<b.getMonth()||(n.getMonth()===b.getMonth()&&n.getDate()<b.getDate()))a--;return a>0?a:'';}
function turnPage(d){currentPage+=d;renderTable();}
function applyFilter(){currentPage=1;checked={};renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';
onFilterUnit();
currentPage=1;checked={};renderTable();
}
function toggleAll(cb){
var list=currentData().slice((currentPage-1)*pageSize,currentPage*pageSize);
list.forEach(function(s){cb.checked?checked[s.id]=1:delete checked[s.id];});
renderTable();
}
function toggleRow(id,v){v?checked[id]=1:delete checked[id];}
function checkedIds(){return Object.keys(checked).filter(function(k){return checked[k];}).map(Number);}
/* ===== 开启状态开关 ===== */
function toggleStatus(id,on){
var s=staff.find(function(x){return x.id===id;});
if(s){s.status=on?1:0;showToast('「'+s.name+'」已'+(on?'开启':'关闭')+'(演示)');}
}
/* ===== 新增/编辑 ===== */
function openForm(id){
editingId=id||null;
document.getElementById('formTitle').textContent=id?'编辑医护人员':'新增医护人员';
document.getElementById('accountSection').style.display=id?'none':'block';
document.getElementById('fUsername').disabled=!!id;
var s=id?staff.find(function(x){return x.id===id;}):null;
document.getElementById('fName').value=s?s.name:'';
document.querySelector('input[name=fSex][value="'+(s?s.sex:'男')+'"]').checked=true;
document.getElementById('fIdCard').value=s?s.idCard:'';
document.getElementById('fTel').value=s?s.tel:'';
document.getElementById('fBirthday').value=s?s.birthday:'';
document.getElementById('fJoinTime').value=s?s.joinTime:'';
document.getElementById('fEdu').value=s?s.edu:'';
document.getElementById('fSchool').value=s?s.school:'';
document.getElementById('fGradTime').value=s?s.gradTime:'';
document.getElementById('fWorkTime').value=s?s.workTime:'';
document.getElementById('fDesc').value=s?s.desc:'';
document.querySelector('input[name=fStatus][value="'+(s?s.status:'1')+'"]').checked=true;
/* 单位→医疗点级联回填 */
var fu=document.getElementById('fUnit'),fp=document.getElementById('fPoint');
fu.value=s?s.unit:'';
onFormUnit();
if(s)fp.value=s.point;
fillOptions(document.getElementById('fJob'),jobOptions,'请选择职称');
fillOptions(document.getElementById('fPost'),postOptions,'请选择岗位');
document.getElementById('fJob').value=s?s.job:'';
document.getElementById('fPost').value=s?s.post:'';
openDrawer('form');
}
function onFormUnit(){
var u=document.getElementById('fUnit').value;
fillOptions(document.getElementById('fPoint'),pointMap[u]||[],u?'请选择医疗点':'请先选择所属单位');
}
function onPointFocus2(){if(!document.getElementById('fUnit').value)showToast('请先选择所属单位');}
function getRadio(n){return document.querySelector('input[name='+n+']:checked').value;}
/* [交互] 保存校验:账号/姓名/身份证/电话/单位/医疗点/驻点时间/职称/岗位必填;新增时密码强度=≥8位且含字母与数字 */
function saveForm(){
var name=document.getElementById('fName').value.trim();
var idCard=document.getElementById('fIdCard').value.trim();
var tel=document.getElementById('fTel').value.trim();
var unit=document.getElementById('fUnit').value,point=document.getElementById('fPoint').value;
var job=document.getElementById('fJob').value,post=document.getElementById('fPost').value;
if(!editingId){
var un=document.getElementById('fUsername').value.trim();
if(!un)return showToast('请输入用户账号');
var p1=document.getElementById('fPwd').value,p2=document.getElementById('fPwd2').value;
if(!(/[A-Za-z]/.test(p1)&&/\d/.test(p1)&&p1.length>=8))return showToast('密码至少8位且含字母和数字');
if(p1!==p2)return showToast('两次输入的密码不一致');
}else{
var cur=staff.find(function(x){return x.id===editingId;});
if(cur)un=cur.username;
}
if(!name)return showToast('请输入姓名');
if(!/^\d{17}[\dXx]$/.test(idCard))return showToast('身份证号码格式有误');
if(!/^1[3-9]\d{9}$/.test(tel))return showToast('手机号码格式有误');
if(!unit)return showToast('请选择所属单位');
if(!point)return showToast('请选择所属医疗点');
if(!document.getElementById('fJoinTime').value)return showToast('请选择驻点时间');
if(!job)return showToast('请选择职称');
if(!post)return showToast('请选择岗位');
var data={name:name,sex:getRadio('fSex'),idCard:idCard,mask:true,tel:tel,birthday:document.getElementById('fBirthday').value,
unit:unit,point:point,joinTime:document.getElementById('fJoinTime').value,job:job,post:post,
edu:document.getElementById('fEdu').value,school:document.getElementById('fSchool').value,
gradTime:document.getElementById('fGradTime').value,workTime:document.getElementById('fWorkTime').value,
desc:document.getElementById('fDesc').value,status:+getRadio('fStatus')};
if(editingId){
var s=staff.find(function(x){return x.id===editingId;});
for(var k in data)s[k]=data[k];
showToast('保存成功(演示)');
}else{
data.id=nextId++;data.username=un;
/* 列表电话/身份证按脱敏格式展示 */
data.tel=tel.slice(0,3)+'****'+tel.slice(7);
data.idCard=idCard.slice(0,6)+'********'+idCard.slice(14);
staff.push(data);showToast('新增成功(演示)');
}
closeDrawer('form');renderTable();
}
/* ===== 重置密码 ===== */
var pwdTarget=null;
function openResetPwd(id){
var s=staff.find(function(x){return x.id===id;});if(!s)return;
pwdTarget=id;
document.getElementById('pwdName').textContent=s.name;
document.getElementById('newPwd').value='';document.getElementById('newPwd2').value='';
document.getElementById('pwdMask').classList.add('show');
}
function savePwd(){
var p1=document.getElementById('newPwd').value,p2=document.getElementById('newPwd2').value;
if(!(/[A-Za-z]/.test(p1)&&/\d/.test(p1)&&p1.length>=8))return showToast('新密码至少8位且含字母和数字');
if(p1!==p2)return showToast('两次输入的密码不一致');
closeModal('pwdMask');showToast('密码重置成功(演示)');
}
/* ===== 删除 ===== */
function askDelete(ids){
if(!ids.length)return showToast('未选中任何数据');
pendingDel=ids;
document.getElementById('delText').textContent=ids.length===1
?'确认删除医护人员「'+staff.find(function(x){return x.id===ids[0];}).name+'」?删除后其 PAD 端账号同步停用。'
:'确认删除所选 '+ids.length+' 名医护人员?删除后其 PAD 端账号同步停用。';
document.getElementById('delMask').classList.add('show');
}
function batchDelete(){askDelete(checkedIds());}
function confirmDelete(){
staff=staff.filter(function(s){return pendingDel.indexOf(s.id)<0;});
pendingDel.forEach(function(id){delete checked[id];});
pendingDel=[];
closeModal('delMask');showToast('删除成功(演示)');renderTable();
}
/* ===== 导出(异步任务) ===== */
var exportRecords=[
{file:'医护人员列表_2026-09-05.xlsx',status:'已完成',time:'2026-09-05 10:23'},
{file:'医护人员列表_2026-08-28.xlsx',status:'已完成',time:'2026-08-28 16:41'}
];
function doExport(){
exportRecords.unshift({file:'医护人员列表_2026-09-07.xlsx',status:'进行中',time:'刚刚'});
showToast('导出任务已提交,请在「导出记录」中查看进度');
}
function openExportRecords(){
document.getElementById('recBody').innerHTML=exportRecords.map(function(r){
var color=r.status==='已完成'?'var(--success)':(r.status==='进行中'?'var(--warning)':'var(--danger)');
return '<tr><td style="color:var(--text-primary);">'+r.file+'</td><td><span style="color:'+color+';">● '+r.status+'</span></td><td>'+r.time+'</td>'
+'<td>'+(r.status==='已完成'?'<button class="btn btn-link" onclick="showToast(\'演示:开始下载该文件\')">下载</button>':'<span style="color:var(--text-placeholder);">--</span>')+'</td></tr>';
}).join('');
openDrawer('rec');
}
renderTable();
</script>
</body>
</html>
@@ -0,0 +1,447 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时库存 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 概览卡 */
.stat-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;display:flex;align-items:center;gap:14px;}
.stat-card__icon{width:44px;height:44px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}
.stat-card__icon--blue{background:var(--primary-light);}
.stat-card__icon--green{background:#F6FFED;}
.stat-card__icon--orange{background:#FFFBE6;}
.stat-card__icon--red{background:#FFF1F0;}
.stat-card__label{font-size:var(--font-xs);color:var(--text-secondary);}
.stat-card__value{font-size:24px;font-weight:600;color:var(--text-primary);line-height:1.3;font-variant-numeric:tabular-nums;}
.stat-card__extra{font-size:var(--font-xs);color:var(--text-placeholder);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:140px;}
.filter-input:focus{border-color:var(--primary);}
.filter-chip{height:32px;padding:0 14px;border:1px solid var(--border);border-radius:16px;background:#fff;font-size:var(--font-xs);color:var(--text-secondary);cursor:pointer;}
.filter-chip.active{border-color:var(--primary);color:var(--primary);background:var(--primary-light);}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 表格 */
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1700px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.td-num{font-variant-numeric:tabular-nums;}
/* 库存量进度条 */
.stock-bar{display:inline-flex;flex-direction:column;align-items:center;gap:4px;min-width:88px;}
.stock-bar__num{font-size:var(--font-sm);font-weight:600;font-variant-numeric:tabular-nums;}
.stock-bar__track{width:88px;height:4px;border-radius:2px;background:#F0F0F0;overflow:hidden;}
.stock-bar__fill{height:100%;border-radius:2px;}
/* 徽标 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:var(--primary-light);color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 分页 */
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 流水摘要与表格 */
.flow-summary{display:flex;gap:24px;margin-bottom:16px;font-size:var(--font-sm);}
.flow-summary div{display:flex;flex-direction:column;gap:2px;}
.flow-summary .k{font-size:var(--font-xs);color:var(--text-placeholder);}
.flow-summary .v{font-size:18px;font-weight:600;font-variant-numeric:tabular-nums;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;}
.mini-table td{white-space:normal;}
.in{color:#389E0D;font-weight:600;}
.out{color:#CF1322;font-weight:600;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item active">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [状态] 概览卡 -->
<div class="stat-cards">
<div class="stat-card"><div class="stat-card__icon stat-card__icon--blue">💊</div><div><div class="stat-card__label">在管药品条目</div><div class="stat-card__value" id="kTotal">0</div><div class="stat-card__extra">覆盖 4 个单位 · 8 个医疗点</div></div></div>
<div class="stat-card"><div class="stat-card__icon stat-card__icon--green">📦</div><div><div class="stat-card__label">库存充足</div><div class="stat-card__value" id="kOk">0</div><div class="stat-card__extra">现存数量 ≥ 安全库存</div></div></div>
<div class="stat-card"><div class="stat-card__icon stat-card__icon--orange">⚠️</div><div><div class="stat-card__label">库存偏低</div><div class="stat-card__value" id="kLow">0</div><div class="stat-card__extra">低于安全库存且未缺货</div></div></div>
<div class="stat-card"><div class="stat-card__icon stat-card__icon--red">🚫</div><div><div class="stat-card__label">缺货</div><div class="stat-card__value" id="kOut">0</div><div class="stat-card__extra">现存数量为 0,需补货</div></div></div>
</div>
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>药品名称</label>
<input class="filter-input" id="filterName" placeholder="请输入药品名称">
</div>
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
<option>采油一厂</option>
<option>采油二厂</option>
<option>第四采油厂</option>
<option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="filterPoint"><option value="">全部医疗点</option></select>
</div>
<div class="filter-item" style="gap:6px;">
<label>库存状态</label>
<button class="filter-chip active" data-st="" onclick="pickStatus(this)">全部</button>
<button class="filter-chip" data-st="ok" onclick="pickStatus(this)">充足</button>
<button class="filter-chip" data-st="low" onclick="pickStatus(this)">偏低</button>
<button class="filter-chip" data-st="out" onclick="pickStatus(this)">缺货</button>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('导出实时库存快照功能演示')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出快照
</button>
<button class="btn btn-default" onclick="showToast('已向 3 个缺货医疗点的负责人推送补货提醒')">一键催办缺货补货</button>
<span class="action-bar__hint">数据口径:每日 0 时快照 + 实时出入库增量</span>
</div>
<!-- 表格 -->
<div class="table-card">
<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><th>最近出库</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 出入库流水抽屉(页内 overlay 实现) -->
<div class="drawer-mask" id="flowMask" onclick="closeDrawer('flow')"></div>
<div class="drawer" id="flowDrawer">
<div class="drawer__header"><span id="flowTitle">出入库流水</span><button class="drawer__close" onclick="closeDrawer('flow')">×</button></div>
<div class="drawer__body">
<div class="flow-summary" id="flowSummary"></div>
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>时间</th><th>类型</th><th>数量</th><th>结存</th><th>操作人</th><th>关联单据</th><th>备注</th></tr></thead>
<tbody id="flowBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('flow')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* 与「药品信息」页保持同一批药品的库存视角:stock=现存 safety=安全库存 */
var stocks=[
{id:1,name:'阿司匹林肠溶片',spec:'100mg×30片/盒',type:'处方药',unit:'采油一厂',point:'樊学作业区医疗点',stock:86,safety:30,sell:23.00,lastIn:'2026-08-21',lastOut:'2026-09-05'},
{id:2,name:'硝苯地平控释片',spec:'30mg×7片/盒',type:'处方药',unit:'采油一厂',point:'樊学作业区医疗点',stock:42,safety:20,sell:33.80,lastIn:'2026-08-21',lastOut:'2026-09-03'},
{id:3,name:'二甲双胍缓释片',spec:'0.5g×20片/盒',type:'处方药',unit:'采油一厂',point:'安塞采油队长住点医疗室',stock:12,safety:20,sell:26.00,lastIn:'2026-07-18',lastOut:'2026-09-06'},
{id:4,name:'硝酸甘油片',spec:'0.5mg×100片/瓶',type:'处方药',unit:'采油一厂',point:'姬塬作业区医疗点',stock:24,safety:10,sell:12.50,lastIn:'2026-08-30',lastOut:'2026-09-01'},
{id:5,name:'布洛芬缓释胶囊',spec:'0.3g×20粒/盒',type:'OTC甲类',unit:'采油二厂',point:'姬圪台作业区医疗点',stock:130,safety:40,sell:16.00,lastIn:'2026-08-25',lastOut:'2026-09-07'},
{id:6,name:'蒙脱石散',spec:'3g×10袋/盒',type:'OTC甲类',unit:'采油二厂',point:'姬圪台作业区医疗点',stock:15,safety:25,sell:18.50,lastIn:'2026-07-15',lastOut:'2026-09-04'},
{id:7,name:'藿香正气水',spec:'10ml×10支/盒',type:'OTC乙类',unit:'采油二厂',point:'桐园作业区医疗点',stock:0,safety:20,sell:10.80,lastIn:'2026-06-02',lastOut:'2026-08-28'},
{id:8,name:'复合丹参滴丸',spec:'40mg×180丸/瓶',type:'中成药',unit:'第四采油厂',point:'西峰基地医疗点',stock:64,safety:25,sell:27.50,lastIn:'2026-08-19',lastOut:'2026-09-06'},
{id:9,name:'阿莫西林胶囊',spec:'0.25g×24粒/盒',type:'处方药',unit:'第四采油厂',point:'西峰基地医疗点',stock:52,safety:30,sell:9.20,lastIn:'2026-08-12',lastOut:'2026-09-05'},
{id:10,name:'0.9%氯化钠注射液',spec:'250ml:2.25g',type:'处方药',unit:'采气一厂',point:'靖边气田医疗点',stock:320,safety:100,sell:3.80,lastIn:'2026-09-02',lastOut:'2026-09-07'},
{id:11,name:'碘伏棉签',spec:'50支/袋',type:'医用耗材',unit:'采气一厂',point:'靖边气田医疗点',stock:18,safety:20,sell:6.00,lastIn:'2026-08-08',lastOut:'2026-09-06'},
{id:12,name:'医用弹性绷带',spec:'8cm×4.5m',type:'医用耗材',unit:'采气一厂',point:'乌审旗采气区医疗点',stock:29,safety:10,sell:7.50,lastIn:'2026-08-16',lastOut:'2026-09-02'}
];
/* 流水演示:按类型生成(入库为正、出库/退药为负) */
function genFlows(m){
var bal=m.stock,flows=[];
var seed=[['2026-09-07 09:12','出库',-6,'就诊发药 · RK20260907003','石维国'],
['2026-09-05 15:40','出库',-4,'就诊发药 · RK20260905011','曲志明'],
['2026-09-03 10:05','退药',2,'处方退回 · TK20260903002','李桂芳'],
[m.lastIn+' 08:30','入库',30,'采购入库 · RK2026'+(100+m.id)+'001','闫婷']];
seed.forEach(function(f){
flows.push({t:f[0],type:f[1],n:f[2],after:bal,bill:f[3],by:f[4]});
bal=bal-f[2];
});
return flows;
}
var curStatus='';
/* ===== 级联选项 ===== */
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function pickStatus(el){
document.querySelectorAll('.filter-chip').forEach(function(c){c.classList.remove('active');});
el.classList.add('active');
curStatus=el.getAttribute('data-st');
}
function statOf(m){return m.stock===0?'out':(m.stock<m.safety?'low':'ok');}
/* ===== 概览卡 ===== */
function renderStats(){
var ok=0,low=0,out=0;
stocks.forEach(function(m){var s=statOf(m);if(s==='ok')ok++;else if(s==='low')low++;else out++;});
document.getElementById('kTotal').textContent=stocks.length;
document.getElementById('kOk').textContent=ok;
document.getElementById('kLow').textContent=low;
document.getElementById('kOut').textContent=out;
}
/* ===== 列表渲染 ===== */
var pageSize=10,currentPage=1,filtered=stocks.slice();
function renderTable(){
var tbody=document.getElementById('tableBody');
var start=(currentPage-1)*pageSize,pageData=filtered.slice(start,start+pageSize);
var totalPages=Math.ceil(filtered.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+filtered.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
if(!filtered.length){tbody.innerHTML='<tr><td colspan="14" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(m,i){
var s=statOf(m);
var pct=Math.min(100,Math.round(m.stock/(m.safety*2)*100));
var color=s==='out'?'#FF4D4F':(s==='low'?'#FAAD14':'#52C41A');
var badge=s==='out'?'<span class="badge badge--danger">缺货</span>':(s==='low'?'<span class="badge badge--warning">偏低</span>':'<span class="badge badge--success">充足</span>');
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="text-align:left;font-weight:600;">'+m.name+'</td>'
+'<td>'+m.spec+'</td>'
+'<td><span class="badge badge--info">'+m.type+'</span></td>'
+'<td>'+m.unit+'</td><td>'+m.point+'</td>'
+'<td><span class="stock-bar"><span class="stock-bar__num">'+m.stock+'</span><span class="stock-bar__track"><span class="stock-bar__fill" style="width:'+pct+'%;background:'+color+';"></span></span></span></td>'
+'<td class="td-num">'+m.safety+'</td>'
+'<td>'+badge+'</td>'
+'<td class="td-num">'+m.sell.toFixed(2)+'</td>'
+'<td class="td-num">'+(m.stock*m.sell).toFixed(2)+'</td>'
+'<td>'+m.lastIn+'</td><td>'+m.lastOut+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openFlow('+m.id+')">查看流水</button></td>'
+'</tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
filtered=stocks.filter(function(m){
return (!name||m.name.indexOf(name)!==-1)&&(!unit||m.unit===unit)&&(!point||m.point===point)&&(!curStatus||statOf(m)===curStatus);
});
currentPage=1;renderTable();
}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';
onFilterUnit();
document.querySelectorAll('.filter-chip').forEach(function(c,i){c.classList.toggle('active',i===0);});
curStatus='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(filtered.length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 流水抽屉 ===== */
function openFlow(id){
var m=stocks.find(function(x){return x.id===id;});
document.getElementById('flowTitle').textContent='出入库流水 · '+m.name+' · '+m.point;
document.getElementById('flowSummary').innerHTML=
'<div><span class="k">现存数量</span><span class="v">'+m.stock+'</span></div>'
+'<div><span class="k">安全库存</span><span class="v">'+m.safety+'</span></div>'
+'<div><span class="k">单位售价</span><span class="v">'+m.sell.toFixed(2)+' 元</span></div>'
+'<div><span class="k">库存金额</span><span class="v">'+(m.stock*m.sell).toFixed(2)+' 元</span></div>';
document.getElementById('flowBody').innerHTML=genFlows(m).map(function(f){
return '<tr><td>'+f.t+'</td>'
+'<td><span class="badge '+(f.type==='出库'?'badge--danger':(f.type==='退药'?'badge--warning':'badge--success'))+'">'+f.type+'</span></td>'
+'<td class="'+(f.n>0?'in':'out')+'">'+(f.n>0?'+':'')+f.n+'</td>'
+'<td class="td-num">'+f.after+'</td>'
+'<td>'+f.by+'</td><td>'+f.bill+'</td>'
+'<td style="text-align:left;">'+(f.type==='出库'?'门诊/巡诊发药':(f.type==='退药'?'患者退药':'采购到货入库'))+'</td></tr>';
}).join('');
document.getElementById('flowMask').classList.add('open');
document.getElementById('flowDrawer').classList.add('open');
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderStats();renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,578 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>药品信息 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:140px;}
.filter-input:focus{border-color:var(--primary);}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-danger{background:#fff;color:var(--danger);border:1px solid #ffccc7;}.btn-danger:hover{background:#fff1f0;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 表格 */
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1900px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.td-num{font-variant-numeric:tabular-nums;}
/* 徽标 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:var(--primary-light);color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 分页 */
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 表单 */
.form-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);padding:8px 0;border-left:3px solid var(--primary);padding-left:8px;margin-bottom:12px;}
.form-section__title:not(:first-child){margin-top:20px;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 24px;}
.form-item{display:flex;flex-direction:column;gap:6px;}
.form-item--full{grid-column:1 / -1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.upload-row{display:flex;gap:8px;align-items:center;}
.upload-box{width:80px;height:80px;border:1px dashed var(--border);border-radius:var(--radius-sm);display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;gap:2px;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);}
.img-ph{width:80px;height:80px;border-radius:var(--radius-sm);background:linear-gradient(135deg,#E6F7FF,#BAE7FF);display:flex;align-items:center;justify-content:center;font-size:22px;}
/* 只读详情 */
.desc-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 24px;}
.desc-item{display:flex;padding:10px 0;border-bottom:1px solid #F0F0F0;font-size:var(--font-sm);}
.desc-item--full{grid-column:1 / -1;}
.desc-item__label{width:110px;color:var(--text-secondary);flex-shrink:0;}
.desc-item__value{color:var(--text-primary);}
/* 确认弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:20px;line-height:1.6;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;}
.modal .form-item{margin-bottom:14px;}
.drop-zone{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:24px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
</style>
</head>
<body>
<!-- 顶部导航栏 -->
<div class="top-bar">
<div class="top-bar__logo"><svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item active">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位→医疗点级联 -->
<div class="filter-bar">
<div class="filter-item">
<label>药品名称</label>
<input class="filter-input" id="filterName" placeholder="请输入药品名称">
</div>
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
<option>采油一厂</option>
<option>采油二厂</option>
<option>第四采油厂</option>
<option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="filterPoint">
<option value="">全部医疗点</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏:新增/批量删除/批量导入/模板下载/导出 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm('新增药品')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增
</button>
<button class="btn btn-default" onclick="batchDelete()">批量删除</button>
<button class="btn btn-default" onclick="openImport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 10V2M4.5 4.5L7 2l2.5 2.5M2 11h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入
</button>
<button class="btn btn-export" onclick="showToast('下载导入模板:药品信息导入模板.xlsx')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>模板下载
</button>
<button class="btn btn-export" onclick="showToast('导出药品列表功能演示')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出
</button>
<span class="action-bar__hint">药品目录与价格由各医疗点维护,库存变动以「实时库存」为准</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th>
<th>序号</th><th>药品名称</th><th>别名</th><th>所属单位</th><th>所属医疗点</th><th>药品单位</th><th>药品规格</th><th>药品类别</th><th>说明书</th><th>入库日期</th><th>现存数量</th><th>进价(元)</th><th>售价(元)</th><th>售出数量</th><th>销售金额(元)</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑抽屉(页内 overlay 实现) -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增药品</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section__title">归属信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="fUnit" onchange="onFormUnit()"><option value="">请选择所属单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="fPoint"><option value="">请先选择所属单位</option></select></div>
</div>
<div class="form-section__title">药品信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 药品名称</span><input class="form-control" id="fName" placeholder="请输入药品名称"></div>
<div class="form-item"><span class="form-label">别名</span><input class="form-control" id="fAlias" placeholder="请输入别名(选填)"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 药品单位</span>
<select class="form-control" id="fMUnit"><option></option><option></option><option></option><option></option><option></option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 药品规格</span><input class="form-control" id="fSpec" placeholder="如 0.25g×24粒/盒"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 药品类别</span>
<select class="form-control" id="fType"><option>处方药</option><option>OTC甲类</option><option>OTC乙类</option><option>中成药</option><option>医用耗材</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 入库日期</span><input class="form-control" id="fDate" type="date"></div>
</div>
<div class="form-section__title">库存与价格</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 现存数量</span><input class="form-control" id="fStock" type="number" min="1" placeholder="请输入大于0的数字"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 进价(元)</span><input class="form-control" id="fBuy" type="number" min="0.01" step="0.01" placeholder="请输入大于0的数字"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 售价(元)</span><input class="form-control" id="fSell" type="number" min="0.01" step="0.01" placeholder="请输入大于0的数字"></div>
<div class="form-item"><span class="form-label">售出数量</span><input class="form-control" id="fSold" type="number" min="0" placeholder="累计售出"></div>
<div class="form-item"><span class="form-label">销售金额(元)</span><input class="form-control" id="fMoney" type="number" min="0" placeholder="自动=售价×售出数量,可改"></div>
</div>
<div class="form-section__title">说明书</div>
<div class="form-item form-item--full"><span class="form-label">药品说明书图片(支持批量上传,.png/.jpg,最多10张)</span>
<div class="upload-row"><div class="img-ph">📄</div><div class="upload-box" onclick="showToast('演示环境暂不支持上传')"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 4v10M4 9h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>上传</div></div></div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 说明书查看抽屉(只读) -->
<div class="drawer-mask" id="specMask" onclick="closeDrawer('spec')"></div>
<div class="drawer drawer--narrow" id="specDrawer">
<div class="drawer__header"><span id="specTitle">药品说明书</span><button class="drawer__close" onclick="closeDrawer('spec')">×</button></div>
<div class="drawer__body">
<div class="upload-row" style="margin-bottom:16px;">
<div class="img-ph" style="width:150px;height:150px;">📄</div>
<div class="img-ph" style="width:150px;height:150px;background:linear-gradient(135deg,#F0F5FF,#D6E4FF);">📄</div>
</div>
<div class="desc-grid" id="specBody"></div>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('spec')">关闭</button></div>
</div>
<!-- [交互] 批量导入弹窗 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 14V3M6.5 6.5L10 3l3.5 3.5M3 16h14" stroke="#1890FF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg> 批量导入药品</div>
<div class="modal__text" style="margin-bottom:14px;">请先选择归属,再上传按模板整理的 Excel 文件。</div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="iUnit" onchange="onImportUnit()"><option value="">请选择所属单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="iPoint"><option value="">请先选择所属单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 文件导入</span>
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" style="display:block;margin:0 auto 6px;"><path d="M13 17V5M8.5 9.5L13 5l4.5 4.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 20h18" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
点击选择或拖拽文件到此处上传<br>药品信息导入模板.xlsx
</div></div>
<div class="modal__footer" style="margin-top:20px;">
<button class="btn btn-default" onclick="closeModal('importMask')">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="delText">确认删除该药品?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var meds=[
{id:1,name:'阿司匹林肠溶片',alias:'拜阿司匹灵',unit:'采油一厂',point:'樊学作业区医疗点',mUnit:'盒',spec:'100mg×30片/盒',type:'处方药',date:'2026-06-12',stock:86,buy:18.50,sell:23.00,sold:120,money:2760},
{id:2,name:'硝苯地平控释片',alias:'拜新同',unit:'采油一厂',point:'樊学作业区医疗点',mUnit:'盒',spec:'30mg×7片/盒',type:'处方药',date:'2026-06-12',stock:42,buy:27.60,sell:33.80,sold:58,money:1960.40},
{id:3,name:'二甲双胍缓释片',alias:'格华止',unit:'采油一厂',point:'安塞采油队长住点医疗室',mUnit:'盒',spec:'0.5g×20片/盒',type:'处方药',date:'2026-05-28',stock:18,buy:21.20,sell:26.00,sold:74,money:1924},
{id:4,name:'硝酸甘油片',alias:'',unit:'采油一厂',point:'姬塬作业区医疗点',mUnit:'瓶',spec:'0.5mg×100片/瓶',type:'处方药',date:'2026-07-03',stock:24,buy:9.80,sell:12.50,sold:36,money:450},
{id:5,name:'布洛芬缓释胶囊',alias:'芬必得',unit:'采油二厂',point:'姬圪台作业区医疗点',mUnit:'盒',spec:'0.3g×20粒/盒',type:'OTC甲类',date:'2026-06-20',stock:130,buy:12.40,sell:16.00,sold:210,money:3360},
{id:6,name:'蒙脱石散',alias:'思密达',unit:'采油二厂',point:'姬圪台作业区医疗点',mUnit:'袋',spec:'3g×10袋/盒',type:'OTC甲类',date:'2026-07-15',stock:95,buy:14.60,sell:18.50,sold:88,money:1628},
{id:7,name:'藿香正气水',alias:'',unit:'采油二厂',point:'桐园作业区医疗点',mUnit:'盒',spec:'10ml×10支/盒',type:'OTC乙类',date:'2026-06-02',stock:0,buy:8.20,sell:10.80,sold:150,money:1620},
{id:8,name:'复合丹参滴丸',alias:'天士力',unit:'第四采油厂',point:'西峰基地医疗点',mUnit:'瓶',spec:'40mg×180丸/瓶',type:'中成药',date:'2026-05-16',stock:64,buy:22.00,sell:27.50,sold:96,money:2640},
{id:9,name:'阿莫西林胶囊',alias:'再林',unit:'第四采油厂',point:'西峰基地医疗点',mUnit:'盒',spec:'0.25g×24粒/盒',type:'处方药',date:'2026-07-01',stock:52,buy:6.80,sell:9.20,sold:143,money:1315.60},
{id:10,name:'0.9%氯化钠注射液',alias:'生理盐水',unit:'采气一厂',point:'靖边气田医疗点',mUnit:'袋',spec:'250ml:2.25g',type:'处方药',date:'2026-07-22',stock:320,buy:2.60,sell:3.80,sold:410,money:1558},
{id:11,name:'碘伏棉签',alias:'',unit:'采气一厂',point:'靖边气田医疗点',mUnit:'袋',spec:'50支/袋',type:'医用耗材',date:'2026-07-22',stock:76,buy:4.50,sell:6.00,sold:64,money:384},
{id:12,name:'医用弹性绷带',alias:'',unit:'采气一厂',point:'乌审旗采气区医疗点',mUnit:'卷',spec:'8cm×4.5m',type:'医用耗材',date:'2026-06-08',stock:29,buy:5.20,sell:7.50,sold:41,money:307.50}
];
var editId=null,delIds=[];
/* ===== 级联选项 ===== */
function fillPoints(sel,unit,keepVal){
var prev=keepVal||sel.value;
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){
sel.innerHTML='<option value="">请选择医疗点</option>';
(pointMap[unit]||[]).forEach(function(p){
var o=document.createElement('option');o.textContent=p;sel.appendChild(o);
});
if(prev)sel.value=prev;
}
}
function onFilterUnit(){fillPoints(document.getElementById('filterPoint'),document.getElementById('filterUnit').value);}
function onFormUnit(){
var unit=document.getElementById('fUnit').value;
var sel=document.getElementById('fPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function onImportUnit(){
var unit=document.getElementById('iUnit').value;
var sel=document.getElementById('iPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
/* ===== 列表渲染 ===== */
var pageSize=10,currentPage=1,filtered=meds.slice();
function renderTable(){
var tbody=document.getElementById('tableBody');
var start=(currentPage-1)*pageSize,pageData=filtered.slice(start,start+pageSize);
var totalPages=Math.ceil(filtered.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+filtered.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
document.getElementById('checkAll').checked=false;
if(!filtered.length){tbody.innerHTML='<tr><td colspan="17" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(m,i){
var stockBadge=m.stock===0?'<span class="badge badge--danger">缺货</span>':(m.stock<=20?'<span class="badge badge--warning">'+m.stock+'</span>':'<span class="td-num">'+m.stock+'</span>');
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+m.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="text-align:left;font-weight:600;">'+m.name+'</td>'
+'<td>'+(m.alias||'—')+'</td>'
+'<td>'+m.unit+'</td><td>'+m.point+'</td><td>'+m.mUnit+'</td><td>'+m.spec+'</td>'
+'<td><span class="badge badge--info">'+m.type+'</span></td>'
+'<td><button class="btn btn-link" onclick="openSpec('+m.id+')">查看</button></td>'
+'<td>'+m.date+'</td>'
+'<td>'+stockBadge+'</td>'
+'<td class="td-num">'+m.buy.toFixed(2)+'</td>'
+'<td class="td-num">'+m.sell.toFixed(2)+'</td>'
+'<td class="td-num">'+m.sold+'</td>'
+'<td class="td-num">'+m.money.toFixed(2)+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openEdit('+m.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete(['+m.id+'])">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
filtered=meds.filter(function(m){
return (!name||m.name.indexOf(name)!==-1)&&(!unit||m.unit===unit)&&(!point||m.point===point);
});
currentPage=1;renderTable();
}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';
fillPoints(document.getElementById('filterPoint'),'');
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(filtered.length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function batchDelete(){
var ids=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return +c.value;});
if(!ids.length){showToast('请先勾选要删除的药品');return;}
askDelete(ids);
}
/* ===== 新增/编辑抽屉 ===== */
function openForm(title){
document.getElementById('formTitle').textContent=title;
editId=null;
['fName','fAlias','fSpec','fDate','fStock','fBuy','fSell','fSold','fMoney'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('fUnit').value='';
onFormUnit();
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function openEdit(id){
var m=meds.find(function(x){return x.id===id;});
openForm('编辑药品');editId=id;
document.getElementById('fUnit').value=m.unit;
onFormUnit();
document.getElementById('fPoint').value=m.point;
document.getElementById('fName').value=m.name;
document.getElementById('fAlias').value=m.alias;
document.getElementById('fMUnit').value=m.mUnit;
document.getElementById('fSpec').value=m.spec;
document.getElementById('fType').value=m.type;
document.getElementById('fDate').value=m.date;
document.getElementById('fStock').value=m.stock;
document.getElementById('fBuy').value=m.buy;
document.getElementById('fSell').value=m.sell;
document.getElementById('fSold').value=m.sold;
document.getElementById('fMoney').value=m.money;
}
function saveForm(){
var unit=document.getElementById('fUnit').value;
if(!unit){showToast('请选择所属单位');return;}
if(!document.getElementById('fPoint').value){showToast('请选择所属医疗点');return;}
var name=document.getElementById('fName').value.trim();
if(!name){showToast('请填写药品名称');return;}
showToast(editId?('药品「'+name+'」已保存'):('药品「'+name+'」已创建'));
closeDrawer('form');
}
/* ===== 说明书查看 ===== */
function openSpec(id){
var m=meds.find(function(x){return x.id===id;});
document.getElementById('specTitle').textContent='药品说明书 · '+m.name;
document.getElementById('specBody').innerHTML=
row('药品名称',m.name)+row('别名',m.alias||'—')
+row('规格',m.spec)+row('单位',m.mUnit)
+row('类别',m.type)+row('入库日期',m.date)
+'<div class="desc-item desc-item--full"><span class="desc-item__label">用法用量</span><span class="desc-item__value">演示说明书内容:口服,一次 1 片,一日 1 次,或遵医嘱。</span></div>'
+'<div class="desc-item desc-item--full"><span class="desc-item__label">禁忌与贮藏</span><span class="desc-item__value">对本品过敏者禁用;密封、阴凉干燥处保存。</span></div>';
document.getElementById('specMask').classList.add('open');
document.getElementById('specDrawer').classList.add('open');
}
function row(l,v){return '<div class="desc-item"><span class="desc-item__label">'+l+'</span><span class="desc-item__value">'+v+'</span></div>';}
/* ===== 批量导入 ===== */
function openImport(){
document.getElementById('iUnit').value='';
onImportUnit();
document.getElementById('importMask').classList.add('open');
}
function doImport(){
if(!document.getElementById('iUnit').value){showToast('请选择所属单位');return;}
if(!document.getElementById('iPoint').value){showToast('请选择所属医疗点');return;}
closeModal('importMask');
showToast('导入任务已提交,完成后将推送站内通知');
}
/* ===== 删除确认 ===== */
function askDelete(ids){
delIds=ids;
document.getElementById('delText').textContent=ids.length>1
?('确认删除勾选的 '+ids.length+' 条药品记录?删除后其出库、就诊关联数据将不再显示该药品。')
:('确认删除「'+meds.find(function(x){return x.id===ids[0];}).name+'」?删除后其出库、就诊关联数据将不再显示该药品。');
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
meds=meds.filter(function(x){return delIds.indexOf(x.id)===-1;});
closeModal('delMask');applyFilter();showToast('已删除 '+delIds.length+' 条药品记录');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,483 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>合作医院 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
/* 顶部导航栏 */
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
/* 侧边栏 */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* 主内容区 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 提示条 */
.tips-note{display:flex;gap:8px;background:var(--primary-light);border:1px solid #91D5FF;border-radius:var(--radius-sm);padding:8px 12px;font-size:var(--font-xs);color:#096DD9;margin-bottom:16px;}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:140px;}
.filter-input:focus{border-color:var(--primary);}
/* 按钮 */
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
/* 表格 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1200px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
/* 徽标 */
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--info{background:var(--primary-light);color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.img-ph{width:50px;height:50px;border-radius:var(--radius-sm);background:linear-gradient(135deg,#E6F7FF,#BAE7FF);display:inline-flex;align-items:center;justify-content:center;font-size:20px;}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
/* 抽屉 */
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 表单 */
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
/* 内嵌小表格 */
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;}
.mini-table td{white-space:normal;}
.drawer__toolbar{display:flex;align-items:center;gap:8px;margin-bottom:14px;}
.drawer__toolbar .grow{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
/* 确认弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__text{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:20px;line-height:1.6;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;}
/* 选择弹窗(管辖医疗点) */
.modal--wide{width:760px;max-height:80vh;display:flex;flex-direction:column;}
.modal--wide .filter-bar{margin-bottom:14px;border-radius:var(--radius-sm);box-shadow:none;padding:0;display:flex;}
.modal--wide .table-holder{flex:1;overflow-y:auto;border:1px solid var(--border);border-radius:var(--radius-sm);}
.modal--wide .mini-table{border:none;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item active">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<div class="tips-note">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="flex-shrink:0;margin-top:1px;"><circle cx="7" cy="7" r="6" stroke="currentColor" stroke-width="1.2"/><path d="M7 6.2v4M7 3.6v1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
医院名称、级别、简介、地址、主图等主信息在「系统管理 → 资源库」中维护,本页仅维护管辖医疗点与医院管理员两项扩展信息。
</div>
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>医院名称</label>
<input class="filter-input" id="filterName" placeholder="请输入医院名称">
</div>
<div class="filter-item">
<label>医院级别</label>
<select class="filter-select" id="filterLevel">
<option value="">全部级别</option>
<option>三级甲等</option>
<option>二级甲等</option>
<option>一级</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th>序号</th><th>医院名称</th><th>医院级别</th><th>医院主图</th><th>详细地址</th><th>管辖医疗点</th><th>医院管理员</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
</main>
<!-- [交互] 管辖范围抽屉(当前医院已管辖医疗点 + 添加) -->
<div class="drawer-mask" id="jurMask" onclick="closeDrawer('jur')"></div>
<div class="drawer" id="jurDrawer">
<div class="drawer__header"><span id="jurTitle">管辖范围</span><button class="drawer__close" onclick="closeDrawer('jur')">×</button></div>
<div class="drawer__body">
<div class="drawer__toolbar">
<button class="btn btn-primary" onclick="openPick()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>添加医疗点
</button>
<span class="grow">医疗点归属唯一合作医院,添加后将从可选列表中移除</span>
</div>
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>医疗点名称</th><th>所属单位</th><th>医疗点地址</th><th>负责人</th><th>设立时间</th><th>操作</th></tr></thead>
<tbody id="jurBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('jur')">关闭</button></div>
</div>
<!-- [交互] 添加管辖医疗点选择弹窗 -->
<div class="modal-mask" id="pickMask">
<div class="modal modal--wide">
<div class="modal__title">选择医疗点 · 添加至「<span id="pickHospital"></span></div>
<div class="filter-bar">
<div class="filter-item">
<label>医疗点名称</label>
<input class="filter-input" id="pickName" placeholder="请输入医疗点名称" style="min-width:120px;" oninput="renderPick()">
</div>
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="pickUnit" onchange="renderPick()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
</div>
<div class="table-holder">
<table class="mini-table">
<thead><tr><th style="width:40px"><input type="checkbox" onchange="pickAll(this)"></th><th>医疗点名称</th><th>医疗点地址</th><th>所属单位</th><th>负责人</th><th>设立时间</th></tr></thead>
<tbody id="pickBody"></tbody>
</table>
</div>
<div class="modal__footer" style="margin-top:16px;">
<button class="btn btn-default" onclick="closeModal('pickMask')">取消</button>
<button class="btn btn-primary" onclick="confirmPick()">确认添加</button>
</div>
</div>
</div>
<!-- [交互] 医院管理员抽屉 -->
<div class="drawer-mask" id="adminMask" onclick="closeDrawer('admin')"></div>
<div class="drawer" id="adminDrawer" style="width:640px;right:-680px;">
<div class="drawer__header"><span id="adminTitle">医院管理员</span><button class="drawer__close" onclick="closeDrawer('admin')">×</button></div>
<div class="drawer__body">
<div class="tips-note">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="flex-shrink:0;margin-top:1px;"><circle cx="7" cy="7" r="6" stroke="currentColor" stroke-width="1.2"/><path d="M7 6.2v4M7 3.6v1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
管理员账号用于医院侧用户登录协作端(如报告解读复审),可在本模块直接创建。
</div>
<div class="drawer__toolbar">
<button class="btn btn-primary" onclick="toggleAdminForm(true)">新增管理员</button>
</div>
<!-- [交互] 新增管理员表单(页内展开) -->
<div id="adminForm" style="display:none;border:1px solid var(--border);border-radius:var(--radius-sm);padding:16px;margin-bottom:14px;background:#FAFCFF;">
<div class="form-item"><span class="form-label"><span class="req">*</span> 账号(仅字母数字下划线)</span><input class="form-control" id="aUser" placeholder="如 hospital_admin01"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 姓名</span><input class="form-control" id="aName" placeholder="请输入姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 初始密码</span><input class="form-control" id="aPwd" type="password" placeholder="不少于8位,含大小写字母与数字"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 确认密码</span><input class="form-control" id="aPwd2" type="password" placeholder="请再次输入密码"></div>
<div style="display:flex;justify-content:flex-end;gap:8px;">
<button class="btn btn-default" onclick="toggleAdminForm(false)">取消</button>
<button class="btn btn-primary" onclick="saveAdmin()">保存</button>
</div>
</div>
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>账号</th><th>姓名</th><th>创建时间</th><th>操作</th></tr></thead>
<tbody id="adminBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('admin')">关闭</button></div>
</div>
<!-- [交互] 移除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 移除确认</div>
<div class="modal__text" id="delText">确认移除?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmRemove()">确认</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
/* 医疗点池:与医疗点配置页保持一致;hospital=null 表示尚未归属 */
var points=[
{id:1,name:'樊学作业区医疗点',addr:'CQ油田采油一厂樊学作业区基地东侧',unit:'采油一厂',leader:'石维国',found:'2021-06-18',hospital:'CQ油田总医院'},
{id:2,name:'姬圪台作业区医疗点',addr:'采油二厂姬圪台联合站生活区1号',unit:'采油二厂',leader:'曲志明',found:'2020-09-02',hospital:'CQ油田总医院'},
{id:3,name:'西峰基地医疗点',addr:'第四采油厂西峰基地综合服务区2层',unit:'第四采油厂',leader:'高建军',found:'2019-04-26',hospital:'第四采油厂总医院'},
{id:4,name:'靖边气田医疗点',addr:'采气一厂靖边净化厂生活小区旁',unit:'采气一厂',leader:'刘玉梅',found:'2022-03-15',hospital:'CQ油田总医院'},
{id:5,name:'桐园作业区医疗点',addr:'采油二厂桐园作业区营地西侧',unit:'采油二厂',leader:'赵万福',found:'2021-11-08',hospital:'环县油田医院'},
{id:6,name:'安塞采油队长住点医疗室',addr:'采油一厂安塞基地综合公寓1层',unit:'采油一厂',leader:'马进良',found:'2023-05-20',hospital:'吴起油田医院'},
{id:7,name:'姬塬作业区医疗点',addr:'采油三厂筹备组姬塬先导试验区板房区',unit:'采油一厂',leader:'杨慧敏',found:'2024-02-27',hospital:null},
{id:8,name:'乌审旗采气区医疗点',addr:'采气一厂苏里格南作业区营地',unit:'采气一厂',leader:'徐立成',found:'2020-12-11',hospital:'庆阳市人民医院'}
];
var hospitals=[
{id:1,name:'CQ油田总医院',level:'三级甲等',addr:'西峰区长庆北路1号',admins:[['hgt_admin','石维国'],['hgt_nurse01','李桂芳']]},
{id:2,name:'第四采油厂总医院',level:'二级甲等',addr:'西峰区石油西路22号',admins:[['sgyy_admin','高建军']]},
{id:3,name:'环县油田医院',level:'二级甲等',addr:'环县环城镇新区大街8号',admins:[['hx_admin','赵万福']]},
{id:4,name:'吴起油田医院',level:'一级',addr:'吴起县红旗街西段',admins:[]},
{id:5,name:'庆阳市人民医院',level:'三级甲等',addr:'西峰区兰州路456号',admins:[['qyrm_admin','徐立成']]}
];
var curHosp=null,pendingRemove=null;
/* ===== 列表渲染 ===== */
function jurCount(h){return points.filter(function(p){return p.hospital===h;}).length;}
function renderTable(){
var name=document.getElementById('filterName').value.trim();
var level=document.getElementById('filterLevel').value;
var list=hospitals.filter(function(h){return (!name||h.name.indexOf(name)!==-1)&&(!level||h.level===level);});
var tbody=document.getElementById('tableBody');
if(!list.length){tbody.innerHTML='<tr><td colspan="8" class="td-empty">暂无匹配数据</td></tr>';return;}
var lvBadge=h.level==='三级甲等'?'<span class="badge badge--danger">三级甲等</span>':(h.level==='二级甲等'?'<span class="badge badge--info">二级甲等</span>':'<span class="badge badge--muted">'+h.level+'</span>');
var html='';
list.forEach(function(h,i){
var adminNames=h.admins.length?h.admins.map(function(a){return a[1];}).join('、'):'<span style="color:var(--text-placeholder);">未设置</span>';
html+='<tr>'
+'<td>'+(i+1)+'</td>'
+'<td style="text-align:left;font-weight:600;">'+h.name+'</td>'
+'<td>'+lvBadge+'</td>'
+'<td><span class="img-ph">🏥</span></td>'
+'<td style="text-align:left;max-width:240px;overflow:hidden;text-overflow:ellipsis;">'+h.addr+'</td>'
+'<td><button class="btn btn-link" onclick="openJur('+h.id+')">'+jurCount(h.name)+' 个医疗点</button></td>'
+'<td style="text-align:left;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:normal;">'+adminNames+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openJur('+h.id+')">管辖范围</button>'
+'<button class="btn btn-link" onclick="openAdmin('+h.id+')">管理员</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){renderTable();}
function resetFilter(){document.getElementById('filterName').value='';document.getElementById('filterLevel').value='';renderTable();}
/* ===== 管辖范围抽屉 ===== */
function hospById(id){return hospitals.find(function(h){return h.id===id;});}
function openJur(id){
curHosp=hospById(id);
document.getElementById('jurTitle').textContent='管辖范围 · '+curHosp.name;
renderJur();
document.getElementById('jurMask').classList.add('open');
document.getElementById('jurDrawer').classList.add('open');
}
function renderJur(){
var rows=points.filter(function(p){return p.hospital===curHosp.name;});
document.getElementById('jurBody').innerHTML=rows.length?rows.map(function(p){
return '<tr><td style="font-weight:600;">'+p.name+'</td><td>'+p.unit+'</td><td>'+p.addr+'</td><td>'+p.leader+'</td><td>'+p.found+'</td>'
+'<td><button class="btn btn-link btn-link--danger" onclick="askRemove(\'point\',\''+p.id+'\')">移除管辖</button></td></tr>';
}).join(''):'<tr><td colspan="6" class="td-empty">该医院暂未管辖医疗点</td></tr>';
}
function openPick(){
document.getElementById('pickHospital').textContent=curHosp.name;
document.getElementById('pickName').value='';
document.getElementById('pickUnit').value='';
renderPick();
document.getElementById('pickMask').classList.add('open');
}
function renderPick(){
var name=document.getElementById('pickName').value.trim();
var unit=document.getElementById('pickUnit').value;
var rows=points.filter(function(p){return !p.hospital&&(!name||p.name.indexOf(name)!==-1)&&(!unit||p.unit===unit);});
document.getElementById('pickBody').innerHTML=rows.length?rows.map(function(p){
return '<tr><td><input type="checkbox" class="pick-check" value="'+p.id+'"></td>'
+'<td style="font-weight:600;">'+p.name+'</td><td>'+p.addr+'</td><td>'+p.unit+'</td><td>'+p.leader+'</td><td>'+p.found+'</td></tr>';
}).join(''):'<tr><td colspan="6" class="td-empty">没有可分配的医疗点</td></tr>';
}
function pickAll(el){document.querySelectorAll('.pick-check').forEach(function(c){c.checked=el.checked;});}
function confirmPick(){
var ids=[].slice.call(document.querySelectorAll('.pick-check:checked')).map(function(c){return +c.value;});
if(!ids.length){showToast('请先勾选医疗点');return;}
ids.forEach(function(id){points.find(function(p){return p.id===id;}).hospital=curHosp.name;});
closeModal('pickMask');renderJur();renderTable();
showToast('已添加 '+ids.length+' 个管辖医疗点');
}
function askRemove(kind,val){
pendingRemove={kind:kind,val:val};
document.getElementById('delText').textContent=kind==='point'
?('确认将该医疗点从「'+curHosp.name+'」管辖范围移除?移除后该医疗点需重新指定归属医院。')
:('确认移除管理员账号「'+pendingRemove.val+'」?其历史操作记录保留。');
document.getElementById('delMask').classList.add('open');
}
function confirmRemove(){
if(pendingRemove.kind==='point'){
points.find(function(p){return p.id===+pendingRemove.val;}).hospital=null;
renderJur();renderTable();showToast('医疗点已移除管辖');
}else{
curHosp.admins=curHosp.admins.filter(function(a){return a[0]!==pendingRemove.val;});
renderAdmin();renderTable();showToast('管理员已移除');
}
closeModal('delMask');
}
/* ===== 医院管理员抽屉 ===== */
function openAdmin(id){
curHosp=hospById(id);
document.getElementById('adminTitle').textContent='医院管理员 · '+curHosp.name;
toggleAdminForm(false);
renderAdmin();
document.getElementById('adminMask').classList.add('open');
document.getElementById('adminDrawer').classList.add('open');
}
function renderAdmin(){
document.getElementById('adminBody').innerHTML=curHosp.admins.length?curHosp.admins.map(function(a){
return '<tr><td>'+a[0]+'</td><td>'+a[1]+'</td><td>2026-0'+((a[1].length%8)+1)+'-1'+(a[0].length%9)+'</td>'
+'<td><button class="btn btn-link btn-link--danger" onclick="askRemove(\'admin\',\''+a[0]+'\')">移除</button></td></tr>';
}).join(''):'<tr><td colspan="4" class="td-empty">暂未设置管理员</td></tr>';
}
function toggleAdminForm(show){
document.getElementById('adminForm').style.display=show?'':'none';
if(show){['aUser','aName','aPwd','aPwd2'].forEach(function(id){document.getElementById(id).value='';});}
}
function saveAdmin(){
var u=document.getElementById('aUser').value.trim(),n=document.getElementById('aName').value.trim();
var p1=document.getElementById('aPwd').value,p2=document.getElementById('aPwd2').value;
if(!u||!n){showToast('请填写账号与姓名');return;}
if(p1.length<8){showToast('初始密码不少于8位');return;}
if(p1!==p2){showToast('两次输入的密码不一致');return;}
curHosp.admins.push([u,n]);
toggleAdminForm(false);renderAdmin();renderTable();
showToast('管理员「'+n+'」已创建');
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,340 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>干预记录 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{margin-bottom:16px;display:flex;align-items:center;gap:12px;}
.page-header__back{display:inline-flex;align-items:center;gap:4px;font-size:var(--font-sm);color:var(--primary);text-decoration:none;padding:4px 10px;border:1px solid var(--primary);border-radius:var(--radius-sm);background:#fff;}
.page-header__back:hover{background:var(--primary-light);}
.emp-card{display:grid;grid-template-columns:repeat(4,1fr);gap:6px 12px;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);padding:14px 18px;margin-bottom:16px;font-size:var(--font-xs);color:var(--text-secondary);box-shadow:var(--shadow-sm);}
.emp-card b{color:var(--text-primary);font-weight:600;}
.tabs-bar{display:flex;gap:4px;background:#fff;padding:8px 12px 0;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.tab-btn{padding:9px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;transition:all .2s;}
.tab-btn:hover{color:var(--primary);}
.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1100px;}
thead th{background:#FAFAFA;padding:8px 10px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.chip{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;margin-right:6px;}
.section-note{padding:12px 24px 0;font-size:var(--font-xs);color:var(--text-placeholder);}
.plan-blocks{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:16px 24px 24px;}
.plan-block{border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;}
.plan-block__title{padding:10px 16px;background:#FAFAFA;font-size:var(--font-sm);font-weight:600;border-bottom:1px solid var(--border);}
.plan-block table{min-width:0;}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item active">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<div class="page-header">
<a class="page-header__back" href="daily-chronic.html">← 返回慢病管理</a>
</div>
<!-- 员工信息卡(由列表页URL参数带入) -->
<div class="emp-card" id="empCard"></div>
<!-- [交互] Tab 切换:就诊/随访/用药/干预计划 -->
<div class="tabs-bar">
<button class="tab-btn active" data-tab="visit" onclick="switchTab('visit')">就诊记录</button>
<button class="tab-btn" data-tab="follow" onclick="switchTab('follow')">随访记录</button>
<button class="tab-btn" data-tab="med" onclick="switchTab('med')">用药记录</button>
<button class="tab-btn" data-tab="plan" onclick="switchTab('plan')">干预计划</button>
</div>
<div class="table-card" id="paneVisit">
<div class="section-note">近 12 个月在医疗点的就诊记录(与「员工就诊」台账同源)</div>
<div class="table-card__content">
<table style="min-width:900px;">
<thead><tr><th>序号</th><th>就诊日期</th><th>就诊医疗点</th><th>接诊医生</th><th>诊断</th><th>处置</th></tr></thead>
<tbody id="visitBody"></tbody>
</table>
</div>
</div>
<div class="table-card" id="paneFollow" style="display:none;">
<div class="section-note">随访执行情况(与「随访管理」台账同源)</div>
<div class="table-card__content">
<table style="min-width:900px;">
<thead><tr><th>序号</th><th>随访日期</th><th>随访医生</th><th>形式</th><th>症状摘要</th><th>血压</th><th>下次随访</th></tr></thead>
<tbody id="followBody"></tbody>
</table>
</div>
</div>
<div class="table-card" id="paneMed" style="display:none;">
<div class="section-note">当前在用与历史用药(处方出库自动关联)</div>
<div class="table-card__content">
<table style="min-width:1000px;">
<thead><tr><th>序号</th><th>药品名称</th><th>规格</th><th>单次剂量</th><th>频次</th><th>开始时间</th><th>结束时间</th><th>状态</th></tr></thead>
<tbody id="medBody"></tbody>
</table>
</div>
</div>
<div class="table-card" id="panePlan" style="display:none;">
<div class="plan-blocks">
<div class="plan-block">
<div class="plan-block__title">一周膳食计划</div>
<table>
<thead><tr><th style="width:64px;">周期</th><th>早餐</th><th>午餐</th><th>晚餐</th></tr></thead>
<tbody id="mealBody"></tbody>
</table>
</div>
<div class="plan-block">
<div class="plan-block__title">运动处方</div>
<table style="min-width:0;">
<thead><tr><th style="width:64px;">序号</th><th>运动项目</th><th>单次时长(分钟)</th><th>频率</th></tr></thead>
<tbody id="sportBody"></tbody>
</table>
<div style="padding:12px 16px;font-size:var(--font-xs);color:var(--text-placeholder);line-height:1.7;" id="planAdvice"></div>
</div>
</div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):干预记录按员工编号组织,与慢病管理/随访/就诊台账勾稽 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var data={
'CQ033096':{name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',type:['高血压'],
visits:[
{date:'2026-09-05',doctor:'曲志明',dx:'高血压2级(高危)',rx:'缬沙坦80mg qd;一周后复测'},
{date:'2026-08-08',doctor:'曲志明',dx:'高血压2级(新诊断)',rx:'启动规范降压治疗,低盐饮食'}],
follows:[
{date:'2026-09-05',doctor:'曲志明',form:'门诊',sym:'头晕较前减轻,血压波动',bp:'146/92',next:'2026-10-05'},
{date:'2026-08-08',doctor:'曲志明',form:'家庭',sym:'首次入组随访,偶有头晕',bp:'158/96',next:'2026-09-05'}],
meds:[
{name:'缬沙坦胶囊',spec:'80mg*7粒',dose:'80mg',freq:'每日1次',start:'2026-08-08',end:'—',using:true},
{name:'苯磺酸氨氯地平片',spec:'5mg*7片',dose:'5mg',freq:'每日1次',start:'2026-08-08',end:'2026-09-05',using:false}],
meals:[
{w:'周一',b:'燕麦粥+水煮蛋',l:'杂粮饭+清蒸鱼+西兰花',d:'蔬菜豆腐汤+凉拌木耳'},
{w:'周二',b:'全麦面包+脱脂奶',l:'糙米饭+鸡胸肉+菠菜',d:'玉米+番茄蛋汤'},
{w:'周三',b:'小米粥+凉拌黄瓜',l:'荞麦面+酱牛肉+焯拌菜',d:'清炒时蔬+杂粮粥'},
{w:'周四~周日',b:'低盐早餐轮换',l:'主食粗细搭配,限盐<5g/日',d:'晚餐少食,睡前不加餐'}],
sports:[{n:'快走',t:30,f:'每周5次'},{n:'游泳',t:40,f:'每周1次'},{n:'工间拉伸',t:10,f:'每个工作日'}],
advice:'减重目标:3个月减重3kg;戒烟限酒;家庭自测血压每周≥3次并记录。'},
'CQ010564':{name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',type:['糖尿病','肥胖'],
visits:[
{date:'2026-09-03',doctor:'闫婷',dx:'2型糖尿病(血糖控制欠佳)',rx:'二甲双胍换缓释剂型;两周复查'},
{date:'2026-07-15',doctor:'闫婷',dx:'2型糖尿病',rx:'调整用药方案,加用监测'}],
follows:[
{date:'2026-09-03',doctor:'闫婷',form:'门诊',sym:'口渴乏力减轻,血糖仍偏高',bp:'132/84',next:'2026-09-17'},
{date:'2026-07-15',doctor:'闫婷',form:'门诊',sym:'血糖波动,调整用药方案',bp:'130/82',next:'2026-09-03'}],
meds:[
{name:'二甲双胍缓释片',spec:'0.5g*30片',dose:'1.0g',freq:'每日2次',start:'2026-09-03',end:'—',using:true},
{name:'格列美脲片',spec:'2mg*30片',dose:'2mg',freq:'每日1次',start:'2026-06-12',end:'2026-09-03',using:false}],
meals:[
{w:'周一',b:'杂粮粥+鸡蛋',l:'控糖餐:糙米饭+鱼+绿叶菜',d:'豆腐蔬菜汤'},
{w:'周二',b:'无糖酸奶+全麦',l:'荞麦饭+鸡胸+菌菇',d:'清炒瓜菜+半碗主食'},
{w:'周三~周日',b:'少食多餐轮换',l:'主食定量(生重75g/餐)',d:'晚餐18点前,加餐可选黄瓜番茄'}],
sports:[{n:'餐后快走',t:30,f:'每日1次'},{n:'抗阻训练(弹力带)',t:20,f:'每周2次'}],
advice:'目标空腹血糖<7.0;餐后2小时血糖<10.0;季度复查糖化血红蛋白。'},
'CQ027710':{name:'张秀兰',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',type:['肥胖','其他'],
visits:[
{date:'2026-07-10',doctor:'曲志明',dx:'超重;血脂偏高',rx:'生活方式干预,三个月复查血脂'}],
follows:[
{date:'2026-09-01',doctor:'曲志明',form:'电话',sym:'腰痛缓解,久坐后复现',bp:'126/80',next:'2026-11-01'},
{date:'2026-07-10',doctor:'曲志明',form:'电话',sym:'血脂复查提醒',bp:'124/78',next:'2026-09-01'}],
meds:[
{name:'阿托伐他汀钙片',spec:'10mg*7片',dose:'10mg',freq:'每晚1次',start:'2026-07-12',end:'—',using:true}],
meals:[
{w:'周一',b:'燕麦+鸡蛋',l:'杂粮饭少油+清蒸虾',d:'蔬菜汤'},
{w:'周二~周日',b:'低脂早餐轮换',l:'减少油炸,晚餐少食',d:'18:30前完成晚餐'}],
sports:[{n:'游泳',t:40,f:'每周2次'},{n:'快走',t:35,f:'每周3次'}],
advice:'减重目标:半年减重5%;关注腰椎保护,避免久坐超1小时。'},
'CQ021578':{name:'石维国',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',type:['高血压'],
visits:[
{date:'2026-08-28',doctor:'李桂芳',dx:'血压正常高值',rx:'暂不用药,生活方式干预'}],
follows:[
{date:'2026-08-28',doctor:'李桂芳',form:'门诊',sym:'无头晕头痛,血压临界',bp:'138/86',next:'2026-10-28'}],
meds:[],
meals:[
{w:'周一~周日',b:'低盐低脂早餐',l:'限盐<5g/日,限酒',d:'晚餐清淡,控制体重'}],
sports:[{n:'快走/慢跑',t:35,f:'每周4次'},{n:'羽毛球',t:45,f:'每周1次'}],
advice:'血压目标<130/80;每月自测血压记录,连续偏高时及时就诊。'},
'CQ022317':{name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',type:['肥胖'],
visits:[
{date:'2026-08-26',doctor:'刘玉梅',dx:'中暑恢复期;超重',rx:'补液休息,减重管理'}],
follows:[
{date:'2026-08-26',doctor:'刘玉梅',form:'巡诊',sym:'中暑后恢复,乏力减轻',bp:'128/84',next:'2026-10-26'}],
meds:[],
meals:[
{w:'周一~周日',b:'清淡补钾早餐',l:'少食腌制,控制主食',d:'夏季作业注意补液'}],
sports:[{n:'骑行',t:30,f:'每周3次(避开高温时段)'}],
advice:'BMI目标<24;高温作业执行轮换防暑制度。'},
'CQ019203':{name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',type:['冠心病'],
visits:[
{date:'2026-06-20',doctor:'张秀兰',dx:'冠状动脉粥样硬化性心脏病(稳定期)',rx:'继续他汀+抗血小板治疗'}],
follows:[
{date:'2026-08-19',doctor:'张秀兰',form:'电话',sym:'无胸闷胸痛,规律服药',bp:'122/78',next:'2026-10-19'}],
meds:[
{name:'阿托伐他汀钙片',spec:'20mg*7片',dose:'20mg',freq:'每晚1次',start:'2026-05-20',end:'—',using:true},
{name:'阿司匹林肠溶片',spec:'100mg*30片',dose:'100mg',freq:'每日1次',start:'2026-05-20',end:'—',using:true}],
meals:[
{w:'周一~周日',b:'低脂早餐',l:'低脂高纤维,控制体重',d:'晚餐少食多蔬菜'}],
sports:[{n:'平地快走',t:25,f:'每周4次(中低强度)'},{n:'八段锦',t:15,f:'每日'}],
advice:'随身携带硝酸甘油;避免剧烈与超强度作业;半年复查冠脉评估。'},
'CQ036441':{name:'刘玉梅',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',type:['高血压'],
visits:[
{date:'2026-04-18',doctor:'呼建国',dx:'高血压1级',rx:'启动小剂量降压药'}],
follows:[
{date:'2026-08-21',doctor:'呼建国',form:'门诊',sym:'血压达标,维持方案',bp:'134/84',next:'2026-10-21'}],
meds:[
{name:'苯磺酸氨氯地平片',spec:'5mg*7片',dose:'5mg',freq:'每日1次',start:'2026-04-18',end:'—',using:true}],
meals:[
{w:'周一~周日',b:'低盐早餐',l:'限盐<5g/日',d:'晚餐清淡'}],
sports:[{n:'快走',t:30,f:'每周5次'}],
advice:'血压目标<130/80;规律服药勿自行停药。'},
'CQ029905':{name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',type:['其他'],
visits:[
{date:'2026-07-02',doctor:'刘玉梅',dx:'颈椎病(神经根型)',rx:'理疗+工间操干预'}],
follows:[
{date:'2026-08-14',doctor:'刘玉梅',form:'门诊',sym:'上肢麻木发作减少',bp:'120/76',next:'2026-11-14'}],
meds:[
{name:'甲钴胺片',spec:'0.5mg*20片',dose:'0.5mg',freq:'每日3次',start:'2026-07-02',end:'2026-08-14',using:false}],
meals:[
{w:'周一~周日',b:'均衡三餐',l:'常规饮食',d:'补钙食物适量'}],
sports:[{n:'颈椎工间操',t:10,f:'每个工作日2次'},{n:'游泳(蛙泳)',t:30,f:'每周2次'}],
advice:'避免久坐超1小时;屏幕抬高水平视;症状加重及时复查。'}
};
/* ===== 员工信息(从URL参数读取) ===== */
var params=new URLSearchParams(location.search);
var code=params.get('emp')||'CQ033096';
var emp=data[code]||data['CQ033096'];
function renderHead(){
document.title='干预记录 · '+emp.name+' | 一线医疗';
document.getElementById('empCard').innerHTML=
'<div>姓名 <b>'+emp.name+'</b></div><div>工号 <b>'+code+'</b></div><div>性别/年龄 <b>'+emp.sex+' / '+emp.age+'岁</b></div><div>慢病类型 <b>'+emp.type.map(function(t){return '<span class="chip" style="margin:0 4px 0 0;">'+t+'</span>';}).join('')+'</b></div>'
+'<div>所属单位 <b>'+emp.unit+'</b></div><div>所属部门 <b>'+emp.dept+'</b></div><div>分配医疗点 <b>'+emp.point+'('+pointUnit[emp.point]+')</b></div><div>本页为干预全过程记录,数据与随访/就诊台账同源</div>';
}
function switchTab(k){
document.querySelectorAll('.tab-btn').forEach(function(b){b.classList.toggle('active',b.dataset.tab===k);});
['Visit','Follow','Med','Plan'].forEach(function(p){document.getElementById('pane'+p).style.display=(p.toLowerCase()===k)?'':'none';});
}
function renderAll(){
document.getElementById('visitBody').innerHTML=emp.visits.length?emp.visits.map(function(v,i){
return '<tr><td>'+(i+1)+'</td><td>'+v.date+'</td><td>'+emp.point+'</td><td>'+v.doctor+'</td><td style="text-align:left;">'+v.dx+'</td><td style="text-align:left;">'+v.rx+'</td></tr>';
}).join(''):'<tr><td colspan="6" class="td-empty">近12个月暂无就诊记录</td></tr>';
document.getElementById('followBody').innerHTML=emp.follows.length?emp.follows.map(function(f,i){
return '<tr><td>'+(i+1)+'</td><td>'+f.date+'</td><td>'+f.doctor+'</td><td><span class="badge badge--info">'+f.form+'</span></td><td style="text-align:left;">'+f.sym+'</td><td>'+f.bp+'</td><td>'+f.next+'</td></tr>';
}).join(''):'<tr><td colspan="7" class="td-empty">暂无随访记录</td></tr>';
document.getElementById('medBody').innerHTML=emp.meds.length?emp.meds.map(function(m,i){
return '<tr><td>'+(i+1)+'</td><td style="text-align:left;">'+m.name+'</td><td>'+m.spec+'</td><td>'+m.dose+'</td><td>'+m.freq+'</td><td>'+m.start+'</td><td>'+m.end+'</td>'
+'<td>'+(m.using?'<span class="badge badge--success">在用</span>':'<span class="badge badge--muted">已停</span>')+'</td></tr>';
}).join(''):'<tr><td colspan="8" class="td-empty">该员工目前无需用药</td></tr>';
document.getElementById('mealBody').innerHTML=emp.meals.map(function(m){
return '<tr><td>'+m.w+'</td><td style="text-align:left;">'+m.b+'</td><td style="text-align:left;">'+m.l+'</td><td style="text-align:left;">'+m.d+'</td></tr>';
}).join('');
document.getElementById('sportBody').innerHTML=emp.sports.map(function(s,i){
return '<tr><td>'+(i+1)+'</td><td>'+s.n+'</td><td>'+s.t+'</td><td>'+s.f+'</td></tr>';
}).join('');
document.getElementById('planAdvice').textContent='干预要点:'+emp.advice;
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderHead();renderAll();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,594 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>慢病管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.chip{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;margin-right:6px;}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.check-group{display:flex;flex-direction:column;gap:10px;}
.check{display:flex;align-items:center;gap:8px;font-size:var(--font-sm);cursor:pointer;}
.check input{accent-color:var(--primary);width:15px;height:15px;cursor:pointer;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item active">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>姓名/工号</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名或工号">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>慢病类型</label>
<select class="filter-select" id="filterType" style="min-width:110px;">
<option value="">全部类型</option>
<option>高血压</option><option>冠心病</option><option>糖尿病</option><option>肥胖</option><option>其他</option>
</select>
</div>
<div class="filter-item">
<label>入组日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-default btn-link--danger" onclick="askBatchDelete()">批量删除</button>
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">员工由体检指标异常与就诊诊断自动入组;如需调整分类请使用「修改慢病」</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></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 id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 查看详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">慢病员工详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('view')">关闭</button>
<button class="btn btn-primary" id="viewGoIntervention">查看干预记录</button>
</div>
</div>
<!-- [交互] 修改慢病弹窗 -->
<div class="modal-mask" id="typeMask">
<div class="modal">
<div class="modal__title" id="typeTitle">修改慢病类型</div>
<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-bottom:14px;">取消全部勾选将把该员工移出慢病管理台账,需二次确认。</div>
<div class="check-group" id="typeChecks"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('typeMask')">取消</button>
<button class="btn btn-primary" onclick="saveTypes()">保存</button>
</div>
</div>
</div>
<!-- [交互] 移出确认弹窗 -->
<div class="modal-mask" id="clearMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FF4D4F" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FF4D4F" stroke-width="1.8" stroke-linecap="round"/></svg> 移出确认</div>
<div id="clearText" style="font-size:var(--font-sm);line-height:1.6;"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('clearMask')">再想想</button>
<button class="btn btn-primary" style="background:var(--danger);" onclick="confirmClear()">确认移出</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):与员工分配名册一致的12人池 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var TYPES=['高血压','冠心病','糖尿病','肥胖','其他'];
var typeBadge={'高血压':'badge--danger','冠心病':'badge--danger','糖尿病':'badge--warning','肥胖':'badge--info','其他':'badge--muted'};
/* 慢病台账:type=慢病类型数组 note=入组依据 */
var chronics=[
{code:'CQ033096',name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',type:['高血压'],join:'2026-08-08',doctor:'曲志明',note:'门诊确诊高血压2级(158/96mmHg)',advice:'规律服药,每周自测血压,月度随访'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',type:['糖尿病','肥胖'],join:'2026-06-12',doctor:'闫婷',note:'年度体检空腹血糖8.9mmol/L+BMI 27.6',advice:'控糖饮食+餐后运动,双周随访,季度复查糖化'},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',type:['肥胖','其他'],join:'2026-07-10',doctor:'曲志明',note:'BMI 25.8 合并血脂偏高(总胆固醇 6.4)',advice:'减重为核心目标,低脂饮食,两月随访'},
{code:'CQ021578',name:'石维国',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',type:['高血压'],join:'2026-08-28',doctor:'李桂芳',note:'血压正常高值(138/86mmHg),入组观察',advice:'暂不用药,生活方式干预,双月随访'},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',type:['肥胖'],join:'2026-08-26',doctor:'刘玉梅',note:'BMI 26.4 合并摄盐偏重',advice:'清淡饮食,夏季作业防暑,季度随访'},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',type:['冠心病'],join:'2026-05-20',doctor:'张秀兰',note:'既往心绞痛病史,长期服用他汀',advice:'随身携带硝酸甘油,避免剧烈作业,月度随访'},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',type:['高血压'],join:'2026-04-18',doctor:'呼建国',note:'体检血压 142/88mmHg,复测确认',advice:'限盐减重,规律服药,月度随访'},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',type:['其他'],join:'2026-07-02',doctor:'刘玉梅',note:'颈椎病史,反复上肢麻木',advice:'工间操干预,避免久坐,季度随访'}
];
var editingCode=null,viewCode=null;
/* ===== 单位部门双列筛选 ===== */
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(deptMap[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'慢病台账导出_2026-09-01 11:08.xlsx',status:'done',time:'2026-09-01 11:08'},
{file:'慢病台账导出_2026-08-20 16:31.xlsx',status:'done',time:'2026-08-20 16:31'},
{file:'慢病台账导出_2026-08-02 10:47.xlsx',status:'fail',time:'2026-08-02 10:47'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);">—</span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'慢病台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var q=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var type=document.getElementById('filterType').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return chronics.filter(function(c){
return (!q||c.name.indexOf(q)!==-1||c.code.toLowerCase().indexOf(q.toLowerCase())!==-1)
&&(!selUnit||c.unit===selUnit)&&(!selDept||c.dept===selDept)
&&(!unit||pointUnit[c.point]===unit)&&(!point||c.point===point)
&&(!type||c.type.indexOf(type)!==-1)
&&(!from||c.join>=from)&&(!to||c.join<=to);
});
}
function typeChips(arr){return arr.map(function(t){return '<span class="chip">'+t+'</span>';}).join('');}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="12" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(c,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+c.code+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+c.name+'</td>'
+'<td>'+c.code+'</td><td>'+c.sex+'</td><td>'+c.age+'</td>'
+'<td style="white-space:normal;min-width:150px;">'+typeChips(c.type)+'</td>'
+'<td>'+c.join+'</td><td>'+c.unit+'</td><td>'+c.dept+'</td><td>'+c.point+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView(\''+c.code+'\')">详情</button>'
+'<button class="btn btn-link" onclick="goIntervention(\''+c.code+'\')">干预记录</button>'
+'<button class="btn btn-link" onclick="openTypeModal(\''+c.code+'\')">修改慢病</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
var ca=document.getElementById('checkAll');if(ca)ca.checked=false;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
resetOrg();
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterType').value='';
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 详情抽屉 ===== */
function openView(code){
var c=chronics.find(function(x){return x.code===code;});
viewCode=code;
document.getElementById('viewTitle').textContent='慢病员工详情 · '+c.name;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">基础信息</div>'
+'<div class="detail-grid">'+d('姓名',c.name)+d('员工编号',c.code)+d('性别/年龄',c.sex+' / '+c.age+'岁')+d('所属单位',c.unit)+d('所属部门',c.dept)+d('分配医疗点',c.point+'('+pointUnit[c.point]+')')+'</div>'
+'<div class="form-section">慢病管理信息</div>'
+'<div class="detail-grid">'+d('入组日期',c.join)+d('管理医生',c.doctor)+d('慢病类型',typeChips(c.type),true)+d('入组依据',c.note,true)+d('干预建议',c.advice,true)+'</div>'
+'<div class="form-section">最近一次随访摘要</div>'
+'<div style="font-size:var(--font-sm);color:var(--text-secondary);line-height:1.7;background:#FAFAFA;border-radius:var(--radius-sm);padding:12px 16px;">'+latestFollow(c)+'</div>';
document.getElementById('viewGoIntervention').onclick=function(){goIntervention(code);};
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
function latestFollow(c){
var map={
'CQ033096':'2026-09-05 门诊随访:头晕较前减轻,血压 146/92mmHg 仍未达标,已强化用药依从性管理。',
'CQ010564':'2026-09-03 门诊随访:口渴乏力减轻,空腹血糖 8.9mmol/L,两周复查并强化饮食管理。',
'CQ027710':'2026-09-01 电话随访:腰痛缓解,血脂复查提醒,减重为核心干预目标。',
'CQ021578':'2026-08-28 门诊随访:无头晕头痛,血压 138/86mmHg 临界,生活方式干预中。',
'CQ022317':'2026-08-26 巡诊随访:中暑后恢复,乏力减轻,注意摄盐与高温作业防护。',
'CQ019203':'2026-08-19 电话随访:无胸闷胸痛,规律服用他汀,避免超强度作业。',
'CQ036441':'2026-08-21 门诊随访:血压 134/84mmHg 达标,维持现有方案。',
'CQ029905':'2026-08-14 门诊随访:上肢麻木发作减少,坚持工间操。'
};
return map[c.code]||'近两个月内暂无随访记录,请安排下次随访。';
}
function goIntervention(code){
closeDrawer('view');
location.href='daily-chronic-intervention.html?emp='+code;
}
/* ===== 修改慢病 ===== */
function openTypeModal(code){
window._batchCodes=null;
editingCode=code;
var c=chronics.find(function(x){return x.code===code;});
document.getElementById('typeTitle').textContent='修改慢病类型 · '+c.name;
document.getElementById('typeChecks').innerHTML=TYPES.map(function(t){
return '<label class="check"><input type="checkbox" value="'+t+'"'+(c.type.indexOf(t)!==-1?' checked':'')+'>'+t+'</label>';
}).join('');
document.getElementById('typeMask').classList.add('open');
}
function saveTypes(){
var picked=[];
document.querySelectorAll('#typeChecks input:checked').forEach(function(r){picked.push(r.value);});
if(!picked.length){
var c=chronics.find(function(x){return x.code===editingCode;});
document.getElementById('clearText').textContent='确认清空「'+c.name+'」的全部慢病类型?该员工将移出慢病管理台账,历史干预记录保留备查。';
document.getElementById('clearMask').classList.add('open');
return;
}
applyTypes(picked);
}
function confirmClear(){
if(window._batchCodes){
chronics=chronics.filter(function(c){return window._batchCodes.indexOf(c.code)===-1;});
var n=window._batchCodes.length;window._batchCodes=null;
closeModal('clearMask');renderTable();
showToast('已批量删除 '+n+' 条慢病台账记录');
return;
}
applyTypes([]);
}
/* ===== 批量选择与删除 ===== */
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function askBatchDelete(){
var codes=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return c.value;});
if(!codes.length){showToast('请先勾选要删除的记录');return;}
window._batchCodes=codes;
document.getElementById('clearText').textContent='确认删除勾选的 '+codes.length+' 条慢病台账记录?该员工将移出慢病管理台账,历史干预记录保留备查。';
document.getElementById('clearMask').classList.add('open');
}
function applyTypes(picked){
var c=chronics.find(function(x){return x.code===editingCode;});
if(!picked.length){chronics=chronics.filter(function(x){return x.code!==editingCode;});}
else{c.type=picked;}
closeModal('typeMask');closeModal('clearMask');renderTable();
showToast(picked.length?'慢病类型已更新「'+c.name+'」':'「'+c.name+'」已移出慢病管理台账');
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,594 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康宣教管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.tabs-bar{display:flex;gap:4px;background:#fff;padding:8px 12px;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.tab-btn{padding:9px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.tab-btn:hover{color:var(--primary);}
.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-btn .cnt{display:inline-block;min-width:18px;height:18px;line-height:18px;text-align:center;border-radius:9px;background:#F0F0F0;color:var(--text-secondary);font-size:var(--font-xs);padding:0 5px;}
.tab-btn.active .cnt{background:var(--primary-light);color:var(--primary);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-item--full{grid-column:1/-1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:14px;display:flex;align-items:center;gap:10px;color:var(--text-placeholder);font-size:var(--font-xs);background:#FAFAFA;cursor:pointer;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);}
.photo-row{display:flex;gap:10px;}
.photo-ph{width:92px;height:66px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#FAFAFA;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;color:var(--text-placeholder);font-size:10px;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item active">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label id="lblFilterTheme">宣教主题</label>
<input class="filter-input" id="filterTheme" placeholder="请输入主题关键字">
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>开展医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>宣教日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] Tab切换:健康宣教/急救培训(老系统为同一数据模型,publicityType=1/0 区分,仅文案与列名切换) -->
<div class="tabs-bar" style="margin-top:16px;">
<button class="tab-btn active" id="tabEdu" onclick="switchTab(1)">健康宣教 <span class="cnt" id="cntEdu"></span></button>
<button class="tab-btn" id="tabTr" onclick="switchTab(0)">急救培训 <span class="cnt" id="cntTr"></span></button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" id="btnAdd" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg><span id="addBtnText">新增宣教记录</span>
</button>
<button class="btn btn-default btn-link--danger" onclick="askBatchDelete()">批量删除</button>
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">活动计入医疗点工作量,参加人数以现场签到为准</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th><th>序号</th><th id="thTheme">宣教主题</th><th id="thDate">宣教日期</th><th>开展医疗点</th><th>所属单位</th><th>覆盖区域</th><th>参加人数</th><th>主讲人</th><th id="thContent">宣教内容</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑宣教抽屉 -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增宣教记录</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section" style="margin-top:0;" id="formSecTitle">宣教活动信息</div>
<div class="form-grid">
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> <span id="lblTheme">宣教主题</span></span><input class="form-control" id="eTheme" placeholder="如:全国高血压日专题宣教"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> <span id="lblDate">宣教日期</span></span><input class="form-control" type="date" id="eDate"></div>
<div class="form-item"><span class="form-label">主讲人</span><input class="form-control" id="eLecturer" placeholder="医护人员姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 医疗点所属单位</span>
<select class="form-control" id="eUnit" onchange="onFormUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 开展医疗点</span>
<select class="form-control" id="ePoint"><option value="">请先选择单位</option></select></div>
<div class="form-item"><span class="form-label">覆盖区域</span><input class="form-control" id="eArea" placeholder="如:桐园作业区各井站"></div>
<div class="form-item"><span class="form-label">参加人数</span><input class="form-control" type="number" id="eNum" placeholder="现场签到人数"></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> <span id="lblContent">宣教内容</span></span><textarea class="form-control" id="eContent" placeholder="活动内容与要点摘要"></textarea></div>
</div>
<div class="form-section" id="secPhoto">现场照片</div>
<div class="photo-row">
<div class="photo-ph"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><rect x="1" y="4" width="16" height="12" rx="2" stroke="#bbb" stroke-width="1.3"/><circle cx="9" cy="10" r="3.2" stroke="#bbb" stroke-width="1.3"/><path d="M6 4l1.2-2h3.6L12 4" stroke="#bbb" stroke-width="1.3"/></svg>照片 1</div>
<div class="photo-ph"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><rect x="1" y="4" width="16" height="12" rx="2" stroke="#bbb" stroke-width="1.3"/><circle cx="9" cy="10" r="3.2" stroke="#bbb" stroke-width="1.3"/><path d="M6 4l1.2-2h3.6L12 4" stroke="#bbb" stroke-width="1.3"/></svg>照片 2</div>
<div class="photo-ph" onclick="showToast('照片上传功能演示')"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 4v10M4 9h10" stroke="#bbb" stroke-width="1.5" stroke-linecap="round"/></svg>继续添加</div>
</div>
<div class="form-section" id="secMaterial">宣教资料</div>
<div class="upload-box" id="materialBox" onclick="showToast('资料上传功能演示')">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M10 13V4m0 0L6.5 7.5M10 4l3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M3 14v2a2 2 0 002 2h10a2 2 0 002-2v-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
<span id="materialText">点击上传宣教手册 / PPT / 折页等电子资料(支持 PDF、PPT,单个不超过 20MB)</span>
</div>
<div class="form-section">员工确认签字</div>
<div class="upload-box">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M3 17c2-4 4 3 6-2s3 4 8-1" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>
参加人员在 PAD 端签到屏完成电子确认签字,签到记录自动回传关联本宣教活动
</div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 查看详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">宣教详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('view')">关闭</button></div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div id="delText">确认删除该条宣教记录?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* type:1=健康宣教 0=急救培训(与老系统 publicityType 口径一致) */
var edus=[
{id:1,type:1,theme:'全国高血压日:减盐降血压专题宣教',date:'2026-09-02',point:'桐园作业区医疗点',area:'桐园作业区各井站、联合站',num:86,lecturer:'曲志明',content:'讲解高血压危害与低盐饮食要点,现场免费测血压42人次,发放限盐勺与宣教折页。'},
{id:2,type:1,theme:'联合国糖尿病日:吃动平衡控血糖',date:'2026-08-24',point:'西峰基地医疗点',area:'基地管理处及后勤单元',num:64,lecturer:'闫婷',content:'糖尿病饮食换算实操演示,餐后运动处方宣讲,血糖义务检测31人次。'},
{id:3,type:0,theme:'现场急救技能培训(心肺复苏+AED)',date:'2026-08-15',point:'樊学作业区医疗点',area:'樊学作业区倒班点',num:52,lecturer:'李桂芳',content:'CPR操作要领与AED使用流程分组的演练,逐人考核通过47人,发放急救明白卡。'},
{id:4,type:0,theme:'夏季高温防暑与中暑急救培训',date:'2026-07-18',point:'乌审旗采气区医疗点',area:'苏里格南作业区井场',num:73,lecturer:'刘玉梅',content:'先兆中暑识别与处置流程实操,防暑药品配备讲解,现场发放藿香正气类物资。'},
{id:5,type:1,theme:'职业人群颈椎健康与工间操推广',date:'2026-07-05',point:'姬圪台作业区医疗点',area:'姬圪台联合站中控、化验室',num:45,lecturer:'张秀兰',content:'久坐危害讲解,颈椎工间操带练教学,建立各班组工间操提醒机制。'},
{id:6,type:1,theme:'低盐低脂饮食与血脂管理讲座',date:'2026-06-20',point:'姬圪台作业区医疗点',area:'姬圪台作业区食堂',num:58,lecturer:'曲志明',content:'血脂异常人群配餐原则,食堂减油减盐行动倡议,现场答疑并发放食谱卡。'},
{id:7,type:0,theme:'创伤救护四项技术(止血包扎固定搬运)培训',date:'2026-06-06',point:'姬圪台作业区医疗点',area:'姬圪台联合站各班组',num:48,lecturer:'张秀兰',content:'绷带三角巾包扎、夹板固定与脊柱板搬运分项实操,逐组过关考核通过44人。'},
{id:8,type:1,theme:'无烟单位健康宣教周活动',date:'2026-05-30',point:'安塞采油队长住点医疗室',area:'安塞长住点全区域',num:91,lecturer:'白瑞',content:'世界无烟日主题宣讲,戒烟门诊转介渠道说明,签名承诺活动参与76人。'},
{id:9,type:1,theme:'女性健康两癌筛查知识讲座',date:'2026-05-14',point:'靖边气田医疗点',area:'靖边净化厂女工集中单元',num:39,lecturer:'刘玉梅',content:'乳腺癌宫颈癌筛查指征与周期讲解,预约转诊通道介绍,现场咨询12人次。'},
{id:10,type:0,theme:'受限空间窒息事故急救演练',date:'2026-04-22',point:'靖边气田医疗点',area:'靖边净化厂检修现场',num:35,lecturer:'李桂芳',content:'受限空间窒息事故情景模拟,救援佩戴与心肺复苏衔接流程演练,复盘讲评。'}
];
var nextId=11,editingId=null,pendingDelId=null,curTab=1;
/* 当前Tab文案映射:宣教↔培训 */
function T(a,b){return curTab===1?a:b;}
function switchTab(t){
curTab=t;currentPage=1;
document.getElementById('tabEdu').classList.toggle('active',t===1);
document.getElementById('tabTr').classList.toggle('active',t===0);
document.getElementById('thTheme').textContent=T('宣教主题','培训标题');
document.getElementById('thDate').textContent=T('宣教日期','培训日期');
document.getElementById('thContent').textContent=T('宣教内容','培训内容');
document.getElementById('addBtnText').textContent=T('新增宣教记录','新增急救培训');
document.getElementById('lblFilterTheme').textContent=T('宣教主题','培训标题');
document.getElementById('formSecTitle').textContent=T('宣教活动信息','培训活动信息');
document.getElementById('lblTheme').textContent=T('宣教主题','培训标题');
document.getElementById('lblDate').textContent=T('宣教日期','培训日期');
document.getElementById('lblContent').textContent=T('宣教内容','培训内容');
document.getElementById('secPhoto').textContent=T('现场照片','现场培训照片');
document.getElementById('secMaterial').textContent=T('宣教资料','培训资料');
document.getElementById('materialText').textContent=T('点击上传宣教手册 / PPT / 折页等电子资料(支持 PDF、PPT,单个不超过 20MB)','点击上传培训课件 / 操作手册 / 明白卡等电子资料(支持 PDF、PPT,单个不超过 20MB)');
renderTable();
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'健康教育台账导出_2026-09-02 15:40.xlsx',status:'done',time:'2026-09-02 15:40'},
{file:'急救培训台账导出_2026-08-18 11:25.xlsx',status:'done',time:'2026-08-18 11:25'},
{file:'健康教育台账导出_2026-07-31 17:12.xlsx',status:'fail',time:'2026-07-31 17:12'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);">—</span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:T('健康教育台账导出_','急救培训台账导出_')+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 批量选择与删除 ===== */
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function askBatchDelete(){
var ids=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return parseInt(c.value);});
if(!ids.length){showToast('请先勾选要删除的记录');return;}
pendingDelId=null;delMode='batch';
document.getElementById('delText').textContent='确认删除勾选的 '+ids.length+' 条'+T('宣教记录','急救培训')+'?删除后关联的签到记录一并解除关联。';
document.getElementById('delMask').classList.add('open');
window._batchIds=ids;
}
/* ===== 列表 ===== */
var pageSize=10,currentPage=1,delMode='single';
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var theme=document.getElementById('filterTheme').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return edus.filter(function(e){
return e.type===curTab
&&(!theme||e.theme.indexOf(theme)!==-1)
&&(!unit||pointUnit[e.point]===unit)&&(!point||e.point===point)
&&(!from||e.date>=from)&&(!to||e.date<=to);
});
}
function renderTabCounts(){
document.getElementById('cntEdu').textContent=edus.filter(function(e){return e.type===1;}).length;
document.getElementById('cntTr').textContent=edus.filter(function(e){return e.type===0;}).length;
}
function renderTable(){
renderTabCounts();
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="11" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(e,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+e.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;text-align:left;max-width:230px;overflow:hidden;text-overflow:ellipsis;" title="'+e.theme+'">'+e.theme+'</td>'
+'<td>'+e.date+'</td><td>'+e.point+'</td><td>'+pointUnit[e.point]+'</td>'
+'<td style="max-width:170px;overflow:hidden;text-overflow:ellipsis;" title="'+e.area+'">'+e.area+'</td>'
+'<td><span class="badge badge--info">'+e.num+' 人</span></td><td>'+e.lecturer+'</td>'
+'<td style="text-align:left;max-width:220px;overflow:hidden;text-overflow:ellipsis;" title="'+e.content+'">'+e.content+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView('+e.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openForm('+e.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete('+e.id+')">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
var ca=document.getElementById('checkAll');if(ca)ca.checked=false;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterTheme').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 新增/编辑 ===== */
function onFormUnit(){
var unit=document.getElementById('eUnit').value;
var sel=document.getElementById('ePoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function openForm(id){
editingId=id||null;
var e=id?edus.find(function(x){return x.id===id;}):null;
document.getElementById('formTitle').textContent=id?((curTab===1?'编辑宣教记录 · ':'编辑急救培训 · ')+e.theme):T('新增宣教记录','新增急救培训');
document.getElementById('eTheme').value=e?e.theme:'';
document.getElementById('eDate').value=e?e.date:'2026-09-07';
document.getElementById('eLecturer').value=e?e.lecturer:'';
document.getElementById('eUnit').value=e?pointUnit[e.point]:'';onFormUnit();
document.getElementById('ePoint').value=e?e.point:'';
document.getElementById('eArea').value=e?e.area:'';
document.getElementById('eNum').value=e?e.num:'';
document.getElementById('eContent').value=e?e.content:'';
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function saveForm(){
var theme=document.getElementById('eTheme').value.trim();
var date=document.getElementById('eDate').value;
var unit=document.getElementById('eUnit').value;
var point=document.getElementById('ePoint').value;
var content=document.getElementById('eContent').value.trim();
if(!theme){showToast('请填写'+T('宣教主题','培训标题'));return;}
if(!date){showToast('请选择'+T('宣教日期','培训日期'));return;}
if(!unit||!point){showToast('请选择开展医疗点');return;}
if(!content){showToast('请填写'+T('宣教内容','培训内容'));return;}
var obj={
id:editingId||nextId++,type:curTab,theme:theme,date:date,point:point,
area:document.getElementById('eArea').value||'—',
num:parseInt(document.getElementById('eNum').value)||0,
lecturer:document.getElementById('eLecturer').value||'—',
content:content
};
if(editingId){edus.splice(edus.findIndex(function(x){return x.id===editingId;}),1,obj);}
else{edus.unshift(obj);}
closeDrawer('form');renderTable();
showToast(editingId?T('宣教记录已更新','急救培训已更新'):T('宣教记录已保存,PAD 端签到数据将自动关联','急救培训已保存,PAD 端签到数据将自动关联'));
}
/* ===== 详情 ===== */
function openView(id){
var e=edus.find(function(x){return x.id===id;});
document.getElementById('viewTitle').textContent=T('宣教详情','急救培训详情');
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">活动信息</div>'
+'<div class="detail-grid">'+d(T('宣教主题','培训标题'),e.theme,true)+d(T('宣教日期','培训日期'),e.date)+d('主讲人',e.lecturer)+d('开展医疗点',e.point)+d('所属单位',pointUnit[e.point])+d('覆盖区域',e.area,true)+d('参加人数',e.num+' 人(PAD 端签到回传)')+d(T('宣教内容','培训内容'),e.content,true)+'</div>'
+'<div class="form-section">'+T('现场照片','现场培训照片')+'</div>'
+'<div class="photo-row"><div class="photo-ph">照片 1</div><div class="photo-ph">照片 2</div></div>'
+'<div class="form-section">'+T('宣教资料','培训资料')+'</div>'
+'<div class="upload-box"><svg width="16" height="16" viewBox="0 0 20 20" fill="none"><path d="M4 2h8l4 4v12H4z" stroke="#999" stroke-width="1.4" fill="none"/><path d="M12 2v4h4" stroke="#999" stroke-width="1.4"/></svg>《'+e.theme+'》'+T('宣教手册','培训资料')+'.pdf(演示附件)</div>';
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
/* ===== 删除 ===== */
function askDelete(id){
pendingDelId=id;delMode='single';
var e=edus.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认删除'+T('宣教记录','急救培训')+'「'+e.theme+'」?删除后关联的签到记录一并解除关联。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
if(delMode==='batch'){
edus=edus.filter(function(e){return window._batchIds.indexOf(e.id)===-1;});
closeModal('delMask');renderTable();
showToast('已批量删除 '+window._batchIds.length+' 条'+T('宣教记录','急救培训'));
return;
}
edus=edus.filter(function(e){return e.id!==pendingDelId;});
closeModal('delMask');renderTable();
showToast(T('宣教记录已删除','急救培训已删除'));
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,380 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>员工档案 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-sm{height:26px;padding:0 10px;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1300px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item active">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>姓名/工号</label>
<input class="filter-input" id="filterQ" placeholder="请输入姓名或工号">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<select class="filter-select" id="filterUnit" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterPU" onchange="onFilterPU()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('导出名册功能演示')">导出</button>
<span class="action-bar__hint">名册来源为「员工分配」页的已分配员工;分配关系调整请前往该页操作</span>
</div>
<!-- 表格 -->
<div class="table-card">
<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 id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 员工档案详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer()"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">员工健康档案</span><button class="drawer__close" onclick="closeDrawer()">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer()">关闭</button>
<button class="btn btn-primary" onclick="showToast('跳转至该员工的就诊登记(演示)')">去登记就诊</button>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):与「员工分配/分配统计」页已分配 12 人一致 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* 体检指标与风险为虚构演示值;visits 与「员工就诊」页记录对应 */
var emps=[
{code:'CQ021578',name:'石维国',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',tel:'139****2051',point:'樊学作业区医疗点',since:'2025-03-12',
exam:{date:'2026-04-18',hospital:'CQ油田总医院',bp:'138/86',glu:5.8,bmi:24.6,tc:5.9,summary:'总体良好,血压处于正常高值,建议低盐饮食并定期监测。',risk:'中',chronic:['血压偏高'],advice:'每季度复测血压;控制体重。'},
visits:[{time:'2026-08-21 18:20',disease:'电光性眼炎',doctor:'李桂芳'}]},
{code:'CQ032144',name:'李桂芳',sex:'女',age:38,unit:'采油一厂',dept:'樊学作业区',tel:'138****7742',point:'樊学作业区医疗点',since:'2025-03-12',
exam:{date:'2026-04-18',hospital:'CQ油田总医院',bp:'118/76',glu:5.1,bmi:22.3,tc:4.6,summary:'各项指标正常。',risk:'低',chronic:[],advice:'保持规律作息与运动。'},
visits:[]},
{code:'CQ045130',name:'杨慧敏',sex:'女',age:33,unit:'采油一厂',dept:'樊学作业区',tel:'188****7214',point:'樊学作业区医疗点',since:'2025-09-01',
exam:{date:'2026-04-18',hospital:'CQ油田总医院',bp:'112/70',glu:4.9,bmi:21.0,tc:4.2,summary:'各项指标正常。',risk:'低',chronic:[],advice:'注意作业防护,预防外伤。'},
visits:[{time:'2026-09-04 11:00',disease:'踝关节扭伤',doctor:'石维国'}]},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',tel:'137****8890',point:'姬圪台作业区医疗点',since:'2025-03-12',
exam:{date:'2026-05-09',hospital:'环县油田医院',bp:'126/82',glu:5.6,bmi:23.8,tc:5.0,summary:'总体良好,轻度超重趋势。',risk:'低',chronic:[],advice:'控制饮食,增加有氧运动。'},
visits:[]},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',tel:'136****2248',point:'姬圪台作业区医疗点',since:'2025-03-12',
exam:{date:'2026-05-09',hospital:'环县油田医院',bp:'128/80',glu:5.4,bmi:25.9,tc:5.5,summary:'超重,血脂轻度升高。',risk:'中',chronic:['超重','血脂偏高'],advice:'减重3-5kg;三个月后复查血脂。'},
visits:[{time:'2026-08-25 09:00',disease:'腰肌劳损',doctor:'曲志明'}]},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',tel:'150****6127',point:'桐园作业区医疗点',since:'2025-06-20',
exam:{date:'2026-05-09',hospital:'环县油田医院',bp:'154/95',glu:6.0,bmi:26.8,tc:6.0,summary:'血压达高血压2级标准,建议心内科进一步评估。',risk:'高',chronic:['高血压(2级)'],advice:'规律服药,每日晨起测量血压;限盐限酒。'},
visits:[{time:'2026-09-05 15:10',disease:'高血压病(2级)',doctor:'曲志明'}]},
{code:'CQ010564',name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',tel:'139****0086',point:'西峰基地医疗点',since:'2025-03-12',
exam:{date:'2026-04-26',hospital:'第四采油厂总医院',bp:'132/85',glu:8.9,bmi:27.4,tc:6.4,summary:'空腹血糖明显升高,糖尿病控制欠佳,需强化管理。',risk:'高',chronic:['2型糖尿病','超重'],advice:'监测空腹与餐后血糖;营养科膳食指导。'},
visits:[{time:'2026-09-03 10:20',disease:'2型糖尿病复诊',doctor:'闫婷'}]},
{code:'CQ041822',name:'闫婷',sex:'女',age:34,unit:'第四采油厂',dept:'健康管理部',tel:'186****5527',point:'西峰基地医疗点',since:'2025-03-12',
exam:{date:'2026-04-26',hospital:'第四采油厂总医院',bp:'110/68',glu:5.0,bmi:21.8,tc:4.4,summary:'各项指标正常。',risk:'低',chronic:[],advice:'保持健康生活方式。'},
visits:[]},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',tel:'135****7719',point:'靖边气田医疗点',since:'2025-03-12',
exam:{date:'2026-05-16',hospital:'庆阳市人民医院',bp:'116/74',glu:5.2,bmi:22.9,tc:4.8,summary:'各项指标正常。',risk:'低',chronic:[],advice:'保持规律作息。'},
visits:[]},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',tel:'147****9003',point:'靖边气田医疗点',since:'2025-07-01',
exam:{date:'2026-05-16',hospital:'庆阳市人民医院',bp:'122/78',glu:5.3,bmi:23.6,tc:4.9,summary:'总体良好。',risk:'低',chronic:[],advice:'注意季节交替期防护,预防呼吸道感染。'},
visits:[{time:'2026-09-06 09:30',disease:'上呼吸道感染',doctor:'刘玉梅'}]},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,unit:'采油一厂',dept:'生产技术部',tel:'155****3316',point:'安塞采油队长住点医疗室',since:'2025-10-15',
exam:{date:'2026-04-18',hospital:'吴起油田医院',bp:'118/75',glu:5.0,bmi:22.1,tc:4.3,summary:'各项指标正常。',risk:'低',chronic:[],advice:'保持锻炼。'},
visits:[]},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',tel:'133****4860',point:'乌审旗采气区医疗点',since:'2025-03-12',
exam:{date:'2026-05-16',hospital:'庆阳市人民医院',bp:'128/84',glu:5.7,bmi:26.4,tc:5.6,summary:'超重,夏季注意防暑。',risk:'中',chronic:['超重'],advice:'减重;高温作业做好轮换与补液。'},
visits:[{time:'2026-08-28 13:30',disease:'中暑(先兆)',doctor:'刘玉梅'}]}
];
/* 指标结论按阈值实时计算(演示规则) */
function bpFlag(s){var p=s.split('/'),a=+p[0],b=+p[1];if(a>=140||b>=90)return '异常';if(a>=130||b>=85)return '关注';return '正常';}
function gluFlag(v){if(v>=7.0)return '异常';if(v>=6.1)return '关注';return '正常';}
function bmiFlag(v){if(v>=28)return '异常';if(v>=24)return '关注';return '正常';}
function tcFlag(v){if(v>=6.2)return '异常';if(v>=5.2)return '关注';return '正常';}
var flagBadge={'正常':'badge--success','关注':'badge--warning','异常':'badge--danger'};
var riskBadge={'低':'badge--success','中':'badge--warning','高':'badge--danger'};
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterPU(){
var unit=document.getElementById('filterPU').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var q=document.getElementById('filterQ').value.trim();
var unit=document.getElementById('filterUnit').value;
var pu=document.getElementById('filterPU').value;
var point=document.getElementById('filterPoint').value;
return emps.filter(function(e){
return (!q||e.name.indexOf(q)!==-1||e.code.toLowerCase().indexOf(q.toLowerCase())!==-1)
&&(!unit||e.unit===unit)
&&(!pu||pointUnit[e.point]===pu)&&(!point||e.point===point);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="12" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(e,i){
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+e.name+'</td><td>'+e.code+'</td><td>'+e.sex+'</td><td>'+e.age+'</td><td>'+e.tel+'</td>'
+'<td>'+e.unit+'</td><td>'+e.dept+'</td>'
+'<td>'+pointUnit[e.point]+'</td><td>'+e.point+'</td>'
+'<td><span class="badge '+riskBadge[e.exam.risk]+'">'+e.exam.risk+'风险</span></td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openView(\''+e.code+'\')">详情</button></td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterQ').value='';
document.getElementById('filterUnit').value='';
document.getElementById('filterPU').value='';onFilterPU();
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 档案详情抽屉 ===== */
function openView(code){
var e=emps.find(function(x){return x.code===code;});
document.getElementById('viewTitle').textContent='员工健康档案 · '+e.name;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
var x=e.exam;
var indRows=[
{n:'血压(mmHg)',v:x.bp,f:bpFlag(x.bp),ref:'<140 / <90'},
{n:'空腹血糖(mmol/L)',v:x.glu.toFixed(1),f:gluFlag(x.glu),ref:'3.9-6.1'},
{n:'BMI',v:x.bmi.toFixed(1),f:bmiFlag(x.bmi),ref:'18.5-23.9'},
{n:'总胆固醇(mmol/L)',v:x.tc.toFixed(1),f:tcFlag(x.tc),ref:'<5.2'}
].map(function(r){return '<tr><td>'+r.n+'</td><td>'+r.v+'</td><td>'+r.ref+'</td><td><span class="badge '+flagBadge[r.f]+'">'+r.f+'</span></td></tr>';}).join('');
var chronicHtml=x.chronic.length?x.chronic.map(function(c){return '<span class="badge badge--muted" style="margin-right:6px;">'+c+'</span>';}).join(''):'<span style="color:var(--text-placeholder);font-size:var(--font-sm);"></span>';
var visitsHtml=e.visits.length?'<table class="mini-table"><thead><tr><th>就诊时间</th><th>诊断疾病</th><th>就诊医生</th></tr></thead><tbody>'+e.visits.map(function(v){return '<tr><td>'+v.time+'</td><td>'+v.disease+'</td><td>'+v.doctor+'</td></tr>';}).join('')+'</tbody></table>':'<span style="color:var(--text-placeholder);font-size:var(--font-sm);">本统计周期内暂无就诊记录</span>';
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">基础信息</div>'
+'<div class="detail-grid">'+d('员工姓名',e.name)+d('员工工号',e.code)+d('性别/年龄',e.sex+' / '+e.age+'岁')+d('联系电话',e.tel)+d('所属单位',e.unit)+d('所属部门',e.dept)+d('分配医疗点',e.point+'('+pointUnit[e.point]+')')+d('分配日期',e.since)+'</div>'
+'<div class="form-section">最近年度体检('+x.date.slice(0,4)+'年度)</div>'
+'<div class="detail-grid">'+d('体检日期',x.date)+d('体检机构',x.hospital)+d('总体结论',x.summary,true)+'</div>'
+'<div style="height:8px;"></div>'
+'<table class="mini-table"><thead><tr><th>指标</th><th>结果</th><th>参考范围</th><th>结论</th></tr></thead><tbody>'+indRows+'</tbody></table>'
+'<div class="form-section">健康风险评估</div>'
+'<div class="detail-grid">'+d('风险等级','<span class="badge '+riskBadge[x.risk]+'">'+x.risk+'风险</span>')+d('慢病标签',chronicHtml)+d('管理建议',x.advice,true)+'</div>'
+'<div class="form-section">就诊记录</div>'+visitsHtml;
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
/* ===== 通用 ===== */
function closeDrawer(){
document.getElementById('viewMask').classList.remove('open');
document.getElementById('viewDrawer').classList.remove('open');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,621 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随访任务 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.btn-link--muted{color:var(--text-secondary);}
.tabs-bar{display:flex;gap:4px;background:#fff;padding:8px 12px 0;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.tab-btn{padding:9px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.tab-btn:hover{color:var(--primary);}
.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-btn .cnt{display:inline-block;min-width:18px;height:18px;line-height:18px;text-align:center;border-radius:9px;background:#F0F0F0;color:var(--text-secondary);font-size:var(--font-xs);padding:0 5px;}
.tab-btn.active .cnt{background:var(--primary-light);color:var(--primary);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 10px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.chip{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:520px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;max-height:82vh;overflow-y:auto;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:16px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
textarea.form-control{height:56px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
.radio-group{display:flex;gap:16px;min-height:32px;align-items:center;flex-wrap:wrap;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item active">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] Tab:单次任务 / 频次任务 / 申请任务 -->
<div class="tabs-bar">
<button class="tab-btn active" id="tabOnce" onclick="switchTab('once')">单次任务 <span class="cnt" id="cntOnce"></span></button>
<button class="tab-btn" id="tabFreq" onclick="switchTab('freq')">频次任务 <span class="cnt" id="cntFreq"></span></button>
<button class="tab-btn" id="tabApply" onclick="switchTab('apply')">申请任务 <span class="cnt" id="cntApply"></span></button>
<span style="margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);align-self:center;padding-right:12px;">
单次/频次任务由系统按慢病干预方案自动派发,也可手工新建
</span>
</div>
<!-- 单次任务 -->
<div class="table-card" id="paneOnce">
<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><th>任务状态</th><th>操作</th></tr></thead>
<tbody id="onceBody"></tbody>
</table>
</div>
</div>
<!-- 频次任务 -->
<div class="table-card" id="paneFreq" style="display:none;">
<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 id="freqBody"></tbody>
</table>
</div>
</div>
<!-- 申请任务 -->
<div class="table-card" id="paneApply" style="display:none;">
<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></tr></thead>
<tbody id="applyBody"></tbody>
</table>
</div>
</div>
<!-- [交互] 操作栏 -->
<div class="tabs-bar" style="padding:0;border-radius:var(--radius-md);margin-top:16px;">
<button class="btn btn-primary" style="margin:12px 8px 12px 16px;" onclick="openCreate()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新建随访任务
</button>
<button class="btn btn-export" style="margin:12px 8px;" onclick="doExport()">导出</button>
<button class="btn btn-default" style="margin:12px 8px;" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint" style="margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);align-self:center;padding-right:16px;">执行随访后自动转入「随访管理」录入结果</span>
</div>
</main>
<!-- [交互] 新建任务弹窗 -->
<div class="modal-mask" id="createMask">
<div class="modal">
<div class="modal__title">新建随访任务</div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 随访员工(仅已分配员工)</span>
<select class="form-control" id="cEmp"><option value="">请选择员工</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 任务类型</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="cType" value="once" checked onchange="onTypeRadio()">单次任务</label>
<label class="radio"><input type="radio" name="cType" value="freq" onchange="onTypeRadio()">频次任务</label>
</div></div>
<div class="form-item" id="boxOnceDate"><span class="form-label"><span class="req">*</span> 计划随访日期</span><input class="form-control" type="date" id="cDate"></div>
<div class="form-grid" id="boxFreq" style="display:none;">
<div class="form-item"><span class="form-label">人员类型</span>
<select class="form-control" id="cGroup"><option>高血压</option><option>冠心病</option><option>糖尿病</option><option>肥胖</option><option>其他</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 随访频次</span>
<select class="form-control" id="cFreq"><option>每月</option><option>每季度</option><option>每半年</option></select></div>
</div>
<div class="form-item form-item--full"><span class="form-label">任务说明</span><textarea class="form-control" id="cNote" placeholder="随访重点与注意事项"></textarea></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('createMask')">取消</button>
<button class="btn btn-primary" onclick="saveCreate()">保存</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):与员工分配名册/慢病台账/随访管理勾稽 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var empPool=[
{code:'CQ021578',name:'石维国',sex:'男',age:46,tel:'139****2051',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',group:'高血压'},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,tel:'136****2248',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',group:'肥胖'},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,tel:'137****8890',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',group:'冠心病'},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,tel:'150****6127',unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',group:'高血压'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,tel:'139****0086',unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',group:'糖尿病'},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,tel:'135****7719',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',group:'高血压'},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,tel:'147****9003',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',group:'其他'},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,tel:'155****3316',unit:'采油一厂',dept:'生产技术部',point:'安塞采油队长住点医疗室',group:'其他'},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,tel:'133****4860',unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',group:'肥胖'}
];
function E(code){return empPool.find(function(e){return e.code===code;});}
/* 单次任务:plan=计划 actual=实际(空为未执行) */
var onceTasks=[
{id:1,code:'CQ033096',plan:'2026-10-05',actual:'',status:'待随访',note:'血压未达标复查,重点评估用药依从性'},
{id:2,code:'CQ010564',plan:'2026-09-17',actual:'',status:'待随访',note:'血糖两周复查,评估换药后反应'},
{id:3,code:'CQ021578',plan:'2026-10-28',actual:'',status:'待随访',note:''},
{id:4,code:'CQ027710',plan:'2026-11-01',actual:'',status:'待随访',note:''},
{id:5,code:'CQ036441',plan:'2026-09-01',actual:'',status:'已逾期',note:''},
{id:6,code:'CQ029905',plan:'2026-08-14',actual:'2026-08-14',status:'已完成',note:''},
{id:7,code:'CQ019203',plan:'2026-08-19',actual:'2026-08-19',status:'已完成',note:''}
];
/* 频次任务:freq=随访频次 run=启用/暂停 */
var freqTasks=[
{id:1,code:'CQ033096',freq:'每月',run:'启用'},
{id:2,code:'CQ010564',freq:'每月',run:'启用'},
{id:3,code:'CQ019203',freq:'每季度',run:'启用'},
{id:4,code:'CQ027710',freq:'每半年',run:'暂停'},
{id:5,code:'CQ021578',freq:'每季度',run:'启用'},
{id:6,code:'CQ022317',freq:'每季度',run:'启用'},
{id:7,code:'CQ036441',freq:'每月',run:'启用'}
];
/* 申请任务 */
var applyTasks=[
{id:1,code:'CQ029905',content:'最近手脚麻木偶有发作,希望安排一次健康随访评估',date:'2026-09-02'},
{id:2,code:'CQ044871',content:'体检报告提示脂肪肝,咨询是否需要纳入慢病随访管理',date:'2026-08-30'},
{id:3,code:'CQ041822',content:'家人血压问题想约门诊随访指导家庭监测',date:'2026-09-04'}
];
/* 闫婷未入empPool简表,补基础字段 */
empPool.push({code:'CQ041822',name:'闫婷',sex:'女',age:34,tel:'186****5527',unit:'第四采油厂',dept:'健康管理部',point:'西峰基地医疗点',group:'其他'});
var nextId=99,curTab='once';
var statusBadge={'待随访':'badge--warning','已完成':'badge--success','已逾期':'badge--danger','启用':'badge--success','暂停':'badge--muted'};
/* ===== 单位部门双列筛选 ===== */
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(deptMap[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'随访任务导出_2026-09-02 09:41.xlsx',status:'done',time:'2026-09-02 09:41'},
{file:'随访任务导出_2026-08-18 15:26.xlsx',status:'done',time:'2026-08-18 15:26'},
{file:'随访任务导出_2026-08-01 11:03.xlsx',status:'fail',time:'2026-08-01 11:03'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);"></span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'随访任务导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 筛选 ===== */
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function matchFilter(e){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
return (!name||e.name.indexOf(name)!==-1)
&&(!selUnit||e.unit===selUnit)&&(!selDept||e.dept===selDept)
&&(!unit||pointUnit[e.point]===unit)&&(!point||e.point===point);
}
function currentOnce(){return onceTasks.filter(function(t){return matchFilter(E(t.code));});}
function currentFreq(){return freqTasks.filter(function(t){return matchFilter(E(t.code));});}
function currentApply(){return applyTasks.filter(function(t){return matchFilter(E(t.code));});}
/* ===== Tab 与渲染 ===== */
function switchTab(k){
curTab=k;
['once','freq','apply'].forEach(function(p){
document.getElementById('tab'+p.charAt(0).toUpperCase()+p.slice(1)).classList.toggle('active',p===k);
document.getElementById('pane'+p.charAt(0).toUpperCase()+p.slice(1)).style.display=p===k?'':'none';
});
renderAll();
}
function empCells(e){
return '<td style="font-weight:600;">'+e.name+'</td><td>'+e.code+'</td><td>'+e.sex+'</td><td>'+e.age+'</td>';
}
function renderAll(){
document.getElementById('cntOnce').textContent=currentOnce().length;
document.getElementById('cntFreq').textContent=currentFreq().length;
document.getElementById('cntApply').textContent=currentApply().length;
var ol=currentOnce();
document.getElementById('onceBody').innerHTML=ol.length?ol.map(function(t,i){
var e=E(t.code);
return '<tr><td>'+(i+1)+'</td>'+empCells(e)+'<td>'+e.tel+'</td><td>'+e.unit+'</td><td>'+e.dept+'</td>'
+'<td><span class="chip">'+e.group+'</span></td><td>'+e.point+'</td>'
+'<td>'+t.plan+'</td><td>'+(t.actual||'—')+'</td>'
+'<td><span class="badge '+statusBadge[t.status]+'">'+t.status+'</span></td>'
+'<td class="td-actions">'
+(t.status==='待随访'||t.status==='已逾期'
?'<button class="btn btn-link" onclick="execFollow()">执行随访</button><button class="btn btn-link btn-link--muted" onclick="showToast(\'任务说明:'+(t.note||'无')+'\')">说明</button>'
:'<button class="btn btn-link" onclick="showToast(\'该任务已完成,随访结果见「随访管理」\')">查看结果</button>')
+'</td></tr>';
}).join(''):'<tr><td colspan="14" class="td-empty">暂无匹配任务</td></tr>';
var fl=currentFreq();
document.getElementById('freqBody').innerHTML=fl.length?fl.map(function(t,i){
var e=E(t.code);
return '<tr><td>'+(i+1)+'</td>'+empCells(e)+'<td>'+e.unit+'</td><td>'+e.dept+'</td>'
+'<td><span class="chip">'+e.group+'</span></td><td>'+t.freq+'</td><td>'+e.point+'</td>'
+'<td><span class="badge '+statusBadge[t.run]+'">'+t.run+'</span></td>'
+'<td class="td-actions">'
+(t.run==='启用'
?'<button class="btn btn-link" onclick="toggleFreq('+t.id+',\'暂停\')">暂停</button>'
:'<button class="btn btn-link" onclick="toggleFreq('+t.id+',\'启用\')">启用</button>')
+'<button class="btn btn-link" onclick="genOnce('+t.id+')">立即生成本期</button>'
+'</td></tr>';
}).join(''):'<tr><td colspan="12" class="td-empty">暂无匹配任务</td></tr>';
var al=currentApply();
document.getElementById('applyBody').innerHTML=al.length?al.map(function(t,i){
var e=E(t.code);
return '<tr><td>'+(i+1)+'</td>'+empCells(e)+'<td>'+e.unit+'</td><td>'+e.dept+'</td><td>'+e.point+'</td>'
+'<td style="text-align:left;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="'+t.content+'">'+t.content+'</td>'
+'<td>'+t.date+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="acceptApply('+t.id+')">接受</button>'
+'<button class="btn btn-link btn-link--danger" onclick="rejectApply('+t.id+')">拒绝</button>'
+'</td></tr>';
}).join(''):'<tr><td colspan="11" class="td-empty">暂无待处理申请</td></tr>';
}
function applyFilter(){renderAll();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
resetOrg();
applyFilter();
}
/* ===== 任务操作 ===== */
function execFollow(){
showToast('正在跳转「随访管理」录入本次随访结果…');
setTimeout(function(){location.href='daily-follow.html';},700);
}
function toggleFreq(id,val){
var t=freqTasks.find(function(x){return x.id===id;});
t.run=val;renderAll();
showToast('「'+E(t.code).name+'」频次任务已'+val);
}
function genOnce(id){
var t=freqTasks.find(function(x){return x.id===id;});
if(t.run==='暂停'){showToast('任务已暂停,请先启用');return;}
var plan='2026-09-'+String(8+onceTasks.length%14).padStart(2,'0');
onceTasks.push({id:nextId++,code:t.code,plan:plan,actual:'',status:'待随访',note:'由频次任务立即生成本期'});
switchTab('once');
showToast('已生成单次任务,计划日期 '+plan);
}
function acceptApply(id){
var t=applyTasks.find(function(x){return x.id===id;});
applyTasks=applyTasks.filter(function(x){return x.id!==id;});
onceTasks.push({id:nextId++,code:t.code,plan:'2026-09-12',actual:'',status:'待随访',note:'员工申请:'+t.content});
switchTab('once');
showToast('已接受申请并转为单次任务(计划 2026-09-12)');
}
function rejectApply(id){
var t=applyTasks.find(function(x){return x.id===id;});
applyTasks=applyTasks.filter(function(x){return x.id!==id;});
renderAll();
showToast('已拒绝「'+E(t.code).name+'」的随访申请');
}
/* ===== 新建任务 ===== */
function fillEmpSel(){
var sel=document.getElementById('cEmp');
sel.innerHTML='<option value="">请选择员工</option>';
empPool.forEach(function(e){
var o=document.createElement('option');o.value=e.code;o.textContent=e.name+'('+e.code+' · '+e.point+')';
sel.appendChild(o);
});
}
function onTypeRadio(){
var v=document.querySelector('input[name="cType"]:checked').value;
document.getElementById('boxOnceDate').style.display=v==='once'?'':'none';
document.getElementById('boxFreq').style.display=v==='freq'?'':'none';
}
function openCreate(){
document.getElementById('cEmp').value='';
document.getElementById('cDate').value='2026-09-15';
document.getElementById('cNote').value='';
document.querySelector('input[name="cType"][value="once"]').checked=true;
onTypeRadio();
document.getElementById('createMask').classList.add('open');
}
function saveCreate(){
var code=document.getElementById('cEmp').value;
if(!code){showToast('请选择随访员工');return;}
var type=document.querySelector('input[name="cType"]:checked').value;
if(type==='once'){
var date=document.getElementById('cDate').value;
if(!date){showToast('请选择计划随访日期');return;}
onceTasks.push({id:nextId++,code:code,plan:date,actual:'',status:'待随访',note:document.getElementById('cNote').value});
closeModal('createMask');switchTab('once');
showToast('单次任务已创建');
}else{
freqTasks.push({id:nextId++,code:code,freq:document.getElementById('cFreq').value,run:'启用'});
closeModal('createMask');switchTab('freq');
showToast('频次任务已创建并启用');
}
}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
fillEmpSel();
renderAll();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,831 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随访管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.btn-sm{height:26px;padding:0 10px;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1650px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
.form-grid--3{grid-template-columns:1fr 1fr 1fr;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-item--full{grid-column:1/-1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
textarea.form-control{height:52px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.radio-group{display:flex;gap:16px;min-height:32px;align-items:center;flex-wrap:wrap;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
.with-btn{display:flex;gap:8px;}
.emp-card{display:grid;grid-template-columns:repeat(4,1fr);gap:6px 12px;background:#F8FBFF;border:1px solid #D6EAFB;border-radius:var(--radius-sm);padding:10px 14px;margin-bottom:14px;font-size:var(--font-xs);color:var(--text-secondary);}
.emp-card b{color:var(--text-primary);font-weight:600;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:680px;max-height:80vh;display:flex;flex-direction:column;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
.modal--wide .table-holder{flex:1;overflow-y:auto;border:1px solid var(--border);border-radius:var(--radius-sm);max-height:340px;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item active">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>随访员工</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>员工确认状态</label>
<select class="filter-select" id="filterConfirm" style="min-width:100px;">
<option value="">全部</option><option>已确认</option><option>待确认</option>
</select>
</div>
<div class="filter-item">
<label>随访日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增随访记录
</button>
<button class="btn btn-default btn-link--danger" onclick="askBatchDelete()">批量删除</button>
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">「随访任务」中的待随访任务可在本处录入执行结果</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th><th>序号</th><th>随访员工</th><th>员工编号</th><th>性别</th><th>年龄</th><th>症状</th><th>随访形式</th><th>随访医生</th><th>分配医疗点</th><th>医疗点所属单位</th><th>随访日期</th><th>下次随访</th><th>员工确认</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑随访抽屉 -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增随访记录</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section">随访员工</div>
<div class="with-btn" style="margin-bottom:12px;">
<input class="form-control" id="fUser" readonly placeholder="点击下方按钮选择随访员工" style="flex:1;">
<button class="btn btn-default" onclick="openEmpPick()">选择员工</button>
</div>
<div class="emp-card" id="empCard" style="display:none;"></div>
<div class="form-grid">
<div class="form-item"><span class="form-label">症状</span><input class="form-control" id="fSymptom" placeholder="本次随访主要症状"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 随访形式</span>
<div class="radio-group" id="rgForm">
<label class="radio"><input type="radio" name="fForm" value="门诊">门诊</label>
<label class="radio"><input type="radio" name="fForm" value="巡诊">巡诊</label>
<label class="radio"><input type="radio" name="fForm" value="家庭">家庭</label>
<label class="radio"><input type="radio" name="fForm" value="电话">电话</label>
</div></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 随访日期</span><input class="form-control" type="date" id="fDate"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 随访医生</span><select class="form-control" id="fDoctor"><option value="">请选择医生</option></select></div>
<div class="form-item"><span class="form-label">下次随访日期</span><input class="form-control" type="date" id="fNext"></div>
</div>
<div class="form-section">体征</div>
<div class="form-grid form-grid--3">
<div class="form-item"><span class="form-label">血压(mmHg)</span><input class="form-control" id="vBp" placeholder="如 130/85"></div>
<div class="form-item"><span class="form-label">心率(次/分)</span><input class="form-control" id="vHr" placeholder="次/分钟"></div>
<div class="form-item"><span class="form-label">血糖(mmol/L)</span><input class="form-control" id="vGlucose" placeholder="空腹"></div>
<div class="form-item"><span class="form-label">身高(cm)</span><input class="form-control" id="vHeight" oninput="calcBmi()" placeholder="厘米"></div>
<div class="form-item"><span class="form-label">体重(kg)</span><input class="form-control" id="vWeight" oninput="calcBmi()" placeholder="公斤"></div>
<div class="form-item"><span class="form-label">体质指数BMI</span><input class="form-control" id="vBmi" readonly placeholder="自动计算"></div>
<div class="form-item"><span class="form-label">腰围(cm)</span><input class="form-control" id="vWaist" placeholder="厘米"></div>
<div class="form-item form-item--full"><span class="form-label">其他体征</span><input class="form-control" id="vOther" placeholder="补充体征"></div>
</div>
<div class="form-section">生活方式</div>
<div class="form-grid form-grid--3">
<div class="form-item"><span class="form-label">日吸烟量(支)</span><input class="form-control" id="lSmoke" placeholder="0"></div>
<div class="form-item"><span class="form-label">日饮酒量(两)</span><input class="form-control" id="lDrink" placeholder="0"></div>
<div class="form-item"><span class="form-label">运动频率(次/周)</span><input class="form-control" id="lSport" placeholder="每周次数"></div>
<div class="form-item"><span class="form-label">摄盐情况</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="lSalt" value="轻"></label>
<label class="radio"><input type="radio" name="lSalt" value="中"></label>
<label class="radio"><input type="radio" name="lSalt" value="重"></label>
</div></div>
<div class="form-item"><span class="form-label">心理行为</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="lMental" value="良好">良好</label>
<label class="radio"><input type="radio" name="lMental" value="一般">一般</label>
<label class="radio"><input type="radio" name="lMental" value="差"></label>
</div></div>
<div class="form-item"><span class="form-label">遵医行为</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="lWill" value="良好">良好</label>
<label class="radio"><input type="radio" name="lWill" value="一般">一般</label>
<label class="radio"><input type="radio" name="lWill" value="差"></label>
</div></div>
</div>
<div class="form-section">用药情况</div>
<div class="form-grid">
<div class="form-item"><span class="form-label">服药依从性</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="mDepend" value="规律">规律</label>
<label class="radio"><input type="radio" name="mDepend" value="间断">间断</label>
<label class="radio"><input type="radio" name="mDepend" value="不服药">不服药</label>
</div></div>
<div class="form-item"><span class="form-label">药物不良反应</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="mReact" value="无"></label>
<label class="radio"><input type="radio" name="mReact" value="有"></label>
</div></div>
<div class="form-item form-item--full"><span class="form-label">其他药物/备注</span><input class="form-control" id="mOther" placeholder="自服药物或其他说明"></div>
</div>
<div class="form-section">健康风险评估与处方</div>
<div class="form-item form-item--full"><span class="form-label">健康风险评估</span><textarea class="form-control" id="fRisk" placeholder="风险等级与主要风险因素"></textarea></div>
<div class="form-grid">
<div class="form-item"><span class="form-label">膳食处方</span><textarea class="form-control" id="fFood" placeholder="饮食建议"></textarea></div>
<div class="form-item"><span class="form-label">运动处方</span><textarea class="form-control" id="fSportPlan" placeholder="运动建议"></textarea></div>
</div>
<div class="form-item form-item--full"><span class="form-label">随访效果评价</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fEval" value="良好">良好</label>
<label class="radio"><input type="radio" name="fEval" value="一般">一般</label>
<label class="radio"><input type="radio" name="fEval" value="差"></label>
</div></div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 查看详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">随访详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('view')">关闭</button></div>
</div>
<!-- [交互] 随访员工选人弹窗(仅已分配员工) -->
<div class="modal-mask" id="empMask">
<div class="modal modal--wide">
<div class="modal__title">选择随访员工</div>
<div class="filter-bar" style="border-radius:var(--radius-sm);box-shadow:none;padding:0;margin-bottom:14px;">
<div class="filter-item">
<label>姓名/工号</label>
<input class="filter-input" id="empQ" placeholder="输入姓名或工号过滤" style="min-width:150px;" oninput="renderEmpPick()">
</div>
</div>
<div class="table-holder">
<table class="mini-table">
<thead><tr><th style="width:40px"></th><th>姓名</th><th>工号</th><th>性别</th><th>年龄</th><th>所属单位</th><th>慢病类型</th><th>分配医疗点</th></tr></thead>
<tbody id="empBody"></tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('empMask')">取消</button>
<button class="btn btn-primary" onclick="confirmEmp()">确认选择</button>
</div>
</div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="delText">确认删除该条随访记录?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):随访对象为已分配员工 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var doctorPool=['石维国','李桂芳','曲志明','张秀兰','高建军','闫婷','刘玉梅'];
/* 可选随访员工池(与员工查询/员工分配一致的12人已分配名册) */
var empPool=[
{code:'CQ021578',name:'石维国',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',tel:'139****2051',point:'樊学作业区医疗点',chronic:'血压偏高'},
{code:'CQ032144',name:'李桂芳',sex:'女',age:38,unit:'采油一厂',dept:'樊学作业区',tel:'138****7742',point:'樊学作业区医疗点',chronic:''},
{code:'CQ045130',name:'杨慧敏',sex:'女',age:33,unit:'采油一厂',dept:'樊学作业区',tel:'188****7214',point:'樊学作业区医疗点',chronic:''},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',tel:'137****8890',point:'姬圪台作业区医疗点',chronic:''},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',tel:'136****2248',point:'姬圪台作业区医疗点',chronic:'超重/血脂偏高'},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',tel:'150****6127',point:'桐园作业区医疗点',chronic:'高血压2级'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',tel:'139****0086',point:'西峰基地医疗点',chronic:'2型糖尿病/超重'},
{code:'CQ041822',name:'闫婷',sex:'女',age:34,unit:'第四采油厂',dept:'健康管理部',tel:'186****5527',point:'西峰基地医疗点',chronic:''},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',tel:'135****7719',point:'靖边气田医疗点',chronic:''},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',tel:'147****9003',point:'靖边气田医疗点',chronic:''},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,unit:'采油一厂',dept:'生产技术部',tel:'155****3316',point:'安塞采油队长住点医疗室',chronic:''},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',tel:'133****4860',point:'乌审旗采气区医疗点',chronic:'超重'}
];
/* 随访记录:s=症状 form=形式 confirm=员工确认 */
var follows=[
{id:1,user:'赵万福',code:'CQ033096',sex:'男',age:39,point:'桐园作业区医疗点',doctor:'曲志明',form:'门诊',date:'2026-09-05',next:'2026-10-05',confirm:'已确认',symptom:'头晕较前减轻,血压波动',
bp:'146/92',hr:76,glucose:5.6,height:172,weight:79,bmi:26.7,waist:92,other:'',
smoke:10,drink:2,sport:2,salt:'中',mental:'良好',will:'一般',depend:'间断',react:'无',otherMed:'自购降压补剂已停用',
risk:'高危:血压未达标+吸烟,建议加强用药依从性管理',food:'低盐低脂,每日食盐<5g',sportPlan:'每周5次快走,每次30分钟',evalv:'一般'},
{id:2,user:'高建军',code:'CQ010564',sex:'男',age:50,point:'西峰基地医疗点',doctor:'闫婷',form:'门诊',date:'2026-09-03',next:'2026-09-17',confirm:'已确认',symptom:'口渴乏力减轻,血糖仍偏高',
bp:'132/84',hr:72,glucose:8.9,height:168,weight:77,bmi:27.3,waist:95,other:'足背动脉搏动正常',
smoke:0,drink:1,sport:3,salt:'中',mental:'良好',will:'良好',depend:'规律',react:'无',otherMed:'',
risk:'高危:血糖控制欠佳,需两周复查并强化饮食管理',food:'控制总热量,主食粗细搭配',sportPlan:'餐后1小时快走30分钟',evalv:'一般'},
{id:3,user:'张秀兰',code:'CQ027710',sex:'女',age:45,point:'姬圪台作业区医疗点',doctor:'曲志明',form:'电话',date:'2026-09-01',next:'2026-11-01',confirm:'待确认',symptom:'腰痛缓解,久坐后复现',
bp:'126/80',hr:70,glucose:5.2,height:160,weight:66,bmi:25.8,waist:84,other:'',
smoke:0,drink:0,sport:2,salt:'轻',mental:'良好',will:'良好',depend:'规律',react:'无',otherMed:'',
risk:'中危:超重合并血脂偏高,减重为核心干预目标',food:'低脂高纤维,晚餐少食',sportPlan:'游泳或快走,每周3次',evalv:'良好'},
{id:4,user:'石维国',code:'CQ021578',sex:'男',age:46,point:'樊学作业区医疗点',doctor:'李桂芳',form:'门诊',date:'2026-08-28',next:'2026-10-28',confirm:'已确认',symptom:'无头晕头痛,血压临界',
bp:'138/86',hr:74,glucose:5.8,height:175,weight:75,bmi:24.5,waist:88,other:'',
smoke:5,drink:2,sport:2,salt:'中',mental:'良好',will:'良好',depend:'不服药',react:'无',otherMed:'',
risk:'中危:正常高值血压,暂不用药,生活方式干预',food:'限盐限酒',sportPlan:'每周4次有氧运动',evalv:'良好'},
{id:5,user:'徐立成',code:'CQ022317',sex:'男',age:44,point:'乌审旗采气区医疗点',doctor:'刘玉梅',form:'巡诊',date:'2026-08-26',next:'2026-10-26',confirm:'待确认',symptom:'中暑后恢复,乏力减轻',
bp:'128/84',hr:75,glucose:5.7,height:174,weight:80,bmi:26.4,waist:93,other:'',
smoke:0,drink:1,sport:1,salt:'重',mental:'一般',will:'一般',depend:'不服药',react:'无',otherMed:'',
risk:'中危:超重+摄盐偏重,夏季作业防暑管理',food:'清淡补钾,少食腌制',sportPlan:'避开高温时段锻炼',evalv:'一般'},
{id:6,user:'赵万福',code:'CQ033096',sex:'男',age:39,point:'桐园作业区医疗点',doctor:'曲志明',form:'家庭',date:'2026-08-08',next:'2026-09-05',confirm:'已确认',symptom:'首次入组随访,偶有头晕',
bp:'158/96',hr:80,glucose:6.0,height:172,weight:80,bmi:27.1,waist:94,other:'',
smoke:12,drink:3,sport:0,salt:'重',mental:'一般',will:'差',depend:'不服药',react:'无',otherMed:'曾自行服药后停药',
risk:'高危:新诊断高血压2级,需尽快启动规范治疗',food:'严格限盐,戒酒',sportPlan:'循序渐进有氧,戒烟',evalv:'差'},
{id:7,user:'高建军',code:'CQ010564',sex:'男',age:50,point:'西峰基地医疗点',doctor:'闫婷',form:'门诊',date:'2026-07-15',next:'2026-09-03',confirm:'已确认',symptom:'血糖波动,调整用药方案',
bp:'130/82',hr:70,glucose:7.8,height:168,weight:78,bmi:27.6,waist:96,other:'',
smoke:0,drink:1,sport:2,salt:'中',mental:'良好',will:'良好',depend:'规律',react:'有',otherMed:'二甲双胍餐后胃肠道反应',
risk:'高危:血糖控制欠佳合并药物反应,考虑换缓释剂型',food:'少食多餐',sportPlan:'餐后活动30分钟',evalv:'一般'},
{id:8,user:'张秀兰',code:'CQ027710',sex:'女',age:45,point:'姬圪台作业区医疗点',doctor:'曲志明',form:'电话',date:'2026-07-10',next:'2026-09-01',confirm:'已确认',symptom:'血脂复查提醒',
bp:'124/78',hr:68,glucose:5.1,height:160,weight:67,bmi:26.2,waist:85,other:'',
smoke:0,drink:0,sport:2,salt:'轻',mental:'良好',will:'良好',depend:'规律',react:'无',otherMed:'',
risk:'中危:血脂偏高,三个月复查',food:'减少油炸食品',sportPlan:'每周3-5次中等强度',evalv:'良好'}
];
var nextId=9,editingId=null,curEmp=null,pendingDelId=null;
/* ===== 单位部门双列筛选 ===== */
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(deptMap[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'随访台账导出_2026-09-01 10:12.xlsx',status:'done',time:'2026-09-01 10:12'},
{file:'随访台账导出_2026-08-15 15:20.xlsx',status:'done',time:'2026-08-15 15:20'},
{file:'随访台账导出_2026-07-28 09:05.xlsx',status:'fail',time:'2026-07-28 09:05'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);"></span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'随访台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 批量选择与删除 ===== */
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function askBatchDelete(){
var ids=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return parseInt(c.value);});
if(!ids.length){showToast('请先勾选要删除的记录');return;}
pendingDelId=null;delMode='batch';
document.getElementById('delText').textContent='确认删除勾选的 '+ids.length+' 条随访记录?关联的随访任务将同步标记为未完成。';
document.getElementById('delMask').classList.add('open');
window._batchIds=ids;
}
/* ===== 列表 ===== */
var pageSize=10,currentPage=1,delMode='single';
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var confirm=document.getElementById('filterConfirm').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return follows.filter(function(f){
var emp=empPool.find(function(x){return x.code===f.code;})||{};
return (!name||f.user.indexOf(name)!==-1)
&&(!selUnit||emp.unit===selUnit)&&(!selDept||emp.dept===selDept)
&&(!unit||pointUnit[f.point]===unit)&&(!point||f.point===point)
&&(!confirm||f.confirm===confirm)
&&(!from||f.date>=from)&&(!to||f.date<=to);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="15" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(f,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+f.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+f.user+'</td>'
+'<td>'+f.code+'</td><td>'+f.sex+'</td><td>'+f.age+'</td>'
+'<td style="text-align:left;max-width:180px;overflow:hidden;text-overflow:ellipsis;" title="'+f.symptom+'">'+f.symptom+'</td>'
+'<td><span class="badge badge--info">'+f.form+'</span></td>'
+'<td>'+f.doctor+'</td><td>'+f.point+'</td><td>'+pointUnit[f.point]+'</td>'
+'<td>'+f.date+'</td><td>'+f.next+'</td>'
+'<td><span class="badge '+(f.confirm==='已确认'?'badge--success':'badge--warning')+'">'+f.confirm+'</span></td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView('+f.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openForm('+f.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete('+f.id+')">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
var ca=document.getElementById('checkAll');if(ca)ca.checked=false;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterConfirm').value='';
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
resetOrg();
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 新增/编辑 ===== */
function fillDoctorSel(keep){
var sel=document.getElementById('fDoctor');
sel.innerHTML='<option value="">请选择医生</option>';
doctorPool.forEach(function(n){
var o=document.createElement('option');o.textContent=n;
if(n===keep)o.selected=true;
sel.appendChild(o);
});
}
function setRadio(name,val){document.querySelectorAll('input[name="'+name+'"]').forEach(function(r){r.checked=(r.value===val);});}
function getRadio(name){var r=document.querySelector('input[name="'+name+'"]:checked');return r?r.value:'';}
function calcBmi(){
var h=parseFloat(document.getElementById('vHeight').value);
var w=parseFloat(document.getElementById('vWeight').value);
document.getElementById('vBmi').value=(h>0&&w>0)?(w/(h*h/10000)).toFixed(1):'';
}
function showEmpCard(e){
var card=document.getElementById('empCard');
if(!e){card.style.display='none';return;}
card.style.display='grid';
card.innerHTML='<div>工号 <b>'+e.code+'</b></div><div>性别/年龄 <b>'+e.sex+' / '+e.age+'岁</b></div><div>联系电话 <b>'+e.tel+'</b></div><div>慢病类型 <b>'+(e.chronic||'无')+'</b></div><div>所属单位 <b>'+e.unit+'</b></div><div>所属部门 <b>'+e.dept+'</b></div><div>分配医疗点 <b>'+e.point+'</b></div>';
}
var TEXT_IDS=['fSymptom','vBp','vHr','vGlucose','vHeight','vWeight','vBmi','vWaist','vOther','lSmoke','lDrink','lSport','mOther','fRisk','fFood','fSportPlan'];
var RADIO_NAMES=['lSalt','lMental','lWill','mDepend','mReact','fEval'];
function openForm(id){
editingId=id||null;
var f=id?follows.find(function(x){return x.id===id;}):null;
document.getElementById('formTitle').textContent=id?('编辑随访记录 · '+f.user):'新增随访记录';
TEXT_IDS.forEach(function(k){document.getElementById(k).value='';});
RADIO_NAMES.forEach(function(n){setRadio(n,'');});
setRadio('fForm','');
document.getElementById('fDate').value=f?f.date:'2026-09-07';
document.getElementById('fNext').value=f?f.next:'';
curEmp=f?empPool.find(function(e){return e.code===f.code;}):null;
document.getElementById('fUser').value=f?f.user:'';
showEmpCard(curEmp);
if(f){
document.getElementById('fSymptom').value=f.symptom;
setRadio('fForm',f.form);
document.getElementById('vBp').value=f.bp;document.getElementById('vHr').value=f.hr;document.getElementById('vGlucose').value=f.glucose;
document.getElementById('vHeight').value=f.height;document.getElementById('vWeight').value=f.weight;document.getElementById('vBmi').value=f.bmi;
document.getElementById('vWaist').value=f.waist;document.getElementById('vOther').value=f.other;
document.getElementById('lSmoke').value=f.smoke;document.getElementById('lDrink').value=f.drink;document.getElementById('lSport').value=f.sport;
setRadio('lSalt',f.salt);setRadio('lMental',f.mental);setRadio('lWill',f.will);
setRadio('mDepend',f.depend);setRadio('mReact',f.react);document.getElementById('mOther').value=f.otherMed;
document.getElementById('fRisk').value=f.risk;document.getElementById('fFood').value=f.food;document.getElementById('fSportPlan').value=f.sportPlan;
setRadio('fEval',f.evalv);
}
fillDoctorSel(f?f.doctor:'');
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function saveForm(){
if(!curEmp){showToast('请选择随访员工');return;}
var form=getRadio('fForm');
var date=document.getElementById('fDate').value;
var doctor=document.getElementById('fDoctor').value;
if(!form){showToast('请选择随访形式');return;}
if(!date){showToast('请选择随访日期');return;}
if(!doctor){showToast('请选择随访医生');return;}
var obj={
id:editingId||nextId++,user:curEmp.name,code:curEmp.code,sex:curEmp.sex,age:curEmp.age,
point:curEmp.point,doctor:doctor,form:form,date:date,next:document.getElementById('fNext').value||'—',
confirm:editingId?follows.find(function(x){return x.id===editingId;}).confirm:'待确认',
symptom:document.getElementById('fSymptom').value||'无',
bp:document.getElementById('vBp').value,hr:document.getElementById('vHr').value,glucose:document.getElementById('vGlucose').value,
height:document.getElementById('vHeight').value,weight:document.getElementById('vWeight').value,bmi:document.getElementById('vBmi').value,
waist:document.getElementById('vWaist').value,other:document.getElementById('vOther').value,
smoke:document.getElementById('lSmoke').value,drink:document.getElementById('lDrink').value,sport:document.getElementById('lSport').value,
salt:getRadio('lSalt'),mental:getRadio('lMental'),will:getRadio('lWill'),
depend:getRadio('mDepend'),react:getRadio('mReact'),otherMed:document.getElementById('mOther').value,
risk:document.getElementById('fRisk').value,food:document.getElementById('fFood').value,sportPlan:document.getElementById('fSportPlan').value,
evalv:getRadio('fEval')
};
if(editingId){follows.splice(follows.findIndex(function(x){return x.id===editingId;}),1,obj);}
else{follows.push(obj);}
closeDrawer('form');renderTable();
showToast(editingId?'随访记录已更新':'随访记录已录入,员工确认签字将在 PAD 端随访时完成');
}
/* ===== 详情抽屉 ===== */
function openView(id){
var f=follows.find(function(x){return x.id===id;});
document.getElementById('viewTitle').textContent='随访详情 · '+f.user;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
var signHtml=f.confirm==='已确认'
?'<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px 16px;display:inline-block;"><svg width="150" height="44" viewBox="0 0 150 44"><path d="M8 32c10-20 16 14 26-6 8-16 12 18 22-2 7-14 10 12 18-2 6-10 10 8 18-2 8-8 14 6 24 2" stroke="#14213D" stroke-width="2" fill="none" stroke-linecap="round"/></svg><div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-top:4px;">电子签名 · '+f.date+' 于 PAD 端确认</div></div>'
:'<span style="color:var(--text-placeholder);font-size:var(--font-sm);">员工尚未在 PAD 端确认签字</span>';
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">随访信息</div>'
+'<div class="detail-grid">'+d('随访员工',f.user)+d('员工编号',f.code)+d('性别/年龄',f.sex+' / '+f.age+'岁')+d('分配医疗点',f.point+'('+pointUnit[f.point]+')')+d('随访形式',f.form)+d('随访日期',f.date)+d('随访医生',f.doctor)+d('下次随访',f.next)+d('症状',f.symptom,true)+'</div>'
+'<div class="form-section">体征</div>'
+'<div class="detail-grid">'+d('血压',f.bp? f.bp+' mmHg':'—')+d('心率',f.hr?f.hr+' 次/分':'—')+d('血糖',f.glucose?f.glucose+' mmol/L':'—')+d('身高/体重',(f.height||'—')+' / '+(f.weight||'—'))+d('BMI',f.bmi)+d('腰围',f.waist?f.waist+' cm':'—')+d('其他体征',f.other,true)+'</div>'
+'<div class="form-section">生活方式</div>'
+'<div class="detail-grid">'+d('日吸烟量',f.smoke+' 支')+d('日饮酒量',f.drink+' 两')+d('运动频率',f.sport+' 次/周')+d('摄盐情况',f.salt)+d('心理行为',f.mental)+d('遵医行为',f.will)+'</div>'
+'<div class="form-section">用药情况</div>'
+'<div class="detail-grid">'+d('服药依从性',f.depend)+d('药物不良反应',f.react)+d('其他药物/备注',f.otherMed,true)+'</div>'
+'<div class="form-section">风险评估与处方</div>'
+'<div class="detail-grid">'+d('健康风险评估',f.risk,true)+d('膳食处方',f.food)+d('运动处方',f.sportPlan)+d('随访效果评价',f.evalv)+d('员工确认状态','<span class="badge '+(f.confirm==='已确认'?'badge--success':'badge--warning')+'">'+f.confirm+'</span>')+'</div>'
+'<div class="form-section">员工确认签字</div>'+signHtml;
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
/* ===== 选人弹窗 ===== */
function openEmpPick(){
document.getElementById('empQ').value='';
renderEmpPick();
document.getElementById('empMask').classList.add('open');
}
function renderEmpPick(){
var q=document.getElementById('empQ').value.trim();
var list=empPool.filter(function(e){return !q||e.name.indexOf(q)!==-1||e.code.toLowerCase().indexOf(q.toLowerCase())!==-1;});
document.getElementById('empBody').innerHTML=list.length?list.map(function(e){
return '<tr><td><input type="radio" name="empSel" value="'+e.code+'"'+(curEmp&&curEmp.code===e.code?' checked':'')+'></td>'
+'<td style="font-weight:600;">'+e.name+'</td><td>'+e.code+'</td><td>'+e.sex+'</td><td>'+e.age+'</td><td>'+e.unit+'</td>'
+'<td>'+(e.chronic?'<span class="badge badge--muted">'+e.chronic+'</span>':'—')+'</td><td>'+e.point+'</td></tr>';
}).join(''):'<tr><td colspan="8" class="td-empty">未找到匹配员工</td></tr>';
}
function confirmEmp(){
var r=document.querySelector('input[name="empSel"]:checked');
if(!r){showToast('请选择一名员工');return;}
curEmp=empPool.find(function(x){return x.code===r.value;});
document.getElementById('fUser').value=curEmp.name;
showEmpCard(curEmp);
closeModal('empMask');
}
/* ===== 删除 ===== */
function askDelete(id){
pendingDelId=id;delMode='single';
var f=follows.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认删除「'+f.user+'」于 '+f.date+' 的随访记录?关联的随访任务将同步标记为未完成。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
if(delMode==='batch'){
follows=follows.filter(function(f){return window._batchIds.indexOf(f.id)===-1;});
closeModal('delMask');renderTable();
showToast('已批量删除 '+window._batchIds.length+' 条随访记录');
return;
}
follows=follows.filter(function(f){return f.id!==pendingDelId;});
closeModal('delMask');renderTable();
showToast('随访记录已删除');
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,508 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>报告解读管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1550px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top: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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item active">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名/工号</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名或工号">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>体检年份</label>
<select class="filter-select" id="filterYear" style="min-width:90px;">
<option value="">全部</option><option>2026</option><option>2025</option>
</select>
</div>
<div class="filter-item">
<label>解读日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">本台账仅收录「已完成」的解读记录,任务进度请在「解读任务」中跟踪</span>
</div>
<!-- 表格 -->
<div class="table-card">
<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><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 解读归档详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">解读归档详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('view')">关闭</button>
<button class="btn btn-primary" id="viewSignBtn" onclick="openSign()">查看员工签字</button>
</div>
</div>
<!-- [交互] 员工签字弹窗 -->
<div class="modal-mask" id="signMask">
<div class="modal">
<div class="modal__title">员工确认签字</div>
<div id="signHolder" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('signMask')">关闭</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):仅已完成解读的归档记录,与「解读任务」页同源 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var groupBadge={'健康人群':'badge--success','亚健康人群':'badge--info','慢性病风险人群':'badge--warning','已确诊慢病人群':'badge--danger','突发风险人群':'badge--danger'};
var records=[
{id:1,code:'CQ033096',name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',year:'2026',doctor:'曲志明',date:'2026-09-06',
summary:'血压 158/96mmHg;BMI 27.1;血脂轻度异常;心电正常',advise:'高血压2级合并超重,已纳入慢病管理;建议低盐减重、规律服药并每月随访。',group:'已确诊慢病人群'},
{id:2,code:'CQ010564',name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',year:'2026',doctor:'闫婷',date:'2026-08-30',
summary:'空腹血糖 8.9mmol/L;糖化 7.8%;BMI 27.6;轻度脂肪肝',advise:'2型糖尿病诊断明确,转内分泌专科复核用药方案;双周随访控制血糖。',group:'已确诊慢病人群'},
{id:3,code:'CQ032144',name:'李桂芳',sex:'女',age:38,unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',doctor:'李桂芳',date:'2026-09-02',
summary:'各项指标基本正常;维生素D偏低',advise:'健康状况良好,补充维生素D并保持运动习惯。',group:'健康人群'},
{id:4,code:'CQ045130',name:'杨慧敏',sex:'女',age:33,unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',doctor:'石维国',date:'2026-09-04',
summary:'血常规轻度贫血;余正常',advise:'轻度缺铁性贫血,加强营养,三个月复查血常规。',group:'亚健康人群'},
{id:5,code:'CQ029905',name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',year:'2025',doctor:'刘玉梅',date:'2025-11-20',
summary:'颈椎退变;尿酸 455μmol/L',advise:'颈椎病干预与尿酸管理,控制高嘌呤饮食,年度复查。',group:'慢性病风险人群'},
{id:6,code:'CQ019203',name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2025',doctor:'张秀兰',date:'2025-11-06',
summary:'冠心病病史;LDL-C 4.1mmol/L',advise:'冠心病二级预防,强化降脂,随身携带急救药物。',group:'已确诊慢病人群'},
{id:7,code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',year:'2025',doctor:'刘玉梅',date:'2025-10-28',
summary:'BMI 26.8;尿酸 438μmol/L;血压正常高值',advise:'体重与尿酸共管,清淡饮食,多饮水。',group:'慢性病风险人群'}
];
/* ===== 单位部门双列筛选 ===== */
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(deptMap[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'解读归档台账导出_2026-09-05 14:18.xlsx',status:'done',time:'2026-09-05 14:18'},
{file:'解读归档台账导出_2026-08-25 10:52.xlsx',status:'done',time:'2026-08-25 10:52'},
{file:'解读归档台账导出_2026-08-09 16:07.xlsx',status:'fail',time:'2026-08-09 16:07'}
];
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);"></span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'解读归档台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var q=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var year=document.getElementById('filterYear').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return records.filter(function(r){
return (!q||r.name.indexOf(q)!==-1||r.code.toLowerCase().indexOf(q.toLowerCase())!==-1)
&&(!selUnit||r.unit===selUnit)&&(!selDept||r.dept===selDept)
&&(!unit||pointUnit[r.point]===unit)&&(!point||r.point===point)&&(!year||r.year===year)
&&(!from||r.date>=from)&&(!to||r.date<=to);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="13" class="td-empty">暂无归档记录</td></tr>';return;}
var html='';
pageData.forEach(function(r,i){
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+r.name+'</td>'
+'<td>'+r.code+'</td><td>'+r.sex+'</td><td>'+r.age+'</td><td>'+r.unit+'</td><td>'+r.dept+'</td>'
+'<td>'+r.point+'</td><td>'+pointUnit[r.point]+'</td><td>'+r.year+'</td><td>'+r.doctor+'</td><td>'+r.date+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView('+r.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openView('+r.id+',true)">签字</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterYear').value='';
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
resetOrg();
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 详情抽屉 ===== */
var curViewId=null;
function openView(id,signFirst){
var r=records.find(function(x){return x.id===id;});
curViewId=id;
document.getElementById('viewTitle').textContent='解读归档详情 · '+r.name;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">员工基本信息</div>'
+'<div class="detail-grid">'+d('姓名',r.name)+d('员工编号',r.code)+d('性别/年龄',r.sex+' / '+r.age+'岁')+d('单位/部门',r.unit+' / '+r.dept)+d('分配医疗点',r.point)+d('医疗点单位',pointUnit[r.point])+d('体检年份',r.year+'年度体检报告')+d('解读医生',r.doctor)+d('解读日期',r.date)+'</div>'
+'<div class="form-section">解读结论</div>'
+'<div class="detail-grid">'+d('报告异常摘要',r.summary,true)+d('综合解读建议',r.advise,true)+d('五类人群分类','<span class="badge '+(groupBadge[r.group]||'badge--muted')+'">'+r.group+'</span>',true)+'</div>';
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
if(signFirst){openSign();}
}
function openSign(){
var r=records.find(function(x){return x.id===curViewId;});
document.getElementById('signHolder').innerHTML=
'<svg width="200" height="64" viewBox="0 0 200 64"><path d="M14 46c16-28 22 18 36-10 10-22 16 26 30-4 9-18 14 16 24-4 8-16 14 12 26 4 12-6 24 8 38 2" stroke="#14213D" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg>'
+'<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-top:8px;">'+r.name+' · '+r.date+' 于 PAD 端确认签字(电子签名哈希存证)</div>';
document.getElementById('signMask').classList.add('open');
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,556 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>解读任务 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1700px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal--wide{width:680px;max-height:84vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-base);font-weight:600;flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:20px 24px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:18px 0 10px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
textarea.form-control{height:72px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:14px;display:flex;align-items:center;gap:10px;color:var(--text-placeholder);font-size:var(--font-xs);background:#FAFAFA;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item active">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名/工号</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名或工号">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>分配医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>体检年份</label>
<select class="filter-select" id="filterYear" style="min-width:90px;">
<option value="">全部</option><option>2026</option><option>2025</option>
</select>
</div>
<div class="filter-item">
<label>任务状态</label>
<select class="filter-select" id="filterStatus" style="min-width:100px;">
<option value="">全部</option><option>待解读</option><option>已逾期</option><option>已完成</option>
</select>
</div>
<div class="filter-item">
<label>解读日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">任务由体检报告导入自动生成;解读完成后同步至「报告解读管理」台账</span>
</div>
<!-- 表格 -->
<div class="table-card">
<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><th>任务状态</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 报告解读弹窗 -->
<div class="modal-mask" id="intMask">
<div class="modal--wide">
<div class="modal__header"><span id="intTitle">报告解读</span><button class="modal__close" onclick="closeModal('intMask')">×</button></div>
<div class="modal__body">
<div class="form-section" style="margin-top:0;">员工体检摘要</div>
<div class="detail-grid" id="intSummary"></div>
<div class="form-section">综合解读建议</div>
<textarea class="form-control" id="intAdvise" style="height:88px;" placeholder="结合体检指标、既往史与生活方式,给出个体化综合解读与建议"></textarea>
<div class="form-section">五类人群重新分类</div>
<div style="display:flex;align-items:center;gap:10px;">
<select class="form-control" id="intGroup" style="max-width:260px;">
<option value="">请选择分类</option>
<option>健康人群</option><option>亚健康人群</option><option>慢性病风险人群</option><option>已确诊慢病人群</option><option>突发风险人群</option>
</select>
<span style="font-size:var(--font-xs);color:var(--text-placeholder);">分类结果将回写健康档案并驱动随访/干预策略</span>
</div>
<div class="form-section">员工确认签字</div>
<div class="upload-box">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 13V4m0 0L6.5 7.5M10 4l3.5 3.5" stroke="#999" stroke-width="1.5" stroke-linecap="round"/><path d="M3 14v2a2 2 0 002 2h10a2 2 0 002-2v-2" stroke="#999" stroke-width="1.5" stroke-linecap="round"/></svg>
签字文件由员工在 PAD 端解读确认时自动回传,无需在此上传
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('intMask')">取消</button>
<button class="btn btn-primary" id="intSaveBtn" onclick="saveInterpret()">保存并完成解读</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):解读任务由2026年度体检报告回传生成,与员工名册/员工查询页体检指标一致 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* summary=摘要 advise=综合建议 group=五类分类 done=完成日期 doctor=解读医生 */
var tasks=[
{id:1,code:'CQ033096',name:'赵万福',sex:'男',age:39,tel:'150****6127',unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',year:'2026',deadline:'2026-10-31',done:'2026-09-06',doctor:'曲志明',status:'已完成',
summary:'血压 158/96mmHg;BMI 27.1;血脂轻度异常;心电正常',advise:'高血压2级合并超重,已纳入慢病管理;建议低盐减重、规律服药并每月随访。',group:'已确诊慢病人群'},
{id:2,code:'CQ010564',name:'高建军',sex:'男',age:50,tel:'139****0086',unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',year:'2026',deadline:'2026-10-31',done:'2026-08-30',doctor:'闫婷',status:'已完成',
summary:'空腹血糖 8.9mmol/L;糖化 7.8%;BMI 27.6;轻度脂肪肝',advise:'2型糖尿病诊断明确,转内分泌专科复核用药方案;双周随访控制血糖。',group:'已确诊慢病人群'},
{id:3,code:'CQ027710',name:'张秀兰',sex:'女',age:45,tel:'136****2248',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'待解读',
summary:'BMI 25.8;总胆固醇 6.4mmol/L;腰椎退变;余大致正常',advise:'',group:''},
{id:4,code:'CQ021578',name:'石维国',sex:'男',age:46,tel:'139****2051',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'待解读',
summary:'血压 138/86mmHg 临界;吸烟指数偏高;余未见明显异常',advise:'',group:''},
{id:5,code:'CQ022317',name:'徐立成',sex:'男',age:44,tel:'133****4860',unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'待解读',
summary:'BMI 26.4;尿酸 442μmol/L;夏季中暑史',advise:'',group:''},
{id:6,code:'CQ036441',name:'刘玉梅',sex:'女',age:40,tel:'135****7719',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'已逾期',
summary:'血压 142/88mmHg;甲状腺结节 TI-RADS 3类',advise:'',group:''},
{id:7,code:'CQ019203',name:'曲志明',sex:'男',age:42,tel:'137****8890',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'已逾期',
summary:'冠心病病史;LDL-C 3.9mmol/L;心电图 ST-T 改变',advise:'',group:''},
{id:8,code:'CQ044871',name:'白瑞',sex:'男',age:31,tel:'155****3316',unit:'采油一厂',dept:'生产技术部',point:'安塞采油队长住点医疗室',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'待解读',
summary:'轻度脂肪肝;尿酸 468μmol/L;余正常',advise:'',group:''},
{id:9,code:'CQ032144',name:'李桂芳',sex:'女',age:38,tel:'138****7742',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',done:'2026-09-02',doctor:'李桂芳',status:'已完成',
summary:'各项指标基本正常;维生素D偏低',advise:'健康状况良好,补充维生素D并保持运动习惯。',group:'健康人群'},
{id:10,code:'CQ041822',name:'闫婷',sex:'女',age:34,tel:'186****5527',unit:'第四采油厂',dept:'健康管理部',point:'西峰基地医疗点',year:'2026',deadline:'2026-10-31',done:'',doctor:'',status:'待解读',
summary:'指标正常;久坐办公,颈肩肌劳损',advise:'',group:''},
{id:11,code:'CQ045130',name:'杨慧敏',sex:'女',age:33,tel:'188****7214',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',done:'2026-09-04',doctor:'石维国',status:'已完成',
summary:'血常规轻度贫血;余正常',advise:'轻度缺铁性贫血,加强营养,三个月复查血常规。',group:'亚健康人群'}
];
var nextId=12,curInterpId=null;
/* ===== 单位部门双列筛选 ===== */
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(deptMap[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'解读任务导出_2026-09-03 10:22.xlsx',status:'done',time:'2026-09-03 10:22'},
{file:'解读任务导出_2026-08-21 16:40.xlsx',status:'done',time:'2026-08-21 16:40'},
{file:'解读任务导出_2026-08-05 09:15.xlsx',status:'fail',time:'2026-08-05 09:15'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);"></span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'解读任务导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var q=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var year=document.getElementById('filterYear').value;
var status=document.getElementById('filterStatus').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return tasks.filter(function(t){
return (!q||t.name.indexOf(q)!==-1||t.code.toLowerCase().indexOf(q.toLowerCase())!==-1)
&&(!selUnit||t.unit===selUnit)&&(!selDept||t.dept===selDept)
&&(!unit||pointUnit[t.point]===unit)&&(!point||t.point===point)
&&(!year||t.year===year)&&(!status||t.status===status)
&&(!from||(t.done&&t.done>=from))&&(!to||(t.done&&t.done<=to));
});
}
var statusBadge={'待解读':'badge--warning','已完成':'badge--success','已逾期':'badge--danger'};
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="14" class="td-empty">暂无匹配任务</td></tr>';return;}
var html='';
pageData.forEach(function(t,i){
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+t.name+'</td>'
+'<td>'+t.code+'</td><td>'+t.sex+'</td><td>'+t.age+'</td><td>'+t.tel+'</td><td>'+t.unit+'</td>'
+'<td>'+t.point+'</td><td>'+pointUnit[t.point]+'</td><td>'+t.year+'</td><td>'+t.deadline+'</td><td>'+(t.done||'—')+'</td>'
+'<td><span class="badge '+statusBadge[t.status]+'">'+t.status+'</span></td>'
+'<td class="td-actions">'
+(t.status==='已完成'
?'<button class="btn btn-link" onclick="openInterpret('+t.id+',true)">查看</button>'
:'<button class="btn btn-link" onclick="openInterpret('+t.id+')">解读</button>')
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterYear').value='';document.getElementById('filterStatus').value='';
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
resetOrg();
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 解读弹窗 ===== */
function openInterpret(id,readonly){
var t=tasks.find(function(x){return x.id===id;});
curInterpId=id;
document.getElementById('intTitle').textContent=(readonly?'解读详情 · ':'报告解读 · ')+t.name+'('+t.year+'年度体检报告)';
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('intSummary').innerHTML=
d('员工',t.name+' / '+t.code)+d('性别/年龄',t.sex+' / '+t.age+'岁')
+d('单位/部门',t.unit+' / '+t.dept)+d('分配医疗点',t.point+'('+pointUnit[t.point]+')')
+d('体检年份',t.year)+d('解读截止',t.deadline)
+d('报告异常摘要',t.summary,true);
document.getElementById('intAdvise').value=t.advise||'';
document.getElementById('intGroup').value=t.group||'';
document.getElementById('intAdvise').readOnly=!!readonly;
document.getElementById('intGroup').disabled=!!readonly;
document.getElementById('intSaveBtn').style.display=readonly?'none':'';
if(readonly){document.getElementById('intSaveBtn').textContent='保存并完成解读';}
document.getElementById('intMask').classList.add('open');
}
function saveInterpret(){
var t=tasks.find(function(x){return x.id===curInterpId;});
var advise=document.getElementById('intAdvise').value.trim();
var group=document.getElementById('intGroup').value;
if(!advise){showToast('请填写综合解读建议');return;}
if(!group){showToast('请选择五类人群重新分类');return;}
t.advise=advise;t.group=group;t.status='已完成';t.done='2026-09-07';
t.doctor=t.doctor||'张秀兰';
closeModal('intMask');renderTable();
showToast('「'+t.name+'」报告解读已完成,结果同步至健康档案');
}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,330 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>入库记录 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1600px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:680px;max-height:82vh;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);display:flex;flex-direction:column;}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-base);font-weight:600;flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:24px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item active">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>药品名称</label>
<input class="filter-input" id="filterMed" placeholder="请输入药品名称">
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>入库医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>入库类型</label>
<select class="filter-select" id="filterType" style="min-width:110px;">
<option value="">全部类型</option>
<option>采购入库</option><option>退药入库</option><option>调拨入库</option>
</select>
</div>
<div class="filter-item">
<label>入库日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏:只读台账,无新增/编辑 -->
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('导出入库流水功能演示')">导出</button>
<span class="action-bar__hint">入库记录为系统流水,不支持人工新增或修改;库存核对请前往「实时库存」</span>
</div>
<!-- 表格 -->
<div class="table-card">
<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><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 入库单详情弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal">
<div class="modal__header"><span id="detailTitle">入库单详情</span><button class="modal__close" onclick="closeModal('detailMask')">×</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeModal('detailMask')">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂','姬塬作业区医疗点':'采油一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* 入库流水:供应商均为虚构名称;入库类型为采购/退药/调拨 */
var stockIns=[
{no:'RK20260905001',m:'阿莫西林胶囊',spec:'0.25g*24粒',unit:'盒',qty:60,type:'采购入库',batch:'AMX-20260612',expire:'2028-05-31',date:'2026-09-05',operator:'李桂芳',point:'樊学作业区医疗点',supplier:'CQ医药配送有限公司',price:18.5,remark:'季度常规补货,验收合格'},
{no:'RK20260905002',m:'硝苯地平控释片',spec:'30mg*7片',unit:'盒',qty:30,type:'采购入库',batch:'NIF-20260418',expire:'2028-03-31',date:'2026-09-05',operator:'李桂芳',point:'樊学作业区医疗点',supplier:'CQ医药配送有限公司',price:32.6,remark:'慢病用药加储'},
{no:'RK20260903001',m:'蒙脱石散',spec:'3g*10袋',unit:'盒',qty:40,type:'采购入库',batch:'MTX-20260225',expire:'2028-01-31',date:'2026-09-03',operator:'张秀兰',point:'姬圪台作业区医疗点',supplier:'陇原药业仓储中心',price:13.9,remark:'夏秋季肠道用药备货'},
{no:'RK20260901001',m:'布洛芬缓释胶囊',spec:'0.3g*20粒',unit:'盒',qty:50,type:'采购入库',batch:'IBU-20260311',expire:'2028-02-28',date:'2026-09-01',operator:'曲志明',point:'姬圪台作业区医疗点',supplier:'陇原药业仓储中心',price:16.0,remark:''},
{no:'RK20260828001',m:'藿香正气口服液',spec:'10ml*10支',unit:'盒',qty:35,type:'调拨入库',batch:'HXX-20251120',expire:'2027-10-31',date:'2026-08-28',operator:'刘玉梅',point:'乌审旗采气区医疗点',supplier:'调拨自靖边气田医疗点',price:15.2,remark:'高温季节防暑用药跨点调拨'},
{no:'RK20260826001',m:'云南白药气雾剂',spec:'85g+30g',unit:'盒',qty:20,type:'采购入库',batch:'YNB-20260115',expire:'2029-01-14',date:'2026-08-26',operator:'闫婷',point:'西峰基地医疗点',supplier:'金城医药集团(虚构)',price:38.0,remark:'外伤应急用药'},
{no:'RK20260825001',m:'复方氯化钠注射液',spec:'500ml',unit:'瓶',qty:30,type:'采购入库',batch:'NS-20260408',expire:'2027-12-31',date:'2026-08-25',operator:'闫婷',point:'西峰基地医疗点',supplier:'金城医药集团(虚构)',price:6.5,remark:'冷链运输,温度记录合格'},
{no:'RK20260822001',m:'二甲双胍缓释片',spec:'0.5g*30片',unit:'盒',qty:25,type:'退药入库',batch:'MET-20250906',expire:'2027-08-31',date:'2026-08-22',operator:'高建军',point:'西峰基地医疗点',supplier:'退药人:慢病患者余量退回',price:25.8,remark:'外包装完好,验收后重新入册'},
{no:'RK20260818001',m:'阿莫西林胶囊',spec:'0.25g*24粒',unit:'盒',qty:40,type:'采购入库',batch:'AMX-20260612',expire:'2028-05-31',date:'2026-08-18',operator:'白瑞',point:'安塞采油队长住点医疗室',supplier:'CQ医药配送有限公司',price:18.5,remark:''},
{no:'RK20260812001',m:'蒙脱石散',spec:'3g*10袋',unit:'盒',qty:30,type:'调拨入库',batch:'MTX-20260225',expire:'2028-01-31',date:'2026-08-12',operator:'呼建国',point:'靖边气田医疗点',supplier:'调拨自桐园作业区医疗点',price:13.9,remark:'支援井站夏秋季备药'}
];
var typeBadge={'采购入库':'badge--info','退药入库':'badge--warning','调拨入库':'badge--success'};
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var med=document.getElementById('filterMed').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var type=document.getElementById('filterType').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return stockIns.filter(function(r){
return (!med||r.m.indexOf(med)!==-1)
&&(!unit||pointUnit[r.point]===unit)&&(!point||r.point===point)
&&(!type||r.type===type)&&(!from||r.date>=from)&&(!to||r.date<=to);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="13" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(r,i){
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+r.no+'</td>'
+'<td style="font-weight:600;">'+r.m+'</td><td>'+r.spec+'</td><td>'+r.unit+'</td>'
+'<td style="color:var(--success);font-weight:600;">+'+r.qty+'</td>'
+'<td><span class="badge '+typeBadge[r.type]+'">'+r.type+'</span></td>'
+'<td>'+r.batch+'</td><td>'+r.expire+'</td><td>'+r.date+'</td>'
+'<td>'+r.operator+'</td><td>'+r.point+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openDetail(\''+r.no+'\')">详情</button></td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
['filterMed','filterFrom','filterTo'].forEach(function(k){document.getElementById(k).value='';});
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterType').value='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 详情弹窗 ===== */
function openDetail(no){
var r=stockIns.find(function(x){return x.no===no;});
document.getElementById('detailTitle').textContent='入库单详情 · '+r.no;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('detailBody').innerHTML=
'<div class="form-section" style="margin-top:0;">单据信息</div>'
+'<div class="detail-grid">'+d('入库单号',r.no)+d('入库类型','<span class="badge '+typeBadge[r.type]+'">'+r.type+'</span>')+d('入库日期',r.date)+d('操作人',r.operator)+d('医疗点所属单位',pointUnit[r.point])+d('入库医疗点',r.point)+d(r.type==='采购入库'?'供应商':'来源',r.supplier,true)+'</div>'
+'<div class="form-section">药品明细</div>'
+'<div class="detail-grid">'+d('药品名称',r.m)+d('规格',r.spec)+d('单位',r.unit)+d('入库数量','<b style="color:var(--success);">+'+r.qty+'</b> '+r.unit)+d('批次号',r.batch)+d('有效期至',r.expire)+d('进货单价',(r.type==='退药入库'?'—':'¥'+r.price.toFixed(2)+' 元/'+r.unit))+d('入库金额',(r.type==='退药入库'?'—':'¥'+(r.price*r.qty).toFixed(2)+' 元'))+d('备注',r.remark,true)+'</div>';
document.getElementById('detailMask').classList.add('open');
}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,333 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>出库记录 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1600px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:680px;max-height:82vh;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);display:flex;flex-direction:column;}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-base);font-weight:600;flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:24px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item active">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>药品名称</label>
<input class="filter-input" id="filterMed" placeholder="请输入药品名称">
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>出库医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>出库类型</label>
<select class="filter-select" id="filterType" style="min-width:110px;">
<option value="">全部类型</option>
<option>处方出库</option><option>非处方出库</option><option>报损出库</option><option>调拨出库</option>
</select>
</div>
<div class="filter-item">
<label>出库日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏:只读台账,无新增/编辑 -->
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('导出出库流水功能演示')">导出</button>
<span class="action-bar__hint">处方出库自动关联就诊记录(员工/非员工);库存核对请前往「实时库存」</span>
</div>
<!-- 表格 -->
<div class="table-card">
<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 id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 出库单详情弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal">
<div class="modal__header"><span id="detailTitle">出库单详情</span><button class="modal__close" onclick="closeModal('detailMask')">×</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeModal('detailMask')">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂','姬塬作业区医疗点':'采油一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* 出库流水:处方出库与「员工就诊/非员工就诊」记录一一对应 */
var stockOuts=[
{no:'CK20260906001',m:'阿莫西林胶囊',spec:'0.25g*24粒',unit:'盒',qty:2,type:'处方出库',ref:'员工就诊·呼建国',date:'2026-09-06',operator:'刘玉梅',point:'靖边气田医疗点',link:'呼建国 / 上呼吸道感染 / 2026-09-06 09:30',remark:'就诊开药自动出库'},
{no:'CK20260905001',m:'硝苯地平控释片',spec:'30mg*7片',unit:'盒',qty:1,type:'处方出库',ref:'员工就诊·赵万福',date:'2026-09-05',operator:'曲志明',point:'桐园作业区医疗点',link:'赵万福 / 高血压病(2级) / 2026-09-05 15:10',remark:'就诊开药自动出库'},
{no:'CK20260905002',m:'硝苯地平控释片',spec:'30mg*7片',unit:'盒',qty:1,type:'处方出库',ref:'非员工就诊·马凤莲',date:'2026-09-05',operator:'李桂芳',point:'樊学作业区医疗点',link:'马凤莲 / 高血压病(2级) / 2026-09-05 10:10',remark:'家属就诊开药'},
{no:'CK20260904001',m:'云南白药气雾剂',spec:'85g+30g',unit:'盒',qty:1,type:'处方出库',ref:'员工就诊·杨慧敏',date:'2026-09-04',operator:'石维国',point:'樊学作业区医疗点',link:'杨慧敏 / 踝关节扭伤 / 2026-09-04 11:00',remark:''},
{no:'CK20260904002',m:'二甲双胍缓释片',spec:'0.5g*30片',unit:'盒',qty:2,type:'处方出库',ref:'非员工就诊·杨来贵',date:'2026-09-04',operator:'曲志明',point:'姬圪台作业区医疗点',link:'杨来贵 / 2型糖尿病复诊 / 2026-09-04 15:40',remark:''},
{no:'CK20260902001',m:'蒙脱石散',spec:'3g*10袋',unit:'盒',qty:1,type:'处方出库',ref:'员工就诊·田震',date:'2026-09-02',operator:'李桂芳',point:'樊学作业区医疗点',link:'田震 / 急性胃肠炎 / 2026-09-02 16:45',remark:'同单另出复方氯化钠1瓶'},
{no:'CK20260902002',m:'复方氯化钠注射液',spec:'500ml',unit:'瓶',qty:1,type:'处方出库',ref:'员工就诊·田震',date:'2026-09-02',operator:'李桂芳',point:'樊学作业区医疗点',link:'田震 / 急性胃肠炎 / 2026-09-02 16:45',remark:'静脉补液'},
{no:'CK20260830001',m:'藿香正气口服液',spec:'10ml*10支',unit:'盒',qty:3,type:'非处方出库',ref:'井站防暑应急领用',date:'2026-08-30',operator:'刘玉梅',point:'乌审旗采气区医疗点',link:'领取班组:苏里格南巡检二班',remark:'高温季节一线班组应急领用,未走就诊'},
{no:'CK20260828001',m:'蒙脱石散',spec:'3g*10袋',unit:'盒',qty:20,type:'调拨出库',ref:'调拨至乌审旗采气区医疗点',date:'2026-08-28',operator:'张秀兰',point:'桐园作业区医疗点',link:'对应入库单:RK20260828001',remark:'防暑止泻用药跨点支援'},
{no:'CK20260820001',m:'布洛芬缓释胶囊',spec:'0.3g*20粒',unit:'盒',qty:3,type:'报损出库',ref:'批号IBU-20250120过期',date:'2026-08-20',operator:'高建军',point:'西峰基地医疗点',link:'报损原因:超过有效期,已拍照留证待销毁',remark:'双人复核确认'}
];
var typeBadge={'处方出库':'badge--info','非处方出库':'badge--muted','报损出库':'badge--danger','调拨出库':'badge--success'};
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var med=document.getElementById('filterMed').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var type=document.getElementById('filterType').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return stockOuts.filter(function(r){
return (!med||r.m.indexOf(med)!==-1)
&&(!unit||pointUnit[r.point]===unit)&&(!point||r.point===point)
&&(!type||r.type===type)&&(!from||r.date>=from)&&(!to||r.date<=to);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="12" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(r,i){
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+r.no+'</td>'
+'<td style="font-weight:600;">'+r.m+'</td><td>'+r.spec+'</td><td>'+r.unit+'</td>'
+'<td style="color:var(--danger);font-weight:600;">-'+r.qty+'</td>'
+'<td><span class="badge '+typeBadge[r.type]+'">'+r.type+'</span></td>'
+'<td style="text-align:left;max-width:180px;overflow:hidden;text-overflow:ellipsis;" title="'+r.ref+'">'+r.ref+'</td>'
+'<td>'+r.date+'</td><td>'+r.operator+'</td><td>'+r.point+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openDetail(\''+r.no+'\')">详情</button></td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
['filterMed','filterFrom','filterTo'].forEach(function(k){document.getElementById(k).value='';});
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterType').value='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 详情弹窗 ===== */
function openDetail(no){
var r=stockOuts.find(function(x){return x.no===no;});
document.getElementById('detailTitle').textContent='出库单详情 · '+r.no;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('detailBody').innerHTML=
'<div class="form-section" style="margin-top:0;">单据信息</div>'
+'<div class="detail-grid">'+d('出库单号',r.no)+d('出库类型','<span class="badge '+typeBadge[r.type]+'">'+r.type+'</span>')+d('出库日期',r.date)+d('操作人',r.operator)+d('医疗点所属单位',pointUnit[r.point])+d('出库医疗点',r.point)+'</div>'
+'<div class="form-section">关联信息</div>'
+'<div class="detail-grid">'+d('关联单据',r.ref,true)+d(r.type==='处方出库'?'就诊摘要':'说明',r.link,true)+'</div>'
+'<div class="form-section">药品明细</div>'
+'<div class="detail-grid">'+d('药品名称',r.m)+d('规格',r.spec)+d('单位',r.unit)+d('出库数量','<b style="color:var(--danger);">-'+r.qty+'</b> '+r.unit)+d('备注',r.remark,true)+'</div>';
document.getElementById('detailMask').classList.add('open');
}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,655 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>非员工就诊 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.btn-sm{height:26px;padding:0 10px;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-item--full{grid-column:1/-1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
textarea.form-control{height:56px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.radio-group{display:flex;gap:18px;min-height:32px;align-items:center;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
.rx-table select,.rx-table input{height:28px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 6px;font-size:var(--font-xs);width:100%;outline:none;}
.rx-table th,.rx-table td{padding:6px 5px;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item active">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>就诊人</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>就诊医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>就诊时间</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增就诊记录
</button>
<button class="btn btn-default btn-link--danger" onclick="askBatchDelete()">批量删除</button>
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">非员工就诊人不在员工库,姓名/证件信息为手工登记</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></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 id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑就诊抽屉 -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增就诊记录</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section">就诊人信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊人姓名</span><input class="form-control" id="fName" placeholder="请输入姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 性别</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fSex" value="男"></label>
<label class="radio"><input type="radio" name="fSex" value="女"></label>
</div></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 年龄</span><input class="form-control" type="number" min="0" max="120" id="fAge" placeholder="岁"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 身份证号</span><input class="form-control" id="fIdcard" maxlength="18" placeholder="18位身份证号码"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 联系电话</span><input class="form-control" id="fPhone" placeholder="手机号"></div>
<div class="form-item"><span class="form-label">与员工关系</span><input class="form-control" id="fRel" placeholder="如:家属/协作单位人员"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊时间</span><input class="form-control" type="datetime-local" id="fTime"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 医疗点所属单位</span>
<select class="form-control" id="fUnit" onchange="onFormUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊医疗点</span>
<select class="form-control" id="fPoint"><option value="">请先选择单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊医生</span>
<select class="form-control" id="fDoctor"><option value="">请选择医生</option></select></div>
<div class="form-item"><span class="form-label">纳入随访</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fFollow" value="是"></label>
<label class="radio"><input type="radio" name="fFollow" value="否" checked></label>
</div></div>
</div>
<div class="form-section">诊断信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label">主诉</span><input class="form-control" id="fMain" placeholder="患者主要症状与持续时间"></div>
<div class="form-item"><span class="form-label">查体</span><input class="form-control" id="fInspect" placeholder="体温/血压/心率等体征"></div>
<div class="form-item"><span class="form-label">既往史</span><input class="form-control" id="fPast" placeholder="既往疾病与手术史"></div>
<div class="form-item"><span class="form-label">现病史</span><input class="form-control" id="fPresent" placeholder="本次发病情况"></div>
<div class="form-item"><span class="form-label">过敏史</span><input class="form-control" id="fAllergy" placeholder="药物/食物过敏情况"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 诊断疾病</span><input class="form-control" id="fDisease" placeholder="如:上呼吸道感染"></div>
</div>
<div class="form-item form-item--full"><span class="form-label">是否复诊</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fRevisit" value="是"></label>
<label class="radio"><input type="radio" name="fRevisit" value="否" checked></label>
</div></div>
<div class="form-section">用药方案</div>
<table class="mini-table rx-table">
<thead><tr><th style="width:34%">药品名称</th><th>规格</th><th style="width:12%">库存</th><th style="width:12%">数量</th><th style="width:24%">用法用量</th><th style="width:44px">操作</th></tr></thead>
<tbody id="rxBody"></tbody>
</table>
<div style="margin-top:8px;"><button class="btn btn-default btn-sm" onclick="addRx()">+ 添加药品</button></div>
<div class="form-section">医嘱与生活建议</div>
<div class="form-item form-item--full"><span class="form-label">医嘱</span><textarea class="form-control" id="fAdvice" placeholder="用药与注意事项"></textarea></div>
<div class="form-grid">
<div class="form-item"><span class="form-label">膳食处方</span><textarea class="form-control" id="fFood" placeholder="饮食建议"></textarea></div>
<div class="form-item"><span class="form-label">运动处方</span><textarea class="form-control" id="fSport" placeholder="运动建议"></textarea></div>
</div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 查看详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">就诊详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('view')">关闭</button></div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="delText">确认删除该条就诊记录?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var doctorPool=['石维国','李桂芳','曲志明','张秀兰','高建军','闫婷','刘玉梅'];
/* 药品池:与「药品信息」页一致的子集 */
var medPool=[
{name:'阿莫西林胶囊',spec:'0.25g*24粒',stock:96,price:18.5},
{name:'布洛芬缓释胶囊',spec:'0.3g*20粒',stock:120,price:16.0},
{name:'硝苯地平控释片',spec:'30mg*7片',stock:45,price:32.6},
{name:'二甲双胍缓释片',spec:'0.5g*30片',stock:80,price:25.8},
{name:'蒙脱石散',spec:'3g*10袋',stock:150,price:13.9},
{name:'复方氯化钠注射液',spec:'500ml',stock:60,price:6.5},
{name:'云南白药气雾剂',spec:'85g+30g',stock:28,price:38.0},
{name:'藿香正气口服液',spec:'10ml*10支',stock:90,price:15.2}
];
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂','姬塬作业区医疗点':'采油一厂'};
/* 非员工就诊:家属/协作单位人员,身份证为虚构号码 */
var nonstaffs=[
{id:1,user:'马凤莲',sex:'女',age:58,idcard:'622822196803124523',phone:'151****3027',rel:'员工家属',point:'樊学作业区医疗点',disease:'高血压病(2级)',doctor:'李桂芳',time:'2026-09-05 10:10',main:'头晕耳鸣3天',inspect:'血压152/94mmHg',past:'高血压4年',present:'劳累后加重',allergy:'无',revisit:'否',follow:'是',rx:[{m:'硝苯地平控释片',spec:'30mg*7片',num:1,dosage:'一次1片 一日1次'}],advice:'每日监测血压',food:'低盐低脂',sport:'适度散步'},
{id:2,user:'杨来贵',sex:'男',age:63,idcard:'620521196307083317',phone:'132****7108',rel:'员工家属',point:'姬圪台作业区医疗点',disease:'2型糖尿病复诊',doctor:'曲志明',time:'2026-09-04 15:40',main:'口渴乏力半月',inspect:'空腹血糖9.2mmol/L',past:'糖尿病8年',present:'血糖控制欠佳',allergy:'无',revisit:'是',follow:'是',rx:[{m:'二甲双胍缓释片',spec:'0.5g*30片',num:2,dosage:'一次1片 一日2次'}],advice:'两周后复查血糖',food:'控制总热量',sport:'餐后快走30分钟'},
{id:3,user:'白莲香',sex:'女',age:41,idcard:'612731198502146625',phone:'158****4410',rel:'协作单位人员',point:'桐园作业区医疗点',disease:'急性胃肠炎',doctor:'张秀兰',time:'2026-09-02 11:20',main:'腹痛腹泻1天',inspect:'脐周压痛,肠鸣音活跃',past:'无',present:'进食不洁饭后腹泻',allergy:'无',revisit:'否',follow:'否',rx:[{m:'蒙脱石散',spec:'3g*10袋',num:1,dosage:'一次1袋 一日3次'},{m:'复方氯化钠注射液',spec:'500ml',num:1,dosage:'静脉滴注 1次'}],advice:'补液防脱水',food:'流质饮食',sport:'注意休息'},
{id:4,user:'丁胜海',sex:'男',age:52,idcard:'620102197403059116',phone:'139****0213',rel:'员工家属',point:'西峰基地医疗点',disease:'腰肌劳损',doctor:'闫婷',time:'2026-08-30 09:15',main:'腰部酸痛2周',inspect:'腰两侧压痛',past:'久坐工作史',present:'渐进性酸痛',allergy:'磺胺类过敏',revisit:'否',follow:'否',rx:[{m:'布洛芬缓释胶囊',spec:'0.3g*20粒',num:1,dosage:'一次1粒 一日2次'}],advice:'避免久坐,配合理疗',food:'均衡营养',sport:'腰背肌锻炼'},
{id:5,user:'周翠兰',sex:'女',age:36,idcard:'610628199001074412',phone:'177****5521',rel:'协作单位人员',point:'乌审旗采气区医疗点',disease:'中暑(先兆)',doctor:'刘玉梅',time:'2026-08-28 14:05',main:'高温作业后头晕出汗',inspect:'体温37.7℃,面色潮红',past:'无',present:'午间露天作业后不适',allergy:'无',revisit:'否',follow:'否',rx:[{m:'藿香正气口服液',spec:'10ml*10支',num:1,dosage:'一次1支 一日2次'}],advice:'阴凉处休息,少量多次饮水',food:'清淡,补充电解质',sport:'避开高温时段'},
{id:6,user:'石长有',sex:'男',age:70,idcard:'622801195601021235',phone:'181****6640',rel:'员工家属',point:'安塞采油队长住点医疗室',disease:'冠心病复诊',doctor:'石维国',time:'2026-08-22 10:35',main:'活动后胸闷1月',inspect:'心率78次/分,律齐',past:'冠心病6年',present:'冬季症状加重',allergy:'无',revisit:'是',follow:'是',rx:[],advice:'随身携带急救药物,一月后心内科复诊',food:'低脂高纤维',sport:'避免剧烈运动'}
];
var nextId=7,editingId=null,curRx=[],pendingDelId=null;
/* ===== 列表 ===== */
var pageSize=10,currentPage=1;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return nonstaffs.filter(function(t){
var d=t.time.slice(0,10);
return (!name||t.user.indexOf(name)!==-1)
&&(!unit||pointUnit[t.point]===unit)&&(!point||t.point===point)
&&(!from||d>=from)&&(!to||d<=to);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
var ca=document.getElementById('checkAll');if(ca)ca.checked=false;
if(!pageData.length){tbody.innerHTML='<tr><td colspan="13" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(t,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+t.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+t.user+(t.follow==='是'?' <span class="badge badge--warning">随访</span>':'')+'</td>'
+'<td>'+t.sex+'</td><td>'+t.age+'</td>'
+'<td>'+t.idcard+'</td>'
+'<td style="text-align:left;max-width:160px;overflow:hidden;text-overflow:ellipsis;" title="'+t.disease+'">'+t.disease+'</td>'
+'<td>'+t.phone+'</td>'
+'<td>'+pointUnit[t.point]+'</td><td>'+t.point+'</td>'
+'<td>'+t.doctor+'</td><td>'+t.time+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView('+t.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openForm('+t.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete('+t.id+')">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 用药方案行 ===== */
function renderRx(){
var html=curRx.map(function(r,i){
var opts='<option value="">请选择药品</option>'+medPool.map(function(m){return '<option'+(m.name===r.m?' selected':'')+'>'+m.name+'</option>';}).join('');
return '<tr>'
+'<td><select onchange="pickMed('+i+',this.value)">'+opts+'</select></td>'
+'<td><input value="'+r.spec+'" readonly placeholder="--"></td>'
+'<td><input value="'+(r.stock||'')+'" readonly placeholder="--"></td>'
+'<td><input type="number" min="1" value="'+r.num+'" oninput="curRx['+i+'].num=this.value" placeholder="盒"></td>'
+'<td><input value="'+r.dosage+'" oninput="curRx['+i+'].dosage=this.value" placeholder="如:一次1粒 一日2次"></td>'
+'<td><button class="btn btn-link btn-link--danger" onclick="curRx.splice('+i+',1);renderRx()">删除</button></td></tr>';
}).join('');
document.getElementById('rxBody').innerHTML=html||'<tr><td colspan="6" style="color:var(--text-placeholder);padding:14px;">暂未添加药品,点击下方「+ 添加药品」</td></tr>';
}
function addRx(){curRx.push({m:'',spec:'',stock:0,num:1,dosage:''});renderRx();}
function pickMed(i,name){
var m=medPool.find(function(x){return x.name===name;});
if(m){curRx[i].m=m.name;curRx[i].spec=m.spec;curRx[i].stock=m.stock;}
renderRx();
}
/* ===== 新增/编辑 ===== */
function fillDoctorSel(keep){
var sel=document.getElementById('fDoctor');
sel.innerHTML='<option value="">请选择医生</option>';
doctorPool.forEach(function(n){
var o=document.createElement('option');o.textContent=n;
if(n===keep)o.selected=true;
sel.appendChild(o);
});
}
function onFormUnit(){
var unit=document.getElementById('fUnit').value;
var sel=document.getElementById('fPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function setRadio(name,val){document.querySelectorAll('input[name="'+name+'"]').forEach(function(r){r.checked=(r.value===val);});}
function getRadio(name){var r=document.querySelector('input[name="'+name+'"]:checked');return r?r.value:'';}
function openForm(id){
editingId=id||null;
var t=id?nonstaffs.find(function(x){return x.id===id;}):null;
document.getElementById('formTitle').textContent=id?('编辑就诊记录 · '+t.user):'新增就诊记录';
['fName','fAge','fIdcard','fPhone','fRel','fMain','fInspect','fPast','fPresent','fAllergy','fDisease','fAdvice','fFood','fSport'].forEach(function(k){document.getElementById(k).value='';});
document.getElementById('fTime').value=t?t.time.replace(' ','T'):'2026-09-07T09:00';
document.getElementById('fUnit').value=t?pointUnit[t.point]:'';
onFormUnit();
if(t){var ps=document.getElementById('fPoint');[].slice.call(ps.options).forEach(function(o){if(o.text===t.point)o.selected=true;});
document.getElementById('fName').value=t.user;
document.getElementById('fAge').value=t.age;
document.getElementById('fIdcard').value=t.idcard;
document.getElementById('fPhone').value=t.phone;
document.getElementById('fRel').value=t.rel;
document.getElementById('fMain').value=t.main;document.getElementById('fInspect').value=t.inspect;
document.getElementById('fPast').value=t.past;document.getElementById('fPresent').value=t.present;
document.getElementById('fAllergy').value=t.allergy;document.getElementById('fDisease').value=t.disease;
document.getElementById('fAdvice').value=t.advice;document.getElementById('fFood').value=t.food;document.getElementById('fSport').value=t.sport;
}
setRadio('fSex',t?t.sex:'');
setRadio('fFollow',t?t.follow:'否');
setRadio('fRevisit',t?t.revisit:'否');
fillDoctorSel(t?t.doctor:'');
curRx=t?t.rx.map(function(r){return {m:r.m,spec:r.spec,stock:(medPool.find(function(m){return m.name===r.m;})||{}).stock||0,num:r.num,dosage:r.dosage};}):[];
renderRx();
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function saveForm(){
var user=document.getElementById('fName').value.trim();
var sex=getRadio('fSex');
var age=document.getElementById('fAge').value;
var idcard=document.getElementById('fIdcard').value.trim();
var phone=document.getElementById('fPhone').value.trim();
var time=document.getElementById('fTime').value;
var unit=document.getElementById('fUnit').value;
var point=document.getElementById('fPoint').value;
var doctor=document.getElementById('fDoctor').value;
var disease=document.getElementById('fDisease').value.trim();
if(!user){showToast('请填写就诊人姓名');return;}
if(!sex){showToast('请选择性别');return;}
if(!age||age<0||age>120){showToast('请填写有效年龄');return;}
if(!/^[1-9]\d{16}[\dXx]$/.test(idcard)){showToast('身份证号需为18位有效号码');return;}
if(!/^1[3-9]\d{9}$/.test(phone.replace(/\*/g,'0'))&&!/^1\d{2}\*{4}\d{4}$/.test(phone)){showToast('请填写有效联系电话');return;}
if(!time){showToast('请选择就诊时间');return;}
if(!unit||!point){showToast('请选择就诊医疗点');return;}
if(!doctor){showToast('请选择就诊医生');return;}
if(!disease){showToast('请填写诊断疾病');return;}
var rx=curRx.filter(function(r){return r.m;});
var bad=rx.find(function(r){return r.num>r.stock;});
if(bad){showToast('「'+bad.m+'」库存仅 '+bad.stock+',开药数量超限');return;}
var obj={
id:editingId||nextId++,user:user,sex:sex,age:Number(age),idcard:idcard,phone:phone,
rel:document.getElementById('fRel').value,point:point,disease:disease,doctor:doctor,time:time.replace('T',' '),
main:document.getElementById('fMain').value,inspect:document.getElementById('fInspect').value,
past:document.getElementById('fPast').value,present:document.getElementById('fPresent').value,
allergy:document.getElementById('fAllergy').value,revisit:getRadio('fRevisit'),follow:getRadio('fFollow'),
rx:rx,advice:document.getElementById('fAdvice').value,
food:document.getElementById('fFood').value,sport:document.getElementById('fSport').value
};
if(editingId){nonstaffs.splice(nonstaffs.findIndex(function(x){return x.id===editingId;}),1,obj);}
else{nonstaffs.push(obj);}
closeDrawer('form');renderTable();
showToast(editingId?'就诊记录已更新':'就诊记录已登记'+(obj.follow==='是'?',并已生成随访任务':''));
}
/* ===== 详情抽屉 ===== */
function openView(id){
var t=nonstaffs.find(function(x){return x.id===id;});
document.getElementById('viewTitle').textContent='就诊详情 · '+t.user;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
var rxHtml=t.rx.length?'<table class="mini-table"><thead><tr><th>药品名称</th><th>规格</th><th>数量</th><th>用法用量</th></tr></thead><tbody>'+t.rx.map(function(r){return '<tr><td>'+r.m+'</td><td>'+r.spec+'</td><td>'+r.num+'</td><td>'+r.dosage+'</td></tr>';}).join('')+'</tbody></table>':'<span style="color:var(--text-placeholder);font-size:var(--font-sm);">本次就诊未开药</span>';
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">就诊人信息</div>'
+'<div class="detail-grid">'+d('就诊人',t.user)+d('性别/年龄',t.sex+' / '+t.age+'岁')+d('身份证号',t.idcard)+d('联系电话',t.phone)+d('与员工关系',t.rel)+d('就诊医疗点',t.point)+d('就诊医生',t.doctor)+d('就诊时间',t.time)+d('是否复诊',t.revisit)+d('纳入随访',t.follow)+'</div>'
+'<div class="form-section">诊断信息</div>'
+'<div class="detail-grid">'+d('主诉',t.main,true)+d('查体',t.inspect,true)+d('既往史',t.past)+d('现病史',t.present)+d('过敏史',t.allergy)+d('诊断疾病',t.disease,true)+'</div>'
+'<div class="form-section">用药方案</div>'+rxHtml
+'<div class="form-section">医嘱与生活建议</div>'
+'<div class="detail-grid">'+d('医嘱',t.advice,true)+d('膳食处方',t.food)+d('运动处方',t.sport)+'</div>';
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
/* ===== 删除 ===== */
function askDelete(id){
pendingDelId=id;delMode='single';
var t=nonstaffs.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认删除「'+t.user+'」于 '+t.time+' 的就诊记录?关联出库的药品不自动回补库存。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
if(delMode==='batch'){
nonstaffs=nonstaffs.filter(function(t){return window._batchIds.indexOf(t.id)===-1;});
showToast('已批量删除 '+window._batchIds.length+' 条记录');
}else{
nonstaffs=nonstaffs.filter(function(t){return t.id!==pendingDelId;});
showToast('就诊记录已删除');
}
closeModal('delMask');renderTable();
}
/* ===== 批量选择与删除 ===== */
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function askBatchDelete(){
var ids=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return parseInt(c.value);});
if(!ids.length){showToast('请先勾选要删除的记录');return;}
pendingDelId=null;delMode='batch';
document.getElementById('delText').textContent='确认删除勾选的 '+ids.length+' 条就诊记录?关联出库的药品不自动回补库存。';
document.getElementById('delMask').classList.add('open');
window._batchIds=ids;
}
/* ===== 异步导出与导出记录 ===== */
var delMode='single';
var exportLogs=[
{file:'非员工就诊记录导出_2026-09-01 09:36.xlsx',status:'done',time:'2026-09-01 09:36'},
{file:'非员工就诊记录导出_2026-08-15 14:02.xlsx',status:'done',time:'2026-08-15 14:02'},
{file:'非员工就诊记录导出_2026-07-30 16:45.xlsx',status:'fail',time:'2026-07-30 16:45'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);"></span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'非员工就诊记录导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,774 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>员工就诊 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:32px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-xs);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.btn-sm{height:26px;padding:0 10px;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1700px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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{min-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;padding:0 6px;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{opacity:.4;cursor:not-allowed;background:#fff;color:var(--text-placeholder);border-color:var(--border);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.drawer-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:900;}
.drawer-mask.open{display:block;}
.drawer{position:fixed;top:56px;right:-760px;bottom:0;width:720px;background:#fff;z-index:910;box-shadow:-8px 0 24px rgba(0,0,0,.12);transition:right .25s ease;display:flex;flex-direction:column;}
.drawer.open{right:0;}
.drawer--narrow{width:640px;right:-680px;}
.drawer__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);font-size:var(--font-lg);font-weight:600;flex-shrink:0;}
.drawer__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.form-section:first-child{margin-top:0;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.form-item--full{grid-column:1/-1;}
.form-label{font-size:var(--font-xs);color:var(--text-secondary);}
.form-label .req{color:var(--danger);}
.form-control{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:100%;}
.form-control:focus{border-color:var(--primary);}
.form-control[readonly]{background:#FAFAFA;color:var(--text-secondary);}
textarea.form-control{height:56px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.radio-group{display:flex;gap:18px;min-height:32px;align-items:center;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
.with-btn{display:flex;gap:8px;}
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;}
.mini-table td{white-space:normal;}
.rx-table select,.rx-table input{height:28px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 6px;font-size:var(--font-xs);width:100%;outline:none;}
.rx-table th,.rx-table td{padding:6px 5px;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:680px;max-height:80vh;display:flex;flex-direction:column;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
.modal--wide .table-holder{flex:1;overflow-y:auto;border:1px solid var(--border);border-radius:var(--radius-sm);max-height:340px;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html" class="active">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>日常诊疗</div>
<div class="sidebar-subgroup__body">
<a href="daily-treatment-staff.html" class="sidebar-item active">员工就诊</a>
<a href="daily-treatment-nonstaff.html" class="sidebar-item">非员工就诊</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>用药记录</div>
<div class="sidebar-subgroup__body">
<a href="daily-stock-in.html" class="sidebar-item">入库记录</a>
<a href="daily-stock-out.html" class="sidebar-item">出库记录</a>
</div>
</div>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>就诊人</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>就诊人所属单位</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>就诊医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:160px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>就诊时间</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增就诊记录
</button>
<button class="btn btn-default btn-link--danger" onclick="askBatchDelete()">批量删除</button>
<button class="btn btn-export" onclick="doExport()">导出</button>
<button class="btn btn-default" onclick="openExport()">查看导出记录</button>
<span class="action-bar__hint">诊断完成后勾选「纳入随访」将自动生成随访任务</span>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th><th>序号</th><th>就诊人</th><th>工号</th><th>性别</th><th>年龄</th><th>就诊人所属单位</th><th>就诊人所属部门</th><th>医疗点所属单位</th><th>就诊医疗点</th><th>就诊疾病</th><th>联系电话</th><th>就诊医生</th><th>就诊时间</th><th>体检报告</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<span id="pageInfo"></span>
<button class="page-btn" id="prevBtn" onclick="turnPage(-1)">上一页</button>
<span id="pageNum" style="min-width:64px;text-align:center;"></span>
<button class="page-btn" id="nextBtn" onclick="turnPage(1)">下一页</button>
</div>
</div>
</main>
<!-- [交互] 新增/编辑就诊抽屉 -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增就诊记录</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section">就诊信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊人</span>
<div class="with-btn">
<input class="form-control" id="fUser" readonly placeholder="点击下方按钮选人" style="flex:1;">
<button class="btn btn-default" onclick="openUserPick()">选择员工</button>
</div></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊时间</span>
<input class="form-control" type="datetime-local" id="fTime"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 医疗点所属单位</span>
<select class="form-control" id="fUnit" onchange="onFormUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊医疗点</span>
<select class="form-control" id="fPoint"><option value="">请先选择单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 就诊医生</span>
<select class="form-control" id="fDoctor"><option value="">请选择医生</option></select></div>
<div class="form-item"><span class="form-label">纳入随访</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fFollow" value="是"></label>
<label class="radio"><input type="radio" name="fFollow" value="否" checked></label>
</div></div>
</div>
<div class="form-section">诊断信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label">主诉</span><input class="form-control" id="fMain" placeholder="患者主要症状与持续时间"></div>
<div class="form-item"><span class="form-label">查体</span><input class="form-control" id="fInspect" placeholder="体温/血压/心率等体征"></div>
<div class="form-item"><span class="form-label">既往史</span><input class="form-control" id="fPast" placeholder="既往疾病与手术史"></div>
<div class="form-item"><span class="form-label">现病史</span><input class="form-control" id="fPresent" placeholder="本次发病情况"></div>
<div class="form-item"><span class="form-label">过敏史</span><input class="form-control" id="fAllergy" placeholder="药物/食物过敏情况"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 诊断疾病</span><input class="form-control" id="fDisease" placeholder="如:上呼吸道感染"></div>
</div>
<div class="form-item form-item--full"><span class="form-label">是否复诊</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fRevisit" value="是"></label>
<label class="radio"><input type="radio" name="fRevisit" value="否" checked></label>
</div></div>
<div class="form-section">用药方案</div>
<table class="mini-table rx-table">
<thead><tr><th style="width:34%">药品名称</th><th>规格</th><th style="width:12%">库存</th><th style="width:12%">数量</th><th style="width:24%">用法用量</th><th style="width:44px">操作</th></tr></thead>
<tbody id="rxBody"></tbody>
</table>
<div style="margin-top:8px;"><button class="btn btn-default btn-sm" onclick="addRx()">+ 添加药品</button></div>
<div class="form-section">医嘱与生活建议</div>
<div class="form-item form-item--full"><span class="form-label">医嘱</span><textarea class="form-control" id="fAdvice" placeholder="用药与注意事项"></textarea></div>
<div class="form-grid">
<div class="form-item"><span class="form-label">膳食处方</span><textarea class="form-control" id="fFood" placeholder="饮食建议"></textarea></div>
<div class="form-item"><span class="form-label">运动处方</span><textarea class="form-control" id="fSport" placeholder="运动建议"></textarea></div>
</div>
</div>
<div class="drawer__footer">
<button class="btn btn-default" onclick="closeDrawer('form')">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
<!-- [交互] 查看详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">就诊详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('view')">关闭</button></div>
</div>
<!-- [交互] 就诊人选人弹窗 -->
<div class="modal-mask" id="userMask">
<div class="modal modal--wide">
<div class="modal__title">选择就诊员工</div>
<div class="filter-bar" style="border-radius:var(--radius-sm);box-shadow:none;padding:0;margin-bottom:14px;">
<div class="filter-item">
<label>姓名/工号</label>
<input class="filter-input" id="userQ" placeholder="输入姓名或工号过滤" style="min-width:150px;" oninput="renderUserPick()">
</div>
</div>
<div class="table-holder">
<table class="mini-table">
<thead><tr><th style="width:40px"></th><th>姓名</th><th>员工工号</th><th>所属单位</th><th>所属部门</th></tr></thead>
<tbody id="userBody"></tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('userMask')">取消</button>
<button class="btn btn-primary" onclick="confirmUser()">确认选择</button>
</div>
</div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div class="modal__text" id="delText">确认删除该条就诊记录?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<!-- [交互] 导出记录抽屉(异步导出任务列表) -->
<div class="drawer-mask" id="exportMask" onclick="closeDrawer('export')"></div>
<div class="drawer drawer--narrow" id="exportDrawer">
<div class="drawer__header"><span>导出记录</span><button class="drawer__close" onclick="closeDrawer('export')">×</button></div>
<div class="drawer__body">
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>文件名</th><th>状态</th><th>生成时间</th><th>操作</th></tr></thead>
<tbody id="exportBody"></tbody>
</table>
</div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* 医生池:分配医疗点的驻点医护 */
var doctorPool=['石维国','李桂芳','曲志明','张秀兰','高建军','闫婷','刘玉梅'];
/* 药品池:与「药品信息」页一致的子集 */
var medPool=[
{name:'阿莫西林胶囊',spec:'0.25g*24粒',stock:96,price:18.5},
{name:'布洛芬缓释胶囊',spec:'0.3g*20粒',stock:120,price:16.0},
{name:'硝苯地平控释片',spec:'30mg*7片',stock:45,price:32.6},
{name:'二甲双胍缓释片',spec:'0.5g*30片',stock:80,price:25.8},
{name:'蒙脱石散',spec:'3g*10袋',stock:150,price:13.9},
{name:'复方氯化钠注射液',spec:'500ml',stock:60,price:6.5},
{name:'云南白药气雾剂',spec:'85g+30g',stock:28,price:38.0},
{name:'藿香正气口服液',spec:'10ml*10支',stock:90,price:15.2}
];
/* 就诊员工池 */
var userPool=[
{name:'石维国',code:'CQ021578',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',tel:'139****2051'},
{name:'李桂芳',code:'CQ032144',sex:'女',age:38,unit:'采油一厂',dept:'樊学作业区',tel:'138****7742'},
{name:'白瑞',code:'CQ044871',sex:'男',age:31,unit:'采油一厂',dept:'生产技术部',tel:'155****3316'},
{name:'曲志明',code:'CQ019203',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',tel:'137****8890'},
{name:'张秀兰',code:'CQ027710',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',tel:'136****2248'},
{name:'赵万福',code:'CQ033096',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',tel:'150****6127'},
{name:'高建军',code:'CQ010564',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',tel:'139****0086'},
{name:'闫婷',code:'CQ041822',sex:'女',age:34,unit:'第四采油厂',dept:'健康管理部',tel:'186****5527'},
{name:'刘玉梅',code:'CQ036441',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',tel:'135****7719'},
{name:'呼建国',code:'CQ029905',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',tel:'147****9003'}
];
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var treatments=[
{id:1,user:'呼建国',code:'CQ029905',sex:'男',age:36,uUnit:'采气一厂',uDept:'靖边净化厂',point:'靖边气田医疗点',disease:'上呼吸道感染',tel:'147****9003',doctor:'刘玉梅',time:'2026-09-06 09:30',main:'发热咽痛2天',inspect:'体温38.2℃,咽部充血',past:'无',present:'受凉后发热',allergy:'无',revisit:'否',follow:'是',rx:[{m:'阿莫西林胶囊',spec:'0.25g*24粒',num:2,dosage:'一次2粒 一日3次'}],advice:'多饮水注意休息,三天后复诊',food:'清淡饮食,忌辛辣',sport:'暂缓运动'},
{id:2,user:'赵万福',code:'CQ033096',sex:'男',age:39,uUnit:'采油二厂',uDept:'桐园作业区',point:'桐园作业区医疗点',disease:'高血压病(2级)',tel:'150****6127',doctor:'曲志明',time:'2026-09-05 15:10',main:'头晕1周,血压波动',inspect:'血压158/96mmHg',past:'高血压3年,规律服药',present:'劳累后头晕加重',allergy:'无',revisit:'是',follow:'是',rx:[{m:'硝苯地平控释片',spec:'30mg*7片',num:1,dosage:'一次1片 一日1次'}],advice:'每日晨起测量血压',food:'低盐低脂,每日食盐<5g',sport:'每周150分钟中等强度有氧'},
{id:3,user:'杨慧敏',code:'CQ045130',sex:'女',age:33,uUnit:'采油一厂',uDept:'樊学作业区',point:'樊学作业区医疗点',disease:'踝关节扭伤',tel:'188****7214',doctor:'石维国',time:'2026-09-04 11:00',main:'跌倒后右踝肿痛3小时',inspect:'右踝外侧肿胀,活动受限',past:'无',present:'井场巡检时滑倒',allergy:'无',revisit:'否',follow:'否',rx:[{m:'云南白药气雾剂',spec:'85g+30g',num:1,dosage:'外用 一日3次'}],advice:'48小时内冰敷,抬高患肢',food:'均衡饮食',sport:'两周制动,后渐进负重训练'},
{id:4,user:'高建军',code:'CQ010564',sex:'男',age:50,uUnit:'第四采油厂',uDept:'基地管理处',point:'西峰基地医疗点',disease:'2型糖尿病复诊',tel:'139****0086',doctor:'闫婷',time:'2026-09-03 10:20',main:'口渴乏力1月',inspect:'空腹血糖8.9mmol/L',past:'糖尿病5年',present:'近1月血糖控制欠佳',allergy:'磺胺类过敏',revisit:'是',follow:'是',rx:[{m:'二甲双胍缓释片',spec:'0.5g*30片',num:2,dosage:'一次1片 一日2次'}],advice:'监测空腹与餐后血糖,两周后复查',food:'控制总热量,主食粗细搭配',sport:'餐后1小时快走30分钟'},
{id:5,user:'田震',code:'CQ037744',sex:'男',age:35,uUnit:'采油一厂',uDept:'樊学作业区',point:'樊学作业区医疗点',disease:'急性胃肠炎',tel:'139****5218',doctor:'李桂芳',time:'2026-09-02 16:45',main:'腹痛腹泻1天',inspect:'肠鸣音活跃,脐周压痛',past:'无',present:'进食隔夜饭后腹泻',allergy:'无',revisit:'否',follow:'否',rx:[{m:'蒙脱石散',spec:'3g*10袋',num:1,dosage:'一次1袋 一日3次'},{m:'复方氯化钠注射液',spec:'500ml',num:1,dosage:'静脉滴注 1次'}],advice:'补液防脱水,症状加重随时就诊',food:'流质饮食过渡,忌生冷油腻',sport:'注意休息'},
{id:6,user:'徐立成',code:'CQ022317',sex:'男',age:44,uUnit:'采气一厂',uDept:'苏里格南作业区',point:'乌审旗采气区医疗点',disease:'中暑(先兆)',tel:'133****4860',doctor:'刘玉梅',time:'2026-08-28 13:30',main:'高温作业后头晕出汗',inspect:'体温37.8℃,面色潮红',past:'无',present:'午间巡检后不适',allergy:'无',revisit:'否',follow:'否',rx:[{m:'藿香正气口服液',spec:'10ml*10支',num:1,dosage:'一次1支 一日2次'}],advice:'阴凉处休息,少量多次饮水',food:'清淡,适当补充电解质',sport:'避开高温时段作业'},
{id:7,user:'张秀兰',code:'CQ027710',sex:'女',age:45,uUnit:'采油二厂',uDept:'姬圪台联合站',point:'姬圪台作业区医疗点',disease:'腰肌劳损',tel:'136****2248',doctor:'曲志明',time:'2026-08-25 09:00',main:'腰部酸痛2周',inspect:'腰两侧压痛,活动受限',past:'久坐工作史',present:'渐进性酸痛',allergy:'无',revisit:'是',follow:'是',rx:[{m:'布洛芬缓释胶囊',spec:'0.3g*20粒',num:1,dosage:'一次1粒 一日2次'}],advice:'避免久坐,配合理疗',food:'均衡营养',sport:'小燕飞等腰背肌锻炼'},
{id:8,user:'石维国',code:'CQ021578',sex:'男',age:46,uUnit:'采油一厂',uDept:'樊学作业区',point:'樊学作业区医疗点',disease:'电光性眼炎',tel:'139****2051',doctor:'李桂芳',time:'2026-08-21 18:20',main:'焊接后眼痛流泪6小时',inspect:'睑结膜充血',past:'无',present:'未戴护目镜作业',allergy:'无',revisit:'否',follow:'否',rx:[],advice:'冷敷,避免揉眼,24小时复查',food:'忌辛辣刺激',sport:'减少用眼'}
];
var nextId=9,editingId=null,curRx=[],curUser=null,pendingDelId=null,pendingUser=null;
/* ===== 单位部门级联筛选 ===== */
var deptMap={
'采油一厂':['樊学作业区','采油大队','安全环保科','生产技术部','工会办公室'],
'采油二厂':['姬圪台联合站','桐园作业区','安全环保科','信息中心'],
'第四采油厂':['基地管理处','健康管理部','生产技术部','财务部'],
'采气一厂':['靖边净化厂','苏里格南作业区','生产技术部']
};
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(deptMap[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
/* ===== 异步导出与导出记录 ===== */
var exportLogs=[
{file:'员工就诊记录导出_2026-09-01 09:36.xlsx',status:'done',time:'2026-09-01 09:36'},
{file:'员工就诊记录导出_2026-08-15 14:02.xlsx',status:'done',time:'2026-08-15 14:02'},
{file:'员工就诊记录导出_2026-07-30 16:45.xlsx',status:'fail',time:'2026-07-30 16:45'}
];
function exportStatusHtml(s){
if(s==='done')return '<span class="badge badge--success">已生成</span>';
if(s==='fail')return '<span class="badge badge--danger">生成失败</span>';
return '<span class="badge badge--warning">生成中</span>';
}
function renderExportList(){
var body=document.getElementById('exportBody');
body.innerHTML=exportLogs.map(function(e){
var act=e.status==='done'?'<button class="btn btn-link" onclick="showToast(\'演示环境不提供真实文件\')">下载</button>'
:e.status==='fail'?'<button class="btn btn-link" onclick="retryExport(\''+e.file+'\')">重试</button>'
:'<span style="color:var(--text-placeholder);"></span>';
return '<tr><td style="text-align:left;">'+e.file+'</td><td>'+exportStatusHtml(e.status)+'</td><td>'+e.time+'</td><td>'+act+'</td></tr>';
}).join('')||'<tr><td colspan="4" class="td-empty">暂无导出任务</td></tr>';
}
function openExport(){
renderExportList();
document.getElementById('exportMask').classList.add('open');
document.getElementById('exportDrawer').classList.add('open');
}
function nowHM(){
var d=new Date(),p=function(n){return (n<10?'0':'')+n;};
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:'员工就诊记录导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
if(e&&e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
function retryExport(file){
var e=exportLogs.find(function(x){return x.file===file;});
if(!e)return;
e.status='ing';e.time='—';renderExportList();
showToast('已重新提交导出任务');
setTimeout(function(){
if(e.status==='ing'){e.status='done';e.time=nowHM();}
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
},1800);
}
/* ===== 批量选择与删除 ===== */
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function askBatchDelete(){
var ids=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return parseInt(c.value);});
if(!ids.length){showToast('请先勾选要删除的记录');return;}
pendingDelId=null;delMode='batch';
document.getElementById('delText').textContent='确认删除勾选的 '+ids.length+' 条就诊记录?关联出库的药品不自动回补库存。';
document.getElementById('delMask').classList.add('open');
window._batchIds=ids;
}
/* ===== 列表 ===== */
var pageSize=10,currentPage=1,delMode='single',pendingBatchIds=null;
function onFilterUnit(){
var unit=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function currentData(){
var name=document.getElementById('filterName').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return treatments.filter(function(t){
var d=t.time.slice(0,10);
return (!name||t.user.indexOf(name)!==-1)
&&(!selUnit||t.uUnit===selUnit)&&(!selDept||t.uDept===selDept)
&&(!unit||pointUnit[t.point]===unit)&&(!point||t.point===point)
&&(!from||d>=from)&&(!to||d<=to);
});
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
var totalPages=Math.ceil(list.length/pageSize)||1;
document.getElementById('pageInfo').textContent='共 '+list.length+' 条记录';
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
var ca=document.getElementById('checkAll');if(ca)ca.checked=false;
if(!pageData.length){tbody.innerHTML='<tr><td colspan="16" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(t,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+t.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+t.user+(t.follow==='是'?' <span class="badge badge--warning">随访</span>':'')+'</td>'
+'<td>'+t.code+'</td><td>'+t.sex+'</td><td>'+t.age+'</td>'
+'<td>'+t.uUnit+'</td><td>'+t.uDept+'</td>'
+'<td>'+pointUnit[t.point]+'</td><td>'+t.point+'</td>'
+'<td style="text-align:left;max-width:160px;overflow:hidden;text-overflow:ellipsis;" title="'+t.disease+'">'+t.disease+'</td>'
+'<td>'+t.tel+'</td><td>'+t.doctor+'</td><td>'+t.time+'</td>'
+'<td><button class="btn btn-link" onclick="showToast(\'体检报告在线预览(演示)\')">查看</button></td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView('+t.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openForm('+t.id+')">编辑</button>'
+'<button class="btn btn-link btn-link--danger" onclick="askDelete('+t.id+')">删除</button>'
+'</td></tr>';
});
tbody.innerHTML=html;
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
resetOrg();
applyFilter();
}
function turnPage(d){
var totalPages=Math.ceil(currentData().length/pageSize)||1;
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
/* ===== 用药方案行 ===== */
function renderRx(){
var html=curRx.map(function(r,i){
var opts='<option value="">请选择药品</option>'+medPool.map(function(m){return '<option'+(m.name===r.m?' selected':'')+'>'+m.name+'</option>';}).join('');
return '<tr>'
+'<td><select onchange="pickMed('+i+',this.value)">'+opts+'</select></td>'
+'<td><input value="'+r.spec+'" readonly placeholder="--"></td>'
+'<td><input value="'+(r.stock||'')+'" readonly placeholder="--"></td>'
+'<td><input type="number" min="1" value="'+r.num+'" oninput="curRx['+i+'].num=this.value" placeholder="盒"></td>'
+'<td><input value="'+r.dosage+'" oninput="curRx['+i+'].dosage=this.value" placeholder="如:一次1粒 一日2次"></td>'
+'<td><button class="btn btn-link btn-link--danger" onclick="curRx.splice('+i+',1);renderRx()">删除</button></td></tr>';
}).join('');
document.getElementById('rxBody').innerHTML=html||'<tr><td colspan="6" style="color:var(--text-placeholder);padding:14px;">暂未添加药品,点击下方「+ 添加药品」</td></tr>';
}
function addRx(){curRx.push({m:'',spec:'',stock:0,num:1,dosage:''});renderRx();}
function pickMed(i,name){
var m=medPool.find(function(x){return x.name===name;});
if(m){curRx[i].m=m.name;curRx[i].spec=m.spec;curRx[i].stock=m.stock;}
renderRx();
}
/* ===== 新增/编辑 ===== */
function fillDoctorSel(keep){
var sel=document.getElementById('fDoctor');
sel.innerHTML='<option value="">请选择医生</option>';
doctorPool.forEach(function(n){
var o=document.createElement('option');o.textContent=n;
if(n===keep)o.selected=true;
sel.appendChild(o);
});
}
function onFormUnit(){
var unit=document.getElementById('fUnit').value;
var sel=document.getElementById('fPoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function setRadio(name,val){document.querySelectorAll('input[name="'+name+'"]').forEach(function(r){r.checked=(r.value===val);});}
function getRadio(name){var r=document.querySelector('input[name="'+name+'"]:checked');return r?r.value:'';}
function openForm(id){
editingId=id||null;
var t=id?treatments.find(function(x){return x.id===id;}):null;
document.getElementById('formTitle').textContent=id?('编辑就诊记录 · '+t.user):'新增就诊记录';
document.getElementById('fUser').value=t?t.user:'';
document.getElementById('fTime').value=t?t.time.replace(' ','T'):'2026-09-07T09:00';
document.getElementById('fUnit').value=t?pointUnit[t.point]:'';
onFormUnit();
if(t){var ps=document.getElementById('fPoint');[].slice.call(ps.options).forEach(function(o){if(o.text===t.point)o.selected=true;});}
fillDoctorSel(t?t.doctor:'');
setRadio('fFollow',t?t.follow:'否');
setRadio('fRevisit',t?t.revisit:'否');
['fMain','fInspect','fPast','fPresent','fAllergy','fDisease','fAdvice','fFood','fSport'].forEach(function(k){document.getElementById(k).value='';});
if(t){
document.getElementById('fMain').value=t.main;document.getElementById('fInspect').value=t.inspect;
document.getElementById('fPast').value=t.past;document.getElementById('fPresent').value=t.present;
document.getElementById('fAllergy').value=t.allergy;document.getElementById('fDisease').value=t.disease;
document.getElementById('fAdvice').value=t.advice;document.getElementById('fFood').value=t.food;document.getElementById('fSport').value=t.sport;
}
curUser=t?userPool.find(function(u){return u.code===t.code;}):null;
curRx=t?t.rx.map(function(r){return {m:r.m,spec:r.spec,stock:(medPool.find(function(m){return m.name===r.m;})||{}).stock||0,num:r.num,dosage:r.dosage};}):[];
renderRx();
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function saveForm(){
var user=document.getElementById('fUser').value;
var time=document.getElementById('fTime').value;
var unit=document.getElementById('fUnit').value;
var point=document.getElementById('fPoint').value;
var doctor=document.getElementById('fDoctor').value;
var disease=document.getElementById('fDisease').value.trim();
if(!user){showToast('请选择就诊人');return;}
if(!time){showToast('请选择就诊时间');return;}
if(!unit||!point){showToast('请选择就诊医疗点');return;}
if(!doctor){showToast('请选择就诊医生');return;}
if(!disease){showToast('请填写诊断疾病');return;}
var rx=curRx.filter(function(r){return r.m;});
var bad=rx.find(function(r){return r.num>r.stock;});
if(bad){showToast('「'+bad.m+'」库存仅 '+bad.stock+',开药数量超限');return;}
var obj={
id:editingId||nextId++,user:user,code:curUser?curUser.code:'--',sex:curUser?curUser.sex:'--',age:curUser?curUser.age:'--',
uUnit:curUser?curUser.unit:'--',uDept:curUser?curUser.dept:'--',point:point,disease:disease,
tel:curUser?curUser.tel:'--',doctor:doctor,time:time.replace('T',' '),
main:document.getElementById('fMain').value,inspect:document.getElementById('fInspect').value,
past:document.getElementById('fPast').value,present:document.getElementById('fPresent').value,
allergy:document.getElementById('fAllergy').value,revisit:getRadio('fRevisit'),follow:getRadio('fFollow'),
rx:rx,advice:document.getElementById('fAdvice').value,
food:document.getElementById('fFood').value,sport:document.getElementById('fSport').value
};
if(editingId){treatments.splice(treatments.findIndex(function(x){return x.id===editingId;}),1,obj);}
else{treatments.push(obj);}
closeDrawer('form');renderTable();
showToast(editingId?'就诊记录已更新':'就诊记录已登记'+(obj.follow==='是'?',并已生成随访任务':''));
}
/* ===== 详情抽屉 ===== */
function openView(id){
var t=treatments.find(function(x){return x.id===id;});
document.getElementById('viewTitle').textContent='就诊详情 · '+t.user;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
var rxHtml=t.rx.length?'<table class="mini-table"><thead><tr><th>药品名称</th><th>规格</th><th>数量</th><th>用法用量</th></tr></thead><tbody>'+t.rx.map(function(r){return '<tr><td>'+r.m+'</td><td>'+r.spec+'</td><td>'+r.num+'</td><td>'+r.dosage+'</td></tr>';}).join('')+'</tbody></table>':'<span style="color:var(--text-placeholder);font-size:var(--font-sm);">本次就诊未开药</span>';
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">就诊信息</div>'
+'<div class="detail-grid">'+d('就诊人',t.user)+d('工号',t.code)+d('性别/年龄',t.sex+' / '+t.age+'岁')+d('联系电话',t.tel)+d('所属单位',t.uUnit)+d('所属部门',t.uDept)+d('就诊医疗点',t.point)+d('就诊医生',t.doctor)+d('就诊时间',t.time)+d('是否复诊',t.revisit)+d('纳入随访',t.follow)+d('体检报告','<button class="btn btn-link" onclick="showToast(\'体检报告在线预览(演示)\')">查看</button>')+'</div>'
+'<div class="form-section">诊断信息</div>'
+'<div class="detail-grid">'+d('主诉',t.main,true)+d('查体',t.inspect,true)+d('既往史',t.past)+d('现病史',t.present)+d('过敏史',t.allergy)+d('诊断疾病',t.disease,true)+'</div>'
+'<div class="form-section">用药方案</div>'+rxHtml
+'<div class="form-section">医嘱与生活建议</div>'
+'<div class="detail-grid">'+d('医嘱',t.advice,true)+d('膳食处方',t.food)+d('运动处方',t.sport)+'</div>';
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
/* ===== 选人弹窗 ===== */
function openUserPick(){
document.getElementById('userQ').value='';
renderUserPick();
document.getElementById('userMask').classList.add('open');
}
function renderUserPick(){
var q=document.getElementById('userQ').value.trim();
var list=userPool.filter(function(e){return !q||e.name.indexOf(q)!==-1||e.code.toLowerCase().indexOf(q.toLowerCase())!==-1;});
document.getElementById('userBody').innerHTML=list.length?list.map(function(e){
return '<tr><td><input type="radio" name="userSel" value="'+e.code+'"'+(curUser&&curUser.code===e.code?' checked':'')+'></td>'
+'<td style="font-weight:600;">'+e.name+'</td><td>'+e.code+'</td><td>'+e.unit+'</td><td>'+e.dept+'</td></tr>';
}).join(''):'<tr><td colspan="5" class="td-empty">未找到匹配员工</td></tr>';
}
function confirmUser(){
var r=document.querySelector('input[name="userSel"]:checked');
if(!r){showToast('请选择一名员工');return;}
curUser=userPool.find(function(x){return x.code===r.value;});
document.getElementById('fUser').value=curUser.name;
closeModal('userMask');
}
/* ===== 删除 ===== */
function askDelete(id){
pendingDelId=id;delMode='single';
var t=treatments.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认删除「'+t.user+'」于 '+t.time+' 的就诊记录?关联出库的药品不自动回补库存。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
if(delMode==='batch'){
treatments=treatments.filter(function(t){return window._batchIds.indexOf(t.id)===-1;});
showToast('已批量删除 '+window._batchIds.length+' 条记录');
}else{
treatments=treatments.filter(function(t){return t.id!==pendingDelId;});
showToast('就诊记录已删除');
}
closeModal('delMask');renderTable();
}
/* ===== 通用 ===== */
function closeDrawer(k){
document.getElementById(k+'Mask').classList.remove('open');
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,118 @@
# 一线医疗 变更记录
## [2026-09-07] feat: 一线医疗模块启动 · Step 1 基础配置首批三页
启动一线医疗模块原型建设(迁移自旧系统 cqyt-admin `interveneNew/medicalPoints`,菜单结构按《健康长庆系统升级改造详细规划V8》里程碑 M18 重新规划,全模块规划 49 页)。本批确立模块壳层(顶栏 7 个一级分组 + 侧边栏完整菜单树,未开发页为占位链接),并交付基础配置前三页。
**涉及文件:**
- `config-medical-point.html` — 医疗点配置(资源列表/地图列表双视图、新增/编辑抽屉、详情抽屉、医护信息只读抽屉[维护入口在系统管理]、状态开关、删除确认)
- `config-medicine.html` — 药品信息(单位→医疗点级联筛选、新增/编辑抽屉、说明书查看抽屉、批量导入弹窗、模板下载、批量删除)
- `config-medicine-stock.html` — 实时库存(新增设计:概览四卡、库存状态三态徽标与进度条、缺货催办、页内出入库流水抽屉)
- `D:\Work\platform-prototype\index.html` — 站点地图新增一线医疗模块条目(入口 config-medical-point.html
## [2026-09-07] feat: 基础配置收尾 · Step 2 五页
交付基础配置余下五页,模块侧边栏「基础配置」组补齐至 8 项并全部回填真实链接(含 Step 1 三页占位链接统一替换、全部页面新增「救护车值班」项)。
**涉及文件:**
- `config-partner-hospital.html` — 合作医院(新增设计:按决策仅维护扩展信息,主信息只读引用系统管理资源库;管辖范围抽屉+添加医疗点宽弹窗(归属互斥)、医院管理员抽屉(页内新增表单、两次密码一致校验、移除确认))
- `config-allocate.html` — 员工分配(迁移 allocationManagement:已分配/未分配双 Tab 与动态表头、单位部门级联+姓名工号+医疗点级联筛选、单条/批量分配弹窗(单位→医疗点级联)、取消分配回落未分配、批量导入)
- `config-allocate-stat.html` — 分配统计(迁移 allocationStatistics7 列汇总,新增概览四卡随筛选实时计算;「已分配员工数量」下钻名册抽屉(单位/部门/姓名/性别/年龄/工号六列),名册与员工分配页数据一致)
- `config-ambulance.html` — 救护车配置(迁移 ambulance:单位→医疗点级联+救护类型筛选、新增/编辑抽屉六节表单(归属/车辆/覆盖/急救能力/驾驶员/证照照片,含覆盖人数正整数、开始服务年份≤当年、电话格式校验)、详情抽屉、批量删除、批量导入/下载模板/导出)
- `config-ambulance-duty.html` — 救护车值班(迁移 ambulanceDataSchemasNew 出勤域:出勤类型含「一键呼叫」禁用态与系统生成记录不可编辑标记、选救护车自动带出司机与电话(只读)、随车医护 chips 多选、救助员工选人弹窗(姓名/工号过滤)、出勤日期区间筛选、编辑/删除、批量导入;与 Step 5 救护车统计不重复——本页为登记口径)
- `D:\Work\platform-prototype\index.html` — 站点地图一线医疗条目追加 Step 2 五页(共 8 页)
- `config-medical-point.html` / `config-medicine.html` / `config-medicine-stock.html` / `config-partner-hospital.html` / `config-allocate.html` — 侧边栏基础配置组统一为 8 项真实链接(新增救护车值班、回填 Step 1 占位项)
## [2026-09-07] feat: 档案管理三页 · Step 3
交付档案域三页:个人就医数据、单位报表数据、算法模型,数据口径与日常业务各模块勾稽。
**涉及文件:**
- `archive-visit-data.html` — 个人就医数据(就诊/随访/报告解读/慢病管理按人归档,多源数据聚合视图)
- `archive-unit-report.html` — 单位报表数据(单位×年度自动汇编医疗点运营年报,明细下钻与导出)
- `archive-algo-model.html` — 算法模型(慢病自动入组/健康风险评估/报告解读辅助/脏数据识别/缺货预警模型版本档案)
## [2026-09-07] feat: 日常业务十二页 · Step 4
交付日常业务域 11 页与慢病干预二级页,打通「就诊→出入库→随访→慢病→解读」业务链。
**涉及文件:**
- `daily-treatment-staff.html` — 员工就诊;`daily-treatment-nonstaff.html` — 非员工就诊
- `daily-stock-in.html` — 入库记录;`daily-stock-out.html` — 出库记录(均为只读流水台账)
- `daily-follow.html` — 随访管理;`daily-follow-task.html` — 随访任务(单次/频次/员工申请三类派发)
- `daily-chronic.html` — 慢病管理;`daily-chronic-intervention.html` — 慢病干预二级页
- `daily-employee-search.html` — 员工查询(诊疗辅助档案);`daily-interpret.html` — 解读任务;`daily-interpret-manage.html` — 报告解读管理(归档台账)
## [2026-09-07] feat: 数据统计与异常事件 · Step 5
交付数据统计四页(表格/图表双口径)与异常事件两页,统计口径与日常业务、救护车值班数据勾稽。
**涉及文件:**
- `stat-clinic-table.html` / `stat-clinic-chart.html` — 医疗点统计(周/月/年切换,分组表头与图表下钻)
- `stat-ambulance-table.html` / `stat-ambulance-chart.html` — 救护车统计(四类任务本期/累计,出勤趋势)
- `alert-dirty.html` — 系统脏数据;`alert-abnormal.html` — 结果异常(模型/中台判定自动生成待办)
## [2026-09-07] feat: 床头呼叫域九页 · Step 6a
迁移旧系统床头呼叫域(决策①确认迁移,属 M33),建立楼栋→宿舍→床位→设备→分配→记录→告警→统计全链路,跨页数据闭环:楼栋5 / 宿舍14 / 床位44 / 呼叫设备12(正常11·故障1) / 终端5 / 床位分配12人 / 呼入10(接通8·完成7) / 呼出8 / 告警6 / 统计汇总(12/11/10/80%/6),异常事件 YC-20260905-05 与赵万福高血压记录勾稽。
**涉及文件:**
- `bed-building.html` — 楼栋管理;`bed-dormitory.html` — 宿舍管理;`bed-bed.html` — 床位管理
- `bed-device.html` — 设备列表(呼叫器/终端双类);`bed-device-allocate.html` — 设备分配;`bed-allocate-record.html` — 分配记录
- `bed-call-record.html` — 呼叫记录(员工呼入/终端呼出双TAB,呼入三段详情含救助处置)
- `bed-timeout-alarm.html` — 超时未归告警(三类告警徽标、概览四卡随日期区间计算、确认处理流转)
- `bed-stat.html` — 统计汇总(部门/站点双维度TAB切换动态表头、接通率计算、合计行、统计周期越界空态提示)
## [2026-09-07] feat: 健康小屋域七页 · Step 6b
迁移旧系统健康小屋域(slashdow/healthRoom/bespeak/planWork/bigDataShow/bigScreen/cabinList/cabinStatistics),按决策④将大屏数据配置合并为单页多TAB。数据与健康室/站点负责人池勾稽:健康室5间·设备18台 / 预约12条 / 排班6日 / 小屋8座·设备43台·金额¥636,000 / 统计与小屋管理同一波动生成规则保证两页数值一致。
**涉及文件:**
- `hut-banner.html` — 轮播图配置(6图、生效/失效灰度缩略、新增/编辑/详情/删除)
- `hut-room.html` — 健康室管理(5室16+设备、设备管理弹窗四类设备含监测设备免预约、法定排班开关、单位级联筛选、chip多选使用部门)
- `hut-appointment.html` — 预约管理(12列宽表、五态流转徽标、详情按状态显使用/归还/取消时间、勾选批量删除)
- `hut-schedule.html` — 默认排班(周排班表、工作日chip互斥、时长与每周开放总时长实时计算、结束早于开始校验)
- `hut-screen.html` — 大屏数据配置(9数据区TAB、实时取数/手工数据开关、录入态单元格转数字输入、风险TAB分组表头、取消还原备份)
- `hut-cabin.html` — 小屋管理(8座台账、导入导出工具条+导出记录弹窗、数据管理弹窗七日测量人次+同日期覆盖录入、手机号格式校验)
- `hut-cabin-stat.html` — 小屋统计(本周/本月/本年/自定义周期切换动态表头、血压/血糖人次点击排序、累计列、合计行)
## [2026-09-07] chore: 全站自检 · 45页链接回填与站点地图收口
模块45页全部建成后执行全站自检:①逐页 console 零报错、关键交互(级联/弹窗/批量/排序/周期切换)浏览器断言通过;②38页旧占位链接批量回填——侧边栏「床头呼叫9项+健康小屋7项」及顶栏分组入口统一替换为真实页面链接,页面级演示交互(如「下载导入模板」)按占位规范保留;③链接可达性扫描45页×站内全部 href 与 index.html 模块入口,零死链;④顶栏分组入口:床头呼叫→bed-building.html,健康小屋→hut-banner.html。
**涉及文件:**
- `web-admin/frontline-medical/*.html` — 38页侧边栏/顶栏占位链接回填(共530+处)
- `D:\Work\platform-prototype\index.html` — 站点地图一线医疗条目由8页补齐至45页(按7域分组标注),入口页调整为 archive-visit-data.html
## [2026-09-07] fix: 壳层菜单结构修正 · 侧边栏按一级分组收敛
用户自查指出壳层结构错误:此前 45 页侧边栏均显示全部 7 个一级分组的 44 项菜单,与项目规范(参照健康体检模块:顶栏=一级分组、左侧=当前分组二级菜单)不符。批量修正:
- 侧边栏整块重写为「当前一级分组标题 + 本组二级菜单」,当前页高亮;慢病干预(二级页)高亮归至父菜单「慢病管理」
- 顶栏 active 与所在分组统一校正(每页恰好一处高亮)
- 全量静态校验 45/45 通过(分组标题数、二级项数、侧栏/顶栏高亮唯一性);浏览器实测抽验 日常业务(二级页)/健康小屋/基础配置 三页结构、console 均正常
- `D:\Work\platform-prototype\index.html` — 一线医疗模块条目补 `done:true`,导航卡片状态由「设计中」改「已完成」
**涉及文件:**
- `web-admin/frontline-medical/*.html` — 45 页侧边栏与顶栏高亮批量修正
## [2026-09-07] fix: 移除全部页面顶部目录面包屑与菜单说明
按用户要求,页面顶部不再展示「一线医疗 / 分组 / 页面名」目录结构与页面功能说明文字,内容区直接从筛选栏开始:
- 44 页整块删除 page-header(breadcrumb + desc),并同步删除对应死 CSS 规则行
- `daily-chronic-intervention.html` 例外处理:保留块内「← 返回慢病管理」链接与其样式,仅删面包屑
- 验证:全模块 grep 无 page-header 残留(干预页除外);浏览器实测 楼栋管理(顶部直落筛选栏、截图确认)/干预页(返回链接完整)两页,console 零报错
**涉及文件:**
- `web-admin/frontline-medical/*.html` — 45 页页头面包屑与说明批量移除
@@ -0,0 +1,327 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>预约管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-select:disabled{background-color:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-danger:disabled{background:#ffccc7;cursor:not-allowed;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1560px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:680px;max-height:86vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:104px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.sec-title{font-size:var(--font-sm);font-weight:600;margin:14px 0 8px;color:var(--text-primary);}
.sec-title:first-child{margin-top:0;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item active">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item">默认排班</a>
<a href="hut-screen.html" class="sidebar-item">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏(单位→部门级联) -->
<div class="filter-bar">
<div class="filter-item">
<label>单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>部门</label>
<select class="filter-select" id="filterDepar" onchange="render()" disabled>
<option value="">全部部门</option>
</select>
</div>
<div class="filter-item">
<label>用户姓名</label>
<input class="filter-input" id="filterName" placeholder="关键字" oninput="render()" style="min-width:90px;">
</div>
<div class="filter-item">
<label>预约状态</label>
<select class="filter-select" id="filterStatus" onchange="render()" style="min-width:96px;">
<option value="">全部</option><option>待使用</option><option>使用中</option><option>已完成</option><option>已取消</option><option>爽约</option>
</select>
</div>
<div class="filter-item">
<label>健康室</label>
<select class="filter-select" id="filterRoom" onchange="render()">
<option value="">全部健康室</option>
<option>樊学站点健康小屋</option><option>麻子山站点健康小屋</option><option>桐园站点健康小屋</option><option>西峰基地健康小屋</option><option>靖边气田健康小屋</option>
</select>
</div>
<div class="filter-item">
<label>设备名称</label>
<input class="filter-input" id="filterDev" placeholder="关键字" oninput="render()" style="min-width:90px;">
</div>
<div class="filter-item">
<label>预约日期</label>
<input class="filter-input" type="date" id="filterDate" onchange="render()" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="render();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
预约状态流转:待使用 → 使用中(到岗开始使用)→ 已完成(归还设备);员工未按时到岗且超时未取消记为「爽约」;「监测设备」为常驻监测,不产生预约记录。
</div>
<!-- [交互] 列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">预约记录列表</span>
<button class="btn btn-danger" id="btnBatchDel" onclick="openBatchDel()">批量删除</button>
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th style="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>
<th style="min-width:150px;">预约单号</th><th>用户姓名</th><th>性别</th><th style="min-width:110px;">所属部门</th>
<th style="min-width:110px;">联系方式</th><th>预约状态</th><th style="min-width:150px;">预约健康室</th>
<th style="min-width:120px;">设备名称</th><th>预约日期</th><th style="min-width:150px;">开始时间</th>
<th style="min-width:150px;">完成时间</th><th style="min-width:150px;">创建时间</th><th style="width:120px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 详情弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span>预约详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认(单条/批量共用) -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__header"><span>删除确认</span><button class="modal__close" onclick="closeDel()">&times;</button></div>
<div class="modal__body" id="delBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDel()">取消</button>
<button class="btn btn-danger" onclick="confirmDel()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 单位→部门共享池(与健康室/床头呼叫域一致) */
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
/* 预约状态徽标配色 */
var stBadge={'待使用':'badge--info','使用中':'badge--warning','已完成':'badge--success','已取消':'badge--muted','爽约':'badge--danger'};
/* 预约记录:健康室与设备与 hut-room 勾稽;监测设备不产生预约 */
var appts=[
{code:'YY-20260901-001',name:'石维国',sex:'男',unit:'采油一厂',depar:'樊学作业区',phone:'138****2211',status:'已完成',room:'樊学站点健康小屋',dev:'血压检测仪',date:'2026-09-01',start:'2026-09-01 09:00',end:'2026-09-01 09:32',created:'2026-08-30 16:22',useTime:'2026-09-01 09:02',backTime:'2026-09-01 09:32'},
{code:'YY-20260901-002',name:'李桂芳',sex:'女',unit:'采油一厂',depar:'樊学作业区',phone:'139****0834',status:'已完成',room:'樊学站点健康小屋',dev:'血糖分析仪',date:'2026-09-01',start:'2026-09-01 10:00',end:'2026-09-01 10:18',created:'2026-08-31 08:40',useTime:'2026-09-01 10:01',backTime:'2026-09-01 10:18'},
{code:'YY-20260902-003',name:'曲志明',sex:'男',unit:'采油一厂',depar:'樊学作业区',phone:'137****5520',status:'已取消',room:'樊学站点健康小屋',dev:'心理测评系统',date:'2026-09-02',start:'2026-09-02 14:00',end:'—',created:'2026-09-01 11:05',cancelTime:'2026-09-02 08:12'},
{code:'YY-20260903-004',name:'刘玉梅',sex:'女',unit:'采气一厂',depar:'靖边气田',phone:'136****7712',status:'已完成',room:'靖边气田健康小屋',dev:'体成分分析仪',date:'2026-09-03',start:'2026-09-03 15:00',end:'2026-09-03 15:21',created:'2026-09-02 17:33',useTime:'2026-09-03 15:00',backTime:'2026-09-03 15:21'},
{code:'YY-20260904-005',name:'高建军',sex:'男',unit:'第四采油厂',depar:'西峰作业区',phone:'135****9086',status:'爽约',room:'西峰基地健康小屋',dev:'激光治疗仪',date:'2026-09-04',start:'2026-09-04 14:30',end:'—',created:'2026-09-03 09:12',cancelTime:'2026-09-04 15:10'},
{code:'YY-20260905-006',name:'徐立成',sex:'男',unit:'采油二厂',depar:'桐园作业区',phone:'138****4417',status:'已完成',room:'桐园站点健康小屋',dev:'血压检测仪',date:'2026-09-05',start:'2026-09-05 09:30',end:'2026-09-05 10:02',created:'2026-09-04 15:48',useTime:'2026-09-05 09:31',backTime:'2026-09-05 10:02'},
{code:'YY-20260905-007',name:'呼建国',sex:'男',unit:'采气一厂',depar:'靖边气田',phone:'139****2680',status:'已取消',room:'靖边气田健康小屋',dev:'血压检测仪',date:'2026-09-05',start:'2026-09-05 09:00',end:'—',created:'2026-09-04 18:02',cancelTime:'2026-09-04 20:15'},
{code:'YY-20260906-008',name:'白瑞',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'137****3341',status:'已取消',room:'西峰基地健康小屋',dev:'血糖分析仪',date:'2026-09-06',start:'2026-09-06 16:00',end:'—',created:'2026-09-05 10:26',cancelTime:'2026-09-06 07:50'},
{code:'YY-20260907-009',name:'赵万福',sex:'男',unit:'采油二厂',depar:'桐园作业区',phone:'136****8123',status:'使用中',room:'桐园站点健康小屋',dev:'音乐放松舱',date:'2026-09-07',start:'2026-09-07 10:00',end:'—',created:'2026-09-06 14:19',useTime:'2026-09-07 10:03',backTime:'—'},
{code:'YY-20260907-010',name:'闫婷',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'135****6208',status:'使用中',room:'西峰基地健康小屋',dev:'血糖分析仪',date:'2026-09-07',start:'2026-09-07 11:00',end:'—',created:'2026-09-06 16:55',useTime:'2026-09-07 11:01',backTime:'—'},
{code:'YY-20260908-011',name:'杨慧敏',sex:'女',unit:'采油一厂',depar:'麻子山作业区',phone:'138****9975',status:'待使用',room:'麻子山站点健康小屋',dev:'体成分分析仪',date:'2026-09-08',start:'2026-09-08 15:00',end:'—',created:'2026-09-07 08:31'},
{code:'YY-20260908-012',name:'张秀兰',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'139****5402',status:'待使用',room:'西峰基地健康小屋',dev:'血压检测仪',date:'2026-09-08',start:'2026-09-08 10:00',end:'—',created:'2026-09-07 09:04'}
];
var delCodes=[];/* 当前待删除单号集合 */
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterDepar');
sel.innerHTML='<option value="">全部部门</option>'+(u?unitDeparts[u].map(function(d){return '<option>'+d+'</option>';}).join(''):'');
sel.disabled=!u;render();
}
function currentList(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepar').value;
var kn=document.getElementById('filterName').value.trim(),st=document.getElementById('filterStatus').value;
var rm=document.getElementById('filterRoom').value,dv=document.getElementById('filterDev').value.trim();
var dt=document.getElementById('filterDate').value;
return appts.filter(function(r){
return (!u||r.unit===u)&&(!d||r.depar===d)&&r.name.indexOf(kn)>=0&&(!st||r.status===st)
&&(!rm||r.room===rm)&&r.dev.indexOf(dv)>=0&&(!dt||r.date===dt);
});
}
function render(){
var list=currentList();
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=appts.indexOf(r);
return '<tr><td><input type="checkbox" class="ck" value="'+i+'" onchange="syncBatch()"></td>'
+'<td style="font-family:monospace;">'+r.code+'</td>'
+'<td style="font-weight:500;">'+r.name+'</td><td>'+r.sex+'</td><td>'+r.depar+'</td><td>'+r.phone+'</td>'
+'<td><span class="badge '+stBadge[r.status]+'">'+r.status+'</span></td>'
+'<td style="text-align:left;padding-left:12px;">'+r.room+'</td><td>'+r.dev+'</td><td>'+r.date+'</td>'
+'<td>'+r.start+'</td><td>'+r.end+'</td><td>'+r.created+'</td>'
+'<td><button class="btn btn-link" onclick="openDetail('+i+')">详情</button>'
+'<button class="btn btn-link" style="color:var(--danger);" onclick="openDel(['+i+'])">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="14">没有符合条件的预约记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 条 · 全部记录 '+appts.length+' 条';
document.getElementById('ckAll').checked=false;syncBatch();
}
function resetFilter(){['filterUnit','filterDepar','filterName','filterStatus','filterRoom','filterDev','filterDate'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterDepar').disabled=true;render();}
function toggleAll(el){document.querySelectorAll('#tbody .ck').forEach(function(c){c.checked=el.checked;});syncBatch();}
function syncBatch(){
var n=document.querySelectorAll('#tbody .ck:checked').length;
var b=document.getElementById('btnBatchDel');
b.disabled=n===0;b.textContent='批量删除'+(n?'('+n+')':'');
}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var r=appts[i];
var h='<div class="sec-title">预约信息</div><div class="detail-grid">'
+di('预约单号',r.code,true)+di('用户姓名',r.name+' / '+r.sex)+di('联系方式',r.phone)
+di('所属部门',r.unit+' / '+r.depar)+di('预约状态','<span class="badge '+stBadge[r.status]+'">'+r.status+'</span>')
+di('预约健康室',r.room)+di('预约设备',r.dev)
+di('预约日期',r.date)+di('预约时段',r.start+' ~ '+(r.status==='已完成'?'归还即止':r.status==='使用中'?'使用中':'计划结束时刻'))
+di('创建时间',r.created,true)+'</div>';
h+='<div class="sec-title">履约情况</div><div class="detail-grid">';
if(r.status==='使用中'){h+=di('开始使用时间',r.useTime)+di('归还时间','<span style="color:var(--text-placeholder);">尚未归还</span>');}
else if(r.status==='已完成'){h+=di('开始使用时间',r.useTime)+di('归还时间',r.backTime);}
else if(r.status==='已取消'||r.status==='爽约'){h+=di(r.status==='爽约'?'超时未归时间':'取消时间',r.cancelTime,true);}
else{h+='<div class="detail-item detail-item--full"><span class="dk">履约情况</span><span class="dv" style="color:var(--text-placeholder);">尚未开始使用,可按计划时段到岗使用设备</span></div>';}
h+='</div>';
document.getElementById('detailBody').innerHTML=h;
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
/* ---- 删除(单条与批量共用确认弹窗) ---- */
function openDel(idxs){delCodes=idxs;
document.getElementById('delBody').innerHTML=idxs.length===1
?'确认删除预约单 <b style="font-family:monospace;color:var(--text-primary);">'+appts[idxs[0]].code+'</b>?删除后该记录不再计入小屋预约统计,操作不可恢复。'
:'已勾选 <b>'+idxs.length+'</b> 条预约记录,确认批量删除?删除后不再计入预约统计,操作不可恢复。';
document.getElementById('delMask').classList.add('open');
}
function openBatchDel(){
var idxs=Array.prototype.slice.call(document.querySelectorAll('#tbody .ck:checked')).map(function(c){return +c.value;});
if(!idxs.length){showToast('请先勾选要删除的预约记录');return;}
openDel(idxs);
}
function closeDel(){document.getElementById('delMask').classList.remove('open');}
function confirmDel(){
/* 按下标从大到小删除,避免前面的删除影响后面的下标 */
delCodes.slice().sort(function(a,b){return b-a;}).forEach(function(i){appts.splice(i,1);});
closeDel();render();showToast('已删除 '+delCodes.length+' 条预约记录');
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
+312
View File
@@ -0,0 +1,312 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>轮播图配置 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1280px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 轮播图缩略图(原型以渐变色块示意) */
.banner-thumb{width:200px;height:80px;margin:0 auto;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);letter-spacing:1px;text-shadow:0 1px 2px rgba(0,0,0,.35);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:520px;max-height:86vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:620px;max-height:86vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.form-row{display:flex;align-items:flex-start;gap:12px;margin-bottom:16px;}
.form-row__label{width:96px;flex-shrink:0;text-align:right;font-size:var(--font-sm);color:var(--text-secondary);padding-top:7px;}
.form-row__label i{color:var(--danger);font-style:normal;margin-right:2px;}
.form-row__control{flex:1;}
.form-input{width:100%;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);outline:none;}
.form-input:focus{border-color:var(--primary);}
.radio-group{display:flex;gap:18px;padding-top:7px;}
.radio-group label{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio-group input{accent-color:var(--primary);}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-md);padding:18px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;transition:all .2s;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);}
.upload-box svg{display:block;margin:0 auto 6px;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:96px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item active">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item">默认排班</a>
<a href="hut-screen.html" class="sidebar-item">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>轮播图标题</label>
<input class="filter-input" id="filterTitle" placeholder="关键字" oninput="render()">
</div>
<div class="filter-item">
<label>所属模块</label>
<select class="filter-select" id="filterMod" onchange="render()">
<option value="">全部</option><option>小屋首页</option><option>健康室详情</option><option>预约页</option>
</select>
</div>
<div class="filter-item">
<label>是否有效</label>
<select class="filter-select" id="filterValid" onchange="render()" style="min-width:90px;">
<option value="">全部</option><option></option><option></option>
</select>
</div>
<button class="btn btn-primary" onclick="render();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
建议尺寸 1280×500(JPG/PNG,≤2MB);图片按序号参与轮播,序号越小越靠前;停用图片保留记录,便于换季复用。
</div>
<!-- [交互] 列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">轮播图列表</span>
<button class="btn btn-primary" onclick="openForm(-1)">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增
</button>
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th style="width:60px;">序号</th><th style="min-width:180px;">标题</th><th style="min-width:120px;">编号</th>
<th style="min-width:220px;">轮播图</th><th>是否有效</th><th>所属模块</th><th style="min-width:140px;">创建时间</th><th style="width:140px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 新增/编辑弹窗 -->
<div class="modal-mask" id="formMask">
<div class="modal">
<div class="modal__header"><span id="formTitle">新增轮播图</span><button class="modal__close" onclick="closeForm()">&times;</button></div>
<div class="modal__body">
<div class="form-row"><div class="form-row__label"><i>*</i>序号</div><div class="form-row__control"><input class="form-input" type="number" id="fNo" placeholder="数字越小越靠前"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>轮播图标题</div><div class="form-row__control"><input class="form-input" id="fTitle" placeholder="请输入标题"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>是否有效</div><div class="form-row__control">
<div class="radio-group"><label><input type="radio" name="fValid" value="是" checked></label><label><input type="radio" name="fValid" value="否"></label></div>
</div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>类型</div><div class="form-row__control">
<select class="filter-select" id="fMod" style="width:100%;height:34px;">
<option value="">请选择所属模块</option><option>小屋首页</option><option>健康室详情</option><option>预约页</option>
</select>
</div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>轮播图</div><div class="form-row__control">
<div class="upload-box" onclick="pickImage()">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect x="2" y="4" width="18" height="14" rx="2" stroke="#999" stroke-width="1.3"/><circle cx="8" cy="9.5" r="1.6" fill="#999"/><path d="M3.5 16l5-4.5 3.5 3 3-2.5 4 4" stroke="#999" stroke-width="1.3" fill="none"/></svg>
<span id="uploadText">点击上传(建议 1280×500,≤2MB)</span>
</div>
</div></div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeForm()">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
</div>
<!-- [交互] 详情弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span>轮播图详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__header"><span>删除确认</span><button class="modal__close" onclick="closeDel()">&times;</button></div>
<div class="modal__body" id="delBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDel()">取消</button>
<button class="btn btn-danger" onclick="confirmDel()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var gradients=['linear-gradient(135deg,#1890FF,#36CFC9)','linear-gradient(135deg,#722ED1,#1890FF)','linear-gradient(135deg,#FA8C16,#F5222D)','linear-gradient(135deg,#52C41A,#13C2C2)','linear-gradient(135deg,#EB2F96,#722ED1)','linear-gradient(135deg,#2F54EB,#B37FEB)'];
/* banners:编号规则 HB-年份-序号;与小屋管理各健康室终端展示对应 */
var banners=[
{no:1,title:'关爱健康 从心开始',code:'HB-2026-001',mod:'小屋首页',valid:'是',date:'2026-08-21 10:12'},
{no:2,title:'CQ油田员工健康工程',code:'HB-2026-002',mod:'小屋首页',valid:'是',date:'2026-08-21 10:30'},
{no:3,title:'体检季 · 健康室开放时间表',code:'HB-2026-003',mod:'预约页',valid:'是',date:'2026-09-01 09:05'},
{no:4,title:'心理健康月 · 减压训练指南',code:'HB-2026-004',mod:'健康室详情',valid:'是',date:'2026-09-03 15:47'},
{no:5,title:'三减三健 健康饮食宣传',code:'HB-2026-005',mod:'小屋首页',valid:'否',date:'2026-06-12 11:20'},
{no:6,title:'旧版 · 健康小屋使用须知',code:'HB-2025-011',mod:'小屋首页',valid:'否',date:'2025-11-03 16:02'}
];
var editIdx=-1;
function render(){
var kt=document.getElementById('filterTitle').value.trim();
var km=document.getElementById('filterMod').value,kv=document.getElementById('filterValid').value;
var list=banners.slice().sort(function(a,b){return a.no-b.no;}).filter(function(b){
return b.title.indexOf(kt)>=0&&(!km||b.mod===km)&&(!kv||b.valid===kv);});
document.getElementById('tbody').innerHTML=list.map(function(b){
var i=banners.indexOf(b);
return '<tr><td>'+b.no+'</td><td style="text-align:left;padding-left:14px;">'+b.title+'</td><td style="font-family:monospace;">'+b.code+'</td>'
+'<td><div class="banner-thumb" style="background:'+gradients[i%gradients.length]+';'+(b.valid==='否'?'filter:grayscale(1);opacity:.55;':'')+'">'+b.title.slice(0,10)+'</div></td>'
+'<td><span class="badge '+(b.valid==='是'?'badge--success':'badge--muted')+'">'+b.valid+'</span></td>'
+'<td><span class="badge badge--info">'+b.mod+'</span></td><td>'+b.date+'</td>'
+'<td><button class="btn btn-link" onclick="openForm('+i+')">编辑</button>'
+'<button class="btn btn-link" onclick="openDetail('+i+')">详情</button>'
+'<button class="btn btn-link" style="color:var(--danger);" onclick="openDel('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="8">没有符合条件的轮播图</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 张 · 生效 '+banners.filter(function(b){return b.valid==='是';}).length+' 张';
}
function resetFilter(){['filterTitle','filterMod','filterValid'].forEach(function(id){document.getElementById(id).value='';});render();}
function pickImage(){document.getElementById('uploadText').textContent='已选择 banner-'+Date.now()%1000+'.jpg(原型示意)';showToast('图片已暂存,保存后生效');}
function openForm(i){
editIdx=i;
document.getElementById('formTitle').textContent=i<0?'新增轮播图':'编辑轮播图 · '+banners[i].code;
document.getElementById('fNo').value=i<0?'':banners[i].no;
document.getElementById('fTitle').value=i<0?'':banners[i].title;
document.getElementById('fMod').value=i<0?'':banners[i].mod;
document.querySelector('input[name=fValid][value='+(i<0?'':banners[i].valid)+']').checked=true;
document.getElementById('uploadText').textContent='点击上传(建议 1280×500,≤2MB)';
document.getElementById('formMask').classList.add('open');
}
function closeForm(){document.getElementById('formMask').classList.remove('open');}
function saveForm(){
var no=parseInt(document.getElementById('fNo').value,10),title=document.getElementById('fTitle').value.trim();
var mod=document.getElementById('fMod').value,valid=document.querySelector('input[name=fValid]:checked').value;
if(!no||!title||!mod){showToast('请完整填写序号/标题/所属模块');return;}
if(editIdx<0){banners.push({no:no,title:title,code:'HB-2026-0'+(6+banners.length%3),mod:mod,valid:valid,date:'2026-09-07 10:00',isNew:true});}
else{var b=banners[editIdx];b.no=no;b.title=title;b.mod=mod;b.valid=valid;}
closeForm();render();showToast(editIdx<0?'新增成功,已排入轮播队列':'保存成功,终端缓存 5 分钟内刷新');
}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var b=banners[i];
document.getElementById('detailBody').innerHTML='<div class="detail-grid">'
+di('序号',b.no)+di('编号',b.code)
+di('标题',b.title,true)
+di('是否有效',b.valid)+di('所属模块',b.mod)
+di('创建时间',b.date,true)
+di('轮播图预览','<div class="banner-thumb" style="background:'+gradients[i%gradients.length]+';width:280px;margin-top:4px;">'+b.title.slice(0,12)+'</div>',true)
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
var delIdx=-1;
function openDel(i){
delIdx=i;var b=banners[i];
document.getElementById('delBody').innerHTML='确认删除轮播图 <b style="color:var(--text-primary);">'+b.title+'</b>('+b.code+')?删除后终端将立即不再展示,操作不可恢复。';
document.getElementById('delMask').classList.add('open');
}
function closeDel(){document.getElementById('delMask').classList.remove('open');}
function confirmDel(){banners.splice(delIdx,1);closeDel();render();showToast('已删除');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
@@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小屋统计 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-select:disabled{background-color:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.seg{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.seg span{padding:6px 14px;font-size:var(--font-xs);color:var(--text-secondary);cursor:pointer;background:#fff;border-right:1px solid var(--border);}
.seg span:last-child{border-right:none;}
.seg span.on{background:var(--primary);color:#fff;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);gap:10px;flex-wrap:wrap;}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1420px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
th.sortable{cursor:pointer;color:var(--primary);}
th.sortable:hover{background:#F0F7FF;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.tfoot td{background:#FAFAFA;font-weight:600;border-top:1px solid var(--border);}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item">默认排班</a>
<a href="hut-screen.html" class="sidebar-item">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item active">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 周期切换 + 单位级联筛选 -->
<div class="filter-bar">
<div class="filter-item">
<label>统计周期</label>
<div class="seg" id="segPeriod">
<span data-p="week" class="on" onclick="setPeriod('week')">本周</span>
<span data-p="month" onclick="setPeriod('month')">本月</span>
<span data-p="year" onclick="setPeriod('year')">本年</span>
<span data-p="custom" onclick="setPeriod('custom')">自定义</span>
</div>
</div>
<div class="filter-item" id="customRange" style="display:none;">
<input class="filter-input" type="date" id="cFrom" value="2026-08-25" onchange="render()">
<span style="color:var(--text-placeholder);">~</span>
<input class="filter-input" type="date" id="cTo" value="2026-09-05" onchange="render()">
</div>
<div class="filter-item">
<label>单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>部门</label>
<select class="filter-select" id="filterDepar" onchange="render()" disabled>
<option value="">全部部门</option>
</select>
</div>
<button class="btn btn-primary" onclick="render();showToast('统计完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>统计
</button>
<button class="btn btn-default" onclick="resetAll()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
统计口径来自「小屋管理 · 数据管理」的每日测量人次录入;切换周期后表头与数值同步重建,「本周」为 2026-09-01 ~ 09-07,「本年 / 累计」自各小屋建成日起算。
</div>
<!-- [交互] 列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title" id="cardTitle">小屋测量统计 · 本周</span>
</div>
<div class="table-card__content">
<table>
<thead id="thead"></thead>
<tbody id="tbody"></tbody>
<tfoot id="tfoot"></tfoot>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
/* 单位→部门共享池(与健康小屋各页一致) */
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
/* 小屋台账(与 hut-cabin 勾稽):base 为日均测量人次基准 */
var cabins=[
{name:'樊学基地健康小屋',unit:'采油一厂',depar:'樊学作业区',date:'2024-05-18',devNum:6,base:28},
{name:'麻子山站点健康小屋',unit:'采油一厂',depar:'麻子山作业区',date:'2024-06-02',devNum:5,base:22},
{name:'桐园基地健康小屋',unit:'采油二厂',depar:'桐园作业区',date:'2024-07-11',devNum:6,base:26},
{name:'姬圪台站点健康小屋',unit:'采油二厂',depar:'姬圪台作业区',date:'2024-09-20',devNum:4,base:18},
{name:'西峰基地健康小屋',unit:'第四采油厂',depar:'西峰作业区',date:'2024-08-15',devNum:7,base:31},
{name:'靖边气田健康小屋',unit:'采气一厂',depar:'靖边气田',date:'2025-03-06',devNum:6,base:25},
{name:'樊学作业区第二健康小屋',unit:'采油一厂',depar:'樊学作业区',date:'2025-04-22',devNum:5,base:20},
{name:'西峰前线健康小屋',unit:'第四采油厂',depar:'西峰作业区',date:'2025-06-10',devNum:4,base:16}
];
var DELTA=[0,3,-2,5,1,7,-6];/* 与小屋管理页同一波动规律,保证两页勾稽 */
/* 按日生成区间内测量人次:血压=基准+当日波动,血糖≈血压×0.8 */
function genStat(from,to,base){
var s=0,g=0;var d=new Date(from+'T00:00:00'),e=new Date(to+'T00:00:00');
while(d<=e){var bp=base+DELTA[(d.getDate()-1)%7];s+=bp;g+=Math.round(bp*0.8);d.setDate(d.getDate()+1);}
return {bp:s,bg:g};
}
var period='week',sortKey='',sortDir='desc';
function periodRange(){
if(period==='week')return{label:'本周',from:'2026-09-01',to:'2026-09-07'};
if(period==='month')return{label:'本月至今',from:'2026-09-01',to:'2026-09-07'};
if(period==='year')return{label:'本年',from:'2026-01-01',to:'2026-09-07'};
return{label:'自定义区间',from:document.getElementById('cFrom').value,to:document.getElementById('cTo').value};
}
function setPeriod(p){
period=p;
document.querySelectorAll('#segPeriod span').forEach(function(el){el.classList.toggle('on',el.getAttribute('data-p')===p);});
document.getElementById('customRange').style.display=p==='custom'?'flex':'none';
render();
}
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterDepar');
sel.innerHTML='<option value="">全部部门</option>'+(u?unitDeparts[u].map(function(d){return '<option>'+d+'</option>';}).join(''):'');
sel.disabled=!u;render();
}
function resetAll(){
['filterUnit','filterDepar'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('filterDepar').disabled=true;
setPeriod('week');
}
function sortBy(k){
if(sortKey===k){sortDir=sortDir==='desc'?'asc':'desc';}else{sortKey=k;sortDir='desc';}
render();
}
function arrow(k){return sortKey!==k?' ⇅':(sortDir==='desc'?' ↓':' ↑');}
function calcRows(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepar').value;
var pr=periodRange();
var rows=cabins.filter(function(c){return (!u||c.unit===u)&&(!d||c.depar===d);}).map(function(c){
var p=genStat(pr.from,pr.to,c.base),t=genStat(c.date,'2026-09-07',c.base);
return{c:c,pbp:p.bp,pbg:p.bg,tbp:t.bp,tbg:t.bg};
});
if(sortKey){rows.sort(function(a,b){var va=sortKey==='bp'?a.pbp:a.pbg,vb=sortKey==='bp'?b.pbp:b.pbg;return sortDir==='desc'?vb-va:va-vb;});}
return{rows:rows,pr:pr};
}
function render(){
var o=calcRows(),rows=o.rows,pr=o.pr;
document.getElementById('cardTitle').textContent='小屋测量统计 · '+pr.label;
document.getElementById('thead').innerHTML='<tr><th style="width:56px;">序号</th><th style="min-width:190px;">小屋名称</th>'
+'<th style="min-width:110px;">单位名称</th><th style="min-width:110px;">部门名称</th><th>建成日期</th><th>配备设备数</th>'
+'<th class="sortable" onclick="sortBy(\'bp\')" style="min-width:150px;">'+pr.label+'血压测量人次'+arrow('bp')+'</th>'
+'<th class="sortable" onclick="sortBy(\'bg\')" style="min-width:150px;">'+pr.label+'血糖测量人次'+arrow('bg')+'</th>'
+'<th style="min-width:130px;">累计血压测量人次</th><th style="min-width:130px;">累计血糖测量人次</th><th style="width:90px;">操作</th></tr>';
document.getElementById('tbody').innerHTML=rows.map(function(r){
return '<tr><td>'+(cabins.indexOf(r.c)+1)+'</td><td style="text-align:left;padding-left:12px;font-weight:500;">'+r.c.name+'</td>'
+'<td>'+r.c.unit+'</td><td>'+r.c.depar+'</td><td>'+r.c.date+'</td><td>'+r.c.devNum+' 台</td>'
+'<td>'+r.pbp+'</td><td>'+r.pbg+'</td><td>'+r.tbp+'</td><td>'+r.tbg+'</td>'
+'<td><button class="btn btn-link" onclick="showToast(\'明细请在「小屋管理·数据管理」中查看\')">查看明细</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="11">所选范围内没有健康小屋</td></tr>';
var sbp=rows.reduce(function(s,r){return s+r.pbp;},0),sbg=rows.reduce(function(s,r){return s+r.pbg;},0);
var stbp=rows.reduce(function(s,r){return s+r.tbp;},0),stbg=rows.reduce(function(s,r){return s+r.tbg;},0);
document.getElementById('tfoot').innerHTML=rows.length?'<tr class="tfoot"><td colspan="5" style="text-align:right;padding-right:12px;">合计('+rows.length+' 座)</td><td>'+rows.reduce(function(s,r){return s+r.c.devNum;},0)+' 台</td>'
+'<td>'+sbp+'</td><td>'+sbg+'</td><td>'+stbp+'</td><td>'+stbg+'</td><td></td></tr>':'';
document.getElementById('totalTip').textContent='区间 '+pr.from+' ~ '+pr.to+' · 共 '+rows.length+' 座小屋';
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
+395
View File
@@ -0,0 +1,395 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小屋管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-select:disabled{background-color:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);gap:10px;flex-wrap:wrap;}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__tools{display:flex;align-items:center;gap:8px;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1560px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.tfoot td{background:#FAFAFA;font-weight:600;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:680px;max-height:86vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.form-row{display:flex;align-items:flex-start;gap:12px;margin-bottom:16px;}
.form-row__label{width:96px;flex-shrink:0;text-align:right;font-size:var(--font-sm);color:var(--text-secondary);padding-top:7px;}
.form-row__label i{color:var(--danger);font-style:normal;margin-right:2px;}
.form-row__control{flex:1;display:flex;align-items:center;gap:8px;}
.form-input{width:100%;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);outline:none;}
.form-input:focus{border-color:var(--primary);}
.inner-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;border-collapse:collapse;}
.inner-table th{background:#FAFAFA;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;padding:7px;border-bottom:1px solid var(--border);}
.inner-table td{font-size:var(--font-sm);padding:8px 7px;text-align:center;border-bottom:1px solid #F0F0F0;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item">默认排班</a>
<a href="hut-screen.html" class="sidebar-item">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item active">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏(单位→部门级联) -->
<div class="filter-bar">
<div class="filter-item">
<label>单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>部门</label>
<select class="filter-select" id="filterDepar" onchange="render()" disabled>
<option value="">全部部门</option>
</select>
</div>
<div class="filter-item">
<label>小屋名称</label>
<input class="filter-input" id="filterName" placeholder="关键字" oninput="render()">
</div>
<button class="btn btn-primary" onclick="render();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
配备金额单位为元(含设备与安装);测量记录按日录入,同一日期重复录入以最新一次为准;删除小屋会连带清除其测量记录且不可恢复。
</div>
<!-- [交互] 列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">健康小屋台账</span>
<div class="table-card__tools">
<button class="btn btn-primary" onclick="openForm(-1)">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增小屋
</button>
<button class="btn btn-default" onclick="showToast('原型示意:已下载《健康小屋批量导入模板.xlsx》')">导入模板下载</button>
<button class="btn btn-default" onclick="showToast('原型示意:请先选择按模板填写的导入文件')">导入</button>
<button class="btn btn-default" onclick="showToast('原型示意:已提交导出任务,完成后可在「查看导出记录」下载')">导出</button>
<button class="btn btn-default" onclick="openExports()">查看导出记录</button>
</div>
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th style="width:56px;">序号</th><th style="min-width:170px;">小屋名称</th><th style="min-width:110px;">所属单位</th>
<th style="min-width:110px;">所属部门</th><th>负责人</th><th style="min-width:120px;">办公室电话</th>
<th style="min-width:120px;">手机</th><th>建成日期</th><th style="min-width:110px;">配备金额</th>
<th>配备设备数</th><th style="width:190px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 新增/编辑弹窗 -->
<div class="modal-mask" id="formMask">
<div class="modal--wide">
<div class="modal__header"><span id="formTitle">新增小屋</span><button class="modal__close" onclick="closeForm()">&times;</button></div>
<div class="modal__body">
<div class="form-row"><div class="form-row__label"><i>*</i>小屋名称</div><div class="form-row__control"><input class="form-input" id="fName" placeholder="如:樊学基地健康小屋"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>所属单位</div><div class="form-row__control">
<select class="filter-select" id="fUnit" style="width:160px;" onchange="onFormUnit()"><option value="">选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select>
<select class="filter-select" id="fDepar" style="width:170px;" disabled><option value="">选择部门</option></select>
</div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>负责人</div><div class="form-row__control"><input class="form-input" id="fMgr" placeholder="姓名" style="max-width:200px;"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>办公室电话</div><div class="form-row__control"><input class="form-input" id="fOffice" placeholder="如:0963-88201" style="max-width:200px;"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>手机</div><div class="form-row__control"><input class="form-input" id="fMobile" placeholder="11位手机号" style="max-width:200px;"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>建成日期</div><div class="form-row__control"><input class="form-input" type="date" id="fDate" style="max-width:200px;"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>配备金额(元)</div><div class="form-row__control"><input class="form-input" type="number" id="fMoney" placeholder="含设备与安装费用" style="max-width:200px;"></div></div>
<div class="form-row"><div class="form-row__label">配备设备数</div><div class="form-row__control"><input class="form-input" type="number" id="fDevNum" placeholder="默认 0,建成验收后回填" style="max-width:200px;"></div></div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeForm()">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
</div>
<!-- [交互] 数据管理弹窗 -->
<div class="modal-mask" id="dataMask">
<div class="modal--wide">
<div class="modal__header"><span id="dataTitle">数据管理</span><button class="modal__close" onclick="closeData()">&times;</button></div>
<div class="modal__body">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap;">
<label style="font-size:var(--font-xs);color:var(--text-secondary);">日期区间</label>
<input class="filter-input" type="date" id="dFrom" value="2026-09-01" onchange="renderData()">
<span style="color:var(--text-placeholder);">~</span>
<input class="filter-input" type="date" id="dTo" value="2026-09-07" onchange="renderData()">
<span style="flex:1;"></span>
<input class="filter-input" type="date" id="nDate" value="2026-09-07" style="min-width:120px;">
<input class="filter-input" type="number" id="nBp" placeholder="血压人次" style="min-width:84px;">
<input class="filter-input" type="number" id="nBg" placeholder="血糖人次" style="min-width:84px;">
<button class="btn btn-primary" onclick="addRecord()">+ 录入</button>
</div>
<table class="inner-table">
<thead><tr><th>日期</th><th>血压测量人次</th><th>血糖测量人次</th></tr></thead>
<tbody id="dataBody"></tbody>
<tfoot id="dataFoot"></tfoot>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeData()">关闭</button></div>
</div>
</div>
<!-- [交互] 导出记录弹窗 -->
<div class="modal-mask" id="expMask">
<div class="modal--wide">
<div class="modal__header"><span>导出记录</span><button class="modal__close" onclick="closeExports()">&times;</button></div>
<div class="modal__body">
<table class="inner-table">
<thead><tr><th>导出时间</th><th>操作人</th><th>文件名</th><th>状态</th></tr></thead>
<tbody>
<tr><td>2026-09-01 09:12</td><td>系统管理员</td><td style="font-family:monospace;">cabin_ledger_20260901.xlsx</td><td><span class="badge badge--success">成功</span></td></tr>
<tr><td>2026-08-28 15:40</td><td>陈国栋</td><td style="font-family:monospace;">cabin_measure_20260828.xlsx</td><td><span class="badge badge--success">成功</span></td></tr>
<tr><td>2026-08-28 15:31</td><td>陈国栋</td><td style="font-family:monospace;">cabin_measure_20260828.xlsx</td><td><span class="badge badge--muted">已过期</span></td></tr>
</tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeExports()">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__header"><span>删除确认</span><button class="modal__close" onclick="closeDel()">&times;</button></div>
<div class="modal__body" id="delBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDel()">取消</button>
<button class="btn btn-danger" onclick="confirmDel()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 单位→部门共享池(与床头呼叫/健康室域一致) */
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
/* 小屋台账:负责人沿用站点负责人池;records 为每日测量人次(血压/血糖) */
var cabins=[
{name:'樊学基地健康小屋',unit:'采油一厂',depar:'樊学作业区',mgr:'王守正',office:'0963-88201',mobile:'138****2211',date:'2024-05-18',money:86000,devNum:6},
{name:'麻子山站点健康小屋',unit:'采油一厂',depar:'麻子山作业区',mgr:'安伟',office:'0963-88302',mobile:'139****0834',date:'2024-06-02',money:75000,devNum:5},
{name:'桐园基地健康小屋',unit:'采油二厂',depar:'桐园作业区',mgr:'陈国栋',office:'0963-88401',mobile:'137****5520',date:'2024-07-11',money:86000,devNum:6},
{name:'姬圪台站点健康小屋',unit:'采油二厂',depar:'姬圪台作业区',mgr:'刘志强',office:'0963-88402',mobile:'136****7712',date:'2024-09-20',money:68000,devNum:4},
{name:'西峰基地健康小屋',unit:'第四采油厂',depar:'西峰作业区',mgr:'马文秀',office:'0963-88501',mobile:'135****9086',date:'2024-08-15',money:92000,devNum:7},
{name:'靖边气田健康小屋',unit:'采气一厂',depar:'靖边气田',mgr:'周铁祥',office:'0963-88601',mobile:'138****4417',date:'2025-03-06',money:86000,devNum:6},
{name:'樊学作业区第二健康小屋',unit:'采油一厂',depar:'樊学作业区',mgr:'张国亮',office:'0963-88203',mobile:'139****2680',date:'2025-04-22',money:75000,devNum:5},
{name:'西峰前线健康小屋',unit:'第四采油厂',depar:'西峰作业区',mgr:'冯建军',office:'0963-88502',mobile:'137****3341',date:'2025-06-10',money:68000,devNum:4}
];
/* 每座小屋生成 09-01~09-07 七日测量记录(血压人次=基准±波动,血糖≈血压×0.8) */
(function(){
var bases=[28,22,26,18,31,25,20,16],delta=[0,3,-2,5,1,7,-6];
cabins.forEach(function(c,ci){
c.records=[0,1,2,3,4,5,6].map(function(k){
var bp=bases[ci]+delta[k];
return {d:'2026-09-0'+(k+1),bp:bp,bg:Math.round(bp*0.8)};
});
});
})();
var formIdx=-1,delIdx=-1,dataIdx=-1;
function money(n){return '¥'+n.toLocaleString('en-US');}
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterDepar');
sel.innerHTML='<option value="">全部部门</option>'+(u?unitDeparts[u].map(function(d){return '<option>'+d+'</option>';}).join(''):'');
sel.disabled=!u;render();
}
function render(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepar').value;
var kn=document.getElementById('filterName').value.trim();
var list=cabins.filter(function(c){return (!u||c.unit===u)&&(!d||c.depar===d)&&c.name.indexOf(kn)>=0;});
document.getElementById('tbody').innerHTML=list.map(function(c){
var i=cabins.indexOf(c);
return '<tr><td>'+(i+1)+'</td><td style="text-align:left;padding-left:12px;font-weight:500;">'+c.name+'</td>'
+'<td>'+c.unit+'</td><td>'+c.depar+'</td><td>'+c.mgr+'</td><td>'+c.office+'</td><td>'+c.mobile+'</td>'
+'<td>'+c.date+'</td><td>'+money(c.money)+'</td><td>'+c.devNum+' 台</td>'
+'<td><button class="btn btn-link" onclick="openData('+i+')">数据管理</button>'
+'<button class="btn btn-link" onclick="openForm('+i+')">编辑</button>'
+'<button class="btn btn-link" style="color:var(--danger);" onclick="openDel('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="11">没有符合条件的健康小屋</td></tr>';
var m=cabins.reduce(function(s,c){return s+c.money;},0);
var dv=cabins.reduce(function(s,c){return s+c.devNum;},0);
document.getElementById('totalTip').textContent='共 '+cabins.length+' 座 · 配备金额合计 '+money(m)+' · 设备合计 '+dv+' 台';
}
function resetFilter(){['filterUnit','filterDepar','filterName'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterDepar').disabled=true;render();}
/* ---- 新增/编辑 ---- */
function onFormUnit(){
var u=document.getElementById('fUnit').value;
var sel=document.getElementById('fDepar');
sel.innerHTML='<option value="">选择部门</option>'+(u?unitDeparts[u].map(function(d){return '<option>'+d+'</option>';}).join(''):'');
sel.disabled=!u;
}
function openForm(i){
formIdx=i;var c=i<0?null:cabins[i];
document.getElementById('formTitle').textContent=i<0?'新增小屋':'编辑小屋 · '+c.name;
['fName','fMgr','fOffice','fMobile','fDate','fMoney','fDevNum'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('fUnit').value='';onFormUnit();
if(c){
document.getElementById('fName').value=c.name;document.getElementById('fMgr').value=c.mgr;
document.getElementById('fOffice').value=c.office;document.getElementById('fMobile').value=c.mobile;
document.getElementById('fDate').value=c.date;document.getElementById('fMoney').value=c.money;
document.getElementById('fDevNum').value=c.devNum;
document.getElementById('fUnit').value=c.unit;onFormUnit();
document.getElementById('fDepar').value=c.depar;document.getElementById('fDepar').disabled=false;
}
document.getElementById('formMask').classList.add('open');
}
function closeForm(){document.getElementById('formMask').classList.remove('open');}
function saveForm(){
var name=document.getElementById('fName').value.trim(),u=document.getElementById('fUnit').value,d=document.getElementById('fDepar').value;
var mgr=document.getElementById('fMgr').value.trim(),of=document.getElementById('fOffice').value.trim(),mb=document.getElementById('fMobile').value.trim();
var dt=document.getElementById('fDate').value,mn=document.getElementById('fMoney').value,dv=document.getElementById('fDevNum').value;
if(!name||!u||!d||!mgr||!of||!mb||!dt||mn===''){showToast('请完整填写带 * 的必填项');return;}
if(!/^1[3-9]\d{9}$/.test(mb)&&!/^1\d{2}\*{4}\d{4}$/.test(mb)){showToast('手机号格式不正确');return;}
var obj={name:name,unit:u,depar:d,mgr:mgr,office:of,mobile:mb,date:dt,money:+mn||0,devNum:+dv||0};
if(formIdx<0){obj.records=[];cabins.push(obj);}
else{obj.records=cabins[formIdx].records;cabins[formIdx]=obj;}
closeForm();render();showToast(formIdx<0?'小屋已建档,请在数据管理中录入测量数据':'保存成功');
}
/* ---- 数据管理 ---- */
function openData(i){
dataIdx=i;
document.getElementById('dFrom').value='2026-09-01';document.getElementById('dTo').value='2026-09-07';
document.getElementById('dataTitle').textContent='数据管理 · '+cabins[i].name;
renderData();document.getElementById('dataMask').classList.add('open');
}
function closeData(){document.getElementById('dataMask').classList.remove('open');}
function renderData(){
var f=document.getElementById('dFrom').value,t=document.getElementById('dTo').value;
var list=cabins[dataIdx].records.filter(function(r){return (!f||r.d>=f)&&(!t||r.d<=t);});
document.getElementById('dataBody').innerHTML=list.map(function(r){
return '<tr><td>'+r.d+'</td><td>'+r.bp+'</td><td>'+r.bg+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="3">所选区间暂无测量记录</td></tr>';
var sb=list.reduce(function(s,r){return s+r.bp;},0),sg=list.reduce(function(s,r){return s+r.bg;},0);
document.getElementById('dataFoot').innerHTML='<tr class="tfoot"><td>合计('+list.length+' 天)</td><td>'+sb+'</td><td>'+sg+'</td></tr>';
}
function addRecord(){
var d=document.getElementById('nDate').value,bp=document.getElementById('nBp').value,bg=document.getElementById('nBg').value;
if(!d||bp===''||bg===''){showToast('请填写日期与血压/血糖人次');return;}
var rec={d:d,bp:+bp,bg:+bg};
var c=cabins[dataIdx];
var old=-1;c.records.forEach(function(r,idx){if(r.d===d)old=idx;});
if(old>=0)c.records[old]=rec;else c.records.push(rec);
c.records.sort(function(a,b){return a.d<b.d?-1:1;});
document.getElementById('nBp').value='';document.getElementById('nBg').value='';
renderData();showToast(old>=0?'该日期已有记录,已按最新录入覆盖':'测量记录已录入');
}
/* ---- 导出记录 / 删除 ---- */
function openExports(){document.getElementById('expMask').classList.add('open');}
function closeExports(){document.getElementById('expMask').classList.remove('open');}
function openDel(i){
delIdx=i;var c=cabins[i];
document.getElementById('delBody').innerHTML='确认删除小屋 <b style="color:var(--text-primary);">'+c.name+'</b>?其 <b>'+c.records.length+'</b> 天测量记录与统计报表取数将一并失效,操作不可恢复。';
document.getElementById('delMask').classList.add('open');
}
function closeDel(){document.getElementById('delMask').classList.remove('open');}
function confirmDel(){cabins.splice(delIdx,1);closeDel();render();showToast('已删除');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
+405
View File
@@ -0,0 +1,405 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康室管理 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-select:disabled{background-color:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
.filter-input{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1280px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--danger{background:#FFF1F0;color:#CF1322;border:1px solid #FFCCC7;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.room-thumb{width:64px;height:44px;margin:0 auto;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-xs);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:760px;max-height:86vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.form-row{display:flex;align-items:flex-start;gap:12px;margin-bottom:16px;}
.form-row__label{width:104px;flex-shrink:0;text-align:right;font-size:var(--font-sm);color:var(--text-secondary);padding-top:7px;}
.form-row__label i{color:var(--danger);font-style:normal;margin-right:2px;}
.form-row__control{flex:1;display:flex;align-items:center;gap:8px;}
.form-input{width:100%;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);outline:none;}
.form-input:focus{border-color:var(--primary);}
.radio-group{display:flex;gap:18px;padding-top:7px;}
.radio-group label{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio-group input{accent-color:var(--primary);}
.chip-check{display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border:1px solid var(--border);border-radius:14px;font-size:var(--font-xs);cursor:pointer;margin:2px 4px 2px 0;user-select:none;}
.chip-check.on{border-color:var(--primary);background:var(--primary-light);color:#096DD9;}
.upload-box{border:1px dashed var(--border);border-radius:var(--radius-md);padding:14px;text-align:center;color:var(--text-placeholder);font-size:var(--font-xs);cursor:pointer;width:100%;}
.upload-box:hover{border-color:var(--primary);color:var(--primary);}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:104px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.inner-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.inner-table th{background:#FAFAFA;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;padding:7px;border-bottom:1px solid var(--border);}
.inner-table td{font-size:var(--font-sm);padding:8px 7px;text-align:center;border-bottom:1px solid #F0F0F0;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item active">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item">默认排班</a>
<a href="hut-screen.html" class="sidebar-item">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏(单位→部门级联) -->
<div class="filter-bar">
<div class="filter-item">
<label>单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>部门</label>
<select class="filter-select" id="filterDepar" onchange="render()" disabled>
<option value="">全部部门</option>
</select>
</div>
<div class="filter-item">
<label>健康室名称</label>
<input class="filter-input" id="filterName" placeholder="关键字" oninput="render()">
</div>
<div class="filter-item">
<label>启用状态</label>
<select class="filter-select" id="filterStatus" onchange="render()" style="min-width:90px;">
<option value="">全部</option><option></option><option></option>
</select>
</div>
<button class="btn btn-primary" onclick="render();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
停用状态的健康室对员工端不可见、不产生新预约;「使用法定排班=否」时该室走自定义排班(在「默认排班」页维护),节假日开放时间以法定排班为准。
</div>
<!-- [交互] 列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">健康室列表</span>
<button class="btn btn-primary" onclick="openForm(-1)">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增健康室
</button>
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th style="width:56px;">序号</th><th style="min-width:150px;">名称</th><th style="min-width:210px;">地点</th>
<th style="min-width:90px;">图片</th><th style="min-width:170px;">所属部门</th>
<th>启用</th><th>法定排班</th><th>设备数</th><th style="width:210px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 新增/编辑弹窗 -->
<div class="modal-mask" id="formMask">
<div class="modal--wide">
<div class="modal__header"><span id="formTitle">新增健康室</span><button class="modal__close" onclick="closeForm()">&times;</button></div>
<div class="modal__body">
<div class="form-row"><div class="form-row__label"><i>*</i>健康室名称</div><div class="form-row__control"><input class="form-input" id="fName" placeholder="如:樊学站点健康小屋"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>健康室地址</div><div class="form-row__control"><input class="form-input" id="fPlace" placeholder="详细地点"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>所属部门</div><div class="form-row__control">
<select class="filter-select" id="fUnit" style="width:150px;" onchange="onFormUnit()"><option value="">选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select>
<select class="filter-select" id="fDepar" style="width:170px;" disabled><option value="">选择部门</option></select>
</div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>使用部门</div><div class="form-row__control"><div id="fUseChips"></div></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>使用法定排班</div><div class="form-row__control">
<div class="radio-group"><label><input type="radio" name="fLegal" value="是"></label><label><input type="radio" name="fLegal" value="否"></label></div>
</div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>启用状态</div><div class="form-row__control">
<div class="radio-group"><label><input type="radio" name="fStatus" value="是" checked>启用</label><label><input type="radio" name="fStatus" value="否">停用</label></div>
</div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>健康室图片</div><div class="form-row__control">
<div class="upload-box" onclick="showToast('原型示意:已选择健康室实景图')">点击上传(1 张,建议 800×600)</div>
</div></div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeForm()">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
</div>
<!-- [交互] 设备管理弹窗 -->
<div class="modal-mask" id="devMask">
<div class="modal--wide">
<div class="modal__header"><span id="devTitle">设备管理</span><button class="modal__close" onclick="closeDev()">&times;</button></div>
<div class="modal__body">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
<label style="font-size:var(--font-xs);color:var(--text-secondary);">设备类型</label>
<select class="filter-select" id="devType" onchange="renderDev()" style="min-width:120px;">
<option value="">全部</option><option value="1">测量设备</option><option value="2">心理检测</option><option value="3">治疗设备</option><option value="4">监测设备</option>
</select>
<span style="flex:1;"></span>
<button class="btn btn-primary" onclick="showToast('原型示意:设备登记需走设备台账流程')">+ 添加设备</button>
</div>
<table class="inner-table">
<thead><tr><th>设备编号</th><th>设备名称</th><th>设备类型</th><th>单位预约时间(分钟)</th><th>可预约人数(人)</th><th>设备状态</th></tr></thead>
<tbody id="devBody"></tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDev()">关闭</button></div>
</div>
</div>
<!-- [交互] 详情弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span>健康室详情</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__header"><span>删除确认</span><button class="modal__close" onclick="closeDel()">&times;</button></div>
<div class="modal__body" id="delBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDel()">取消</button>
<button class="btn btn-danger" onclick="confirmDel()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 单位→部门共享池(与床头呼叫域一致) */
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
/* 健康室数据:与站点/作业区勾稽;devices 内类型 1测量/2心理检测/3治疗设备/4监测设备 */
var rooms=[
{name:'樊学站点健康小屋',place:'樊学站点基地综合楼一层',unit:'采油一厂',depar:'樊学作业区',use:['樊学作业区'],status:'是',legal:'是',
devices:[{code:'FX-BP-01',name:'血压检测仪',cat:1,len:30,num:4},{code:'FX-BG-02',name:'血糖分析仪',cat:1,len:20,num:6},{code:'FX-PSY-03',name:'心理测评系统',cat:2,len:40,num:2},{code:'FX-ECG-04',name:'十二导联心电',cat:4,len:0,num:0}]},
{name:'麻子山站点健康小屋',place:'麻子山站点综合办公楼二层',unit:'采油一厂',depar:'麻子山作业区',use:['麻子山作业区'],status:'是',legal:'是',
devices:[{code:'MZ-BP-01',name:'血压检测仪',cat:1,len:30,num:4},{code:'MZ-BG-02',name:'血糖分析仪',cat:1,len:20,num:6},{code:'MZ-BMI-03',name:'体成分分析仪',cat:1,len:20,num:8}]},
{name:'桐园站点健康小屋',place:'桐园站点基地服务中心一层',unit:'采油二厂',depar:'桐园作业区',use:['桐园作业区','姬圪台作业区'],status:'是',legal:'是',
devices:[{code:'TY-BP-01',name:'血压检测仪',cat:1,len:30,num:4},{code:'TY-BMI-02',name:'体成分分析仪',cat:1,len:20,num:8},{code:'TY-PSY-03',name:'心理测评系统',cat:2,len:40,num:2},{code:'TY-DEC-04',name:'音乐放松舱',cat:3,len:60,num:2}]},
{name:'西峰基地健康小屋',place:'西峰基地职工服务中心二层',unit:'第四采油厂',depar:'西峰作业区',use:['西峰作业区'],status:'是',legal:'否',
devices:[{code:'XF-BP-01',name:'血压检测仪',cat:1,len:30,num:4},{code:'XF-BG-02',name:'血糖分析仪',cat:1,len:20,num:6},{code:'XF-LAS-03',name:'激光治疗仪',cat:3,len:30,num:3},{code:'XF-ECG-04',name:'十二导联心电',cat:4,len:0,num:0},{code:'XF-WCH-05',name:'智能手表充电柜',cat:4,len:0,num:0}]},
{name:'靖边气田健康小屋',place:'靖边气田站点营地活动室',unit:'采气一厂',depar:'靖边气田',use:['靖边气田'],status:'否',legal:'是',
devices:[{code:'JB-BP-01',name:'血压检测仪',cat:1,len:30,num:4},{code:'JB-BMI-02',name:'体成分分析仪',cat:1,len:20,num:8}]}
];
var catName={1:'测量设备',2:'心理检测',3:'治疗设备',4:'监测设备'};
var catBadge={1:'badge--info',2:'badge--warning',3:'badge--success',4:'badge--muted'};
var gradients=['linear-gradient(135deg,#1890FF,#36CFC9)','linear-gradient(135deg,#722ED1,#1890FF)','linear-gradient(135deg,#FA8C16,#F5222D)','linear-gradient(135deg,#52C41A,#13C2C2)','linear-gradient(135deg,#EB2F96,#722ED1)'];
var formIdx=-1,devIdx=-1,delIdx=-1;
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterDepar');
sel.innerHTML='<option value="">全部部门</option>'+(u?unitDeparts[u].map(function(d){return '<option>'+d+'</option>';}).join(''):'');
sel.disabled=!u;render();
}
function render(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepar').value;
var kn=document.getElementById('filterName').value.trim(),st=document.getElementById('filterStatus').value;
var list=rooms.filter(function(r){return (!u||r.unit===u)&&(!d||r.depar===d)&&r.name.indexOf(kn)>=0&&(!st||r.status===st);});
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=rooms.indexOf(r);
return '<tr><td>'+(i+1)+'</td><td style="text-align:left;padding-left:12px;font-weight:500;">'+r.name+'</td>'
+'<td style="text-align:left;padding-left:12px;">'+r.place+'</td>'
+'<td><div class="room-thumb" style="background:'+gradients[i%gradients.length]+';'+(r.status==='否'?'filter:grayscale(1);opacity:.6;':'')+'">实景</div></td>'
+'<td>'+r.unit+' / '+r.depar+'</td>'
+'<td><span class="badge '+(r.status==='是'?'badge--success':'badge--muted')+'">'+r.status+'</span></td>'
+'<td><span class="badge '+(r.legal==='是'?'badge--info':'badge--warning')+'">'+r.legal+'</span></td>'
+'<td>'+r.devices.length+' 台</td>'
+'<td><button class="btn btn-link" onclick="openDev('+i+')">设备管理</button>'
+'<button class="btn btn-link" onclick="openDetail('+i+')">详情</button>'
+'<button class="btn btn-link" onclick="openForm('+i+')">编辑</button>'
+'<button class="btn btn-link" style="color:var(--danger);" onclick="openDel('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的健康室</td></tr>';
var totalDev=rooms.reduce(function(s,r){return s+r.devices.length;},0);
document.getElementById('totalTip').textContent='共 '+list.length+' 间 · 在册 '+rooms.length+' 间(设备合计 '+totalDev+' 台)';
}
function resetFilter(){['filterUnit','filterDepar','filterName','filterStatus'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterDepar').disabled=true;render();}
/* ---- 新增/编辑 ---- */
function onFormUnit(){
var u=document.getElementById('fUnit').value;
var sel=document.getElementById('fDepar');
sel.innerHTML='<option value="">选择部门</option>'+(u?unitDeparts[u].map(function(d){return '<option>'+d+'</option>';}).join(''):'');
sel.disabled=!u;
fillChips(u);
}
function fillChips(u){
var box=document.getElementById('fUseChips');
var deps=u?unitDeparts[u]:[];
box.innerHTML=deps.length?deps.map(function(d){return '<span class="chip-check" data-d="'+d+'" onclick="toggleChip(this)">'+d+'</span>';}).join(''):'<span style="font-size:var(--font-xs);color:var(--text-placeholder);">请先选择所属单位</span>';
}
function toggleChip(el){el.classList.toggle('on');}
function openForm(i){
formIdx=i;
document.getElementById('formTitle').textContent=i<0?'新增健康室':'编辑健康室 · '+rooms[i].name;
document.getElementById('fName').value=i<0?'':rooms[i].name;
document.getElementById('fPlace').value=i<0?'':rooms[i].place;
var fu=document.getElementById('fUnit'),fd=document.getElementById('fDepar');
fu.value=i<0?'':rooms[i].unit;onFormUnit();
if(i>=0){fd.value=rooms[i].depar;fd.disabled=false;
rooms[i].use.forEach(function(d){var c=document.querySelector('#fUseChips [data-d="'+d+'"]');if(c)c.classList.add('on');});}
document.querySelectorAll('input[name=fLegal]')[i>=0&&rooms[i].legal==='是'?0:1].checked=true;
document.querySelectorAll('input[name=fStatus]')[i>=0&&rooms[i].status==='是'?0:1].checked=true;
document.getElementById('formMask').classList.add('open');
}
function closeForm(){document.getElementById('formMask').classList.remove('open');}
function saveForm(){
var name=document.getElementById('fName').value.trim(),place=document.getElementById('fPlace').value.trim();
var u=document.getElementById('fUnit').value,d=document.getElementById('fDepar').value;
var use=Array.prototype.slice.call(document.querySelectorAll('#fUseChips .on')).map(function(c){return c.getAttribute('data-d');});
var legal=document.querySelector('input[name=fLegal]:checked').value,status=document.querySelector('input[name=fStatus]:checked').value;
if(!name||!place||!u||!d||!use.length){showToast('请完整填写名称/地址/所属与使用部门');return;}
if(formIdx<0){rooms.push({name:name,place:place,unit:u,depar:d,use:use,status:status,legal:legal,devices:[]});}
else{var r=rooms[formIdx];r.name=name;r.place=place;r.unit=u;r.depar=d;r.use=use;r.status=status;r.legal=legal;}
closeForm();render();showToast(formIdx<0?'新增成功,可继续为其登记设备':'保存成功');
}
function di(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var r=rooms[i];
document.getElementById('detailBody').innerHTML='<div class="detail-grid">'
+di('健康室名称',r.name)+di('地点',r.place)
+di('所属部门',r.unit+' / '+r.depar)+di('使用部门',r.use.join('、'))
+di('启用状态',r.status)+di('使用法定排班',r.legal)
+di('在册设备数',r.devices.length+' 台',true)
+di('设备清单',r.devices.map(function(d){return d.name+'('+d.code+')';}).join('、')||'—',true)
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
/* ---- 设备管理 ---- */
function openDev(i){
devIdx=i;document.getElementById('devType').value='';
document.getElementById('devTitle').textContent='设备管理 · '+rooms[i].name;
renderDev();document.getElementById('devMask').classList.add('open');
}
function closeDev(){document.getElementById('devMask').classList.remove('open');}
function renderDev(){
var t=document.getElementById('devType').value;
var list=rooms[devIdx].devices.filter(function(d){return !t||String(d.cat)===t;});
document.getElementById('devBody').innerHTML=list.map(function(d){
return '<tr><td style="font-family:monospace;">'+d.code+'</td><td>'+d.name+'</td>'
+'<td><span class="badge '+catBadge[d.cat]+'">'+catName[d.cat]+'</span></td>'
+'<td>'+(d.cat===4?'—':d.len)+'</td><td>'+(d.cat===4?'—':d.num)+'</td>'
+'<td><span class="badge badge--success">有效</span></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="6">该类型下暂无设备</td></tr>';
}
/* ---- 删除 ---- */
function openDel(i){
delIdx=i;var r=rooms[i];
document.getElementById('delBody').innerHTML='确认删除健康室 <b style="color:var(--text-primary);">'+r.name+'</b>?其名下 <b>'+r.devices.length+'</b> 台设备登记与历史预约记录将一并失效,操作不可恢复。';
document.getElementById('delMask').classList.add('open');
}
function closeDel(){document.getElementById('delMask').classList.remove('open');}
function confirmDel(){rooms.splice(delIdx,1);closeDel();render();showToast('已删除');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
@@ -0,0 +1,262 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>默认排班 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-danger{background:var(--danger);color:#fff;}.btn-danger:hover{background:#ff7875;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1280px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
.form-row{display:flex;align-items:center;gap:12px;margin-bottom:16px;}
.form-row__label{width:88px;flex-shrink:0;text-align:right;font-size:var(--font-sm);color:var(--text-secondary);}
.form-row__label i{color:var(--danger);font-style:normal;margin-right:2px;}
.form-row__control{flex:1;display:flex;align-items:center;gap:8px;}
.form-time{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-sm);outline:none;width:120px;}
.form-time:focus{border-color:var(--primary);}
.week-chips{display:flex;flex-wrap:wrap;gap:6px;}
.week-chip{padding:6px 14px;border:1px solid var(--border);border-radius:16px;font-size:var(--font-sm);cursor:pointer;user-select:none;}
.week-chip.on{border-color:var(--primary);background:var(--primary-light);color:#096DD9;font-weight:600;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item active">默认排班</a>
<a href="hut-screen.html" class="sidebar-item">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>工作日</label>
<select class="filter-select" id="filterWeek" onchange="render()" style="min-width:110px;">
<option value="">全部工作日</option>
<option>周一</option><option>周二</option><option>周三</option><option>周四</option><option>周五</option><option>周六</option><option>周日</option>
</select>
</div>
<button class="btn btn-primary" onclick="render();showToast('查询完成')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="document.getElementById('filterWeek').value='';render()">重置</button>
</div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
每个工作日仅允许一条排班记录;调整本表实时生效,已产生的历史预约不受影响,但受影响工作日的未来可约时段将同步变化。
</div>
<!-- [交互] 列表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title">周排班表</span>
<button class="btn btn-primary" onclick="openForm(-1)">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>新增排班
</button>
</div>
<div class="table-card__content">
<table>
<thead><tr>
<th style="width:56px;">序号</th><th style="min-width:120px;">工作日</th><th style="min-width:140px;">开始时间</th>
<th style="min-width:140px;">结束时间</th><th style="min-width:120px;">开放时长</th><th style="width:150px;">操作</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 新增/编辑弹窗 -->
<div class="modal-mask" id="formMask">
<div class="modal">
<div class="modal__header"><span id="formTitle">新增排班</span><button class="modal__close" onclick="closeForm()">&times;</button></div>
<div class="modal__body">
<div class="form-row"><div class="form-row__label"><i>*</i>工作日</div><div class="form-row__control"><div class="week-chips" id="weekChips"></div></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>开始时间</div><div class="form-row__control"><input class="form-time" type="time" id="fStart" value="08:30"></div></div>
<div class="form-row"><div class="form-row__label"><i>*</i>结束时间</div><div class="form-row__control"><input class="form-time" type="time" id="fEnd" value="17:30"></div></div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeForm()">取消</button>
<button class="btn btn-primary" onclick="saveForm()">保存</button>
</div>
</div>
</div>
<!-- [交互] 删除确认 -->
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__header"><span>删除确认</span><button class="modal__close" onclick="closeDel()">&times;</button></div>
<div class="modal__body" id="delBody"></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDel()">取消</button>
<button class="btn btn-danger" onclick="confirmDel()">确认删除</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var weekDays=['周一','周二','周三','周四','周五','周六','周日'];
/* 默认排班:周一至周五全天,周六半天,周日未排班(不开放) */
var schedules=[
{w:'周一',s:'08:30',e:'17:30'},
{w:'周二',s:'08:30',e:'17:30'},
{w:'周三',s:'08:30',e:'17:30'},
{w:'周四',s:'08:30',e:'17:30'},
{w:'周五',s:'08:30',e:'17:30'},
{w:'周六',s:'09:00',e:'12:00'}
];
var formIdx=-1,delIdx=-1,pickedWeek='';
function toMin(t){var p=t.split(':');return +p[0]*60+ +p[1];}
function dur(s,e){var m=(toMin(e)-toMin(s))/60;return (m%1?m.toFixed(1):m)+' 小时';}
function sortedList(){return schedules.slice().sort(function(a,b){return weekDays.indexOf(a.w)-weekDays.indexOf(b.w);});}
function render(){
var fw=document.getElementById('filterWeek').value;
var list=sortedList().filter(function(r){return !fw||r.w===fw;});
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=schedules.indexOf(r);
var off=r.w==='周六'||r.w==='周日';
return '<tr><td>'+(weekDays.indexOf(r.w)+1)+'</td>'
+'<td style="font-weight:500;">'+r.w+'<span class="badge '+(off?'badge--muted':'badge--success')+'" style="margin-left:6px;">'+(off?'周末':'工作日')+'</span></td>'
+'<td>'+r.s+'</td><td>'+r.e+'</td><td>'+dur(r.s,r.e)+'</td>'
+'<td><button class="btn btn-link" onclick="openForm('+i+')">编辑</button>'
+'<button class="btn btn-link" style="color:var(--danger);" onclick="openDel('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="6">没有符合条件的排班</td></tr>';
document.getElementById('totalTip').textContent='共 '+schedules.length+' 个排班日 · 每周开放 '+schedules.reduce(function(x,r){return x+toMin(r.e)-toMin(r.s);},0)/60+' 小时';
}
function fillChips(){
document.getElementById('weekChips').innerHTML=weekDays.map(function(w){
var used=schedules.some(function(r,idx){return r.w===w&&idx!==formIdx;});
return '<span class="week-chip'+(pickedWeek===w?' on':'')+(used?'':'')+'" data-w="'+w+'" style="'+(used?'opacity:.45;cursor:not-allowed;':'')+'" onclick="pickWeek(this,\''+w+'\','+used+')">'+w+'</span>';
}).join('');
}
function pickWeek(el,w,used){
if(used){showToast(w+' 已有排班,请先删除原排班再新增');return;}
pickedWeek=w;fillChips();
}
function openForm(i){
formIdx=i;var r=i<0?null:schedules[i];
pickedWeek=r?r.w:'';
document.getElementById('formTitle').textContent=i<0?'新增排班':'编辑排班 · '+r.w;
document.getElementById('fStart').value=r?r.s:'08:30';
document.getElementById('fEnd').value=r?r.e:'17:30';
fillChips();document.getElementById('formMask').classList.add('open');
}
function closeForm(){document.getElementById('formMask').classList.remove('open');}
function saveForm(){
if(!pickedWeek){showToast('请选择工作日');return;}
var s=document.getElementById('fStart').value,e=document.getElementById('fEnd').value;
if(!s||!e){showToast('请填写开始与结束时间');return;}
if(toMin(e)<=toMin(s)){showToast('结束时间须晚于开始时间');return;}
if(formIdx<0){schedules.push({w:pickedWeek,s:s,e:e});}
else{schedules[formIdx].w=pickedWeek;schedules[formIdx].s=s;schedules[formIdx].e=e;}
closeForm();render();showToast(formIdx<0?'排班新增成功':'排班已更新,实时生效');
}
function openDel(i){
delIdx=i;var r=schedules[i];
document.getElementById('delBody').innerHTML='确认删除 <b style="color:var(--text-primary);">'+r.w+'</b>( '+r.s+' ~ '+r.e+' )的排班?删除后该工作日所有法定排班健康室将停止开放。';
document.getElementById('delMask').classList.add('open');
}
function closeDel(){document.getElementById('delMask').classList.remove('open');}
function confirmDel(){schedules.splice(delIdx,1);closeDel();render();showToast('已删除该排班');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
render();
</script>
</body>
</html>
+221
View File
@@ -0,0 +1,221 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大屏数据配置 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.tab-bar{background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);display:flex;flex-wrap:wrap;padding:0 8px;border-bottom:1px solid var(--border);}
.tab-item{padding:12px 16px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;white-space:nowrap;}
.tab-item:hover{color:var(--primary);}
.tab-item.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.hint-bar{margin-top:16px;background:#E6F7FF;border:1px solid #91D5FF;border-radius:var(--radius-md);padding:10px 16px;font-size:var(--font-xs);color:#096DD9;display:flex;align-items:center;gap:8px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:16px;}
.table-card__head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);gap:12px;flex-wrap:wrap;}
.table-card__title{font-size:var(--font-sm);font-weight:600;}
.table-card__actions{display:flex;align-items:center;gap:10px;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1280px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
thead tr.grp th{background:#F0F5FF;color:#096DD9;font-weight:600;border-bottom:1px solid var(--border);}
tbody td{padding:12px 7px;text-align:center;font-size:var(--font-base);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;font-weight:500;}
td.ro{color:var(--text-placeholder);font-weight:400;}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.cell-input{width:104px;height:30px;border:1px solid var(--primary);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-sm);text-align:center;outline:none;}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-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="hut-banner.html" class="sidebar-item">轮播图配置</a>
<a href="hut-room.html" class="sidebar-item">健康室管理</a>
<a href="hut-appointment.html" class="sidebar-item">预约管理</a>
<a href="hut-schedule.html" class="sidebar-item">默认排班</a>
<a href="hut-screen.html" class="sidebar-item active">大屏数据配置</a>
<a href="hut-cabin.html" class="sidebar-item">小屋管理</a>
<a href="hut-cabin-stat.html" class="sidebar-item">小屋统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 数据区 TAB -->
<div class="tab-bar" id="tabBar"></div>
<div class="hint-bar">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="6" stroke="#1890FF" stroke-width="1.2"/><path d="M7 6.2v4M7 3.8v.6" stroke="#1890FF" stroke-width="1.4" stroke-linecap="round"/></svg>
录入流程:将状态切到「关」→ 点击「录入数据」进入编辑态 → 修改数值后「保存」。切回「开」时录入值保留但大屏不再展示,改回实时数据。
</div>
<!-- [交互] 单行数据表 -->
<div class="table-card">
<div class="table-card__head">
<span class="table-card__title" id="cardTitle"></span>
<div class="table-card__actions" id="cardActions"></div>
</div>
<div class="table-card__content">
<table id="screenTable"></table>
</div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
/* 9 个数据区:cols 中 ro=系统统计只读;risk 区带 g 分组(低/中/高风险) */
var tabs=[
{label:'五类人群',unit:'人',
cols:[{k:'totalUser',label:'员工总人数',ro:true},{k:'illnessNum',label:'大病人员'},{k:'slowVirusNum',label:'慢病人员'},{k:'failNum',label:'指标异常'},{k:'riskNum',label:'高风险人群'},{k:'healthNum',label:'健康人群'}],
val:{totalUser:12860,illnessNum:486,slowVirusNum:2317,failNum:1542,riskNum:2894,healthNum:5621},status:true},
{label:'一线医疗点',unit:'人次',
cols:[{k:'weekNum',label:'近一周就诊'},{k:'totalNum',label:'累计就诊'},{k:'weekAmbulanceNum',label:'近一周救护车出动'},{k:'totalAmbulanceNum',label:'累计救护车出动'}],
val:{weekNum:46,totalNum:5327,weekAmbulanceNum:3,totalAmbulanceNum:184},status:false},
{label:'员工营养监控',unit:'人次',
cols:[{k:'bCount',label:'早餐监测'},{k:'bOver',label:'早餐能量超标'},{k:'lCount',label:'午餐监测'},{k:'lOver',label:'午餐能量超标'},{k:'dCount',label:'晚餐监测'},{k:'dOver',label:'晚餐能量超标'}],
val:{bCount:420,bOver:37,lCount:1350,lOver:214,dCount:980,dOver:96},status:false},
{label:'总体健康水平',unit:'分/人',
cols:[{k:'health',label:'健康值(综合)',ro:true},{k:'sub',label:'亚健康'},{k:'mild',label:'轻度风险'},{k:'moderate',label:'中度风险'},{k:'serious',label:'高度风险'}],
val:{health:82.6,sub:2431,mild:1892,moderate:640,serious:216},status:true},
{label:'干预效果',unit:'分/%',
cols:[{k:'sleepBefore',label:'睡眠干预前得分'},{k:'sleepAfter',label:'睡眠干预后得分'},{k:'meditBefore',label:'冥想干预前得分'},{k:'meditAfter',label:'冥想干预后得分'},{k:'cycBefore',label:'骑行干预前得分'},{k:'cycAfter',label:'骑行干预后得分'},{k:'total',label:'总体改善率'}],
val:{sleepBefore:62.5,sleepAfter:78.3,meditBefore:55.8,meditAfter:69.4,cycBefore:48.6,cycAfter:61.2,total:12.7},status:false},
{label:'手表监测',unit:'人',
cols:[{k:'spo2N',label:'血氧正常'},{k:'spo2W',label:'血氧警戒'},{k:'spo2D',label:'血氧危险'},{k:'hrN',label:'心率正常'},{k:'hrW',label:'心率警戒'},{k:'hrD',label:'心率危险'}],
val:{spo2N:1024,spo2W:86,spo2D:9,hrN:1092,hrW:21,hrD:6},status:true},
{label:'运动压力',unit:'人·天',
cols:[{k:'e1P',label:'<2km 本周'},{k:'e1L',label:'<2km 上周'},{k:'e1M',label:'<2km 本月'},{k:'e1LM',label:'<2km 上月'},
{k:'e2P',label:'3-5km 本周'},{k:'e2L',label:'3-5km 上周'},{k:'e2M',label:'3-5km 本月'},{k:'e2LM',label:'3-5km 上月'},
{k:'e3P',label:'>5km 本周'},{k:'e3L',label:'>5km 上周'},{k:'e3M',label:'>5km 本月'},{k:'e3LM',label:'>5km 上月'},
{k:'pR',label:'压力·放松'},{k:'pN',label:'压力·正常'},{k:'pM',label:'压力·中等'},{k:'pH',label:'压力·偏高'},{k:'pAll',label:'参与统计人数',ro:true}],
val:{e1P:1284,e1L:1350,e1M:5420,e1LM:5710,e2P:862,e2L:810,e2M:3544,e2LM:3320,e3P:214,e3L:198,e3M:906,e3LM:875,pR:480,pN:1350,pM:620,pH:146,pAll:2596},status:true},
{label:'员工健康风险',unit:'人',group:true,
diseases:['代谢综合征','高血压','肺癌','糖尿病','缺血性心血管病'],
cols:[{k:'m_l',g:'低',label:'代谢综合征'},{k:'h_l',g:'低',label:'高血压'},{k:'lu_l',g:'低',label:'肺癌'},{k:'d_l',g:'低',label:'糖尿病'},{k:'ic_l',g:'低',label:'缺血性心血管病'},
{k:'m_m',g:'中',label:'代谢综合征'},{k:'h_m',g:'中',label:'高血压'},{k:'lu_m',g:'中',label:'肺癌'},{k:'d_m',g:'中',label:'糖尿病'},{k:'ic_m',g:'中',label:'缺血性心血管病'},
{k:'m_h',g:'高',label:'代谢综合征'},{k:'h_h',g:'高',label:'高血压'},{k:'lu_h',g:'高',label:'肺癌'},{k:'d_h',g:'高',label:'糖尿病'},{k:'ic_h',g:'高',label:'缺血性心血管病'}],
val:{m_l:1820,h_l:1450,lu_l:2100,d_l:1680,ic_l:1520,m_m:640,h_m:720,lu_m:180,d_m:540,ic_m:610,m_h:210,h_h:315,lu_h:42,d_h:160,ic_h:248},status:true},
{label:'今日概况(手工)',unit:'次',
cols:[{k:'todayCheck',label:'今日就诊'},{k:'totalCheck',label:'累计就诊'},{k:'todayAmbulance',label:'今日救护车出动'},{k:'totalAmbulance',label:'累计救护车出动'}],
val:{todayCheck:8,totalCheck:5327,todayAmbulance:0,totalAmbulance:184},status:false}
];
var cur=0,editing=false,backup=null;
function switchTab(i){cur=i;editing=false;backup=null;renderAll();}
function renderTabs(){
document.getElementById('tabBar').innerHTML=tabs.map(function(t,i){
return '<div class="tab-item'+(i===cur?' active':'')+'" onclick="switchTab('+i+')">'+(i+1)+'. '+t.label+'</div>';
}).join('');
}
function renderActions(){
var t=tabs[cur];
var h='<span class="badge '+(t.status?'badge--success':'badge--warning')+'">数据源:'+(t.status?'开 · 实时取数':'关 · 手工数据')+'</span>';
h+='<button class="btn btn-default" onclick="toggleStatus()">'+(t.status?'切为手工数据':'切为实时取数')+'</button>';
if(editing){h+='<button class="btn btn-default" onclick="cancelEdit()">取消</button><button class="btn btn-primary" onclick="saveEdit()">保存</button>';}
else{h+='<button class="btn btn-primary" onclick="startEdit()">录入数据</button>';}
document.getElementById('cardActions').innerHTML=h;
}
function renderTable(){
var t=tabs[cur],h='';
if(t.group){
var gOrder=['低','中','高'],gName={'低':'低风险人群','中':'中风险人群','高':'高风险人群'};
h+='<thead><tr class="grp"><th rowspan="2" style="min-width:120px;">疾病 \ 风险档位</th>'+gOrder.map(function(g){return '<th colspan="5">'+gName[g]+'</th>';}).join('')+'</tr><tr>'+t.cols.map(function(c){return '<th>'+c.label+'</th>';}).join('')+'</tr></thead>';
}else{
h+='<thead><tr>'+t.cols.map(function(c){return '<th'+(c.ro?' style="color:#096DD9;"':'')+'>'+c.label+(c.ro?' 🔒':'')+'</th>';}).join('')+'</tr>';
}
h+='<tbody><tr>'+t.cols.map(function(c){
var v=t.val[c.k];
if(editing&&!c.ro){return '<td><input class="cell-input" type="number" step="any" data-k="'+c.k+'" value="'+v+'"></td>';}
return '<td class="'+(c.ro?'ro':'')+'"'+(c.ro?' title="系统自动统计,不可录入"':'')+'>'+v+'</td>';
}).join('')+'</tr></tbody>';
document.getElementById('screenTable').innerHTML=h;
}
function renderAll(){
document.getElementById('cardTitle').textContent='大屏数据 · '+tabs[cur].label+'(单位:'+tabs[cur].unit+')';
renderTabs();renderActions();renderTable();
}
function toggleStatus(){
var t=tabs[cur];t.status=!t.status;editing=false;backup=null;
renderActions();renderTable();
showToast(t.status?'已切换:大屏实时取数,录入值已保留':'已切换:大屏将展示手工录入值');
}
function startEdit(){
var t=tabs[cur];
if(t.status){showToast('当前为实时取数,建议先切为手工数据再录入');}
backup={};Object.keys(t.val).forEach(function(k){backup[k]=t.val[k];});
editing=true;renderActions();renderTable();
}
function saveEdit(){
var t=tabs[cur];
document.querySelectorAll('#screenTable .cell-input').forEach(function(inp){
var k=inp.getAttribute('data-k');
var n=inp.value===''?0:+inp.value;
t.val[k]=n;
});
editing=false;backup=null;renderActions();renderTable();showToast('录入值已保存');
}
function cancelEdit(){
var t=tabs[cur];
if(backup){Object.keys(backup).forEach(function(k){t.val[k]=backup[k];});}
editing=false;backup=null;renderActions();renderTable();showToast('已取消录入');
}
function showToast(msg){
var el=document.getElementById('toast');el.textContent=msg;el.classList.add('show');
clearTimeout(el._timer);el._timer=setTimeout(function(){el.classList.remove('show');},2200);
}
renderAll();
</script>
</body>
</html>
@@ -0,0 +1,302 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>救护车统计(图表视图) | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.view-switch{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.view-switch a{display:inline-flex;align-items:center;padding:0 14px;height:30px;font-size:var(--font-xs);color:var(--text-secondary);text-decoration:none;background:#fff;}
.view-switch a+a{border-left:1px solid var(--border);}
.view-switch a.active{background:var(--primary);color:#fff;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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;}
.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:16px;}
.kpi-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;}
.kpi-card__label{font-size:var(--font-xs);color:var(--text-secondary);}
.kpi-card__value{font-size:26px;font-weight:600;color:var(--text-primary);margin-top:6px;}
.kpi-card__value small{font-size:var(--font-sm);font-weight:400;color:var(--text-placeholder);margin-left:4px;}
.kpi-card__sub{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:6px;}
.chart-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px;}
.chart-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;}
.chart-card__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);display:flex;align-items:center;justify-content:space-between;}
.chart-card__title .lg{display:flex;gap:12px;font-size:var(--font-xs);font-weight:400;color:var(--text-secondary);}
.chart-card__title .lg i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:4px;vertical-align:-1px;}
.chart-card svg{width:100%;height:auto;display:block;margin-top:10px;}
.chart-card--full{grid-column:1/-1;}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--font-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item active">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位级联 + 周/月维度,图表联动刷新 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="renderAll()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>时间维度</label>
<select class="filter-select" id="filterDim" onchange="renderAll()" style="min-width:90px;">
<option></option><option selected></option>
</select>
</div>
<button class="btn btn-primary" onclick="showToast('图表已按当前条件刷新')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7a5 5 0 1 1 1.5 3.5M2 12V9h3" stroke="#fff" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>刷新
</button>
<!-- [交互] 表格/图表视图切换 -->
<div class="view-switch" style="margin-left:auto;">
<a href="stat-ambulance-table.html">表格视图</a>
<a class="active" href="stat-ambulance-chart.html">图表视图</a>
</div>
</div>
<div class="kpi-row" id="kpiRow"></div>
<div class="chart-grid">
<div class="chart-card">
<div class="chart-card__title">单车出勤对比(本<span class="dw"></span>,按任务类型)
<span class="lg"><i style="background:#FF4D4F;"></i>病情<i style="background:#FAAD14;"></i>伤情<i style="background:#1890FF;"></i>巡诊<i style="background:#B37FEB;"></i>戒备</span></div>
<svg id="chart1" viewBox="0 0 560 280"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">任务结构占比(本<span class="dw"></span>总出车)</div>
<svg id="chart2" viewBox="0 0 560 280"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">累计出勤排行<span class="lg"><i style="background:#1890FF;"></i>出勤次数(建立至今)</span></div>
<svg id="chart3" viewBox="0 0 560 260"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">近12个月总出勤趋势<span class="lg"><i style="background:#52C41A;"></i>出车次数</span></div>
<svg id="chart4" viewBox="0 0 560 260"></svg>
</div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
/* 与「救护车统计-表格」同源:cars 各任务类型本月出勤 */
var cars=[
{unit:'采油一厂',plate:'CQ·0712A',short:'0712A',ill:8,wound:5,day:14,emerg:6,illT:96,woundT:64,dayT:180,emergT:72},
{unit:'采油二厂',plate:'CQ·1366B',short:'1366B',ill:6,wound:4,day:12,emerg:5,illT:88,woundT:55,dayT:165,emergT:60},
{unit:'第四采油厂',plate:'CQ·0208C',short:'0208C',ill:3,wound:1,day:20,emerg:2,illT:40,woundT:12,dayT:260,emergT:25},
{unit:'采气一厂',plate:'CQ·1533D',short:'1533D',ill:5,wound:4,day:10,emerg:4,illT:70,woundT:52,dayT:140,emergT:48},
{unit:'采油一厂',plate:'CQ·0917E',short:'0917E',ill:2,wound:0,day:9,emerg:1,illT:28,woundT:8,dayT:120,emergT:12},
{unit:'采气一厂',plate:'CQ·2201F',short:'2201F',ill:4,wound:3,day:8,emerg:5,illT:52,woundT:41,dayT:110,emergT:40}
];
var dimFactor={'周':0.25,'月':1};
var C=['#FF4D4F','#FAAD14','#1890FF','#B37FEB'];
function getDim(){return document.getElementById('filterDim').value;}
function cur(v){return Math.max(0,Math.round(v*dimFactor[getDim()]));}
function filtered(){var u=document.getElementById('filterUnit').value;return cars.filter(function(c){return !u||c.unit===u;});}
function esc(s){return String(s);}
/* 四系列分组柱状图 */
function drawGroup4(data){
var svg=document.getElementById('chart1'),W=560,H=280,left=36,bottom=40,top=14;
var max=Math.max.apply(null,[].concat.apply([],data.map(function(d){return [d.a,d.b,d.c,d.d];})))||1;
var n=data.length,step=(W-left-16)/Math.max(1,n),bw=Math.min(14,(step-10)/4);
var html='';
for(var g=0;g<=4;g++){
var y=top+(H-bottom-top)*g/4;
html+='<line x1="'+left+'" y1="'+y+'" x2="'+(W-8)+'" y2="'+y+'" stroke="#F0F0F0"/>';
html+='<text x="'+(left-6)+'" y="'+(y+4)+'" text-anchor="end" font-size="10" fill="#999">'+Math.round(max*(4-g)/4)+'</text>';
}
data.forEach(function(d,i){
var x0=left+step*i+(step-bw*4-6)/2;
[[d.a,0],[d.b,1],[d.c,2],[d.d,3]].forEach(function(pair,j){
var h=Math.max(1,(H-bottom-top)*pair[0]/max);
html+='<rect x="'+(x0+j*(bw+2))+'" y="'+(H-bottom-h)+'" width="'+bw+'" height="'+h+'" rx="2" fill="'+C[pair[1]]+'"><title>'+esc(d.k)+':'+pair[0]+'</title></rect>';
});
html+='<text x="'+(left+step*i+step/2)+'" y="'+(H-bottom+18)+'" text-anchor="middle" font-size="10" fill="#666">'+esc(d.k)+'</text>';
});
html+='<line x1="'+left+'" y1="'+(H-bottom)+'" x2="'+(W-8)+'" y2="'+(H-bottom)+'" stroke="#E8E8E8"/>';
svg.innerHTML=html;
}
/* 多切片环形图 */
function drawDonutN(items){
var svg=document.getElementById('chart2'),cx=140,cy=140,R=95,r=58;
var tot=items.reduce(function(s,i){return s+i.v;},0)||1;
var html='',ang=0;
function pt(rad,a){return (cx+rad*Math.sin(a)).toFixed(2)+' '+(cy-rad*Math.cos(a)).toFixed(2);}
items.forEach(function(it,idx){
var sweep=it.v/tot*Math.PI*2,large=sweep>Math.PI?1:0,end=ang+sweep;
if(it.v>0){
html+='<path d="M'+pt(R,ang)+' A'+R+' '+R+' 0 '+large+' 1 '+pt(R,end)+' L'+pt(r,end)+' A'+r+' '+r+' 0 '+large+' 0 '+pt(r,ang)+' Z" fill="'+it.c+'"><title>'+esc(it.k)+':'+it.v+'</title></path>';
}
ang=end;
});
html+='<text x="'+cx+'" y="'+(cy-6)+'" text-anchor="middle" font-size="24" font-weight="600" fill="#333">'+tot+'</text>';
html+='<text x="'+cx+'" y="'+(cy+16)+'" text-anchor="middle" font-size="11" fill="#999">总出车次数</text>';
var ly=70;
items.forEach(function(it){
html+='<rect x="300" y="'+ly+'" width="12" height="12" rx="3" fill="'+it.c+'"/><text x="320" y="'+(ly+11)+'" font-size="12" fill="#666">'+esc(it.k)+' '+it.v+'('+(it.v/tot*100).toFixed(0)+'%)</text>';
ly+=28;
});
svg.innerHTML=html;
}
/* 横向排行 */
function drawHBar(id,data,unit){
var svg=document.getElementById(id),max=Math.max.apply(null,data.map(function(d){return d.v;}))||1;
var bh=24,gap=8,top=8,labelW=76,W=560;
var h=top+data.length*(bh+gap)+4,html='';
data.forEach(function(d,i){
var y=top+i*(bh+gap),w=Math.max(2,(W-labelW-60)*d.v/max);
html+='<text x="'+(labelW-6)+'" y="'+(y+bh/2+4)+'" text-anchor="end" font-size="11" fill="#666">'+esc(d.k)+'</text>';
html+='<rect x="'+labelW+'" y="'+y+'" width="'+w+'" height="'+bh+'" rx="4" fill="'+unit+'"/>';
html+='<text x="'+(labelW+w+6)+'" y="'+(y+bh/2+4)+'" font-size="12" fill="#333" font-weight="600">'+d.v+'</text>';
});
svg.innerHTML=html;
svg.setAttribute('viewBox','0 0 '+W+' '+h);
}
/* 折线趋势:近12个月 */
function drawLine(data){
var svg=document.getElementById('chart4'),W=560,H=260,left=40,bottom=36,top=14,right=14;
var max=Math.max.apply(null,data.map(function(d){return d.v;}))||1;
var step=(W-left-right)/(data.length-1);
var pts=data.map(function(d,i){return [left+step*i,top+(H-bottom-top)*(1-d.v/max)];});
var html='';
for(var g=0;g<=4;g++){
var y=top+(H-bottom-top)*g/4;
html+='<line x1="'+left+'" y1="'+y+'" x2="'+(W-8)+'" y2="'+y+'" stroke="#F0F0F0"/>';
html+='<text x="'+(left-6)+'" y="'+(y+4)+'" text-anchor="end" font-size="10" fill="#999">'+Math.round(max*(4-g)/4)+'</text>';
}
html+='<polyline points="'+pts.map(function(p){return p[0].toFixed(1)+','+p[1].toFixed(1);}).join(' ')+'" fill="none" stroke="#52C41A" stroke-width="2"/>';
pts.forEach(function(p,i){html+='<circle cx="'+p[0].toFixed(1)+'" cy="'+p[1].toFixed(1)+'" r="3" fill="#fff" stroke="#52C41A" stroke-width="2"><title>'+esc(data[i].k)+':'+data[i].v+'次</title></circle>';});
data.forEach(function(d,i){if(i%2===0)html+='<text x="'+pts[i][0]+'" y="'+(H-bottom+18)+'" text-anchor="middle" font-size="9" fill="#666">'+esc(d.k)+'</text>';});
html+='<line x1="'+left+'" y1="'+(H-bottom)+'" x2="'+(W-8)+'" y2="'+(H-bottom)+'" stroke="#E8E8E8"/>';
svg.innerHTML=html;
}
function renderAll(){
var d=getDim(),list=filtered();
document.querySelectorAll('.dw').forEach(function(el){el.textContent=d;});
var ill=0,wound=0,day=0,emerg=0,pers=0;
list.forEach(function(c){ill+=cur(c.ill);wound+=cur(c.wound);day+=cur(c.day);emerg+=cur(c.emerg);pers+=cur(Math.round((c.ill+c.wound)*1.5));});
document.getElementById('kpiRow').innerHTML=
'<div class="kpi-card"><div class="kpi-card__label">总出车(本'+d+')</div><div class="kpi-card__value">'+(ill+wound+day+emerg)+'<small></small></div><div class="kpi-card__sub">'+list.length+' 辆车在编</div></div>'
+'<div class="kpi-card"><div class="kpi-card__label">急救出勤(本'+d+')</div><div class="kpi-card__value">'+(ill+wound)+'<small></small></div><div class="kpi-card__sub">病情 '+ill+' · 伤情 '+wound+'</div></div>'
+'<div class="kpi-card"><div class="kpi-card__label">巡诊+戒备(本'+d+')</div><div class="kpi-card__value">'+(day+emerg)+'<small></small></div><div class="kpi-card__sub">巡诊 '+day+' · 戒备 '+emerg+'</div></div>'
+'<div class="kpi-card"><div class="kpi-card__label">急救服务人次(本'+d+')</div><div class="kpi-card__value">'+pers+'<small>人次</small></div><div class="kpi-card__sub">随车救治员工数</div></div>';
drawGroup4(list.map(function(c){return {k:c.short,a:cur(c.ill),b:cur(c.wound),c:cur(c.day),d:cur(c.emerg)};}));
drawDonutN([{k:'病情急救',v:ill,c:C[0]},{k:'伤情急救',v:wound,c:C[1]},{k:'日常巡诊',v:day,c:C[2]},{k:'应急戒备',v:emerg,c:C[3]}]);
var rank=list.map(function(c){return {k:c.plate,v:c.illT+c.woundT+c.dayT+c.emergT};}).sort(function(a,b){return b.v-a.v;});
drawHBar('chart3',rank,'#1890FF');
/* 近12个月趋势(演示数据:2025-10 至 2026-09) */
var months=['25/10','25/11','25/12','26/01','26/02','26/03','26/04','26/05','26/06','26/07','26/08','26/09'];
var base=[68,72,65,58,52,74,81,86,79,90,96,0];
var f={'周':0.25,'月':1};
var tot=ill+wound+day+emerg;
drawLine(months.map(function(m,i){return {k:m,v:i===11?tot:Math.round(tot*base[i]/96*f['月']/1)};}));
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderAll();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,314 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>救护车统计 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.view-switch{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;margin-right:8px;}
.view-switch a{display:inline-flex;align-items:center;padding:0 14px;height:30px;font-size:var(--font-xs);color:var(--text-secondary);text-decoration:none;background:#fff;}
.view-switch a+a{border-left:1px solid var(--border);}
.view-switch a.active{background:var(--primary);color:#fff;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:2000px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;margin-bottom:16px;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal--wide{width:680px;max-height:80vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding-top:12px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:8px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
.grp-table thead tr.grp th{background:#F0F5FF;color:var(--text-primary);font-weight:600;border-right:1px solid var(--border);border-bottom:1px solid var(--border);}
.grp-table thead tr.grp th:last-child{background:#FAFAFA;}
.grp-table thead tr.leaf th:last-child{position:static;box-shadow:none;right:auto;z-index:auto;background:#FAFAFA;}
.num-cur{color:var(--primary);font-weight:600;}
.num-total{color:var(--text-secondary);}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html" class="active">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="config-medical-point.html" class="sidebar-item">医疗点配置</a>
<a href="config-partner-hospital.html" class="sidebar-item">合作医院</a>
<a href="config-medical-staff.html" class="sidebar-item">医护人员管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
医药配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-medicine.html" class="sidebar-item">药品信息</a>
<a href="config-medicine-stock.html" class="sidebar-item">实时库存</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
员工分配管理
</div>
<div class="sidebar-subgroup__body">
<a href="config-allocate.html" class="sidebar-item">分配管理</a>
<a href="config-allocate-stat.html" class="sidebar-item">分配统计</a>
</div>
</div>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>
救护车配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-ambulance.html" class="sidebar-item">救护车列表</a>
<a href="stat-ambulance-table.html" class="sidebar-item active">救护车统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位→医疗点级联 + 救护类型 + 周/月维度切换 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:180px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>救护类型</label>
<select class="filter-select" id="filterType" onchange="renderTable()" style="min-width:100px;">
<option value="">全部类型</option><option>急救型</option><option>转运型</option>
</select>
</div>
<div class="filter-item">
<label>时间维度</label>
<select class="filter-select" id="filterDim" onchange="renderTable()" style="min-width:90px;">
<option></option><option selected></option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="action-bar">
<!-- [交互] 表格/图表视图切换 -->
<div class="view-switch">
<a class="active" href="stat-ambulance-table.html">表格视图</a>
<a href="stat-ambulance-chart.html">图表视图</a>
</div>
<button class="btn btn-export" onclick="showToast('已按当前筛选条件导出 Excel,请在浏览器下载列表查看')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5v8M4 6.5L7 9.5l3-3M2 11.5h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>导出报表
</button>
<span class="action-bar__hint">出勤 = 救护车出车次数;急救人次 = 随车救治的员工人数。</span>
</div>
<!-- [交互] 分组表头:车辆信息 + 四类任务量 + 操作固定列 -->
<div class="table-card">
<div class="table-card__content">
<table class="grp-table">
<thead>
<tr class="grp">
<th colspan="5">车辆信息</th>
<th colspan="4">病情急救</th>
<th colspan="4">伤情急救</th>
<th colspan="2">日常巡诊</th>
<th colspan="2">应急戒备</th>
<th rowspan="2" style="width:80px;">操作</th>
</tr>
<tr class="leaf">
<th>所属单位</th><th style="min-width:150px;">所属医疗点</th><th>救护类型</th><th style="min-width:170px;">车型</th><th>车牌号</th>
<th><span class="dw"></span>出勤</th><th>累计出勤</th><th><span class="dw"></span>急救人次</th><th>累计急救人次</th>
<th><span class="dw"></span>出勤</th><th>累计出勤</th><th><span class="dw"></span>急救人次</th><th>累计急救人次</th>
<th><span class="dw"></span>出车</th><th>累计出车</th>
<th><span class="dw"></span>出车</th><th>累计出车</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 车辆明细弹窗 -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">救护车明细</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 车辆信息与「救护车配置」页勾稽(车牌/车型/类型/驾驶员/医院/GPS) */
var cars=[
{unit:'采油一厂',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',plate:'CQ·0712A',driver:'田震',tel:'139****5218',gps:'是',org:'CQ油田总医院',equip:'除颤仪、心电监护仪、氧气袋、创伤固定包',
ill:[8,96,12,150],wound:[5,64,6,80],day:[14,180],emerg:[6,72]},
{unit:'采油二厂',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',plate:'CQ·1366B',driver:'冯保山',tel:'135****6174',gps:'是',org:'CQ油田总医院',equip:'除颤仪、便携式彩超、呼吸机',
ill:[6,88,9,132],wound:[4,55,5,70],day:[12,165],emerg:[5,60]},
{unit:'第四采油厂',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',plate:'CQ·0208C',driver:'罗建平',tel:'150****8357',gps:'是',org:'第四采油厂总医院',equip:'担架系统、输液泵、供氧系统',
ill:[3,40,4,58],wound:[1,12,1,18],day:[20,260],emerg:[2,25]},
{unit:'采气一厂',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',plate:'CQ·1533D',driver:'邓成',tel:'133****9481',gps:'否',org:'CQ油田总医院',equip:'除颤监护仪、氧气瓶、急救药箱',
ill:[5,70,7,105],wound:[4,52,5,74],day:[10,140],emerg:[4,48]},
{unit:'采油一厂',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',plate:'CQ·0917E',driver:'苏文斌',tel:'155****6703',gps:'是',org:'吴起油田医院',equip:'自动上车担架、颈胸腰固定支具',
ill:[2,28,2,36],wound:[0,8,0,10],day:[9,120],emerg:[1,12]},
{unit:'采气一厂',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',plate:'CQ·2201F',driver:'曹雪芹',tel:'186****2940',gps:'是',org:'庆阳市人民医院',equip:'除颤仪、心电监护仪、简易呼吸器',
ill:[4,52,6,78],wound:[3,41,4,55],day:[8,110],emerg:[5,40]}
];
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var dimFactor={'周':0.25,'月':1};
function getDim(){return document.getElementById('filterDim').value;}
function cur(v){return Math.max(0,Math.round(v*dimFactor[getDim()]));}
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var all=[].concat.apply([],Object.keys(pointMap).map(function(k){return pointMap[k];}));
document.getElementById('filterPoint').innerHTML='<option value="">全部医疗点</option>'+(u?pointMap[u]:all).map(function(p){return '<option>'+p+'</option>';}).join('');
}
function quad(arr){return '<td class="num-cur">'+cur(arr[0])+'</td><td class="num-total">'+arr[1]+'</td><td class="num-cur">'+cur(arr[2])+'</td><td class="num-total">'+arr[3]+'</td>';}
function pair(arr){return '<td class="num-cur">'+cur(arr[0])+'</td><td class="num-total">'+arr[1]+'</td>';}
function renderTable(){
var u=document.getElementById('filterUnit').value,p=document.getElementById('filterPoint').value,t=document.getElementById('filterType').value;
var list=cars.filter(function(c){return (!u||c.unit===u)&&(!p||c.point===p)&&(!t||c.type===t);});
document.getElementById('tbody').innerHTML=list.map(function(c){
var i=cars.indexOf(c);
return '<tr><td>'+c.unit+'</td><td style="text-align:left;">'+c.point+'</td>'
+'<td><span class="badge '+(c.type==='急救型'?'badge--info':'badge--success')+'">'+c.type+'</span></td>'
+'<td style="text-align:left;">'+c.model+'</td><td style="font-weight:600;">'+c.plate+'</td>'
+quad(c.ill)+quad(c.wound)+pair(c.day)+pair(c.emerg)
+'<td><button class="btn btn-link" onclick="openDetail('+i+')">明细</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="18">没有符合条件的车辆数据</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 辆车 · 时间维度:本'+getDim();
document.querySelectorAll('.dw').forEach(function(el){el.textContent=getDim();});
}
function applyFilter(){renderTable();showToast('查询完成');}
function resetFilter(){document.getElementById('filterUnit').value='';document.getElementById('filterType').value='';document.getElementById('filterDim').value='月';onFilterUnit();renderTable();}
function di(k,v){return '<div class="detail-item"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var c=cars[i],d=getDim();
document.getElementById('detailTitle').textContent='救护车明细 · '+c.plate;
document.getElementById('detailBody').innerHTML=
'<div class="form-section">车辆信息</div><div class="detail-grid">'
+di('所属单位',c.unit)+di('所属医疗点',c.point)+di('救护类型',c.type)+di('车型',c.model)
+di('车牌号',c.plate)+di('驾驶员',c.driver+'('+c.tel+')')+di('GPS定位',c.gps)+di('挂靠医院',c.org)
+'<div class="detail-item" style="grid-column:1/-1;"><span class="dk">随车设备</span><span class="dv">'+c.equip+'</span></div>'
+'</div>'
+'<div class="form-section">任务量(本'+d+' / 累计)</div><div class="detail-grid">'
+di('病情急救出勤',cur(c.ill[0])+' 次 / '+c.ill[1]+' 次')+di('病情急救人次',cur(c.ill[2])+' / '+c.ill[3])
+di('伤情急救出勤',cur(c.wound[0])+' 次 / '+c.wound[1]+' 次')+di('伤情急救人次',cur(c.wound[2])+' / '+c.wound[3])
+di('日常巡诊出车',cur(c.day[0])+' 次 / '+c.day[1]+' 次')+di('应急戒备出车',cur(c.emerg[0])+' 次 / '+c.emerg[1]+' 次')
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
onFilterUnit();renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
</script>
</body>
</html>
@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>医疗点统计-图表 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:16px;}
.kpi-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;}
.kpi-card__label{font-size:var(--font-xs);color:var(--text-secondary);}
.kpi-card__value{font-size:26px;font-weight:600;color:var(--text-primary);margin-top:6px;}
.kpi-card__value small{font-size:var(--font-sm);font-weight:400;color:var(--text-placeholder);margin-left:4px;}
.kpi-card__sub{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:6px;}
.chart-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px;}
.chart-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;}
.chart-card__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);display:flex;align-items:center;justify-content:space-between;}
.chart-card__title .lg{display:flex;gap:14px;font-size:var(--font-xs);font-weight:400;color:var(--text-secondary);}
.chart-card__title .lg i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:4px;vertical-align:-1px;}
.chart-card svg{width:100%;height:auto;display:block;margin-top:10px;}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html" class="active">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="stat-clinic-table.html" class="sidebar-item">医疗点统计-表格</a>
<a href="stat-clinic-chart.html" class="sidebar-item active">医疗点统计-图表</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位级联 + 时间维度,图表即时重绘 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>时间维度</label>
<select class="filter-select" id="filterDim" onchange="renderAll()" style="min-width:100px;">
<option></option><option selected></option><option></option>
</select>
</div>
<button class="btn btn-primary" onclick="showToast('图表已按当前条件刷新')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7a5 5 0 1 1 1.5 3.5M2 12V9h3" stroke="#fff" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>刷新
</button>
<span style="margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);">图表数据与「医疗点统计-表格」同源。</span>
</div>
<!-- [交互] KPI 汇总卡 -->
<div class="kpi-row" id="kpiRow"></div>
<!-- [交互] 图表网格:横向排行 / 双柱对比 / 分组柱 / 环形占比 -->
<div class="chart-grid">
<div class="chart-card">
<div class="chart-card__title">就诊人次排行(本<span class="dw"></span>)<span class="lg"><i style="background:#1890FF;"></i>人次</span></div>
<svg id="chart1" viewBox="0 0 560 260"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">慢病管理:应服务 vs 已服务(本<span class="dw"></span>)
<span class="lg"><i style="background:#91D5FF;"></i>应服务<i style="background:#1890FF;"></i>已服务</span></div>
<svg id="chart2" viewBox="0 0 560 260"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">培训宣教覆盖人次(本<span class="dw"></span>)
<span class="lg"><i style="background:#FAAD14;"></i>急救培训<i style="background:#52C41A;"></i>健康宣教</span></div>
<svg id="chart3" viewBox="0 0 560 260"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">报告解读构成(本<span class="dw"></span>)<span class="lg"><i style="background:#1890FF;"></i>一对一<i style="background:#B37FEB;"></i>群体</span></div>
<svg id="chart4" viewBox="0 0 560 260"></svg>
</div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
/* 与「医疗点统计-表格」同源的数据(本月基准值,周/年维度按系数折算) */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var rows=[
{unit:'采油一厂',point:'樊学',short:'樊学',visit:186,chShould:18,chDone:15,trainP:45,eduP:160,one2:12,grp:3},
{unit:'采油二厂',point:'姬圪台',short:'姬圪台',visit:142,chShould:22,chDone:20,trainP:38,eduP:95,one2:9,grp:2},
{unit:'第四采油厂',point:'西峰',short:'西峰',visit:268,chShould:34,chDone:31,trainP:96,eduP:240,one2:21,grp:5},
{unit:'采气一厂',point:'靖边',short:'靖边',visit:96,chShould:14,chDone:12,trainP:42,eduP:88,one2:7,grp:1},
{unit:'采油二厂',point:'桐园',short:'桐园',visit:88,chShould:12,chDone:10,trainP:0,eduP:70,one2:6,grp:2},
{unit:'采油一厂',point:'安塞',short:'安塞',visit:32,chShould:5,chDone:4,trainP:0,eduP:0,one2:3,grp:0},
{unit:'采油一厂',point:'姬塬',short:'姬塬',visit:118,chShould:7,chDone:6,trainP:60,eduP:90,one2:8,grp:1},
{unit:'采气一厂',point:'乌审旗',short:'乌审旗',visit:104,chShould:11,chDone:9,trainP:36,eduP:75,one2:7,grp:2}
];
var dimFactor={'周':0.25,'月':1,'年':11.5};
var CUR='#1890FF',LIGHT='#91D5FF',WARN='#FAAD14',OK='#52C41A',PURPLE='#B37FEB';
function cur(v){var f=dimFactor[getDim()];return Math.max(0,Math.round(v*f));}
function getDim(){return document.getElementById('filterDim').value;}
function filtered(){var u=document.getElementById('filterUnit').value;return rows.filter(function(r){return !u||r.unit===u;});}
function onFilterUnit(){renderAll();}
function esc(s){return String(s);}
/* 横向条形图:就诊排行 */
function drawHBar(id,data,unit){
var svg=document.getElementById(id),max=Math.max.apply(null,data.map(function(d){return d.v;}))||1;
var bh=24,gap=8,top=10,labelW=64,W=560;
var h=top+data.length*(bh+gap)+6;
var html='';
data.forEach(function(d,i){
var y=top+i*(bh+gap),w=Math.max(2,(W-labelW-56)*d.v/max);
html+='<text x="'+(labelW-6)+'" y="'+(y+bh/2+4)+'" text-anchor="end" font-size="12" fill="#666">'+esc(d.k)+'</text>';
html+='<rect x="'+labelW+'" y="'+y+'" width="'+w+'" height="'+bh+'" rx="4" fill="'+unit+'"/>';
html+='<text x="'+(labelW+w+6)+'" y="'+(y+bh/2+4)+'" font-size="12" fill="#333" font-weight="600">'+d.v+'</text>';
});
svg.innerHTML=html;
svg.setAttribute('viewBox','0 0 '+W+' '+h);
}
/* 分组双柱图 */
function drawGroupBar(id,data,u1,u2,legend){
var svg=document.getElementById(id),W=560,H=260,left=40,bottom=34,top=14;
var max=Math.max.apply(null,[].concat.apply([],data.map(function(d){return [d.a,d.b];})))||1;
var n=data.length,step=(W-left-16)/n,bw=Math.min(22,step/2-8);
var html='';
for(var g=0;g<=4;g++){
var y=top+(H-bottom-top)*g/4,vv=Math.round(max*(4-g)/4);
html+='<line x1="'+left+'" y1="'+y+'" x2="'+(W-8)+'" y2="'+y+'" stroke="#F0F0F0"/>';
html+='<text x="'+(left-6)+'" y="'+(y+4)+'" text-anchor="end" font-size="10" fill="#999">'+vv+'</text>';
}
data.forEach(function(d,i){
var cx=left+step*i+step/2;
var ha=Math.max(1,(H-bottom-top)*d.a/max),hb=Math.max(1,(H-bottom-top)*d.b/max);
html+='<rect x="'+(cx-bw-2)+'" y="'+(H-bottom-ha)+'" width="'+bw+'" height="'+ha+'" rx="3" fill="'+u1+'"><title>'+esc(d.k)+' '+legend[0]+':'+d.a+'</title></rect>';
html+='<rect x="'+(cx+2)+'" y="'+(H-bottom-hb)+'" width="'+bw+'" height="'+hb+'" rx="3" fill="'+u2+'"><title>'+esc(d.k)+' '+legend[1]+':'+d.b+'</title></rect>';
html+='<text x="'+cx+'" y="'+(H-bottom+16)+'" text-anchor="middle" font-size="10" fill="#666">'+esc(d.k)+'</text>';
});
html+='<line x1="'+left+'" y1="'+(H-bottom)+'" x2="'+(W-8)+'" y2="'+(H-bottom)+'" stroke="#E8E8E8"/>';
svg.innerHTML=html;
}
/* 环形图 */
function drawDonut(id,a,b){
var svg=document.getElementById(id),cx=150,cy=130,R=88,r=54;
var tot=(a+b)||1,ang=a/tot*Math.PI*2;
function pt(rad,an){return (cx+rad*Math.sin(an)).toFixed(2)+' '+(cy-rad*Math.cos(an)).toFixed(2);}
var large=ang>Math.PI?1:0;
var html=''
+'<path d="M'+pt(R,0)+' A'+R+' '+R+' 0 '+large+' 1 '+pt(R,ang)+' L'+pt(r,ang)+' A'+r+' '+r+' 0 '+large+' 0 '+pt(r,0)+' Z" fill="'+CUR+'"/>'
+'<path d="M'+pt(R,ang)+' A'+R+' '+R+' 0 '+(1-large)+' 1 '+pt(R,Math.PI*2)+' L'+pt(r,Math.PI*2)+' A'+r+' '+r+' 0 '+(1-large)+' 0 '+pt(r,ang)+' Z" fill="'+PURPLE+'"/>'
+'<text x="'+cx+'" y="'+(cy-6)+'" text-anchor="middle" font-size="24" font-weight="600" fill="#333">'+(a+b)+'</text>'
+'<text x="'+cx+'" y="'+(cy+16)+'" text-anchor="middle" font-size="11" fill="#999">解读总人次</text>';
var ly=cy-20;
html+='<rect x="330" y="'+ly+'" width="12" height="12" rx="3" fill="'+CUR+'"/><text x="350" y="'+(ly+11)+'" font-size="12" fill="#666">一对一 '+a+'('+(a/tot*100).toFixed(0)+'%)</text>';
html+='<rect x="330" y="'+(ly+26)+'" width="12" height="12" rx="3" fill="'+PURPLE+'"/><text x="350" y="'+(ly+37)+'" font-size="12" fill="#666">群体 '+b+'('+(b/tot*100).toFixed(0)+'%)</text>';
svg.innerHTML=html;
}
function renderAll(){
var d=getDim(),list=filtered();
document.querySelectorAll('.dw').forEach(function(el){el.textContent=d;});
var vSum=0,chS=0,chD=0,tr=0,ed=0,one2=0,grp=0;
list.forEach(function(r){vSum+=cur(r.visit);chS+=cur(r.chShould);chD+=cur(r.chDone);tr+=cur(r.trainP);ed+=cur(r.eduP);one2+=cur(r.one2);grp+=cur(r.grp);});
document.getElementById('kpiRow').innerHTML=
'<div class="kpi-card"><div class="kpi-card__label">就诊人次(本'+d+')</div><div class="kpi-card__value">'+vSum+'<small>人次</small></div><div class="kpi-card__sub">覆盖 '+list.length+' 个医疗点</div></div>'
+'<div class="kpi-card"><div class="kpi-card__label">慢病服务完成率(本'+d+')</div><div class="kpi-card__value">'+(chS?Math.round(chD/chS*100):0)+'<small>%</small></div><div class="kpi-card__sub">应服务 '+chS+' 人 · 已服务 '+chD+' 人</div></div>'
+'<div class="kpi-card"><div class="kpi-card__label">培训宣教人次(本'+d+')</div><div class="kpi-card__value">'+(tr+ed)+'<small>人次</small></div><div class="kpi-card__sub">培训 '+tr+' · 宣教 '+ed+'</div></div>'
+'<div class="kpi-card"><div class="kpi-card__label">报告解读(本'+d+')</div><div class="kpi-card__value">'+(one2+grp)+'<small>人次</small></div><div class="kpi-card__sub">一对一 '+one2+' · 群体 '+grp+'</div></div>';
var vRank=list.map(function(r){return {k:r.short,v:cur(r.visit)};}).sort(function(a,b){return b.v-a.v;});
drawHBar('chart1',vRank,CUR);
drawGroupBar('chart2',list.map(function(r){return {k:r.short,a:cur(r.chShould),b:cur(r.chDone)};}),LIGHT,CUR,['应服务','已服务']);
drawGroupBar('chart3',list.map(function(r){return {k:r.short,a:cur(r.trainP),b:cur(r.eduP)};}),WARN,OK,['急救培训','健康宣教']);
drawDonut('chart4',one2,grp);
}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
renderAll();
</script>
</body>
</html>
@@ -0,0 +1,287 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>医疗点统计-表格 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#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;
--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.10);
}
.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:200px;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);cursor:pointer;display:flex;align-items:center;gap:4px;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);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;padding-bottom:24px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:32px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);color:var(--text-primary);outline:none;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:32px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-xs);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);}
.btn-export{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-export:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.btn-link--danger{color:var(--danger);}
.btn-sm{height:26px;padding:0 10px;}
.action-bar{display:flex;align-items:center;gap:8px;padding:16px 24px;background:#fff;margin-top:16px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.action-bar__hint{margin-left:auto;font-size:var(--font-xs);color:var(--text-placeholder);}
.table-card{background:#fff;border-radius:0 0 var(--radius-md) var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:2600px;}
thead th{background:#FAFAFA;padding:8px 7px;text-align:center;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:10px 7px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
thead tr th:last-child,tbody td:last-child{position:sticky;right:0;z-index:5;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);}
thead tr th:last-child{z-index:8;background:#FAFAFA;}
tbody tr:hover td:last-child{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.badge{display:inline-block;padding:2px 10px;border-radius:10px;font-size:var(--font-xs);}
.badge--success{background:#F6FFED;color:#389E0D;border:1px solid #B7EB8F;}
.badge--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.badge--info{background:#E6F7FF;color:#096DD9;border:1px solid #91D5FF;}
.badge--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
.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);}
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 24px;border-radius:var(--radius-sm);font-size:var(--font-sm);z-index:9999;opacity:0;transition:opacity .2s;pointer-events:none;}
.toast.show{opacity:1;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;margin-bottom:16px;}
.detail-item{display:flex;border-bottom:1px solid var(--border);}
.detail-item--full{grid-column:1/-1;}
.detail-item .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-xs);color:var(--text-secondary);border-right:1px solid var(--border);}
.detail-item .dv{flex:1;padding:9px 12px;font-size:var(--font-sm);color:var(--text-primary);white-space:normal;line-height:1.5;}
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:950;align-items:center;justify-content:center;}
.modal-mask.open{display:flex;}
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--wide{width:680px;max-height:80vh;display:flex;flex-direction:column;}
.modal__header{display:flex;align-items:center;justify-content:space-between;font-size:var(--font-base);font-weight:600;padding-bottom:12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.modal__close{cursor:pointer;color:var(--text-placeholder);font-size:20px;line-height:1;border:none;background:none;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding-top:12px;border-top:1px solid var(--border);flex-shrink:0;}
.form-section{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:8px 0 12px;padding-left:8px;border-left:3px solid var(--primary);}
/* 分组表头专用:首行为组行,次行为叶子列行;次行末列取消误触发的 sticky */
.grp-table thead tr.grp th{background:#F0F5FF;color:var(--text-primary);font-weight:600;border-right:1px solid var(--border);border-bottom:1px solid var(--border);}
.grp-table thead tr.grp th:last-child{background:#FAFAFA;}
.grp-table thead tr.leaf th:last-child{position:static;box-shadow:none;right:auto;z-index:auto;background:#FAFAFA;}
.num-cur{color:var(--primary);font-weight:600;}
.num-total{color:var(--text-secondary);}
</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="M14 6.5l7 3.5v4.5c0 4.2-2.9 6.8-7 7.5-4.1-.7-7-3.3-7-7.5V10l7-3.5z" stroke="#fff" stroke-width="1.8" fill="none"/><path d="M11 13.5h6M14 10.5v6" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg> 一线医疗</div>
<nav class="top-bar__nav">
<a href="archive-visit-data.html">档案</a>
<a href="config-medical-point.html">基础配置</a>
<a href="daily-treatment-staff.html">日常业务</a>
<a href="bed-building.html">床头呼叫</a>
<a href="stat-clinic-table.html" class="active">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-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="stat-clinic-table.html" class="sidebar-item active">医疗点统计-表格</a>
<a href="stat-clinic-chart.html" class="sidebar-item">医疗点统计-图表</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏:单位→医疗点级联;时间维度切换实时刷新「本期」口径 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>医疗点</label>
<select class="filter-select" id="filterPoint" style="min-width:180px;">
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item">
<label>时间维度</label>
<select class="filter-select" id="filterDim" onchange="renderTable()" style="min-width:100px;">
<option></option><option selected></option><option></option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('已按当前筛选条件导出 Excel,请在浏览器下载列表查看')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5v8M4 6.5L7 9.5l3-3M2 11.5h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>导出报表
</button>
<span class="action-bar__hint">本期 = 当前所选时间维度区间;累计 = 医疗点建立至今。</span>
</div>
<!-- [交互] 分组表头表格:首行合并组,次行叶子列;操作列固定右侧 -->
<div class="table-card">
<div class="table-card__content">
<table class="grp-table">
<thead>
<tr class="grp">
<th colspan="7">基础信息</th>
<th colspan="2">就诊人次</th>
<th colspan="4">慢病管理</th>
<th colspan="4">急救培训</th>
<th colspan="4">健康宣教</th>
<th colspan="2">报告解读</th>
<th rowspan="2" style="width:90px;">操作</th>
</tr>
<tr class="leaf">
<th>所属单位</th><th style="min-width:150px;">医疗点名称</th><th>建立时间</th><th style="min-width:130px;">合作医院</th><th>医生</th><th>护士</th><th>已分配员工</th>
<th><span class="dimWord"></span></th><th>累计</th>
<th>入组人数</th><th><span class="dimWord"></span>应服务</th><th><span class="dimWord"></span>已服务</th><th>累计服务人次</th>
<th><span class="dimWord"></span>场次</th><th>累计场次</th><th><span class="dimWord"></span>人次</th><th>累计人次</th>
<th><span class="dimWord"></span>场次</th><th>累计场次</th><th><span class="dimWord"></span>人次</th><th>累计人次</th>
<th>一对一</th><th>群体</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</main>
<!-- [交互] 医疗点明细弹窗:点击「明细」打开,分组展示各项指标 -->
<div class="modal-mask" id="detailMask">
<div class="modal modal--wide">
<div class="modal__header"><span id="detailTitle">医疗点明细</span><button class="modal__close" onclick="closeDetail()">&times;</button></div>
<div class="modal__body" id="detailBody"></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDetail()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 数据与「医疗点配置」页勾稽:单位/医疗点/建立时间/合作医院/医生/护士/辐射人数 */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
/* cur 为「本月」口径基准值,周/年维度按比例折算;total 为建立至今累计 */
var rows=[
{unit:'采油一厂',point:'樊学作业区医疗点',found:'2021-06-18',hospital:'环县油田医院',doc:3,nurse:4,rad:2860,visit:[186,4120],chronic:[23,18,15,326],train:[1,9,45,620],edu:[2,14,160,1180],interp:[12,3]},
{unit:'采油二厂',point:'姬圪台作业区医疗点',found:'2020-09-02',hospital:'CQ油田总医院',doc:2,nurse:3,rad:2140,visit:[142,3310],chronic:[31,22,20,415],train:[1,7,38,480],edu:[1,11,95,860],interp:[9,2]},
{unit:'第四采油厂',point:'西峰基地医疗点',found:'2019-04-26',hospital:'第四采油厂总医院',doc:5,nurse:6,rad:4350,visit:[268,6420],chronic:[45,34,31,728],train:[2,12,96,1350],edu:[3,18,240,1760],interp:[21,5]},
{unit:'采气一厂',point:'靖边气田医疗点',found:'2022-03-15',hospital:'CQ油田总医院',doc:2,nurse:2,rad:1680,visit:[96,2240],chronic:[18,14,12,196],train:[1,6,42,350],edu:[1,9,88,640],interp:[7,1]},
{unit:'采油二厂',point:'桐园作业区医疗点',found:'2021-11-08',hospital:'环县油田医院',doc:2,nurse:2,rad:1420,visit:[88,1960],chronic:[15,12,10,158],train:[0,5,0,260],edu:[1,8,70,520],interp:[6,2]},
{unit:'采油一厂',point:'安塞采油队长住点医疗室',found:'2023-05-20',hospital:'吴起油田医院',doc:1,nurse:2,rad:960,visit:[32,780],chronic:[6,5,4,74],train:[0,3,0,120],edu:[0,4,0,180],interp:[3,0]},
{unit:'采油一厂',point:'姬塬作业区医疗点',found:'2024-02-27',hospital:'第四采油厂总医院',doc:2,nurse:3,rad:1730,visit:[118,860],chronic:[9,7,6,63],train:[1,2,60,140],edu:[1,3,90,210],interp:[8,1]},
{unit:'采气一厂',point:'乌审旗采气区医疗点',found:'2020-12-11',hospital:'CQ油田总医院',doc:2,nurse:3,rad:1550,visit:[104,2480],chronic:[14,11,9,210],train:[1,5,36,280],edu:[1,8,75,590],interp:[7,2]}
];
var dimFactor={'周':0.25,'月':1,'年':11.5};
function getDim(){return document.getElementById('filterDim').value;}
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var sel=document.getElementById('filterPoint');
sel.innerHTML='<option value="">全部医疗点</option>'+(u?pointMap[u]:[].concat.apply([],Object.keys(pointMap).map(function(k){return pointMap[k];}))).map(function(p){return '<option>'+p+'</option>';}).join('');
}
function cur(v){var f=dimFactor[getDim()];return Math.max(0,Math.round(v*f));}
function cellPair(a,b){return '<td class="num-cur">'+a+'</td><td class="num-total">'+b+'</td>';}
function renderTable(){
var u=document.getElementById('filterUnit').value,p=document.getElementById('filterPoint').value;
var list=rows.filter(function(r){return (!u||r.unit===u)&&(!p||r.point===p);});
var html=list.map(function(r){
var i=rows.indexOf(r);
return '<tr>'
+'<td>'+r.unit+'</td><td style="text-align:left;">'+r.point+'</td><td>'+r.found+'</td><td>'+r.hospital+'</td>'
+'<td>'+r.doc+'</td><td>'+r.nurse+'</td><td>'+r.rad+'</td>'
+cellPair(cur(r.visit[0]),r.visit[1])
+'<td>'+r.chronic[0]+'</td>'
+'<td class="num-cur">'+cur(r.chronic[1])+'</td>'
+'<td class="num-cur">'+cur(r.chronic[2])+'</td>'
+'<td class="num-total">'+r.chronic[3]+'</td>'
+cellPair(cur(r.train[0]),r.train[1])+cellPair(cur(r.train[2]),r.train[3])
+cellPair(cur(r.edu[0]),r.edu[1])+cellPair(cur(r.edu[2]),r.edu[3])
+'<td class="num-cur">'+cur(r.interp[0])+'</td><td class="num-cur">'+cur(r.interp[1])+'</td>'
+'<td><button class="btn btn-link" onclick="openDetail('+i+')">明细</button></td>'
+'</tr>';
}).join('');
document.getElementById('tbody').innerHTML=html||'<tr><td class="td-empty" colspan="24">没有符合条件的统计数据</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 个医疗点 · 时间维度:本'+getDim();
var w=getDim();
var words=document.querySelectorAll('.dimWord');
for(var i=0;i<words.length;i++){words[i].textContent=w;}
}
function applyFilter(){renderTable();showToast('查询完成');}
function resetFilter(){
document.getElementById('filterUnit').value='';
document.getElementById('filterDim').value='月';
onFilterUnit();renderTable();
}
function di(k,v){return '<div class="detail-item"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function openDetail(i){
var r=rows[i],d=getDim();
document.getElementById('detailTitle').textContent='医疗点明细 · '+r.point+'(本'+d+')';
document.getElementById('detailBody').innerHTML=
'<div class="form-section">基础信息</div><div class="detail-grid">'
+di('所属单位',r.unit)+di('医疗点名称',r.point)+di('建立时间',r.found)+di('合作医院',r.hospital)
+di('医生人数',r.doc+' 人')+di('护士人数',r.nurse+' 人')+di('已分配员工',r.rad+' 人')+di('当前维度','本'+d)
+'</div>'
+'<div class="form-section">就诊与慢病</div><div class="detail-grid">'
+di('就诊本'+d,cur(r.visit[0])+' 人次')+di('就诊累计',r.visit[1]+' 人次')
+di('慢病入组',r.chronic[0]+' 人')+di('累计服务人次',r.chronic[3]+' 人次')
+di('本'+d+'应服务',cur(r.chronic[1])+' 人')+di('本'+d+'已服务',cur(r.chronic[2])+' 人')
+di('本'+d+'服务率',(r.chronic[1]?Math.round(r.chronic[2]/r.chronic[1]*100):0)+'%',r.chronic[2]>=r.chronic[1]?'<span class="badge badge--success">达标</span>':'<span class="badge badge--warning">偏低</span>')
+'</div>'
+'<div class="form-section">培训宣教与解读</div><div class="detail-grid">'
+di('急救培训本'+d,cur(r.train[0])+' 场 / '+cur(r.train[2])+' 人次')+di('急救培训累计',r.train[1]+' 场 / '+r.train[3]+' 人次')
+di('健康宣教本'+d,cur(r.edu[0])+' 场 / '+cur(r.edu[2])+' 人次')+di('健康宣教累计',r.edu[1]+' 场 / '+r.edu[3]+' 人次')
+di('一对一解读本'+d,cur(r.interp[0])+' 人')+di('群体解读本'+d,cur(r.interp[1])+' 场')
+'</div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
onFilterUnit();renderTable();
</script>
</body>
</html>