feat: 新增员工端应用主页与个人中心,优化应急三端页面

- 新增 employee-app/home/index.html 应用主页(功能按钮收起展开、体重管理、健康数据、智能监测24h曲线、膳食摄入)
- 新增 employee-app/profile/ 个人中心模块(16页:个人资料、头像、密码、手机号、紧急联系人、设置等)
- 修复 health-consult/index.html 返回导航逻辑
- 优化应急三端(驻场/操作/专业人员端)页面交互与截图
- 新增操作人员端、专业人员端 PRD 文档
- 更新 SITE_MAP 导航页
- 新增截图工具脚本(gen_screenshots.py、screenshot.js)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-29 09:55:33 +08:00
co-authored by Claude Opus 4.6
parent f8f710ad43
commit b8382c862f
40 changed files with 3026 additions and 234 deletions
+3 -20
View File
@@ -41,6 +41,7 @@ stateDiagram-v2
stateDiagram-v2
[*] --> 待处理 : 操作人员确认预约并派单
待处理 --> 已完成 : 驻场人员提交挂号结果
待处理 --> 待处理 : 驻场人员转单给其他驻场人员
```
## 页面清单
@@ -219,36 +220,18 @@ stateDiagram-v2
### 7. 知识库首页
![页面截图](screenshots/knowledge-index.jpg)
**功能说明:** 分类展示知识文章和规章制度。
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
**业务规则:**
- 一级 Tab:知识文章(默认)/ 规章制度
- 知识文章面板:二级 Tab 分类(调度流程、急救知识、沟通技巧),每个分类下展示知识卡片
- 规章制度面板:展示 PDF 文件列表
- 知识卡片展示:图标、标题、简要描述
**交互说明:**
- 点击一级 Tab:切换知识文章/规章制度面板
- 点击二级 Tab:切换知识分类
- 点击知识卡片:跳转到知识详情页
---
### 8. 知识详情
![页面截图](screenshots/knowledge-article-detail.jpg)
**功能说明:** 展示单篇知识文章的完整内容,根据 URL 参数动态渲染。
**业务规则**
- 通过 URL 参数 `?key=xxx` 获取知识条目标识
- 页面动态渲染:标题、分类标签、更新日期、概述、主要内容列表
- 内置 10 个知识条目数据
**功能说明** 等待后续规划,提示“功能开发中,敬请期待~”
**交互说明:**
- 点击返回:返回知识库首页
---
@@ -46,7 +46,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.modal-center.active { display: flex; }
.modal-center.modal-bottom { align-items: flex-end; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.transfer-modal { width: calc(100% - 24px); max-width: 366px; height: 88vh; max-height: 88vh; border-radius: 20px 20px 0 0; overflow: visible; padding-bottom: 6px; display: flex; flex-direction: column; }
.transfer-modal { width: calc(100% - 24px); max-width: 366px; height: 50vh; max-height: 50vh; border-radius: 20px 20px 0 0; overflow: visible; padding-bottom: 6px; display: flex; flex-direction: column; }
.person-select { position: relative; padding: 0 16px; }
.person-select__input-wrap { position: relative; }
.person-select__input { width: 100%; height: 40px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 38px 0 12px; font-size: 14px; outline: none; color: #1a1a1a; background: #fff; }
@@ -258,10 +258,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="person-info__row"><span class="person-info__label">联系方式</span><span id="info-phone" class="person-info__value">-</span></div>
<div class="person-info__row"><span class="person-info__label">当前状态</span><span id="info-status" class="person-info__value">-</span></div>
</div>
<div class="transfer-note">
<div class="transfer-note__label"><span class="required">*</span>转单说明</div>
<textarea class="transfer-note__textarea" placeholder="请输入转单原因或补充说明,如:当前医院距离较远,转交附近驻场人员接单处理"></textarea>
</div>
</div>
<div style="display:flex;border-top:1px solid #f0f0f0;margin-top:0;flex-shrink:0;background:#fff;">
<button onclick="closeTransferModal()" style="flex:1;padding:14px;font-size:16px;cursor:pointer;background:none;border:none;color:#666;border-right:1px solid #f0f0f0;">取消</button>
@@ -48,7 +48,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.modal-center.active { display: flex; }
.modal-center.modal-bottom { align-items: flex-end; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.transfer-modal { width: calc(100% - 24px); max-width: 366px; height: 88vh; max-height: 88vh; border-radius: 20px 20px 0 0; overflow: visible; padding-bottom: 6px; display: flex; flex-direction: column; }
.transfer-modal { width: calc(100% - 24px); max-width: 366px; height: 50vh; max-height: 50vh; border-radius: 20px 20px 0 0; overflow: visible; padding-bottom: 6px; display: flex; flex-direction: column; }
.person-select { position: relative; padding: 0 16px; }
.person-select__input-wrap { position: relative; }
.person-select__input { width: 100%; height: 40px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 38px 0 12px; font-size: 14px; outline: none; color: #1a1a1a; background: #fff; }
@@ -141,6 +141,14 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="desc-block__label">救助结果:</div>
<div class="desc-block__text" style="color:#999;">暂未填写</div>
</div>
<div class="desc-block">
<div class="desc-block__label">相关照片:</div>
<div class="desc-block__text" style="color:#999;">暂未上传</div>
</div>
<div class="desc-block">
<div class="desc-block__label">补充小结:</div>
<div class="desc-block__text" style="color:#999;">暂未填写</div>
</div>
</div>
</div>
@@ -252,10 +260,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="person-info__row"><span class="person-info__label">联系方式</span><span id="info-phone" class="person-info__value">-</span></div>
<div class="person-info__row"><span class="person-info__label">当前状态</span><span id="info-status" class="person-info__value">-</span></div>
</div>
<div class="transfer-note">
<div class="transfer-note__label"><span class="required">*</span>转单说明</div>
<textarea class="transfer-note__textarea" placeholder="请输入转单原因或补充说明,如:当前现场距离较远,转交附近驻场人员前往处理"></textarea>
</div>
</div>
<div style="display:flex;border-top:1px solid #f0f0f0;margin-top:0;flex-shrink:0;background:#fff;">
<button onclick="closeTransferModal()" style="flex:1;padding:14px;font-size:16px;cursor:pointer;background:none;border:none;color:#666;border-right:1px solid #f0f0f0;">取消</button>
@@ -36,6 +36,9 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.desc-block__text { font-size: 13px; color: #333; line-height: 1.8; }
.photo-list { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
.photo-item { width: 90px; height: 68px; border-radius: 8px; background: linear-gradient(135deg, #e6f7ff, #bae7ff); border: 1px solid #d9d9d9; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #1677ff; }
.summary-list { display: flex; flex-direction: column; gap: 8px; }
.summary-item { font-size: 13px; color: #333; line-height: 1.8; padding: 8px 12px; background: #f7f8fa; border-radius: 8px; position: relative; padding-left: 22px; }
.summary-item::before { content: ''; position: absolute; left: 10px; top: 14px; width: 5px; height: 5px; border-radius: 50%; background: #1677ff; }
</style>
</head>
<body>
@@ -87,6 +90,21 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="desc-block__label">救助结果:</div>
<div class="desc-block__text">员工已送达长庆油田职工医院急诊科,完成头颅CT检查,排除脑出血,诊断为高血压引起的眩晕,已开具降压药物并留观。</div>
</div>
<div class="desc-block">
<div class="desc-block__label">相关照片:</div>
<div class="photo-list">
<div class="photo-item">照片 1</div>
<div class="photo-item">照片 2</div>
</div>
</div>
<div class="desc-block">
<div class="desc-block__label">补充小结:</div>
<div class="summary-list">
<div class="summary-item">患者血压已从180/110mmHg降至145/90mmHg,头晕症状明显缓解,意识清醒。</div>
<div class="summary-item">已与家属沟通病情,建议留院观察24小时,家属表示同意。</div>
<div class="summary-item">后续需定期监测血压,按时服用降压药物,一周后门诊复查。</div>
</div>
</div>
</div>
</div>
+527
View File
@@ -0,0 +1,527 @@
# 操作人员端(健康应急APP)产品需求文档
## 模块概述
操作人员端为应急调度中心的操作人员提供移动端工作平台,负责接收员工应急求助(视频/电话/120),在线实时沟通调度,协调专业人员、驻场人员、救护车等资源完成应急救助闭环。同时承担大病就医预约的确认、派单和取消管理。操作人员是整个应急流程的核心调度枢纽。
### 页面导航结构
```
操作人员端
├── 工作台(底部Tab
│ ├── 应急求助
│ │ ├── 进行中 → 应急聊天(进行中)
│ │ │ ├── 驻场派单
│ │ │ ├── 救护车支援
│ │ │ ├── 救助信息汇总
│ │ │ ├── 患者定位地图
│ │ │ └── 工单详情(进行中)
│ │ └── 已完成 → 应急聊天(已结束)
│ │ └── 工单详情(已完成)
│ └── 大病就医
│ ├── 待确认 → 大病就医详情(待确认)
│ ├── 已确认 → 大病就医详情(已确认)
│ ├── 已完成 → 大病就医详情(已完成)
│ └── 已取消 → 大病就医详情(已取消)
├── 知识库(底部Tab
│ ├── 知识文章(分类Tab)→ 知识详情
│ └── 规章制度(PDF列表)
├── AI助手(底部Tab
├── 消息(底部Tab
└── 我的(底部Tab
├── 值班信息 → 排班配置
├── 修改个人信息
├── 修改密码
└── 退出登录(确认弹窗)
```
### 应急工单状态流转图
```mermaid
stateDiagram-v2
[*] --> 进行中 : 未结束的应急工单
进行中 --> 已结束 : 员工/操作人员/专业人员任意一方结束应急
```
### 大病就医状态流转图
```mermaid
stateDiagram-v2
[*] --> 待确认 : 员工发起大病就医预约
待确认 --> 已确认 : 操作人员确认预约并派单
待确认 --> 已取消 : 操作人员/员工取消预约
已确认 --> 已完成 : 驻场人员提交挂号结果
已确认 --> 已取消 : 操作人员/员工取消预约
```
## 页面清单
| 序号 | 文件名 | 页面名称 | 所属模块 |
|------|--------|----------|----------|
| 1 | workbench/index.html | 工作台首页 | 工作台 |
| 2 | workbench/emergency-chat.html | 应急聊天(进行中) | 工作台·应急求助 |
| 3 | workbench/emergency-chat-done.html | 应急聊天(已结束) | 工作台·应急求助 |
| 4 | workbench/dispatch-onsite.html | 驻场派单 | 工作台·应急求助 |
| 5 | workbench/dispatch-ambulance.html | 救护车支援 | 工作台·应急求助 |
| 6 | workbench/rescue-summary.html | 救助信息汇总 | 工作台·应急求助 |
| 7 | workbench/patient-location.html | 患者定位地图 | 工作台·应急求助 |
| 8 | workbench/work-order-detail-ongoing.html | 工单详情(进行中) | 工作台·应急求助 |
| 9 | workbench/work-order-detail.html | 工单详情(已完成) | 工作台·应急求助 |
| 10 | workbench/serious-illness-list.html | 大病就医列表 | 工作台·大病就医 |
| 11 | workbench/serious-illness-detail-pending.html | 大病就医详情(待确认) | 工作台·大病就医 |
| 12 | workbench/serious-illness-detail-confirmed.html | 大病就医详情(已确认) | 工作台·大病就医 |
| 13 | workbench/serious-illness-detail-done.html | 大病就医详情(已完成) | 工作台·大病就医 |
| 14 | workbench/serious-illness-detail-cancelled.html | 大病就医详情(已取消) | 工作台·大病就医 |
| 15 | knowledge/index.html | 知识库首页 | 知识库 |
| 16 | knowledge/article-detail.html | 知识详情 | 知识库 |
| 17 | ../ai-assistant/index.html | AI 助手 | AI 助手 |
| 18 | ../message/index.html | 消息列表 | 消息 |
| 19 | profile/index.html | 个人中心主页 | 我的 |
| 20 | profile/edit-profile.html | 修改个人信息 | 我的 |
| 21 | profile/change-pwd.html | 修改密码 | 我的 |
| 22 | profile/duty-schedule.html | 值班信息 | 我的 |
| 23 | profile/duty-config.html | 排班配置 | 我的 |
## 逐页说明
---
## 一、工作台模块
### 1. 工作台首页
![页面截图](screenshots/index.jpg)
**功能说明:** 操作人员工作台入口,展示应急求助和大病就医两大业务的任务队列,按状态分类管理。
**业务规则:**
- 顶部统计卡片展示:应急进行中数量、大病待确认数量
- 一级 Tab 切换:应急求助(默认)/ 大病就医
- 应急求助面板:二级 Tab 切换进行中 / 已完成
- 大病就医面板:跳转至大病就医列表页
- 应急卡片展示:患者姓名、年龄、求助方式标签、求助时间
- 应急求助方式分为“拨打120”、“应急视频”、“应急电话”、“线下应急”四种,“线下应急”为操作人员手动录入工单方式。
- 已完成卡片额外展示完成时间
**交互说明:**
- 点击进行中卡片:求助方式如果是“应急视频”则跳转到应急聊天(进行中)页,其余跳转到工单详情(进行中)页
- 点击已结束卡片:求助方式如果是“应急视频”则跳转到应急聊天(已结束)页, 其余跳转到工单详情(已完成)页
- 点击大病就医 Tab:跳转到大病就医列表页
- 底部导航栏 5 个 Tab:工作台、知识库、AI 助手、消息、我的
---
## 二、应急求助业务线
### 2. 应急聊天(进行中)
![页面截图](screenshots/emergency-chat.jpg)
**功能说明:** 操作人员与患者、专业人员的实时三方聊天界面,同时提供调度操作入口(驻场派单、救护车支援、救助信息、患者定位)。
**业务规则:**
- 患者信息条(可折叠)展示:求助时间、患者姓名、年龄、所属单位、联系电话、求助方式、查看工单入口
- 聊天区域展示三方消息:患者(左侧气泡)、操作人员(右侧气泡)、专业人员(左侧色气泡)
- 救助意见以绿色卡片形式展示在聊天区
- 操作按钮栏固定在输入区上方,提供 4 个调度快捷入口
- 输入区支持语音/文字模式切换和媒体选择面板
**交互说明:**
- 点击"查看工单":跳转到工单详情(进行中)页,
- 点击"驻场派单":跳转到驻场派单页
- 点击"救护车支援":跳转到救护车支援页
- 点击"救助信息":跳转到救助信息汇总页
- 点击"员工位置":跳转到患者定位地图页
- 点击"结束应急":弹出二次确认弹窗,确认后跳转回工作台首页(已完成 Tab)
- 点击"🎤"图标:切换语音/文字输入模式
- 点击"+"图标:展开/收起媒体选择面板
- 点击"➤"发送按钮:将输入框内容发送为操作人员消息气泡
---
### 3. 应急聊天(已结束)
![页面截图](screenshots/emergency-chat-done.jpg)
**功能说明:** 查看已结束应急的完整聊天记录,只读模式,不可发送消息。
**业务规则:**
- 患者信息条(可折叠)展示患者基本信息
- 聊天区域展示完整历史消息
- 应急结束卡片以灰色样式展示结束时间
- 底部固定提示条:"本次应急已结束"
**交互说明:**
- 点击"查看工单":跳转到工单详情(已完成)页
- 点击返回:跳转回工作台首页(已完成 Tab)
---
### 4. 驻场派单
![页面截图](screenshots/dispatch-onsite.jpg)
**功能说明:** 操作人员选择驻场人员并下发派单指令,派单成功后展示派单结果。
**业务规则:**
- 患者信息摘要条展示患者姓名、年龄、求助方式
- 驻场人员列表支持按姓名或所在医院搜索过滤
- 每个驻场人员展示:姓名、所在医院、联系方式、当前状态(空闲/有派单)
- 选中人员后自动展示详细信息卡片
- 确认派单后表单区域隐藏,显示派单结果卡片(含派单时间、人员、状态、说明)
**交互说明:**
- 点击搜索框:展开驻场人员下拉列表
- 输入关键词:实时过滤匹配的人员
- 选择人员:自动填充信息卡片,启用确认按钮
- 点击"确认派单":隐藏选择表单,显示派单结果
- 点击返回:返回应急聊天页
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 驻场人员 | 搜索下拉选择 | 是 | 必须选择一名人员 |
| 派单说明 | 多行文本 | 否 | 无 |
---
### 5. 救护车支援
![页面截图](screenshots/dispatch-ambulance.jpg)
**功能说明:** 操作人员选择救护车并填写行程信息,完成救护车调度。
**业务规则:**
- 患者信息摘要条展示患者基本信息
- 救护车列表支持按医疗点或医院名称搜索过滤
- 每辆救护车展示:车牌号、所属医疗点、所属医院、驾驶员、联系电话
- 选中救护车后自动展示详细信息卡片
- 目的地为必填项,其余行程信息选填
**交互说明:**
- 点击搜索框:展开救护车下拉列表
- 输入关键词:实时过滤匹配的救护车
- 选择救护车:自动填充信息卡片
- 点击"确认填写":验证目的地必填,提示成功后返回上一页
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 救护车 | 搜索下拉选择 | 是 | 必须选择一辆救护车 |
| 随行人 | 单行文本 | 否 | 无 |
| 随行人电话 | 电话输入 | 否 | 无 |
| 目的地 | 单行文本 | 是 | 不能为空 |
| 备注信息 | 多行文本 | 否 | 无 |
---
### 6. 救助信息汇总
![页面截图](screenshots/rescue-summary.jpg)
**功能说明:** 集中管理救助过程中的补充小结、救助结果和相关照片,支持新增、编辑、删除操作。
**业务规则:**
- 患者信息摘要条展示患者基本信息
- 三个可折叠区块:补充小结、救助结果、相关照片
- 补充小结支持多条记录,每条展示时间、内容、作者,支持编辑和删除
- 新增小结输入区展示字数统计(上限 200 字)
- 救助结果为单条文本,支持切换编辑模式
- 相关照片展示已上传照片列表,支持上传和删除
**交互说明:**
- 点击区块标题:折叠/展开对应区块
- 点击"添加"(小结):展开输入区,填写后插入列表
- 点击"编辑"(小结):进入编辑模式
- 点击"删除"(小结):确认后删除,更新计数
- 点击"编辑"(救助结果):切换为编辑模式
- 点击"保存"(救助结果):保存并切换回展示模式
- 点击"上传照片":添加照片
- 点击"删除"(照片):确认后删除,更新计数
---
### 7. 患者定位地图
![页面截图](screenshots/patient-location.jpg)
**功能说明:** 地图展示患者实时位置和周边医疗资源分布,支持按资源类型筛选查看。
**业务规则:**
- 地图中心展示患者位置标记(红色脉冲动画)
- 资源类型 Tab 栏:油田医院、合作医院、医疗点、救护车、AED、急救包
- 不同类型资源使用不同颜色标记
- 资源卡片展示:名称、所属机构、距离患者距离、电话、地址
- 卡片提供"联系"和"导航"两个操作按钮
**交互说明:**
- 点击资源类型 Tab:切换显示对应类型的资源标记
- 点击资源标记:底部弹出资源详情卡片
- 点击"联系":拨打资源联系电话
- 点击"导航":打开导航应用
- 点击地图空白处:关闭资源卡片
---
### 8. 工单详情(进行中)
![页面截图](screenshots/work-order-detail-ongoing.jpg)
**功能说明:** 展示进行中工单的完整信息,支持编辑救助信息和救护车信息。包含基本信息、救助信息、派单信息、救护车支援四个可折叠卡片。
**业务规则:**
- 基本信息卡片:工单编号、应急中心、求助员工、部门、求助方式、时间、位置、操作人员、专业人员、状态
- 救助信息卡片:救助意见(只读)、救助结果(可编辑)、相关照片(可上传/删除)、补充小结(可新增/编辑/删除)、保存按钮
- 派单信息卡片:展示已派单记录或空状态提示
- 救护车支援卡片:展示救护车详情,支持编辑
- 根据 URL 参数 method 动态设置求助方式和返回目标:应急视频返回聊天页,应急电话/拨打120 显示结束应急按钮
**交互说明:**
- 点击卡片标题:折叠/展开卡片
- 点击"新增/编辑"(救助结果):打开编辑弹窗
- 点击"新增/编辑"(补充小结):打开小结编辑弹窗
- 点击"编辑"(救护车):打开救护车编辑弹窗(含搜索选择和行程信息)
- 点击"结束应急"(仅非视频求助):弹出确认弹窗,确认后跳转回工作台
---
### 9. 工单详情(已完成)
![页面截图](screenshots/work-order-detail.jpg)
**功能说明:** 展示已完成工单的完整信息,结构与进行中版本一致,救助信息、派单信息、救护车信息均已填充完整数据。
**业务规则:**
- 四个可折叠卡片结构同进行中版本
- 救助结果、补充小结、相关照片均展示已填写内容
- 派单信息展示完整派单历史(派单时间、驻场人员、状态、说明)
- 救护车支援展示完整调度记录
**交互说明:**
- 点击卡片标题:折叠/展开卡片
- 点击返回:跳转到应急聊天(已结束)页
---
## 三、大病就医业务线
### 10. 大病就医列表
![页面截图](screenshots/serious-illness-list.jpg)
**功能说明:** 大病就医预约任务列表,按状态分类展示,支持 Tab 切换。
**业务规则:**
- 二级 Tab 切换:待确认(含数量角标)、已确认、已完成、已取消
- 卡片展示:预约医院、状态标签、申请员工、科室、医生、挂号类别、预约时间
- 不同状态使用不同颜色标签:待确认(橙色)、已确认(蓝色)、已完成(绿色)、已取消(灰色)
**交互说明:**
- 点击待确认卡片:跳转到大病就医详情(待确认)页
- 点击已确认卡片:跳转到大病就医详情(已确认)页
- 点击已完成卡片:跳转到大病就医详情(已完成)页
- 点击已取消卡片:跳转到大病就医详情(已取消)页
---
### 11. 大病就医详情(待确认)
![页面截图](screenshots/serious-illness-detail-pending.jpg)
**功能说明:** 展示员工大病就医预约详情,操作人员可确认预约并派单,或取消预约。
**业务规则:**
- 申请员工信息卡片(可折叠):姓名、性别、年龄、员工编号、所属单位、所属部门、联系电话、身份证
- 预约信息卡片(可折叠):发起预约时间、预约医院、科室、医生、挂号类别、期望预约时间、病情描述
- 底部双按钮:取消预约、确认预约
**交互说明:**
- 点击"确认预约":弹出确认派单弹窗,需选择驻场人员后方可确认,确认后跳转回大病就医列表
- 点击"取消预约":弹出取消确认弹窗,需填写取消原因后方可确认,确认后跳转回大病就医列表
**确认派单弹窗表单:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 驻场人员 | 搜索下拉选择 | 是 | 必须选择一名人员,选中后启用确认按钮 |
**取消预约弹窗表单:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 取消原因 | 多行文本 | 是 | 不能为空,输入后启用确认按钮 |
---
### 12. 大病就医详情(已确认)
![页面截图](screenshots/serious-illness-detail-confirmed.jpg)
**功能说明:** 展示已确认的大病就医预约详情,操作人员可联系驻场人员或取消预约。
**业务规则:**
- 申请员工信息卡片和预约信息卡片内容同待确认页
- 底部双按钮:取消预约、联系驻场人员
**交互说明:**
- 点击"取消预约":弹出取消确认弹窗,需填写取消原因,确认后跳转回大病就医列表
- 点击"联系驻场人员":触发联系操作
---
### 13. 大病就医详情(已完成)
![页面截图](screenshots/serious-illness-detail-done.jpg)
**功能说明:** 展示已完成的大病就医预约详情,包含驻场人员提交的挂号结果。
**业务规则:**
- 申请员工信息卡片和预约信息卡片内容同上
- 新增挂号结果卡片(可折叠):就诊科室、就诊医生、就诊时段、排队序号、就诊医院、挂号类别、备注
- 底部单按钮:联系驻场人员
**交互说明:**
- 点击卡片标题:折叠/展开卡片
- 点击"联系驻场人员":触发联系操作
---
### 14. 大病就医详情(已取消)
![页面截图](screenshots/serious-illness-detail-cancelled.jpg)
**功能说明:** 展示已取消的大病就医预约详情,包含取消原因。
**业务规则:**
- 申请员工信息卡片和预约信息卡片内容同上
- 取消信息展示:取消时间、取消原因
- 无操作按钮,纯只读页面
**交互说明:**
- 点击卡片标题:折叠/展开卡片
- 点击返回:返回大病就医列表
---
## 四、知识库模块
### 15. 知识库首页
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
---
### 16. 知识详情
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
---
## 五、个人中心模块
### 17. 个人中心主页
![页面截图](screenshots/profile-index.jpg)
**功能说明:** 用户信息展示和功能菜单导航,展示头像、姓名、角色和所属应急中心。
**业务规则:**
- 用户信息区:头像、姓名(王昊)、角色(操作人员)、所属应急中心
- 菜单项:值班信息、修改个人信息、修改密码、关于我们
- 退出登录按钮
**交互说明:**
- 点击"值班信息":跳转到值班信息页
- 点击"修改个人信息":跳转到修改个人信息页
- 点击"修改密码":跳转到修改密码页
- 点击"退出登录":弹出二次确认弹窗
---
### 18. 修改个人信息
![页面截图](screenshots/edit-profile.jpg)
**功能说明:** 编辑用户头像和姓名。
**交互说明:**
- 点击头像:弹出底部操作菜单(拍照/从相册选择)
- 点击"保存":验证姓名非空,显示保存成功提示
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 头像 | 图片选择 | 否 | 支持拍照或从相册选择 |
| 姓名 | 单行文本 | 是 | 不能为空 |
---
### 19. 修改密码
![页面截图](screenshots/change-pwd.jpg)
**功能说明:** 修改用户登录密码。
**交互说明:**
- 点击"保存修改":弹出修改成功确认弹窗,确认后跳转回个人中心
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 当前密码 | 密码输入 | 是 | 不能为空 |
| 新密码 | 密码输入 | 是 | 不能为空 |
| 确认新密码 | 密码输入 | 是 | 必须与新密码一致 |
---
### 20. 值班信息
![页面截图](screenshots/duty-schedule.jpg)
**功能说明:** 查看值班日历和当日值班人员安排。
**业务规则:**
- 日历网格展示当前月份,标记今天(蓝色背景)和排班日(浅蓝色背景+蓝色圆点)
- 下方展示当日值班人员列表
- 支持月份前后切换
**交互说明:**
- 点击月份导航箭头:切换上/下月
- 点击"修改":跳转到排班配置页
---
### 21. 排班配置
![页面截图](screenshots/duty-config.jpg)
**功能说明:** 配置周排班,为每天选择专业人员和操作人员。
**业务规则:**
- 按周展示排班表,每天可配置专业人员和操作人员
- 人员选择为多选下拉,支持搜索
- 支持"复制上周排班"快捷操作
**交互说明:**
- 点击周导航箭头:切换上/下周
- 点击"复制上周排班":复制上周配置,无数据时提示
- 点击人员下拉:展开多选列表
- 点击"保存":保存成功后跳转回值班信息页
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 专业人员 | 多选下拉 | 否 | 可选多人 |
| 操作人员 | 多选下拉 | 否 | 可选多人 |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -63,7 +63,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</button>
<span style="font-size:11px;color:#666;">求助时间:2026-04-24 14:30</span>
</div>
<button onclick="location.href='./work-order-detail.html'" style="font-size:12px;color:#fa8c16;background:none;border:1px solid #fa8c16;border-radius:6px;padding:4px 10px;cursor:pointer;">查看工单</button>
<button onclick="sessionStorage.setItem('operatorWorkOrderDetailSource','./emergency-chat-done.html');location.href='./work-order-detail.html'" style="font-size:12px;color:#fa8c16;background:none;border:1px solid #fa8c16;border-radius:6px;padding:4px 10px;cursor:pointer;">查看工单</button>
</div>
<div id="emergency-info-detail" class="info-detail">
<div class="info-avatar">👨</div>
@@ -77,13 +77,13 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="scroll-content" style="padding:12px 16px;">
<div style="text-align:center;margin:8px 0;"><span style="font-size:11px;color:#bbb;background:#ebebeb;padding:3px 10px;border-radius:10px;">14:30</span></div>
<div style="text-align:center;margin:8px 0 14px;"><span style="font-size:11px;color:#fa8c16;background:#fff7e6;padding:4px 12px;border-radius:10px;border:1px solid #ffe7ba;">新应急事件 · 陈伟发起求助</span></div>
<!-- <div style="text-align:center;margin:8px 0 14px;"><span style="font-size:11px;color:#fa8c16;background:#fff7e6;padding:4px 12px;border-radius:10px;border:1px solid #ffe7ba;">新应急事件 · 陈伟发起求助</span></div>-->
<div style="display:flex;gap:8px;margin-bottom:14px;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#ffe0e0,#ffb3b3);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">👨</div>
<div>
<div style="font-size:11px;color:#999;margin-bottom:4px;">陈伟(患者)</div>
<div style="background:#fff;border-radius:0 12px 12px 12px;padding:10px 14px;font-size:14px;color:#1a1a1a;max-width:220px;box-shadow:0 1px 4px rgba(0,0,0,0.06);line-height:1.5;">我胸口很闷,呼吸困难,已经持续20分钟了。</div>
<div style="background:#fff;border-radius:0 12px 12px 12px;padding:10px 14px;font-size:14px;color:#1a1a1a;max-width:220px;box-shadow:0 1px 4px rgba(0,0,0,0.06);line-height:1.5;">📹 通话时长 12:01</div>
</div>
</div>
@@ -95,7 +95,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<div style="text-align:center;margin:8px 0 14px;"><span style="font-size:11px;color:#1677ff;background:#e6f4ff;padding:4px 12px;border-radius:10px;border:1px solid #bae0ff;">已派专业人员:张旭 | 已派救护车</span></div>
<!-- <div style="text-align:center;margin:8px 0 14px;"><span style="font-size:11px;color:#1677ff;background:#e6f4ff;padding:4px 12px;border-radius:10px;border:1px solid #bae0ff;">已派专业人员:张旭 | 已派救护车</span></div>-->
<div style="text-align:center;margin:8px 0;"><span style="font-size:11px;color:#bbb;background:#ebebeb;padding:3px 10px;border-radius:10px;">14:38</span></div>
@@ -1,147 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>操作人员 - 结束应急 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.form-section { background: #fff; margin: 12px 16px; border-radius: 12px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.form-section__title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 14px; display: flex; align-items: center; gap: 6px; }
.form-section__title::before { content: ''; width: 3px; height: 16px; background: #1677ff; border-radius: 2px; }
.form-field { margin-bottom: 16px; }
.form-field:last-child { margin-bottom: 0; }
.form-label { font-size: 13px; color: #333; margin-bottom: 6px; display: flex; align-items: center; gap: 4px; }
.form-label .required { color: #ff4d4f; }
.form-input { width: 100%; height: 40px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 12px; font-size: 14px; outline: none; color: #1a1a1a; background: #fff; }
.form-input:focus { border-color: #1677ff; }
.form-textarea { width: 100%; min-height: 100px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; resize: vertical; font-family: inherit; line-height: 1.6; color: #1a1a1a; }
.form-textarea:focus { border-color: #1677ff; }
.form-select { width: 100%; height: 40px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 12px; font-size: 14px; outline: none; color: #1a1a1a; background: #fff; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none'%3E%3Cpath d='M3 4.5l3 3 3-3' stroke='%23999' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; }
.form-select:focus { border-color: #1677ff; }
/* 患者信息卡 */
.patient-card { background: #fff7e6; border: 1px solid #ffe7ba; border-radius: 10px; padding: 12px 14px; display: flex; align-items: center; gap: 10px; }
.patient-card__avatar { width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg,#fff1f0,#ffccc7); display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.patient-card__info { flex: 1; }
.patient-card__name { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.patient-card__detail { font-size: 12px; color: #666; margin-top: 2px; }
/* 照片上传 */
.photo-grid { display: flex; gap: 10px; flex-wrap: wrap; }
.photo-item { width: 72px; height: 72px; border-radius: 8px; border: 1px solid #e8e8e8; overflow: hidden; background: #fafafa; display: flex; align-items: center; justify-content: center; }
.photo-item img { width: 100%; height: 100%; object-fit: cover; }
.photo-add { width: 72px; height: 72px; border-radius: 8px; border: 2px dashed #d9d9d9; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; color: #bbb; font-size: 24px; transition: all .15s; }
.photo-add:hover { border-color: #1677ff; color: #1677ff; }
.photo-add span { font-size: 10px; margin-top: 2px; }
/* 底部按钮 */
.bottom-bar { padding: 12px 16px 28px; background: #fff; border-top: 1px solid #f0f0f0; flex-shrink: 0; }
.btn-submit { width: 100%; height: 44px; border-radius: 10px; font-size: 16px; font-weight: 600; cursor: pointer; border: none; background: #ff4d4f; color: #fff; transition: background .2s; }
.btn-submit:hover { background: #ff7875; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<!-- [交互] 点击返回: ./emergency-chat.html -->
<button class="back-btn" onclick="history.back()">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">结束应急</span>
</div>
<div class="scroll-content">
<!-- 患者信息 -->
<div style="padding:12px 16px 0;">
<div class="patient-card">
<div class="patient-card__avatar">👨</div>
<div class="patient-card__info">
<div class="patient-card__name">陈伟 · 男 · 47岁</div>
<div class="patient-card__detail">采油三厂 · 第二作业区 | 求助时间:14:30</div>
</div>
</div>
</div>
<!-- 结束原因 -->
<div class="form-section">
<div class="form-section__title">结束信息</div>
<div class="form-field">
<div class="form-label"><span class="required">*</span>结束原因</div>
<select class="form-select">
<option value="">请选择结束原因</option>
<option selected>患者已送达医院</option>
<option>患者症状缓解</option>
<option>误报/取消求助</option>
<option>转交其他机构处理</option>
<option>其他</option>
</select>
</div>
<div class="form-field">
<div class="form-label"><span class="required">*</span>处理结果</div>
<textarea class="form-textarea" placeholder="请描述本次应急的处理结果...">患者已由救护车送达西安市第一人民医院急诊科,初步诊断为心绞痛,目前状态稳定,已安排住院观察。</textarea>
</div>
<div class="form-field">
<div class="form-label">送达医院</div>
<input class="form-input" type="text" value="西安市第一人民医院" placeholder="请输入送达医院名称" />
</div>
</div>
<!-- 现场照片 -->
<div class="form-section">
<div class="form-section__title">现场照片</div>
<div class="form-field">
<div class="photo-grid">
<div class="photo-item" style="background:linear-gradient(135deg,#e6f4ff,#bae0ff);font-size:28px;color:#1677ff;">🏥</div>
<div class="photo-item" style="background:linear-gradient(135deg,#f6ffed,#d9f7be);font-size:28px;color:#52c41a;">📋</div>
<div class="photo-add">+<span>添加</span></div>
</div>
<div style="font-size:11px;color:#999;margin-top:6px;">已上传 2/9 张</div>
</div>
</div>
<!-- 备注 -->
<div class="form-section">
<div class="form-section__title">补充说明</div>
<div class="form-field">
<textarea class="form-textarea" placeholder="如有其他需要补充的信息,请在此填写..." style="min-height:80px;"></textarea>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 底部提交按钮 -->
<div class="bottom-bar">
<!-- [交互] 点击确认结束应急,返回工作台 -->
<button class="btn-submit" onclick="confirmEnd()">确认结束应急</button>
</div>
</div>
<script>
sessionStorage.setItem('appRole', 'operator');
function confirmEnd() {
if (confirm('确认结束本次应急?结束后不可撤销。')) {
sessionStorage.setItem('operatorWorkbenchTab', 'done');
location.href = './index.html';
}
}
</script>
</body>
</html>
+2 -2
View File
@@ -134,7 +134,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- 已完成 -->
<div id="panel-done" class="content-panel">
<div style="height:10px;"></div>
<div class="case-card" onclick="location.href='./work-order-detail.html'">
<div class="case-card" onclick="sessionStorage.setItem('operatorWorkOrderDetailSource','./index.html');location.href='./work-order-detail.html'">
<div class="case-head">
<div class="case-avatar case-avatar-emergency"></div>
<div class="case-main">
@@ -149,7 +149,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<div class="case-card" onclick="location.href='./emergency-chat.html'">
<div class="case-card" onclick="location.href='./emergency-chat-done.html'">
<div class="case-head">
<div class="case-avatar case-avatar-emergency">📄</div>
<div class="case-main">
@@ -137,7 +137,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="bottom-btns">
<button class="bottom-btns__btn bottom-btns__btn--cancel" onclick="document.getElementById('cancelModal').classList.add('modal-mask--show')">取消预约</button>
<button class="bottom-btns__btn bottom-btns__btn--primary" onclick="document.getElementById('confirmModal').classList.add('modal-mask--show')">确认预约</button>
<button class="bottom-btns__btn bottom-btns__btn--primary" onclick="document.getElementById('confirmModal').classList.add('modal-mask--show')">立即派单</button>
</div>
<div class="modal-mask" id="confirmModal">
@@ -173,6 +173,10 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
</div>
<div class="modal__field">
<div class="modal__field-label">派单说明</div>
<textarea id="dispatchRemark" class="modal__textarea" placeholder="请输入派单说明(将同步给驻场人员)"></textarea>
</div>
<div class="modal__btns">
<button class="modal__btn modal__btn--cancel" onclick="closeConfirmModal()">取消</button>
<button class="modal__btn modal__btn--primary disabled" id="confirmBookBtn" onclick="doConfirmBooking()">确认派单</button>
@@ -204,6 +208,12 @@ function toggleInfoCard(cardId, trigger) {
var selectedPerson = null;
function updateConfirmBookBtn() {
var btn = document.getElementById('confirmBookBtn');
if (!btn) return;
btn.classList.toggle('disabled', !selectedPerson);
}
function openPersonDrop() {
document.getElementById('personSelect').classList.add('open');
}
@@ -217,7 +227,7 @@ function selectPerson(el) {
selectedPerson = el.getAttribute('data-name');
document.getElementById('personSearch').value = selectedPerson;
closePersonDrop();
document.getElementById('confirmBookBtn').classList.remove('disabled');
updateConfirmBookBtn();
}
function filterPersons() {
@@ -233,7 +243,10 @@ function filterPersons() {
});
document.getElementById('personEmpty').classList.toggle('show', visible === 0);
openPersonDrop();
if (!keyword) { selectedPerson = null; document.getElementById('confirmBookBtn').classList.add('disabled'); }
if (!keyword) {
selectedPerson = null;
updateConfirmBookBtn();
}
}
function closeConfirmModal() {
@@ -241,10 +254,18 @@ function closeConfirmModal() {
}
function doConfirmBooking() {
if (!selectedPerson) return;
if (!selectedPerson) {
alert('请选择驻场人员');
return;
}
location.href = './serious-illness-list.html';
}
var dispatchRemarkEl = document.getElementById('dispatchRemark');
if (dispatchRemarkEl) {
dispatchRemarkEl.addEventListener('input', updateConfirmBookBtn);
}
document.addEventListener('click', function(e) {
var sel = document.getElementById('personSelect');
if (sel && !sel.contains(e.target)) {
@@ -110,8 +110,8 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="nav-bar">
<!-- [交互] 点击返回: ./emergency-chat-done.html -->
<button class="back-btn" onclick="location.href='./emergency-chat-done.html'">
<!-- [交互] 点击返回: 来源页面(无来源则 ./index.html -->
<button class="back-btn" onclick="goBackToSource()">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">工单详情</span>
@@ -309,6 +309,15 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<script>
sessionStorage.setItem('appRole', 'operator');
function goBackToSource() {
var src = sessionStorage.getItem('operatorWorkOrderDetailSource');
if (src) {
location.href = src;
return;
}
location.href = './index.html';
}
function toggleCard(cardId, headerEl) {
var card = document.getElementById(cardId);
card.classList.toggle('card--collapsed');
+292
View File
@@ -0,0 +1,292 @@
# 专业人员端(健康应急APP)产品需求文档
## 模块概述
专业人员端为具备医学专业背景的应急人员提供移动端工作平台,负责在应急求助过程中提供专业医学指导,包括实时聊天沟通、给出救助意见、查看患者定位和周边医疗资源。专业人员通过聊天界面与操作人员、患者进行三方协作,是应急救助中的医学决策支撑角色。
### 页面导航结构
```
专业人员端
├── 工作台(底部Tab
│ ├── 进行中 → 应急聊天(进行中)
│ │ ├── 救助意见表单
│ │ ├── 患者定位地图
│ │ └── 工单详情(进行中)
│ └── 已结束 → 应急聊天(已结束)
│ └── 工单详情(已完成)
├── 知识库(底部Tab
│ ├── 知识文章(分类Tab)→ 知识详情
│ └── 规章制度(PDF列表)
├── AI助手(底部Tab
├── 消息(底部Tab
└── 我的(底部Tab
├── 值班信息
├── 修改个人信息
├── 修改密码
└── 退出登录(确认弹窗)
```
### 应急状态流转图
```mermaid
stateDiagram-v2
[*] --> 进行中 : 未结束的应急工单
进行中 --> 已结束 : 员工/操作人员/专业人员任意一方结束应急
```
## 页面清单
| 序号 | 文件名 | 页面名称 | 所属模块 |
|------|--------|----------|----------|
| 1 | workbench/index.html | 工作台首页 | 工作台 |
| 2 | workbench/emergency-chat-ongoing.html | 应急聊天(进行中) | 工作台·应急求助 |
| 3 | workbench/emergency-chat-done.html | 应急聊天(已结束) | 工作台·应急求助 |
| 4 | workbench/rescue-opinion.html | 救助意见表单 | 工作台·应急求助 |
| 5 | workbench/patient-location.html | 患者定位地图 | 工作台·应急求助 |
| 6 | workbench/work-order-detail-ongoing.html | 工单详情(进行中) | 工作台·应急求助 |
| 7 | workbench/work-order-detail.html | 工单详情(已完成) | 工作台·应急求助 |
| 8 | knowledge/index.html | 知识库首页 | 知识库 |
| 9 | knowledge/article-detail.html | 知识详情 | 知识库 |
| 10 | ../ai-assistant/index.html | AI 助手 | AI 助手 |
| 11 | ../message/index.html | 消息列表 | 消息 |
| 12 | profile/index.html | 个人中心主页 | 我的 |
| 13 | profile/edit-profile.html | 修改个人信息 | 我的 |
| 14 | profile/change-pwd.html | 修改密码 | 我的 |
| 15 | profile/duty-schedule.html | 值班信息 | 我的 |
## 逐页说明
---
## 一、工作台模块
### 1. 工作台首页
![页面截图](screenshots/index.jpg)
**功能说明:** 专业人员工作台入口,展示当前参与的应急工单列表,按进行中/已结束分类管理。
**业务规则:**
- 二级 Tab 切换:进行中(含数量角标)/ 已结束(含数量角标)
- 应急卡片展示:患者姓名、年龄、求助方式标签、求助时间
- 应急求助方式分为“拨打120”、“应急视频”、“应急电话”、“线下应急”四种,“线下应急”为操作人员手动录入工单方式。
- 已结束卡片额外展示结束时间
**交互说明:**
- 点击进行中卡片:求助方式如果是“应急视频”则跳转到应急聊天(进行中)页,其余跳转到工单详情(进行中)页
- 点击已结束卡片:求助方式如果是“应急视频”则跳转到应急聊天(已结束)页,其余跳转到工单详情(已完成)页
- 底部导航栏 5 个 Tab:工作台、知识库、AI 助手、消息、我的
---
## 二、应急求助业务线
### 2. 应急聊天(进行中)
![页面截图](screenshots/emergency-chat-ongoing.jpg)
**功能说明:** 专业人员与患者、操作人员的实时三方聊天界面。
**业务规则:**
- 患者信息条(可折叠)展示:求助时间、患者姓名、年龄、所属单位、联系电话、求助方式、查看工单详情入口
- 聊天区域展示三方消息:患者(左侧气泡)、专业人员(右侧气泡)、操作人员(左侧气泡)
- 操作按钮栏提供 2 个功能入口:救助意见、患者定位
- 输入区支持语音/文字模式切换和媒体选择面板,媒体选择支持拍照、图库、视频通话。
**交互说明:**
- 点击"查看工单":跳转到工单详情(进行中)页
- 点击"救助意见":跳转到救助意见表单页,确认完后在聊天页发送一条自定义救助意见卡片消息
- 点击"患者定位":跳转到患者定位地图页
- 点击"结束应急":弹出二次确认弹窗,确认后发送一条结束应急的卡片消息,并跳转回工作台首页(已结束 Tab),
- 点击"🎤"图标:切换语音/文字输入模式
- 点击"+"图标:展开/收起媒体选择面板
- 点击"➤"发送按钮:将输入框内容发送为专业人员消息气泡
---
### 3. 应急聊天(已结束)
![页面截图](screenshots/emergency-chat-done.jpg)
**功能说明:** 查看已结束应急的完整聊天记录,只读模式。
**业务规则:**
- 患者信息条(可折叠)展示患者基本信息
- 聊天区域展示完整历史消息
- 应急结束卡片以灰色样式展示结束时间
- 底部固定提示条:"本次应急已结束"
**交互说明:**
- 点击"查看工单":跳转到工单详情(已完成)页
- 点击返回:跳转回工作台首页(已结束 Tab)
---
### 4. 救助意见表单
![页面截图](screenshots/rescue-opinion.jpg)
**功能说明:** 专业人员填写救助意见,提交后意见将发送到聊天页面中。
**业务规则:**
- 患者信息展示区(只读):患者姓名、性别、年龄、单位、部门
- 救助意见输入区:多行文本输入
**交互说明:**
- 点击"提交":提交救助意见,跳转回应急聊天页,发送救助意见卡片消息在聊天页中
- 点击返回:返回应急聊天页
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|----|----------|
| 救助意见 | 多行文本 | 否 | 最大限制200字 |
---
### 5. 患者定位地图
![页面截图](screenshots/patient-location.jpg)
**功能说明:** 地图展示患者实时位置和周边医疗资源分布,功能与操作人员端一致。
**业务规则:**
- 地图中心展示患者位置标记
- 资源类型 Tab 栏:油田医院、合作医院、医疗点、救护车、AED、急救包
- 不同类型资源使用不同颜色标记
- 资源卡片展示:点击资源图标后,底部弹窗显示资源卡片,显示资源图片、名称、所属单位/机构、距离、电话、地址
**交互说明:**
- 点击资源类型 Tab:切换显示对应类型的资源标记
- 点击资源标记:底部弹出资源详情卡片
- 点击地图空白处:关闭资源卡片
---
### 6. 工单详情(进行中)
![页面截图](screenshots/work-order-detail-ongoing.jpg)
**功能说明:** 展示进行中工单的完整信息。
**业务规则:**
- 四个可折叠卡片:基本信息、救助信息、派单信息、救护车支援
- 基本信息:工单编号、类型、应急中心、求助员工、部门、求助方式、时间、位置、操作人员、专业人员、状态
- 救助信息:救助意见、救助结果、相关照片、补充小结。除了应急视频,其余的求助方式可以编辑救助意见。在救助信息意见显示“编辑”按钮,参考已完成状态的工单详情
- 派单信息和救护车支援:展示已有记录或空状态提示
**交互说明:**
- 点击卡片标题:折叠/展开卡片
- 点击返回:返回应急聊天页
---
### 7. 工单详情(已完成)
![页面截图](screenshots/work-order-detail.jpg)
**功能说明:** 展示已完成工单的完整信息,所有卡片数据均已填充。
**业务规则:**
- 四个可折叠卡片结构同进行中版本
- 救助信息展示完整的救助意见、救助结果、相关照片、补充小结
- 派单信息展示完整派单历史
- 救护车支援展示完整调度记录
**交互说明:**
- 点击卡片标题:折叠/展开卡片
- 点击返回:返回应急聊天(已结束)页
---
## 三、知识库模块
### 8. 知识库首页
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
### 9. 知识详情
**功能说明:** 等待后续规划,提示“功能开发中,敬请期待~”
## 四、个人中心模块
### 10. 个人中心主页
![页面截图](screenshots/profile-index.jpg)
**功能说明:** 用户信息展示和功能菜单导航。
**业务规则:**
- 用户信息区:头像、姓名、角色(专业人员)、所属应急中心
- 菜单项:值班信息、修改个人信息、修改密码、关于我们
- 退出登录按钮
**交互说明:**
- 点击"值班信息":跳转到值班信息页
- 点击"修改个人信息":跳转到修改个人信息页
- 点击"修改密码":跳转到修改密码页
- 点击"退出登录":弹出二次确认弹窗
---
### 11. 修改个人信息
![页面截图](screenshots/edit-profile.jpg)
**功能说明:** 编辑用户头像和姓名。
**交互说明:**
- 点击头像:弹出底部操作菜单(拍照/从相册选择)
- 点击"保存":验证姓名非空,显示保存成功提示
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 头像 | 图片选择 | 否 | 支持拍照或从相册选择 |
| 姓名 | 单行文本 | 是 | 不能为空 |
---
### 12. 修改密码
![页面截图](screenshots/change-pwd.jpg)
**功能说明:** 修改用户登录密码。
**交互说明:**
- 点击"保存修改":弹出修改成功确认弹窗,确认后跳转回个人中心
**表单字段表:**
| 字段名 | 类型 | 必填 | 校验规则 |
|--------|------|------|----------|
| 当前密码 | 密码输入 | 是 | 不能为空 |
| 新密码 | 密码输入 | 是 | 不能为空 |
| 确认新密码 | 密码输入 | 是 | 必须与新密码一致 |
---
### 13. 值班信息
![页面截图](screenshots/duty-schedule.jpg)
**功能说明:** 查看值班日历和当日值班人员安排。
**业务规则:**
- 日历网格展示当前月份,标记今天(蓝色背景)和排班日(浅蓝色背景+蓝色圆点)
- 下方展示当日值班人员列表
- 支持月份前后切换
**交互说明:**
- 点击月份导航箭头:切换上/下月
- 点击返回:返回个人中心
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 40 KiB

@@ -82,7 +82,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span style="font-size:11px;color:#666;">求助时间:2026-04-24 09:15</span>
</div>
<!-- [交互] 点击跳转至: ./work-order-detail-ongoing.html -->
<button onclick="location.href='./work-order-detail-ongoing.html'" style="font-size:12px;color:#fa8c16;background:none;border:1px solid #fa8c16;border-radius:6px;padding:4px 10px;cursor:pointer;">查看工单</button>
<button onclick="sessionStorage.setItem('professionalWorkOrderOngoingSource','./emergency-chat-ongoing.html');sessionStorage.setItem('professionalWorkOrderMethod','应急视频');location.href='./work-order-detail-ongoing.html'" style="font-size:12px;color:#fa8c16;background:none;border:1px solid #fa8c16;border-radius:6px;padding:4px 10px;cursor:pointer;">查看工单</button>
</div>
<div id="emergency-info-detail" class="info-detail">
<div class="info-avatar">👩</div>
@@ -31,6 +31,8 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.tag-blue { background: #e6f4ff; color: #1677ff; }
.tag-green { background: #f6ffed; color: #52c41a; }
.tag-gray { background: #f5f5f5; color: #999; }
.tag-purple { background: #f9f0ff; color: #722ed1; }
.tag-orange { background: #fff7e6; color: #fa8c16; }
/* 应急卡片 */
.emergency-card { background: #fff; border-radius: 12px; margin: 10px 16px 0; padding: 14px; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.06); transition: transform 0.15s; }
.emergency-card:active { transform: scale(0.98); }
@@ -70,7 +72,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- 二级Tab:进行中 / 已结束 -->
<div class="seg-tabs" style="flex-shrink:0;">
<div class="seg-tab active" onclick="switchTab(this,'panel-ongoing')">进行中<span class="badge" style="background:#fa8c16;">2</span></div>
<div class="seg-tab active" onclick="switchTab(this,'panel-ongoing')">进行中<span class="badge" style="background:#fa8c16;">3</span></div>
<div class="seg-tab" onclick="switchTab(this,'panel-done')">已结束</div>
</div>
@@ -85,20 +87,34 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="ec-avatar">🚑</div>
<div class="ec-info">
<div class="ec-name">陈伟<span class="age">47岁</span></div>
<div class="ec-time">求助方式:<span class="tag tag-blue">应急视频</span></div>
<div class="ec-time">求助时间:2026-04-23 14:30</div>
</div>
<span class="tag tag-blue">进行中</span>
<span class="tag tag-red">进行中</span>
</div>
</div>
<!-- [交互] 点击跳转至: ./emergency-chat-ongoing.html -->
<div class="emergency-card" onclick="location.href='./emergency-chat-ongoing.html'">
<!-- [交互] 点击跳转至: ./work-order-detail-ongoing.html -->
<div class="emergency-card" onclick="sessionStorage.setItem('professionalWorkOrderOngoingSource','./index.html');sessionStorage.setItem('professionalWorkOrderMethod','应急电话');location.href='./work-order-detail-ongoing.html'">
<div class="ec-top">
<div class="ec-avatar">🚑</div>
<div class="ec-info">
<div class="ec-name">刘海波<span class="age">55岁</span></div>
<div class="ec-time">求助方式:<span class="tag tag-purple">应急电话</span></div>
<div class="ec-time">求助时间:2026-04-23 10:20</div>
</div>
<span class="tag tag-blue">进行中</span>
<span class="tag tag-red">进行中</span>
</div>
</div>
<!-- [交互] 点击跳转至: ./work-order-detail-ongoing.html -->
<div class="emergency-card" onclick="sessionStorage.setItem('professionalWorkOrderOngoingSource','./index.html');sessionStorage.setItem('professionalWorkOrderMethod','拨打120');location.href='./work-order-detail-ongoing.html'">
<div class="ec-top">
<div class="ec-avatar">🚑</div>
<div class="ec-info">
<div class="ec-name">王建国<span class="age">38岁</span></div>
<div class="ec-time">求助方式:<span class="tag tag-orange">拨打120</span></div>
<div class="ec-time">求助时间:2026-04-23 08:15</div>
</div>
<span class="tag tag-red">进行中</span>
</div>
</div>
<div style="height:16px;"></div>
@@ -106,24 +122,26 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- 已结束列表 -->
<div id="panel-done" style="display:none;">
<!-- [交互] 点击跳转至: ./emergency-chat-done.html -->
<div class="emergency-card" onclick="location.href='./emergency-chat-done.html'">
<!-- [交互] 点击跳转至: ./work-order-detail.html -->
<div class="emergency-card" onclick="sessionStorage.setItem('professionalWorkOrderDetailSource','./index.html');sessionStorage.setItem('professionalWorkOrderMethod','应急视频');location.href='./work-order-detail.html'">
<div class="ec-top">
<div class="ec-avatar">🚑</div>
<div class="ec-info">
<div class="ec-name">孙丽<span class="age">41岁</span></div>
<div class="ec-time">求助方式:<span class="tag tag-blue">应急视频</span></div>
<div class="ec-time">求助时间:2026-04-22 16:00</div>
<div class="ec-time">结束时间:2026-04-22 17:35</div>
</div>
<span class="tag tag-green">已结束</span>
</div>
</div>
<!-- [交互] 点击跳转至: ./emergency-chat-done.html -->
<div class="emergency-card" onclick="location.href='./emergency-chat-done.html'">
<!-- [交互] 点击跳转至: ./work-order-detail.html -->
<div class="emergency-card" onclick="sessionStorage.setItem('professionalWorkOrderDetailSource','./index.html');sessionStorage.setItem('professionalWorkOrderMethod','线下应急');location.href='./work-order-detail.html'">
<div class="ec-top">
<div class="ec-avatar">🚑</div>
<div class="ec-info">
<div class="ec-name">周明<span class="age">33岁</span></div>
<div class="ec-time">求助方式:<span class="tag tag-orange">线下应急</span></div>
<div class="ec-time">求助时间:2026-04-21 09:45</div>
<div class="ec-time">结束时间:2026-04-21 11:20</div>
</div>
@@ -16,8 +16,15 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.card { background: #fff; border-radius: 12px; margin: 12px 16px 0; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.card__title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
.card { background: #fff; border-radius: 12px; margin: 12px 16px 0; box-shadow: 0 1px 4px rgba(0,0,0,0.06); overflow: hidden; }
.card__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
.card__title { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.card__toggle { display: inline-flex; align-items: center; gap: 4px; color: #1677ff; font-size: 12px; font-weight: 600; }
.card__arrow { width: 14px; height: 14px; transition: transform .2s ease; }
.card__body { padding: 0 16px 16px; }
.card--collapsed .card__body { display: none; }
.card--collapsed .card__arrow { transform: rotate(-90deg); }
.card--collapsed .card__header { border-bottom: none; }
.info-item { display: flex; font-size: 13px; line-height: 1.6; padding: 5px 0; }
.info-item__label { color: #999; min-width: 72px; flex-shrink: 0; }
.info-item__value { color: #333; flex: 1; word-break: break-all; }
@@ -36,6 +43,33 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 28px 0 12px; }
.empty-state__icon { font-size: 36px; margin-bottom: 8px; opacity: .35; }
.empty-state__text { font-size: 13px; color: #bbb; }
/* 编辑按钮 */
.edit-btn { background: none; border: none; color: #1677ff; font-size: 13px; cursor: pointer; padding: 0; margin-left: 8px; }
/* 编辑弹窗 */
.edit-mask { position: absolute; inset: 0; background: rgba(0,0,0,0.45); display: none; align-items: center; justify-content: center; z-index: 30; padding: 24px; }
.edit-mask.show { display: flex; }
.edit-dialog { width: 100%; background: #fff; border-radius: 16px; overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,0.2); max-height: 80vh; display: flex; flex-direction: column; }
.edit-dialog__head { padding: 18px 20px; font-size: 17px; font-weight: 700; color: #1a1a1a; border-bottom: 1px solid #f0f0f0; }
.edit-dialog__body { flex: 1; overflow-y: auto; padding: 20px; }
.edit-dialog__textarea { width: 100%; min-height: 120px; padding: 12px; border: 1px solid #d9d9d9; border-radius: 8px; font-size: 14px; font-family: inherit; resize: vertical; }
.edit-dialog__actions { display: flex; gap: 8px; padding: 16px 20px; border-top: 1px solid #f0f0f0; }
.edit-dialog__btn { flex: 1; height: 40px; border: none; border-radius: 8px; font-size: 15px; cursor: pointer; font-weight: 600; }
.edit-dialog__btn--cancel { background: #f5f5f5; color: #666; }
.edit-dialog__btn--confirm { background: #1677ff; color: #fff; }
/* 底部结束应急按钮 */
.end-bar { padding: 12px 16px; background: #fff; border-top: 1px solid #f0f0f0; flex-shrink: 0; display: none; }
.end-btn { width: 100%; height: 44px; border: none; border-radius: 10px; background: #ff4d4f; color: #fff; font-size: 15px; font-weight: 600; cursor: pointer; }
/* 二次确认弹窗 */
.confirm-mask { position: absolute; inset: 0; background: rgba(0,0,0,0.45); display: none; align-items: center; justify-content: center; z-index: 30; padding: 24px; }
.confirm-mask.show { display: flex; }
.confirm-dialog { width: 100%; background: #fff; border-radius: 16px; overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,0.2); }
.confirm-dialog__head { padding: 18px 20px 8px; font-size: 17px; font-weight: 700; color: #1a1a1a; text-align: center; }
.confirm-dialog__body { padding: 0 20px 20px; font-size: 14px; line-height: 1.7; color: #666; text-align: center; }
.confirm-dialog__actions { display: flex; border-top: 1px solid #f0f0f0; }
.confirm-dialog__btn { flex: 1; height: 48px; border: none; background: #fff; font-size: 15px; cursor: pointer; }
.confirm-dialog__btn + .confirm-dialog__btn { border-left: 1px solid #f0f0f0; }
.confirm-dialog__btn--cancel { color: #666; }
.confirm-dialog__btn--confirm { color: #ff4d4f; font-weight: 600; }
</style>
</head>
<body>
@@ -46,8 +80,8 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="nav-bar">
<!-- [交互] 点击返回: ./emergency-chat-ongoing.html -->
<button class="back-btn" onclick="location.href='./emergency-chat-ongoing.html'">
<!-- [交互] 点击返回: 来源页面(无来源则 ./index.html -->
<button class="back-btn" onclick="goBackToSource()">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">工单详情</span>
@@ -55,28 +89,39 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="scroll-content">
<!-- 基本信息 -->
<div class="card">
<div class="card__title">基本信息</div>
<div class="card" id="basic-info-card">
<div class="card__header" onclick="toggleCard('basic-info-card', this)">
<div class="card__title">基本信息</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="info-item"><span class="info-item__label">工单编号</span><span class="info-item__value">YJ5633280</span></div>
<div class="info-item"><span class="info-item__label">工单类型</span><span class="info-item__value">正常派单</span></div>
<div class="info-item"><span class="info-item__label">应急中心</span><span class="info-item__value">西安应急中心</span></div>
<div class="info-item"><span class="info-item__label">求助员工</span><span class="info-item__value">陈伟(EMP-71023</span></div>
<div class="info-item"><span class="info-item__label">所属单位</span><span class="info-item__value">采油一厂</span></div>
<div class="info-item"><span class="info-item__label">所属部门</span><span class="info-item__value">第一作业区</span></div>
<div class="info-item"><span class="info-item__label">求助方式</span><span class="info-item__value">应急视频</span></div>
<div class="info-item"><span class="info-item__label">求助方式</span><span class="info-item__value" id="help-method">应急视频</span></div>
<div class="info-item"><span class="info-item__label">求助时间</span><span class="info-item__value">2026-04-23 14:30:00</span></div>
<div class="info-item"><span class="info-item__label">位置信息</span><span class="info-item__value"><span class="location-link"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 1C5.24 1 3 3.24 3 6c0 3.75 5 7 5 7s5-3.25 5-7c0-2.76-2.24-5-5-5z" stroke="currentColor" stroke-width="1.4"/><circle cx="8" cy="6" r="1.5" stroke="currentColor" stroke-width="1.2"/></svg>陕西省庆阳市华池县采油一厂作业区</span></span></div>
<div class="info-item"><span class="info-item__label">操作人员</span><span class="info-item__value">刘旭</span></div>
<div class="info-item"><span class="info-item__label">专业人员</span><span class="info-item__value">张旭</span></div>
<div class="info-item"><span class="info-item__label">工单状态</span><span class="info-item__value"><span class="tag tag-blue">进行中</span></span></div>
</div>
</div>
<!-- 救助信息 -->
<div class="card">
<div class="card__title">救助信息</div>
<div class="card" id="rescue-info-card">
<div class="card__header" onclick="toggleCard('rescue-info-card', this)">
<div class="card__title">救助信息</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="desc-block">
<div class="desc-block__label">救助意见:</div>
<div class="desc-block__text">-</div>
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="desc-block__label">救助意见:</div>
</div>
<div class="desc-block__text" id="advice-text">-</div>
</div>
<div class="desc-block">
<div class="desc-block__label">救助结果:</div>
@@ -96,33 +141,142 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<li class="summary-item"><span class="summary-item__idx">2.</span><span>已联系操作人员安排驻场人员前往现场协助转运</span></li>
</ul>
</div>
</div>
</div>
<!-- 派单信息 -->
<div class="card">
<div class="card__title">派单信息</div>
<div class="card" id="dispatch-info-card">
<div class="card__header" onclick="toggleCard('dispatch-info-card', this)">
<div class="card__title">派单信息</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="empty-state">
<div class="empty-state__icon">📋</div>
<div class="empty-state__text">暂无派单信息</div>
</div>
</div>
</div>
<!-- 救护车支援 -->
<div class="card">
<div class="card__title">救护车支援</div>
<div class="card" id="ambulance-card">
<div class="card__header" onclick="toggleCard('ambulance-card', this)">
<div class="card__title">救护车支援</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="empty-state">
<div class="empty-state__icon">🚑</div>
<div class="empty-state__text">暂无救护车支援信息</div>
</div>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 编辑弹窗 -->
<div id="edit-mask" class="edit-mask" onclick="closeEditAdvice(event)">
<div class="edit-dialog">
<div class="edit-dialog__head">编辑救助意见</div>
<div class="edit-dialog__body">
<textarea id="edit-textarea" class="edit-dialog__textarea" placeholder="请输入救助意见..."></textarea>
</div>
<div class="edit-dialog__actions">
<button class="edit-dialog__btn edit-dialog__btn--cancel" onclick="closeEditAdvice(event)">取消</button>
<button class="edit-dialog__btn edit-dialog__btn--confirm" onclick="saveEditAdvice(event)">保存</button>
</div>
</div>
</div>
<!-- 底部结束应急按钮 -->
<div id="end-bar" class="end-bar">
<button class="end-btn" onclick="openEndConfirm()">结束应急</button>
</div>
<!-- 结束应急确认弹窗 -->
<div id="confirm-mask" class="confirm-mask" onclick="closeEndConfirm(event)">
<div class="confirm-dialog">
<div class="confirm-dialog__head">是否确认结束应急</div>
<div class="confirm-dialog__body">结束后本次应急工单将更改为"已完成"状态。</div>
<div class="confirm-dialog__actions">
<button class="confirm-dialog__btn confirm-dialog__btn--cancel" onclick="closeEndConfirm(event)">取消</button>
<button class="confirm-dialog__btn confirm-dialog__btn--confirm" onclick="confirmEndEmergency(event)">确认结束</button>
</div>
</div>
</div>
</div>
<script>
sessionStorage.setItem('appRole', 'professional');
function goBackToSource() {
var src = sessionStorage.getItem('professionalWorkOrderOngoingSource');
if (src) {
location.href = src;
return;
}
location.href = './index.html';
}
// 读取并显示求助方式
var method = sessionStorage.getItem('professionalWorkOrderMethod');
if (method) {
document.getElementById('help-method').textContent = method;
// 如果求助方式是"应急电话"或"拨打120",显示结束应急按钮
if (method === '应急电话' || method === '拨打120') {
document.getElementById('end-bar').style.display = 'block';
}
}
function toggleCard(cardId, headerEl) {
var card = document.getElementById(cardId);
if (!card) return;
card.classList.toggle('card--collapsed');
var isCollapsed = card.classList.contains('card--collapsed');
var toggle = headerEl ? headerEl.querySelector('.card__toggle span') : null;
if (toggle) toggle.textContent = isCollapsed ? '展开' : '收起';
}
function openEditAdvice() {
var text = document.getElementById('advice-text').textContent;
document.getElementById('edit-textarea').value = text === '-' ? '' : text;
document.getElementById('edit-mask').classList.add('show');
}
function closeEditAdvice(event) {
if (event) {
event.stopPropagation();
if (event.currentTarget !== event.target && !event.target.classList.contains('edit-dialog__btn--cancel')) return;
}
document.getElementById('edit-mask').classList.remove('show');
}
function saveEditAdvice(event) {
event.stopPropagation();
var text = document.getElementById('edit-textarea').value.trim();
document.getElementById('advice-text').textContent = text || '-';
closeEditAdvice();
}
function openEndConfirm() {
document.getElementById('confirm-mask').classList.add('show');
}
function closeEndConfirm(event) {
if (event) {
event.stopPropagation();
if (event.currentTarget !== event.target && !event.target.classList.contains('confirm-dialog__btn--cancel')) return;
}
document.getElementById('confirm-mask').classList.remove('show');
}
function confirmEndEmergency(event) {
event.stopPropagation();
sessionStorage.setItem('professionalWorkbenchTab', 'done');
location.href = './index.html';
}
</script>
</body>
</html>
@@ -16,8 +16,15 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.card { background: #fff; border-radius: 12px; margin: 12px 16px 0; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.card__title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
.card { background: #fff; border-radius: 12px; margin: 12px 16px 0; box-shadow: 0 1px 4px rgba(0,0,0,0.06); overflow: hidden; }
.card__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
.card__title { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.card__toggle { display: inline-flex; align-items: center; gap: 4px; color: #1677ff; font-size: 12px; font-weight: 600; }
.card__arrow { width: 14px; height: 14px; transition: transform .2s ease; }
.card__body { padding: 0 16px 16px; }
.card--collapsed .card__body { display: none; }
.card--collapsed .card__arrow { transform: rotate(-90deg); }
.card--collapsed .card__header { border-bottom: none; }
.info-item { display: flex; font-size: 13px; line-height: 1.6; padding: 5px 0; }
.info-item__label { color: #999; min-width: 72px; flex-shrink: 0; }
.info-item__value { color: #333; flex: 1; word-break: break-all; }
@@ -68,8 +75,12 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="scroll-content">
<!-- 基本信息 -->
<div class="card">
<div class="card__title">基本信息</div>
<div class="card" id="basic-info-card">
<div class="card__header" onclick="toggleCard('basic-info-card', this)">
<div class="card__title">基本信息</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="info-item"><span class="info-item__label">工单编号</span><span class="info-item__value">YJ5633256</span></div>
<div class="info-item"><span class="info-item__label">工单类型</span><span class="info-item__value">正常派单</span></div>
<div class="info-item"><span class="info-item__label">应急中心</span><span class="info-item__value">西安应急中心</span></div>
@@ -83,11 +94,16 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="info-item"><span class="info-item__label">专业人员</span><span class="info-item__value">刘旭</span></div>
<div class="info-item"><span class="info-item__label">工单状态</span><span class="info-item__value"><span class="tag tag-green">已完成</span></span></div>
<div class="info-item"><span class="info-item__label">完成时间</span><span class="info-item__value">2026-03-21 16:45:00</span></div>
</div>
</div>
<!-- 救助信息 -->
<div class="card">
<div class="card__title">救助信息</div>
<div class="card" id="rescue-info-card">
<div class="card__header" onclick="toggleCard('rescue-info-card', this)">
<div class="card__title">救助信息</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="desc-block">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="desc-block__label">救助意见:</div>
@@ -115,11 +131,16 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<li class="summary-item"><span class="summary-item__idx">3.</span><span>已完成头颅CT检测,员工由同事留守陪护,已告知家属及部门负责人</span></li>
</ul>
</div>
</div>
</div>
<!-- 派单信息 -->
<div class="card">
<div class="card__title">派单信息</div>
<div class="card" id="dispatch-info-card">
<div class="card__header" onclick="toggleCard('dispatch-info-card', this)">
<div class="card__title">派单信息</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="info-item"><span class="info-item__label">派单时间</span><span class="info-item__value">2026-03-21 15:18:00</span></div>
<div class="info-item"><span class="info-item__label">驻场人员</span><span class="info-item__value">王希(138-9201-3001</span></div>
<div class="info-item"><span class="info-item__label">派单状态</span><span class="info-item__value"><span class="tag tag-green">已完成</span></span></div>
@@ -132,11 +153,16 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="desc-block__label">驻场说明:</div>
<div class="desc-block__text">已经挂号并且开了头颅CT检查单,已陪同就诊完毕,员工状态稳定</div>
</div>
</div>
</div>
<!-- 救护车支援 -->
<div class="card">
<div class="card__title">救护车支援</div>
<div class="card" id="ambulance-card">
<div class="card__header" onclick="toggleCard('ambulance-card', this)">
<div class="card__title">救护车支援</div>
<div class="card__toggle"><span>收起</span><svg class="card__arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l5 5 5-5"/></svg></div>
</div>
<div class="card__body">
<div class="info-item"><span class="info-item__label">车牌号</span><span class="info-item__value">陕A·120-01</span></div>
<div class="info-item"><span class="info-item__label">所属医疗点</span><span class="info-item__value">长庆油田职工医疗点</span></div>
<div class="info-item"><span class="info-item__label">所属机构</span><span class="info-item__value">长庆油田职工医院</span></div>
@@ -144,6 +170,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="info-item"><span class="info-item__label">驾驶人电话</span><span class="info-item__value">138-9101-2001</span></div>
<div class="info-item"><span class="info-item__label">随行人</span><span class="info-item__value">张伟</span></div>
<div class="info-item"><span class="info-item__label">随行人电话</span><span class="info-item__value">138-9201-5001</span></div>
</div>
</div>
<div style="height:16px;"></div>
@@ -202,6 +229,16 @@ function closeEditAdvice(event) {
document.getElementById('edit-mask').classList.remove('show');
}
function toggleCard(cardId, headerEl) {
var card = document.getElementById(cardId);
if (!card) return;
card.classList.toggle('card--collapsed');
var isCollapsed = card.classList.contains('card--collapsed');
var toggle = headerEl ? headerEl.querySelector('.card__toggle span') : null;
if (toggle) toggle.textContent = isCollapsed ? '展开' : '收起';
}
function saveEditAdvice(event) {
event.stopPropagation();
var text = document.getElementById('edit-textarea').value.trim();
+16 -2
View File
@@ -154,7 +154,7 @@
<!-- [交互] 导航栏:返回按钮 + 标题 + 右侧消息图标 -->
<div class="nav-bar">
<span class="nav-back" style="cursor:default;color:#999;">← 返回</span>
<span class="nav-back" onclick="goBackFromSource()">← 返回</span>
<span class="nav-title">健康咨询</span>
</div>
@@ -261,6 +261,20 @@
<div class="bottom-spacer"></div>
</div><!-- /.phone-shell -->
<!-- index.html 是模块入口页,返回按钮仅作占位,无点击事件 -->
<script>
function goBackFromSource() {
var params = new URLSearchParams(window.location.search);
var from = params.get('from');
if (from === 'index') {
window.location.href = '../home/index.html';
return;
}
if (document.referrer) {
history.back();
return;
}
window.location.href = '../home/index.html';
}
</script>
</body>
</html>
+419
View File
@@ -0,0 +1,419 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>应用主页 - 应用 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f5f7fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; background: #fff; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #333; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 功能按钮区 */
.func-area { background: #fff; padding: 16px 12px 12px; }
.func-grid { display: grid; grid-template-columns: repeat(5, 1fr); row-gap: 16px; }
.func-grid.collapsed .func-item:nth-child(n+6) { display: none; }
.func-item { display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; text-decoration: none; }
.func-item__icon { width: 44px; height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; }
.func-item__icon svg { width: 24px; height: 24px; }
.func-item__name { font-size: 11px; color: #333; text-align: center; }
.func-toggle { display: flex; align-items: center; justify-content: center; gap: 4px; font-size: 12px; color: #999; margin-top: 8px; cursor: pointer; user-select: none; }
.func-toggle svg { width: 12px; height: 12px; }
/* 模块通用 */
.module { margin: 10px 12px 0; background: #fff; border-radius: 12px; padding: 16px; }
.module__hd { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.module__title { font-size: 16px; font-weight: 700; color: #1a1a1a; }
.module__more { font-size: 12px; color: #999; cursor: pointer; }
/* 体重管理 */
.weight-stats { display: flex; justify-content: space-around; text-align: center; margin-bottom: 16px; }
.weight-stats__item span { display: block; }
.weight-stats__val { font-size: 18px; font-weight: 700; color: #1a1a1a; }
.weight-stats__label { font-size: 11px; color: #999; margin-top: 2px; }
.weight-ball-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 16px; }
.weight-side { width: 74px; text-align: center; flex-shrink: 0; }
.weight-side__label { font-size: 12px; color: #666; margin-bottom: 6px; }
.weight-side__val { font-size: 32px; line-height: 1; font-weight: 700; color: #1a1a1a; }
.weight-side__unit { font-size: 12px; color: #999; margin-top: 4px; }
.weight-ball { width: 140px; height: 140px; margin: 0; border-radius: 50%; background: linear-gradient(180deg, #e6f7ff 0%, #1890ff 100%); position: relative; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 20px rgba(24,144,255,0.3); }
.weight-ball__inner { width: 110px; height: 110px; border-radius: 50%; background: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.weight-ball__val { font-size: 28px; font-weight: 800; color: #1890ff; }
.weight-ball__unit { font-size: 12px; color: #999; }
.weight-ball__wave { position: absolute; bottom: 0; left: 0; right: 0; height: 60%; border-radius: 0 0 70px 70px; background: linear-gradient(180deg, rgba(24,144,255,0.15), rgba(24,144,255,0.4)); overflow: hidden; }
.daily-date { font-size: 14px; color: #666; text-align: center; margin-bottom: 10px; }
.daily-checkin { display: flex; justify-content: space-around; margin-bottom: 14px; }
.checkin-item { text-align: center; flex: 1; }
.checkin-item__icon { font-size: 24px; margin-bottom: 4px; }
.checkin-item__label { font-size: 11px; color: #999; }
.checkin-item__val { font-size: 13px; font-weight: 600; color: #333; margin-top: 2px; }
.checkin-item__sub { font-size: 11px; color: #999; margin-top: 2px; line-height: 1.4; }
.bmi-box { background: #f5f7fa; border-radius: 8px; padding: 12px; text-align: center; }
.bmi-box__formula { font-size: 13px; color: #666; }
.bmi-box__result { font-size: 20px; font-weight: 700; color: #52c41a; margin-top: 4px; }
.bmi-box__tag { display: inline-block; font-size: 11px; color: #52c41a; background: #f6ffed; border-radius: 4px; padding: 2px 8px; margin-top: 4px; }
/* 健康数据 */
.health-scroll { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.health-scroll::-webkit-scrollbar { display: none; }
.health-card { min-width: 130px; background: #f5f7fa; border-radius: 10px; padding: 14px 12px; flex-shrink: 0; }
.health-card__title { font-size: 12px; color: #999; margin-bottom: 6px; }
.health-card__val { font-size: 20px; font-weight: 700; color: #1a1a1a; }
.health-card__unit { font-size: 11px; color: #999; font-weight: 400; }
.health-card__status { font-size: 11px; margin-top: 4px; padding: 2px 6px; border-radius: 4px; display: inline-block; }
.health-card__status--normal { color: #52c41a; background: #f6ffed; }
.health-card__status--warn { color: #faad14; background: #fffbe6; }
/* 智能监测 */
.monitor-tabs { display: flex; gap: 0; margin-bottom: 12px; background: #f5f7fa; border-radius: 8px; overflow: hidden; }
.monitor-tab { flex: 1; text-align: center; padding: 8px 0; font-size: 12px; color: #666; cursor: pointer; transition: all .2s; }
.monitor-tab.active { background: #1890ff; color: #fff; border-radius: 8px; }
.monitor-chart { background: linear-gradient(180deg, #fff, #f8fbff); border: 1px solid #e6f4ff; border-radius: 12px; padding: 12px; margin-bottom: 12px; }
.monitor-chart__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.monitor-chart__title { font-size: 13px; font-weight: 600; color: #333; }
.monitor-chart__time { font-size: 11px; color: #999; }
.monitor-chart__svg { width: 100%; height: 132px; display: block; }
.monitor-chart__axis { display: flex; justify-content: space-between; font-size: 10px; color: #bbb; margin-top: 4px; }
.monitor-data { display: flex; justify-content: space-between; gap: 8px; text-align: center; }
.monitor-data__item { flex: 1; background: #f5f7fa; border-radius: 10px; padding: 10px 6px; }
.monitor-data__item span { display: block; }
.monitor-data__val { font-size: 22px; font-weight: 700; color: #1a1a1a; line-height: 1.1; }
.monitor-data__unit { font-size: 11px; color: #999; margin-top: 2px; }
.monitor-data__label { font-size: 11px; color: #666; margin-top: 6px; }
.monitor-data__time { font-size: 10px; color: #999; margin-top: 2px; }
/* 膳食摄入 */
.diet-module { padding: 0; overflow: hidden; background: linear-gradient(180deg, #efeaff 0%, #ffffff 26%); }
.diet-module .module__hd { padding: 16px 16px 12px; margin-bottom: 0; }
.diet-module .module__more { color: #8f8f8f; font-weight: 600; }
.diet-panel { background: #fff; border-radius: 18px 18px 0 0; padding-bottom: 16px; }
.diet-month-bar { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px; border-bottom: 1px solid #f2f2f2; }
.diet-month-arrow { width: 24px; height: 24px; border-radius: 50%; background: #f7f7f7; color: #999; display: flex; align-items: center; justify-content: center; font-size: 16px; cursor: pointer; }
.diet-month-title { font-size: 17px; font-weight: 700; color: #1f1f1f; }
.diet-days { display: grid; grid-template-columns: repeat(7, 1fr); row-gap: 12px; padding: 18px 16px 10px; margin-bottom: 6px; }
.diet-day { text-align: center; background: transparent; padding: 0; border-radius: 0; cursor: pointer; }
.diet-day__week { font-size: 13px; font-weight: 600; color: #333; margin-bottom: 10px; }
.diet-day__num { width: 38px; height: 38px; margin: 0 auto; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; color: #333; }
.diet-day.active .diet-day__num { border: 2px solid #8b6cff; color: #8b6cff; background: #fff; }
.diet-day--today .diet-day__week { color: #8b6cff; }
.diet-day--today .diet-day__num { background: #8b6cff; color: #fff; }
.diet-orbs { display: flex; gap: 16px; padding: 10px 16px 18px; }
.diet-orb { flex: 1; aspect-ratio: 1 / 1; border-radius: 50%; background: radial-gradient(circle at 50% 35%, #ffffff 0%, #f5fff7 42%, #dafbe2 72%, #c0f2cf 100%); box-shadow: inset 0 0 0 10px rgba(115, 221, 137, 0.18), 0 4px 18px rgba(115, 221, 137, 0.18); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; text-align: center; }
.diet-orb::after { content: ''; position: absolute; left: -8%; right: -8%; bottom: -4%; height: 32%; background: #3fd266; border-radius: 50% 50% 0 0 / 100% 100% 0 0; }
.diet-orb__content { position: relative; z-index: 1; padding: 0 10px; }
.diet-orb__top { font-size: 12px; line-height: 1.45; color: #36a454; margin-bottom: 10px; }
.diet-orb__value { font-size: 28px; line-height: 1; font-weight: 800; color: #1f1f1f; }
.diet-orb__unit { font-size: 13px; color: #36a454; margin-left: 4px; }
.diet-orb__bottom { margin-top: 12px; font-size: 12px; color: rgba(0,0,0,0.28); }
.diet-summary { display: flex; gap: 12px; padding: 0 16px; }
.diet-summary__card { flex: 1; background: #f7f7f7; border-radius: 14px; padding: 14px 10px 12px; text-align: center; }
.diet-summary__value { font-size: 18px; font-weight: 800; color: #1f1f1f; line-height: 1.2; }
.diet-summary__meta { font-size: 12px; color: #58c96d; margin-top: 6px; min-height: 18px; }
.diet-summary__label { font-size: 12px; color: #808080; margin-top: 8px; }
/* 底部 Tab */
.tab-bar { height: 83px; background: #fff; border-top: 1px solid #e8e8e8; display: flex; align-items: flex-start; padding-top: 8px; flex-shrink: 0; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; padding: 4px 0; }
.tab-item .tab-icon { font-size: 22px; line-height: 1; }
.tab-item .tab-label { font-size: 10px; color: #999; font-weight: 500; }
.tab-item.active .tab-label { color: #1677ff; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="scroll-content">
<!-- 功能按钮区 -->
<div class="func-area">
<div class="func-grid collapsed" id="funcGrid">
<!-- JS 渲染 -->
</div>
<div class="func-toggle" id="funcToggle">
<span>展开</span>
<svg viewBox="0 0 12 12" fill="none" stroke="#999" stroke-width="1.5"><path d="M2 4.5l4 4 4-4"/></svg>
</div>
</div>
<!-- 体重管理模块 -->
<div class="module">
<div class="module__hd"><span class="module__title">体重管理</span><span class="module__more">查看详情 </span></div>
<div class="weight-stats">
<div class="weight-stats__item"><span class="weight-stats__val">28</span><span class="weight-stats__label">活动天数</span></div>
<div class="weight-stats__item"><span class="weight-stats__val">156</span><span class="weight-stats__label">排名</span></div>
</div>
<div class="weight-ball-row">
<div class="weight-side">
<div class="weight-side__label">初始体重</div>
<div class="weight-side__val">75.0</div>
<div class="weight-side__unit">BMI:30</div>
</div>
<div class="weight-ball">
<div class="weight-ball__wave"></div>
<div class="weight-ball__inner">
<span class="weight-ball__val">70.5</span>
<span class="weight-ball__unit">当前体重(kg)</span>
<span class="weight-ball__unit">BMI:28</span>
<span class="weight-ball__unit">体脂率:26</span>
</div>
</div>
<div class="weight-side">
<div class="weight-side__label">目标体重</div>
<div class="weight-side__val">65.0</div>
<div class="weight-side__unit">BMI:25</div>
</div>
</div>
<div class="bmi-box">
<div class="bmi-box__formula">BMI = 体重(kg) ÷ 身高(m)² = 70.5 ÷ 1.75² </div>
<div class="bmi-box__formula">BMI<18.5消瘦 BMI<24正常 BMI<28超重 BMI>32肥胖</div>
<span class="bmi-box__tag">中心型肥胖:男性腰围≥90cm 女性腰围≥85cm</span>
</div>
<div class="daily-date">📅 2026年4月29日 星期三</div>
<div class="daily-checkin">
<div class="checkin-item"><div class="checkin-item__icon">🍽️</div><div class="checkin-item__label"></div><div class="checkin-item__val">1680 kcal</div><div class="checkin-item__sub">推荐 1900 kcal</div><div class="checkin-item__sub">累计行动 52 天</div></div>
<div class="checkin-item"><div class="checkin-item__icon">🏃</div><div class="checkin-item__label"></div><div class="checkin-item__val">320 kcal</div><div class="checkin-item__sub">推荐 300 kcal</div><div class="checkin-item__sub">累计行动 36 天</div></div>
<div class="checkin-item"><div class="checkin-item__icon">⚖️</div><div class="checkin-item__label">体重</div><div class="checkin-item__val">70.5 kg</div><div class="checkin-item__sub">推荐 68.0 kg</div><div class="checkin-item__sub">累计行动 28 天</div></div>
</div>
</div>
<!-- 健康数据模块 -->
<div class="module">
<div class="module__hd"><span class="module__title">健康数据</span><span class="module__more">全部 </span></div>
<div class="health-scroll">
<div class="health-card"><div class="health-card__title">血压</div><div class="health-card__val">120<span class="health-card__unit">/80 mmHg</span></div><span class="health-card__status health-card__status--normal">正常</span></div>
<div class="health-card"><div class="health-card__title">体重</div><div class="health-card__val">70.5<span class="health-card__unit"> kg</span></div><span class="health-card__status health-card__status--normal">正常</span></div>
<div class="health-card"><div class="health-card__title">血脂</div><div class="health-card__val">4.8<span class="health-card__unit"> mmol/L</span></div><span class="health-card__status health-card__status--normal">正常</span></div>
<div class="health-card"><div class="health-card__title">血糖</div><div class="health-card__val">5.6<span class="health-card__unit"> mmol/L</span></div><span class="health-card__status health-card__status--warn">偏高</span></div>
<div class="health-card"><div class="health-card__title">尿酸</div><div class="health-card__val">380<span class="health-card__unit"> μmol/L</span></div><span class="health-card__status health-card__status--normal">正常</span></div>
</div>
</div>
<!-- 智能监测模块 -->
<div class="module">
<div class="module__hd"><span class="module__title">智能监测</span><span class="module__more">查看设备 </span></div>
<div class="monitor-tabs" id="monitorTabs">
<div class="monitor-tab active" data-type="heart">心率</div>
<div class="monitor-tab" data-type="oxygen">血氧</div>
<div class="monitor-tab" data-type="stress">压力</div>
<div class="monitor-tab" data-type="temp">体温</div>
<div class="monitor-tab" data-type="sleep">睡眠</div>
</div>
<div class="monitor-chart">
<div class="monitor-chart__head">
<span class="monitor-chart__title" id="monitorChartTitle">最近24小时心率波动</span>
<span class="monitor-chart__time">2026-03-24 13:02</span>
</div>
<svg class="monitor-chart__svg" id="monitorChart" viewBox="0 0 320 132" preserveAspectRatio="none"></svg>
<div class="monitor-chart__axis"><span>00:00</span><span>06:00</span><span>12:00</span><span>18:00</span><span>24:00</span></div>
</div>
<div class="monitor-data" id="monitorData">
<div class="monitor-data__item"><span class="monitor-data__val">126</span><span class="monitor-data__unit">次/分</span><span class="monitor-data__label">最高</span><span class="monitor-data__time">14:20</span></div>
<div class="monitor-data__item"><span class="monitor-data__val">68</span><span class="monitor-data__unit">次/分</span><span class="monitor-data__label">最低</span><span class="monitor-data__time">03:40</span></div>
<div class="monitor-data__item"><span class="monitor-data__val">82</span><span class="monitor-data__unit">次/分</span><span class="monitor-data__label">平均</span><span class="monitor-data__time">24h均值</span></div>
</div>
</div>
<!-- 膳食摄入模块 -->
<div class="module diet-module">
<div class="module__hd"><span class="module__title">膳食摄入</span><span class="module__more">更多 </span></div>
<div class="diet-panel">
<div class="diet-month-bar">
<div class="diet-month-arrow" id="prevWeek"></div>
<div class="diet-month-title" id="weekText">2026年03月</div>
<div class="diet-month-arrow" id="nextWeek"></div>
</div>
<div class="diet-days" id="dietDays">
<div class="diet-day"><div class="diet-day__week"></div><div class="diet-day__num">10</div></div>
<div class="diet-day"><div class="diet-day__week"></div><div class="diet-day__num">11</div></div>
<div class="diet-day"><div class="diet-day__week"></div><div class="diet-day__num">12</div></div>
<div class="diet-day"><div class="diet-day__week"></div><div class="diet-day__num">13</div></div>
<div class="diet-day diet-day--today active"><div class="diet-day__week"></div><div class="diet-day__num"></div></div>
<div class="diet-day"><div class="diet-day__week"></div><div class="diet-day__num">15</div></div>
<div class="diet-day"><div class="diet-day__week"></div><div class="diet-day__num">16</div></div>
</div>
<div class="diet-orbs">
<div class="diet-orb">
<div class="diet-orb__content">
<div class="diet-orb__top">早餐可摄入<br>580千卡</div>
<div><span class="diet-orb__value">586</span><span class="diet-orb__unit">千卡</span></div>
<div class="diet-orb__bottom">早餐已摄入</div>
</div>
</div>
<div class="diet-orb">
<div class="diet-orb__content">
<div class="diet-orb__top">全天可摄入<br>2536千卡</div>
<div><span class="diet-orb__value">586</span><span class="diet-orb__unit">千卡</span></div>
<div class="diet-orb__bottom">全天已摄入</div>
</div>
</div>
</div>
<div class="diet-summary" id="dietDetail">
<div class="diet-summary__card">
<div class="diet-summary__value">586</div>
<div class="diet-summary__meta">70%(适量)</div>
<div class="diet-summary__label">早餐(kcal)</div>
</div>
<div class="diet-summary__card">
<div class="diet-summary__value">--</div>
<div class="diet-summary__meta">--</div>
<div class="diet-summary__label">午餐(kcal)</div>
</div>
<div class="diet-summary__card">
<div class="diet-summary__value">--</div>
<div class="diet-summary__meta">--</div>
<div class="diet-summary__label">晚餐(kcal)</div>
</div>
</div>
</div>
</div>
</div>
<!-- 底部 Tab 栏 -->
<div class="tab-bar">
<div class="tab-item active"><span class="tab-icon">🏠</span><span class="tab-label">应用</span></div>
<div class="tab-item"><span class="tab-icon">📁</span><span class="tab-label">档案</span></div>
<div class="tab-item"><span class="tab-icon">🤖</span><span class="tab-label">AI助手</span></div>
<div class="tab-item"><span class="tab-icon">📚</span><span class="tab-label">知识</span></div>
<div class="tab-item" onclick="location.href='../profile/index.html'"><span class="tab-icon">👤</span><span class="tab-label">我的</span></div>
</div>
</div>
<script>
var funcItems = [
{ name: '健康咨询', color: '#1890FF', icon: '💬', href: '../health-consult/index.html' },
{ name: '应急就医', color: '#FF4D4F', icon: '🚑', href: '../emergency-medical/index.html' },
{ name: '一线医疗', color: '#52C41A', icon: '🏥', href: '../frontline-medical/index.html' },
{ name: '健康体检', color: '#FAAD14', icon: '📋', href: '../health-checkup/index.html' },
{ name: '体重管理', color: '#722ED1', icon: '⚖️', href: '../weight-management/index.html' },
{ name: '营养管理', color: '#52C41A', icon: '🥗', href: '../nutrition/index.html' },
{ name: '运动管理', color: '#FA8C16', icon: '🏃', href: '../exercise/index.html' },
{ name: '健康监测', color: '#13C2C2', icon: '💓', href: '../health-monitor/index.html' },
{ name: '健康评估', color: '#2F54EB', icon: '📊', href: '../health-assessment/index.html' },
{ name: '健康档案', color: '#1890FF', icon: '📁', href: '../health-record/index.html' },
{ name: '心脑血管', color: '#F5222D', icon: '❤️', href: '../cardiovascular/index.html' },
{ name: '糖尿病', color: '#722ED1', icon: '💉', href: '../diabetes/index.html' },
{ name: '癌症预防', color: '#EB2F96', icon: '🛡️', href: '../cancer/index.html' },
{ name: '知识普及', color: '#1890FF', icon: '📖', href: '../knowledge/index.html' }
];
var monitorMap = {
heart: {
title: '最近24小时心率波动',
unit: '次/分',
max: '126',
maxTime: '14:20',
min: '68',
minTime: '03:40',
avg: '82',
avgTime: '24h均值',
color: '#ff7a8a',
points: [84,82,80,78,76,74,72,70,68,75,79,83,88,96,102,126,108,94,90,87,85,84,83,82]
},
oxygen: {
title: '最近24小时血氧波动',
unit: '%',
max: '99',
maxTime: '10:10',
min: '95',
minTime: '02:50',
avg: '97',
avgTime: '24h均值',
color: '#52c41a',
points: [97,97,96,96,95,96,96,97,98,98,97,97,98,99,98,97,97,96,97,98,98,97,97,97]
},
stress: {
title: '最近24小时压力波动',
unit: '',
max: '82',
maxTime: '16:30',
min: '18',
minTime: '05:10',
avg: '46',
avgTime: '24h均值',
color: '#faad14',
points: [35,32,30,28,24,18,22,26,34,40,52,60,68,74,82,78,66,58,49,45,41,38,36,34]
},
temp: {
title: '最近24小时体温波动',
unit: '°C',
max: '37.1',
maxTime: '13:00',
min: '36.2',
minTime: '04:20',
avg: '36.6',
avgTime: '24h均值',
color: '#722ed1',
points: [36.5,36.4,36.4,36.3,36.2,36.2,36.3,36.4,36.5,36.5,36.6,36.7,36.8,37.1,36.9,36.8,36.7,36.6,36.6,36.5,36.5,36.4,36.4,36.5]
},
sleep: {
title: '最近24小时睡眠深度波动',
unit: 'h',
max: '8.1',
maxTime: '07:00',
min: '5.3',
minTime: '01:30',
avg: '6.8',
avgTime: '24h均值',
color: '#1890ff',
points: [5.6,5.3,5.5,5.9,6.2,6.8,7.1,8.1,7.8,7.4,7.0,6.8,6.6,6.4,6.2,6.1,6.3,6.5,6.7,6.8,6.9,6.8,6.7,6.8]
}
};
function renderMonitorChart(type) {
var cfg = monitorMap[type];
var svg = document.getElementById('monitorChart');
var title = document.getElementById('monitorChartTitle');
title.textContent = cfg.title;
var points = cfg.points;
var max = Math.max.apply(null, points);
var min = Math.min.apply(null, points);
var width = 320;
var height = 132;
var step = width / (points.length - 1);
var line = points.map(function(v, i) {
var x = i * step;
var y = height - ((v - min) / (max - min || 1)) * 92 - 20;
return x.toFixed(1) + ',' + y.toFixed(1);
}).join(' ');
var area = '0,' + height + ' ' + line + ' ' + width + ',' + height;
svg.innerHTML = '<defs><linearGradient id="monitorGradient" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="' + cfg.color + '" stop-opacity="0.35"/><stop offset="100%" stop-color="' + cfg.color + '" stop-opacity="0.02"/></linearGradient></defs>' +
'<polyline points="' + line + '" fill="none" stroke="' + cfg.color + '" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>' +
'<polygon points="' + area + '" fill="url(#monitorGradient)"/>';
document.getElementById('monitorData').innerHTML =
'<div class="monitor-data__item"><span class="monitor-data__val">' + cfg.max + '</span><span class="monitor-data__unit">' + cfg.unit + '</span><span class="monitor-data__label">最高</span><span class="monitor-data__time">' + cfg.maxTime + '</span></div>' +
'<div class="monitor-data__item"><span class="monitor-data__val">' + cfg.min + '</span><span class="monitor-data__unit">' + cfg.unit + '</span><span class="monitor-data__label">最低</span><span class="monitor-data__time">' + cfg.minTime + '</span></div>' +
'<div class="monitor-data__item"><span class="monitor-data__val">' + cfg.avg + '</span><span class="monitor-data__unit">' + cfg.unit + '</span><span class="monitor-data__label">平均</span><span class="monitor-data__time">' + cfg.avgTime + '</span></div>';
}
function renderFuncGrid() {
var grid = document.getElementById('funcGrid');
var toggle = document.getElementById('funcToggle');
var html = '';
funcItems.forEach(function(item) {
html += '<a class="func-item" href="' + item.href + '"><div class="func-item__icon" style="background:' + item.color + '"><span style="font-size:22px">' + item.icon + '</span></div><div class="func-item__name">' + item.name + '</div></a>';
});
html += '<div class="func-item"><div class="func-item__icon" style="background:#52c41a"><span style="font-size:22px">▦</span></div><div class="func-item__name">更多</div></div>';
grid.innerHTML = html;
toggle.onclick = function() {
var collapsed = grid.classList.toggle('collapsed');
toggle.innerHTML = collapsed
? '<span>展开</span><svg viewBox="0 0 12 12" fill="none" stroke="#999" stroke-width="1.5"><path d="M2 4.5l4 4 4-4"/></svg>'
: '<span>收起</span><svg viewBox="0 0 12 12" fill="none" stroke="#999" stroke-width="1.5"><path d="M2 7.5l4-4 4 4"/></svg>';
};
}
renderFuncGrid();
document.querySelectorAll('.monitor-tab').forEach(function(tab) {
tab.onclick = function() {
document.querySelectorAll('.monitor-tab').forEach(function(t) { t.classList.remove('active'); });
tab.classList.add('active');
renderMonitorChart(tab.dataset.type);
};
});
renderMonitorChart('heart');
document.querySelectorAll('.diet-day').forEach(function(day) {
day.onclick = function() {
document.querySelectorAll('.diet-day').forEach(function(d) {
if (!d.classList.contains('diet-day--today')) d.classList.remove('active');
else if (d !== day) d.classList.remove('active');
});
day.classList.add('active');
};
});
</script>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 修改头像 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #000; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #fff; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: transparent; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #fff; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #fff; }
.avatar-area { flex: 1; display: flex; align-items: center; justify-content: center; }
.avatar-area__img { width: 280px; height: 280px; border-radius: 50%; background: linear-gradient(135deg, #e6f4ff, #91caff); display: flex; align-items: center; justify-content: center; font-size: 120px; overflow: hidden; }
/* 底部操作面板 */
.action-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 16px 16px 0 0; padding-bottom: 34px; z-index: 100; }
.action-sheet__item { padding: 16px; text-align: center; font-size: 16px; color: #1a1a1a; cursor: pointer; border-bottom: 1px solid #f5f5f5; }
.action-sheet__item:active { background: #f5f5f5; }
.action-sheet__cancel { padding: 16px; text-align: center; font-size: 16px; color: #999; cursor: pointer; margin-top: 8px; border-top: 8px solid #f5f5f5; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-title">头像</span>
</div>
<div class="avatar-area">
<div class="avatar-area__img">👤</div>
</div>
<!-- 底部操作面板:从手机相册选择 / 拍照 -->
<div class="action-sheet">
<div class="action-sheet__item">从手机相册选择</div>
<div class="action-sheet__item">拍照</div>
<div class="action-sheet__cancel" onclick="history.back()">取消</div>
</div>
</div>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 查看头像 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #000; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #fff; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: transparent; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #fff; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #fff; }
.avatar-area { flex: 1; display: flex; align-items: center; justify-content: center; }
.avatar-area__img { width: 280px; height: 280px; border-radius: 50%; background: linear-gradient(135deg, #e6f4ff, #91caff); display: flex; align-items: center; justify-content: center; font-size: 120px; overflow: hidden; }
.avatar-area__img img { width: 100%; height: 100%; object-fit: cover; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-title">头像</span>
</div>
<!-- 头像大图展示 -->
<div class="avatar-area">
<div class="avatar-area__img">👤</div>
</div>
<div style="height:34px;flex-shrink:0;"></div>
</div>
</body>
</html>
+75
View File
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 修改密码 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.form-area { padding: 32px 24px; }
.form-area__title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin-bottom: 24px; }
.input-group { margin-bottom: 20px; }
.input-group__label { font-size: 14px; color: #666; margin-bottom: 8px; }
.input-group__field { display: flex; align-items: center; border-bottom: 1px solid #e8e8e8; padding: 10px 0; }
.input-group__field input { flex: 1; border: none; outline: none; font-size: 16px; color: #1a1a1a; background: transparent; }
.input-group__field input::placeholder { color: #ccc; }
.input-group__field .toggle-eye { font-size: 18px; color: #999; cursor: pointer; }
.pwd-hint { font-size: 12px; color: #999; margin-top: 8px; line-height: 1.6; }
.pwd-hint strong { color: #ff4d4f; font-weight: 500; }
.submit-btn { width: 100%; height: 48px; border: none; border-radius: 24px; background: #1677ff; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 32px; }
.submit-btn:active { opacity: 0.85; }
.alt-link { text-align: center; margin-top: 20px; font-size: 13px; color: #1677ff; cursor: pointer; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="location.href='settings.html'"></span>
<span class="nav-title">设置</span>
</div>
<div class="form-area">
<div class="form-area__title">修改密码</div>
<div class="input-group">
<div class="input-group__label">原密码:</div>
<div class="input-group__field">
<input type="password" placeholder="请输入密码">
<span class="toggle-eye">👁</span>
</div>
</div>
<div class="input-group">
<div class="input-group__label">新密码:</div>
<div class="input-group__field">
<input type="password" placeholder="请输入密码">
<span class="toggle-eye">👁</span>
</div>
</div>
<div class="input-group">
<div class="input-group__label">确认密码:</div>
<div class="input-group__field">
<input type="password" placeholder="请输入密码">
<span class="toggle-eye">👁</span>
</div>
</div>
<div class="pwd-hint">密码不能少于12位,必须包含<strong>大小写字母、数字、特殊符号</strong></div>
<button class="submit-btn">确定</button>
<!-- [交互] 忘记密码跳转身份验证 -->
<div class="alt-link" onclick="location.href='forgot-password-verify.html'">忘记密码?</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 身份证验证 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.form-area { padding: 32px 24px; }
.form-area__title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin-bottom: 8px; }
.form-area__desc { font-size: 14px; color: #999; margin-bottom: 32px; line-height: 1.6; }
.input-row { display: flex; align-items: center; border-bottom: 1px solid #e8e8e8; padding: 12px 0; margin-bottom: 8px; }
.input-row input { flex: 1; border: none; outline: none; font-size: 16px; color: #1a1a1a; background: transparent; }
.input-row input::placeholder { color: #ccc; }
.submit-btn { width: 100%; height: 48px; border: none; border-radius: 24px; background: #1677ff; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 32px; }
.submit-btn:active { opacity: 0.85; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-title">设置</span>
</div>
<div class="form-area">
<div class="form-area__title">身份证验证</div>
<div class="form-area__desc">请输入该账号身份证号完成验证</div>
<div class="input-row">
<input type="text" placeholder="请输入身份证">
</div>
<!-- [交互] 验证通过后跳转新手机号验证 -->
<button class="submit-btn" onclick="location.href='change-phone-verify-new.html'">确定</button>
</div>
</div>
</body>
</html>
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 新手机号验证 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.form-area { padding: 32px 24px; }
.form-area__title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin-bottom: 8px; }
.input-row { display: flex; align-items: center; border-bottom: 1px solid #e8e8e8; padding: 12px 0; margin-bottom: 8px; }
.input-row input { flex: 1; border: none; outline: none; font-size: 16px; color: #1a1a1a; background: transparent; }
.input-row input::placeholder { color: #ccc; }
.input-row__btn { font-size: 14px; color: #1677ff; cursor: pointer; white-space: nowrap; padding-left: 12px; }
.submit-btn { width: 100%; height: 48px; border: none; border-radius: 24px; background: #1677ff; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 32px; }
.submit-btn:active { opacity: 0.85; }
.checkbox-row { display: flex; align-items: center; gap: 8px; margin-top: 16px; font-size: 13px; color: #666; }
.checkbox-row input[type="checkbox"] { width: 16px; height: 16px; accent-color: #1677ff; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-title">设置</span>
</div>
<div class="form-area">
<div class="form-area__title">新手机号验证</div>
<div class="input-row">
<input type="tel" placeholder="请输入手机号">
</div>
<div class="input-row">
<input type="text" placeholder="请输入验证码">
<span class="input-row__btn">获取验证码</span>
</div>
<div class="checkbox-row">
<input type="checkbox" checked> 设为默认
</div>
<!-- [交互] 确定后返回手机号列表 -->
<button class="submit-btn" onclick="location.href='phone-list.html'">确定</button>
</div>
</div>
</body>
</html>
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 原手机号验证 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.form-area { padding: 32px 24px; }
.form-area__title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin-bottom: 8px; }
.form-area__desc { font-size: 14px; color: #999; margin-bottom: 32px; line-height: 1.6; }
.input-row { display: flex; align-items: center; border-bottom: 1px solid #e8e8e8; padding: 12px 0; margin-bottom: 8px; }
.input-row input { flex: 1; border: none; outline: none; font-size: 16px; color: #1a1a1a; background: transparent; }
.input-row input::placeholder { color: #ccc; }
.input-row__btn { font-size: 14px; color: #1677ff; cursor: pointer; white-space: nowrap; padding-left: 12px; }
.submit-btn { width: 100%; height: 48px; border: none; border-radius: 24px; background: #1677ff; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 32px; }
.submit-btn:active { opacity: 0.85; }
/* [交互] 手机号无法接收验证码链接 */
.alt-link { text-align: center; margin-top: 20px; font-size: 13px; color: #1677ff; cursor: pointer; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="location.href='phone-list.html'"></span>
<span class="nav-title">设置</span>
</div>
<div class="form-area">
<div class="form-area__title">原手机号验证</div>
<div class="form-area__desc">请输入手机号18545643256验证码,完成手机验证</div>
<div class="input-row">
<input type="text" placeholder="请输入验证码">
<!-- [交互] 获取验证码,120秒倒计时 -->
<span class="input-row__btn">获取验证码</span>
</div>
<!-- [交互] 点击确定跳转 change-phone-verify-new.html -->
<button class="submit-btn" onclick="location.href='change-phone-verify-new.html'">确定</button>
<!-- [交互] 手机号无法接收验证码,跳转身份证验证 -->
<div class="alt-link" onclick="location.href='change-phone-unavailable.html'">手机号无法接收验证码?</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,339 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 紧急联系人 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; padding: 0 0 34px; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 分组 */
.section { margin: 16px 16px 0; }
.section__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.section__title { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.section__hint { font-size: 12px; color: #999; }
.section__add { font-size: 13px; color: #1677ff; cursor: pointer; display: flex; align-items: center; gap: 4px; }
/* 联系人卡片 */
.contact-card { background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.05); position: relative; }
.contact-card__row { display: flex; align-items: center; margin-bottom: 6px; }
.contact-card__row:last-child { margin-bottom: 0; }
.contact-card__label { font-size: 13px; color: #999; width: 60px; flex-shrink: 0; }
.contact-card__value { font-size: 14px; color: #1a1a1a; flex: 1; }
.contact-card__name { font-size: 16px; font-weight: 600; color: #1a1a1a; flex: 1; }
.contact-card__actions { display: flex; gap: 16px; }
.contact-card__btn { font-size: 13px; cursor: pointer; padding: 4px 0; }
.contact-card__btn--edit { color: #1677ff; }
.contact-card__btn--delete { color: #ff4d4f; }
/* 空状态 */
.empty-state { text-align: center; padding: 32px 16px; color: #999; font-size: 14px; }
/* 弹窗遮罩 */
.modal-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-mask.active { display: flex; }
.modal-box { width: 320px; background: #fff; border-radius: 16px; overflow: hidden; max-height: 600px; }
.modal-box__header { padding: 16px 20px; font-size: 17px; font-weight: 700; text-align: center; border-bottom: 1px solid #f0f0f0; position: relative; }
.modal-box__close { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); font-size: 20px; color: #999; cursor: pointer; }
.modal-box__body { padding: 20px; }
.form-group { margin-bottom: 16px; }
.form-group__label { font-size: 14px; color: #666; margin-bottom: 6px; }
.form-group__input { width: 100%; height: 40px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 12px; font-size: 14px; color: #1a1a1a; outline: none; transition: border-color .2s; }
.form-group__input:focus { border-color: #1677ff; }
.form-group__select { width: 100%; height: 40px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 12px; font-size: 14px; color: #1a1a1a; outline: none; background: #fff; }
.modal-box__footer { padding: 0 20px 20px; display: flex; gap: 12px; }
.btn { flex: 1; height: 40px; border-radius: 20px; font-size: 15px; font-weight: 600; cursor: pointer; border: none; }
.btn--cancel { background: #f5f5f5; color: #666; }
.btn--primary { background: #1677ff; color: #fff; }
.btn--primary:active { opacity: 0.85; }
/* 确认弹窗 */
.confirm-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.confirm-box__title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; }
.confirm-box__desc { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; }
.confirm-box__actions { display: flex; border-top: 1px solid #f0f0f0; }
.confirm-box__btn { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; text-align: center; }
.confirm-box__btn--cancel { color: #666; border-right: 1px solid #f0f0f0; }
.confirm-box__btn--danger { color: #ff4d4f; font-weight: 600; }
/* 搜索选择框 */
.search-select__input-wrap { display: flex; align-items: center; gap: 8px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 12px; height: 40px; transition: border-color .2s; }
.search-select__input-wrap:focus-within { border-color: #1677ff; }
.search-select__input { flex: 1; border: none; outline: none; font-size: 14px; color: #1a1a1a; background: transparent; }
.search-select__input::placeholder { color: #ccc; }
.search-select__list { max-height: 200px; overflow-y: auto; margin-top: 8px; border: 1px solid #e8e8e8; border-radius: 8px; display: none; }
.search-select__list.visible { display: block; }
.search-select__item { padding: 10px 12px; cursor: pointer; border-bottom: 1px solid #f5f5f5; transition: background .15s; }
.search-select__item:last-child { border-bottom: none; }
.search-select__item:hover, .search-select__item.selected { background: #e6f4ff; }
.search-select__item-name { font-size: 14px; color: #1a1a1a; font-weight: 500; }
.search-select__item-info { font-size: 12px; color: #999; margin-top: 2px; }
.search-select__empty { padding: 16px; text-align: center; font-size: 13px; color: #999; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="location.href='./index.html'"></span>
<span class="nav-title">紧急联系人</span>
</div>
<div class="scroll-content" id="contactList">
<!-- 由 JS 渲染 -->
</div>
<!-- 新增/编辑单位联系人弹窗 - 搜索选择模式 -->
<div id="modal-unit" class="modal-mask">
<div class="modal-box">
<div class="modal-box__header"><span id="modal-unit-title">选择单位联系人</span><span class="modal-box__close" onclick="closeModal('modal-unit')">×</span></div>
<div class="modal-box__body">
<div class="search-select">
<div class="search-select__input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2" style="flex-shrink:0;"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
<input class="search-select__input" id="unit-search" placeholder="搜索姓名" oninput="onUnitSearch(this.value)">
</div>
<div class="search-select__list" id="unit-search-list"></div>
</div>
<!-- 选中后显示的信息预览 -->
<div id="unit-preview" style="display:none;margin-top:16px;padding:12px;background:#f5f7fa;border-radius:8px;">
<div style="font-size:14px;color:#1a1a1a;font-weight:600;margin-bottom:8px;" id="preview-name"></div>
<div style="font-size:13px;color:#666;margin-bottom:4px;" id="preview-company"></div>
<div style="font-size:13px;color:#666;margin-bottom:4px;" id="preview-dept"></div>
<div style="font-size:13px;color:#666;" id="preview-phone"></div>
</div>
</div>
<div class="modal-box__footer">
<button class="btn btn--cancel" onclick="closeModal('modal-unit')">取消</button>
<button class="btn btn--primary" onclick="saveUnitContact()">确定</button>
</div>
</div>
</div>
<!-- 新增/编辑亲友联系人弹窗 -->
<div id="modal-friend" class="modal-mask">
<div class="modal-box">
<div class="modal-box__header"><span id="modal-friend-title">新增亲友联系人</span><span class="modal-box__close" onclick="closeModal('modal-friend')">×</span></div>
<div class="modal-box__body">
<div class="form-group"><div class="form-group__label">姓名</div><input class="form-group__input" id="friend-name" placeholder="请输入姓名"></div>
<div class="form-group"><div class="form-group__label">关系</div>
<select class="form-group__select" id="friend-relation">
<option value="">请选择关系</option>
<option value="父亲">父亲</option><option value="母亲">母亲</option>
<option value="配偶">配偶</option><option value="子女">子女</option>
<option value="兄弟">兄弟</option><option value="姐妹">姐妹</option>
<option value="朋友">朋友</option><option value="其他">其他</option>
</select>
</div>
<div class="form-group"><div class="form-group__label">联系电话</div><input class="form-group__input" id="friend-phone" placeholder="请输入联系电话" type="tel"></div>
</div>
<div class="modal-box__footer">
<button class="btn btn--cancel" onclick="closeModal('modal-friend')">取消</button>
<button class="btn btn--primary" onclick="saveFriendContact()">保存</button>
</div>
</div>
</div>
<!-- 删除确认弹窗 -->
<div id="modal-delete" class="modal-mask">
<div class="confirm-box">
<div class="confirm-box__title">删除联系人</div>
<div class="confirm-box__desc">确认删除该紧急联系人?</div>
<div class="confirm-box__actions">
<button class="confirm-box__btn confirm-box__btn--cancel" onclick="closeModal('modal-delete')">取消</button>
<button class="confirm-box__btn confirm-box__btn--danger" onclick="confirmDelete()">删除</button>
</div>
</div>
</div>
</div>
<script>
/* 数据模型 */
var unitContact = { name: '李建军', company: '公司机关', dept: '安全环保部', phone: '13899876543' };
var friendContacts = [
{ name: '张美玲', relation: '配偶', phone: '13566778899' },
{ name: '李国强', relation: '父亲', phone: '13012345678' }
];
/* 模拟单位通讯录数据(搜索用) */
var staffList = [
{ name: '李建军', company: '公司机关', dept: '安全环保部', phone: '13899876543' },
{ name: '李明辉', company: '公司机关', dept: '财务部', phone: '13700112233' },
{ name: '王晓东', company: '采油一厂', dept: '生产运行科', phone: '13988776655' },
{ name: '张伟', company: '采油二厂', dept: '安全环保科', phone: '15099887766' },
{ name: '刘洋', company: '公司机关', dept: '人力资源部', phone: '13611223344' },
{ name: '陈思远', company: '采气厂', dept: '技术科', phone: '13855667788' },
{ name: '赵丽华', company: '公司机关', dept: '健康科', phone: '13922334455' },
{ name: '李国栋', company: '炼化公司', dept: '生产部', phone: '13766554433' }
];
var selectedStaff = null;
var editingFriendIdx = -1;
var deletingType = '';
var deletingIdx = -1;
/* 渲染列表 */
function render() {
var html = '';
/* 单位联系人 */
html += '<div class="section">';
html += '<div class="section__header"><span class="section__title">单位联系人</span><span class="section__hint">限1人</span></div>';
if (unitContact) {
html += '<div class="contact-card">';
html += '<div class="contact-card__row"><span class="contact-card__name">' + unitContact.name + '</span>';
html += '<div class="contact-card__actions"><span class="contact-card__btn contact-card__btn--edit" onclick="editUnit()">编辑</span><span class="contact-card__btn contact-card__btn--delete" onclick="deleteContact(\'unit\',-1)">删除</span></div></div>';
html += '<div class="contact-card__row"><span class="contact-card__label">单位</span><span class="contact-card__value">' + unitContact.company + '</span></div>';
html += '<div class="contact-card__row"><span class="contact-card__label">部门</span><span class="contact-card__value">' + unitContact.dept + '</span></div>';
html += '<div class="contact-card__row"><span class="contact-card__label">电话</span><span class="contact-card__value">' + unitContact.phone + '</span></div>';
html += '</div>';
} else {
html += '<div class="contact-card" style="text-align:center;cursor:pointer;" onclick="addUnit()"><span style="color:#1677ff;font-size:14px;">+ 添加单位联系人</span></div>';
}
html += '</div>';
/* 亲友联系人 */
html += '<div class="section">';
html += '<div class="section__header"><span class="section__title">亲友联系人</span><span class="section__hint">最多3人</span>';
if (friendContacts.length < 3) {
html += '<span class="section__add" onclick="addFriend()">+ 新增</span>';
}
html += '</div>';
if (friendContacts.length === 0) {
html += '<div class="contact-card"><div class="empty-state">暂无亲友联系人</div></div>';
}
for (var i = 0; i < friendContacts.length; i++) {
var f = friendContacts[i];
html += '<div class="contact-card">';
html += '<div class="contact-card__row"><span class="contact-card__name">' + f.name + '</span>';
html += '<div class="contact-card__actions"><span class="contact-card__btn contact-card__btn--edit" onclick="editFriend(' + i + ')">编辑</span><span class="contact-card__btn contact-card__btn--delete" onclick="deleteContact(\'friend\',' + i + ')">删除</span></div></div>';
html += '<div class="contact-card__row"><span class="contact-card__label">关系</span><span class="contact-card__value">' + f.relation + '</span></div>';
html += '<div class="contact-card__row"><span class="contact-card__label">电话</span><span class="contact-card__value">' + f.phone + '</span></div>';
html += '</div>';
}
html += '</div>';
document.getElementById('contactList').innerHTML = html;
}
/* 弹窗控制 */
function openModal(id) { document.getElementById(id).classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
/* 单位联系人 - 搜索选择模式 */
function addUnit() {
document.getElementById('modal-unit-title').textContent = '选择单位联系人';
document.getElementById('unit-search').value = '';
document.getElementById('unit-search-list').classList.remove('visible');
document.getElementById('unit-search-list').innerHTML = '';
document.getElementById('unit-preview').style.display = 'none';
selectedStaff = null;
openModal('modal-unit');
}
function editUnit() {
document.getElementById('modal-unit-title').textContent = '更换单位联系人';
document.getElementById('unit-search').value = '';
document.getElementById('unit-search-list').classList.remove('visible');
document.getElementById('unit-search-list').innerHTML = '';
selectedStaff = unitContact;
showPreview(unitContact);
openModal('modal-unit');
}
function onUnitSearch(keyword) {
var list = document.getElementById('unit-search-list');
var kw = keyword.trim();
if (!kw) { list.classList.remove('visible'); list.innerHTML = ''; return; }
var results = staffList.filter(function(s) { return s.name.indexOf(kw) !== -1; });
if (results.length === 0) {
list.innerHTML = '<div class="search-select__empty">未找到匹配的联系人</div>';
} else {
var html = '';
for (var i = 0; i < results.length; i++) {
var s = results[i];
html += '<div class="search-select__item" onclick="selectStaff(' + staffList.indexOf(s) + ')">';
html += '<div class="search-select__item-name">' + s.name + '</div>';
html += '<div class="search-select__item-info">' + s.company + ' · ' + s.dept + ' · ' + s.phone + '</div>';
html += '</div>';
}
list.innerHTML = html;
}
list.classList.add('visible');
}
function selectStaff(idx) {
selectedStaff = staffList[idx];
document.getElementById('unit-search').value = selectedStaff.name;
document.getElementById('unit-search-list').classList.remove('visible');
showPreview(selectedStaff);
}
function showPreview(staff) {
var el = document.getElementById('unit-preview');
el.style.display = 'block';
document.getElementById('preview-name').textContent = staff.name;
document.getElementById('preview-company').textContent = '单位:' + staff.company;
document.getElementById('preview-dept').textContent = '部门:' + staff.dept;
document.getElementById('preview-phone').textContent = '电话:' + staff.phone;
}
function saveUnitContact() {
if (!selectedStaff) { alert('请搜索并选择一位单位联系人'); return; }
unitContact = { name: selectedStaff.name, company: selectedStaff.company, dept: selectedStaff.dept, phone: selectedStaff.phone };
closeModal('modal-unit');
render();
}
/* 亲友联系人 */
function addFriend() {
editingFriendIdx = -1;
document.getElementById('modal-friend-title').textContent = '新增亲友联系人';
document.getElementById('friend-name').value = '';
document.getElementById('friend-relation').value = '';
document.getElementById('friend-phone').value = '';
openModal('modal-friend');
}
function editFriend(idx) {
editingFriendIdx = idx;
var f = friendContacts[idx];
document.getElementById('modal-friend-title').textContent = '编辑亲友联系人';
document.getElementById('friend-name').value = f.name;
document.getElementById('friend-relation').value = f.relation;
document.getElementById('friend-phone').value = f.phone;
openModal('modal-friend');
}
function saveFriendContact() {
var n = document.getElementById('friend-name').value.trim();
var r = document.getElementById('friend-relation').value;
var p = document.getElementById('friend-phone').value.trim();
if (!n || !r || !p) { alert('请填写完整信息'); return; }
if (editingFriendIdx === -1) {
friendContacts.push({ name: n, relation: r, phone: p });
} else {
friendContacts[editingFriendIdx] = { name: n, relation: r, phone: p };
}
closeModal('modal-friend');
render();
}
/* 删除 */
function deleteContact(type, idx) {
deletingType = type;
deletingIdx = idx;
openModal('modal-delete');
}
function confirmDelete() {
if (deletingType === 'unit') { unitContact = null; }
else if (deletingType === 'friend') { friendContacts.splice(deletingIdx, 1); }
closeModal('modal-delete');
render();
}
/* 初始化 */
render();
</script>
</body>
</html>
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 重置密码 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.form-area { padding: 32px 24px; }
.form-area__title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin-bottom: 24px; }
.input-group { margin-bottom: 20px; }
.input-group__label { font-size: 14px; color: #666; margin-bottom: 8px; }
.input-group__field { display: flex; align-items: center; border-bottom: 1px solid #e8e8e8; padding: 10px 0; }
.input-group__field input { flex: 1; border: none; outline: none; font-size: 16px; color: #1a1a1a; background: transparent; }
.input-group__field input::placeholder { color: #ccc; }
.input-group__field .toggle-eye { font-size: 18px; color: #999; cursor: pointer; }
.pwd-hint { font-size: 12px; color: #999; margin-top: 8px; line-height: 1.6; }
.pwd-hint strong { color: #ff4d4f; font-weight: 500; }
.submit-btn { width: 100%; height: 48px; border: none; border-radius: 24px; background: #1677ff; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 32px; }
.submit-btn:active { opacity: 0.85; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-title">设置</span>
</div>
<div class="form-area">
<div class="form-area__title">重置密码</div>
<div class="input-group">
<div class="input-group__label">新密码:</div>
<div class="input-group__field">
<input type="password" placeholder="请输入密码">
<span class="toggle-eye">👁</span>
</div>
</div>
<div class="input-group">
<div class="input-group__label">确认密码:</div>
<div class="input-group__field">
<input type="password" placeholder="请输入密码">
<span class="toggle-eye">👁</span>
</div>
</div>
<div class="pwd-hint">密码不能少于12位,必须包含<strong>大小写字母、数字、特殊符号</strong></div>
<!-- [交互] 重置成功后返回设置页 -->
<button class="submit-btn" onclick="location.href='settings.html'">确定</button>
</div>
</div>
</body>
</html>
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 身份验证 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.form-area { padding: 32px 24px; }
.form-area__title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin-bottom: 24px; }
.input-group { margin-bottom: 20px; }
.input-group__label { font-size: 14px; color: #666; margin-bottom: 8px; }
.input-group__field { border-bottom: 1px solid #e8e8e8; padding: 10px 0; }
.input-group__field input { width: 100%; border: none; outline: none; font-size: 16px; color: #1a1a1a; background: transparent; }
.input-group__field input::placeholder { color: #ccc; }
.submit-btn { width: 100%; height: 48px; border: none; border-radius: 24px; background: #1677ff; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 32px; }
.submit-btn:active { opacity: 0.85; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-title">设置</span>
</div>
<div class="form-area">
<div class="form-area__title">身份验证</div>
<div class="input-group">
<div class="input-group__label">姓名:</div>
<div class="input-group__field"><input type="text" placeholder="请输入"></div>
</div>
<div class="input-group">
<div class="input-group__label">手机号:</div>
<div class="input-group__field"><input type="tel" placeholder="请输入"></div>
</div>
<div class="input-group">
<div class="input-group__label">身份证号:</div>
<div class="input-group__field"><input type="text" placeholder="请输入"></div>
</div>
<!-- [交互] 验证通过后跳转重置密码 -->
<button class="submit-btn" onclick="location.href='forgot-password-reset.html'">确定</button>
</div>
</div>
</body>
</html>
+136
View File
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 我的 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; position: absolute; top: 0; left: 0; right: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #fff; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 底部 Tab */
.tab-bar { height: 83px; background: #fff; border-top: 1px solid #e8e8e8; display: flex; align-items: flex-start; padding-top: 8px; flex-shrink: 0; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; padding: 4px 0; }
.tab-item .tab-icon { font-size: 22px; line-height: 1; }
.tab-item .tab-label { font-size: 10px; color: #999; font-weight: 500; }
.tab-item.active .tab-label { color: #1677ff; }
/* 头部用户信息 */
.profile-header { background: linear-gradient(135deg, #1677ff, #0958d9); padding: 60px 20px 24px; flex-shrink: 0; position: relative; }
.profile-header__user { display: flex; align-items: center; gap: 14px; }
.profile-header__avatar { width: 64px; height: 64px; border-radius: 50%; background: linear-gradient(135deg, #e6f4ff, #91caff); display: flex; align-items: center; justify-content: center; font-size: 32px; border: 3px solid rgba(255,255,255,0.5); cursor: pointer; overflow: hidden; }
.profile-header__avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-header__info { flex: 1; }
.profile-header__name { color: #fff; font-size: 20px; font-weight: 700; }
.profile-header__dept { color: rgba(255,255,255,0.8); font-size: 13px; margin-top: 3px; }
.profile-header__id { color: rgba(255,255,255,0.7); font-size: 12px; margin-top: 4px; }
/* 编辑按钮 */
.profile-header__edit { position: absolute; top: 56px; right: 20px; color: rgba(255,255,255,0.9); font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 4px; }
/* 应用记录入口 */
.app-record { margin: 12px 16px 0; background: #fff; border-radius: 12px; padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 1px 4px rgba(0,0,0,0.05); cursor: pointer; }
.app-record__label { font-size: 15px; color: #1a1a1a; font-weight: 500; display: flex; align-items: center; gap: 8px; }
/* PLACEHOLDER_MENU_CARD */
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<!-- 头部用户信息区 -->
<div class="profile-header">
<div class="profile-header__user">
<!-- [交互] 点击头像跳转查看头像页 avatar-view.html -->
<div class="profile-header__avatar" onclick="location.href='avatar-view.html'">👤</div>
<div class="profile-header__info">
<div class="profile-header__name">张旭晨</div>
<div class="profile-header__dept">公司机关-财务部</div>
<div class="profile-header__id">员工编号:87566650</div>
</div>
</div>
<!-- [交互] 点击编辑跳转个人资料页 personal-info.html -->
<div class="profile-header__edit" onclick="location.href='personal-info.html'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</div>
</div>
<div class="scroll-content">
<!-- [交互] 紧急联系人入口,跳转 emergency-contacts.html -->
<div class="app-record" onclick="location.href='emergency-contacts.html'" style="cursor:pointer;">
<span class="app-record__label">🆘 紧急联系人</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<!-- 功能菜单 -->
<div style="margin:12px 16px 0;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.05);">
<!-- [交互] 应用记录 -->
<div style="display:flex;align-items:center;padding:16px;cursor:pointer;border-bottom:1px solid #f5f5f5;">
<span style="font-size:18px;margin-right:12px;">📋</span>
<span style="font-size:15px;color:#1a1a1a;flex:1;">应用记录</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<!-- [交互] 我的点赞 -->
<div style="display:flex;align-items:center;padding:16px;cursor:pointer;border-bottom:1px solid #f5f5f5;">
<span style="font-size:18px;margin-right:12px;">👍</span>
<span style="font-size:15px;color:#1a1a1a;flex:1;">我的点赞</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<!-- [交互] 我的收藏 -->
<div style="display:flex;align-items:center;padding:16px;cursor:pointer;border-bottom:1px solid #f5f5f5;">
<span style="font-size:18px;margin-right:12px;"></span>
<span style="font-size:15px;color:#1a1a1a;flex:1;">我的收藏</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<!-- [交互] 设置,跳转 settings.html -->
<div style="display:flex;align-items:center;padding:16px;cursor:pointer;" onclick="location.href='settings.html'">
<span style="font-size:18px;margin-right:12px;">⚙️</span>
<span style="font-size:15px;color:#1a1a1a;flex:1;">设置</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<!-- 退出登录 -->
<div style="margin:16px 16px;">
<button onclick="openModal('modal-logout')" style="width:100%;height:48px;border:none;border-radius:12px;background:#fff1f0;color:#ff4d4f;font-size:15px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;box-shadow:0 1px 4px rgba(0,0,0,0.05);">退出登录</button>
</div>
<div style="height:16px;"></div>
</div>
<!-- 退出登录确认弹窗 -->
<div id="modal-logout" style="display:none;position:absolute;inset:0;background:rgba(0,0,0,0.5);z-index:200;align-items:center;justify-content:center;">
<div style="width:280px;background:#fff;border-radius:16px;overflow:hidden;">
<div style="padding:20px 20px 8px;font-size:17px;font-weight:700;text-align:center;color:#1a1a1a;">退出登录</div>
<div style="padding:8px 20px 20px;font-size:14px;color:#666;text-align:center;">确认退出当前账号?</div>
<div style="display:flex;border-top:1px solid #f0f0f0;">
<button onclick="closeModal('modal-logout')" style="flex:1;padding:14px;font-size:16px;cursor:pointer;background:none;border:none;color:#666;border-right:1px solid #f0f0f0;">取消</button>
<button onclick="closeModal('modal-logout')" style="flex:1;padding:14px;font-size:16px;cursor:pointer;background:none;border:none;color:#ff4d4f;font-weight:600;">确认退出</button>
</div>
</div>
</div>
<!-- 底部 Tab 栏 -->
<div class="tab-bar">
<!-- [交互] 点击跳转至: ../home/index.html -->
<div class="tab-item" onclick="location.href='../home/index.html'"><span class="tab-icon">🏠</span><span class="tab-label">应用</span></div>
<div class="tab-item"><span class="tab-icon">📁</span><span class="tab-label">档案</span></div>
<div class="tab-item"><span class="tab-icon">🤖</span><span class="tab-label">AI助手</span></div>
<div class="tab-item"><span class="tab-icon">📚</span><span class="tab-label">知识</span></div>
<div class="tab-item active"><span class="tab-icon">👤</span><span class="tab-label">我的</span></div>
</div>
</div>
<script>
function openModal(id){var m=document.getElementById(id);if(m)m.style.display='flex';}
function closeModal(id){var m=document.getElementById(id);if(m)m.style.display='none';}
</script>
</body>
</html>
+78
View File
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 个人资料 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.info-group { background: #fff; margin: 12px 0; }
.info-item { display: flex; align-items: center; padding: 16px; border-bottom: 1px solid #f5f5f5; }
.info-item:last-child { border-bottom: none; }
.info-item__label { font-size: 15px; color: #666; width: 80px; flex-shrink: 0; }
.info-item__value { font-size: 15px; color: #1a1a1a; flex: 1; text-align: right; }
.info-item__arrow { margin-left: 8px; }
.info-item__avatar { width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #e6f4ff, #91caff); display: flex; align-items: center; justify-content: center; font-size: 24px; overflow: hidden; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<!-- [交互] 导航栏,返回我的主页 -->
<div class="nav-bar">
<span class="nav-back" onclick="location.href='./index.html'"></span>
<span class="nav-title">个人资料</span>
</div>
<div class="scroll-content">
<div class="info-group">
<!-- [交互] 点击头像行跳转 avatar-view.html -->
<div class="info-item" style="cursor:pointer;" onclick="location.href='avatar-view.html'">
<span class="info-item__label">头像</span>
<div style="flex:1;display:flex;justify-content:flex-end;">
<div class="info-item__avatar">👤</div>
</div>
<svg class="info-item__arrow" width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div class="info-item">
<span class="info-item__label">姓名</span>
<span class="info-item__value">张旭</span>
</div>
<div class="info-item">
<span class="info-item__label">员工编号</span>
<span class="info-item__value">56999852</span>
</div>
<div class="info-item">
<span class="info-item__label">单位</span>
<span class="info-item__value">公司机关</span>
</div>
<div class="info-item">
<span class="info-item__label">部门</span>
<span class="info-item__value">健康科</span>
</div>
<!-- [交互] 点击手机号行跳转 phone-list.html -->
<div class="info-item" style="cursor:pointer;" onclick="location.href='phone-list.html'">
<span class="info-item__label">手机号</span>
<span class="info-item__value">18544555589</span>
<svg class="info-item__arrow" width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
</div>
</div>
</body>
</html>
+78
View File
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 手机号 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.phone-card { background: #fff; margin: 12px 16px; border-radius: 12px; padding: 16px; display: flex; align-items: center; box-shadow: 0 1px 4px rgba(0,0,0,0.05); }
.phone-card__number { font-size: 17px; color: #1a1a1a; font-weight: 600; flex: 1; }
.phone-card__badge { font-size: 11px; color: #1677ff; background: #e6f4ff; padding: 2px 8px; border-radius: 10px; margin-right: 12px; }
.phone-card__edit { font-size: 14px; color: #1677ff; cursor: pointer; }
.add-phone { margin: 0 16px; background: #fff; border-radius: 12px; padding: 16px; text-align: center; color: #1677ff; font-size: 15px; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.05); display: flex; align-items: center; justify-content: center; gap: 6px; }
/* 底部操作面板 */
.action-sheet-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 100; }
.action-sheet-mask.active { display: flex; align-items: flex-end; }
.action-sheet { width: 100%; background: #fff; border-radius: 16px 16px 0 0; padding-bottom: 34px; }
.action-sheet__item { padding: 16px; text-align: center; font-size: 16px; color: #1a1a1a; cursor: pointer; border-bottom: 1px solid #f5f5f5; }
.action-sheet__item--danger { color: #ff4d4f; }
.action-sheet__item:active { background: #f5f5f5; }
.action-sheet__cancel { padding: 16px; text-align: center; font-size: 16px; color: #999; cursor: pointer; border-top: 8px solid #f5f5f5; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="location.href='personal-info.html'"></span>
<span class="nav-title">手机号</span>
</div>
<div class="scroll-content">
<!-- 手机号卡片 -->
<div class="phone-card">
<span class="phone-card__number">15899655523</span>
<span class="phone-card__badge">默认</span>
<!-- [交互] 点击编辑弹出底部操作面板 -->
<span class="phone-card__edit" onclick="openSheet()">编辑</span>
</div>
<!-- [交互] 新增手机号,跳转 change-phone-verify-new.html -->
<div class="add-phone" onclick="location.href='change-phone-verify-new.html'">
<span style="font-size:18px;">+</span> 新增手机号
</div>
</div>
<!-- [交互] 底部操作面板:修改/删除/设为默认 -->
<div id="action-sheet" class="action-sheet-mask">
<div class="action-sheet">
<div style="padding:12px 16px;font-size:13px;color:#999;text-align:center;">15899655523</div>
<!-- [交互] 修改 → 跳转修改手机号流程 -->
<div class="action-sheet__item" onclick="location.href='change-phone-verify-old.html'">修改</div>
<div class="action-sheet__item action-sheet__item--danger">删除</div>
<div class="action-sheet__item">设为默认</div>
<div class="action-sheet__cancel" onclick="closeSheet()">取消</div>
</div>
</div>
</div>
<script>
function openSheet(){document.getElementById('action-sheet').classList.add('active');}
function closeSheet(){document.getElementById('action-sheet').classList.remove('active');}
</script>
</body>
</html>
+113
View File
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心 - 设置 | 健康长庆原型</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .nav-back { font-size: 20px; color: #1a1a1a; cursor: pointer; width: 32px; }
.nav-bar .nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.section-label { font-size: 13px; color: #999; padding: 16px 16px 8px; }
.menu-group { background: #fff; }
.menu-item { display: flex; align-items: center; padding: 16px; border-bottom: 1px solid #f5f5f5; cursor: pointer; }
.menu-item:last-child { border-bottom: none; }
.menu-item__label { font-size: 15px; color: #1a1a1a; flex: 1; }
.menu-item__value { font-size: 14px; color: #999; margin-right: 8px; }
.menu-item__arrow svg { display: block; }
.switch { width: 44px; height: 26px; border-radius: 13px; background: #e8e8e8; position: relative; cursor: pointer; transition: background .2s; flex-shrink: 0; }
.switch.on { background: #1677ff; }
.switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.2); transition: transform .2s; }
.switch.on::after { transform: translateX(18px); }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">12:00</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 6l5 5 5-5"/><path d="M4 9l3 3 3-3" opacity="0.5"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M21 10v4"/></svg>
</div>
</div>
<div class="nav-bar">
<span class="nav-back" onclick="location.href='./index.html'"></span>
<span class="nav-title">设置</span>
</div>
<div class="scroll-content">
<!-- 账号 -->
<div class="section-label">账号</div>
<div class="menu-group">
<!-- [交互] 跳转个人资料 -->
<div class="menu-item" onclick="location.href='personal-info.html'">
<span class="menu-item__label">个人资料</span>
<span class="menu-item__arrow"><svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg></span>
</div>
<!-- [交互] 跳转修改密码 -->
<div class="menu-item" onclick="location.href='change-password.html'">
<span class="menu-item__label">修改密码</span>
<span class="menu-item__arrow"><svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg></span>
</div>
</div>
<!-- 通用 -->
<div class="section-label">通用</div>
<div class="menu-group">
<div class="menu-item">
<span class="menu-item__label">主题色</span>
<span class="menu-item__arrow"><svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg></span>
</div>
<div class="menu-item">
<span class="menu-item__label">健康精灵配置</span>
<span class="menu-item__arrow"><svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg></span>
</div>
</div>
<!-- 短信提醒 -->
<div class="section-label">短信提醒</div>
<div class="menu-group">
<div class="menu-item">
<span class="menu-item__label">体重管理报名</span>
<div class="switch on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="menu-item">
<span class="menu-item__label">体检预约</span>
<div class="switch on" onclick="this.classList.toggle('on')"></div>
</div>
</div>
<!-- 关于 -->
<div class="section-label">关于</div>
<div class="menu-group">
<div class="menu-item">
<span class="menu-item__label">版本号</span>
<span class="menu-item__value">3.1.03</span>
</div>
<div class="menu-item">
<span class="menu-item__label">检查更新</span>
<span class="menu-item__value">已是最新版本</span>
</div>
<div class="menu-item">
<span class="menu-item__label">备案号</span>
<span class="menu-item__value">陕ICP备1558852号-4A</span>
</div>
<div class="menu-item">
<span class="menu-item__label">用户协议</span>
<span class="menu-item__arrow"><svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg></span>
</div>
<div class="menu-item">
<span class="menu-item__label">隐私政策</span>
<span class="menu-item__arrow"><svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg></span>
</div>
</div>
<div style="height:34px;"></div>
</div>
</div>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
path = "D:/AI/work/platform-prototype/web-admin/health-consult/archive-personal-detail.html"
html = []
html.append("""<!DOCTYPE html>
<html lang=\"zh-CN\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>专家咨询 - 员工咨询明细 | 健康长庆原型</title>""")
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(html))
print("ok")
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
自动截图脚本 - 为三个端的所有页面生成截图
"""
import subprocess
import time
import os
from pathlib import Path
# 页面配置
PAGES = {
'onsite': {
'base_url': 'http://localhost:8000/emergency-app/onsite',
'pages': [
('workbench/index.html', 'screenshots/index.jpg'),
('workbench/work-order-detail-ongoing.html', 'screenshots/work-order-detail-ongoing.jpg'),
('workbench/work-order-detail.html', 'screenshots/work-order-detail.jpg'),
('workbench/serious-illness-list.html', 'screenshots/serious-illness-list.jpg'),
('workbench/serious-illness-detail-pending.html', 'screenshots/serious-illness-detail-pending.jpg'),
('workbench/serious-illness-detail-done.html', 'screenshots/serious-illness-detail-done.jpg'),
('knowledge/index.html', 'screenshots/knowledge-index.jpg'),
('knowledge/article-detail.html', 'screenshots/knowledge-article-detail.jpg'),
('profile/index.html', 'screenshots/profile-index.jpg'),
('profile/edit-profile.html', 'screenshots/edit-profile.jpg'),
('profile/change-pwd.html', 'screenshots/change-pwd.jpg'),
]
},
'operator': {
'base_url': 'http://localhost:8000/emergency-app/operator',
'pages': [
('workbench/index.html', 'screenshots/index.jpg'),
('workbench/emergency-chat-ongoing.html', 'screenshots/emergency-chat-ongoing.jpg'),
('workbench/emergency-chat-done.html', 'screenshots/emergency-chat-done.jpg'),
('workbench/work-order-detail.html', 'screenshots/work-order-detail.jpg'),
('workbench/serious-illness-detail-pending.html', 'screenshots/serious-illness-detail-pending.jpg'),
('knowledge/index.html', 'screenshots/knowledge-index.jpg'),
('knowledge/article-detail.html', 'screenshots/knowledge-article-detail.jpg'),
('profile/index.html', 'screenshots/profile-index.jpg'),
('profile/edit-profile.html', 'screenshots/edit-profile.jpg'),
('profile/change-pwd.html', 'screenshots/change-pwd.jpg'),
]
},
'professional': {
'base_url': 'http://localhost:8000/emergency-app/professional',
'pages': [
('workbench/index.html', 'screenshots/index.jpg'),
('workbench/emergency-chat-ongoing.html', 'screenshots/emergency-chat-ongoing.jpg'),
('workbench/emergency-chat-done.html', 'screenshots/emergency-chat-done.jpg'),
('workbench/work-order-detail-ongoing.html', 'screenshots/work-order-detail-ongoing.jpg'),
('workbench/work-order-detail.html', 'screenshots/work-order-detail.jpg'),
('knowledge/index.html', 'screenshots/knowledge-index.jpg'),
('knowledge/article-detail.html', 'screenshots/knowledge-article-detail.jpg'),
('profile/index.html', 'screenshots/profile-index.jpg'),
('profile/edit-profile.html', 'screenshots/edit-profile.jpg'),
('profile/change-pwd.html', 'screenshots/change-pwd.jpg'),
]
}
}
def take_screenshot(url, output_path):
"""使用Puppeteer截图"""
try:
# 使用npx puppeteer-cli截图
cmd = [
'npx', 'puppeteer', 'screenshot',
'--url', url,
'--path', output_path,
'--width', '375',
'--height', '812',
'--device-scale-factor', '2'
]
result = subprocess.run(cmd, capture_output=True, timeout=30)
if result.returncode == 0:
print(f"{output_path}")
return True
else:
print(f"{output_path}: {result.stderr.decode()}")
return False
except Exception as e:
print(f"{output_path}: {e}")
return False
def main():
base_dir = Path('D:/AI/work/platform-prototype/emergency-app')
for role, config in PAGES.items():
print(f"\n[{role}] Starting screenshots...")
role_dir = base_dir / role / 'doc'
for page_path, screenshot_rel in config['pages']:
url = f"{config['base_url']}/{page_path}"
output_path = str(role_dir / screenshot_rel)
# 确保目录存在
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
take_screenshot(url, output_path)
time.sleep(1) # 避免请求过快
if __name__ == '__main__':
main()
+21 -6
View File
@@ -297,7 +297,9 @@ const SITE_MAP = [
]},
{ id:'employee-app', label:'员工端 APP', icon:'app', color:'#95de64', modules:[
{ name:'AI', dir:'employee-app/ai', pages:[] },
{ name:'应用主页', dir:'employee-app/home', pages:[] },
{ name:'应用主页', dir:'employee-app/home', entry:'index.html', pages:[
{n:'应用主页(收起态)',f:'index.html'},
] },
{ name:'健康体检', dir:'employee-app/health-checkup', pages:[] },
{ name:'体重管理', dir:'employee-app/weight-management', pages:[] },
{ name:'营养管理', dir:'employee-app/nutrition', pages:[] },
@@ -357,7 +359,21 @@ const SITE_MAP = [
{ name:'一线医疗', dir:'employee-app/frontline-medical', pages:[] },
{ name:'健康档案', dir:'employee-app/health-record', pages:[] },
{ name:'知识普及', dir:'employee-app/knowledge', pages:[] },
{ name:'个人中心', dir:'employee-app/profile', pages:[] }
{ name:'个人中心', dir:'employee-app/profile', entry:'index.html', pages:[
{ file:'index.html', label:'我的主页' },
{ file:'emergency-contacts.html', label:'紧急联系人' },
{ file:'personal-info.html', label:'个人资料' },
{ file:'settings.html', label:'设置' },
{ file:'avatar-view.html', label:'查看头像' },
{ file:'avatar-edit.html', label:'查看头像-修改' },
{ file:'change-phone-verify-old.html', label:'修改手机号-原手机号验证' },
{ file:'change-phone-unavailable.html', label:'修改手机号-手机不可用' },
{ file:'change-phone-verify-new.html', label:'修改手机号-新手机号验证' },
{ file:'change-password.html', label:'修改密码' },
{ file:'forgot-password-verify.html', label:'忘记密码-身份验证' },
{ file:'forgot-password-reset.html', label:'忘记密码-重置密码' },
{ file:'phone-list.html', label:'手机号' }
]}
]},
{ id:'emergency-app', label:'健康应急 APP', icon:'emergency', color:'#ffc069',
roles:[
@@ -400,7 +416,7 @@ const SITE_MAP = [
] }
]},
{ role:'应急操作人员', id:'operator', modules:[
{name:'工作台',dir:'emergency-app/operator/workbench',pages:[
{name:'工作台',dir:'emergency-app/operator/workbench',prd:'emergency-app/operator/doc/PRD.md',pages:[
{file:'index.html',label:'工作台首页(应急求助)'},
{file:'serious-illness-list.html',label:'大病就医首页'},
{file:'emergency-chat.html',label:'应急聊天'},
@@ -411,7 +427,6 @@ const SITE_MAP = [
{file:'work-order-detail.html',label:'工单详情(已完成)'},
{file:'work-order-detail-ongoing.html',label:'工单详情(进行中)'},
{file:'rescue-summary.html',label:'救助信息'},
{file:'end-emergency.html',label:'结束应急'},
{file:'serious-illness-detail-pending.html',label:'大病就医详情(待确认)'},
{file:'serious-illness-detail-confirmed.html',label:'大病就医详情(已确认)'},
{file:'serious-illness-detail-done.html',label:'大病就医详情(已完成)'},
@@ -429,7 +444,7 @@ const SITE_MAP = [
]}
]},
{ role:'应急专业人员', id:'professional', modules:[
{ name:'工作台', dir:'emergency-app/professional/workbench', pages:[
{ name:'工作台', dir:'emergency-app/professional/workbench', prd:'emergency-app/professional/doc/PRD.md', pages:[
{file:'index.html',label:'工作台首页'},
{file:'emergency-chat-ongoing.html',label:'应急聊天(进行中)'},
{file:'emergency-chat-done.html',label:'应急聊天(已结束)'},
@@ -448,7 +463,7 @@ const SITE_MAP = [
]}
]},
{ role:'应急驻场人员', id:'onsite', modules:[
{name:'工作台',dir:'emergency-app/onsite/workbench',pages:[
{name:'工作台',dir:'emergency-app/onsite/workbench',prd:'emergency-app/onsite/doc/PRD.md',pages:[
{file:'index.html',label:'工作台首页(应急求助)'},
{file:'serious-illness-list.html',label:'大病就医首页'},
{file:'work-order-detail-ongoing.html',label:'工单详情(待处理)'},
+6 -1
View File
@@ -5,7 +5,9 @@
"packages": {
"": {
"dependencies": {
"chrome-devtools-mcp": "^0.21.0",
"chrome-devtools-mcp": "^0.21.0"
},
"devDependencies": {
"playwright": "^1.59.1"
}
},
@@ -26,6 +28,7 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -40,6 +43,7 @@
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.59.1"
@@ -58,6 +62,7 @@
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
+3 -1
View File
@@ -1,6 +1,8 @@
{
"dependencies": {
"chrome-devtools-mcp": "^0.21.0",
"chrome-devtools-mcp": "^0.21.0"
},
"devDependencies": {
"playwright": "^1.59.1"
}
}
+107
View File
@@ -0,0 +1,107 @@
const fs = require('fs');
const path = require('path');
// 页面配置
const PAGES = {
onsite: {
baseUrl: 'http://localhost:8000/emergency-app/onsite',
pages: [
{ page: 'workbench/index.html', screenshot: 'screenshots/index.jpg' },
{ page: 'workbench/work-order-detail-ongoing.html', screenshot: 'screenshots/work-order-detail-ongoing.jpg' },
{ page: 'workbench/work-order-detail.html', screenshot: 'screenshots/work-order-detail.jpg' },
{ page: 'workbench/serious-illness-list.html', screenshot: 'screenshots/serious-illness-list.jpg' },
{ page: 'workbench/serious-illness-detail-pending.html', screenshot: 'screenshots/serious-illness-detail-pending.jpg' },
{ page: 'workbench/serious-illness-detail-done.html', screenshot: 'screenshots/serious-illness-detail-done.jpg' },
{ page: 'knowledge/index.html', screenshot: 'screenshots/knowledge-index.jpg' },
{ page: 'knowledge/article-detail.html', screenshot: 'screenshots/knowledge-article-detail.jpg' },
{ page: 'profile/index.html', screenshot: 'screenshots/profile-index.jpg' },
{ page: 'profile/edit-profile.html', screenshot: 'screenshots/edit-profile.jpg' },
{ page: 'profile/change-pwd.html', screenshot: 'screenshots/change-pwd.jpg' },
]
},
operator: {
baseUrl: 'http://localhost:8000/emergency-app/operator',
pages: [
{ page: 'workbench/index.html', screenshot: 'screenshots/index.jpg' },
{ page: 'workbench/emergency-chat-ongoing.html', screenshot: 'screenshots/emergency-chat-ongoing.jpg' },
{ page: 'workbench/emergency-chat-done.html', screenshot: 'screenshots/emergency-chat-done.jpg' },
{ page: 'workbench/work-order-detail.html', screenshot: 'screenshots/work-order-detail.jpg' },
{ page: 'workbench/serious-illness-detail-pending.html', screenshot: 'screenshots/serious-illness-detail-pending.jpg' },
{ page: 'knowledge/index.html', screenshot: 'screenshots/knowledge-index.jpg' },
{ page: 'knowledge/article-detail.html', screenshot: 'screenshots/knowledge-article-detail.jpg' },
{ page: 'profile/index.html', screenshot: 'screenshots/profile-index.jpg' },
{ page: 'profile/edit-profile.html', screenshot: 'screenshots/edit-profile.jpg' },
{ page: 'profile/change-pwd.html', screenshot: 'screenshots/change-pwd.jpg' },
]
},
professional: {
baseUrl: 'http://localhost:8000/emergency-app/professional',
pages: [
{ page: 'workbench/index.html', screenshot: 'screenshots/index.jpg' },
{ page: 'workbench/emergency-chat-ongoing.html', screenshot: 'screenshots/emergency-chat-ongoing.jpg' },
{ page: 'workbench/emergency-chat-done.html', screenshot: 'screenshots/emergency-chat-done.jpg' },
{ page: 'workbench/work-order-detail-ongoing.html', screenshot: 'screenshots/work-order-detail-ongoing.jpg' },
{ page: 'workbench/work-order-detail.html', screenshot: 'screenshots/work-order-detail.jpg' },
{ page: 'knowledge/index.html', screenshot: 'screenshots/knowledge-index.jpg' },
{ page: 'knowledge/article-detail.html', screenshot: 'screenshots/knowledge-article-detail.jpg' },
{ page: 'profile/index.html', screenshot: 'screenshots/profile-index.jpg' },
{ page: 'profile/edit-profile.html', screenshot: 'screenshots/edit-profile.jpg' },
{ page: 'profile/change-pwd.html', screenshot: 'screenshots/change-pwd.jpg' },
]
}
};
async function takeScreenshots() {
const playwright = require('playwright');
const browser = await playwright.chromium.launch();
// 仅跑指定页面:ONLY_PAGES="professional:workbench/patient-location.html,professional:workbench/rescue-opinion.html"
const onlyPages = (process.env.ONLY_PAGES || '').split(',').map(s => s.trim()).filter(Boolean);
if (onlyPages.length > 0) console.log(`[Filter] Only run: ${onlyPages.join(', ')}`);
for (const [role, config] of Object.entries(PAGES)) {
console.log(`\nScreenshotting ${role}...`);
for (const item of config.pages) {
if (onlyPages.length > 0) {
const key = `${role}:${item.page}`;
if (!onlyPages.includes(key)) continue;
}
const url = `${config.baseUrl}/${item.page}`;
const outputPath = path.join(__dirname, 'emergency-app', role, 'doc', item.screenshot);
// Create directory if not exists
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
try {
const page = await browser.newPage();
await page.setViewportSize({ width: 375, height: 812 });
console.log(` Loading: ${url}`);
await page.goto(url, { waitUntil: 'networkidle' });
// 修复:部分页面 fullPage 计算高度异常,改为截取手机壳容器
if (role === 'professional' && (item.page === 'workbench/patient-location.html' || item.page === 'workbench/rescue-opinion.html')) {
console.log(` [FIX] Using .phone-shell selector for ${item.page}`);
const shell = await page.$('.phone-shell');
if (!shell) throw new Error('Cannot find .phone-shell');
await shell.screenshot({ path: outputPath });
} else {
await page.screenshot({ path: outputPath, fullPage: true });
}
await page.close();
console.log(` OK: ${item.screenshot}`);
} catch (e) {
console.log(` FAIL: ${item.screenshot} - ${e.message}`);
}
}
}
await browser.close();
}
takeScreenshots().catch(console.error);