fix: 更新文档截图和页面,新增管理员体重管理文档
@@ -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
|
||||
|
||||

|
||||
|
||||
**功能说明:** 管理员工作台入口,顶部展示用户信息(头像、姓名、角色、部门),下方以功能卡片形式呈现四大管理入口:体重管理详情、体检进度追踪、应急求助工单、穿戴设备预警记录。
|
||||
**功能说明:** 管理员工作台入口,顶部展示用户信息(头像、姓名、角色、单位部门),下方以功能卡片形式呈现四大管理入口:体重管理详情、体检进度追踪、应急求助工单、穿戴设备预警记录。
|
||||
|
||||
**业务规则:**
|
||||
- 用户信息区:头像、姓名(王建国)、角色(健康管理员)、所属部门(安全环保部·健康管理科)
|
||||
- 功能卡片共 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
|
||||
|
||||

|
||||
|
||||
**功能说明:** 体重管理活动总览页面,支持切换不同活动期次,展示活动整体统计数据,并通过三个主 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. 目标审核(待审核)
|
||||
|
||||

|
||||

|
||||
|
||||
**功能说明:** 管理员审核员工提交的体重管理减重目标申请,可选择通过或驳回,驳回时需填写驳回原因。已审核的目标以只读方式展示审核结果。
|
||||
**功能说明:** 管理员查看待审核的减重目标申请,可选择通过或驳回。驳回时需填写驳回原因。
|
||||
|
||||
**业务规则:**
|
||||
- 通过 URL 参数 `status` 区分页面模式:pending(待审核)/ approved(已通过)/ signed(待签收)/ rejected(已驳回)
|
||||
- 待审核模式:
|
||||
- 展示员工基本信息卡片(姓名、性别、年龄、身高)
|
||||
- 数据对比卡片:初始数据(体重/BMI/体脂率/腰围)、目标数据(绿色)、推荐数据范围(蓝色参考)
|
||||
- 活动时间范围卡片:开始日期 → 结束日期
|
||||
- 底部操作区:单选按钮(通过 / 驳回)、驳回时展开驳回原因文本框、提交审核按钮
|
||||
- 已通过/待签收/已驳回模式:
|
||||
- 以只读方式展示审核结果,包含状态标签、审核人、审核时间
|
||||
- 已驳回额外展示驳回原因
|
||||
- 无操作按钮
|
||||
- 员工信息:姓名、性别、年龄、身高
|
||||
- 申请时间
|
||||
- 数据对比卡片:
|
||||
- 初始数据:体重/ BMI/ 体脂/ 腰围
|
||||
- 目标数据:体重/ BMI/ 体脂/ 腰围
|
||||
- 推荐范围:体重/ BMI/ 体脂/ 腰围
|
||||
- 目标起止日期
|
||||
- 底部操作区:单选按钮(审核通过 / 审核驳回)、驳回原因文本框(默认隐藏)、提交审核按钮
|
||||
|
||||
**交互说明:**
|
||||
- 点击"通过"单选按钮:隐藏驳回原因文本框
|
||||
- 点击"驳回"单选按钮:展开驳回原因文本框(placeholder: "请输入驳回原因...")
|
||||
- 点击"审核通过"单选按钮:选中通过,隐藏驳回原因文本框
|
||||
- 点击"审核驳回"单选按钮:选中驳回,展开驳回原因文本框
|
||||
- 点击"提交审核":弹出确认弹窗
|
||||
- 确认弹窗:标题根据操作变化(通过审核 / 驳回申请),显示简要确认信息,取消按钮关闭弹窗,确认按钮执行操作
|
||||
- 确认后弹出成功弹窗:"审核完成",点击"返回列表"跳转回体重管理详情页
|
||||
- 通过时:"确认通过该目标申请?",确认按钮为蓝色"确认通过"
|
||||
- 驳回时:"确认驳回该目标申请?驳回原因:…",确认按钮为红色"确认驳回"
|
||||
- 确认弹窗点击取消:关闭弹窗
|
||||
- 确认弹窗点击确认:关闭确认弹窗,弹出成功弹窗("审核完成"),点击"返回列表"跳转回体重管理详情页
|
||||
- 点击返回:返回体重管理详情页
|
||||
|
||||
**审核操作表单:**
|
||||
@@ -297,20 +280,61 @@ stateDiagram-v2
|
||||
|
||||
---
|
||||
|
||||
### 10. 目标审核(已通过)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 查看已通过审核的减重目标申请详情,纯只读展示,无操作按钮。
|
||||
|
||||
**业务规则:**
|
||||
- 员工信息:姓名、性别、年龄、身高
|
||||
- 申请时间
|
||||
- 数据对比卡片:
|
||||
- 初始数据:体重/ BMI/ 体脂/ 腰围
|
||||
- 目标数据:体重/ BMI/ 体脂/ 腰围
|
||||
- 推荐范围:体重/ BMI/ 体脂/ 腰围
|
||||
- 目标起止日期
|
||||
- 审核结果卡片:审核结果标签、审核人、审核时间
|
||||
|
||||
**交互说明:**
|
||||
- 点击返回:返回体重管理详情页
|
||||
|
||||
---
|
||||
|
||||
### 11. 目标审核(已驳回)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 查看已被驳回的减重目标申请详情,包含驳回原因,纯只读展示,无操作按钮。
|
||||
|
||||
**业务规则:**
|
||||
- 员工信息:姓名、性别、年龄、身高
|
||||
- 申请时间
|
||||
- 数据对比卡片:
|
||||
- 初始数据:体重/ BMI/ 体脂/ 腰围
|
||||
- 目标数据:体重/ BMI/ 体脂/ 腰围
|
||||
- 推荐范围:体重/ BMI/ 体脂/ 腰围
|
||||
- 目标起止日期
|
||||
- 审核结果卡片:审核结果标签、审核人、审核时间、驳回原因
|
||||
|
||||
**交互说明:**
|
||||
- 点击返回:返回体重管理详情页
|
||||
|
||||
---
|
||||
|
||||
## 三、体检管理业务线
|
||||
|
||||
### 10. 体检进度追踪
|
||||
### 12. 体检进度追踪
|
||||
|
||||

|
||||
|
||||
**功能说明:** 按体检计划期次查看员工的体检预约和完成进度,通过四个分段 Tab(已预约、已检查、未预约、未检查)统计并展示各状态的员工列表。
|
||||
|
||||
**业务规则:**
|
||||
- 顶部体检计划选择器:下拉切换不同体检计划(如"2026年度体检""2025年度体检""2025职业病体检"),切换后刷新各状态统计数据
|
||||
- 顶部体检计划选择器:下拉切换不同体检计划,切换后刷新各状态统计数据
|
||||
- 统计区:预期人数、已预约人数、已检查人数、未预约人数、未检查人数
|
||||
- 四个分段 Tab:已预约(角标数字)、已检查(角标数字)、未预约(角标数字)、未检查(角标数字)
|
||||
- 员工卡片信息:头像(姓名首字)、姓名 + 工号、所属单位 + 部门、联系电话
|
||||
- 数据通过 JS 模拟数据动态渲染
|
||||
- 员工卡片信息:头像、姓名 + 工号、所属单位 + 部门、联系电话
|
||||
|
||||
**交互说明:**
|
||||
- 点击体检计划下拉框:展开计划列表,选择后切换数据
|
||||
@@ -321,17 +345,16 @@ stateDiagram-v2
|
||||
|
||||
## 四、应急管理业务线
|
||||
|
||||
### 11. 应急求助工单
|
||||
### 13. 应急求助工单
|
||||
|
||||

|
||||
|
||||
**功能说明:** 查看所有应急求助工单的处理情况,分"进行中"和"已完成"两个 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. 应急工单详情
|
||||
|
||||

|
||||
|
||||
@@ -350,8 +373,8 @@ stateDiagram-v2
|
||||
|
||||
**业务规则:**
|
||||
- 四个可折叠卡片(默认展开):
|
||||
1. 基本信息:工单编号、应急中心、求助员工姓名/工号、单位、部门、求助方式、求助时间、位置信息(含地图图标)、操作人员、专业人员、工单状态(已完成绿色标签)、完成时间
|
||||
2. 救助信息:救助意见(文本)、救助结果(文本)、相关照片(3 张占位图)、补充小结(多条带圆点标记的列表)
|
||||
1. 基本信息:工单编号、应急中心、求助员工姓名/工号、单位、部门、求助方式、求助时间、位置信息、操作人员、专业人员、工单状态(已完成绿色标签)、完成时间
|
||||
2. 救助信息:救助意见(文本)、救助结果(文本)、相关照片、补充小结(多条带圆点标记的列表)
|
||||
3. 派单信息:派单时间、驻场人员、派单状态(已完成)、结束时间、派单说明、驻场说明
|
||||
4. 救护车支持:车牌号、机构、驾驶员、驾驶员电话、护送人员、护送人员电话、目的地、备注
|
||||
- 纯只读展示,无编辑或操作按钮
|
||||
@@ -364,17 +387,15 @@ stateDiagram-v2
|
||||
|
||||
## 五、预警管理业务线
|
||||
|
||||
### 13. 穿戴设备预警记录
|
||||
### 15. 穿戴设备预警记录
|
||||
|
||||

|
||||
|
||||
**功能说明:** 查看所有员工穿戴设备的异常告警记录,支持按月份和告警类型筛选。
|
||||
|
||||
**业务规则:**
|
||||
- 筛选栏:月份下拉(全部 / 2026-06 / 2026-05 / 2026-04)、告警类型下拉(全部 / SOS 求助 / 体温异常 / 心率异常 / 血氧异常 / 压力异常 / 跌倒检测)
|
||||
- 告警卡片信息:告警类型标签(颜色区分类型)、时间戳、异常数值(含单位)、员工信息(头像、姓名、工号、性别、年龄、单位、部门)、位置信息(含定位图标)
|
||||
- 告警类型共 6 种:SOS 求助(红色)、体温异常(橙色)、心率异常(红色)、血氧异常(紫色)、压力异常(蓝色)、跌倒检测(红色)
|
||||
- 数据通过 JS 模拟,筛选条件联动更新列表
|
||||
- 筛选栏:月份下拉、告警类型下拉(全部 / 一键告警 / 体温异常 / 心率异常 / 血氧异常 / 压力异常 / 跌倒)
|
||||
- 告警卡片信息:告警类型标签、时间戳、异常数值(含单位)、员工信息(头像、姓名、工号、性别、年龄、单位、部门)、位置信息
|
||||
|
||||
**交互说明:**
|
||||
- 点击月份下拉:按月份筛选告警记录
|
||||
@@ -385,137 +406,88 @@ stateDiagram-v2
|
||||
|
||||
## 六、知识库模块
|
||||
|
||||
### 14. 知识库首页
|
||||
### 16. 知识库首页
|
||||
|
||||

|
||||
|
||||
**功能说明:** 专业知识参考库,分"知识文章"和"规章制度"两个大类,知识文章下再按二级分类 Tab 组织。
|
||||
|
||||
**业务规则:**
|
||||
- 两大主 Tab:知识文章(默认)/ 规章制度
|
||||
- 知识文章二级分类 Tab(4 个):体检知识(4 篇)、药品知识(2 篇)、疾病知识(2 篇)、健康知识(2 篇)
|
||||
- 知识文章列表:每篇文章展示图标、标题、简介描述、右箭头
|
||||
- 规章制度列表:5 条 PDF 文档条目(员工健康管理办法、职业病防治规则、体检异常处理流程、健康咨询服务规范、应急救援操作标准)
|
||||
- 文章数据通过 JS 对象动态渲染
|
||||
|
||||
**交互说明:**
|
||||
- 点击知识文章/规章制度主 Tab:切换大类
|
||||
- 点击二级分类 Tab:切换知识分类
|
||||
- 点击文章条目:跳转到知识详情页(通过 `?key=<文章标识>` 传递参数)
|
||||
- 点击规章制度条目:打开对应 PDF(原型中为占位链接)
|
||||
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
|
||||
|
||||
---
|
||||
|
||||
### 15. 知识详情
|
||||
### 17. 知识详情
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示单篇知识文章的详细内容,包括文章基本信息、摘要和要点列表。
|
||||
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
|
||||
|
||||
**业务规则:**
|
||||
- 通过 URL 参数 `key` 确定展示哪篇文章,共有 10 篇文章数据
|
||||
- 文章内容:图标、标题、分类、发布日期、摘要段、要点列表(彩色标题 + 灰色圆角说明卡片)
|
||||
- 体检知识(4 篇):血常规解读、心电图判读、肝功能解读、血糖解读
|
||||
- 药品知识(2 篇):降压药物、他汀类药物
|
||||
- 疾病知识(2 篇):高血压、偏头痛
|
||||
- 健康知识(2 篇):营养指导、运动与心血管健康
|
||||
|
||||
**交互说明:**
|
||||
- 点击返回:返回知识库首页
|
||||
|
||||
---
|
||||
|
||||
## 七、AI 助手
|
||||
|
||||
### 16. AI 助手
|
||||
### 18. AI 助手
|
||||
|
||||

|
||||
|
||||
**功能说明:** 智能健康问答助手,支持文字输入提问,提供快捷问题入口。全角色共享页面。
|
||||
|
||||
**业务规则:**
|
||||
- 欢迎语和三个快捷问题入口(偏头痛鉴别诊断、高血压用药建议、血脂异常处理方案)
|
||||
- AI 对话模拟:用户发送消息后,AI 延迟回复占位文字
|
||||
- 通过 `sessionStorage.appRole` 识别当前角色为 admin,设置正确的 Tab 导航路径
|
||||
|
||||
**交互说明:**
|
||||
- 输入文字后点击发送或按回车:发送消息,显示用户消息气泡(紫色右对齐)
|
||||
- 点击快捷问题标签:自动填入输入框并发送
|
||||
- 底部 Tab 切换:跳转到对应角色页面
|
||||
**功能说明:** 后续规划,暂时显示”开发中,敬请期待~”。
|
||||
|
||||
---
|
||||
|
||||
## 八、消息模块
|
||||
|
||||
### 17. 消息列表
|
||||
### 19. 消息列表
|
||||
|
||||

|
||||

|
||||
|
||||
**功能说明:** 即时通讯消息汇总列表,展示最近对话的联系人及消息预览。全角色共享页面。
|
||||
**功能说明:** 内部消息模块,与当前系统中”咨询专家”、”咨询小助手”、”操作人员”、”专业人员”、”驻场人员”、”健康管理员”进行对话聊天的消息列表。
|
||||
|
||||
**业务规则:**
|
||||
- 6 条消息会话:咨询小助手(未读"2")、李主任(专业人员)、张护士长(专业人员)、陈运营(操作人员)、刘驻场(驻场人员)、系统管理员
|
||||
- 每条展示:头像、姓名、角色标签(颜色区分)、时间、消息预览
|
||||
- 通过 `sessionStorage.appRole` 识别当前角色,设置正确的底部 Tab 导航路径
|
||||
- 消息列表按最新消息时间倒序排列
|
||||
- 每条消息显示联系人头像、姓名、角色标签、最后一条消息预览、时间
|
||||
|
||||
**交互说明:**
|
||||
- 点击消息条目:跳转到即时通讯页
|
||||
- 点击右上角"通讯录":跳转到通讯录页
|
||||
- 底部 Tab 切换:跳转到对应角色页面
|
||||
- 点击消息项:跳转到 IM 聊天页
|
||||
- 点击右上角通讯录图标:跳转到通讯录页
|
||||
- 底部 Tab 导航切换到其他模块
|
||||
|
||||
---
|
||||
|
||||
### 18. 通讯录
|
||||
### 20. 通讯录
|
||||
|
||||

|
||||

|
||||
|
||||
**功能说明:** 组织内联系人列表,按角色分组展示,支持搜索过滤。全角色共享页面。
|
||||
|
||||
**业务规则:**
|
||||
- 6 个联系人分组:小助手、咨询专家、健康管理员、操作人员、专业人员、驻场人员
|
||||
- 每组可折叠/展开,展示成员姓名、角色标签、所属机构
|
||||
- 搜索框支持按姓名实时过滤,空分组自动隐藏
|
||||
**功能说明:** 展示系统内所有可联系的内部人员,支持按角色筛选和姓名搜索。
|
||||
|
||||
**交互说明:**
|
||||
- 点击分组标题:折叠/展开该分组
|
||||
- 在搜索框输入文字:实时过滤联系人
|
||||
- 点击联系人:跳转到即时通讯页
|
||||
- 点击返回:返回消息列表页
|
||||
- 点击联系人:跳转到 IM 聊天页
|
||||
- 输入名称进行模糊搜索
|
||||
- 角色分类可收起展开
|
||||
- 返回按钮:跳转到消息列表
|
||||
|
||||
---
|
||||
|
||||
### 19. 即时通讯
|
||||
### 21. 即时通讯
|
||||
|
||||

|
||||

|
||||
|
||||
**功能说明:** 单聊页面,支持文字和语音输入,可发送图片。全角色共享页面。
|
||||
|
||||
**业务规则:**
|
||||
- 导航栏标题显示当前对话对象名称
|
||||
- 预置示例对话(与"咨询小助手"的聊天记录)
|
||||
- 支持文字输入、语音输入(按住说话)、多媒体附件(拍照/图库)
|
||||
- 发送的消息为蓝色右对齐气泡,接收的消息为白色左对齐气泡
|
||||
**功能说明:** 与内部人员的即时通讯聊天界面。
|
||||
|
||||
**交互说明:**
|
||||
- 在输入框输入文字后点击发送:消息以蓝色气泡展示在右侧
|
||||
- 点击语音切换按钮:切换为按住说话模式
|
||||
- 点击"+"按钮:展开多媒体面板(拍照/图库选项,原型中为占位)
|
||||
- 点击返回:返回消息列表页
|
||||
- 底部输入框输入文字并发送
|
||||
- 支持语音和输入切换
|
||||
- 支持拍照和发送图库照片
|
||||
- 返回按钮:跳转到消息列表
|
||||
|
||||
---
|
||||
|
||||
## 九、个人中心模块
|
||||
|
||||
### 20. 个人中心主页
|
||||
### 22. 个人中心主页
|
||||
|
||||

|
||||
|
||||
**功能说明:** 管理员个人信息展示和功能菜单导航。
|
||||
|
||||
**业务规则:**
|
||||
- 用户信息区:头像、姓名(王建国)、角色(健康管理员)、所属部门(安全环保部·健康管理科)
|
||||
- 用户信息区:头像、姓名、角色、所属单位部门
|
||||
- 菜单项:修改个人信息、修改密码、关于我们
|
||||
- 退出登录按钮(红色文字)
|
||||
- 退出登录按钮
|
||||
|
||||
**交互说明:**
|
||||
- 点击"修改个人信息":跳转到修改个人信息页
|
||||
@@ -525,7 +497,7 @@ stateDiagram-v2
|
||||
|
||||
---
|
||||
|
||||
### 21. 修改个人信息
|
||||
### 23. 修改个人信息
|
||||
|
||||

|
||||
|
||||
@@ -536,28 +508,15 @@ stateDiagram-v2
|
||||
- 点击"保存":验证姓名非空,显示"保存成功"提示(1.5 秒自动消失)
|
||||
- 点击返回:返回个人中心主页
|
||||
|
||||
**表单字段表:**
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验规则 |
|
||||
|--------|------|------|----------|
|
||||
| 头像 | 图片选择 | 否 | 支持拍照或从相册选择 |
|
||||
| 姓名 | 单行文本 | 是 | 不能为空 |
|
||||
|
||||
---
|
||||
|
||||
### 22. 修改密码
|
||||
### 24. 修改密码
|
||||
|
||||

|
||||
|
||||
**功能说明:** 修改管理员登录密码。
|
||||
|
||||
**交互说明:**
|
||||
- 点击"保存修改":弹出修改成功确认弹窗("密码修改成功,下次登录请使用新密码"),点击确定后跳转回个人中心
|
||||
- 点击"保存修改":弹出修改成功确认弹窗("密码修改成功"),退回到登录页面进行重新登录
|
||||
|
||||
**表单字段表:**
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验规则 |
|
||||
|--------|------|------|----------|
|
||||
| 当前密码 | 密码输入 | 是 | 不能为空 |
|
||||
| 新密码 | 密码输入 | 是 | 不能为空 |
|
||||
| 确认新密码 | 密码输入 | 是 | 必须与新密码一致 |
|
||||
|
||||
@@ -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">×</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>
|
||||
@@ -1,10 +1,15 @@
|
||||
/**
|
||||
* 管理员端(健康应急APP)页面截图脚本
|
||||
* 使用 Puppeteer 批量截取 HTML 原型页面,输出 JPEG(质量 85%)到 doc/screenshots/
|
||||
* 使用 Playwright 批量截取 HTML 原型页面,输出 JPEG 到 doc/screenshots/
|
||||
*
|
||||
* 截图约束(参考项目背景.md §7.4):
|
||||
* - 视口宽度 390px,1x 缩放,全页长图
|
||||
* - JPEG 质量:普通页 80%,长图(>1800px)65%
|
||||
* - 目标体积 ≤ 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);
|
||||
|
||||
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 40 KiB |
|
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: '目标设定偏低,建议调整'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
});
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 75 KiB |
@@ -105,7 +105,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
|
||||
<div>科室:心血管内科</div>
|
||||
<div>医生:李建国</div>
|
||||
<div>挂号类别:专家号</div>
|
||||
<div>期望预约时间:2026-04-28</div>
|
||||
<div>期望预约时间:2026-04-26 15:00</div>
|
||||
</div>
|
||||
<div class="card__tip">请尽快确认预约信息并安排驻场人员</div>
|
||||
</div>
|
||||
@@ -120,7 +120,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
|
||||
<div>科室:肿瘤放疗科</div>
|
||||
<div>医生:王明辉</div>
|
||||
<div>挂号类别:专家号</div>
|
||||
<div>期望预约时间:2026-04-27</div>
|
||||
<div>期望预约时间:2026-04-26 15:00</div>
|
||||
</div>
|
||||
<div class="card__tip">请尽快确认预约信息并安排驻场人员</div>
|
||||
</div>
|
||||
@@ -147,7 +147,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
|
||||
<div>科室:心血管内科</div>
|
||||
<div>医生:赵伟民</div>
|
||||
<div>挂号类别:专家号</div>
|
||||
<div>期望预约时间:2026-04-26 14:00–14:20</div>
|
||||
<div>期望预约时间:2026-04-26 15:00</div>
|
||||
</div>
|
||||
<div class="card__tip">已派单至驻场人员(赵强)</div>
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="nav">
|
||||
<span class="nav__title">员工端健康监测 · 设计总览</span>
|
||||
<a class="nav__link" href="../home/index.html" target="_top">← 返回员工端首页</a>
|
||||
<a class="nav__link" href="javascript:history.back()">← 返回首页</a>
|
||||
</nav>
|
||||
|
||||
<!-- 首页入口 -->
|
||||
|
||||
@@ -162,6 +162,7 @@ stateDiagram-v2
|
||||
- 审核状态:待审核(黄色)、审核通过(绿色)、已驳回(红色,含驳回原因)
|
||||
- 驳回后可修改重新提交
|
||||
- 审核通过后可签订承诺书
|
||||
- 特别注意:员工如参加过活动(签署过承诺书),后续活动则自动参加(无需提交审核+签署承诺书),承诺书使用之前的数据
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
<!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; }
|
||||
.tag--flow { background: #1a3a1a; color: #69db7c; }
|
||||
|
||||
.sub-title { font-size: 12px; color: #888; margin: 20px 0 8px 0; font-weight: 500; }
|
||||
|
||||
.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">体重管理员工端APP · 设计总览</span>
|
||||
<a class="nav__link" href="#sec-commit">目标承诺</a>
|
||||
<a class="nav__link" href="#sec-main">体重管理主页</a>
|
||||
<a class="nav__link" href="#sec-checkin">每日打卡</a>
|
||||
<a class="nav__link" href="#sec-history">历史记录</a>
|
||||
<a class="nav__link" href="#sec-advice">建议与报告</a>
|
||||
</nav>
|
||||
|
||||
<section class="section" id="sec-commit">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">一、目标承诺(多步骤流程)</h2>
|
||||
<span class="section__count">31</span>
|
||||
</div>
|
||||
<div class="sub-title">1. 目标承诺(goal-commitment.html)— 7 种状态</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment.jpg','目标承诺 · 未选目标-有活动')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment.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/goal-commitment-unset-noact.jpg','目标承诺 · 未选目标-无活动')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-unset-noact.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 未选目标-无活动</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment-personal.jpg','目标承诺 · 已选个人目标')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-personal.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 已选个人目标</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment-wait.jpg','目标承诺 · 待审核')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-wait.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 待审核</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment-pass.jpg','目标承诺 · 审核通过')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-pass.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 审核通过</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment-reject.jpg','目标承诺 · 已驳回')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-reject.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 已驳回</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment-signed.jpg','目标承诺 · 已签署')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-signed.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 已签署</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-commitment-reason.jpg','目标承诺 · 驳回原因弹窗')">
|
||||
<div class="card__img"><img src="screenshots/goal-commitment-reason.jpg" alt=""></div>
|
||||
<div class="card__label">目标承诺 · 驳回原因弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-title">2. 目标管理(goal-management.html)— 7 种状态</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management.jpg','目标管理 · 未选目标-有活动')">
|
||||
<div class="card__img"><img src="screenshots/goal-management.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/goal-management-unset-noact.jpg','目标管理 · 未选目标-无活动')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-unset-noact.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 未选目标-无活动</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-personal.jpg','目标管理 · 已选个人目标')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-personal.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 已选个人目标</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-wait.jpg','目标管理 · 待审核')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-wait.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 待审核</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-pass.jpg','目标管理 · 审核通过')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-pass.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 审核通过</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-reject.jpg','目标管理 · 已驳回')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-reject.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 已驳回</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-signed.jpg','目标管理 · 已签署')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-signed.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 已签署</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-act-sheet.jpg','目标管理 · 活动选择弹窗')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-act-sheet.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 活动选择弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-target-sheet.jpg','目标管理 · 标尺修改弹窗')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-target-sheet.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 标尺修改弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-confirm.jpg','目标管理 · 提交确认弹窗')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-confirm.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 提交确认弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/goal-management-reason.jpg','目标管理 · 驳回原因弹窗')">
|
||||
<div class="card__img"><img src="screenshots/goal-management-reason.jpg" alt=""></div>
|
||||
<div class="card__label">目标管理 · 驳回原因弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-title">3~9. 其他页面</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/basic-info.jpg','基本信息')">
|
||||
<div class="card__img"><img src="screenshots/basic-info.jpg" alt=""></div>
|
||||
<div class="card__label">基本信息</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/basic-info-height.jpg','基本信息 · 身高测量弹窗')">
|
||||
<div class="card__img"><img src="screenshots/basic-info-height.jpg" alt=""></div>
|
||||
<div class="card__label">基本信息 · 身高测量弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/basic-info-activity.jpg','基本信息 · 活动等级弹窗')">
|
||||
<div class="card__img"><img src="screenshots/basic-info-activity.jpg" alt=""></div>
|
||||
<div class="card__label">基本信息 · 活动等级弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/indicators-diseases.jpg','指标慢病 · 重点指标')">
|
||||
<div class="card__img"><img src="screenshots/indicators-diseases.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/indicators-diseases-diseases.jpg','指标慢病 · 慢性疾病')">
|
||||
<div class="card__img"><img src="screenshots/indicators-diseases-diseases.jpg" alt=""></div>
|
||||
<div class="card__label">指标慢病 · 慢性疾病</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/indicators-diseases-measure.jpg','指标慢病 · 测量标尺弹窗')">
|
||||
<div class="card__img"><img src="screenshots/indicators-diseases-measure.jpg" alt=""></div>
|
||||
<div class="card__label">指标慢病 · 测量标尺弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/indicators-diseases-disease-edit.jpg','指标慢病 · 慢病修改弹窗')">
|
||||
<div class="card__img"><img src="screenshots/indicators-diseases-disease-edit.jpg" alt=""></div>
|
||||
<div class="card__label">指标慢病 · 慢病修改弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/contraindications.jpg','禁忌证')">
|
||||
<div class="card__img"><img src="screenshots/contraindications.jpg" alt=""></div>
|
||||
<div class="card__label">禁忌证</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/promise-signing.jpg','签订承诺')">
|
||||
<div class="card__img"><img src="screenshots/promise-signing.jpg" alt=""></div>
|
||||
<div class="card__label">签订承诺</div>
|
||||
<div class="card__tags"><span class="tag tag--flow">横屏签名</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/promise-signing-giveup.jpg','签订承诺 · 放弃确认弹窗')">
|
||||
<div class="card__img"><img src="screenshots/promise-signing-giveup.jpg" alt=""></div>
|
||||
<div class="card__label">签订承诺 · 放弃确认弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/my-promise.jpg','我的承诺')">
|
||||
<div class="card__img"><img src="screenshots/my-promise.jpg" alt=""></div>
|
||||
<div class="card__label">我的承诺</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/activity-detail.jpg','活动详情')">
|
||||
<div class="card__img"><img src="screenshots/activity-detail.jpg" alt=""></div>
|
||||
<div class="card__label">活动详情</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<section class="section" id="sec-main">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">二、体重管理主页(核心中枢)</h2>
|
||||
<span class="section__count">3</span>
|
||||
</div>
|
||||
<div class="sub-title">9. 体重管理主页(weight-main.html)— 3 种模式</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-main.jpg','体重管理主页 · 已参加活动')">
|
||||
<div class="card__img"><img src="screenshots/weight-main.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-main-personal.jpg','体重管理主页 · 已设置个人目标')">
|
||||
<div class="card__img"><img src="screenshots/weight-main-personal.jpg" alt=""></div>
|
||||
<div class="card__label">体重管理主页 · 已设置个人目标</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-main-none.jpg','体重管理主页 · 未参加未设置未组织')">
|
||||
<div class="card__img"><img src="screenshots/weight-main-none.jpg" alt=""></div>
|
||||
<div class="card__label">体重管理主页 · 未参加未设置未组织</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<section class="section" id="sec-checkin">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">三、每日打卡</h2>
|
||||
<span class="section__count">15</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/eat-checkin.jpg','吃打卡')">
|
||||
<div class="card__img"><img src="screenshots/eat-checkin.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/eat-checkin-portion.jpg','吃打卡 · 份量录入弹窗')">
|
||||
<div class="card__img"><img src="screenshots/eat-checkin-portion.jpg" alt=""></div>
|
||||
<div class="card__label">吃打卡 · 份量录入弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/eat-checkin-summary.jpg','吃打卡 · 已选汇总弹窗')">
|
||||
<div class="card__img"><img src="screenshots/eat-checkin-summary.jpg" alt=""></div>
|
||||
<div class="card__label">吃打卡 · 已选汇总弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/eat-checkin-confirm.jpg','吃打卡 · 确认提交弹窗')">
|
||||
<div class="card__img"><img src="screenshots/eat-checkin-confirm.jpg" alt=""></div>
|
||||
<div class="card__label">吃打卡 · 确认提交弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/eat-checkin-tip.jpg','吃打卡 · 提示弹窗')">
|
||||
<div class="card__img"><img src="screenshots/eat-checkin-tip.jpg" alt=""></div>
|
||||
<div class="card__label">吃打卡 · 提示弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-checkin.jpg','动打卡')">
|
||||
<div class="card__img"><img src="screenshots/exercise-checkin.jpg" alt=""></div>
|
||||
<div class="card__label">动打卡</div>
|
||||
<div class="card__tags"><span class="tag tag--state">4种来源</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-checkin-duration.jpg','动打卡 · 时长录入弹窗')">
|
||||
<div class="card__img"><img src="screenshots/exercise-checkin-duration.jpg" alt=""></div>
|
||||
<div class="card__label">动打卡 · 时长录入弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-checkin-summary.jpg','动打卡 · 已选汇总弹窗')">
|
||||
<div class="card__img"><img src="screenshots/exercise-checkin-summary.jpg" alt=""></div>
|
||||
<div class="card__label">动打卡 · 已选汇总弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-checkin-confirm.jpg','动打卡 · 确认提交弹窗')">
|
||||
<div class="card__img"><img src="screenshots/exercise-checkin-confirm.jpg" alt=""></div>
|
||||
<div class="card__label">动打卡 · 确认提交弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-checkin-tip.jpg','动打卡 · 提示弹窗')">
|
||||
<div class="card__img"><img src="screenshots/exercise-checkin-tip.jpg" alt=""></div>
|
||||
<div class="card__label">动打卡 · 提示弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-checkin.jpg','体重打卡')">
|
||||
<div class="card__img"><img src="screenshots/weight-checkin.jpg" alt=""></div>
|
||||
<div class="card__label">体重打卡</div>
|
||||
<div class="card__tags"><span class="tag tag--state">4种来源</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-checkin-value.jpg','体重打卡 · 数值录入弹窗')">
|
||||
<div class="card__img"><img src="screenshots/weight-checkin-value.jpg" alt=""></div>
|
||||
<div class="card__label">体重打卡 · 数值录入弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-checkin-summary.jpg','体重打卡 · 已选汇总弹窗')">
|
||||
<div class="card__img"><img src="screenshots/weight-checkin-summary.jpg" alt=""></div>
|
||||
<div class="card__label">体重打卡 · 已选汇总弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-checkin-confirm.jpg','体重打卡 · 确认提交弹窗')">
|
||||
<div class="card__img"><img src="screenshots/weight-checkin-confirm.jpg" alt=""></div>
|
||||
<div class="card__label">体重打卡 · 确认提交弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-checkin-tip.jpg','体重打卡 · 提示弹窗')">
|
||||
<div class="card__img"><img src="screenshots/weight-checkin-tip.jpg" alt=""></div>
|
||||
<div class="card__label">体重打卡 · 提示弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<section class="section" id="sec-history">
|
||||
<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-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/report-detail.jpg','详细报告 · 体脂仪')">
|
||||
<div class="card__img"><img src="screenshots/report-detail.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/report-detail-scale.jpg','详细报告 · 体重秤')">
|
||||
<div class="card__img"><img src="screenshots/report-detail-scale.jpg" alt=""></div>
|
||||
<div class="card__label">详细报告 · 体重秤</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/report-detail-bluetooth.jpg','详细报告 · 蓝牙秤')">
|
||||
<div class="card__img"><img src="screenshots/report-detail-bluetooth.jpg" alt=""></div>
|
||||
<div class="card__label">详细报告 · 蓝牙秤</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/eat-history.jpg','摄入记录')">
|
||||
<div class="card__img"><img src="screenshots/eat-history.jpg" alt=""></div>
|
||||
<div class="card__label">摄入记录</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-history.jpg','消耗记录')">
|
||||
<div class="card__img"><img src="screenshots/exercise-history.jpg" alt=""></div>
|
||||
<div class="card__label">消耗记录</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/balance-history.jpg','吃动平衡历史')">
|
||||
<div class="card__img"><img src="screenshots/balance-history.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-advice">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">五、建议与报告</h2>
|
||||
<span class="section__count">13</span>
|
||||
</div>
|
||||
<div class="sub-title">21. 体重报告(weight-report.html)— 3 种模式</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-report.jpg','体重报告 · 已参加活动')">
|
||||
<div class="card__img"><img src="screenshots/weight-report.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-personal.jpg','体重报告 · 已设置个人目标')">
|
||||
<div class="card__img"><img src="screenshots/weight-report-personal.jpg" alt=""></div>
|
||||
<div class="card__label">体重报告 · 已设置个人目标</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-report-none.jpg','体重报告 · 未参加未设置')">
|
||||
<div class="card__img"><img src="screenshots/weight-report-none.jpg" alt=""></div>
|
||||
<div class="card__label">体重报告 · 未参加未设置</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-title">22. 排行榜(weight-ranking.html)— 3 种模式</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-ranking.jpg','排行榜 · 已参加活动')">
|
||||
<div class="card__img"><img src="screenshots/weight-ranking.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-ranking-personal.jpg','排行榜 · 已设置个人目标')">
|
||||
<div class="card__img"><img src="screenshots/weight-ranking-personal.jpg" alt=""></div>
|
||||
<div class="card__label">排行榜 · 已设置个人目标</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/weight-ranking-none.jpg','排行榜 · 未参加未设置未组织')">
|
||||
<div class="card__img"><img src="screenshots/weight-ranking-none.jpg" alt=""></div>
|
||||
<div class="card__label">排行榜 · 未参加未设置未组织</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-title">18~20. 其他页面</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/diet-advice.jpg','膳食建议')">
|
||||
<div class="card__img"><img src="screenshots/diet-advice.jpg" alt=""></div>
|
||||
<div class="card__label">膳食建议</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/diet-advice-detail.jpg','膳食建议 · 餐品详情弹窗')">
|
||||
<div class="card__img"><img src="screenshots/diet-advice-detail.jpg" alt=""></div>
|
||||
<div class="card__label">膳食建议 · 餐品详情弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/diet-advice-dislike.jpg','膳食建议 · 不喜欢确认弹窗')">
|
||||
<div class="card__img"><img src="screenshots/diet-advice-dislike.jpg" alt=""></div>
|
||||
<div class="card__label">膳食建议 · 不喜欢确认弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/diet-questionnaire.jpg','膳食偏好设置')">
|
||||
<div class="card__img"><img src="screenshots/diet-questionnaire.jpg" alt=""></div>
|
||||
<div class="card__label">膳食偏好设置</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/diet-questionnaire-region.jpg','膳食偏好 · 地区选择弹窗')">
|
||||
<div class="card__img"><img src="screenshots/diet-questionnaire-region.jpg" alt=""></div>
|
||||
<div class="card__label">膳食偏好 · 地区选择弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/diet-questionnaire-avoid.jpg','膳食偏好 · 饮食禁忌弹窗')">
|
||||
<div class="card__img"><img src="screenshots/diet-questionnaire-avoid.jpg" alt=""></div>
|
||||
<div class="card__label">膳食偏好 · 饮食禁忌弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/exercise-advice.jpg','运动建议')">
|
||||
<div class="card__img"><img src="screenshots/exercise-advice.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">×</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');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,49 +11,164 @@ const fs = require('fs');
|
||||
const MODULE_DIR = __dirname.replace(/\\doc$/, ''); // weight-management/
|
||||
const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
|
||||
|
||||
// 所有需要截图的页面(相对于模块目录)
|
||||
/**
|
||||
* 页面截图配置
|
||||
* 每项可以是 string(简单页面名)或 object:
|
||||
* { file: 'xxx.html', type: 'state', outName?: 'override-name' }
|
||||
* - type: URL 查询参数 ?type= 的值(页面通过右上角菜单切换状态)
|
||||
* - outName: 输出文件名(不含扩展名),默认取自 file + type
|
||||
*/
|
||||
const PAGES = [
|
||||
'weight-main.html',
|
||||
// 每日打卡
|
||||
'eat-checkin.html',
|
||||
'exercise-checkin.html',
|
||||
'weight-checkin.html',
|
||||
// 历史记录
|
||||
'report-detail.html',
|
||||
'weight-history.html',
|
||||
'eat-history.html',
|
||||
'exercise-history.html',
|
||||
'balance-history.html',
|
||||
// 建议与报告
|
||||
'diet-advice.html',
|
||||
'diet-questionnaire.html',
|
||||
'exercise-advice.html',
|
||||
'weight-report.html',
|
||||
'weight-ranking.html',
|
||||
// 目标承诺
|
||||
'goal-commitment.html',
|
||||
'goal-management.html',
|
||||
// ===== 目标承诺(多步骤流程) =====
|
||||
// 目标承诺 - 7 种状态(默认:unset-hasact)
|
||||
{ file: 'goal-commitment.html', type: 'unset-hasact', outName: 'goal-commitment' },
|
||||
{ file: 'goal-commitment.html', type: 'unset-noact', outName: 'goal-commitment-unset-noact' },
|
||||
{ file: 'goal-commitment.html', type: 'personal', outName: 'goal-commitment-personal' },
|
||||
{ file: 'goal-commitment.html', type: 'wait', outName: 'goal-commitment-wait' },
|
||||
{ file: 'goal-commitment.html', type: 'pass', outName: 'goal-commitment-pass' },
|
||||
{ file: 'goal-commitment.html', type: 'reject', outName: 'goal-commitment-reject' },
|
||||
{ file: 'goal-commitment.html', type: 'signed', outName: 'goal-commitment-signed' },
|
||||
|
||||
// 目标管理 - 7 种状态(默认:unset-hasact)
|
||||
{ file: 'goal-management.html', type: 'unset-hasact', outName: 'goal-management' },
|
||||
{ file: 'goal-management.html', type: 'unset-noact', outName: 'goal-management-unset-noact' },
|
||||
{ file: 'goal-management.html', type: 'personal', outName: 'goal-management-personal' },
|
||||
{ file: 'goal-management.html', type: 'wait', outName: 'goal-management-wait' },
|
||||
{ file: 'goal-management.html', type: 'pass', outName: 'goal-management-pass' },
|
||||
{ file: 'goal-management.html', type: 'reject', outName: 'goal-management-reject' },
|
||||
{ file: 'goal-management.html', type: 'signed', outName: 'goal-management-signed' },
|
||||
|
||||
// 其余目标承诺流程页面(单状态)
|
||||
'basic-info.html',
|
||||
// 指标慢病 - 2 种 Tab(默认:重点指标;慢性疾病需 eval 切换)
|
||||
'indicators-diseases.html',
|
||||
{ file: 'indicators-diseases.html', outName: 'indicators-diseases-diseases', eval: 'switchTab("diseases")' },
|
||||
|
||||
'contraindications.html',
|
||||
'promise-signing.html',
|
||||
'my-promise.html',
|
||||
'activity-detail.html',
|
||||
|
||||
// ===== 体重管理主页 - 3 种模式(默认:hasact) =====
|
||||
{ file: 'weight-main.html', type: 'hasact', outName: 'weight-main' },
|
||||
{ file: 'weight-main.html', type: 'personal', outName: 'weight-main-personal' },
|
||||
{ file: 'weight-main.html', type: 'none', outName: 'weight-main-none' },
|
||||
|
||||
// ===== 每日打卡(单状态) =====
|
||||
'eat-checkin.html',
|
||||
'exercise-checkin.html',
|
||||
'weight-checkin.html',
|
||||
|
||||
// ===== 历史记录(单状态) =====
|
||||
// 详细报告 - 3 种设备(默认:体脂仪 fat)
|
||||
{ file: 'report-detail.html', type: 'fat', outName: 'report-detail' },
|
||||
{ file: 'report-detail.html', type: 'scale', outName: 'report-detail-scale' },
|
||||
{ file: 'report-detail.html', type: 'bluetooth', outName: 'report-detail-bluetooth' },
|
||||
'weight-history.html',
|
||||
'eat-history.html',
|
||||
'exercise-history.html',
|
||||
'balance-history.html',
|
||||
|
||||
// ===== 建议与报告 =====
|
||||
// 体重报告 - 3 种模式(默认:hasact)
|
||||
{ file: 'weight-report.html', type: 'hasact', outName: 'weight-report' },
|
||||
{ file: 'weight-report.html', type: 'personal', outName: 'weight-report-personal' },
|
||||
{ file: 'weight-report.html', type: 'none', outName: 'weight-report-none' },
|
||||
|
||||
// 排行榜 - 3 种模式(默认:hasact)
|
||||
{ file: 'weight-ranking.html', type: 'hasact', outName: 'weight-ranking' },
|
||||
{ file: 'weight-ranking.html', type: 'personal', outName: 'weight-ranking-personal' },
|
||||
{ file: 'weight-ranking.html', type: 'none', outName: 'weight-ranking-none' },
|
||||
|
||||
// 其余建议与报告页面(单状态)
|
||||
'diet-advice.html',
|
||||
'diet-questionnaire.html',
|
||||
'exercise-advice.html',
|
||||
|
||||
// ===== 弹窗截图 =====
|
||||
// 目标承诺 - 驳回原因弹窗(需要先切换到 reject 状态)
|
||||
{ file: 'goal-commitment.html', type: 'reject', outName: 'goal-commitment-reason', eval: 'openReasonModal()' },
|
||||
|
||||
// 目标管理 - 4 个弹窗
|
||||
{ file: 'goal-management.html', outName: 'goal-management-act-sheet', eval: 'openActSheet()' },
|
||||
{ file: 'goal-management.html', outName: 'goal-management-target-sheet', eval: 'openTargetSheet("targetWeight")' },
|
||||
{ file: 'goal-management.html', outName: 'goal-management-confirm', eval: 'openConfirm()' },
|
||||
{ file: 'goal-management.html', type: 'reject', outName: 'goal-management-reason', eval: 'openRejectModal()' },
|
||||
|
||||
// 基本信息 - 2 个弹窗
|
||||
{ file: 'basic-info.html', outName: 'basic-info-height', eval: 'openHeightSheet()' },
|
||||
{ file: 'basic-info.html', outName: 'basic-info-activity', eval: 'openSheet()' },
|
||||
|
||||
// 指标慢病 - 2 个弹窗
|
||||
{ file: 'indicators-diseases.html', outName: 'indicators-diseases-measure', eval: 'openSheet("weight")' },
|
||||
{ file: 'indicators-diseases.html', outName: 'indicators-diseases-disease-edit', eval: 'openDiseaseModal("原发性高血压")' },
|
||||
|
||||
// 吃打卡 - 4 个弹窗
|
||||
{ file: 'eat-checkin.html', outName: 'eat-checkin-portion', eval: 'selectedMeal="lunch";openGramPanel(1)' },
|
||||
{ file: 'eat-checkin.html', outName: 'eat-checkin-summary', eval: 'selectedMeal="lunch";cart[1]=[150];cart[6]=[100];cart[7]=[120];openSummary()' },
|
||||
{ file: 'eat-checkin.html', outName: 'eat-checkin-confirm', eval: 'selectedMeal="lunch";cart[1]=[150];cart[6]=[100];openConfirm()' },
|
||||
{ file: 'eat-checkin.html', outName: 'eat-checkin-tip', eval: 'if(typeof showAutoMealTip==="function")showAutoMealTip();else document.getElementById("tipMask").classList.add("show")' },
|
||||
|
||||
// 动打卡 - 4 个弹窗
|
||||
{ file: 'exercise-checkin.html', outName: 'exercise-checkin-duration', eval: 'selectedDay="wed";openDurationPanel(1)' },
|
||||
{ file: 'exercise-checkin.html', outName: 'exercise-checkin-summary', eval: 'cart[1]=[30];cart[2]=[45];cart[9]=[20];openSummary()' },
|
||||
{ file: 'exercise-checkin.html', outName: 'exercise-checkin-confirm', eval: 'cart[1]=[30];cart[2]=[45];openConfirm()' },
|
||||
{ file: 'exercise-checkin.html', outName: 'exercise-checkin-tip', eval: 'if(typeof showWearableTip==="function")showWearableTip();else document.getElementById("tipMask").classList.add("show")' },
|
||||
|
||||
// 体重打卡 - 4 个弹窗
|
||||
{ file: 'weight-checkin.html', outName: 'weight-checkin-value', eval: 'selectedDay="wed";openValuePanel("weight")' },
|
||||
{ file: 'weight-checkin.html', outName: 'weight-checkin-summary', eval: 'cart["weight"]=65.5;cart["fat"]=22.3;cart["waist"]=78;openSummary()' },
|
||||
{ file: 'weight-checkin.html', outName: 'weight-checkin-confirm', eval: 'cart["weight"]=65.5;cart["fat"]=22.3;openConfirm()' },
|
||||
{ file: 'weight-checkin.html', outName: 'weight-checkin-tip', eval: 'if(typeof showHeightScaleTip==="function")showHeightScaleTip();else document.getElementById("tipMask").classList.add("show")' },
|
||||
|
||||
// 膳食建议 - 3 个弹窗
|
||||
{ file: 'diet-advice.html', outName: 'diet-advice-detail', eval: 'if(window._currentMealItems&&window._currentMealItems.length>0)openMealDetail(0)' },
|
||||
{ file: 'diet-advice.html', outName: 'diet-advice-dislike', eval: 'if(window._currentMealItems&&window._currentMealItems.length>0){openMealDetail(0);setTimeout(function(){var b=document.querySelector("[class*=dislike]");if(b)b.click()},400)}' },
|
||||
{ file: 'diet-advice.html', outName: 'diet-advice-region', eval: 'var g=document.getElementById("guideOverlay");if(g)g.classList.add("hidden");if(typeof openRegionSheet==="function")openRegionSheet()' },
|
||||
|
||||
// 膳食偏好 - 2 个弹窗
|
||||
{ file: 'diet-questionnaire.html', outName: 'diet-questionnaire-region', eval: 'if(typeof openRegionSheet==="function")openRegionSheet()' },
|
||||
{ file: 'diet-questionnaire.html', outName: 'diet-questionnaire-avoid', eval: 'if(typeof openCustomAvoidInput==="function")openCustomAvoidInput()' },
|
||||
|
||||
// 签订承诺 - 1 个弹窗
|
||||
{ file: 'promise-signing.html', outName: 'promise-signing-giveup', eval: 'if(typeof openGiveup==="function")openGiveup();else document.getElementById("giveupMask").classList.add("show")' },
|
||||
];
|
||||
|
||||
// 特殊页面配置
|
||||
// 特殊页面配置(按输出文件名匹配)
|
||||
const PAGE_CONFIGS = {
|
||||
'promise-signing.html': { viewportWidth: 844, viewportHeight: 390 },
|
||||
// 膳食建议页需要注入 localStorage 跳过引导遮罩
|
||||
'diet-advice.html': { injectPrefs: true },
|
||||
'promise-signing': { viewportWidth: 844, viewportHeight: 390 },
|
||||
// 膳食建议页需要注入 localStorage 跳过引导遮罩(guide 截图除外)
|
||||
'diet-advice': { injectPrefs: true },
|
||||
'diet-advice-detail': { injectPrefs: true },
|
||||
'diet-advice-dislike': { injectPrefs: true },
|
||||
'diet-advice-region': { injectPrefs: true },
|
||||
};
|
||||
|
||||
async function screenshotPage(browser, htmlFile) {
|
||||
/**
|
||||
* 解析页面配置项,返回 { file, url, outName }
|
||||
*/
|
||||
function resolvePageConfig(item) {
|
||||
if (typeof item === 'string') {
|
||||
return { file: item, url: item, outName: item.replace('.html', '') };
|
||||
}
|
||||
const file = item.file;
|
||||
const type = item.type;
|
||||
const outName = item.outName || file.replace('.html', '') + (type ? '-' + type : '');
|
||||
const url = type ? file + '?type=' + encodeURIComponent(type) : file;
|
||||
const evalAfterLoad = item.eval || null;
|
||||
return { file, url, outName, evalAfterLoad };
|
||||
}
|
||||
|
||||
async function screenshotPage(browser, pageConfig) {
|
||||
const page = await browser.newPage();
|
||||
const filePath = path.join(MODULE_DIR, htmlFile);
|
||||
const { file, url, outName, evalAfterLoad } = pageConfig;
|
||||
const filePath = path.join(MODULE_DIR, file);
|
||||
const fileUrl = `file:///${filePath.replace(/\\/g, '/')}`;
|
||||
|
||||
const config = PAGE_CONFIGS[htmlFile] || {};
|
||||
// 为带参数的 URL 追加查询字符串
|
||||
const fullUrl = url.includes('?') ? fileUrl + '?' + url.split('?')[1] : fileUrl;
|
||||
|
||||
const config = PAGE_CONFIGS[outName] || PAGE_CONFIGS[file.replace('.html', '')] || {};
|
||||
const viewportWidth = config.viewportWidth || 390;
|
||||
const viewportHeight = config.viewportHeight || 844;
|
||||
|
||||
@@ -72,7 +187,7 @@ async function screenshotPage(browser, htmlFile) {
|
||||
});
|
||||
}
|
||||
|
||||
await page.goto(fileUrl, { waitUntil: 'networkidle0', timeout: 15000 });
|
||||
await page.goto(fullUrl, { waitUntil: 'networkidle0', timeout: 15000 });
|
||||
|
||||
// 等待页面渲染完成
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
@@ -124,6 +239,12 @@ async function screenshotPage(browser, htmlFile) {
|
||||
// 等待样式生效
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
// 执行页面特定的 JS(如切换 Tab)
|
||||
if (pageConfig.evalAfterLoad) {
|
||||
await page.evaluate((code) => { eval(code); }, pageConfig.evalAfterLoad);
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
|
||||
// 获取实际内容高度
|
||||
const bodyHandle = await page.$('.phone-shell');
|
||||
const contentHeight = await page.evaluate((el) => {
|
||||
@@ -134,8 +255,8 @@ async function screenshotPage(browser, htmlFile) {
|
||||
const finalHeight = Math.max(contentHeight || viewportHeight, 400);
|
||||
await page.setViewport({ width: viewportWidth, height: Math.min(finalHeight + 40, 12000), deviceScaleFactor: 1 });
|
||||
|
||||
const outName = htmlFile.replace('.html', '.jpg');
|
||||
const outPath = path.join(SCREENSHOTS_DIR, outName);
|
||||
const outFile = outName + '.jpg';
|
||||
const outPath = path.join(SCREENSHOTS_DIR, outFile);
|
||||
|
||||
await page.screenshot({
|
||||
path: outPath,
|
||||
@@ -144,9 +265,9 @@ async function screenshotPage(browser, htmlFile) {
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
console.log(` ✓ ${htmlFile} → ${outName} (${viewportWidth}×${finalHeight})`);
|
||||
console.log(` ✓ ${url} → ${outFile} (${viewportWidth}×${finalHeight})`);
|
||||
} catch (err) {
|
||||
console.error(` ✗ ${htmlFile}: ${err.message}`);
|
||||
console.error(` ✗ ${url}: ${err.message}`);
|
||||
} finally {
|
||||
await page.close();
|
||||
}
|
||||
@@ -156,7 +277,7 @@ async function screenshotPage(browser, htmlFile) {
|
||||
console.log('体重管理模块 - 页面截图');
|
||||
console.log(`模块目录: ${MODULE_DIR}`);
|
||||
console.log(`输出目录: ${SCREENSHOTS_DIR}`);
|
||||
console.log(`共 ${PAGES.length} 个页面\n`);
|
||||
console.log(`共 ${PAGES.length} 个页面状态\n`);
|
||||
|
||||
// 确保输出目录存在
|
||||
if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
||||
@@ -171,9 +292,11 @@ async function screenshotPage(browser, htmlFile) {
|
||||
|
||||
try {
|
||||
for (let i = 0; i < PAGES.length; i++) {
|
||||
const pageName = PAGES[i];
|
||||
console.log(`[${i + 1}/${PAGES.length}] ${pageName}`);
|
||||
await screenshotPage(browser, pageName);
|
||||
const item = PAGES[i];
|
||||
const cfg = resolvePageConfig(item);
|
||||
const label = typeof item === 'string' ? item : `${item.file}?type=${item.type}`;
|
||||
console.log(`[${i + 1}/${PAGES.length}] ${label}`);
|
||||
await screenshotPage(browser, cfg);
|
||||
}
|
||||
console.log(`\n✅ 全部完成!共 ${PAGES.length} 张截图,保存在 ${SCREENSHOTS_DIR}`);
|
||||
} catch (err) {
|
||||
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -576,6 +576,7 @@ const SITE_MAP = [
|
||||
{ file:'project-detail.html', label:'体检项目详情' }
|
||||
] },
|
||||
{ name:'体重管理', dir:'employee-app/weight-management', entry:'weight-main.html', prd:'employee-app/weight-management/doc/PRD.md', done:true, pages:[
|
||||
{ file:'doc/design-overview.html', label:'页面总览' },
|
||||
{ file:'weight-main.html', label:'体重管理主页' },
|
||||
{ file:'weight-ranking.html', label:'排行榜' },
|
||||
{ file:'eat-checkin.html', label:'吃打卡' },
|
||||
@@ -807,13 +808,13 @@ const SITE_MAP = [
|
||||
]}
|
||||
]},
|
||||
{ role:'管理员', id:'admin', modules:[
|
||||
{name:'工作台',dir:'emergency-app/admin/workbench',pages:[
|
||||
{name:'工作台',dir:'emergency-app/admin/workbench',prd:'emergency-app/admin/doc/PRD.md',done:true,pages:[
|
||||
{file:'index.html',label:'工作台首页'},{file:'checkup-progress.html',label:'体检进度跟踪'},{file:'emergency-orders.html',label:'应急求助工单'},{file:'emergency-order-detail.html',label:'应急工单详情'},{file:'wearable-alerts.html',label:'穿戴预警记录'},{file:'weight-detail.html',label:'体重管理详情'},{file:'goal-review.html',label:'目标审核'},{file:'eat-detail.html',label:'饮食记录详情'},{file:'diet-employee-detail.html',label:'饮食明细'},{file:'exercise-detail.html',label:'运动记录详情'},{file:'exercise-employee-detail.html',label:'运动明细'},{file:'weight-record.html',label:'体重记录详情'},{file:'weight-employee-detail.html',label:'体重明细'}
|
||||
]},
|
||||
{name:'知识库',dir:'emergency-app/admin/knowledge',pages:[
|
||||
{name:'知识库',dir:'emergency-app/admin/knowledge',done:true,pages:[
|
||||
{file:'index.html',label:'知识库首页'},{file:'detail.html',label:'知识详情'}
|
||||
]},
|
||||
{name:'我的',dir:'emergency-app/admin/profile',pages:[
|
||||
{name:'我的',dir:'emergency-app/admin/profile',done:true,pages:[
|
||||
{file:'index.html',label:'个人中心'},
|
||||
{file:'edit-profile.html',label:'修改个人信息'},{file:'change-pwd.html',label:'修改密码'}
|
||||
]}
|
||||
|
||||
@@ -0,0 +1,988 @@
|
||||
# 体重管理(管理端)产品需求文档
|
||||
|
||||
## 模块概述
|
||||
|
||||
体重管理管理端为管理人员提供员工体重健康的全方位干预管理能力,涵盖干预计划创建与管理、员工报名跟踪与目标体重审核、过程数据监测(行动记录/详细数据/体重排名/活动总览/食谱记录)、历史干预数据归档、个人体重目标管理、多类型体重测量设备管理以及异常事件汇总等功能。
|
||||
|
||||
### 页面导航结构
|
||||
|
||||
```
|
||||
体重管理(管理端)
|
||||
├── 顶部导航栏(所有页面共用)
|
||||
│ ├── 档案
|
||||
│ ├── 计划管理
|
||||
│ ├── 报名管理
|
||||
│ ├── 过程管理
|
||||
│ ├── 历史干预
|
||||
│ ├── 个人体重管理
|
||||
│ ├── 设备管理
|
||||
│ └── 异常事件
|
||||
│
|
||||
├── 档案(侧边栏)
|
||||
│ ├── 个人体重数据
|
||||
│ │ └── 个人体重详情
|
||||
│ ├── 单位报表数据
|
||||
│ │ └── 单位报表详情(内Tab:二级单位/三级单位)
|
||||
│ └── 算法模型(占位页面)
|
||||
│
|
||||
├── 计划管理(侧边栏)
|
||||
│ └── 计划列表
|
||||
│ └── 新增/编辑计划(表单页)
|
||||
│ ├── 人员筛选弹窗
|
||||
│ ├── 员工选择弹窗
|
||||
│ └── 审核人员选择弹窗
|
||||
│
|
||||
├── 报名管理(侧边栏)
|
||||
│ ├── 报名跟踪
|
||||
│ │ └── 内Tab:未提交任务书/已提交待审核/已通过待签署/已签署
|
||||
│ └── 目标体重审核
|
||||
│ ├── 内Tab:待审核/已通过/已通过待签署/已驳回
|
||||
│ ├── 审核弹窗(通过/驳回)
|
||||
│ └── 承诺书预览弹窗
|
||||
│
|
||||
├── 设备管理(侧边栏,×4种设备类型,每组含设备管理+设备监测)
|
||||
│ ├── 体重秤
|
||||
│ │ ├── 设备管理
|
||||
│ │ │ └── 新增设备(弹窗)
|
||||
│ │ └── 设备监测
|
||||
│ │ └── 监测详情(弹窗)
|
||||
│ ├── 蓝牙称(同上结构)
|
||||
│ ├── 体脂仪(同上结构)
|
||||
│ ├── 运动器械(同上结构)
|
||||
│ ├── 员工监测数据
|
||||
│ │ └── 测量记录
|
||||
│ ├── 单位数据统计
|
||||
│ │ └── 内Tab:二级单位/三级单位
|
||||
│ └── 员工数据统计
|
||||
│
|
||||
├── 过程管理(侧边栏)
|
||||
│ ├── 行动记录
|
||||
│ │ ├── 单位统计表(含三测率等指标)
|
||||
│ │ └── 个人详情抽屉弹窗(含体重趋势图/饮食记录/运动记录)
|
||||
│ ├── 详细数据
|
||||
│ │ └── 个人指标详情抽屉弹窗(含体重趋势图)
|
||||
│ ├── 体重排名
|
||||
│ │ └── 内Tab:二级单位/三级单位
|
||||
│ ├── 活动总览
|
||||
│ │ └── 活动详情
|
||||
│ ├── 活动统计
|
||||
│ └── 食谱记录
|
||||
│ └── 餐品详情弹窗
|
||||
│
|
||||
├── 历史干预(侧边栏)
|
||||
│ ├── 计划列表
|
||||
│ │ ├── 人员范围弹窗
|
||||
│ │ └── 计划详情
|
||||
│ └── 人员列表
|
||||
│ └── 人员详情
|
||||
│
|
||||
├── 个人体重管理(侧边栏)
|
||||
│ └── 个人目标数据
|
||||
│ └── 个人目标详情(含体重趋势图)
|
||||
│
|
||||
└── 异常事件(侧边栏)
|
||||
├── 系统脏数据
|
||||
└── 结果异常
|
||||
```
|
||||
|
||||
### 状态流转图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 创建干预计划
|
||||
创建干预计划 --> 计划进行中: 发布计划
|
||||
计划进行中 --> 员工报名: 员工收到通知
|
||||
员工报名 --> 提交任务书: 员工设置目标体重
|
||||
提交任务书 --> 管理员审核: 提交审核
|
||||
管理员审核 --> 签署承诺书: 审核通过
|
||||
管理员审核 --> 重新提交: 审核驳回
|
||||
重新提交 --> 管理员审核
|
||||
签署承诺书 --> 过程监测: 签署完成
|
||||
过程监测 --> 计划结束: 计划到期
|
||||
计划结束 --> 历史归档: 数据归档
|
||||
|
||||
note right of 过程监测
|
||||
行动记录/详细数据/
|
||||
体重排名/活动总览/
|
||||
食谱记录
|
||||
end note
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 页面清单
|
||||
|
||||
### 档案(侧边栏组:"档案")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 1 | 个人体重数据 | 个人体重数据列表 | archive-personal-weight-data.html | 页面 |
|
||||
| | | 个人体重详情 | archive-personal-weight-detail.html | 页面 |
|
||||
| 2 | 单位报表数据 | 单位报表数据列表 | archive-unit-report.html | 页面 |
|
||||
| | | 单位报表详情 | archive-unit-report-detail.html | 页面 |
|
||||
| 3 | 算法模型 | 算法模型(占位) | archive-algorithm-model.html | 页面 |
|
||||
|
||||
### 计划管理(侧边栏组:"计划管理")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 4 | 计划列表 | 计划列表 | plan-list.html | 页面 |
|
||||
| | | 新增/编辑计划 | plan-edit.html | 页面 |
|
||||
| | | 人员筛选弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
| | | 员工选择弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
| | | 审核人员弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
|
||||
### 报名管理(侧边栏组:"报名管理")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 5 | 报名跟踪 | 报名跟踪列表 | registration-tracking.html | 页面 |
|
||||
| | | 承诺书预览弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
| 6 | 目标体重审核 | 目标体重审核列表 | target-weight-review.html | 页面 |
|
||||
| | | 审核弹窗(含指标对比) | (内嵌弹窗) | 弹窗 |
|
||||
| | | 承诺书预览弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
|
||||
### 设备管理(侧边栏组:设备管理,×4设备类型 + 3个公共统计页)
|
||||
|
||||
| 序号 | 设备类型 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|---------|--------|-----------|------|------|
|
||||
| 7 | 体重秤 | 设备管理 | 体重秤设备列表 | scale-device.html | 页面 |
|
||||
| | | | 新增设备 | scale-device-add.html | 弹窗 |
|
||||
| | | 设备监测 | 体重秤监测数据 | scale-monitor.html | 页面 |
|
||||
| | | | 监测详情 | scale-monitor-detail.html | 弹窗 |
|
||||
| 8 | 蓝牙称 | 设备管理 | 蓝牙称设备列表 | bluetooth-device.html | 页面 |
|
||||
| | | | 新增设备 | bluetooth-device-add.html | 弹窗 |
|
||||
| | | 设备监测 | 蓝牙称监测数据 | bluetooth-monitor.html | 页面 |
|
||||
| | | | 监测详情 | bluetooth-monitor-detail.html | 弹窗 |
|
||||
| 9 | 体脂仪 | 设备管理 | 体脂仪设备列表 | fat-device.html | 页面 |
|
||||
| | | | 新增设备 | fat-device-add.html | 弹窗 |
|
||||
| | | 设备监测 | 体脂仪监测数据 | fat-monitor.html | 页面 |
|
||||
| | | | 监测详情 | fat-monitor-detail.html | 弹窗 |
|
||||
| 10 | 运动器械 | 设备管理 | 运动器械设备列表 | sport-device.html | 页面 |
|
||||
| | | | 新增设备 | sport-device-add.html | 弹窗 |
|
||||
| | | 设备监测 | 运动器械监测数据 | sport-monitor.html | 页面 |
|
||||
| | | | 监测详情 | sport-monitor-detail.html | 弹窗 |
|
||||
| 11 | (公共) | 员工监测数据 | 员工监测数据列表 | employee-data.html | 页面 |
|
||||
| | | | 测量记录详情 | employee-data-record.html | 页面 |
|
||||
| 12 | (公共) | 单位数据统计 | 单位数据统计列表 | dept-stat.html | 页面 |
|
||||
| 13 | (公共) | 员工数据统计 | 员工数据统计列表 | employee-stat.html | 页面 |
|
||||
|
||||
### 过程管理(侧边栏组:"过程管理")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 14 | 行动记录 | 行动记录列表 | action-record.html | 页面 |
|
||||
| | | 个人详情抽屉 | (内嵌抽屉弹窗) | 弹窗 |
|
||||
| | | 饮食记录弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
| | | 运动记录弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
| 15 | 详细数据 | 详细数据列表 | detail-data.html | 页面 |
|
||||
| | | 个人指标详情抽屉 | (内嵌抽屉弹窗) | 弹窗 |
|
||||
| 16 | 体重排名 | 体重排名列表 | weight-ranking.html | 页面 |
|
||||
| 17 | 活动总览 | 活动总览列表 | action-overview.html | 页面 |
|
||||
| | | 活动详情 | action-overview-detail.html | 页面 |
|
||||
| 18 | 活动统计 | 活动统计列表 | activity-statistics.html | 页面 |
|
||||
| 19 | 食谱记录 | 食谱记录列表 | recipe-record.html | 页面 |
|
||||
| | | 餐品详情弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
|
||||
### 历史干预(侧边栏组:"历史干预")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 20 | 计划列表 | 历史计划列表 | history-plan-list.html | 页面 |
|
||||
| | | 人员范围弹窗 | (内嵌弹窗) | 弹窗 |
|
||||
| | | 计划详情 | history-plan-detail.html | 页面 |
|
||||
| 21 | 人员列表 | 历史人员列表 | history-personnel-list.html | 页面 |
|
||||
| | | 人员详情 | history-personnel-detail.html | 页面 |
|
||||
|
||||
### 个人体重管理(侧边栏组:"个人体重管理")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 22 | 个人目标数据 | 个人目标数据列表 | personal-target-data.html | 页面 |
|
||||
| | | 个人目标详情 | personal-target-detail.html | 页面 |
|
||||
|
||||
### 异常事件(侧边栏组:"异常事件")
|
||||
|
||||
| 序号 | 菜单项 | 页面/弹窗 | 文件 | 类型 |
|
||||
|------|--------|-----------|------|------|
|
||||
| 23 | 系统脏数据 | 系统脏数据列表 | exception-system-dirty.html | 页面 |
|
||||
| 24 | 结果异常 | 结果异常列表 | exception-result-abnormal.html | 页面 |
|
||||
|
||||
---
|
||||
|
||||
## 逐页说明
|
||||
|
||||
---
|
||||
|
||||
## 一、档案
|
||||
|
||||
### 1. 个人体重数据(archive-personal-weight-data.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以员工为维度展示个人体重管理相关数据的汇总列表,支持按姓名、单位等多维筛选。
|
||||
|
||||
**筛选条件:** 姓名(输入框)、员工编号(输入框)、单位部门(二级联动下拉)
|
||||
|
||||
**表格列(多级表头):**
|
||||
- 第一级:姓名、员工编号、性别、年龄、单位、部门、体重(kg)、BMI、体脂肪率(%)、腰围(cm)、操作
|
||||
- 体重子列:初始体重、当前体重、目标体重、变化(带颜色标注:下降绿色、上升红色)
|
||||
|
||||
**业务规则:**
|
||||
- 体重变化正值红色标注(增重),负值绿色标注(减重)
|
||||
- 变化列使用 `change-up`/`change-down` 样式区分颜色
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"详情"跳转到个人体重详情页
|
||||
- 支持导出列表数据
|
||||
- 各数值列支持排序
|
||||
|
||||
---
|
||||
|
||||
### 1-1. 个人体重详情(archive-personal-weight-detail.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示单个员工的完整体重管理数据,包括体重趋势、目标达成情况、历史测量记录等。
|
||||
|
||||
**页面布局:**
|
||||
- 顶部员工信息卡片:头像(首字母)、姓名、工号、单位、部门
|
||||
- 体重趋势图区域:SVG折线图展示体重变化趋势
|
||||
- 指标总览网格:初始体重、当前体重、目标体重、已减重量、BMI变化等
|
||||
- 历史测量记录表格
|
||||
|
||||
**交互说明:**
|
||||
- 通过URL参数加载指定员工数据
|
||||
- 趋势图以SVG动态渲染
|
||||
- 返回按钮跳转回个人体重数据列表页
|
||||
|
||||
---
|
||||
|
||||
### 2. 单位报表数据(archive-unit-report.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以单位/部门为维度展示体重管理干预的汇总统计数据,支持二级单位和三级单位两个维度切换。
|
||||
|
||||
**Tab切换:** 二级单位 / 三级单位
|
||||
|
||||
**筛选条件:** 组织机构(二级联动下拉)、年份(下拉,默认当年)
|
||||
|
||||
**二级单位表格列:**
|
||||
- 序号、单位、总人数、组织活动次数、人均减重(kg)、人均减重率(%)、人均BMI降低、人均体脂率降低(%)、人均腰围减少(cm)、人均每日摄入热量(kcal)、人均每日消耗热量(kcal)、人均每日热量缺口、人均综合得分、人均行动得分、操作
|
||||
|
||||
**三级单位表格列:** 在二级单位基础上增加"部门"列(位于单位和总人数之间)
|
||||
|
||||
**业务规则:**
|
||||
- 表格列较多,序号、单位、总人数三列做左侧固定(sticky),操作列右侧固定
|
||||
- 所有数据列支持排序
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"详细数据"跳转到单位报表详情页(archive-unit-report-detail.html),通过URL参数传递单位/部门及各项数值
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 2-1. 单位报表详情(archive-unit-report-detail.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示指定单位/部门的干预计划维度的详细数据,汇总该单位下各干预计划的执行情况和效果指标。
|
||||
|
||||
**面包屑导航:** 体重管理 / 档案 / 单位报表数据 / **单位名称**
|
||||
|
||||
**筛选条件:** 年份(下拉)
|
||||
|
||||
**表格列:**
|
||||
- 干预计划、组织机构、组织部门、起止日期、人员范围、应参加人数、已参加人数、未参加人数、完成体重目标人数、完成体脂目标人数、完成腰围目标人数、人均减重(kg)、人均减重率(%)、人均腰围减少(cm)、人均每日摄入(千卡)、人均每日消耗(千卡)、人均每日热量缺口、人均综合得分、人均行动得分、负责人
|
||||
|
||||
**交互说明:**
|
||||
- 面包屑导航支持点击跳回上级页面
|
||||
- 返回按钮跳转回单位报表数据列表页
|
||||
- 页面标题和面包屑通过URL参数动态显示单位信息
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 3. 算法模型(archive-algorithm-model.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 占位页面,主内容区域为空,仅包含顶部导航栏和侧边栏框架。**当前为占位页面**,实际内容待实现。
|
||||
|
||||
**业务规则:**
|
||||
- 预留用于管理体重管理相关的算法模型文档(如体重预测模型、健康风险评估模型等)
|
||||
|
||||
---
|
||||
|
||||
## 二、计划管理
|
||||
|
||||
### 4. 计划列表(plan-list.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 体重管理干预计划的增删改查管理,管理员在此创建和管理各单位/部门的体重干预计划。
|
||||
|
||||
**筛选条件:** 计划名称(输入框)、组织机构(二级联动下拉)、活动状态(下拉:全部/进行中/已结束/未开始)、年份选择(下拉)
|
||||
|
||||
**表格列:** 序号、组织机构、计划名称、起止日期、人员筛选(可点击数字)、审核人员(可点击数字)、活动状态(标签)、组织部门、负责人、负责人电话、操作
|
||||
|
||||
**业务规则:**
|
||||
- 活动状态标签:进行中(蓝色)、已结束(绿色)、未开始(橙色)
|
||||
- 人员筛选列显示参与人数,点击弹出人员筛选弹窗
|
||||
- 审核人员列显示审核人数,点击弹出审核人员明细弹窗
|
||||
- 操作列包含编辑和删除,删除需二次确认
|
||||
|
||||
**交互说明:**
|
||||
- 点击"新增计划"跳转到新增计划页面(plan-edit.html)
|
||||
- 点击"编辑"跳转到编辑计划页面,回填数据
|
||||
- 点击"删除"弹出确认提示后删除
|
||||
- 点击人员筛选数字打开人员筛选弹窗(展示指定/全部参与人员列表)
|
||||
- 点击审核人员数字打开审核人员弹窗
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 4-1. 新增/编辑计划(plan-edit.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 完整的干预计划创建/编辑表单页面,包含组织机构选择、人员筛选、审核人指定等功能。
|
||||
|
||||
**页面布局:** 表单卡片式布局
|
||||
- 页面标题区:动态显示"新增计划"或"编辑计划"
|
||||
- 表单区域:按照表单字段排列
|
||||
- 底部保存栏:固定在页面底部,包含取消和保存按钮
|
||||
|
||||
**表单字段:**
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验规则 |
|
||||
|--------|------|------|----------|
|
||||
| 活动组织机构 | 树形选择器 | 是 | 选择单位→部门的树形结构 |
|
||||
| 计划名称 | 文本输入框 | 是 | 不超过50字符 |
|
||||
| 人员筛选 | 单选按钮组 | 是 | "全体人员" / "指定人员" |
|
||||
| 指定部门 | 多选下拉 | 否 | 选择"指定人员"时显示,需先选组织机构 |
|
||||
| 指定员工 | 员工选择器 | 否 | 选择"指定人员"时显示,点击打开员工选择弹窗 |
|
||||
| 起止日期 | 日期范围选择 | 是 | 结束日期不得早于开始日期 |
|
||||
| 审核人员 | 搜索下拉 | 是 | 支持搜索姓名,可多选 |
|
||||
| 负责人 | 搜索下拉 | 是 | 支持搜索姓名,单选,选项含头像和手机号 |
|
||||
| 负责人电话 | 文本输入框 | 是 | 11位手机号格式 |
|
||||
| 活动说明 | 多行文本域 | 否 | 不超过500字符 |
|
||||
|
||||
**人员选择弹窗:**
|
||||
- 筛选条件:部门、姓名、工号
|
||||
- 表格列:复选框、姓名、工号、性别、年龄、单位、部门
|
||||
- 已选员工以可移除标签形式展示在表单中
|
||||
|
||||
**审核人员弹窗:**
|
||||
- 搜索输入框,支持按姓名实时搜索
|
||||
- 列表展示可选审核人员,含头像、姓名、职位信息
|
||||
- 支持多选,已选审核人以标签展示
|
||||
|
||||
**交互说明:**
|
||||
- 选择组织机构后启用部门多选和员工选择器
|
||||
- 人员筛选切换"全体人员"/"指定人员"时,展开/收起指定人员配置区
|
||||
- 已选员工以可移除标签列表展示
|
||||
- 底部保存栏固定,滚动表单时始终可见
|
||||
- 点击"取消"返回计划列表页
|
||||
- 编辑模式下通过URL参数(`?id=N`)回填表单数据
|
||||
- 审核人员和负责人搜索使用搜索下拉组件
|
||||
|
||||
---
|
||||
|
||||
## 三、报名管理
|
||||
|
||||
### 5. 报名跟踪(registration-tracking.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 跟踪干预计划中员工的报名和任务书提交流程,按不同的报名状态分Tab管理。
|
||||
|
||||
**干预计划选择区:** 搜索下拉选择干预计划,选中后右侧展示计划基本信息(组织机构、起止日期、进行天数、应参加人数、实际参加人数)
|
||||
|
||||
**Tab切换:** 未提交任务书 / 已提交待审核 / 已通过待签署 / 已签署(各Tab带数量角标)
|
||||
|
||||
**筛选条件:** 单位部门(二级联动下拉)、姓名(输入框)、员工编号(输入框)、体重(kg)(运算符+值输入,支持等于/大于/小于/大于等于/小于等于/范围)、BMI(运算符+值输入,同上)
|
||||
|
||||
**各Tab表格列:**
|
||||
|
||||
| Tab | 表格列 |
|
||||
|-----|-------|
|
||||
| 未提交任务书 | 计划名称、姓名、员工编号、性别、年龄、所属单位、所属部门、身高(cm)、初始体重(kg)、初始BMI、目标体重(kg)、目标BMI |
|
||||
| 已提交待审核 | 同上 + 提交时间 |
|
||||
| 已通过待签署 | 同上 + 通过时间、操作(承诺书) |
|
||||
| 已签署 | 同上 + 签署时间、操作(承诺书) |
|
||||
|
||||
**承诺书预览弹窗:**
|
||||
- 展示承诺书正文内容,含员工信息、目标体重、承诺事项
|
||||
- 包含承诺人签名和日期区域
|
||||
- 仅查看模式
|
||||
|
||||
**交互说明:**
|
||||
- 必须先选择干预计划才能查看数据,未选择时显示占位提示
|
||||
- Tab切换筛选不同状态的报名记录
|
||||
- 体重和BMI筛选支持自定义运算符(等于/大于/小于/大于等于/小于等于/范围),选择"范围"时展开第二个输入框
|
||||
- 承诺书弹窗展示完整的承诺书内容
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 6. 目标体重审核(target-weight-review.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 管理员审核员工提交的目标体重设置,支持通过/驳回操作,驳回时需填写原因。
|
||||
|
||||
**干预计划选择区:** 与报名跟踪页面结构一致
|
||||
|
||||
**Tab切换:** 待审核 / 已通过 / 已通过待签署 / 已驳回(各Tab带数量角标)
|
||||
|
||||
**筛选条件:** 单位部门(二级联动下拉)、姓名(输入框)、员工编号(输入框)、体重(kg)(运算符筛选)、BMI(运算符筛选)
|
||||
|
||||
**各Tab表格列:**
|
||||
|
||||
| Tab | 表格列 |
|
||||
|-----|-------|
|
||||
| 待审核 | 姓名、员工编号、性别、年龄、所属单位、所属部门、身高(cm)、初始体重(kg)、初始BMI、目标体重(kg)、目标BMI、目标体脂(%)、目标腰围(cm)、操作(审核) |
|
||||
| 已通过 | 同上 + 审核时间、审核人、操作(查看) |
|
||||
| 已通过待签署 | 同上 + 操作(承诺书) |
|
||||
| 已驳回 | 同上 + 驳回原因(hover展示完整文本)、驳回时间、操作(查看) |
|
||||
|
||||
**审核弹窗:**
|
||||
- 员工基本信息卡片(姓名、工号、单位、部门)
|
||||
- 指标对比表格:指标名称、干预前数值、箭头、目标数值、变化量(差值带颜色:达标绿色、未达标红色)
|
||||
- 对比指标:体重(kg)、BMI、体脂率(%)、腰围(cm)
|
||||
- 审核决策按钮组:通过 / 驳回(大按钮,点击选中高亮)
|
||||
- 选择"驳回"时展开驳回原因输入区:
|
||||
- 驳回原因文本域(必填,最多200字)
|
||||
|
||||
**承诺书预览弹窗:** 与报名跟踪中的承诺书弹窗结构一致
|
||||
|
||||
**业务规则:**
|
||||
- 指标对比表格中,变化量为正(增重)红色标注,变化量为负(减重)绿色标注
|
||||
- 目标体重不得低于健康体重下限(BMI 18.5对应体重)
|
||||
- 通过审核后员工状态变为"已通过",需员工签署承诺后变为"已签署"
|
||||
|
||||
**交互说明:**
|
||||
- 点击"审核"打开审核弹窗
|
||||
- 审核弹窗中通过/驳回按钮为互斥选择,选中后高亮显示
|
||||
- 选择驳回时必须填写驳回原因,否则无法提交
|
||||
- 查看模式下弹窗仅展示信息,无决策按钮
|
||||
- 承诺书预览弹窗展示完整承诺书
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
## 四、设备管理
|
||||
|
||||
### 7-10. 四种设备类型的设备管理与设备监测
|
||||
|
||||
体重管理模块支持四种设备类型:**体重秤、蓝牙称、体脂仪、运动器械**。每种设备类型均包含设备管理和设备监测两个子页面,功能结构完全一致。
|
||||
|
||||
#### 设备管理(以体重秤为例:scale-device.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 管理体重秤设备的增删改查,维护设备基础信息。
|
||||
|
||||
**筛选条件:** 设备编码(输入框)、设备名称(输入框)、所属单位(下拉)、设备厂商(下拉)、设备型号(下拉)、设备状态(下拉:全部/正常/维护中/已停用)
|
||||
|
||||
**表格列:** 设备编码、设备名称、设备厂商、设备型号、所属单位、布放位置、入库日期、设备状态(标签)、操作
|
||||
|
||||
**业务规则:**
|
||||
- 设备状态标签:正常(绿色)、维护中(橙色)、已停用(红色)
|
||||
- 操作列包含详情、编辑、删除
|
||||
|
||||
**交互说明:**
|
||||
- 点击"新增设备"打开新增设备弹窗
|
||||
- 点击"详情"查看设备详细信息
|
||||
- 点击"编辑"打开编辑弹窗,回填数据
|
||||
- 点击"删除"弹出确认提示后删除
|
||||
- 支持导出列表数据
|
||||
|
||||
**新增/编辑设备弹窗(scale-device-add.html、bluetooth-device-add.html、fat-device-add.html、sport-device-add.html):**
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验规则 |
|
||||
|--------|------|------|----------|
|
||||
| 设备编码 | 文本输入框 | 是 | 唯一性校验 |
|
||||
| 设备名称 | 文本输入框 | 是 | 不超过30字符 |
|
||||
| 设备厂商 | 下拉选择 | 是 | 预设厂商列表 |
|
||||
| 设备型号 | 文本输入框 | 是 | 不超过20字符 |
|
||||
| 所属单位 | 下拉选择 | 是 | 从组织机构选择 |
|
||||
| 布放位置 | 文本输入框 | 是 | 不超过50字符 |
|
||||
| 入库日期 | 日期选择 | 是 | 不得晚于当前日期 |
|
||||
|
||||
---
|
||||
|
||||
#### 设备监测(以体重秤为例:scale-monitor.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 实时展示体重秤设备的监测数据,监控设备运行状态。
|
||||
|
||||
**筛选条件:** 设备编码(输入框)、设备名称(输入框)、所属单位(下拉)、日期范围
|
||||
|
||||
**表格列(体重秤):** 设备编码、设备名称、所属单位、布放位置、最后测量时间、当日测量人次、当日异常次数、设备状态(标签)、操作
|
||||
|
||||
**监测详情弹窗(scale-monitor-detail.html、bluetooth-monitor-detail.html、fat-monitor-detail.html、sport-monitor-detail.html):**
|
||||
- 设备基本信息卡片
|
||||
- 当日测量统计(测量人次、异常次数)
|
||||
- 近期测量趋势折线图
|
||||
- 测量记录明细表格
|
||||
|
||||
**各设备类型监测指标差异:**
|
||||
|
||||
| 设备类型 | 监测指标 |
|
||||
|---------|---------|
|
||||
| 体重秤 | 测量时间、体重(kg)、BMI |
|
||||
| 蓝牙称 | 测量时间、体重(kg)、BMI、蓝牙连接状态 |
|
||||
| 体脂仪 | 测量时间、体重(kg)、BMI、体脂率(%)、肌肉量(kg)、水分率(%)、骨量(kg)、基础代谢(kcal) |
|
||||
| 运动器械 | 使用时间、运动类型、运动时长(分钟)、消耗热量(kcal)、运动距离(km) |
|
||||
|
||||
**交互说明:**
|
||||
- 点击"详情"打开监测详情弹窗
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 11. 员工监测数据(employee-data.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以员工为维度展示各设备采集的最新体重测量数据汇总。
|
||||
|
||||
**筛选条件:** 姓名(输入框)、员工编号(输入框)、单位部门(二级联动下拉)
|
||||
|
||||
**表格列(多级表头):**
|
||||
- 第一级:姓名、员工编号、性别、年龄、所属单位、所属部门、最新测量数据(6列)、操作
|
||||
- 最新测量数据子列:测量时间、测量设备、身高(cm)、体重(kg)、BMI、体脂肪率(%)
|
||||
|
||||
**业务规则:**
|
||||
- 异常BMI值红色标注(BMI<18.5或BMI≥28)
|
||||
- 测量设备显示设备类型名称(体重秤/蓝牙称/体脂仪)
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"测量记录"跳转到员工测量记录详情页(employee-data-record.html)
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 11-1. 测量记录(employee-data-record.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示单个员工在所有设备上的历史测量记录详情。
|
||||
|
||||
**页面布局:**
|
||||
- 员工信息卡片
|
||||
- 筛选条件:日期范围、测量设备类型(下拉)
|
||||
- BMI趋势折线图
|
||||
- 测量记录表格:测量时间、测量设备、身高(cm)、体重(kg)、BMI、体脂肪率(%)
|
||||
|
||||
**交互说明:**
|
||||
- 通过URL参数加载指定员工数据
|
||||
- 表格支持按时间和设备类型筛选
|
||||
- 返回按钮跳转回员工监测数据列表页
|
||||
|
||||
---
|
||||
|
||||
### 12. 单位数据统计(dept-stat.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以单位/部门为维度统计各类型设备的使用情况,包括入库数、使用人次等指标。
|
||||
|
||||
**Tab切换:** 二级单位 / 三级单位
|
||||
|
||||
**筛选条件:** 组织机构(二级联动下拉)、年份(下拉,默认当年)
|
||||
|
||||
**二级单位表格列(多级表头):**
|
||||
- 第一级:单位、总人数
|
||||
- 体重秤子列:入库数、使用人次、使用率
|
||||
- 蓝牙称子列:入库数、使用人次、使用率
|
||||
- 体脂仪子列:入库数、使用人次、使用率
|
||||
- 运动器械子列:入库数、使用人次、使用率
|
||||
|
||||
**三级单位表格列:** 在二级单位基础上增加"部门"列
|
||||
|
||||
**交互说明:**
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 13. 员工数据统计(employee-stat.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以员工为维度统计各类型设备的使用频次和最近使用时间。
|
||||
|
||||
**筛选条件:** 姓名(输入框)、员工编号(输入框)、单位部门(二级联动下拉)
|
||||
|
||||
**表格列:**
|
||||
- 姓名、员工编号、性别、年龄、所属单位、所属部门
|
||||
- 体重秤使用次数(可排序)
|
||||
- 蓝牙称使用次数(可排序)
|
||||
- 体脂仪使用次数(可排序)
|
||||
- 运动器械使用次数(可排序)
|
||||
- 最近使用时间
|
||||
|
||||
**交互说明:**
|
||||
- 各使用次数列支持点击表头排序
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
## 五、过程管理
|
||||
|
||||
> **说明:** 过程管理模块的所有页面均包含干预计划选择区,必须先选择干预计划才能查看数据。
|
||||
|
||||
### 14. 行动记录(action-record.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 监控干预计划中各单位/部门员工的体重管理行动执行情况,包括三测率(测体重、测体脂、测腰围的完成比例)。
|
||||
|
||||
**干预计划选择区:** 搜索下拉选择干预计划,选中后右侧展示计划基本信息
|
||||
|
||||
**页面布局:** 上半部分为单位统计汇总表,下半部分为个人详情抽屉
|
||||
|
||||
**单位统计表格列:** 单位、总人数、三测人数、三测率(进度条)
|
||||
|
||||
**业务规则:**
|
||||
- 三测率 = 三测人数 / 总人数 × 100%
|
||||
- 三测率以进度条形式展示,低于60%标红
|
||||
|
||||
**交互说明:**
|
||||
- 点击单位行展开该单位的员工详情列表(抽屉弹窗)
|
||||
- 员工详情抽屉内含:
|
||||
- 员工基本信息
|
||||
- 体重变化趋势图(SVG折线图,含目标线)
|
||||
- 饮食记录入口(点击打开饮食记录弹窗)
|
||||
- 运动记录入口(点击打开运动记录弹窗)
|
||||
- 最新指标数据网格(体重、BMI、体脂率、腰围及其变化量)
|
||||
|
||||
**饮食记录弹窗:**
|
||||
- 筛选:日期范围
|
||||
- 表格列:序号、餐次(早餐/午餐/晚餐/加餐)、摄入时间、总摄入(千卡)、数据来源(手动/自动识别)
|
||||
- 餐饮明细可展开
|
||||
|
||||
**运动记录弹窗:**
|
||||
- 筛选:日期范围
|
||||
- 表格列:日期、运动类型、开始时间、结束时间、运动时长(分钟)、消耗热量(千卡)、数据来源
|
||||
|
||||
---
|
||||
|
||||
### 15. 详细数据(detail-data.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以员工为维度展示干预计划中每个人的详细体重指标数据和变化情况。
|
||||
|
||||
**干预计划选择区:** 标准结构
|
||||
|
||||
**筛选条件:** 日期范围(默认当日)、单位部门(二级联动下拉)、姓名(输入框)、员工编号(输入框)
|
||||
|
||||
**表格列:** 姓名、员工编号、性别、年龄、所属单位、所属部门、身高(cm)、初始体重(kg)、初始BMI、最近体重(kg)、最近BMI、体重变化(kg)、BMI变化、三测状态(标签)、操作
|
||||
|
||||
**业务规则:**
|
||||
- 体重变化正值红色(增重),负值绿色(减重)
|
||||
- 三测状态标签:已完成(绿色)、部分完成(橙色)、未完成(红色)
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"详情"打开个人指标详情抽屉弹窗
|
||||
- 弹窗内含体重趋势折线图(含初始值线和目标值线)、指标变化明细表
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 16. 体重排名(weight-ranking.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 在干预计划内按单位或员工进行体重管理效果的排名对比。
|
||||
|
||||
**干预计划选择区:** 标准结构
|
||||
|
||||
**Tab切换(分段控件):** 二级单位 / 三级单位
|
||||
|
||||
**筛选条件:** 单位多选下拉(支持全选/清空)
|
||||
|
||||
**二级单位排名表格列:**
|
||||
- 排名、单位、总人数
|
||||
- 人均减重量(kg)、人均减重率(%)、人均BMI降低
|
||||
- 排名变化(↑/↓/-,带颜色)
|
||||
- 三测率(%)
|
||||
|
||||
**三级单位排名表格列:** 在二级单位基础上增加"部门"列
|
||||
|
||||
**业务规则:**
|
||||
- 排名变化列:排名上升绿色↑、排名下降红色↓、不变灰色-
|
||||
- 排名支持按各指标列排序(点击表头)
|
||||
- 表格较宽,排名靠后的列(变化、三测率等)做右侧sticky固定
|
||||
|
||||
**交互说明:**
|
||||
- 点击表头排序列切换升降序
|
||||
- 排名变化hover展示上次排名数值
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 17. 活动总览(action-overview.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以活动为维度展示干预计划下所有体重管理活动的参与情况和效果汇总。
|
||||
|
||||
**干预计划选择区:** 标准结构
|
||||
|
||||
**筛选条件:** 单位部门(二级联动下拉)、姓名(输入框)、员工编号(输入框)
|
||||
|
||||
**表格列:** 活动名称、活动类型、活动日期、组织单位、应参加人数、实际参加人数、参与率、活动得分、操作
|
||||
|
||||
**业务规则:**
|
||||
- 参与率 = 实际参加人数 / 应参加人数 × 100%
|
||||
- 活动类型含体重测量、体脂检测、腰围测量、饮食指导、运动指导等
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"详情"跳转到活动详情页(action-overview-detail.html)
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 17-1. 活动详情(action-overview-detail.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示单个活动的完整详情,包括参与人员列表和活动效果数据。
|
||||
|
||||
**页面布局:**
|
||||
- 面包屑导航
|
||||
- 活动信息卡片(活动名称、类型、日期、组织单位、负责人等)
|
||||
- Tab切换:已参加人员 / 未参加人员(带数量角标)
|
||||
|
||||
**已参加人员Tab表格列:** 姓名、员工编号、性别、年龄、所属单位、所属部门、参与时间、测量数据(体重变化/BMI变化)、活动得分
|
||||
|
||||
**未参加人员Tab表格列:** 姓名、员工编号、性别、年龄、所属单位、所属部门
|
||||
|
||||
**交互说明:**
|
||||
- 面包屑导航支持返回活动总览
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 18. 活动统计(activity-statistics.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以单位维度汇总干预计划中各项活动的统计数据。
|
||||
|
||||
**干预计划选择区:** 标准结构
|
||||
|
||||
**筛选条件:** 单位多选下拉(支持全选/清空)
|
||||
|
||||
**表格列:** 干预计划、组织机构、起止日期、应参加人数、已参加人数、未参加人数、人均初始体重(kg)、人均初始BMI、人均现体重(kg)、人均现BMI、人均减重量(kg)、人均减重率(%)、人均综合得分、人均行动得分
|
||||
|
||||
**业务规则:**
|
||||
- 减重量正值绿色(减重),负值红色(增重)
|
||||
|
||||
**交互说明:**
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 19. 食谱记录(recipe-record.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示干预计划中各食堂/供餐点的餐品供应记录,辅助管理人员了解员工的饮食营养摄入情况。
|
||||
|
||||
**干预计划选择区:** 标准结构
|
||||
|
||||
**筛选条件:** 单位(下拉)、日期范围
|
||||
|
||||
**表格列:** 单位、食堂名称、用餐日期、餐次(早餐/午餐/晚餐/加餐)、餐品名称
|
||||
|
||||
**餐品详情弹窗:**
|
||||
- 餐品基本信息:名称、类别、份量
|
||||
- 营养成分表:热量(kcal)、蛋白质(g)、脂肪(g)、碳水化合物(g)、膳食纤维(g)、钠(mg)
|
||||
- 食材清单
|
||||
|
||||
**交互说明:**
|
||||
- 点击餐品名称打开餐品详情弹窗
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
## 六、历史干预
|
||||
|
||||
### 20. 历史计划列表(history-plan-list.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 查看所有已结束的历史干预计划及其执行效果统计数据。
|
||||
|
||||
**筛选条件:** 组织机构(二级联动下拉)、年份(下拉:全部年份/2026/2025/2024)
|
||||
|
||||
**表格列:** 干预计划、组织机构、组织部门、起止日期、人员范围(可点击查看)、应参加人数、已参加人数、未参加人数、完成体重目标人数、完成体脂目标人数、完成腰围目标人数、人均减重(kg)、人均减重率(%)、人均腰围减少(cm)、人均每日摄入(千卡)、人均每日消耗(千卡)、人均每日热量缺口、人均综合得分、人均行动得分、负责人
|
||||
|
||||
**业务规则:**
|
||||
- 表格列较多,前几列(干预计划、组织机构等)做左侧固定,支持横向滚动
|
||||
- 人员范围列显示人数,可点击查看人员明细
|
||||
|
||||
**交互说明:**
|
||||
- 点击"人员范围"数字打开人员范围弹窗(展示参与人员列表)
|
||||
- 操作列点击"详情"跳转到计划详情页(history-plan-detail.html)
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
**人员范围弹窗:**
|
||||
- 标题:人员范围 - **计划名称**
|
||||
- 表格列:姓名、员工编号、性别、年龄、所属单位、所属部门、初始体重(kg)、最终体重(kg)、体重变化(kg)、是否完成目标
|
||||
|
||||
---
|
||||
|
||||
### 20-1. 历史计划详情(history-plan-detail.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示历史干预计划的完整详情和效果分析数据。
|
||||
|
||||
**页面布局:**
|
||||
- 面包屑导航:体重管理 / 历史干预 / 计划列表 / **计划名称**
|
||||
- 计划信息卡片:计划名称、组织机构、起止日期、负责人、审核人等
|
||||
- 效果统计汇总卡片组:应参加/已参加/未参加人数、完成目标人数、人均减重、人均BMI降低等
|
||||
- 人员明细表格
|
||||
|
||||
**交互说明:**
|
||||
- 面包屑支持返回历史计划列表
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 21. 历史人员列表(history-personnel-list.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 以员工为维度查看所有参与过历史干预计划的人员及其干预效果数据。
|
||||
|
||||
**筛选条件:** 组织机构(二级联动下拉)、姓名(输入框)、员工编号(输入框)、年份(下拉)
|
||||
|
||||
**表格列:** 姓名、员工编号、性别、年龄、所属单位、所属部门、参与计划数、初始体重(kg)、最终体重(kg)、体重变化(kg)、BMI变化、体脂率变化(%)、腰围变化(cm)、操作
|
||||
|
||||
**业务规则:**
|
||||
- 体重变化负值绿色(减重),正值红色(增重)
|
||||
- 参与计划数列点击可展开查看参与的计划明细
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"详情"跳转到人员详情页(history-personnel-detail.html)
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 21-1. 历史人员详情(history-personnel-detail.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示单个员工在所有历史干预计划中的完整数据和效果变化。
|
||||
|
||||
**页面布局:**
|
||||
- 面包屑导航
|
||||
- 员工信息卡片
|
||||
- 参与历史时间轴
|
||||
- 体重变化趋势图(跨多次干预计划)
|
||||
- 各干预计划效果明细表格
|
||||
|
||||
**交互说明:**
|
||||
- 面包屑支持返回历史人员列表
|
||||
- 趋势图展示跨计划周期的体重变化
|
||||
|
||||
---
|
||||
|
||||
## 七、个人体重管理
|
||||
|
||||
### 22. 个人目标数据(personal-target-data.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示所有员工的个人体重管理目标设置情况和目标达成进度。
|
||||
|
||||
**筛选条件:** 姓名(输入框)、员工编号(输入框)、单位部门(二级联动下拉)
|
||||
|
||||
**表格列(多级表头):**
|
||||
- 第一级:姓名、员工编号、性别、年龄、所属单位、所属部门
|
||||
- 目标信息子列:目标起止日期、目标天数
|
||||
- 初始指标子列:身高(cm)、初始体重(kg)、初始BMI、初始体脂(%)、初始腰围(cm)
|
||||
- 目标指标子列:目标体重(kg)、目标BMI、目标体脂(%)、目标腰围(cm)
|
||||
- 当前指标子列:现体重(kg)、现BMI、现体脂(%)、现腰围(cm)
|
||||
- 变化子列:体重变化(kg)、BMI变化、体脂变化(%)、腰围变化(cm)
|
||||
- 操作列:详细数据
|
||||
|
||||
**业务规则:**
|
||||
- 变化列负值绿色(改善),正值红色(恶化)
|
||||
- 未设置目标的员工,目标周期和目标指标列显示"-",仅当前指标有实际称重数据
|
||||
- 姓名和员工编号列做左侧固定
|
||||
- 详细数据列做右侧固定
|
||||
|
||||
**交互说明:**
|
||||
- 操作列点击"查看"跳转到个人目标详情页(personal-target-detail.html),通过URL参数传递员工ID
|
||||
- 支持导出列表数据
|
||||
|
||||
---
|
||||
|
||||
### 22-1. 个人目标详情(personal-target-detail.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示单个员工的目标设定详情、达成进度和历史称重记录。
|
||||
|
||||
**页面布局:**
|
||||
- 面包屑导航
|
||||
- 员工信息卡片
|
||||
- 目标达成进度卡片(目标体重 vs 实际体重、达成率百分比)
|
||||
- 体重变化趋势图(SVG折线图,含目标线)
|
||||
- 历史称重记录表格:测量时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、测量设备
|
||||
|
||||
**交互说明:**
|
||||
- 返回按钮跳转回个人目标数据列表页
|
||||
- 趋势图以SVG动态渲染
|
||||
|
||||
---
|
||||
|
||||
## 八、异常事件
|
||||
|
||||
### 23. 系统脏数据(exception-system-dirty.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 展示体重管理模块中检测到的脏数据记录,供管理员排查和清理。
|
||||
|
||||
**筛选条件:** 业务模块(下拉)、异常类型(下拉)、年份(下拉)
|
||||
|
||||
**表格列:** 业务模块(标签)、异常类型(标签)、异常描述、异常检测时间、表名、字段名、编号、异常值(高亮显示)、数据产生时间
|
||||
|
||||
**业务规则:**
|
||||
- 异常值以红色/橙色高亮显示
|
||||
- 操作栏显示"共N条脏数据"统计
|
||||
|
||||
**交互说明:**
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
|
||||
### 24. 结果异常(exception-result-abnormal.html)
|
||||
|
||||

|
||||
|
||||
**功能说明:** 汇总展示体重管理各业务模块产生的结果异常数据,支持多维度筛选。
|
||||
|
||||
**筛选条件:** 员工名称(输入框)、员工编号(输入框)、单位部门(下拉)、业务模块(下拉:全部/体重管理)、年份(下拉)
|
||||
|
||||
**表格列:** 员工、员工编号、单位名称、部门名称、业务模块、异常指标、异常值、异常等级(标签)、异常时间
|
||||
|
||||
**业务规则:**
|
||||
- 异常等级标签:严重(红色)、中等(橙色)、轻微(绿色)
|
||||
- 异常指标含体重异常波动、BMI异常变化、设备数据异常等
|
||||
|
||||
**交互说明:**
|
||||
- 支持导出列表数据、查看导出任务
|
||||
|
||||
---
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Web管理端 - 体重管理模块页面截图脚本
|
||||
* 使用 Playwright 批量截取 HTML 原型页面,输出 JPEG 到 doc/screenshots/
|
||||
*
|
||||
* 截图约束(参考项目背景.md §7.4):
|
||||
* - Web端视口 1440×900,1x 缩放,全页截图
|
||||
* - JPEG 质量 85%
|
||||
* - 目标体积 ≤ 80KB
|
||||
*
|
||||
* 用法: node doc/screenshot.js
|
||||
*/
|
||||
const { chromium } = require('playwright');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const MODULE_DIR = __dirname.replace(/\\doc$/, ''); // weight-management/
|
||||
const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
|
||||
|
||||
// 主页面列表(排除内嵌弹窗文件,弹窗无需单独截图)
|
||||
const PAGES = [
|
||||
// ===== 档案 =====
|
||||
{ file: 'archive-personal-weight-data.html', desc: '个人体重数据列表' },
|
||||
{ file: 'archive-personal-weight-detail.html', desc: '个人体重详情' },
|
||||
{ file: 'archive-unit-report.html', desc: '单位报表数据列表' },
|
||||
{ file: 'archive-unit-report-detail.html', desc: '单位报表详情' },
|
||||
{ file: 'archive-algorithm-model.html', desc: '算法模型(占位)' },
|
||||
|
||||
// ===== 计划管理 =====
|
||||
{ file: 'plan-list.html', desc: '计划列表' },
|
||||
{ file: 'plan-edit.html', desc: '新增/编辑计划' },
|
||||
|
||||
// ===== 报名管理 =====
|
||||
{ file: 'registration-tracking.html', desc: '报名跟踪' },
|
||||
{ file: 'target-weight-review.html', desc: '目标体重审核' },
|
||||
|
||||
// ===== 设备管理 =====
|
||||
{ file: 'scale-device.html', desc: '体重秤-设备管理' },
|
||||
{ file: 'scale-monitor.html', desc: '体重秤-设备监测' },
|
||||
{ file: 'bluetooth-device.html', desc: '蓝牙称-设备管理' },
|
||||
{ file: 'bluetooth-monitor.html', desc: '蓝牙称-设备监测' },
|
||||
{ file: 'fat-device.html', desc: '体脂仪-设备管理' },
|
||||
{ file: 'fat-monitor.html', desc: '体脂仪-设备监测' },
|
||||
{ file: 'sport-device.html', desc: '运动器械-设备管理' },
|
||||
{ file: 'sport-monitor.html', desc: '运动器械-设备监测' },
|
||||
{ file: 'employee-data.html', desc: '员工监测数据' },
|
||||
{ file: 'employee-data-record.html', desc: '测量记录详情' },
|
||||
{ file: 'dept-stat.html', desc: '单位数据统计' },
|
||||
{ file: 'employee-stat.html', desc: '员工数据统计' },
|
||||
|
||||
// ===== 过程管理 =====
|
||||
{ file: 'action-record.html', desc: '行动记录' },
|
||||
{ file: 'detail-data.html', desc: '详细数据' },
|
||||
{ file: 'weight-ranking.html', desc: '体重排名' },
|
||||
{ file: 'action-overview.html', desc: '活动总览' },
|
||||
{ file: 'action-overview-detail.html', desc: '活动详情' },
|
||||
{ file: 'activity-statistics.html', desc: '活动统计' },
|
||||
{ file: 'recipe-record.html', desc: '食谱记录' },
|
||||
|
||||
// ===== 历史干预 =====
|
||||
{ file: 'history-plan-list.html', desc: '历史计划列表' },
|
||||
{ file: 'history-plan-detail.html', desc: '历史计划详情' },
|
||||
{ file: 'history-personnel-list.html', desc: '历史人员列表' },
|
||||
{ file: 'history-personnel-detail.html', desc: '历史人员详情' },
|
||||
|
||||
// ===== 个人体重管理 =====
|
||||
{ file: 'personal-target-data.html', desc: '个人目标数据列表' },
|
||||
{ file: 'personal-target-detail.html', desc: '个人目标详情' },
|
||||
|
||||
// ===== 异常事件 =====
|
||||
{ file: 'exception-system-dirty.html', desc: '系统脏数据' },
|
||||
{ file: 'exception-result-abnormal.html', desc: '结果异常' },
|
||||
];
|
||||
|
||||
function getOutputName(file) {
|
||||
return file.replace('.html', '.jpg');
|
||||
}
|
||||
|
||||
async function screenshotPage(browser, entry, index, total) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1440, height: 900 },
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const filePath = path.join(MODULE_DIR, entry.file);
|
||||
const fileUrl = `file:///${filePath.replace(/\\/g, '/')}`;
|
||||
|
||||
try {
|
||||
await page.goto(fileUrl, { waitUntil: 'networkidle', timeout: 15000 });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// 隐藏所有弹窗遮罩、下拉面板、toast等
|
||||
await page.addStyleTag({
|
||||
content: `
|
||||
.modal-overlay, .modal-mask, .dialog-overlay,
|
||||
.dropdown-panel, .dropdown-menu, .select-panel,
|
||||
.search-select__panel, .org-select-panel, .org-panel,
|
||||
.multi-select__panel, .tree-select__panel,
|
||||
.plan-selector__panel, .toast,
|
||||
.calendar-picker, .date-picker-panel {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
const outName = getOutputName(entry.file);
|
||||
const outPath = path.join(SCREENSHOTS_DIR, outName);
|
||||
|
||||
// 获取页面实际内容尺寸
|
||||
const bodySize = await page.evaluate(() => {
|
||||
const main = document.querySelector('.main');
|
||||
const body = document.body;
|
||||
return {
|
||||
scrollWidth: Math.max(body.scrollWidth, main ? main.scrollWidth + 240 : 0),
|
||||
scrollHeight: Math.max(body.scrollHeight, main ? main.scrollHeight + 56 : 0),
|
||||
};
|
||||
});
|
||||
|
||||
const contentHeight = Math.max(bodySize.scrollHeight || 900, 600);
|
||||
|
||||
// 根据页面高度动态调整质量,控制体积 ≤ 80KB
|
||||
// 1440×900 复杂表格页面 q75 约 100-140KB,需降到 q65 才能接近 80KB
|
||||
let jpegQuality;
|
||||
if (contentHeight > 3000) jpegQuality = 55;
|
||||
else if (contentHeight > 1800) jpegQuality = 60;
|
||||
else if (contentHeight > 1200) jpegQuality = 65;
|
||||
else jpegQuality = 65; // 900px 高度统一 65%,复杂表格页面约 65-85KB
|
||||
|
||||
await page.screenshot({
|
||||
path: outPath,
|
||||
type: 'jpeg',
|
||||
quality: jpegQuality,
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
// 检查文件大小
|
||||
const stats = fs.statSync(outPath);
|
||||
const sizeKB = (stats.size / 1024).toFixed(1);
|
||||
|
||||
const label = `[${String(index + 1).padStart(2, '0')}/${total}]`;
|
||||
console.log(` ${label} ${entry.desc} → ${outName} (${sizeKB}KB, h${contentHeight}, q${jpegQuality})`);
|
||||
} catch (err) {
|
||||
console.error(` ${'✗'.padStart(9)} ${entry.file}: ${err.message}`);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
console.log('Web管理端 - 体重管理模块 页面截图');
|
||||
console.log(`模块目录: ${MODULE_DIR}`);
|
||||
console.log(`输出目录: ${SCREENSHOTS_DIR}`);
|
||||
console.log(`视口: 1440×900, 1x`);
|
||||
console.log(`共 ${PAGES.length} 个页面\n`);
|
||||
|
||||
if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
||||
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
|
||||
try {
|
||||
for (let i = 0; i < PAGES.length; i++) {
|
||||
await screenshotPage(browser, PAGES[i], i, PAGES.length);
|
||||
}
|
||||
console.log(`\n✅ 全部完成!共 ${PAGES.length} 张截图`);
|
||||
console.log(` 输出目录: ${SCREENSHOTS_DIR}`);
|
||||
} catch (err) {
|
||||
console.error('执行出错:', err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
})();
|
||||
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 54 KiB |