Files
hms/docs/superpowers/specs/2026-05-02-hardcoding-cleanup-design.md
iven c208dcc6f5 docs(specs): 7 份设计规格 — 工作台/适老化/硬编码清理/项目分析
新增: 适老化小程序/Action Inbox/统一工作台/医生操作台/
硬编码清理/健康管理台/全项目深度分析报告
2026-05-03 19:32:25 +08:00

18 KiB
Raw Blame History

系统硬编码清理设计规格

日期: 2026-05-02 | 状态: DRAFT | 范围: 前端硬编码清理 + 后端 API 补建 + 常量统一 + 医疗阈值配置化


1. 背景与动机

HMS 健康管理平台已进入关键节点 — 后端 328 条路由、45 个 Entity、772 个测试函数已就位。但前端工作台页面存在大量硬编码假数据,直接影响系统可信度:

  • AdminDashboard 中 70% 面板数据为假值(系统健康条、用户活跃度、模块状态、角色分布)
  • OperatorWorkbench 中积分动态使用假姓名、内容矩阵数字硬编码、待办事项不可操作
  • 20+ 处状态映射在多个文件中重复定义(严重度映射 5 处、性别映射 4 处、设备类型 3 处)
  • 医疗报警阈值(血压/心率/血糖)硬编码在小程序前端,不同患者/年龄段无法差异化

目标:清除所有 CRITICAL 级硬编码,确保用户看到的每个数字都来自真实 API同时建立常量管理规范防止回退。

原则

  • 新建 API 全部遵循现有 /api/v1/ 前缀 + ApiResponse<T> 包装 + 多租户隔离
  • 常量统一采用混合策略:静态枚举收敛到 constants/health.ts,动态选项对接字典 API
  • 医疗阈值复用现有 critical_value_threshold 表及 CRUD API补充患者端只读接口

2. 影响范围与严重度矩阵

2.1 CRITICAL — 用户看到虚假数据

ID 文件 硬编码内容 影响面
C-1 AdminDashboard.tsx 行 108-115 系统健康条 6 项全部假数据 所有管理员看到的"API服务正常""队列积压12"等均为虚假
C-2 AdminDashboard.tsx 行 230-258 用户活跃度 4 项 + 角色分布 5 项全部假数据 今日活跃 23 人、医生 12 人等均为虚假
C-3 AdminDashboard.tsx 行 41-50 模块状态 8 项硬编码 "44 实体 · 328 路由"等描述不反映真实状态
C-4 AdminDashboard.tsx 行 87 待处理工单硬编码为 5 管理员误以为有 5 个工单待处理
C-5 OperatorWorkbench.tsx 行 40-44 积分动态 3 人假姓名 张伟/王建国/李秀英均为虚构
C-6 OperatorWorkbench.tsx 行 142-147 内容矩阵"已发布 24 / 草稿箱 3"硬编码 不反映真实文章数量
C-7 OperatorWorkbench.tsx 行 61 AI Hero 卡片固定文案"3 个运营洞察" 不随实际数据变化

2.2 HIGH — 限制扩展性

ID 类别 数量 典型案例
H-1 重复状态映射 20+ 处 SEVERITY_COLOR 在 5 个文件各自定义
H-2 动态选项硬编码 6 类 科室/职称/设备类型/随访类型/咨询类型/家庭关系
H-3 默认角色为 admin 1 处 useDashboardRole.ts 无角色时返回 admin
H-4 医疗阈值硬编码 2 处 血压 140/90、心率 100/60、血糖 6.1/7.8

2.3 MEDIUM — 代码质量

ID 类别 说明
M-1 小程序菜单用中文做 key MENU_PATHS 用中文字符串做映射 key
M-2 待办模板硬编码 5 条固定待办文本,不可操作

3. 轨道 1工作台真实数据化

3.1 现有 API 清单与缺口分析

已有且已被前端调用的 API

API 路径 使用者
工作台统计 GET /health/action-inbox/statsWorkbenchStats DoctorWorkbench, OperatorWorkbench
团队概览 GET /health/action-inbox/teamTeamOverview DoctorWorkbench
行动收件箱 GET /health/action-inboxActionItem[] DoctorWorkbench
6 类统计 useStatsData → patient/consultation/followup/points/healthData/dialysis 三个工作台
个人统计 pointsApi.getPersonalStatsPersonalStats DoctorWorkbench
审计日志 listAuditLogs AdminDashboard

缺口 — 需要新建的 5 个 API

3.2 新增后端 API 设计

API-1: 系统健康检查

GET /health/admin/system-health
(完整路径: /api/v1/health/admin/system-health前缀由 erp-server nest 自动添加)
权限: health.dashboard.manage新增需在 module.rs 权限描述符中注册)
响应: ApiResponse<SystemHealth>
interface SystemHealth {
  services: {
    name: string;       // "API 服务" / "数据库" / "Redis" / ...
    status: 'healthy' | 'degraded' | 'down';
    message: string;    // "正常" / "队列积压 12" / "连接超时"
    response_ms?: number;
  }[];
  checked_at: string;   // ISO timestamp
}

实现:后端 handler 逐项检查 DB 连接(SELECT 1、Redis PING、SMTP 配置状态、存储路径可用性、定时任务心跳。结果缓存 30 秒避免频繁检查。

API-2: 用户活跃度统计

GET /health/admin/user-activity
权限: health.dashboard.manage同 API-1
响应: ApiResponse<UserActivity>
interface UserActivity {
  daily_active: number;
  weekly_active: number;
  monthly_active: number;
  total_registered: number;
  by_role: {
    role: string;       // "医生" / "护士" / ...
    count: number;
  }[];
}

实现:基于 userslast_login_at 字段统计,角色分布通过 user_roles JOIN roles 聚合。

API-3: 模块状态

GET /health/admin/modules
权限: health.dashboard.manage同 API-1
响应: ApiResponse<ModuleStatus[]>
interface ModuleStatus {
  name: string;         // "erp-health" / "erp-auth" / ...
  display_name: string; // "健康管理" / "身份权限" / ...
  description: string;
  active: boolean;
  entity_count?: number;
  route_count?: number;
}

实现:从 AppState 中读取已注册的 ErpModule 列表 + 查询 plugins 表补充插件状态。

API-4: 积分动态流

GET /health/points/recent-activity?limit=10
权限: health.points.list
响应: ApiResponse<PointsActivityItem[]>

命名约定TypeScript 接口字段使用 snake_case 镜像后端字段名,与本项目既有模式一致。

interface PointsActivityItem {
  id: string;
  user_name: string;    // 患者姓名
  detail: string;       // "兑换 · 血压计袖带" / "每日上报 · 血压"
  amount: string;       // "+10" / "-500"
  type: 'earn' | 'spend';
  created_at: string;
}

实现:查询 points_account_transactions 最新 N 条JOIN patients 获取姓名。

API-5: 内容统计

GET /health/articles/stats
权限: health.articles.list注意复数形式匹配 module.rs 注册的权限码)
响应: ApiResponse<ArticleStats>
interface ArticleStats {
  published: number;
  draft: number;
  pending_review: number;
  rejected: number;
  total_views: number;
}

实现:SELECT status, COUNT(*) FROM articles WHERE tenant_id = $1 AND deleted_at IS NULL GROUP BY status

3.3 工作台页面改造方案

AdminDashboard 改造

面板 当前(假数据) 改造后(真实数据)
系统健康条 6 项硬编码 调用 API-1 systemHealth
统计卡片 - 注册用户 useStatsData (已真实) 保持不变
统计卡片 - 业务模块 硬编码 MODULES.length 调用 API-3 计算活跃数
统计卡片 - 今日操作 auditLogs.length (已真实) 保持不变
统计卡片 - 待处理工单 硬编码 5 改用 actionInboxApi.stats().total_pending
用户活跃度 4 项百分比 + 角色分布全部假 调用 API-2 userActivity
模块状态 MODULES 数组硬编码 调用 API-3 modules
快捷管理 QUICK_ACTIONS 硬编码 保留(属 UI 配置)
问候语 "X主任" 硬编码"主任" 移除称呼后缀,只显示姓

OperatorWorkbench 改造

面板 当前 改造后
AI Hero 卡片 固定文案 "3 个运营洞察" 动态生成文案基于 stats 数据
统计卡片 useStatsData + actionInbox (已真实) 保持不变
今日待办 5 条硬编码 todo 模板 改用 actionInboxApi.list 筛选 pending 项
积分动态 3 人假姓名 调用 API-4 recentActivity
内容矩阵 "已发布 24 / 草稿箱 3" 调用 API-5 articleStats
问候语 "X美玲" 硬编码"美玲" 移除,只显示姓

DoctorWorkbench

基本已是真实数据80%),仅需微调:

  • 问候语 "X医生" 中"医生"后缀 → 移除,只显示姓
  • 确认 personalStats.consultations_this_month 字段后端已实现(否则用 0 占位)

4. 轨道 2常量统一

4.1 分类原则

分类 存放位置 判断标准 例子
静态映射 constants/health.ts 枚举固定,后端不可能新增项 性别、血型、严重度颜色、状态颜色
动态选项 后端字典 API 业务运营可能新增 科室、职称、设备类型、随访类型
UI 配置 各组件内部 纯前端行为,无后端对应 快捷操作按钮、Tab 标签文案

4.2 静态映射收敛

统一以下映射组到 constants/health.ts,消除所有重复定义:

导出名 当前分散位置 统一后
SEVERITY_CONFIG AlertDashboard, AlertList, AlertRuleList, ActionInbox, DoctorDashboard (5处) 1 处
GENDER_OPTIONS constants/health.ts, PatientDetail, PatientTagManage, PatientSelect (4处) 1 处
DEVICE_TYPE_OPTIONS + DEVICE_TYPE_COLOR DeviceManage, DeviceReadingsTab, AlertRuleList (3处) 1 处
ALERT_STATUS_CONFIG AlertDashboard, AlertList, StatusTag (3处) 1 处
APPOINTMENT_STATUS_CONFIG AppointmentList (1处但含复杂流转) 1 处
CONSULTATION_STATUS_CONFIG ConsultationList (1处) 1 处
BLOOD_TYPE_OPTIONS constants/health.ts (1处) 保持
STATUS_OPTIONS (患者状态) constants/health.ts (1处) 保持

每个映射组的统一格式:

export const SEVERITY_CONFIG: Record<string, { label: string; color: string; bg: string }> = {
  critical: { label: '危急', color: '#DC2626', bg: '#FEF2F2' },
  high:     { label: '高',   color: '#D97706', bg: '#FFFBEB' },
  medium:   { label: '中',   color: '#2563EB', bg: '#EFF6FF' },
  low:      { label: '低',   color: '#6B7280', bg: '#F9FAFB' },
};

4.3 动态选项字典化

新增以下字典编码到 erp-config 的字典系统(后端已有 GET /config/dictionaries/items?code=xxx API前端 apps/web/src/api/dictionaries.ts 已封装 listItemsByCode

字典编码 用途 种子数据来源 影响文件
health_department 科室列表 DoctorList 现有 DEPARTMENT_OPTIONS DoctorList, DoctorSchedule
health_title 医护职称 DoctorList 现有 TITLE_OPTIONS DoctorList
health_device_type 设备类型 DeviceManage 现有 DEVICE_TYPE_OPTIONS DeviceManage, DeviceReadingsTab, AlertRuleList
health_follow_up_type 随访类型 FollowUpTaskList 现有 FOLLOW_UP_TYPE_OPTIONS FollowUpTaskList
health_consultation_type 咨询类型 ConsultationList 现有 CONSULTATION_TYPE_OPTIONS ConsultationList
health_relationship 家庭关系 FamilyMembersTab 现有 RELATIONSHIP_OPTIONS FamilyMembersTab, family-add(MP)

前端新增 useDictionary(code) hook 封装字典获取 + 缓存逻辑:

export function useDictionary(code: string) {
  const [items, setItems] = useState<DictionaryItem[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    listItemsByCode(code)
      .then(setItems)
      .catch(() => setItems([]))
      .finally(() => setLoading(false));
  }, [code]);

  return { items, loading };
}

4.4 小程序联动

小程序对应的硬编码同步改为字典 API

文件 当前硬编码 改造
pages/pkg-health/input/index.tsx INDICATORS 6 个体征指标 从字典获取 + 缓存
pages/health/index.tsx VITAL_TABS 4 个体征 tab 从字典获取
pages/pkg-profile/family-add/index.tsx RELATION_OPTIONS 3 个关系选项 从字典获取
pages/pkg-profile/family-add/index.tsx GENDER_OPTIONS 3 个性别选项 保留为静态映射(不在字典化范围)
pages/mall/index.tsx PRODUCT_TYPE_TABS 商品类型 tab 从字典获取

小程序端新增 useDict(code) hook + Taro.storage 本地缓存24h TTL离线时使用缓存或内置默认值。


5. 轨道 3医疗阈值配置

5.1 复用现有 critical_value_threshold

系统已有完整的危急值阈值基础设施,无需新建表

  • : critical_value_threshold(迁移 m20260426_000060
  • Entity: crates/erp-health/src/entity/critical_value_threshold.rs
  • Handler: crates/erp-health/src/handler/critical_value_threshold_handler.rs
  • Service: crates/erp-health/src/service/critical_value_threshold_service.rs
  • 路由: /health/critical-value-thresholdsCRUD 已注册,见 module.rs 行 602-609
  • 权限: health.critical-value-thresholds.list + health.critical-value-thresholds.manage(已定义)
  • 种子数据: 8 条默认记录已存在

现有表字段:indicatordirection(高/低)、threshold_valuelevel(警告/危急)、departmentage_minage_maxis_active + 标准审计字段。

5.2 需补充的工作

工作项 说明
补充 warning 级别种子数据 现有种子仅有 critical 级别,需新增 warning 级别阈值(血压 140/90、心率 100/60、血糖 6.1/7.8
新增患者端只读 API GET /health/critical-value-thresholds/public — 认证即可,无需管理权限,返回当前租户所有 is_active 阈值
Web 端阈值管理 UI 在告警规则管理页面增加"阈值配置"Tab复用现有 CRUD API

5.3 前端改造

小程序改造流程:

  1. App 启动时调用 GET /health/critical-value-thresholds/public 获取全量阈值
  2. 存入 Taro.storagekey: health_thresholdsTTL: 24h
  3. 体征页 health/index.tsx 从缓存读取阈值替代 REF_RANGES 和判断逻辑
  4. 输入页 pkg-health/input/index.tsx 从缓存读取阈值替代 WARN_THRESHOLDS
  5. 缓存未命中时使用内置默认值(与当前硬编码值一致)

5.4 补充种子数据warning 级别)

新增迁移脚本插入 warning 级别阈值(现有 critical 级别已由 m20260426_000060 种子覆盖):

indicator level direction threshold_value 说明
blood_pressure_systolic warning high 140 收缩压参考上限
blood_pressure_diastolic warning high 90 舒张压参考上限
heart_rate warning high 100 心率参考上限
heart_rate warning low 60 心率参考下限
blood_sugar_fasting warning high 6.1 空腹血糖参考上限
blood_sugar_postprandial warning high 7.8 餐后血糖参考上限

共 6 条 warning 级别配置,与现有 8 条 critical 级别共同构成完整的阈值体系。


6. 跨切面关注点

6.1 错误处理

  • 所有新建 API 遵循现有 AppErrorApiResponse 错误链
  • 前端调用失败时降级显示:统计卡片显示 "—",列表显示"暂无数据"
  • 系统健康检查 API 本身失败时,前端显示"检查中..."而非假数据

6.2 测试策略

层级 测试内容 工具
后端单元测试 每个 handler + service 函数 #[tokio::test]
后端集成测试 5 个新 API 的完整请求/响应 Testcontainers
前端单元测试 useDictionary hook + 常量导出一致性 vitest
前端组件测试 工作台页面 loading/empty/data 三态渲染 vitest + testing-library
E2E 测试 工作台页面加载无硬编码假数据 playwright

6.3 迁移与部署顺序

三条轨道互不依赖,但建议按以下顺序部署:

  1. 轨道 2常量统一 — 纯前端重构,零后端改动,可立即部署
  2. 轨道 3医疗阈值 — 复用现有表,仅需补充种子数据 + 新增患者端只读 API独立部署
  3. 轨道 1工作台 API — 需要新建 5 个 API最后部署

轨道 1 内部的 API 实现顺序:

  1. API-5 内容统计(最简单,单表 COUNT
  2. API-4 积分动态(单表 JOIN
  3. API-3 模块状态(读取 AppState
  4. API-2 用户活跃度(多表聚合)
  5. API-1 系统健康检查(外部连接检测)

7. 验证清单

轨道 1 验证

  • AdminDashboard 系统健康条数据来自 API不出现假数据
  • AdminDashboard 用户活跃度数据来自 API
  • AdminDashboard 模块状态来自 API
  • OperatorWorkbench 积分动态来自 API
  • OperatorWorkbench 内容矩阵来自 API
  • OperatorWorkbench 待办来自 actionInbox
  • 全部 5 个新 API 在 Swagger UI 可测试
  • cargo test --workspace 全部通过
  • pnpm build 前端构建通过

轨道 2 验证

  • SEVERITY_CONFIG 仅在 constants/health.ts 定义一次,其余 4 处改为引用
  • GENDER_OPTIONS 仅在 constants/health.ts 定义一次
  • 6 个字典编码在后端种子数据中存在
  • useDictionary hook 可正常获取字典数据
  • 小程序 useDict hook + storage 缓存工作正常

轨道 3 验证

  • critical_value_threshold 表已有 + warning 级别种子数据 6 条正确插入
  • 患者端只读 API GET /health/critical-value-thresholds/public 可正常访问
  • 小程序体征页使用 API 阈值(非硬编码)
  • 小程序输入页使用 API 阈值(非硬编码)
  • 离线场景下降级到内置默认值