feat: 健康监测模块UI优化与员工端PRD文档更新

员工端:新增健康现状入口、健康排查文案优化(打卡→记录)、穿戴监测单位/图表/预警页面优化、环境监测地图缩放按钮
管理端:档案管理/环境监测/体重监测/穿戴监测等模块优化
文档:同步更新员工端PRD文字描述与12张页面截图

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 21:55:05 +08:00
co-authored by Claude Opus 4.6
parent 14157f92e4
commit b120bb691c
146 changed files with 2031 additions and 1022 deletions
+2 -2
View File
@@ -1,11 +1,11 @@
_Last synced: 2026-05-26 | Base commit: `869d555`_
_Last synced: 2026-05-27 | Base commit: `d92bed3`_
# 记忆索引
| 文件 | 描述 | 类型 | 最后更新 |
|------|------|------|----------|
| project_overview.md | 项目技术栈、架构与模块结构 | project | 2026-05-14 |
| project_progress.md | 各模块原型开发进度 | project | 2026-05-26 |
| project_progress.md | 各模块原型开发进度 | project | 2026-05-27 |
| decisions.md | 关键设计决策与架构约定 | project | 2026-04-21 |
| feedback.md | 协作规范与用户纠正记录 | feedback | 2026-05-15 |
| user_profile.md | 用户角色与偏好 | user | 2026-04-21 |
+4 -3
View File
@@ -2,8 +2,8 @@
name: 项目进度
description: 各模块原型开发进度与近期里程碑
type: project
last_updated: 2026-05-26
commit: 869d555
last_updated: 2026-05-27
commit: d92bed3
---
# 项目进度
@@ -35,7 +35,7 @@ commit: 869d555
- `emergency-dispatch/` — 应急就医管理(含 admin + operator 子模块,70+ 页)✅
- `health-checkup/` — 健康体检管理 ✅
- `nutrition/` — 营养管理(70+ 页:溯源、档案、种采、供应、生产、用餐、异常、食堂、数据、模型、终端、字典)🔨 设计中
- `health-monitor/` — 健康监测管理(65 页)🔨 设计中
- `health-monitor/` — 健康监测管理(83 页)🔨 设计中
- `wearable/` — 穿戴监测(8 页:设备管理/绑定记录/用户数据/部门报告/部门统计/个人统计/异常事件/通知配置)
- `weight/` — 体重监测(20 页:体重秤/蓝牙秤/体脂仪/运动器械各含设备管理+监测+明细+新增,员工数据/部门统计/员工统计)
- `environment/` — 环境监测(25 页:室内/室外/花粉/水质各含设备类型+设备列表+数据监测+历史数据+APP记录+异常告警,花粉知识)
@@ -55,6 +55,7 @@ Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、
- 覆盖模块:17 个(含三端+其他终端)
## 近期里程碑
- 2026-05-27:健康监测模块优化——用户数据详情总览卡片统一为大数字居中样式,新增「健康现状」一级菜单至PRD文档及导航,管理端PRD与HTML原型一致性校正(10处筛选条件/表格列修正),员工端PRD一致性检查通过
- 2026-05-26:卫生供应模块净菜内供/外售页面交互重构——列表去掉溯源码和仓库/出库区域列,新建弹窗包装批次号始终显示(非必填)并回显批次信息,备货完成改为补全备货信息弹窗(选择批次号),详情抽屉按净菜类型定制渲染(净菜包显示溯源码/餐品净菜包显示批次号+明细表),外售弹窗客户信息后置且改为输入框
- 2026-05-25:员工端环境监测模块新增——地图/列表视图入口页、设备详情页、报警数据页
- 2026-05-22:味养创新交互完善——人工创新查看详情弹窗/审核双模/AI推演弹窗重构及侧边栏修复,功能说明9.5章节同步更新
+15 -2
View File
@@ -7,9 +7,21 @@
<value>
<TabSessionState>
<option name="provider" value="claude" />
<option name="sessionId" value="ed043cb0-ae56-4fb9-a5c9-dabc258c9363" />
<option name="sessionId" value="7db53556-8225-461e-af81-15299a529394" />
<option name="cwd" value="$PROJECT_DIR$" />
<option name="model" value="claude-haiku-4-5" />
<option name="model" value="claude-opus-4-6" />
<option name="permissionMode" value="bypassPermissions" />
<option name="reasoningEffort" value="medium" />
</TabSessionState>
</value>
</entry>
<entry key="1">
<value>
<TabSessionState>
<option name="provider" value="claude" />
<option name="sessionId" value="f8a1212a-15f3-4af8-b668-bdc598497ea4" />
<option name="cwd" value="$PROJECT_DIR$" />
<option name="model" value="claude-opus-4-6" />
<option name="permissionMode" value="bypassPermissions" />
<option name="reasoningEffort" value="medium" />
</TabSessionState>
@@ -17,5 +29,6 @@
</entry>
</map>
</option>
<option name="tabCount" value="2" />
</component>
</project>
+1 -1
View File
@@ -1,4 +1,4 @@
<!-- Last updated: 2026-05-26 | Commit: 869d555 -->
<!-- Last updated: 2026-05-27 | Commit: d92bed3 -->
# 项目约束
+34 -28
View File
@@ -11,8 +11,8 @@
├── 健康监测主页
├── 健康排查
│ ├── 健康排查首页(积分/排行榜)
│ ├── 每日健康打卡(问卷填写)
│ ├── 打卡记录(日历+列表)
│ ├── 每日健康记录(问卷填写)
│ ├── 历史记录(日历+列表)
│ ├── 问卷详情(只读回顾)
│ └── 题目设置(自定义显示)
├── 穿戴监测
@@ -49,8 +49,8 @@
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 2 | 健康排查首页 | health-screening/index.html | 页面 |
| 3 | 每日健康打卡 | health-screening/questionnaire-fill.html | 页面 |
| 4 | 打卡记录 | health-screening/checkin-record.html | 页面 |
| 3 | 每日健康记录 | health-screening/questionnaire-fill.html | 页面 |
| 4 | 历史记录 | health-screening/checkin-record.html | 页面 |
| 5 | 问卷详情 | health-screening/questionnaire-detail.html | 页面 |
| 6 | 题目设置 | health-screening/questionnaire-setting.html | 页面 |
@@ -103,15 +103,17 @@
![页面截图](screenshots/index.jpg)
**功能说明:** 员工端健康监测模块的主入口,以卡片形式聚合展示四大子模块的关键指标概览,引导用户进入各子模块。
**功能说明:** 员工端健康监测模块的主入口,以卡片形式聚合展示子模块的关键指标概览,引导用户进入各子模块。
**主要UI元素:**
- 健康现状入口卡片(渐变背景,含人群分类图标如A/B/C/D/E、分类标签如"A类·大病人群"、更新时间)
- 穿戴监测大卡片(含设备图标、模块名称、最近同步时间、4个指标预览格:步数/心率/血氧/体温,各带状态徽章)
- 体重监测大卡片(含模块名称、最近测量时间、3个指标预览格:体重/BMI/体脂率,各带状态徽章)
- 环境监测入口卡片(含模块名称、4类状态徽章:室外空气/室内空气/水质/花粉、更新时间)
- 健康排查入口卡片(含模块名称、积分数值、连续签到天数、更新时间)
- 健康排查入口卡片(含模块名称、积分数值、连续记录天数、更新时间)
**交互说明:**
- 点击"健康现状"卡片进入健康现状页面
- 点击"穿戴监测"卡片进入穿戴监测首页
- 点击"体重监测"卡片进入体重监测首页
- 点击"环境监测"卡片进入环境监测首页
@@ -125,29 +127,29 @@
![页面截图](screenshots/health-screening/index.jpg)
**功能说明:** 展示员工健康打卡积分、连续签到天数和积分排行榜,引导员工进行每日打卡
**功能说明:** 展示员工健康记录积分、连续记录天数和积分排行榜,引导员工进行每日健康记录
**主要UI元素:**
- 积分卡片(显示当前积分、连续签到天数)
- "立即打卡"按钮
- "打卡记录"按钮
- 积分排行榜列表
- 积分卡片(显示当前积分、连续记录天数)
- "立即记录"按钮
- "历史记录"按钮
- 积分排行榜列表(含"我的排名"置顶卡片和完整排名列表)
**交互说明:**
- 点击"立即打卡"跳转到每日健康打卡页面
- 点击"打卡记录"跳转到打卡记录页面
- 点击"立即记录"跳转到每日健康记录页面
- 点击"历史记录"跳转到历史记录页面
---
### 3. 每日健康打卡health-screening/questionnaire-fill.html
### 3. 每日健康记录health-screening/questionnaire-fill.html
![页面截图](screenshots/health-screening/questionnaire-fill.jpg)
**功能说明:** 员工填写每日健康自查问卷,包含身体状况、心理状态、生活习惯三个类别共8个问题。
**功能说明:** 员工填写每日健康自查问卷,包含身体状况、心理状态、生活习惯三个类别共8个问题,每个问题标注填写频率
**主要UI元素:**
- 分类标题(身体状况、心理状态、生活习惯)
- 问题卡片(含必填/选填标签)
- 问题卡片(含必填/选填标签、频率标签如"每日"/"每周"/"每月"
- 单选选项
- 提交按钮
- 成功弹窗(显示积分奖励)
@@ -155,6 +157,7 @@
**业务规则:**
- 必填题目必须全部作答才能提交
- 选填题目可跳过
- 每个问题标注填写频率(每日/每周/每月),指示该问题的出现周期
- 提交成功后弹窗显示获得的积分
**交互说明:**
@@ -164,16 +167,16 @@
---
### 4. 打卡记录(health-screening/checkin-record.html
### 4. 历史记录(health-screening/checkin-record.html
![页面截图](screenshots/health-screening/checkin-record.jpg)
**功能说明:** 以日历视图和列表形式展示员工的打卡历史记录
**功能说明:** 以日历视图和列表形式展示员工的健康记录历史
**主要UI元素:**
- 月份日历(已打卡日期标记)
- 月份日历(已记录日期标记)
- 日历展开/收起按钮
- 历史记录列表(打卡时间、日期、积分)
- 历史记录列表(记录时间、日期、积分)
**交互说明:**
- 点击日历导航按钮切换月份
@@ -260,7 +263,7 @@
- 时间维度Tab(日/周/月/年)
- 当前心率大字显示
- 折线趋势图
- 最高/最低/平均值统计卡片
- 最高/最低/平均值统计卡片(单位:次/分钟)
- "查看预警历史"按钮
**交互说明:**
@@ -276,13 +279,14 @@
**功能说明:** 展示心率预警记录列表,支持按年份和月份筛选。
**主要UI元素:**
- 预警统计卡片(本月预警次数)
- 预警记录列表标题(含总条数)
- 筛选按钮
- 预警记录卡片(时间、等级、数值、持续时长、触发原因)
- 预警记录卡片(时间、等级、数值、持续时长、触发原因、右侧阈值范围显示
- 时间筛选弹窗(年份/月份下拉)
**业务规则:**
- 预警等级:严重(红色)、警告(橙色)
- 每条预警卡片右侧显示正常阈值范围参考
**交互说明:**
- 点击筛选按钮打开时间筛选弹窗
@@ -292,7 +296,9 @@
### 10-11. 血氧详情与预警历史
**血氧详情(wearable/blood-oxygen.html** **血氧预警历史(wearable/blood-oxygen-alerts.html** 的页面结构与心率详情/预警历史一致,展示血氧监测数据和预警记录
**血氧详情(wearable/blood-oxygen.html** 展示血氧监测数据,图表采用柱状图形式(正常/警告/危险三色区分),支持日/周/月/年多时间维度查看,底部时间轴滑块可交互选择具体时间点
**血氧预警历史(wearable/blood-oxygen-alerts.html):** 页面结构与心率预警历史一致,展示血氧预警记录列表,每条卡片右侧显示正常阈值范围。
![血氧详情](screenshots/wearable/blood-oxygen.jpg)
![血氧预警](screenshots/wearable/blood-oxygen-alerts.jpg)
@@ -306,13 +312,13 @@
**血压详情(wearable/blood-pressure.html):** 展示收缩压/舒张压双线图表,统计卡片分别显示收缩压和舒张压的最高/最低值。
**血压预警历史(wearable/blood-pressure-alerts.html):** 预警记录卡片同时显示收缩压和舒张压数值。
**血压预警历史(wearable/blood-pressure-alerts.html):** 预警记录卡片同时显示收缩压和舒张压数值,右侧显示正常阈值范围
---
### 14-15. 体温详情与预警历史
**体温详情(wearable/temperature.html****体温预警历史(wearable/temperature-alerts.html** 的页面结构与心率一致。
**体温详情(wearable/temperature.html****体温预警历史(wearable/temperature-alerts.html** 的页面结构与心率一致,预警历史页面每条卡片右侧显示正常阈值范围
![体温详情](screenshots/wearable/temperature.jpg)
![体温预警](screenshots/wearable/temperature-alerts.jpg)
@@ -321,7 +327,7 @@
### 16-17. 压力详情与预警历史
**压力详情(wearable/stress.html****压力预警历史(wearable/stress-alerts.html** 的页面结构与心率一致,额外显示压力等级评估徽章。
**压力详情(wearable/stress.html****压力预警历史(wearable/stress-alerts.html** 的页面结构与心率一致,额外显示压力等级评估徽章,预警历史页面每条卡片右侧显示正常阈值范围
![压力详情](screenshots/wearable/stress.jpg)
![压力预警](screenshots/wearable/stress-alerts.jpg)
@@ -489,7 +495,7 @@
**主要UI元素:**
- 监测类型Tab(室外空气、室内空气、水质、花粉)
- 地图视图(设备标记、图例、搜索框、定位按钮)
- 地图视图(设备标记、图例、搜索框、定位按钮、缩放按钮+/-
- 列表视图(含筛选和搜索)
- 底部弹窗(设备简要信息)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@@ -79,6 +79,32 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
z-index: 10;
}
/* 地图缩放按钮 */
.map-zoom-controls {
position: absolute;
right: 12px;
bottom: 130px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 10;
}
.map-zoom-btn {
width: 36px; height: 36px;
background: #fff;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
display: flex; align-items: center; justify-content: center;
cursor: pointer;
font-size: 20px;
font-weight: 600;
color: #666;
user-select: none;
}
.map-zoom-btn:active {
transform: scale(0.95);
}
/* 图例 */
.map-legend {
position: absolute;
@@ -313,6 +339,18 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- 设备标记容器 -->
<div id="markerContainer"></div>
<!-- 缩放按钮 -->
<div class="map-zoom-controls">
<div class="map-zoom-btn">+</div>
<div class="map-zoom-btn"></div>
</div>
<!-- 缩放按钮 -->
<div class="map-zoom-controls">
<div class="map-zoom-btn">+</div>
<div class="map-zoom-btn"></div>
</div>
<!-- 定位按钮 -->
<div class="map-locate-btn">
<svg width="18" height="18" fill="none"><circle cx="9" cy="9" r="3" fill="#1aad8d"/><path d="M9 1v3M9 15v3M1 9h3M15 9h3" stroke="#1aad8d" stroke-width="1.5" stroke-linecap="round"/></svg>
@@ -64,7 +64,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="nav-back" onclick="location.href='index.html'">
<svg width="12" height="20" fill="none"><path d="M10 2L2 10l8 8" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="nav-title">打卡记录</div>
<div class="nav-title">历史记录</div>
</div>
<div class="scroll-content">
<!-- 日历 -->
@@ -91,7 +91,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<!-- 历史记录 -->
<div class="history-section">
<div class="history-title">历史记录</div>
<!-- <div class="history-title">历史记录</div>-->
<div class="history-list">
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
@@ -80,16 +80,16 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="score-card__num">1,280</span>
<span class="score-card__unit">积分</span>
</div>
<div class="score-card__streak">🔥 连续签到 <strong>7</strong></div>
<div class="score-card__streak">🔥 连续记录 <strong>7</strong></div>
</div>
<div class="score-card__actions">
<button class="btn-checkin" onclick="location.href='questionnaire-fill.html'">
<svg width="18" height="18" fill="none"><path d="M9 1v16M1 9h16" stroke="#667eea" stroke-width="2.5" stroke-linecap="round"/></svg>
立即打卡
立即记录
</button>
<button class="btn-record" onclick="location.href='checkin-record.html'">
<svg width="16" height="16" fill="none"><path d="M3 4h10M3 8h10M3 12h7" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>
打卡记录
历史记录
</button>
</div>
</div>
@@ -24,6 +24,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.question-card { background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 12px; }
.question-card__header { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
.question-card__title { font-size: 14px; font-weight: 600; color: #1a1a1a; line-height: 1.5; flex: 1; }
.question-card__freq { font-size: 11px; color: #999; font-weight: 400; }
.tag-required { font-size: 10px; color: #ff4d4f; background: #fff2f0; padding: 1px 6px; border-radius: 3px; flex-shrink: 0; border: 1px solid #ffccc7; }
.tag-optional { font-size: 10px; color: #999; background: #f5f5f5; padding: 1px 6px; border-radius: 3px; flex-shrink: 0; }
.question-card__options { display: flex; flex-direction: column; gap: 8px; }
@@ -76,7 +77,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="category-title">身体状况</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">1. 今天您的整体身体感觉如何?</div>
<div class="question-card__title">1. 今天您的整体身体感觉如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
@@ -88,7 +89,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">2. 昨晚的睡眠质量如何?</div>
<div class="question-card__title">2. 昨晚的睡眠质量如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
@@ -100,7 +101,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">3. 是否有头痛、头晕等不适症状?</div>
<div class="question-card__title">3. 是否有头痛、头晕等不适症状?<span class="question-card__freq">(每周)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
@@ -114,7 +115,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="category-title">心理状态</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">4. 今天的情绪状态如何?</div>
<div class="question-card__title">4. 今天的情绪状态如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
@@ -126,7 +127,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">5. 近期是否感到工作压力较大?</div>
<div class="question-card__title">5. 近期是否感到工作压力较大?<span class="question-card__freq">(每月)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
@@ -140,7 +141,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="category-title">生活习惯</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">6. 昨天是否进行了运动锻炼?</div>
<div class="question-card__title">6. 昨天是否进行了运动锻炼?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
@@ -152,7 +153,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">7. 昨天的饮食是否规律?</div>
<div class="question-card__title">7. 昨天的饮食是否规律?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
@@ -164,7 +165,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">8. 昨天饮水量是否充足(8杯以上)?</div>
<div class="question-card__title">8. 昨天饮水量是否充足(8杯以上)?<span class="question-card__freq">(每周)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康现状 - 员工端 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f5f7fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; background: #fff; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #333; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; }
.nav-back { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.nav-title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; color: #1a1a1a; margin-right: 28px; }
.scroll-content { flex: 1; overflow-y: auto; padding: 0; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 仪表盘区域 */
.gauge-section { background: linear-gradient(180deg, #f0f0ff 0%, #f5f7fa 100%); padding: 32px 16px 20px; display: flex; flex-direction: column; align-items: center; }
.gauge-wrap { position: relative; width: 200px; height: 120px; margin-bottom: 12px; }
.gauge-wrap svg { width: 100%; height: 100%; }
.gauge-current { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); text-align: center; }
.gauge-current__letter { font-size: 36px; font-weight: 800; color: #ff4d4f; }
.gauge-current__label { font-size: 11px; color: #999; margin-top: 2px; }
.gauge-badge { position: absolute; top: 10px; right: -10px; width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800; color: #fff; }
.gauge-badge--A { background: #ff4d4f; }
/* 人群标签 */
.crowd-tag { display: inline-flex; align-items: center; padding: 6px 16px; border-radius: 20px; font-size: 15px; font-weight: 700; margin-bottom: 8px; }
.crowd-tag--A { color: #ff4d4f; background: #fff1f0; border: 1px solid #ffccc7; }
.crowd-tag--B { color: #fa8c16; background: #fff7e6; border: 1px solid #ffd591; }
.crowd-tag--C { color: #faad14; background: #fffbe6; border: 1px solid #ffe58f; }
.crowd-tag--D { color: #1890ff; background: #e6f7ff; border: 1px solid #91d5ff; }
.crowd-tag--E { color: #52c41a; background: #f6ffed; border: 1px solid #b7eb8f; }
.update-time { font-size: 12px; color: #999; }
/* 说明区 */
.note-section { padding: 16px; }
.note-card { background: #fff; border-radius: 12px; padding: 14px 16px; }
.note-card__title { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 6px; display: flex; align-items: center; gap: 6px; }
.note-card__text { font-size: 12px; color: #666; line-height: 1.8; }
/* 信息卡片 */
.info-section { padding: 0 16px 16px; }
.info-section__title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin-bottom: 10px; }
.info-card { background: #fff; border-radius: 12px; overflow: hidden; margin-bottom: 12px; }
.info-row { display: flex; align-items: flex-start; padding: 12px 16px; border-bottom: 1px solid #f5f5f5; }
.info-row:last-child { border-bottom: none; }
.info-row__label { width: 80px; font-size: 13px; color: #999; flex-shrink: 0; }
.info-row__value { flex: 1; font-size: 13px; color: #1a1a1a; text-align: right; }
.info-row__value--highlight { color: #ff4d4f; font-weight: 600; }
.info-row__value--primary { color: #1890ff; font-weight: 600; }
/* 分类信息 */
.category-header { display: flex; align-items: center; gap: 6px; padding: 14px 16px 10px; font-size: 14px; font-weight: 600; color: #1a1a1a; }
.category-header__bar { width: 3px; height: 16px; border-radius: 2px; background: #722ed1; }
.bottom-safe { height: 34px; flex-shrink: 0; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<span class="icons">
<svg width="16" height="16" fill="none"><rect x="1" y="4" width="3" height="8" rx="1" fill="#333"/><rect x="5" y="3" width="3" height="9" rx="1" fill="#333"/><rect x="9" y="1" width="3" height="11" rx="1" fill="#333"/><rect x="13" y="0" width="3" height="12" rx="1" fill="#333" opacity=".3"/></svg>
<svg width="16" height="16" fill="none"><path d="M8 3C5.5 3 3.2 4 1.5 5.5l1.4 1.4C4.3 5.5 6.1 5 8 5s3.7.5 5.1 1.9l1.4-1.4C12.8 4 10.5 3 8 3zm0 4c-1.4 0-2.6.5-3.5 1.3L6 9.8c.6-.5 1.3-.8 2-.8s1.4.3 2 .8l1.5-1.5C10.6 7.5 9.4 7 8 7zm0 4c-.6 0-1 .4-1 1s.4 1 1 1 1-.4 1-1-.4-1-1-1z" fill="#333"/></svg>
<svg width="22" height="12" fill="none"><rect x=".5" y=".5" width="19" height="11" rx="2" stroke="#333"/><rect x="20" y="3.5" width="1.5" height="5" rx=".75" fill="#333"/><rect x="2" y="2" width="14" height="8" rx="1" fill="#333"/></svg>
</span>
</div>
<div class="nav-bar">
<div class="nav-back" onclick="history.back()">
<svg width="12" height="20" fill="none"><path d="M10 2L2 10l8 8" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="nav-title">健康现状</div>
</div>
<div class="scroll-content">
<!-- 仪表盘 -->
<div class="gauge-section">
<div class="gauge-wrap">
<svg viewBox="0 0 200 120" fill="none">
<!-- 背景弧线 E-D-C-B-A -->
<path d="M20 110 A80 80 0 0 1 52 42" stroke="#e8e8e8" stroke-width="12" stroke-linecap="round"/>
<path d="M56 38 A80 80 0 0 1 90 18" stroke="#e8e8e8" stroke-width="12" stroke-linecap="round"/>
<path d="M94 16 A80 80 0 0 1 110 16" stroke="#e8e8e8" stroke-width="12" stroke-linecap="round"/>
<path d="M114 18 A80 80 0 0 1 148 38" stroke="#e8e8e8" stroke-width="12" stroke-linecap="round"/>
<path d="M152 42 A80 80 0 0 1 180 110" stroke="#ff4d4f" stroke-width="12" stroke-linecap="round"/>
<!-- 刻度标签 -->
<text x="14" y="115" font-size="12" fill="#ccc" font-weight="600">E</text>
<text x="42" y="38" font-size="12" fill="#ccc" font-weight="600">D</text>
<text x="95" y="12" font-size="12" fill="#ccc" font-weight="600">C</text>
<text x="150" y="38" font-size="12" fill="#ccc" font-weight="600">B</text>
<text x="180" y="115" font-size="12" fill="#ccc" font-weight="600">A</text>
</svg>
<div class="gauge-current">
<div class="gauge-current__letter">A</div>
<div class="gauge-current__label">当前属于</div>
</div>
<div class="gauge-badge gauge-badge--A">A</div>
</div>
<div class="crowd-tag crowd-tag--A">A类人群</div>
<div class="update-time">更新时间:2026-03-26 14:36</div>
</div>
<!-- 说明 -->
<div class="note-section">
<div class="note-card">
<div class="note-card__title">💬 说明</div>
<div class="note-card__text">现状评估的人群分类按A、B、C、D、E五个分类进行分类。<br>E类:健康人群 | D类:风险人群 | C类:异常人群 | B类:慢病人群 | A类:大病人群</div>
</div>
</div>
<!-- 住院信息 -->
<div class="info-section">
<div class="info-section__title">住院信息</div>
<div class="info-card">
<div class="info-row">
<span class="info-row__label">住院状态</span>
<span class="info-row__value"><span class="info-row__value--highlight">治疗中</span><br><span style="font-size:12px;color:#999;">宁夏宝石花医院</span></span>
</div>
<div class="info-row">
<span class="info-row__label">住院时间</span>
<span class="info-row__value">2026-03-26</span>
</div>
<div class="info-row">
<span class="info-row__label">住院次数</span>
<span class="info-row__value">4</span>
</div>
<div class="info-row">
<span class="info-row__label">联系人</span>
<span class="info-row__value">张智博</span>
</div>
<div class="info-row">
<span class="info-row__label">联系电话</span>
<span class="info-row__value info-row__value--primary">15735365522</span>
</div>
</div>
</div>
<!-- 分类信息 -->
<div class="info-section">
<div class="info-section__title">分类信息(大病信息)</div>
<div class="info-card">
<div class="category-header"><span class="category-header__bar"></span>肺癌</div>
<div class="info-row">
<span class="info-row__label">确诊年份</span>
<span class="info-row__value">2022年</span>
</div>
<div class="info-row">
<span class="info-row__label">确诊医院</span>
<span class="info-row__value">宁夏宝石花医院</span>
</div>
<div class="info-row">
<span class="info-row__label">大病状态</span>
<span class="info-row__value">可控</span>
</div>
<div class="info-row">
<span class="info-row__label">治疗情况</span>
<span class="info-row__value">目前属于血糖偏高,可控阶段,按时服药,定期复诊。</span>
</div>
</div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
</body>
</html>
+30 -2
View File
@@ -74,7 +74,25 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.entry-arrow { width: 16px; height: 16px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.entry-time-tag { font-size: 10px; color: #bbb; margin-top: 3px; }
/* 底部安全区 */
/* 健康现状卡片 */
.health-status-card { background: linear-gradient(135deg, #f0f5ff 0%, #e6fffb 100%); border-radius: 16px; padding: 14px 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 14px; cursor: pointer; transition: transform 0.15s; text-decoration: none; }
.health-status-card:active { transform: scale(0.98); }
.health-status__icon { width: 44px; height: 44px; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800; flex-shrink: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.health-status__icon--A { color: #ff4d4f; border: 2px solid #ff4d4f; }
.health-status__icon--B { color: #fa8c16; border: 2px solid #fa8c16; }
.health-status__icon--C { color: #faad14; border: 2px solid #faad14; }
.health-status__icon--D { color: #1890ff; border: 2px solid #1890ff; }
.health-status__icon--E { color: #52c41a; border: 2px solid #52c41a; }
.health-status__info { flex: 1; min-width: 0; }
.health-status__title { font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 3px; }
.health-status__desc { font-size: 12px; color: #666; }
.health-status__tag { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 600; }
.health-status__tag--A { color: #ff4d4f; background: #fff1f0; }
.health-status__tag--B { color: #fa8c16; background: #fff7e6; }
.health-status__tag--C { color: #faad14; background: #fffbe6; }
.health-status__tag--D { color: #1890ff; background: #e6f7ff; }
.health-status__tag--E { color: #52c41a; background: #f6ffed; }
.health-status__arrow { width: 16px; height: 16px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.bottom-safe { height: 20px; flex-shrink: 0; }
</style>
</head>
@@ -97,6 +115,16 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="scroll-content">
<!-- 健康现状 -->
<a class="health-status-card" href="health-status.html">
<div class="health-status__icon health-status__icon--A">A</div>
<div class="health-status__info">
<div class="health-status__title">健康现状 <span class="health-status__tag health-status__tag--A">A类·大病人群</span></div>
<div class="health-status__desc">更新时间:2026-05-26 10:30</div>
</div>
<div class="health-status__arrow"><svg width="7" height="12" fill="none"><path d="M1 1l5 5-5 5" stroke="#ccc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</a>
<!-- 穿戴监测 大卡片 -->
<a class="core-card" href="wearable/index.html">
@@ -195,7 +223,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="entry-name">健康排查</div>
<div class="entry-metrics">
<span class="entry-metric">积分 <strong>1,280</strong></span>
<span class="entry-metric">连续签到 <strong>7</strong></span>
<span class="entry-metric">连续记录 <strong>7</strong></span>
</div>
<div class="entry-time-tag">05-25 08:00 更新</div>
</div>
@@ -205,6 +205,24 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
@@ -401,41 +419,6 @@ body {
<!-- 滚动内容区 -->
<div class="scroll-content">
<!-- 预警统计卡片 -->
<div class="stats-card">
<div class="stats-card__left">
<div class="stats-card__label">本月预警</div>
<div class="stats-card__count-row">
<span class="stats-card__count">0</span>
<span class="stats-card__unit"></span>
</div>
</div>
<div class="stats-card__right">
<!-- 血氧水滴图标,绿色背景表示正常 -->
<div class="stats-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 2C12 2 5 9.5 5 14a7 7 0 0 0 14 0C19 9.5 12 2 12 2z" fill="#52c41a" opacity="0.85"/>
<path d="M9 13.5a3 3 0 0 0 3 3" stroke="#fff" stroke-width="1.5" stroke-linecap="round" opacity="0.7"/>
</svg>
</div>
<!-- <div class="stats-card__recent-label">最近预警时间</div>-->
<div class="stats-card__unit">无预警,请继续保持</div>
</div>
</div>
<!-- 空状态:本月无预警记录 -->
<!-- <div class="empty-state">-->
<!-- &lt;!&ndash; 大图标:绿色盾牌表示健康安全 &ndash;&gt;-->
<!-- <div class="empty-state__icon">-->
<!-- <svg width="40" height="40" viewBox="0 0 24 24" fill="none">-->
<!-- <path d="M12 2L3 7v5c0 5.25 3.75 10.15 9 11.35C17.25 22.15 21 17.25 21 12V7L12 2z" fill="#52c41a" opacity="0.15" stroke="#52c41a" stroke-width="1.5" stroke-linejoin="round"/>-->
<!-- &lt;!&ndash; 对勾 &ndash;&gt;-->
<!-- <polyline points="9 12 11 14 15 10" stroke="#52c41a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>-->
<!-- </svg>-->
<!-- </div>-->
<!-- <div class="empty-state__text">暂无预警记录,请继续保持</div>-->
<!-- </div>-->
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 1 条)</div>
@@ -455,36 +438,20 @@ body {
<div class="alert-card__dot alert-card__dot--critical"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 严重等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-03-08 03:22</span>
<span class="alert-card__badge alert-card__badge--critical">严重</span>
</div>
<!-- 血氧监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">93</span>
<span class="alert-card__value-unit">%</span>
</div>
<!-- 持续时长 + 血氧标签 -->
<div class="alert-card__detail-row">
<!-- 时钟图标 + 持续时长 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 8 分钟</span>
</div>
<!-- 血氧水滴图标 + 标签 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M12 2C12 2 6 9 6 14a6 6 0 0 0 12 0C18 9 12 2 12 2z"/>
</svg>
<span>血氧</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:血氧低于 95% 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>100%</span></div>
<div class="alert-card__threshold-item">下限 <span>95%</span></div>
</div>
</div>
@@ -37,17 +37,27 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.chart-wrap { position: relative; background: #fff; padding: 6px 16px 0; }
.chart-box { position: relative; height: 160px; }
.chart-svg { width: 100%; height: 100%; display: block; }
.chart-grid line { stroke: #ececec; stroke-width: 1; }
.chart-path { fill: none; stroke: var(--theme); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart-dot { fill: var(--theme); }
.chart-grid line { stroke: #f0f0f0; stroke-width: 0.5; stroke-dasharray: none; }
.chart-bar { rx: 2; }
.chart-bar--normal { fill: #95de64; }
.chart-bar--warn { fill: #fa8c16; }
.chart-bar--danger { fill: #ff7875; }
.y-axis-labels { position: absolute; top: 6px; right: 0; width: 56px; height: 208px; pointer-events: none; }
.y-axis-label { position: absolute; right: 0; transform: translateY(50%); font-size: 12px; color: #666; }
.x-axis { display: grid; gap: 0; padding: 8px 14px 0 0; font-size: 12px; color: #666; }
.x-axis span { text-align: center; white-space: nowrap; }
.x-axis span { text-align: center; white-space: nowrap; cursor: pointer; transition: all 0.15s; }
.x-axis span.active { color: #1a1a1a; font-weight: 700; font-size: 14px; }
.x-axis--dense { font-size: 10px; }
.timeline-knob-row { position: relative; margin: 0; height: 24px; }
.timeline-track { position: absolute; left: 0; right: 0; top: 10px; height: 1px; background: #e8e8e8; }
.timeline-knob { position: absolute; right: 16px; top: -4px; width: 24px; height: 24px; border-radius: 50%; background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.12); }
.x-axis--dense span.active { font-size: 12px; }
.timeline-knob-row { position: relative; margin: 0; height: 32px; }
.timeline-track { position: absolute; left: 0; right: 0; top: 14px; height: 2px; background: #e8e8e8; border-radius: 1px; }
.timeline-knob { position: absolute; top: 4px; width: 28px; height: 28px; border-radius: 50%; background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.15); cursor: grab; transition: left 0.15s; display: flex; align-items: center; justify-content: center; }
.timeline-knob::after { content: ''; width: 10px; height: 10px; border-radius: 50%; background: #e0e0e0; }
.timeline-knob:active { cursor: grabbing; box-shadow: 0 3px 14px rgba(0,0,0,0.2); }
/* 选中柱子指示线 */
.chart-indicator { stroke: #ffaaa5; stroke-width: 1; stroke-dasharray: 3 2; }
.chart-bar--selected { opacity: 1; }
.chart-bar--dimmed { opacity: 0.5; }
.card { margin: 8px 12px 0; background: #fff; border-radius: 22px; padding: 14px 12px; display: flex; gap: 0; }
.metric-row { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 6px 6px; }
@@ -105,15 +115,15 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="chart-box">
<svg class="chart-svg" id="chart" viewBox="0 0 358 258" preserveAspectRatio="none">
<g class="chart-grid" id="chartGrid"></g>
<path id="line" class="chart-path"></path>
<circle id="dot" class="chart-dot" r="3.2"></circle>
<g id="chartBars"></g>
<line id="chartIndicator" class="chart-indicator" x1="0" y1="0" x2="0" y2="258" style="display:none;"/>
</svg>
<div class="y-axis-labels" id="yAxisLabels"></div>
</div>
<div class="x-axis" id="xAxis"></div>
<div class="timeline-knob-row">
<div class="timeline-knob-row" id="timelineRow">
<div class="timeline-track"></div>
<div class="timeline-knob"></div>
<div class="timeline-knob" id="timelineKnob"></div>
</div>
</div>
</div>
@@ -161,56 +171,56 @@ var dataMap = {
rangeText: '00:00-23:59',
unit: '%',
decimals: 0,
points: [97,97,96,96,95,96,96,97,98,98,97,97,98,99,98,97,97,96,97,98,98,97,97,97],
points: [97,97,96,96,95,96,88,96,97,98,98,97,89,98,99,98,97,97,96,97,88,98,97,97],
totalMode: 'last',
yTicks: [100, 98, 96, 94],
yTicks: [100, 90, 80],
xLabels: ['0:00','6:00','12:00','18:00','24:00'],
status: '正常',
max: { v:99, hint:'10:10' },
min: { v:95, hint:'02:50' },
avg: { v:97, hint:'全天' }
min: { v:88, hint:'06:30' },
avg: { v:96, hint:'全天' }
},
week: {
dateText: '4月27日-5月3日',
rangeText: '周一-周日',
unit: '%',
decimals: 0,
points: [97,96,98,97,97,98,98],
totalMode: 'avg',
yTicks: [100, 98, 96, 94],
ranges: [{min:92,max:98},{min:90,max:97},{min:93,max:99},{min:91,max:98},{min:86,max:95},{min:93,max:99},{min:94,max:98}],
totalMode: 'range',
yTicks: [100, 90, 80],
xLabels: ['周一','周二','周三','周四','周五','周六','周日'],
status: '正常',
max: { v:98, hint:'周三' },
min: { v:96, hint:'周' },
avg: { v:97, hint:'本周' }
max: { v:99, hint:'周三' },
min: { v:86, hint:'周' },
avg: { v:96, hint:'本周' }
},
month: {
dateText: '2026年4月',
rangeText: '4月1日-4月30日',
unit: '%',
decimals: 0,
points: [97,97,96,96,95,96,96,97,98,98,97,97,98,99,98,97,97,96,97,98,98,97,97,97,97,97,96,96,95,96],
totalMode: 'avg',
yTicks: [100, 98, 96, 94],
ranges: [{min:92,max:98},{min:91,max:97},{min:90,max:98},{min:88,max:96},{min:93,max:99},{min:91,max:98},{min:92,max:97},{min:93,max:99},{min:94,max:98},{min:90,max:97},{min:91,max:98},{min:92,max:99},{min:93,max:98},{min:94,max:99},{min:91,max:97},{min:90,max:96},{min:86,max:94},{min:91,max:97},{min:92,max:98},{min:93,max:99},{min:94,max:98},{min:92,max:97},{min:91,max:98},{min:93,max:98},{min:92,max:97},{min:91,max:98},{min:90,max:97},{min:89,max:96},{min:87,max:95},{min:91,max:97}],
totalMode: 'range',
yTicks: [100, 90, 80],
xLabels: ['1日','7日','14日','21日','30日'],
status: '正常',
max: { v:99, hint:'04-14' },
min: { v:95, hint:'04-05' },
avg: { v:97, hint:'本月' }
min: { v:86, hint:'04-17' },
avg: { v:96, hint:'本月' }
},
year: {
dateText: '2026年',
rangeText: '1月-12月',
unit: '%',
decimals: 0,
points: [97,97,96,96,97,96,96,97,98,97,96,97],
totalMode: 'avg',
yTicks: [100, 98, 96, 94],
ranges: [{min:91,max:98},{min:90,max:97},{min:89,max:97},{min:86,max:99},{min:88,max:98},{min:91,max:97},{min:92,max:98},{min:93,max:99},{min:91,max:98},{min:92,max:97},{min:90,max:98},{min:91,max:97}],
totalMode: 'range',
yTicks: [100, 90, 80],
xLabels: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
status: '正常',
max: { v:98, hint:'01月' },
min: { v:96, hint:'03月' },
avg: { v:97, hint:'全年' }
max: { v:99, hint:'04月' },
min: { v:86, hint:'04月' },
avg: { v:96, hint:'全年' }
}
};
@@ -234,43 +244,72 @@ function formatValue(val, decimals) {
return String(val);
}
function renderChart(cfg) {
var chartWidth = 330;
function renderChart(cfg, mode) {
var chartWidth = 310;
var chartHeight = 208;
var startY = 22;
var stepX = chartWidth / (cfg.points.length - 1 || 1);
var maxY = cfg.yTicks[0];
var yMin = cfg.yTicks[cfg.yTicks.length - 1];
var yMax = cfg.yTicks[0];
var grid = document.getElementById('chartGrid');
var yAxis = document.getElementById('yAxisLabels');
var xAxis = document.getElementById('xAxis');
var lineEl = document.getElementById('line');
var dotEl = document.getElementById('dot');
var barsGroup = document.getElementById('chartBars');
grid.innerHTML = '';
cfg.yTicks.forEach(function(_, index) {
var y = startY + (chartHeight / (cfg.yTicks.length - 1)) * index;
grid.innerHTML += '<line x1="0" y1="' + y.toFixed(1) + '" x2="330" y2="' + y.toFixed(1) + '"></line>';
grid.innerHTML += '<line x1="0" y1="' + y.toFixed(1) + '" x2="' + chartWidth + '" y2="' + y.toFixed(1) + '"></line>';
});
yAxis.innerHTML = cfg.yTicks.map(function(tick, index) {
return '<span class="y-axis-label" style="top:' + (index * 52) + 'px">' + formatValue(tick, 0) + '</span>';
return '<span class="y-axis-label" style="top:' + (index * 52) + 'px">' + tick + '%</span>';
}).join('');
var pts = cfg.points.map(function(v, i) {
var x = i * stepX;
var y = startY + chartHeight - (v / maxY) * chartHeight;
return { x: x, y: y };
});
var barsHtml = '';
var threshold = 90;
var thresholdY = startY + ((yMax - threshold) / (yMax - yMin)) * chartHeight;
var d = pts.map(function(p, index) {
return (index === 0 ? 'M' : 'L') + p.x.toFixed(1) + ',' + p.y.toFixed(1);
}).join(' ');
lineEl.setAttribute('d', d);
var last = pts[pts.length - 1];
dotEl.setAttribute('cx', last.x.toFixed(1));
dotEl.setAttribute('cy', last.y.toFixed(1));
if (mode === 'day') {
// 日视图:单值柱子从顶部垂下
var barGap = 2;
var barWidth = Math.max(3, (chartWidth / cfg.points.length) - barGap);
var totalBarSpace = barWidth + barGap;
var offsetX = (chartWidth - cfg.points.length * totalBarSpace) / 2;
cfg.points.forEach(function(v, i) {
var x = offsetX + i * totalBarSpace;
var valY = startY + ((yMax - v) / (yMax - yMin)) * chartHeight;
var h = (startY + chartHeight) - valY;
var colorClass = v < 90 ? 'chart-bar--warn' : 'chart-bar--normal';
barsHtml += '<rect class="chart-bar ' + colorClass + '" x="' + x.toFixed(1) + '" y="' + valY.toFixed(1) + '" width="' + barWidth.toFixed(1) + '" height="' + h.toFixed(1) + '" rx="1.5"/>';
});
} else {
// 周/月/年:范围柱状图,每根柱子从min到max,90分界线分色
var data = cfg.ranges;
var barGap = mode === 'month' ? 2 : 6;
var barWidth = Math.max(4, (chartWidth / data.length) - barGap);
var totalBarSpace = barWidth + barGap;
var offsetX = (chartWidth - data.length * totalBarSpace) / 2;
data.forEach(function(r, i) {
var x = offsetX + i * totalBarSpace;
var topY = startY + ((yMax - r.max) / (yMax - yMin)) * chartHeight;
var botY = startY + ((yMax - r.min) / (yMax - yMin)) * chartHeight;
if (r.max <= threshold) {
// 全部在90以下,整根橙色
barsHtml += '<rect class="chart-bar chart-bar--warn" x="' + x.toFixed(1) + '" y="' + topY.toFixed(1) + '" width="' + barWidth.toFixed(1) + '" height="' + (botY - topY).toFixed(1) + '" rx="2"/>';
} else if (r.min >= threshold) {
// 全部在90以上,整根绿色
barsHtml += '<rect class="chart-bar chart-bar--normal" x="' + x.toFixed(1) + '" y="' + topY.toFixed(1) + '" width="' + barWidth.toFixed(1) + '" height="' + (botY - topY).toFixed(1) + '" rx="2"/>';
} else {
// 跨越90线:上半绿色,下半橙色
var greenH = thresholdY - topY;
var orangeH = botY - thresholdY;
barsHtml += '<rect class="chart-bar chart-bar--normal" x="' + x.toFixed(1) + '" y="' + topY.toFixed(1) + '" width="' + barWidth.toFixed(1) + '" height="' + greenH.toFixed(1) + '" rx="2"/>';
barsHtml += '<rect class="chart-bar chart-bar--warn" x="' + x.toFixed(1) + '" y="' + thresholdY.toFixed(1) + '" width="' + barWidth.toFixed(1) + '" height="' + orangeH.toFixed(1) + '" rx="2"/>';
}
});
}
barsGroup.innerHTML = barsHtml;
xAxis.className = 'x-axis' + (cfg.xLabels.length > 7 ? ' x-axis--dense' : '');
xAxis.style.gridTemplateColumns = 'repeat(' + cfg.xLabels.length + ', 1fr)';
@@ -281,11 +320,18 @@ function renderChart(cfg) {
function renderMode(mode) {
var cfg = dataMap[mode];
var value = getValue(cfg);
document.getElementById('dateText').textContent = cfg.dateText;
document.getElementById('rangeText').textContent = cfg.rangeText;
document.getElementById('mainValue').textContent = formatValue(value, cfg.decimals);
document.getElementById('mainUnit').textContent = cfg.unit;
if (cfg.totalMode === 'range') {
// 周/月/年显示范围
document.getElementById('mainValue').textContent = cfg.min.v + '% - ' + cfg.max.v + '%';
document.getElementById('mainUnit').textContent = '';
} else {
var value = getValue(cfg);
document.getElementById('mainValue').textContent = formatValue(value, cfg.decimals);
document.getElementById('mainUnit').textContent = cfg.unit;
}
document.getElementById('statusBadge').textContent = cfg.status;
document.getElementById('maxValue').textContent = formatValue(cfg.max.v, cfg.decimals);
@@ -295,14 +341,153 @@ function renderMode(mode) {
document.getElementById('avgValue').textContent = formatValue(cfg.avg.v, cfg.decimals);
document.getElementById('avgHint').textContent = cfg.avg.hint;
renderChart(cfg);
renderChart(cfg, mode);
// 初始化选中状态
selectBar(currentMode === 'day' ? cfg.points.length - 1 : cfg.ranges.length - 1);
}
var currentMode = 'day';
var selectedIndex = -1;
// 选中某根柱子
function selectBar(idx) {
var cfg = dataMap[currentMode];
var count = currentMode === 'day' ? cfg.points.length : cfg.ranges.length;
if (idx < 0) idx = 0;
if (idx >= count) idx = count - 1;
selectedIndex = idx;
// 更新柱子高亮
var bars = document.querySelectorAll('#chartBars rect');
bars.forEach(function(b) { b.classList.remove('chart-bar--dimmed', 'chart-bar--selected'); });
// 对于范围模式每根柱子可能有2个rect
var barRects = [];
if (currentMode === 'day') {
barRects = [bars[idx]];
} else {
// 范围模式:找到对应index的rect(可能1或2个)
var data = cfg.ranges;
var rectIdx = 0;
for (var i = 0; i < data.length; i++) {
var rectsForBar = (data[i].min >= 90 || data[i].max <= 90) ? 1 : 2;
if (i === idx) {
for (var r = 0; r < rectsForBar; r++) barRects.push(bars[rectIdx + r]);
break;
}
rectIdx += rectsForBar;
}
}
bars.forEach(function(b) { if (barRects.indexOf(b) === -1) b.classList.add('chart-bar--dimmed'); });
barRects.forEach(function(b) { if (b) b.classList.add('chart-bar--selected'); });
// 指示线
var indicator = document.getElementById('chartIndicator');
var chartWidth = 310;
if (currentMode === 'day') {
var barGap = 2;
var barWidth = Math.max(3, (chartWidth / cfg.points.length) - barGap);
var totalBarSpace = barWidth + barGap;
var offsetX = (chartWidth - cfg.points.length * totalBarSpace) / 2;
var cx = offsetX + idx * totalBarSpace + barWidth / 2;
} else {
var data = cfg.ranges;
var barGap2 = currentMode === 'month' ? 2 : 6;
var barWidth2 = Math.max(4, (chartWidth / data.length) - barGap2);
var totalBarSpace2 = barWidth2 + barGap2;
var offsetX2 = (chartWidth - data.length * totalBarSpace2) / 2;
var cx = offsetX2 + idx * totalBarSpace2 + barWidth2 / 2;
}
indicator.setAttribute('x1', cx.toFixed(1));
indicator.setAttribute('x2', cx.toFixed(1));
indicator.style.display = '';
// 更新滚轮位置
var knob = document.getElementById('timelineKnob');
var row = document.getElementById('timelineRow');
var rowWidth = row.offsetWidth - 28;
var knobLeft = (idx / (count - 1)) * rowWidth;
knob.style.left = knobLeft.toFixed(1) + 'px';
// 更新X轴高亮
var xSpans = document.querySelectorAll('#xAxis span');
xSpans.forEach(function(s) { s.classList.remove('active'); });
// 映射选中index到xLabel index
var xCount = cfg.xLabels.length;
var mappedX = Math.round(idx / (count - 1) * (xCount - 1));
if (xSpans[mappedX]) xSpans[mappedX].classList.add('active');
// 更新顶部rangeText
updateSummaryForSelection(cfg, idx);
}
function updateSummaryForSelection(cfg, idx) {
if (currentMode === 'day') {
var hour = Math.floor(idx * 24 / cfg.points.length);
var hourEnd = Math.floor((idx + 1) * 24 / cfg.points.length);
document.getElementById('rangeText').textContent = hour + ':00 - ' + hourEnd + ':00';
document.getElementById('mainValue').textContent = cfg.points[idx];
document.getElementById('mainUnit').textContent = '%';
} else {
var r = cfg.ranges[idx];
document.getElementById('mainValue').textContent = r.min + '% - ' + r.max + '%';
document.getElementById('mainUnit').textContent = '';
if (currentMode === 'week') {
var days = ['周一','周二','周三','周四','周五','周六','周日'];
document.getElementById('rangeText').textContent = days[idx] || '';
} else if (currentMode === 'month') {
document.getElementById('rangeText').textContent = (idx + 1) + '日';
} else if (currentMode === 'year') {
document.getElementById('rangeText').textContent = (idx + 1) + '月';
}
}
}
// 滚轮拖动
(function() {
var knob = document.getElementById('timelineKnob');
var row = document.getElementById('timelineRow');
var dragging = false;
function getIndexFromX(clientX) {
var rect = row.getBoundingClientRect();
var x = clientX - rect.left - 14;
var width = rect.width - 28;
var ratio = Math.max(0, Math.min(1, x / width));
var cfg = dataMap[currentMode];
var count = currentMode === 'day' ? cfg.points.length : cfg.ranges.length;
return Math.round(ratio * (count - 1));
}
knob.addEventListener('mousedown', function(e) { dragging = true; e.preventDefault(); });
knob.addEventListener('touchstart', function(e) { dragging = true; });
document.addEventListener('mousemove', function(e) { if (dragging) selectBar(getIndexFromX(e.clientX)); });
document.addEventListener('touchmove', function(e) { if (dragging) selectBar(getIndexFromX(e.touches[0].clientX)); });
document.addEventListener('mouseup', function() { dragging = false; });
document.addEventListener('touchend', function() { dragging = false; });
// 点击轨道也可选择
row.addEventListener('click', function(e) { selectBar(getIndexFromX(e.clientX)); });
})();
// X轴标签点击选择
document.getElementById('xAxis').addEventListener('click', function(e) {
var span = e.target.closest('span');
if (!span) return;
var spans = Array.from(document.querySelectorAll('#xAxis span'));
var xIdx = spans.indexOf(span);
if (xIdx === -1) return;
var cfg = dataMap[currentMode];
var count = currentMode === 'day' ? cfg.points.length : cfg.ranges.length;
var barIdx = Math.round(xIdx / (cfg.xLabels.length - 1) * (count - 1));
selectBar(barIdx);
});
document.querySelectorAll('.time-tab').forEach(function(tab) {
tab.onclick = function() {
document.querySelectorAll('.time-tab').forEach(function(item) { item.classList.remove('active'); });
tab.classList.add('active');
renderMode(tab.getAttribute('data-mode'));
currentMode = tab.getAttribute('data-mode');
renderMode(currentMode);
};
});
@@ -188,6 +188,24 @@ body {
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
.filter-btn {
display: flex;
align-items: center;
@@ -235,26 +253,6 @@ body {
<div class="scroll-content">
<div class="stats-card">
<div class="stats-card__left">
<div class="stats-card__label">本月预警</div>
<div class="stats-card__count-row">
<span class="stats-card__count">4</span>
<span class="stats-card__unit"></span>
</div>
</div>
<div class="stats-card__right">
<div class="stats-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 2C8 2 4 5 4 9c0 5 8 13 8 13s8-8 8-13c0-4-4-7-8-7z" fill="#7c5cfc" stroke="#7c5cfc" stroke-width="1.5"/>
<circle cx="12" cy="9" r="3" fill="#fff"/>
</svg>
</div>
<div class="stats-card__recent-label">最近预警时间</div>
<div class="stats-card__recent-time">2026-05-11 08:45</div>
</div>
</div>
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 4 条)</div>
<button class="filter-btn">
@@ -274,28 +272,15 @@ body {
<div class="alert-card__body">
<div class="alert-card__header">
<span class="alert-card__time">2026-05-11 08:45</span>
<span class="alert-card__badge alert-card__badge--critical">严重</span>
</div>
<div class="alert-card__value-row">
<span class="alert-card__value">158/98</span>
<span class="alert-card__value-unit">mmHg</span>
</div>
<div class="alert-card__detail-row">
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 15 分钟</span>
</div>
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M12 2C8 2 4 5 4 9c0 5 8 13 8 13s8-8 8-13c0-4-4-7-8-7z"/>
</svg>
<span>血压</span>
</div>
</div>
<div class="alert-card__reason">触发原因:收缩压超过 150 mmHg 阈值</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
@@ -307,28 +292,15 @@ body {
<div class="alert-card__body">
<div class="alert-card__header">
<span class="alert-card__time">2026-05-08 14:20</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<div class="alert-card__value-row">
<span class="alert-card__value">142/92</span>
<span class="alert-card__value-unit">mmHg</span>
</div>
<div class="alert-card__detail-row">
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 8 分钟</span>
</div>
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M12 2C8 2 4 5 4 9c0 5 8 13 8 13s8-8 8-13c0-4-4-7-8-7z"/>
</svg>
<span>血压</span>
</div>
</div>
<div class="alert-card__reason">触发原因:收缩压超过 140 mmHg 阈值</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
@@ -340,28 +312,15 @@ body {
<div class="alert-card__body">
<div class="alert-card__header">
<span class="alert-card__time">2026-05-05 07:30</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<div class="alert-card__value-row">
<span class="alert-card__value">145/90</span>
<span class="alert-card__value-unit">mmHg</span>
</div>
<div class="alert-card__detail-row">
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 10 分钟</span>
</div>
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M12 2C8 2 4 5 4 9c0 5 8 13 8 13s8-8 8-13c0-4-4-7-8-7z"/>
</svg>
<span>血压</span>
</div>
</div>
<div class="alert-card__reason">触发原因:收缩压超过 140 mmHg 阈值</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
@@ -373,28 +332,15 @@ body {
<div class="alert-card__body">
<div class="alert-card__header">
<span class="alert-card__time">2026-05-02 19:15</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<div class="alert-card__value-row">
<span class="alert-card__value">138/94</span>
<span class="alert-card__value-unit">mmHg</span>
</div>
<div class="alert-card__detail-row">
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 6 分钟</span>
</div>
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M12 2C8 2 4 5 4 9c0 5 8 13 8 13s8-8 8-13c0-4-4-7-8-7z"/>
</svg>
<span>血压</span>
</div>
</div>
<div class="alert-card__reason">触发原因:舒张压超过 90 mmHg 阈值</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
@@ -204,6 +204,24 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
@@ -400,27 +418,6 @@ body {
<!-- 滚动内容区 -->
<div class="scroll-content">
<!-- 预警统计卡片 -->
<div class="stats-card">
<div class="stats-card__left">
<div class="stats-card__label">本月预警</div>
<div class="stats-card__count-row">
<span class="stats-card__count">3</span>
<span class="stats-card__unit"></span>
</div>
</div>
<div class="stats-card__right">
<!-- 预警心形图标 -->
<div class="stats-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" fill="#ff4d4f" stroke="#ff4d4f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="stats-card__recent-label">最近预警时间</div>
<div class="stats-card__recent-time">2026-04-28 16:32</div>
</div>
</div>
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 3 条)</div>
@@ -440,36 +437,20 @@ body {
<div class="alert-card__dot alert-card__dot--critical"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-04-28 16:32</span>
<span class="alert-card__badge alert-card__badge--critical">严重</span>
</div>
<!-- 监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">142</span>
<span class="alert-card__value-unit">bpm</span>
<span class="alert-card__value-unit">次/分钟</span>
</div>
<!-- 持续时长 + 心率标签 -->
<div class="alert-card__detail-row">
<!-- 时钟图标 + 持续时长 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 5 分钟</span>
</div>
<!-- 心率标签 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
<span>心率</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:心率超过 140 bpm 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140 次/分钟</span></div>
<div class="alert-card__threshold-item">下限 <span>50 次/分钟</span></div>
</div>
</div>
@@ -480,34 +461,20 @@ body {
<div class="alert-card__dot alert-card__dot--warning"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-04-25 14:18</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<!-- 监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">128</span>
<span class="alert-card__value-unit">bpm</span>
<span class="alert-card__value-unit">次/分钟</span>
</div>
<!-- 持续时长 + 心率标签 -->
<div class="alert-card__detail-row">
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 3 分钟</span>
</div>
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
<span>心率</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:心率超过 120 bpm 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>120 次/分钟</span></div>
<div class="alert-card__threshold-item">下限 <span>50 次/分钟</span></div>
</div>
</div>
@@ -518,34 +485,20 @@ body {
<div class="alert-card__dot alert-card__dot--warning"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-04-20 09:45</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<!-- 监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">126</span>
<span class="alert-card__value-unit">bpm</span>
<span class="alert-card__value-unit">次/分钟</span>
</div>
<!-- 持续时长 + 心率标签 -->
<div class="alert-card__detail-row">
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 2 分钟</span>
</div>
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
<span>心率</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:心率超过 120 bpm 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>120 次/分钟</span></div>
<div class="alert-card__threshold-item">下限 <span>50 次/分钟</span></div>
</div>
</div>
@@ -98,7 +98,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="main-row__value" id="mainValue">72</span>
<span class="main-row__unit" id="mainUnit">bpm</span>
</div>
<div class="main-badge" id="statusBadge">正常</div>
</div>
<div class="chart-wrap">
@@ -122,7 +122,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="metric-row">
<div class="metric-left">
<div class="metric-left__label">最高</div>
<div class="metric-left__value"><span class="metric-left__num" id="maxValue">126</span><span class="metric-left__unit">bpm</span></div>
<div class="metric-left__value"><span class="metric-left__num" id="maxValue">126</span><span class="metric-left__unit">次/分钟</span></div>
</div>
<div class="metric-right" id="maxHint">14:20</div>
</div>
@@ -130,7 +130,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="metric-row">
<div class="metric-left">
<div class="metric-left__label">最低</div>
<div class="metric-left__value"><span class="metric-left__num" id="minValue">68</span><span class="metric-left__unit">bpm</span></div>
<div class="metric-left__value"><span class="metric-left__num" id="minValue">68</span><span class="metric-left__unit">次/分钟</span></div>
</div>
<div class="metric-right" id="minHint">03:40</div>
</div>
@@ -138,7 +138,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="metric-row">
<div class="metric-left">
<div class="metric-left__label">平均</div>
<div class="metric-left__value"><span class="metric-left__num" id="avgValue">82</span><span class="metric-left__unit">bpm</span></div>
<div class="metric-left__value"><span class="metric-left__num" id="avgValue">82</span><span class="metric-left__unit">次/分钟</span></div>
</div>
<div class="metric-right" id="avgHint">全天</div>
</div>
@@ -159,7 +159,7 @@ var dataMap = {
day: {
dateText: '4月30日 周四',
rangeText: '00:00-23:59',
unit: 'bpm',
unit: '次/分钟',
decimals: 0,
points: [84,82,80,78,76,74,72,70,68,75,79,83,88,96,102,126,108,94,90,87,85,84,83,72],
totalMode: 'last',
@@ -174,7 +174,7 @@ var dataMap = {
week: {
dateText: '4月27日-5月3日',
rangeText: '周一-周日',
unit: 'bpm',
unit: '次/分钟',
decimals: 0,
points: [78, 80, 82, 79, 83, 81, 80],
totalMode: 'avg',
@@ -189,7 +189,7 @@ var dataMap = {
month: {
dateText: '2026年4月',
rangeText: '4月1日-4月30日',
unit: 'bpm',
unit: '次/分钟',
decimals: 0,
points: [79,81,80,82,83,78,80,81,82,79,80,81,82,83,79,80,81,82,80,79,81,82,80,79,81,82,80,79,81,80],
totalMode: 'avg',
@@ -204,7 +204,7 @@ var dataMap = {
year: {
dateText: '2026年',
rangeText: '1月-12月',
unit: 'bpm',
unit: '次/分钟',
decimals: 0,
points: [79,80,81,80,79,78,79,80,81,80,79,80],
totalMode: 'avg',
@@ -290,7 +290,7 @@ function renderMode(mode) {
document.getElementById('rangeText').textContent = cfg.rangeText;
document.getElementById('mainValue').textContent = formatValue(value, cfg.decimals);
document.getElementById('mainUnit').textContent = cfg.unit;
document.getElementById('statusBadge').textContent = cfg.status;
document.getElementById('maxValue').textContent = formatValue(cfg.max.v, 0);
document.getElementById('maxHint').textContent = cfg.max.hint;
@@ -209,6 +209,24 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
@@ -405,30 +423,6 @@ body {
<!-- 滚动内容区 -->
<div class="scroll-content">
<!-- 预警统计卡片 -->
<div class="stats-card">
<div class="stats-card__left">
<div class="stats-card__label">本月预警</div>
<!-- 本月预警次数:橙色大字 -->
<div class="stats-card__count-row">
<span class="stats-card__count">2</span>
<span class="stats-card__unit"></span>
</div>
</div>
<div class="stats-card__right">
<!-- 压力预警图标:橙色脑波/压力符号 -->
<div class="stats-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<!-- 简化压力波形图标 -->
<path d="M3 12 L6 7 L9 15 L12 9 L15 14 L18 10 L21 12"
stroke="#fa8c16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="stats-card__recent-label">最近预警时间</div>
<div class="stats-card__recent-time">2026-04-30 16:30</div>
</div>
</div>
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 2 条)</div>
@@ -448,36 +442,20 @@ body {
<div class="alert-card__dot alert-card__dot--warning"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 警告等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-04-30 16:30</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<!-- 压力监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">82</span>
<span class="alert-card__value-unit">压力值</span>
</div>
<!-- 持续时长 + 压力标签 -->
<div class="alert-card__detail-row">
<!-- 时钟图标 + 持续时长 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 25 分钟</span>
</div>
<!-- 压力类型标签 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M3 12 L6 8 L9 14 L12 9 L15 13 L18 10 L21 12"/>
</svg>
<span>压力</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:压力值超过 80 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>80</span></div>
<div class="alert-card__threshold-item">下限 <span>0</span></div>
</div>
</div>
@@ -488,36 +466,20 @@ body {
<div class="alert-card__dot alert-card__dot--critical"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 严重等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-04-22 15:10</span>
<span class="alert-card__badge alert-card__badge--critical">严重</span>
</div>
<!-- 压力监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">88</span>
<span class="alert-card__value-unit">压力值</span>
</div>
<!-- 持续时长 + 压力标签 -->
<div class="alert-card__detail-row">
<!-- 时钟图标 + 持续时长 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 40 分钟</span>
</div>
<!-- 压力类型标签 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M3 12 L6 8 L9 14 L12 9 L15 13 L18 10 L21 12"/>
</svg>
<span>压力</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:压力值超过 85 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>85</span></div>
<div class="alert-card__threshold-item">下限 <span>0</span></div>
</div>
</div>
@@ -204,6 +204,24 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
@@ -400,27 +418,6 @@ body {
<!-- 滚动内容区 -->
<div class="scroll-content">
<!-- 预警统计卡片 -->
<div class="stats-card">
<div class="stats-card__left">
<div class="stats-card__label">本月预警</div>
<div class="stats-card__count-row">
<span class="stats-card__count">1</span>
<span class="stats-card__unit"></span>
</div>
</div>
<div class="stats-card__right">
<!-- 体温预警图标 -->
<div class="stats-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M14 14.76V3.5a2.5 2.5 0 0 0-5 0v11.26a4.5 4.5 0 1 0 5 0z" stroke="#faad14" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="stats-card__recent-label">最近预警时间</div>
<div class="stats-card__recent-time">2026-04-15 13:28</div>
</div>
</div>
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 1 条)</div>
@@ -440,36 +437,20 @@ body {
<div class="alert-card__dot alert-card__dot--warning"></div>
</div>
<div class="alert-card__body">
<!-- 时间 + 警告等级标签 -->
<!-- 时间 -->
<div class="alert-card__header">
<span class="alert-card__time">2026-04-15 13:28</span>
<span class="alert-card__badge alert-card__badge--warning">警告</span>
</div>
<!-- 体温监测值 -->
<div class="alert-card__value-row">
<span class="alert-card__value">37.8</span>
<span class="alert-card__value-unit">°C</span>
</div>
<!-- 持续时长 + 体温标签 -->
<div class="alert-card__detail-row">
<!-- 时钟图标 + 持续时长 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>持续 45 分钟</span>
</div>
<!-- 体温标签 -->
<div class="alert-card__detail-item">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2">
<path d="M14 14.76V3.5a2.5 2.5 0 0 0-5 0v11.26a4.5 4.5 0 1 0 5 0z"/>
</svg>
<span>体温</span>
</div>
</div>
<!-- 触发原因 -->
<div class="alert-card__reason">触发原因:体温超过 37.3°C 阈值</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>37.3°C</span></div>
<div class="alert-card__threshold-item">下限 <span>35.0°C</span></div>
</div>
</div>
+1
View File
@@ -291,6 +291,7 @@ const SITE_MAP = [
{ name:'运动管理', dir:'web-admin/exercise', pages:[] },
{ name:'健康监测', dir:'web-admin/health-monitor', entry:'archive-personal.html', prd:'web-admin/health-monitor/doc/PRD.md', done:true, pages:[
{ file:'archive-personal.html', label:'个人监测数据' },
{ file:'archive-personal-detail.html', label:'个人监测数据详情' },
{ file:'archive-unit.html', label:'单位报表数据' },
{ file:'archive-unit-health-checkup-detail.html', label:'单位报表-健康排查详情' },
{ file:'archive-unit-wearable-detail.html', label:'单位报表-穿戴监测详情' },
+282 -8
View File
@@ -70,6 +70,58 @@
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
/* 弹窗遮罩 */
.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.45);z-index:1000;display:none;align-items:center;justify-content:center;}
.modal-overlay.show{display:flex;}
/* 弹窗容器 */
.modal{background:#fff;border-radius:var(--radius-md);width:90%;max-width:800px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 4px 24px rgba(0,0,0,0.15);}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--border);}
.modal__title{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
.modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);transition:background .2s;}
.modal__close:hover{background:#F5F5F5;}
.modal__body{padding:24px;overflow-y:auto;flex:1;}
.modal__footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border);}
/* 表单 */
.form-row{display:flex;align-items:flex-start;margin-bottom:20px;}
.form-row label{width:120px;padding-top:8px;font-size:var(--font-sm);color:var(--text-primary);text-align:right;flex-shrink:0;}
.form-row label .required{color:var(--danger);margin-right:4px;}
.form-row .form-control{flex:1;margin-left:16px;}
.form-input{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.form-input:focus{border-color:var(--primary);}
.form-textarea{width:100%;min-height:100px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;resize:vertical;font-family:inherit;}
.form-textarea:focus{border-color:var(--primary);}
.form-datepicker{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cpath d='M11 2h1a1 1 0 011 1v9a1 1 0 01-1 1H2a1 1 0 01-1-1V3a1 1 0 011-1h1M4 1v2M10 1v2M1 5h12' stroke='%23999' stroke-width='1.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 12px center;}
.form-datepicker:focus{border-color:var(--primary);}
/* 上传按钮 */
.upload-btn{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 16px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-primary);font-size:var(--font-sm);cursor:pointer;transition:all .2s;}
.upload-btn:hover{color:var(--primary);border-color:var(--primary);}
.upload-hint{margin-top:8px;font-size:var(--font-xs);color:var(--text-secondary);}
.upload-hint .count{color:var(--danger);font-weight:500;}
/* 附件列表 */
.file-list{margin-top:8px;}
.file-item{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#FAFAFA;}
.file-item__icon{width:16px;height:16px;flex-shrink:0;}
.file-item__icon path{stroke:var(--primary);}
.file-item__name{flex:1;font-size:var(--font-sm);color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.file-item__actions{display:flex;align-items:center;gap:8px;}
.file-item__btn{width:24px;height:24px;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);transition:background .2s;padding:0;}
.file-item__btn:hover{background:#E8E8E8;}
.file-item__btn svg{width:16px;height:16px;}
.file-item__btn--download path{stroke:#666;}
.file-item__btn--delete path{stroke:var(--danger);}
/* 预览弹窗 */
.preview-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);z-index:2000;display:none;align-items:center;justify-content:center;}
.preview-modal.show{display:flex;}
.preview-modal__container{width:90%;max-width:1200px;height:90vh;background:#fff;border-radius:var(--radius-md);display:flex;flex-direction:column;box-shadow:0 8px 32px rgba(0,0,0,0.3);}
.preview-modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);background:#FAFAFA;}
.preview-modal__title{font-size:var(--font-base);font-weight:500;color:var(--text-primary);}
.preview-modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);transition:background .2s;}
.preview-modal__close:hover{background:#E8E8E8;}
.preview-modal__body{flex:1;overflow:auto;padding:24px;display:flex;align-items:center;justify-content:center;background:#F5F5F5;}
.preview-modal__content{max-width:100%;max-height:100%;}
.preview-modal__content iframe{width:100%;height:100%;border:none;background:#fff;}
.preview-placeholder{text-align:center;color:var(--text-secondary);}
.preview-placeholder svg{width:64px;height:64px;margin-bottom:16px;opacity:0.3;}
</style>
</head>
<body>
@@ -116,7 +168,7 @@
<!-- 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/></svg>新增</button>
<button class="btn btn-primary" onclick="openAddModal()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/></svg>新增</button>
<button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
</div>
@@ -147,8 +199,8 @@
<td>研发人员</td>
<td>2023</td>
<td>2026-04-17 13:36:34</td>
<td><a class="link">预览</a>&nbsp;&nbsp;<a class="link">下载</a></td>
<td><a class="link">编辑</a>&nbsp;&nbsp;<a class="btn-danger-link">删除</a></td>
<td><a class="link" onclick="openPreview('测试PDF', 'pdf')">预览</a>&nbsp;&nbsp;<a class="link">下载</a></td>
<td><a class="link" onclick="openEditModal(1)">编辑</a>&nbsp;&nbsp;<a class="btn-danger-link">删除</a></td>
</tr>
<tr>
<td>2</td>
@@ -158,8 +210,8 @@
<td>张老师</td>
<td>2025</td>
<td>2026-04-17 13:06:38</td>
<td><a class="link">预览</a>&nbsp;&nbsp;<a class="link">下载</a></td>
<td><a class="link">编辑</a>&nbsp;&nbsp;<a class="btn-danger-link">删除</a></td>
<td><a class="link" onclick="openPreview('预览WORD', 'word')">预览</a>&nbsp;&nbsp;<a class="link">下载</a></td>
<td><a class="link" onclick="openEditModal(2)">编辑</a>&nbsp;&nbsp;<a class="btn-danger-link">删除</a></td>
</tr>
<tr>
<td>3</td>
@@ -169,8 +221,8 @@
<td>王老师</td>
<td>2025</td>
<td>2026-04-15 16:46:54</td>
<td><a class="link">预览</a>&nbsp;&nbsp;<a class="link">下载</a></td>
<td><a class="link">编辑</a>&nbsp;&nbsp;<a class="btn-danger-link">删除</a></td>
<td><a class="link" onclick="openPreview('心理算法', 'pdf')">预览</a>&nbsp;&nbsp;<a class="link">下载</a></td>
<td><a class="link" onclick="openEditModal(3)">编辑</a>&nbsp;&nbsp;<a class="btn-danger-link">删除</a></td>
</tr>
</tbody>
</table>
@@ -184,6 +236,228 @@
</div>
</div>
</main>
<script>function toggleSubgroup(h){h.parentElement.classList.toggle('open');}(function(){var a=document.querySelector('.sidebar-item.active');if(!a)return;var s=a.closest('.sidebar-subgroup');if(s)s.classList.add('open');})();</script>
<!-- 新增弹窗 -->
<div class="modal-overlay" id="addModal">
<div class="modal">
<div class="modal__header">
<div class="modal__title">新增算法模型</div>
<button class="modal__close" onclick="closeAddModal()">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M5 5l10 10M15 5L5 15" stroke="#999" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="modal__body">
<form id="addForm">
<div class="form-row">
<label><span class="required">*</span>文档名称:</label>
<div class="form-control">
<input class="form-input" type="text" placeholder="请输入文档名称" required>
</div>
</div>
<div class="form-row">
<label><span class="required">*</span>文档描述:</label>
<div class="form-control">
<textarea class="form-textarea" placeholder="请输入文档描述" required></textarea>
</div>
</div>
<div class="form-row">
<label>制作机构:</label>
<div class="form-control">
<input class="form-input" type="text" placeholder="请输入制作机构">
</div>
</div>
<div class="form-row">
<label>制作人:</label>
<div class="form-control">
<input class="form-input" type="text" placeholder="请输入制作人">
</div>
</div>
<div class="form-row">
<label><span class="required">*</span>制作年份:</label>
<div class="form-control">
<input class="form-datepicker" type="text" placeholder="请选择年份" required>
</div>
</div>
<div class="form-row">
<label><span class="required">*</span>附件:</label>
<div class="form-control">
<button type="button" class="upload-btn">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M7 10V4M4.5 6.5L7 4l2.5 2.5M2 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
上传
</button>
<div class="upload-hint">已上传 <span class="count">0</span> / 1 个文件</div>
</div>
</div>
</form>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeAddModal()">取消</button>
<button class="btn btn-primary" onclick="submitAdd()">确定</button>
</div>
</div>
</div>
<!-- 编辑弹窗 -->
<div class="modal-overlay" id="editModal">
<div class="modal">
<div class="modal__header">
<div class="modal__title">编辑算法模型</div>
<button class="modal__close" onclick="closeEditModal()">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M5 5l10 10M15 5L5 15" stroke="#999" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="modal__body">
<form id="editForm">
<div class="form-row">
<label><span class="required">*</span>文档名称:</label>
<div class="form-control">
<input class="form-input" id="editName" type="text" placeholder="请输入文档名称" required>
</div>
</div>
<div class="form-row">
<label><span class="required">*</span>文档描述:</label>
<div class="form-control">
<textarea class="form-textarea" id="editDesc" placeholder="请输入文档描述" required></textarea>
</div>
</div>
<div class="form-row">
<label>制作机构:</label>
<div class="form-control">
<input class="form-input" id="editOrg" type="text" placeholder="请输入制作机构">
</div>
</div>
<div class="form-row">
<label>制作人:</label>
<div class="form-control">
<input class="form-input" id="editAuthor" type="text" placeholder="请输入制作人">
</div>
</div>
<div class="form-row">
<label><span class="required">*</span>制作年份:</label>
<div class="form-control">
<input class="form-datepicker" id="editYear" type="text" placeholder="请选择年份" required>
</div>
</div>
<div class="form-row">
<label><span class="required">*</span>附件:</label>
<div class="form-control">
<button type="button" class="upload-btn">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M7 10V4M4.5 6.5L7 4l2.5 2.5M2 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
上传
</button>
<div class="file-list">
<div class="file-item">
<svg class="file-item__icon" viewBox="0 0 16 16" fill="none">
<path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6L9 2z" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 2v4h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="file-item__name">71db2f3c-9cc6-4e75-a0c8-2bece8b7f5f8.pdf</span>
<div class="file-item__actions">
<button class="file-item__btn file-item__btn--download" title="下载">
<svg viewBox="0 0 16 16" fill="none">
<path d="M8 2v8M5.5 7.5L8 10l2.5-2.5M3 13h10" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="file-item__btn file-item__btn--delete" title="删除">
<svg viewBox="0 0 16 16" fill="none">
<path d="M3 4h10M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1M5 4v8a1 1 0 001 1h4a1 1 0 001-1V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
<div class="upload-hint">已上传 <span class="count">1</span> / 1 个文件</div>
</div>
</div>
</form>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeEditModal()">取消</button>
<button class="btn btn-primary" onclick="submitEdit()">确定</button>
</div>
</div>
</div>
<!-- 预览弹窗 -->
<div id="previewModal" class="preview-modal" style="display: none;">
<div class="preview-modal__container">
<div class="preview-modal__header">
<h3 id="previewTitle" class="preview-modal__title">文件预览</h3>
<button class="preview-modal__close" onclick="closePreview()">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M5 5l10 10M15 5L5 15" stroke="#999" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="preview-modal__body">
<div class="preview-modal__content">
<iframe id="previewFrame" style="width: 900px; height: 600px; border: none; background: #fff;"></iframe>
</div>
</div>
</div>
</div>
<script>function toggleSubgroup(h){h.parentElement.classList.toggle('open');}(function(){var a=document.querySelector('.sidebar-item.active');if(!a)return;var s=a.closest('.sidebar-subgroup');if(s)s.classList.add('open');})();
// 模拟数据
var mockData = {
1: {name: '测试PDF', desc: '测试PDF预览', org: '研发', author: '研发人员', year: '2023'},
2: {name: '预览WORD', desc: '测试的', org: '家里', author: '张老师', year: '2025'},
3: {name: '心理算法', desc: '心理算法模型', org: '交大', author: '王老师', year: '2025'}
};
function openAddModal(){document.getElementById('addModal').classList.add('show');}
function closeAddModal(){document.getElementById('addModal').classList.remove('show');document.getElementById('addForm').reset();}
function submitAdd(){alert('提交新增');closeAddModal();}
function openEditModal(id){
var data = mockData[id];
if(!data) return;
document.getElementById('editName').value = data.name;
document.getElementById('editDesc').value = data.desc;
document.getElementById('editOrg').value = data.org;
document.getElementById('editAuthor').value = data.author;
document.getElementById('editYear').value = data.year;
document.getElementById('editModal').classList.add('show');
}
function closeEditModal(){document.getElementById('editModal').classList.remove('show');document.getElementById('editForm').reset();}
function submitEdit(){alert('提交编辑');closeEditModal();}
function openPreview(filename, type){
var modal = document.getElementById('previewModal');
var title = document.getElementById('previewTitle');
var frame = document.getElementById('previewFrame');
title.textContent = filename;
// 根据文件类型设置预览内容
if(type === 'pdf'){
frame.src = 'about:blank';
setTimeout(function(){
frame.contentDocument.write('<div style="display: flex; align-items: center; justify-content: center; height: 100%; font-size: 18px; color: #666;">PDF预览功能</div>');
}, 100);
} else if(type === 'word'){
frame.src = 'about:blank';
setTimeout(function(){
frame.contentDocument.write('<div style="display: flex; align-items: center; justify-content: center; height: 100%; font-size: 18px; color: #666;">WORD预览功能</div>');
}, 100);
}
modal.style.display = 'flex';
}
function closePreview(){
var modal = document.getElementById('previewModal');
modal.style.display = 'none';
}
</script>
</body>
</html>
@@ -0,0 +1,494 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康监测 - 个人监测详情 | 健康长庆原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;}
:root{
--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;
--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;--background:#F5F7FA;
--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;
--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__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.top-bar__right .btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.top-bar__right .btn-back:hover{color:#fff;}
.avatar{width:32px;height:32px;border-radius:50%;background:#1890FF;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;text-transform:uppercase;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
.sidebar-item.active .mi{opacity:0.9;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 面包屑 */
.breadcrumb{display:flex;align-items:center;gap:8px;margin-bottom:16px;font-size:var(--font-sm);color:var(--text-secondary);}
.breadcrumb a{color:var(--primary);text-decoration:none;}.breadcrumb a:hover{color:#40a9ff;}
/* 员工信息卡片 */
.info-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:24px;margin-bottom:16px;display:flex;align-items:center;gap:32px;}
.info-card__avatar{width:56px;height:56px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:600;flex-shrink:0;}
.info-card__detail{display:flex;flex-direction:column;gap:6px;}
.info-card__name{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
.info-card__meta{display:flex;gap:24px;font-size:var(--font-sm);color:var(--text-secondary);}
/* Tab栏 */
.tab-bar{display:flex;gap:0;border-bottom:2px solid var(--border);margin-bottom:20px;background:#fff;padding:0 24px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.tab-item{padding:14px 24px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .2s;white-space:nowrap;}
.tab-item:hover{color:var(--primary);}
.tab-item.active{color:var(--primary);font-weight:600;border-bottom-color:var(--primary);}
.tab-panel{display:none;}.tab-panel.active{display:block;}
/* 通用 */
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;min-width:140px;}
.filter-input:focus{border-color:var(--primary);}
.filter-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;appearance:none;cursor:pointer;min-width:140px;}
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__content{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:collapse;white-space:nowrap;}
thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
tbody td{padding:14px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-orange{background:#FFF7E6;color:var(--warning);}
.tag-red{background:#FFF1F0;color:var(--danger);}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;}.link:hover{color:#40a9ff;}
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
/* 穿戴监测 */
.detail-tabs{display:flex;gap:0;border-bottom:2px solid var(--border);margin-bottom:20px;}
.detail-tab{padding:10px 20px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px;}
.detail-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500;}
.overview-time-bar{display:flex;justify-content:center;gap:0;margin-bottom:20px;}
.time-btn{height:32px;padding:0 16px;border:1px solid var(--border);background:#fff;font-size:var(--font-sm);cursor:pointer;color:var(--text-secondary);transition:all .15s;}
.time-btn:first-child{border-radius:var(--radius-sm) 0 0 var(--radius-sm);}
.time-btn:last-child{border-radius:0 var(--radius-sm) var(--radius-sm) 0;}
.time-btn:not(:first-child){border-left:none;}
.time-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.overview-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;}
.overview-card{border:1px solid var(--border);border-radius:var(--radius-md);padding:16px 20px;}
.overview-card__title{font-size:var(--font-sm);font-weight:600;margin-bottom:12px;color:var(--text-primary);display:flex;align-items:center;gap:6px;}
.overview-card__title svg{width:16px;height:16px;flex-shrink:0;}
.metric-hero{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:12px 0;}
.metric-hero__value{font-size:32px;font-weight:700;color:var(--text-primary);line-height:1.2;}
.metric-hero__unit{font-size:var(--font-xs);color:var(--text-secondary);margin-top:2px;}
.metric-hero__sub{display:flex;gap:24px;margin-top:12px;}
.metric-hero__sub-item{display:flex;flex-direction:column;align-items:center;gap:2px;}
.metric-hero__sub-item .sub-val{font-size:var(--font-base);font-weight:600;color:var(--text-primary);}
.metric-hero__sub-item .sub-label{font-size:var(--font-xs);color:var(--text-secondary);}
.metric-toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;}
.metric-toolbar__left{display:flex;align-items:center;gap:16px;}
.time-granularity{display:flex;gap:0;}
.time-granularity .time-btn{min-width:48px;}
.date-nav{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:16px;}
.date-nav__btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:16px;display:inline-flex;align-items:center;justify-content:center;}
.date-nav__btn:hover{border-color:var(--primary);color:var(--primary);}
.date-nav__text{font-size:var(--font-base);font-weight:600;min-width:120px;text-align:center;}
.metric-summary{display:flex;justify-content:center;gap:48px;margin-bottom:20px;}
.metric-summary__item{display:flex;flex-direction:column;align-items:center;gap:2px;}
.metric-summary__value{font-size:22px;font-weight:700;color:var(--text-primary);}
.metric-summary__unit{font-size:var(--font-xs);color:var(--text-secondary);}
.metric-summary__label{font-size:var(--font-xs);color:var(--text-secondary);margin-top:2px;}
.chart-area{width:100%;height:300px;border:1px solid var(--border);border-radius:var(--radius-md);background:#fafafa;position:relative;overflow:hidden;}
.data-history-table{width:100%;}
/* 体重监测弹窗 */
.modal-mask{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.45);z-index:500;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .15s;}
.modal-mask.show{opacity:1;pointer-events:auto;}
.modal{background:#fff;border-radius:var(--radius-lg);width:680px;max-height:85vh;overflow-y:auto;box-shadow:0 8px 40px rgba(0,0,0,0.18);}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--border);}
.modal__header h3{font-size:var(--font-lg);font-weight:600;}
.modal__close{width:32px;height:32px;border:none;background:none;cursor:pointer;font-size:20px;color:var(--text-secondary);border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;}
.modal__close:hover{background:#f5f5f5;color:var(--text-primary);}
.modal__body{padding:24px;}
.detail-section{margin-bottom:20px;}.detail-section:last-child{margin-bottom:0;}
.detail-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:12px;padding-left:8px;border-left:3px solid var(--primary);}
.detail-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:0;}
.detail-item{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border:1px solid #f0f0f0;margin:-0.5px;}
.detail-item__label{font-size:var(--font-xs);color:var(--text-secondary);}
.detail-item__value{font-size:var(--font-xs);color:var(--text-primary);font-weight:500;}
/* 健康现状 */
.hs-field{display:flex;align-items:center;gap:12px;margin-bottom:24px;}
.hs-field__label{font-size:var(--font-sm);color:var(--text-primary);white-space:nowrap;}
.hs-field__label .required{color:var(--danger);margin-right:2px;}
.hs-tag{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:var(--radius-sm);border:1px solid #91D5FF;background:#E6F7FF;color:var(--primary);font-size:var(--font-sm);font-weight:500;}
.hs-tag svg{width:16px;height:16px;}
.hs-section{margin-bottom:28px;}
.hs-section__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:16px;}
.hs-info-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px 48px;padding:0 16px;}
.hs-info-item{font-size:var(--font-sm);color:var(--text-primary);}
.hs-info-item__label{color:var(--text-secondary);margin-right:8px;}
.hs-classify-text{font-size:var(--font-sm);color:var(--text-primary);margin-bottom:16px;}
.hs-table{width:100%;border-collapse:collapse;border:1px solid var(--border);}
.hs-table th{background:#FAFAFA;padding:14px 20px;text-align:center;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border:1px solid var(--border);}
.hs-table td{padding:14px 20px;text-align:center;font-size:var(--font-sm);color:var(--text-primary);border:1px solid var(--border);}
</style>
</head>
<body>
<!-- 顶部导航 -->
<header class="top-bar">
<div class="top-bar__logo">
<svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
健康监测
</div>
<nav class="top-bar__nav">
<a href="archive-personal.html" class="active">档案</a>
<a href="crowd-status-employee.html">健康现状</a>
<a href="health-checkup/survey-category.html">健康排查</a>
<a href="wearable/tool-manage.html">穿戴监测</a>
<a href="weight/scale-device.html">体重监测</a>
<a href="environment/indoor-device-list.html">环境监测</a>
<a href="exception-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right">
<a class="btn-back" href="../../index.html">← 返回首页</a>
<div class="avatar"></div>
<span>系统管理员 ▾</span>
</div>
</header>
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">档案</div>
<a class="sidebar-item active" href="archive-personal.html"><span class="mi">👤</span>个人监测数据</a>
<a class="sidebar-item" href="archive-unit.html"><span class="mi">🏢</span>单位报表数据</a>
<a class="sidebar-item" href="archive-algorithm.html"><span class="mi">🤖</span>算法模型</a>
</aside>
<!-- 主内容区 -->
<div class="main">
<!-- 返回+面包屑 -->
<div style="margin-bottom:12px;">
<a href="archive-personal.html" style="display:inline-flex;align-items:center;gap:6px;color:var(--primary);text-decoration:none;font-size:var(--font-sm);">
<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>
<div class="breadcrumb">
<a href="archive-personal.html">个人监测数据</a>
<span style="color:var(--text-placeholder);">/</span>
<span>详细信息</span>
</div>
<!-- 员工信息卡片 -->
<div class="info-card">
<div class="info-card__avatar" id="empAvatar"></div>
<div class="info-card__detail">
<div class="info-card__name" id="empName">张伟</div>
<div class="info-card__meta">
<span id="empCode">员工编号:EMP001</span>
<span id="empUnit">长庆油田总部</span>
<span id="empDept">研发部</span>
</div>
</div>
</div>
<!-- [交互] 四个Tab -->
<div class="tab-bar">
<div class="tab-item active" onclick="switchMainPanel('health-status',this)">健康现状</div>
<div class="tab-item" onclick="switchMainPanel('health-checkup',this)">健康排查</div>
<div class="tab-item" onclick="switchMainPanel('wearable',this)">穿戴监测</div>
<div class="tab-item" onclick="switchMainPanel('weight',this)">体重监测</div>
</div>
<!-- Tab1: 健康现状 -->
<div class="tab-panel active" id="panel-health-status">
<!-- 健康分类 -->
<div class="hs-field">
<span class="hs-field__label"><span class="required">*</span>健康分类:</span>
<span class="hs-tag">
<svg viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="14" height="14" rx="3" fill="#1890FF"/><path d="M4 8l3 3 5-5" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
B类人群
</span>
</div>
<!-- 住院信息 -->
<div class="hs-section">
<div class="hs-section__title">住院信息</div>
<div class="hs-info-grid">
<div class="hs-info-item"><span class="hs-info-item__label">住院状态:</span>正常在院</div>
<div class="hs-info-item"><span class="hs-info-item__label">所在医院:</span>西京医院</div>
<div class="hs-info-item"><span class="hs-info-item__label">住院时间:</span>2026-03-21</div>
<div class="hs-info-item"><span class="hs-info-item__label">住院次数:</span>2</div>
<div class="hs-info-item"><span class="hs-info-item__label">联系人:</span>张旭</div>
<div class="hs-info-item"><span class="hs-info-item__label">联系电话:</span>15899655523</div>
</div>
</div>
<!-- 分类信息 -->
<div class="hs-section">
<div class="hs-section__title">分类信息</div>
<div class="hs-classify-text">B类慢病信息</div>
<table class="hs-table">
<thead>
<tr><th>慢病名称</th><th>确诊年份</th><th>确诊医院</th><th>治疗情况</th></tr>
</thead>
<tbody>
<tr><td>冠状动脉硬化性心脏病</td><td>2015</td><td>西京医院</td><td></td></tr>
<tr><td>糖尿病</td><td>2025</td><td></td><td></td></tr>
</tbody>
</table>
</div>
</div>
<!-- Tab2: 健康排查 -->
<div class="tab-panel" id="panel-health-checkup">
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;">
<div class="filter-item"><label>筛选日期</label><input class="filter-input" type="date" value="2026-05-01" style="min-width:140px;"><span style="margin:0 6px;color:var(--text-placeholder);">~</span><input class="filter-input" type="date" value="2026-05-13" style="min-width:140px;"></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<div class="table-card">
<div class="table-card__content">
<table>
<thead><tr><th>记录时间</th><th>增加积分</th><th>操作</th></tr></thead>
<tbody>
<tr><td>2026-05-13 08:30</td><td>+5</td><td><span class="link" onclick="openSurveyModal('2026-05-13 08:30')">查看问卷</span></td></tr>
<tr><td>2026-05-12 08:15</td><td>+5</td><td><span class="link" onclick="openSurveyModal('2026-05-12 08:15')">查看问卷</span></td></tr>
<tr><td>2026-05-11 07:50</td><td>+5</td><td><span class="link" onclick="openSurveyModal('2026-05-11 07:50')">查看问卷</span></td></tr>
<tr><td>2026-05-10 08:05</td><td>+3</td><td><span class="link" onclick="openSurveyModal('2026-05-10 08:05')">查看问卷</span></td></tr>
<tr><td>2026-05-09 09:00</td><td>+5</td><td><span class="link" onclick="openSurveyModal('2026-05-09 09:00')">查看问卷</span></td></tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>共 128 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<button class="page-btn">...</button>
<button class="page-btn">26</button>
<button class="page-btn"></button>
</div>
</div>
</div>
<!-- Tab3: 穿戴监测 -->
<div class="tab-panel" id="panel-wearable">
<!-- 穿戴子Tab -->
<div class="detail-tabs">
<div class="detail-tab active" onclick="switchWearableTab(this,'wt-heart')">心率</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-oxygen')">血氧</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-stress')">压力</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-sleep')">睡眠</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-temp')">体温</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-exercise')">锻炼</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-steps')">步数</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-bp')">血压</div>
<div class="detail-tab" onclick="switchWearableTab(this,'wt-ecg')">心电图</div>
</div>
<!-- 指标详情面板 -->
<div id="wt-metric">
<div class="metric-toolbar">
<div class="metric-toolbar__left"><div class="time-granularity"><button class="time-btn active" onclick="switchGranularity(this)"></button><button class="time-btn" onclick="switchGranularity(this)"></button><button class="time-btn" onclick="switchGranularity(this)"></button></div></div>
<button class="btn btn-default" id="btnToggleList" onclick="toggleMetricView()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 3h10M2 7h10M2 11h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>列表展示</button>
</div>
<div id="metric-chart-view">
<div class="date-nav"><button class="date-nav__btn"></button><span class="date-nav__text">2026-05-12</span><button class="date-nav__btn"></button></div>
<div class="metric-summary"><div class="metric-summary__item"><span class="metric-summary__value" id="msVal1">92</span><span class="metric-summary__unit" id="msUnit1">次/分钟</span><span class="metric-summary__label" id="msLabel1">最高心率</span></div><div class="metric-summary__item"><span class="metric-summary__value" id="msVal2">62</span><span class="metric-summary__unit" id="msUnit2">次/分钟</span><span class="metric-summary__label" id="msLabel2">最低心率</span></div><div class="metric-summary__item"><span class="metric-summary__value" id="msVal3">76</span><span class="metric-summary__unit" id="msUnit3">次/分钟</span><span class="metric-summary__label" id="msLabel3">平均心率</span></div></div>
<div class="chart-area" id="trendChart"></div>
</div>
<div id="metric-table-view" style="display:none;">
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;"><div class="filter-item"><label>日期</label><input type="date" value="2026-05-12" class="filter-input" style="min-width:130px;"></div><button class="btn btn-default">导出</button></div>
<table class="data-history-table"><thead><tr><th>上报时间</th><th>数值</th><th>状态</th></tr></thead><tbody id="metricTableBody"><tr><td>14:32:05</td><td>76</td><td><span class="tag tag-green">正常</span></td></tr><tr><td>14:02:05</td><td>79</td><td><span class="tag tag-green">正常</span></td></tr><tr><td>13:32:05</td><td>72</td><td><span class="tag tag-green">正常</span></td></tr><tr><td>13:02:05</td><td>85</td><td><span class="tag tag-green">正常</span></td></tr><tr><td>12:32:05</td><td>108</td><td><span class="tag tag-red">异常</span></td></tr></tbody></table>
</div>
</div>
<!-- 锻炼面板 -->
<div id="wt-exercise" style="display:none;">
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;"><div class="filter-item"><label>日期</label><input type="date" class="filter-input" style="min-width:130px;"></div><button class="btn btn-default">导出</button></div>
<table class="data-history-table"><thead><tr><th>日期</th><th>开始时间</th><th>结束时间</th><th>运动类型</th><th>时长(分钟)</th><th>距离(km)</th><th>卡路里(千卡)</th></tr></thead><tbody><tr><td>2026-05-12</td><td>07:15</td><td>08:07</td><td>跑步</td><td>52</td><td>5.2</td><td>320</td></tr><tr><td>2026-05-12</td><td>17:30</td><td>17:55</td><td>步行</td><td>25</td><td>1.8</td><td>150</td></tr></tbody></table>
</div>
<!-- 心电图面板 -->
<div id="wt-ecg" style="display:none;">
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;"><div class="filter-item"><label>日期</label><input type="date" class="filter-input" style="min-width:130px;"></div><button class="btn btn-default">导出</button></div>
<table class="data-history-table"><thead><tr><th>测量时间</th><th>测量结果</th><th>测量值(次/分钟)</th><th>操作</th></tr></thead><tbody><tr><td>2026-05-12 14:30</td><td><span class="tag tag-green">窦性心率</span></td><td>75</td><td><span class="link" onclick="openEcgDetail('2026-05-12 14:30','窦性心率',75)">查看</span></td></tr><tr><td>2026-05-12 12:15</td><td><span class="tag tag-red">房颤</span></td><td>100</td><td><span class="link" onclick="openEcgDetail('2026-05-12 12:15','房颤',100)">查看</span></td></tr><tr><td>2026-05-12 10:00</td><td><span class="tag tag-orange">高低心率</span></td><td>112</td><td><span class="link" onclick="openEcgDetail('2026-05-12 10:00','高低心率',112)">查看</span></td></tr><tr><td>2026-05-12 08:45</td><td><span class="tag tag-green">窦性心率</span></td><td>68</td><td><span class="link" onclick="openEcgDetail('2026-05-12 08:45','窦性心率',68)">查看</span></td></tr></tbody></table>
</div>
</div>
<!-- Tab4: 体重监测 -->
<div class="tab-panel" id="panel-weight">
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;">
<div class="filter-item"><label>测量设备</label><select class="filter-select"><option value="">全部</option><option>体重秤</option><option>蓝牙称</option><option>体脂仪</option></select></div>
<div class="filter-item"><label>测量日期</label><input class="filter-input" type="date" style="min-width:130px;"> <span style="color:var(--text-placeholder);font-size:var(--font-sm);"></span> <input class="filter-input" type="date" style="min-width:130px;"></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<div class="table-card">
<div class="table-card__content">
<table>
<thead><tr><th>测量时间</th><th>测量设备</th><th>设备编码</th><th>身高(cm)</th><th>体重(kg)</th><th>BMI</th><th>基础代谢(kcal)</th><th>体脂肪量(kg)</th><th>体脂肪率(%)</th><th>体水分量(kg)</th><th>蛋白质量(kg)</th><th>操作</th></tr></thead>
<tbody>
<tr><td>2026-05-10 08:30</td><td>体重秤</td><td>SC-2024-001</td><td>175</td><td>72.5</td><td>23.7</td><td>1680</td><td>13.2</td><td>18.2</td><td>40.1</td><td>11.6</td><td><a class="link" onclick="openWeightModal(0)">详细信息</a></td></tr>
<tr><td>2026-05-08 07:45</td><td>体脂仪</td><td>FT-2024-002</td><td>175</td><td>72.8</td><td>23.8</td><td>1675</td><td>13.4</td><td>18.4</td><td>39.8</td><td>11.5</td><td><a class="link" onclick="openWeightModal(1)">详细信息</a></td></tr>
<tr><td>2026-05-06 18:20</td><td>蓝牙称</td><td>BT-2024-003</td><td>175</td><td>73.0</td><td>23.8</td><td>1682</td><td>13.5</td><td>18.5</td><td>40.0</td><td>11.6</td><td><a class="link" onclick="openWeightModal(2)">详细信息</a></td></tr>
<tr><td>2026-05-04 08:10</td><td>体重秤</td><td>SC-2024-001</td><td>175</td><td>72.3</td><td>23.6</td><td>1678</td><td>13.1</td><td>18.1</td><td>40.2</td><td>11.7</td><td><a class="link" onclick="openWeightModal(3)">详细信息</a></td></tr>
<tr><td>2026-05-02 09:00</td><td>体脂仪</td><td>FT-2024-002</td><td>175</td><td>73.2</td><td>23.9</td><td>1685</td><td>13.6</td><td>18.6</td><td>39.9</td><td>11.5</td><td><a class="link" onclick="openWeightModal(4)">详细信息</a></td></tr>
</tbody>
</table>
</div>
<div class="pagination"><span>共 142 条</span><button class="page-btn"></button><button class="page-btn active">1</button><button class="page-btn">2</button><button class="page-btn">3</button><button class="page-btn">...</button><button class="page-btn">15</button><button class="page-btn"></button></div>
</div>
</div>
</div>
<!-- 体重详情弹窗 -->
<div class="modal-mask" id="weightModal">
<div class="modal">
<div class="modal__header"><h3 id="weightModalTitle">测量详情</h3><button class="modal__close" onclick="closeWeightModal()">×</button></div>
<div class="modal__body" id="weightModalBody"></div>
</div>
</div>
<!-- 心电图趋势详情弹窗 -->
<div class="modal-mask" id="ecgDetailModal">
<div class="modal" style="width:720px;">
<div class="modal__header"><h3>心电图趋势</h3><button class="modal__close" onclick="document.getElementById('ecgDetailModal').classList.remove('show')">×</button></div>
<div class="modal__body">
<div style="display:flex;gap:24px;margin-bottom:16px;">
<div><span style="font-size:var(--font-xs);color:var(--text-secondary);">测量时间</span><br><span style="font-weight:500;" id="ecgTime"></span></div>
<div><span style="font-size:var(--font-xs);color:var(--text-secondary);">测量结果</span><br><span style="font-weight:500;" id="ecgResult"></span></div>
<div><span style="font-size:var(--font-xs);color:var(--text-secondary);">测量值</span><br><span style="font-weight:500;" id="ecgValue"></span> <span style="font-size:var(--font-xs);color:var(--text-secondary);">次/分钟</span></div>
</div>
<div style="background:#F9FAFB;border-radius:var(--radius-md);padding:16px;border:1px solid var(--border);" id="ecgChartArea"></div>
</div>
</div>
</div>
<!-- 查看问卷弹窗 -->
<div class="modal-mask" id="surveyModal">
<div class="modal" style="width:780px;">
<div class="modal__header">
<h3>问卷详情</h3>
<button class="modal__close" onclick="document.getElementById('surveyModal').classList.remove('show')">×</button>
</div>
<div class="modal__body" style="padding:0;">
<div style="padding:16px 24px 0;font-size:var(--font-sm);color:var(--text-secondary);">记录时间:<span id="surveyTime" style="color:var(--text-primary);font-weight:500;"></span></div>
<div style="padding:16px 24px;">
<table>
<thead><tr><th>问题分类</th><th>问题描述</th><th>员工选项</th></tr></thead>
<tbody id="surveyTbody"></tbody>
</table>
</div>
</div>
<div style="padding:16px 24px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;">
<button class="btn btn-default" onclick="document.getElementById('surveyModal').classList.remove('show')">关闭</button>
</div>
</div>
</div>
<script>
// 主Tab切换
function switchMainPanel(name, el) {
document.querySelectorAll('.tab-bar .tab-item').forEach(function(t){t.classList.remove('active');});
el.classList.add('active');
document.querySelectorAll('.main > .tab-panel').forEach(function(p){p.classList.remove('active');});
document.getElementById('panel-'+name).classList.add('active');
}
// 穿戴监测子Tab
var currentWearableTab = 'wt-heart';
function switchWearableTab(el, tab) {
currentWearableTab = tab;
document.querySelectorAll('.detail-tab').forEach(function(t){t.classList.remove('active');});
el.classList.add('active');
document.getElementById('wt-metric').style.display='none';
document.getElementById('wt-exercise').style.display='none';
document.getElementById('wt-ecg').style.display='none';
if(tab==='wt-exercise'){document.getElementById('wt-exercise').style.display='';}
else if(tab==='wt-ecg'){document.getElementById('wt-ecg').style.display='';}
else{document.getElementById('wt-metric').style.display='';var m=tab.replace('wt-','');updateMetricSummary(m);renderTrendChart();}
}
function switchTime(el){document.querySelectorAll('.overview-time-bar .time-btn').forEach(function(b){b.classList.remove('active');});el.classList.add('active');}
// 指标面板
var isListView=false;
function updateMetricSummary(tab){
var cfg={heart:{v:['92','62','76'],u:['次/分钟','次/分钟','次/分钟'],l:['最高心率','最低心率','平均心率']},oxygen:{v:['99','95','97'],u:['%','%','%'],l:['最高血氧','最低血氧','平均血氧']},stress:{v:['55','18','32'],u:['','',''],l:['最高压力','最低压力','平均压力']},sleep:{v:['7h32m','0h45m','8h17m'],u:['','',''],l:['深睡时长','浅睡时长','总时长']},temp:{v:['36.8','36.2','36.5'],u:['℃','℃','℃'],l:['最高体温','最低体温','平均体温']},steps:{v:['8432','5216','3216'],u:['步','km','千卡'],l:['总步数','总距离','总消耗']},bp:{v:['135/88','112/72','120/78'],u:['mmHg','mmHg','mmHg'],l:['最高血压','最低血压','平均血压']}};
var d=cfg[tab]||cfg.heart;
for(var i=1;i<=3;i++){document.getElementById('msVal'+i).textContent=d.v[i-1];document.getElementById('msUnit'+i).textContent=d.u[i-1];document.getElementById('msLabel'+i).textContent=d.l[i-1];}
}
function switchGranularity(el){document.querySelectorAll('.time-granularity .time-btn').forEach(function(b){b.classList.remove('active');});el.classList.add('active');renderTrendChart();}
function toggleMetricView(){
isListView=!isListView;var btn=document.getElementById('btnToggleList');
if(isListView){document.getElementById('metric-chart-view').style.display='none';document.getElementById('metric-table-view').style.display='';btn.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3h8v8H3z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M3 7h8" stroke="currentColor" stroke-width="1"/></svg> 趋势图展示';}
else{document.getElementById('metric-chart-view').style.display='';document.getElementById('metric-table-view').style.display='none';btn.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 3h10M2 7h10M2 11h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg> 列表展示';renderTrendChart();}
}
function renderTrendChart(){
var c=document.getElementById('trendChart');if(!c)return;
var W=c.offsetWidth||880,H=280;var pad={t:20,r:20,b:40,l:50};var cw=W-pad.l-pad.r,ch=H-pad.t-pad.b;
var data=[72,70,68,66,65,64,66,72,78,82,85,80,76,74,79,83,88,84,80,76,74,72,70,68,66];
var minV=55,maxV=95;
function px(i){return pad.l+i/24*cw;}function py(v){return pad.t+ch-(v-minV)/(maxV-minV)*ch;}
var pts=data.map(function(v,i){return px(i)+','+py(v);}).join(' ');
var areaPath='M'+data.map(function(v,i){return px(i)+','+py(v);}).join(' L')+' L'+px(24)+','+(pad.t+ch)+' L'+pad.l+','+(pad.t+ch)+' Z';
var yLines=[60,70,80,90];var gridLines=yLines.map(function(v){return '<line x1="'+pad.l+'" y1="'+py(v)+'" x2="'+(pad.l+cw)+'" y2="'+py(v)+'" stroke="#f0f0f0" stroke-width="1"/><text x="'+(pad.l-8)+'" y="'+(py(v)+4)+'" text-anchor="end" font-size="11" fill="#999">'+v+'</text>';}).join('');
var xTicks=[0,4,8,12,16,20,24];var xLabels=xTicks.map(function(h){return '<text x="'+px(h)+'" y="'+(H-10)+'" text-anchor="middle" font-size="10" fill="#aaa">'+(h<10?'0':'')+h+':00</text>';}).join('');
c.innerHTML='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'">'+gridLines+'<defs><linearGradient id="ag" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#1890FF" stop-opacity="0.15"/><stop offset="100%" stop-color="#1890FF" stop-opacity="0"/></linearGradient></defs><path d="'+areaPath+'" fill="url(#ag)"/><polyline points="'+pts+'" fill="none" stroke="#1890FF" stroke-width="2"/>'+xLabels+'</svg>';
}
// 心电图
function openEcgDetail(time,result,value){document.getElementById('ecgTime').textContent=time;document.getElementById('ecgResult').textContent=result;document.getElementById('ecgValue').textContent=value;document.getElementById('ecgDetailModal').classList.add('show');renderEcgChart();}
function renderEcgChart(){
var c=document.getElementById('ecgChartArea');if(!c)return;var W=660,H=200;var pad={t:20,r:20,b:30,l:40};var cw=W-pad.l-pad.r,ch=H-pad.t-pad.b;
var points=[];var segLen=cw/5;
for(var s=0;s<5;s++){var ox=pad.l+s*segLen;var baseY=pad.t+ch*0.5;points.push(ox+','+baseY);points.push((ox+segLen*0.1)+','+(baseY-ch*0.08));points.push((ox+segLen*0.18)+','+baseY);points.push((ox+segLen*0.25)+','+(baseY+ch*0.05));points.push((ox+segLen*0.3)+','+(baseY-ch*0.45));points.push((ox+segLen*0.35)+','+(baseY+ch*0.15));points.push((ox+segLen*0.42)+','+baseY);points.push((ox+segLen*0.55)+','+(baseY-ch*0.12));points.push((ox+segLen*0.65)+','+baseY);points.push((ox+segLen*1)+','+baseY);}
var gridH='';for(var i=0;i<=4;i++){var gy=pad.t+ch*i/4;gridH+='<line x1="'+pad.l+'" y1="'+gy+'" x2="'+(W-pad.r)+'" y2="'+gy+'" stroke="#f0f0f0" stroke-width="0.5"/>';}
for(var j=0;j<=10;j++){var gx=pad.l+cw*j/10;gridH+='<line x1="'+gx+'" y1="'+pad.t+'" x2="'+gx+'" y2="'+(H-pad.b)+'" stroke="#f0f0f0" stroke-width="0.5"/>';}
var xLabels='';for(var k=0;k<=5;k++){xLabels+='<text x="'+(pad.l+cw*k/5)+'" y="'+(H-8)+'" text-anchor="middle" font-size="10" fill="#999">'+(k*0.2).toFixed(1)+'s</text>';}
c.innerHTML='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'">'+gridH+xLabels+'<polyline points="'+points.join(' ')+'" fill="none" stroke="#FF4D4F" stroke-width="1.5" stroke-linejoin="round"/></svg>';
}
document.getElementById('ecgDetailModal').addEventListener('click',function(e){if(e.target===this)this.classList.remove('show');});
// 查看问卷弹窗
var surveyAnswers=[
{cat:'饮食习惯',desc:'今天是否按时吃早餐?',answer:'是'},
{cat:'饮食习惯',desc:'今天饮水量是否达到1500ml',answer:'是'},
{cat:'运动锻炼',desc:'今天是否进行了30分钟以上运动?',answer:'否'},
{cat:'睡眠质量',desc:'昨晚睡眠时间是否达到7小时?',answer:'是'},
{cat:'心理状态',desc:'今天是否感到焦虑或压力过大?',answer:'否'}
];
function openSurveyModal(time){
document.getElementById('surveyTime').textContent=time;
var html='';
surveyAnswers.forEach(function(s){html+='<tr><td>'+s.cat+'</td><td>'+s.desc+'</td><td>'+s.answer+'</td></tr>';});
document.getElementById('surveyTbody').innerHTML=html;
document.getElementById('surveyModal').classList.add('show');
}
document.getElementById('surveyModal').addEventListener('click',function(e){if(e.target===this)this.classList.remove('show');});
// 体重监测弹窗
var weightRecords=[
{device:'体重秤',data:{"基础指标":{"身高(cm)":"175","体重(kg)":"72.5","BMI":"23.7","基础代谢":"1,680","体脂肪量(kg)":"13.2","体脂肪率(%)":"18.2"},"水分与蛋白质":{"体水分量(kg)":"40.1","体水分率(%)":"55.3","蛋白质量(kg)":"11.6","蛋白质率(%)":"16.0"},"肌肉与骨骼":{"肌肉率(%)":"77.0","肌肉量(kg)":"55.8","体骨骼量(kg)":"2.9"},"脂肪分析":{"皮下脂肪率(%)":"14.9","内脏脂肪等级":"6","去脂体重(kg)":"59.3"}}},
{device:'体脂仪',data:{"基础指标":{"身高(cm)":"175","体重(kg)":"72.8","BMI":"23.8","基础代谢":"1,675","InBody评分(分)":"65"},"体脂分析":{"体脂肪量(kg)":"13.4","体脂肪率(%)":"18.4","内脏脂肪等级":"6","去脂体重(kg)":"59.4"},"体成分":{"体水分(L)":"43.5","蛋白质(kg)":"11.5","无机盐(kg)":"4.05","骨骼肌量(kg)":"33.2"}}},
{device:'蓝牙称',data:{"基础指标":{"身高(cm)":"175","体重(kg)":"73.0","BMI":"23.8","基础代谢":"1,682","体脂肪量(kg)":"13.5","体脂肪率(%)":"18.5"},"水分与蛋白质":{"体水分量(kg)":"40.0","体水分率(%)":"54.8","蛋白质量(kg)":"11.6","蛋白质率(%)":"15.9"},"肌肉与骨骼":{"体肌肉量(kg)":"55.6","体肌肉率(%)":"76.2","体骨骼量(kg)":"2.9"}}},
{device:'体重秤',data:{"基础指标":{"身高(cm)":"175","体重(kg)":"72.3","BMI":"23.6","基础代谢":"1,678","体脂肪量(kg)":"13.1","体脂肪率(%)":"18.1"},"水分与蛋白质":{"体水分量(kg)":"40.2","体水分率(%)":"55.6","蛋白质量(kg)":"11.7","蛋白质率(%)":"16.2"},"肌肉与骨骼":{"肌肉率(%)":"77.3","肌肉量(kg)":"55.9","体骨骼量(kg)":"3.0"}}},
{device:'体脂仪',data:{"基础指标":{"身高(cm)":"175","体重(kg)":"73.2","BMI":"23.9","基础代谢":"1,685","InBody评分(分)":"63"},"体脂分析":{"体脂肪量(kg)":"13.6","体脂肪率(%)":"18.6","内脏脂肪等级":"6","去脂体重(kg)":"59.6"},"体成分":{"体水分(L)":"43.8","蛋白质(kg)":"11.5","无机盐(kg)":"4.08","骨骼肌量(kg)":"33.4"}}}
];
function openWeightModal(idx){
var r=weightRecords[idx];document.getElementById('weightModalTitle').textContent='测量详情 - '+r.device;
var html='';for(var section in r.data){html+='<div class="detail-section"><div class="detail-section__title">'+section+'</div><div class="detail-grid">';var items=r.data[section];for(var k in items){html+='<div class="detail-item"><span class="detail-item__label">'+k+'</span><span class="detail-item__value">'+items[k]+'</span></div>';}html+='</div></div>';}
document.getElementById('weightModalBody').innerHTML=html;document.getElementById('weightModal').classList.add('show');
}
function closeWeightModal(){document.getElementById('weightModal').classList.remove('show');}
document.getElementById('weightModal').addEventListener('click',function(e){if(e.target===this)closeWeightModal();});
// URL参数回显
(function(){
var params=new URLSearchParams(location.search);
var id=params.get('id');
var empData={EMP001:{name:'张伟',unit:'长庆油田总部',dept:'研发部'},EMP002:{name:'李娜',unit:'长庆油田总部',dept:'市场部'},EMP005:{name:'陈刚',unit:'长庆油田总部',dept:'人事部'},EMP003:{name:'王磊',unit:'采油一厂',dept:'运营部'},EMP004:{name:'赵敏',unit:'采油二厂',dept:'财务部'}};
var emp=empData[id]||empData.EMP001;
document.getElementById('empAvatar').textContent=emp.name.charAt(0);
document.getElementById('empName').textContent=emp.name;
document.getElementById('empCode').textContent='员工编号:'+(id||'EMP001');
document.getElementById('empUnit').textContent=emp.unit;
document.getElementById('empDept').textContent=emp.dept;
})();
// 穿戴监测默认渲染心率图表(切到该tab时触发)
var origSwitch=switchMainPanel;
switchMainPanel=function(name,el){origSwitch(name,el);if(name==='wearable'){updateMetricSummary('heart');setTimeout(renderTrendChart,50);}};
</script>
</body>
</html>
@@ -77,6 +77,7 @@ tbody tr:hover{background:#F8FBFF;}
.tag-orange{background:#FFF7E6;color:var(--warning);}
.tag-red{background:#FFF1F0;color:var(--danger);}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;}.link:hover{color:#40a9ff;}
.tag-A{background:#FFF1F0;color:#FF4D4F;}.tag-B{background:#FFF7E6;color:#FA8C16;}.tag-C{background:#FFFBE6;color:#FAAD14;}.tag-D{background:#E6F7FF;color:#1890FF;}.tag-E{background:#F6FFED;color:#52C41A;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
@@ -167,6 +168,7 @@ tbody tr:hover{background:#F8FBFF;}
<th rowspan="2">员工编号</th>
<th rowspan="2">所属单位</th>
<th rowspan="2">所属部门</th>
<th rowspan="2">健康现状</th>
<th colspan="2">健康排查</th>
<th colspan="2">穿戴监测</th>
<th colspan="6">体重监测</th>
@@ -182,38 +184,49 @@ tbody tr:hover{background:#F8FBFF;}
<th>最初BMI ↕</th>
<th>最终BMI ↕</th>
<th>BMI变化 ↕</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>张伟</td><td>EMP001</td><td>长庆油田总部</td><td>研发部</td>
<td><span class="tag tag-E">E类</span></td>
<td>92</td><td>128</td>
<td><span class="tag tag-green"></span></td><td>2</td>
<td>72.5</td><td>70.2</td><td>-2.3</td><td>24.1</td><td>23.4</td><td>-0.7</td>
<td><a class="link" href="archive-personal-detail.html?id=EMP001">详细信息</a></td>
</tr>
<tr>
<td>李娜</td><td>EMP002</td><td>长庆油田总部</td><td>市场部</td>
<td><span class="tag tag-D">D类</span></td>
<td>88</td><td>125</td>
<td><span class="tag tag-green"></span></td><td>1</td>
<td>58.3</td><td>57.8</td><td>-0.5</td><td>21.8</td><td>21.6</td><td>-0.2</td>
<td><a class="link" href="archive-personal-detail.html?id=EMP002">详细信息</a></td>
</tr>
<tr>
<td>陈刚</td><td>EMP005</td><td>长庆油田总部</td><td>人事部</td>
<td><span class="tag tag-B">B类</span></td>
<td>78</td><td>120</td>
<td><span class="tag tag-green"></span></td><td>3</td>
<td>78.9</td><td>76.5</td><td>-2.4</td><td>26.1</td><td>25.3</td><td>-0.8</td>
<td><a class="link" href="archive-personal-detail.html?id=EMP005">详细信息</a></td>
</tr>
<tr>
<td>王磊</td><td>EMP003</td><td>采油一厂</td><td>运营部</td>
<td><span class="tag tag-C">C类</span></td>
<td>76</td><td>98</td>
<td><span class="tag tag-orange"></span></td><td>0</td>
<td>85.6</td><td>83.4</td><td>-2.2</td><td>28.2</td><td>27.5</td><td>-0.7</td>
<td><a class="link" href="archive-personal-detail.html?id=EMP003">详细信息</a></td>
</tr>
<tr>
<td>赵敏</td><td>EMP004</td><td>采油二厂</td><td>财务部</td>
<td><span class="tag tag-A">A类</span></td>
<td>95</td><td>131</td>
<td><span class="tag tag-green"></span></td><td>0</td>
<td>62.1</td><td>61.5</td><td>-0.6</td><td>23.2</td><td>23.0</td><td>-0.2</td>
<td><a class="link" href="archive-personal-detail.html?id=EMP004">详细信息</a></td>
</tr>
</tbody>
</table>
@@ -158,6 +158,7 @@ tbody tr:hover{background:#F8FBFF;}
<th>员工编号</th>
<th>部门名称</th>
<th>积分</th>
<th>总签到天数</th>
<th>连续签到天数</th>
<th>最新签到时间</th>
</tr>
@@ -169,6 +170,7 @@ tbody tr:hover{background:#F8FBFF;}
<td>EMP001</td>
<td>研发部</td>
<td>92</td>
<td>156</td>
<td>128</td>
<td>2026-05-13 08:30</td>
</tr>
@@ -178,6 +180,7 @@ tbody tr:hover{background:#F8FBFF;}
<td>EMP002</td>
<td>市场部</td>
<td>88</td>
<td>142</td>
<td>125</td>
<td>2026-05-13 09:15</td>
</tr>
@@ -187,6 +190,7 @@ tbody tr:hover{background:#F8FBFF;}
<td>EMP003</td>
<td>运营部</td>
<td>76</td>
<td>113</td>
<td>98</td>
<td>2026-05-13 07:45</td>
</tr>
@@ -125,8 +125,8 @@ tbody tr:hover{background:#F8FBFF;}
<div class="tab-bar">
<div class="tab-item active" onclick="switchTab('inbound',this)">入库设备</div>
<div class="tab-item" onclick="switchTab('allocated',this)">分配设备</div>
<div class="tab-item" onclick="switchTab('exception-times',this)">异常次数</div>
<div class="tab-item" onclick="switchTab('exception-people',this)">异常人数</div>
<div class="tab-item" onclick="switchTab('exception-times',this)">告警次数</div>
<div class="tab-item" onclick="switchTab('exception-people',this)">告警人数</div>
</div>
<!-- 入库设备面板 -->
@@ -374,9 +374,21 @@ tbody tr:hover{background:#F8FBFF;}
<label>员工姓名</label>
<input class="filter-input" type="text" placeholder="请输入员工姓名">
</div>
<div class="filter-item">
<div class="filter-item">
<label>员工编号</label>
<input class="filter-input" type="text" placeholder="请输入员工姓名">
</div>
<div class="filter-item">
<label>告警类型</label>
<select class="filter-select">
<option>全部</option>
<option>一键告警</option>
<option>心率异常</option>
<option>血氧异常</option>
<option>体温异常</option>
<option>压力异常</option>
<option>摔倒</option>
</select>
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
@@ -386,7 +398,7 @@ tbody tr:hover{background:#F8FBFF;}
</div>
<div class="table-card">
<div class="table-card__header">
<span class="table-card__title">异常员工列表</span>
<span class="table-card__title">告警员工列表</span>
<span style="font-size:var(--font-sm);color:var(--text-secondary);">共 4 条</span>
</div>
<div class="table-card__content">
@@ -397,9 +409,9 @@ tbody tr:hover{background:#F8FBFF;}
<th>员工姓名</th>
<th>员工编号</th>
<th>部门名称</th>
<th>异常次数</th>
<th>最后异常时间</th>
<th>异常类型</th>
<th>告警次数</th>
<th>最后告警时间</th>
<th>告警类型</th>
<th>告警位置</th>
</tr>
</thead>
+2 -2
View File
@@ -182,8 +182,8 @@ tbody tr.stat-row:hover{background:#E6F0FF;}
<th>未签到人数 ↕</th>
<th>入库设备数 ↕</th>
<th>分配设备数 ↕</th>
<th>异常次数 ↕</th>
<th>异常人数 ↕</th>
<th>告警次数 ↕</th>
<th>告警人数 ↕</th>
<th>体重秤设备数 ↕</th>
<th>蓝牙称设备数 ↕</th>
<th>体脂仪设备数 ↕</th>
+51 -52
View File
@@ -25,15 +25,15 @@
│ ├── 问卷评分
│ ├── 问卷模板
│ ├── 签到格言
│ ├── 员工签到记录
│ └── 部门签到记录
│ ├── 员工排查统计
│ └── 单位排查统计
├── 穿戴监测
│ ├── 工具管理
│ ├── 绑定记录
│ ├── 用户数据
│ ├── 异常事件
│ ├── 告警记录
│ ├── 通知消息
│ ├── 部门数据统计
│ ├── 单位数据统计
│ ├── 部门报表
│ └── 个人数据统计
├── 体重监测
@@ -57,7 +57,6 @@
│ ├── APP记录
│ ├── 历史数据 → 监测详情
│ └── 历史报警 → 报警详情
│ └── 花粉知识(仅花粉)
└── 异常事件
├── 系统脏数据
└── 结果异常
@@ -93,8 +92,8 @@
| 12 | 问卷评分 | 问卷评分配置 | health-checkup/survey-score.html | 页面 |
| 13 | 问卷模板 | 问卷模板列表 | health-checkup/survey-template.html | 页面 |
| 14 | 签到格言 | 签到格言管理 | health-checkup/maxim.html | 页面 |
| 15 | 员工签到记录 | 员工签到记录 | health-checkup/record-user.html | 页面 |
| 16 | 部门签到记录 | 部门签到记录 | health-checkup/record-dept.html | 页面 |
| 15 | 员工排查统计 | 员工排查统计 | health-checkup/record-user.html | 页面 |
| 16 | 单位排查统计 | 单位排查统计 | health-checkup/record-dept.html | 页面 |
### 穿戴监测
@@ -103,9 +102,9 @@
| 17 | 工具管理 | 设备管理列表 | wearable/tool-manage.html | 页面 |
| 18 | 绑定记录 | 绑定记录列表 | wearable/bind-record.html | 页面 |
| 19 | 用户数据 | 用户监测数据 | wearable/user-data.html | 页面 |
| 20 | 异常事件 | 告警事件列表 | wearable/exception-event.html | 页面 |
| 20 | 告警记录 | 告警事件列表 | wearable/exception-event.html | 页面 |
| 21 | 通知消息 | 通知消息列表 | wearable/notification.html | 页面 |
| 22 | 部门数据统计 | 部门数据统计 | wearable/dept-stat.html | 页面 |
| 22 | 单位数据统计 | 单位数据统计 | wearable/dept-stat.html | 页面 |
| 23 | 部门报表 | 部门报表 | wearable/dept-report.html | 页面 |
| 24 | 个人数据统计 | 个人数据统计 | wearable/personal-stat.html | 页面 |
@@ -174,7 +173,6 @@
| 78 | 花粉-历史数据 | 花粉历史数据 | environment/pollen-history-data.html | 页面 |
| 79 | | 花粉历史监测详情 | environment/pollen-history-monitor-detail.html | 页面 |
| 80 | | 花粉历史报警详情 | environment/pollen-history-alarm-detail.html | 页面 |
| 81 | 花粉知识 | 花粉知识管理 | environment/pollen-knowledge.html | 页面 |
### 异常事件
@@ -200,15 +198,16 @@
**筛选条件:** 姓名/员工编号(输入框)、单位部门(下拉)、年份(下拉,默认当年)
**表格列(多级表头):**
- 第一级:姓名、员工编号、所属单位、所属部门、健康排查、穿戴监测、体重监测
- 第一级:姓名、员工编号、所属单位、所属部门、健康现状、健康排查、穿戴监测、体重监测、操作
- 健康现状:A~E类标签(A类红色、B类橙色、C类黄色、D类蓝色、E类绿色)
- 健康排查子列:积分、连续签到天数
- 穿戴监测子列:绑定设备、异常次数
- 体重监测子列:最初体重(kg)、最终体重(kg)、体重变化(kg)、最初BMI、最终BMI、BMI变化
**交互说明:**
- 操作列"详细信息"跳转到个人监测数据详情页(archive-personal-detail.html
- 支持导出列表数据
- 各数据列支持排序
- 点击行可跳转到对应模块的个人详情
---
@@ -272,7 +271,7 @@
**功能说明:** 展示指定单位/部门的穿戴监测详情,包含设备入库、分配、异常次数和异常人数四个维度。
**Tab切换:** 入库设备 / 分配设备 / 异常次数 / 异常人数
**Tab切换:** 入库设备 / 分配设备 / 告警次数 / 告警人数
**入库设备筛选:** 设备编码(输入框)
**入库设备表格列:** 序号、设备编码、设备名称、设备类型、厂商、入库时间
@@ -280,11 +279,11 @@
**分配设备筛选:** 员工姓名(输入框)
**分配设备表格列:** 序号、员工姓名、员工编号、部门名称、设备编码、设备类型、分配日期
**异常次数筛选:** 员工姓名(输入框)、员工编号(输入框)、告警类型(下拉:全部/一键告警/心率异常/血氧异常/体温异常/压力异常/摔倒)
**异常次数表格列:** 序号、员工姓名、员工编号、设备编码、告警类型、告警值、参考范围、告警时间、告警位置
**告警次数筛选:** 员工姓名(输入框)、员工编号(输入框)、告警类型(下拉:全部/一键告警/心率异常/血氧异常/体温异常/压力异常/摔倒)
**告警次数表格列:** 序号、员工姓名、员工编号、设备编码、告警类型、告警值、参考范围、告警时间、告警位置
**异常人数筛选:** 员工姓名(输入框)、员工编号(输入框)
**异常人数表格列:** 序号、员工姓名、员工编号、部门名称、总异常次数、最后异常时间、异常类型、告警位置
**告警人数筛选:** 员工姓名(输入框)、员工编号(输入框)、告警类型(下拉:全部/一键告警/心率异常/血氧异常/体温异常/压力异常/摔倒)
**告警人数表格列:** 序号、员工姓名、员工编号、部门名称、总告警次数、最后告警时间、告警类型、告警位置
**交互说明:**
- 面包屑导航:单位报表数据 / 单位名称 / 穿戴监测
@@ -499,34 +498,40 @@
---
### 15. 员工签到记录health-checkup/record-user.html
### 15. 员工排查统计health-checkup/record-user.html
![页面截图](screenshots/health-checkup/record-user.jpg)
**功能说明:** 查看员工健康排查签到的明细记录,支持按员工、单位、时间筛选。
**功能说明:** 查看员工健康排查的明细记录,支持按员工、单位、时间筛选。
**筛选条件:** 姓名(输入框)、员工编号(输入框)、单位部门(下拉)
**表格列:** 姓名、员工编号、单位名称、部门名称、总计积分、连续签到天数、最新签到时间、操作
**表格列:** 姓名、员工编号、单位名称、部门名称、总计积分、连续记录天数、最新记录时间、操作
**交互说明:**
- 操作列:查看签到明细
- 操作列:按月导出、排查记录
- 点击"排查记录"打开排查记录弹窗
- 支持导出列表数据
**排查记录弹窗:**
- 顶部信息卡:姓名、编号、单位、部门、总积分、连续记录天数
- 表格列:记录时间、增加积分、操作
- 操作列:查看问卷(打开问卷详情弹窗)
---
### 16. 部门签到记录health-checkup/record-dept.html
### 16. 单位排查统计health-checkup/record-dept.html
![页面截图](screenshots/health-checkup/record-dept.jpg)
**功能说明:** 以部门为维度统计健康排查签到情况,展示各部门签到率。
**功能说明:** 以部门为维度统计健康排查记录情况,展示各部门记录率。
**筛选条件:** 筛选日期(日期选择)、单位(下拉)
**筛选条件:** 记录日期(日期选择)、单位(下拉)
**表格列:** 单位名称、员工总数、已签到人数、未签到人数、签到日期、操作
**表格列:** 单位名称、员工总数、已记录人数、未记录人数、记录日期、操作
**交互说明:**
- 操作列:查看签到明细(弹窗展示员工签到详情
- 操作列:查看排查记录(弹窗展示员工排查详情,含已记录/未记录Tab
- 支持导出列表数据
---
@@ -610,12 +615,12 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
- 血压:最近测量值(大数字),子项:最高、最低
- 心电图:最近测量结论(文字),子项:今日测量次数、最近心率
- **指标详情面板(心率/血氧/压力/睡眠/体温/步数/血压):** 时间粒度选择(日/周/月)+ 视图切换(趋势图/列表)+ 图表区域(折线图,含最大值/最小值/平均值标注)
- **锻炼面板:** 表格列——开始时间、结束时间、运动类型、运动时长、运动距离、消耗卡路里
- **锻炼面板:** 表格列——日期、开始时间、结束时间、运动类型、运动时长、运动距离、消耗卡路里
- **心电图面板:** 表格列——测量时间、心率、心电结论;点击"查看详情"打开心电图趋势详情弹窗(含波形图)
---
### 20. 异常事件wearable/exception-event.html
### 20. 告警记录wearable/exception-event.html
![页面截图](screenshots/wearable/exception-event.jpg)
@@ -669,7 +674,7 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
---
### 22. 部门数据统计(wearable/dept-stat.html
### 22. 单位数据统计(wearable/dept-stat.html
![页面截图](screenshots/wearable/dept-stat.jpg)
@@ -756,9 +761,14 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
**交互说明:**
- 点击"新增"打开新增设备弹窗
- 操作列:编辑、删除
- 操作列:详情、成员查看、编辑、删除
- 点击"成员查看"打开成员查看弹窗(展示设备绑定成员列表)
- 支持导出列表数据
**成员查看弹窗:**
- 顶部设备信息栏:设备编码、设备名称、所属单位、布放位置
- 成员表格列:序号、员工姓名、性别、年龄、所属单位、所属部门、绑定状态(标签)
#### 新增设备弹窗(scale-device-add / bluetooth-device-add / fat-device-add / sport-device-add
**功能说明:** 录入新设备信息,支持地图选点定位布放位置。
@@ -885,7 +895,16 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
**表格列:** 测量时间、测量设备、设备编码、身高、体重、BMI、基础代谢、体脂肪量、体脂肪率、体水分量、蛋白质量、操作
**交互说明:**
- 点击"查看"打开测量详情弹窗
- 点击"详细信息"打开测量详情弹窗
**测量详情弹窗:**
- 标题动态显示设备类型(如"测量详情 - 体重秤"
- 按分类分组展示完整体成分数据,每组有独立标题(左侧蓝色竖线标识)
- 数据以三列网格布局展示,每项含标签和数值
- 不同设备类型展示不同分组:
- 体重秤:基础指标、水分与蛋白质、肌肉与骨骼、脂肪分析、其他
- 蓝牙称:基础指标、水分与蛋白质、肌肉与骨骼、脂肪分析、细胞液、其他
- 体脂仪:基础指标、体脂分析、体成分、体重管理、节段脂肪、节段肌肉
---
@@ -1023,7 +1042,7 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
**筛选条件:** 设备编码(输入框)、报警类型(下拉)、报警时间(日期范围)
**表格列:** 序号、设备名称、设备编码、监测类型、报警值、阈值、报警时间、报警位置、操作
**表格列:** 序号、设备名称、设备编码、设备分类、所属单位、设备位置、监测类型、单位、阈值下限、阈值上限、异常值、报警时间、操作
**交互说明:**
- 支持导出列表数据
@@ -1103,26 +1122,6 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
---
### 81. 花粉知识(environment/pollen-knowledge.html
![页面截图](screenshots/environment/pollen-knowledge.jpg)
**功能说明:** 管理花粉相关的科普知识文章,供员工端APP展示。仅花粉监测类型独有此页面。
**筛选条件:** 文章标题(输入框)
**表格列:** 文章标题、封面图、文章内容(预览)、创建日期、查看次数、排序、状态(标签)、操作
**业务规则:**
- 状态标签:已发布(绿色)、草稿(灰色)
**交互说明:**
- 点击"新增"打开右侧抽屉弹窗(含富文本编辑器)
- 点击"编辑"打开编辑抽屉弹窗
- 操作列:编辑、删除
---
### 各类型差异补充说明
以下列出室外、水质、花粉与室内基准页面的具体差异:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

@@ -0,0 +1,156 @@
# 健康监测模块截图更新清单
本次代码变更涉及以下页面需要重新截图,请按照清单逐一完成。
## 一、档案模块
### 1. 个人监测数据 (archive-personal.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\archive-personal.html`
**截图保存:** `doc/screenshots/archive-personal.jpg`
**变更内容:**
- 表格新增「健康现状」列(显示A~E类标签,不同颜色)
- 表格新增「操作」列(含"详细信息"链接)
**截图要求:** 确保表格完整显示,包含新增的两列
---
### 2. 穿戴监测详情 (archive-unit-wearable-detail.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\archive-unit-wearable-detail.html`
**截图保存:** `doc/screenshots/archive-unit-wearable-detail.jpg`
**变更内容:**
- Tab标签名称变更:"异常次数" → "告警次数""异常人数" → "告警人数"
- "告警次数"Tab 新增「告警类型」筛选下拉框
- 表格列名变更:"总异常次数" → "总告警次数""最后异常时间" → "最后告警时间""异常类型" → "告警类型"
**截图要求:** 切换到"告警次数"或"告警人数"Tab,确保新名称清晰可见
---
### 3. 算法模型 (archive-algorithm.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\archive-algorithm.html`
**截图保存:** `doc/screenshots/archive-algorithm.jpg`
**变更内容:**
- 新增"新增"按钮功能(打开弹窗)
- 操作列"预览"和"编辑"功能完善
**截图要求:** 主列表页面截图即可(弹窗功能可选)
---
## 二、健康排查模块
### 4. 员工排查统计 (record-user.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\health-checkup\record-user.html`
**截图保存:** `doc/screenshots/health-checkup/record-user.jpg`
**变更内容:**
- 表格列名变更:"连续签到天数" → "连续记录天数""最新签到时间" → "最新记录时间"
- 操作列变更:"签到记录" → "排查记录"
**截图要求:** 确保表格列名清晰可见
---
### 5. 单位排查统计 (record-dept.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\health-checkup\record-dept.html`
**截图保存:** `doc/screenshots/health-checkup/record-dept.jpg`
**变更内容:**
- 筛选条件:"筛选日期" → "记录日期"
- 表格列名变更:"已签到人数" → "已记录人数""未签到人数" → "未记录人数""签到日期" → "记录日期"
**截图要求:** 确保筛选栏和表格列名都清晰可见
---
## 三、穿戴监测模块
### 6. 告警记录 (exception-event.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\wearable\exception-event.html`
**截图保存:** `doc/screenshots/wearable/exception-event.jpg`
**变更内容:**
- 侧边栏菜单名称:"异常事件" → "告警记录"
**截图要求:** 确保侧边栏菜单名称清晰可见
---
### 7. 用户数据 (user-data.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\wearable\user-data.html`
**截图保存:** `doc/screenshots/wearable/user-data.jpg`
**变更内容:**
- 侧边栏菜单名称:"异常事件" → "告警记录"
- 数据详情弹窗 → 锻炼面板表格新增「日期」列
**截图要求:** 主页面截图即可(弹窗内容可选)
---
## 四、体重监测模块
### 8. 蓝牙称设备 (bluetooth-device.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\weight\bluetooth-device.html`
**截图保存:** `doc/screenshots/weight/bluetooth-device.jpg`
**变更内容:**
- 操作列新增"成员查看"链接(位于"详情"和"编辑"之间)
**截图要求:** 确保操作列完整显示,包含新增的"成员查看"链接
---
### 9. 员工数据记录 (employee-data-record.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\weight\employee-data-record.html`
**截图保存:** `doc/screenshots/weight/employee-data-record.jpg`
**变更内容:**
- 操作列:"查看" → "详细信息"
- 测量详情弹窗样式重构(分组展示,网格布局)
**截图要求:** 主页面截图即可(弹窗样式可选)
---
## 五、环境监测模块
### 10. 室内异常报警 (indoor-exception-alarm.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\environment\indoor-exception-alarm.html`
**截图保存:** `doc/screenshots/environment/indoor-exception-alarm.jpg`
**变更内容:**
- 侧边栏移除"花粉-知识科普"菜单项
- 表格列变更:"阈值"拆分为"阈值下限"和"阈值上限"两列
**截图要求:** 确保表格列完整显示,包含拆分后的两列
---
### 11. 室外异常报警 (outdoor-exception-alarm.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\environment\outdoor-exception-alarm.html`
**截图保存:** `doc/screenshots/environment/outdoor-exception-alarm.jpg`
**变更内容:** 同室内异常报警
---
### 12. 水质异常报警 (water-exception-alarm.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\environment\water-exception-alarm.html`
**截图保存:** `doc/screenshots/environment/water-exception-alarm.jpg`
**变更内容:** 同室内异常报警
---
### 13. 花粉异常报警 (pollen-exception-alarm.html)
**文件路径:** `D:\AI\work\platform-prototype\web-admin\health-monitor\environment\pollen-exception-alarm.html`
**截图保存:** `doc/screenshots/environment/pollen-exception-alarm.jpg`
**变更内容:** 同室内异常报警
---
## 截图规范
1. **浏览器:** 使用 Chrome 浏览器,窗口最大化
2. **分辨率:** 1920×1080 或更高
3. **格式:** JPG 格式,质量 85%
4. **内容:** 确保页面主要内容完整,包含侧边栏、顶部导航、筛选栏、表格等
5. **数据:** 使用页面现有的模拟数据即可
6. **命名:** 严格按照上述文件名保存
## 完成后
所有截图完成后,PRD 文档中的截图引用路径已经正确配置,无需修改 Markdown 文件。
@@ -147,7 +147,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -161,7 +161,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -149,7 +149,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -161,7 +161,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -161,7 +161,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -134,7 +134,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -170,7 +169,8 @@
<th style="min-width:140px;">设备位置</th>
<th style="min-width:80px;">监测类型</th>
<th style="min-width:80px;">单位</th>
<th style="min-width:70px;">阈值</th>
<th style="min-width:80px;">阈值下限</th>
<th style="min-width:80px;">阈值上限</th>
<th style="min-width:80px;">异常值</th>
<th style="min-width:160px;">报警时间</th>
</tr>
@@ -183,6 +183,7 @@
<td>办公楼1层大厅</td>
<td>甲醛</td>
<td>mg/m³</td>
<td>0</td>
<td>0.08</td>
<td class="danger-text">0.15</td>
<td>2026-05-15 09:12:36</td>
@@ -194,6 +195,7 @@
<td>办公楼2层会议室</td>
<td>PM2.5</td>
<td>μg/m³</td>
<td>0</td>
<td>75</td>
<td class="danger-text">128</td>
<td>2026-05-15 08:45:20</td>
@@ -205,6 +207,7 @@
<td>地下车库B1层</td>
<td>CO₂</td>
<td>ppm</td>
<td>400</td>
<td>1000</td>
<td class="danger-text">1350</td>
<td>2026-05-14 16:30:15</td>
@@ -216,6 +219,7 @@
<td>生产车间1号</td>
<td>温度</td>
<td></td>
<td>16</td>
<td>35</td>
<td class="danger-text">38.6</td>
<td>2026-05-14 14:22:08</td>
@@ -227,6 +231,7 @@
<td>新装修办公室3层</td>
<td>PM10</td>
<td>μg/m³</td>
<td>0</td>
<td>150</td>
<td class="danger-text">203</td>
<td>2026-05-14 11:05:42</td>
@@ -135,7 +135,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -127,7 +127,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -139,7 +139,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -141,7 +141,6 @@
<a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a>
<a href="pollen-app-record.html" class="sidebar-item">应用记录</a>
<a href="pollen-history-data.html" class="sidebar-item">历史数据</a>
<a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>
@@ -129,7 +129,6 @@
<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="pollen-device-list.html" class="sidebar-item">设备列表</a><a href="pollen-device-category.html" class="sidebar-item">设备分类</a><a href="pollen-data-monitor.html" class="sidebar-item">数据监测</a><a href="pollen-exception-alarm.html" class="sidebar-item">异常报警</a><a href="pollen-app-record.html" class="sidebar-item">应用记录</a><a href="pollen-history-data.html" class="sidebar-item">历史数据</a><a href="pollen-knowledge.html" class="sidebar-item">知识科普</a>
</div>
</div>
</aside>

Some files were not shown because too many files have changed in this diff Show More