Files
hms/docs/superpowers/plans/2026-04-23-hms-miniprogram-plan.md
iven 9ef65b9a9f
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
feat(health+miniprogram): 预约/报告/随访/资讯/家庭管理 — Chunk 4-6
后端:
- 添加 articles 表迁移 + Entity + Service + Handler
- 健康数据趋势 API (get_mini_trend) 注册路由
- article CRUD (list/get) + DTO

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

1245 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HMS 患者小程序实施计划
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 基于 Taro 4 + React 19 构建微信小程序患者端,直连 erp-server 后端 API。
**Architecture:** 小程序代码位于 `apps/miniprogram/`,通过 Taro 编译为微信小程序。后端在 erp-auth 新增微信登录支持erp-health 新增小程序所需 API。前端用 Zustand 管理状态services 层封装 Taro.request 调用后端 `/api/v1/` 端点。
**Tech Stack:** Taro 4, React 19, TypeScript 6, Zustand 5, SCSS, echarts-taro3-react, Rust/Axum/SeaORM (后端)
**Spec:** `docs/superpowers/specs/2026-04-23-hms-miniprogram-design.md`
---
## Chunk 1: Phase 1 — 后端微信认证
### Task 1: 创建 wechat_users 数据库迁移
**Files:**
- Create: `crates/erp-server/migration/src/m20260423_000001_wechat_users.rs`
- [ ] **Step 1: 创建迁移文件**
```rust
// m20260423_000001_wechat_users.rs
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationAction)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(WechatUsers::Table)
.if_not_exists()
.col(ColumnDef::new(WechatUsers::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(WechatUsers::TenantId).uuid().not_null())
.col(ColumnDef::new(WechatUsers::Openid).string().not_null())
.col(ColumnDef::new(WechatUsers::UnionId).string())
.col(ColumnDef::new(WechatUsers::UserId).uuid().not_null())
.col(ColumnDef::new(WechatUsers::Phone).string())
.col(ColumnDef::new(WechatUsers::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
.col(ColumnDef::new(WechatUsers::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
.col(ColumnDef::new(WechatUsers::DeletedAt).timestamp_with_time_zone())
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_wechat_users_openid")
.table(WechatUsers::Table)
.col(WechatUsers::Openid)
.col(WechatUsers::TenantId)
.unique()
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_index(Index::drop().name("idx_wechat_users_openid").table(WechatUsers::Table).to_owned()).await?;
manager.drop_table(Table::drop().table(WechatUsers::Table).to_owned()).await
}
}
#[derive(DeriveIden)]
enum WechatUsers {
Table,
Id,
TenantId,
Openid,
UnionId,
UserId,
Phone,
CreatedAt,
UpdatedAt,
DeletedAt,
}
```
- [ ] **Step 2: 注册迁移**
`crates/erp-server/migration/src/lib.rs` 的 migration 列表中添加新迁移。
- [ ] **Step 3: 验证迁移**
Run: `cargo check -p erp-server`
Expected: 编译通过
- [ ] **Step 4: 提交**
```bash
git add crates/erp-server/migration/src/m20260423_000001_wechat_users.rs crates/erp-server/migration/src/lib.rs
git commit -m "feat(auth): 添加 wechat_users 表迁移"
```
---
### Task 2: 创建 wechat_users Entity
**Files:**
- Create: `crates/erp-auth/src/entity/wechat_user.rs`
- Modify: `crates/erp-auth/src/entity/mod.rs`
- [ ] **Step 1: 创建 Entity 文件**
参照 `crates/erp-auth/src/entity/user_token.rs` 的模式,创建 `wechat_user.rs`,包含 `Model``Relation``ActiveModelBehavior`
- [ ] **Step 2: 在 mod.rs 注册**
`crates/erp-auth/src/entity/mod.rs` 添加 `pub mod wechat_user;`
- [ ] **Step 3: 验证**
Run: `cargo check -p erp-auth`
Expected: 编译通过
- [ ] **Step 4: 提交**
```bash
git add crates/erp-auth/src/entity/wechat_user.rs crates/erp-auth/src/entity/mod.rs
git commit -m "feat(auth): 添加 wechat_user SeaORM entity"
```
---
### Task 3: 微信登录 DTO
**Files:**
- Modify: `crates/erp-auth/src/dto.rs`
- [ ] **Step 1: 添加微信登录相关 DTO**
`crates/erp-auth/src/dto.rs` 中新增:
```rust
// 微信登录请求
#[derive(Deserialize, ToSchema)]
pub struct WechatLoginReq {
pub code: String,
}
// 微信登录响应
#[derive(Serialize, ToSchema)]
pub struct WechatLoginResp {
pub bound: bool,
pub openid: String,
pub token: Option<LoginResp>, // 已绑定时返回
}
// 绑定手机号请求
#[derive(Deserialize, ToSchema)]
pub struct WechatBindPhoneReq {
pub openid: String,
pub encrypted_data: String,
pub iv: String,
}
```
- [ ] **Step 2: 验证**
Run: `cargo check -p erp-auth`
- [ ] **Step 3: 提交**
```bash
git add crates/erp-auth/src/dto.rs
git commit -m "feat(auth): 添加微信登录 DTO"
```
---
### Task 4: 微信登录 Service
**Files:**
- Create: `crates/erp-auth/src/service/wechat_service.rs`
- Modify: `crates/erp-auth/src/service/mod.rs`
- [ ] **Step 1: 实现 wechat_service.rs**
核心逻辑:
- `login(state, tenant_id, code)`: 用 code 调用微信 API 换 openid → 查 wechat_users 表 → 返回绑定状态
- `bind_phone(state, tenant_id, req)`: 解密手机号 → 查找/创建 user → 创建 wechat_user 记录 → 签发 JWT
- 微信 API 调用:`GET https://api.weixin.qq.com/sns/jscode2session?appid=...&secret=...&js_code={code}&grant_type=authorization_code`
注意appid 和 secret 从 AppConfig 中读取,需在 erp-server 配置中新增 `wechat` 段。
- [ ] **Step 2: 在 mod.rs 注册**
添加 `pub mod wechat_service;`
- [ ] **Step 3: 验证**
Run: `cargo check -p erp-auth`
- [ ] **Step 4: 提交**
```bash
git add crates/erp-auth/src/service/wechat_service.rs crates/erp-auth/src/service/mod.rs
git commit -m "feat(auth): 实现微信登录/绑定手机号 service"
```
---
### Task 5: 微信登录 Handler + 路由注册
**Files:**
- Create: `crates/erp-auth/src/handler/wechat_handler.rs`
- Modify: `crates/erp-auth/src/handler/mod.rs`
- Modify: `crates/erp-auth/src/lib.rs` (路由注册)
- [ ] **Step 1: 创建 handler**
```rust
// wechat_handler.rs
pub async fn wechat_login<S>(State(state): State<AuthState>, ...) -> Result<Json<ApiResponse<WechatLoginResp>>, AppError>
pub async fn wechat_bind_phone<S>(State(state): State<AuthState>, ...) -> Result<Json<ApiResponse<LoginResp>>, AppError>
```
- [ ] **Step 2: 注册公开路由**
微信登录端点是公开的(无需 JWT`AuthModule::public_routes()` 中添加:
- `POST /auth/wechat/login`
- `POST /auth/wechat/bind-phone`
- [ ] **Step 3: 在 AppConfig 中添加微信配置**
`crates/erp-server/src/config.rs` 中新增:
```rust
pub wechat: WechatConfig,
pub struct WechatConfig { pub appid: String, pub secret: String }
```
- [ ] **Step 4: 验证**
Run: `cargo check -p erp-server`
- [ ] **Step 5: 提交**
```bash
git add crates/erp-auth/src/handler/wechat_handler.rs crates/erp-auth/src/handler/mod.rs crates/erp-auth/src/lib.rs crates/erp-server/src/config.rs
git commit -m "feat(auth): 添加微信登录 handler 和公开路由"
```
---
### Task 6: 验证微信登录端到端
- [ ] **Step 1: 启动后端**
Run: `cargo run -p erp-server`
- [ ] **Step 2: 测试端点**
用 curl 测试:
```bash
curl -X POST http://localhost:3000/api/v1/auth/wechat/login -H "Content-Type: application/json" -d '{"code":"test_code"}'
```
Expected: 返回 `{ "success": true, "data": { "bound": false, "openid": "..." } }` 或类似响应
- [ ] **Step 3: 确认迁移执行**
检查 `wechat_users` 表已创建:
```bash
PGPASSWORD=123123 "D:\postgreSQL\bin\psql.exe" -U postgres -h localhost -d erp -c "\dt wechat_users"
```
- [ ] **Step 4: 提交确认**
如果有修复:
```bash
git add -A && git commit -m "fix(auth): 微信登录端到端验证修复"
```
---
## Chunk 2: Phase 1 — 小程序项目骨架 + 登录 + 首页
### Task 7: 初始化 Taro 项目
- [ ] **Step 1: 用 Taro CLI 初始化项目**
```bash
cd apps
npx @tarojs/cli init miniprogram --template react-ts
```
选择:
- 框架React
- TypeScriptYes
- CSS 预处理Sass
- 模板:默认模板
- 编译工具Webpack5
- [ ] **Step 2: 安装核心依赖**
```bash
cd apps/miniprogram
pnpm add zustand
pnpm add echarts echarts-taro3-react
```
- [ ] **Step 3: 配置 project.config.json**
设置 appid先用测试号 `touristappid`、ES6 转 ES5、增强编译等。
- [ ] **Step 4: 验证**
```bash
cd apps/miniprogram && pnpm dev:weapp
```
用微信开发者工具打开 `dist/` 目录,确认空白小程序可运行。
- [ ] **Step 5: 提交**
```bash
git add apps/miniprogram/
git commit -m "feat(miniprogram): 初始化 Taro 4 + React 项目骨架"
```
---
### Task 8: 全局样式 + 主题变量
**Files:**
- Modify: `apps/miniprogram/src/app.scss`
- Create: `apps/miniprogram/src/styles/variables.scss`
- Create: `apps/miniprogram/src/styles/mixins.scss`
- [ ] **Step 1: 创建主题变量**
`src/styles/variables.scss`:
```scss
$pri: #0891B2;
$pri-l: #E0F7FA;
$pri-d: #065A73;
$pri-surface: #ECFEFF;
$acc: #059669;
$acc-l: #D1FAE5;
$bg: #F0FDFA;
$card: #FFFFFF;
$tx: #134E4A;
$tx2: #6B7280;
$tx3: #94A3B8;
$bd: #E5E7EB;
$bd-l: #F3F4F6;
$dan: #DC2626;
$dan-l: #FEE2E2;
$wrn: #D97706;
$wrn-l: #FEF3C7;
$r: 12px;
$r-sm: 8px;
$r-lg: 16px;
```
- [ ] **Step 2: 创建常用 mixins**
`src/styles/mixins.scss`:
```scss
@mixin card {
background: $card;
border-radius: $r;
box-shadow: 0 2px 8px rgba(0,0,0,.06);
padding: 16px;
margin: 0 16px 12px;
}
```
- [ ] **Step 3: 更新 app.scss**
导入变量和全局基础样式(字体、背景色)。
- [ ] **Step 4: 提交**
```bash
git add apps/miniprogram/src/styles/ apps/miniprogram/src/app.scss
git commit -m "feat(miniprogram): 添加医疗清新主题样式变量"
```
---
### Task 9: services/request.ts — API 请求层
**Files:**
- Create: `apps/miniprogram/src/services/request.ts`
- [ ] **Step 1: 实现 request 封装**
```typescript
import Taro from '@tarojs/taro';
const BASE_URL = process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1';
interface ApiResponse<T> {
success: boolean;
data?: T;
message?: string;
}
async function getHeaders(): Promise<Record<string, string>> {
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
const token = Taro.getStorageSync('access_token');
if (token) headers['Authorization'] = `Bearer ${token}`;
const patientId = Taro.getStorageSync('current_patient_id');
if (patientId) headers['X-Patient-Id'] = patientId;
const tenantId = Taro.getStorageSync('tenant_id');
if (tenantId) headers['X-Tenant-Id'] = tenantId;
return headers;
}
export async function request<T>(method: string, path: string, data?: unknown): Promise<T> {
const headers = await getHeaders();
const res = await Taro.request({ url: `${BASE_URL}${path}`, method, data, header: headers });
if (res.statusCode === 401) {
const refreshed = await tryRefreshToken();
if (refreshed) return request(method, path, data);
Taro.redirectTo({ url: '/pages/login/index' });
throw new Error('登录已过期');
}
const body = res.data as ApiResponse<T>;
if (!body.success) throw new Error(body.message || '请求失败');
return body.data as T;
}
async function tryRefreshToken(): Promise<boolean> {
const refreshToken = Taro.getStorageSync('refresh_token');
if (!refreshToken) return false;
try {
const res = await Taro.request({
url: `${BASE_URL}/auth/refresh`,
method: 'POST',
data: { refresh_token: refreshToken },
});
if (res.statusCode === 200 && res.data?.success) {
Taro.setStorageSync('access_token', res.data.data.access_token);
Taro.setStorageSync('refresh_token', res.data.data.refresh_token);
return true;
}
} catch {}
Taro.removeStorageSync('access_token');
Taro.removeStorageSync('refresh_token');
return false;
}
export const api = {
get: <T>(path: string) => request<T>('GET', path),
post: <T>(path: string, data?: unknown) => request<T>('POST', path, data),
put: <T>(path: string, data?: unknown) => request<T>('PUT', path, data),
delete: <T>(path: string) => request<T>('DELETE', path),
};
```
- [ ] **Step 2: 提交**
```bash
git add apps/miniprogram/src/services/request.ts
git commit -m "feat(miniprogram): 实现 API 请求层封装"
```
---
### Task 10: services/auth.ts + auth store
**Files:**
- Create: `apps/miniprogram/src/services/auth.ts`
- Create: `apps/miniprogram/src/stores/auth.ts`
- [ ] **Step 1: auth service**
```typescript
// services/auth.ts
import { api } from './request';
export interface LoginResp {
bound: boolean;
openid: string;
token?: { access_token: string; refresh_token: string; user: UserInfo };
}
export interface UserInfo {
id: string; name: string; phone: string; avatar?: string; tenant_id: string;
}
export interface PatientInfo {
id: string; name: string; gender?: string; birthday?: string; relation: string;
}
export async function wechatLogin(code: string): Promise<LoginResp> {
return api.post('/auth/wechat/login', { code });
}
export async function wechatBindPhone(openid: string, encryptedData: string, iv: string) {
return api.post('/auth/wechat/bind-phone', { openid, encrypted_data: encryptedData, iv });
}
export async function getPatients() {
return api.get<PatientInfo[]>('/health/patients');
}
```
- [ ] **Step 2: auth store**
参照 Web 端 `stores/auth.ts` 模式,使用 `Taro.getStorageSync` / `setStorageSync` 替代 `localStorage`
State: `token, refreshToken, user, currentPatient, patients, loading`
Actions: `login(), bindPhone(), setCurrentPatient(), logout()`
- [ ] **Step 3: 提交**
```bash
git add apps/miniprogram/src/services/auth.ts apps/miniprogram/src/stores/auth.ts
git commit -m "feat(miniprogram): 实现 auth service 和 store"
```
---
### Task 11: 登录页
**Files:**
- Create: `apps/miniprogram/src/pages/login/index.tsx`
- Create: `apps/miniprogram/src/pages/login/index.scss`
- [ ] **Step 1: 实现登录页 UI**
登录页包含:
- 品牌标识Logo + "健康管理" 标题)
- 微信一键登录按钮(调用 `wx.login``wechatLogin(code)`
- 如果返回 `bound: false`,显示手机号授权按钮(使用 `<Button openType="getPhoneNumber">`
- 登录成功后 `Taro.switchTab({ url: '/pages/index/index' })`
- [ ] **Step 2: 实现样式**
医疗清新风格:青色渐变背景、白色卡片、圆角按钮。
- [ ] **Step 3: 注册页面路由**
`src/app.config.ts``pages` 数组中添加 `/pages/login/index`
- [ ] **Step 4: 提交**
```bash
git add apps/miniprogram/src/pages/login/ apps/miniprogram/src/app.config.ts
git commit -m "feat(miniprogram): 实现微信登录页面"
```
---
### Task 12: 首页(静态数据)
**Files:**
- Create: `apps/miniprogram/src/pages/index/index.tsx`
- Create: `apps/miniprogram/src/pages/index/index.scss`
- Create: `apps/miniprogram/src/components/HealthCard/index.tsx`
- Modify: `apps/miniprogram/src/app.config.ts` (TabBar 配置)
- [ ] **Step 1: 配置 TabBar**
`app.config.ts` 中定义 5 个 Tab
```typescript
tabBar: {
color: '#94A3B8',
selectedColor: '#0891B2',
backgroundColor: '#FFFFFF',
list: [
{ pagePath: 'pages/index/index', text: '首页', iconPath: 'assets/tab-home.png', selectedIconPath: 'assets/tab-home-active.png' },
{ pagePath: 'pages/health/index', text: '健康', iconPath: 'assets/tab-health.png', selectedIconPath: 'assets/tab-health-active.png' },
{ pagePath: 'pages/appointment/index', text: '预约', iconPath: 'assets/tab-appt.png', selectedIconPath: 'assets/tab-appt-active.png' },
{ pagePath: 'pages/article/index', text: '资讯', iconPath: 'assets/tab-article.png', selectedIconPath: 'assets/tab-article-active.png' },
{ pagePath: 'pages/profile/index', text: '我的', iconPath: 'assets/tab-profile.png', selectedIconPath: 'assets/tab-profile-active.png' },
],
}
```
- [ ] **Step 2: 创建 Tab 图标**
准备 10 个图标文件5 普通 + 5 选中态),放到 `src/assets/`
- [ ] **Step 3: 实现 HealthCard 组件**
健康指标卡片:接收 `title, value, unit, status`,显示渐变背景 + 数值 + 状态标签。
- [ ] **Step 4: 实现首页**
按设计规格布局:问候栏 + 今日健康2×2 网格)+ 快捷服务4 宫格)+ 即将到来 + 待办随访。
MVP 先用静态数据,后续 Phase 对接真实 API。
- [ ] **Step 5: 创建其他 Tab 占位页**
`health/index``appointment/index``article/index``profile/index` 创建简单占位页,显示页面名称。
- [ ] **Step 6: 验证**
```bash
pnpm dev:weapp
```
微信开发者工具中确认登录流程可用、首页布局正确、Tab 切换正常。
- [ ] **Step 7: 提交**
```bash
git add apps/miniprogram/src/
git commit -m "feat(miniprogram): 实现首页布局 + TabBar + 登录流程"
```
---
## Chunk 3: Phase 2 — 健康数据录入 + 趋势图
### Task 13: 后端 — vital_signs 趋势查询 API
**Files:**
- Modify: `crates/erp-health/src/handler/health_data_handler.rs`
- Modify: `crates/erp-health/src/service/health_data_service.rs`
- Modify: `crates/erp-health/src/dto/health_data_dto.rs`
- Modify: `crates/erp-health/src/module.rs` (路由)
- [ ] **Step 1: 添加趋势查询 DTO**
`health_data_dto.rs` 新增:
```rust
#[derive(Deserialize, ToSchema)]
pub struct TrendQueryParams {
pub indicator: String, // "blood_pressure_systolic" 等
pub start_date: Option<chrono::NaiveDate>,
pub end_date: Option<chrono::NaiveDate>,
pub range: Option<String>, // "7d", "30d", "90d"
}
#[derive(Serialize, ToSchema)]
pub struct TrendResp {
pub indicator: String,
pub data_points: Vec<DataPoint>,
}
#[derive(Serialize, ToSchema)]
pub struct DataPoint {
pub date: String,
pub value: f64,
}
```
- [ ] **Step 2: 添加趋势查询 service 函数**
`health_data_service.rs` 新增 `get_vital_signs_trend()`,基于已有的 `get_indicator_timeseries()` 封装,支持 `range` 参数自动计算日期范围。
- [ ] **Step 3: 添加 handler**
```rust
pub async fn get_trend<S>(...) -> Result<Json<ApiResponse<TrendResp>>, AppError>
```
- [ ] **Step 4: 注册路由**
`module.rs``protected_routes()` 中添加 `GET /health/vital-signs/trend`
- [ ] **Step 5: 验证**
Run: `cargo check -p erp-health`
- [ ] **Step 6: 提交**
```bash
git add crates/erp-health/src/
git commit -m "feat(health): 添加 vital_signs 趋势查询 API"
```
---
### Task 14: 小程序 — 健康数据录入页
**Files:**
- Create: `apps/miniprogram/src/pages/health/index.tsx`
- Create: `apps/miniprogram/src/pages/health/index.scss`
- Create: `apps/miniprogram/src/pages/health/input/index.tsx`
- Create: `apps/miniprogram/src/pages/health/input/index.scss`
- Create: `apps/miniprogram/src/services/health.ts`
- [ ] **Step 1: health service**
```typescript
// services/health.ts
import { api } from './request';
export interface VitalSignInput {
indicator_type: string;
value: number;
measured_at?: string;
note?: string;
}
export interface TodaySummary {
blood_pressure?: { systolic: number; diastolic: number; status: string };
heart_rate?: { value: number; status: string };
blood_sugar?: { value: number; status: string };
weight?: { value: number; status: string };
}
export async function getTodaySummary() {
return api.get<TodaySummary>('/health/vital-signs?date=today');
}
export async function inputVitalSign(data: VitalSignInput) {
return api.post('/health/vital-signs', data);
}
export async function getTrend(indicator: string, range: string) {
return api.get<{ indicator: string; data_points: { date: string; value: number }[] }>(
`/health/vital-signs/trend?indicator=${indicator}&range=${range}`
);
}
```
- [ ] **Step 2: 健康数据首页**
展示今日健康数据概览2×2 卡片网格)+ 最近趋势缩略图 + "录入数据" 按钮。
- [ ] **Step 3: 数据录入页**
表单选择指标类型Picker→ 输入数值 → 选择测量时间DateTimePicker→ 备注(可选)→ 提交。
提交成功后 `Taro.navigateBack()` 并刷新上一页数据。
- [ ] **Step 4: 注册页面路由**
`app.config.ts` 中添加 `pages/health/input/index`
- [ ] **Step 5: 验证**
微信开发者工具中确认录入表单可操作,提交后数据出现在健康首页。
- [ ] **Step 6: 提交**
```bash
git add apps/miniprogram/src/pages/health/ apps/miniprogram/src/services/health.ts apps/miniprogram/src/app.config.ts
git commit -m "feat(miniprogram): 实现健康数据录入页面"
```
---
### Task 15: 小程序 — 趋势图组件
**Files:**
- Create: `apps/miniprogram/src/components/TrendChart/index.tsx`
- Create: `apps/miniprogram/src/pages/health/trend/index.tsx`
- Create: `apps/miniprogram/src/pages/health/trend/index.scss`
- [ ] **Step 1: TrendChart 组件**
使用 `echarts-taro3-react` 渲染折线图。接收 `data: { date: string; value: number }[]``title: string`
- [ ] **Step 2: 趋势详情页**
顶部:指标名称 + 时间范围切换7天/30天/90天
中部TrendChart 折线图
底部:数据表格(日期 + 数值)
- [ ] **Step 3: 注册路由**
`app.config.ts` 添加 `pages/health/trend/index`
- [ ] **Step 4: 验证**
切换时间范围,图表数据刷新。
- [ ] **Step 5: 提交**
```bash
git add apps/miniprogram/src/components/TrendChart/ apps/miniprogram/src/pages/health/trend/ apps/miniprogram/src/app.config.ts
git commit -m "feat(miniprogram): 实现健康趋势图页面"
```
---
### Task 16: health store + 首页数据联动
**Files:**
- Create: `apps/miniprogram/src/stores/health.ts`
- Modify: `apps/miniprogram/src/pages/index/index.tsx` (对接真实数据)
- [ ] **Step 1: health store**
```typescript
// stores/health.ts
import { create } from 'zustand';
import * as healthApi from '../services/health';
interface HealthState {
todaySummary: healthApi.TodaySummary | null;
loading: boolean;
refreshToday: () => Promise<void>;
}
export const useHealthStore = create<HealthState>((set) => ({
todaySummary: null,
loading: false,
refreshToday: async () => {
set({ loading: true });
try {
const data = await healthApi.getTodaySummary();
set({ todaySummary: data, loading: false });
} catch { set({ loading: false }); }
},
}));
```
- [ ] **Step 2: 首页接入真实数据**
替换首页静态数据,从 `useHealthStore().todaySummary` 读取。`useDidShow` 生命周期触发 `refreshToday()`
- [ ] **Step 3: 提交**
```bash
git add apps/miniprogram/src/stores/health.ts apps/miniprogram/src/pages/index/
git commit -m "feat(miniprogram): 首页对接真实健康数据 API"
```
---
## Chunk 4: Phase 3 — 预约挂号
### Task 17: 后端 — 排班查询 API 适配
**Files:**
- Modify: `crates/erp-health/src/handler/health_data_handler.rs` 或对应的排班 handler
- Modify: `crates/erp-health/src/dto/health_data_dto.rs`
- [ ] **Step 1: 确认排班 API 支持按科室/医生查询**
检查现有 `GET /health/doctor-schedules` 是否支持 `department_id``doctor_id` 查询参数。如不支持,添加相应过滤。
- [ ] **Step 2: 添加"可用时段"聚合**
新增 DTO 和 handler输入医生 + 日期范围 → 返回每日可用时段及剩余名额。
- [ ] **Step 3: 验证**
Run: `cargo check -p erp-health`
- [ ] **Step 4: 提交**
```bash
git add crates/erp-health/src/
git commit -m "feat(health): 排班查询支持科室/医生过滤 + 可用时段聚合"
```
---
### Task 18: 小程序 — 预约服务 + 列表页
**Files:**
- Create: `apps/miniprogram/src/services/appointment.ts`
- Modify: `apps/miniprogram/src/pages/appointment/index.tsx`
- Create: `apps/miniprogram/src/pages/appointment/index.scss`
- [ ] **Step 1: appointment service**
```typescript
// services/appointment.ts
import { api } from './request';
export interface Appointment {
id: string; doctor_name: string; department: string;
date: string; time_slot: string; status: string;
}
export async function listAppointments(page = 1) {
return api.get<{ data: Appointment[]; total: number }>(`/health/appointments?page=${page}`);
}
export async function createAppointment(data: {
doctor_id: string; schedule_id: string; patient_id: string;
}) {
return api.post('/health/appointments', data);
}
export async function cancelAppointment(id: string) {
return api.put(`/health/appointments/${id}/cancel`);
}
export async function getDoctorSchedules(doctorId: string, startDate: string, endDate: string) {
return api.get(`/health/doctor-schedules?doctor_id=${doctorId}&start_date=${startDate}&end_date=${endDate}`);
}
```
- [ ] **Step 2: 预约列表页**
显示预约记录列表(按时间倒序),每项包含医生名、科室、日期、时段、状态标签。支持下拉刷新 + 上拉加载更多。
- [ ] **Step 3: 提交**
```bash
git add apps/miniprogram/src/services/appointment.ts apps/miniprogram/src/pages/appointment/
git commit -m "feat(miniprogram): 实现预约列表页"
```
---
### Task 19: 小程序 — 新建预约页
**Files:**
- Create: `apps/miniprogram/src/pages/appointment/create/index.tsx`
- Create: `apps/miniprogram/src/pages/appointment/create/index.scss`
- Create: `apps/miniprogram/src/pages/appointment/detail/index.tsx`
- [ ] **Step 1: 新建预约页 — 步骤流程**
4 步向导:
1. 选择科室Picker
2. 选择医生(列表,显示头像+姓名+职称+科室)
3. 选择日期和时段(周视图日历 + 时段列表)
4. 确认预约(显示摘要 + 提交按钮)
- [ ] **Step 2: 预约详情页**
展示预约详情 + 取消预约按钮 + 导航到体检中心(地图)。
- [ ] **Step 3: 注册路由**
`app.config.ts` 添加 `pages/appointment/create/index``pages/appointment/detail/index`
- [ ] **Step 4: 验证**
完整走通:选科室 → 选医生 → 选时段 → 确认 → 查看详情 → 取消。
- [ ] **Step 5: 提交**
```bash
git add apps/miniprogram/src/pages/appointment/ apps/miniprogram/src/app.config.ts
git commit -m "feat(miniprogram): 实现新建预约和预约详情页"
```
---
## Chunk 5: Phase 4 — 报告查询 + 家庭管理
### Task 20: 小程序 — 报告服务 + 列表/详情页
**Files:**
- Create: `apps/miniprogram/src/services/report.ts`
- Create: `apps/miniprogram/src/pages/report/index.tsx`
- Modify: `apps/miniprogram/src/app.config.ts`
- [ ] **Step 1: report service**
```typescript
import { api } from './request';
export interface LabReport {
id: string; title: string; type: string; date: string; status: string;
indicators?: LabIndicator[];
}
export interface LabIndicator {
name: string; value: number; unit: string; reference_min: number; reference_max: number;
status: 'normal' | 'high' | 'low';
}
export async function listReports(page = 1) {
return api.get<{ data: LabReport[]; total: number }>(`/health/lab-reports?page=${page}`);
}
export async function getReportDetail(id: string) {
return api.get<LabReport>(`/health/lab-reports/${id}`);
}
```
- [ ] **Step 2: 报告列表页**
卡片列表:报告名称 + 类型标签 + 日期。下拉刷新 + 上拉分页。
- [ ] **Step 3: 报告详情页**
基本信息卡 + 指标列表(正常灰色、异常红色高亮 + 箭头)+ 附件预览区。
- [ ] **Step 4: 提交**
```bash
git add apps/miniprogram/src/services/report.ts apps/miniprogram/src/pages/report/
git commit -m "feat(miniprogram): 实现报告查询列表和详情页"
```
---
### Task 21: 小程序 — 家庭管理(就诊人切换)
**Files:**
- Create: `apps/miniprogram/src/components/FamilyPicker/index.tsx`
- Create: `apps/miniprogram/src/pages/profile/family/index.tsx`
- Create: `apps/miniprogram/src/pages/profile/family-add/index.tsx`
- Modify: `apps/miniprogram/src/pages/profile/index.tsx`
- [ ] **Step 1: FamilyPicker 组件**
顶部下拉选择器:显示当前就诊人姓名 + 头像,点击展开列表切换。切换后更新 `auth store``currentPatient` + `Taro.setStorageSync('current_patient_id', id)`
- [ ] **Step 2: 就诊人管理页**
列表显示已添加的就诊人(本人 + 家属),支持添加新就诊人。
- [ ] **Step 3: 添加就诊人页**
表单:姓名 + 与本人关系Picker+ 性别 + 出生日期 + 身份证号(可选)。提交 `POST /health/patients`
- [ ] **Step 4: 首页/健康/预约页接入 FamilyPicker**
在这些页面顶部添加 FamilyPicker切换就诊人后重新拉取数据。
- [ ] **Step 5: 注册路由并提交**
```bash
git add apps/miniprogram/src/components/FamilyPicker/ apps/miniprogram/src/pages/profile/family/ apps/miniprogram/src/pages/profile/family-add/
git commit -m "feat(miniprogram): 实现家庭管理 + 就诊人切换"
```
---
## Chunk 6: Phase 5 — 随访 + 资讯 + 用药提醒
### Task 22: 后端 — articles 表 + API
**Files:**
- Create: `crates/erp-server/migration/src/m20260423_000002_articles.rs`
- Create: `crates/erp-health/src/entity/article.rs`
- Create: `crates/erp-health/src/dto/article_dto.rs`
- Create: `crates/erp-health/src/handler/article_handler.rs`
- Create: `crates/erp-health/src/service/article_service.rs`
- [ ] **Step 1: 创建 articles 迁移**
表结构:`id, tenant_id, title, summary, content(富文本), cover_image, category, author, published_at, created_at, updated_at, deleted_at, version`
- [ ] **Step 2: Entity + DTO + Service + Handler**
参照现有 health_data 的模式创建完整 CRUD。公开接口只需 `list``detail`
- [ ] **Step 3: 注册路由和权限**
- [ ] **Step 4: 验证**
Run: `cargo check -p erp-health`
- [ ] **Step 5: 提交**
```bash
git add crates/erp-health/src/ crates/erp-server/migration/
git commit -m "feat(health): 添加健康资讯 articles 表和 API"
```
---
### Task 23: 小程序 — 随访功能
**Files:**
- Create: `apps/miniprogram/src/services/followup.ts`
- Create: `apps/miniprogram/src/pages/followup/index.tsx`
- Create: `apps/miniprogram/src/pages/followup/detail/index.tsx`
- [ ] **Step 1: followup service**
- [ ] **Step 2: 随访任务列表页**
按状态分组:待完成 / 已完成 / 已过期。每项显示任务名 + 截止日期 + 状态。
- [ ] **Step 3: 随访详情/填写页**
动态表单渲染(后端定义字段)→ 提交 → 标记完成。
- [ ] **Step 4: 首页待办随访接入真实数据**
- [ ] **Step 5: 提交**
```bash
git add apps/miniprogram/src/services/followup.ts apps/miniprogram/src/pages/followup/
git commit -m "feat(miniprogram): 实现随访任务列表和填写页"
```
---
### Task 24: 小程序 — 健康资讯
**Files:**
- Create: `apps/miniprogram/src/services/article.ts`
- Modify: `apps/miniprogram/src/pages/article/index.tsx`
- Create: `apps/miniprogram/src/pages/article/detail/index.tsx`
- [ ] **Step 1: article service**
- [ ] **Step 2: 资讯列表页**
缩略图 + 标题 + 摘要 + 时间,下拉刷新 + 分页。
- [ ] **Step 3: 资讯详情页**
使用 Taro `RichText` 渲染富文本内容。
- [ ] **Step 4: 提交**
```bash
git add apps/miniprogram/src/services/article.ts apps/miniprogram/src/pages/article/
git commit -m "feat(miniprogram): 实现健康资讯列表和详情页"
```
---
### Task 25: 小程序 — 用药提醒
**Files:**
- Create: `apps/miniprogram/src/pages/profile/medication/index.tsx`
- Create: `apps/miniprogram/src/utils/medication-reminder.ts`
- [ ] **Step 1: 用药提醒工具**
本地 storage 存储提醒规则:`{ id, name, frequency, times: string[], enabled }`
- [ ] **Step 2: 用药提醒管理页**
列表展示已添加的提醒 + 添加/编辑/删除/开关。
- [ ] **Step 3: 提交**
```bash
git add apps/miniprogram/src/pages/profile/medication/ apps/miniprogram/src/utils/medication-reminder.ts
git commit -m "feat(miniprogram): 实现用药提醒管理页"
```
---
## Chunk 7: Phase 6 — 打磨 + 真机测试 + 提审
### Task 26: "我的" 页面完善
**Files:**
- Modify: `apps/miniprogram/src/pages/profile/index.tsx`
- Create: `apps/miniprogram/src/pages/profile/settings/index.tsx`
- [ ] **Step 1: 个人中心页**
展示头像 + 姓名 + 手机号。功能入口:就诊人管理、我的报告、我的随访、用药提醒、设置。
- [ ] **Step 2: 设置页**
清除缓存、关于我们、隐私政策、退出登录。
- [ ] **Step 3: 提交**
```bash
git add apps/miniprogram/src/pages/profile/
git commit -m "feat(miniprogram): 完善个人中心页"
```
---
### Task 27: 空状态 + 错误处理 + Loading
**Files:**
- Create: `apps/miniprogram/src/components/EmptyState/index.tsx`
- Create: `apps/miniprogram/src/components/ErrorState/index.tsx`
- Create: `apps/miniprogram/src/components/Loading/index.tsx`
- [ ] **Step 1: 通用空状态组件**
显示图标 + 文案 + 操作按钮(如"去预约")。
- [ ] **Step 2: 通用错误组件**
网络错误 / 服务器错误 / 无权限,带重试按钮。
- [ ] **Step 3: 各页面接入**
所有列表页添加空状态、错误状态、Loading 态。
- [ ] **Step 4: 提交**
```bash
git add apps/miniprogram/src/components/EmptyState/ apps/miniprogram/src/components/ErrorState/ apps/miniprogram/src/components/Loading/
git commit -m "feat(miniprogram): 添加空状态/错误/Loading 通用组件"
```
---
### Task 28: 分包 + 性能优化
- [ ] **Step 1: 配置分包**
`app.config.ts` 中定义 subPackages
- `health/` — 健康数据相关页面
- `appointment/` — 预约相关
- `report/` — 报告相关
- `followup/` — 随访相关
- `article/` — 资讯相关
主包保留:首页 + 登录 + 我的 + 公共组件。
- [ ] **Step 2: 图表库按需引入**
echarts 只引入折线图模块,减小体积。
- [ ] **Step 3: 提交**
```bash
git add apps/miniprogram/src/app.config.ts
git commit -m "perf(miniprogram): 配置分包加载 + 图表按需引入"
```
---
### Task 29: 真机测试 + 提审准备
- [ ] **Step 1: 真机调试**
通过微信开发者工具预览/真机调试,在多款真机上验证。
- [ ] **Step 2: 检查清单**
- [ ] 登录流程正常(微信授权 + 手机号绑定)
- [ ] 健康数据可录入并查看趋势
- [ ] 预约可创建/取消
- [ ] 报告可查看
- [ ] 随访可填写
- [ ] 就诊人可切换
- [ ] 无白屏、无 JS 报错
- [ ] 包体积 < 2MB主包
- [ ] 隐私政策和用户协议已配置
- [ ] **Step 3: 构建生产版本**
```bash
pnpm build:weapp
```
- [ ] **Step 4: 提交最终版本**
```bash
git add -A
git commit -m "release(miniprogram): v1.0.0 提审版本"
git push
```