fix: 更新文档截图和页面,新增管理员体重管理文档

This commit is contained in:
liuyunqin
2026-07-09 16:48:38 +08:00
parent 42af90e39f
commit 9b3fed3fc1
124 changed files with 2302 additions and 325 deletions
+141 -182
View File
@@ -15,7 +15,9 @@
│ │ └── 体重记录 → 员工体重详情
│ ├── 体重管理详情 → 减重排名
│ ├── 体重管理详情 → 目标审核
│ │ ── 目标审核
│ │ ── 目标审核(待审核)
│ │ ├── 目标审核(已通过)
│ │ └── 目标审核(已驳回)
│ ├── 体检进度追踪
│ ├── 应急求助工单 → 应急工单详情
│ └── 穿戴设备预警记录
@@ -33,23 +35,6 @@
└── 退出登录(确认弹窗)
```
### 目标审核状态流转图
```mermaid
stateDiagram-v2
[*] --> 待审核 : 员工提交减重目标
待审核 --> 已通过 : 管理员审核通过
待审核 --> 已驳回 : 管理员审核驳回
已通过 --> 待签收 : 员工签收确认
```
### 应急工单状态流转图
```mermaid
stateDiagram-v2
[*] --> 进行中 : 操作人员派单
进行中 --> 已完成 : 驻场人员完成处理
```
## 页面清单
@@ -63,20 +48,22 @@ stateDiagram-v2
| 6 | workbench/exercise-employee-detail.html | 员工运动详情 | 工作台·体重管理 |
| 7 | workbench/weight-record.html | 体重记录 | 工作台·体重管理 |
| 8 | workbench/weight-employee-detail.html | 员工体重详情 | 工作台·体重管理 |
| 9 | workbench/goal-review.html | 目标审核 | 工作台·体重管理 |
| 10 | workbench/checkup-progress.html | 体检进度追踪 | 工作台·体管理 |
| 11 | workbench/emergency-orders.html | 应急求助工单 | 工作台·应急管理 |
| 12 | workbench/emergency-order-detail.html | 应急工单详情 | 工作台·应急管理 |
| 13 | workbench/wearable-alerts.html | 穿戴设备预警记录 | 工作台·预警管理 |
| 14 | knowledge/index.html | 知识库首页 | 知识库 |
| 15 | knowledge/detail.html | 知识详情 | 知识库 |
| 16 | ../ai-assistant/index.html | AI 助手 | AI 助手 |
| 17 | ../message/index.html | 消息列表 | 消息 |
| 18 | ../message/contacts.html | 通讯录 | 消息 |
| 19 | ../message/im-chat.html | 即时通讯 | 消息 |
| 20 | profile/index.html | 个人中心主页 | 我的 |
| 21 | profile/edit-profile.html | 修改个人信息 | 我的 |
| 22 | profile/change-pwd.html | 修改密码 | 我的 |
| 9 | workbench/goal-review.html?status=pending | 目标审核(待审核) | 工作台·体重管理 |
| 10 | workbench/goal-review.html?status=approved | 目标审核(已通过) | 工作台·体管理 |
| 11 | workbench/goal-review.html?status=rejected | 目标审核(已驳回) | 工作台·体重管理 |
| 12 | workbench/checkup-progress.html | 体检进度追踪 | 工作台·体检管理 |
| 13 | workbench/emergency-orders.html | 应急求助工单 | 工作台·应急管理 |
| 14 | workbench/emergency-order-detail.html | 应急工单详情 | 工作台·应急管理 |
| 15 | workbench/wearable-alerts.html | 穿戴设备预警记录 | 工作台·预警管理 |
| 16 | knowledge/index.html | 知识库首页 | 知识库 |
| 17 | knowledge/detail.html | 知识详情 | 知识库 |
| 18 | ../ai-assistant/index.html | AI 助手 | AI 助手 |
| 19 | ../message/index.html | 消息列表 | 消息 |
| 20 | ../message/contacts.html | 通讯录 | 消息 |
| 21 | ../message/im-chat.html | 即时通讯 | 消息 |
| 22 | profile/index.html | 个人中心主页 | 我的 |
| 23 | profile/edit-profile.html | 修改个人信息 | 我的 |
| 24 | profile/change-pwd.html | 修改密码 | 我的 |
## 逐页说明
@@ -88,23 +75,22 @@ stateDiagram-v2
![页面截图](screenshots/index.jpg)
**功能说明:** 管理员工作台入口,顶部展示用户信息(头像、姓名、角色、部门),下方以功能卡片形式呈现四大管理入口:体重管理详情、体检进度追踪、应急求助工单、穿戴设备预警记录。
**功能说明:** 管理员工作台入口,顶部展示用户信息(头像、姓名、角色、单位部门),下方以功能卡片形式呈现四大管理入口:体重管理详情、体检进度追踪、应急求助工单、穿戴设备预警记录。
**业务规则:**
- 用户信息区:头像、姓名(王建国)、角色(健康管理员)、所属部门(安全环保部·健康管理科)
- 功能卡片共 4 个,两行两列排列
1. 体重管理详情(蓝色图标)
2. 体检进度追踪(绿色图标)
3. 应急求助工单(橙色图标,角标数字"3"表示进行中工单数)
4. 穿戴设备预警记录(红色图标)
- 底部 5 个 Tab:工作台(当前激活,角标"3"、知识库、AI 助手、消息(角标"2"、我的
- 用户信息区:头像、姓名、角色、所属单位部门
- 功能卡片共 4 个:
1. 体重管理详情
2. 体检进度追踪
3. 应急求助工单
4. 穿戴设备预警记录
- 底部 5 个 Tab:工作台、知识库、AI 助手、消息、我的
**交互说明:**
- 点击"体重管理详情"卡片:跳转到体重管理详情页
- 点击"体检进度追踪"卡片:跳转到体检进度追踪页
- 点击"应急求助工单"卡片:跳转到应急求助工单页
- 点击"穿戴设备预警记录"卡片:跳转到穿戴设备预警记录页
- 点击头像:跳转到个人中心主页
- 底部导航栏切换:跳转到对应 Tab 页面
---
@@ -115,15 +101,15 @@ stateDiagram-v2
![页面截图](screenshots/weight-detail.jpg)
**功能说明:** 体重管理活动总览页面,支持切换不同活动期次,展示活动整体统计数据,并通过三个主 Tab(每日打卡、减重排名、目标审核)分类查看管理数据。
**功能说明:** 体重管理活动总览页面,支持切换不同活动,展示活动整体统计数据,并通过三个主 Tab(每日打卡、减重排名、目标审核)分类查看管理数据。
**业务规则:**
- 顶部活动选择器:下拉切换不同活动期次(如"2026年Q2体重管理活动"),切换后刷新活动统计数据
- 活动统计区:已进行天数 / 总天数、预期参与人数、实际参与人数
- 三个主 Tab
- 每日打卡(默认):按日查看饮食、运动、体重的打卡统计数据
- 减重排名:4 个子 Tab(减重总量排名 / 减重率排名 / 综合评分排名 / 行动评分排名),各显示前 20 名
- 目标审核:4 个子 Tab(待审核 / 已通过 / 待签收 / 已驳回),显示员工减重目标申请
- 减重排名:4 个子 Tab(减重总量排名 / 减重率排名 / 综合评分排名 / 行动评分排名)
- 目标审核:3 个子 Tab(待审核 / 已通过 / 已驳回),显示员工减重目标申请
- 每日打卡面板:日期导航(左右箭头 + 日历选择器)、三张统计卡片(饮食已打卡XX人/未打卡XX人、运动已打卡XX人/未打卡XX人、体重已打卡XX人/未打卡XX人)
- 减重排名:前三名展示金/银/铜牌勋章 + 员工脱敏姓名 + 部门 + 数值
- 目标审核卡片:展示员工姓名、初始/目标数据对比、操作按钮
@@ -154,7 +140,7 @@ stateDiagram-v2
**业务规则:**
- 日期导航:支持左右箭头切换日期、日历面板选择日期
- 两个 Tab:已记录(角标数字显示已打卡人数)、未记录(角标数字显示未打卡人数)
- 员工卡片信息:头像(取姓名首字)、姓名、性别、年龄、部门、身高
- 员工卡片信息:姓名、性别、年龄、部门、身高
- 已记录员工卡片右侧有"记录详情 >"链接
**交互说明:**
@@ -177,7 +163,6 @@ stateDiagram-v2
- 数据对比表(三列):初始数据 / 目标数据 / 当前数据,包含体重、BMI、体脂率、腰围
- 变化指标:体重变化、BMI 变化、体脂率变化、腰围变化(绿色数值表示下降)
- 当日饮食记录:按餐次展示(早餐、午餐、加餐、晚餐),每条记录显示热量(kcal)、时间、记录来源标签
- 记录来源标签颜色:蓝色(智能记录)、紫色(手动记录)
**交互说明:**
- 点击返回:返回饮食记录详情页
@@ -193,7 +178,7 @@ stateDiagram-v2
**业务规则:**
- 日期导航:支持左右箭头切换日期、日历面板选择日期
- 两个 Tab:已记录(角标数字)、未记录(角标数字)
- 员工卡片信息:头像、姓名、性别、年龄、部门
- 员工卡片信息:姓名、性别、年龄、单位部门、身高
- 已记录员工卡片右侧有"记录详情 >"链接
**交互说明:**
@@ -215,8 +200,7 @@ stateDiagram-v2
- 员工信息:姓名(陈伟)、性别、年龄
- 数据对比表:初始数据 / 目标数据 / 当前数据(体重、BMI、体脂率、腰围)
- 变化指标:绿色显示下降数值
- 当日运动记录:跑步(45min/320kcal/穿戴设备)、骑行(30min/180kcal/手动记录)、游泳(60min/480kcal/器材记录)、步行(8,520步/150kcal/微信运动)
- 记录来源标签颜色:蓝色(穿戴设备/器材)、紫色(手动/微信运动)
- 当日运动记录:运动名称、运动时长/步数、消耗千卡、时间、来源(穿戴、器械、微信、自记)
**交互说明:**
- 点击返回:返回运动记录详情页
@@ -232,7 +216,7 @@ stateDiagram-v2
**业务规则:**
- 日期导航:支持左右箭头切换日期、日历面板选择日期
- 两个 Tab:已记录(角标数字)、未记录(角标数字)
- 员工卡片信息:头像、姓名、性别、年龄、部门
- 员工卡片信息:姓名、性别、年龄、单位部门、身高
- 已记录员工卡片右侧有"记录详情 >"链接
**交互说明:**
@@ -254,38 +238,37 @@ stateDiagram-v2
- 员工信息:姓名(张晓红)、性别、年龄、部门
- 数据对比表:初始数据 / 目标数据 / 当前数据(体重、BMI、体脂率、腰围)
- 变化指标:绿色显示下降数值
- 体重记录列表:按时间倒序,每条显示时间、体重、体脂率、腰围、来源标签
- 记录来源标签颜色:蓝色(蓝牙秤/身高体重秤)、紫色(体脂仪/手动/穿戴设备)
- 体重记录列表:按时间倒序,每条显示时间、体重、体脂率、腰围、来源标签(身高秤、蓝牙称、体脂仪、自记)
**交互说明:**
- 点击返回:返回体重记录页
---
### 9. 目标审核
### 9. 目标审核(待审核)
![页面截图](screenshots/goal-review.jpg)
![页面截图](screenshots/goal-review-pending.jpg)
**功能说明:** 管理员审核员工提交的体重管理减重目标申请,可选择通过或驳回驳回时需填写驳回原因。已审核的目标以只读方式展示审核结果。
**功能说明:** 管理员查看待审核的减重目标申请,可选择通过或驳回驳回时需填写驳回原因。
**业务规则:**
- 通过 URL 参数 `status` 区分页面模式:pending(待审核)/ approved(已通过)/ signed(待签收)/ rejected(已驳回)
- 待审核模式:
- 展示员工基本信息卡片(姓名、性别、年龄、身高)
- 数据对比卡片:初始数据体重/BMI/体脂率/腰围)、目标数据(绿色)、推荐数据范围(蓝色参考)
- 活动时间范围卡片:开始日期 → 结束日期
- 底部操作区:单选按钮(通过 / 驳回)、驳回时展开驳回原因文本框、提交审核按钮
- 已通过/待签收/已驳回模式:
- 以只读方式展示审核结果,包含状态标签、审核人、审核时间
- 已驳回额外展示驳回原因
- 无操作按钮
- 员工信息:姓名、性别、年龄、身高
- 申请时间
- 数据对比卡片:
- 初始数据体重/ BMI/ 体脂/ 腰围
- 目标数据:体重/ BMI/ 体脂/ 腰围
- 推荐范围:体重/ BMI/ 体脂/ 腰围
- 目标起止日期
- 底部操作区:单选按钮(审核通过 / 审核驳回)、驳回原因文本框(默认隐藏)、提交审核按钮
**交互说明:**
- 点击"通过"单选按钮:隐藏驳回原因文本框
- 点击"驳回"单选按钮:展开驳回原因文本框placeholder: "请输入驳回原因..."
- 点击"审核通过"单选按钮:选中通过,隐藏驳回原因文本框
- 点击"审核驳回"单选按钮:选中驳回,展开驳回原因文本框
- 点击"提交审核":弹出确认弹窗
- 确认弹窗:标题根据操作变化(通过审核 / 驳回申请),显示简要确认信息,取消按钮关闭弹窗,确认按钮执行操作
- 确认后弹出成功弹窗:"审核完成",点击"返回列表"跳转回体重管理详情页
- 通过时:"确认通过该目标申请?",确认按钮为蓝色"确认通过"
- 驳回时:"确认驳回该目标申请?驳回原因:…",确认按钮为红色"确认驳回"
- 确认弹窗点击取消:关闭弹窗
- 确认弹窗点击确认:关闭确认弹窗,弹出成功弹窗("审核完成"),点击"返回列表"跳转回体重管理详情页
- 点击返回:返回体重管理详情页
**审核操作表单:**
@@ -297,20 +280,61 @@ stateDiagram-v2
---
### 10. 目标审核(已通过)
![页面截图](screenshots/goal-review-approved.jpg)
**功能说明:** 查看已通过审核的减重目标申请详情,纯只读展示,无操作按钮。
**业务规则:**
- 员工信息:姓名、性别、年龄、身高
- 申请时间
- 数据对比卡片:
- 初始数据:体重/ BMI/ 体脂/ 腰围
- 目标数据:体重/ BMI/ 体脂/ 腰围
- 推荐范围:体重/ BMI/ 体脂/ 腰围
- 目标起止日期
- 审核结果卡片:审核结果标签、审核人、审核时间
**交互说明:**
- 点击返回:返回体重管理详情页
---
### 11. 目标审核(已驳回)
![页面截图](screenshots/goal-review-rejected.jpg)
**功能说明:** 查看已被驳回的减重目标申请详情,包含驳回原因,纯只读展示,无操作按钮。
**业务规则:**
- 员工信息:姓名、性别、年龄、身高
- 申请时间
- 数据对比卡片:
- 初始数据:体重/ BMI/ 体脂/ 腰围
- 目标数据:体重/ BMI/ 体脂/ 腰围
- 推荐范围:体重/ BMI/ 体脂/ 腰围
- 目标起止日期
- 审核结果卡片:审核结果标签、审核人、审核时间、驳回原因
**交互说明:**
- 点击返回:返回体重管理详情页
---
## 三、体检管理业务线
### 10. 体检进度追踪
### 12. 体检进度追踪
![页面截图](screenshots/checkup-progress.jpg)
**功能说明:** 按体检计划期次查看员工的体检预约和完成进度,通过四个分段 Tab(已预约、已检查、未预约、未检查)统计并展示各状态的员工列表。
**业务规则:**
- 顶部体检计划选择器:下拉切换不同体检计划(如"2026年度体检""2025年度体检""2025职业病体检",切换后刷新各状态统计数据
- 顶部体检计划选择器:下拉切换不同体检计划,切换后刷新各状态统计数据
- 统计区:预期人数、已预约人数、已检查人数、未预约人数、未检查人数
- 四个分段 Tab:已预约(角标数字)、已检查(角标数字)、未预约(角标数字)、未检查(角标数字)
- 员工卡片信息:头像(姓名首字)、姓名 + 工号、所属单位 + 部门、联系电话
- 数据通过 JS 模拟数据动态渲染
- 员工卡片信息:头像、姓名 + 工号、所属单位 + 部门、联系电话
**交互说明:**
- 点击体检计划下拉框:展开计划列表,选择后切换数据
@@ -321,17 +345,16 @@ stateDiagram-v2
## 四、应急管理业务线
### 11. 应急求助工单
### 13. 应急求助工单
![页面截图](screenshots/emergency-orders.jpg)
**功能说明:** 查看所有应急求助工单的处理情况,分"进行中"和"已完成"两个 Tab 展示,支持按月度和求助方式筛选。管理员以只读监控视角查看工单流转。
**业务规则:**
- 两个主 Tab:进行中(角标数字"3")、已完成(角标数字"12"
- 筛选栏:月份下拉(全部 / 2026-06 / 2026-05 / 2026-04、求助方式下拉(全部 / 应急视频 / 应急电话 / 拨120)
- 工单卡片信息:求助方式标签(颜色区分:视频粉色、电话橙色、120红色)、时间、求助员工信息(头像、姓名、工号、性别、年龄、单位、部门、电话)、工单详情行(应急中心、操作人员、专业人员)
- 数据通过 JS 模拟,筛选条件联动更新两个 Tab 的列表
- 两个主 Tab:进行中(角标数字)、已完成(角标数字)
- 筛选栏:月份下拉、求助方式下拉(全部 / 应急视频 / 应急电话 / 拨120)
- 工单卡片信息:求助方式标签、时间、求助员工信息(头像、姓名、工号、性别、年龄、单位、部门、电话)、工单信息(应急中心、操作人员、专业人员)
**交互说明:**
- 点击进行中/已完成 Tab:切换工单列表
@@ -342,7 +365,7 @@ stateDiagram-v2
---
### 12. 应急工单详情
### 14. 应急工单详情
![页面截图](screenshots/emergency-order-detail.jpg)
@@ -350,8 +373,8 @@ stateDiagram-v2
**业务规则:**
- 四个可折叠卡片(默认展开):
1. 基本信息:工单编号、应急中心、求助员工姓名/工号、单位、部门、求助方式、求助时间、位置信息(含地图图标)、操作人员、专业人员、工单状态(已完成绿色标签)、完成时间
2. 救助信息:救助意见(文本)、救助结果(文本)、相关照片3 张占位图)、补充小结(多条带圆点标记的列表)
1. 基本信息:工单编号、应急中心、求助员工姓名/工号、单位、部门、求助方式、求助时间、位置信息、操作人员、专业人员、工单状态(已完成绿色标签)、完成时间
2. 救助信息:救助意见(文本)、救助结果(文本)、相关照片、补充小结(多条带圆点标记的列表)
3. 派单信息:派单时间、驻场人员、派单状态(已完成)、结束时间、派单说明、驻场说明
4. 救护车支持:车牌号、机构、驾驶员、驾驶员电话、护送人员、护送人员电话、目的地、备注
- 纯只读展示,无编辑或操作按钮
@@ -364,17 +387,15 @@ stateDiagram-v2
## 五、预警管理业务线
### 13. 穿戴设备预警记录
### 15. 穿戴设备预警记录
![页面截图](screenshots/wearable-alerts.jpg)
**功能说明:** 查看所有员工穿戴设备的异常告警记录,支持按月份和告警类型筛选。
**业务规则:**
- 筛选栏:月份下拉(全部 / 2026-06 / 2026-05 / 2026-04、告警类型下拉(全部 / SOS 求助 / 体温异常 / 心率异常 / 血氧异常 / 压力异常 / 跌倒检测
- 告警卡片信息:告警类型标签(颜色区分类型)、时间戳、异常数值(含单位)、员工信息(头像、姓名、工号、性别、年龄、单位、部门)、位置信息(含定位图标)
- 告警类型共 6 种:SOS 求助(红色)、体温异常(橙色)、心率异常(红色)、血氧异常(紫色)、压力异常(蓝色)、跌倒检测(红色)
- 数据通过 JS 模拟,筛选条件联动更新列表
- 筛选栏:月份下拉、告警类型下拉(全部 / 一键告警 / 体温异常 / 心率异常 / 血氧异常 / 压力异常 / 跌倒)
- 告警卡片信息:告警类型标签、时间戳、异常数值(含单位)、员工信息(头像、姓名、工号、性别、年龄、单位、部门)、位置信息
**交互说明:**
- 点击月份下拉:按月份筛选告警记录
@@ -385,137 +406,88 @@ stateDiagram-v2
## 六、知识库模块
### 14. 知识库首页
### 16. 知识库首页
![页面截图](screenshots/knowledge-index.jpg)
**功能说明:** 专业知识参考库,分"知识文章"和"规章制度"两个大类,知识文章下再按二级分类 Tab 组织。
**业务规则:**
- 两大主 Tab:知识文章(默认)/ 规章制度
- 知识文章二级分类 Tab(4 个):体检知识(4 篇)、药品知识(2 篇)、疾病知识(2 篇)、健康知识(2 篇)
- 知识文章列表:每篇文章展示图标、标题、简介描述、右箭头
- 规章制度列表:5 条 PDF 文档条目(员工健康管理办法、职业病防治规则、体检异常处理流程、健康咨询服务规范、应急救援操作标准)
- 文章数据通过 JS 对象动态渲染
**交互说明:**
- 点击知识文章/规章制度主 Tab:切换大类
- 点击二级分类 Tab:切换知识分类
- 点击文章条目:跳转到知识详情页(通过 `?key=<文章标识>` 传递参数)
- 点击规章制度条目:打开对应 PDF(原型中为占位链接)
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
---
### 15. 知识详情
### 17. 知识详情
![页面截图](screenshots/knowledge-detail.jpg)
**功能说明:** 展示单篇知识文章的详细内容,包括文章基本信息、摘要和要点列表。
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
**业务规则:**
- 通过 URL 参数 `key` 确定展示哪篇文章,共有 10 篇文章数据
- 文章内容:图标、标题、分类、发布日期、摘要段、要点列表(彩色标题 + 灰色圆角说明卡片)
- 体检知识(4 篇):血常规解读、心电图判读、肝功能解读、血糖解读
- 药品知识(2 篇):降压药物、他汀类药物
- 疾病知识(2 篇):高血压、偏头痛
- 健康知识(2 篇):营养指导、运动与心血管健康
**交互说明:**
- 点击返回:返回知识库首页
---
## 七、AI 助手
### 16. AI 助手
### 18. AI 助手
![页面截图](screenshots/../ai-assistant/screenshots/index.jpg)
**功能说明:** 智能健康问答助手,支持文字输入提问,提供快捷问题入口。全角色共享页面。
**业务规则:**
- 欢迎语和三个快捷问题入口(偏头痛鉴别诊断、高血压用药建议、血脂异常处理方案)
- AI 对话模拟:用户发送消息后,AI 延迟回复占位文字
- 通过 `sessionStorage.appRole` 识别当前角色为 admin,设置正确的 Tab 导航路径
**交互说明:**
- 输入文字后点击发送或按回车:发送消息,显示用户消息气泡(紫色右对齐)
- 点击快捷问题标签:自动填入输入框并发送
- 底部 Tab 切换:跳转到对应角色页面
**功能说明:** 后续规划,暂时显示”开发中,敬请期待~”。
---
## 八、消息模块
### 17. 消息列表
### 19. 消息列表
![页面截图](screenshots/../message/screenshots/index.jpg)
![页面截图](../../message/screenshots/index.jpg)
**功能说明:** 即时通讯消息汇总列表,展示最近对话的联系人及消息预览。全角色共享页面
**功能说明:** 内部消息模块,与当前系统中”咨询专家”、”咨询小助手”、”操作人员”、”专业人员”、”驻场人员”、”健康管理员”进行对话聊天的消息列表
**业务规则:**
- 6 条消息会话:咨询小助手(未读"2")、李主任(专业人员)、张护士长(专业人员)、陈运营(操作人员)、刘驻场(驻场人员)、系统管理员
- 每条展示:头像、姓名、角色标签(颜色区分)、时间、消息预览
- 通过 `sessionStorage.appRole` 识别当前角色,设置正确的底部 Tab 导航路径
- 消息列表按最新消息时间倒序排列
- 每条消息显示联系人头像、姓名、角色标签、最后一条消息预览、时间
**交互说明:**
- 点击消息条目:跳转到即时通讯
- 点击右上角"通讯录":跳转到通讯录页
- 底部 Tab 切换:跳转到对应角色页面
- 点击消息:跳转到 IM 聊天
- 点击右上角通讯录图标:跳转到通讯录页
- 底部 Tab 导航切换到其他模块
---
### 18. 通讯录
### 20. 通讯录
![页面截图](screenshots/../message/screenshots/contacts.jpg)
![页面截图](../../message/screenshots/contacts.jpg)
**功能说明:** 组织内联系人列表,按角色分组展示,支持搜索过滤。全角色共享页面
**业务规则:**
- 6 个联系人分组:小助手、咨询专家、健康管理员、操作人员、专业人员、驻场人员
- 每组可折叠/展开,展示成员姓名、角色标签、所属机构
- 搜索框支持按姓名实时过滤,空分组自动隐藏
**功能说明:** 展示系统内所有可联系的内部人员,支持按角色筛选和姓名搜索
**交互说明:**
- 点击分组标题:折叠/展开该分组
- 在搜索框输入文字:实时过滤联系人
- 点击联系人:跳转到即时通讯页
- 点击返回:返回消息列表
- 点击联系人:跳转到 IM 聊天页
- 输入名称进行模糊搜索
- 角色分类可收起展开
- 返回按钮:跳转到消息列表
---
### 19. 即时通讯
### 21. 即时通讯
![页面截图](screenshots/../message/screenshots/im-chat.jpg)
![页面截图](../../message/screenshots/im-chat.jpg)
**功能说明:** 单聊页面,支持文字和语音输入,可发送图片。全角色共享页面。
**业务规则:**
- 导航栏标题显示当前对话对象名称
- 预置示例对话(与"咨询小助手"的聊天记录)
- 支持文字输入、语音输入(按住说话)、多媒体附件(拍照/图库)
- 发送的消息为蓝色右对齐气泡,接收的消息为白色左对齐气泡
**功能说明:** 与内部人员的即时通讯聊天界面。
**交互说明:**
- 输入框输入文字后点击发送:消息以蓝色气泡展示在右侧
- 点击语音切换按钮:切换为按住说话模式
- 点击"+"按钮:展开多媒体面板(拍照/图库选项,原型中为占位)
- 点击返回:返回消息列表
- 底部输入框输入文字并发送
- 支持语音和输入切换
- 支持拍照和发送图库照片
- 返回按钮:跳转到消息列表
---
## 九、个人中心模块
### 20. 个人中心主页
### 22. 个人中心主页
![页面截图](screenshots/profile-index.jpg)
**功能说明:** 管理员个人信息展示和功能菜单导航。
**业务规则:**
- 用户信息区:头像、姓名(王建国)、角色(健康管理员)、所属部门(安全环保部·健康管理科)
- 用户信息区:头像、姓名、角色、所属单位部门
- 菜单项:修改个人信息、修改密码、关于我们
- 退出登录按钮(红色文字)
- 退出登录按钮
**交互说明:**
- 点击"修改个人信息":跳转到修改个人信息页
@@ -525,7 +497,7 @@ stateDiagram-v2
---
### 21. 修改个人信息
### 23. 修改个人信息
![页面截图](screenshots/profile-edit-profile.jpg)
@@ -536,28 +508,15 @@ stateDiagram-v2
- 点击"保存":验证姓名非空,显示"保存成功"提示(1.5 秒自动消失)
- 点击返回:返回个人中心主页
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 头像 | 图片选择 | 否 | 支持拍照或从相册选择 |
| 姓名 | 单行文本 | 是 | 不能为空 |
---
### 22. 修改密码
### 24. 修改密码
![页面截图](screenshots/profile-change-pwd.jpg)
**功能说明:** 修改管理员登录密码。
**交互说明:**
- 点击"保存修改":弹出修改成功确认弹窗("密码修改成功,下次登录请使用新密码"),点击确定后跳转回个人中心
- 点击"保存修改":弹出修改成功确认弹窗("密码修改成功"),退回到登录页面进行重新登录
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 当前密码 | 密码输入 | 是 | 不能为空 |
| 新密码 | 密码输入 | 是 | 不能为空 |
| 确认新密码 | 密码输入 | 是 | 必须与新密码一致 |
@@ -0,0 +1,258 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理员端 - 页面设计总览</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; }
/* 顶部导航 */
.nav { position: sticky; top: 0; z-index: 100; background: #16213e; border-bottom: 1px solid #0f3460; padding: 14px 24px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.nav__title { font-size: 16px; font-weight: 600; color: #4fc3f7; margin-right: 20px; white-space: nowrap; }
.nav__link { font-size: 13px; color: #aaa; text-decoration: none; padding: 5px 12px; border-radius: 20px; transition: all .2s; white-space: nowrap; border: 1px solid transparent; }
.nav__link:hover { background: #0f3460; color: #4fc3f7; border-color: #4fc3f7; }
/* 业务主线区块 */
.section { padding: 24px 24px 8px; }
.section__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.section__title { font-size: 15px; font-weight: 600; color: #fff; }
.section__count { font-size: 12px; color: #666; background: #2a2a4a; padding: 2px 8px; border-radius: 10px; }
/* 横向滚动容器 */
.scroll-row { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
.scroll-row::-webkit-scrollbar { height: 6px; }
.scroll-row::-webkit-scrollbar-track { background: transparent; }
.scroll-row::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
/* 卡片 */
.card { flex-shrink: 0; width: 260px; cursor: pointer; transition: transform .2s; }
.card:hover { transform: scale(1.02); }
.card__img { width: 260px; height: 564px; border-radius: 12px; overflow: hidden; border: 2px solid #2a2a4a; background: #111; transition: border-color .2s; }
.card:hover .card__img { border-color: #4fc3f7; }
.card__img img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
.card__label { margin-top: 8px; font-size: 12px; color: #999; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card__tags { display: flex; justify-content: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
.tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
.tag--modal { background: #4a2800; color: #ffa940; }
.tag--state { background: #002a4a; color: #4fc3f7; }
/* 分隔线 */
.divider { height: 1px; background: #2a2a4a; margin: 8px 24px; }
/* 灯箱 */
.lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.92); align-items: center; justify-content: center; cursor: zoom-out; }
.lightbox.show { display: flex; }
.lightbox img { max-width: 90vw; max-height: 92vh; border-radius: 8px; }
.lightbox__close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 24px; cursor: pointer; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.1); }
.lightbox__name { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #ccc; font-size: 13px; background: rgba(0,0,0,0.6); padding: 6px 16px; border-radius: 20px; }
</style>
</head>
<body>
<!-- 顶部导航 -->
<nav class="nav">
<span class="nav__title">管理员端 · 设计总览</span>
<a class="nav__link" href="#sec-workbench">工作台首页</a>
<a class="nav__link" href="#sec-emergency">应急工单</a>
<a class="nav__link" href="#sec-weight">体重管理</a>
<a class="nav__link" href="#sec-goal">目标审核</a>
<a class="nav__link" href="#sec-checkup">体检与预警</a>
<a class="nav__link" href="#sec-knowledge">知识库</a>
<a class="nav__link" href="#sec-profile">个人中心</a>
</nav>
<!-- 工作台首页 -->
<section class="section" id="sec-workbench">
<div class="section__header">
<h2 class="section__title">工作台首页</h2>
<span class="section__count">1 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/index.jpg','工作台首页')">
<div class="card__img"><img src="screenshots/index.jpg" alt="工作台首页"></div>
<div class="card__label">工作台首页</div>
<div class="card__tags"><span class="tag tag--state">功能入口</span></div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- 应急工单管理 -->
<section class="section" id="sec-emergency">
<div class="section__header">
<h2 class="section__title">应急工单管理</h2>
<span class="section__count">2 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/emergency-orders.jpg','应急求助工单')">
<div class="card__img"><img src="screenshots/emergency-orders.jpg" alt="应急求助工单"></div>
<div class="card__label">应急求助工单</div>
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
</div>
<div class="card" onclick="openLightbox('screenshots/emergency-order-detail.jpg','应急工单详情')">
<div class="card__img"><img src="screenshots/emergency-order-detail.jpg" alt="应急工单详情"></div>
<div class="card__label">应急工单详情</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- 体重管理 -->
<section class="section" id="sec-weight">
<div class="section__header">
<h2 class="section__title">体重管理</h2>
<span class="section__count">7 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/weight-detail.jpg','体重管理详情')">
<div class="card__img"><img src="screenshots/weight-detail.jpg" alt="体重管理详情"></div>
<div class="card__label">体重管理详情</div>
<div class="card__tags"><span class="tag tag--state">多Tab</span></div>
</div>
<div class="card" onclick="openLightbox('screenshots/weight-record.jpg','体重记录')">
<div class="card__img"><img src="screenshots/weight-record.jpg" alt="体重记录"></div>
<div class="card__label">体重记录</div>
</div>
<div class="card" onclick="openLightbox('screenshots/weight-employee-detail.jpg','员工体重详情')">
<div class="card__img"><img src="screenshots/weight-employee-detail.jpg" alt="员工体重详情"></div>
<div class="card__label">员工体重详情</div>
</div>
<div class="card" onclick="openLightbox('screenshots/eat-detail.jpg','饮食记录详情')">
<div class="card__img"><img src="screenshots/eat-detail.jpg" alt="饮食记录详情"></div>
<div class="card__label">饮食记录详情</div>
</div>
<div class="card" onclick="openLightbox('screenshots/diet-employee-detail.jpg','员工饮食详情')">
<div class="card__img"><img src="screenshots/diet-employee-detail.jpg" alt="员工饮食详情"></div>
<div class="card__label">员工饮食详情</div>
</div>
<div class="card" onclick="openLightbox('screenshots/exercise-detail.jpg','运动记录详情')">
<div class="card__img"><img src="screenshots/exercise-detail.jpg" alt="运动记录详情"></div>
<div class="card__label">运动记录详情</div>
</div>
<div class="card" onclick="openLightbox('screenshots/exercise-employee-detail.jpg','员工运动详情')">
<div class="card__img"><img src="screenshots/exercise-employee-detail.jpg" alt="员工运动详情"></div>
<div class="card__label">员工运动详情</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- 目标审核 -->
<section class="section" id="sec-goal">
<div class="section__header">
<h2 class="section__title">目标审核</h2>
<span class="section__count">3 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/goal-review-pending.jpg','目标审核(待审核)')">
<div class="card__img"><img src="screenshots/goal-review-pending.jpg" alt="目标审核(待审核)"></div>
<div class="card__label">目标审核(待审核)</div>
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
</div>
<div class="card" onclick="openLightbox('screenshots/goal-review-approved.jpg','目标审核(已通过)')">
<div class="card__img"><img src="screenshots/goal-review-approved.jpg" alt="目标审核(已通过)"></div>
<div class="card__label">目标审核(已通过)</div>
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
</div>
<div class="card" onclick="openLightbox('screenshots/goal-review-rejected.jpg','目标审核(已驳回)')">
<div class="card__img"><img src="screenshots/goal-review-rejected.jpg" alt="目标审核(已驳回)"></div>
<div class="card__label">目标审核(已驳回)</div>
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- 体检与预警 -->
<section class="section" id="sec-checkup">
<div class="section__header">
<h2 class="section__title">体检与预警</h2>
<span class="section__count">2 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/checkup-progress.jpg','体检进度追踪')">
<div class="card__img"><img src="screenshots/checkup-progress.jpg" alt="体检进度追踪"></div>
<div class="card__label">体检进度追踪</div>
</div>
<div class="card" onclick="openLightbox('screenshots/wearable-alerts.jpg','穿戴设备预警记录')">
<div class="card__img"><img src="screenshots/wearable-alerts.jpg" alt="穿戴设备预警记录"></div>
<div class="card__label">穿戴设备预警记录</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- 知识库 -->
<section class="section" id="sec-knowledge">
<div class="section__header">
<h2 class="section__title">知识库</h2>
<span class="section__count">2 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/knowledge-index.jpg','知识库首页')">
<div class="card__img"><img src="screenshots/knowledge-index.jpg" alt="知识库首页"></div>
<div class="card__label">知识库首页</div>
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
</div>
<div class="card" onclick="openLightbox('screenshots/knowledge-detail.jpg','知识详情')">
<div class="card__img"><img src="screenshots/knowledge-detail.jpg" alt="知识详情"></div>
<div class="card__label">知识详情</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- 个人中心 -->
<section class="section" id="sec-profile">
<div class="section__header">
<h2 class="section__title">个人中心</h2>
<span class="section__count">3 页</span>
</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/profile-index.jpg','个人中心主页')">
<div class="card__img"><img src="screenshots/profile-index.jpg" alt="个人中心主页"></div>
<div class="card__label">个人中心主页</div>
</div>
<div class="card" onclick="openLightbox('screenshots/profile-edit-profile.jpg','修改个人信息')">
<div class="card__img"><img src="screenshots/profile-edit-profile.jpg" alt="修改个人信息"></div>
<div class="card__label">修改个人信息</div>
</div>
<div class="card" onclick="openLightbox('screenshots/profile-change-pwd.jpg','修改密码')">
<div class="card__img"><img src="screenshots/profile-change-pwd.jpg" alt="修改密码"></div>
<div class="card__label">修改密码</div>
</div>
</div>
</section>
<div style="height:40px;"></div>
<!-- 灯箱 -->
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
<span class="lightbox__close">&times;</span>
<img id="lightbox-img" src="" alt="">
<div class="lightbox__name" id="lightbox-name"></div>
</div>
<script>
function openLightbox(src, name) {
document.getElementById('lightbox-img').src = src;
document.getElementById('lightbox-name').textContent = name;
document.getElementById('lightbox').classList.add('show');
}
function closeLightbox() {
document.getElementById('lightbox').classList.remove('show');
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeLightbox();
});
</script>
</body>
</html>
+103 -77
View File
@@ -1,10 +1,15 @@
/**
* 管理员端(健康应急APP)页面截图脚本
* 使用 Puppeteer 批量截取 HTML 原型页面,输出 JPEG(质量 85%到 doc/screenshots/
* 使用 Playwright 批量截取 HTML 原型页面,输出 JPEG 到 doc/screenshots/
*
* 截图约束(参考项目背景.md §7.4):
* - 视口宽度 390px,1x 缩放,全页长图
* - JPEG 质量:普通页 80%,长图(>1800px65%
* - 目标体积 ≤ 80KB
*
* 用法: node doc/screenshot.js
*/
const puppeteer = require('puppeteer');
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');
@@ -12,150 +17,171 @@ const MODULE_DIR = __dirname.replace(/\\doc$/, ''); // admin/
const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
// 所有需要截图的页面(相对于模块目录)
// 格式:{ file: 'path.html', query: '?key=val', out: 'output-name.jpg' }
const PAGES = [
// 工作台
'workbench/index.html',
'workbench/weight-detail.html',
'workbench/eat-detail.html',
'workbench/diet-employee-detail.html',
'workbench/exercise-detail.html',
'workbench/exercise-employee-detail.html',
'workbench/weight-record.html',
'workbench/weight-employee-detail.html',
'workbench/goal-review.html',
'workbench/checkup-progress.html',
'workbench/emergency-orders.html',
'workbench/emergency-order-detail.html',
'workbench/wearable-alerts.html',
{ file: 'workbench/index.html' },
{ file: 'workbench/weight-detail.html' },
{ file: 'workbench/eat-detail.html' },
{ file: 'workbench/diet-employee-detail.html' },
{ file: 'workbench/exercise-detail.html' },
{ file: 'workbench/exercise-employee-detail.html' },
{ file: 'workbench/weight-record.html' },
{ file: 'workbench/weight-employee-detail.html' },
// 目标审核 - 3 种状态
{ file: 'workbench/goal-review.html', query: '?status=pending', out: 'goal-review-pending.jpg' },
{ file: 'workbench/goal-review.html', query: '?status=approved', out: 'goal-review-approved.jpg' },
{ file: 'workbench/goal-review.html', query: '?status=rejected', out: 'goal-review-rejected.jpg' },
// 体检管理
{ file: 'workbench/checkup-progress.html' },
// 应急管理
{ file: 'workbench/emergency-orders.html' },
{ file: 'workbench/emergency-order-detail.html' },
// 预警管理
{ file: 'workbench/wearable-alerts.html' },
// 知识库
'knowledge/index.html',
'knowledge/detail.html',
{ file: 'knowledge/index.html' },
{ file: 'knowledge/detail.html' },
// 个人中心
'profile/index.html',
'profile/edit-profile.html',
'profile/change-pwd.html',
{ file: 'profile/index.html' },
{ file: 'profile/edit-profile.html' },
{ file: 'profile/change-pwd.html' },
];
async function screenshotPage(browser, htmlFile) {
const page = await browser.newPage();
const filePath = path.join(MODULE_DIR, htmlFile);
const fileUrl = `file:///${filePath.replace(/\\/g, '/')}`;
// 共享页面(ai-assistant、message),截图输出到各自 screenshots 目录
const EMERGENCY_DIR = path.resolve(MODULE_DIR, '..'); // emergency-app/
const SHARED_PAGES = [
{ file: path.join(EMERGENCY_DIR, 'ai-assistant/index.html'), outDir: path.join(EMERGENCY_DIR, 'ai-assistant/screenshots'), out: 'index.jpg' },
{ file: path.join(EMERGENCY_DIR, 'message/index.html'), outDir: path.join(EMERGENCY_DIR, 'message/screenshots'), out: 'index.jpg' },
{ file: path.join(EMERGENCY_DIR, 'message/contacts.html'), outDir: path.join(EMERGENCY_DIR, 'message/screenshots'), out: 'contacts.jpg' },
{ file: path.join(EMERGENCY_DIR, 'message/im-chat.html'), outDir: path.join(EMERGENCY_DIR, 'message/screenshots'), out: 'im-chat.jpg' },
];
const viewportWidth = 390;
const viewportHeight = 844;
function getOutputName(entry) {
if (entry.out) return entry.out;
let name = entry.file.replace('.html', '.jpg');
if (name.startsWith('workbench/')) name = name.replace('workbench/', '');
else if (name.startsWith('knowledge/')) name = name.replace('knowledge/', 'knowledge-');
else if (name.startsWith('profile/')) name = name.replace('profile/', 'profile-');
return name;
}
await page.setViewport({ width: viewportWidth, height: viewportHeight, deviceScaleFactor: 2 });
async function screenshotPage(browser, entry, outDir) {
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
const filePath = typeof entry.file === 'string' && path.isAbsolute(entry.file)
? entry.file
: path.join(MODULE_DIR, entry.file);
const query = entry.query || '';
const fileUrl = `file:///${filePath.replace(/\\/g, '/')}${query}`;
try {
await page.goto(fileUrl, { waitUntil: 'networkidle0', timeout: 15000 });
await page.goto(fileUrl, { waitUntil: 'networkidle', timeout: 15000 });
// 等待页面渲染完成
await new Promise(r => setTimeout(r, 500));
await page.waitForTimeout(500);
// 注入 CSS:移除 phone-shell 限制,展开全部内容
await page.evaluate(() => {
const style = document.createElement('style');
style.id = 'screenshot-unwrap';
style.textContent = `
/* 移除手机壳固定高度和装饰 */
await page.addStyleTag({
content: `
.phone-shell {
height: auto !important;
min-height: auto !important;
min-height: 844px !important;
max-height: none !important;
border-radius: 0 !important;
box-shadow: none !important;
overflow: visible !important;
}
/* 滚动区域全部展开 */
.scroll-content,
[class*="scroll"] {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
/* 页面外层背景改为白色 */
body {
background: #fff !important;
padding: 0 !important;
margin: 0 !important;
}
/* 隐藏下拉菜单、遮罩等弹窗遮挡元素 */
.dropdown-menu, .dropdown-mask, .calendar-mask,
.plan-dropdown, .plan-mask {
display: none !important;
}
/* 底部安全区缩到最小 */
.bottom-safe {
height: 8px !important;
}
`;
document.head.appendChild(style);
`,
});
// 等待样式生效
await new Promise(r => setTimeout(r, 300));
await page.waitForTimeout(300);
// 获取实际内容高度
const bodyHandle = await page.$('.phone-shell');
const contentHeight = await page.evaluate((el) => {
if (el) return el.scrollHeight;
const contentHeight = await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (shell) return shell.scrollHeight;
return document.body.scrollHeight;
}, bodyHandle);
});
const finalHeight = Math.max(contentHeight || viewportHeight, 400);
await page.setViewport({ width: viewportWidth, height: Math.min(finalHeight + 40, 12000), deviceScaleFactor: 2 });
const finalHeight = Math.max(contentHeight || 844, 400);
const clipHeight = Math.min(finalHeight + 40, 12000);
// 使用简洁文件名,与 PRD 引用一致
// workbench/index.html → index.jpg, knowledge/index.html → knowledge-index.jpg
let outName = htmlFile.replace('.html', '.jpg');
if (outName.startsWith('workbench/')) {
outName = outName.replace('workbench/', '');
} else if (outName.startsWith('knowledge/')) {
outName = outName.replace('knowledge/', 'knowledge-');
} else if (outName.startsWith('profile/')) {
outName = outName.replace('profile/', 'profile-');
}
const outPath = path.join(SCREENSHOTS_DIR, outName);
const outName = entry.out || getOutputName(entry);
const outPath = path.join(outDir, outName);
// 长图适当压缩:>1800px 用 65% 质量,普通用 80%
const jpegQuality = finalHeight > 1800 ? 65 : 80;
await page.screenshot({
path: outPath,
type: 'jpeg',
quality: 85,
quality: jpegQuality,
fullPage: true,
});
console.log(`${htmlFile}${outName} (${viewportWidth}×${finalHeight})`);
const label = query ? `${entry.file}${query}` : entry.file;
console.log(`${label}${outName} (390×${finalHeight}, q${jpegQuality})`);
} catch (err) {
console.error(`${htmlFile}: ${err.message}`);
console.error(`${entry.file}: ${err.message}`);
} finally {
await page.close();
await context.close();
}
}
(async () => {
const allPages = [
...PAGES.map(e => ({ ...e, _outDir: SCREENSHOTS_DIR })),
...SHARED_PAGES.map(e => ({ ...e, _outDir: e.outDir })),
];
console.log('管理员端(健康应急APP)- 页面截图');
console.log(`模块目录: ${MODULE_DIR}`);
console.log(`输出目录: ${SCREENSHOTS_DIR}`);
console.log(`${PAGES.length} 个页面\n`);
console.log(`${allPages.length} 个页面(含共享页面)\n`);
// 确保输出目录存在
if (!fs.existsSync(SCREENSHOTS_DIR)) {
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
// 确保所有输出目录存在
const dirs = new Set(allPages.map(e => e._outDir));
for (const d of dirs) {
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
}
const browser = await puppeteer.launch({
const browser = await chromium.launch({
headless: true,
executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
try {
for (let i = 0; i < PAGES.length; i++) {
const pageName = PAGES[i];
console.log(`[${i + 1}/${PAGES.length}] ${pageName}`);
await screenshotPage(browser, pageName);
for (let i = 0; i < allPages.length; i++) {
const entry = allPages[i];
const label = entry.query ? `${entry.file}${entry.query}` : entry.file;
console.log(`[${i + 1}/${allPages.length}] ${label}`);
await screenshotPage(browser, entry, entry._outDir);
}
console.log(`\n✅ 全部完成!共 ${allPages.length} 张截图`);
console.log(` 管理员截图: ${SCREENSHOTS_DIR}`);
for (const sp of SHARED_PAGES) {
console.log(` 共享截图: ${sp.outDir}`);
}
console.log(`\n✅ 全部完成!共 ${PAGES.length} 张截图,保存在 ${SCREENSHOTS_DIR}`);
} catch (err) {
console.error('执行出错:', err);
process.exit(1);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 35 KiB

@@ -291,7 +291,7 @@ var personData = {
],
dateStart: '2026-06-15', dateEnd: '2026-09-15',
result: {
status: 'approved', reviewer: '刘主管', time: '2026-06-16'
status: 'approved', reviewer: '刘主管', time: '2026-06-16 10:25:38'
}
},
signed: {
@@ -306,7 +306,7 @@ var personData = {
],
dateStart: '2026-06-12', dateEnd: '2026-09-12',
result: {
status: 'signed', reviewer: '刘主管', time: '2026-06-14'
status: 'signed', reviewer: '刘主管', time: '2026-06-14 14:30:15'
}
},
rejected: {
@@ -321,7 +321,7 @@ var personData = {
],
dateStart: '2026-06-10', dateEnd: '2026-09-10',
result: {
status: 'rejected', reviewer: '刘主管', time: '2026-06-17', reason: '目标设定偏低,建议调整'
status: 'rejected', reviewer: '刘主管', time: '2026-06-17 09:12:47', reason: '目标设定偏低,建议调整'
}
}
};
+1 -1
View File
@@ -114,7 +114,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- 底部5Tab导航栏 -->
<div class="tab-bar">
<div class="tab-item active" onclick="location.href='./index.html'"><span class="tab-icon">🏥</span><span class="tab-label">工作台</span><span class="tab-badge">3</span></div>
<div class="tab-item active" onclick="location.href='./index.html'"><span class="tab-icon">🏥</span><span class="tab-label">工作台</span></div>
<div class="tab-item" onclick="location.href='../knowledge/index.html'"><span class="tab-icon">📚</span><span class="tab-label">知识库</span></div>
<div class="tab-item" onclick="location.href='../../ai-assistant/index.html'"><span class="tab-icon">🤖</span><span class="tab-label">AI助手</span></div>
<div class="tab-item" onclick="location.href='../../message/index.html'"><span class="tab-icon">💬</span><span class="tab-label">消息</span><span class="tab-badge">2</span></div>
@@ -324,7 +324,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="sub-tabs" id="reviewTabs">
<div class="sub-tab active" onclick="switchReviewTab(this,'rv-pending')">待审核<span class="badge">2</span></div>
<div class="sub-tab" onclick="switchReviewTab(this,'rv-approved')">已通过</div>
<div class="sub-tab" onclick="switchReviewTab(this,'rv-signed')">已通过待签署</div>
<div class="sub-tab" onclick="switchReviewTab(this,'rv-rejected')">已驳回</div>
</div>
@@ -422,22 +421,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div style="height:16px;"></div>
</div>
<!-- 已通过待签署列表 -->
<div id="rv-signed" style="display:none;">
<!-- [交互] 点击卡片跳转至审核详情页面 -->
<div class="review-card" onclick="location.href='./goal-review.html?status=signed'" style="cursor:pointer;">
<div class="review-card__head">
<span class="review-card__name">陈伟</span>
<span class="review-card__time">申请时间:2026-06-12 16:20</span>
</div>
<div class="review-card__basic"><span></span><span>36岁</span><span>身高 178cm</span></div>
<div class="review-card__row"><div class="review-card__col"><div class="review-card__col-title">初始</div><div class="review-card__col-vals"><span class="review-card__col-val">体重 92kg</span><span class="review-card__col-val">BMI 29.0</span><span class="review-card__col-val">体脂 30.2%</span><span class="review-card__col-val">腰围 102cm</span></div></div></div>
<div class="review-card__row"><div class="review-card__col"><div class="review-card__col-title">目标</div><div class="review-card__col-vals"><span class="review-card__col-val" style="color:#52c41a;">体重 80kg</span><span class="review-card__col-val" style="color:#52c41a;">BMI 25.2</span><span class="review-card__col-val" style="color:#52c41a;">体脂 22%</span><span class="review-card__col-val" style="color:#52c41a;">腰围 90cm</span></div></div></div>
<div style="text-align:right;margin-top:8px;"><span class="tag tag-orange">待签署</span><span style="font-size:11px;color:#999;margin-left:6px;">审核人:刘主管</span><span style="font-size:11px;color:#999;margin-left:6px;">审核时间:2026-06-14</span></div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 已驳回列表 -->
<div id="rv-rejected" style="display:none;">
<!-- [交互] 点击卡片跳转至审核详情页面 -->
@@ -672,7 +655,7 @@ function renderRankList(data) {
function switchReviewTab(el, panelId) {
el.closest('.sub-tabs').querySelectorAll('.sub-tab').forEach(function(t) { t.classList.remove('active'); });
el.classList.add('active');
['rv-pending','rv-approved','rv-signed','rv-rejected'].forEach(function(id) {
['rv-pending','rv-approved','rv-rejected'].forEach(function(id) {
document.getElementById(id).style.display = id === panelId ? 'block' : 'none';
});
}