审计发现并修复的问题: HIGH: - H1: ConsultationDetail 使用 getSession(id) 替代错误的列表搜索 - H2: SessionResp 添加 version/updated_at 字段 - H3: 移除 FollowUpRecordList 调用不存在的导出端点 - H4: 新增 articles.ts 前端 API 模块 MEDIUM: - M1: article delete 添加乐观锁 (expected_version) - M2: 取消预约排班释放传播错误 (log::warn -> ?) - M3: FollowUpTaskList 日期格式 Dayjs -> string - M4: 补充 15 个缺失审计日志 LOW: - L1: 替换 follow_up_service 中的 .unwrap() - L2: PatientListItem 添加 version 字段 CRITICAL (新发现): - 权限未同步: 健康模块 14 个权限从未写入数据库,添加启动时自动同步 - migration 表名错误: patients -> patient - 编译错误: health_trend entity 未导入, ToPrimitive trait 未导入 - HealthError 缺少 From<AppError> 实现
26 KiB
健康管理模块全面迭代设计
文档版本: 1.0 日期: 2026-04-24 状态: 待评审 基于: 5 位专家(后端架构/前端架构/医疗业务/安全质量/产品策略)深度审查
0. 审查发现总览
0.1 V1 发布阻塞项
| # | 阻塞项 | 来源 | 影响 |
|---|---|---|---|
| B1 | Web 健康模块 10 页面未实现 | 前端架构/产品策略 | 无法演示和交付 |
| B2 | 医疗数据安全不合规 | 安全质量 | 零 sanitize / 零审计 / 身证明文 / 零测试 |
| B3 | 数据一致性缺陷 | 医疗业务/后端架构 | 排班可超额 / 名额释放可能失败 / 随访逾期未实现 |
| B4 | 事件处理器空壳 | 后端架构 | 随访状态/咨询消息不联动 |
0.2 当前完成度
| 层级 | 模块 | 完成度 |
|---|---|---|
| 后端 | erp-health(16 实体/8 服务/7 handler/40+ API) | 95% |
| 后端 | 事件处理器业务逻辑 | 0%(框架已搭建,需填充 db 操作) |
| 后端 | sanitize / 审计 / 加密 | 0% |
| 后端 | 测试覆盖 | 0% |
| Web 前端 | 健康模块页面 | 0% |
| Web 前端 | 健康模块 API 服务层 | 0% |
| 小程序 | 初版 21 页面 | 85% |
1. 安全省基(阶段 1,1.5-2 周)
1.1 sanitize 全覆盖
问题: erp-health 模块没有任何对 strip_html_tags 的调用,攻击者可在患者姓名、病史等字段注入 XSS payload。
参考实现: crates/erp-auth/src/dto.rs 第 96-118 行,CreateUserReq 和 UpdateUserReq 已实现 sanitize() 方法。
修复方案: 为每个 DTO 的字符串输入字段添加 sanitize。
覆盖字段清单:
| DTO 文件 | 字段 |
|---|---|
patient_dto.rs CreatePatientReq / UpdatePatientReq |
name, notes, allergy_history, medical_history_summary, emergency_contact_name, source |
patient_dto.rs FamilyMemberReq(create + update 共用) |
name, notes |
patient_handler.rs AssignDoctorReq(位于 handler 非 dto) |
— (无字符串字段) |
health_data_dto.rs CreateVitalSignsReq |
notes |
health_data_dto.rs CreateLabReportReq |
doctor_interpretation |
health_data_dto.rs CreateHealthRecordReq |
source, overall_assessment, notes |
appointment_dto.rs CreateAppointmentReq |
notes, cancel_reason |
follow_up_dto.rs CreateFollowUpTaskReq / UpdateFollowUpTaskReq |
content_template |
follow_up_dto.rs CreateFollowUpRecordReq |
patient_condition, medical_advice |
consultation_dto.rs CreateMessageReq |
content |
consultation_dto.rs CreateSessionReq |
— (无字符串字段) |
doctor_dto.rs CreateDoctorReq / UpdateDoctorReq |
department, title, specialty, bio |
实现模式:
// 封装 sanitize 辅助函数(与 erp-auth 的 sanitize_option 模式一致)
fn sanitize_option_string(opt: Option<String>) -> Option<String> {
opt.map(|s| strip_html_tags(&s))
}
// 在每个 DTO 的 impl 中添加 sanitize 方法
impl CreatePatientReq {
pub fn sanitize(&mut self) {
self.name = strip_html_tags(&self.name);
self.notes = sanitize_option_string(self.notes.take());
self.allergy_history = sanitize_option_string(self.allergy_history.take());
self.medical_history_summary = sanitize_option_string(self.medical_history_summary.take());
// ...
}
}
// 在 handler 调用 service 前执行
async fn create_patient(/* ... */) -> AppResult<Json<ApiResponse<PatientResp>>> {
let mut req: CreatePatientReq = Json(req).0;
req.sanitize();
// ...
}
前端安全: ChatBubble 组件必须使用 React 默认 JSX 转义渲染文本内容(不使用 dangerouslySetInnerHTML),图片消息 URL 需做白名单校验。
1.2 审计日志注入
问题: erp-health 整个模块没有任何对 audit_service::record 的调用。
参考实现: crates/erp-auth/src/service/auth_service.rs 第 168-177 行。
修复方案: 在所有写入操作的 service 层添加审计记录。
覆盖操作清单:
| Service | 操作 | 审计 action |
|---|---|---|
| patient_service | create_patient | patient.created |
| patient_service | update_patient | patient.updated |
| patient_service | delete_patient | patient.deleted |
| patient_service | manage_patient_tags | patient.tags_updated |
| health_data_service | create_vital_signs | vital_signs.created |
| health_data_service | create_lab_report | lab_report.created |
| health_data_service | create_health_record | health_record.created |
| appointment_service | create_appointment | appointment.created |
| appointment_service | update_appointment_status | appointment.status_changed |
| follow_up_service | create_task | follow_up_task.created |
| follow_up_service | create_record | follow_up_record.created |
| consultation_service | create_session | consultation.opened |
| consultation_service | close_session | consultation.closed |
| consultation_service | create_message | consultation.message_sent |
| doctor_service | create/update/delete_doctor | doctor.* |
审计日志内容: tenant_id、user_id、action、resource_type、resource_id、变更前后值摘要。
注意: 当前 audit_service::record 是 fire-and-forget,审计日志丢失对医疗合规不可接受。修复方案:
- 新增
record_in_txn(log: AuditLog, txn: &DatabaseTransaction)方法,在事务内 await 写入 - 保留原
record方法用于不要求事务保证的场景 - erp-health 的关键写入操作使用
record_in_txn,失败时回滚整个事务 - 需要改为事务包裹的 service 方法:create_patient、update_patient、delete_patient、create_appointment、update_appointment_status、create_record(随访)、create_message(咨询)
1.3 身份证号加密存储
问题: patient.id_number 明文存储在数据库中,违反《个人信息保护法》。
方案: AES-256-GCM 应用层加密。
新增文件: crates/erp-health/src/crypto.rs
pub struct HealthCrypto { key: [u8; 32] }
impl HealthCrypto {
pub fn from_env() -> Self { /* 从 ERP__HEALTH__ENCRYPTION_KEY 读取 */ }
pub fn encrypt(&self, plaintext: &str) -> AppResult<String> { /* AES-256-GCM + Base64 */ }
pub fn decrypt(&self, ciphertext: &str) -> AppResult<String> { /* 解密 */ }
}
集成点:
patient_service::create_patient— 加密 id_number 后存储patient_service::update_patient— 同上patient_service::get_patient— 解密后返回patient_service::list_patients— 列表不返回 id_number(脱敏)
密钥管理: 环境变量 ERP__HEALTH__ENCRYPTION_KEY(32 字节 hex),必须在 default.toml 中标记为 __MUST_SET_VIA_ENV__。
搜索兼容: patient.id_number 的模糊搜索(contains)改为精确匹配(eq),在加密后使用 HMAC 索引做等值查询。
HMAC 索引详情:
- 新增数据库列
id_number_hash VARCHAR(64),存储 HMAC-SHA256 哈希 - HMAC 密钥独立于 AES 密钥,从环境变量
ERP__HEALTH__HMAC_KEY读取 - 创建/更新患者时同时写入 hash 列,等值查询使用
WHERE id_number_hash = hmac(输入值) - 迁移 SQL:新增列 → 批量加密现有明文 → 删除原明文列(可选)
数据迁移方案:
- 停机窗口(预估 1-2 小时,视数据量)
- 迁移脚本:
SELECT id, id_number FROM patients WHERE id_number IS NOT NULL AND deleted_at IS NULL→ 批量加密 →UPDATE patients SET id_number = $encrypted WHERE id = $id - 同步写入
id_number_hash列 - 验证脚本:抽样解密比对原值
- 回滚方案:保留明文备份表
patients_id_number_backup,72 小时后确认无误再删除
问题: 列表接口直接返回完整身份证号、病史等敏感字段。
修复方案: 拆分响应 DTO。
// 列表用 — 不含敏感字段
pub struct PatientListResp {
pub id: Uuid,
pub name: String,
pub gender: Option<String>,
pub birth_date: Option<NaiveDate>,
pub status: String,
pub tags: Vec<TagResp>,
// 无 id_number, allergy_history, medical_history_summary, emergency_contact_phone 等
}
// 详情用 — 敏感字段掩码
pub struct PatientDetailResp {
// ... 全部字段
pub id_number: Option<String>, // "320***********1234"
pub emergency_contact_phone: Option<String>, // "138****1234"
}
2. 后端补完(阶段 2,1.5 周)
2.1 事件处理器实现
问题: event.rs 中两个事件处理器只有 tracing::info,无实际业务逻辑。且 handler 中没有 DatabaseConnection,无法执行数据库操作。
方案: 在 HealthModule::on_startup 中创建 HealthState 并注册需要数据库访问的事件处理器。将现有 register_event_handlers 中的空壳代码迁移到 on_startup,register_event_handlers 改为空实现。
修改 crates/erp-health/src/module.rs:
// register_event_handlers 改为空实现
fn register_event_handlers(&self, _bus: &EventBus) {
// 事件处理器迁移到 on_startup,此处不再注册
}
// on_startup 中注册带 db 的事件处理器
async fn on_startup(&self, ctx: &ModuleContext) -> AppResult<()> {
let state = HealthState {
db: ctx.db.clone(),
event_bus: ctx.event_bus.clone(),
};
crate::event::register_handlers_with_state(state);
Ok(())
}
修改 crates/erp-health/src/event.rs:
新增 register_handlers_with_state(state: HealthState) 函数替代原有 register_handlers。
事件处理器业务逻辑:
workflow.task.completed:
- 从 payload 中提取
task_id - 查询
follow_up_task WHERE related_appointment_id或通过 payload 映射 - 更新随访任务状态为
completed
message.sent:
- 从 payload 中提取
session_id(或通过 sender/recipient 关联) - 更新
consultation_session SET last_message_at = NOW(), unread_count = unread_count + 1 - 使用
check_version乐观锁
2.2 数据一致性修复
2.2.1 排班名额保护
问题: update_schedule 可以将 max_appointments 改为小于 current_appointments 的值。
修复: 在 appointment_service.rs 的 update_schedule 方法中增加校验:
if req.max_appointments < model.current_appointments {
return Err(HealthError::Validation(
"max_appointments 不能小于当前已预约数".into()
).into());
}
2.2.2 取消预约名额释放
问题: update_appointment_status 中取消时名额释放失败只 log error 不回滚。
修复: 将名额释放作为事务的一部分,失败时回滚整个操作(包括状态更新)。
2.2.3 咨询消息原子性
问题: create_message 中消息已插入,但后续 CAS 更新 session 失败时返回错误 — 消息已持久化但 session 元数据未更新。
修复: 将消息 INSERT + session CAS 更新放在同一个事务中。
2.3 随访逾期定时任务
问题: 设计规格定义了 overdue 状态和定时任务自动标记,但代码中:
validation.rs不允许转换到overdue- 没有后台定时任务
修复:
- 在
validation.rs中添加overdue转换规则:pending -> overdue(仅限系统自动触发) - 在
erp-server/src/main.rs后台任务区增加逾期检查器,使用与现有start_timeout_checker一致的tokio::spawn+loop+tokio::time::interval模式(每 6 小时执行一次,非 cron 表达式):
// erp-server/src/main.rs 后台任务区
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(6 * 3600));
loop {
interval.tick().await;
// 调用 health module 的 check_overdue_tasks
}
});
- 在
erp-healthmodule 中添加一个公开方法check_overdue_tasks供定时任务调用。
2.4 article 管理 CRUD
问题: 权限声明中有 health.articles.manage,但 service/handler 只有 list 和 get。
修复: 在 article_service.rs 和 article_handler.rs 中补充 create/update/delete 方法。在 module.rs 中添加路由。工时估算: 0.5 天。
3. Web 前端 10 页面(阶段 3,3.5-4 周)
3.1 页面文件组织
apps/web/src/
├── api/health/
│ ├── patients.ts # 12 端点
│ ├── healthData.ts # 13 端点
│ ├── appointments.ts # 6 端点
│ ├── followUp.ts # 6 端点
│ ├── consultations.ts # 6 端点
│ └── doctors.ts # 4 端点
├── pages/health/
│ ├── PatientList.tsx # 患者列表
│ ├── PatientDetail.tsx # 患者详情(5 Tab)
│ ├── PatientTagManage.tsx # 标签管理
│ ├── DoctorList.tsx # 医护列表
│ ├── AppointmentList.tsx # 预约管理
│ ├── DoctorSchedule.tsx # 排班管理
│ ├── FollowUpTaskList.tsx # 随访任务
│ ├── FollowUpRecordList.tsx # 随访台账
│ ├── ConsultationList.tsx # 会话管理
│ ├── ConsultationDetail.tsx # 对话详情
│ └── components/
│ ├── StatusTag.tsx # 通用状态标签
│ ├── PatientSelect.tsx # 患者搜索选择器
│ ├── DoctorSelect.tsx # 医护选择器
│ ├── VitalSignsChart.tsx # ECharts 趋势图
│ ├── CalendarView.tsx # 日历视图
│ ├── ChatBubble.tsx # 聊天气泡
│ ├── ImagePreview.tsx # 图片预览
│ └── ExportButton.tsx # 导出按钮
3.2 API 服务层设计
每个 service 文件遵循现有 api/users.ts 的解构模式:
// api/health/patients.ts
import client from '../client';
export interface Patient {
id: string;
name: string;
gender?: string;
birth_date?: string;
status: string;
tags: Tag[];
// ...
}
export interface CreatePatientReq {
name: string;
gender?: string;
// ...
}
export const patientApi = {
list: async (params: ListParams) => {
const { data } = await client.get<{ success: boolean; data: PaginatedResponse<Patient> }>(
'/health/patients', { params }
);
return data.data;
},
get: async (id: string) => {
const { data } = await client.get<{ success: boolean; data: Patient }>(
`/health/patients/${id}`
);
return data.data;
},
create: async (req: CreatePatientReq) => {
const { data } = await client.post<{ success: boolean; data: Patient }>(
'/health/patients', req
);
return data.data;
},
// ...
};
3.3 路由注册
在 App.tsx 中新增:
// lazy imports
const PatientList = lazy(() => import('./pages/health/PatientList'));
const PatientDetail = lazy(() => import('./pages/health/PatientDetail'));
// ... 共 10 个路由组件
// Routes 内
<Route path="/health/patients" element={<PatientList />} />
<Route path="/health/patients/:id" element={<PatientDetail />} />
<Route path="/health/tags" element={<PatientTagManage />} />
<Route path="/health/doctors" element={<DoctorList />} />
<Route path="/health/appointments" element={<AppointmentList />} />
<Route path="/health/schedules" element={<DoctorSchedule />} />
<Route path="/health/follow-up-tasks" element={<FollowUpTaskList />} />
<Route path="/health/follow-up-records" element={<FollowUpRecordList />} />
<Route path="/health/consultations" element={<ConsultationList />} />
<Route path="/health/consultations/:id" element={<ConsultationDetail />} />
3.4 侧边栏菜单
在 MainLayout.tsx 中新增 healthMenuItems 数组(参照现有 bizMenuItems 模式),使用 @ant-design/icons 图标(如 MedicineBoxOutlined、HeartOutlined、CalendarOutlined、PhoneOutlined、CommentOutlined、TagsOutlined):
侧边栏布局:
├── 首页 (HomeOutlined)
├── 用户管理 (UserOutlined)
├── 权限管理 (SafetyOutlined)
├── 工作流 (ApartmentOutlined)
├── 消息中心 (BellOutlined)
├── ─────────
├── 健康管理 (MedicineBoxOutlined) ← 新增组
│ ├── 患者管理 (TeamOutlined)
│ ├── 医护管理 (HeartOutlined)
│ ├── 预约排班 (CalendarOutlined)
│ ├── 随访管理 (PhoneOutlined)
│ ├── 咨询管理 (CommentOutlined)
│ └── 标签管理 (TagsOutlined)
├── ─────────
├── 插件管理 (AppstoreOutlined)
├── 系统设置 (SettingOutlined)
3.5 前端权限集成
后端已有完整权限体系(14 个权限码),前端 V1 阶段采用以下策略:
- 路由级权限: 所有健康模块路由在
PrivateRoute内(已实现),后端require_permission拦截无权限请求返回 403 - 按钮级权限(V1 简化): 不做前端按钮级权限控制,依赖后端 403 响应。后续可扩展
usePermissionhook - 菜单可见性: 健康模块菜单组始终显示,但无权限用户点击任何页面会收到 403 提示
3.5 13 页面逐一设计
PatientList.tsx(中复杂度,1.5 天)
- Ant Design
Table组件(与 Users.tsx 模式一致,不使用 ProTable) - 搜索:姓名模糊 + 状态筛选 + 标签多选筛选
- 每行显示患者标签为
Tag组件列表 - 行点击跳转
/health/patients/:id - 批量操作:批量打标
- 导出功能
PatientDetail.tsx(高复杂度,3 天)
- 顶部:患者摘要卡片(姓名/性别/年龄/状态/标签)
- Ant Design
Tabs5 个 Tab:- 基本信息 —
Descriptions展示 + 编辑 Modal - 健康趋势 —
VitalSignsChart组件 + 时间范围选择器 - 化验报告 — 报告卡片列表 +
ImagePreview指标详情 - 就诊记录 — 嵌套列表(体检/门诊/住院)
- 随访记录 — 嵌套列表 + 关联的随访记录
- 基本信息 —
PatientTagManage.tsx(低复杂度,0.5 天)
- 标准 CRUD 表格
- 颜色选择器(Ant Design
ColorPicker) - 批量打标功能
DoctorList.tsx(低复杂度,0.5 天)
- 标准 CRUD 表格
- 科室筛选 + 在线状态 Badge(online=绿/busy=黄/offline=灰)
- 详情 Drawer
AppointmentList.tsx(中复杂度,2 天)
Segmented切换列表/日历视图- 列表模式:表格 + 状态筛选 + 日期筛选
- 日历模式:
Calendar+cellRender显示当日预约数 - 状态流转 Dropdown(pending → confirmed → completed/no_show/cancelled)
- 创建预约 Modal(选择患者 + 医生 + 日期时段 + 检查排班余量)
DoctorSchedule.tsx(高复杂度,2.5 天)
- 选择医生后展示其排班
- 周视图(自定义 7 列网格,每列显示一天的排班时段)
- 月视图(Ant Design Calendar)
- 批量创建排班(选择日期范围 + 时段模板)
- 显示已预约/最大预约数
FollowUpTaskList.tsx(中复杂度,1.5 天)
- 表格 + 状态筛选(pending/in_progress/completed/overdue/cancelled)
- 分配给医护(
DoctorSelect) - 创建任务 Modal
- 快捷"填写随访记录"按钮打开子 Modal
FollowUpRecordList.tsx(低复杂度,0.5 天)
- 纯只读台账
- 筛选:日期范围、患者、任务、结果
- 导出功能(
ExportButton)
ConsultationList.tsx(中复杂度,1 天)
- 表格 + 状态筛选(waiting/active/closed)
- 未读消息数 Badge
- 最后消息时间
- 关闭会话操作
- 点击跳转
/health/consultations/:id
ConsultationDetail.tsx(高复杂度,2 天)
ChatBubble组件渲染聊天气泡- 根据
sender_role区分左右对齐 - 支持内容类型:text / image(
ImagePreview)/ voice / file - 消息按时间排列,支持滚动加载更多(分页)
- 导出按钮
3.6 技术难点方案
ECharts 趋势图
使用已安装的 @ant-design/charts 的 Line 组件。
- 后端 API
/patients/:id/trends/:indicator返回时序数据 - 前端转换为
{ date: string, value: number }[] - 支持多指标叠加(血压收缩压/舒张压双线)
- 封装为
VitalSignsChart,接收patientId+indicators参数 - 时间范围选择器(7天/30天/90天)
日历视图
Ant Design Calendar + 自定义 cellRender:
- DoctorSchedule:每个日期格显示排班时段标签
- AppointmentList:每个日期格显示预约数量气泡
聊天 UI
自定义 ChatBubble 组件,基于 Ant Design Typography.Paragraph + Avatar:
- 根据
sender_role区分样式 - 只读模式(PC 后台只查看不发送)
- 图片消息使用
Image.PreviewGroup
导出
后端 blob 导出 + 前端触发下载,参照 PluginCRUDPage 中已有的 exportPluginDataAsBlob 模式。
文件上传/预览
- 上传:Ant Design
Upload.Dragger,上传到后端文件接口 - 图片预览:Ant Design
Image.PreviewGroup - PDF 预览:新窗口打开(V1 简化方案)
3.7 开发顺序
| Phase | 内容 | 天数 | 依赖 |
|---|---|---|---|
| 1 | API 层 6 文件 + 通用组件 + 路由菜单 | 1.5 | 无 |
| 2 | PatientList + PatientTagManage + PatientDetail 基本信息Tab | 2 | Phase 1 |
| 3 | VitalSignsChart + 健康趋势 Tab + LabReportList + HealthRecordList | 3 | Phase 2 |
| 4 | DoctorList + AppointmentList + DoctorSchedule | 3 | Phase 1 |
| 5 | FollowUpTaskList + FollowUpRecordList + ConsultationList + ConsultationDetail | 3 | Phase 1 |
| 6 | 打磨(暗色主题 + 响应式 + 联调) | 1 | Phase 2-5 |
| 合计 | 13.5 天 |
4. 测试策略(阶段 2-3 交叉进行)
4.1 优先级排序
| 优先级 | 测试目标 | 预估用例数 | 工作量 |
|---|---|---|---|
| P0 | validation.rs 纯函数 |
20-30 | 1 天 |
| P0 | appointment_service CAS + 状态流转 |
15-20 | 2 天 |
| P0 | patient_service CRUD + 状态机 |
15-20 | 2 天 |
| P1 | consultation_service 消息原子性 |
10-15 | 2 天 |
| P1 | health_data_service 指标数据 |
10-15 | 1 天 |
| P2 | follow_up_service 链式任务 |
10 | 1 天 |
4.2 测试基础设施
在 erp-health/Cargo.toml 中添加 [dev-dependencies]:
tokio的test和macrosfeaturesea-orm的mockfeature(用于简单单元测试,如 validation 纯函数)
对于涉及事务和 CAS 的集成测试(预约并发、消息原子性),使用 testcontainers-postgreSQL 做真实数据库测试,因为 SeaORM 的 MockDatabaseConnection 不支持复杂事务模拟。
创建 tests/test_helpers.rs 提供:
create_test_health_state()— 带 mock db 的 HealthState(单元测试用)create_integration_db()— testcontainers PostgreSQL 实例(集成测试用)- 共享 fixture 工厂
4.3 关键测试场景
预约 CAS 并发:
- 排班已满 → 创建预约失败
- 排班有余 → CAS 成功 + 名额减 1
- 并发创建 → 只有 max_appointments 个成功
状态机转换:
- 合法转换:pending → confirmed → completed
- 非法转换:completed → pending → 拒绝
- 取消:任意状态 → cancelled(填 cancel_reason)
随访链式任务:
- next_follow_up_date 不为空 → 自动创建新任务
- 新任务的 assigned_to 沿用当前医护
- next_follow_up_date 为空 → 不创建新任务
5. 实施路线图
5.1 总时间线(调整为 7 周)
Week 1-2 | 安全地基(1.5-2 周)
| ├── sanitize 全覆盖(2 天)
| ├── 审计日志注入(2 天)
| ├── 身份证号加密 + HMAC 索引 + 数据迁移(3-4 天)
| └── 字段级脱敏(1-2 天)
Week 2-4 | 后端补完 + 测试(1.5-2 周)
| ├── 事件处理器实现(2 天)
| ├── 数据一致性修复(2 天)
| ├── 随访逾期定时任务(1 天)
| ├── article CRUD(0.5 天)
| └── 核心路径测试(5-6 天)
Week 4-7 | Web 前端(3.5-4 周)
| ├── Phase 1: API 层 + 通用组件 + 路由菜单(1.5 天)
| ├── Phase 2: 核心入口页面(2 天)
| ├── Phase 3: 健康数据页面(3 天)
| ├── Phase 4: 预约排班页面(3 天)
| ├── Phase 5: 随访咨询页面(3 天)
| └── Phase 6: 打磨联调(1 天)
Week 7-8 | 端到端验证(1 周)
| ├── 小程序联调
| ├── 种子数据填充
| ├── Docker 演示环境
| └── 文档更新
5.2 里程碑
| 里程碑 | 交付物 | 验收标准 |
|---|---|---|
| M1 | 安全省基完成 | sanitize + 审计 + 加密 + 脱敏全部到位,cargo test 通过 |
| M2 | 后端功能完整 | 事件处理器 + 数据一致性 + 测试覆盖,cargo test 通过 |
| M3 | Web 3 核心页面 | PatientList + AppointmentList + DoctorSchedule 可操作 |
| M4 | Web 10 页面完成 | 所有页面功能可用,pnpm build 通过 |
| M5 | 端到端验证 | Web + 小程序 + 后端全链路可演示 |
5.3 风险和缓解
| 风险 | 概率 | 缓解 |
|---|---|---|
| ECharts 集成复杂度高 | 中 | 使用 @ant-design/charts 已安装,降低自研成本 |
| 身份证加密影响现有查询 | 中 | HMAC 索引 + 数据迁移脚本 + 备份表 + 回滚方案 |
| 10 页面开发时间超预期 | 高 | 按优先级裁剪,MVP 先做 3 核心页面 |
| 文件上传能力未就绪 | 中 | V1 先支持 URL 存储,文件上传推迟到 V1.1 |
6. 不在本设计范围内(推迟到 V2)
- 积分商城
- 数据统计中心 / 运营驾驶舱
- AI 辅助诊断/报告解读
- 实时 WebSocket 在线咨询
- 咨询消息按月分区
- 事件幂等性(processed_events 去重表)
- Polling Outbox 重试机制
- HealthState 扩展 Redis 缓存
- 国际化(英文等多语言)
- 小程序医护端