18 KiB
系统硬编码清理设计规格
日期: 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/stats → WorkbenchStats |
DoctorWorkbench, OperatorWorkbench |
| 团队概览 | GET /health/action-inbox/team → TeamOverview |
DoctorWorkbench |
| 行动收件箱 | GET /health/action-inbox → ActionItem[] |
DoctorWorkbench |
| 6 类统计 | useStatsData → patient/consultation/followup/points/healthData/dialysis |
三个工作台 |
| 个人统计 | pointsApi.getPersonalStats → PersonalStats |
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;
}[];
}
实现:基于 users 表 last_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-thresholds(CRUD 已注册,见module.rs行 602-609) - 权限:
health.critical-value-thresholds.list+health.critical-value-thresholds.manage(已定义) - 种子数据: 8 条默认记录已存在
现有表字段:indicator、direction(高/低)、threshold_value、level(警告/危急)、department、age_min、age_max、is_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 前端改造
小程序改造流程:
- App 启动时调用
GET /health/critical-value-thresholds/public获取全量阈值 - 存入
Taro.storage(key:health_thresholds,TTL: 24h) - 体征页
health/index.tsx从缓存读取阈值替代REF_RANGES和判断逻辑 - 输入页
pkg-health/input/index.tsx从缓存读取阈值替代WARN_THRESHOLDS - 缓存未命中时使用内置默认值(与当前硬编码值一致)
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 遵循现有
AppError→ApiResponse错误链 - 前端调用失败时降级显示:统计卡片显示 "—",列表显示"暂无数据"
- 系统健康检查 API 本身失败时,前端显示"检查中..."而非假数据
6.2 测试策略
| 层级 | 测试内容 | 工具 |
|---|---|---|
| 后端单元测试 | 每个 handler + service 函数 | #[tokio::test] |
| 后端集成测试 | 5 个新 API 的完整请求/响应 | Testcontainers |
| 前端单元测试 | useDictionary hook + 常量导出一致性 |
vitest |
| 前端组件测试 | 工作台页面 loading/empty/data 三态渲染 | vitest + testing-library |
| E2E 测试 | 工作台页面加载无硬编码假数据 | playwright |
6.3 迁移与部署顺序
三条轨道互不依赖,但建议按以下顺序部署:
- 轨道 2(常量统一) — 纯前端重构,零后端改动,可立即部署
- 轨道 3(医疗阈值) — 复用现有表,仅需补充种子数据 + 新增患者端只读 API,独立部署
- 轨道 1(工作台 API) — 需要新建 5 个 API,最后部署
轨道 1 内部的 API 实现顺序:
- API-5 内容统计(最简单,单表 COUNT)
- API-4 积分动态(单表 JOIN)
- API-3 模块状态(读取 AppState)
- API-2 用户活跃度(多表聚合)
- 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 个字典编码在后端种子数据中存在
useDictionaryhook 可正常获取字典数据- 小程序
useDicthook + storage 缓存工作正常
轨道 3 验证
critical_value_threshold表已有 + warning 级别种子数据 6 条正确插入- 患者端只读 API
GET /health/critical-value-thresholds/public可正常访问 - 小程序体征页使用 API 阈值(非硬编码)
- 小程序输入页使用 API 阈值(非硬编码)
- 离线场景下降级到内置默认值