feat: 健康监测模块同步—清理废弃页面、补充环境监测页面、导航页与PRD对齐

- 删除 web-admin 健康监测 7 个废弃页面(survey-question/score/template、maxim、record-user/dept)
- 删除 employee-app 健康监测 5 个废弃健康排查页面与 3 个废弃体重监测截图
- 新增 employee-app 环境监测多分类截图(indoor/outdoor/water/pollen 各维度细分)
- 更新 index.html 导航页:健康监测条目对齐磁盘实际文件,web 端标记为已完成
- 同步更新 PRD、design-overview、CHANGELOG 及相关页面截图

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
liuyunqin
2026-06-09 17:53:44 +08:00
co-authored by Claude Opus 4.7
parent 7cd7e83679
commit 9b57f6164e
162 changed files with 1191 additions and 3373 deletions
+3 -3
View File
@@ -23,11 +23,11 @@ commit: b4373f3
- `health-consult/` — 健康咨询(32 页)✅
- `emergency-medical/` — 应急就医(25+ 页)✅
- `health-checkup/` — 健康体检(20+ 页)✅
- `health-monitor/` — 健康监测(25+ 页)🔨 设计中
- `health-monitor/` — 健康监测(20+ 页)🔨 设计中
- `wearable/` — 穿戴设备监测(16 页:血氧/心率/压力/体温/步数/运动/睡眠/血压/心电+告警)
- `weight/` — 体重监测(3 页:主页/历史/报告详情)
- `health-screening/` — 健康筛查/每日打卡(5 页:首页/问卷填写/问卷设置/问卷详情/打卡记录)
- `environment/` — 环境监测(3 页:入口/设备详情/报警数据)
- `health-checkup/` — 健康排查V2(活动列表/活动详情/问卷填写)
- `index.html` — 健康监测入口页
### Web 管理端(web-admin
@@ -69,7 +69,7 @@ Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、
- 2026-05-21:健康生产模块交互完善——余量处置重构(筛选栏级联+更多下拉+员工福利领取记录弹窗),上餐监控操作列合并为更多hover下拉+上餐弹窗自动回填,生产统计日期范围选择器默认当月
- 2026-05-20:营养用餐模块交互完善——用户管理等级配置tab增加新增/查看/编辑弹窗,消费管理筛选优化(日期默认今天、金额范围双输入框、消费类型标签化、单位部门级联筛选、流水号筛选、交易明细弹窗全字段对齐),取餐管理新增总金额列和餐品金额列
- 2026-05-19:卫生供应模块8页面交互优化(侧边栏菜单顺序统一、多页面新增弹窗表单、表格列重构、溯源码前移、菜品名称格式统一),补充营养管理功能说明文档及调整记录
- 2026-05-14:员工端健康监测模块大量新增(穿戴设备16页、体重3页、健康筛查5页),Web管理端健康监测模块新增(穿戴8页、体重20页、环境25页、健康排查7页、档案/异常5页),健康体检检前专项配置及异常事件优化
- 2026-05-14:员工端健康监测模块大量新增(穿戴设备16页、体重3页、环境监测3页、健康排查V2),Web管理端健康监测模块新增(穿戴8页、体重20页、环境25页、健康排查7页、档案/异常5页),健康体检检前专项配置及异常事件优化
- 2026-05-14:小程序新增今日食谱展示页、购物车页,做-社区模块重构(make-home重构+创作排行/分享赚钱/热门创作子页面),创作页和详情页支持图片视频上传展示,多页面交互优化(扫码提示、食谱卡片价格/热量格式),页面总数21→26
- 2026-05-14:营养管理异常事件页面重构(脏数据/结果异常去统计卡片、筛选项/表格列重新设计),多页面交互优化(数据/模型/终端/订单等),删除用户管理弹窗,SITE_MAP 同步清理
- 2026-05-13:营养管理健康生产模块交互优化(食谱规划/组配/烹制/上餐/余量/统计页面去统计卡片、操作按钮标准化、新增多个业务弹窗),小程序原型迁移(21页)
+313 -202
View File
@@ -2,19 +2,19 @@
## 模块概述
健康监测员工端为员工提供个人健康数据的查看与管理能力,涵盖健康排查打卡签到、穿戴设备监测数据查看(心率/血氧/血压/体温/压力/步数/睡眠/心电图/锻炼)、体重监测数据与报告、环境监测设备数据查看与报警记录等功能。
健康监测员工端为员工提供个人健康数据的查看与管理能力,涵盖健康现状仪表盘、健康排查活动参与、穿戴设备监测数据查看(心率/血氧/血压/体温/压力/步数/睡眠/心电图/锻炼)、环境监测设备数据查看与报警记录等功能。
### 页面导航结构
```
健康监测(员工端)
├── 健康监测主页
├── 健康现状(仪表盘)
├── 健康排查
│ ├── 健康排查首页(积分/排行榜
│ ├── 每日健康记录(问卷填写)
│ ├── 历史记录(日历+列表
── 问卷详情(只读回顾)
│ └── 题目设置(自定义显示)
│ ├── 活动列表(已参加/待参加
│ ├── 活动详情
│ ├── 活动填写(问卷填写
── 问卷填写
├── 穿戴监测
│ ├── 穿戴监测首页(指标卡片)
│ ├── 心率详情 → 心率预警历史
@@ -26,14 +26,14 @@
│ ├── 睡眠详情
│ ├── 心电图列表 → 心电图详情
│ └── 锻炼记录
── 体重监测
│ ├── 体重监测首页
│ ├── 历史数据(图表/列表)
│ └── 详细报告
└── 环境监测
── 环境监测(Tab 切换:室外空气 / 室内空气 / 水质监测 / 花粉监测)
├── 环境监测首页(地图/列表)
├── 设备详情
└── 报警数据
│ ├── 室外空气(6级评价 · 7项指标)
│ ├── 室内空气(5级评价 · 6项指标)
│ ├── 水质监测(达标/未达标 · 6项指标)
│ └── 花粉监测(5级评价 · 8种花粉+总数)
├── 设备详情(依 Tab 切换指标下拉、阈值线与等级文案)
└── 报警数据(依 Tab 切换可选报警类型与阈值范围)
```
## 页面清单
@@ -44,15 +44,20 @@
|------|-----------|------|------|
| 1 | 健康监测主页 | index.html | 页面 |
### 健康现状
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 2 | 健康现状 | health-status.html | 页面 |
### 健康排查
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 2 | 健康排查首页 | health-screening/index.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 | 页面 |
| 3 | 活动列表 | health-checkup/index.html | 页面 |
| 4 | 活动详情 | health-checkup/activity-detail.html | 页面 |
| 5 | 活动填写 | health-checkup/activity-fill.html | 页面 |
| 6 | 问卷填写 | health-checkup/questionnaire-fill.html | 页面 |
### 穿戴监测
@@ -75,21 +80,15 @@
| 21 | 心电图详情 | wearable/ecg-detail.html | 页面 |
| 22 | 锻炼记录 | wearable/exercise.html | 页面 |
### 体重监测
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 23 | 体重监测首页 | weight/index.html | 页面 |
| 24 | 历史数据 | weight/history.html | 页面 |
| 25 | 详细报告 | weight/report-detail.html | 页面 |
### 环境监测
> 四种监测类型(室外空气 / 室内空气 / 水质监测 / 花粉监测)通过页面顶部 Tab 切换共用同一组 HTML 文件,逐节差异详见"五、环境监测"。
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 26 | 环境监测首页 | environment/index.html | 页面 |
| 27 | 设备详情 | environment/device-detail.html | 页面 |
| 28 | 报警数据 | environment/alarm-data.html | 页面 |
| 23 | 环境监测首页 | environment/index.html | 页面 |
| 24 | 设备详情 | environment/device-detail.html | 页面 |
| 25 | 报警数据 | environment/alarm-data.html | 页面 |
---
@@ -107,128 +106,117 @@
**主要UI元素:**
- 健康现状入口卡片(渐变背景,含人群分类图标如A/B/C/D/E、分类标签如"A类·大病人群"、更新时间)
- 穿戴监测大卡片(含设备图标、模块名称、最近同步时间、4个指标预览格:步数/心率/血氧/体温,各带状态徽章。如果员工未绑定手表时,显示缺省样式,提示未绑定手表,请联系健康管理员,并且不能点击查看详情)
- 体重监测大卡片(含模块名称、最近测量时间、3个指标预览格:体重/BMI/体脂率,各带状态徽章)
- 穿戴监测大卡片(含设备图标、模块名称、最近同步时间、4个指标预览格:步数/心率/血氧/体温,各带状态徽章。如果员工未绑定手表时,显示缺省样式,提示"未绑定手表,请联系健康管理员",并且不能点击查看详情)
- 环境监测入口卡片(含模块名称、4类状态徽章:室外空气/室内空气/水质/花粉、更新时间)
- 健康排查入口卡片(含模块名称、积分数值、连续记录天数、更新时间)
- 健康排查入口卡片(含模块名称、已参加活动数、待参加活动数、更新时间)
**交互说明:**
- 点击"健康现状"卡片进入健康现状页面
- 点击"穿戴监测"卡片进入穿戴监测首页
- 点击"体重监测"卡片进入体重监测首页
- 点击"环境监测"卡片进入环境监测首页
- 点击"健康排查"卡片进入健康排查首页
- 点击"健康排查"卡片进入健康排查活动列表
---
## 二、健康排查
## 二、健康现状
### 2. 健康排查首页health-screening/index.html
### 2. 健康现状health-status.html
![页面截图](screenshots/health-screening/index.jpg)
![页面截图](screenshots/health-status.jpg)
**功能说明:** 展示员工健康记录积分、连续记录天数和积分排行榜,引导员工进行每日健康记录
**功能说明:** 展示员工健康等级评估结果(A-E级)、人群分类标签和健康信息详情
**主要UI元素:**
- 积分卡片(显示当前积分、连续记录天数
- "立即记录"按钮
- "历史记录"按钮
- 积分排行榜列表(含"我的排名"置顶卡片和完整排名列表
- 仪表盘(半圆弧仪表盘,显示健康等级字母如A,右上角等级徽章
- 人群分类标签(如"A类·大病人群",带颜色区分)
- 更新时间显示
- 健康信息卡片(基本信息、体检指标、住院信息等分类展示
**交互说明:**
- 点击"立即记录"跳转到每日健康记录页面
- 点击"历史记录"跳转到历史记录页面
- 点击返回按钮返回健康监测主页
---
### 3. 每日健康记录(health-screening/questionnaire-fill.html
## 三、健康排查
![页面截图](screenshots/health-screening/questionnaire-fill.jpg)
### 3. 活动列表(health-checkup/index.html
**功能说明:** 员工填写每日健康自查问卷,包含身体状况、心理状态、生活习惯三个类别共8个问题,每个问题标注填写频率。
![页面截图](screenshots/health-checkup/index.jpg)
**功能说明:** 展示健康排查活动列表,支持按"已参加"/"待参加"Tab切换,显示活动状态、时间、组织部门、参加人数进度等信息。
**主要UI元素:**
- 分类标题(身体状况、心理状态、生活习惯
- 问题卡片(含必填/选填标签、频率标签如"每日"/"每周"/"每月"
- 单选选项
- 提交按钮
- 成功弹窗(显示积分奖励)
- Tab切换栏(已参加、待参加,待参加带数量徽章
- 活动卡片列表:
- 活动名称
- 状态标签(进行中/已结束/待开始)
- 活动时间范围
- 组织部门
- 参加人数进度条(应参加人数/已参加人数)
- 已参加活动的参与状态标识(✓ 已参加 + 提交时间)
**业务规则**
- 必填题目必须全部作答才能提交
- 选填题目可跳过
- 每个问题标注填写频率(每日/每周/每月),指示该问题的出现周期
- 提交成功后弹窗显示获得的积分
**交互说明**
- 点击Tab切换已参加/待参加列表
- 点击活动卡片进入活动详情页面
---
### 4. 活动详情(health-checkup/activity-detail.html
![页面截图](screenshots/health-checkup/activity-detail.jpg)
**功能说明:** 展示健康排查活动的详细信息,包括活动基本信息、问卷内容、参与状态等,支持进入填写页面。
**主要UI元素:**
- 活动基本信息(名称、状态、时间、组织部门、参加人数等)
- 问卷题目预览
- 参与状态展示(已参加显示提交时间)
- 操作按钮(立即填写/查看问卷等)
**交互说明:**
- 点击"立即填写"跳转到问卷填写页面
- 点击"查看问卷"跳转到已填问卷详情
- 点击返回按钮返回活动列表
---
### 5. 活动填写(health-checkup/activity-fill.html
![页面截图](screenshots/health-checkup/activity-fill.jpg)
**功能说明:** 员工填写健康排查问卷,包含身体状况、心理状态、生活习惯等问题分类。
**主要UI元素:**
- 活动信息头部(活动名称、状态)
- 问题分类(身体状况、心理状态、生活习惯等)
- 单选/多选项
- 提交按钮
**交互说明:**
- 点击选项选中答案
- 点击"提交"按钮提交问卷
- 提交成功弹出积分奖励弹窗
- 返回时需要二次确认弹窗
---
### 4. 历史记录health-screening/checkin-record.html
### 6. 问卷填写health-checkup/questionnaire-fill.html
![页面截图](screenshots/health-screening/checkin-record.jpg)
![页面截图](screenshots/health-checkup/questionnaire-fill.jpg)
**功能说明:** 以日历视图和列表形式展示员工的健康记录历史
**功能说明:** 员工填写健康排查问卷的另一种表单形式
**主要UI元素:**
- 月份日历(已记录日期标记)
- 日历展开/收起按钮
- 历史记录列表(记录时间、日期、积分)
- 问卷信息头部
- 问题分类与选项
- 提交按钮
**交互说明:**
- 点击日历导航按钮切换月份
- 点击展开/收起按钮折叠日历
- 点击历史记录项查看问卷详情
- 点击选项选中答案
- 点击"提交"按钮提交问卷
---
### 5. 问卷详情(health-screening/questionnaire-detail.html
![页面截图](screenshots/health-screening/questionnaire-detail.jpg)
**功能说明:** 只读展示已提交的每日健康自查问卷的详细内容和答案,每道题目标注填写频率。
**主要UI元素:**
- 问卷信息头(标题、提交时间、积分)
- 分类问题卡片(身体状况、心理状态、生活习惯)
- 题目标题后的频率标签(如"每日"/"每周"/"每月",灰色小字)
- 已选中的选项(高亮显示)
**业务规则:**
- 纯只读页面,不可修改已提交的答案
- 每个问题标注填写频率,与填写页面保持一致
**交互说明:**
- 点击返回按钮返回打卡记录页面
---
### 6. 题目设置(health-screening/questionnaire-setting.html
![页面截图](screenshots/health-screening/questionnaire-setting.jpg)
**功能说明:** 允许员工自定义问卷中选填题目的显示/隐藏,个性化打卡体验。
**主要UI元素:**
- 提示信息
- 分类标题
- 题目卡片(含开关按钮)
- 保存按钮
**业务规则:**
- 必填题目的开关为禁用状态,不可关闭
- 选填题目可自由开启/关闭
**交互说明:**
- 点击开关切换选填题目的显示状态
- 点击"保存"按钮保存设置
---
## 三、穿戴监测
## 四、穿戴监测
### 7. 穿戴监测首页(wearable/index.html
@@ -259,7 +247,7 @@
![页面截图](screenshots/wearable/heart-rate.jpg)
**功能说明:** 展示心率监测数据的详细信息,支持日/周/月/年多时间维度查看。
**功能说明:** 展示心率监测数据的详细信息,支持日/周/月/年多时间维度查看,底部时间轴滑块可交互选择具体时间点
**主要UI元素:**
- 时间维度Tab(日/周/月/年)
@@ -298,7 +286,7 @@
### 10-11. 血氧详情与预警历史
**血氧详情(wearable/blood-oxygen.html):** 展示血氧监测数据,图表采用柱状图形式(正常/警告/危险三色区分),支持日/周/月/年多时间维度查看,底部时间轴滑块可交互选择具体时间点。
**血氧详情(wearable/blood-oxygen.html):** 展示血氧监测数据,图表采用柱状图形式,支持日/周/月/年多时间维度查看,底部时间轴滑块可交互选择具体时间点。
**血氧预警历史(wearable/blood-oxygen-alerts.html):** 页面结构与心率预警历史一致,展示血氧预警记录列表,每条卡片右侧显示正常阈值范围。
@@ -361,7 +349,7 @@
![页面截图](screenshots/wearable/sleep.jpg)
**功能说明:** 展示睡眠数据,支持日/周/月/年查看,显示睡眠时长、阶段分布和质量分析
**功能说明:** 展示睡眠数据,支持日/周/月/年查看,显示睡眠时长、阶段分布。
**主要UI元素:**
- 时间维度Tab(日/周/月/年)
@@ -429,116 +417,239 @@
---
## 四、体重监测
### 23. 体重监测首页(weight/index.html
![页面截图](screenshots/weight/index.jpg)
**功能说明:** 展示最近一次体重测量数据和详细身体指标。
**主要UI元素:**
- 最近测量卡片(测量时间、体重大字显示、较上次变化值及趋势箭头、身高、BMI及状态标签)
- 详细信息卡片(体脂率/体脂肪量/基础代谢,各含正常范围提示文字)
- "查看历史数据"按钮
- 健康建议卡片(根据指标生成的个性化建议列表)
**交互说明:**
- 点击"查看历史数据"跳转到历史数据页面
---
### 24. 历史数据(weight/history.html
![页面截图](screenshots/weight/history.jpg)
**功能说明:** 展示体重历史数据,支持图表和列表两种视图,支持多时间范围筛选。
**主要UI元素:**
- 时间Tab(近7天/30天/3个月)
- 视图切换按钮(图表/列表)
- 统计卡片(最高/最低/平均/变化)
- 趋势折线图(含数据点Tip
- 历史记录列表
**交互说明:**
- 点击Tab切换时间范围
- 点击视图按钮切换图表/列表
- 点击图表数据点显示详细Tip
- 点击列表项跳转到详细报告
---
### 25. 详细报告(weight/report-detail.html
![页面截图](screenshots/weight/report-detail.jpg)
**功能说明:** 展示单次体重测量的完整数据报告,根据设备类型显示不同指标。
**主要UI元素:**
- 头部信息卡片(时间、设备、体重、BMI标签)
- 数据分类卡片(基础指标、水分蛋白质、肌肉骨骼、脂肪分析等)
- BMI指示条
- 节段数据表格
**业务规则:**
- 根据URL参数 device 类型(体重秤/蓝牙秤/体脂仪)动态渲染对应的数据内容
- 体脂仪设备显示最完整的数据(含节段分析)
---
## 五、环境监测
### 26. 环境监测首页(environment/index.html
环境监测模块包含四种监测类型:**室外空气、室内空气、水质监测、花粉监测**,统一通过顶部 Tab 切换。三个页面(首页、设备详情、报警数据)的整体结构保持一致,但每种类型在 **等级体系、监测指标、阈值规则** 上存在差异,下文按"通用结构 + 各类型差异"的方式分别说明。
---
### 23. 环境监测首页(environment/index.html
![页面截图](screenshots/environment/index.jpg)
**功能说明:** 展示环境监测设备分布和数据,支持地图和列表两种视图,支持四种监测类型切换。
**功能说明:** 展示环境监测设备分布和数据,支持地图和列表两种视图,通过顶部 Tab 在四种监测类型切换。
**主要UI元素:**
- 监测类型Tab(室外空气、室内空气、水质、花粉
- 地图视图设备标记图例、搜索框、定位按钮、缩放按钮+/-
- 列表视图(含筛选和搜索)
- 底部弹窗(设备简要信息)
**通用 UI 元素:**
- 监测类型 Tab(室外空气🏔️ / 室内空气🏠 / 水质监测💧 / 花粉监测🌸
- 地图视图设备标记(按等级着色)、左上角图例、顶部搜索框、右下定位按钮、缩放按钮+/-
- 列表视图:等级筛选下拉、设备名称搜索、地图/列表切换按钮
- 底部弹窗:等级徽章 + 等级描述、监测指标网格、设备编码/所属单位/位置、"查看详情"按钮
**交互说明:**
- 点击Tab切换监测类型
- 点击视图按钮切地图/列表
- 点击设备标记或列表项弹出底部弹窗
**通用交互说明:**
- 点击 Tab 切换监测类型,地图标记、图例、列表筛选项同步刷新
- 点击右下"列表"按钮切换到列表视图,列表视图右上"地图"按钮切地图
- 点击地图标记弹出底部弹窗
- 点击列表项跳转到设备详情页
- 点击弹窗中"查看详情"跳转到设备详情页
#### 23.1 室外空气(Taboutdoor
![室外空气首页](screenshots/environment/index-outdoor.jpg)
**等级体系(6级):** 优 / 良 / 轻度污染 / 中度污染 / 重度污染 / 严重污染
**监测指标:** 温度、湿度、太阳辐射、PM2.5、PM10、照度、风速
**典型设备:** 空气质量监测站、PM10检测仪、臭氧检测仪、气象参数监测仪、VOC检测仪(共6台示例)
**等级描述(示例):**
- 优:空气质量令人满意,基本无污染
- 严重污染:健康人群运动耐受力降低,有明显强烈症状,提前出现某些疾病
##### 23.1.1 设备底部弹窗
![室外空气底部弹窗](screenshots/environment/index-outdoor-sheet.jpg)
点击地图上设备标记后从底部弹出,呈现该设备的等级与全部 7 项实时指标。
**主要内容:**
- 弹窗头部:设备名称(如"空气质量监测站-A01")、关闭按钮(×)、更新时间(如"14:30 更新"
- 等级行:等级徽章(如"优",按等级着色)+ 等级描述短句(如"空气质量令人满意,基本无污染")
- 指标网格(3 列):温度 32.5℃ / 湿度 45% / 太阳辐射 680 W/m² / PM2.5 28 μg/m³ / PM10 52 μg/m³ / 照度 65000 lm/m² / 风速 3.2 m/s
- 设备信息区:设备编码(OA-2024-001)、所属单位(采油一厂)、设备位置(厂区大门东侧)
- 底部 CTA"查看详情"按钮(绿色主按钮,跳转设备详情页并携带 tab=outdoor
#### 23.2 室内空气(Tabindoor
![室内空气首页](screenshots/environment/index-indoor.jpg)
**等级体系(5级):** 清洁 / 未污染 / 轻污染 / 中污染 / 重污染
**监测指标:** 温度、湿度、CO₂、甲醛、PM2.5、PM10
**典型设备:** 综合空气监测仪、PM2.5检测仪、甲醛检测仪、CO₂检测仪(共5台示例)
**等级描述(示例):**
- 清洁:空气清洁,各项指标均在安全范围内
- 重污染:空气质量较差,敏感人群应离开该区域
##### 23.2.1 设备底部弹窗
![室内空气底部弹窗](screenshots/environment/index-indoor-sheet.jpg)
**主要内容:**
- 弹窗头部:设备名称(如"综合空气监测仪-A01")、关闭按钮(×)、更新时间
- 等级行:等级徽章(如"清洁")+ 描述短句(如"空气清洁,各项指标均在安全范围内")
- 指标网格(3 列 × 2 行):温度 24.5℃ / 湿度 52% / CO₂ 456 ppm / 甲醛 0.03 mg/m³ / PM2.5 35 μg/m³ / PM10 68 μg/m³
- 设备信息区:设备编码(IA-2024-001)、所属单位(采油一厂)、设备位置(办公楼1层大厅)
- 底部 CTA"查看详情"按钮(跳转携带 tab=indoor
#### 23.3 水质监测(Tabwater
![水质监测首页](screenshots/environment/index-water.jpg)
**等级体系(2级):** 达标 / 未达标
**监测指标:** 温度、电导率、PH、盐度、电阻率、TDS
**典型设备:** 多参数水质分析仪、在线PH计、电导率监测仪、TDS检测仪(共5台示例)
**等级描述(示例):**
- 达标:水质各项指标符合标准要求
- 未达标:水质存在超标项目,需检查处理
##### 23.3.1 设备底部弹窗
![水质监测底部弹窗](screenshots/environment/index-water-sheet.jpg)
**主要内容:**
- 弹窗头部:设备名称(如"多参数水质分析仪-A01")、关闭按钮(×)、更新时间
- 等级行:等级徽章(如"达标",绿色 / 未达标则红色)+ 描述短句(如"水质各项指标符合标准要求")
- 指标网格(3 列 × 2 行):温度 18.5℃ / 电导率 320 μS/cm / PH 7.2 / 盐度 180 mg/L / 电阻率 3125 Ω·m / TDS 160 mg/L
- 设备信息区:设备编码(WQ-2024-001)、所属单位(采油一厂)、设备位置(生活区供水站)
- 底部 CTA"查看详情"按钮(跳转携带 tab=water
#### 23.4 花粉监测(Tabpollen
![花粉监测首页](screenshots/environment/index-pollen.jpg)
**等级体系(5级):** 很低 / 低 / 中等 / 高 / 很高
**监测指标:** 榆属、松属、杨属、柏科、柳属、蒿属、藜科、苋科、花粉总数(按粒/千mm² 计量)
**典型设备:** 花粉采样器、花粉浓度监测仪、花粉自动计数仪、综合气象花粉站(共5台示例)
**等级描述(示例):**
- 很低:花粉浓度极低,对过敏人群基本无影响
- 很高:花粉浓度极高,过敏人群应尽量避免外出
##### 23.4.1 设备底部弹窗
![花粉监测底部弹窗](screenshots/environment/index-pollen-sheet.jpg)
**主要内容:**
- 弹窗头部:设备名称(如"花粉采样器-A01")、关闭按钮(×)、更新时间
- 等级行:等级徽章(如"很低")+ 描述短句(如"花粉浓度极低,对过敏人群基本无影响")
- 指标网格(3 列 × 3 行):榆属 12 / 松属 8 / 杨属 15 / 柏科 6 / 柳属 10 / 蒿属 5 / 藜科 3 / 苋科 2 / 花粉总数 61(统一单位:粒/千mm²)
- 设备信息区:设备编码(PL-2024-001)、所属单位(采油一厂)、设备位置(办公区绿化带)
- 底部 CTA"查看详情"按钮(跳转携带 tab=pollen
---
### 27. 设备详情(environment/device-detail.html
### 24. 设备详情(environment/device-detail.html
![页面截图](screenshots/environment/device-detail.jpg)
**功能说明:** 展示单个环境监测设备的详细数据,包括当前指标、等级评价和变化趋势。
**功能说明:** 展示单个环境监测设备的详细数据,包括当前指标、等级评价和变化趋势。页面根据 URL 参数 `tab` 区分监测类型,标题、等级评价文案、可选指标随类型变化。
**主要UI元素:**
- 等级名称和指数显示
- 主卡片位置时间、描述、仪表盘、指标网格
- 变化趋势卡片指标选择下拉、时间Tab、柱状图
- "报警数据"按钮
**通用 UI 元素:**
- 顶部等级头部:左侧等级名称大字 + 评价副标题,右侧综合指数 + 等级
- 主卡片:设备位置/时间、等级描述文案、半圆仪表盘、指标网格
- 变化趋势卡片指标下拉、时间 Tab(近24小时/7日/30日)、折线/柱状图(含阈值线
- 底部"报警数据"按钮(带上下文参数跳转)
**交互说明:**
- 点击指标下拉选择不同监测指标
- 点击时间Tab切换近24小时/7日/30日
- 点击"报警数据"跳转到报警数据页面
**通用交互说明:**
- 点击指标下拉选择不同监测指标,趋势图随之刷新
- 点击时间 Tab 切换近24小时/7日/30日
- 点击"报警数据"跳转到报警数据页面(带 tab 与设备 id
#### 24.1 室外空气
![室外空气设备详情](screenshots/environment/device-detail-outdoor.jpg)
- **评价副标题:** "室外空气质量评价"
- **指数标签:** "综合指数",等级标签:"空气质量级别"I-VI 级)
- **指标下拉可选:** 温度 / 湿度 / 太阳辐射 / PM2.5 / PM10 / 照度 / 风速
- **趋势图阈值线(示例):** PM2.5 上限 75 μg/m³,PM10 上限 150 μg/m³,风速 上限 10 m/s
- **示例等级文案:** "空气质量令人满意,基本无污染,对人体健康没有危害"
#### 24.2 室内空气
![室内空气设备详情](screenshots/environment/device-detail-indoor.jpg)
- **评价副标题:** "室内空气质量评价"
- **指数标签:** "综合指数",等级标签:"空气质量级别"I-V 级)
- **指标下拉可选:** 温度 / 湿度 / CO₂ / 甲醛 / PM2.5 / PM10
- **趋势图阈值线(示例):** CO₂ 上限 1000 ppm(敏感场所 800 ppm),甲醛 上限 0.10 mg/m³
- **示例等级文案:** "至少有一个环境污染物超标。除了敏感者外,一般不会发生慢急性中毒"
#### 24.3 水质监测
![水质监测设备详情](screenshots/environment/device-detail-water.jpg)
- **评价副标题:** "水质综合评价"
- **指数标签:** "综合指数",等级标签:"水质等级"(I 级或 V 级)
- **指标下拉可选:** 温度 / 电导率 / PH / 盐度 / 电阻率 / TDS
- **趋势图阈值线(示例):** PH 区间 6.5~8.0,电导率 上限 1000 μS/cmTDS 上限 500 mg/L
- **示例等级文案:** "水质各项指标符合国家标准要求,可正常使用"
#### 24.4 花粉监测
![花粉监测设备详情](screenshots/environment/device-detail-pollen.jpg)
- **评价副标题:** "花粉过敏风险评价"
- **指数标签:** "过敏指数",等级标签:"过敏等级"I-V 级)
- **指标下拉可选:** 榆属 / 松属 / 杨属 / 柏科 / 柳属 / 蒿属 / 藜科 / 苋科 / 花粉总数
- **趋势图阈值线(示例):** 花粉总数 上限 500 粒/千mm²、下限 100 粒/千mm²
- **示例等级文案:** "花粉浓度极低,对过敏人群基本无影响,可正常户外活动"
---
### 28. 报警数据(environment/alarm-data.html
### 25. 报警数据(environment/alarm-data.html
![页面截图](screenshots/environment/alarm-data.jpg)
**功能说明:** 展示环境监测设备的报警记录,支持按日期范围和报警类型筛选。
**功能说明:** 展示环境监测设备的报警记录,支持按日期范围和报警类型筛选。页面根据 URL 参数 `tab` 决定可选报警类型与展示的报警列表。
**主要UI元素:**
- 筛选区日期范围、类型下拉)
- 统计条报警总
- 报警卡片列表(报警值、阈值范围、设备信息)
**通用 UI 元素:**
- 筛选区日期范围(起始/结束日期选择器)、类型下拉、重置按钮、查询按钮
- 统计条报警总条数(红色突出
- 报警卡片列表:左侧红色边条,顶部"{指标} 超标报警" + 时间,中部报警值(红底)→ 阈值范围(灰底),底部设备名称/位置
**交互说明:**
**通用交互说明:**
- 设置日期范围和类型后点击"查询"筛选
- 点击"重置"恢复默认筛选条件
- 点击"重置"恢复默认日期与类型筛选条件
- 列表为空时显示"暂无报警数据"占位
**业务规则:**
- 报警值高于阈值上限或低于阈值下限时进入报警列表
- 报警卡片同时呈现实际值与允许阈值范围(min~max),便于直观判断超标幅度
#### 25.1 室外空气
![室外空气报警数据](screenshots/environment/alarm-data-outdoor.jpg)
**可选报警类型:** PM2.5、PM10、温度、湿度、风速、太阳辐射、照度
#### 25.2 室内空气
![室内空气报警数据](screenshots/environment/alarm-data-indoor.jpg)
**可选报警类型:** PM2.5、PM10、CO₂、甲醛、温度、湿度
#### 25.3 水质监测
![水质监测报警数据](screenshots/environment/alarm-data-water.jpg)
**可选报警类型:** PH、电导率、盐度、TDS、电阻率、温度
#### 25.4 花粉监测
![花粉监测报警数据](screenshots/environment/alarm-data-pollen.jpg)
**可选报警类型:** 花粉总数
> 备注:花粉监测仅对"花粉总数"设置统一报警阈值,各属/科单一指标暂不单独报警。
@@ -61,7 +61,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<div class="section" id="sec-home">
<div class="section__header">
<span class="section__title">📱 首页入口</span>
<span class="section__count">2 页面</span>
<span class="section__count">1 页面</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/index.jpg', '健康监测首页')">
@@ -71,6 +71,18 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">5个导航入口</span>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- 健康现状 -->
<div class="section" id="sec-health-status">
<div class="section__header">
<span class="section__title">🩺 健康现状</span>
<span class="section__count">1 页面</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/health-status.jpg', '健康现状')">
<div class="card__img"><img src="screenshots/health-status.jpg" alt="健康现状"></div>
<div class="card__label">健康现状</div>
@@ -85,46 +97,64 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<div class="divider"></div>
<!-- 健康排查 -->
<div class="section" id="sec-health-screening">
<div class="section" id="sec-health-checkup">
<div class="section__header">
<span class="section__title">📋 健康排查</span>
<span class="section__count">5 页面</span>
<span class="section__count">7 视图 · 4 页面 + 活动详情 4 状态</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/health-screening/index.jpg', '健康排查首页')">
<div class="card__img"><img src="screenshots/health-screening/index.jpg" alt="健康排查首页"></div>
<div class="card__label">健康排查首页</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/index.jpg', '活动列表')">
<div class="card__img"><img src="screenshots/health-checkup/index.jpg" alt="活动列表"></div>
<div class="card__label">活动列表</div>
<div class="card__tags">
<span class="tag tag--state">积分/排行榜</span>
<span class="tag tag--state">已参加/待参加</span>
<span class="tag tag--state">进度条</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/health-screening/questionnaire-fill.jpg', '每日健康记录')">
<div class="card__img"><img src="screenshots/health-screening/questionnaire-fill.jpg" alt="每日健康记录"></div>
<div class="card__label">每日健康记录</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/activity-detail.jpg', '活动详情(进行中-未参加)')">
<div class="card__img"><img src="screenshots/health-checkup/activity-detail.jpg" alt="活动详情(进行中)"></div>
<div class="card__label">活动详情(进行中-未参加)</div>
<div class="card__tags">
<span class="tag tag--state">问卷填写</span>
<span class="tag tag--state">频率标签</span>
<span class="tag tag--state">进行中</span>
<span class="tag tag--state">可填写</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/health-screening/checkin-record.jpg', '历史记录')">
<div class="card__img"><img src="screenshots/health-screening/checkin-record.jpg" alt="历史记录"></div>
<div class="card__label">历史记录</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/activity-detail-participated.jpg', '活动详情(进行中-已参加)')">
<div class="card__img"><img src="screenshots/health-checkup/activity-detail-participated.jpg" alt="活动详情(已参加)"></div>
<div class="card__label">活动详情(进行中-已参加)</div>
<div class="card__tags">
<span class="tag tag--state">日历+列表</span>
<span class="tag tag--state">已参加</span>
<span class="tag tag--state">显示提交时间</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/health-screening/questionnaire-detail.jpg', '问卷详情')">
<div class="card__img"><img src="screenshots/health-screening/questionnaire-detail.jpg" alt="问卷详情"></div>
<div class="card__label">问卷详情</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/activity-detail-pending.jpg', '活动详情(待开始)')">
<div class="card__img"><img src="screenshots/health-checkup/activity-detail-pending.jpg" alt="活动详情(待开始)"></div>
<div class="card__label">活动详情(待开始)</div>
<div class="card__tags">
<span class="tag tag--state">只读回顾</span>
<span class="tag tag--state">待开始</span>
<span class="tag tag--state">禁止填写</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/health-screening/questionnaire-setting.jpg', '题目设置')">
<div class="card__img"><img src="screenshots/health-screening/questionnaire-setting.jpg" alt="题目设置"></div>
<div class="card__label">题目设置</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/activity-detail-ended.jpg', '活动详情(已结束)')">
<div class="card__img"><img src="screenshots/health-checkup/activity-detail-ended.jpg" alt="活动详情(已结束)"></div>
<div class="card__label">活动详情(已结束)</div>
<div class="card__tags">
<span class="tag tag--state">自定义显示</span>
<span class="tag tag--state">已结束</span>
<span class="tag tag--state">禁止填写</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/activity-fill.jpg', '活动填写')">
<div class="card__img"><img src="screenshots/health-checkup/activity-fill.jpg" alt="活动填写"></div>
<div class="card__label">活动填写</div>
<div class="card__tags">
<span class="tag tag--state">问卷表单</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/health-checkup/questionnaire-fill.jpg', '问卷填写')">
<div class="card__img"><img src="screenshots/health-checkup/questionnaire-fill.jpg" alt="问卷填写"></div>
<div class="card__label">问卷填写</div>
<div class="card__tags">
<span class="tag tag--state">问卷表单</span>
</div>
</div>
</div>
@@ -136,7 +166,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<div class="section" id="sec-wearable">
<div class="section__header">
<span class="section__title">⌚ 穿戴监测</span>
<span class="section__count">17 页面</span>
<span class="section__count">16 页面 · 日视图 + 周视图</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/wearable/index.jpg', '穿戴监测首页')">
@@ -146,13 +176,20 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">指标卡片</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/heart-rate.jpg', '心率详情')">
<div class="card__img"><img src="screenshots/wearable/heart-rate.jpg" alt="心率详情"></div>
<div class="card__label">心率详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/heart-rate.jpg', '心率详情(日)')">
<div class="card__img"><img src="screenshots/wearable/heart-rate.jpg" alt="心率详情(日)"></div>
<div class="card__label">心率详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">趋势图</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/heart-rate-week.jpg', '心率详情(周)')">
<div class="card__img"><img src="screenshots/wearable/heart-rate-week.jpg" alt="心率详情(周)"></div>
<div class="card__label">心率详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周趋势</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/heart-rate-alerts.jpg', '心率预警历史')">
<div class="card__img"><img src="screenshots/wearable/heart-rate-alerts.jpg" alt="心率预警历史"></div>
<div class="card__label">心率预警历史</div>
@@ -160,14 +197,21 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">预警记录</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-oxygen.jpg', '血氧详情')">
<div class="card__img"><img src="screenshots/wearable/blood-oxygen.jpg" alt="血氧详情"></div>
<div class="card__label">血氧详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-oxygen.jpg', '血氧详情(日)')">
<div class="card__img"><img src="screenshots/wearable/blood-oxygen.jpg" alt="血氧详情(日)"></div>
<div class="card__label">血氧详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">柱状图</span>
<span class="tag tag--state">三色区分</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-oxygen-week.jpg', '血氧详情(周)')">
<div class="card__img"><img src="screenshots/wearable/blood-oxygen-week.jpg" alt="血氧详情(周)"></div>
<div class="card__label">血氧详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周柱状图</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-oxygen-alerts.jpg', '血氧预警历史')">
<div class="card__img"><img src="screenshots/wearable/blood-oxygen-alerts.jpg" alt="血氧预警历史"></div>
<div class="card__label">血氧预警历史</div>
@@ -175,13 +219,20 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">预警记录</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-pressure.jpg', '血压详情')">
<div class="card__img"><img src="screenshots/wearable/blood-pressure.jpg" alt="血压详情"></div>
<div class="card__label">血压详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-pressure.jpg', '血压详情(日)')">
<div class="card__img"><img src="screenshots/wearable/blood-pressure.jpg" alt="血压详情(日)"></div>
<div class="card__label">血压详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">双线图表</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-pressure-week.jpg', '血压详情(周)')">
<div class="card__img"><img src="screenshots/wearable/blood-pressure-week.jpg" alt="血压详情(周)"></div>
<div class="card__label">血压详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周双线图</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/blood-pressure-alerts.jpg', '血压预警历史')">
<div class="card__img"><img src="screenshots/wearable/blood-pressure-alerts.jpg" alt="血压预警历史"></div>
<div class="card__label">血压预警历史</div>
@@ -189,13 +240,20 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">预警记录</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/temperature.jpg', '体温详情')">
<div class="card__img"><img src="screenshots/wearable/temperature.jpg" alt="体温详情"></div>
<div class="card__label">体温详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/temperature.jpg', '体温详情(日)')">
<div class="card__img"><img src="screenshots/wearable/temperature.jpg" alt="体温详情(日)"></div>
<div class="card__label">体温详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">趋势图</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/temperature-week.jpg', '体温详情(周)')">
<div class="card__img"><img src="screenshots/wearable/temperature-week.jpg" alt="体温详情(周)"></div>
<div class="card__label">体温详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周趋势</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/temperature-alerts.jpg', '体温预警历史')">
<div class="card__img"><img src="screenshots/wearable/temperature-alerts.jpg" alt="体温预警历史"></div>
<div class="card__label">体温预警历史</div>
@@ -203,13 +261,20 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">预警记录</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/stress.jpg', '压力详情')">
<div class="card__img"><img src="screenshots/wearable/stress.jpg" alt="压力详情"></div>
<div class="card__label">压力详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/stress.jpg', '压力详情(日)')">
<div class="card__img"><img src="screenshots/wearable/stress.jpg" alt="压力详情(日)"></div>
<div class="card__label">压力详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">压力等级</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/stress-week.jpg', '压力详情(周)')">
<div class="card__img"><img src="screenshots/wearable/stress-week.jpg" alt="压力详情(周)"></div>
<div class="card__label">压力详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周趋势</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/stress-alerts.jpg', '压力预警历史')">
<div class="card__img"><img src="screenshots/wearable/stress-alerts.jpg" alt="压力预警历史"></div>
<div class="card__label">压力预警历史</div>
@@ -217,20 +282,34 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">预警记录</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/steps.jpg', '步数详情')">
<div class="card__img"><img src="screenshots/wearable/steps.jpg" alt="步数详情"></div>
<div class="card__label">步数详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/steps.jpg', '步数详情(日)')">
<div class="card__img"><img src="screenshots/wearable/steps.jpg" alt="步数详情(日)"></div>
<div class="card__label">步数详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">生活化换算</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/sleep.jpg', '睡眠详情')">
<div class="card__img"><img src="screenshots/wearable/sleep.jpg" alt="睡眠详情"></div>
<div class="card__label">睡眠详情</div>
<div class="card" onclick="openLightbox('screenshots/wearable/steps-week.jpg', '步数详情(周)')">
<div class="card__img"><img src="screenshots/wearable/steps-week.jpg" alt="步数详情(周)"></div>
<div class="card__label">步数详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周趋势</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/sleep.jpg', '睡眠详情(日)')">
<div class="card__img"><img src="screenshots/wearable/sleep.jpg" alt="睡眠详情(日)"></div>
<div class="card__label">睡眠详情(日视图)</div>
<div class="card__tags">
<span class="tag tag--state">Hypnogram</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/sleep-week.jpg', '睡眠详情(周)')">
<div class="card__img"><img src="screenshots/wearable/sleep-week.jpg" alt="睡眠详情(周)"></div>
<div class="card__label">睡眠详情(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周堆积柱状图</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/ecg.jpg', '心电图列表')">
<div class="card__img"><img src="screenshots/wearable/ecg.jpg" alt="心电图列表"></div>
<div class="card__label">心电图列表</div>
@@ -246,44 +325,18 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<span class="tag tag--state">可横向滑动</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable/exercise.jpg', '锻炼记录')">
<div class="card__img"><img src="screenshots/wearable/exercise.jpg" alt="锻炼记录"></div>
<div class="card__label">锻炼记录</div>
<div class="card" onclick="openLightbox('screenshots/wearable/exercise.jpg', '锻炼记录(周)')">
<div class="card__img"><img src="screenshots/wearable/exercise.jpg" alt="锻炼记录(周)"></div>
<div class="card__label">锻炼记录(周视图)</div>
<div class="card__tags">
<span class="tag tag--state">周/月视图</span>
<span class="tag tag--state">本周概览</span>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- 体重监测 -->
<div class="section" id="sec-weight">
<div class="section__header">
<span class="section__title">⚖️ 体重监测</span>
<span class="section__count">3 页面</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/weight/index.jpg', '体重监测首页')">
<div class="card__img"><img src="screenshots/weight/index.jpg" alt="体重监测首页"></div>
<div class="card__label">体重监测首页</div>
<div class="card" onclick="openLightbox('screenshots/wearable/exercise-month.jpg', '锻炼记录(月)')">
<div class="card__img"><img src="screenshots/wearable/exercise-month.jpg" alt="锻炼记录(月)"></div>
<div class="card__label">锻炼记录(月视图)</div>
<div class="card__tags">
<span class="tag tag--state">最近测量</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/weight/history.jpg', '历史数据')">
<div class="card__img"><img src="screenshots/weight/history.jpg" alt="历史数据"></div>
<div class="card__label">历史数据</div>
<div class="card__tags">
<span class="tag tag--state">图表/列表</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/weight/report-detail.jpg', '详细报告')">
<div class="card__img"><img src="screenshots/weight/report-detail.jpg" alt="详细报告"></div>
<div class="card__label">详细报告</div>
<div class="card__tags">
<span class="tag tag--state">完整报告</span>
<span class="tag tag--state">本月概览</span>
</div>
</div>
</div>
@@ -295,30 +348,198 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<div class="section" id="sec-environment">
<div class="section__header">
<span class="section__title">🌡️ 环境监测</span>
<span class="section__count">3 页面</span>
<span class="section__count">24 视图 · 4 监测类型 × (首页+底部弹窗+设备详情+设备详情近7日+设备详情指标+报警)</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/environment/index.jpg', '环境监测首页')">
<div class="card__img"><img src="screenshots/environment/index.jpg" alt="环境监测首页"></div>
<div class="card__label">环境监测首页</div>
<!-- 室外空气 -->
<div class="card" onclick="openLightbox('screenshots/environment/index-outdoor.jpg', '室外空气 · 首页')">
<div class="card__img"><img src="screenshots/environment/index-outdoor.jpg" alt="室外空气首页"></div>
<div class="card__label">室外空气 · 首页</div>
<div class="card__tags">
<span class="tag tag--state">6级评价</span>
<span class="tag tag--state">地图/列表</span>
<span class="tag tag--state">底部弹窗</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail.jpg', '设备详情')">
<div class="card__img"><img src="screenshots/environment/device-detail.jpg" alt="设备详情"></div>
<div class="card__label">设备详情</div>
<div class="card" onclick="openLightbox('screenshots/environment/index-outdoor-sheet.jpg', '室外空气 · 底部弹窗')">
<div class="card__img"><img src="screenshots/environment/index-outdoor-sheet.jpg" alt="室外空气底部弹窗"></div>
<div class="card__label">室外空气 · 底部弹窗</div>
<div class="card__tags">
<span class="tag tag--state">指标/趋势</span>
<span class="tag tag--modal">弹窗</span>
<span class="tag tag--state">设备简要</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/alarm-data.jpg', '报警数据')">
<div class="card__img"><img src="screenshots/environment/alarm-data.jpg" alt="报警数据"></div>
<div class="card__label">报警数据</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-outdoor.jpg', '室外空气 · 设备详情')">
<div class="card__img"><img src="screenshots/environment/device-detail-outdoor.jpg" alt="室外空气设备详情"></div>
<div class="card__label">室外空气 · 设备详情</div>
<div class="card__tags">
<span class="tag tag--state">报警记录</span>
<span class="tag tag--state">日期筛选</span>
<span class="tag tag--state">7项指标</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-outdoor-7d.jpg', '室外空气 · 设备详情(近7日趋势)')">
<div class="card__img"><img src="screenshots/environment/device-detail-outdoor-7d.jpg" alt="室外空气设备详情(近7日)"></div>
<div class="card__label">室外空气 · 设备详情(近7日)</div>
<div class="card__tags">
<span class="tag tag--state">近7日</span>
<span class="tag tag--state">综合指数</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-outdoor-metric.jpg', '室外空气 · 设备详情(PM2.5 近24小时)')">
<div class="card__img"><img src="screenshots/environment/device-detail-outdoor-metric.jpg" alt="室外空气设备详情(PM2.5)"></div>
<div class="card__label">室外空气 · 设备详情(PM2.5</div>
<div class="card__tags">
<span class="tag tag--state">PM2.5</span>
<span class="tag tag--state">近24小时</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/alarm-data-outdoor.jpg', '室外空气 · 报警数据')">
<div class="card__img"><img src="screenshots/environment/alarm-data-outdoor.jpg" alt="室外空气报警数据"></div>
<div class="card__label">室外空气 · 报警数据</div>
<div class="card__tags">
<span class="tag tag--state">PM2.5/PM10超标</span>
</div>
</div>
<!-- 室内空气 -->
<div class="card" onclick="openLightbox('screenshots/environment/index-indoor.jpg', '室内空气 · 首页')">
<div class="card__img"><img src="screenshots/environment/index-indoor.jpg" alt="室内空气首页"></div>
<div class="card__label">室内空气 · 首页</div>
<div class="card__tags">
<span class="tag tag--state">5级评价</span>
<span class="tag tag--state">地图/列表</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/index-indoor-sheet.jpg', '室内空气 · 底部弹窗')">
<div class="card__img"><img src="screenshots/environment/index-indoor-sheet.jpg" alt="室内空气底部弹窗"></div>
<div class="card__label">室内空气 · 底部弹窗</div>
<div class="card__tags">
<span class="tag tag--modal">弹窗</span>
<span class="tag tag--state">设备简要</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-indoor.jpg', '室内空气 · 设备详情')">
<div class="card__img"><img src="screenshots/environment/device-detail-indoor.jpg" alt="室内空气设备详情"></div>
<div class="card__label">室内空气 · 设备详情</div>
<div class="card__tags">
<span class="tag tag--state">CO₂/甲醛</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-indoor-7d.jpg', '室内空气 · 设备详情(近7日趋势)')">
<div class="card__img"><img src="screenshots/environment/device-detail-indoor-7d.jpg" alt="室内空气设备详情(近7日)"></div>
<div class="card__label">室内空气 · 设备详情(近7日)</div>
<div class="card__tags">
<span class="tag tag--state">近7日</span>
<span class="tag tag--state">综合指数</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-indoor-metric.jpg', '室内空气 · 设备详情(CO₂ 近24小时)')">
<div class="card__img"><img src="screenshots/environment/device-detail-indoor-metric.jpg" alt="室内空气设备详情(CO₂)"></div>
<div class="card__label">室内空气 · 设备详情(CO₂)</div>
<div class="card__tags">
<span class="tag tag--state">CO₂</span>
<span class="tag tag--state">近24小时</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/alarm-data-indoor.jpg', '室内空气 · 报警数据')">
<div class="card__img"><img src="screenshots/environment/alarm-data-indoor.jpg" alt="室内空气报警数据"></div>
<div class="card__label">室内空气 · 报警数据</div>
<div class="card__tags">
<span class="tag tag--state">CO₂/甲醛超标</span>
</div>
</div>
<!-- 水质监测 -->
<div class="card" onclick="openLightbox('screenshots/environment/index-water.jpg', '水质监测 · 首页')">
<div class="card__img"><img src="screenshots/environment/index-water.jpg" alt="水质监测首页"></div>
<div class="card__label">水质监测 · 首页</div>
<div class="card__tags">
<span class="tag tag--state">达标/未达标</span>
<span class="tag tag--state">地图/列表</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/index-water-sheet.jpg', '水质监测 · 底部弹窗')">
<div class="card__img"><img src="screenshots/environment/index-water-sheet.jpg" alt="水质监测底部弹窗"></div>
<div class="card__label">水质监测 · 底部弹窗</div>
<div class="card__tags">
<span class="tag tag--modal">弹窗</span>
<span class="tag tag--state">设备简要</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-water.jpg', '水质监测 · 设备详情')">
<div class="card__img"><img src="screenshots/environment/device-detail-water.jpg" alt="水质监测设备详情"></div>
<div class="card__label">水质监测 · 设备详情</div>
<div class="card__tags">
<span class="tag tag--state">PH/电导率/TDS</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-water-7d.jpg', '水质监测 · 设备详情(近7日趋势)')">
<div class="card__img"><img src="screenshots/environment/device-detail-water-7d.jpg" alt="水质监测设备详情(近7日)"></div>
<div class="card__label">水质监测 · 设备详情(近7日)</div>
<div class="card__tags">
<span class="tag tag--state">近7日</span>
<span class="tag tag--state">综合指数</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-water-metric.jpg', '水质监测 · 设备详情(PH 近24小时)')">
<div class="card__img"><img src="screenshots/environment/device-detail-water-metric.jpg" alt="水质监测设备详情(PH)"></div>
<div class="card__label">水质监测 · 设备详情(PH</div>
<div class="card__tags">
<span class="tag tag--state">PH</span>
<span class="tag tag--state">近24小时</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/alarm-data-water.jpg', '水质监测 · 报警数据')">
<div class="card__img"><img src="screenshots/environment/alarm-data-water.jpg" alt="水质监测报警数据"></div>
<div class="card__label">水质监测 · 报警数据</div>
<div class="card__tags">
<span class="tag tag--state">PH/电导率超标</span>
</div>
</div>
<!-- 花粉监测 -->
<div class="card" onclick="openLightbox('screenshots/environment/index-pollen.jpg', '花粉监测 · 首页')">
<div class="card__img"><img src="screenshots/environment/index-pollen.jpg" alt="花粉监测首页"></div>
<div class="card__label">花粉监测 · 首页</div>
<div class="card__tags">
<span class="tag tag--state">5级过敏</span>
<span class="tag tag--state">地图/列表</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/index-pollen-sheet.jpg', '花粉监测 · 底部弹窗')">
<div class="card__img"><img src="screenshots/environment/index-pollen-sheet.jpg" alt="花粉监测底部弹窗"></div>
<div class="card__label">花粉监测 · 底部弹窗</div>
<div class="card__tags">
<span class="tag tag--modal">弹窗</span>
<span class="tag tag--state">设备简要</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-pollen.jpg', '花粉监测 · 设备详情')">
<div class="card__img"><img src="screenshots/environment/device-detail-pollen.jpg" alt="花粉监测设备详情"></div>
<div class="card__label">花粉监测 · 设备详情</div>
<div class="card__tags">
<span class="tag tag--state">8花粉属/科</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-pollen-7d.jpg', '花粉监测 · 设备详情(近7日趋势)')">
<div class="card__img"><img src="screenshots/environment/device-detail-pollen-7d.jpg" alt="花粉监测设备详情(近7日)"></div>
<div class="card__label">花粉监测 · 设备详情(近7日)</div>
<div class="card__tags">
<span class="tag tag--state">近7日</span>
<span class="tag tag--state">过敏指数</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/device-detail-pollen-metric.jpg', '花粉监测 · 设备详情(花粉总数 近24小时)')">
<div class="card__img"><img src="screenshots/environment/device-detail-pollen-metric.jpg" alt="花粉监测设备详情(花粉总数)"></div>
<div class="card__label">花粉监测 · 设备详情(花粉总数)</div>
<div class="card__tags">
<span class="tag tag--state">花粉总数</span>
<span class="tag tag--state">近24小时</span>
</div>
</div>
<div class="card" onclick="openLightbox('screenshots/environment/alarm-data-pollen.jpg', '花粉监测 · 报警数据')">
<div class="card__img"><img src="screenshots/environment/alarm-data-pollen.jpg" alt="花粉监测报警数据"></div>
<div class="card__label">花粉监测 · 报警数据</div>
<div class="card__tags">
<span class="tag tag--state">花粉总数超标</span>
</div>
</div>
</div>
@@ -0,0 +1,218 @@
/**
* 统一截图脚本(重截修复版)
* 规则:
* - 标准 phone-shell 尺寸 390×844
* - 短页面(实测内容 < 844):保持 844 高度,外壳完整
* - 长页面(实测内容 > 844):phone-shell 解除 flex 高度限制,按真实内容展开
* - 环境监测首页(含底部弹窗):固定 390×844,map-view 自然填充,弹窗叠在地图上方
*/
import { chromium } from 'playwright';
import { fileURLToPath } from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const docDir = __dirname;
const root = path.resolve(__dirname, '..');
// 模式:
// - 'fixed' : 严格 390×844(环境首页、含弹窗)
// - 'auto' : 短页面 → 最低 844;长页面 → 真实 scroll 高度
const tasks = [
// ========== 主页 / 健康现状 ==========
{ html: 'index.html', out: 'index', mode: 'auto' },
{ html: 'health-status.html', out: 'health-status', mode: 'auto' },
// ========== 健康排查 ==========
{ html: 'health-checkup/index.html', out: 'health-checkup/index', mode: 'auto' },
{ html: 'health-checkup/activity-detail.html', out: 'health-checkup/activity-detail', mode: 'auto', query: '?activityStatus=ongoing' },
{ html: 'health-checkup/activity-detail.html', out: 'health-checkup/activity-detail-participated', mode: 'auto', query: '?activityStatus=ongoing&participated=1&submitTime=2026-05-20%2014:32' },
{ html: 'health-checkup/activity-detail.html', out: 'health-checkup/activity-detail-ended', mode: 'auto', query: '?activityStatus=ended' },
{ html: 'health-checkup/activity-detail.html', out: 'health-checkup/activity-detail-pending', mode: 'auto', query: '?activityStatus=pending' },
{ html: 'health-checkup/activity-fill.html', out: 'health-checkup/activity-fill', mode: 'auto' },
{ html: 'health-checkup/questionnaire-fill.html', out: 'health-checkup/questionnaire-fill', mode: 'auto' },
// ========== 穿戴监测 ==========
{ html: 'wearable/index.html', out: 'wearable/index', mode: 'auto' },
{ html: 'wearable/heart-rate.html', out: 'wearable/heart-rate', mode: 'auto' },
{ html: 'wearable/heart-rate-alerts.html', out: 'wearable/heart-rate-alerts', mode: 'auto' },
{ html: 'wearable/blood-oxygen.html', out: 'wearable/blood-oxygen', mode: 'auto' },
{ html: 'wearable/blood-oxygen-alerts.html', out: 'wearable/blood-oxygen-alerts',mode: 'auto' },
{ html: 'wearable/blood-pressure.html', out: 'wearable/blood-pressure', mode: 'auto' },
{ html: 'wearable/blood-pressure-alerts.html', out: 'wearable/blood-pressure-alerts', mode: 'auto' },
{ html: 'wearable/temperature.html', out: 'wearable/temperature', mode: 'auto' },
{ html: 'wearable/temperature-alerts.html', out: 'wearable/temperature-alerts', mode: 'auto' },
{ html: 'wearable/stress.html', out: 'wearable/stress', mode: 'auto' },
{ html: 'wearable/stress-alerts.html', out: 'wearable/stress-alerts', mode: 'auto' },
{ html: 'wearable/steps.html', out: 'wearable/steps', mode: 'auto' },
{ html: 'wearable/sleep.html', out: 'wearable/sleep', mode: 'auto' },
{ html: 'wearable/ecg.html', out: 'wearable/ecg', mode: 'auto' },
{ html: 'wearable/ecg-detail.html', out: 'wearable/ecg-detail', mode: 'auto' },
{ html: 'wearable/exercise.html', out: 'wearable/exercise', mode: 'auto' },
{ html: 'wearable/exercise.html', out: 'wearable/exercise-month', mode: 'auto', click: { sel: '.tab-item', text: '月' } },
// ========== 环境监测首页(4 Tab - 严格 390×844 ==========
{ html: 'environment/index.html', out: 'environment/index', mode: 'fixed', envTab: 'outdoor' },
{ html: 'environment/index.html', out: 'environment/index-outdoor', mode: 'fixed', envTab: 'outdoor' },
{ html: 'environment/index.html', out: 'environment/index-indoor', mode: 'fixed', envTab: 'indoor' },
{ html: 'environment/index.html', out: 'environment/index-water', mode: 'fixed', envTab: 'water' },
{ html: 'environment/index.html', out: 'environment/index-pollen', mode: 'fixed', envTab: 'pollen' },
// ========== 环境监测首页 - 底部弹窗 - 严格 390×844 ==========
{ html: 'environment/index.html', out: 'environment/index-outdoor-sheet', mode: 'fixed', envTab: 'outdoor', clickMarker: true },
{ html: 'environment/index.html', out: 'environment/index-indoor-sheet', mode: 'fixed', envTab: 'indoor', clickMarker: true },
{ html: 'environment/index.html', out: 'environment/index-water-sheet', mode: 'fixed', envTab: 'water', clickMarker: true },
{ html: 'environment/index.html', out: 'environment/index-pollen-sheet', mode: 'fixed', envTab: 'pollen', clickMarker: true },
// ========== 环境监测设备详情 / 报警数据 ==========
{ html: 'environment/device-detail.html', out: 'environment/device-detail', mode: 'auto', query: '?tab=outdoor&id=OA001' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-outdoor', mode: 'auto', query: '?tab=outdoor&id=OA001' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-indoor', mode: 'auto', query: '?tab=indoor&id=IA001' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-water', mode: 'auto', query: '?tab=water&id=WQ001' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-pollen', mode: 'auto', query: '?tab=pollen&id=PL001' },
// 趋势变体:每个监测类型 - 近7日(综合指数)
{ html: 'environment/device-detail.html', out: 'environment/device-detail-outdoor-7d', mode: 'auto', query: '?tab=outdoor&id=OA001', trendPeriod: '7d' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-indoor-7d', mode: 'auto', query: '?tab=indoor&id=IA001', trendPeriod: '7d' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-water-7d', mode: 'auto', query: '?tab=water&id=WQ001', trendPeriod: '7d' },
{ html: 'environment/device-detail.html', out: 'environment/device-detail-pollen-7d', mode: 'auto', query: '?tab=pollen&id=PL001', trendPeriod: '7d' },
// 趋势变体:每个监测类型 - 某具体指标的近24小时(切换下拉指标)
// outdoor → PM2.5(索引按 metrics 顺序:0温度1湿度2太阳辐射3PM2.5)
{ html: 'environment/device-detail.html', out: 'environment/device-detail-outdoor-metric', mode: 'auto', query: '?tab=outdoor&id=OA001', trendMetric: 3 },
// indoor → CO₂(0温度1湿度2CO₂)
{ html: 'environment/device-detail.html', out: 'environment/device-detail-indoor-metric', mode: 'auto', query: '?tab=indoor&id=IA001', trendMetric: 2 },
// water → PH0温度1电导率2PH
{ html: 'environment/device-detail.html', out: 'environment/device-detail-water-metric', mode: 'auto', query: '?tab=water&id=WQ001', trendMetric: 2 },
// pollen → 花粉总数(0榆属...8花粉总数)
{ html: 'environment/device-detail.html', out: 'environment/device-detail-pollen-metric', mode: 'auto', query: '?tab=pollen&id=PL001', trendMetric: 8 },
{ html: 'environment/alarm-data.html', out: 'environment/alarm-data', mode: 'auto', query: '?tab=outdoor' },
{ html: 'environment/alarm-data.html', out: 'environment/alarm-data-outdoor', mode: 'auto', query: '?tab=outdoor' },
{ html: 'environment/alarm-data.html', out: 'environment/alarm-data-indoor', mode: 'auto', query: '?tab=indoor' },
{ html: 'environment/alarm-data.html', out: 'environment/alarm-data-water', mode: 'auto', query: '?tab=water' },
{ html: 'environment/alarm-data.html', out: 'environment/alarm-data-pollen', mode: 'auto', query: '?tab=pollen' },
];
(async () => {
const browser = await chromium.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const context = await browser.newContext({ viewport: { width: 390, height: 4000 } });
for (const t of tasks) {
const page = await context.newPage();
const filePath = path.join(root, t.html);
const url = `file:///${filePath.replace(/\\/g, '/')}${t.query || ''}`;
await page.goto(url, { waitUntil: 'networkidle' });
// 环境首页:切换 Tab
if (t.envTab && t.envTab !== 'outdoor') {
await page.evaluate((tab) => {
if (typeof switchTab === 'function') switchTab(tab);
}, t.envTab);
await page.waitForTimeout(200);
}
// 普通 click 操作
if (t.click) {
await page.evaluate((c) => {
const els = Array.from(document.querySelectorAll(c.sel));
const target = els.find(el => el.textContent.trim() === c.text);
if (target) target.click();
}, t.click);
await page.waitForTimeout(250);
}
// 点击地图设备标记弹出底部弹窗
if (t.clickMarker) {
await page.evaluate(() => {
const m = document.querySelector('.device-marker');
if (m) m.click();
});
await page.waitForTimeout(300);
}
// 环境设备详情:切换趋势时间维度(近7日)
if (t.trendPeriod && t.trendPeriod !== '24h') {
await page.evaluate((period) => {
const map = { '24h': '近24小时', '7d': '近7日', '30d': '近30日' };
const text = map[period];
const tabs = Array.from(document.querySelectorAll('.time-tab'));
const target = tabs.find(el => el.textContent.trim() === text);
if (target) target.click();
}, t.trendPeriod);
await page.waitForTimeout(250);
}
// 环境设备详情:切换趋势指标下拉
if (typeof t.trendMetric === 'number') {
await page.evaluate((idx) => {
const sel = document.getElementById('trendMetricSelect');
if (!sel) return;
sel.value = String(idx);
if (typeof onTrendMetricChange === 'function') onTrendMetricChange(sel);
else sel.dispatchEvent(new Event('change', { bubbles: true }));
}, t.trendMetric);
await page.waitForTimeout(250);
}
if (t.mode === 'fixed') {
// ========== 固定 390×844 ==========
// 还原 phone-shell 默认布局(不解除 flex/不调高)
await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (shell) {
shell.style.borderRadius = '0';
shell.style.boxShadow = 'none';
// 保留 height: 844px / display: flex 默认
}
document.body.style.padding = '0';
document.body.style.minHeight = 'auto';
document.body.style.background = '#fff';
});
await page.setViewportSize({ width: 390, height: 844 });
await page.waitForTimeout(200);
} else {
// ========== auto:短页面最低 844 / 长页面真实高度 ==========
await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (shell) {
shell.style.height = 'auto';
shell.style.minHeight = '844px'; // 关键:短页面下保住外壳高度
shell.style.maxHeight = 'none';
shell.style.borderRadius = '0';
shell.style.boxShadow = 'none';
shell.style.overflow = 'visible';
shell.style.display = 'block';
}
document.querySelectorAll('.scroll-content, .page-content, .main-content, .list-wrap, .content').forEach(c => {
c.style.overflow = 'visible';
c.style.maxHeight = 'none';
c.style.height = 'auto';
c.style.flex = 'none';
});
// 环境页 map-view 默认高度合理保留
document.body.style.padding = '0';
document.body.style.minHeight = 'auto';
document.body.style.background = '#fff';
});
await page.waitForTimeout(300);
const measured = await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (!shell) return document.body.scrollHeight;
return Math.ceil(shell.getBoundingClientRect().height);
});
const h = Math.max(measured, 844);
await page.setViewportSize({ width: 390, height: h });
await page.waitForTimeout(150);
}
const shell = await page.$('.phone-shell');
const target = shell || page;
await target.screenshot({
path: path.join(docDir, 'screenshots', `${t.out}.jpg`),
type: 'jpeg',
quality: 85,
});
console.log(`${t.out}.jpg [${t.mode}]`);
await page.close();
}
await browser.close();
console.log(`\n完成!共截取 ${tasks.length}`);
})();
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

@@ -352,7 +352,7 @@ function genTrend(metricName, period) {
'杨属':{range:[8,280],unit:'粒/千mm²'},'柏科':{range:[3,110],unit:'粒/千mm²'},
'柳属':{range:[5,170],unit:'粒/千mm²'},'蒿属':{range:[2,90],unit:'粒/千mm²'},
'藜科':{range:[2,55],unit:'粒/千mm²'},'苋科':{range:[1,38],unit:'粒/千mm²'},
'花粉总数':{range:[50,1100],unit:'粒/千mm²'},
'花粉总数':{range:[50,1100],unit:'粒/千mm²',limitUpper:500,limitLower:100},
'电导率':{range:[200,1700],unit:'μS/cm'},'PH':{range:[6.0,9.5],unit:''},
'盐度':{range:[100,950],unit:'mg/L'},'电阻率':{range:[400,3200],unit:'Ω·m'},
'TDS':{range:[100,850],unit:'mg/L'}
@@ -98,7 +98,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="q-title-card__name">职工健康状况问卷</div>
<div class="q-title-card__meta">
<span>共 8 题</span>
<span>预计用时 5 分钟</span>
</div>
</div>
@@ -1,222 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>打卡记录 - 员工端 | 健康CQ原型</title>
<style>
* { 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; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 日历区域 */
.calendar-section { background: #fff; margin: 0; padding: 16px; }
.calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.calendar-month { font-size: 16px; font-weight: 700; color: #1a1a1a; display: flex; align-items: center; gap: 8px; }
.calendar-nav { display: flex; align-items: center; gap: 12px; }
.calendar-nav-btn { width: 28px; height: 28px; border: none; background: #f5f5f5; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s; }
.calendar-nav-btn:active { background: #e8e8e8; }
.calendar-toggle { border: none; background: none; font-size: 12px; color: #667eea; cursor: pointer; display: flex; align-items: center; gap: 4px; }
.calendar-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 8px; }
.calendar-weekdays span { text-align: center; font-size: 12px; color: #999; padding: 4px 0; }
.calendar-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.calendar-day { height: 44px; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 14px; color: #333; border-radius: 8px; position: relative; cursor: pointer; gap: 2px; }
.calendar-day--empty { visibility: hidden; }
.calendar-day--today { font-weight: 700; color: #667eea; background: #f0edff; }
.calendar-day--checked .calendar-dot { display: block; }
.calendar-dot { display: none; width: 6px; height: 6px; border-radius: 50%; background: #667eea; }
.calendar-day--other { color: #ccc; }
.calendar-collapsed .calendar-days { max-height: 44px; overflow: hidden; }
/* 历史记录 */
.history-section { padding: 16px 12px; }
.history-title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin-bottom: 12px; padding: 0 4px; }
.history-list { display: flex; flex-direction: column; gap: 10px; }
.history-item { background: #fff; border-radius: 12px; padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; transition: transform 0.15s; text-decoration: none; }
.history-item:active { transform: scale(0.98); }
.history-item__left { display: flex; flex-direction: column; gap: 4px; }
.history-item__time { font-size: 14px; font-weight: 500; color: #1a1a1a; }
.history-item__date { font-size: 12px; color: #999; }
.history-item__right { display: flex; align-items: center; gap: 8px; }
.history-item__score { font-size: 14px; font-weight: 700; color: #667eea; }
.history-item__arrow { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; }
/* 底部安全区 */
.bottom-safe { height: 20px; 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="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>
<div class="scroll-content">
<!-- 日历 -->
<div class="calendar-section" id="calendarSection">
<div class="calendar-header">
<div class="calendar-month">
<span id="monthLabel">2026年5月</span>
</div>
<div class="calendar-nav">
<button class="calendar-nav-btn" onclick="changeMonth(-1)"><svg width="10" height="10" fill="none"><path d="M7 1L2 5l5 4" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
<button class="calendar-nav-btn" onclick="changeMonth(1)"><svg width="10" height="10" fill="none"><path d="M3 1l5 4-5 4" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
<button class="calendar-toggle" id="toggleBtn" onclick="toggleCalendar()">
<span id="toggleText">收起</span>
<svg id="toggleIcon" width="12" height="12" fill="none" style="transition:transform .2s"><path d="M2 5l4 4 4-4" stroke="#667eea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
</div>
<div class="calendar-weekdays">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="calendar-days" id="calendarDays">
<!-- JS动态渲染 -->
</div>
</div>
<!-- 历史记录 -->
<div class="history-section">
<!-- <div class="history-title">历史记录</div>-->
<div class="history-list">
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">08:15 完成打卡</div>
<div class="history-item__date">2026-05-14</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">07:52 完成打卡</div>
<div class="history-item__date">2026-05-13</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">08:30 完成打卡</div>
<div class="history-item__date">2026-05-12</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">09:05 完成打卡</div>
<div class="history-item__date">2026-05-11</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">08:20 完成打卡</div>
<div class="history-item__date">2026-05-10</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">07:45 完成打卡</div>
<div class="history-item__date">2026-05-09</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
<a class="history-item" href="questionnaire-detail.html">
<div class="history-item__left">
<div class="history-item__time">08:10 完成打卡</div>
<div class="history-item__date">2026-05-08</div>
</div>
<div class="history-item__right">
<div class="history-item__score">+10 积分</div>
<div class="history-item__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>
</div>
</a>
</div>
</div>
<div class="bottom-safe"></div>
</div>
</div>
<script>
var currentYear = 2026, currentMonth = 5;
var checkedDays = [8,9,10,11,12,13,14];
var collapsed = false;
function renderCalendar() {
var firstDay = new Date(currentYear, currentMonth - 1, 1).getDay();
var daysInMonth = new Date(currentYear, currentMonth, 0).getDate();
var html = '';
for (var i = 0; i < firstDay; i++) html += '<div class="calendar-day calendar-day--empty"></div>';
for (var d = 1; d <= daysInMonth; d++) {
var cls = 'calendar-day';
var isToday = (d === 14 && currentMonth === 5 && currentYear === 2026);
var isChecked = (checkedDays.indexOf(d) !== -1 && currentMonth === 5 && currentYear === 2026);
if (isToday) cls += ' calendar-day--today';
if (isChecked) cls += ' calendar-day--checked';
html += '<div class="' + cls + '"><span>' + d + '</span><span class="calendar-dot"></span></div>';
}
document.getElementById('calendarDays').innerHTML = html;
document.getElementById('monthLabel').textContent = currentYear + '年' + currentMonth + '月';
}
function changeMonth(dir) {
currentMonth += dir;
if (currentMonth > 12) { currentMonth = 1; currentYear++; }
if (currentMonth < 1) { currentMonth = 12; currentYear--; }
renderCalendar();
}
function toggleCalendar() {
collapsed = !collapsed;
var section = document.getElementById('calendarSection');
var days = document.getElementById('calendarDays');
var text = document.getElementById('toggleText');
var icon = document.getElementById('toggleIcon');
if (collapsed) {
days.style.maxHeight = '44px';
days.style.overflow = 'hidden';
text.textContent = '展开';
icon.style.transform = 'rotate(180deg)';
} else {
days.style.maxHeight = '';
days.style.overflow = '';
text.textContent = '收起';
icon.style.transform = '';
}
}
renderCalendar();
</script>
</body>
</html>
@@ -1,213 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康排查 - 员工端 | 健康CQ原型</title>
<style>
* { 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; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 顶部积分卡片 */
.score-card { margin: 12px 12px 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; padding: 20px; color: #fff; position: relative; overflow: hidden; }
.score-card::after { content: ''; position: absolute; top: -30px; right: -30px; width: 120px; height: 120px; background: rgba(255,255,255,0.08); border-radius: 50%; }
.score-card__top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.score-card__points { display: flex; align-items: baseline; gap: 4px; }
.score-card__num { font-size: 36px; font-weight: 800; line-height: 1; }
.score-card__unit { font-size: 13px; opacity: 0.8; }
.score-card__streak { display: flex; align-items: center; gap: 4px; font-size: 13px; opacity: 0.9; background: rgba(255,255,255,0.15); padding: 4px 10px; border-radius: 20px; }
.score-card__streak strong { font-weight: 700; }
.score-card__actions { display: flex; gap: 10px; }
.btn-checkin { flex: 1; height: 44px; background: #fff; border: none; border-radius: 22px; font-size: 15px; font-weight: 600; color: #667eea; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; transition: transform 0.15s; }
.btn-checkin:active { transform: scale(0.96); }
.btn-record { height: 44px; background: rgba(255,255,255,0.2); border: none; border-radius: 22px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 4px; padding: 0 14px; font-size: 13px; color: #fff; font-weight: 500; transition: transform 0.15s; }
.btn-record:active { transform: scale(0.9); }
/* 积分榜 */
.rank-section { margin: 16px 12px 0; }
.rank-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; padding: 0 4px; }
.rank-header__title { font-size: 16px; font-weight: 700; color: #1a1a1a; display: flex; align-items: center; gap: 6px; }
.rank-header__sub { font-size: 12px; color: #999; }
.rank-me-top { background: linear-gradient(135deg, #f8f6ff 0%, #ede9fe 100%); border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; display: flex; align-items: center; gap: 12px; border: 1px solid #e0d4fc; }
.rank-me-top .rank-no { background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; }
.rank-me-top .rank-score { color: #667eea; }
.rank-list { background: #fff; border-radius: 16px; overflow: hidden; }
.rank-item { display: flex; align-items: center; padding: 14px 16px; gap: 12px; border-bottom: 1px solid #f5f5f5; }
.rank-item:last-child { border-bottom: none; }
.rank-no { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex-shrink: 0; }
.rank-no--1 { background: linear-gradient(135deg, #ffd700, #ffb800); color: #fff; }
.rank-no--2 { background: linear-gradient(135deg, #c0c0c0, #a8a8a8); color: #fff; }
.rank-no--3 { background: linear-gradient(135deg, #cd7f32, #b8690e); color: #fff; }
.rank-no--normal { background: #f5f5f5; color: #999; }
.rank-info { flex: 1; min-width: 0; }
.rank-name { font-size: 14px; font-weight: 500; color: #1a1a1a; }
.rank-dept { font-size: 12px; color: #999; margin-top: 2px; }
.rank-score { font-size: 15px; font-weight: 700; color: #667eea; }
.rank-me { background: #f8f6ff; }
.rank-me-tag { font-size: 10px; color: #667eea; background: #ede9fe; padding: 1px 6px; border-radius: 4px; margin-left: 6px; }
/* 底部安全区 */
.bottom-safe { height: 20px; 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="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>
<div class="scroll-content">
<!-- 积分卡片 -->
<div class="score-card">
<div class="score-card__top">
<div class="score-card__points">
<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>
<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>
<!-- 积分榜排名 -->
<div class="rank-section">
<div class="rank-header">
<div class="rank-header__title">🏆 积分榜</div>
<div class="rank-header__sub">采油一厂</div>
</div>
<div class="rank-me-top">
<div class="rank-no">12</div>
<div class="rank-info">
<div class="rank-name"><span class="rank-me-tag">我的排名</span></div>
<div class="rank-dept">生产运行部</div>
</div>
<div class="rank-score">1,280</div>
</div>
<div class="rank-list">
<div class="rank-item">
<div class="rank-no rank-no--1">1</div>
<div class="rank-info">
<div class="rank-name">张*明</div>
<div class="rank-dept">生产运行部</div>
</div>
<div class="rank-score">2,580</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--2">2</div>
<div class="rank-info">
<div class="rank-name">李*华</div>
<div class="rank-dept">安全环保部</div>
</div>
<div class="rank-score">2,340</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--3">3</div>
<div class="rank-info">
<div class="rank-name">王*强</div>
<div class="rank-dept">技术发展部</div>
</div>
<div class="rank-score">2,120</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">4</div>
<div class="rank-info">
<div class="rank-name">赵*丽</div>
<div class="rank-dept">综合管理部</div>
</div>
<div class="rank-score">1,950</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">5</div>
<div class="rank-info">
<div class="rank-name">刘*伟</div>
<div class="rank-dept">地质研究所</div>
</div>
<div class="rank-score">1,820</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">6</div>
<div class="rank-info">
<div class="rank-name">孙*芳</div>
<div class="rank-dept">人力资源部</div>
</div>
<div class="rank-score">1,680</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">7</div>
<div class="rank-info">
<div class="rank-name">陈*军</div>
<div class="rank-dept">设备管理部</div>
</div>
<div class="rank-score">1,520</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">8</div>
<div class="rank-info">
<div class="rank-name">周*霞</div>
<div class="rank-dept">财务资产部</div>
</div>
<div class="rank-score">1,410</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">9</div>
<div class="rank-info">
<div class="rank-name">吴*涛</div>
<div class="rank-dept">工程技术部</div>
</div>
<div class="rank-score">1,380</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">10</div>
<div class="rank-info">
<div class="rank-name">郑*娜</div>
<div class="rank-dept">党群工作部</div>
</div>
<div class="rank-score">1,350</div>
</div>
<div class="rank-item">
<div class="rank-no rank-no--normal">11</div>
<div class="rank-info">
<div class="rank-name">马*飞</div>
<div class="rank-dept">物资供应部</div>
</div>
<div class="rank-score">1,310</div>
</div>
<div class="rank-item rank-me">
<div class="rank-no rank-no--normal">12</div>
<div class="rank-info">
<div class="rank-name"><span class="rank-me-tag">我的排名</span></div>
<div class="rank-dept">生产运行部</div>
</div>
<div class="rank-score">1,280</div>
</div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
</div>
</body>
</html>
@@ -1,172 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>问卷详情 - 员工端 | 健康CQ原型</title>
<style>
* { 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; background: #f5f7fa; padding: 12px; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 问卷信息 */
.detail-header { background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 12px; }
.detail-header__title { font-size: 16px; font-weight: 700; color: #1a1a1a; margin-bottom: 6px; }
.detail-header__meta { font-size: 12px; color: #999; display: flex; gap: 16px; }
/* 分类标题 */
.category-title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin: 8px 0 10px; padding-left: 10px; border-left: 3px solid #667eea; }
/* 问题卡片 */
.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; }
.option-item { padding: 10px 14px; border-radius: 8px; border: 1px solid #e8e8e8; font-size: 13px; color: #666; display: flex; align-items: center; gap: 8px; }
.option-item--selected { border-color: #667eea; background: #f8f6ff; color: #667eea; font-weight: 500; }
.option-item__check { width: 18px; height: 18px; border-radius: 50%; border: 2px solid #ddd; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.option-item--selected .option-item__check { border-color: #667eea; background: #667eea; }
.option-item--selected .option-item__check::after { content: '✓'; color: #fff; font-size: 10px; font-weight: 700; }
/* 底部安全区 */
.bottom-safe { height: 20px; 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="location.href='checkin-record.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>
<div class="scroll-content">
<div class="detail-header">
<div class="detail-header__title">每日健康自查问卷</div>
<div class="detail-header__meta">
<span>提交时间:2026-05-14 08:15</span>
<span>+10 积分</span>
</div>
</div>
<!-- 身体状况 -->
<div class="category-title">身体状况</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">1. 今天您的整体身体感觉如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item option-item--selected"><span class="option-item__check"></span>精力充沛,状态良好</div>
<div class="option-item"><span class="option-item__check"></span>一般,没有明显不适</div>
<div class="option-item"><span class="option-item__check"></span>有些疲惫或不适</div>
<div class="option-item"><span class="option-item__check"></span>身体明显不舒服</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">2. 昨晚的睡眠质量如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item"><span class="option-item__check"></span>很好,一觉到天亮</div>
<div class="option-item option-item--selected"><span class="option-item__check"></span>还行,中途醒过1-2次</div>
<div class="option-item"><span class="option-item__check"></span>较差,多次醒来或难以入睡</div>
<div class="option-item"><span class="option-item__check"></span>几乎没怎么睡着</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">3. 是否有头痛、头晕等不适症状?<span class="question-card__freq">(每周)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
<div class="option-item option-item--selected"><span class="option-item__check"></span>没有任何不适</div>
<div class="option-item"><span class="option-item__check"></span>轻微不适,不影响工作</div>
<div class="option-item"><span class="option-item__check"></span>有明显不适,但可以坚持</div>
<div class="option-item"><span class="option-item__check"></span>不适严重,需要休息</div>
</div>
</div>
<!-- 心理状态 -->
<div class="category-title">心理状态</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">4. 今天的情绪状态如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item option-item--selected"><span class="option-item__check"></span>心情愉悦,积极乐观</div>
<div class="option-item"><span class="option-item__check"></span>平静,情绪稳定</div>
<div class="option-item"><span class="option-item__check"></span>有些焦虑或烦躁</div>
<div class="option-item"><span class="option-item__check"></span>情绪低落,压力较大</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">5. 近期是否感到工作压力较大?<span class="question-card__freq">(每月)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
<div class="option-item option-item--selected"><span class="option-item__check"></span>压力适中,能正常应对</div>
<div class="option-item"><span class="option-item__check"></span>略有压力,基本可控</div>
<div class="option-item"><span class="option-item__check"></span>压力较大,有些吃力</div>
<div class="option-item"><span class="option-item__check"></span>压力很大,需要帮助</div>
</div>
</div>
<!-- 生活习惯 -->
<div class="category-title">生活习惯</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">6. 昨天是否进行了运动锻炼?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item"><span class="option-item__check"></span>是,运动30分钟以上</div>
<div class="option-item option-item--selected"><span class="option-item__check"></span>是,运动了15-30分钟</div>
<div class="option-item"><span class="option-item__check"></span>简单活动了一下</div>
<div class="option-item"><span class="option-item__check"></span>没有运动</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">7. 昨天的饮食是否规律?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item option-item--selected"><span class="option-item__check"></span>三餐规律,营养均衡</div>
<div class="option-item"><span class="option-item__check"></span>基本规律,偶尔不均衡</div>
<div class="option-item"><span class="option-item__check"></span>有一餐没按时吃</div>
<div class="option-item"><span class="option-item__check"></span>饮食很不规律</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<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">
<div class="option-item"><span class="option-item__check"></span>充足,达到8杯以上</div>
<div class="option-item option-item--selected"><span class="option-item__check"></span>基本够,5-8杯</div>
<div class="option-item"><span class="option-item__check"></span>偏少,不到5杯</div>
<div class="option-item"><span class="option-item__check"></span>很少喝水</div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
</div>
</body>
</html>
@@ -1,208 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>每日打卡 - 员工端 | 健康CQ原型</title>
<style>
* { 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; }
.nav-setting { font-size: 12px; color: #667eea; cursor: pointer; white-space: nowrap; text-decoration: none; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; padding: 12px; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 分类标题 */
.category-title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin: 8px 0 10px; padding-left: 10px; border-left: 3px solid #667eea; }
.category-title:first-child { margin-top: 0; }
/* 问题卡片 */
.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; }
.option-item { padding: 10px 14px; border-radius: 8px; border: 1px solid #e8e8e8; font-size: 13px; color: #666; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: all 0.15s; }
.option-item:active { transform: scale(0.98); }
.option-item.selected { border-color: #667eea; background: #f8f6ff; color: #667eea; font-weight: 500; }
.option-item__radio { width: 18px; height: 18px; border-radius: 50%; border: 2px solid #ddd; display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: all 0.15s; }
.option-item.selected .option-item__radio { border-color: #667eea; background: #667eea; }
.option-item.selected .option-item__radio::after { content: ''; width: 6px; height: 6px; background: #fff; border-radius: 50%; }
/* 底部提交 */
.submit-bar { padding: 12px 16px; background: #fff; border-top: 1px solid #f0f0f0; flex-shrink: 0; }
.btn-submit { width: 100%; height: 48px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 24px; font-size: 16px; font-weight: 600; color: #fff; cursor: pointer; transition: transform 0.15s; }
.btn-submit:active { transform: scale(0.97); }
.bottom-safe { height: 20px; flex-shrink: 0; background: #fff; }
/* 打卡成功弹窗 */
.dialog-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.45); z-index: 300; display: none; align-items: center; justify-content: center; }
.dialog-mask.show { display: flex; }
.dialog { background: #fff; border-radius: 20px; width: 320px; padding: 32px 24px 24px; text-align: center; box-shadow: 0 12px 48px rgba(0,0,0,0.2); animation: dialogIn 0.25s ease; }
@keyframes dialogIn { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.dialog__icon { width: 64px; height: 64px; margin: 0 auto 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.dialog__title { font-size: 20px; font-weight: 800; color: #1a1a1a; margin-bottom: 6px; }
.dialog__points { font-size: 14px; color: #667eea; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; justify-content: center; gap: 4px; }
.dialog__points-num { font-size: 24px; font-weight: 800; }
.dialog__divider { width: 40px; height: 2px; background: linear-gradient(90deg, #667eea, #764ba2); border-radius: 1px; margin: 0 auto 16px; }
.dialog__quote { font-size: 13px; color: #666; line-height: 1.8; padding: 12px 16px; background: #f8f6ff; border-radius: 10px; margin-bottom: 20px; position: relative; font-style: italic; }
.dialog__quote::before { content: '\201C'; font-size: 28px; color: #667eea; opacity: 0.3; position: absolute; top: 2px; left: 8px; line-height: 1; font-style: normal; }
.dialog__btn { width: 100%; height: 44px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 22px; font-size: 16px; font-weight: 600; color: #fff; cursor: pointer; transition: transform 0.15s; }
.dialog__btn:active { transform: scale(0.97); }
</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="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>
<a class="nav-setting" href="questionnaire-setting.html">题目设置</a>
</div>
<div class="scroll-content">
<!-- 身体状况 -->
<div class="category-title">身体状况</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">1. 今天您的整体身体感觉如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>精力充沛,状态良好</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>一般,没有明显不适</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>有些疲惫或不适</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>身体明显不舒服</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">2. 昨晚的睡眠质量如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>很好,一觉到天亮</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>还行,中途醒过1-2次</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>较差,多次醒来或难以入睡</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>几乎没怎么睡着</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">3. 是否有头痛、头晕等不适症状?<span class="question-card__freq">(每周)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>没有任何不适</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>轻微不适,不影响工作</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>有明显不适,但可以坚持</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>不适严重,需要休息</div>
</div>
</div>
<!-- 心理状态 -->
<div class="category-title">心理状态</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">4. 今天的情绪状态如何?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>心情愉悦,积极乐观</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>平静,情绪稳定</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>有些焦虑或烦躁</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>情绪低落,压力较大</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">5. 近期是否感到工作压力较大?<span class="question-card__freq">(每月)</span></div>
<span class="tag-optional">选填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>压力适中,能正常应对</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>略有压力,基本可控</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>压力较大,有些吃力</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>压力很大,需要帮助</div>
</div>
</div>
<!-- 生活习惯 -->
<div class="category-title">生活习惯</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">6. 昨天是否进行了运动锻炼?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>是,运动30分钟以上</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>是,运动了15-30分钟</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>简单活动了一下</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>没有运动</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<div class="question-card__title">7. 昨天的饮食是否规律?<span class="question-card__freq">(每日)</span></div>
<span class="tag-required">必填</span>
</div>
<div class="question-card__options">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>三餐规律,营养均衡</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>基本规律,偶尔不均衡</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>有一餐没按时吃</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>饮食很不规律</div>
</div>
</div>
<div class="question-card">
<div class="question-card__header">
<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">
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>充足,达到8杯以上</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>基本够,5-8杯</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>偏少,不到5杯</div>
<div class="option-item" onclick="selectOption(this)"><span class="option-item__radio"></span>很少喝水</div>
</div>
</div>
</div>
<div class="submit-bar">
<button class="btn-submit" onclick="showSuccess()">提交打卡</button>
</div>
<div class="bottom-safe"></div>
<!-- 打卡成功弹窗 -->
<div class="dialog-mask" id="dialogMask">
<div class="dialog">
<div class="dialog__icon">
<svg width="32" height="32" fill="none"><path d="M8 16l5 5 11-11" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="dialog__title">打卡成功!</div>
<div class="dialog__points">积分 +<span class="dialog__points-num">10</span></div>
<div class="dialog__divider"></div>
<div class="dialog__quote">健康是人生第一财富,每一次坚持打卡,都是对自己最好的投资。</div>
<button class="dialog__btn" onclick="location.href='index.html'">确 认</button>
</div>
</div>
</div>
<script>
function selectOption(el) {
var siblings = el.parentElement.querySelectorAll('.option-item');
siblings.forEach(function(s) { s.classList.remove('selected'); });
el.classList.add('selected');
}
function showSuccess() {
document.getElementById('dialogMask').classList.add('show');
}
</script>
</body>
</html>
@@ -1,176 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>题目设置 - 员工端 | 健康CQ原型</title>
<style>
* { 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; background: #f5f7fa; padding: 12px; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 提示 */
.setting-tip { background: #fffbe6; border: 1px solid #ffe58f; border-radius: 10px; padding: 12px 14px; margin-bottom: 12px; font-size: 12px; color: #ad8b00; line-height: 1.6; display: flex; gap: 8px; }
.setting-tip__icon { flex-shrink: 0; font-size: 16px; }
/* 分类标题 */
.category-title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin: 8px 0 10px; padding-left: 10px; border-left: 3px solid #667eea; }
.category-title:first-child { margin-top: 0; }
/* 题目卡片 */
.setting-card { background: #fff; border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; display: flex; align-items: flex-start; gap: 12px; }
.setting-card__content { flex: 1; min-width: 0; }
.setting-card__header { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.setting-card__title { font-size: 13px; font-weight: 500; color: #1a1a1a; line-height: 1.5; flex: 1; }
.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; }
.setting-card__toggle { flex-shrink: 0; margin-top: 4px; }
/* 开关 */
.toggle-switch { width: 44px; height: 24px; background: #ddd; border-radius: 12px; position: relative; cursor: pointer; transition: background 0.2s; }
.toggle-switch.on { background: #667eea; }
.toggle-switch__knob { width: 20px; height: 20px; background: #fff; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: left 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
.toggle-switch.on .toggle-switch__knob { left: 22px; }
/* 必填项开关置灰 */
.toggle-switch.disabled { background: #c5cbe8; opacity: 0.6; cursor: not-allowed; }
.toggle-switch.disabled .toggle-switch__knob { left: 22px; }
/* 底部保存 */
.save-bar { padding: 12px 16px; background: #fff; border-top: 1px solid #f0f0f0; flex-shrink: 0; }
.btn-save { width: 100%; height: 48px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 24px; font-size: 16px; font-weight: 600; color: #fff; cursor: pointer; transition: transform 0.15s; }
.btn-save:active { transform: scale(0.97); }
.bottom-safe { height: 20px; flex-shrink: 0; background: #fff; }
</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="location.href='questionnaire-fill.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>
<div class="scroll-content">
<div class="setting-tip">
<span class="setting-tip__icon">💡</span>
<span>必填题目无法隐藏,仅选填题目可以控制显示或隐藏。隐藏后将不会在每日打卡中显示。</span>
</div>
<!-- 身体状况 -->
<div class="category-title">身体状况</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">1. 今天您的整体身体感觉如何?</div>
<span class="tag-required">必填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch disabled"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">2. 昨晚的睡眠质量如何?</div>
<span class="tag-required">必填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch disabled"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">3. 是否有头痛、头晕等不适症状?</div>
<span class="tag-optional">选填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch on" onclick="toggleSwitch(this)"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<!-- 心理状态 -->
<div class="category-title">心理状态</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">4. 今天的情绪状态如何?</div>
<span class="tag-required">必填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch disabled"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">5. 近期是否感到工作压力较大?</div>
<span class="tag-optional">选填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch on" onclick="toggleSwitch(this)"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<!-- 生活习惯 -->
<div class="category-title">生活习惯</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">6. 昨天是否进行了运动锻炼?</div>
<span class="tag-required">必填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch disabled"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">7. 昨天的饮食是否规律?</div>
<span class="tag-required">必填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch disabled"><div class="toggle-switch__knob"></div></div>
</div>
</div>
<div class="setting-card">
<div class="setting-card__content">
<div class="setting-card__header">
<div class="setting-card__title">8. 昨天饮水量是否充足(8杯以上)?</div>
<span class="tag-optional">选填</span>
</div>
</div>
<div class="setting-card__toggle">
<div class="toggle-switch" onclick="toggleSwitch(this)"><div class="toggle-switch__knob"></div></div>
</div>
</div>
</div>
<div class="save-bar">
<button class="btn-save" onclick="alert('设置已保存')">保存设置</button>
</div>
<div class="bottom-safe"></div>
</div>
<script>
function toggleSwitch(el) {
if (el.classList.contains('disabled')) return;
el.classList.toggle('on');
}
</script>
</body>
</html>
-14
View File
@@ -56,7 +56,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.entry-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
.entry-icon { width: 44px; height: 44px; border-radius: 13px; display: flex; align-items: center; justify-content: center; font-size: 22px; flex-shrink: 0; }
.entry-icon--environment { background: linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%); }
.entry-icon--screening { background: linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%); }
.entry-info { flex: 1; min-width: 0; }
.entry-name { font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 5px; }
.entry-metrics { display: flex; gap: 10px; flex-wrap: wrap; }
@@ -170,19 +169,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="entry-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="entry-card" href="health-screening/index.html">-->
<!-- <div class="entry-icon entry-icon&#45;&#45;screening">🔍</div>-->
<!-- <div class="entry-info">-->
<!-- <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>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="entry-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>-->
<!-- 健康排查 V2 -->
<a class="entry-card" href="health-checkup/index.html">
<div class="entry-icon" style="background: linear-gradient(135deg, #f9f0ff 0%, #d3adf7 100%);">📋</div>
+144 -58
View File
@@ -29,31 +29,55 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.module__title { font-size: 16px; font-weight: 700; color: #1a1a1a; }
.module__more { font-size: 12px; color: #999; cursor: pointer; }
/* 体重管理 */
.weight-stats { display: flex; justify-content: space-around; text-align: center; margin-bottom: 16px; }
.weight-stats__item span { display: block; }
.weight-stats__val { font-size: 18px; font-weight: 700; color: #1a1a1a; }
.weight-stats__label { font-size: 11px; color: #999; margin-top: 2px; }
.weight-ball-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 16px; }
.weight-side { width: 74px; text-align: center; flex-shrink: 0; }
.weight-side__label { font-size: 12px; color: #666; margin-bottom: 6px; }
.weight-side__val { font-size: 32px; line-height: 1; font-weight: 700; color: #1a1a1a; }
.weight-side__unit { font-size: 12px; color: #999; margin-top: 4px; }
.weight-ball { width: 140px; height: 140px; margin: 0; border-radius: 50%; background: linear-gradient(180deg, #e6f7ff 0%, #1890ff 100%); position: relative; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 20px rgba(24,144,255,0.3); }
.weight-ball__inner { width: 110px; height: 110px; border-radius: 50%; background: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.weight-ball__val { font-size: 28px; font-weight: 800; color: #1890ff; }
.weight-ball__unit { font-size: 12px; color: #999; }
.weight-ball__wave { position: absolute; bottom: 0; left: 0; right: 0; height: 60%; border-radius: 0 0 70px 70px; background: linear-gradient(180deg, rgba(24,144,255,0.15), rgba(24,144,255,0.4)); overflow: hidden; }
.daily-date { font-size: 14px; color: #666; text-align: center; margin-bottom: 10px; }
.daily-checkin { display: flex; justify-content: space-around; margin-bottom: 14px; }
.checkin-item { text-align: center; flex: 1; }
.checkin-item__icon { font-size: 24px; margin-bottom: 4px; }
.checkin-item__label { font-size: 11px; color: #999; }
.checkin-item__val { font-size: 13px; font-weight: 600; color: #333; margin-top: 2px; }
.checkin-item__sub { font-size: 11px; color: #999; margin-top: 2px; line-height: 1.4; }
.bmi-box { background: #f5f7fa; border-radius: 8px; padding: 12px; text-align: center; }
.bmi-box__formula { font-size: 13px; color: #666; }
.bmi-box__result { font-size: 20px; font-weight: 700; color: #52c41a; margin-top: 4px; }
.bmi-box__tag { display: inline-block; font-size: 11px; color: #52c41a; background: #f6ffed; border-radius: 4px; padding: 2px 8px; margin-top: 4px; }
.weight-header { background: linear-gradient(135deg, #ff6b6b, #ff9a56); border-radius: 12px 12px 0 0; margin: -16px -16px 0; padding: 16px 16px 20px; position: relative; }
.weight-header__title { font-size: 20px; font-weight: 700; color: #fff; }
.weight-header__info { position: absolute; top: 12px; right: 16px; font-size: 11px; color: rgba(255,255,255,0.85); }
.weight-header__info strong { color: #fff; }
.weight-ball-section { background: #fff; border-radius: 0 0 12px 12px; margin-bottom: 16px; overflow: hidden; }
.weight-avatars { display: flex; align-items: center; justify-content: space-around; padding: 20px 16px 16px; }
.weight-avatar-side { text-align: center; flex: 1; }
.weight-avatar__img { width: 56px; height: 72px; margin: 0 auto 8px; position: relative; }
.weight-avatar__img svg { width: 100%; height: 100%; }
.weight-avatar__label { font-size: 13px; color: #666; margin-bottom: 4px; }
.weight-avatar__val { font-size: 22px; font-weight: 700; color: #1a1a1a; }
.weight-avatar__bmi { font-size: 12px; color: #999; margin-top: 2px; }
.weight-ball-wrap { text-align: center; flex: 0 0 auto; position: relative; z-index: 1; margin: 0 8px; }
.weight-ball-wrap__top { font-size: 12px; color: #666; margin-bottom: 2px; }
.weight-ball-wrap__ruler { width: 2px; height: 12px; background: #ddd; margin: 0 auto 2px; }
.weight-ball { width: 150px; height: 150px; border-radius: 50%; position: relative; overflow: hidden; box-shadow: 0 4px 20px rgba(24,144,255,0.15); }
.weight-ball__bg { position: absolute; inset: 0; background: linear-gradient(180deg, #f0f8ff 0%, #e6f7ff 50%, #7ec8e3 100%); }
.weight-ball__wave { position: absolute; bottom: 0; left: 0; right: 0; height: 55%; }
.weight-ball__wave svg { width: 100%; height: 100%; display: block; }
.weight-ball__content { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 2; }
.weight-ball__val { font-size: 36px; font-weight: 800; color: #1a1a1a; line-height: 1; }
.weight-ball__unit { font-size: 13px; color: #333; }
.weight-ball__meta { font-size: 11px; color: #666; margin-top: 2px; }
.weight-middle { padding: 0 16px 16px; display: flex; justify-content: center; }
.weight-change { text-align: center; }
.weight-change__label { font-size: 12px; color: #999; }
.weight-change__val { font-size: 20px; font-weight: 700; color: #1890ff; margin: 4px 0; }
.weight-change__val.negative { color: #52c41a; }
.weight-change__need { font-size: 12px; color: #faad14; }
.weight-change__need span { font-weight: 600; }
.daily-date { font-size: 13px; color: #666; text-align: center; margin-bottom: 12px; font-weight: 600; }
.daily-checkin { display: flex; gap: 10px; margin-bottom: 16px; }
.checkin-card { flex: 1; border-radius: 12px; padding: 14px 10px; text-align: center; color: #fff; }
.checkin-card--eat { background: linear-gradient(135deg, #52c41a, #73d13d); }
.checkin-card--move { background: linear-gradient(135deg, #fa8c16, #ffa940); }
.checkin-card--weight { background: linear-gradient(135deg, #722ed1, #9254de); }
.checkin-card__icon { font-size: 22px; margin-bottom: 4px; }
.checkin-card__val { font-size: 18px; font-weight: 800; line-height: 1.2; }
.checkin-card__unit { font-size: 12px; font-weight: 400; }
.checkin-card__recommend { font-size: 11px; opacity: 0.85; margin-top: 4px; }
.checkin-card__days { font-size: 11px; opacity: 0.85; margin-top: 2px; }
.bmi-section { background: #f8f9fa; border-radius: 10px; padding: 14px; margin-bottom: 12px; }
.bmi-formula { font-size: 13px; color: #666; text-align: center; margin-bottom: 12px; }
.bmi-table { display: flex; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; background: #fff; }
.bmi-table__cell { flex: 1; text-align: center; padding: 10px 4px; border-right: 1px solid #e0e0e0; }
.bmi-table__cell:last-child { border-right: none; }
.bmi-table__range { font-size: 12px; color: #333; font-weight: 600; }
.bmi-table__label { font-size: 11px; color: #999; margin-top: 4px; }
.bmi-center-note { font-size: 11px; color: #999; text-align: center; margin-top: 10px; }
/* 健康数据 */
.health-scroll { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.health-scroll::-webkit-scrollbar { display: none; }
@@ -138,44 +162,106 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<!-- 体重管理模块 -->
<div class="module">
<div class="module__hd"><span class="module__title">体重管理</span><span class="module__more">查看详情 </span></div>
<div class="weight-stats">
<div class="weight-stats__item"><span class="weight-stats__val">28</span><span class="weight-stats__label">活动天数</span></div>
<div class="weight-stats__item"><span class="weight-stats__val">156</span><span class="weight-stats__label">排名</span></div>
<div class="module" style="padding:0;overflow:hidden;">
<div class="weight-header">
<span class="weight-header__title">体重管理</span>
<span class="weight-header__info">体重管理活动 <strong>58</strong> 天,第 <strong>8</strong></span>
</div>
<div class="weight-ball-row">
<div class="weight-side">
<div class="weight-side__label">初始体重</div>
<div class="weight-side__val">75.0</div>
<div class="weight-side__unit">BMI:30</div>
</div>
<div class="weight-ball">
<div class="weight-ball__wave"></div>
<div class="weight-ball__inner">
<span class="weight-ball__val">70.5</span>
<span class="weight-ball__unit">当前体重(kg)</span>
<span class="weight-ball__unit">BMI:28</span>
<span class="weight-ball__unit">体脂率:26</span>
<div class="weight-ball-section">
<div class="weight-avatars">
<div class="weight-avatar-side">
<div class="weight-avatar__img">
<svg viewBox="0 0 56 72" fill="none">
<circle cx="28" cy="12" r="10" fill="#FFD591"/>
<rect x="16" y="22" width="24" height="20" rx="4" fill="#FF4D4F"/>
<rect x="12" y="42" width="10" height="22" rx="4" fill="#FF4D4F"/>
<rect x="34" y="42" width="10" height="22" rx="4" fill="#FF4D4F"/>
<circle cx="22" cy="10" r="1.5" fill="#333"/>
<circle cx="34" cy="10" r="1.5" fill="#333"/>
<path d="M25 14 Q28 16 31 14" stroke="#333" stroke-width="1" fill="none"/>
</svg>
</div>
<div class="weight-avatar__label">初始体重</div>
<div class="weight-avatar__val">73.5</div>
<div class="weight-avatar__bmi">BMI 23.6</div>
</div>
<div class="weight-ball-wrap">
<div class="weight-ball-wrap__top">当前体重</div>
<div class="weight-ball-wrap__ruler"></div>
<div class="weight-ball">
<div class="weight-ball__bg"></div>
<div class="weight-ball__wave">
<svg viewBox="0 0 150 82" preserveAspectRatio="none">
<path d="M0 30 Q25 15 50 30 Q75 45 100 30 Q125 15 150 30 L150 82 L0 82 Z" fill="#36cfc9" opacity="0.7"/>
<path d="M0 35 Q25 20 50 35 Q75 50 100 35 Q125 20 150 35 L150 82 L0 82 Z" fill="#1890ff" opacity="0.5"/>
</svg>
</div>
<div class="weight-ball__content">
<span class="weight-ball__val">62.6</span>
<span class="weight-ball__unit">公斤</span>
<span class="weight-ball__meta">BMI 23.6</span>
<span class="weight-ball__meta">体脂率 20%</span>
</div>
</div>
</div>
<div class="weight-avatar-side">
<div class="weight-avatar__img">
<svg viewBox="0 0 56 72" fill="none">
<circle cx="28" cy="12" r="10" fill="#FFD591"/>
<rect x="16" y="22" width="24" height="20" rx="4" fill="#1890ff"/>
<rect x="12" y="42" width="10" height="22" rx="4" fill="#1890ff"/>
<rect x="34" y="42" width="10" height="22" rx="4" fill="#1890ff"/>
<circle cx="22" cy="10" r="1.5" fill="#333"/>
<circle cx="34" cy="10" r="1.5" fill="#333"/>
<path d="M25 14 Q28 16 31 14" stroke="#333" stroke-width="1" fill="none"/>
</svg>
</div>
<div style="font-size:11px;color:#ff4d4f;margin-bottom:4px;">目标承诺</div>
<div class="weight-avatar__label">目标体重</div>
<div class="weight-avatar__val">56</div>
<div class="weight-avatar__bmi">BMI 22.5</div>
</div>
</div>
<div class="weight-side">
<div class="weight-side__label">目标体重</div>
<div class="weight-side__val">65.0</div>
<div class="weight-side__unit">BMI:25</div>
<div class="weight-middle">
<div class="weight-change">
<div class="weight-change__label">体重变化</div>
<div class="weight-change__val negative">-10.9</div>
<div class="weight-change__need">还要减 <span>6.6</span></div>
</div>
</div>
</div>
<div class="bmi-box">
<div class="bmi-box__formula">BMI = 体重(kg) ÷ 身高(m)² = 70.5 ÷ 1.75² </div>
<div class="bmi-box__formula">BMI<18.5消瘦 BMI<24正常 BMI<28超重 BMI>32肥胖</div>
<span class="bmi-box__tag">中心型肥胖:男性腰围≥90cm 女性腰围≥85cm</span>
</div>
<div class="daily-date">📅 2026年4月29日 星期三</div>
<div class="daily-checkin">
<div class="checkin-item"><div class="checkin-item__icon">🍽️</div><div class="checkin-item__label"></div><div class="checkin-item__val">1680 kcal</div><div class="checkin-item__sub">推荐 1900 kcal</div><div class="checkin-item__sub">累计行动 52 天</div></div>
<div class="checkin-item"><div class="checkin-item__icon">🏃</div><div class="checkin-item__label"></div><div class="checkin-item__val">320 kcal</div><div class="checkin-item__sub">推荐 300 kcal</div><div class="checkin-item__sub">累计行动 36 天</div></div>
<div class="checkin-item"><div class="checkin-item__icon">⚖️</div><div class="checkin-item__label">体重</div><div class="checkin-item__val">70.5 kg</div><div class="checkin-item__sub">推荐 68.0 kg</div><div class="checkin-item__sub">累计行动 28 天</div></div>
<div style="padding:0 16px;">
<div class="daily-date">2025年08月12日</div>
<div class="daily-checkin">
<div class="checkin-card checkin-card--eat">
<div class="checkin-card__icon">🍽️</div>
<div class="checkin-card__val">1086<span class="checkin-card__unit">kcal</span></div>
<div class="checkin-card__recommend">推荐 1950千卡</div>
<div class="checkin-card__days">累计行动52天</div>
</div>
<div class="checkin-card checkin-card--move">
<div class="checkin-card__icon">🏃</div>
<div class="checkin-card__val">12586<span class="checkin-card__unit"></span></div>
<div class="checkin-card__recommend">推荐 12000步</div>
<div class="checkin-card__days">累计行动362天</div>
</div>
<div class="checkin-card checkin-card--weight">
<div class="checkin-card__icon">⚖️</div>
<div class="checkin-card__val">62.60<span class="checkin-card__unit">kg</span></div>
<div class="checkin-card__recommend">推荐 75-80公斤</div>
<div class="checkin-card__days">累计行动3122天</div>
</div>
</div>
<div class="bmi-section">
<div class="bmi-formula">BMI = 体重(kg) ÷ 身高(m)²</div>
<div class="bmi-table">
<div class="bmi-table__cell"><div class="bmi-table__range">BMI&lt;18.5</div><div class="bmi-table__label">消瘦</div></div>
<div class="bmi-table__cell"><div class="bmi-table__range">18.5≤BMI&lt;24</div><div class="bmi-table__label">正常</div></div>
<div class="bmi-table__cell"><div class="bmi-table__range">24≤BMI&lt;28</div><div class="bmi-table__label">超重</div></div>
<div class="bmi-table__cell"><div class="bmi-table__range">BMI≥28</div><div class="bmi-table__label">肥胖</div></div>
</div>
<div class="bmi-center-note">(中心型肥胖:男性腰围≥90cm、女性≥85cm)</div>
</div>
</div>
</div>
<!-- 健康数据模块 -->
+14 -16
View File
@@ -325,10 +325,11 @@ const SITE_MAP = [
{ file:'ano-result.html', label:'结果异常' }
]},
{ name:'运动管理', dir:'web-admin/exercise', pages:[] },
{ name:'健康监测', dir:'web-admin/health-monitor', entry:'archive-personal.html', prd:'web-admin/health-monitor/doc/PRD.md', 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-crowd-status-detail.html', label:'单位报表-健康现状详情' },
{ file:'archive-unit-health-checkup-detail.html', label:'单位报表-健康排查详情' },
{ file:'archive-unit-wearable-detail.html', label:'单位报表-穿戴监测详情' },
{ file:'archive-unit-environment-detail.html', label:'单位报表-环境监测详情' },
@@ -336,18 +337,12 @@ const SITE_MAP = [
{ file:'crowd-status-employee.html', label:'健康现状-员工评估信息' },
{ file:'crowd-status-unit.html', label:'健康现状-单位评估统计' },
{ file:'health-checkup/survey-category.html', label:'排查问卷-问题分类' },
{ file:'health-checkup/survey-score.html', label:'排查问卷-积分规则' },
{ file:'health-checkup/survey-template.html', label:'排查问卷-模板信息' },
{ file:'health-checkup/survey-question.html', label:'排查问卷-问题管理' },
{ file:'health-checkup/survey-manage.html', label:'问卷管理' },
{ file:'health-checkup/survey-edit.html', label:'新增/编辑问卷', dialog:true },
{ file:'health-checkup/activity-manage.html', label:'活动管理' },
{ file:'health-checkup/activity-stat.html', label:'活动统计' },
{ file:'health-checkup/activity-edit.html', label:'新增/编辑活动', dialog:true },
{ file:'health-checkup/activity-detail.html', label:'活动详情', dialog:true },
{ file:'health-checkup/maxim.html', label:'格言管理' },
{ file:'health-checkup/record-user.html', label:'排查记录-用户排查' },
{ file:'health-checkup/record-dept.html', label:'排查记录-部门排查' },
{ file:'wearable/tool-manage.html', label:'穿戴监测-工具管理' },
{ file:'wearable/bind-record.html', label:'穿戴监测-绑定记录' },
{ file:'wearable/user-data.html', label:'穿戴监测-用户数据' },
@@ -378,28 +373,39 @@ const SITE_MAP = [
{ file:'environment/indoor-device-list.html', label:'室内空气-设备列表' },
{ file:'environment/indoor-device-category.html', label:'室内空气-设备分类' },
{ file:'environment/indoor-data-monitor.html', label:'室内空气-数据监测' },
{ file:'environment/indoor-data-analysis.html', label:'室内空气-数据分析' },
{ file:'environment/indoor-exception-alarm.html', label:'室内空气-异常报警' },
{ file:'environment/indoor-app-record.html', label:'室内空气-应用记录' },
{ file:'environment/indoor-history-data.html', label:'室内空气-历史数据' },
{ file:'environment/indoor-history-monitor-detail.html', label:'室内空气-历史监测详情' },
{ file:'environment/indoor-history-alarm-detail.html', label:'室内空气-历史报警详情' },
{ file:'environment/outdoor-device-list.html', label:'室外空气-设备列表' },
{ file:'environment/outdoor-device-category.html', label:'室外空气-设备分类' },
{ file:'environment/outdoor-data-monitor.html', label:'室外空气-数据监测' },
{ file:'environment/outdoor-data-analysis.html', label:'室外空气-数据分析' },
{ file:'environment/outdoor-exception-alarm.html', label:'室外空气-异常报警' },
{ file:'environment/outdoor-app-record.html', label:'室外空气-应用记录' },
{ file:'environment/outdoor-history-data.html', label:'室外空气-历史数据' },
{ file:'environment/outdoor-history-monitor-detail.html', label:'室外空气-历史监测详情' },
{ file:'environment/outdoor-history-alarm-detail.html', label:'室外空气-历史报警详情' },
{ file:'environment/water-device-list.html', label:'水质监测-设备列表' },
{ file:'environment/water-device-category.html', label:'水质监测-设备分类' },
{ file:'environment/water-data-monitor.html', label:'水质监测-数据监测' },
{ file:'environment/water-data-analysis.html', label:'水质监测-数据分析' },
{ file:'environment/water-exception-alarm.html', label:'水质监测-异常报警' },
{ file:'environment/water-app-record.html', label:'水质监测-应用记录' },
{ file:'environment/water-history-data.html', label:'水质监测-历史数据' },
{ file:'environment/water-history-monitor-detail.html', label:'水质监测-历史监测详情' },
{ file:'environment/water-history-alarm-detail.html', label:'水质监测-历史报警详情' },
{ file:'environment/pollen-device-list.html', label:'花粉监测-设备列表' },
{ file:'environment/pollen-device-category.html', label:'花粉监测-设备分类' },
{ file:'environment/pollen-data-monitor.html', label:'花粉监测-数据监测' },
{ file:'environment/pollen-data-analysis.html', label:'花粉监测-数据分析' },
{ file:'environment/pollen-exception-alarm.html', label:'花粉监测-异常报警' },
{ file:'environment/pollen-app-record.html', label:'花粉监测-应用记录' },
{ file:'environment/pollen-history-data.html', label:'花粉监测-历史数据' },
{ file:'environment/pollen-knowledge.html', label:'花粉监测-知识科普' },
{ file:'environment/pollen-history-monitor-detail.html', label:'花粉监测-历史监测详情' },
{ file:'environment/pollen-history-alarm-detail.html', label:'花粉监测-历史报警详情' },
{ file:'exception-dirty.html', label:'异常事件-系统脏数据' },
{ file:'exception-result.html', label:'异常事件-结果异常' }
]},
@@ -578,18 +584,10 @@ const SITE_MAP = [
{ file:'wearable/ecg.html', label:'心电图列表' },
{ file:'wearable/ecg-detail.html', label:'心电图详情' },
{ file:'wearable/exercise.html', label:'锻炼记录' },
{ file:'health-screening/index.html', label:'健康排查首页' },
{ file:'health-screening/questionnaire-fill.html', label:'每日打卡填写' },
{ file:'health-screening/questionnaire-setting.html', label:'题目设置' },
{ file:'health-screening/checkin-record.html', label:'打卡记录' },
{ file:'health-screening/questionnaire-detail.html', label:'问卷详情' },
{ file:'health-checkup/index.html', label:'健康排查活动列表' },
{ file:'health-checkup/activity-detail.html', label:'活动详情' },
{ file:'health-checkup/activity-fill.html', label:'问卷填写' },
{ file:'health-checkup/questionnaire-fill.html', label:'问卷详情查看' },
{ file:'weight/index.html', label:'体重监测首页' },
{ file:'weight/history.html', label:'体重历史数据' },
{ file:'weight/report-detail.html', label:'体重详细报告' },
{ file:'environment/index.html', label:'环境监测首页' },
{ file:'environment/device-detail.html', label:'环境监测-设备详情' },
{ file:'environment/alarm-data.html', label:'环境监测-报警数据' }
@@ -0,0 +1,182 @@
/**
* 健康监测模块 PRD 截图脚本
*
* 用法:node screenshot-prd.js [--only=过滤]
*
* 行为:
* - 通过 file:// 加载 HTML(避免 server 登录),保持自包含原型可独立截图
* - 视口 1440×900;页面截图 fullPage=true;弹窗截图 fullPage=false
* - JPEG 质量 85%,保存到 doc/screenshots/ 下,路径与 PRD 引用一致
*/
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');
const delay = ms => new Promise(r => setTimeout(r, ms));
const MODULE_DIR = path.resolve(__dirname, '..');
const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
const baseUrl = 'file:///' + MODULE_DIR.replace(/\\/g, '/');
/* ========================
* 任务列表
* 每项:{ page, output, action?, fullPage? }
* - page: HTML 相对 MODULE_DIR 的路径
* - output: 截图相对 SCREENSHOTS_DIR 的路径
* - action: 弹窗触发 JS 字符串(可选;不传则为页面截图)
* - fullPage: 默认页面 true、弹窗 false;可手动覆盖
* ======================== */
const TASKS = [
// ===== 一、档案 =====
{ page: 'archive-personal.html', output: 'archive-personal.jpg' },
{ page: 'archive-personal-detail.html', output: 'archive-personal-detail.jpg' },
{ page: 'archive-unit.html', output: 'archive-unit.jpg' },
{ page: 'archive-unit-crowd-status-detail.html', output: 'archive-unit-crowd-status-detail.jpg' },
{ page: 'archive-unit-health-checkup-detail.html', output: 'archive-unit-health-checkup-detail.jpg' },
{ page: 'archive-unit-wearable-detail.html', output: 'archive-unit-wearable-detail.jpg' },
{ page: 'archive-unit-environment-detail.html', output: 'archive-unit-environment-detail.jpg' },
{ page: 'archive-algorithm.html', output: 'archive-algorithm.jpg' },
// ===== 二、健康现状 =====
{ page: 'crowd-status-employee.html', output: 'crowd-status-employee.jpg' },
{ page: 'crowd-status-unit.html', output: 'crowd-status-unit.jpg' },
// ===== 三、健康排查 =====
{ page: 'health-checkup/survey-manage.html', output: 'health-checkup/survey-manage.jpg' },
{ page: 'health-checkup/survey-edit.html', output: 'health-checkup/survey-edit.jpg' },
{ page: 'health-checkup/activity-manage.html', output: 'health-checkup/activity-manage.jpg' },
{ page: 'health-checkup/activity-edit.html', output: 'health-checkup/activity-edit.jpg' },
{ page: 'health-checkup/activity-detail.html', output: 'health-checkup/activity-detail.jpg' },
{ page: 'health-checkup/activity-stat.html', output: 'health-checkup/activity-stat.jpg' },
// ===== watch5 告警类 =====
{ page: 'wearable/ops/alarm-heartrate.html', output: 'wearable/ops/alarm-heartrate.jpg' },
{ page: 'wearable/ops/alarm-spo2.html', output: 'wearable/ops/alarm-spo2.jpg' },
{ page: 'wearable/ops/alarm-temp.html', output: 'wearable/ops/alarm-temp.jpg' },
{ page: 'wearable/ops/alarm-stress.html', output: 'wearable/ops/alarm-stress.jpg' },
{ page: 'wearable/ops/monitor-sleep-watch5.html', output: 'wearable/ops/monitor-sleep-watch5.jpg' },
// ===== 四、穿戴监测 =====
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage.jpg' },
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage/add.jpg',
action: `openAddModal()` },
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage/bind.jpg',
action: `openBindModal('WD-2024-015','')` },
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage/unbind-confirm.jpg',
action: `openUnbindConfirm('WD-2024-001','采油一厂','张伟')` },
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage/bind-record.jpg',
action: `openBindRecord('WD-2024-001')` },
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage/import.jpg',
action: `openImportModal()` },
{ page: 'wearable/tool-manage.html', output: 'wearable/tool-manage/monitor-switch.jpg',
action: `openMonitorSwitch('WD-2024-001')` },
{ page: 'wearable/bind-record.html', output: 'wearable/bind-record.jpg' },
{ page: 'wearable/user-data.html', output: 'wearable/user-data.jpg' },
{ page: 'wearable/user-data.html', output: 'wearable/user-data-detail.jpg',
action: `openDetail('张伟','CQ001234')` },
{ page: 'wearable/exception-event.html', output: 'wearable/exception-event.jpg' },
{ page: 'wearable/exception-event.html', output: 'wearable/exception-event-detail.jpg',
action: `clickFirstLink('详情')` },
{ page: 'wearable/notification.html', output: 'wearable/notification.jpg' },
{ page: 'wearable/notification.html', output: 'wearable/notification/send.jpg',
action: `openSendModal()` },
{ page: 'wearable/notification.html', output: 'wearable/notification/detail.jpg',
action: `openDetailModal(0)` },
{ page: 'wearable/notification.html', output: 'wearable/notification/read-stat.jpg',
action: `openReadStat()` },
{ page: 'wearable/dept-stat.html', output: 'wearable/dept-stat.jpg' },
{ page: 'wearable/dept-stat.html', output: 'wearable/dept-stat/detail.jpg',
action: `openDetailModal('采油一厂','')` },
{ page: 'wearable/dept-report.html', output: 'wearable/dept-report.jpg' },
{ page: 'wearable/personal-stat.html', output: 'wearable/personal-stat.jpg' },
// ===== 五、运维中心 =====
{ page: 'wearable/ops/terminal-default-config.html', output: 'wearable/ops/terminal-default-config.jpg' },
{ page: 'wearable/ops/terminal-config-info.html', output: 'wearable/ops/terminal-config-info.jpg' },
{ page: 'wearable/ops/terminal-config-info.html', output: 'wearable/ops/terminal-config-info-detail.jpg',
action: `clickFirstLink('详情')` },
...['monitor-ecg','monitor-spo2','monitor-temp','monitor-stress','monitor-exercise',
'monitor-heartrate','monitor-sleep','sdc-overview','monitor-activity','alarm-data',
'log-network','log-version','log-error','log-task','log-mqtt'
].flatMap(name => [
{ page: `wearable/ops/${name}.html`, output: `wearable/ops/${name}.jpg` },
{ page: `wearable/ops/${name}.html`, output: `wearable/ops/${name}-detail.jpg`,
action: `clickFirstLink('详情')` },
]),
// ===== 六、环境监测(PRD 只列了 indoor =====
{ page: 'environment/indoor-device-list.html', output: 'environment/indoor-device-list.jpg' },
{ page: 'environment/indoor-device-category.html', output: 'environment/indoor-device-category.jpg' },
{ page: 'environment/indoor-data-analysis.html', output: 'environment/indoor-data-analysis.jpg' },
{ page: 'environment/indoor-data-monitor.html', output: 'environment/indoor-data-monitor.jpg' },
{ page: 'environment/indoor-exception-alarm.html', output: 'environment/indoor-exception-alarm.jpg' },
{ page: 'environment/indoor-app-record.html', output: 'environment/indoor-app-record.jpg' },
{ page: 'environment/indoor-history-data.html', output: 'environment/indoor-history-data.jpg' },
{ page: 'environment/indoor-history-monitor-detail.html', output: 'environment/indoor-history-monitor-detail.jpg' },
{ page: 'environment/indoor-history-alarm-detail.html', output: 'environment/indoor-history-alarm-detail.jpg' },
// ===== 七、异常事件 =====
{ page: 'exception-dirty.html', output: 'exception-dirty.jpg' },
{ page: 'exception-result.html', output: 'exception-result.jpg' },
];
/* ========== 主流程 ========== */
(async () => {
const onlyArg = (process.argv.find(a => a.startsWith('--only=')) || '').slice('--only='.length);
const tasks = onlyArg ? TASKS.filter(t => t.output.includes(onlyArg)) : TASKS;
console.log(`[i] Total tasks: ${tasks.length}`);
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
const page = await context.newPage();
// 注入辅助函数:clickFirstLink(textIncludes) — 用于触发表格行内"详情"等 .link 链接
await context.addInitScript(() => {
window.clickFirstLink = function(text) {
const el = [...document.querySelectorAll('.link, .btn-link, a, span[onclick]')]
.find(e => e.textContent.trim() === text || e.textContent.includes(text));
if (el) el.click();
else throw new Error('clickFirstLink: not found: ' + text);
};
});
let ok = 0, fail = 0;
const failures = [];
for (const task of tasks) {
const fullPage = task.fullPage !== undefined ? task.fullPage : !task.action;
const outPath = path.join(SCREENSHOTS_DIR, task.output);
const outDir = path.dirname(outPath);
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
try {
await page.goto(`${baseUrl}/${task.page}`, { waitUntil: 'networkidle', timeout: 30000 });
await delay(150);
if (task.action) {
await page.evaluate(task.action);
await delay(350);
}
await page.screenshot({ path: outPath, type: 'jpeg', quality: 85, fullPage });
ok++;
console.log(`${task.output}`);
} catch (err) {
fail++;
failures.push({ output: task.output, error: err.message });
console.log(`${task.output} -- ${err.message}`);
}
}
await browser.close();
console.log(`\n[Done] success=${ok} fail=${fail} out=${SCREENSHOTS_DIR}`);
if (failures.length) {
console.log('\nFailures:');
failures.forEach(f => console.log(` - ${f.output}: ${f.error}`));
process.exit(1);
}
})();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 93 KiB

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