feat(health+miniprogram): 预约/报告/随访/资讯/家庭管理 — Chunk 4-6
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

后端:
- 添加 articles 表迁移 + Entity + Service + Handler
- 健康数据趋势 API (get_mini_trend) 注册路由
- article CRUD (list/get) + DTO

前端 (11个新页面 + 5个服务):
- 预约挂号: 列表/创建向导/详情页
- 报告管理: 列表/详情页
- 随访管理: 任务列表/记录详情页
- 资讯文章: 文章详情页
- 个人中心: 就诊人管理/新增/我的报告/我的随访/用药提醒/设置
- 更新 app.config.ts 注册全部路由
- 更新 profile/article 页面为真实功能
This commit is contained in:
iven
2026-04-24 00:58:40 +08:00
parent ee9a5c4da1
commit 9ef65b9a9f
53 changed files with 6044 additions and 32 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,509 @@
# HMS 患者小程序设计规格
> **版本**: v1.0
> **日期**: 2026-04-23
> **状态**: 草案
> **关联**: 健康模块设计规格 `2026-04-23-health-management-module-design.md`
---
## 1. 概述
### 1.1 产品定位
HMS 患者小程序是**综合健康管理入口**,面向体检中心/医疗机构的患者。覆盖体检预约、报告查询、健康数据长期监测、随访管理、家庭健康管理等场景。
医护端以 PC 管理后台(`apps/web/`)为主力,小程序聚焦患者体验。医护端小程序可在后续按需补一个轻量版(随访提醒、排班查看),不在本规格范围内。
### 1.2 核心决策
| 维度 | 决策 | 原因 |
|------|------|------|
| 技术选型 | Taro 4 + React 19 | 与 Web 端 React 技能复用,支持多端编译 |
| 架构方案 | 直连后端 | MVP 阶段最务实,复用 erp-server API |
| 登录方式 | 微信授权 + 手机号补充 | 降低门槛 + 确保身份可靠 |
| 代码位置 | `apps/miniprogram/`Monorepo | 方便接口同步,共享类型定义 |
| 目标平台 | 微信小程序优先 | 覆盖最广泛用户,后续可扩展 |
| 数据录入 | 手动 + 蓝牙预留接口 | MVP 快速交付,后续对接设备 |
| 视觉风格 | 医疗清新(青色主调) | 专业可靠,沿用现有 HTML 原型风格 |
### 1.3 MVP 功能范围
**MVP 包含7 个功能模块):**
1. 登录 + 个人中心
2. 健康数据录入 + 趋势图
3. 预约挂号
4. 报告查询
5. 随访管理
6. 家庭健康管理(就诊人切换)
7. 健康资讯 + 用药提醒
**后续版本:**
- 在线咨询即时通讯WebSocket 长连接)
---
## 2. 项目结构
```
apps/miniprogram/
├── config/ # Taro 编译配置
│ ├── index.ts # 通用配置
│ ├── dev.ts # 开发环境
│ └── prod.ts # 生产环境
├── project.config.json # 微信小程序项目配置
├── src/
│ ├── app.config.ts # Taro 全局配置TabBar、页面路由
│ ├── app.tsx # 入口组件
│ ├── app.scss # 全局样式(医疗清新主题变量)
│ ├── components/ # 通用组件
│ │ ├── HealthCard/ # 健康指标卡片(血压/血糖/体重)
│ │ ├── AppointmentCard/ # 预约卡片
│ │ ├── ReportItem/ # 报告列表项
│ │ ├── FamilyPicker/ # 就诊人切换器
│ │ ├── EmptyState/ # 空状态占位
│ │ └── TrendChart/ # 趋势图echarts-taro3-react
│ ├── pages/
│ │ ├── index/ # 首页(今日健康+快捷入口+待办)
│ │ ├── health/ # 健康数据(录入+趋势图)
│ │ ├── appointment/ # 预约(列表+新建预约)
│ │ ├── report/ # 报告(体检报告+化验单)
│ │ ├── followup/ # 随访(任务+问卷填写)
│ │ ├── article/ # 健康资讯(文章列表+详情)
│ │ ├── profile/ # 我的(个人信息+就诊人管理+设置)
│ │ └── login/ # 登录(微信授权+手机号)
│ ├── services/ # API 调用层
│ │ ├── request.ts # 封装 Taro.requestJWT 注入、错误处理)
│ │ ├── auth.ts # 登录/刷新 token
│ │ ├── health.ts # 健康数据 CRUD
│ │ ├── appointment.ts # 预约 CRUD
│ │ ├── report.ts # 报告查询
│ │ ├── followup.ts # 随访任务/记录
│ │ └── article.ts # 资讯/科普
│ ├── stores/ # Zustand 状态管理
│ │ ├── auth.ts # 登录态、用户信息、就诊人列表
│ │ └── health.ts # 健康数据缓存
│ ├── utils/
│ │ ├── bluetooth.ts # 蓝牙接口预留MVP 不实现)
│ │ ├── format.ts # 日期/数值格式化
│ │ └── constants.ts # 常量定义
│ └── styles/
│ ├── variables.scss # 主题变量(青色主调)
│ └── mixins.scss # 常用样式混入
├── package.json
└── tsconfig.json
```
**设计原则:**
- services 层与 Web 端 `apps/web/src/api/` 职责对齐,用 Taro.request 替代 fetch
- stores 复用 Zustand 模式,与 Web 端保持一致的状态管理风格
- 组件命名 PascalCase 目录,与 Web 端风格统一
- MVP 阶段不强抽取 `packages/shared/`,等两端跑起来后根据重复度决定
---
## 3. 认证流程
### 3.1 整体流程
```
用户打开小程序
检查本地 storage 有无有效 JWT
├── 有且未过期 → 直接进入首页
└── 无或已过期 ↓
Step 1: 微信静默登录
wx.login() → code
→ POST /api/v1/auth/wechat/login { code }
→ 后端用 code 换 openid查找绑定用户
├── 已绑定 → 签发 JWT { token, user }
└── 未绑定 → 返回 { need_bind: true, openid }
Step 2: 手机号绑定(仅新用户)
wx.getPhoneNumber 按钮组件 → encryptedData + iv
→ POST /api/v1/auth/wechat/bind-phone { openid, encryptedData, iv }
→ 后端解密手机号,创建/关联 user + patient 档案
→ 签发 JWT { token, user, patient }
Step 3: 补充档案(首次绑定后)
→ 引导填写姓名、性别、出生日期、身份证号(可选)
→ PUT /api/v1/health/patients/me { name, gender, birthday }
```
### 3.2 后端新增内容
**`erp-auth` 新增:**
| 新增 | 说明 |
|------|------|
| `wechat_users` 表 | `id, openid, union_id, user_id, phone, created_at, updated_at` |
| `POST /api/v1/auth/wechat/login` | code → openid 查询,返回绑定状态 |
| `POST /api/v1/auth/wechat/bind-phone` | 绑定手机号,创建 user + patient |
| `GET /api/v1/auth/wechat/qrcode` | 生成带参数小程序码PC 端扫码登录场景) |
`wechat_users` 表必须包含 `tenant_id`(多租户隔离)和标准审计字段(`created_at`, `updated_at`, `deleted_at`)。
### 3.3 Token 策略
| Token | 有效期 | 存储 |
|-------|--------|------|
| Access Token (JWT) | 15 分钟 | 内存 + Taro.setStorage |
| Refresh Token | 7 天 | Taro.setStorage |
自动刷新机制:`services/request.ts` 拦截 401 → 调用 `POST /auth/refresh` → 重试原请求。刷新失败则跳转登录页。
### 3.4 多就诊人
- 一个微信账号可管理多个 patient本人 + 家人)
- 切换就诊人时请求 header 带 `X-Patient-Id`
- 后端校验该 patient 属于当前 user
---
## 4. 页面结构与导航
### 4.1 Tab Bar
底部导航栏 5 个入口:
| Tab | 图标 | 页面路径 |
|-----|------|----------|
| 首页 | 🏠 | /pages/index/index |
| 健康 | 📊 | /pages/health/index |
| 预约 | 📅 | /pages/appointment/index |
| 资讯 | 📰 | /pages/article/index |
| 我的 | 👤 | /pages/profile/index |
### 4.2 页面层级
```
Tab: 首页 /pages/index
├── /pages/notifications/index # 通知列表
└── /pages/followup/detail/index # 随访任务详情
Tab: 健康 /pages/health
├── /pages/health/input/index # 录入数据
├── /pages/health/trend/index # 指标趋势
└── /pages/health/history/index # 历史记录
Tab: 预约 /pages/appointment
├── /pages/appointment/create/index # 新建预约
└── /pages/appointment/detail/index # 预约详情
Tab: 资讯 /pages/article
└── /pages/article/detail/index # 文章详情
Tab: 我的 /pages/profile
├── /pages/profile/family/index # 就诊人管理
├── /pages/profile/family-add/index # 添加就诊人
├── /pages/profile/reports/index # 我的报告
├── /pages/profile/followups/index # 我的随访
├── /pages/profile/medication/index # 用药提醒
└── /pages/profile/settings/index # 设置
独立页面(不在 Tab 内):
├── /pages/login/index # 登录
└── /pages/login/profile/index # 档案补全
```
### 4.3 首页布局
```
┌─────────────────────────────┐
│ 问候栏(渐变青色背景) │ 用户名 + 日期 + 通知铃铛
├─────────────────────────────┤
│ 今日健康卡片(上浮 -20px │ 血压/心率/血糖/体重 2×2 网格
├─────────────────────────────┤
│ 快捷服务4 宫格) │ 录数据/预约/报告/随访
├─────────────────────────────┤
│ 即将到来 │ 最近 1 条预约卡片
├─────────────────────────────┤
│ 待办随访 │ 最多 2 条待办 + 查看全部
├─────────────────────────────┤
│ [ 首页 ] [ 健康 ] [ 预约 ] [ 资讯 ] [ 我的 ] │
└─────────────────────────────┘
```
---
## 5. 核心功能数据流
### 5.1 健康数据录入
```
选择指标类型 → 输入数值 + 测量时间 → 添加备注(可选)→ POST /vital-signs
成功 → 更新首页卡片 + 趋势缓存
失败 → Toast + 本地暂存
```
**MVP 支持的指标类型:**
| 指标 | 单位 | 输入控件 |
|------|------|---------|
| 收缩压 / 舒张压 | mmHg | 两个数字输入框 |
| 心率 | bpm | 数字输入框 |
| 空腹血糖 | mmol/L | 数字输入框 |
| 餐后血糖 | mmol/L | 数字输入框 |
| 体重 | kg | 数字输入框1 位小数) |
| 体温 | ℃ | 数字输入框1 位小数) |
每次可同时填多项或只填一项。录入时间默认当前,可手动调整为当天任意时间。
### 5.2 预约挂号
```
选择科室 → 选择医生 → 选择日期(排班日历)→ 选择时段 → 确认预约
POST /appointments
成功 → 订阅消息通知 + 日历同步
满员 → 提示"该时段已满"
```
**关键交互:**
- 排班日历用周视图,有排班的日期标绿点
- 点击日期后展示该日可用时段
- 时段显示"剩余 X 位"
- 预约成功后支持微信订阅消息提醒
### 5.3 报告查询
```
报告列表(分页,时间倒序)
↓ 点击某份报告
报告详情 → 基本信息卡 + 指标列表 + PDF/图片附件预览
```
**指标状态标记:**
- 异常偏高:红色 + ↑ 箭头
- 异常偏低:红色 + ↓ 箭头
- 正常范围:灰色
### 5.4 随访管理
```
待办列表(按截止日期排序)
↓ 支持"待完成/已完成/已过期"筛选
点击任务 → 动态表单(后端定义字段)→ 提交 → 标记完成
```
问卷由 PC 端医护创建follow_up_task小程序负责展示和填写。提交后创建 follow_up_record。
### 5.5 家庭健康管理
```
就诊人列表(本人 + 已添加家属)
↓ 点击头像或下拉切换
切换就诊人 → 全局 X-Patient-Id 更新 → 所有页面数据刷新
↓ 添加家属
填写信息 → 姓名 + 关系 + 身份证号(可选)→ POST /patients
```
切换就诊人通过全局 store 更新,所有 service 请求自动携带新 `X-Patient-Id`
### 5.6 健康资讯 + 用药提醒
**资讯:**
- `GET /articles` → 分页列表(缩略图 + 标题 + 摘要 + 时间)
- 文章详情使用 Taro `RichText` 组件渲染富文本
**用药提醒MVP**
- 小程序本地 storage 存储提醒规则(药品名 + 频率 + 时间)
- 每日触发检查
- 通过微信订阅消息推送提醒
- 不依赖后端新表
---
## 6. API 集成与状态管理
### 6.1 请求层封装
`services/request.ts` 职责:
| 拦截点 | 行为 |
|--------|------|
| 请求拦截 | 自动注入 `Authorization: Bearer {token}` |
| 请求拦截 | 自动注入 `X-Patient-Id`(当前选中就诊人) |
| 请求拦截 | 自动注入 `X-Tenant-Id`(从登录信息获取) |
| 响应拦截 | 401 → 静默刷新 token → 重试原请求 |
| 响应拦截 | 刷新失败 → 跳转登录页 |
| 错误处理 | 网络错误 / 业务错误 / 超时统一处理 |
多租户处理:患者只属于一个租户。登录时后端返回 `tenant_id`,前端每次请求带上。不走 `tenant_id` 中间件自动注入。
### 6.2 Zustand Stores
**auth store**
```typescript
interface AuthState {
token: string | null
refreshToken: string | null
user: { id: string; name: string; phone: string; avatar: string } | null
currentPatient: Patient | null
patients: Patient[]
setCurrentPatient: (id: string) => void
login: (code: string) => Promise<void>
bindPhone: (data: BindPhoneData) => Promise<void>
logout: () => void
}
```
**health store**
```typescript
interface HealthState {
todaySummary: VitalSigns | null
trendData: Record<string, TrendPoint[]>
refreshToday: () => Promise<void>
getTrend: (type: string, range: '7d' | '30d' | '90d') => Promise<TrendPoint[]>
}
```
### 6.3 API 端点对应表
| 小程序 service | 后端端点 | 方法 |
|----------------|----------|------|
| `auth.login(code)` | `/api/v1/auth/wechat/login` | POST |
| `auth.bindPhone(data)` | `/api/v1/auth/wechat/bind-phone` | POST |
| `auth.refresh()` | `/api/v1/auth/refresh` | POST |
| `health.getToday()` | `/api/v1/health/vital-signs?date=today` | GET |
| `health.input(data)` | `/api/v1/health/vital-signs` | POST |
| `health.getTrend(type, range)` | `/api/v1/health/vital-signs/trend` | GET |
| `appointment.list()` | `/api/v1/health/appointments` | GET |
| `appointment.create(data)` | `/api/v1/health/appointments` | POST |
| `appointment.cancel(id)` | `/api/v1/health/appointments/:id/cancel` | PUT |
| `schedule.getByDoctor(id)` | `/api/v1/health/doctor-schedules` | GET |
| `report.list()` | `/api/v1/health/lab-reports` | GET |
| `report.detail(id)` | `/api/v1/health/lab-reports/:id` | GET |
| `followup.list()` | `/api/v1/health/follow-up-tasks` | GET |
| `followup.submit(id, data)` | `/api/v1/health/follow-up-records` | POST |
| `patient.list()` | `/api/v1/health/patients` | GET |
| `patient.create(data)` | `/api/v1/health/patients` | POST |
| `patient.update(id, data)` | `/api/v1/health/patients/:id` | PUT |
**后端需新增的端点(尚未实现):**
| 端点 | 说明 |
|------|------|
| `POST /auth/wechat/login` | 微信登录 |
| `POST /auth/wechat/bind-phone` | 手机号绑定 |
| `GET /vital-signs/trend` | 趋势聚合查询 |
| `GET /doctor-schedules` | 按科室/医生查询排班 |
| `GET /articles` | 健康资讯列表 |
| `GET /articles/:id` | 资讯详情 |
---
## 7. 视觉设计
### 7.1 主题色
沿用现有 HTML 原型的医疗清新风格:
| 用途 | 色值 | 说明 |
|------|------|------|
| 主色 | `#0891B2` | 青色,按钮、导航、强调 |
| 主色浅 | `#E0F7FA` | 背景、卡片高亮 |
| 主色深 | `#065A73` | 渐变、按压态 |
| 辅助色 | `#059669` | 绿色,成功、正常指标 |
| 危险色 | `#DC2626` | 红色,异常指标、删除 |
| 警告色 | `#D97706` | 琥珀,待办、提醒 |
| 背景色 | `#F0FDFA` | 页面底色 |
| 卡片色 | `#FFFFFF` | 卡片背景 |
| 主文字 | `#134E4A` | 标题、正文 |
| 副文字 | `#6B7280` | 说明、标签 |
| 轻文字 | `#94A3B8` | 时间戳、占位符 |
### 7.2 圆角规范
| 元素 | 圆角 |
|------|------|
| 卡片 | 12px |
| 按钮 | 8px |
| 输入框 | 8px |
| 头像 | 50% |
| 快捷图标 | 14px |
### 7.3 阴影规范
| 层级 | 值 |
|------|---|
| 轻阴影 | `0 1px 3px rgba(0,0,0,.04)` |
| 标准阴影 | `0 2px 8px rgba(0,0,0,.06)` |
| 中阴影 | `0 4px 16px rgba(0,0,0,.08)` |
| 重阴影 | `0 8px 32px rgba(0,0,0,.12)` |
---
## 8. 开发工作流
### 8.1 开发环境
```bash
# 安装依赖
cd apps/miniprogram && pnpm install
# 开发模式(需配合微信开发者工具)
pnpm dev:weapp # Taro 编译 + watch → dist/
# 用微信开发者工具打开 dist/ 目录预览
# 生产构建
pnpm build:weapp # 压缩 + tree-shaking
# 后端联调
# 需同时运行 erp-server (port 3000)
# 小程序开发设置中关闭域名校验(开发阶段)
```
### 8.2 与 Web 端的代码复用
| 复用内容 | 方式 | 说明 |
|---------|------|------|
| TypeScript 类型 | 按需引用 Web 端 DTO 类型 | API 请求/响应结构一致 |
| 主题变量值 | Web CSS 变量 → SCSS 变量 | 青色主调色值保持一致 |
| Zustand 模式 | 相同 store 设计模式 | 各自独立实现 |
| API 接口定义 | service 层函数签名对齐 | Web 用 fetch小程序用 Taro.request |
### 8.3 后端需同步开发的内容
| 优先级 | 内容 | 涉及 crate |
|--------|------|-----------|
| P0 | `wechat_users` 表 + 微信登录/绑定 API | erp-auth |
| P0 | `vital_signs` 趋势查询 API | erp-health |
| P0 | `doctor_schedules` 按科室/医生查询 API | erp-health |
| P1 | `lab_reports` 指标异常标注字段 | erp-health |
| P1 | `follow_up_tasks` 动态问卷字段扩展 | erp-health |
| P2 | `articles` 表 + CRUD | erp-health |
| P2 | 微信订阅消息模板注册 | erp-server |
---
## 9. 分期交付计划
| 阶段 | 内容 | 目标 |
|------|------|------|
| Phase 1 | 项目骨架 + 登录流程 + 首页(静态数据) | 基础搭建 |
| Phase 2 | 健康数据录入 + 趋势图 | 核心功能 |
| Phase 3 | 预约挂号 + 排班日历 | 核心功能 |
| Phase 4 | 报告查询 + 家庭管理 | 扩展功能 |
| Phase 5 | 随访 + 资讯 + 用药提醒 | 扩展功能 |
| Phase 6 | 打磨 + 真机测试 + 提审 | 上线准备 |
每个 Phase 内部遵循:先对接后端 API → 再实现 UI → 真机验证 → 提交。
---
## 10. 约束与风险
| 约束/风险 | 应对策略 |
|-----------|---------|
| 小程序包体积限制2MB 主包) | 按功能分包加载,图表库按需引入 |
| 微信审核周期3-7 天) | Phase 6 预留充足审核时间 |
| 后端 API 部分未实现 | 小程序开发与后端同步推进,优先实现 P0 端点 |
| 微信订阅消息需用户主动触发 | 在预约成功、随访提交等场景引导用户订阅 |
| 蓝牙设备适配复杂 | MVP 预留接口不实现,后续按设备型号逐一对接 |
| 多就诊人数据隔离 | 后端严格校验 user-patient 归属关系 |