安全修复: - H1: Token 刷新竞态条件 → Singleton Promise 模式防止并发刷新 - H4: 移除 store 中的 token 明文状态,统一走 secure storage - H5: 登录/绑定手机号添加 loading 防重复点击保护 - H6: Analytics 改用 request.ts 统一请求层,不再绕过认证 - M1: logout 清理所有残留数据(openid/tenant_id/analytics_queue) - M2/M7: 敏感数据(user/openid/tenant_id)统一走加密存储 - M3: 移除开发日志中的请求体打印 - M4: secure-storage 解密失败返回 null 而非空串 功能修复: - F1: 今日体征概览 API 支持 patient_id 查询参数(后端+前端) - F2: 积分商城对无患者档案用户展示引导 UI - M6: daily-monitoring 添加 Zod 数值范围验证 清理: - L4: 移除 devLogin 开发辅助函数
241 lines
9.1 KiB
Markdown
241 lines
9.1 KiB
Markdown
---
|
||
title: 微信小程序(患者端)
|
||
updated: 2026-04-26
|
||
status: active
|
||
tags: [miniprogram, taro, wechat, patient]
|
||
---
|
||
|
||
# 微信小程序(患者端)
|
||
|
||
> 从 [[index]] 导航。关联: [[infrastructure]] [[erp-server]] [[erp-health]]
|
||
|
||
## 1. 设计决策
|
||
|
||
- **Taro 4.2 + React 18** — 跨平台小程序框架,React 生态
|
||
- **Zustand 状态管理** — 与 Web 前端一致的 store 模式
|
||
- **微信登录流程** — `Taro.login()` → code → 后端 `jscode2session` → openid → JWT
|
||
- **build 时注入环境变量** — `process.env` 在小程序运行时不存在,必须通过 `defineConstants` 编译时替换
|
||
- **不使用浏览器 Web API** — `btoa`/`atob` 等在小程序中不可用,用 Taro 原生 API 替代
|
||
- **Zod 输入验证** — 健康数据录入使用 Zod schema 验证
|
||
|
||
### 版本
|
||
|
||
Taro 4.2 / React 18 / TypeScript / Zustand 5 / Sass / Zod
|
||
|
||
## 2. 关键文件 + 数据流
|
||
|
||
### 核心文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `apps/miniprogram/config/index.ts` | Taro 构建配置(defineConstants 注入环境变量) |
|
||
| `apps/miniprogram/src/services/request.ts` | HTTP 请求封装(401 自动刷新、错误处理) |
|
||
| `apps/miniprogram/src/services/auth.ts` | 微信登录/绑定手机号 API |
|
||
| `apps/miniprogram/src/stores/auth.ts` | 认证状态(login/bindPhone/restore) |
|
||
| `apps/miniprogram/src/utils/secure-storage.ts` | token 安全存储(XOR + Base64 混淆) |
|
||
| `apps/miniprogram/project.config.json` | 微信开发者工具配置(AppID、urlCheck) |
|
||
|
||
### 微信登录流程
|
||
|
||
```
|
||
用户点击"微信一键登录"
|
||
↓
|
||
Taro.login() → 临时 code
|
||
↓
|
||
POST /auth/wechat/login { code }
|
||
↓
|
||
后端调用微信 jscode2session → 获取 openid + session_key
|
||
↓
|
||
查询 wechat_users 表
|
||
├── 已绑定 → 返回 { bound: true, token: JWT } → 跳转首页
|
||
└── 未绑定 → 返回 { bound: false, openid } → 显示"授权手机号"按钮
|
||
↓ 用户授权手机号
|
||
POST /auth/wechat/bind-phone { openid, encrypted_data, iv }
|
||
↓
|
||
后端解密手机号 → 创建/关联用户 → 返回 JWT → 跳转首页
|
||
```
|
||
|
||
### 页面结构(40 个页面,15 个目录)
|
||
|
||
#### 患者端页面
|
||
|
||
| 页面路径 | 说明 |
|
||
|----------|------|
|
||
| `pages/login/index` | 登录页(微信登录 + 协议勾选) |
|
||
| `pages/index/index` | 首页(今日健康、快捷服务) |
|
||
| `pages/health/index` | 健康上报(Tab 页) |
|
||
| `pages/health/input/index` | 健康数据录入(Zod 验证) |
|
||
| `pages/health/trend/index` | 健康趋势(体征数据折线图) |
|
||
| `pages/health/daily-monitoring/index` | 日常监测数据 |
|
||
| `pages/appointment/index` | 预约列表 |
|
||
| `pages/appointment/create/index` | 预约挂号 |
|
||
| `pages/appointment/detail/index` | 预约详情 |
|
||
| `pages/article/index` | 健康资讯列表 |
|
||
| `pages/article/detail/index` | 文章详情 |
|
||
| `pages/report/detail/index` | 健康报告详情 |
|
||
| `pages/ai-report/list/index` | AI 分析报告列表 |
|
||
| `pages/ai-report/detail/index` | AI 分析报告详情 |
|
||
| `pages/followup/detail/index` | 随访详情 |
|
||
| `pages/consultation/index` | 咨询列表(Tab 页) |
|
||
| `pages/consultation/detail/index` | 咨询详情 |
|
||
| `pages/mall/index` | 积分商城(Tab 页) |
|
||
| `pages/mall/detail/index` | 商品详情 |
|
||
| `pages/mall/exchange/index` | 积分兑换 |
|
||
| `pages/mall/orders/index` | 积分订单 |
|
||
| `pages/events/index` | 线下活动 |
|
||
| `pages/profile/index` | 个人中心(Tab 页) |
|
||
| `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` | 设置 |
|
||
| `pages/legal/user-agreement` | 用户服务协议 |
|
||
| `pages/legal/privacy-policy` | 隐私政策 |
|
||
|
||
#### 医护端页面(8 个)
|
||
|
||
| 页面路径 | 说明 |
|
||
|----------|------|
|
||
| `pages/doctor/index` | 医护首页 |
|
||
| `pages/doctor/patients/index` | 患者列表 |
|
||
| `pages/doctor/patients/detail/index` | 患者详情 |
|
||
| `pages/doctor/consultation/index` | 咨询管理 |
|
||
| `pages/doctor/consultation/detail/index` | 咨询详情 |
|
||
| `pages/doctor/followup/index` | 随访管理 |
|
||
| `pages/doctor/followup/detail/index` | 随访详情 |
|
||
| `pages/doctor/report/index` | 报告管理 |
|
||
| `pages/doctor/report/detail/index` | 报告详情 |
|
||
|
||
### 服务层(10+ 个文件)
|
||
|
||
| 文件 | 覆盖 |
|
||
|------|------|
|
||
| `request.ts` | HTTP 封装(401 刷新、错误处理) |
|
||
| `auth.ts` | 微信登录/绑定手机号 |
|
||
| `health.ts` | 体征数据/健康趋势 |
|
||
| `patient.ts` | 患者信息/家庭成员 |
|
||
| `appointment.ts` | 预约挂号/详情/取消 |
|
||
| `followup.ts` | 随访任务/详情 |
|
||
| `article.ts` | 健康文章 |
|
||
| `report.ts` | 健康报告 |
|
||
| `analytics.ts` | 数据分析 |
|
||
| `wechat-templates.ts` | 微信模板消息 ID |
|
||
|
||
### 组件(9 个)
|
||
|
||
| 组件 | 用途 |
|
||
|------|------|
|
||
| `EmptyState` | 空状态占位 |
|
||
| `ErrorBoundary` | 错误边界捕获 |
|
||
| `ErrorState` | 错误状态展示 |
|
||
| `FamilyPicker` | 家庭成员选择器 |
|
||
| `HealthCard` | 健康数据卡片 |
|
||
| `Loading` | 加载状态 |
|
||
| `StepIndicator` | 步骤指示器 |
|
||
| `TrendChart` | 趋势图表(ECharts) |
|
||
| `WeekCalendar` | 周日历组件 |
|
||
|
||
### 集成契约
|
||
|
||
| 方向 | 模块 | 接口 | 触发时机 |
|
||
|------|------|------|---------|
|
||
| 调用 → | [[erp-server]] | `POST /auth/wechat/login` | 微信登录 |
|
||
| 调用 → | [[erp-server]] | `POST /auth/wechat/bind-phone` | 手机号绑定 |
|
||
| 调用 → | [[erp-health]] | `/api/v1/health/*` | 健康数据查询 |
|
||
| 调用 → | [[erp-server]] | `/api/v1/auth/refresh` | Token 刷新 |
|
||
|
||
## 3. 代码逻辑
|
||
|
||
### 环境变量注入(关键)
|
||
|
||
小程序运行时没有 `process.env`,必须在 `config/index.ts` 中通过 `defineConstants` 编译时替换:
|
||
|
||
```typescript
|
||
defineConstants: {
|
||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||
'process.env.TARO_APP_API_URL': JSON.stringify(process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1'),
|
||
'process.env.TARO_APP_ENCRYPTION_KEY': JSON.stringify(process.env.TARO_APP_ENCRYPTION_KEY || ''),
|
||
},
|
||
```
|
||
|
||
### Token 安全存储
|
||
|
||
`secure-storage.ts` 使用 XOR 混淆 + Base64 编码存储 token:
|
||
- **不使用 `btoa`/`atob`** — 小程序环境不支持
|
||
- 使用 `Taro.arrayBufferToBase64` / `Taro.base64ToArrayBuffer` 替代
|
||
- 加密密钥通过 `TARO_APP_ENCRYPTION_KEY` 环境变量注入
|
||
|
||
### 请求封装(request.ts)
|
||
|
||
- 401 自动尝试 refresh token,失败后跳转登录页
|
||
- 错误响应格式: `{ error: string, message: string }`(无 `success` 字段)
|
||
- 成功响应格式: `{ success: true, data: T }`
|
||
- 当前 URL 拼接方式构建查询参数(待重构为 params 对象)
|
||
|
||
### 健康数据录入验证
|
||
|
||
使用 Zod schema 验证用户输入的体征数据(血压、心率、体重、血糖),确保数据类型和范围合法。
|
||
|
||
## 4. 构建与调试
|
||
|
||
### 构建
|
||
|
||
```bash
|
||
cd apps/miniprogram && pnpm build:weapp # 生产构建
|
||
cd apps/miniprogram && npx taro build --type weapp # 等效
|
||
```
|
||
|
||
### 微信开发者工具配置
|
||
|
||
| 配置项 | 值 | 说明 |
|
||
|--------|-----|------|
|
||
| AppID | `wx20f4ef9cc2ec66c5` | 真实微信 AppID |
|
||
| urlCheck | `false` | 不校验合法域名(开发模式) |
|
||
| miniprogramRoot | `dist/` | 编译输出目录 |
|
||
|
||
### 后端微信配置
|
||
|
||
`crates/erp-server/config/default.toml`:
|
||
|
||
```toml
|
||
[wechat]
|
||
appid = "wx20f4ef9cc2ec66c5"
|
||
secret = "<通过环境变量 ERP__WECHAT__SECRET 设置>"
|
||
```
|
||
|
||
## 5. 活跃问题 + 陷阱
|
||
|
||
### 历史教训
|
||
|
||
| 问题 | 根因 | 修复 |
|
||
|------|------|------|
|
||
| 页面空白 `ReferenceError: process is not defined` | `process.env` 在运行时不存在 | `defineConstants` 编译时替换 |
|
||
| 登录成功但前端报失败 `btoa is not defined` | `secure-storage.ts` 使用 Web API | 改用 `Taro.arrayBufferToBase64` |
|
||
| 微信登录 500 `wechat_users.created_by 不存在` | 迁移创建的表缺少标准字段 | `ALTER TABLE` 补列 |
|
||
| 401 循环重定向 | 首页未登录时 `request.ts` 反复 redirectTo | 检查当前路径避免重复跳转 |
|
||
|
||
### 待优化
|
||
|
||
| 问题 | 级别 | 说明 |
|
||
|------|------|------|
|
||
| URL 拼接构建查询参数 | P2 | `request.ts` 应支持 params 对象 |
|
||
| 加密密钥硬编码 | P0 | 需外部化到 `TARO_APP_ENCRYPTION_KEY` 环境变量 |
|
||
| Auth token 日志输出 | P0 | 生产环境需移除 console.log |
|
||
| 生产配置 | P2 | `urlCheck`/`minified` 需区分环境 |
|
||
|
||
### 注意事项
|
||
|
||
- `Taro.login()` 的 code 一次性使用,每次调用会返回新 code
|
||
- `session_key` 缓存 5 分钟(TTL),过期需重新登录
|
||
- 微信开发者工具中 `getPhoneNumber` 需要真机调试或使用测试号
|
||
- Redis 不可达时限流降级为 fail-open,不影响登录
|
||
|
||
## 6. 变更记录
|
||
|
||
| 日期 | 变更 |
|
||
|------|------|
|
||
| 2026-04-26 | 全面更新:40 页面(含 9 个医护端页面)、15 目录、5 个 Tab 页、积分商城、线下活动 |
|
||
| 2026-04-25 | 全面更新:20 页面、10 服务、9 组件、Zod 验证、加密密钥外部化说明 |
|
||
| 2026-04-24 | 创建小程序 wiki 页面,记录登录流程、环境配置、历史陷阱 |
|