feat: 一线医疗管理端二线迭代(45页改版+3页新增:侧栏二级分组改造——基础配置(药配/分配/救护车)、日常业务(日常诊疗/用药记录/任务管理)、床头呼叫(设备管理/宿舍管理)收敛为可展开收起子组,默认收起带交互注释;床头呼叫新增站点管理页(单位部门筛选+台账列表+双列表单弹窗)并更名楼栋/宿舍/床位信息、分配管理、告警记录、汇总统计,顶栏入口调整为设备列表;数据统计重排——救护车统计两页由基础配置移入,表格/图表双页收拢为单一入口页内图表/报表view-switch互切,新增救助物品统计图表页(三环形占比图)与报表页(小药棒/药品箱/急救包分组表头本期累计明细)共享导入工具条;全站视觉尺寸升级font-xs→font-sm、控件高32→36、表格padding与min-width上调、表单双列栅格与下拉箭头/上传框通用样式;首页SITE_MAP同步新增/移域/更名条目并移除done状态回退设计中,CHANGELOG记录)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-09-08 13:52:07 +08:00
co-authored by Claude Opus 4.7
parent b4f3d5cb8c
commit 47ab38e28d
50 changed files with 4615 additions and 2457 deletions
+13 -10
View File
@@ -586,7 +586,7 @@ const SITE_MAP = [
{ file:'professional.html', label:'正在应急' },
{ file:'chat-message.html', label:'对话消息' }
]},
{ name:'一线医疗', dir:'web-admin/frontline-medical', entry:'archive-visit-data.html', done:true, pages:[
{ name:'一线医疗', dir:'web-admin/frontline-medical', entry:'archive-visit-data.html', pages:[
{ file:'archive-visit-data.html', label:'档案 · 个人就医数据' },
{ file:'archive-unit-report.html', label:'档案 · 单位报表数据' },
{ file:'archive-algo-model.html', label:'档案 · 算法模型' },
@@ -598,8 +598,6 @@ const SITE_MAP = [
{ 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:'日常业务 · 入库记录' },
@@ -612,17 +610,22 @@ const SITE_MAP = [
{ 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-station.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-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:'bed-timeout-alarm.html', label:'床头呼叫 · 告警记录' },
{ file:'bed-stat.html', label:'床头呼叫 · 汇总统计' },
{ file:'stat-clinic-chart.html', label:'数据统计 · 医疗点统计-图表' },
{ file:'stat-clinic-table.html', label:'数据统计 · 医疗点统计-报表' },
{ file:'stat-ambulance-chart.html', label:'数据统计 · 救护车统计-图表' },
{ file:'stat-ambulance-table.html', label:'数据统计 · 救护车统计-报表' },
{ file:'stat-rescue-chart.html', label:'数据统计 · 救助物品统计-图表' },
{ file:'stat-rescue-table.html', label:'数据统计 · 救助物品统计-报表' },
{ file:'hut-banner.html', label:'健康小屋 · 轮播图配置' },
{ file:'hut-room.html', label:'健康小屋 · 健康室管理' },
{ file:'hut-appointment.html', label:'健康小屋 · 预约管理' },
+54 -146
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,28 +33,24 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
.stat-card__value{font-size:var(--font-xxl);font-weight:600;color:var(--text-primary);}
.stat-card__label{font-size:var(--font-sm);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;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1500px;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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;}
@@ -64,38 +60,21 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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>
@@ -106,8 +85,8 @@ textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html" class="active">异常事件</a>
</nav>
@@ -133,7 +112,13 @@ textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1
<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>
<option value="">全部类型</option><option>缺货预警</option><option>任务逾期</option><option>监测告警</option>
</select>
</div>
<div class="filter-item">
<label>医疗点</label>
<select class="filter-select" id="filterPoint" onchange="renderTable()" style="min-width:170px;">
<option value="">全部医疗点</option><option>乌审旗采气区医疗点</option><option>西峰基地医疗点</option><option>靖边气田医疗点</option><option>樊学作业区医疗点</option><option>安塞采油队长住点医疗室</option><option>姬圪台作业区医疗点</option><option>桐园作业区医疗点</option><option>姬塬作业区医疗点</option>
</select>
</div>
<div class="filter-item">
@@ -142,22 +127,12 @@ textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1
<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('查询完成')">
<button class="btn btn-primary" onclick="renderTable()">
<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>
@@ -167,8 +142,8 @@ textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1
<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>
<th style="min-width:130px;">异常编号</th><th style="min-width:150px;">医疗点</th><th>异常类型</th><th style="min-width:160px;">预警对象</th><th style="min-width:280px;">预警内容</th>
<th>严重度</th><th>触发时间</th><th>推送方式</th><th style="width:80px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
@@ -178,36 +153,7 @@ textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1
</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>
@@ -216,81 +162,50 @@ textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1
</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:'加强饮食干预与用药,随访已回落,关闭。'}]}
{no:'YC-20260907-01',point:'桐园作业区医疗点',type:'缺货预警',obj:'藿香正气水',content:'现存 0 盒(安全库存 20),急救类药品断货',level:'高',time:'2026-09-07 06:30',push:'实时推送'},
{no:'YC-20260907-02',point:'姬圪台作业区医疗点',type:'缺货预警',obj:'蒙脱石散',content:'现存 15 盒(安全库存 25),按近30天速率可支撑约 8 天',level:'中',time:'2026-09-07 08:00',push:'每日定时'},
{no:'YC-20260907-03',point:'西峰基地医疗点',type:'任务逾期',obj:'随访任务 FW-20260825-011',content:'慢病随访计划要求 2026-09-05 前完成,已逾期 2 天未执行',level:'中',time:'2026-09-07 08:00',push:'每日定时'},
{no:'YC-20260906-04',point:'靖边气田医疗点',type:'监测告警',obj:'刘玉梅(CQ036441)',content:'合作医院标本培养回报 MRSA(耐甲氧西林金黄色葡萄球菌)阳性',level:'高',time:'2026-09-06 11:47',push:'实时推送'},
{no:'YC-20260905-05',point:'桐园作业区医疗点',type:'监测告警',obj:'赵万福(CQ033096)',content:'随访录入血压 182/110 mmHg,达高血压危象风险区间',level:'高',time:'2026-09-05 09:18',push:'实时推送'},
{no:'YC-20260905-06',point:'安塞采油队长住点医疗室',type:'任务逾期',obj:'库房盘点任务 PD-20260901-003',content:'9 月例行盘点要求 2026-09-04 前完成,逾期 1 天未提交盘点单',level:'低',time:'2026-09-05 08:00',push:'每日定时'},
{no:'YC-20260904-07',point:'姬塬作业区医疗点',type:'缺货预警',obj:'碘伏棉签',content:'现存 18 袋(安全库存 20),医用耗材接近缺口',level:'',time:'2026-09-04 08:00',push:'每日定时'},
{no:'YC-20260904-08',point:'乌审旗采气区医疗点',type:'任务逾期',obj:'设备校准任务 JB-20260820-002',content:'除颤仪年度校准已超计划日期 15 天,未完成校准登记',level:'',time:'2026-09-04 08:00',push:'每日定时'},
{no:'YC-20260903-09',point:'西峰基地医疗点',type:'监测告警',obj:'高建军(CQ010564)',content:'痰培养回报 ESBL(产超广谱β内酰胺酶)大肠埃希菌',level:'中',time:'2026-09-03 08:00',push:'每日定时'},
{no:'YC-20260902-10',point:'樊学作业区医疗点',type:'监测告警',obj:'石维国(CQ021578)',content:'随访录入空腹血糖 15.2 mmol/L,显著高于控制目标',level:'中',time:'2026-09-02 08:00',push:'每日定时'}
];
var typeBadge={'缺货预警':'badge--warning','多重耐药菌检出':'badge--danger','体征极端值':'badge--danger'};
var typeBadge={'缺货预警':'badge--warning','任务逾期':'badge--info','监测告警':'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 total=alerts.length;
var stock=alerts.filter(function(a){return a.type==='缺货预警';}).length;
var overdue=alerts.filter(function(a){return a.type==='任务逾期';}).length;
var monitor=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>';
'<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">'+total+'</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>'
+'<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">'+overdue+'</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="M3 10h3l2-5 4 10 2-5h3" stroke="#FF4D4F" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div><div><div class="stat-card__value">'+monitor+'</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);});
var t=document.getElementById('filterType').value,p=document.getElementById('filterPoint').value,l=document.getElementById('filterLevel').value;
var list=alerts.filter(function(a){return (!t||a.type===t)&&(!p||a.point===p)&&(!l||a.level===l);});
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 style="text-align:left;">'+a.point+'</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+' 条预警';
+'<td>'+a.time+'</td><td>'+a.push+'</td>'
+'<td><button class="btn btn-link" onclick="openDetail('+i+')">详情</button></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 resetFilter(){['filterType','filterPoint','filterLevel'].forEach(function(id){document.getElementById(id).value='';});renderTable();}
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];
@@ -298,21 +213,14 @@ function openDetail(i){
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.point)+di('预警对象',a.obj)
+di('严重度',a.level)+di('触发时间',a.time)
+di('推送方式',a.push)
+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>
+242 -219
View File
@@ -5,293 +5,316 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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-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.10);
--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{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
.top-bar__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 14px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.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;}
.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:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;text-transform:uppercase;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.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-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-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;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;cursor:pointer;min-width:120px;}
/* 按钮 */
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
.action-bar__right{margin-left:auto;font-size:var(--font-sm);color:var(--text-secondary);}
.action-bar__right strong{color:var(--primary);font-weight:600;}
/* 表格卡片 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__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:10px 14px;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:12px 14px;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;}
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;}
/* 标签 */
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-red{background:#FFF1F0;color:var(--danger);}
.tag-orange{background:#FFF7E6;color:var(--warning);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-gray{background:#F5F5F5;color:#999;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-top:1px solid var(--border);}
.pagination__left{display:flex;align-items:center;gap:8px;font-size:var(--font-sm);color:var(--text-secondary);}
.pagination__left .filter-select{min-width:80px;height:36px;}
.pagination__right{display:flex;align-items:center;gap:6px;}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-ellipsis{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;font-size:var(--font-sm);color:var(--text-secondary);}
/* 异常值高亮 */
.val-error{color:var(--danger);font-weight:500;}
</style>
</head>
<body>
<!-- 顶部导航栏 -->
<div class="top-bar">
<!-- 顶部导航 -->
<header class="top-bar">
<div class="top-bar__logo"><svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.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>
<a class="btn-back" href="../../index.html"> 返回首页</a>
<div class="avatar"></div>
<span style="color:rgba(255,255,255,0.65);">系统管理员 ▾</span>
<span>系统管理员 ▾</span>
</div>
</div>
</header>
<!-- 侧边栏(当前一级分组「异常事件」的二级菜单) -->
<!-- 侧边栏 -->
<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="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>
<label>业务模块</label>
<select class="filter-select">
<option>全部</option>
<option>档案管理</option>
<option>基础配置</option>
<option>日常业务</option>
<option>床头呼叫</option>
<option>数据统计</option>
<option>健康小屋</option>
</select>
</div>
<div class="filter-item">
<label>处理状态</label>
<select class="filter-select" id="filterStatus" onchange="renderTable()" style="min-width:110px;">
<option value="">全部状态</option><option>待处理</option><option>处理中</option><option>已处理</option><option>已忽略</option>
<label>异常类型</label>
<select class="filter-select">
<option>全部</option>
<option>数据缺失</option>
<option>格式错误</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 class="filter-item">
<label>年份</label>
<select class="filter-select" style="min-width:90px;">
<option>2026</option>
<option>2025</option>
<option>2024</option>
</select>
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<!-- 操作栏 -->
<div class="action-bar">
<span class="action-bar__hint">脏数据实时推送运维值班群;超过 24 小时未处理自动升级提醒。</span>
<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="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
<div class="action-bar__right"><strong>128</strong> 条脏数据</div>
</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>
<th>业务模块</th>
<th>异常类型</th>
<th>异常描述</th>
<th>异常检测时间</th>
<th>表名</th>
<th>字段名</th>
<th>编号</th>
<th>异常值</th>
<th>数据产生时间</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
<tbody>
<tr>
<td>日常业务</td>
<td><span class="tag tag-red">数据缺失</span></td>
<td>发药记录未关联处方号,无法完成处方-发药勾稽</td>
<td>2026-09-07 09:23</td>
<td>t_dispense_record</td>
<td>prescription_no</td>
<td>FP-20260907-0142</td>
<td class="val-error">NULL</td>
<td>2026-09-07 08:10</td>
</tr>
<tr>
<td>基础配置</td>
<td><span class="tag tag-red">范围越界</span></td>
<td>药品库存现存量为负值,疑似 PAD 离线发药重复上传</td>
<td>2026-09-07 07:42</td>
<td>t_medicine_stock</td>
<td>stock_qty</td>
<td>乌审旗采气区医疗点 · 硝酸甘油片</td>
<td class="val-error">-3 盒</td>
<td>2026-09-07 06:55</td>
</tr>
<tr>
<td>档案管理</td>
<td><span class="tag tag-orange">逻辑矛盾</span></td>
<td>员工出生日期晚于入职日期,档案年龄为负数</td>
<td>2026-09-06 16:05</td>
<td>t_health_archive</td>
<td>birth_date / hire_date</td>
<td>EMP-CQ00156</td>
<td class="val-error">birth=2001-11, hire=1998-05</td>
<td>2026-09-06 14:30</td>
</tr>
<tr>
<td>基础配置</td>
<td><span class="tag tag-blue">重复数据</span></td>
<td>同一医疗点下存在两条同名同编码的药品配置</td>
<td>2026-09-06 10:30</td>
<td>t_medicine_config</td>
<td>medicine_code</td>
<td>西峰基地医疗点 · 阿莫西林胶囊</td>
<td class="val-error">2条重复</td>
<td>2026-09-06 09:15</td>
</tr>
<tr>
<td>床头呼叫</td>
<td><span class="tag tag-orange">格式错误</span></td>
<td>呼叫应答时长字段含非数字字符,无法统计响应时效</td>
<td>2026-09-05 17:30</td>
<td>t_bed_call_record</td>
<td>response_seconds</td>
<td>CALL-20260905-0087</td>
<td class="val-error">"12.5s"</td>
<td>2026-09-05 12:00</td>
</tr>
<tr>
<td>日常业务</td>
<td><span class="tag tag-red">关联缺失</span></td>
<td>随访任务引用的员工工号在健康档案库中不存在</td>
<td>2026-09-05 10:00</td>
<td>t_follow_task</td>
<td>emp_no</td>
<td>FW-20260905-0218</td>
<td class="val-error">CQ999999</td>
<td>2026-09-05 08:30</td>
</tr>
<tr>
<td>数据统计</td>
<td><span class="tag tag-blue">重复数据</span></td>
<td>靖边气田医疗点同日生成 3 条门诊量统计快照</td>
<td>2026-09-04 18:26</td>
<td>t_clinic_stat</td>
<td>point_id / stat_date</td>
<td>STAT-20260904-006</td>
<td class="val-error">3条重复</td>
<td>2026-09-04 15:20</td>
</tr>
<tr>
<td>健康小屋</td>
<td><span class="tag tag-red">数据缺失</span></td>
<td>预约记录缺少所属小屋ID,无法归属小屋统计</td>
<td>2026-09-04 09:10</td>
<td>t_hut_appointment</td>
<td>hut_id</td>
<td>AP-20260904-0336</td>
<td class="val-error">NULL</td>
<td>2026-09-03 17:48</td>
</tr>
<tr>
<td>档案管理</td>
<td><span class="tag tag-orange">格式错误</span></td>
<td>合作医院回传体检报告日期格式非法,解析失败</td>
<td>2026-09-03 11:05</td>
<td>t_visit_report</td>
<td>report_date</td>
<td>RPT-20260903-0451</td>
<td class="val-error">2026/13/45</td>
<td>2026-09-03 10:45</td>
</tr>
<tr>
<td>基础配置</td>
<td><span class="tag tag-red">范围越界</span></td>
<td>救护车核定载员数为 0,低于系统下限</td>
<td>2026-09-02 15:10</td>
<td>t_ambulance</td>
<td>capacity</td>
<td>樊学作业区医疗点 · 救护车 0012</td>
<td class="val-error">0</td>
<td>2026-09-02 14:00</td>
</tr>
</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 class="pagination">
<div class="pagination__left">
<span>共 128 条</span>
<span>每页</span>
<select class="filter-select" style="min-width:70px;">
<option>10</option>
<option>20</option>
<option>50</option>
</select>
<span></span>
</div>
<div class="pagination__right">
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<span class="page-ellipsis"></span>
<button class="page-btn">13</button>
<button class="page-btn"></button>
</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>
@@ -7,7 +7,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -62,8 +62,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;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:12px 14px;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;}
@@ -80,17 +80,17 @@ tbody tr:hover{background:#F8FBFF;}
.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-title{font-size:var(--font-base);font-weight:600;color:#262626;}
.modal-close{font-size:20px;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{font-size:var(--font-sm);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,.form-item select,.form-item textarea{height:36px;padding:0 12px;border:1px solid #e8e8e8;border-radius:4px;font-size:var(--font-sm);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>
@@ -104,8 +104,8 @@ tbody tr:hover{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -149,7 +149,7 @@ tbody tr:hover{background:#F8FBFF;}
<!-- [交互] 操作栏:新增模型文档 / 异步导出 -->
<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-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="currentColor" 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>
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,20 +33,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -71,8 +69,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;padding:8px;font-size:var(--font-sm);}
</style>
</head>
<body>
@@ -83,8 +81,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -128,10 +126,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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>
<button class="btn btn-primary" 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>
</div>
<!-- [交互] 年报列表:操作列固定,明细弹窗展示医疗点分解 -->
@@ -210,8 +205,7 @@ function openDetail(unit,year){
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>';
+'</tbody></table>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,22 +33,21 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -79,15 +78,15 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{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-sm);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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;padding:8px;font-size:var(--font-sm);}
</style>
</head>
<body>
@@ -98,8 +97,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -153,7 +152,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</div>
<div class="action-bar">
<span class="action-bar__hint">档案为只读归档视图;修改业务数据请回到对应的日常业务模块。</span>
</div>
<!-- [交互] 档案列表:操作列固定,打开右侧档案抽屉 -->
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,19 +30,26 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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;}
.filter-date{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
@@ -51,8 +58,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -64,6 +71,14 @@ tbody tr:hover{background:#F8FBFF;}
.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: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__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border-bottom:1px solid var(--border);padding:7px 8px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:center;}
.record-table td{border-bottom:1px solid #F0F0F0;padding:8px;font-size:var(--font-sm);text-align:center;}
</style>
</head>
<body>
@@ -74,8 +89,8 @@ tbody tr:hover{background:#F8FBFF;}
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -86,41 +101,91 @@ tbody tr:hover{background:#F8FBFF;}
</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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<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 id="fgrpDev" style="display:contents;">
<div class="filter-item">
<label>设备所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;"><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="filterDevCode" placeholder="如:HC-" 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-input" id="filterHostel" placeholder="如:101室" oninput="renderAll()">
</div>
</div>
<!-- 床位分配记录筛选组 -->
<div id="fgrpEmp" style="display:none;">
<div class="filter-item">
<label>所属楼栋</label>
<select class="filter-select" id="filterEBuilding" onchange="onFilterEBuilding()" style="min-width:180px;"><option value="">全部楼栋</option></select>
</div>
<div class="filter-item">
<label>所属宿舍</label>
<select class="filter-select" id="filterEDorm" onchange="renderAll()" style="min-width:110px;"><option value="">全部宿舍</option></select>
</div>
<div class="filter-item">
<label>员工姓名</label>
<input class="filter-input" id="filterEName" placeholder="请输入员工姓名" oninput="renderAll()">
</div>
<div class="filter-item">
<label>员工编号</label>
<input class="filter-input" id="filterENo" placeholder="请输入员工编号" oninput="renderAll()">
</div>
</div>
<!-- 公共筛选:床位名称 + 分配日期 -->
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="renderAll()"><option value="">全部单位</option></select>
<label>床位名称</label>
<input class="filter-input" id="filterBed" placeholder="如:101-1" oninput="renderAll()" style="min-width:100px;">
</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>
<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()">
@@ -131,21 +196,20 @@ tbody tr:hover{background:#F8FBFF;}
<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 -->
<!-- [交互] 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 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>
<span class="table-card__title" id="cardTitle">设备分配记录</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" 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="openTaskRecord()"><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>
<div class="table-card__content">
<table>
@@ -157,27 +221,36 @@ tbody tr:hover{background:#F8FBFF;}
</div>
</main>
<!-- [交互] 导出任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal--wide">
<div class="modal__title">分配记录导出任务</div>
<table class="record-table" id="taskTable"></table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var unitDeparts=['采油一厂','采油二厂','第四采油厂','采气一厂'];
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:''}
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',type:2,space:'樊学倒班宿舍1号楼 · 101-1',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',type:2,space:'樊学倒班宿舍1号楼 · 101-2',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',type:2,space:'樊学倒班宿舍1号楼 · 102-1',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',type:2,space:'樊学倒班宿舍1号楼 · 201-1',bind:'2026-04-02',unbind:'2026-04-08'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',type:1,space:'樊学倒班宿舍1号楼 · 101室',bind:'2026-03-05',unbind:''},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',type:1,space:'麻子山倒班宿舍楼 · 101室',bind:'2026-03-19',unbind:''},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',type:2,space:'桐园倒班宿舍1号楼 · 301-1',bind:'2026-04-10',unbind:''},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',type:1,space:'桐园倒班宿舍1号楼 · 201室',bind:'2026-04-10',unbind:''},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',type:2,space:'西峰基地综合宿舍楼 · 301-1',bind:'2026-05-12',unbind:''},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',type:2,space:'西峰基地综合宿舍楼 · 302-1',bind:'2026-05-12',unbind:''},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',type:1,space:'西峰基地综合宿舍楼 · 402室',bind:'2026-05-12',unbind:''},
{unit:'采气一厂',depart:'靖边气田',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:''},
@@ -195,50 +268,94 @@ var empRecords=[
{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 headDev(){return '<tr><th style="width:56px;">号</th><th>设备所属单位</th><th>设备所属部门</th><th style="min-width:100px;">设备编号</th><th>分配形式</th><th style="min-width:230px;">宿舍/床位名称</th><th>分配日期</th><th>解除日期</th></tr>';}
function headEmp(){return '<tr><th style="width:56px;">序号</th><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></tr>';}
function inRange(d){var f=document.getElementById('filterFrom').value,t=document.getElementById('filterTo').value;return (!f||d>=f)&&(!t||d<=t);}
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();
}
var buildings=[];empRecords.forEach(function(r){if(buildings.indexOf(r.building)<0)buildings.push(r.building);});
function onFilterEBuilding(){
var b=document.getElementById('filterEBuilding').value;
var ds=[];empRecords.forEach(function(r){if((!b||r.building===b)&&ds.indexOf(r.dorm)<0)ds.push(r.dorm);});
document.getElementById('filterEDorm').innerHTML='<option value="">全部宿舍</option>'+ds.map(function(d){return '<option>'+d+'</option>';}).join('');
renderAll();
}
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>'
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,kc=document.getElementById('filterDevCode').value.trim().toUpperCase();
var tp=document.getElementById('filterType').value,hs=document.getElementById('filterHostel').value.trim(),bd=document.getElementById('filterBed').value.trim();
var list=devRecords.filter(function(r){
return (!u||r.unit===u)&&(!d||r.depart===d)&&(!kc||r.code.toUpperCase().indexOf(kc)>=0)
&&(!tp||String(r.type)===tp)&&(!hs||r.space.indexOf(hs)>=0)&&(!bd||r.space.indexOf(bd)>=0)&&inRange(r.bind);
});
rows=list.map(function(r,i){
return '<tr><td>'+(i+1)+'</td><td>'+r.unit+'</td><td>'+r.depart+'</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>';
+'<td>'+r.space+'</td><td>'+r.bind+'</td><td>'+(r.unbind||'-')+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="8">没有符合条件的分配记录</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='设备-空间分配历史';
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>';
var b2=document.getElementById('filterEBuilding').value,d2=document.getElementById('filterEDorm').value;
var en=document.getElementById('filterEName').value.trim(),eo=document.getElementById('filterENo').value.trim().toUpperCase();
var bd2=document.getElementById('filterBed').value.trim();
var list2=empRecords.filter(function(r){
return (!b2||r.building===b2)&&(!d2||r.dorm===d2)&&(!en||r.name.indexOf(en)>=0)
&&(!eo||r.no.toUpperCase().indexOf(eo)>=0)&&(!bd2||r.bed.indexOf(bd2)>=0)&&inRange(r.bind);
});
rows=list2.map(function(r,i){
return '<tr><td>'+(i+1)+'</td><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></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('totalTip').textContent='共 '+list2.length+' 条 · 未解除 '+list2.filter(function(r){return !r.unbind;}).length+' ';
document.getElementById('cardTitle').textContent='床位分配记录';
}
document.getElementById('tbody').innerHTML=rows;
}
/* [交互] TAB 切换驱动筛选字段组显隐 */
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';
document.getElementById('fgrpDev').style.display=t==='dev'?'contents':'none';
document.getElementById('fgrpEmp').style.display=t==='emp'?'contents':'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 resetFilter(){
['filterUnit','filterDepart','filterType','filterEBuilding','filterEDorm'].forEach(function(id){document.getElementById(id).value='';});
['filterDevCode','filterHostel','filterEName','filterENo','filterBed'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('filterFrom').value='2026-01-01';document.getElementById('filterTo').value='2026-09-07';
renderAll();
}
/* [交互] 导出任务(异步任务演示) */
function openTaskRecord(){
var head='<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>';
var rows=[['设备分配记录-20260907.xlsx','2026-09-07 15:33:27','成功'],['床位分配记录-20260831.xlsx','2026-08-31 11:26:04','成功']].map(function(r){
return '<tr><td>'+r[0]+'</td><td>'+r[1]+'</td><td><span class="badge badge--success">'+r[2]+'</span></td><td><button class="btn btn-link" onclick="showToast(\'文件下载中…\')">下载</button></td></tr>';
}).join('');
document.getElementById('taskTable').innerHTML=head+'<tbody>'+rows+'</tbody>';
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
}
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+unitDeparts.map(function(u){return '<option>'+u+'</option>';}).join('');
renderAll();
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
document.getElementById('filterEBuilding').innerHTML='<option value="">全部楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
onFilterUnit();onFilterEBuilding();
</script>
</body>
</html>
+328 -55
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>床位管理 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,27 +30,35 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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__head .btns{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;}
.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;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1300px;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -65,7 +73,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{min-width:28px;height:28px;border:1px solid var(--border);background:#fff;border-radius:var(--radius-sm);font-size:var(--font-sm);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;}
@@ -77,10 +85,18 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-label{font-size:var(--font-sm);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-sm);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;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border-bottom:1px solid var(--border);padding:7px 8px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:center;}
.record-table td{border-bottom:1px solid #F0F0F0;padding:8px;font-size:var(--font-sm);text-align:center;}
.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;}
@@ -97,8 +113,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -109,18 +125,33 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group open" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub active">床位信息</a>
</div>
</div>
<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>
<a href="bed-timeout-alarm.html" class="sidebar-item">告警记录</a>
<a href="bed-stat.html" class="sidebar-item">汇总统计</a>
</aside>
<!-- 主内容 -->
@@ -128,6 +159,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 筛选栏:楼栋→宿舍级联 -->
<div class="filter-bar">
<div class="filter-item">
<label>床位编号</label>
<input class="filter-input" id="filterBed" placeholder="请输入床位编号" oninput="renderTable()" style="min-width:110px;">
</div>
<div class="filter-item">
<label>所属楼栋</label>
<select class="filter-select" id="filterBuilding" onchange="onFilterBuilding()" style="min-width:180px;">
@@ -140,19 +175,19 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<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;">
<label>员工姓名</label>
<input class="filter-input" id="filterEmpName" placeholder="请输入员工姓名" oninput="renderTable()" style="min-width:110px;">
</div>
<div class="filter-item">
<label>员工编号</label>
<input class="filter-input" id="filterEmpNo" placeholder="请输入员工编号" oninput="renderTable()" style="min-width:110px;">
</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>查询
@@ -160,22 +195,29 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<span class="table-card__title">床位列表</span>
<div class="btns">
<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-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openTaskRecord('import')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" onclick="openAllocImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入分配数据</button>
<button class="btn btn-default" onclick="openTaskRecord('alloc')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看分配导入任务</button>
<button class="btn btn-primary" 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-danger" onclick="batchDelete()">批量删除</button>
</div>
</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>
<th style="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>
<th style="width:56px;"></th><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:170px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
@@ -185,7 +227,91 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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="eBuilding" onchange="onEditBuilding()"><option value="">请选择楼栋</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属宿舍</span>
<select class="form-control" id="eDorm"><option value="">请先选择楼栋</option></select>
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 床位编号</span>
<input class="form-control" id="eCode" placeholder="如:101-5(宿舍内唯一)">
</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" onchange="onImportBuilding()"><option value="">请选择楼栋</option></select>
</div>
<div class="form-item">
<span class="form-label">所属宿舍(留空则按模板内宿舍列挂接)</span>
<select class="form-control" id="iDorm"><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="allocImportMask">
<div class="modal">
<div class="modal__title">批量导入分配数据</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="closeAllocImport()">取消</button>
<button class="btn btn-primary" onclick="doAllocImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 导入/分配导入:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal--wide">
<div class="modal__title" id="taskTitle">床位导入任务</div>
<table class="record-table" id="taskTable"></table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<!-- [交互] 分配员工 -->
<div class="modal-mask" id="allocMask">
<div class="modal">
<div class="modal__title">分配员工 · <span id="allocBed"></span></div>
@@ -201,10 +327,23 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</div>
</div>
<!-- [交互] 删除确认(单条/批量共用) -->
<div class="modal-mask" id="confirmMask">
<div class="modal">
<div class="modal__title" id="confirmHead">删除确认</div>
<div class="modal__text" id="confirmText">确认删除该床位?</div>
<div class="modal__text" id="confirmWarn" style="color:var(--danger);">床位若已分配员工或绑定床头设备,请先在「分配管理」中解除设备、解除员工分配后再删除。</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeConfirm()">取消</button>
<button class="btn btn-danger" id="confirmOk" onclick="doConfirmOk()">确认删除</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},
@@ -226,7 +365,7 @@ 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:'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'},
@@ -253,25 +392,30 @@ dormMeta.forEach(function(d){
}
});
var pageNo=1,pageSize=10;
function uniqBuildings(){var bs=[];dormMeta.forEach(function(d){if(bs.indexOf(d.building)<0)bs.push(d.building);});return bs;}
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('');
document.getElementById('filterBuilding').innerHTML='<option value="">全部楼栋</option>'+uniqBuildings().map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('eBuilding').innerHTML='<option value="">请选择楼栋</option>'+uniqBuildings().map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('iBuilding').innerHTML='<option value="">请选择楼栋</option>'+uniqBuildings().map(function(b){return '<option>'+b+'</option>';}).join('');
}
function dormOptionsHtml(b,cur){
var ds=[];dormMeta.forEach(function(d){if((!b||d.building===b)&&ds.indexOf(d.name)<0)ds.push(d.name);});
return ds.map(function(d){return '<option'+(d===cur?' selected':'')+'>'+d+'</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('');
document.getElementById('filterDorm').innerHTML='<option value="">全部宿舍</option>'+dormOptionsHtml(b);
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();
var a=document.getElementById('filterAlloc').value,en=document.getElementById('filterEmpName').value.trim(),eo=document.getElementById('filterEmpNo').value.trim().toUpperCase();
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)));
&&(a===''||String(assigns[key]?1:0)===a)
&&(!en||(e&&e.name.indexOf(en)>=0))&&(!eo||(assigns[key]&&assigns[key].indexOf(eo)>=0));
});
}
function renderTable(){
@@ -280,20 +424,109 @@ function renderTable(){
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>'
var i=beds.indexOf(r),key=r.building+'|'+r.code,no=assigns[key],e=no?empOf(no):null;
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><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>';
+'<td>'+(e?'<button class="btn btn-link--danger" onclick="askUnassign(\''+key+'\')">解除分配</button>':'<button class="btn btn-link" onclick="openAlloc(\''+key+'\',\''+r.code+'\')">分配员工</button>')
+'<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="11">没有符合条件的床位</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 床 · 已分配 '+Object.keys(assigns).length+' · 第 '+pageNo+'/'+pages+' 页';
document.getElementById('ckAll').checked=false;
}
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();}
function resetFilter(){['filterBuilding','filterDorm','filterAlloc'].forEach(function(id){document.getElementById(id).value='';});['filterBed','filterEmpName','filterEmpNo'].forEach(function(id){document.getElementById(id).value='';});onFilterBuilding();}
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
var cks=[].slice.call(document.querySelectorAll('.rowCk'));
document.getElementById('ckAll').checked=cks.length>0&&cks.every(function(c){return c.checked;});
}
function batchDelete(){
var ids=[].slice.call(document.querySelectorAll('.rowCk')).filter(function(c){return c.checked;}).map(function(c){return +c.value;});
if(!ids.length){showToast('请选择需要删除的数据');return;}
var bound=ids.filter(function(i){var k=beds[i].building+'|'+beds[i].code;return assigns[k]||bedDevices[k];});
if(bound.length){showToast('选中的 '+bound.length+' 张床位存在分配关系,请先解除');return;}
delIdx=-1;delBatch=ids;
document.getElementById('confirmHead').textContent='删除确认';
document.getElementById('confirmText').textContent='确认删除选中的 '+ids.length+' 张床位?';
batchAskDelete();
}
/* [交互] 新增/编辑床位 */
var editIdx=-1;
function onEditBuilding(){
var b=document.getElementById('eBuilding').value;
document.getElementById('eDorm').innerHTML='<option value="">请选择宿舍</option>'+dormOptionsHtml(b);
}
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=i<0?'新增床位信息':'编辑床位信息';
if(i>=0){
var r=beds[i];
document.getElementById('eBuilding').value=r.building;onEditBuilding();
document.getElementById('eDorm').innerHTML='<option value="">请选择宿舍</option>'+dormOptionsHtml(r.building,r.dorm);
document.getElementById('eCode').value=r.code;
}else{
document.getElementById('eBuilding').value='';onEditBuilding();
document.getElementById('eCode').value='';
}
document.getElementById('editMask').classList.add('open');
}
function closeEdit(){document.getElementById('editMask').classList.remove('open');}
function saveEdit(){
var b=document.getElementById('eBuilding').value,d=document.getElementById('eDorm').value,c=document.getElementById('eCode').value.trim();
if(!b||!d||!c){showToast('请完整填写带 * 的必填项');return;}
var dup=beds.some(function(r,i){return i!==editIdx&&r.building===b&&r.dorm===d&&r.code===c;});
if(dup){showToast('该宿舍下已存在同名床位编号');return;}
if(editIdx<0){beds.push({code:c,building:b,dorm:d});showToast('床位已新增');}
else{
var old=beds[editIdx],ok=old.building+'|'+old.code,nk=b+'|'+c;
/* 床位定位变更后迁移其员工分配与设备绑定关系 */
if(ok!==nk){
if(assigns[ok]){delete assigns[ok];}
if(bedDevices[ok]){delete bedDevices[ok];}
}
beds[editIdx]={code:c,building:b,dorm:d};
showToast('床位已更新');
}
closeEdit();pageNo=1;renderTable();
}
/* [交互] 导入 */
function openImport(){document.getElementById('importMask').classList.add('open');}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function onImportBuilding(){
var b=document.getElementById('iBuilding').value;
document.getElementById('iDorm').innerHTML='<option value="">全部宿舍</option>'+dormOptionsHtml(b);
}
function doImport(){
if(!document.getElementById('iBuilding').value){showToast('请先选择所属楼栋');return;}
closeImport();showToast('导入任务已提交,请到「查看导入任务」查看结果');
}
/* [交互] 分配导入 */
function openAllocImport(){document.getElementById('allocImportMask').classList.add('open');}
function closeAllocImport(){document.getElementById('allocImportMask').classList.remove('open');}
function doAllocImport(){closeAllocImport();showToast('分配导入任务已提交,请到「查看分配导入任务」查看结果');}
/* [交互] 导入/分配导入任务(异步任务演示) */
var taskRecords={
import:[['床位清单-模板.xlsx','2026-09-05 17:15:08','成功:新增 8 床'],['床位清单-8月.xlsx','2026-08-20 09:55:17','成功:新增 12 床']],
alloc:[['床位分配-模板.xlsx','2026-09-06 11:20:34','成功:绑定 12 条、失败 0 条'],['床位分配-8月.xlsx','2026-08-22 15:03:49','成功:绑定 20 条、失败 2 条']]
};
function openTaskRecord(type){
var titleMap={import:'床位导入任务',alloc:'分配导入任务'};
document.getElementById('taskTitle').textContent=titleMap[type];
var head='<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>';
var rows=taskRecords[type].map(function(r){
return '<tr><td>'+r[0]+'</td><td>'+r[1]+'</td><td><span class="badge badge--success">'+r[2]+'</span></td><td><button class="btn btn-link" onclick="showToast(\'导入明细下载中…\')">下载明细</button></td></tr>';
}).join('');
document.getElementById('taskTable').innerHTML=head+'<tbody>'+rows+'</tbody>';
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 分配员工 */
var allocKey='',allocSel='';
function openAlloc(key,code){
@@ -320,11 +553,51 @@ function confirmAlloc(){
assigns[allocKey]=allocSel;
closeAlloc();renderTable();showToast('已将「'+e.name+'」分配到该床位');
}
function unassign(key){
/* [交互] 解除分配与删除确认共用弹窗 */
var unassignKey='',confirmMode='delete';
function askUnassign(key){
unassignKey=key;confirmMode='unassign';
var e=empOf(assigns[key]);
delete assigns[key];
renderTable();showToast('已解除「'+(e?e.name:'')+'」的床位分配并记入分配记录');
document.getElementById('confirmHead').textContent='解除分配确认';
document.getElementById('confirmText').textContent='确认解除「'+(e?e.name:'')+'」的床位分配?';
document.getElementById('confirmWarn').textContent='解除后将写入「分配记录」的床位分配记录(解除日期)。';
document.getElementById('confirmOk').textContent='确认解除';
document.getElementById('confirmMask').classList.add('open');
}
var delIdx=-1,delBatch=null;
function askDelete(i){
delIdx=i;delBatch=null;confirmMode='delete';
var r=beds[i],key=r.building+'|'+r.code;
if(assigns[key]||bedDevices[key]){showToast('该床位存在分配关系,请先解除');return;}
document.getElementById('confirmHead').textContent='删除确认';
document.getElementById('confirmText').textContent='确认删除「'+r.building+' · '+r.code+'」床位?';
document.getElementById('confirmWarn').textContent='床位若已分配员工或绑定床头设备,请先在「分配管理」中解除设备、解除员工分配后再删除。';
document.getElementById('confirmOk').textContent='确认删除';
document.getElementById('confirmMask').classList.add('open');
}
function batchAskDelete(){
confirmMode='delete';
document.getElementById('confirmHead').textContent='删除确认';
document.getElementById('confirmWarn').textContent='仅可删除无分配关系的床位,所选床位已通过校验。';
document.getElementById('confirmOk').textContent='确认删除';
document.getElementById('confirmMask').classList.add('open');
}
function closeConfirm(){document.getElementById('confirmMask').classList.remove('open');}
function doConfirmOk(){
if(confirmMode==='unassign'){
var e=empOf(assigns[unassignKey]);
delete assigns[unassignKey];
showToast('已解除「'+(e?e.name:'')+'」的床位分配并记入分配记录');
}else if(delBatch){
delBatch.sort(function(a,b){return b-a;}).forEach(function(i){beds.splice(i,1);});
showToast('已批量删除 '+delBatch.length+' 张床位');delBatch=null;
}else{
beds.splice(delIdx,1);showToast('床位已删除');
}
closeConfirm();renderTable();
}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
+190 -45
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>楼栋管理 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,27 +30,34 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -63,6 +70,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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);}
.link-num{color:var(--primary);cursor:pointer;text-decoration:underline;}
.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;}
@@ -71,6 +79,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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--list{width:860px;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;}
@@ -78,14 +87,17 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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{border:1px dashed var(--border);border-radius:var(--radius-md);padding:28px 16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);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;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border-bottom:1px solid var(--border);padding:7px 8px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:center;}
.record-table td{border-bottom:1px solid #F0F0F0;padding:8px;font-size:var(--font-sm);text-align:center;}
</style>
</head>
<body>
@@ -96,8 +108,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -108,18 +120,33 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group open" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub active">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<a href="bed-timeout-alarm.html" class="sidebar-item">告警记录</a>
<a href="bed-stat.html" class="sidebar-item">汇总统计</a>
</aside>
<!-- 主内容 -->
@@ -135,7 +162,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderTable()" style="min-width:130px;">
<select class="filter-select" id="filterDepart" onchange="onFilterDepart()" style="min-width:130px;">
<option value="">全部部门</option>
</select>
</div>
@@ -155,27 +182,26 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<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>新增楼栋
<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>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openTaskRecord('import')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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-danger" onclick="batchDelete()">批量删除</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="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>
<th style="width:56px;">序号</th><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>
@@ -186,10 +212,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</main>
<!-- [交互] 新增/编辑楼栋 -->
<!-- [交互] 新增/编辑楼栋信息 -->
<div class="modal-mask" id="editMask">
<div class="modal--wide">
<div class="modal__title" id="editTitle">新增楼栋</div>
<div class="modal__title" id="editTitle">新增楼栋信息</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属单位</span>
@@ -234,10 +260,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 批量导入:选择站点后上传楼栋清单 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title">批量导入楼栋</div>
<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>
<select class="form-control" id="iStation"><option value="">请选择站点</option><option>樊学站点基地</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>
@@ -256,12 +282,37 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 删除确认 -->
<!-- [交互] 宿舍数量下钻:该楼栋下的宿舍清单(列与「宿舍信息」一致) -->
<div class="modal-mask" id="dormMask">
<div class="modal--list">
<div class="modal__title" id="dormTitle">楼栋宿舍清单</div>
<div class="filter-item" style="margin-bottom:12px;">
<label>宿舍名称</label>
<input class="filter-input" id="dormFilter" placeholder="请输入宿舍名称" oninput="renderDormList()" style="min-width:200px;">
</div>
<table class="record-table">
<thead><tr><th>宿舍名称</th><th>床位数量</th><th>负责人</th><th>负责人电话</th><th>是否分配设备</th><th>设备编号</th></tr></thead>
<tbody id="dormTbody"></tbody>
</table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeDorm()">关闭</button></div>
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal--wide">
<div class="modal__title" id="taskTitle">楼栋导入任务</div>
<table class="record-table" id="taskTable"></table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</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__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>
@@ -282,6 +333,33 @@ var buildings=[
{unit:'第四采油厂',depart:'西峰作业区',station:'西峰基地',name:'西峰基地综合宿舍楼',addr:'西峰基地食堂南侧',dorms:4,manager:'马文秀',mobile:'13991784320'},
{unit:'采气一厂',depart:'靖边气田',station:'靖边气田站点',name:'靖边气田倒班宿舍楼',addr:'靖边气田综合管理营地北侧',dorms:2,manager:'周铁祥',mobile:'13991923745'}
];
/* [交互] 宿舍数量下钻演示数据:按楼栋名挂宿舍清单,列与宿舍信息页一致 */
var bldDorms={
'樊学倒班宿舍1号楼':[
{name:'301室',beds:6,manager:'石维国',mobile:'13991203457',alloc:1,dev:'BC-2026-0031'},
{name:'302室',beds:4,manager:'曲志明',mobile:'13991203458',alloc:1,dev:'BC-2026-0032'},
{name:'305室',beds:6,manager:'李怀安',mobile:'13991203459',alloc:0,dev:'—'}
],
'麻子山倒班宿舍楼':[
{name:'201室',beds:4,manager:'高建民',mobile:'13991345013',alloc:1,dev:'BC-2026-0015'},
{name:'204室',beds:4,manager:'杨立强',mobile:'13991345014',alloc:0,dev:'—'}
],
'桐园倒班宿舍1号楼':[
{name:'401室',beds:8,manager:'白吉增',mobile:'13991567209',alloc:1,dev:'BC-2026-0021'},
{name:'402室',beds:6,manager:'师振峰',mobile:'13991567210',alloc:1,dev:'BC-2026-0022'},
{name:'406室',beds:6,manager:'冯志清',mobile:'13991567211',alloc:0,dev:'—'}
],
'西峰基地综合宿舍楼':[
{name:'101室',beds:4,manager:'康维桢',mobile:'13991784321',alloc:1,dev:'BC-2026-0008'},
{name:'102室',beds:4,manager:'邵瑞庭',mobile:'13991784322',alloc:1,dev:'BC-2026-0009'},
{name:'201室',beds:6,manager:'卢翰文',mobile:'13991784323',alloc:0,dev:'—'},
{name:'202室',beds:6,manager:'崔秉章',mobile:'13991784324',alloc:1,dev:'BC-2026-0010'}
],
'靖边气田倒班宿舍楼':[
{name:'501室',beds:4,manager:'戴文渊',mobile:'13991923746',alloc:1,dev:'BC-2026-0003'},
{name:'503室',beds:4,manager:'田广润',mobile:'13991923747',alloc:0,dev:'—'}
]
};
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('');
@@ -303,18 +381,41 @@ function onFilterUnit(){
staSel.dataset.tip='全部站点';fillOptions(staSel,stas);
renderTable();
}
function onFilterDepart(){
var d=document.getElementById('filterDepart').value;
var staSel=document.getElementById('filterStation');
staSel.dataset.tip='全部站点';
var stas=d?(depStations[d]||[]):Object.keys(depStations).reduce(function(a,k){return a.concat(depStations[k]);},[]);
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>'
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><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><span class="link-num" onclick="openDorm('+i+')">'+b.dorms+'</span></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>';
}).join('')||'<tr><td class="td-empty" colspan="11">没有符合条件的楼栋</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 栋';
document.getElementById('ckAll').checked=false;
}
function resetFilter(){['filterUnit','filterDepart','filterStation'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';onFilterUnit();}
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
var cks=[].slice.call(document.querySelectorAll('.rowCk'));
document.getElementById('ckAll').checked=cks.length>0&&cks.every(function(c){return c.checked;});
}
function batchDelete(){
var ids=[].slice.call(document.querySelectorAll('.rowCk')).filter(function(c){return c.checked;}).map(function(c){return +c.value;});
if(!ids.length){showToast('请选择需要删除的数据');return;}
delIdx=-1;delBatch=ids;
document.getElementById('confirmText').textContent='确认删除选中的 '+ids.length+' 栋楼栋?其下宿舍将一并失效。';
document.getElementById('confirmMask').classList.add('open');
}
/* [交互] 新增/编辑 */
var editIdx=-1;
function onEditUnit(){
@@ -323,14 +424,19 @@ function onEditUnit(){
depSel.innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
staSel.innerHTML='<option value="">请先选择部门</option>';
}
function onEditDepart(){
var d=document.getElementById('eDepart').value;
document.getElementById('eStation').innerHTML='<option value="">请选择站点</option>'+(d?depStations[d]:[]).map(function(s){return '<option>'+s+'</option>';}).join('');
}
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=i<0?'新增楼栋':'编辑楼栋';
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('');
onEditDepart();
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;
@@ -356,17 +462,56 @@ 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 栋');
closeImport();showToast('导入任务已提交,请到「查看导入任务」查看结果');
}
/* [交互] 导入任务(异步任务演示) */
var taskRecords={
import:[['楼栋清单-模板.xlsx','2026-09-05 16:41:02','成功:新增 2 栋、更新 1 栋'],['楼栋清单-8月.xlsx','2026-08-20 09:37:15','成功:新增 3 栋']]
};
function openTaskRecord(type){
document.getElementById('taskTitle').textContent='楼栋导入任务';
var head='<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>';
var rows=taskRecords[type].map(function(r){
return '<tr><td>'+r[0]+'</td><td>'+r[1]+'</td><td><span class="badge badge--success">'+r[2]+'</span></td><td>'+'<button class="btn btn-link" onclick="showToast(\'导入明细下载中…\')">下载明细</button>'+'</td></tr>';
}).join('');
document.getElementById('taskTable').innerHTML=head+'<tbody>'+rows+'</tbody>';
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 宿舍数量下钻弹窗 */
var dormIdx=-1;
function openDorm(i){
dormIdx=i;
document.getElementById('dormTitle').textContent='「'+buildings[i].name+'」宿舍清单';
document.getElementById('dormFilter').value='';
renderDormList();
document.getElementById('dormMask').classList.add('open');
}
function closeDorm(){document.getElementById('dormMask').classList.remove('open');}
function renderDormList(){
var k=document.getElementById('dormFilter').value.trim();
var list=(bldDorms[buildings[dormIdx]?buildings[dormIdx].name:'']||[]).filter(function(r){return !k||r.name.indexOf(k)>=0;});
document.getElementById('dormTbody').innerHTML=list.map(function(r){
return '<tr><td>'+r.name+'</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>'+r.dev+'</td></tr>';
}).join('')||'<tr><td colspan="6" style="padding:28px 0;color:var(--text-placeholder);">该楼栋暂无宿舍清单</td></tr>';
}
/* [交互] 删除 */
var delIdx=-1;
var delIdx=-1,delBatch=null;
function askDelete(i){
delIdx=i;
delIdx=i;delBatch=null;
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 doDelete(){
if(delBatch){
delBatch.sort(function(a,b){return b-a;}).forEach(function(i){buildings.splice(i,1);});
showToast('已批量删除 '+delBatch.length+' 栋');delBatch=null;
}else{buildings.splice(delIdx,1);showToast('楼栋已删除');}
closeConfirm();renderTable();
}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
+243 -115
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,28 +30,36 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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__ops{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: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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -70,19 +78,35 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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--wide{width:750px;max-height:84vh;display:flex;flex-direction:column;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal--md{width:640px;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;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{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:16px 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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
/* 工单详情:呼叫类型+阶段标签+处置状态 */
.ct-head{display:flex;align-items:center;gap:10px;font-size:var(--font-base);font-weight:600;margin-bottom:14px;}
.ct-head .st{font-size:var(--font-sm);font-weight:400;color:var(--text-secondary);}
.info-card{border-radius:var(--radius-sm);padding:12px 14px;display:grid;grid-template-columns:1fr 1fr;gap:8px 18px;}
.info-card--blue{background:#F0F8FF;border:1px solid #D6E9FF;}
.info-card--grey{background:#FAFAFA;border:1px solid var(--border);}
.info-card .ik{display:flex;gap:8px;font-size:var(--font-sm);min-width:0;}
.info-card .ik b{color:var(--text-secondary);font-weight:400;font-size:var(--font-sm);white-space:nowrap;line-height:20px;}
.info-card .ik span{color:var(--text-primary);white-space:normal;line-height:20px;}
.info-card .ik--full{grid-column:1/-1;}
.info-empty{grid-column:1/-1;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);padding:6px 0;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:left;}
.record-table td{border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);color:var(--text-primary);}
.record-table thead tr th:last-child,.record-table tbody td:last-child{position:static;box-shadow:none;background:transparent;}
</style>
</head>
<body>
@@ -93,8 +117,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -108,47 +132,60 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- 侧边栏(当前一级分组「床头呼叫」的二级菜单) -->
<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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<a href="bed-timeout-alarm.html" class="sidebar-item">告警记录</a>
<a href="bed-stat.html" class="sidebar-item">汇总统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 筛选栏(按 TAB 切换显示对应字段) -->
<!-- [交互] 筛选栏:员工呼入/终端呼出各自独立一组条件,切换 TAB 时整组替换 -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名/编号</label>
<input class="filter-input" id="filterEmp" placeholder="关键字" oninput="renderAll()">
<div id="fgrpIn" style="display:contents;">
<div class="filter-item"><label>员工姓名</label><input class="filter-input" id="inName" placeholder="姓名" oninput="renderAll()"></div>
<div class="filter-item"><label>员工编号</label><input class="filter-input" id="inNo" placeholder="如:CQ021578" oninput="renderAll()"></div>
<div class="filter-item"><label>主呼设备</label><input class="filter-input" id="inDev" placeholder="如:HC-FX" oninput="renderAll()"></div>
<div class="filter-item"><label>呼叫形式</label>
<select class="filter-select" id="inType" onchange="renderAll()" style="min-width:110px;"><option value="">全部</option><option>宿舍呼叫</option><option>床位呼叫</option></select>
</div>
<div class="filter-item"><label>呼叫状态</label>
<select class="filter-select" id="inStatus" onchange="renderAll()" style="min-width:100px;"><option value="">全部</option><option>已接通</option><option>未接通</option></select>
</div>
<div class="filter-item"><label>救助状态</label>
<select class="filter-select" id="inRescue" onchange="renderAll()" style="min-width:110px;"><option value="">全部</option><option>等待处理</option><option>处理中</option><option>处理完成</option></select>
</div>
</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 id="fgrpOut" style="display:none;">
<div class="filter-item"><label>主呼设备</label><input class="filter-input" id="outFrom" placeholder="如:HD-FX" oninput="renderAll()"></div>
<div class="filter-item"><label>被呼设备</label><input class="filter-input" id="outTo" placeholder="如:HC-FX" oninput="renderAll()"></div>
<div class="filter-item"><label>呼叫形式</label>
<select class="filter-select" id="outType" onchange="renderAll()" style="min-width:110px;"><option value="">全部</option><option>语音呼叫</option><option>视频呼叫</option><option>一键呼叫</option></select>
</div>
<div class="filter-item"><label>呼叫状态</label>
<select class="filter-select" id="outStatus" onchange="renderAll()" style="min-width:110px;"><option value="">全部</option><option>已接听</option><option>未接听</option><option>已挂断</option></select>
</div>
</div>
<div class="filter-item">
<label>呼叫日期</label>
@@ -162,12 +199,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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 -->
<!-- [交互] 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>
@@ -176,7 +209,11 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<!-- [交互] 导出/查看导出任务仅员工呼入 TAB 提供,终端呼出不支持导出 -->
<div class="table-card__ops" id="inBtns">
<button class="btn btn-primary" 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="openTaskRecord()"><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>
<div class="table-card__content">
<table>
@@ -188,114 +225,205 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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__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="taskMask">
<div class="modal--md">
<div class="modal__header"><span>导出任务</span><button class="modal__close" onclick="closeTask()">&times;</button></div>
<div class="modal__body">
<table class="record-table">
<thead><tr><th style="width:46%;">文件名称</th><th style="width:28%;">导出时间</th><th style="width:14%;">状态</th><th style="width:12%;">操作</th></tr></thead>
<tbody id="taskBody"></tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
var curTab='in';
/* 员工呼入:主呼设备/空间与分配记录勾稽;赵万福(182/110)与结果异常 YC-20260905-05 同源事件 */
/* 员工档案简版:供工单详情「员工信息」区块取数 */
var empProfiles={
'CQ021578':{name:'石维国',sex:'男',age:52,unit:'采油一厂',dept:'樊学作业区',phone:'182****3367',chronic:'高血压(2级)'},
'CQ032144':{name:'李桂芳',sex:'女',age:47,unit:'采油一厂',dept:'樊学作业区',phone:'137****2208',chronic:'无明确慢性病史'},
'CQ033096':{name:'赵万福',sex:'男',age:58,unit:'采油二厂',dept:'桐园作业区',phone:'158****6620',chronic:'高血压(3级)、2型糖尿病'},
'CQ036441':{name:'刘玉梅',sex:'女',age:44,unit:'采气一厂',dept:'靖边气田',phone:'188****9032',chronic:'无明确慢性病史'},
'CQ010564':{name:'高建军',sex:'男',age:55,unit:'第四采油厂',dept:'西峰作业区',phone:'135****4471',chronic:'颈椎病史、高脂血症'},
'CQ045130':{name:'杨慧敏',sex:'女',age:38,unit:'采油一厂',dept:'麻子山作业区',phone:'176****5583',chronic:'无明确慢性病史'},
'CQ027710':{name:'张秀兰',sex:'女',age:50,unit:'第四采油厂',dept:'西峰作业区',phone:'139****2760',chronic:'冠心病'},
'CQ022317':{name:'徐立成',sex:'男',age:49,unit:'采油二厂',dept:'桐园作业区',phone:'150****8819',chronic:'慢性支气管炎'},
'CQ029905':{name:'呼建国',sex:'男',age:53,unit:'采气一厂',dept:'靖边气田',phone:'187****3345',chronic:'支气管哮喘'}
};
/* 员工呼入:主呼设备=床头/面板(HC-*),被呼设备=值班室终端(HD-*);赵万福(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:'值班医生送备用吸入剂并预约复诊。'}
{dev:'HC-FX-01',to:'HD-FX-01',time:'2026-09-07 02:14',resp:'2026-09-07 02:15',end:'2026-09-07 02:41',ct:'床位呼叫',no:'CQ021578',cs:'已接通',rs:'处理完成',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101-1',out:'否',reason:'夜间胸闷,请求值班医生到场',info:'值班医生 2 分钟到场,心电监护无异常,处置后回床休息。'},
{dev:'HC-FX-04',to:'HD-FX-01',time:'2026-09-06 23:41',resp:'2026-09-06 23:42',end:'2026-09-06 23:58',ct:'宿舍呼叫',no:'CQ032144',cs:'已接通',rs:'处理完成',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101室',out:'否',reason:'同事突发头晕',info:'到 101 室处置,测血压 126/78,判定为体位性低血压,补液后缓解。'},
{dev:'HC-TY-02',to:'HD-TY-01',time:'2026-09-05 21:07',resp:'2026-09-05 21:08',end:'2026-09-05 21:52',ct:'宿舍呼叫',no:'CQ033096',cs:'已接通',rs:'处理完成',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 201室',out:'是',amb:'桐园医疗点救护车 1 号',drv:'马永林',med:'值班医生白文斌、护士钱晓芸',adate:'2026-09-05',reason:'自测血压 182/110 伴头痛',info:'值班医生到场复测确认,按高血压危象指引处置并通知管理医生,救护车送医疗点降压治疗后平稳,该事件进入结果异常台账(YC-20260905-05)。'},
{dev:'HC-JB-01',to:'HD-JB-01',time:'2026-09-05 14:22',resp:'2026-09-05 14:23',end:'2026-09-05 14:50',ct:'床位呼叫',no:'CQ036441',cs:'已接通',rs:'处理完成',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1',out:'否',reason:'发热 38.6℃ 需退热处置',info:'靖边医疗点到宿舍处置,物理降温+口服退热,次随访已退。'},
{dev:'HC-XF-01',to:'HD-XF-01',time:'2026-09-04 07:52',resp:'—',end:'2026-09-04 07:58',ct:'床位呼叫',no:'CQ010564',cs:'未接通',rs:'处理完成',station:'西峰基地',space:'西峰基地综合宿舍楼 · 301-1',out:'否',reason:'晨起手指麻木请求咨询',info:'首呼未接听,5 分钟后值班室回拨接通,指导活动观察,当日无碍。'},
{dev:'HC-MZ-01',to:'HD-MZ-01',time:'2026-09-03 19:36',resp:'2026-09-03 19:37',end:'',ct:'宿舍呼叫',no:'CQ045130',cs:'已接通',rs:'等待处理',station:'麻子山站点',space:'麻子山倒班宿舍楼 · 101室',out:'',reason:'踝扭伤冰敷后疼痛加重',info:'待夜班值班医生到场评估,已电话指导制动抬高。'},
{dev:'HC-XF-03',to:'HD-XF-01',time:'2026-09-02 12:03',resp:'2026-09-02 12:04',end:'',ct:'宿舍呼叫',no:'CQ027710',cs:'已接通',rs:'处理中',station:'西峰基地',space:'西峰基地综合宿舍楼 · 402室',out:'',reason:'外伤换药到期提醒请求处理',info:'值班室已派单,14:00 医疗点护士到宿舍换药。'},
{dev:'HC-FX-02',to:'HD-FX-01',time:'2026-09-01 22:48',resp:'2026-09-01 22:49',end:'2026-09-01 23:05',ct:'床位呼叫',no:'CQ032144',cs:'已接通',rs:'处理完成',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101-2',out:'否',reason:'失眠需助眠指导',info:'健康指导并纳入随访关注,无需药物介入。'},
{dev:'HC-TY-01',to:'HD-TY-01',time:'2026-09-07 06:02',resp:'',end:'—',ct:'床位呼叫',no:'CQ022317',cs:'未接通',rs:'等待处理',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 301-1',out:'',reason:'晨起呼叫后设备侧无声',info:'终端未接听,值班员正现场核实;超过 10 分钟将联动告警考核。'},
{dev:'HC-JB-01',to:'HD-JB-01',time:'2026-09-06 05:36',resp:'2026-09-06 05:37',end:'2026-09-06 06:01',ct:'床位呼叫',no:'CQ029905',cs:'已接通',rs:'处理完成',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1',out:'否',reason:'哮喘吸入剂用完需应急配药',info:'值班医生送备用吸入剂并预约复诊。'}
];
/* 终端呼出:值班室查房呼叫 */
/* 终端呼出:值班室查房呼叫,无救助状态;被呼床位空置时详情不关联员工 */
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'}
{from:'HD-FX-01',to:'HC-FX-01',time:'2026-09-07 07:10',resp:'2026-09-07 07:11',end:'2026-09-07 07:13',ct:'语音呼叫',cs:'已接听',no:'CQ021578',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101-1'},
{from:'HD-FX-01',to:'HC-FX-04',time:'2026-09-06 21:30',resp:'2026-09-06 21:31',end:'2026-09-06 21:35',ct:'视频呼叫',cs:'已接听',no:'CQ032144',station:'樊学站点基地',space:'樊学倒班宿舍1号楼 · 101室'},
{from:'HD-MZ-01',to:'HC-MZ-01',time:'2026-09-06 20:05',resp:'—',end:'—',ct:'语音呼叫',cs:'未接听',no:'CQ045130',station:'麻子山站点',space:'麻子山倒班宿舍楼 · 101室'},
{from:'HD-TY-01',to:'HC-TY-01',time:'2026-09-05 19:50',resp:'2026-09-05 19:51',end:'2026-09-05 19:54',ct:'视频呼叫',cs:'已接听',no:'CQ022317',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 301-1'},
{from:'HD-TY-01',to:'HC-TY-02',time:'2026-09-05 08:20',resp:'—',end:'2026-09-05 08:21',ct:'一键呼叫',cs:'已挂断',no:'CQ033096',station:'桐园站点基地',space:'桐园倒班宿舍1号楼 · 201室'},
{from:'HD-XF-01',to:'HC-XF-02',time:'2026-09-04 18:40',resp:'—',end:'—',ct:'语音呼叫',cs:'未接听',no:'',station:'西峰基地',space:'西峰基地综合宿舍楼 · 302-1'},
{from:'HD-JB-01',to:'HC-JB-01',time:'2026-09-04 07:15',resp:'2026-09-04 07:16',end:'2026-09-04 07:19',ct:'视频呼叫',cs:'已接听',no:'CQ036441',station:'靖边气田站点',space:'靖边气田倒班宿舍楼 · 401-1'},
{from:'HD-JB-01',to:'HC-JB-01',time:'2026-09-01 20:30',resp:'2026-09-01 20:31',end:'2026-09-01 20:36',ct:'语音呼叫',cs:'已接听',no:'CQ029905',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 headIn(){return '<tr><th style="width:56px;">序号</th><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="width:56px;">序号</th><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><th style="width:80px;">操作</th></tr>';}
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;});
var ke=document.getElementById('inName').value.trim(),kn=document.getElementById('inNo').value.trim().toUpperCase();
var kd=document.getElementById('inDev').value.trim().toUpperCase();
var tc=document.getElementById('inType').value,sc=document.getElementById('inStatus').value,sr=document.getElementById('inRescue').value;
var list=incoming.filter(function(r){
var p=empProfiles[r.no]||{};
return (!ke||(p.name||'').indexOf(ke)>=0)&&(!kn||r.no.toUpperCase().indexOf(kn)>=0)&&r.dev.toUpperCase().indexOf(kd)>=0&&(!tc||r.ct===tc)&&(!sc||r.cs===sc)&&(!sr||r.rs===sr)&&r.time.slice(0,10)>=f&&r.time.slice(0,10)<=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>'
var i=incoming.indexOf(r),p=empProfiles[r.no]||{};
return '<tr><td>'+(i+1)+'</td><td style="font-family:monospace;">'+r.dev+'</td><td>'+r.time+'</td><td>'+r.ct+'</td><td>'+(p.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+' 次';
+'<td>'+r.station+'</td><td>'+r.space+'</td><td><button class="btn btn-link" onclick="openDetailIn('+i+')">详细信息</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="11">没有符合条件的呼叫记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.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;});
var kf=document.getElementById('outFrom').value.trim().toUpperCase();
var kt=document.getElementById('outTo').value.trim().toUpperCase();
var tc2=document.getElementById('outType').value,sc2=document.getElementById('outStatus').value;
var list2=outgoing.filter(function(r){return r.from.toUpperCase().indexOf(kf)>=0&&r.to.toUpperCase().indexOf(kt)>=0&&(!tc2||r.ct===tc2)&&(!sc2||r.cs===sc2)&&r.time.slice(0,10)>=f&&r.time.slice(0,10)<=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+' 次';
var i=outgoing.indexOf(r);
return '<tr><td>'+(i+1)+'</td><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>'
+'<td><button class="btn btn-link" onclick="openDetailOut('+i+')">详细信息</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的呼叫记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list2.length+' 条记录';
document.getElementById('cardTitle').textContent='终端呼出记录';
}
}
function filter(arr,fn){return arr.filter(fn);}
function day(dt){return dt.slice(0,10);}
/* [交互] TAB 切换:呼入/呼出各自独立筛选组与导出按钮组 */
function switchTab(t){
curTab=t;
document.getElementById('tabIn').classList.toggle('active',t==='in');
document.getElementById('tabOut').classList.toggle('active',t==='out');
fillCallTypeFilters();renderAll();
document.getElementById('fgrpIn').style.display=t==='in'?'contents':'none';
document.getElementById('fgrpOut').style.display=t==='out'?'contents':'none';
document.getElementById('inBtns').style.display=t==='in'?'flex':'none';
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)
function resetFilter(){
['inName','inNo','inDev','inType','inStatus','inRescue','outFrom','outTo','outType','outStatus'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('filterFrom').value='2026-09-01';
document.getElementById('filterTo').value='2026-09-07';
renderAll();
}
function dk(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><span class="dk">'+k+'</span><span class="dv">'+v+'</span></div>';}
function ik(k,v,full){return '<div class="ik'+(full?' ik--full':'')+'"><b>'+k+'</b><span>'+(v||'—')+'</span></div>';}
/* 员工信息蓝底卡:未关联床位人员时给出占位文案 */
function empCard(no){
var p=empProfiles[no];
if(!p)return '<div class="info-card info-card--blue"><div class="info-empty">暂未关联员工信息</div></div>';
return '<div class="info-card info-card--blue">'
+ik('姓名',p.name)+ik('性别',p.sex)+ik('年龄',p.age+' 岁')+ik('员工编号',no)
+ik('单位',p.unit)+ik('部门',p.dept)+ik('联系电话',p.phone)+ik('慢病信息',p.chronic)
+'</div>';
}
/* 宿舍信息灰底卡:从「楼栋 · 床位」拆出楼栋与床位号 */
function dormCard(r){
var seg=r.space.split(' · ');
return '<div class="info-card info-card--grey">'
+ik('宿舍床位',seg[1])+ik('所属楼栋',seg[0])+ik('所属站点',r.station)
+ik('详细地址',r.station+' '+r.space,true)
+'</div>';
}
/* 呼入工单详情:呼叫三段式时间+员工/宿舍/急救三区块 */
function openDetailIn(i){
var r=incoming[i];
var stage=r.cs==='已接通'?'<span class="badge badge--success">已接听</span>':'<span class="badge badge--danger">未接听</span>';
var emg=r.rs==='处理完成'
? '<div class="info-card">'
+ik('是否出勤',r.out==='是'?'是':'否')
+ik('出勤救护车',r.amb||'—')+ik('出勤司机',r.drv||'—')+ik('随车医护',r.med||'—')+ik('出勤日期',r.adate||'—')
+ik('救助信息',r.info,true)+'</div>'
: '<div class="info-card info-card--grey" style="grid-template-columns:1fr;"><div class="info-empty">暂无急救信息</div></div>';
document.getElementById('detailBody').innerHTML=
'<div class="ct-head">'+r.ct+' '+stage+'<span class="st">'+r.rs+'</span></div>'
+'<div class="detail-grid">'
+dk('呼叫时间',r.time)+dk('响应时间',r.resp)
+dk('结束时间',r.end)+dk('呼叫类型',r.ct)
+dk('主呼设备',r.dev)+dk('被呼设备',r.to)
+'</div>'
+'<div class="form-section">员工信息</div>'+empCard(r.no)
+'<div class="form-section">宿舍信息</div>'+dormCard(r)
+'<div class="form-section">急救信息</div>'+emg;
document.getElementById('detailMask').classList.add('open');
}
/* 呼出工单详情:无救助处置环节,急救信息固定占位 */
function openDetailOut(i){
var r=outgoing[i];
var stage=r.cs==='已接听'?'<span class="badge badge--success">已接听</span>':'<span class="badge badge--danger">未接听</span>';
document.getElementById('detailBody').innerHTML=
'<div class="ct-head">'+r.ct+' '+stage+'<span class="st">'+r.cs+'</span></div>'
+'<div class="detail-grid">'
+dk('呼叫时间',r.time)+dk('响应时间',r.resp)
+dk('结束时间',r.end)+dk('呼叫类型',r.ct)
+dk('主呼设备',r.from)+dk('被呼设备',r.to)
+'</div>'
+'<div class="form-section">员工信息</div>'+empCard(r.no)
+'<div class="form-section">宿舍信息</div>'+dormCard(r)
+'<div class="form-section">急救信息</div><div class="info-card info-card--grey" style="grid-template-columns:1fr;"><div class="info-empty">暂无急救信息</div></div>';
document.getElementById('detailMask').classList.add('open');
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
function doExport(){showToast('导出任务已提交,完成后可在「查看导出任务」下载');}
/* 异步导出任务记录 */
var taskRecords=[
['呼叫记录_2026-09-01至2026-09-07.xlsx','2026-09-07 08:02','导出成功'],
['呼叫记录_2026-08-25至2026-08-31.xlsx','2026-09-01 09:15','导出成功'],
['呼叫记录_2026-08-01至2026-08-31.xlsx','2026-08-31 18:20','导出中']
];
function openTaskRecord(){
document.getElementById('taskBody').innerHTML=taskRecords.map(function(t){
return '<tr><td>'+t[0]+'</td><td>'+t[1]+'</td><td><span class="badge '+(t[2]==='导出成功'?'badge--success':'badge--warning')+'">'+t[2]+'</span></td>'
+'<td>'+(t[2]==='导出成功'?'<button class="btn btn-link" onclick="showToast(\'文件开始下载\')">下载</button>':'—')+'</td></tr>';
}).join('');
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 二级分组菜单展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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();
renderAll();
</script>
</body>
</html>
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>设备分配 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,26 +30,34 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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: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;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1250px;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -68,17 +76,25 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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;}
.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-sm);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;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border-bottom:1px solid var(--border);padding:7px 8px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:center;}
.record-table td{border-bottom:1px solid #F0F0F0;padding:8px;font-size:var(--font-sm);text-align:center;}
</style>
</head>
<body>
@@ -89,8 +105,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -101,18 +117,33 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
<div class="sidebar-group open" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub active">分配管理</a>
</div>
</div>
<div class="sidebar-group" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<a href="bed-timeout-alarm.html" class="sidebar-item">告警记录</a>
<a href="bed-stat.html" class="sidebar-item">汇总统计</a>
</aside>
<!-- 主内容 -->
@@ -144,22 +175,23 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<span class="table-card__title">设备分配</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" onclick="openAllocImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openTaskRecord('alloc')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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="openTaskRecord('export')"><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>
<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>
<th style="width:56px;">序号</th><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:150px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
@@ -172,7 +204,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 分配弹窗:类型+楼栋→宿舍→(床位)级联 -->
<div class="modal-mask" id="allocMask">
<div class="modal">
<div class="modal__title">分配设备 · <span id="aCode"></span></div>
<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">
@@ -201,11 +233,54 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 分配导入:按设备编号+空间批量绑定 -->
<div class="modal-mask" id="allocImportMask">
<div class="modal">
<div class="modal__title">批量导入数据</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="closeAllocImport()">取消</button>
<button class="btn btn-primary" onclick="doAllocImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 分配导入/导出任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal--wide">
<div class="modal__title" id="taskTitle">设备分配导出任务</div>
<table class="record-table" id="taskTable"></table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</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="doUnbind()">确认解除</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},
@@ -243,15 +318,16 @@ function renderTable(){
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>'
?'<button class="btn btn-link" onclick="openAlloc('+i+')">重新分配</button><button class="btn btn-link--danger" onclick="askUnbind('+i+')">解除分配</button>'
:'<button class="btn btn-link" onclick="openAlloc('+i+')">设备分配</button>';
return '<tr><td>'+(i+1)+'</td><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>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的设备</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;
@@ -287,10 +363,40 @@ function confirmAlloc(){
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+'」已解除绑定,历史见分配记录');
/* [交互] 解除分配 */
var unbindIdx=-1;
function askUnbind(i){
unbindIdx=i;
document.getElementById('confirmText').textContent='确认解除「'+devices[i].code+'」与「'+devices[i].space+'」的分配?';
document.getElementById('confirmMask').classList.add('open');
}
function closeConfirm(){document.getElementById('confirmMask').classList.remove('open');}
function doUnbind(){
var r=devices[unbindIdx];r.alloc=0;r.type=0;r.space='';r.time='';
closeConfirm();renderTable();showToast('「'+r.code+'」已解除分配,历史见分配记录');
}
/* [交互] 分配导入 */
function openAllocImport(){document.getElementById('allocImportMask').classList.add('open');}
function closeAllocImport(){document.getElementById('allocImportMask').classList.remove('open');}
function doAllocImport(){closeAllocImport();showToast('分配导入任务已提交,请到「查看导入任务」查看结果');}
/* [交互] 分配导入/导出任务(异步任务演示) */
var taskRecords={
export:[['设备分配清单-20260907.xlsx','2026-09-07 15:26:18','成功'],['设备分配清单-20260831.xlsx','2026-08-31 11:14:52','成功']],
alloc:[['设备分配-模板.xlsx','2026-09-06 09:47:23','成功:绑定 11 台、失败 1 台'],['设备分配-8月.xlsx','2026-08-25 16:31:40','成功:绑定 9 台、失败 0 条']]
};
function openTaskRecord(type){
document.getElementById('taskTitle').textContent=type==='export'?'设备分配导出任务':'设备分配导入任务';
var isExp=type==='export';
var head='<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>';
var rows=taskRecords[type].map(function(r){
return '<tr><td>'+r[0]+'</td><td>'+r[1]+'</td><td><span class="badge badge--success">'+r[2]+'</span></td><td>'+(isExp?'<button class="btn btn-link" onclick="showToast(\'文件下载中…\')">下载</button>':'<button class="btn btn-link" onclick="showToast(\'导入明细下载中…\')">下载明细</button>')+'</td></tr>';
}).join('');
document.getElementById('taskTable').innerHTML=head+'<tbody>'+rows+'</tbody>';
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
+239 -46
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,30 +30,38 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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__head .btns{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;}
.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;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1560px;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -73,19 +81,23 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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{border:1px dashed var(--border);border-radius:var(--radius-md);padding:28px 16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);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;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border-bottom:1px solid var(--border);padding:7px 8px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:center;}
.record-table td{border-bottom:1px solid #F0F0F0;padding:8px;font-size:var(--font-sm);text-align:center;}
</style>
</head>
<body>
@@ -96,8 +108,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -108,18 +120,33 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
<div class="sidebar-group open" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub active">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<a href="bed-timeout-alarm.html" class="sidebar-item">告警记录</a>
<a href="bed-stat.html" class="sidebar-item">汇总统计</a>
</aside>
<!-- 主内容 -->
@@ -151,10 +178,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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">
@@ -164,10 +187,15 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<span class="table-card__title" id="cardTitle">呼叫设备</span>
<div class="btns">
<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-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openTaskRecord('import')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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-danger" onclick="batchDelete()">批量删除</button>
</div>
</div>
<div class="table-card__content">
@@ -180,10 +208,75 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
<input class="form-control" id="eCode" placeholder="全局唯一,如:HC-FX-05">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> <span id="eNameLabel">设备名称</span></span>
<input class="form-control" id="eName" placeholder="如:床头呼叫按钮">
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">设备型号</span>
<input class="form-control" id="eModel" placeholder="如:KQ-P1">
</div>
<div class="form-item">
<span class="form-label">入库日期</span>
<input class="form-control" id="eStore" type="date">
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">设备状态</span>
<select class="form-control" id="eStatus"><option value="0">正常</option><option value="1">故障</option><option value="2">停用</option></select>
</div>
<div class="form-item">
<span class="form-label">管理人</span>
<input class="form-control" id="eManager" placeholder="设备管理人姓名">
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">管理人电话</span>
<input class="form-control" id="eMphone" placeholder="11位手机号">
</div>
<div class="form-item">
<span class="form-label">设备厂家</span>
<input class="form-control" id="eMfr" placeholder="生产厂家名称">
</div>
</div>
<div class="form-item">
<span class="form-label">厂家电话</span>
<input class="form-control" id="eMfrTel" placeholder="厂家售后服务电话">
</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" id="importTitle">批量导入呼叫设备</div>
<div class="modal__title" id="importTitle">导入呼叫设备</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属单位</span>
@@ -211,13 +304,35 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal--wide">
<div class="modal__title" id="taskTitle">设备导入任务</div>
<table class="record-table" id="taskTable"></table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认(单条/批量共用) -->
<div class="modal-mask" id="confirmMask">
<div class="modal">
<div class="modal__title" id="confirmHead">删除确认</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 statusBadge={'0':'badge--success','1':'badge--danger','2':'badge--muted'};
var statusText={'0':'正常','1':'故障','2':'停用'};
/* 呼叫设备:分配关系与「设备分配」「床位管理」勾稽(HC-FX-03 故障见超时告警演示) */
/* 呼叫设备:分配关系与「分配管理」「床位信息」勾稽(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'},
@@ -241,9 +356,11 @@ var termDevices=[
{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 tabName(){return 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>';
return '<tr><th style="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>'
+'<th style="width:56px;">序号</th><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:110px;">操作</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;
@@ -252,13 +369,14 @@ function renderAll(){
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>'
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><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>';
+'<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="14">没有符合条件的设备</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'?'呼叫设备台账':'终端设备台账';
document.getElementById('cardTitle').textContent=tabName();
}
function switchTab(t){
curTab=t;
@@ -275,19 +393,61 @@ function onFilterUnit(){
renderAll();
}
function resetFilter(){['filterUnit','filterDepart','filterStatus'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterCode').value='';onFilterUnit();}
/* [交互] 状态变更:正常↔故障↔停用 轮转演示 */
function openEditStatus(i){
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
var cks=[].slice.call(document.querySelectorAll('.rowCk'));
document.getElementById('ckAll').checked=cks.length>0&&cks.every(function(c){return c.checked;});
}
function batchDelete(){
var ids=[].slice.call(document.querySelectorAll('.rowCk')).filter(function(c){return c.checked;}).map(function(c){return +c.value;});
if(!ids.length){showToast('请选择需要删除的数据');return;}
delIdx=-1;delBatch=ids;
document.getElementById('confirmText').textContent='确认删除选中的 '+ids.length+' 台'+tabName()+'?';
document.getElementById('confirmMask').classList.add('open');
}
/* [交互] 新增/编辑设备 */
var editIdx=-1;
function onEditUnit(){
var u=document.getElementById('eUnit').value;
document.getElementById('eDepart').innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
}
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=(i<0?'新增':'编辑')+tabName();
document.getElementById('eNameLabel').textContent=curTab==='call'?'设备名称':'终端名称';
document.getElementById('eUnit').innerHTML='<option value="">请选择单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
if(i>=0){
var r=(curTab==='call'?callDevices:termDevices)[i];
document.getElementById('eUnit').value=r.unit;onEditUnit();
document.getElementById('eDepart').innerHTML='<option value="">请选择部门</option>'+unitDeparts[r.unit].map(function(d){return '<option'+(d===r.depart?' selected':'')+'>'+d+'</option>';}).join('');
document.getElementById('eCode').value=r.code;document.getElementById('eName').value=r.name;
document.getElementById('eModel').value=r.model;document.getElementById('eStore').value=r.store;
document.getElementById('eStatus').value=r.status;document.getElementById('eManager').value=r.manager;
document.getElementById('eMphone').value=r.mphone;document.getElementById('eMfr').value=r.mfr;
document.getElementById('eMfrTel').value=r.mfrTel;
}else{
document.getElementById('eUnit').value='';onEditUnit();
['eCode','eName','eModel','eStore','eManager','eMphone','eMfr','eMfrTel'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('eStatus').value='0';
}
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,c=document.getElementById('eCode').value.trim(),n=document.getElementById('eName').value.trim();
if(!u||!d||!c||!n){showToast('请完整填写带 * 的必填项');return;}
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]);
var dup=callDevices.concat(termDevices).some(function(r){return r!==src[editIdx]&&r.code===c;});
if(dup){showToast('设备编号已存在,须全局唯一');return;}
var obj={unit:u,depart:d,code:c,name:n,model:document.getElementById('eModel').value.trim()||'—',store:document.getElementById('eStore').value||'—',status:document.getElementById('eStatus').value,manager:document.getElementById('eManager').value.trim()||'—',mphone:document.getElementById('eMphone').value.trim()||'—',mfr:document.getElementById('eMfr').value.trim()||'—',mfrTel:document.getElementById('eMfrTel').value.trim()||'—'};
if(editIdx<0)src.push(obj);else src[editIdx]=obj;
closeEdit();renderAll();showToast(editIdx<0?tabName()+'已新增':tabName()+'已更新');
}
/* [交互] 导入 */
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('importTitle').textContent='导入'+tabName();
document.getElementById('iUnit').innerHTML='<option value="">请选择单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
document.getElementById('iDepart').innerHTML='<option value="">请选择部门</option>';
document.getElementById('iUnit').onchange=function(){
var u=this.value;
@@ -298,8 +458,41 @@ function openImport(){
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')+' 台设备');
closeImport();showToast('导入任务已提交,请到「查看导入任务」查看结果');
}
/* [交互] 导入任务(异步任务演示) */
var taskRecords={
import:[['设备台账-模板.xlsx','2026-09-05 17:30:12','成功:新增 4 台、更新 1 台'],['设备台账-6月批次.xlsx','2026-06-23 10:11:39','成功:新增 2 台']]
};
function openTaskRecord(type){
document.getElementById('taskTitle').textContent='设备导入任务';
var head='<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>';
var rows=taskRecords[type].map(function(r){
return '<tr><td>'+r[0]+'</td><td>'+r[1]+'</td><td><span class="badge badge--success">'+r[2]+'</span></td><td>'+'<button class="btn btn-link" onclick="showToast(\'导入明细下载中…\')">下载明细</button>'+'</td></tr>';
}).join('');
document.getElementById('taskTable').innerHTML=head+'<tbody>'+rows+'</tbody>';
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 删除 */
var delIdx=-1,delBatch=null;
function askDelete(i){
delIdx=i;delBatch=null;
var src=curTab==='call'?callDevices:termDevices;
document.getElementById('confirmText').textContent='确认删除「'+src[i].code+' · '+src[i].name+'」?';
document.getElementById('confirmMask').classList.add('open');
}
function closeConfirm(){document.getElementById('confirmMask').classList.remove('open');}
function doDelete(){
var src=curTab==='call'?callDevices:termDevices;
if(delBatch){
delBatch.sort(function(a,b){return b-a;}).forEach(function(i){src.splice(i,1);});
showToast('已批量删除 '+delBatch.length+' 台设备');delBatch=null;
}else{src.splice(delIdx,1);showToast('设备已删除');}
closeConfirm();renderAll();
}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
+175 -49
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宿舍管理 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,27 +30,34 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
table{width:100%;border-collapse:separate;border-spacing:0;min-width:1300px;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -63,6 +70,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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);}
.link-num{color:var(--primary);cursor:pointer;text-decoration:underline;}
.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;}
@@ -71,19 +79,23 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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--list{width:900px;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{font-size:var(--font-sm);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{border:1px dashed var(--border);border-radius:var(--radius-md);padding:28px 16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);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;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border-bottom:1px solid var(--border);padding:7px 8px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:center;}
.record-table td{border-bottom:1px solid #F0F0F0;padding:8px;font-size:var(--font-sm);text-align:center;}
</style>
</head>
<body>
@@ -94,8 +106,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -106,18 +118,33 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group open" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub active">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<a href="bed-timeout-alarm.html" class="sidebar-item">告警记录</a>
<a href="bed-stat.html" class="sidebar-item">汇总统计</a>
</aside>
<!-- 主内容 -->
@@ -133,7 +160,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
<div class="filter-item">
<label>宿舍名称</label>
<input class="filter-input" id="filterName" placeholder="如:101" oninput="renderTable()">
<input class="filter-input" id="filterName" placeholder="请输入宿舍名称" oninput="renderTable()">
</div>
<div class="filter-item">
<label>是否分配设备</label>
@@ -147,27 +174,26 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<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>新增宿舍
<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>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openTaskRecord('import')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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-danger" onclick="batchDelete()">批量删除</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="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>
<th style="width:56px;">序号</th><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>
@@ -178,10 +204,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</main>
<!-- [交互] 新增/编辑宿舍 -->
<!-- [交互] 新增/编辑宿舍信息 -->
<div class="modal-mask" id="editMask">
<div class="modal--wide">
<div class="modal__title" id="editTitle">新增宿舍</div>
<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>
@@ -199,7 +225,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="form-row">
<div class="form-item">
<span class="form-label">负责人</span>
<input class="form-control" id="eManager" placeholder="宿舍">
<input class="form-control" id="eManager" placeholder="宿舍负责人">
</div>
<div class="form-item">
<span class="form-label">负责人电话</span>
@@ -216,7 +242,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 批量导入:选择楼栋后上传宿舍清单 -->
<div class="modal-mask" id="importMask">
<div class="modal">
<div class="modal__title">批量导入宿舍</div>
<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>
@@ -229,7 +255,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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> · 导入后将按「床位数」自动生成床位记录
<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>
@@ -238,12 +264,33 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 删除确认 -->
<!-- [交互] 床位数量下钻:该宿舍的床位清单(列与「床位信息」一致) -->
<div class="modal-mask" id="bedMask">
<div class="modal--list">
<div class="modal__title" id="bedTitle">宿舍床位清单</div>
<table class="record-table">
<thead><tr><th>床位编号</th><th>是否分配员工</th><th>员工姓名</th><th>员工编号</th><th>员工所属单位</th><th>员工所属部门</th><th>员工联系方式</th></tr></thead>
<tbody id="bedTbody"></tbody>
</table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeBed()">关闭</button></div>
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal--wide">
<div class="modal__title" id="taskTitle">宿舍导入任务</div>
<table class="record-table" id="taskTable"></table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</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__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>
@@ -254,9 +301,9 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* 楼栋清单与「楼栋管理」页勾稽 */
/* 楼栋清单与「楼栋信息」页勾稽 */
var buildings=['樊学倒班宿舍1号楼','麻子山倒班宿舍楼','桐园倒班宿舍1号楼','西峰基地综合宿舍楼','靖边气田倒班宿舍楼'];
/* alloc:绑定到宿舍本身的设备(房间级呼叫);床位级绑定见「设备分配」 */
/* 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:''},
@@ -273,6 +320,27 @@ var dorms=[
{name:'401室',building:'靖边气田倒班宿舍楼',beds:4,manager:'刘玉梅',mobile:'13991923745',alloc:0,device:''},
{name:'402室',building:'靖边气田倒班宿舍楼',beds:2,manager:'呼建国',mobile:'13991923870',alloc:0,device:''}
];
/* [交互] 床位下钻演示数据:员工池与「床位信息」页一致(key=楼栋|床位编号) */
var empLite={
'CQ021578':{name:'石维国',unit:'采油一厂',depart:'樊学作业区',phone:'139****3456'},
'CQ032144':{name:'李桂芳',unit:'采油一厂',depart:'樊学作业区',phone:'139****7788'},
'CQ019203':{name:'曲志明',unit:'采油一厂',depart:'樊学作业区',phone:'139****7208'},
'CQ045130':{name:'杨慧敏',unit:'采油一厂',depart:'麻子山作业区',phone:'139****5012'},
'CQ033096':{name:'赵万福',unit:'采油二厂',depart:'桐园作业区',phone:'139****6630'},
'CQ022317':{name:'徐立成',unit:'第四采油厂',depart:'西峰作业区(轮训)',phone:'139****4906'},
'CQ010564':{name:'高建军',unit:'第四采油厂',depart:'西峰作业区',phone:'139****4320'},
'CQ041822':{name:'闫婷',unit:'第四采油厂',depart:'西峰作业区',phone:'139****4531'},
'CQ044871':{name:'白瑞',unit:'采气一厂',depart:'靖边气田(轮训)',phone:'139****2317'},
'CQ027710':{name:'张秀兰',unit:'采油二厂',depart:'姬圪台作业区',phone:'139****4415'},
'CQ036441':{name:'刘玉梅',unit:'采气一厂',depart:'靖边气田',phone:'139****3745'},
'CQ029905':{name:'呼建国',unit:'采气一厂',depart:'靖边气田',phone:'139****3870'}
};
var bedAssigns={
'樊学倒班宿舍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'
};
function initFilters(){
var opts='<option value="">全部楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('filterBuilding').innerHTML=opts;
@@ -284,18 +352,34 @@ function renderTable(){
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>'
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><td style="font-weight:500;">'+r.name+'</td><td>'+r.building+'</td><td><span class="link-num" onclick="openBed('+i+')">'+r.beds+'</span></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>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的宿舍</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 间 · 房间级设备 '+dorms.filter(function(r){return r.alloc;}).length+' 台';
document.getElementById('ckAll').checked=false;
}
function resetFilter(){['filterBuilding','filterAlloc'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';renderTable();}
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
var cks=[].slice.call(document.querySelectorAll('.rowCk'));
document.getElementById('ckAll').checked=cks.length>0&&cks.every(function(c){return c.checked;});
}
function batchDelete(){
var ids=[].slice.call(document.querySelectorAll('.rowCk')).filter(function(c){return c.checked;}).map(function(c){return +c.value;});
if(!ids.length){showToast('请选择需要删除的数据');return;}
delIdx=-1;delBatch=ids;
document.getElementById('confirmText').textContent='确认删除选中的 '+ids.length+' 间宿舍?其下床位将一并失效。';
document.getElementById('confirmMask').classList.add('open');
}
/* [交互] 新增/编辑 */
var editIdx=-1;
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=i<0?'新增宿舍':'编辑宿舍';
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;
@@ -309,27 +393,69 @@ function openEdit(i){
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;}
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 条床位');
closeImport();showToast('导入任务已提交,请到「查看导入任务」查看结果');
}
var delIdx=-1;
/* [交互] 导入任务(异步任务演示) */
var taskRecords={
import:[['宿舍清单-模板.xlsx','2026-09-05 17:02:26','成功:新增 6 间、生成 22 条床位'],['宿舍清单-8月.xlsx','2026-08-20 09:48:53','成功:新增 4 间']]
};
function openTaskRecord(type){
document.getElementById('taskTitle').textContent='宿舍导入任务';
var head='<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>';
var rows=taskRecords[type].map(function(r){
return '<tr><td>'+r[0]+'</td><td>'+r[1]+'</td><td><span class="badge badge--success">'+r[2]+'</span></td><td>'+'<button class="btn btn-link" onclick="showToast(\'导入明细下载中…\')">下载明细</button>'+'</td></tr>';
}).join('');
document.getElementById('taskTable').innerHTML=head+'<tbody>'+rows+'</tbody>';
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 床位数量下钻弹窗 */
var bedIdx=-1;
function openBed(i){
bedIdx=i;
var r=dorms[i];
document.getElementById('bedTitle').textContent='「'+r.building+' · '+r.name+'」床位清单';
var room=r.name.replace('室',''),rows='';
for(var nn=1;nn<=r.beds;nn++){
var code=room+'-'+nn,no=bedAssigns[r.building+'|'+code],e=no?empLite[no]:null;
rows+='<tr><td style="font-family:monospace;">'+code+'</td>'
+(e?'<td><span class="badge badge--success">是</span></td><td>'+e.name+'</td><td>'+no+'</td><td>'+e.unit+'</td><td>'+e.depart+'</td><td>'+e.phone+'</td>'
:'<td><span class="badge badge--muted">否</span></td><td>—</td><td>—</td><td>—</td><td>—</td><td>—</td>')
+'</tr>';
}
document.getElementById('bedTbody').innerHTML=rows;
document.getElementById('bedMask').classList.add('open');
}
function closeBed(){document.getElementById('bedMask').classList.remove('open');}
/* [交互] 删除 */
var delIdx=-1,delBatch=null;
function askDelete(i){
delIdx=i;
delIdx=i;delBatch=null;
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 doDelete(){
if(delBatch){
delBatch.sort(function(a,b){return b-a;}).forEach(function(i){dorms.splice(i,1);});
showToast('已批量删除 '+delBatch.length+' 间宿舍');delBatch=null;
}else{dorms.splice(delIdx,1);showToast('宿舍已删除');}
closeConfirm();renderTable();
}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
+140 -76
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>统计汇总 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,21 +30,29 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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;}
/* [交互] 统计周期:周/月/年/选时 四选一,选时才展开时间范围 */
.seg{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.seg__btn{height:32px;padding:0 14px;border:none;background:#fff;color:var(--text-primary);font-size:var(--font-sm);cursor:pointer;border-right:1px solid var(--border);}
.seg__btn:last-child{border-right:none;}
.seg__btn.active{background:var(--primary);color:#fff;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.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;}
@@ -53,8 +61,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -62,6 +70,20 @@ tfoot td{padding:10px 7px;text-align:center;font-size:var(--font-sm);font-weight
.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--md{width:640px;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;margin-top:12px;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:left;}
.record-table td{border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);color:var(--text-primary);}
.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;}
</style>
</head>
<body>
@@ -72,8 +94,8 @@ tfoot td{padding:10px 7px;text-align:center;font-size:var(--font-sm);font-weight
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -87,67 +109,71 @@ tfoot td{padding:10px 7px;text-align:center;font-size:var(--font-sm);font-weight
<!-- 侧边栏(当前一级分组「床头呼叫」的二级菜单) -->
<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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<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;">
<div class="seg">
<button class="seg__btn active" id="segWeek" onclick="setPeriod('week')">周统计</button>
<button class="seg__btn" id="segMonth" onclick="setPeriod('month')">月统计</button>
<button class="seg__btn" id="segYear" onclick="setPeriod('year')">年统计</button>
<button class="seg__btn" id="segCustom" onclick="setPeriod('custom')">选时统计</button>
</div>
</div>
<div class="filter-item" id="rangeItem" style="display:none;">
<label>时间范围</label>
<input class="filter-input" type="date" id="filterFrom" value="2026-08-31" 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;">
<input class="filter-input" type="date" id="filterTo" value="2026-09-06" onchange="render()" style="min-width:130px;">
<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>
</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 -->
<!-- [交互] 统计维度 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 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>
<span class="table-card__title" id="cardTitle">部门统计</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" 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="openTaskRecord()"><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>
<div class="table-card__content">
<table>
@@ -160,10 +186,24 @@ tfoot td{padding:10px 7px;text-align:center;font-size:var(--font-sm);font-weight
</div>
</main>
<!-- [交互] 导出任务:异步导出任务列表,成功后可下载文件 -->
<div class="modal-mask" id="taskMask">
<div class="modal--md">
<div class="modal__header"><span>导出任务</span><button class="modal__close" onclick="closeTask()">&times;</button></div>
<div class="modal__body">
<table class="record-table">
<thead><tr><th style="width:46%;">文件名称</th><th style="width:28%;">导出时间</th><th style="width:14%;">状态</th><th style="width:12%;">操作</th></tr></thead>
<tbody id="taskBody"></tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 统计数据与已建页面勾稽:设备/已分配=设备分配页(12/11);呼叫8接通2未接通=呼叫记录呼入TAB;告警6=超时告警页 */
/* 统计数据与已建页面勾稽:设备/已分配=分配管理页;呼叫8接通2未接通=呼叫记录呼入;告警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},
@@ -172,37 +212,45 @@ var rows=[
{unit:'采气一厂',dep:'靖边气田',station:'靖边气田站点',dev:2,alloc:1,calls:2,ok:2,miss:0,done:2,undone:0,alarm:0}
];
var dim='dep';
var period='week';
/* 各周期对应的统计区间(演示口径:周=上一自然周,月=本月,年=本年度) */
function curRange(){
if(period==='week')return ['2026-09-01','2026-09-07'];
if(period==='month')return ['2026-09-01','2026-09-30'];
if(period==='year')return ['2026-01-01','2026-12-31'];
return [document.getElementById('filterFrom').value,document.getElementById('filterTo').value];
}
function head(){
var dimCol=dim==='dep'?'部门':'站点';
return '<tr><th style="min-width:100px;">单位</th><th style="min-width:120px;">'+dimCol+'</th>'
var dimCol=dim==='dep'?'部门':'站点名称';
return '<tr><th style="width:56px;">序号</th><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;}):[];
var rg=curRange(),f=rg[0],t=rg[1];
var inRange=!!(f&&t)&&!(t<'2026-09-01'||f>'2026-09-30');
var list=inRange?rows.slice():[];
document.getElementById('thead').innerHTML=head();
document.getElementById('tbody').innerHTML=list.map(function(r){
document.getElementById('tbody').innerHTML=list.map(function(r,i){
var dimCell=dim==='dep'?r.dep:r.station;
return '<tr><td class="td-name">'+r.unit+'</td><td class="td-name">'+dimCell+'</td>'
return '<tr><td>'+(i+1)+'</td><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>';
}).join('')||'<tr><td class="td-empty" colspan="11">'+(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>'
var tv=list.length?'<tr><td></td><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||'—');
document.getElementById('cardTitle').textContent=dim==='dep'?'部门统计':'站点统计';
document.getElementById('totalTip').textContent='共 '+list.length+' 行 · 统计区间 '+(f||'—')+' 至 '+(t||'—');
}
/* [交互] 周期切换:非选时隐藏时间范围并即时出数,选时默认展示上一周期区间 */
function setPeriod(p){
period=p;
['Week','Month','Year','Custom'].forEach(function(k){document.getElementById('seg'+k).classList.toggle('active',p===k.toLowerCase());});
document.getElementById('rangeItem').style.display=p==='custom'?'flex':'none';
render();
}
function switchDim(d){
dim=d;
@@ -210,7 +258,23 @@ function switchDim(d){
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 doExport(){showToast('导出任务已提交,完成后可在「查看导出任务」下载');}
/* 异步导出任务记录 */
var taskRecords=[
['床头呼叫汇总统计_2026-09-01至2026-09-07.xlsx','2026-09-07 08:10','导出成功'],
['床头呼叫汇总统计_2026-08月度.xlsx','2026-09-01 10:22','导出成功'],
['床头呼叫汇总统计_2026-08-25至2026-08-31.xlsx','2026-09-01 09:20','导出中']
];
function openTaskRecord(){
document.getElementById('taskBody').innerHTML=taskRecords.map(function(t){
return '<tr><td>'+t[0]+'</td><td>'+t[1]+'</td><td><span class="badge '+(t[2]==='导出成功'?'badge--success':'badge--warning')+'">'+t[2]+'</span></td>'
+'<td>'+(t[2]==='导出成功'?'<button class="btn btn-link" onclick="showToast(\'文件开始下载\')">下载</button>':'—')+'</td></tr>';
}).join('');
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 二级分组菜单展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
@@ -0,0 +1,426 @@
<!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;font-size:var(--font-sm);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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;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;}
.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:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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;}
.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-sm);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:none;}
input[type="date"].form-control{padding-right:6px;}
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-line{display:flex;align-items:center;gap:18px;font-size:var(--font-sm);}
.radio-line label{display:flex;align-items:center;gap:5px;cursor:pointer;}
.map-pick{border:1px solid var(--border);border-radius:var(--radius-md);background:#F0F6FC;height:170px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;cursor:pointer;color:var(--text-secondary);font-size:var(--font-sm);}
.map-pick:hover{border-color:var(--primary);color:var(--primary);}
.map-pick .picked{color:var(--primary);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-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.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-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group open" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub active">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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="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="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-primary" 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>
</div>
</div>
<div class="table-card__content">
<table>
<thead>
<tr>
<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:140px;">站点名称</th>
<th style="min-width:220px;">详细地址</th><th>负责人</th><th style="min-width:110px;">联系电话</th>
<th style="min-width:100px;">设立日期</th><th style="min-width:220px;">简介</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-item">
<span class="form-label"><span class="req">*</span> 站点名称</span>
<input class="form-control" id="eName" placeholder="如:樊学站点基地">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 地点</span>
<div class="radio-line">
<label><input type="radio" name="geoType" value="0" checked onchange="switchGeoType()"> 地图选点</label>
<label><input type="radio" name="geoType" value="1" onchange="switchGeoType()"> 手动输入</label>
</div>
</div>
<!-- 地图选点模式 -->
<div id="geoMapMode">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 地图选点</span>
<div class="map-pick" id="mapPick" onclick="demoPickPoint()">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M13 3c4 0 7 3 7 6.8 0 5-7 13.2-7 13.2S6 14.8 6 9.8C6 6 9 3 13 3z" stroke="#1890FF" stroke-width="1.6" fill="none"/><circle cx="13" cy="9.8" r="2.4" stroke="#1890FF" stroke-width="1.6"/></svg>
<span id="mapPickText">点击在地图上拾取站点坐标</span>
</div>
<div class="form-item" style="margin-bottom:0;">
<span class="form-label">选址地址</span>
<input class="form-control" id="eMapAddr" readonly placeholder="选点后自动回填地址">
</div>
</div>
</div>
<!-- 手动输入模式 -->
<div id="geoInputMode" style="display:none;">
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 经度</span>
<input class="form-control" id="eLng" placeholder="如:107.3412">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 纬度</span>
<input class="form-control" id="eLat" placeholder="如:36.1147">
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 详细地址</span>
<input class="form-control" id="eAddr2" placeholder="站点详细地址">
</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="form-item">
<span class="form-label">设立日期</span>
<input class="form-control" type="date" id="eSetUp">
</div>
<div class="form-item">
<span class="form-label">简介</span>
<textarea class="form-control" id="eDesc" placeholder="站点简介,如规模、覆盖班组等"></textarea>
</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="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 stations=[
{unit:'采油一厂',depart:'樊学作业区',name:'樊学站点基地',addr:'甘肃省环县樊学作业区基地营地',manager:'王守正',mobile:'13991203456',setUp:'2019-04-01',desc:'樊学作业区中心站点,设医疗点与应急值班室,覆盖倒班宿舍3栋。'},
{unit:'采油一厂',depart:'麻子山作业区',name:'麻子山站点',addr:'陕西省定边县麻子山作业区站点北门',manager:'安伟',mobile:'13991345012',setUp:'2020-06-15',desc:'麻子山作业区驻地站点,承担井区巡诊与急救联动。'},
{unit:'采油二厂',depart:'桐园作业区',name:'桐园站点基地',addr:'甘肃省华池县桐园作业区生活区西区',manager:'陈国栋',mobile:'13991567208',setUp:'2018-10-01',desc:'桐园作业区综合基地,含医疗点、健康小屋与倒班宿舍生活区。'},
{unit:'采油二厂',depart:'姬圪台作业区',name:'姬圪台站点',addr:'陕西省吴起县姬圪台作业区营地',manager:'刘志强',mobile:'13991642087',setUp:'2021-03-20',desc:'姬圪台作业区前沿站点,覆盖井场值班点5处。'},
{unit:'第四采油厂',depart:'西峰作业区',name:'西峰基地',addr:'甘肃省西峰市西峰基地食堂南侧',manager:'马文秀',mobile:'13991784320',setUp:'2017-09-01',desc:'第四采油厂机关基地,医疗点与救护车驻点合一。'},
{unit:'采气一厂',depart:'靖边气田',name:'靖边气田站点',addr:'陕西省靖边县靖边气田综合管理营地北侧',manager:'周铁祥',mobile:'13991923745',setUp:'2020-11-11',desc:'靖边气田处理厂站点,负责气田倒班人员床头呼叫值守。'}
];
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('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');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
depSel.dataset.tip='全部部门';fillOptions(depSel,deps);
renderTable();
}
function renderTable(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,k=document.getElementById('filterName').value.trim();
var list=stations.filter(function(s){return (!u||s.unit===u)&&(!d||s.depart===d)&&(!k||s.name.indexOf(k)>=0);});
document.getElementById('tbody').innerHTML=list.map(function(s){
var i=stations.indexOf(s);
return '<tr><td>'+(i+1)+'</td><td>'+s.unit+'</td><td>'+s.depart+'</td><td style="font-weight:500;">'+s.name+'</td>'
+'<td>'+s.addr+'</td><td>'+s.manager+'</td><td>'+s.mobile+'</td><td>'+s.setUp+'</td>'
+'<td style="white-space:normal;min-width:220px;">'+s.desc+'</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="10">没有符合条件的站点</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 个站点';
}
function resetFilter(){['filterUnit','filterDepart'].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');
depSel.innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
}
function geoTypeVal(){var r=document.querySelector('input[name="geoType"]:checked');return r?r.value:'0';}
function switchGeoType(){
var map=geoTypeVal()==='0';
document.getElementById('geoMapMode').style.display=map?'':'none';
document.getElementById('geoInputMode').style.display=map?'none':'';
}
/* [交互] 地图选点演示:点击拾取坐标并回填地址 */
var pickedPoint=null;
function demoPickPoint(){
pickedPoint={lng:'107.3412',lat:'36.1147'};
document.getElementById('mapPickText').innerHTML='<span class="picked">已选坐标:107.3412, 36.1147(点击可重选)</span>';
var addr=document.getElementById('eName').value.trim();
document.getElementById('eMapAddr').value='已拾取:'+(addr||'站点位置')+'(经度107.3412 纬度36.1147)';
showToast('地图选点完成');
}
function openEdit(i){
editIdx=i;
document.getElementById('editTitle').textContent=i<0?'新增站点':'编辑站点';
onEditUnit();
pickedPoint=null;document.getElementById('mapPickText').textContent='点击在地图上拾取站点坐标';
document.getElementById('eMapAddr').value='';
if(i>=0){
var s=stations[i];
document.getElementById('eUnit').value=s.unit;onEditUnit();
document.getElementById('eDepart').innerHTML='<option>请选择部门</option>'+unitDeparts[s.unit].map(function(d){return '<option'+(d===s.depart?' selected':'')+'>'+d+'</option>';}).join('');
document.getElementById('eName').value=s.name;
document.querySelector('input[name="geoType"][value="0"]').checked=true;switchGeoType();
document.getElementById('eMapAddr').value='已拾取:'+s.addr;
document.getElementById('mapPickText').innerHTML='<span class="picked">已选坐标:107.3412, 36.1147(点击可重选)</span>';
document.getElementById('eManager').value=s.manager;document.getElementById('eMobile').value=s.mobile;
document.getElementById('eSetUp').value=s.setUp;document.getElementById('eDesc').value=s.desc;
}else{
['eName','eManager','eMobile','eSetUp','eDesc','eLng','eLat','eAddr2'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('eUnit').value='';onEditUnit();
document.querySelector('input[name="geoType"][value="0"]').checked=true;switchGeoType();
}
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,n=document.getElementById('eName').value.trim();
if(!u||!d||!n){showToast('请完整填写带 * 的必填项');return;}
var addr;
if(geoTypeVal()==='0'){
if(!document.getElementById('eMapAddr').value){showToast('请先在地图上拾取站点坐标');return;}
addr=document.getElementById('eMapAddr').value;
}else{
var lng=document.getElementById('eLng').value.trim(),lat=document.getElementById('eLat').value.trim(),a2=document.getElementById('eAddr2').value.trim();
if(!lng||!lat||!a2){showToast('手动输入模式下经度/纬度/详细地址为必填');return;}
addr=a2;
}
var dup=stations.some(function(s,i){return i!==editIdx&&s.unit===u&&s.name===n;});
if(dup){showToast('该单位下已存在同名站点');return;}
var obj={unit:u,depart:d,name:n,addr:addr,
manager:document.getElementById('eManager').value.trim()||'—',
mobile:document.getElementById('eMobile').value.trim()||'—',
setUp:document.getElementById('eSetUp').value||'—',
desc:document.getElementById('eDesc').value.trim()||'—'};
if(editIdx<0)stations.push(obj);else stations[editIdx]=obj;
closeEdit();renderTable();showToast(editIdx<0?'站点已新增':'站点已更新');
}
/* [交互] 删除 */
var delIdx=-1;
function askDelete(i){
delIdx=i;
document.getElementById('confirmText').textContent='确认删除「'+stations[i].name+'」?';
document.getElementById('confirmMask').classList.add('open');
}
function closeConfirm(){document.getElementById('confirmMask').classList.remove('open');}
function doDelete(){stations.splice(delIdx,1);closeConfirm();renderTable();showToast('站点已删除');}
/* [交互] 侧栏分组展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
}
initFilters();onFilterUnit();
</script>
</body>
</html>
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超时未归告警 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,62 +30,54 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
/* [交互] 二级分组菜单展开/收起:点击分组标题切换 open 类,三级子项默认收起 */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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--md{width:640px;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;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;}
.modal__body{flex:1;overflow-y:auto;padding:16px 2px 4px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px;}
.record-table{width:100%;border-collapse:collapse;min-width:0;}
.record-table th{background:#FAFAFA;border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;text-align:left;}
.record-table td{border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);color:var(--text-primary);}
</style>
</head>
<body>
@@ -96,8 +88,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html" class="active">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -111,21 +103,36 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- 侧边栏(当前一级分组「床头呼叫」的二级菜单) -->
<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>
<div class="sidebar-group" id="grpDevice">
<div class="sidebar-group__label" onclick="toggleGroup('grpDevice')">设备管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-device.html" class="sidebar-item sidebar-item--sub">设备列表</a>
<a href="bed-device-allocate.html" class="sidebar-item sidebar-item--sub">分配管理</a>
</div>
</div>
<div class="sidebar-group" id="grpDorm">
<div class="sidebar-group__label" onclick="toggleGroup('grpDorm')">宿舍管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="bed-station.html" class="sidebar-item sidebar-item--sub">站点管理</a>
<a href="bed-building.html" class="sidebar-item sidebar-item--sub">楼栋信息</a>
<a href="bed-dormitory.html" class="sidebar-item sidebar-item--sub">宿舍信息</a>
<a href="bed-bed.html" class="sidebar-item sidebar-item--sub">床位信息</a>
</div>
</div>
<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>
<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>
@@ -147,43 +154,22 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<span class="table-card__title">告警记录</span>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" 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="openTaskRecord()"><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>
<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>
<th style="width:56px;">序号</th><th style="min-width:140px;">告警时间</th><th style="min-width:130px;">告警类型</th>
<th style="min-width:340px;">告警原因</th><th style="min-width:110px;">告警设备</th>
<th style="min-width:220px;">宿舍名称</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
@@ -192,53 +178,33 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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 class="modal-mask" id="taskMask">
<div class="modal--md">
<div class="modal__header"><span>导出任务</span><button class="modal__close" onclick="closeTask()">&times;</button></div>
<div class="modal__body">
<table class="record-table">
<thead><tr><th style="width:46%;">文件名称</th><th style="width:28%;">导出时间</th><th style="width:14%;">状态</th><th style="width:12%;">操作</th></tr></thead>
<tbody id="taskBody"></tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 告警数据:与呼叫记录/设备列表跨页勾稽——徐立成未接通升级、杨慧敏超时未派单、HC-FX-03 与 HD-XF-01 故障离线 */
/* 告警数据:与呼叫记录/设备列表跨页勾稽——徐立成未接通升级、杨慧敏超时未派单、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'}
{time:'2026-09-07 06:14',type:'呼叫超时未接听',reason:'床位呼叫(HC-TY-01)发起后 10 分钟内值班终端未接听,关联 2026-09-07 06:02 徐立成(CQ022317)呼入',dev:'HC-TY-01',dorm:'桐园倒班宿舍1号楼 · 301室'},
{time:'2026-09-06 20:16',type:'呼叫超时未接听',reason:'值班终端(HD-MZ-01)呼出语音呼叫 10 分钟内被呼侧未接听,员工巡检途中未携房卡',dev:'HC-MZ-01',dorm:'麻子山倒班宿舍楼 · 101室'},
{time:'2026-09-04 18:51',type:'呼叫超时未接听',reason:'值班终端(HD-XF-01)呼出语音呼叫 10 分钟内被呼侧未接听,被呼员工外出就餐',dev:'HC-XF-02',dorm:'西峰基地综合宿舍楼 · 302室'},
{time:'2026-09-04 09:20',type:'设备离线告警',reason:'呼叫设备心跳丢失超过 15 分钟,判定线路老化已派单维修(102-1 曲志明床位)',dev:'HC-FX-03',dorm:'樊学倒班宿舍1号楼 · 102室'},
{time:'2026-09-03 20:08',type:'救助处置超时',reason:'呼入已接通但 30 分钟内未派单到场处置,关联 2026-09-03 19:36 杨慧敏(CQ045130)踝扭伤呼入',dev:'HC-MZ-01',dorm:'麻子山倒班宿舍楼 · 101'},
{time:'2026-09-02 23:47',type:'设备离线告警',reason:'值班终端离线超过 15 分钟,疑似电源适配器故障,呼入转接至备用座机',dev:'HD-XF-01',dorm:'西峰基地值班室'}
];
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();
@@ -247,50 +213,30 @@ function render(){
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;
return '<tr><td>'+(i+1)+'</td><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:400px;">'+a.reason+'</td>'
+'<td style="font-family:monospace;">'+a.dev+'</td><td>'+a.dorm+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="6">没有符合条件的告警记录</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.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 doExport(){showToast('导出任务已提交,完成后可在「查看导出任务」下载');}
/* 异步导出任务记录 */
var taskRecords=[
['告警记录_2026-09-01至2026-09-07.xlsx','2026-09-07 08:05','导出成功'],
['告警记录_2026-08-25至2026-08-31.xlsx','2026-09-01 09:18','导出成功'],
['告警记录_2026-08-01至2026-08-31.xlsx','2026-08-31 18:24','导出中']
];
function openTaskRecord(){
document.getElementById('taskBody').innerHTML=taskRecords.map(function(t){
return '<tr><td>'+t[0]+'</td><td>'+t[1]+'</td><td><span class="badge '+(t[2]==='导出成功'?'badge--success':'badge--warning')+'">'+t[2]+'</span></td>'
+'<td>'+(t[2]==='导出成功'?'<button class="btn btn-link" onclick="showToast(\'文件开始下载\')">下载</button>':'—')+'</td></tr>';
}).join('');
document.getElementById('taskMask').classList.add('open');
}
function closeTask(){document.getElementById('taskMask').classList.remove('open');}
/* [交互] 二级分组菜单展开/收起 */
function toggleGroup(id){document.getElementById(id).classList.toggle('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);
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -53,35 +53,33 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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__label{font-size:var(--font-sm);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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -116,12 +114,12 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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);}
.drawer__toolbar .grow{margin-left:auto;font-size:var(--font-sm);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;}
.mini-table thead th{background:#FAFAFA;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
</style>
</head>
<body>
@@ -132,8 +130,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -177,7 +175,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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>
@@ -210,7 +207,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 操作栏 -->
<div class="action-bar">
<span class="action-bar__hint">医护人员数量来自「系统管理 → 用户管理」的医护角色统计,本页只读汇总;点击「已分配员工数量」可下钻名册</span>
</div>
<!-- 表格 -->
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -55,42 +55,40 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
/* 筛选栏 */
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -101,6 +99,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
/* 徽标 */
.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--muted{background:#F5F5F5;color:var(--text-secondary);border:1px solid var(--border);}
/* 分页 */
@@ -121,13 +120,18 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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:640px;}
.record-table{width:100%;min-width:0;border-collapse:collapse;font-size:var(--font-sm);margin-bottom:16px;}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.record-table th:last-child,.record-table td:last-child{position:static;box-shadow:none;}
.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{font-size:var(--font-sm);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{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
</style>
</head>
@@ -139,8 +143,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -184,7 +188,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
@@ -250,19 +253,14 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 操作栏:按钮随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>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><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>
</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>
<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-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><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>
</span>
<span class="action-bar__hint" id="tabHint">已分配员工在 PAD 端按所属医疗点接收任务</span>
</div>
<!-- 表格 -->
@@ -302,7 +300,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 批量导入弹窗 -->
<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__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>
@@ -316,6 +314,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal modal--wide">
<div class="modal__title">员工分配导入任务</div>
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>员工分配-模板.xlsx</td><td>2026-09-05 14:32:06</td><td><span class="badge badge--success">成功:绑定 45 人</span></td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
<tr><td>员工分配-8月.xlsx</td><td>2026-08-18 10:05:41</td><td><span class="badge badge--warning">部分成功:绑定 28 人、失败 3 人</span></td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
</tbody>
</table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeModal('taskMask')">关闭</button></div>
</div>
</div>
<!-- [交互] 取消分配确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
@@ -382,7 +395,6 @@ function switchTab(toAlloc){
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();
}
@@ -569,6 +581,8 @@ function doImport(){
closeModal('importMask');
showToast('导入任务已提交,完成后将推送站内通知');
}
/* [交互] 查看导入任务(异步任务演示) */
function openImportTasks(){document.getElementById('taskMask').classList.add('open');}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -48,27 +48,25 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
/* 筛选栏 */
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -112,7 +110,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -133,7 +131,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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 .dk{width:130px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
/* 弹窗 */
@@ -143,7 +141,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
/* 数据管理(出勤记录)所需样式 */
@@ -156,17 +154,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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{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;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;}
.record-table{width:100%;min-width:0;border-collapse:collapse;font-size:var(--font-sm);margin-bottom:16px;}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.record-table th:last-child,.record-table td:last-child{position:static;box-shadow:none;}
.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;}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.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);}
.duty-toolbar__hint{margin-left:auto;font-size:var(--font-sm);color:var(--text-placeholder);}
</style>
</head>
<body>
@@ -177,8 +179,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -222,7 +224,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
@@ -263,13 +264,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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="askDelete(checkedIds())">批量删除</button>
<span class="action-bar__hint">救护车与医疗点一对多,删除后出勤记录保留历史快照</span>
</div>
<!-- 表格 -->
@@ -384,7 +382,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-default" onclick="showToast('导出该救护车出勤记录功能演示')">导出</button>
<span class="duty-toolbar__hint" id="dutyHint"></span>
</div>
<div class="table-holder" style="max-height:calc(100vh - 300px);">
@@ -507,6 +505,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal modal--wide">
<div class="modal__title">救护车导入任务</div>
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>救护车台账-模板.xlsx</td><td>2026-09-04 11:26:08</td><td><span class="badge badge--success">成功:新增 5 辆</span></td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
<tr><td>救护车台账-8月.xlsx</td><td>2026-08-19 09:47:52</td><td><span class="badge badge--warning">部分成功:新增 3 辆、失败 1 辆</span></td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
</tbody>
</table>
<div class="modal__footer" style="margin-top:14px;"><button class="btn btn-default" onclick="closeModal('taskMask')">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
@@ -753,6 +766,8 @@ function doImport(){
closeModal('importMask');
showToast('导入任务已提交,完成后将推送站内通知');
}
/* [交互] 查看导入任务(异步任务演示) */
function openImportTasks(){document.getElementById('taskMask').classList.add('open');}
/* ===== 数据管理(单车出勤记录,原「救护车值班」页功能并入) ===== */
var staffPool=['石维国','李桂芳','曲志明','张秀兰','高建军','闫婷','刘玉梅','徐立成'];
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -48,16 +48,15 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
/* 筛选栏 */
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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);}
@@ -65,15 +64,15 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
/* 视图切换 + 操作栏 */
.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{border:none;background:transparent;padding:4px 14px;font-size:var(--font-sm);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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -124,7 +123,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -136,7 +135,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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{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-sm);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;}
@@ -147,10 +146,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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;}
@@ -165,7 +160,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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-pin__label{font-size:var(--font-xs);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;}
@@ -182,8 +177,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -227,7 +222,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
@@ -257,16 +251,13 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" 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>
<div class="view-switch">
<button id="btnList" class="active" onclick="switchView('list')">资源列表</button>
<button id="btnMap" onclick="switchView('map')">地图列表</button>
@@ -372,19 +363,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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">
@@ -537,56 +515,8 @@ function openDetail(id){
}
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 doExport(){showToast('医疗点列表已导出(演示)');}
/* ===== 删除确认 ===== */
function askDelete(id){
@@ -39,18 +39,16 @@ body{font-family:"Microsoft YaHei","PingFang SC",sans-serif;font-size:var(--font
/* 筛选栏 */
.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-item label{font-size:var(--font-sm);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{height:36px;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);}
@@ -58,14 +56,13 @@ body{font-family:"Microsoft YaHei","PingFang SC",sans-serif;font-size:var(--font
/* 操作栏 */
.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;}
thead th{position:sticky;top:0;background:#FAFAFA;font-weight:600;font-size:var(--font-sm);color:var(--text-primary);text-align:left;padding:10px 14px;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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;}
@@ -91,7 +88,7 @@ tbody tr:hover{background:#FAFCFF;}
.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{border:none;background:none;font-size:20px;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;}
@@ -134,8 +131,8 @@ textarea.form-control{height:72px;padding:8px 10px;resize:vertical;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -179,7 +176,6 @@ textarea.form-control{height:72px;padding:8px 10px;resize:vertical;}
</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>
@@ -212,7 +208,7 @@ textarea.form-control{height:72px;padding:8px 10px;resize:vertical;}
<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>新增
@@ -221,11 +217,7 @@ textarea.form-control{height:72px;padding:8px 10px;resize:vertical;}
<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>
<button class="btn btn-primary" 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>
</div>
<!-- 表格 -->
@@ -347,19 +339,6 @@ textarea.form-control{height:72px;padding:8px 10px;resize:vertical;}
</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>
@@ -590,23 +569,8 @@ function confirmDelete(){
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');
}
/* ===== 同步导出(存量台账,数据量有限) ===== */
function doExport(){showToast('医护人员列表已导出(演示)');}
renderTable();
</script>
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -53,37 +53,35 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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__label{font-size:var(--font-sm);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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -134,8 +132,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.mini-table thead th{background:#FAFAFA;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.in{color:#389E0D;font-weight:600;}
.out{color:#CF1322;font-weight:600;}
</style>
@@ -148,8 +146,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -193,7 +191,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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>
@@ -244,11 +241,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 操作栏 -->
<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-primary" 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>
<!-- 表格 -->
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -48,30 +48,28 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
/* 筛选栏 */
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -117,14 +115,14 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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{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-sm);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;}
@@ -142,8 +140,13 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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:640px;}
.record-table{width:100%;min-width:0;border-collapse:collapse;font-size:var(--font-sm);margin-bottom:16px;}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.record-table th:last-child,.record-table td:last-child{position:static;box-shadow:none;}
.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{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:24px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
</style>
</head>
@@ -155,8 +158,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -200,7 +203,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
@@ -236,22 +238,15 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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>
</div>
<!-- 表格 -->
@@ -353,6 +348,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal modal--wide">
<div class="modal__title">药品导入任务</div>
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>药品目录-模板.xlsx</td><td>2026-09-05 10:12:44</td><td><span class="badge badge--success">成功:新增 26 条</span></td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
<tr><td>药品目录-8月.xlsx</td><td>2026-08-21 16:03:27</td><td><span class="badge badge--warning">部分成功:新增 14 条、失败 2 条</span></td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
</tbody>
</table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeModal('taskMask')">关闭</button></div>
</div>
</div>
<!-- [交互] 删除确认弹窗 -->
<div class="modal-mask" id="delMask">
<div class="modal">
@@ -545,6 +555,8 @@ function doImport(){
closeModal('importMask');
showToast('导入任务已提交,完成后将推送站内通知');
}
/* [交互] 查看导入任务(异步任务演示) */
function openImportTasks(){document.getElementById('taskMask').classList.add('open');}
/* ===== 删除确认 ===== */
function askDelete(ids){
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -45,19 +45,16 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
/* 主内容区 */
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
@@ -67,8 +64,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -81,7 +78,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.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:22px;}
/* 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;}
@@ -100,7 +97,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
/* 表单 */
.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{font-size:var(--font-sm);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);}
@@ -108,10 +105,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
/* 内嵌小表格 */
.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;}
.mini-table thead th{background:#FAFAFA;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.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);}
.drawer__toolbar .grow{margin-left:auto;font-size:var(--font-sm);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;}
@@ -136,8 +133,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -181,7 +178,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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>
@@ -189,11 +185,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- 主内容 -->
<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">
@@ -284,10 +275,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
@@ -354,9 +341,9 @@ function renderTable(){
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 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 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>'
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -41,7 +41,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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{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-sm);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;}
@@ -50,8 +50,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -60,7 +60,6 @@ tbody tr:hover{background:#F8FBFF;}
.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);}
@@ -77,8 +76,8 @@ tbody tr:hover{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -138,7 +137,6 @@ tbody tr:hover{background:#F8FBFF;}
</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>
@@ -148,7 +146,6 @@ tbody tr:hover{background:#F8FBFF;}
</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>
@@ -158,7 +155,6 @@ tbody tr:hover{background:#F8FBFF;}
</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>
@@ -304,7 +300,7 @@ 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>';
+'<div>所属单位 <b>'+emp.unit+'</b></div><div>所属部门 <b>'+emp.dept+'</b></div><div>分配医疗点 <b>'+emp.point+'('+pointUnit[emp.point]+')</b></div>';
}
function switchTab(k){
document.querySelectorAll('.tab-btn').forEach(function(b){b.classList.toggle('active',b.dataset.tab===k);});
+20 -23
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,35 +40,33 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -104,7 +102,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
@@ -115,8 +113,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
</style>
</head>
<body>
@@ -127,8 +125,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -234,9 +232,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 操作栏 -->
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -297,10 +294,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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__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>
@@ -374,7 +371,7 @@ function resetOrg(){
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'},
@@ -405,7 +402,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'慢病台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,23 +40,21 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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);}
@@ -66,8 +64,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -100,7 +98,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -110,12 +108,12 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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{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-sm);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;}
.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:var(--font-xs);}
.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;}
@@ -131,8 +129,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -220,9 +218,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -308,10 +305,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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__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>
@@ -367,7 +364,7 @@ function switchTab(t){
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'},
@@ -398,7 +395,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:T('健康教育台账导出_','急救培训台账导出_')+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,23 +40,21 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -90,11 +88,11 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
</style>
</head>
<body>
@@ -105,8 +103,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -185,8 +183,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-export" onclick="showToast('导出名册功能演示')">导出</button>
<span class="action-bar__hint">名册来源为「员工分配」页的已分配员工;分配关系调整请前往该页操作</span>
<button class="btn btn-primary" 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>
</div>
<!-- 表格 -->
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,18 +40,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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;}
@@ -66,12 +66,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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);}
@@ -84,8 +83,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -107,7 +106,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -127,8 +126,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -263,9 +262,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" 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()"><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>
</main>
@@ -295,10 +293,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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__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>
@@ -400,7 +398,7 @@ function resetOrg(){
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'},
@@ -435,7 +433,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'随访任务导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
+22 -25
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,24 +40,22 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -84,13 +82,13 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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;}
@@ -105,7 +103,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -116,12 +114,12 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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{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-sm);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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
@@ -131,8 +129,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
</style>
</head>
<body>
@@ -143,8 +141,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -252,9 +250,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -419,10 +416,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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__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>
@@ -534,7 +531,7 @@ function resetOrg(){
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'},
@@ -565,7 +562,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'随访台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,36 +40,34 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -104,7 +102,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
@@ -121,8 +119,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -226,9 +224,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 操作栏 -->
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -275,10 +272,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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__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>
@@ -355,7 +352,7 @@ function resetOrg(){
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'},
@@ -386,7 +383,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'解读归档台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,18 +40,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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;}
@@ -66,21 +66,19 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -113,17 +111,17 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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{font-size:var(--font-sm);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;}
.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-sm);background:#FAFAFA;}
</style>
</head>
<body>
@@ -134,8 +132,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -245,9 +243,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 操作栏 -->
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -302,10 +299,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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__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>
@@ -391,7 +388,7 @@ function resetOrg(){
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'},
@@ -426,7 +423,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'解读任务导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
+38 -15
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,22 +40,20 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -81,12 +79,16 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.record-table{width:100%;min-width:0;border-collapse:collapse;font-size:var(--font-sm);}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.record-table th:last-child,.record-table td:last-child{position:static;box-shadow:none;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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>
@@ -98,8 +100,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -182,10 +184,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<button class="btn btn-primary" 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="openExportTasks()"><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>
<!-- 表格 -->
@@ -219,6 +221,24 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</div>
</div>
<!-- [交互] 导出任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal">
<div class="modal__header"><span>入库流水导出任务</span><button class="modal__close" onclick="closeModal('taskMask')">×</button></div>
<div class="modal__body">
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>入库流水-2026年9月.xlsx</td><td>2026-09-06 16:20:11</td><td><span class="badge badge--success">成功</span></td><td><button class="btn btn-link" onclick="showToast('文件下载中…')">下载</button></td></tr>
<tr><td>入库流水-2026年8月.xlsx</td><td>2026-08-31 09:05:47</td><td><span class="badge badge--success">成功</span></td><td><button class="btn btn-link" onclick="showToast('文件下载中…')">下载</button></td></tr>
<tr><td>入库流水-近7天.xlsx</td><td>2026-09-07 08:41:33</td><td><span class="badge badge--warning">导出中…</span></td><td></td></tr>
</tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeModal('taskMask')">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
@@ -318,6 +338,9 @@ function openDetail(no){
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
/* ===== 异步导出与导出任务 ===== */
function doExport(){showToast('导出任务已提交,完成后可在「查看导出任务」下载');}
function openExportTasks(){document.getElementById('taskMask').classList.add('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);
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,22 +40,20 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -66,6 +64,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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--warning{background:#FFFBE6;color:#D48806;border:1px solid #FFE58F;}
.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);}
@@ -82,12 +81,16 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.record-table{width:100%;min-width:0;border-collapse:collapse;font-size:var(--font-sm);}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.record-table th:last-child,.record-table td:last-child{position:static;box-shadow:none;}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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>
@@ -99,8 +102,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -183,10 +186,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<button class="btn btn-primary" 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="openExportTasks()"><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>
<!-- 表格 -->
@@ -220,6 +223,24 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</div>
</div>
<!-- [交互] 导出任务:异步任务列表 -->
<div class="modal-mask" id="taskMask">
<div class="modal">
<div class="modal__header"><span>出库流水导出任务</span><button class="modal__close" onclick="closeModal('taskMask')">×</button></div>
<div class="modal__body">
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>出库流水-2026年9月.xlsx</td><td>2026-09-06 16:24:02</td><td><span class="badge badge--success">成功</span></td><td><button class="btn btn-link" onclick="showToast('文件下载中…')">下载</button></td></tr>
<tr><td>出库流水-2026年8月.xlsx</td><td>2026-08-31 09:12:56</td><td><span class="badge badge--success">成功</span></td><td><button class="btn btn-link" onclick="showToast('文件下载中…')">下载</button></td></tr>
<tr><td>出库流水-近7天.xlsx</td><td>2026-09-07 08:45:19</td><td><span class="badge badge--warning">导出中…</span></td><td></td></tr>
</tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeModal('taskMask')">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
@@ -321,6 +342,9 @@ function openDetail(no){
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
/* ===== 异步导出与导出任务 ===== */
function doExport(){showToast('导出任务已提交,完成后可在「查看导出任务」下载');}
function openExportTasks(){document.getElementById('taskMask').classList.add('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);
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,24 +40,22 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -91,7 +89,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -102,14 +100,14 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.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-sm);width:100%;outline:none;}
.rx-table th,.rx-table td{padding:6px 5px;font-size:var(--font-sm);}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
@@ -126,8 +124,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -209,9 +207,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -322,10 +319,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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__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>
@@ -406,7 +403,7 @@ function renderTable(){
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 style="font-weight:600;">'+t.user+'</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>'
@@ -586,7 +583,7 @@ function askBatchDelete(){
window._batchIds=ids;
}
/* ===== 异步导出与导出记录 ===== */
/* ===== 异步导出与导出任务 ===== */
var delMode='single';
var exportLogs=[
{file:'非员工就诊记录导出_2026-09-01 09:36.xlsx',status:'done',time:'2026-09-01 09:36'},
@@ -618,7 +615,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'非员工就诊记录导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -40,35 +40,33 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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: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;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{height:36px;min-width:160px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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-col__title{padding:6px 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -102,7 +100,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{font-size:var(--font-sm);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);}
@@ -114,14 +112,14 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.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;}
.mini-table thead th{background:#FAFAFA;position:sticky;top:0;font-size:var(--font-sm);}
.mini-table td{white-space:normal;font-size:var(--font-sm);}
.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-sm);width:100%;outline:none;}
.rx-table th,.rx-table td{padding:6px 5px;font-size:var(--font-sm);}
.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 .dk{width:110px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
@@ -140,8 +138,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -243,9 +241,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<button class="btn btn-primary" 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()"><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>
<!-- 表格 -->
@@ -375,10 +372,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</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__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>
@@ -475,7 +472,7 @@ function resetOrg(){
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'},
@@ -506,7 +503,7 @@ function nowHM(){
}
function doExport(){
exportLogs.unshift({file:'员工就诊记录导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出记录」下载');
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
var e=exportLogs[0];
@@ -574,7 +571,7 @@ function renderTable(){
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 style="font-weight:600;">'+t.user+'</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>'
@@ -116,3 +116,19 @@
**涉及文件:**
- `web-admin/frontline-medical/*.html` — 45 页页头面包屑与说明批量移除
## [2026-09-08] feat: 二线迭代 · 侧栏二级分组、统计域收拢与全站视觉尺寸升级
基于首版 45 页的整体改版(45 页修改 + 3 页新增,站点地图同步)。
- **侧栏二级分组**:基础配置/日常业务/床头呼叫三域侧栏收敛为可展开收起的二级分组——基础配置(药配[药品信息/实时库存]、分配[分配管理/分配统计]、救护车[救护车列表]三个子组,医疗点配置/合作医院/医护人员管理保持顶层平铺);日常业务(日常诊疗/用药记录/任务管理);床头呼叫(设备管理[设备列表/分配管理]、宿舍管理[站点管理/楼栋/宿舍/床位信息],分配记录/呼叫记录/告警记录/汇总统计顶层平铺)。分组默认收起,点击标题展开、箭头旋转,带交互注释
- **床头呼叫域**:新增 `bed-station.html` 站点管理(单位/部门/站点名称筛选、站点列表含详细地址/负责人/联系电话/设立日期/简介台账列、新增编辑双列表单弹窗);页面更名——楼栋管理→楼栋信息、宿舍管理→宿舍信息、床位管理→床位信息、设备分配→分配管理、超时未归告警→告警记录、统计汇总→汇总统计;顶栏「床头呼叫」分组入口调整为设备列表页
- **数据统计域重排**:救护车统计表格/图表两页由基础配置移入数据统计;「表格/图表」双页收拢为侧栏单一入口(医疗点统计/救护车统计/救助物品统计三项),页内图表视图/报表视图 view-switch 互切,三套统计同源改版;新增 `stat-rescue-chart.html`(三环形占比图:配发数量/新增配发/成本金额)与 `stat-rescue-table.html`(医疗点选择 + 占比表 + 小药棒/药品箱/急救包分组表头本期/累计明细表),双页共享批量导入/导入任务/模板下载工具条
- **全站视觉尺寸升级**:筛选标签/按钮/表头/表单文字统一 font-xs→font-sm;输入框、下拉、按钮高度 32→36;表格单元格 padding 提至 10/14、min-width 相应上调;表单新增 .form-row 双列栅格、select 下拉箭头与上传框 .upload-box 等通用样式;移除冗余 hint-bar
- **`D:\Work\platform-prototype\index.html`** — SITE_MAP 同步:新增站点管理与救助物品统计两页、救护车统计两页移入数据统计、更名条目同步;移除模块 `done:true`(改版期间状态回退为设计中)
**涉及文件:**
- `web-admin/frontline-medical/bed-station.html` / `stat-rescue-chart.html` / `stat-rescue-table.html` — 新增 3 页
- `web-admin/frontline-medical/*.html` — 45 页侧栏分组/更名/视觉尺寸批量改版
- `D:\Work\platform-prototype\index.html` — 站点地图同步
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,25 +33,24 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -80,7 +79,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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 .dk{width:104px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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;}
@@ -94,8 +93,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -167,10 +166,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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">
+11 -16
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,23 +33,22 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -84,13 +83,13 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{border:1px dashed var(--border);border-radius:var(--radius-md);padding:18px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);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 .dk{width:96px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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>
@@ -102,8 +101,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -153,10 +152,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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">
+11 -16
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,34 +33,33 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{padding:6px 14px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);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 td{padding:12px 14px;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);}
.tfoot td{background:#FAFAFA;font-weight:600;border-top:1px solid var(--border);font-size:var(--font-sm);}
.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);}
@@ -76,8 +75,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -138,10 +137,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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">
+46 -32
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,31 +33,31 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
.inner-table th:last-child,.inner-table td:last-child{position:static;box-shadow:none;}
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;}
.tfoot td{background:#FAFAFA;font-weight:600;font-size:var(--font-sm);}
.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);}
@@ -72,14 +72,16 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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;}
.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-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
.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{width:100%;min-width:0;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;border-collapse:collapse;}
.inner-table th{background:#FAFAFA;font-size:var(--font-sm);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>
@@ -91,8 +93,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -143,10 +145,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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">
@@ -156,10 +154,9 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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>
<button class="btn btn-primary" onclick="openCabinImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" 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>
</div>
</div>
<div class="table-card__content">
@@ -227,21 +224,34 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</div>
</div>
<!-- [交互] 导出记录弹窗 -->
<div class="modal-mask" id="expMask">
<!-- [交互] 批量导入小屋弹窗(异步) -->
<div class="modal-mask" id="cabinImportMask">
<div class="modal">
<div class="modal__header"><span>批量导入数据</span><button class="modal__close" onclick="closeCabinImport()">&times;</button></div>
<div class="modal__body">
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>健康小屋导入模板.xlsx</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeCabinImport()">取消</button>
<button class="btn btn-primary" onclick="doCabinImport()">开始导入</button>
</div>
</div>
</div>
<!-- [交互] 导入任务:异步任务列表 -->
<div class="modal-mask" id="importTaskMask">
<div class="modal--wide">
<div class="modal__header"><span>导出记录</span><button class="modal__close" onclick="closeExports()">&times;</button></div>
<div class="modal__header"><span>小屋导入任务</span><button class="modal__close" onclick="closeImportTasks()">&times;</button></div>
<div class="modal__body">
<table class="inner-table">
<thead><tr><th>导出时间</th><th>操作人</th><th>文件名</th><th>状态</th></tr></thead>
<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>
<tr><td style="font-family:monospace;">cabin_ledger_20260905.xlsx</td><td>2026-09-05 14:22</td><td style="color:#389E0D;">成功:新增 2 座、更新 1 座</td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
<tr><td style="font-family:monospace;">cabin_ledger_20260820.xlsx</td><td>2026-08-20 10:05</td><td style="color:#D48806;">部分成功:新增 1 座、失败 1 座</td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
</tbody>
</table>
</div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeExports()">关闭</button></div>
<div class="modal__footer"><button class="btn btn-default" onclick="closeImportTasks()">关闭</button></div>
</div>
</div>
@@ -375,9 +385,13 @@ function addRecord(){
renderData();showToast(old>=0?'该日期已有记录,已按最新录入覆盖':'测量记录已录入');
}
/* ---- 导出记录 / 删除 ---- */
function openExports(){document.getElementById('expMask').classList.add('open');}
function closeExports(){document.getElementById('expMask').classList.remove('open');}
/* ---- 批量导入(异步)与导入任务 ---- */
function openCabinImport(){document.getElementById('cabinImportMask').classList.add('open');}
function closeCabinImport(){document.getElementById('cabinImportMask').classList.remove('open');}
function doCabinImport(){closeCabinImport();showToast('导入任务已提交,完成后将推送站内通知');}
function openImportTasks(){document.getElementById('importTaskMask').classList.add('open');}
function closeImportTasks(){document.getElementById('importTaskMask').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> 天测量记录与统计报表取数将一并失效,操作不可恢复。';
+12 -17
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,24 +33,23 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -88,15 +87,15 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.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{border:1px dashed var(--border);border-radius:var(--radius-md);padding:14px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);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 .dk{width:104px;flex-shrink:0;background:#FAFAFA;padding:9px 12px;font-size:var(--font-sm);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 th{background:#FAFAFA;font-size:var(--font-sm);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>
@@ -108,8 +107,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -166,10 +165,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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">
+8 -13
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -33,21 +33,20 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 14px;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);}
@@ -88,8 +87,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -130,10 +129,6 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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">
+7 -12
View File
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -35,19 +35,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.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;}
thead th{background:#FAFAFA;padding:10px 14px;text-align:center;font-size:var(--font-sm);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);font-size:var(--font-sm);}
tbody td{padding:12px 14px;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;}
@@ -65,8 +64,8 @@ td.ro{color:var(--text-placeholder);font-weight:400;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html">数据统计</a>
<a href="hut-banner.html" class="active">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -95,10 +94,6 @@ td.ro{color:var(--text-placeholder);font-weight:400;}
<!-- [交互] 数据区 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">
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>救护车统计(图表视图) | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,38 +30,44 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
.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{display:inline-flex;align-items:center;padding:0 14px;height:32px;font-size:var(--font-sm);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);}
/* [交互] 所属单位→所属医疗点级联;清空单位回到全局统计(与老系统 allowClear 一致) */
.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;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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;}
/* 卡片:基本数据(32%)+出勤次数(66%),布局与老系统 number-container 一致 */
.num-row{display:flex;gap:16px;margin-top:16px;align-items:stretch;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;}
.stat-card__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:12px;font-size:var(--font-base);}
.stat-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:14px 8px;}
.stat-cell{text-align:center;}
.stat-cell__val{font-size:22px;font-weight:600;color:var(--text-primary);}
.stat-cell__val small{font-size:var(--font-xs);font-weight:400;color:var(--text-placeholder);margin-left:2px;}
.stat-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:4px;}
.num-card--l{width:32%;}.num-card--r{width:68%;}
.chart-grid{display:grid;grid-template-columns:1fr 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--full{grid-column:1/-1;}
.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;}
.wk-switch{display:inline-flex;height:26px;line-height:26px;text-align:center;font-weight:400;cursor:pointer;font-size:var(--font-sm);}
.wk-switch span{width:50px;border:1px solid var(--primary);}
.wk-switch span:first-child{border-radius:var(--radius-sm) 0 0 var(--radius-sm);}
.wk-switch span:last-child{border-radius:0 var(--radius-sm) var(--radius-sm) 0;border-left:none;}
.wk-switch .on{background:#1684FC;color:#fff;}
.wk-switch .off{background:#fff;color:#1684FC;}
.title-line{display:flex;align-items:center;margin-bottom:16px;}
.title-line span{font-size:var(--font-base);font-weight:600;}
.empty-tip{text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);padding:60px 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>
@@ -71,10 +77,10 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<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="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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html" class="active">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -85,91 +91,85 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</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>
<div class="sidebar-menu__title">数据统计</div>
<a href="stat-clinic-chart.html" class="sidebar-item">医疗点统计</a>
<a href="stat-ambulance-chart.html" class="sidebar-item active">救护车统计</a>
<a href="stat-rescue-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="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="title-line">
<span>救护车统计</span>
<div class="view-switch" style="margin-left:auto;">
<a href="stat-ambulance-table.html">表格视图</a>
<a class="active" href="stat-ambulance-chart.html">图表视图</a>
<a href="stat-ambulance-table.html">报表视图</a>
</div>
</div>
<div class="kpi-row" id="kpiRow"></div>
<!-- [交互] 单位→医疗点级联筛选,清空单位回全局;统计口径随筛选联动(照搬老系统新版页) -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="selUnit" onchange="onUnitChange()" style="min-width:160px;">
<option value="">全部单位</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="selPoint" onchange="renderScope()" style="min-width:220px;" disabled>
<option value="">请先选择所属单位</option>
</select>
</div>
</div>
<!-- 卡片:基本数据 + 出勤次数(累计口径,与老系统 dispatchTypeTotal 一致) -->
<div class="num-row">
<div class="stat-card num-card--l">
<div class="stat-card__title">基本数据</div>
<div class="stat-card__grid" id="cardBase"></div>
</div>
<div class="stat-card num-card--r">
<div class="stat-card__title">出勤次数</div>
<div class="stat-card__grid" id="cardDispatch"></div>
</div>
</div>
<!-- 三图并排:类型占比饼 / 出勤次数占比饼(本周累计) / 医护出勤TOP5(本周累计+类型) -->
<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 class="chart-card__title">救护车类型占比<span style="font-weight:400;color:var(--text-placeholder);font-size:var(--font-xs);">(辆)</span></div>
<svg id="pieType" viewBox="0 0 560 240"></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 class="chart-card__title">出勤次数占比
<span class="wk-switch" id="wkDisp"><span class="on" onclick="setWeek('wkDisp',true)">本周</span><span class="off" onclick="setWeek('wkDisp',false)">累计</span></span>
</div>
<svg id="pieDisp" viewBox="0 0 560 260"></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 class="chart-card__title">医护人员出勤次数排名TOP5
<span class="wk-switch" id="wkTop"><span class="on" onclick="setWeek('wkTop',true)">本周</span><span class="off" onclick="setWeek('wkTop',false)">累计</span></span>
</div>
<!-- [交互] 出勤类型下拉:选择后两项周期切换复位为「本周」,与老系统 changeTopType 行为一致 -->
<div style="display:flex;align-items:center;gap:8px;margin-top:8px;">
<span style="font-size:var(--font-xs);color:var(--text-secondary);">出勤类型:</span>
<select class="filter-select" id="selTopType" onchange="onTopType()" style="min-width:120px;height:28px;">
<option value="">全部类型</option>
</select>
</div>
<svg id="chartTop" viewBox="0 0 560 220"></svg>
<div class="empty-tip" id="topEmpty" style="display:none;">暂无出勤数据</div>
</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>
<!-- 本周逐日堆积柱状图,照搬老系统 barCharts(5 系列 stack) -->
<div class="chart-card chart-card--full">
<div class="chart-card__title">本周出勤次数变化趋势
<span class="lg" id="trendLg"></span>
</div>
<svg id="chartTrend" viewBox="0 0 1120 280"></svg>
</div>
</div>
</main>
@@ -177,126 +177,198 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<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}
/* 数据源与「救护车配置」页勾稽:6 辆车=4 急救型+2 转运型,辐射人数取车辆覆盖人数;
出勤五类口径:急救转运/平诊转运/日常巡诊/应急戒备/其他出勤(dispatch_type 字典) */
var TYPES=['jz','pz','rc','yj','qt'];
var TYPE_NAME={jz:'急救转运',pz:'平诊转运',rc:'日常巡诊',yj:'应急戒备',qt:'其他出勤'};
var DISC=['#5087ec','#68bbc4','#58a55c','#f2bd42','#ee752f'];
var pts=[
{unit:'采油一厂',name:'樊学作业区医疗点',total:1,jj:1,zy:0,rad:860,
w:{jz:3,pz:2,rc:5,yj:1,qt:1},t:{jz:42,pz:28,rc:76,yj:15,qt:9},
staff:[{name:'李桂芳',w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:24,pz:16,rc:41,yj:8,qt:5}},
{name:'石维国',w:{jz:1,pz:1,rc:2,yj:0,qt:0},t:{jz:18,pz:12,rc:35,yj:7,qt:4}}]},
{unit:'采油一厂',name:'安塞采油队长住点医疗室',total:1,jj:0,zy:1,rad:540,
w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10},
staff:[{name:'周娟',w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10}}]},
{unit:'采油一厂',name:'姬塬作业区医疗点',total:0,jj:0,zy:0,rad:0,
w:{jz:0,pz:0,rc:0,yj:0,qt:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0},staff:[]},
{unit:'采油二厂',name:'姬圪台作业区医疗点',total:1,jj:1,zy:0,rad:1240,
w:{jz:2,pz:1,rc:4,yj:2,qt:0},t:{jz:35,pz:22,rc:61,yj:18,qt:7},
staff:[{name:'曲志明',w:{jz:1,pz:1,rc:2,yj:1,qt:0},t:{jz:19,pz:13,rc:34,yj:10,qt:4}},
{name:'张秀兰',w:{jz:1,pz:0,rc:2,yj:1,qt:0},t:{jz:16,pz:9,rc:27,yj:8,qt:3}}]},
{unit:'采油二厂',name:'桐园作业区医疗点',total:0,jj:0,zy:0,rad:0,
w:{jz:0,pz:0,rc:0,yj:0,qt:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0},staff:[]},
{unit:'第四采油厂',name:'西峰基地医疗点',total:1,jj:0,zy:1,rad:2100,
w:{jz:1,pz:6,rc:7,yj:3,qt:2},t:{jz:18,pz:88,rc:97,yj:26,qt:14},
staff:[{name:'高建军',w:{jz:1,pz:3,rc:3,yj:2,qt:1},t:{jz:10,pz:45,rc:49,yj:14,qt:8}},
{name:'闫婷',w:{jz:0,pz:3,rc:4,yj:1,qt:1},t:{jz:8,pz:43,rc:48,yj:12,qt:6}}]},
{unit:'采气一厂',name:'靖边气田医疗点',total:1,jj:1,zy:0,rad:760,
w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:27,pz:19,rc:48,yj:12,qt:6},
staff:[{name:'刘玉梅',w:{jz:2,pz:1,rc:2,yj:1,qt:0},t:{jz:16,pz:11,rc:29,yj:7,qt:3}},
{name:'呼建国',w:{jz:0,pz:0,rc:1,yj:0,qt:1},t:{jz:11,pz:8,rc:19,yj:5,qt:3}}]},
{unit:'采气一厂',name:'乌审旗采气区医疗点',total:1,jj:1,zy:0,rad:680,
w:{jz:1,pz:1,rc:3,yj:2,qt:0},t:{jz:22,pz:14,rc:40,yj:16,qt:5},
staff:[{name:'徐立成',w:{jz:1,pz:0,rc:2,yj:1,qt:0},t:{jz:13,pz:8,rc:24,yj:10,qt:3}},
{name:'马丽华',w:{jz:0,pz:1,rc:1,yj:1,qt:0},t:{jz:9,pz:6,rc:16,yj:6,qt:2}}]}
];
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;
var weekState={wkDisp:true,wkTop:true};
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
/* 当前筛选范围:未选单位=全局;选单位未选点=单位;选点=单点 */
function scope(){
var u=document.getElementById('selUnit').value,p=document.getElementById('selPoint').value;
if(p)return pts.filter(function(x){return x.name===p;});
if(u)return pts.filter(function(x){return x.unit===u;});
return pts;
}
/* 多切片环形图 */
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 sumField(list,period,key){var s=0;list.forEach(function(p){s+=p[period][key];});return s;}
function sumV(obj){return TYPES.reduce(function(s,k){return s+obj[k];},0);}
/* ===== 卡片 ===== */
function renderCards(){
var sc=scope(),total=0,jj=0,zy=0,rad=0;
sc.forEach(function(p){total+=p.total;jj+=p.jj;zy+=p.zy;rad+=p.rad;});
document.getElementById('cardBase').innerHTML=
cell(total,'辆','总数量')+cell(jj,'辆','急救型数量')+cell(zy,'辆','转运型数量')+cell(rad,'人','辐射人数');
document.getElementById('cardDispatch').innerHTML=TYPES.map(function(k){return cell(sumField(sc,'t',k),'次',TYPE_NAME[k]);}).join('');
}
function cell(v,u,n){return '<div class="stat-cell"><div class="stat-cell__val">'+v+'<small>'+u+'</small></div><div class="stat-cell__name">'+n+'</div></div>';}
/* ===== 饼图:任意切片 + 中心合计 + 右侧图例 ===== */
function drawPie(id,items,colors,unit){
var svg=document.getElementById(id),cx=150,cy=118,R=88;
var tot=items.reduce(function(s,i){return s+i.v;},0)||1,ang=0,html='';
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>';
html+='<path d="M'+cx+' '+cy+' L'+pt(R,ang)+' A'+R+' '+R+' 0 '+large+' 1 '+pt(R,end)+' Z" fill="'+colors[idx]+'"><title>'+esc(it.k)+' '+it.v+unit+'('+(it.v/tot*100).toFixed(0)+'%)</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;
html+='<text x="'+cx+'" y="'+(cy-4)+'" text-anchor="middle" font-size="22" font-weight="600" fill="#333">'+items.reduce(function(s,i){return s+i.v;},0)+'</text>';
html+='<text x="'+cx+'" y="'+(cy+16)+'" text-anchor="middle" font-size="11" fill="#999">合计('+unit+')</text>';
var ly=cy-(items.length*24)/2+6;
items.forEach(function(it,idx){
var t=items.reduce(function(s,i){return s+i.v;},0)||1;
html+='<rect x="320" y="'+ly+'" width="12" height="12" rx="3" fill="'+colors[idx]+'"/><text x="340" y="'+(ly+11)+'" font-size="12" fill="#666">'+esc(it.k)+' '+it.v+'('+(it.v/t*100).toFixed(0)+'%)</text>';
ly+=24;
});
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;
function renderPieType(){
var sc=scope(),jj=0,zy=0;
sc.forEach(function(p){jj+=p.jj;zy+=p.zy;});
drawPie('pieType',[{k:'急救型',v:jj},{k:'转运型',v:zy}],['#5087ec','#68bbc4'],'辆');
}
function renderPieDisp(){
var period=weekState.wkDisp?'w':'t';
drawPie('pieDisp',TYPES.map(function(k,i){return {k:TYPE_NAME[k],v:sumField(scope(),period,k)};}),DISC,'次');
}
/* ===== TOP5 横向条(本周/累计 + 出勤类型) ===== */
function staffRows(sc){
var map={},order=[];
sc.forEach(function(p){p.staff.forEach(function(s){
if(!map[s.name]){map[s.name]={name:s.name,unit:p.unit,point:p.name,w:{},t:{}};TYPES.forEach(function(k){map[s.name].w[k]=0;map[s.name].t[k]=0;});order.push(s.name);}
TYPES.forEach(function(k){map[s.name].w[k]+=s.w[k];map[s.name].t[k]+=s.t[k];});
});});
return order.map(function(n){return map[n];});
}
function renderTop(){
var t=document.getElementById('selTopType').value,period=weekState.wkTop?'w':'t';
var rows=staffRows(scope()).map(function(s){return {k:s.name,v:t?s[period][t]:sumV(s[period])};})
.filter(function(d){return d.v>0;}).sort(function(a,b){return b.v-a.v;}).slice(0,5);
var has=rows.length>0;
document.getElementById('topEmpty').style.display=has?'none':'block';
document.getElementById('chartTop').style.display=has?'block':'none';
if(has)drawTop5('chartTop',rows,'#5087ec','次');
}
function drawTop5(id,data,color,unit){
var svg=document.getElementById(id);
var max=Math.max.apply(null,data.map(function(d){return d.v;}))||1;
var bh=24,gap=12,top=8,labelW=64,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-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="'+color+'"><title>'+esc(d.k)+' '+d.v+unit+'</title></rect>';
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='';
/* ===== 本周逐日堆积柱状(5系列 stack,照搬老系统 barCharts) ===== */
var DAYS=['周一','周二','周三','周四','周五','周六','周日'];
var WT=[2,1,2,1,3,2,1];
function splitWeek(v){
var S=WT.reduce(function(a,b){return a+b;},0),out=WT.map(function(x){return Math.floor(v*x/S);});
var rem=v-out.reduce(function(a,b){return a+b;},0),i=0;
while(rem>0){out[i%7]++;rem--;i++;}
return out;
}
function renderTrend(){
var sc=scope();
var series=TYPES.map(function(k,i){
var total=sumField(sc,'w',k),days=splitWeek(total);
return {name:TYPE_NAME[k],color:DISC[i],data:days};
});
document.getElementById('trendLg').innerHTML=series.map(function(s){return '<span><i style="background:'+s.color+';"></i>'+s.name+'</span>';}).join('');
var svg=document.getElementById('chartTrend'),W=1120,H=280,left=44,bottom=34,top=12,right=16;
var max=0;DAYS.forEach(function(d,i){var s=0;series.forEach(function(se){s+=se.data[i];});if(s>max)max=s;});
max=max||1;
var step=(W-left-right)/7,bw=Math.min(56,step*0.5),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+='<line x1="'+left+'" y1="'+y+'" x2="'+(W-right)+'" 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"/>';
DAYS.forEach(function(d,i){
var xc=left+step*i+step/2,cum=0;
series.forEach(function(se){
var v=se.data[i];if(!v)return;
var h=(H-bottom-top)*v/max;
html+='<rect x="'+(xc-bw/2)+'" y="'+(H-bottom-cum-h)+'" width="'+bw+'" height="'+h+'" fill="'+se.color+'"><title>'+d+' '+se.name+' '+v+'次</title></rect>';
cum+=h;
});
html+='<text x="'+xc+'" y="'+(H-bottom+18)+'" text-anchor="middle" font-size="12" fill="#666">'+d+'</text>';
});
html+='<line x1="'+left+'" y1="'+(H-bottom)+'" x2="'+(W-right)+'" 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 setWeek(swId,isWeek){
weekState[swId]=isWeek;
var sw=document.getElementById(swId),sp=sw.querySelectorAll('span');
sp[0].className=isWeek?'on':'off';sp[1].className=isWeek?'off':'on';
if(swId==='wkDisp')renderPieDisp();else renderTop();
}
function onTopType(){
/* 老系统:切换出勤类型后两个周期切换均复位为本周 */
weekState.wkDisp=true;weekState.wkTop=true;
document.querySelectorAll('.wk-switch').forEach(function(sw){
var sp=sw.querySelectorAll('span');sp[0].className='on';sp[1].className='off';
});
renderPieDisp();renderTop();
}
function onUnitChange(){
var u=document.getElementById('selUnit').value,sel=document.getElementById('selPoint');
if(!u){sel.innerHTML='<option value="">请先选择所属单位</option>';sel.disabled=true;}
else{
sel.disabled=false;
var list=pts.filter(function(p){return p.unit===u;});
sel.innerHTML='<option value="">全部医疗点(该单位)</option>'+list.map(function(p){return '<option>'+esc(p.name)+'</option>';}).join('');
}
renderScope();
}
function renderScope(){renderCards();renderPieType();renderPieDisp();renderTop();renderTrend();}
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');}
/* 初始化:单位下拉 + 出勤类型下拉(dispatch_type 字典五类) */
(function(){
var us=[];pts.forEach(function(p){if(us.indexOf(p.unit)<0)us.push(p.unit);});
document.getElementById('selUnit').innerHTML='<option value="">全部单位</option>'+us.map(function(u){return '<option>'+esc(u)+'</option>';}).join('');
document.getElementById('selTopType').innerHTML='<option value="">全部类型</option>'+TYPES.map(function(k){return '<option value="'+k+'">'+TYPE_NAME[k]+'</option>';}).join('');
})();
renderScope();
</script>
</body>
</html>
@@ -6,7 +6,7 @@
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -30,65 +30,43 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.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:32px;font-size:var(--font-sm);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);}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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;}
.num-row{display:flex;gap:16px;margin-top:16px;align-items:stretch;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;}
.stat-card__title{font-weight:600;color:var(--text-primary);margin-bottom:12px;font-size:var(--font-base);}
.stat-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:14px 8px;}
.stat-cell{text-align:center;}
.stat-cell__val{font-size:22px;font-weight:600;color:var(--text-primary);}
.stat-cell__val small{font-size:var(--font-xs);font-weight:400;color:var(--text-placeholder);margin-left:2px;}
.stat-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:4px;}
.num-card--l{width:32%;}.num-card--r{width:68%;}
.title-line{display:flex;align-items:center;margin-bottom:16px;}
.title-line span{font-size:var(--font-base);font-weight:600;}
.tbl-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;margin-top:16px;}
.tbl-card__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:12px;}
.tbl-scroll{overflow-x:auto;}
table.rpt{width:100%;border-collapse:separate;border-spacing:0;min-width:1100px;}
.rpt th,.rpt td{border:1px solid #F0F0F0;padding:10px 12px;font-size:var(--font-sm);text-align:center;white-space:nowrap;}
.rpt thead th{background:#FAFAFA;color:var(--text-secondary);font-weight:500;font-size:var(--font-sm);}
.rpt thead tr:first-child th{background:#F0F5FF;color:var(--text-primary);font-weight:600;font-size:var(--font-sm);}
.rpt tbody tr:hover{background:#F8FBFF;}
.rpt .num{font-variant-numeric:tabular-nums;}
.rpt .hl{color:#1684FC;font-weight:600;}
.rpt tfoot td{background:#FAFAFA;font-weight:600;font-size:var(--font-sm);}
.rpt--sm{min-width:0;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.td-empty{text-align:center;padding:32px 0;color:var(--text-placeholder);font-size:var(--font-sm);border:1px solid #F0F0F0;}
.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>
@@ -97,10 +75,10 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html" class="active">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -111,204 +89,219 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</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>
<div class="sidebar-menu__title">数据统计</div>
<a href="stat-clinic-chart.html" class="sidebar-item">医疗点统计</a>
<a href="stat-ambulance-chart.html" class="sidebar-item active">救护车统计</a>
<a href="stat-rescue-chart.html" class="sidebar-item">救助物品统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 图表/报表两种查看形态,数据同源互切 -->
<div class="title-line">
<span>救护车统计</span>
<div class="view-switch" style="margin-left:auto;">
<a href="stat-ambulance-chart.html">图表视图</a>
<a class="active" href="stat-ambulance-table.html">报表视图</a>
</div>
</div>
<!-- [交互] 筛选栏:单位→医疗点级联 + 救护类型 + 周/月维度切换 -->
<!-- [交互] 与图表视图共用同一单位→医疗点级联筛选,报表口径随筛选联动 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:120px;">
<select class="filter-select" id="selUnit" onchange="onUnitChange()" style="min-width:160px;">
<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 class="filter-select" id="selPoint" onchange="renderScope()" style="min-width:220px;" disabled>
<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 class="num-row">
<div class="stat-card num-card--l">
<div class="stat-card__title">基本数据</div>
<div class="stat-card__grid" id="cardBase"></div>
</div>
<div class="stat-card num-card--r">
<div class="stat-card__title">出勤次数</div>
<div class="stat-card__grid" id="cardDispatch"></div>
</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 class="two-col">
<div class="tbl-card" style="margin-top:16px;">
<div class="tbl-card__title">救护车类型占比</div>
<table class="rpt rpt--sm" id="tblType"></table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
<div class="tbl-card" style="margin-top:16px;">
<div class="tbl-card__title">出勤次数占比</div>
<table class="rpt rpt--sm" id="tblDisp"></table>
</div>
</div>
<!-- 医护人员出勤排名:五类×(本周/累计)分组表头,替代 TOP5 条形图的全量口径 -->
<div class="tbl-card">
<div class="tbl-card__title">医护人员出勤次数排名</div>
<div class="tbl-scroll"><table class="rpt" id="tblStaff"></table></div>
</div>
<!-- 本周逐日出勤趋势:表格形态(5 类 + 合计,含周合计行) -->
<div class="tbl-card">
<div class="tbl-card__title">本周出勤次数变化趋势</div>
<div class="tbl-scroll"><table class="rpt rpt--sm" id="tblWeek"></table></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 TYPES=['jz','pz','rc','yj','qt'];
var TYPE_NAME={jz:'急救转运',pz:'平诊转运',rc:'日常巡诊',yj:'应急戒备',qt:'其他出勤'};
var pts=[
{unit:'采油厂',name:'樊学作业区医疗点',total:1,jj:1,zy:0,rad:860,
w:{jz:3,pz:2,rc:5,yj:1,qt:1},t:{jz:42,pz:28,rc:76,yj:15,qt:9},
staff:[{name:'李桂芳',w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:24,pz:16,rc:41,yj:8,qt:5}},
{name:'石维国',w:{jz:1,pz:1,rc:2,yj:0,qt:0},t:{jz:18,pz:12,rc:35,yj:7,qt:4}}]},
{unit:'采一厂',name:'安塞采油队长住点医疗',total:1,jj:0,zy:1,rad:540,
w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10},
staff:[{name:'周娟',w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10}}]},
{unit:'采油一厂',name:'姬塬作业区医疗点',total:0,jj:0,zy:0,rad:0,
w:{jz:0,pz:0,rc:0,yj:0,qt:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0},staff:[]},
{unit:'采油二厂',name:'姬圪台作业区医疗点',total:1,jj:1,zy:0,rad:1240,
w:{jz:2,pz:1,rc:4,yj:2,qt:0},t:{jz:35,pz:22,rc:61,yj:18,qt:7},
staff:[{name:'曲志明',w:{jz:1,pz:1,rc:2,yj:1,qt:0},t:{jz:19,pz:13,rc:34,yj:10,qt:4}},
{name:'张秀兰',w:{jz:1,pz:0,rc:2,yj:1,qt:0},t:{jz:16,pz:9,rc:27,yj:8,qt:3}}]},
{unit:'采油二厂',name:'桐园作业区医疗点',total:0,jj:0,zy:0,rad:0,
w:{jz:0,pz:0,rc:0,yj:0,qt:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0},staff:[]},
{unit:'第四采油厂',name:'西峰基地医疗点',total:1,jj:0,zy:1,rad:2100,
w:{jz:1,pz:6,rc:7,yj:3,qt:2},t:{jz:18,pz:88,rc:97,yj:26,qt:14},
staff:[{name:'高建军',w:{jz:1,pz:3,rc:3,yj:2,qt:1},t:{jz:10,pz:45,rc:49,yj:14,qt:8}},
{name:'闫婷',w:{jz:0,pz:3,rc:4,yj:1,qt:1},t:{jz:8,pz:43,rc:48,yj:12,qt:6}}]},
{unit:'采气一厂',name:'靖边气田医疗点',total:1,jj:1,zy:0,rad:760,
w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:27,pz:19,rc:48,yj:12,qt:6},
staff:[{name:'刘玉梅',w:{jz:2,pz:1,rc:2,yj:1,qt:0},t:{jz:16,pz:11,rc:29,yj:7,qt:3}},
{name:'呼建国',w:{jz:0,pz:0,rc:1,yj:0,qt:1},t:{jz:11,pz:8,rc:19,yj:5,qt:3}}]},
{unit:'采气一厂',name:'乌审旗采气区医疗点',total:1,jj:1,zy:0,rad:680,
w:{jz:1,pz:1,rc:3,yj:2,qt:0},t:{jz:22,pz:14,rc:40,yj:16,qt:5},
staff:[{name:'徐立成',w:{jz:1,pz:0,rc:2,yj:1,qt:0},t:{jz:13,pz:8,rc:24,yj:10,qt:3}},
{name:'马丽华',w:{jz:0,pz:1,rc:1,yj:1,qt:0},t:{jz:9,pz:6,rc:16,yj:6,qt:2}}]}
];
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 esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
function scope(){
var u=document.getElementById('selUnit').value,p=document.getElementById('selPoint').value;
if(p)return pts.filter(function(x){return x.name===p;});
if(u)return pts.filter(function(x){return x.unit===u;});
return pts;
}
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 sumField(list,period,key){var s=0;list.forEach(function(p){s+=p[period][key];});return s;}
function sumV(obj){return TYPES.reduce(function(s,k){return s+obj[k];},0);}
function renderCards(){
var sc=scope(),total=0,jj=0,zy=0,rad=0;
sc.forEach(function(p){total+=p.total;jj+=p.jj;zy+=p.zy;rad+=p.rad;});
document.getElementById('cardBase').innerHTML=cell(total,'辆','总数量')+cell(jj,'辆','急救型数量')+cell(zy,'辆','转运型数量')+cell(rad,'人','辐射人数');
document.getElementById('cardDispatch').innerHTML=TYPES.map(function(k){return cell(sumField(sc,'t',k),'次',TYPE_NAME[k]);}).join('');
}
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 cell(v,u,n){return '<div class="stat-cell"><div class="stat-cell__val">'+v+'<small>'+u+'</small></div><div class="stat-cell__name">'+n+'</div></div>';}
/* 类型占比表 */
function renderTblType(){
var sc=scope(),jj=0,zy=0;
sc.forEach(function(p){jj+=p.jj;zy+=p.zy;});
var tot=jj+zy||1;
document.getElementById('tblType').innerHTML=
'<thead><tr><th>救护类型</th><th>数量(辆)</th><th>占比</th></tr></thead><tbody>'
+'<tr><td>急救型</td><td class="num hl">'+jj+'</td><td class="num">'+(jj/tot*100).toFixed(1)+'%</td></tr>'
+'<tr><td>转运型</td><td class="num hl">'+zy+'</td><td class="num">'+(zy/tot*100).toFixed(1)+'%</td></tr>'
+'</tbody><tfoot><tr><td>合计</td><td class="num">'+(jj+zy)+'</td><td class="num">100%</td></tr></tfoot>';
}
function closeDetail(){document.getElementById('detailMask').classList.remove('open');}
/* 出勤次数占比表:本周/累计并列 + 累计占比 */
function renderTblDisp(){
var sc=scope(),tTot=TYPES.reduce(function(s,k){return s+sumField(sc,'t',k);},0)||1;
var html='<thead><tr><th>出勤类型</th><th>本周(次)</th><th>累计(次)</th><th>累计占比</th></tr></thead><tbody>';
TYPES.forEach(function(k){
var w=sumField(sc,'w',k),t=sumField(sc,'t',k);
html+='<tr><td>'+TYPE_NAME[k]+'</td><td class="num">'+w+'</td><td class="num hl">'+t+'</td><td class="num">'+(t/tTot*100).toFixed(1)+'%</td></tr>';
});
html+='</tbody><tfoot><tr><td>合计</td><td class="num">'+TYPES.reduce(function(s,k){return s+sumField(sc,'w',k);},0)+'</td><td class="num">'+TYPES.reduce(function(s,k){return s+sumField(sc,'t',k);},0)+'</td><td class="num">100%</td></tr></tfoot>';
document.getElementById('tblDisp').innerHTML=html;
}
/* 医护出勤排名:分组表头 5类×本周/累计+合计;未选点时带单位/医疗点归属列 */
function staffRows(sc){
var map={},order=[];
sc.forEach(function(p){p.staff.forEach(function(s){
if(!map[s.name]){map[s.name]={name:s.name,unit:p.unit,point:p.name,w:{},t:{}};TYPES.forEach(function(k){map[s.name].w[k]=0;map[s.name].t[k]=0;});order.push(s.name);}
TYPES.forEach(function(k){map[s.name].w[k]+=s.w[k];map[s.name].t[k]+=s.t[k];});
});});
return order.map(function(n){return map[n];});
}
function renderTblStaff(){
var showOwner=!document.getElementById('selPoint').value;
var rows=staffRows(scope()).filter(function(s){return sumV(s.t)>0||sumV(s.w)>0;})
.sort(function(a,b){return sumV(b.t)-sumV(a.t);});
var h='<thead><tr><th rowspan="2">排名</th><th rowspan="2">医护人员</th>'
+(showOwner?'<th rowspan="2">所属单位</th><th rowspan="2">所属医疗点</th>':'')
+TYPES.map(function(k){return '<th colspan="2">'+TYPE_NAME[k]+'</th>';}).join('')
+'<th colspan="2">合计</th></tr><tr>'
+TYPES.map(function(){return '<th>本周</th><th>累计</th>';}).join('')
+'<th>本周</th><th>累计</th></tr></thead>';
if(!rows.length){document.getElementById('tblStaff').innerHTML=h+'<tbody><tr><td colspan="'+(showOwner?15:13)+'" class="td-empty" style="border-top:none;">当前范围内暂无医护人员出勤记录</td></tr></tbody>';return;}
h+='<tbody>'+rows.map(function(s,i){
return '<tr><td class="num">'+(i+1)+'</td><td style="font-weight:600;">'+esc(s.name)+'</td>'
+(showOwner?'<td>'+esc(s.unit)+'</td><td>'+esc(s.point)+'</td>':'')
+TYPES.map(function(k){return '<td class="num">'+s.w[k]+'</td><td class="num">'+s.t[k]+'</td>';}).join('')
+'<td class="num hl">'+sumV(s.w)+'</td><td class="num hl">'+sumV(s.t)+'</td></tr>';
}).join('')+'</tbody>';
document.getElementById('tblStaff').innerHTML=h;
}
/* 本周趋势表:逐日×5类+合计,含总计行(与图表页堆积柱同源拆分) */
var DAYS=['周一','周二','周三','周四','周五','周六','周日'];
var WT=[2,1,2,1,3,2,1];
function splitWeek(v){
var S=WT.reduce(function(a,b){return a+b;},0),out=WT.map(function(x){return Math.floor(v*x/S);});
var rem=v-out.reduce(function(a,b){return a+b;},0),i=0;
while(rem>0){out[i%7]++;rem--;i++;}
return out;
}
function renderTblWeek(){
var sc=scope(),cols=TYPES.map(function(k){return splitWeek(sumField(sc,'w',k));});
var h='<thead><tr><th>星期</th>'+TYPES.map(function(k){return '<th>'+TYPE_NAME[k]+'(次)</th>';}).join('')+'<th>当日合计</th></tr></thead><tbody>';
DAYS.forEach(function(d,i){
var dayTot=0;TYPES.forEach(function(k,j){dayTot+=cols[j][i];});
h+='<tr><td>'+d+'</td>'+TYPES.map(function(k,j){return '<td class="num">'+cols[j][i]+'</td>';}).join('')+'<td class="num hl">'+dayTot+'</td></tr>';
});
h+='</tbody><tfoot><tr><td>本周合计</td>'+TYPES.map(function(k,j){return '<td class="num">'+cols[j].reduce(function(a,b){return a+b;},0)+'</td>';}).join('')
+'<td class="num">'+TYPES.reduce(function(s,k,j){return s+cols[j].reduce(function(a,b){return a+b;},0);},0)+'</td></tr></tfoot>';
document.getElementById('tblWeek').innerHTML=h;
}
function onUnitChange(){
var u=document.getElementById('selUnit').value,sel=document.getElementById('selPoint');
if(!u){sel.innerHTML='<option value="">请先选择所属单位</option>';sel.disabled=true;}
else{
sel.disabled=false;
var list=pts.filter(function(p){return p.unit===u;});
sel.innerHTML='<option value="">全部医疗点(该单位)</option>'+list.map(function(p){return '<option>'+esc(p.name)+'</option>';}).join('');
}
renderScope();
}
function renderScope(){renderCards();renderTblType();renderTblDisp();renderTblStaff();renderTblWeek();}
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');}
(function(){
var us=[];pts.forEach(function(p){if(us.indexOf(p.unit)<0)us.push(p.unit);});
document.getElementById('selUnit').innerHTML='<option value="">全部单位</option>'+us.map(function(u){return '<option>'+esc(u)+'</option>';}).join('');
})();
renderScope();
</script>
</body>
</html>
+261 -142
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>医疗点统计-图表 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -31,26 +31,52 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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;}
.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:32px;font-size:var(--font-sm);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;}
/* [交互] 一级Tab:全部医疗点 / 单个医疗点,与老系统页内Tab一致 */
.tabs{display:flex;gap:4px;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);padding:0 16px;margin-bottom:16px;}
.tab{padding:14px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-pane{display:none;}
.tab-pane.active{display:block;}
/* KPI 渐变卡:配色照搬老系统 5 色渐变 */
.kpi-row{display:flex;gap:16px;}
.kpi{flex:1;height:96px;border-radius:10px;color:#fff;display:flex;align-items:center;justify-content:space-around;padding:0 12px;}
.kpi--c1{background:linear-gradient(90deg,#0093FF 0%,#57B8FF 100%);}
.kpi--c2{background:linear-gradient(90deg,#48D147 0%,#88D187 100%);}
.kpi--c3{background:linear-gradient(90deg,#FFA440 0%,#FFC17C 100%);}
.kpi--c4{background:linear-gradient(90deg,#FF5640 0%,#FF8A7B 100%);}
.kpi--c5{background:linear-gradient(90deg,#8167F5 0%,#A897F5 100%);}
.kpi__mid{text-align:center;}
.kpi__val{font-size:28px;font-weight:700;}
.kpi__val small{font-size:var(--font-sm);font-weight:400;margin-left:3px;}
.kpi__name{font-size:var(--font-sm);margin-top:4px;}
.kpi__ico{width:34px;height:34px;opacity:.9;}
.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;}
/* [交互] 每张图独立的 本周/累计 切换,照搬老系统 title-switch 蓝底/描边双态 */
.wk-switch{display:inline-flex;height:26px;line-height:26px;text-align:center;font-weight:400;cursor:pointer;font-size:var(--font-sm);}
.wk-switch span{width:50px;border:1px solid var(--primary);}
.wk-switch span:first-child{border-radius:var(--radius-sm) 0 0 var(--radius-sm);}
.wk-switch span:last-child{border-radius:0 var(--radius-sm) var(--radius-sm) 0;border-left:none;}
.wk-switch .on{background:#1684FC;color:#fff;}
.wk-switch .off{background:#fff;color:#1684FC;}
.chart-card svg{width:100%;height:auto;display:block;margin-top:10px;}
.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-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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;}
/* 单个医疗点:统计卡(灰底分组,照搬老系统 #f8f8fa 圆角卡) */
.stat-cards{display:grid;grid-template-columns:1fr 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__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:12px;}
.stat-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:14px 8px;}
.stat-cell{text-align:center;}
.stat-cell__val{font-size:22px;font-weight:600;color:var(--text-primary);}
.stat-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:4px;}
.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>
@@ -63,8 +89,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html" class="active">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -78,56 +104,100 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<!-- 侧边栏(当前一级分组「数据统计」的二级菜单) -->
<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>
<a href="stat-clinic-chart.html" class="sidebar-item active">医疗点统计</a>
<a href="stat-ambulance-chart.html" class="sidebar-item">救护车统计</a>
<a href="stat-rescue-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 style="display:flex;align-items:center;margin-bottom:16px;">
<span style="font-size:var(--font-base);font-weight:600;">医疗点统计</span>
<div class="view-switch" style="margin-left:auto;">
<a class="active" href="stat-clinic-chart.html">图表视图</a>
<a href="stat-clinic-table.html">报表视图</a>
</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="tabs">
<div class="tab active" onclick="switchTab(0,this)">全部医疗点</div>
<div class="tab" onclick="switchTab(1,this)">单个医疗点</div>
</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>
<!-- ============ Tab1 全部医疗点 ============ -->
<div class="tab-pane active" id="paneAll">
<div class="kpi-row" id="kpiRow"></div>
<!-- [交互] 4 张 TOP5 横向条形图,照搬老系统「本周/累计」独立切换 -->
<div class="chart-grid">
<div class="chart-card">
<div class="chart-card__title">就诊服务人数排名TOP5
<span class="wk-switch" id="wkVisit"><span class="on" onclick="setWeek('wkVisit',true,'chartVisit')">本周</span><span class="off" onclick="setWeek('wkVisit',false,'chartVisit')">累计</span></span>
</div>
<svg id="chartVisit" viewBox="0 0 560 220"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">慢病服务人数排名TOP5
<span class="wk-switch" id="wkChronic"><span class="on" onclick="setWeek('wkChronic',true,'chartChronic')">本周</span><span class="off" onclick="setWeek('wkChronic',false,'chartChronic')">累计</span></span>
</div>
<svg id="chartChronic" viewBox="0 0 560 220"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">急救培训组织场次排名TOP5
<span class="wk-switch" id="wkTrain"><span class="on" onclick="setWeek('wkTrain',true,'chartTrain')">本周</span><span class="off" onclick="setWeek('wkTrain',false,'chartTrain')">累计</span></span>
</div>
<svg id="chartTrain" viewBox="0 0 560 220"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">健康宣教组织场次排名TOP5
<span class="wk-switch" id="wkEdu"><span class="on" onclick="setWeek('wkEdu',true,'chartEdu')">本周</span><span class="off" onclick="setWeek('wkEdu',false,'chartEdu')">累计</span></span>
</div>
<svg id="chartEdu" viewBox="0 0 560 220"></svg>
</div>
</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>
<!-- ============ Tab2 单个医疗点 ============ -->
<div class="tab-pane" id="paneOne">
<!-- [交互] 所属单位→所属医疗点级联联动;未选单位时医疗点下拉置灰提示 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="selUnit" onchange="onUnitChange()" style="min-width:160px;">
<option value="">请选择所属单位</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="selPoint" onchange="renderOne()" style="min-width:220px;">
<option value="">请先选择所属单位</option>
</select>
</div>
</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 class="stat-cards" id="statCards"></div>
<!-- [交互] 3 张饼图,各带 本周/累计 切换;中心显示合计 -->
<div class="chart-grid" style="grid-template-columns:1fr 1fr 1fr;">
<div class="chart-card">
<div class="chart-card__title">服务人数
<span class="wk-switch" id="wkP1"><span class="on" onclick="setWeek('wkP1',true,'chartP1')">本周</span><span class="off" onclick="setWeek('wkP1',false,'chartP1')">累计</span></span>
</div>
<svg id="chartP1" viewBox="0 0 560 240"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">培训宣教人次
<span class="wk-switch" id="wkP2"><span class="on" onclick="setWeek('wkP2',true,'chartP2')">本周</span><span class="off" onclick="setWeek('wkP2',false,'chartP2')">累计</span></span>
</div>
<svg id="chartP2" viewBox="0 0 560 240"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">培训宣教场次
<span class="wk-switch" id="wkP3"><span class="on" onclick="setWeek('wkP3',true,'chartP3')">本周</span><span class="off" onclick="setWeek('wkP3',false,'chartP3')">累计</span></span>
</div>
<svg id="chartP3" viewBox="0 0 560 240"></svg>
</div>
</div>
</div>
</main>
@@ -135,105 +205,154 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<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}
/* 数据源:8 个医疗点,与「医疗点配置」「医疗点统计-表」同源勾稽
字段口径与老系统 analysisPoint 一致:就诊/慢病按人数,培训/宣教按场次+人次,分 本周/累计 */
var points=[
{unit:'采油厂',name:'樊学作业区医疗点',short:'樊学',staff:7,doc:3,nurse:4,rad:2860,visitW:186,visitT:4120,chrW:15,chrT:326,trainSW:1,trainST:9,trainPW:45,trainPT:620,eduSW:2,eduST:14,eduPW:160,eduPT:1180},
{unit:'采油厂',name:'安塞采油队长住点医疗室',short:'安塞',staff:3,doc:1,nurse:2,rad:960,visitW:32,visitT:780,chrW:4,chrT:74,trainSW:0,trainST:3,trainPW:0,trainPT:120,eduSW:0,eduST:4,eduPW:0,eduPT:180},
{unit:'采一厂',name:'姬塬作业区医疗点',short:'姬塬',staff:5,doc:2,nurse:3,rad:1730,visitW:118,visitT:860,chrW:6,chrT:63,trainSW:1,trainST:2,trainPW:60,trainPT:140,eduSW:1,eduST:3,eduPW:90,eduPT:210},
{unit:'采油二厂',name:'姬圪台作业区医疗点',short:'姬圪台',staff:5,doc:2,nurse:3,rad:2140,visitW:142,visitT:3310,chrW:20,chrT:415,trainSW:1,trainST:7,trainPW:38,trainPT:480,eduSW:1,eduST:11,eduPW:95,eduPT:860},
{unit:'采油二厂',name:'桐园作业区医疗点',short:'桐园',staff:4,doc:2,nurse:2,rad:1420,visitW:88,visitT:1960,chrW:10,chrT:158,trainSW:0,trainST:5,trainPW:0,trainPT:260,eduSW:1,eduST:8,eduPW:70,eduPT:520},
{unit:'第四采油厂',name:'西峰基地医疗点',short:'西峰',staff:11,doc:5,nurse:6,rad:4350,visitW:268,visitT:6420,chrW:31,chrT:728,trainSW:2,trainST:12,trainPW:96,trainPT:1350,eduSW:3,eduST:18,eduPW:240,eduPT:1760},
{unit:'采气一厂',name:'靖边气田医疗点',short:'靖边',staff:4,doc:2,nurse:2,rad:1680,visitW:96,visitT:2240,chrW:12,chrT:196,trainSW:1,trainST:6,trainPW:42,trainPT:350,eduSW:1,eduST:9,eduPW:88,eduPT:640},
{unit:'采气一厂',name:'乌审旗采气区医疗点',short:'乌审旗',staff:5,doc:2,nurse:3,rad:1550,visitW:104,visitT:2480,chrW:9,chrT:210,trainSW:1,trainST:5,trainPW:36,trainPT:280,eduSW:1,eduST:8,eduPW:75,eduPT:590}
];
var dimFactor={'周':0.25,'月':1,'年':11.5};
var CUR='#1890FF',LIGHT='#91D5FF',WARN='#FAAD14',OK='#52C41A',PURPLE='#B37FEB';
var C5=['#0093FF','#49D148','#FFA440','#FF5641','#8168F5'];
var BLUE='#0093FF';
var PIE_C={p1:['#49D148','#FFAE53'],p2:['#48D147','#1E92FF'],p3:['#8D76F6','#FFA440']};
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='';
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
function switchTab(i,el){
document.querySelectorAll('.tab').forEach(function(t,k){t.classList.toggle('active',k===i);});
var panes=[document.getElementById('paneAll'),document.getElementById('paneOne')];
panes.forEach(function(p,k){p.classList.toggle('active',k===i);});
}
/* ===== 图表绘制 ===== */
function drawTop5(id,data,color,unit){
/* 老系统 TOP5:横向条形,x轴数值,标签在左,数值在条末;取前5 */
var svg=document.getElementById(id);
var max=Math.max.apply(null,data.map(function(d){return d.v;}))||1;
var bh=24,gap=12,top=8,labelW=64,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-56)*d.v/max);
var y=top+i*(bh+gap),w=Math.max(2,(W-labelW-60)*d.v/max);
var c=Array.isArray(color)?color[i%color.length]:color;
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+='<rect x="'+labelW+'" y="'+y+'" width="'+w+'" height="'+bh+'" rx="4" fill="'+c+'"><title>'+esc(d.k)+' '+d.v+unit+'</title></rect>';
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 drawPie2(id,a,b,colors,unit){
/* 老系统饼图:2 切片 + 中心合计 + 右侧图例(值/占比) */
var svg=document.getElementById(id),cx=150,cy=118,R=88;
var tot=(a.v+b.v)||1,ang=0,html='';
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>';
[a,b].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'+cx+' '+cy+' L'+pt(R,ang)+' A'+R+' '+R+' 0 '+large+' 1 '+pt(R,end)+' Z" fill="'+colors[idx]+'"><title>'+esc(it.k)+' '+it.v+unit+'('+(it.v/tot*100).toFixed(0)+'%)</title></path>';
}
ang=end;
});
html+='<text x="'+cx+'" y="'+(cy-4)+'" text-anchor="middle" font-size="22" font-weight="600" fill="#333">'+(a.v+b.v)+'</text>';
html+='<text x="'+cx+'" y="'+(cy+16)+'" text-anchor="middle" font-size="11" fill="#999">合计('+unit+')</text>';
var ly=cy-22;
[a,b].forEach(function(it,idx){
html+='<rect x="330" y="'+ly+'" width="12" height="12" rx="3" fill="'+colors[idx]+'"/><text x="350" 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 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);
/* 本周/累计 开关状态(按图记忆,切 Tab 不重置) */
var weekState={wkVisit:true,wkChronic:true,wkTrain:true,wkEdu:true,wkP1:true,wkP2:true,wkP3:true};
function setWeek(swId,isWeek,chartId){
weekState[swId]=isWeek;
var sw=document.getElementById(swId),sp=sw.querySelectorAll('span');
sp[0].className=isWeek?'on':'off';sp[1].className=isWeek?'off':'on';
if(chartId.indexOf('chartP')===0){renderOneChart(chartId);}else{renderAllCharts();}
}
function top5Data(key){
var w=weekState[key];
var map={wkVisit:['visitW','visitT'],wkChronic:['chrW','chrT'],wkTrain:['trainSW','trainST'],wkEdu:['eduSW','eduST']}[key];
var f=w?map[0]:map[1];
return points.map(function(p){return {k:p.short,v:p[f]};}).sort(function(a,b){return b.v-a.v;}).slice(0,5);
}
function renderAllCharts(){
drawTop5('chartVisit',top5Data('wkVisit'),BLUE,'人');
drawTop5('chartChronic',top5Data('wkChronic'),BLUE,'人');
drawTop5('chartTrain',top5Data('wkTrain'),C5,'场');
drawTop5('chartEdu',top5Data('wkEdu'),C5,'场');
}
function renderKpi(){
var count=points.length,staff=0,doc=0,nurse=0,rad=0;
points.forEach(function(p){staff+=p.staff;doc+=p.doc;nurse+=p.nurse;rad+=p.rad;});
var items=[
{v:count,u:'个',n:'医疗点总数量',c:'c1',ico:'M4 24h4V12H4v12zm7 0h4V4h-4v20zm7 0h4V16h-4v8z'},
{v:staff,u:'人',n:'医务人员总数量',c:'c2',ico:'M16 4a5 5 0 1 1-5 5 5 5 0 0 1 5-5zm0 12c-6 0-10 3-10 6v2h20v-2c0-3-4-6-10-6z'},
{v:doc,u:'人',n:'医生总数量',c:'c3',ico:'M16 4a5 5 0 1 1-5 5 5 5 0 0 1 5-5zm0 12c-6 0-10 3-10 6v2h20v-2c0-3-4-6-10-6z'},
{v:nurse,u:'人',n:'护士总数量',c:'c4',ico:'M16 4a5 5 0 1 1-5 5 5 5 0 0 1 5-5zm0 12c-6 0-10 3-10 6v2h20v-2c0-3-4-6-10-6z'},
{v:rad,u:'人',n:'辐射员工总数量',c:'c5',ico:'M16 12a4 4 0 1 1-4 4 4 4 0 0 1 4-4zm0-9a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm0 26a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm-9-11a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm18 0a2 2 0 1 1-2 2 2 2 0 0 1 2-2z'}
];
document.getElementById('kpiRow').innerHTML=items.map(function(it){
return '<div class="kpi kpi--'+it.c+'">'
+'<svg class="kpi__ico" viewBox="0 0 32 32" fill="#fff" style="opacity:.85;"><path d="'+it.ico+'"/></svg>'
+'<div class="kpi__mid"><div class="kpi__val">'+it.v+'<small>'+it.u+'</small></div><div class="kpi__name">'+it.n+'</div></div>'
+'<span style="width:34px;"></span></div>';
}).join('');
}
/* ===== 单个医疗点 ===== */
function initUnitSel(){
var us=[];points.forEach(function(p){if(us.indexOf(p.unit)<0)us.push(p.unit);});
document.getElementById('selUnit').innerHTML='<option value="">请选择所属单位</option>'+us.map(function(u){return '<option>'+esc(u)+'</option>';}).join('');
}
function onUnitChange(){
var u=document.getElementById('selUnit').value,sel=document.getElementById('selPoint');
if(!u){sel.innerHTML='<option value="">请先选择所属单位</option>';sel.disabled=true;document.getElementById('statCards').innerHTML='';document.getElementById('paneOne').querySelectorAll('.chart-card svg').forEach(function(s){s.innerHTML='';});return;}
sel.disabled=false;
var list=points.filter(function(p){return p.unit===u;});
sel.innerHTML=list.map(function(p){return '<option value="'+p.name+'">'+esc(p.name)+'</option>';}).join('');
renderOne();
}
function cellFor(val,name){return '<div class="stat-cell"><div class="stat-cell__val">'+val+'</div><div class="stat-cell__name">'+name+'</div></div>';}
function renderOne(){
var n=document.getElementById('selPoint').value;
var p=points.filter(function(x){return x.name===n;})[0];
if(!p)return;
document.getElementById('statCards').innerHTML=
'<div class="stat-card"><div class="stat-card__title">基本数据</div><div class="stat-card__grid">'
+cellFor(p.staff,'医务人员总数')+cellFor(p.doc,'医生人数')+cellFor(p.nurse,'护士人数')+cellFor(p.rad,'辐射员工人数')
+'</div></div>'
+'<div class="stat-card"><div class="stat-card__title">急救培训</div><div class="stat-card__grid">'
+cellFor(p.trainSW,'本周培训场次')+cellFor(p.trainST,'累计培训场次')+cellFor(p.trainPW,'本周培训人次')+cellFor(p.trainPT,'累计培训人次')
+'</div></div>'
+'<div class="stat-card"><div class="stat-card__title">就诊服务</div><div class="stat-card__grid">'
+cellFor(p.visitW,'本周人数')+cellFor(p.visitT,'累计人数')
+'</div></div>'
+'<div class="stat-card"><div class="stat-card__title">慢病服务</div><div class="stat-card__grid">'
+cellFor(p.chrW,'本周人数')+cellFor(p.chrT,'累计人数')
+'</div></div>'
+'<div class="stat-card" style="grid-column:1/-1;"><div class="stat-card__title">健康宣教</div><div class="stat-card__grid">'
+cellFor(p.eduSW,'本周宣教场次')+cellFor(p.eduST,'累计宣教场次')+cellFor(p.eduPW,'本周宣教人次')+cellFor(p.eduPT,'累计宣教人次')
+'</div></div>';
['chartP1','chartP2','chartP3'].forEach(function(id){renderOneChart(id);});
}
function renderOneChart(id){
var n=document.getElementById('selPoint').value;
var p=points.filter(function(x){return x.name===n;})[0];
if(!p)return;
var swMap={chartP1:'wkP1',chartP2:'wkP2',chartP3:'wkP3'};
var w=weekState[swMap[id]];
if(id==='chartP1')drawPie2(id,{k:'就诊服务人员',v:w?p.visitW:p.visitT},{k:'慢病服务人数',v:w?p.chrW:p.chrT},PIE_C.p1,'人');
if(id==='chartP2')drawPie2(id,{k:'健康宣教',v:w?p.eduPW:p.eduPT},{k:'急救培训',v:w?p.trainPW:p.trainPT},PIE_C.p2,'人');
if(id==='chartP3')drawPie2(id,{k:'健康宣教',v:w?p.eduSW:p.eduST},{k:'急救培训',v:w?p.trainSW:p.trainST},PIE_C.p3,'场');
}
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();
initUnitSel();renderKpi();renderAllCharts();
</script>
</body>
</html>
+193 -197
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>医疗点统计-表格 | 一线医疗</title>
<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;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);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;
@@ -31,62 +31,50 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.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);}
.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:32px;font-size:var(--font-sm);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;}
.tabs{display:flex;gap:4px;background:#fff;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);padding:0 16px;margin-bottom:16px;}
.tab{padding:14px 18px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;}
.tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.tab-pane{display:none;}
.tab-pane.active{display:block;}
.kpi-row{display:flex;gap:16px;}
.kpi{flex:1;height:96px;border-radius:10px;color:#fff;display:flex;align-items:center;justify-content:space-around;padding:0 12px;}
.kpi--c1{background:linear-gradient(90deg,#0093FF 0%,#57B8FF 100%);}
.kpi--c2{background:linear-gradient(90deg,#48D147 0%,#88D187 100%);}
.kpi--c3{background:linear-gradient(90deg,#FFA440 0%,#FFC17C 100%);}
.kpi--c4{background:linear-gradient(90deg,#FF5640 0%,#FF8A7B 100%);}
.kpi--c5{background:linear-gradient(90deg,#8167F5 0%,#A897F5 100%);}
.kpi__mid{text-align:center;}
.kpi__val{font-size:28px;font-weight:700;}
.kpi__val small{font-size:var(--font-sm);font-weight:400;margin-left:3px;}
.kpi__name{font-size:var(--font-sm);margin-top:4px;}
.kpi__ico{width:34px;height:34px;opacity:.9;}
.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);}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-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;}
/* 分组表头:与图表页同源的「本周/累计」双列结构 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);margin-top:16px;}
.table-card__content{overflow-x:auto;}
table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
thead th{background:#FAFAFA;color:var(--text-secondary);font-weight:600;padding:10px 14px;border-bottom:1px solid var(--border);white-space:nowrap;text-align:center;font-size:var(--font-sm);}
thead tr.grp th{background:#F0F5FF;color:var(--text-primary);border-right:1px solid var(--border);font-size:var(--font-sm);}
tbody td{padding:12px 14px;border-bottom:1px solid #F0F0F0;text-align:center;white-space:nowrap;font-size:var(--font-sm);}
tbody tr:hover td{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);}
.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);}
.stat-name{text-align:left;font-weight:600;color:var(--text-primary);background:#FAFAFA;}
.grp-cell{text-align:left;font-weight:600;color:var(--primary);background:#F0F5FF;vertical-align:middle;}
.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>
@@ -97,8 +85,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<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="bed-device.html">床头呼叫</a>
<a href="stat-clinic-chart.html" class="active">数据统计</a>
<a href="hut-banner.html">健康小屋</a>
<a href="alert-dirty.html">异常事件</a>
</nav>
@@ -112,176 +100,184 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- 侧边栏(当前一级分组「数据统计」的二级菜单) -->
<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>
<a href="stat-clinic-chart.html" class="sidebar-item active">医疗点统计</a>
<a href="stat-ambulance-chart.html" class="sidebar-item">救护车统计</a>
<a href="stat-rescue-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 style="display:flex;align-items:center;margin-bottom:16px;">
<span style="font-size:var(--font-base);font-weight:600;">医疗点统计</span>
<div class="view-switch" style="margin-left:auto;">
<a href="stat-clinic-chart.html">图表视图</a>
<a class="active" href="stat-clinic-table.html">报表视图</a>
</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 class="tabs">
<div class="tab active" onclick="switchTab(0,this)">全部医疗点</div>
<div class="tab" onclick="switchTab(1,this)">单个医疗点</div>
</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>
<!-- ============ Tab1 全部医疗点:KPI + 逐点报表 ============ -->
<div class="tab-pane active" id="paneAll">
<div class="kpi-row" id="kpiRow"></div>
<!-- [交互] 与图表页 TOP5 同源:每点 4 组指标,均分 本周/累计 两列 -->
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr class="grp">
<th rowspan="2" style="min-width:110px;">所属单位</th>
<th rowspan="2" style="min-width:170px;">医疗点名称</th>
<th colspan="4">基本数据</th>
<th colspan="2">就诊服务人数</th>
<th colspan="2">慢病服务人数</th>
<th colspan="2">急救培训场次</th>
<th colspan="2">急救培训人次</th>
<th colspan="2">健康宣教场</th>
<th colspan="2">健康宣教人次</th>
</tr>
<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>
<th>本周</th><th>累计</th>
</tr>
</thead>
<tbody id="tbodyAll"></tbody>
</table>
</div>
<div class="pagination"><span id="totalTip"></span></div>
</div>
</div>
<!-- ============ Tab2 单个医疗点:级联筛选 + 指标报表 ============ -->
<div class="tab-pane" id="paneOne">
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="selUnit" onchange="onUnitChange()" style="min-width:160px;">
<option value="">请选择所属单位</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="selPoint" onchange="renderOne()" style="min-width:220px;">
<option value="">请先选择所属单位</option>
</select>
</div>
</div>
<div class="table-card">
<div class="table-card__content">
<table>
<thead>
<tr><th style="width:160px;text-align:left;padding-left:16px;">指标分组</th><th style="text-align:left;padding-left:16px;">指标名称</th><th style="width:160px;">本周</th><th style="width:160px;">累计</th></tr>
</thead>
<tbody id="tbodyOne"><tr><td class="td-empty" colspan="4">请先选择单位与医疗点</td></tr></tbody>
</table>
</div>
<div class="pagination"><span id="oneTip"></span></div>
</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 points=[
{unit:'采油一厂',name:'樊学作业区医疗点',short:'樊学',staff:7,doc:3,nurse:4,rad:2860,visitW:186,visitT:4120,chrW:15,chrT:326,trainSW:1,trainST:9,trainPW:45,trainPT:620,eduSW:2,eduST:14,eduPW:160,eduPT:1180},
{unit:'采油厂',name:'安塞采油队长住点医疗',short:'安塞',staff:3,doc:1,nurse:2,rad:960,visitW:32,visitT:780,chrW:4,chrT:74,trainSW:0,trainST:3,trainPW:0,trainPT:120,eduSW:0,eduST:4,eduPW:0,eduPT:180},
{unit:'采油厂',name:'姬塬作业区医疗点',short:'姬塬',staff:5,doc:2,nurse:3,rad:1730,visitW:118,visitT:860,chrW:6,chrT:63,trainSW:1,trainST:2,trainPW:60,trainPT:140,eduSW:1,eduST:3,eduPW:90,eduPT:210},
{unit:'采油二厂',name:'姬圪台作业区医疗点',short:'姬圪台',staff:5,doc:2,nurse:3,rad:2140,visitW:142,visitT:3310,chrW:20,chrT:415,trainSW:1,trainST:7,trainPW:38,trainPT:480,eduSW:1,eduST:11,eduPW:95,eduPT:860},
{unit:'采油二厂',name:'桐园作业区医疗点',short:'桐园',staff:4,doc:2,nurse:2,rad:1420,visitW:88,visitT:1960,chrW:10,chrT:158,trainSW:0,trainST:5,trainPW:0,trainPT:260,eduSW:1,eduST:8,eduPW:70,eduPT:520},
{unit:'第四采油厂',name:'西峰基地医疗点',short:'西峰',staff:11,doc:5,nurse:6,rad:4350,visitW:268,visitT:6420,chrW:31,chrT:728,trainSW:2,trainST:12,trainPW:96,trainPT:1350,eduSW:3,eduST:18,eduPW:240,eduPT:1760},
{unit:'采气一厂',name:'靖边气田医疗点',short:'靖边',staff:4,doc:2,nurse:2,rad:1680,visitW:96,visitT:2240,chrW:12,chrT:196,trainSW:1,trainST:6,trainPW:42,trainPT:350,eduSW:1,eduST:9,eduPW:88,eduPT:640},
{unit:'采一厂',name:'乌审旗采气区医疗点',short:'乌审旗',staff:5,doc:2,nurse:3,rad:1550,visitW:104,visitT:2480,chrW:9,chrT:210,trainSW:1,trainST:5,trainPW:36,trainPT:280,eduSW:1,eduST:8,eduPW:75,eduPT:590}
];
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 esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
function pair(a,b){return '<td class="num-cur">'+a+'</td><td class="num-total">'+b+'</td>';}
function switchTab(i,el){
document.querySelectorAll('.tab').forEach(function(t,k){t.classList.toggle('active',k===i);});
document.getElementById('paneAll').classList.toggle('active',i===0);
document.getElementById('paneOne').classList.toggle('active',i===1);
}
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>'
function renderKpi(){
var staff=0,doc=0,nurse=0,rad=0;
points.forEach(function(p){staff+=p.staff;doc+=p.doc;nurse+=p.nurse;rad+=p.rad;});
var items=[
{v:points.length,u:'个',n:'医疗点总数量',c:'c1',ico:'M4 24h4V12H4v12zm7 0h4V4h-4v20zm7 0h4V16h-4v8z'},
{v:staff,u:'人',n:'医务人员总数量',c:'c2',ico:'M16 4a5 5 0 1 1-5 5 5 5 0 0 1 5-5zm0 12c-6 0-10 3-10 6v2h20v-2c0-3-4-6-10-6z'},
{v:doc,u:'人',n:'医生总数量',c:'c3',ico:'M16 4a5 5 0 1 1-5 5 5 5 0 0 1 5-5zm0 12c-6 0-10 3-10 6v2h20v-2c0-3-4-6-10-6z'},
{v:nurse,u:'人',n:'护士总数量',c:'c4',ico:'M16 4a5 5 0 1 1-5 5 5 5 0 0 1 5-5zm0 12c-6 0-10 3-10 6v2h20v-2c0-3-4-6-10-6z'},
{v:rad,u:'人',n:'辐射员工总数量',c:'c5',ico:'M16 12a4 4 0 1 1-4 4 4 4 0 0 1 4-4zm0-9a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm0 26a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm-9-11a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm18 0a2 2 0 1 1-2 2 2 2 0 0 1 2-2z'}
];
document.getElementById('kpiRow').innerHTML=items.map(function(it){
return '<div class="kpi kpi--'+it.c+'">'
+'<svg class="kpi__ico" viewBox="0 0 32 32" fill="#fff" style="opacity:.85;"><path d="'+it.ico+'"/></svg>'
+'<div class="kpi__mid"><div class="kpi__val">'+it.v+'<small>'+it.u+'</small></div><div class="kpi__name">'+it.n+'</div></div>'
+'<span style="width:34px;"></span></div>';
}).join('');
}
function renderAll(){
/* 与图表 TOP5 同口径:按累计就诊人数降序排行 */
var list=points.slice().sort(function(a,b){return b.visitT-a.visitT;});
document.getElementById('tbodyAll').innerHTML=list.map(function(p){
return '<tr><td>'+esc(p.unit)+'</td><td style="text-align:left;font-weight:600;">'+esc(p.name)+'</td>'
+'<td>'+p.staff+'</td><td>'+p.doc+'</td><td>'+p.nurse+'</td><td>'+p.rad+'</td>'
+pair(p.visitW,p.visitT)+pair(p.chrW,p.chrT)
+pair(p.trainSW,p.trainST)+pair(p.trainPW,p.trainPT)
+pair(p.eduSW,p.eduST)+pair(p.eduPW,p.eduPT)
+'</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;}
document.getElementById('totalTip').textContent='共 '+list.length+' 个医疗点';
}
function applyFilter(){renderTable();showToast('查询完成');}
function resetFilter(){
document.getElementById('filterUnit').value='';
document.getElementById('filterDim').value='月';
onFilterUnit();renderTable();
/* ===== 单个医疗点报表 ===== */
function initUnitSel(){
var us=[];points.forEach(function(p){if(us.indexOf(p.unit)<0)us.push(p.unit);});
document.getElementById('selUnit').innerHTML='<option value="">请选择单位</option>'+us.map(function(u){return '<option>'+esc(u)+'</option>';}).join('');
}
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 onUnitChange(){
var u=document.getElementById('selUnit').value,sel=document.getElementById('selPoint');
if(!u){sel.innerHTML='<option value="">请先选择所属单位</option>';sel.disabled=true;clearOne();return;}
sel.disabled=false;
sel.innerHTML=points.filter(function(p){return p.unit===u;}).map(function(p){return '<option value="'+p.name+'">'+esc(p.name)+'</option>';}).join('');
renderOne();
}
function clearOne(){
document.getElementById('tbodyOne').innerHTML='<tr><td class="td-empty" colspan="4">请先选择单位与医疗点</td></tr>';
document.getElementById('oneTip').textContent='';
}
function row(groupSpan,name,w,t){return '<tr>'+groupSpan+'<td style="text-align:left;padding-left:16px;">'+esc(name)+'</td><td class="num-cur">'+w+'</td><td class="num-total">'+t+'</td></tr>';}
function renderOne(){
var n=document.getElementById('selPoint').value;
var p=points.filter(function(x){return x.name===n;})[0];
if(!p){clearOne();return;}
var g=function(rows,title){
return rows.map(function(r,i){
var span=i===0?'<td class="grp-cell" rowspan="'+rows.length+'" style="text-align:left;padding-left:16px;">'+title+'</td>':'';
return row(span,r[0],r[1],r[2]);
}).join('');
};
var html='';
html+=g([['医务人员总数',p.staff,'—'],['医生人数',p.doc,'—'],['护士人数',p.nurse,'—'],['辐射员工人数',p.rad,'—']],'基本数据');
html+=g([['就诊服务人数',p.visitW,p.visitT],['慢病服务人数',p.chrW,p.chrT]],'服务人数');
html+=g([['培训场次',p.trainSW,p.trainST],['培训人次',p.trainPW,p.trainPT],['宣教场次',p.eduSW,p.eduST],['宣教人次',p.eduPW,p.eduPT]],'培训宣教');
document.getElementById('tbodyOne').innerHTML=html;
document.getElementById('oneTip').textContent='医疗点:'+p.name;
}
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();
initUnitSel();renderKpi();renderAll();
</script>
</body>
</html>
@@ -0,0 +1,295 @@
<!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;font-size:var(--font-sm);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);}
.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:32px;font-size:var(--font-sm);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;}
.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-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.title-line{display:flex;align-items:center;margin-bottom:16px;}
.title-line span{font-size:var(--font-base);font-weight:600;}
/* 三组物品卡:小药棒(通栏) + 药品箱/急救包(并排),灰底圆角,布局照搬老系统 number-container */
.item-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;margin-top:16px;}
.item-card__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:12px;}
.item-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:16px 8px;}
.item-cell{text-align:center;}
.item-cell__ico{width:36px;height:36px;margin:0 auto;border-radius:50%;display:flex;align-items:center;justify-content:center;}
.item-cell__val{font-size:24px;font-weight:600;color:var(--text-primary);margin-top:6px;}
.item-cell__val small{font-size:var(--font-xs);font-weight:400;color:var(--text-placeholder);margin-left:2px;}
.item-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:4px;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.chart-grid{display:grid;grid-template-columns:1fr 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 svg{width:100%;height:auto;display:block;margin-top: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:16px;}
.modal--wide{width:640px;}
.record-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.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-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
.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-device.html">床头呼叫</a>
<a href="stat-clinic-chart.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-chart.html" class="sidebar-item">医疗点统计</a>
<a href="stat-ambulance-chart.html" class="sidebar-item">救护车统计</a>
<a href="stat-rescue-chart.html" class="sidebar-item active">救助物品统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 图表/报表两种查看形态,数据同源互切 -->
<div class="title-line">
<span>救助物品统计</span>
<div class="view-switch" style="margin-left:auto;">
<a class="active" href="stat-rescue-chart.html">图表视图</a>
<a href="stat-rescue-table.html">报表视图</a>
</div>
</div>
<!-- [交互] 单下拉「全部+各医疗点」(老系统此页无单位级联);数据导入与模板下载为启用功能 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="selPoint" onchange="renderScope()" style="min-width:220px;">
<option value="">全部</option>
</select>
</div>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-default" onclick="showToast('救助物品模板下载功能演示')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v7M4.5 6.5L7 9l2.5-2.5M2 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>救助物品模板下载
</button>
</div>
<!-- 小药棒:6 项指标通栏 -->
<div class="item-card">
<div class="item-card__title">小药棒</div>
<div class="item-card__grid" id="cardStick"></div>
</div>
<div class="two-col">
<div class="item-card" style="margin-top:0;">
<div class="item-card__title">药品箱</div>
<div class="item-card__grid" id="cardBox"></div>
</div>
<div class="item-card" style="margin-top:0;">
<div class="item-card__title">急救包</div>
<div class="item-card__grid" id="cardAid"></div>
</div>
</div>
<!-- 三张占比饼图:总数量 / 今年新增数量 / 总费用(三色,无周期切换,与老系统一致) -->
<div class="chart-grid">
<div class="chart-card">
<div class="chart-card__title">总数量比例</div>
<svg id="pieQty" viewBox="0 0 560 240"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">今年新增数量比例</div>
<svg id="pieNew" viewBox="0 0 560 240"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">总费用比例</div>
<svg id="pieCost" viewBox="0 0 560 240"></svg>
</div>
</div>
</main>
<!-- [交互] 数据导入弹窗(页内实现,与老系统 FileUpload 对应) -->
<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 style="margin-top:14px;">
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>救助物品模板.xlsx</div>
</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="taskMask">
<div class="modal modal--wide">
<div class="modal__title">救助物品导入任务</div>
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>结果</th><th>操作</th></tr></thead>
<tbody>
<tr><td>救助物品数据-2026年9月.xlsx</td><td>2026-09-05 15:41:36</td><td style="color:#389E0D;">成功:新增 96 条领用记录</td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
<tr><td>救助物品数据-2026年8月.xlsx</td><td>2026-08-28 11:09:52</td><td style="color:#D48806;">部分成功:新增 88 条、失败 4 条</td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
</tbody>
</table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 数据源:8 个医疗点救助物品台账(口径与老系统 rescueItems 一致:
bzxpsl 本周新配/ljpbsl 累计配备/bzxprs 本周新配人数/ljpzrs 累计配备人数/bzghyp 本周更换药片/xybzfy 小药棒总费用/
ypxljsl 药品箱数量/ypxxpsl 今年新配/ypxzfy 费用/jjbljsl 急救包数量/jjbxpsl 今年新配/jjbzfy 费用/xybxpsl 小药棒今年新配数量) */
var sites=[
{name:'樊学作业区医疗点',bzxpsl:26,xybxpsl:96,ljpbsl:1280,bzxprs:22,ljpzrs:940,bzghyp:360,xybzfy:42600,ypxljsl:6,ypxxpsl:2,ypxzfy:18000,jjbljsl:4,jjbxpsl:1,jjbzfy:6400},
{name:'安塞采油队长住点医疗室',bzxpsl:8,xybxpsl:34,ljpbsl:420,bzxprs:6,ljpzrs:310,bzghyp:120,xybzfy:13800,ypxljsl:2,ypxxpsl:1,ypxzfy:5600,jjbljsl:1,jjbxpsl:0,jjbzfy:1600},
{name:'姬塬作业区医疗点',bzxpsl:12,xybxpsl:47,ljpbsl:560,bzxprs:9,ljpzrs:402,bzghyp:150,xybzfy:18600,ypxljsl:3,ypxxpsl:1,ypxzfy:8200,jjbljsl:2,jjbxpsl:0,jjbzfy:3100},
{name:'姬圪台作业区医疗点',bzxpsl:18,xybxpsl:68,ljpbsl:890,bzxprs:15,ljpzrs:660,bzghyp:240,xybzfy:29800,ypxljsl:4,ypxxpsl:1,ypxzfy:12000,jjbljsl:3,jjbxpsl:1,jjbzfy:4800},
{name:'桐园作业区医疗点',bzxpsl:10,xybxpsl:41,ljpbsl:470,bzxprs:8,ljpzrs:350,bzghyp:130,xybzfy:15200,ypxljsl:3,ypxxpsl:1,ypxzfy:8400,jjbljsl:2,jjbxpsl:1,jjbzfy:3200},
{name:'西峰基地医疗点',bzxpsl:32,xybxpsl:118,ljpbsl:1520,bzxprs:28,ljpzrs:1180,bzghyp:410,xybzfy:51200,ypxljsl:8,ypxxpsl:2,ypxzfy:23600,jjbljsl:5,jjbxpsl:2,jjbzfy:8600},
{name:'靖边气田医疗点',bzxpsl:14,xybxpsl:52,ljpbsl:620,bzxprs:11,ljpzrs:450,bzghyp:170,xybzfy:20400,ypxljsl:3,ypxxpsl:1,ypxzfy:9200,jjbljsl:2,jjbxpsl:1,jjbzfy:3500},
{name:'乌审旗采气区医疗点',bzxpsl:12,xybxpsl:45,ljpbsl:540,bzxprs:9,ljpzrs:388,bzghyp:140,xybzfy:17900,ypxljsl:3,ypxxpsl:1,ypxzfy:8800,jjbljsl:2,jjbxpsl:1,jjbzfy:3300}
];
var RC=['#48D147','#FFAE53','#1E92FF'];
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
function scope(){
var p=document.getElementById('selPoint').value;
return p?sites.filter(function(x){return x.name===p;}):sites;
}
function sumField(list,f){return list.reduce(function(s,x){return s+x[f];},0);}
function cellFor(v,unit,name,bg,svg){
return '<div class="item-cell"><div class="item-cell__ico" style="background:'+bg+';"><svg width="20" height="20" viewBox="0 0 20 20" fill="none">'+svg+'</svg></div>'
+'<div class="item-cell__val">'+v+(unit?'<small>'+unit+'</small>':'')+'</div><div class="item-cell__name">'+name+'</div></div>';
}
/* 图标内联SVG(墨刀兼容):药片/数量/人数/费用 */
var ICO={
pill:'<rect x="6" y="3" width="8" height="14" rx="4" stroke="#fff" stroke-width="1.6"/><path d="M6 10h8" stroke="#fff" stroke-width="1.6"/>',
num:'<path d="M4 16h12M6 12l3-6 3 4 2-3" stroke="#fff" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>',
people:'<circle cx="10" cy="6.5" r="3" stroke="#fff" stroke-width="1.6"/><path d="M4.5 16c0-3 2.5-4.5 5.5-4.5S15.5 13 15.5 16" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/>',
cost:'<circle cx="10" cy="10" r="6.5" stroke="#fff" stroke-width="1.6"/><path d="M7.5 7l2.5 3 2.5-3M10 10v4M7.5 12h5" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/>'
};
function renderCards(){
var sc=scope();
document.getElementById('cardStick').innerHTML=
cellFor(sumField(sc,'bzxpsl'),'个','本周新配数量','#1E92FF',ICO.pill)
+cellFor(sumField(sc,'ljpbsl'),'个','累计配备数量','#48D147',ICO.num)
+cellFor(sumField(sc,'bzxprs'),'人','本周新配人数','#FFAE53',ICO.people)
+cellFor(sumField(sc,'ljpzrs'),'人','累计配备人数','#8168F5',ICO.people)
+cellFor(sumField(sc,'bzghyp'),'片','本周更换药品(片)','#FF5641',ICO.pill)
+cellFor(sumField(sc,'xybzfy').toLocaleString(),'元','总费用','#57B8FF',ICO.cost);
document.getElementById('cardBox').innerHTML=
cellFor(sumField(sc,'ypxljsl'),'个','数量','#1E92FF',ICO.num)
+cellFor(sumField(sc,'ypxxpsl'),'个','今年新配数量','#48D147',ICO.num)
+cellFor(sumField(sc,'ypxzfy').toLocaleString(),'元','总费用','#FFAE53',ICO.cost);
document.getElementById('cardAid').innerHTML=
cellFor(sumField(sc,'jjbljsl'),'个','数量','#1E92FF',ICO.num)
+cellFor(sumField(sc,'jjbxpsl'),'个','今年新配数量','#48D147',ICO.num)
+cellFor(sumField(sc,'jjbzfy').toLocaleString(),'元','总费用','#FFAE53',ICO.cost);
}
/* 三色饼图:2~3 切片+中心合计+右侧图例(与医疗点/救护车统计页饼图同风格) */
function drawPie(id,items){
var svg=document.getElementById(id),cx=150,cy=118,R=88;
var tot=items.reduce(function(s,i){return s+i.v;},0);
var ang=0,html='';
function pt(rad,a){return (cx+rad*Math.sin(a)).toFixed(2)+' '+(cy-rad*Math.cos(a)).toFixed(2);}
if(tot<=0){
html+='<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="#F5F5F5"/><text x="'+cx+'" y="'+(cy+4)+'" text-anchor="middle" font-size="12" fill="#999">暂无数据</text>';
svg.innerHTML=html;return;
}
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'+cx+' '+cy+' L'+pt(R,ang)+' A'+R+' '+R+' 0 '+large+' 1 '+pt(R,end)+' Z" fill="'+RC[idx]+'"><title>'+esc(it.k)+' '+it.v.toLocaleString()+'('+(it.v/tot*100).toFixed(0)+'%)</title></path>';
}
ang=end;
});
html+='<text x="'+cx+'" y="'+(cy-4)+'" text-anchor="middle" font-size="20" font-weight="600" fill="#333">'+tot.toLocaleString()+'</text>';
html+='<text x="'+cx+'" y="'+(cy+16)+'" text-anchor="middle" font-size="11" fill="#999">合计</text>';
var ly=cy-(items.length*24)/2+6;
items.forEach(function(it,idx){
html+='<rect x="320" y="'+ly+'" width="12" height="12" rx="3" fill="'+RC[idx]+'"/><text x="340" y="'+(ly+11)+'" font-size="12" fill="#666">'+esc(it.k)+' '+it.v.toLocaleString()+'('+(it.v/tot*100).toFixed(0)+'%)</text>';
ly+=24;
});
svg.innerHTML=html;
}
function renderPies(){
var sc=scope();
drawPie('pieQty',[{k:'小药棒',v:sumField(sc,'ljpbsl')},{k:'药品箱',v:sumField(sc,'ypxljsl')},{k:'急救包',v:sumField(sc,'jjbljsl')}]);
drawPie('pieNew',[{k:'小药棒',v:sumField(sc,'xybxpsl')},{k:'药品箱',v:sumField(sc,'ypxxpsl')},{k:'急救包',v:sumField(sc,'jjbxpsl')}]);
drawPie('pieCost',[{k:'小药棒',v:sumField(sc,'xybzfy')},{k:'药品箱',v:sumField(sc,'ypxzfy')},{k:'急救包',v:sumField(sc,'jjbzfy')}]);
}
function renderScope(){renderCards();renderPies();}
function openImport(){document.getElementById('importMask').classList.add('open');}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){closeImport();showToast('导入任务已提交,完成后将推送站内通知');}
/* [交互] 查看导入任务(异步任务演示) */
function openImportTasks(){document.getElementById('taskMask').classList.add('open');}
function closeTask(){document.getElementById('taskMask').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);
}
document.getElementById('importMask').addEventListener('click',function(e){if(e.target===this)closeImport();});
document.getElementById('taskMask').addEventListener('click',function(e){if(e.target===this)closeTask();});
document.getElementById('selPoint').innerHTML='<option value="">全部</option>'+sites.map(function(s){return '<option>'+esc(s.name)+'</option>';}).join('');
renderScope();
</script>
</body>
</html>
@@ -0,0 +1,304 @@
<!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;font-size:var(--font-sm);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);}
.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:32px;font-size:var(--font-sm);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;}
.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-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.btn-link{background:none;color:var(--primary);padding:0 4px;height:auto;}.btn-link:hover{color:#40a9ff;}
.title-line{display:flex;align-items:center;margin-bottom:16px;}
.title-line span{font-size:var(--font-base);font-weight:600;}
.item-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;margin-top:16px;}
.item-card__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:12px;}
.item-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:16px 8px;}
.item-cell{text-align:center;}
.item-cell__ico{width:36px;height:36px;margin:0 auto;border-radius:50%;display:flex;align-items:center;justify-content:center;}
.item-cell__val{font-size:24px;font-weight:600;color:var(--text-primary);margin-top:6px;}
.item-cell__val small{font-size:var(--font-xs);font-weight:400;color:var(--text-placeholder);margin-left:2px;}
.item-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:4px;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.tbl-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;margin-top:16px;}
.tbl-card__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:12px;}
.tbl-scroll{overflow-x:auto;}
table.rpt{width:100%;border-collapse:separate;border-spacing:0;min-width:1200px;}
.rpt th,.rpt td{border:1px solid #F0F0F0;padding:10px 12px;font-size:var(--font-sm);text-align:center;white-space:nowrap;}
.rpt thead th{background:#FAFAFA;color:var(--text-secondary);font-weight:500;font-size:var(--font-sm);}
.rpt thead tr:first-child th{background:#F0F5FF;color:var(--text-primary);font-weight:600;font-size:var(--font-sm);}
.rpt tbody tr:hover{background:#F8FBFF;}
.rpt .num{font-variant-numeric:tabular-nums;}
.rpt .hl{color:#1684FC;font-weight:600;}
.rpt tfoot td{background:#FAFAFA;font-weight:600;font-size:var(--font-sm);}
.rpt--sm{min-width:0;}
.td-empty{text-align:center;padding:32px 0;color:var(--text-placeholder);font-size:var(--font-sm);}
/* 导入弹窗(页内实现) */
.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;}
.modal--wide{width:640px;}
.record-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.record-table th{background:#FAFAFA;padding:8px 10px;text-align:left;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);white-space:nowrap;}
.record-table td{padding:10px;border-bottom:1px solid #F0F0F0;white-space:nowrap;}
.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-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
.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-device.html">床头呼叫</a>
<a href="stat-clinic-chart.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-chart.html" class="sidebar-item">医疗点统计</a>
<a href="stat-ambulance-chart.html" class="sidebar-item">救护车统计</a>
<a href="stat-rescue-chart.html" class="sidebar-item active">救助物品统计</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [交互] 图表/报表两种查看形态,数据同源互切 -->
<div class="title-line">
<span>救助物品统计</span>
<div class="view-switch" style="margin-left:auto;">
<a href="stat-rescue-chart.html">图表视图</a>
<a class="active" href="stat-rescue-table.html">报表视图</a>
</div>
</div>
<!-- [交互] 与图表视图共用同一医疗点下拉(含「全部」)与导入/模板功能 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="selPoint" onchange="renderScope()" style="min-width:220px;">
<option value="">全部</option>
</select>
</div>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-default" onclick="showToast('救助物品模板下载功能演示')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v7M4.5 6.5L7 9l2.5-2.5M2 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>救助物品模板下载
</button>
</div>
<!-- 物品卡与图表视图同源 -->
<div class="item-card">
<div class="item-card__title">小药棒</div>
<div class="item-card__grid" id="cardStick"></div>
</div>
<div class="two-col">
<div class="item-card" style="margin-top:0;">
<div class="item-card__title">药品箱</div>
<div class="item-card__grid" id="cardBox"></div>
</div>
<div class="item-card" style="margin-top:0;">
<div class="item-card__title">急救包</div>
<div class="item-card__grid" id="cardAid"></div>
</div>
</div>
<!-- 三张占比饼图的报表形态:物品类别 × 三项指标与占比 -->
<div class="tbl-card">
<div class="tbl-card__title">救助物品占比统计</div>
<div class="tbl-scroll"><table class="rpt rpt--sm" id="tblRatio"></table></div>
</div>
<!-- 明细报表:逐医疗点全量指标(分组表头),对应图表页的全局/单点视角 -->
<div class="tbl-card">
<div class="tbl-card__title" id="tblDetailTitle">各医疗点救助物品明细</div>
<div class="tbl-scroll"><table class="rpt" id="tblDetail"></table></div>
</div>
</main>
<!-- [交互] 数据导入弹窗(页内实现,与图表视图一致) -->
<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 style="margin-top:14px;">
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>救助物品模板.xlsx</div>
</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="taskMask">
<div class="modal modal--wide">
<div class="modal__title">救助物品导入任务</div>
<table class="record-table">
<thead><tr><th>文件名称</th><th>时间</th><th>结果</th><th>操作</th></tr></thead>
<tbody>
<tr><td>救助物品数据-2026年9月.xlsx</td><td>2026-09-05 15:41:36</td><td style="color:#389E0D;">成功:新增 96 条领用记录</td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
<tr><td>救助物品数据-2026年8月.xlsx</td><td>2026-08-28 11:09:52</td><td style="color:#D48806;">部分成功:新增 88 条、失败 4 条</td><td><button class="btn btn-link" onclick="showToast('导入明细下载中…')">下载明细</button></td></tr>
</tbody>
</table>
<div class="modal__footer"><button class="btn btn-default" onclick="closeTask()">关闭</button></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/* 数据源与「救助物品统计-图表」完全一致 */
var sites=[
{name:'樊学作业区医疗点',bzxpsl:26,xybxpsl:96,ljpbsl:1280,bzxprs:22,ljpzrs:940,bzghyp:360,xybzfy:42600,ypxljsl:6,ypxxpsl:2,ypxzfy:18000,jjbljsl:4,jjbxpsl:1,jjbzfy:6400},
{name:'安塞采油队长住点医疗室',bzxpsl:8,xybxpsl:34,ljpbsl:420,bzxprs:6,ljpzrs:310,bzghyp:120,xybzfy:13800,ypxljsl:2,ypxxpsl:1,ypxzfy:5600,jjbljsl:1,jjbxpsl:0,jjbzfy:1600},
{name:'姬塬作业区医疗点',bzxpsl:12,xybxpsl:47,ljpbsl:560,bzxprs:9,ljpzrs:402,bzghyp:150,xybzfy:18600,ypxljsl:3,ypxxpsl:1,ypxzfy:8200,jjbljsl:2,jjbxpsl:0,jjbzfy:3100},
{name:'姬圪台作业区医疗点',bzxpsl:18,xybxpsl:68,ljpbsl:890,bzxprs:15,ljpzrs:660,bzghyp:240,xybzfy:29800,ypxljsl:4,ypxxpsl:1,ypxzfy:12000,jjbljsl:3,jjbxpsl:1,jjbzfy:4800},
{name:'桐园作业区医疗点',bzxpsl:10,xybxpsl:41,ljpbsl:470,bzxprs:8,ljpzrs:350,bzghyp:130,xybzfy:15200,ypxljsl:3,ypxxpsl:1,ypxzfy:8400,jjbljsl:2,jjbxpsl:1,jjbzfy:3200},
{name:'西峰基地医疗点',bzxpsl:32,xybxpsl:118,ljpbsl:1520,bzxprs:28,ljpzrs:1180,bzghyp:410,xybzfy:51200,ypxljsl:8,ypxxpsl:2,ypxzfy:23600,jjbljsl:5,jjbxpsl:2,jjbzfy:8600},
{name:'靖边气田医疗点',bzxpsl:14,xybxpsl:52,ljpbsl:620,bzxprs:11,ljpzrs:450,bzghyp:170,xybzfy:20400,ypxljsl:3,ypxxpsl:1,ypxzfy:9200,jjbljsl:2,jjbxpsl:1,jjbzfy:3500},
{name:'乌审旗采气区医疗点',bzxpsl:12,xybxpsl:45,ljpbsl:540,bzxprs:9,ljpzrs:388,bzghyp:140,xybzfy:17900,ypxljsl:3,ypxxpsl:1,ypxzfy:8800,jjbljsl:2,jjbxpsl:1,jjbzfy:3300}
];
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
function scope(){
var p=document.getElementById('selPoint').value;
return p?sites.filter(function(x){return x.name===p;}):sites;
}
function sumField(list,f){return list.reduce(function(s,x){return s+x[f];},0);}
function cellFor(v,unit,name,bg,svg){
return '<div class="item-cell"><div class="item-cell__ico" style="background:'+bg+';"><svg width="20" height="20" viewBox="0 0 20 20" fill="none">'+svg+'</svg></div>'
+'<div class="item-cell__val">'+v+(unit?'<small>'+unit+'</small>':'')+'</div><div class="item-cell__name">'+name+'</div></div>';
}
var ICO={
pill:'<rect x="6" y="3" width="8" height="14" rx="4" stroke="#fff" stroke-width="1.6"/><path d="M6 10h8" stroke="#fff" stroke-width="1.6"/>',
num:'<path d="M4 16h12M6 12l3-6 3 4 2-3" stroke="#fff" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>',
people:'<circle cx="10" cy="6.5" r="3" stroke="#fff" stroke-width="1.6"/><path d="M4.5 16c0-3 2.5-4.5 5.5-4.5S15.5 13 15.5 16" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/>',
cost:'<circle cx="10" cy="10" r="6.5" stroke="#fff" stroke-width="1.6"/><path d="M7.5 7l2.5 3 2.5-3M10 10v4M7.5 12h5" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/>'
};
function renderCards(){
var sc=scope();
document.getElementById('cardStick').innerHTML=
cellFor(sumField(sc,'bzxpsl'),'个','本周新配数量','#1E92FF',ICO.pill)
+cellFor(sumField(sc,'ljpbsl'),'个','累计配备数量','#48D147',ICO.num)
+cellFor(sumField(sc,'bzxprs'),'人','本周新配人数','#FFAE53',ICO.people)
+cellFor(sumField(sc,'ljpzrs'),'人','累计配备人数','#8168F5',ICO.people)
+cellFor(sumField(sc,'bzghyp'),'片','本周更换药品(片)','#FF5641',ICO.pill)
+cellFor(sumField(sc,'xybzfy').toLocaleString(),'元','总费用','#57B8FF',ICO.cost);
document.getElementById('cardBox').innerHTML=
cellFor(sumField(sc,'ypxljsl'),'个','数量','#1E92FF',ICO.num)
+cellFor(sumField(sc,'ypxxpsl'),'个','今年新配数量','#48D147',ICO.num)
+cellFor(sumField(sc,'ypxzfy').toLocaleString(),'元','总费用','#FFAE53',ICO.cost);
document.getElementById('cardAid').innerHTML=
cellFor(sumField(sc,'jjbljsl'),'个','数量','#1E92FF',ICO.num)
+cellFor(sumField(sc,'jjbxpsl'),'个','今年新配数量','#48D147',ICO.num)
+cellFor(sumField(sc,'jjbzfy').toLocaleString(),'元','总费用','#FFAE53',ICO.cost);
}
/* 占比统计表:三张饼图(总数量/今年新增/总费用)的行式表达 */
function renderRatio(){
var sc=scope();
var qty=[sumField(sc,'ljpbsl'),sumField(sc,'ypxljsl'),sumField(sc,'jjbljsl')];
var nw=[sumField(sc,'xybxpsl'),sumField(sc,'ypxxpsl'),sumField(sc,'jjbxpsl')];
var cf=[sumField(sc,'xybzfy'),sumField(sc,'ypxzfy'),sumField(sc,'jjbzfy')];
var tq=qty[0]+qty[1]+qty[2]||1,tn=nw[0]+nw[1]+nw[2]||1,tc=cf[0]+cf[1]+cf[2]||1;
var names=['小药棒','药品箱','急救包'];
var h='<thead><tr><th>物品类别</th><th>总数量</th><th>数量占比</th><th>今年新增数量</th><th>新增占比</th><th>总费用(元)</th><th>费用占比</th></tr></thead><tbody>';
names.forEach(function(n,i){
h+='<tr><td style="font-weight:600;">'+n+'</td><td class="num hl">'+qty[i].toLocaleString()+'</td><td class="num">'+(qty[i]/tq*100).toFixed(1)+'%</td>'
+'<td class="num hl">'+nw[i].toLocaleString()+'</td><td class="num">'+(nw[i]/tn*100).toFixed(1)+'%</td>'
+'<td class="num hl">'+cf[i].toLocaleString()+'</td><td class="num">'+(cf[i]/tc*100).toFixed(1)+'%</td></tr>';
});
h+='</tbody><tfoot><tr><td>合计</td><td class="num">'+(qty[0]+qty[1]+qty[2]).toLocaleString()+'</td><td class="num">100%</td>'
+'<td class="num">'+(nw[0]+nw[1]+nw[2]).toLocaleString()+'</td><td class="num">100%</td>'
+'<td class="num">'+(cf[0]+cf[1]+cf[2]).toLocaleString()+'</td><td class="num">100%</td></tr></tfoot>';
document.getElementById('tblRatio').innerHTML=h;
}
/* 明细报表:分组表头 小药棒(7)/药品箱(3)/急救包(3),按累计配备数量降序 */
function renderDetail(){
var sc=scope().slice().sort(function(a,b){return b.ljpbsl-a.ljpbsl;});
document.getElementById('tblDetailTitle').textContent=document.getElementById('selPoint').value?('救助物品明细 · '+document.getElementById('selPoint').value):'各医疗点救助物品明细';
var h='<thead><tr><th rowspan="2">所属医疗点</th><th colspan="7">小药棒</th><th colspan="3">药品箱</th><th colspan="3">急救包</th></tr>'
+'<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>';
if(!sc.length){h+='<tbody><tr><td colspan="14" class="td-empty">暂无数据</td></tr></tbody>';document.getElementById('tblDetail').innerHTML=h;return;}
h+='<tbody>'+sc.map(function(s){
return '<tr><td style="font-weight:600;">'+esc(s.name)+'</td>'
+'<td class="num">'+s.bzxpsl+'</td><td class="num">'+s.xybxpsl+'</td><td class="num hl">'+s.ljpbsl+'</td><td class="num">'+s.bzxprs+'</td><td class="num">'+s.ljpzrs+'</td><td class="num">'+s.bzghyp+'</td><td class="num">'+s.xybzfy.toLocaleString()+'</td>'
+'<td class="num">'+s.ypxljsl+'</td><td class="num">'+s.ypxxpsl+'</td><td class="num">'+s.ypxzfy.toLocaleString()+'</td>'
+'<td class="num">'+s.jjbljsl+'</td><td class="num">'+s.jjbxpsl+'</td><td class="num">'+s.jjbzfy.toLocaleString()+'</td></tr>';
}).join('')+'</tbody>';
var T={};
['bzxpsl','xybxpsl','ljpbsl','bzxprs','ljpzrs','bzghyp','xybzfy','ypxljsl','ypxxpsl','ypxzfy','jjbljsl','jjbxpsl','jjbzfy'].forEach(function(f){T[f]=sumField(sc,f);});
h+='<tfoot><tr><td>合计</td><td class="num">'+T.bzxpsl+'</td><td class="num">'+T.xybxpsl+'</td><td class="num">'+T.ljpbsl+'</td><td class="num">'+T.bzxprs+'</td><td class="num">'+T.ljpzrs+'</td><td class="num">'+T.bzghyp+'</td><td class="num">'+T.xybzfy.toLocaleString()+'</td>'
+'<td class="num">'+T.ypxljsl+'</td><td class="num">'+T.ypxxpsl+'</td><td class="num">'+T.ypxzfy.toLocaleString()+'</td>'
+'<td class="num">'+T.jjbljsl+'</td><td class="num">'+T.jjbxpsl+'</td><td class="num">'+T.jjbzfy.toLocaleString()+'</td></tr></tfoot>';
document.getElementById('tblDetail').innerHTML=h;
}
function renderScope(){renderCards();renderRatio();renderDetail();}
function openImport(){document.getElementById('importMask').classList.add('open');}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){closeImport();showToast('导入任务已提交,完成后将推送站内通知');}
/* [交互] 查看导入任务(异步任务演示) */
function openImportTasks(){document.getElementById('taskMask').classList.add('open');}
function closeTask(){document.getElementById('taskMask').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);
}
document.getElementById('importMask').addEventListener('click',function(e){if(e.target===this)closeImport();});
document.getElementById('taskMask').addEventListener('click',function(e){if(e.target===this)closeTask();});
document.getElementById('selPoint').innerHTML='<option value="">全部</option>'+sites.map(function(s){return '<option>'+esc(s.name)+'</option>';}).join('');
renderScope();
</script>
</body>
</html>