feat: 医疗点PAD端细节优化与管理端体检统计二级/三级单位切换

医疗点PAD端(other-module/medical-pad/):
- 退出登录统一加二次确认(confirm弹窗后再回登录页)
- 全站字段名规范:工号统一为员工编号
- 随访抽屉:新增/编辑双模式,任务中心带参进入编辑态隐藏搜索并回填员工信息、标题联动编辑随访
- 健康宣教抽屉:签字方式改为手写签名/签字图片二选一(图片上传预览),去掉负责人字段,活动资料加文件不超过10M提示
- 报告解读抽屉:新增查看详情模式(?view=1纯显示只读,隐藏删除/重新解读,签字静态化)
- 非员工就诊抽屉:手机号改联系电话、去掉身份证自动解析的性别/年龄只读字段
- 员工档案详情:新增体检历史/分析报告/就诊记录/随访记录/就医推荐Tab,体检报告查看弹窗,就医推荐地图医疗点卡片
- 健康监测页:事件列表由表格改为双列卡片按事件类型着色,新增定位地图,搜索栏改为更多筛选展开收起,移除仅A01A34医疗点可见提示
- 床头呼叫等页面搜索栏统一重构为更多筛选展开收起

管理端健康体检(web-admin/health-checkup/):
- 冠脉CTA/胃肠镜统计页新增二级/三级单位切换TAB,二级按单位聚合并隐藏部门列与部门筛选,三级按部门展示
- 新增受检率/检出率/患病率三列,表头问号悬浮气泡说明计算口径
This commit is contained in:
liuyunqin
2026-09-09 18:08:00 +08:00
parent 03a186fc7d
commit 8a3a116501
20 changed files with 1605 additions and 585 deletions
+66 -15
View File
@@ -46,6 +46,25 @@
.sidebar-subgroup .sidebar-subgroup>.sidebar-subgroup__header{font-size:12px;}
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 单位级别TAB */
.unit-level-tab{display:flex;align-items:center;gap:0;margin-bottom:16px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:0 24px;border-bottom:1px solid var(--border);}
.unit-level-tab__item{position:relative;padding:14px 20px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;transition:all .2s;user-select:none;}
.unit-level-tab__item:hover{color:var(--primary);}
.unit-level-tab__item.active{color:var(--primary);font-weight:500;}
.unit-level-tab__item.active::after{content:'';position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--primary);border-radius:1px;}
/* 表头计算规则提示(问号+悬浮气泡) */
.th-help{position:relative;display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:#BFBFBF;color:#fff;font-size:10px;font-weight:600;cursor:help;vertical-align:middle;margin-left:4px;}
.th-help:hover{background:var(--primary);}
.th-help__tip{visibility:hidden;opacity:0;position:absolute;top:calc(100% + 9px);left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.8);color:#fff;font-size:var(--font-sm);font-weight:400;padding:7px 12px;border-radius:var(--radius-sm);white-space:nowrap;z-index:130;transition:opacity .15s;line-height:1.5;}
.th-help__tip::before{content:'';position:absolute;top:-4px;left:50%;margin-left:-4px;width:8px;height:8px;background:rgba(0,0,0,0.8);transform:rotate(45deg);}
.th-help__tip--right{left:auto;right:-7px;transform:none;}
.th-help__tip--right::before{left:auto;right:11px;margin-left:0;}
.th-help:hover .th-help__tip{visibility:visible;opacity:1;}
/* 二级单位tab下隐藏部门列与部门筛选 */
.col-dept{display:none;}
body.level-3 .col-dept{display:table-cell;}
.filter-item.filter-dept{display:none;}
body.level-3 .filter-item.filter-dept{display:flex;}
/* 筛选栏 */
.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;}
@@ -183,6 +202,12 @@
<!-- 主内容 -->
<main class="main">
<!-- 单位级别切换TAB -->
<div class="unit-level-tab">
<div class="unit-level-tab__item active" onclick="switchUnitLevel(this)">二级单位</div>
<div class="unit-level-tab__item" onclick="switchUnitLevel(this)">三级单位</div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
@@ -203,7 +228,7 @@
<option value="">请选择单位</option>
</select>
</div>
<div class="filter-item">
<div class="filter-item filter-dept">
<label>部门:</label>
<select class="filter-select" id="f-dept" onchange="state.dept=this.value;state.page=1;renderTable();">
<option value="">请选择部门</option>
@@ -241,7 +266,7 @@
<div class="table-card__content">
<table id="statTable">
<thead>
<tr><th>单位</th><th>部门</th><th>应检人数</th><th>已体检人数</th><th>已预约人数</th><th>已出报告人数</th><th>未预约人数</th><th>体检年份</th></tr>
<tr><th>单位</th><th class="col-dept">部门</th><th>应检人数</th><th>已体检人数</th><th>已预约人数</th><th>已出报告人数</th><th>未预约人数</th><th>受检率<span class="th-help">?<span class="th-help__tip">受检率 = 实际检查人数 ÷ 应检人数</span></span></th><th>检出率<span class="th-help">?<span class="th-help__tip">检出率 = 异常人数 ÷ 实际检查人数</span></span></th><th>患病率<span class="th-help">?<span class="th-help__tip th-help__tip--right">患病率 = 单病种异常人数 ÷ 实际参加检查人数</span></span></th><th>体检年份</th></tr>
</thead>
<tbody></tbody>
</table>
@@ -254,32 +279,36 @@
/* ============ 数据 ============ */
/* 确定性伪随机(0-1),保证每次打开数据一致 */
function pseudo(i){var x=Math.sin(i*127.1)*43758.5453;return x-Math.floor(x);}
function mkRow(unit,dept,total,checked,booked){
return {unit:unit,dept:dept,total:total,checked:checked,booked:booked,reported:0,unbooked:total-checked-booked,year:'2026'};
/* 按总人数与种子确定性生成一行:受检率=已体检/应检;检出率=异常/已体检;患病率=单病种异常/已体检 */
function mkRow(unit,dept,total,seed){
var checked=Math.floor(total*(0.6+pseudo(seed+0.3)*0.35));
var booked=Math.floor(total*(0.1+pseudo(seed+0.6)*0.2));
var reported=Math.floor(checked*(0.88+pseudo(seed+0.8)*0.12));
var abnormal=Math.max(0,Math.floor(checked*(0.15+pseudo(seed+1.1)*0.4)));
var disease=Math.max(0,Math.floor(abnormal*(0.3+pseudo(seed+1.3)*0.5)));
return {unit:unit,dept:dept,total:total,checked:checked,booked:booked,reported:reported,unbooked:Math.max(0,total-checked-booked),abnormal:abnormal,disease:disease,year:'2026'};
}
var rows=[];
/* 参考图前10条(第二采油厂) */
[['南梁采油作业区',205,0,4],['消防大队',88,0,1],['温台采油作业区',174,1,27],['樊家川采油作业区',161,0,15],['科技信息部',7,0,0],['西峰采油二区',206,0,5],['地质研究所',31,0,2],['西峰采油三区',158,0,10],['华池采油作业区',198,0,30],['马岭北采油作业区',174,0,11]].forEach(function(d){
rows.push(mkRow('第二采油厂',d[0],d[1],d[2],d[3]));
[['南梁采油作业区',205],['消防大队',88],['温台采油作业区',174],['樊家川采油作业区',161],['科技信息部',7],['西峰采油二区',206],['地质研究所',31],['西峰采油三区',158],['华池采油作业区',198],['马岭北采油作业区',174]].forEach(function(d,i){
rows.push(mkRow('第二采油厂',d[0],d[1],1000+i));
});
var BASE_DEPTS=['采油作业一区','采油作业二区','采油作业三区','采油作业四区','采油作业五区','采油作业六区','集输作业区','地质研究所','工艺研究所','信息中心','安全环保科','生产运行科','经营管理科','人力资源科','财务资产科','物资供应站','维修抢修中心','消防大队','综合办公室','后勤服务部','质量监督站','培训站','离退休管理科','保卫科'];
var EXTRA_UNITS=[['第三采油厂',24],['第四采油厂',24],['第五采油厂',24],['公用事业公司',24],['运输公司',24],['水电厂',24],['采气一厂',10]];
/* 第二采油厂补充5条,其余单位按部门池生成,合计169条 */
['采油作业一区','采油作业六区','集输作业区','生产运行科','保卫科'].forEach(function(dept,i){
var idx=100+i,total=20+Math.floor(pseudo(idx)*260),checked=pseudo(idx+0.31)<0.12?1:0;
var booked=Math.floor(pseudo(idx+0.67)*Math.min(35,Math.floor(total*0.18)));
rows.push(mkRow('第二采油厂',dept,total,checked,booked));
var idx=100+i,total=20+Math.floor(pseudo(idx)*260);
rows.push(mkRow('第二采油厂',dept,total,idx));
});
var gi=0;
EXTRA_UNITS.forEach(function(u){
for(var i=0;i<u[1];i++){
gi++;
var idx=200+gi,total=20+Math.floor(pseudo(idx)*260),checked=pseudo(idx+0.31)<0.12?1:0;
var booked=Math.floor(pseudo(idx+0.67)*Math.min(35,Math.floor(total*0.18)));
rows.push(mkRow(u[0],BASE_DEPTS[i],total,checked,booked));
var idx=200+gi,total=20+Math.floor(pseudo(idx)*260);
rows.push(mkRow(u[0],BASE_DEPTS[i],total,idx));
}
});
var state={unit:'',dept:'',year:'2026',page:1,pageSize:10};
var state={unit:'',dept:'',year:'2026',page:1,pageSize:10,level:'level-2'};
/* ============ 筛选 ============ */
function initUnitOptions(){
@@ -346,16 +375,27 @@ function filteredRows(){
return (!state.unit||r.unit===state.unit)&&(!state.dept||r.dept===state.dept)&&(!state.year||r.year===state.year);
});
}
function ratePct(a,b){return b>0?(a/b*100).toFixed(1)+'%':'0.0%';}
function aggregateRows(list){
var map={};
list.forEach(function(r){
var u=map[r.unit];
if(!u){u=map[r.unit]={unit:r.unit,dept:'',total:0,checked:0,booked:0,reported:0,unbooked:0,abnormal:0,disease:0,year:r.year};}
u.total+=r.total;u.checked+=r.checked;u.booked+=r.booked;u.reported+=r.reported;u.unbooked+=r.unbooked;u.abnormal+=r.abnormal;u.disease+=r.disease;
});
return Object.keys(map).map(function(k){return map[k];});
}
function renderTable(){
var list=filteredRows();
if(state.level==='level-2')list=aggregateRows(list);
var totalPages=Math.max(1,Math.ceil(list.length/state.pageSize));
if(state.page>totalPages)state.page=totalPages;
var start=(state.page-1)*state.pageSize;
var pageRows=list.slice(start,start+state.pageSize);
var tbody=document.querySelector('#statTable tbody');
tbody.innerHTML=pageRows.length?pageRows.map(function(r){
return '<tr><td>'+r.unit+'</td><td>'+r.dept+'</td><td>'+r.total+'</td><td>'+r.checked+'</td><td>'+r.booked+'</td><td>'+r.reported+'</td><td>'+r.unbooked+'</td><td>'+r.year+'</td></tr>';
}).join(''):'<tr><td colspan="8" style="padding:0;border-bottom:none;"><div class="empty-tip">暂无数据</div></td></tr>';
return '<tr><td>'+r.unit+'</td><td class="col-dept">'+r.dept+'</td><td>'+r.total+'</td><td>'+r.checked+'</td><td>'+r.booked+'</td><td>'+r.reported+'</td><td>'+r.unbooked+'</td><td>'+ratePct(r.checked,r.total)+'</td><td>'+ratePct(r.abnormal,r.checked)+'</td><td>'+ratePct(r.disease,r.checked)+'</td><td>'+r.year+'</td></tr>';
}).join(''):'<tr><td colspan="11" style="padding:0;border-bottom:none;"><div class="empty-tip">暂无数据</div></td></tr>';
renderPagination(list.length,totalPages);
}
@@ -395,6 +435,17 @@ function jumpTo(totalPages){
renderTable();
}
/* 二级/三级单位切换:二级按单位聚合并隐藏部门;三级按部门展示 */
function switchUnitLevel(el){
var items=el.parentElement.querySelectorAll('.unit-level-tab__item');
items.forEach(function(item){item.classList.remove('active');});
el.classList.add('active');
var tab=el.textContent.trim();
state.level=(tab==='三级单位')?'level-3':'level-2';
document.body.classList.toggle('level-3',state.level==='level-3');
state.page=1;
renderTable();
}
/* ============ 通用 ============ */
function toggleSubgroup(header){
header.parentElement.classList.toggle('open');
+66 -15
View File
@@ -46,6 +46,25 @@
.sidebar-subgroup .sidebar-subgroup>.sidebar-subgroup__header{font-size:12px;}
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 单位级别TAB */
.unit-level-tab{display:flex;align-items:center;gap:0;margin-bottom:16px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:0 24px;border-bottom:1px solid var(--border);}
.unit-level-tab__item{position:relative;padding:14px 20px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;transition:all .2s;user-select:none;}
.unit-level-tab__item:hover{color:var(--primary);}
.unit-level-tab__item.active{color:var(--primary);font-weight:500;}
.unit-level-tab__item.active::after{content:'';position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--primary);border-radius:1px;}
/* 表头计算规则提示(问号+悬浮气泡) */
.th-help{position:relative;display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:#BFBFBF;color:#fff;font-size:10px;font-weight:600;cursor:help;vertical-align:middle;margin-left:4px;}
.th-help:hover{background:var(--primary);}
.th-help__tip{visibility:hidden;opacity:0;position:absolute;top:calc(100% + 9px);left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.8);color:#fff;font-size:var(--font-sm);font-weight:400;padding:7px 12px;border-radius:var(--radius-sm);white-space:nowrap;z-index:130;transition:opacity .15s;line-height:1.5;}
.th-help__tip::before{content:'';position:absolute;top:-4px;left:50%;margin-left:-4px;width:8px;height:8px;background:rgba(0,0,0,0.8);transform:rotate(45deg);}
.th-help__tip--right{left:auto;right:-7px;transform:none;}
.th-help__tip--right::before{left:auto;right:11px;margin-left:0;}
.th-help:hover .th-help__tip{visibility:visible;opacity:1;}
/* 二级单位tab下隐藏部门列与部门筛选 */
.col-dept{display:none;}
body.level-3 .col-dept{display:table-cell;}
.filter-item.filter-dept{display:none;}
body.level-3 .filter-item.filter-dept{display:flex;}
/* 筛选栏 */
.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;}
@@ -183,6 +202,12 @@
<!-- 主内容 -->
<main class="main">
<!-- 单位级别切换TAB -->
<div class="unit-level-tab">
<div class="unit-level-tab__item active" onclick="switchUnitLevel(this)">二级单位</div>
<div class="unit-level-tab__item" onclick="switchUnitLevel(this)">三级单位</div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
@@ -203,7 +228,7 @@
<option value="">请选择单位</option>
</select>
</div>
<div class="filter-item">
<div class="filter-item filter-dept">
<label>部门:</label>
<select class="filter-select" id="f-dept" onchange="state.dept=this.value;state.page=1;renderTable();">
<option value="">请选择部门</option>
@@ -241,7 +266,7 @@
<div class="table-card__content">
<table id="statTable">
<thead>
<tr><th>单位</th><th>部门</th><th>应检人数</th><th>已体检人数</th><th>已预约人数</th><th>已出报告人数</th><th>未预约人数</th><th>体检年份</th></tr>
<tr><th>单位</th><th class="col-dept">部门</th><th>应检人数</th><th>已体检人数</th><th>已预约人数</th><th>已出报告人数</th><th>未预约人数</th><th>受检率<span class="th-help">?<span class="th-help__tip">受检率 = 实际检查人数 ÷ 应检人数</span></span></th><th>检出率<span class="th-help">?<span class="th-help__tip">检出率 = 异常人数 ÷ 实际检查人数</span></span></th><th>患病率<span class="th-help">?<span class="th-help__tip th-help__tip--right">患病率 = 单病种异常人数 ÷ 实际参加检查人数</span></span></th><th>体检年份</th></tr>
</thead>
<tbody></tbody>
</table>
@@ -254,32 +279,36 @@
/* ============ 数据 ============ */
/* 确定性伪随机(0-1),保证每次打开数据一致 */
function pseudo(i){var x=Math.sin(i*127.1)*43758.5453;return x-Math.floor(x);}
function mkRow(unit,dept,total,checked,booked){
return {unit:unit,dept:dept,total:total,checked:checked,booked:booked,reported:0,unbooked:total-checked-booked,year:'2026'};
/* 按总人数与种子确定性生成一行:受检率=已体检/应检;检出率=异常/已体检;患病率=单病种异常/已体检 */
function mkRow(unit,dept,total,seed){
var checked=Math.floor(total*(0.6+pseudo(seed+0.3)*0.35));
var booked=Math.floor(total*(0.1+pseudo(seed+0.6)*0.2));
var reported=Math.floor(checked*(0.88+pseudo(seed+0.8)*0.12));
var abnormal=Math.max(0,Math.floor(checked*(0.15+pseudo(seed+1.1)*0.4)));
var disease=Math.max(0,Math.floor(abnormal*(0.3+pseudo(seed+1.3)*0.5)));
return {unit:unit,dept:dept,total:total,checked:checked,booked:booked,reported:reported,unbooked:Math.max(0,total-checked-booked),abnormal:abnormal,disease:disease,year:'2026'};
}
var rows=[];
/* 参考图前10条(第二采油厂) */
[['南梁采油作业区',205,0,4],['消防大队',88,0,1],['温台采油作业区',174,1,27],['樊家川采油作业区',161,0,15],['科技信息部',7,0,0],['西峰采油二区',206,0,5],['地质研究所',31,0,2],['西峰采油三区',158,0,10],['华池采油作业区',198,0,30],['马岭北采油作业区',174,0,11]].forEach(function(d){
rows.push(mkRow('第二采油厂',d[0],d[1],d[2],d[3]));
[['南梁采油作业区',205],['消防大队',88],['温台采油作业区',174],['樊家川采油作业区',161],['科技信息部',7],['西峰采油二区',206],['地质研究所',31],['西峰采油三区',158],['华池采油作业区',198],['马岭北采油作业区',174]].forEach(function(d,i){
rows.push(mkRow('第二采油厂',d[0],d[1],1000+i));
});
var BASE_DEPTS=['采油作业一区','采油作业二区','采油作业三区','采油作业四区','采油作业五区','采油作业六区','集输作业区','地质研究所','工艺研究所','信息中心','安全环保科','生产运行科','经营管理科','人力资源科','财务资产科','物资供应站','维修抢修中心','消防大队','综合办公室','后勤服务部','质量监督站','培训站','离退休管理科','保卫科'];
var EXTRA_UNITS=[['第三采油厂',24],['第四采油厂',24],['第五采油厂',24],['公用事业公司',24],['运输公司',24],['水电厂',24],['采气一厂',10]];
/* 第二采油厂补充5条,其余单位按部门池生成,合计169条 */
['采油作业一区','采油作业六区','集输作业区','生产运行科','保卫科'].forEach(function(dept,i){
var idx=100+i,total=20+Math.floor(pseudo(idx)*260),checked=pseudo(idx+0.31)<0.12?1:0;
var booked=Math.floor(pseudo(idx+0.67)*Math.min(35,Math.floor(total*0.18)));
rows.push(mkRow('第二采油厂',dept,total,checked,booked));
var idx=100+i,total=20+Math.floor(pseudo(idx)*260);
rows.push(mkRow('第二采油厂',dept,total,idx));
});
var gi=0;
EXTRA_UNITS.forEach(function(u){
for(var i=0;i<u[1];i++){
gi++;
var idx=200+gi,total=20+Math.floor(pseudo(idx)*260),checked=pseudo(idx+0.31)<0.12?1:0;
var booked=Math.floor(pseudo(idx+0.67)*Math.min(35,Math.floor(total*0.18)));
rows.push(mkRow(u[0],BASE_DEPTS[i],total,checked,booked));
var idx=200+gi,total=20+Math.floor(pseudo(idx)*260);
rows.push(mkRow(u[0],BASE_DEPTS[i],total,idx));
}
});
var state={unit:'',dept:'',year:'2026',page:1,pageSize:10};
var state={unit:'',dept:'',year:'2026',page:1,pageSize:10,level:'level-2'};
/* ============ 筛选 ============ */
function initUnitOptions(){
@@ -346,16 +375,27 @@ function filteredRows(){
return (!state.unit||r.unit===state.unit)&&(!state.dept||r.dept===state.dept)&&(!state.year||r.year===state.year);
});
}
function ratePct(a,b){return b>0?(a/b*100).toFixed(1)+'%':'0.0%';}
function aggregateRows(list){
var map={};
list.forEach(function(r){
var u=map[r.unit];
if(!u){u=map[r.unit]={unit:r.unit,dept:'',total:0,checked:0,booked:0,reported:0,unbooked:0,abnormal:0,disease:0,year:r.year};}
u.total+=r.total;u.checked+=r.checked;u.booked+=r.booked;u.reported+=r.reported;u.unbooked+=r.unbooked;u.abnormal+=r.abnormal;u.disease+=r.disease;
});
return Object.keys(map).map(function(k){return map[k];});
}
function renderTable(){
var list=filteredRows();
if(state.level==='level-2')list=aggregateRows(list);
var totalPages=Math.max(1,Math.ceil(list.length/state.pageSize));
if(state.page>totalPages)state.page=totalPages;
var start=(state.page-1)*state.pageSize;
var pageRows=list.slice(start,start+state.pageSize);
var tbody=document.querySelector('#statTable tbody');
tbody.innerHTML=pageRows.length?pageRows.map(function(r){
return '<tr><td>'+r.unit+'</td><td>'+r.dept+'</td><td>'+r.total+'</td><td>'+r.checked+'</td><td>'+r.booked+'</td><td>'+r.reported+'</td><td>'+r.unbooked+'</td><td>'+r.year+'</td></tr>';
}).join(''):'<tr><td colspan="8" style="padding:0;border-bottom:none;"><div class="empty-tip">暂无数据</div></td></tr>';
return '<tr><td>'+r.unit+'</td><td class="col-dept">'+r.dept+'</td><td>'+r.total+'</td><td>'+r.checked+'</td><td>'+r.booked+'</td><td>'+r.reported+'</td><td>'+r.unbooked+'</td><td>'+ratePct(r.checked,r.total)+'</td><td>'+ratePct(r.abnormal,r.checked)+'</td><td>'+ratePct(r.disease,r.checked)+'</td><td>'+r.year+'</td></tr>';
}).join(''):'<tr><td colspan="11" style="padding:0;border-bottom:none;"><div class="empty-tip">暂无数据</div></td></tr>';
renderPagination(list.length,totalPages);
}
@@ -395,6 +435,17 @@ function jumpTo(totalPages){
renderTable();
}
/* 二级/三级单位切换:二级按单位聚合并隐藏部门;三级按部门展示 */
function switchUnitLevel(el){
var items=el.parentElement.querySelectorAll('.unit-level-tab__item');
items.forEach(function(item){item.classList.remove('active');});
el.classList.add('active');
var tab=el.textContent.trim();
state.level=(tab==='三级单位')?'level-3':'level-2';
document.body.classList.toggle('level-3',state.level==='level-3');
state.page=1;
renderTable();
}
/* ============ 通用 ============ */
function toggleSubgroup(header){
header.parentElement.classList.toggle('open');