This commit is contained in:
fengpu
2026-09-20 10:48:54 +08:00
54 changed files with 1055 additions and 179 deletions
+1 -1
View File
@@ -238,7 +238,7 @@
- 员工信息:姓名(张晓红)、性别、年龄、部门
- 数据对比表:初始数据 / 目标数据 / 当前数据(体重、BMI、体脂率、腰围)
- 变化指标:绿色显示下降数值
- 体重记录列表:按时间倒序,每条显示时间、体重、体脂率、腰围、来源标签(身高秤、蓝牙称、体脂仪、自记)
- 体重记录列表:按时间倒序,每条显示时间、体重、体脂率、腰围、来源标签(体重秤、蓝牙称、体脂仪、自记)
**交互说明:**
- 点击返回:返回体重记录页
@@ -158,7 +158,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="record-item__metric"><div class="record-item__metric-label">体重</div><div class="record-item__metric-val">66.8<span class="record-item__metric-unit">kg</span></div></div>
<div class="record-item__metric"><div class="record-item__metric-label">体脂率</div><div class="record-item__metric-val">29.1<span class="record-item__metric-unit">%</span></div></div>
<div class="record-item__metric"><div class="record-item__metric-label">腰围</div><div class="record-item__metric-val">80.5<span class="record-item__metric-unit">cm</span></div></div>
<div class="record-item__source"><span class="source-badge source-badge--scale">身高</span></div>
<div class="record-item__source"><span class="source-badge source-badge--scale">体重</span></div>
</div>
</div>
+9 -9
View File
@@ -17,7 +17,7 @@
│ ├── 吃动平衡
│ ├── 饮食记录 → 饮食录入 → 拍照识别
│ ├── 运动记录 → 运动录入
│ ├── 体重记录 → 详细报告 / 体脂仪报告 / 身高秤报告
│ ├── 体重记录 → 详细报告 / 体脂仪报告 / 体重秤报告
│ ├── 今日饮食建议
│ ├── 智能运动计划
│ ├── 排行榜
@@ -89,7 +89,7 @@ stateDiagram-v2
| 17 | 体重记录 | weight-detail-v2.html | 页面 |
| 18 | 详细报告 | report-detail.html | 页面 |
| 19 | 体脂仪报告 | body-fat-report.html | 页面 |
| 20 | 身高秤报告 | height-scale-report.html | 页面 |
| 20 | 体重秤报告 | height-scale-report.html | 页面 |
| 21 | 体重报告 | weight-report.html | 页面 |
| 22 | 数据明细 | weight-record-detail-v2.html | 页面 |
| 23 | 排行榜 | weight-ranking.html | 页面 |
@@ -693,9 +693,9 @@ stateDiagram-v2
- 无记录日期显示空状态("立即称重"按钮)
- BMI 分档:<18.5 偏瘦 / 18.5-24 正常 / 24-28 超重 / ≥28 肥胖
- 非自记来源(蓝牙称/体重秤/体脂仪)记录可点击查看详细报告,自记不可点击;蓝牙称跳 report-detail.html,体脂仪跳 body-fat-report.html,体重秤跳 height-scale-report.html
- 原"记录体重"底部按钮已移除,记录入口收敛至数据来源卡片的"自记"卡片;原"身高秤"来源在本页统一更名为"体重秤"(对应报告页 height-scale-report.html 仍为身高秤报告)
- 原"记录体重"底部按钮已移除,记录入口收敛至数据来源卡片的"自记"卡片
- 当日最新记录与上一次记录对比显示变化,无历史记录时显示"首次记录"
- 体重数据来源目前有体检数据、自记数据、后台管理员记录、身高秤、蓝牙秤、体脂仪这6种,其中体检数据和后台管理员需要更新最新体重但记录不计入员工打卡行动,另外三种测量设备的数据可能出现延迟上传的情况,系统至多允许补录前一天的体重数据(定时修正前一天的行动状态和排名数据),再早的数据补传后不计入体重管理行动体重打卡
- 体重数据来源目前有体检数据、自记数据、后台管理员记录、体重秤、蓝牙秤、体脂仪这6种,其中体检数据和后台管理员需要更新最新体重但记录不计入员工打卡行动,另外三种测量设备的数据可能出现延迟上传的情况,系统至多允许补录前一天的体重数据(定时修正前一天的行动状态和排名数据),再早的数据补传后不计入体重管理行动体重打卡
---
### 18. 详细报告(report-detail.html
@@ -718,7 +718,7 @@ stateDiagram-v2
| 蓝牙秤(device=bluetooth) | 基础指标、身体成分、脂肪分析、体态管理、体重管控、细胞液分析(8电极)、进阶成分(8电极)、躯干/四肢脂肪(8电极)、躯干/四肢肌肉(8电极) |
| 体脂仪(device=fat) | 基础指标、体脂分析、体成分、体重管理、节段脂肪、节段肌肉 |
注:体重记录页中"体重秤"(原身高秤)来源的记录已改跳转至独立页面 height-scale-report.html"体脂仪"来源的记录已改跳转至 body-fat-report.html,本页 device=fat / device=scale 模式保留用于兼容。
注:体重记录页中"体重秤"来源的记录已改跳转至独立页面 height-scale-report.html"体脂仪"来源的记录已改跳转至 body-fat-report.html,本页 device=fat / device=scale 模式保留用于兼容。
**交互说明:**
- 点击返回按钮返回来源页
@@ -750,16 +750,16 @@ stateDiagram-v2
---
### 20. 身高秤报告(height-scale-report.html
### 20. 体重秤报告(height-scale-report.html
![页面截图](screenshots/height-scale-report.jpg)
**功能说明:** 展示身高秤单次测量的身体成分报告,含身高、体重及身体成分、体态相关指标。
**功能说明:** 展示体重秤单次测量的身体成分报告,含身高、体重及身体成分、体态相关指标。
**页面状态:** 从体重记录页(weight-detail-v2.html)记录明细列表中体重秤(原身高秤)来源的记录跳转进入,页面只读。
**页面状态:** 从体重记录页(weight-detail-v2.html)记录明细列表中体重秤来源的记录跳转进入,页面只读。
**主要UI元素:**
- 报告头部(蓝青渐变卡片):测量时间、设备标签「身高秤」、当前体重(大字)、BMI 状态标签、较上次变化
- 报告头部(蓝青渐变卡片):测量时间、设备标签「体重秤」、当前体重(大字)、BMI 状态标签、较上次变化
- 基础指标:身高 / 体重 / BMI / 体脂率(含 BMI 指示条:消瘦/正常/超重/肥胖四段色轨 + 指针)
- 身体成分:体脂肪 / 水分率 / 水分量 / 肌肉率 / 肌肉量 / 骨量 / 去脂体重
- 体态分析:内脏脂肪 / 体型 / 理想体重 / 超重百分比 / 体表面积
@@ -350,7 +350,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<section class="section" id="sec-history">
<div class="section__header">
<h2 class="section__title">四、历史记录与报告详情</h2>
<span class="section__count">16</span>
<span class="section__count">14</span>
</div>
<div class="sub-title">记录明细(周视图 / 月历浮层 / 数据点浮签 / 设备来源)</div>
<div class="scroll-row">
@@ -408,16 +408,8 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
</div>
</div>
<div class="sub-title">详细报告(report-detail.html 三设备 + 体脂 / 身高报告)</div>
<div class="sub-title">详细报告(report-detail.html 蓝牙秤 + 体脂 / 身高报告)</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/report-detail-fat.jpg','详细报告 · 体脂仪')">
<div class="card__img"><img src="screenshots/report-detail-fat.jpg" alt=""></div>
<div class="card__label">报告 · 体脂仪</div>
</div>
<div class="card" onclick="openLightbox('screenshots/report-detail-scale.jpg','详细报告 · 体重秤')">
<div class="card__img"><img src="screenshots/report-detail-scale.jpg" alt=""></div>
<div class="card__label">报告 · 体重秤</div>
</div>
<div class="card" onclick="openLightbox('screenshots/report-detail-bluetooth.jpg','详细报告 · 蓝牙秤')">
<div class="card__img"><img src="screenshots/report-detail-bluetooth.jpg" alt=""></div>
<div class="card__label">报告 · 蓝牙秤</div>
@@ -426,9 +418,9 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<div class="card__img"><img src="screenshots/body-fat-report.jpg" alt=""></div>
<div class="card__label">体脂报告</div>
</div>
<div class="card" onclick="openLightbox('screenshots/height-scale-report.jpg','身高测量报告')">
<div class="card" onclick="openLightbox('screenshots/height-scale-report.jpg','体重秤报告')">
<div class="card__img"><img src="screenshots/height-scale-report.jpg" alt=""></div>
<div class="card__label">身高测量报告</div>
<div class="card__label">体重秤报告</div>
</div>
</div>
</section>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@@ -87,7 +87,7 @@ function goBack() {
}
/* ========================================================================
* 身高秤报告数据
* 体重秤报告数据
* 字段名沿用统一后的命名:脂肪率→体脂率、脂肪量→体脂肪
* ======================================================================== */
var report = {
@@ -183,7 +183,7 @@ var changeText = (report.weightChange < 0 ? '' : '+') + fmtVal(report.weightChan
document.getElementById('reportHeader').innerHTML =
'<div class="report-header__top">' +
'<div class="report-header__time"><svg width="14" height="14" fill="none"><circle cx="7" cy="7" r="6" stroke="#fff" stroke-width="1.5"/><path d="M7 4v3.5l2.5 1.5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> ' + report.time + '</div>' +
'<span class="device-tag device-tag--stadiometer">身高秤</span>' +
'<span class="device-tag device-tag--stadiometer">体重秤</span>' +
'</div>' +
'<div class="report-header__main">' +
'<div class="report-header__weight"><span class="report-header__weight-num">' + fmtVal(report.weight) + '</span><span class="report-header__weight-unit">kg</span></div>' +
+2 -1
View File
@@ -255,6 +255,7 @@ const SITE_MAP = [
{ file:'post-unit-count.html', label:'单位体检人数统计' },
{ file:'post-unit-cost-stat.html', label:'单位体检费用统计' },
{ file:'post-unit-item-stat.html', label:'单位体检项目统计' },
{ file:'post-result-comprehensive-stat.html', label:'结果动态综合统计' },
{ file:'post-hospital-count.html', label:'医院体检人数统计' },
{ file:'post-doctor-count.html', label:'医生检查人数统计' },
{ file:'post-item-stat.html', label:'医院体检项目统计' },
@@ -707,7 +708,7 @@ const SITE_MAP = [
{ file:'weight-detail-v2.html', label:'体重明细' },
{ file:'report-detail.html', label:'测量报告详情' },
{ file:'body-fat-report.html', label:'体脂报告' },
{ file:'height-scale-report.html', label:'身高测量报告' },
{ file:'height-scale-report.html', label:'体重秤报告' },
{ file:'diet-advice-v2.html', label:'膳食建议' },
{ file:'exercise-advice-v2.html', label:'运动建议' },
{ file:'weight-record-detail-v2.html', label:'体重记录明细' },
+10
View File
@@ -1,5 +1,15 @@
# 健康体检模块 CHANGELOG
## [Unreleased] 2026-09-20
### Added
- 检后「体检统计汇总」分组新增「结果动态综合统计」页面 `post-result-comprehensive-stat.html`:以 2×2 网格展示 4 张堆积柱状图,统计维度固定为性别/年龄/部门/岗位,每张图自带「项目」「维度」下拉可独立切换,查看该维度各取值下的正常/异常人数及占比——竖向堆积柱状图(X 轴为维度取值、Y 轴为人数,每柱正常在下绿/异常在上红堆叠,柱顶标总数、段内标百分比、悬停显示明细);纯手写 SVG,无第三方图表库;体检项目下拉 12 项数据驱动,异常判定按年龄/性别/岗位浮动、哈希保证结果稳定;4 个面板互相独立,默认项目均为血常规、维度分别为性别/年龄/部门/岗位
- 23 个检后页面侧边栏「体检统计汇总」分组批量同步新增「结果动态综合统计」菜单项(紧邻「单位体检项目统计」之后)
- `index.html` SITE_MAP 健康体检模块新增「结果动态综合统计」入口(紧邻「单位体检项目统计」之后)
- `PRD.md`:补全缺失的「单位体检项目统计(post-unit-item-stat.html)」文档,新增「结果动态综合统计」导航节点/页面清单条目/逐页说明(52、53 节),页面清单与逐页说明 52 节及之后编号整体顺延 +2(原 52–68 → 5470
- `PRD.md` 按实际页面补全 6 个此前未入册的页面文档并配截图——检中「体检预约跟踪(mid-appointment-track2.html4 页签贯穿应检→已选→已预约→已参检)」、检后常规体检「单位群体报告(post-group-report.html10 张概览卡含较上年趋势 + 6 个报告卡片)」、检后新增「体检影像管理」分组 4 页(用户体检信息/用户影像文件分类/用户影像文件详细/文件处理日志);导航树新增「体检影像管理」分组节点,页面清单与逐页说明编号顺延(33–45 → 34–46、4667 → 4869、6870 → 7476,新增 33/47/7073 六节)
- `screenshot-prd.js` TASKS 新增 6 页并重拍截图 `mid-appointment-track2 / post-group-report / post-imc-user / post-imc-class / post-imc-info / post-imc-log`
## [Unreleased] 2026-09-18
### Added
+236 -73
View File
@@ -13,6 +13,7 @@
│ ├── 基础配置 → config-material-notice.html
│ ├── 检前 → pre-hospital-scope.html
│ ├── 检中 → mid-appointment-track.html
│ │ ├── 体检预约跟踪 → mid-appointment-track2.html
│ ├── 检后 → post-unit-count.html
│ ├── 前置机管理 → agent-device.html
│ └── 异常事件 → exception-dirty.html
@@ -93,6 +94,7 @@
├── 检后(侧边栏)
│ ├── 常规体检
│ │ ├── 常规体检报告 → post-report-employee.html
│ │ ├── 单位群体报告 → post-group-report.html
│ │ ├── 员工体检大表 → post-employee-master.html
│ │ ├── 大病人员信息 → post-illness-personnel.html
│ │ ├── 指标异常通知 → post-indicator-notify.html
@@ -102,6 +104,8 @@
│ │ │ └── Tab:二级单位 / 三级单位 / 体检医院
│ │ ├── 单位体检费用统计 → post-unit-cost-stat.html
│ │ │ └── Tab:二级单位 / 三级单位
│ │ ├── 单位体检项目统计 → post-unit-item-stat.html
│ │ ├── 结果动态综合统计 → post-result-comprehensive-stat.html
│ │ ├── 医院体检人数统计 → post-hospital-count.html
│ │ ├── 医生检查人数统计 → post-doctor-count.html
│ │ ├── 医院体检项目统计 → post-item-stat.html
@@ -117,10 +121,15 @@
│ │ ├── 胃肠镜体检报告 → post-report-gastro.html
│ │ └── 胃肠镜体检统计 → post-gastro-stat.html
│ │ └── Tab:二级单位 / 三级单位
── 冠脉CTA体检
── 冠脉CTA体检
│ ├── 冠脉CTA体检报告 → post-cta-report.html
│ └── 冠脉CTA体检统计 → post-cta-stat.html
│ └── Tab:二级单位 / 三级单位
│ └── 体检影像管理
│ ├── 用户体检信息 → post-imc-user.html
│ ├── 用户影像文件分类 → post-imc-class.html
│ ├── 用户影像文件详细 → post-imc-info.html
│ └── 文件处理日志 → post-imc-log.html
├── 前置机管理(侧边栏)
│ └── 常规体检前置服务 → agent-device.html
@@ -196,57 +205,65 @@
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 32 | 体检预约 | mid-appointment-track.html | 页面 |
| 33 | 已选项目 | mid-selected-items.html | 页面 |
| 34 | 已检员工 | mid-participation.html | 页面 |
| 35 | 单位预约人数统计 | mid-unit-appointment.html | 页面 |
| 36 | 医院实时预约人数 | mid-hospital-realtime.html | 页面 |
| 37 | 医实时检查情况 | mid-doctor-realtime.html | 页面 |
| 38 | 医院预约人数统计 | mid-hospital-appointment.html | 页面 |
| 39 | 签约单位应检人数 | mid-unit-expected.html | 页面 |
| 40 | 员工预约跟踪(胃肠镜) | mid-gastro-appointment-track.html | 页面 |
| 41 | 单位预约统计(胃肠镜) | mid-gastro-unit-appointment.html | 页面 |
| 42 | 胃肠镜问卷信息 | mid-gastro-questionnaire.html | 页面 |
| 43 | 员工预约跟踪(冠脉CTA | mid-cta-appointment-track.html | 页面 |
| 44 | 单位预约统计(冠脉CTA | mid-cta-unit-appointment.html | 页面 |
| 33 | 体检预约跟踪 | mid-appointment-track2.html | 页面 |
| 34 | 已选项目 | mid-selected-items.html | 页面 |
| 35 | 已检员工 | mid-participation.html | 页面 |
| 36 | 单位预约人数统计 | mid-unit-appointment.html | 页面 |
| 37 | 医实时预约人数 | mid-hospital-realtime.html | 页面 |
| 38 | 医生实时检查情况 | mid-doctor-realtime.html | 页面 |
| 39 | 医院预约人数统计 | mid-hospital-appointment.html | 页面 |
| 40 | 签约单位应检人数 | mid-unit-expected.html | 页面 |
| 41 | 员工预约跟踪(胃肠镜) | mid-gastro-appointment-track.html | 页面 |
| 42 | 单位预约统计(胃肠镜) | mid-gastro-unit-appointment.html | 页面 |
| 43 | 胃肠镜问卷信息 | mid-gastro-questionnaire.html | 页面 |
| 44 | 员工预约跟踪(冠脉CTA | mid-cta-appointment-track.html | 页面 |
| 45 | 单位预约统计(冠脉CTA | mid-cta-unit-appointment.html | 页面 |
### 五、检后(侧边栏:检后)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 45 | 常规体检报告 | post-report-employee.html | 页面 |
| 46 | 员工体检大表 | post-employee-master.html | 页面 |
| 47 | 大病人员信息 | post-illness-personnel.html | 页面 |
| 48 | 指标异常通知 | post-indicator-notify.html | 页面 |
| 49 | 体检报告未匹配项目 | post-report-unmatched.html | 页面 |
| 50 | 单位体检人数统计 | post-unit-count.html | 页面 |
| 51 | 单位体检费用统计 | post-unit-cost-stat.html | 页面 |
| 52 | 医院体检人数统计 | post-hospital-count.html | 页面 |
| 53 | 医生检查人数统计 | post-doctor-count.html | 页面 |
| 54 | 医院体检项目统计 | post-item-stat.html | 页面 |
| 55 | 防癌筛查人数统计 | post-cancer-screening.html | 页面 |
| 56 | 系统标准化项目统计 | post-standard-stat.html | 页面 |
| 57 | 签约医院人数统计 | post-contracted-hospital.html | 页面 |
| 58 | 重点指标异常统计 | post-key-indicator-stat.html | 页面 |
| 59 | 病种信息配置 | post-disease-info.html | 页面 |
| 60 | 体检结论关联病种 | post-conclusion-disease.html | 页面 |
| 61 | 员工病种信息 | post-auto-select-disease.html | 页面 |
| 62 | 胃肠镜体检报告 | post-report-gastro.html | 页面 |
| 63 | 胃肠镜体检统计 | post-gastro-stat.html | 页面 |
| 64 | 冠脉CTA体检报告 | post-cta-report.html | 页面 |
| 65 | 冠脉CTA体检统计 | post-cta-stat.html | 页面 |
| 46 | 常规体检报告 | post-report-employee.html | 页面 |
| 47 | 单位群体报告 | post-group-report.html | 页面 |
| 48 | 员工体检大表 | post-employee-master.html | 页面 |
| 49 | 大病人员信息 | post-illness-personnel.html | 页面 |
| 50 | 指标异常通知 | post-indicator-notify.html | 页面 |
| 51 | 体检报告未匹配项目 | post-report-unmatched.html | 页面 |
| 52 | 单位体检人数统计 | post-unit-count.html | 页面 |
| 53 | 单位体检费用统计 | post-unit-cost-stat.html | 页面 |
| 54 | 单位体检项目统计 | post-unit-item-stat.html | 页面 |
| 55 | 结果动态综合统计 | post-result-comprehensive-stat.html | 页面 |
| 56 | 医院体检人数统计 | post-hospital-count.html | 页面 |
| 57 | 医生检查人数统计 | post-doctor-count.html | 页面 |
| 58 | 医院体检项目统计 | post-item-stat.html | 页面 |
| 59 | 防癌筛查人数统计 | post-cancer-screening.html | 页面 |
| 60 | 系统标准化项目统计 | post-standard-stat.html | 页面 |
| 61 | 签约医院人数统计 | post-contracted-hospital.html | 页面 |
| 62 | 重点指标异常统计 | post-key-indicator-stat.html | 页面 |
| 63 | 病种信息配置 | post-disease-info.html | 页面 |
| 64 | 体检结论关联病种 | post-conclusion-disease.html | 页面 |
| 65 | 员工病种信息 | post-auto-select-disease.html | 页面 |
| 66 | 胃肠镜体检报告 | post-report-gastro.html | 页面 |
| 67 | 胃肠镜体检统计 | post-gastro-stat.html | 页面 |
| 68 | 冠脉CTA体检报告 | post-cta-report.html | 页面 |
| 69 | 冠脉CTA体检统计 | post-cta-stat.html | 页面 |
| 70 | 用户体检信息 | post-imc-user.html | 页面 |
| 71 | 用户影像文件分类 | post-imc-class.html | 页面 |
| 72 | 用户影像文件详细 | post-imc-info.html | 页面 |
| 73 | 文件处理日志 | post-imc-log.html | 页面 |
### 六、前置机管理(侧边栏:前置机管理)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 66 | 常规体检前置服务(含设备详情/编辑设备/日志查看/升级管理/接口测试/操作审计/重启确认弹窗) | agent-device.html | 页面 |
| 74 | 常规体检前置服务(含设备详情/编辑设备/日志查看/升级管理/接口测试/操作审计/重启确认弹窗) | agent-device.html | 页面 |
### 七、异常事件(侧边栏:异常事件)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 67 | 系统脏数据 | exception-dirty.html | 页面 |
| 68 | 结果异常 | exception-result.html | 页面 |
| 75 | 系统脏数据 | exception-dirty.html | 页面 |
| 76 | 结果异常 | exception-result.html | 页面 |
---
@@ -978,7 +995,27 @@
---
#### 33. 已选项目(mid-selected-items.html
#### 33. 体检预约跟踪(mid-appointment-track2.html
![页面截图](screenshots/mid-appointment-track2.jpg)
**功能说明:** 以 4 个页签纵向贯穿「应检 → 已选项目 → 已预约 → 已参检」的完整预约跟踪流程,支持批量状态流转与逐项置检/弃检。
**Tab 切换:** 所有员工 / 已选项目员工 / 已预约员工 / 已参检员工
**筛选条件:** 体检年份(年份选择控件,默认当年)、单位部门、医院选择、姓名、员工编号、身份证号,另有性别/年龄范围/机构分类/是否上市员工/防癌项/体检状态/预约状态/前置机预约状态(仅所有员工页签);已参检页签额外含体检编码、系统体检状态、医院体检状态、体检日期起止;各页签均带查询/重置
**操作按钮:** 所有员工——导出列表数据、查看导出任务、同步医院体检预约信息;已选项目员工——导出列表/项目/预约数据、查看导出任务;已预约员工——置为已体检、置为未体检、取消预约、导出列表/项目/预约数据、查看导出任务;已参检员工——导出项目状态、导出体检指引单、查看导出任务
**表格列(按页签):** 所有员工单级表头(姓名/员工编号/性别/年龄/身份证号/单位/部门/机构分类/体检编码/用工形式/婚姻状态/防癌项/是否上市员工/预约状态/前置机预约状态/预约日期/体检状态/体检日期/体检年份/体检计划/操作);已选项目员工两级表头(体检明细费用下分必检/选检/妇科/防癌筛查费用 4 子列,另含体检项目数/体检总费用/提交项目日期等);已预约员工单级表头(含体检编码/预约日期);已参检员工单级表头(含系统体检状态/医院体检状态/已检项/全部)
**详情弹窗(4 个页签各 1 个):** 所有员工「预约跟踪详情」(基本信息 + 预约与体检信息);已选项目员工(信息卡 + 已选项目列表含费用合计);已预约/已参检员工(信息卡 + 3 个 Tab:员工体检表信息/已体检项目/未体检项目,支持逐项置为已检/未检/弃检)
**业务规则:** 状态彩色标签(未预约橙/已预约绿/已取消红/未体检橙/体检中与已体检蓝);前置机预约状态独立枚举(未预约/预约成功/已取消预约/预约失败/预约中/取消预约失败);已预约页签批量置为已体检/未体检/取消预约需先勾选行;已参检员工区分系统体检状态与医院体检状态双口径。
---
#### 34. 已选项目(mid-selected-items.html
![页面截图](screenshots/mid-selected-items.jpg)
@@ -1000,7 +1037,7 @@
---
#### 34. 已检员工(mid-participation.html
#### 35. 已检员工(mid-participation.html
![页面截图](screenshots/mid-participation.jpg)
@@ -1020,7 +1057,7 @@
---
#### 35. 单位预约人数统计(mid-unit-appointment.html
#### 36. 单位预约人数统计(mid-unit-appointment.html
![页面截图](screenshots/mid-unit-appointment.jpg)
@@ -1036,7 +1073,7 @@
---
#### 36. 医院实时预约人数(mid-hospital-realtime.html
#### 37. 医院实时预约人数(mid-hospital-realtime.html
![页面截图](screenshots/mid-hospital-realtime.jpg)
@@ -1050,7 +1087,7 @@
---
#### 37. 医生实时检查情况(mid-doctor-realtime.html
#### 38. 医生实时检查情况(mid-doctor-realtime.html
![页面截图](screenshots/mid-doctor-realtime.jpg)
@@ -1064,7 +1101,7 @@
---
#### 38. 医院预约人数统计(mid-hospital-appointment.html
#### 39. 医院预约人数统计(mid-hospital-appointment.html
![页面截图](screenshots/mid-hospital-appointment.jpg)
@@ -1078,7 +1115,7 @@
---
#### 39. 签约单位应检人数(mid-unit-expected.html
#### 40. 签约单位应检人数(mid-unit-expected.html
![页面截图](screenshots/mid-unit-expected.jpg)
@@ -1094,7 +1131,7 @@
### 胃肠镜体检
#### 40. 员工预约跟踪(胃肠镜)(mid-gastro-appointment-track.html
#### 41. 员工预约跟踪(胃肠镜)(mid-gastro-appointment-track.html
![页面截图](screenshots/mid-gastro-appointment-track.jpg)
@@ -1114,7 +1151,7 @@
---
#### 41. 单位预约统计(胃肠镜)(mid-gastro-unit-appointment.html
#### 42. 单位预约统计(胃肠镜)(mid-gastro-unit-appointment.html
![页面截图](screenshots/mid-gastro-unit-appointment.jpg)
@@ -1130,7 +1167,7 @@
---
#### 42. 胃肠镜问卷信息(mid-gastro-questionnaire.html
#### 43. 胃肠镜问卷信息(mid-gastro-questionnaire.html
![页面截图](screenshots/mid-gastro-questionnaire.jpg)
@@ -1148,7 +1185,7 @@
### 冠脉CTA体检
#### 43. 员工预约跟踪(冠脉CTA)(mid-cta-appointment-track.html
#### 44. 员工预约跟踪(冠脉CTA)(mid-cta-appointment-track.html
![页面截图](screenshots/mid-cta-appointment-track.jpg)
@@ -1157,7 +1194,7 @@
---
#### 44. 单位预约统计(冠脉CTA)(mid-cta-unit-appointment.html
#### 45. 单位预约统计(冠脉CTA)(mid-cta-unit-appointment.html
![页面截图](screenshots/mid-cta-unit-appointment.jpg)
@@ -1170,7 +1207,7 @@
### 常规体检
#### 45. 常规体检报告(post-report-employee.html
#### 46. 常规体检报告(post-report-employee.html
![页面截图](screenshots/post-report-employee.jpg)
@@ -1190,7 +1227,25 @@
---
#### 46. 员工体检大表(post-employee-master.html
#### 47. 单位群体报告(post-group-report.html
![页面截图](screenshots/post-group-report.jpg)
**功能说明:** 选定某单位与体检年份后,以概览指标卡 + 图表 + 表格呈现该单位群体体检的受检/检出/异常/疾病/专项筛查情况,并支持导出 CSV。
**筛选条件:** 体检年份(年份选择控件,默认当年)、单位(左侧列表,含单位名称模糊搜索)
**操作按钮:** 导出群体报告(导出 CSV
**概览指标卡(5 列 × 2 行共 10 张):** 应检人数、已检人数、受检率(绿)、检出率(橙)、异常人数(红)、重点指标异常(红)、复查率、数据完整率、平均年龄、未见异常(绿);每张卡下方带「较上年」趋势标注(率类 ±X.Xpp、人数/年龄类整数 + 单位,按指标方向判定绿 good/红 bad/灰 flat
**报告卡片(6 个):** ① 近三年趋势(受检率/检出率双折线图);② 异常率 × 年龄/性别(表:年龄区间/男性异常率/女性异常率/合计异常率/异常人数,含合计行);③ 异常指标排名(横向条形,按检出人数降序,top1 红/top2 橙/top3 绿、其余蓝,附上年对照与趋势);④ 重点指标异常拆解(表:重点指标/异常人数/检出率/较上年,5 项:血压/血脂/空腹血糖/高尿酸/超重 BMI);⑤ 疾病(病种)分布(按 8 类病种分组归类,表列:病种/人数/占比/较上年);⑥ 专项筛查结果(防癌筛查 Septin9、CA724、冠脉 CTA、胃肠镜 4 项,阳性/异常率 + 已筛人数 + 较上年)
**业务规则:** 受检率 = 已检 ÷ 应检、检出率 = 异常 ÷ 已检,各率保留 1 位小数、除数为 0 显示 0.0%;同单位同一年数据恒定(确定性伪随机);报告头元信息含体检年份/承检医院/体检计划起止/同比基准上一年。
---
#### 48. 员工体检大表(post-employee-master.html
![页面截图](screenshots/post-employee-master.jpg)
@@ -1211,7 +1266,7 @@
---
#### 47. 大病人员信息(post-illness-personnel.html
#### 49. 大病人员信息(post-illness-personnel.html
![页面截图](screenshots/post-illness-personnel.jpg)
@@ -1235,7 +1290,7 @@
---
#### 48. 指标异常通知(post-indicator-notify.html
#### 50. 指标异常通知(post-indicator-notify.html
![页面截图](screenshots/post-indicator-notify.jpg)
@@ -1251,7 +1306,7 @@
---
#### 49. 体检报告未匹配项目(post-report-unmatched.html
#### 51. 体检报告未匹配项目(post-report-unmatched.html
![页面截图](screenshots/post-report-unmatched.jpg)
@@ -1267,7 +1322,7 @@
### 体检统计汇总
#### 50. 单位体检人数统计(post-unit-count.html
#### 52. 单位体检人数统计(post-unit-count.html
![页面截图](screenshots/post-unit-count.jpg)
@@ -1291,7 +1346,7 @@
---
#### 51. 单位体检费用统计(post-unit-cost-stat.html
#### 53. 单位体检费用统计(post-unit-cost-stat.html
![页面截图](screenshots/post-unit-cost-stat.jpg)
@@ -1317,7 +1372,39 @@
---
#### 52. 医院体检人数统计(post-hospital-count.html
#### 54. 单位体检项目统计(post-unit-item-stat.html
![页面截图](screenshots/post-unit-item-stat.jpg)
**功能说明:** 按"单位 × 体检医院 × 体检项目"维度统计各体检项目的已选/已检/异常人数及患病率,人数列可点击下钻查看对应人员明细。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉)、医院(下拉)、项目名称(输入)、项目编码(输入)
**操作按钮:** 导出列表数据、查看导出任务
**表格列:** 序号、单位、体检医院、体检项目名称、项目编码、已选人数、已检人数、异常人数、患病率
**业务规则:** 患病率 = 异常人数 ÷ 已检人数,患病率 ≥10% 标红;已选/已检/异常三列数字为蓝色链接,点击打开对应口径人员列表弹窗(列:序号、姓名、员工编号、部门、体检医院、体检日期、体检状态/体检结果/异常结果)。
---
#### 55. 结果动态综合统计(post-result-comprehensive-stat.html
![页面截图](screenshots/post-result-comprehensive-stat.jpg)
**功能说明:** 以 2×2 网格展示 4 张堆积柱状图,统计维度固定为性别/年龄/部门/岗位;每张图自带「项目」「维度」两个下拉,可独立切换体检项目与统计维度,查看该维度各取值下的正常/异常人数及占比。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉,默认第一采油厂)
**图表面板:** 每个面板顶部为标题 + 项目下拉(12 项)+ 维度下拉(性别/年龄/部门/岗位)+ 正常/异常图例,下方为竖向堆积柱状图。
**图表展示:** 竖向堆积柱状图——X 轴为所选维度取值(性别→男/女、年龄→各年龄段、部门→各部门、岗位→各岗位),Y 轴为人数;每根柱子正常在下(绿)/异常在上(红)堆叠,柱顶标总数、段内标百分比,悬停显示明细。
**业务规则:** 4 个面板互相独立,默认项目均为血常规、维度分别为性别/年龄/部门/岗位;异常率 = 异常人数 ÷ 受检人数;维度取值(性别男/女、年龄 5 档、部门 6 个、岗位 6 个)。
---
#### 56. 医院体检人数统计(post-hospital-count.html
![页面截图](screenshots/post-hospital-count.jpg)
@@ -1331,7 +1418,7 @@
---
#### 53. 医生检查人数统计(post-doctor-count.html
#### 57. 医生检查人数统计(post-doctor-count.html
![页面截图](screenshots/post-doctor-count.jpg)
@@ -1345,7 +1432,7 @@
---
#### 54. 医院体检项目统计(post-item-stat.html
#### 58. 医院体检项目统计(post-item-stat.html
![页面截图](screenshots/post-item-stat.jpg)
@@ -1359,7 +1446,7 @@
---
#### 55. 防癌筛查人数统计(post-cancer-screening.html
#### 59. 防癌筛查人数统计(post-cancer-screening.html
![页面截图](screenshots/post-cancer-screening.jpg)
@@ -1373,7 +1460,7 @@
---
#### 56. 系统标准化项目统计(post-standard-stat.html
#### 60. 系统标准化项目统计(post-standard-stat.html
![页面截图](screenshots/post-standard-stat.jpg)
@@ -1387,7 +1474,7 @@
---
#### 57. 签约医院人数统计(post-contracted-hospital.html
#### 61. 签约医院人数统计(post-contracted-hospital.html
![页面截图](screenshots/post-contracted-hospital.jpg)
@@ -1401,7 +1488,7 @@
---
#### 58. 重点指标异常统计(post-key-indicator-stat.html
#### 62. 重点指标异常统计(post-key-indicator-stat.html
![页面截图](screenshots/post-key-indicator-stat.jpg)
@@ -1421,7 +1508,7 @@
### 病种信息管理
#### 59. 病种信息配置(post-disease-info.html
#### 63. 病种信息配置(post-disease-info.html
![页面截图](screenshots/post-disease-info.jpg)
@@ -1441,7 +1528,7 @@
---
#### 60. 体检结论关联病种(post-conclusion-disease.html
#### 64. 体检结论关联病种(post-conclusion-disease.html
![页面截图](screenshots/post-conclusion-disease.jpg)
@@ -1461,7 +1548,7 @@
---
#### 61. 员工病种信息(post-auto-select-disease.html
#### 65. 员工病种信息(post-auto-select-disease.html
![页面截图](screenshots/post-auto-select-disease.jpg)
@@ -1484,7 +1571,7 @@
### 胃肠镜体检
#### 62. 胃肠镜体检报告(post-report-gastro.html
#### 66. 胃肠镜体检报告(post-report-gastro.html
![页面截图](screenshots/post-report-gastro.jpg)
@@ -1519,7 +1606,7 @@
---
#### 63. 胃肠镜体检统计(post-gastro-stat.html
#### 67. 胃肠镜体检统计(post-gastro-stat.html
![页面截图](screenshots/post-gastro-stat.jpg)
@@ -1542,7 +1629,7 @@
### 冠脉CTA体检
#### 64. 冠脉CTA体检报告(post-cta-report.html
#### 68. 冠脉CTA体检报告(post-cta-report.html
![页面截图](screenshots/post-cta-report.jpg)
@@ -1572,7 +1659,7 @@
---
#### 65. 冠脉CTA体检统计(post-cta-stat.html
#### 69. 冠脉CTA体检统计(post-cta-stat.html
![页面截图](screenshots/post-cta-stat.jpg)
@@ -1582,9 +1669,85 @@
---
### 体检影像管理
#### 70. 用户体检信息(post-imc-user.html
![页面截图](screenshots/post-imc-user.jpg)
**功能说明:** 以「人」为维度聚合展示用户体检影像信息,行内可展开查看该人各项检查项目的影像与报告处理情况。
**筛选条件:** 姓名、身份证号、体检年份(年份选择控件,默认当年)、医院名称
**操作按钮:** 批量删除
**表格列:** 复选框、序号、展开箭头列、单位信息、姓名、身份证号、体检年份、医院名称、体检编号、创建日期、操作(删除)
**展开子表列:** 序号、检查项目名称、处理状态、影像信息、影像报告;影像信息列渲染「预览」链接(无数据显示「暂无」)、影像报告列渲染「查看」链接(无数据显示「无文件」)、子表无数据显示「暂无检查项目数据」
**业务规则:** 姓名/身份证号/医院名称为模糊匹配、体检年份精确匹配;分页 10/20/50 条/页;删除/批量删除带确认。
---
#### 71. 用户影像文件分类(post-imc-class.html
![页面截图](screenshots/post-imc-class.jpg)
**功能说明:** 按「检查项目」维度展示影像文件与报告文件的类型分类、处理状态与结果。
**筛选条件:** 影像分类(超声/CT/MRI/X射线/心电)、检查项目名称、处理状态
**操作按钮:** 批量删除
**表格列:** 复选框、序号、影像分类、检查项目名称、影像文件类型、报告文件类型、处理状态、检查结果、错误信息、出报告时间、创建日期、操作(详情 + 删除)
**详情弹窗:** 标题「用户影像文件分类详情」,字段:用户体检信息ID、影像分类、检查项目名称、影像文件类型、报告文件类型、处理状态、检查结果、错误信息、出报告时间、创建日期
**业务规则:** 检查结果/错误信息超 18 字符截断并悬停显示全文、空值显示「—」;影像文件类型 dcm/非DICOM、报告文件类型 pdf/png/jpg。
---
#### 72. 用户影像文件详细(post-imc-info.html
![页面截图](screenshots/post-imc-info.jpg)
**功能说明:** 文件级别的影像/报告明细,展示单个文件的存储地址、DICOM 实例与研究/患者标识。
**筛选条件:** 文件类型(影像/报告)、患者ID、研究实例ID
**操作按钮:** 批量删除
**表格列:** 复选框、序号、文件类型(影像蓝标签/报告紫标签)、影像序号、错误信息、文件地址(下载链接)、dcm实例ID、研究实例ID、患者ID、研究ID、创建日期、操作(详情 + 删除)
**详情弹窗:** 标题「用户影像文件详细」,字段:用户影像文件分类ID、文件类型、影像序号、错误信息、文件地址、dcm实例ID、研究实例ID、患者ID、研究ID、创建日期
**业务规则:** 错误信息超 18 字符截断并悬停全文;患者ID/dcm实例ID/错误信息可为空(空显示「—」,报告的 dcm实例ID 恒为空)。
---
#### 73. 文件处理日志(post-imc-log.html
![页面截图](screenshots/post-imc-log.jpg)
**功能说明:** 展示影像压缩包的传输、解析全过程日志(时间线 + 状态 + 原始 json)。
**筛选条件:** 身份证号、体检年份(年份选择控件)、体检编号、影像分类(超声/CT/MRI/X射线/心电)、处理状态
**操作按钮:** 批量删除
**表格列:** 复选框、序号、医院名称、姓名、身份证号、体检年份、体检编号、影像分类、json数据、文件地址(下载链接)、文件大小(MB)、处理状态、错误信息、传输开始时间、传输完成时间、解析开始时间、解析完成时间、操作(详情 + 删除)
**详情弹窗:** 标题「文件处理日志详情」,字段含医院名称/姓名/身份证号/体检年份/体检编号/影像分类/json 数据(代码块展示)/文件地址/文件大小(MB)/处理状态/错误信息/传输开始/传输完成/解析开始/解析完成时间
**业务规则:** json 数据超 24 字符截断为等宽字体并悬停全文、详情弹窗内 json 代码块可滚动;错误信息超 18 字符截断;空时间显示「—」;示例数据按传输开始时间倒序。
**数据字典(四页通用):** 处理状态 1 新建(蓝)/2 待解析(灰)/3 失败(红)/4 解析中(橙)/5 成功(绿)/6 失败待重试(橙)/7 最终失败(红)。
---
## 六、前置机管理
### 66. 常规体检前置服务(agent-device.html
### 74. 常规体检前置服务(agent-device.html
![页面截图](screenshots/agent-device.jpg)
@@ -1625,7 +1788,7 @@
## 七、异常事件
### 67. 系统脏数据(exception-dirty.html
### 75. 系统脏数据(exception-dirty.html
![页面截图](screenshots/exception-dirty.jpg)
@@ -1643,7 +1806,7 @@
---
### 68. 结果异常(exception-result.html
### 76. 结果异常(exception-result.html
![页面截图](screenshots/exception-result.jpg)
@@ -55,6 +55,7 @@ const TASKS = [
{ page: 'pre-special-cta.html', output: 'pre-special-cta.jpg' },
// 四、检中 · 常规体检
{ page: 'mid-appointment-track.html', output: 'mid-appointment-track.jpg' },
{ page: 'mid-appointment-track2.html', output: 'mid-appointment-track2.jpg' },
{ page: 'mid-selected-items.html', output: 'mid-selected-items.jpg' },
{ page: 'mid-participation.html', output: 'mid-participation.jpg' },
{ page: 'mid-unit-appointment.html', output: 'mid-unit-appointment.jpg' },
@@ -71,6 +72,7 @@ const TASKS = [
{ page: 'mid-cta-unit-appointment.html', output: 'mid-cta-unit-appointment.jpg' },
// 五、检后 · 常规体检
{ page: 'post-report-employee.html', output: 'post-report-employee.jpg' },
{ page: 'post-group-report.html', output: 'post-group-report.jpg' },
{ page: 'post-employee-master.html', output: 'post-employee-master.jpg' },
{ page: 'post-illness-personnel.html', output: 'post-illness-personnel.jpg' },
{ page: 'post-indicator-notify.html', output: 'post-indicator-notify.jpg' },
@@ -78,6 +80,8 @@ const TASKS = [
// 五、检后 · 体检统计汇总
{ page: 'post-unit-count.html', output: 'post-unit-count.jpg' },
{ page: 'post-unit-cost-stat.html', output: 'post-unit-cost-stat.jpg' },
{ page: 'post-unit-item-stat.html', output: 'post-unit-item-stat.jpg' },
{ page: 'post-result-comprehensive-stat.html', output: 'post-result-comprehensive-stat.jpg' },
{ page: 'post-hospital-count.html', output: 'post-hospital-count.jpg' },
{ page: 'post-doctor-count.html', output: 'post-doctor-count.jpg' },
{ page: 'post-item-stat.html', output: 'post-item-stat.jpg' },
@@ -95,6 +99,11 @@ const TASKS = [
// 五、检后 · 冠脉CTA体检
{ page: 'post-cta-report.html', output: 'post-cta-report.jpg' },
{ page: 'post-cta-stat.html', output: 'post-cta-stat.jpg' },
// 五、检后 · 体检影像管理
{ page: 'post-imc-user.html', output: 'post-imc-user.jpg' },
{ page: 'post-imc-class.html', output: 'post-imc-class.jpg' },
{ page: 'post-imc-info.html', output: 'post-imc-info.jpg' },
{ page: 'post-imc-log.html', output: 'post-imc-log.jpg' },
// 六、异常事件
{ page: 'exception-dirty.html', output: 'exception-dirty.jpg' },
{ page: 'exception-result.html', output: 'exception-result.jpg' },
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

@@ -158,6 +158,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -168,6 +168,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -132,6 +132,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -199,6 +199,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -158,6 +158,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -166,6 +166,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item active">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -158,6 +158,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -158,6 +158,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
+287 -67
View File
@@ -90,7 +90,7 @@
.report-head__meta{display:flex;align-items:center;gap:24px;margin-top:10px;font-size:var(--font-sm);color:var(--text-secondary);flex-wrap:wrap;}
.report-head__meta b{color:var(--text-primary);font-weight:500;}
/* 概览指标卡 */
.overview-cards{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px;}
.overview-cards{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:16px;}
.ov-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 12px;text-align:center;}
.ov-card__value{font-size:var(--font-xxl);font-weight:600;color:var(--text-primary);line-height:1.2;}
.ov-card__value--primary{color:var(--primary);}
@@ -98,6 +98,10 @@
.ov-card__value--warning{color:var(--warning);}
.ov-card__value--danger{color:var(--danger);}
.ov-card__label{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:8px;}
.ov-card__trend{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:6px;font-variant-numeric:tabular-nums;}
.trend-good{color:var(--success);}
.trend-bad{color:var(--danger);}
.trend-flat{color:var(--text-placeholder);}
/* 报告卡片 */
.report-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);margin-bottom:16px;overflow:hidden;}
.report-card__header{padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;}
@@ -123,8 +127,9 @@
.bar-row__fill.top1{background:linear-gradient(90deg,#FF9C6E,#FF4D4F);}
.bar-row__fill.top2{background:linear-gradient(90deg,#FFD591,#FAAD14);}
.bar-row__fill.top3{background:linear-gradient(90deg,#95DE64,#52C41A);}
.bar-row__val{width:140px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-secondary);font-variant-numeric:tabular-nums;}
.bar-row__val{width:210px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-secondary);font-variant-numeric:tabular-nums;}
.bar-row__val b{color:var(--text-primary);font-weight:600;}
.bar-row__delta{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:2px;}
/* 专项筛查卡片项 */
.screen-item{display:flex;align-items:center;padding:13px 0;border-bottom:1px solid #F0F0F0;}
.screen-item:first-child{padding-top:0;}
@@ -132,7 +137,11 @@
.screen-item__name{width:180px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-primary);}
.screen-item__val{font-size:var(--font-base);font-weight:600;color:var(--danger);margin-right:6px;font-variant-numeric:tabular-nums;}
.screen-item__unit{font-size:var(--font-sm);color:var(--text-secondary);}
.screen-item__rate{margin-left:auto;font-size:var(--font-sm);color:var(--text-placeholder);font-variant-numeric:tabular-nums;}
.screen-item__rate{margin-left:auto;font-size:var(--font-sm);color:var(--text-placeholder);font-variant-numeric:tabular-nums;text-align:right;}
.screen-item__rate .sub{font-size:var(--font-xs);margin-top:3px;}
/* 趋势图例 */
.trend-legend{display:flex;gap:18px;font-size:var(--font-xs);color:var(--text-secondary);margin-bottom:10px;}
.trend-legend i{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:5px;vertical-align:-1px;}
</style>
</head>
<body>
@@ -185,6 +194,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -274,6 +284,8 @@
/* 确定性伪随机(0-1),保证同一单位每次打开数据一致 */
function pseudo(i){var x=Math.sin(i*127.1)*43758.5453;return x-Math.floor(x);}
function fmt(n){return String(n).replace(/\B(?=(\d{3})+(?!\d))/g,',');}
function pct(a,b){return b?((a/b)*100).toFixed(1)+'%':'0.0%';}
function pct1(n){return (n==null?'0.0':n.toFixed(1))+'%';}
var UNITS=['公司机关','第一采油厂','第二采油厂','第三采油厂','第八采油厂','第九采油厂','第十一采油厂','采气一厂','水电厂','公用事业公司','运输公司','伴生气综合利用项目部','质量安全环保技术监督中心'];
var HOSPITALS=['宝石花医院','延安医院','西安高新医院','长安心血管医院','宁夏宝石花','CQ总医院'];
@@ -282,6 +294,8 @@ var HOSPITALS=['宝石花医院','延安医院','西安高新医院','长安心
var AGE_BANDS=['35岁以下','35岁至44岁','45岁及以上'];
/* 异常指标排名(名称 / 排名着色 class) */
var INDICATORS=['血脂异常','血压升高','超重(肥胖)','高尿酸','脂肪肝','空腹血糖升高','甲状腺结节','肺部结节','肝功能异常','心电图异常'];
/* 重点指标(管理层关注的核心指标,逐项拆解) */
var KEY_INDICATORS=['血压异常','血脂异常','空腹血糖升高','高尿酸','超重(BMI)'];
/* 疾病(病种)分布,按病种类别分组 */
var DISEASE_CATS=[
{name:'血液及动脉病种',items:['高脂血','高血压','痛风(高尿酸)','动脉斑块']},
@@ -296,24 +310,55 @@ var DISEASE_CATS=[
var state={unitIdx:0, year:'2026', keyword:''};
/* 某单位某年的群体基础数据(确定性生成) */
/* 某单位某年的群体基础数据(确定性生成,显式传单位/年份 */
function yearSeed(idx,year){return idx*13.7+parseInt(year,10)*5.3;}
function unitData(idx){
var s=yearSeed(idx,state.year);
function unitData(idx,year){
var s=yearSeed(idx,year);
var total=500+Math.floor(pseudo(s+1.3)*7500); // 应检人数 500~8000
var examined=Math.round(total*(0.75+pseudo(s+2.1)*0.20)); // 已检人数
var abnormal=Math.round(examined*(0.22+pseudo(s+3.7)*0.38)); // 异常人数
var keyAbn=Math.round(abnormal*(0.25+pseudo(s+4.9)*0.30)); // 重点指标异常人数
var cancerPos=Math.round(total*(0.008+pseudo(s+5.3)*0.022)); // 防癌筛查阳性人数
var ctaAbn=Math.round(examined*(0.012+pseudo(s+6.1)*0.038)); // 冠脉CTA异常人数
var healthy=examined-abnormal; // 未见异常人数
var recheckAdvised=Math.round(abnormal*(0.55+pseudo(s+12.3)*0.25));// 建议复查人数
var recheckDone=Math.round(recheckAdvised*(0.60+pseudo(s+13.1)*0.30)); // 已完成复查人数
var completeCnt=Math.round(examined*(0.90+pseudo(s+14.7)*0.09)); // 数据完整人数
var avgAge=34+Math.round(pseudo(s+15.2)*16); // 平均年龄 34~50
var cancerScreened=Math.round(examined*(0.55+pseudo(s+16.1)*0.30));// 防癌筛查已筛人数
var cancerPos=Math.round(cancerScreened*(0.05+pseudo(s+16.7)*0.09)); // Septin9 阳性
var ca724Pos=Math.round(cancerScreened*(0.04+pseudo(s+17.3)*0.08)); // CA724 阳性
var ctaScreened=Math.round(examined*(0.20+pseudo(s+18.1)*0.25)); // 冠脉CTA已检人数
var ctaAbn=Math.round(ctaScreened*(0.08+pseudo(s+18.9)*0.14)); // 冠脉CTA异常人数
var gastroScreened=Math.round(examined*(0.18+pseudo(s+20.5)*0.22)); // 胃肠镜已检人数
var gastroAbn=Math.round(gastroScreened*(0.10+pseudo(s+21.1)*0.20));// 胃肠镜异常人数
return {
name:UNITS[idx], total:total, examined:examined, abnormal:abnormal, keyAbn:keyAbn,
cancerPos:cancerPos, ctaAbn:ctaAbn, hospital:HOSPITALS[idx%HOSPITALS.length]
name:UNITS[idx], total:total, examined:examined, abnormal:abnormal, keyAbn:keyAbn, healthy:healthy,
recheckAdvised:recheckAdvised, recheckDone:recheckDone, completeCnt:completeCnt, avgAge:avgAge,
cancerScreened:cancerScreened, cancerPos:cancerPos, ca724Pos:ca724Pos,
ctaScreened:ctaScreened, ctaAbn:ctaAbn, gastroScreened:gastroScreened, gastroAbn:gastroAbn,
hospital:HOSPITALS[idx%HOSPITALS.length]
};
}
/* 汇总数值统计(含各类率,用于同比对比) */
function computeStats(idx,year){
var d=unitData(idx,year);
var ag=ageGender(idx,year);
var male=ag.reduce(function(a,r){return a+r.male;},0);
var female=ag.reduce(function(a,r){return a+r.female;},0);
return {
total:d.total, examined:d.examined, attendRate:d.examined/d.total*100,
abnormal:d.abnormal, detectRate:d.abnormal/d.examined*100, keyAbn:d.keyAbn, healthy:d.healthy,
recheckAdvised:d.recheckAdvised, recheckDone:d.recheckDone,
recheckRate:d.recheckAdvised?d.recheckDone/d.recheckAdvised*100:0,
completeCnt:d.completeCnt, completeRate:d.examined?d.completeCnt/d.examined*100:0,
avgAge:d.avgAge, male:male, female:female,
cancerScreened:d.cancerScreened, cancerPos:d.cancerPos, ca724Pos:d.ca724Pos,
ctaScreened:d.ctaScreened, ctaAbn:d.ctaAbn, gastroScreened:d.gastroScreened, gastroAbn:d.gastroAbn
};
}
/* 年龄×性别分布 */
function ageGender(d){
var s=yearSeed(state.unitIdx,state.year);
function ageGender(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var a35=Math.round(d.examined*(0.26+pseudo(s+7.7)*0.10));
var a44=Math.round(d.examined*(0.32+pseudo(s+8.3)*0.08));
var a45=d.examined-a35-a44;
@@ -325,8 +370,9 @@ function ageGender(d){
];
}
/* 异常指标排名(人数近似递减) */
function indicatorRank(d){
var s=yearSeed(state.unitIdx,state.year);
function indicatorRank(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var list=INDICATORS.map(function(name,ki){
var share=(0.05+pseudo(s+ki*2.9)*0.16)*(INDICATORS.length-ki)/INDICATORS.length;
return {name:name, cnt:Math.max(3,Math.round(d.examined*share))};
@@ -334,9 +380,41 @@ function indicatorRank(d){
list.sort(function(a,b){return b.cnt-a.cnt;});
return list;
}
/* 重点指标异常拆解(人次,同一人可有多项) */
function keyIndicatorDetail(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var list=KEY_INDICATORS.map(function(name,ki){
var share=(0.10+pseudo(s+ki*3.5+0.7)*0.15)*(KEY_INDICATORS.length-ki)/KEY_INDICATORS.length;
return {name:name, cnt:Math.max(1,Math.round(d.keyAbn*share))};
});
list.sort(function(a,b){return b.cnt-a.cnt;});
return list;
}
/* 异常率×年龄/性别 交叉(异常人数归一化到总体异常人数) */
function ageGenderAbnormal(idx,year){
var d=unitData(idx,year);
var ag=ageGender(idx,year);
var s=yearSeed(idx,year);
var rows=ag.map(function(r,bi){
var mAbn=Math.round(r.male*(0.15+pseudo(s+bi*4.7+2.3)*0.35));
var fAbn=Math.round(r.female*(0.12+pseudo(s+bi*4.7+3.9)*0.32));
return {band:r.band, male:r.male, female:r.female, maleAbn:mAbn, femaleAbn:fAbn};
});
var sumAbn=rows.reduce(function(a,r){return a+r.maleAbn+r.femaleAbn;},0);
if(sumAbn>0){var k=d.abnormal/sumAbn;rows.forEach(function(r){r.maleAbn=Math.round(r.maleAbn*k);r.femaleAbn=Math.round(r.femaleAbn*k);});}
rows.forEach(function(r){
r.total=r.male+r.female; r.totalAbn=r.maleAbn+r.femaleAbn;
r.maleRate=r.male?r.maleAbn/r.male*100:0;
r.femaleRate=r.female?r.femaleAbn/r.female*100:0;
r.rate=r.total?r.totalAbn/r.total*100:0;
});
return rows;
}
/* 疾病(病种)分布:按病种类别分组,类别与病种均按人数降序 */
function diseaseDist(d){
var s=yearSeed(state.unitIdx,state.year);
function diseaseDist(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var groups=DISEASE_CATS.map(function(cat,ci){
var items=cat.items.map(function(name,ii){
var share=(0.018+pseudo(s+ci*3.3+ii*1.9)*0.11)*(DISEASE_CATS.length-ci)/DISEASE_CATS.length;
@@ -348,6 +426,25 @@ function diseaseDist(d){
groups.sort(function(a,b){return b.items[0].cnt-a.items[0].cnt;});
return groups;
}
/* 体检计划周期(随年份与单位动态) */
function planPeriod(idx,year){
var s=yearSeed(idx,year);
var sm=4+Math.floor(pseudo(s+19.1)*3);
var sd=5+Math.floor(pseudo(s+19.2)*20);
var em=sm+2, ed=10+Math.floor(pseudo(s+19.3)*20);
function p(n){return (n<10?'0':'')+n;}
return year+'-'+p(sm)+'-'+p(sd)+' 至 '+year+'-'+p(em)+'-'+p(ed);
}
/* 同比变化(带方向着色):goodDir=up/down 表示"升是好事/降是好事"flat 中性 */
function trendSpan(numDelta,isRate,goodDir,unit){
if(Math.abs(numDelta)<0.05)return '<span class="trend-flat">— 持平</span>';
var up=numDelta>0, arrow=up?'↑':'↓';
var txt=(up?'+':'')+(isRate?Math.abs(numDelta).toFixed(1)+'pp':fmt(Math.abs(Math.round(numDelta)))+(unit||''));
var cls='trend-flat';
if(goodDir==='up')cls=up?'trend-good':'trend-bad';
else if(goodDir==='down')cls=up?'trend-bad':'trend-good';
return '<span class="'+cls+'">'+arrow+' '+txt+'</span>';
}
/* ============ 左侧单位列表 ============ */
function renderUnitList(){
@@ -356,7 +453,7 @@ function renderUnitList(){
if(!list.length){box.innerHTML='<div class="unit-list__empty">未找到匹配单位</div>';return;}
var html=list.map(function(u){
var idx=UNITS.indexOf(u);
var d=unitData(idx);
var d=unitData(idx,state.year);
return '<div class="unit-list__item'+(idx===state.unitIdx?' active':'')+'" onclick="selectUnit('+idx+')">'
+'<div class="unit-list__name">'+u+'</div>'
+'<div class="unit-list__meta"><span>应检 '+fmt(d.total)+'</span><span>检出率 '+pct(d.abnormal,d.examined)+'</span></div>'
@@ -366,107 +463,230 @@ function renderUnitList(){
}
function filterUnits(kw){state.keyword=kw.trim();renderUnitList();}
function selectUnit(idx){state.unitIdx=idx;renderUnitList();renderReport();}
function pct(a,b){return b?((a/b)*100).toFixed(1)+'%':'0.0%';}
/* ============ 近三年趋势图(受检率 / 检出率 折线) ============ */
function trendChartSvg(idx,years){
var attend=[],detect=[];
years.forEach(function(y){var s=computeStats(idx,y);attend.push(s.attendRate);detect.push(s.detectRate);});
var all=attend.concat(detect);
var min=Math.floor(Math.min.apply(null,all))-3, max=Math.ceil(Math.max.apply(null,all))+3;
var W=560,H=150,padL=38,padR=14,padT=14,padB=26;
var iw=W-padL-padR, ih=H-padT-padB;
function x(i){return padL+(years.length===1?iw/2:iw*i/(years.length-1));}
function y(v){return padT+ih*(1-(v-min)/(max-min));}
var grid='';
for(var g=0;g<=4;g++){
var gv=min+(max-min)*g/4, gy=y(gv).toFixed(1);
grid+='<line x1="'+padL+'" y1="'+gy+'" x2="'+(W-padR)+'" y2="'+gy+'" stroke="#F0F0F0"/>'
+'<text x="'+(padL-6)+'" y="'+(parseFloat(gy)+4).toFixed(1)+'" text-anchor="end" font-size="10" fill="#999">'+gv.toFixed(0)+'</text>';
}
function pline(arr,color){return '<polyline points="'+arr.map(function(v,i){return x(i).toFixed(1)+','+y(v).toFixed(1);}).join(' ')+'" fill="none" stroke="'+color+'" stroke-width="2"/>';}
var dots='';
attend.forEach(function(v,i){dots+='<circle cx="'+x(i).toFixed(1)+'" cy="'+y(v).toFixed(1)+'" r="3.5" fill="#1890FF"/>';});
detect.forEach(function(v,i){dots+='<circle cx="'+x(i).toFixed(1)+'" cy="'+y(v).toFixed(1)+'" r="3.5" fill="#FAAD14"/>';});
var labels='';
years.forEach(function(yr,i){
var active=yr===state.year;
labels+='<text x="'+x(i).toFixed(1)+'" y="'+(H-8)+'" text-anchor="middle" font-size="11" '+(active?'font-weight="600" fill="#1890FF"':'fill="#666"')+'>'+yr+'</text>';
});
return '<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;height:auto;">'+grid
+pline(attend,'#1890FF')+pline(detect,'#FAAD14')+dots+labels+'</svg>';
}
/* ============ 右侧群体分析报告 ============ */
function renderReport(){
var d=unitData(state.unitIdx);
var ag=ageGender(d);
var rank=indicatorRank(d);
var dist=diseaseDist(d);
var maxCnt=rank[0].cnt;
var idx=state.unitIdx, year=state.year;
var prevYear=String(parseInt(year,10)-1);
var d=unitData(idx,year);
var pd=unitData(idx,prevYear);
var cur=computeStats(idx,year);
var prev=computeStats(idx,prevYear);
var rank=indicatorRank(idx,year);
var rankPrevMap={}; indicatorRank(idx,prevYear).forEach(function(r){rankPrevMap[r.name]={cnt:r.cnt,rate:r.cnt/unitData(idx,prevYear).examined*100};});
var keyList=keyIndicatorDetail(idx,year);
var keyPrevMap={}; keyIndicatorDetail(idx,prevYear).forEach(function(k){keyPrevMap[k.name]=k.cnt;});
var dist=diseaseDist(idx,year);
var distPrevMap={}; diseaseDist(idx,prevYear).forEach(function(g){g.items.forEach(function(x){distPrevMap[x.name]=x.cnt;});});
var cross=ageGenderAbnormal(idx,year);
var years=[String(parseInt(year,10)-2),prevYear,year];
var panel=document.getElementById('report-panel');
/* 概览指标卡 */
/* 概览指标卡(含同比) */
var cards=[
['应检人数',fmt(d.total),''],
['已检人数',fmt(d.examined),'ov-card__value--primary'],
['受检率',pct(d.examined,d.total),'ov-card__value--success'],
['检出率',pct(d.abnormal,d.examined),'ov-card__value--warning'],
['异常人数',fmt(d.abnormal),'ov-card__value--danger'],
['重点指标异常',fmt(d.keyAbn),'ov-card__value--danger']
['应检人数',fmt(cur.total),'',cur.total-prev.total,false,'flat','人'],
['已检人数',fmt(cur.examined),'ov-card__value--primary',cur.examined-prev.examined,false,'flat','人'],
['受检率',pct1(cur.attendRate),'ov-card__value--success',cur.attendRate-prev.attendRate,true,'up',''],
['检出率',pct1(cur.detectRate),'ov-card__value--warning',cur.detectRate-prev.detectRate,true,'down',''],
['异常人数',fmt(cur.abnormal),'ov-card__value--danger',cur.abnormal-prev.abnormal,false,'down','人'],
['重点指标异常',fmt(cur.keyAbn),'ov-card__value--danger',cur.keyAbn-prev.keyAbn,false,'down','人'],
['复查率',pct1(cur.recheckRate),'',cur.recheckRate-prev.recheckRate,true,'up',''],
['数据完整率',pct1(cur.completeRate),'',cur.completeRate-prev.completeRate,true,'up',''],
['平均年龄',cur.avgAge+'岁','',cur.avgAge-prev.avgAge,false,'flat','岁'],
['未见异常',fmt(cur.healthy),'ov-card__value--success',cur.healthy-prev.healthy,false,'up','人']
].map(function(c){
return '<div class="ov-card"><div class="ov-card__value '+c[2]+'">'+c[1]+'</div><div class="ov-card__label">'+c[0]+'</div></div>';
return '<div class="ov-card"><div class="ov-card__value '+c[2]+'">'+c[1]+'</div><div class="ov-card__label">'+c[0]+'</div><div class="ov-card__trend">较上年 '+trendSpan(c[3],c[4],c[5],c[6])+'</div></div>';
}).join('');
/* 年龄性别分布表 */
var agRows=ag.map(function(r){
return '<tr><td>'+r.band+'</td><td class="num">'+fmt(r.male)+'</td><td class="num">'+fmt(r.female)+'</td><td class="num">'+fmt(r.male+r.female)+'</td><td class="rate">'+pct(r.male+r.female,d.examined)+'</td></tr>';
}).join('');
var agTotal=ag.reduce(function(a,r){return a+r.male+r.female;},0);
var agMale=ag.reduce(function(a,r){return a+r.male;},0);
var agFemale=ag.reduce(function(a,r){return a+r.female;},0);
/* 异常指标条形图 */
/* 异常指标条形图(带上年对照) */
var maxCnt=rank[0].cnt;
var bars=rank.map(function(r,i){
var cls=i===0?'top1':(i===1?'top2':(i===2?'top3':''));
var pv=rankPrevMap[r.name]||{cnt:0,rate:0};
var delta=r.cnt-pv.cnt;
return '<div class="bar-row">'
+'<div class="bar-row__label">'+r.name+'</div>'
+'<div class="bar-row__track"><div class="bar-row__fill '+cls+'" style="width:'+(r.cnt/maxCnt*100).toFixed(1)+'%;"></div></div>'
+'<div class="bar-row__val"><b>'+fmt(r.cnt)+'</b> 人 · '+pct(r.cnt,d.examined)+'</div>'
+'<div class="bar-row__val"><div><b>'+fmt(r.cnt)+'</b> 人 · '+pct(r.cnt,d.examined)+'</div><div class="bar-row__delta">上年 '+pct1(pv.rate)+' · '+trendSpan(delta,false,'down','人')+'</div></div>'
+'</div>';
}).join('');
/* 疾病分布表 */
/* 重点指标异常拆解 */
var keyRows=keyList.map(function(k){
var pv=keyPrevMap[k.name]||0;
return '<tr><td>'+k.name+'</td><td class="num">'+fmt(k.cnt)+'</td><td class="rate">'+pct(k.cnt,d.examined)+'</td><td>'+trendSpan(k.cnt-pv,false,'down','人')+'</td></tr>';
}).join('');
/* 异常率×年龄/性别 交叉 */
var crossRows=cross.map(function(r){
return '<tr><td>'+r.band+'</td><td class="rate">'+pct1(r.maleRate)+'</td><td class="rate">'+pct1(r.femaleRate)+'</td><td class="rate">'+pct1(r.rate)+'</td><td class="num">'+fmt(r.totalAbn)+'</td></tr>';
}).join('');
var crossTotalAbn=cross.reduce(function(a,r){return a+r.totalAbn;},0);
var crossMaleAbn=cross.reduce(function(a,r){return a+r.maleAbn;},0);
var crossFemaleAbn=cross.reduce(function(a,r){return a+r.femaleAbn;},0);
var crossMale=cross.reduce(function(a,r){return a+r.male;},0);
var crossFemale=cross.reduce(function(a,r){return a+r.female;},0);
var crossMaleRate=crossMale?crossMaleAbn/crossMale*100:0;
var crossFemaleRate=crossFemale?crossFemaleAbn/crossFemale*100:0;
/* 疾病分布(带较上年) */
var distRows='';
dist.forEach(function(g){
distRows+='<tr class="cat-row"><td colspan="4">'+g.cat+'</td></tr>';
distRows+='<tr class="cat-row"><td colspan="5">'+g.cat+'</td></tr>';
g.items.forEach(function(x){
distRows+='<tr><td></td><td>'+x.name+'</td><td class="num">'+fmt(x.cnt)+'</td><td class="rate">'+pct(x.cnt,d.examined)+'</td></tr>';
var pv=distPrevMap[x.name]||0;
distRows+='<tr><td></td><td>'+x.name+'</td><td class="num">'+fmt(x.cnt)+'</td><td class="rate">'+pct(x.cnt,d.examined)+'</td><td>'+trendSpan(x.cnt-pv,false,'down','人')+'</td></tr>';
});
});
/* 专项筛查(阳性率按实际受检人数计算) */
var screenHtml=''
+'<div class="screen-item"><div class="screen-item__name">防癌筛查 · Septin9</div><span class="screen-item__val">'+fmt(d.cancerPos)+'</span><span class="screen-item__unit">人阳性</span>'
+'<div class="screen-item__rate"><div>阳性率 '+pct(d.cancerPos,d.cancerScreened)+' · 已筛 '+fmt(d.cancerScreened)+' 人</div><div class="sub">较上年 '+trendSpan(d.cancerPos-pd.cancerPos,false,'down','人')+'</div></div></div>'
+'<div class="screen-item"><div class="screen-item__name">防癌筛查 · CA724</div><span class="screen-item__val">'+fmt(d.ca724Pos)+'</span><span class="screen-item__unit">人阳性</span>'
+'<div class="screen-item__rate"><div>阳性率 '+pct(d.ca724Pos,d.cancerScreened)+' · 已筛 '+fmt(d.cancerScreened)+' 人</div><div class="sub">较上年 '+trendSpan(d.ca724Pos-pd.ca724Pos,false,'down','人')+'</div></div></div>'
+'<div class="screen-item"><div class="screen-item__name">冠脉CTA</div><span class="screen-item__val">'+fmt(d.ctaAbn)+'</span><span class="screen-item__unit">人异常</span>'
+'<div class="screen-item__rate"><div>异常率 '+pct(d.ctaAbn,d.ctaScreened)+' · 已检 '+fmt(d.ctaScreened)+' 人</div><div class="sub">较上年 '+trendSpan(d.ctaAbn-pd.ctaAbn,false,'down','人')+'</div></div></div>'
+'<div class="screen-item"><div class="screen-item__name">胃肠镜</div><span class="screen-item__val">'+fmt(d.gastroAbn)+'</span><span class="screen-item__unit">人异常</span>'
+'<div class="screen-item__rate"><div>异常率 '+pct(d.gastroAbn,d.gastroScreened)+' · 已检 '+fmt(d.gastroScreened)+' 人</div><div class="sub">较上年 '+trendSpan(d.gastroAbn-pd.gastroAbn,false,'down','人')+'</div></div></div>';
/* 近三年趋势 */
var trendLegend='<div class="trend-legend"><span><i style="background:#1890FF"></i>受检率</span><span><i style="background:#FAAD14"></i>检出率</span></div>';
panel.innerHTML=
'<div class="report-head">'
+'<div class="report-head__title">'+d.name+' · 群体分析报告</div>'
+'<div class="report-head__meta">'
+'<span>体检年份:<b>'+state.year+'</b></span>'
+'<span>体检年份:<b>'+year+'</b></span>'
+'<span>承检医院:<b>'+d.hospital+'</b></span>'
+'<span>体检计划:<b>2026-06-23 至 2026-08-31</b></span>'
+'<span>体检计划:<b>'+planPeriod(idx,year)+'</b></span>'
+'<span>同比基准:<b>'+prevYear+' 年</b></span>'
+'</div></div>'
+'<div class="overview-cards">'+cards+'</div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">年龄与性别分布</div><div class="report-card__sub">按已检人数统计</div></div>'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">近三年趋势</div><div class="report-card__sub">受检率 / 检出率</div></div>'
+'<div class="report-card__body">'+trendLegend+trendChartSvg(idx,years)+'</div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">异常率 × 年龄 / 性别</div><div class="report-card__sub">各年龄段、性别异常检出率</div></div>'
+'<div class="report-card__body"><table class="dist-table">'
+'<thead><tr><th>年龄区间</th><th></th><th></th><th>合计</th><th>占比</th></tr></thead>'
+'<tbody>'+agRows
+'<tr><td style="font-weight:600;">合计</td><td class="num" style="font-weight:600;">'+fmt(agMale)+'</td><td class="num" style="font-weight:600;">'+fmt(agFemale)+'</td><td class="num" style="font-weight:600;">'+fmt(agTotal)+'</td><td class="rate">100%</td></tr>'
+'<thead><tr><th>年龄区间</th><th>性异常率</th><th>性异常率</th><th>合计异常率</th><th>异常人数</th></tr></thead>'
+'<tbody>'+crossRows
+'<tr><td style="font-weight:600;">合计</td><td class="rate" style="font-weight:600;">'+pct1(crossMaleRate)+'</td><td class="rate" style="font-weight:600;">'+pct1(crossFemaleRate)+'</td><td class="rate" style="font-weight:600;">'+pct1(cur.detectRate)+'</td><td class="num" style="font-weight:600;">'+fmt(crossTotalAbn)+'</td></tr>'
+'</tbody></table></div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">异常指标排名</div><div class="report-card__sub">按检出人数排序</div></div>'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">异常指标排名</div><div class="report-card__sub">按检出人数排序 · 附上年对照</div></div>'
+'<div class="report-card__body">'+bars+'</div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">疾病(病种)分布</div><div class="report-card__sub">按病种字典归类</div></div>'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">重点指标异常拆解</div><div class="report-card__sub">同一人可有多项异常</div></div>'
+'<div class="report-card__body"><table class="dist-table">'
+'<thead><tr><th style="width:24px;"></th><th>病种</th><th>人数</th><th>占比</th></tr></thead>'
+'<thead><tr><th>重点指标</th><th>异常人数</th><th>检出率</th><th>较上年</th></tr></thead>'
+'<tbody>'+keyRows+'</tbody></table></div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">疾病(病种)分布</div><div class="report-card__sub">按病种字典归类 · 附较上年</div></div>'
+'<div class="report-card__body"><table class="dist-table">'
+'<thead><tr><th style="width:24px;"></th><th>病种</th><th>人数</th><th>占比</th><th>较上年</th></tr></thead>'
+'<tbody>'+distRows+'</tbody></table></div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">专项筛查结果</div></div>'
+'<div class="report-card__body">'
+'<div class="screen-item"><div class="screen-item__name">防癌筛查 · Septin9</div><span class="screen-item__val">'+d.cancerPos+'</span><span class="screen-item__unit">人阳性</span><span class="screen-item__rate">占应检 '+pct(d.cancerPos,d.total)+'</span></div>'
+'<div class="screen-item"><div class="screen-item__name">防癌筛查 · CA724</div><span class="screen-item__val">'+Math.max(1,Math.round(d.cancerPos*0.8))+'</span><span class="screen-item__unit">人阳性</span><span class="screen-item__rate">占应检 '+pct(Math.round(d.cancerPos*0.8),d.total)+'</span></div>'
+'<div class="screen-item"><div class="screen-item__name">冠脉CTA</div><span class="screen-item__val">'+d.ctaAbn+'</span><span class="screen-item__unit">人异常</span><span class="screen-item__rate">占已检 '+pct(d.ctaAbn,d.examined)+'</span></div>'
+'</div></div>';
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">专项筛查结果</div><div class="report-card__sub">阳性率按实际受检人数计算</div></div>'
+'<div class="report-card__body">'+screenHtml+'</div></div>';
}
/* 导出群体报告(CSVExcel 兼容) */
function exportReport(){
var d=unitData(state.unitIdx);
var rank=indicatorRank(d);
var dist=diseaseDist(d);
var rows=[['单位群体报告',''],['单位',d.name],['体检年份',state.year],['承检医院',d.hospital],[],
['应检人数',d.total],['已检人数',d.examined],['受检率',pct(d.examined,d.total)],['检出率',pct(d.abnormal,d.examined)],['异常人数',d.abnormal],['重点指标异常人数',d.keyAbn],[],
['异常指标排名','人数'],[]].concat(rank.map(function(r){return [r.name,r.cnt];})).concat([[],['疾病(病种)分布','人数']]);
dist.forEach(function(g){g.items.forEach(function(x){rows.push([g.cat+' · '+x.name,x.cnt]);});});
var idx=state.unitIdx, year=state.year;
var prevYear=String(parseInt(year,10)-1);
var d=unitData(idx,year), pd=unitData(idx,prevYear);
var cur=computeStats(idx,year), prev=computeStats(idx,prevYear);
var rank=indicatorRank(idx,year);
var rankPrevMap={}; indicatorRank(idx,prevYear).forEach(function(r){rankPrevMap[r.name]=r.cnt;});
var keyList=keyIndicatorDetail(idx,year);
var keyPrevMap={}; keyIndicatorDetail(idx,prevYear).forEach(function(k){keyPrevMap[k.name]=k.cnt;});
var dist=diseaseDist(idx,year);
var distPrevMap={}; diseaseDist(idx,prevYear).forEach(function(g){g.items.forEach(function(x){distPrevMap[x.name]=x.cnt;});});
var cross=ageGenderAbnormal(idx,year);
var years=[String(parseInt(year,10)-2),prevYear,year];
var rows=[];
rows.push(['单位群体报告',''],['单位',d.name],['体检年份',year],['承检医院',d.hospital],['体检计划',planPeriod(idx,year)],['同比基准',prevYear+'年'],[]);
rows.push(['概览指标','今年','上年','较上年']);
rows.push(['应检人数',d.total,pd.total,d.total-pd.total]);
rows.push(['已检人数',d.examined,pd.examined,d.examined-pd.examined]);
rows.push(['受检率(%)',cur.attendRate.toFixed(1),prev.attendRate.toFixed(1),(cur.attendRate-prev.attendRate).toFixed(1)]);
rows.push(['检出率(%)',cur.detectRate.toFixed(1),prev.detectRate.toFixed(1),(cur.detectRate-prev.detectRate).toFixed(1)]);
rows.push(['异常人数',d.abnormal,pd.abnormal,d.abnormal-pd.abnormal]);
rows.push(['重点指标异常人数',d.keyAbn,pd.keyAbn,d.keyAbn-pd.keyAbn]);
rows.push(['复查率(%)',cur.recheckRate.toFixed(1),prev.recheckRate.toFixed(1),(cur.recheckRate-prev.recheckRate).toFixed(1)]);
rows.push(['数据完整率(%)',cur.completeRate.toFixed(1),prev.completeRate.toFixed(1),(cur.completeRate-prev.completeRate).toFixed(1)]);
rows.push(['平均年龄',cur.avgAge,prev.avgAge,cur.avgAge-prev.avgAge]);
rows.push(['未见异常人数',d.healthy,pd.healthy,d.healthy-pd.healthy]);
rows.push([]);
rows.push(['异常率×年龄/性别','男性异常率(%)','女性异常率(%)','合计异常率(%)','异常人数']);
cross.forEach(function(r){rows.push([r.band,r.maleRate.toFixed(1),r.femaleRate.toFixed(1),r.rate.toFixed(1),r.totalAbn]);});
rows.push([]);
rows.push(['异常指标排名','人数','检出率','上年人数','较上年']);
rank.forEach(function(r){var pv=rankPrevMap[r.name]||0;rows.push([r.name,r.cnt,pct(r.cnt,d.examined),pv,r.cnt-pv]);});
rows.push([]);
rows.push(['重点指标异常','异常人数','检出率','上年人数','较上年']);
keyList.forEach(function(k){var pv=keyPrevMap[k.name]||0;rows.push([k.name,k.cnt,pct(k.cnt,d.examined),pv,k.cnt-pv]);});
rows.push([]);
rows.push(['疾病(病种)分布','人数','占比','上年人数','较上年']);
dist.forEach(function(g){g.items.forEach(function(x){var pv=distPrevMap[x.name]||0;rows.push([g.cat+' · '+x.name,x.cnt,pct(x.cnt,d.examined),pv,x.cnt-pv]);});});
rows.push([]);
rows.push(['专项筛查结果','阳性/异常','率','已筛/已检','较上年']);
rows.push(['防癌筛查 Septin9',d.cancerPos,pct(d.cancerPos,d.cancerScreened),'已筛 '+d.cancerScreened,d.cancerPos-pd.cancerPos]);
rows.push(['防癌筛查 CA724',d.ca724Pos,pct(d.ca724Pos,d.cancerScreened),'已筛 '+d.cancerScreened,d.ca724Pos-pd.ca724Pos]);
rows.push(['冠脉CTA',d.ctaAbn,pct(d.ctaAbn,d.ctaScreened),'已检 '+d.ctaScreened,d.ctaAbn-pd.ctaAbn]);
rows.push(['胃肠镜',d.gastroAbn,pct(d.gastroAbn,d.gastroScreened),'已检 '+d.gastroScreened,d.gastroAbn-pd.gastroAbn]);
rows.push([]);
rows.push(['近三年趋势','受检率(%)','检出率(%)']);
years.forEach(function(y){var s=computeStats(idx,y);rows.push([y+'年',s.attendRate.toFixed(1),s.detectRate.toFixed(1)]);});
var csv=rows.map(function(r){return r.map(function(c){return '"'+String(c).replace(/"/g,'""')+'"';}).join(',');}).join('\r\n');
var a=document.createElement('a');
a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'}));
a.download=d.name+'_'+state.year+'年_群体分析报告.csv';
a.download=d.name+'_'+year+'年_群体分析报告.csv';
document.body.appendChild(a);a.click();document.body.removeChild(a);
setTimeout(function(){URL.revokeObjectURL(a.href);},1000);
}
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item active">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -168,6 +168,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -140,6 +140,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -132,6 +132,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item active">医院体检项目统计</a>
@@ -165,6 +165,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -168,6 +168,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -218,6 +218,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -151,6 +151,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -0,0 +1,459 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康体检 - 结果动态综合统计 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;overflow:hidden;}
:root{
--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;
--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;--background:#F5F7FA;
--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;
--radius-sm:4px;--radius-md:8px;--radius-lg:12px;
--shadow-sm:0 1px 4px rgba(0,0,0,0.08);--shadow-md:0 4px 12px rgba(0,0,0,0.12);
}
/* 顶部导航 */
.top-bar{display:flex;align-items:center;height:56px;background:#001529;padding:0 24px;position:fixed;top:0;left:0;right:0;z-index:100;}
.top-bar__logo{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.top-bar__right .btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.top-bar__right .btn-back:hover{color:#fff;}
.avatar{width:32px;height:32px;border-radius:50%;background:#1890FF;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);cursor:pointer;}
.top-bar__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
/* 侧边栏 */
.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);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
.sidebar-subgroup__header .arrow path{stroke:rgba(255,255,255,0.45);}
.sidebar-subgroup.open>.sidebar-subgroup__header{color:rgba(255,255,255,0.85);}
.sidebar-subgroup.open>.sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;padding-left:16px;}
.sidebar-subgroup.open>.sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:36px;font-size:12px;}
.sidebar-subgroup .sidebar-subgroup{padding-left:12px;}
.sidebar-subgroup .sidebar-subgroup .sidebar-item{padding-left:40px;}
.sidebar-subgroup .sidebar-subgroup>.sidebar-subgroup__header{font-size:12px;}
/* 主内容 */
.main{margin-left:240px;margin-top:56px;padding:20px 24px;height:calc(100vh - 56px);display:flex;flex-direction:column;overflow:hidden;}
/* 页头 */
.page-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-shrink:0;}
.page-title{font-size:20px;font-weight:600;color:var(--text-primary);}
/* 筛选栏 */
.filter-bar{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);flex-shrink:0;}
.filter-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap;}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:120px;}
.filter-input:focus{border-color:var(--primary);}
.year-picker{position:relative;}
.year-picker .filter-input{padding-right:28px;cursor:pointer;}
.year-picker>svg{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;}
.year-picker__dropdown{display:none;position:absolute;top:38px;left:0;width:110px;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);z-index:50;max-height:170px;overflow-y:auto;}
.year-picker__dropdown.open{display:block;}
.year-picker__dropdown div{padding:7px 14px;font-size:var(--font-sm);cursor:pointer;}
.year-picker__dropdown div:hover{background:var(--primary-light);}
.year-picker__dropdown div.selected{color:var(--primary);font-weight:600;}
/* 2×2 图表网格 */
.chart-grid{flex:1;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:16px;min-height:0;}
.panel{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:12px 16px;display:flex;flex-direction:column;min-width:0;min-height:0;}
.panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-shrink:0;}
.panel__title{font-size:var(--font-base);font-weight:600;white-space:nowrap;}
.panel__filters{display:flex;align-items:center;gap:8px;}
.panel__filters label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.legend{display:inline-flex;align-items:center;gap:12px;font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:4px;vertical-align:middle;}
.filter-select{height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 26px 0 10px;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 8px center;appearance:none;cursor:pointer;}
.filter-select--item{min-width:108px;}
.filter-select--dim{min-width:64px;}
.panel__chart{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;}
.col-svg{width:100%;height:100%;display:block;}
.axis-label{font-size:11px;fill:#999;}
.total-label{font-size:12px;fill:#333;font-weight:600;}
.seg-label{font-size:11px;fill:#fff;font-weight:600;}
.col rect{transition:opacity .2s;}
.col:hover rect{opacity:0.85;}
.empty-hint{text-align:center;color:var(--text-placeholder);padding:40px 0;font-size:var(--font-sm);}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="top-bar">
<div class="top-bar__logo">
<svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
健康体检
</div>
<nav class="top-bar__nav">
<a href="archive-personal.html">档案</a>
<a href="config-material-notice.html">基础配置</a>
<a href="pre-hospital-scope.html">检前</a>
<a href="mid-appointment-track.html">检中</a>
<a href="post-unit-count.html" class="active">检后</a>
<a href="exception-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right">
<a class="btn-back" href="../../index.html">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
返回首页
</a>
<div class="avatar"></div>
<span style="color:rgba(255,255,255,0.65);">系统管理员 ▾</span>
</div>
</div>
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">检后</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
<a href="post-indicator-notify.html" class="sidebar-item">指标异常通知</a>
<a href="post-report-unmatched.html" class="sidebar-item">体检报告未匹配项目</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
体检统计汇总
</div>
<div class="sidebar-subgroup__body">
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item active">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
<a href="post-cancer-screening.html" class="sidebar-item">防癌筛查人数统计</a>
<a href="post-standard-stat.html" class="sidebar-item">系统标准化项目统计</a>
<a href="post-contracted-hospital.html" class="sidebar-item">签约医院人数统计</a>
<a href="post-key-indicator-stat.html" class="sidebar-item">重点指标异常统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
病种信息管理
</div>
<div class="sidebar-subgroup__body">
<a href="post-disease-info.html" class="sidebar-item">病种信息配置</a>
<a href="post-conclusion-disease.html" class="sidebar-item">体检结论关联病种</a>
<a href="post-auto-select-disease.html" class="sidebar-item">员工病种信息</a>
</div>
</div>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- 页头 -->
<div class="page-header">
<div class="page-title">结果动态综合统计</div>
</div>
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-row">
<div class="filter-item">
<label>体检年份:</label>
<div class="year-picker">
<input class="filter-input" type="text" readonly value="2026" style="min-width:96px;">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="2.5" width="11" height="10" rx="1.5" stroke="#999" stroke-width="1.2"/><path d="M1.5 5.5h11M4.5 1v3M9.5 1v3" stroke="#999" stroke-width="1.2" stroke-linecap="round"/></svg>
<div class="year-picker__dropdown">
<div class="selected" onclick="pickYear(this)">2026</div>
<div onclick="pickYear(this)">2025</div>
<div onclick="pickYear(this)">2024</div>
</div>
</div>
</div>
<div class="filter-item">
<label>单位:</label>
<select class="filter-select filter-select--item">
<option>请选择</option><option selected>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
</div>
</div>
<!-- 2×2 图表 -->
<div class="chart-grid" id="chart-grid"></div>
</main>
<script>
/* 可统计的体检项目;base 为整体异常率基准,维度会在此基础上浮动 */
var ITEMS=[
{name:'血常规',code:'XM1001',base:0.022},
{name:'尿常规',code:'XM1002',base:0.030},
{name:'肝功能',code:'XM1003',base:0.080},
{name:'肾功能',code:'XM1004',base:0.055},
{name:'血脂',code:'XM1005',base:0.120},
{name:'空腹血糖',code:'XM1006',base:0.065},
{name:'心电图',code:'XM1007',base:0.045},
{name:'腹部彩超',code:'XM1008',base:0.110},
{name:'胸部正位片',code:'XM1009',base:0.060},
{name:'肿瘤标志物',code:'XM1010',base:0.018},
{name:'骨密度',code:'XM1011',base:0.070},
{name:'甲状腺彩超',code:'XM1012',base:0.040}
];
/* 统计维度:性别 / 年龄 / 部门 / 岗位 */
var DIMS=[
{key:'gender',label:'性别'},
{key:'age',label:'年龄'},
{key:'dept',label:'部门'},
{key:'position',label:'岗位'}
];
var CATS={
gender:['男','女'],
age:['20-29岁','30-39岁','40-49岁','50-59岁','60岁及以上'],
dept:['采油作业一区','采油作业二区','集输作业一区','集输作业二区','机关','后勤保障'],
position:['采油工','集输工','技术员','管理岗','后勤岗','机关岗']
};
/* 四个图表面板,各自默认一个维度 */
var PANELS=[
{dim:'gender'},
{dim:'age'},
{dim:'dept'},
{dim:'position'}
];
var DEFAULT_ITEM='XM1001';
/* 简易伪随机(确定性,保证每次刷新数据一致) */
function mulberry32(a){
return function(){
var t=a+=0x6D2B79F5;
t=Math.imul(t^t>>>15,t|1);
t^=t+Math.imul(t^t>>>7,t|61);
return ((t^t>>>14)>>>0)/4294967296;
};
}
function hashNum(str){
var h=5381;
for(var i=0;i<str.length;i++){h=((h<<5)+h+str.charCodeAt(i))>>>0;}
return h;
}
/* 示例员工池:按部门/岗位/年龄/性别等属性生成 */
var SURNAMES=['石','曲','李','张','王','刘','陈','杨','赵','孙','周','吴','郑','冯','蒋','沈'];
var GIVEN=['卫国','志明','秀兰','卫东','海燕','振华','国庆','春梅','建军','丽华','文博','晓燕','志强','雪梅','永刚','红霞'];
var EMP_POOL=[];
(function buildPool(){
var rnd=mulberry32(20260920);
var posByDept={
'采油作业一区':['采油工','技术员','管理岗'],
'采油作业二区':['采油工','技术员','管理岗'],
'集输作业一区':['集输工','技术员','管理岗'],
'集输作业二区':['集输工','技术员','管理岗'],
'机关':['机关岗','管理岗'],
'后勤保障':['后勤岗','管理岗']
};
var n=0;
CATS.dept.forEach(function(dept){
var count=60+Math.floor(rnd()*30);
for(var i=0;i<count;i++){
n++;
var list=posByDept[dept];
var position=list[Math.floor(rnd()*list.length)];
var gender=rnd()<0.62?'':'';
var roll=rnd();
var age=roll<0.15?CATS.age[0]:roll<0.45?CATS.age[1]:roll<0.75?CATS.age[2]:roll<0.93?CATS.age[3]:CATS.age[4];
EMP_POOL.push({
name:SURNAMES[Math.floor(rnd()*SURNAMES.length)]+GIVEN[Math.floor(rnd()*GIVEN.length)],
id:'CQ'+String(n).padStart(6,'0'),
dept:dept,position:position,gender:gender,age:age
});
}
});
})();
/* 员工在某项目下是否异常:随年龄/性别/岗位浮动,哈希保证结果稳定 */
function isAbnormal(emp,item){
var ageIdx=CATS.age.indexOf(emp.age);
var ageFactor=[0.55,0.8,1.15,1.55,2.1][ageIdx];
var genderFactor=emp.gender==='男'?1.15:0.88;
var posFactor=(emp.position==='采油工'||emp.position==='集输工')?1.2:0.9;
var p=item.base*ageFactor*genderFactor*posFactor;
if(p>0.5)p=0.5;
return (hashNum(emp.id+item.code)%10000)/10000<p;
}
/* 按所选维度分组(保持维度取值的自然顺序) */
function computeGroups(item,key){
var map={};
EMP_POOL.forEach(function(emp){
var abnormal=isAbnormal(emp,item);
var val=emp[key];
if(!map[val])map[val]={cats:[val],total:0,abnormal:0};
map[val].total++;
if(abnormal)map[val].abnormal++;
});
var order=CATS[key];
return order.map(function(v){return map[v];}).filter(Boolean);
}
function dimLabel(key){for(var i=0;i<DIMS.length;i++){if(DIMS[i].key===key)return DIMS[i].label;}return key;}
function getItemByCode(code){for(var i=0;i<ITEMS.length;i++){if(ITEMS[i].code===code)return ITEMS[i];}return null;}
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function niceStep(max){
if(max<=20)return 5;
if(max<=50)return 10;
if(max<=100)return 20;
if(max<=200)return 50;
if(max<=500)return 100;
if(max<=1000)return 200;
return 500;
}
/* 生成单个堆积柱状图 SVG */
function columnChartSvg(groups,dimKey){
var n=groups.length;
var maxTotal=0;
groups.forEach(function(g){if(g.total>maxTotal)maxTotal=g.total;});
var step=niceStep(maxTotal);
var yMax=Math.ceil(maxTotal/step)*step;
if(yMax<=0)yMax=step;
var chartW=620, chartH=430;
var x0=56, y0=26, x1=chartW-16, y1=chartH-56;
var plotW=x1-x0, plotH=y1-y0;
var grid='<line x1="'+x0+'" y1="'+y1+'" x2="'+x1+'" y2="'+y1+'" stroke="#E8E8E8" stroke-width="1"/>';
for(var v=0;v<=yMax;v+=step){
var y=y1-(v/yMax)*plotH;
grid+='<line x1="'+x0+'" y1="'+y+'" x2="'+x1+'" y2="'+y+'" stroke="#F0F0F0" stroke-width="1"/>';
grid+='<text x="'+(x0-10)+'" y="'+(y+4)+'" text-anchor="end" class="axis-label">'+v+'</text>';
}
var slot=plotW/n;
var colW=Math.min(slot*0.55, 64);
var bars='', labels='';
groups.forEach(function(g,i){
var cx=x0+i*slot+slot/2;
var bx=cx-colW/2;
var total=g.total, abnormal=g.abnormal, normal=total-abnormal;
var nh=(normal/yMax)*plotH;
var ah=(abnormal/yMax)*plotH;
var np=normal/total*100, ap=abnormal/total*100;
var normalY=y1-nh;
var abnormalY=y1-nh-ah;
var tip=g.cats.join(' · ')+':总 '+total+' 人,正常 '+normal+' 人('+np.toFixed(1)+'%),异常 '+abnormal+' 人('+ap.toFixed(1)+'%';
bars+='<g class="col" title="'+esc(tip)+'">'+
'<rect x="'+bx+'" y="'+normalY+'" width="'+colW+'" height="'+nh+'" rx="2" fill="#52C41A"/>'+
'<rect x="'+bx+'" y="'+abnormalY+'" width="'+colW+'" height="'+ah+'" rx="2" fill="#FF4D4F"/>';
if(ah>=22)bars+='<text x="'+cx+'" y="'+(abnormalY+ah/2+4)+'" text-anchor="middle" class="seg-label">'+ap.toFixed(1)+'%</text>';
if(nh>=22)bars+='<text x="'+cx+'" y="'+(normalY+nh/2+4)+'" text-anchor="middle" class="seg-label">'+np.toFixed(1)+'%</text>';
bars+='<text x="'+cx+'" y="'+(abnormalY-6)+'" text-anchor="middle" class="total-label">'+total+'</text>';
bars+='</g>';
labels+='<text x="'+cx+'" y="'+(y1+22)+'" text-anchor="middle" class="axis-label">'+esc(g.cats.join(' · '))+'</text>';
});
return '<svg viewBox="0 0 '+chartW+' '+chartH+'" class="col-svg" preserveAspectRatio="xMidYMid meet">'+grid+bars+labels+'</svg>';
}
/* 生成一个图表面板 */
function buildPanels(){
var grid=document.getElementById('chart-grid');
var html='';
PANELS.forEach(function(p,i){
var idx=i+1;
var itemOpts='', dimOpts='';
ITEMS.forEach(function(it){itemOpts+='<option value="'+it.code+'">'+it.name+'</option>';});
DIMS.forEach(function(d){dimOpts+='<option value="'+d.key+'">'+d.label+'</option>';});
html+='<div class="panel">'+
'<div class="panel__header">'+
'<div class="panel__title" id="panel-title-'+idx+'">'+dimLabel(p.dim)+'</div>'+
'<div class="panel__filters">'+
'<label>项目</label>'+
'<select class="filter-select filter-select--item" id="panel-item-'+idx+'" onchange="renderPanel('+idx+')">'+itemOpts+'</select>'+
'<label>维度</label>'+
'<select class="filter-select filter-select--dim" id="panel-dim-'+idx+'" onchange="renderPanel('+idx+')">'+dimOpts+'</select>'+
'<span class="legend"><span><i style="background:var(--success);"></i>正常</span><span><i style="background:var(--danger);"></i>异常</span></span>'+
'</div>'+
'</div>'+
'<div class="panel__chart" id="panel-chart-'+idx+'"></div>'+
'</div>';
});
grid.innerHTML=html;
}
/* 渲染单个面板图表 */
function renderPanel(idx){
var item=getItemByCode(document.getElementById('panel-item-'+idx).value);
var dimKey=document.getElementById('panel-dim-'+idx).value;
var el=document.getElementById('panel-chart-'+idx);
document.getElementById('panel-title-'+idx).textContent=dimLabel(dimKey);
if(!item){el.innerHTML='<div class="empty-hint">请选择体检项目</div>';return;}
el.innerHTML=columnChartSvg(computeGroups(item,dimKey),dimKey);
}
/* 侧边栏展开/折叠 */
function toggleSubgroup(header){
header.parentElement.classList.toggle('open');
}
(function(){
var active=document.querySelector('.sidebar-item.active');
if(!active)return;
var sub=active.closest('.sidebar-subgroup');
while(sub){sub.classList.add('open');sub=sub.parentElement.closest('.sidebar-subgroup');}
})();
/* 体检年份选择控件 */
function pickYear(el){
var dp=el.parentElement,input=dp.parentElement.querySelector('input');
input.value=el.textContent;
dp.querySelectorAll('div').forEach(function(d){d.classList.toggle('selected',d===el);});
dp.classList.remove('open');
}
document.querySelectorAll('.year-picker>input').forEach(function(inp){
inp.addEventListener('click',function(e){
e.stopPropagation();
var dp=inp.parentElement.querySelector('.year-picker__dropdown');
var wasOpen=dp.classList.contains('open');
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
if(!wasOpen)dp.classList.add('open');
});
});
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* 初始化:生成四个面板并各自默认项目 + 维度渲染 */
(function init(){
buildPanels();
PANELS.forEach(function(p,i){
var idx=i+1;
document.getElementById('panel-item-'+idx).value=DEFAULT_ITEM;
document.getElementById('panel-dim-'+idx).value=p.dim;
renderPanel(idx);
});
})();
</script>
</body>
</html>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -161,6 +161,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item active">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -172,6 +172,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -152,6 +152,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item active">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">结果动态综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -324,18 +324,18 @@ var sportRecords = [
/* 体重记录 */
var weightRecords = [
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'身高秤'},
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'体重秤'},
{time:'2026-06-28 07:45',weight:82.1,bmi:28.2,fatRate:25.8,waist:94.2,source:'体脂仪'},
{time:'2026-06-25 18:20',weight:82.5,bmi:28.4,fatRate:26.1,waist:94.8,source:'蓝牙称'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'身高秤'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'体重秤'},
{time:'2026-06-19 20:00',weight:83.2,bmi:28.6,fatRate:26.6,waist:95.5,source:'自记'},
{time:'2026-06-16 09:00',weight:83.7,bmi:28.8,fatRate:26.9,waist:96.0,source:'体脂仪'},
{time:'2026-06-13 07:30',weight:84.1,bmi:28.9,fatRate:27.2,waist:96.4,source:'蓝牙称'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'身高秤'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'体重秤'},
{time:'2026-06-07 08:45',weight:84.9,bmi:29.2,fatRate:27.8,waist:97.2,source:'体脂仪'},
{time:'2026-06-04 19:30',weight:85.2,bmi:29.3,fatRate:28.1,waist:97.6,source:'自记'},
{time:'2026-06-01 08:00',weight:85.6,bmi:29.4,fatRate:28.4,waist:98.2,source:'蓝牙称'},
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'身高秤'}
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'体重秤'}
];
var pageSize = 10;
@@ -464,7 +464,7 @@ function renderWeight(){
var totalPages = Math.ceil(filtered.length / pageSize) || 1;
document.getElementById('weightPageInfo').textContent = '共 ' + filtered.length + ' 条';
document.getElementById('weightPageNum').textContent = '第 ' + (weightPage + 1) + '/' + totalPages + ' 页';
var srcTag = {身高秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var srcTag = {体重秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var html = '';
pageData.forEach(function(d,i){
html += '<tr>';
+3 -3
View File
@@ -252,7 +252,7 @@
**运动记录Tab表格列:** 时间、运动类型、运动时长/数据、消耗热量(千卡)、来源(标签:穿戴/微信/自记/器械)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:身高秤/体脂仪/蓝牙称/自记)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:体重秤/体脂仪/蓝牙称/自记)
**业务规则:**
- 变化正值红色标注(体重增加),负值绿色标注(体重减少)
@@ -891,7 +891,7 @@
**运动记录Tab表格列:** 时间、运动类型、运动时长/数据、消耗热量(千卡)、来源(标签:穿戴/微信/自记/器械)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:身高秤/体脂仪/蓝牙称/自记)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:体重秤/体脂仪/蓝牙称/自记)
**交互说明:**
- 面包屑支持返回总览记录
@@ -1144,7 +1144,7 @@
**员工匿名 Tab:** 仅含使用日期筛选,表格列为姓名、使用时间 + 同上10项监测数据列 + 更多信息
**详情弹窗(监测数据详情 - **员工姓名**3组指标,对齐员工端身高秤报告):**
**详情弹窗(监测数据详情 - **员工姓名**3组指标,对齐员工端体重秤报告):**
- **基础指标:** 身高(cm)、体重(kg)、BMI、体脂率(%)
- **身体成分:** 体脂肪(kg)、水分率(%)、水分量(kg)、肌肉率(%)、肌肉量(kg)、骨量(kg)、去脂体重(kg)
- **体态分析:** 内脏脂肪、体型、理想体重(kg)、超重百分比(%)、体表面积(m²)
@@ -328,18 +328,18 @@ var sportRecords = [
/* 体重记录 */
var weightRecords = [
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'身高秤'},
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'体重秤'},
{time:'2026-06-28 07:45',weight:82.1,bmi:28.2,fatRate:25.8,waist:94.2,source:'体脂仪'},
{time:'2026-06-25 18:20',weight:82.5,bmi:28.4,fatRate:26.1,waist:94.8,source:'蓝牙称'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'身高秤'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'体重秤'},
{time:'2026-06-19 20:00',weight:83.2,bmi:28.6,fatRate:26.6,waist:95.5,source:'自记'},
{time:'2026-06-16 09:00',weight:83.7,bmi:28.8,fatRate:26.9,waist:96.0,source:'体脂仪'},
{time:'2026-06-13 07:30',weight:84.1,bmi:28.9,fatRate:27.2,waist:96.4,source:'蓝牙称'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'身高秤'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'体重秤'},
{time:'2026-06-07 08:45',weight:84.9,bmi:29.2,fatRate:27.8,waist:97.2,source:'体脂仪'},
{time:'2026-06-04 19:30',weight:85.2,bmi:29.3,fatRate:28.1,waist:97.6,source:'自记'},
{time:'2026-06-01 08:00',weight:85.6,bmi:29.4,fatRate:28.4,waist:98.2,source:'蓝牙称'},
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'身高秤'}
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'体重秤'}
];
var pageSize = 10;
@@ -445,7 +445,7 @@ function renderWeight(){
var totalPages = Math.ceil(weightRecords.length / pageSize) || 1;
document.getElementById('weightPageInfo').textContent = '共 ' + weightRecords.length + ' 条';
document.getElementById('weightPageNum').textContent = '第 ' + (weightPage + 1) + '/' + totalPages + ' 页';
var srcTag = {身高秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var srcTag = {体重秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var html = '';
pageData.forEach(function(d,i){
html += '<tr>';