# 行动收件箱与上下文线程设计规格 > 日期: 2026-05-01 | 状态: Draft | 关联: `erp-health`, `erp-ai`, Web 前端, 小程序 ## 1. 背景与问题 ### 1.1 问题陈述 HMS 平台功能完善(328 路由、25 事件类型、97.5% 测试通过率),但用户体验存在**系统性流程断裂**: - **告警 → 行动断裂**:医生看到告警后,无直接路径跳到查看患者→安排随访→记录处置 - **数据录入 → 反馈断裂**:患者录入体征后无即时趋势对比和行动建议 - **AI 建议 → 执行追踪断裂**:审批通过后,后续执行/追踪/结果反馈在 UI 上不可见 **根因**:前端交互模型是"功能陈列式"的——每个功能是死胡同,没有出口指向下一步。 ### 1.2 设计目标 建立统一的**行动收件箱 + 上下文线程**机制,让每个"终点"变成"起点": 1. 用户登录后第一眼看到待处理事项(按优先级排列) 2. 每个待办有清晰的时间线展示完整生命周期 3. 时间线每一步都可直接跳转到对应功能页 4. 操作按钮根据当前状态动态变化 5. 架构可扩展——新增行动类型(告警/随访/异常)只需注册聚合器 ### 1.3 范围 **Phase 1(本次)**:AI 建议闭环 — 后端已完整实现(10 次提交),补齐前端体验 **Future**:告警处理闭环、体征反馈闭环、随访到期提醒 --- ## 2. 设计概览 ``` 事件源 → 行动收件箱服务 → 前端展示 ├── Web: 顶栏铃铛 → 列表页 → Drawer └── 小程序: "待办" Tab → 列表 → 半屏弹窗 ``` **核心抽象**:`ActionItem` — 统一的待办数据结构,不同类型共用相同接口 **核心交互**:`Context Thread` — Drawer/半屏弹窗中的时间线,展示完整处理进度 --- ## 3. 数据模型 ### 3.1 ActionItem(聚合视图,不建新表) ```typescript interface ActionItem { id: string; // 格式: "{type}:{source_id}" type: "ai_suggestion" | "alert" | "followup" | "data_anomaly"; priority: "urgent" | "high" | "medium" | "low"; status: "pending" | "in_progress" | "completed" | "dismissed"; title: string; summary: string; patient_id: string; patient_name: string; source_ref: string; // 指向原始实体 ID created_at: string; updated_at: string; } ``` ### 3.2 ThreadEvent(上下文线程事件) ```typescript interface ThreadEvent { step: string; // "ai_analysis" | "doctor_approval" | "followup_scheduled" | "followup_completed" | "reanalysis" label: string; // 展示文本 status: "completed" | "in_progress" | "pending"; detail?: string; // 补充说明 timestamp?: string; // 完成时间 operator?: string; // 操作人 link_to?: string; // 跳转路径 } ``` ### 3.3 ActionDefinition(可用操作) ```typescript interface ActionDefinition { key: string; // "approve" | "reject" | "complete_early" | "acknowledge" label: string; // 按钮文本 variant: "primary" | "danger" | "default"; confirm_message?: string; // 确认弹窗文案 api_endpoint: string; // 调用的 API } ``` --- ## 4. 后端 API 设计 ### 4.1 GET /api/v1/action-inbox 获取当前用户的行动收件箱列表。 **Query Params**: - `status`: `pending` | `in_progress` | `completed` | `dismissed`(可选,默认全部) - `type`: `ai_suggestion` | `alert` | `followup` | `data_anomaly`(可选,默认全部) - `page` / `page_size`: 分页(默认 1/20) **Response**: `PaginatedResponse` **实现策略**:聚合查询,不建新表。Phase 1 只查 `ai_suggestion` 表: ```sql -- 注意:patient_id 在 ai_analysis 表上,不在 ai_suggestion 上,需三表 JOIN SELECT s.id, s.suggestion_type, s.risk_level, s.status, s.params, s.created_at, s.updated_at, p.name as patient_name, a.patient_id, a.result_content, a.analysis_type FROM ai_suggestion s JOIN ai_analysis a ON s.analysis_id = a.id JOIN patient p ON a.patient_id = p.id WHERE s.tenant_id = $1 AND s.deleted_at IS NULL ORDER BY CASE s.risk_level WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, s.created_at DESC ``` ### 4.2 GET /api/v1/action-inbox/:source_ref/thread 获取某个行动项的上下文线程。 **Response**: ```typescript interface ThreadResponse { action_item: ActionItem; thread: ThreadEvent[]; available_actions: ActionDefinition[]; } ``` **线程拼装逻辑**(以 AI 建议为例): | 步骤 | 数据来源 | 状态判断 | |------|---------|---------| | AI 分析完成 | `ai_analysis.status = 'completed'` | 始终 completed | | 医生审批 | `ai_suggestion.status` | Approved→completed, Pending→in_progress | | 执行安排 | `ai_suggestion.workflow_instance_id IS NOT NULL` | 有值→completed | | 等待随访 | `follow_up` 关联查询 | 根据随访状态判断 | | 再分析对比 | `ai_suggestion.reanalysis_id IS NOT NULL` | 有值→completed | **操作按钮动态生成**: | 建议状态 | 可用操作 | |---------|---------| | Pending | 批准(→ `POST /ai/suggestions/:id/approve`)、拒绝 | | Approved/执行中 | 提前完成随访、标记已知悉 | | Executed | 查看前后对比报告 | | Expired/Rejected/ParseFailed | 无操作(只读展示) | | Executed | 查看前后对比报告 | | Expired | 无操作 | ### 4.3 代码归属与权限码 **代码位置**: - `ActionInboxService` → `crates/erp-health/src/service/action_inbox_service.rs`(erp-health crate) - `ActionInboxHandler` → `crates/erp-health/src/handler/action_inbox_handler.rs` - 路由注册:在 `erp-health` 模块的 `protected_routes()` 中新增 - 跨 crate 查询:通过 raw SQL 直接查 `ai_suggestion` + `ai_analysis` 表(与现有 `ai_suggestion_loader.rs` 模式一致,erp-health 已有跨 crate 读 ai 表的先例) **权限码**:新增独立权限码,注册在 `erp-health` 模块的 `permissions()` 中: - `health.action_inbox.list` — 查看行动收件箱 - `health.action_inbox.manage` — 执行操作(审批/拒绝/标记已知悉) 理由:`action-inbox` 是聚合端点,未来包含告警/随访/异常等多种类型,不应绑定到 `ai.suggestion.*` 权限。用 `health.` 前缀与 erp-health crate 归属一致。 **用户范围**: - Phase 1(AI 建议):返回当前租户下所有有权限查看的待办(`ai_suggestion` 无 `assigned_to` 字段) - Future(告警/随访):告警有 `assigned_to` 关联的负责医生,随访有 `assigned_to` 字段,届时 API 增加 `scope` 参数(`all` | `mine`) --- ## 5. Web 前端设计 ### 5.1 入口改造:NotificationPanel 扩展 **现有**:`apps/web/src/components/NotificationPanel.tsx`(铃铛 + Popover,SSE 推送消息/告警) **改造**: - Popover 中增加"待办"区域,显示最近 3 条 ActionItem - 底部增加"查看全部待办"链接,跳转到 `/action-inbox` 页面 - 角标数字改为:未读消息数 + 待处理 ActionItem 数 ### 5.2 新增页面:ActionInboxPage **路由**:`/action-inbox` **组件**:`apps/web/src/pages/ActionInboxPage.tsx` **布局**: - 顶部:筛选栏(状态 Tab + 类型下拉) - 主体:Ant Design List 组件,每项显示: - 类型标签(颜色区分) - 标题 + 患者姓名 - 优先级标记 - 创建时间 - 点击 → 打开 Drawer ### 5.3 新增组件:ActionThreadDrawer **组件**:`apps/web/src/components/ActionThreadDrawer.tsx` **实现**:Ant Design Drawer + Ant Design Steps/Timeline **结构**: ``` Drawer (width 480px) ├── 头部:标题 + 患者摘要卡(年龄/最近体征/关键指标) ├── 时间线:Ant Design Timeline 组件 │ ├── 每个节点:状态图标 + 文本 + 时间 + 跳转链接 │ └── 当前步骤高亮,未来步骤灰色 ├── 关联信息:可点击跳转的快捷链接 └── 操作区:动态按钮组 ``` **状态设计**: - **加载中**:Ant Design Skeleton(3 行卡片骨架屏) - **数据获取失败**:Ant Design Result + 重试按钮 - **空列表**:插图 + "暂无待办事项"文案 - **操作按钮请求中**:按钮 loading 状态 + 禁用 ### 5.4 实时更新策略 **Phase 1(轮询)**:用户手动刷新或切换页面时重新拉取列表。Drawer 打开时,操作按钮点击后主动调用 `GET /action-inbox/:id/thread` 刷新时间线。 **Phase 2(SSE 优化)**:复用 `apps/web/src/stores/message.ts` 中的 SSE 连接。后端在建议状态变更时,通过事件总线发布 → 消息服务推送到 SSE 流。前端监听 `suggestion.status_changed` 事件,触发列表/Drawer 刷新。 Phase 1 选择轮询的理由:现有 SSE 只推送 `message` 和 `alert` 两种类型,新增 `suggestion` 事件需要后端消息服务改造,且 Phase 1 的使用场景(医生不频繁审批)对实时性要求不高。 --- ## 6. 小程序设计 ### 6.1 TabBar 改造 **当前**:首页 | 健康 | 消息 | 我的(4 Tab) **新**:首页 | 健康 | 待办 | 消息 | 我的(5 Tab) - 在"健康"和"消息"之间插入"待办"Tab - Tab 图标:`todo-list` 或 `check-circle` - 角标显示待处理数量(调用 `GET /action-inbox?status=pending` 获取计数) - 修改文件:`apps/miniprogram/src/app.config.ts` ### 6.2 新增页面:待办列表 **路径**:`apps/miniprogram/src/pages/action-inbox/index.tsx` **布局**: - 顶部:状态筛选 Tab(全部 | 待处理 | 进行中 | 已完成) - 列表:按优先级排序的待办卡片 - 每张卡片:类型标签 + 标题 + 患者名 + 时间 + 箭头 - 点击卡片 → 弹出半屏弹窗 ### 6.3 新增组件:ActionHalfScreenDialog 使用 Taro `half-screen-dialog` 组件或自定义半屏弹窗: ``` 半屏弹窗 ├── 头部:拖拽条 + 类型标签 + 标题 ├── 摘要:关键信息(患者/风险等级/时间) ├── 时间线:简化版(只显示已完成和当前步骤) ├── 操作按钮 └── 底部:跳转详情链接 ``` ### 6.4 Service 层 **文件**:`apps/miniprogram/src/services/action-inbox.ts` ```typescript // 复用现有 listPendingSuggestions() // 新增: listActionItems(params: { status?: string; type?: string; page: number }) → GET /api/v1/action-inbox getActionThread(sourceRef: string) → GET /api/v1/action-inbox/:source_ref/thread ``` --- ## 7. 数据映射详细规则 ### 7.1 AI 建议 → ActionItem 映射 | ActionItem 字段 | 映射来源 | 规则 | |----------------|---------|------| | id | `"ai_suggestion:" + ai_suggestion.id` | 类型前缀 + 原 ID | | type | 固定 `"ai_suggestion"` | — | | priority | `ai_suggestion.risk_level` | High→urgent, Medium→high, Low→medium | | status | `ai_suggestion.status` | Pending→pending, Approved→in_progress, Executed→completed, Expired/Rejected/ParseFailed→dismissed | | title | `suggestion_type` + `risk_level` 拼接模板 | 如 `suggestion_type=Followup` → "建议安排随访";`suggestion_type=Alert` → "建议关注指标异常";从 `params` JSON 提取 `reason` 字段作为补充(若存在) | | summary | `ai_analysis.result_content` | 截取前 100 字符 | | patient_id | `ai_analysis.patient_id`(通过 `analysis_id` JOIN) | 三表 JOIN | | patient_name | `patient.name` | JOIN 查询 | | source_ref | `ai_suggestion.id` | 原始 ID | ### 7.2 线程步骤映射 | ThreadEvent.step | 触发条件 | 数据来源 | |-----------------|---------|---------| | `ai_analysis` | 始终存在 | `ai_analysis` 表 | | `doctor_approval` | `suggestion.status ∈ {Approved, Rejected}` | `suggestion.updated_at` 作为近似审批时间(无专用 `approved_at` 字段) | | `followup_scheduled` | `workflow_instance_id IS NOT NULL` | `workflow_instance` 表。注意:`action_dispatched` 步骤已合并到此处——`ai_action_dispatcher` 是事件消费者,不回写时间戳到 `ai_suggestion`,其效果体现为 `workflow_instance_id` 被设置 | | `followup_completed` | `follow_up.status = completed` | `follow_up` 表 | | `reanalysis` | `reanalysis_id IS NOT NULL` | `ai_analysis` (reanalysis) 表 | --- ## 8. 实施分阶段 ### Phase 1A:后端 API(2-3 天) - [ ] 创建 `ActionInboxService` → `crates/erp-health/src/service/action_inbox_service.rs` - [ ] 创建 `ActionInboxHandler` → `crates/erp-health/src/handler/action_inbox_handler.rs` - [ ] 在 erp-health `protected_routes()` 中注册 2 个新路由 - [ ] 实现三表 JOIN 聚合查询(`ai_suggestion` + `ai_analysis` + `patient`) - [ ] 实现线程拼装逻辑(合并 `action_dispatched` 到 `followup_scheduled` 步骤) - [ ] 实现操作按钮动态生成 - [ ] 在 erp-health `permissions()` 中注册 `health.action_inbox.list` / `health.action_inbox.manage` ### Phase 1B:Web 前端(2-3 天) - [ ] 扩展 `NotificationPanel`,增加待办区域 - [ ] 创建 `ActionInboxPage` 路由页面 - [ ] 创建 `ActionThreadDrawer` 组件 - [ ] SSE 实时更新集成 - [ ] 路由注册 + 菜单入口 ### Phase 1C:小程序(2-3 天) - [ ] TabBar 改造(4 Tab → 5 Tab,在"健康"和"消息"间插入"待办") - [ ] 创建待办列表页面 - [ ] 创建半屏弹窗组件 - [ ] Service 层对接 ### Phase 1D:验证与收尾(1 天) - [ ] 三端联调测试 - [ ] 更新 wiki 文档 - [ ] 提交并推送 --- ## 9. 验证方案 ### 9.1 后端验证 ```bash # 1. 启动后端 cd crates/erp-server && cargo run # 2. 创建 AI 分析 → 产生建议 # 3. 调用 API 验证 curl -H "Authorization: Bearer $TOKEN" \ http://localhost:3000/api/v1/action-inbox curl -H "Authorization: Bearer $TOKEN" \ http://localhost:3000/api/v1/action-inbox/ai_suggestion:{id}/thread ``` ### 9.2 Web 验证 1. 登录 → 顶栏铃铛应显示待办数量 2. 点击铃铛 → Popover 显示最近待办 3. "查看全部" → 进入 ActionInboxPage 4. 点击待办项 → Drawer 打开,时间线正确 5. 点击"批准" → 状态变更,时间线更新 6. SSE 推送 → Drawer 实时更新 ### 9.3 小程序验证 1. 底部 TabBar 显示"待办"Tab + 角标 2. 进入待办页 → 列表正确 3. 点击卡片 → 半屏弹窗弹出 4. 操作后 → 状态更新,列表刷新 --- ## 10. 未来扩展 新增行动类型只需: 1. 在 `ActionInboxService` 中注册新的聚合查询器 2. 定义 `type → ActionItem` 的映射规则 3. 定义线程步骤模板 4. 前端自动渲染(共用相同组件) **规划中的扩展类型**: - `alert` — 告警处理闭环(告警→评估→处置→追踪) - `followup` — 随访到期提醒(到期→执行→记录→再分析) - `data_anomaly` — 体征异常反馈(异常→趋势对比→建议→操作)