Files
hms/docs/superpowers/specs/2026-04-26-frontend-engineering-design.md
iven d1ab8074a3
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
docs: 多专家组头脑风暴产出 — 5 份设计规格
基于全景审计分析,产出 5 份跨领域设计规格:

1. 性能优化 — 后端批量INSERT/合并COUNT/告警预加载 + 前端N+1内联name
2. 安全纵深防御 — PostgreSQL RLS/行级数据范围/session_key Redis/审计哈希链
3. 事件驱动架构增强 — 6个业务域11个缺失事件补发 + Outbox LISTEN/NOTIFY
4. 前端工程化 — 14个大组件拆分 + 3个重复模式统一 + Bundle优化
5. 可观测性与运维 — 深度健康检查/Prometheus/OpenTelemetry/生产Docker
2026-04-27 07:46:36 +08:00

10 KiB
Raw Blame History

前端工程化改进设计

日期: 2026-04-26 | 状态: draft | 主题: 组件拆分、重复模式统一、Bundle 优化

1. 背景

HMS Web 前端共 139 个源文件77 TSX + 62 TS总代码量 27,000 行。随着健康管理模块的持续迭代,工程化债务逐步积累,主要体现在四个方面:

  1. 组件膨胀 — 14 个文件超过 400 行,最大 872 行PluginCRUDPage.tsx
  2. 重复模式 — 错误处理、分页列表、ID→名称缓存三处重复已有统一抽象但未被全面采用
  3. API 层风格混用 — 对象风格(patientApi.list())与函数风格(listAlerts())并存
  4. Bundle 体积 — 大型依赖未拆独立 chunkchunkSizeWarningLimit 已提升至 600KB

1.1 数据概览

指标 数值
超过 400 行的组件 14 个
超过 500 行的组件 7 个
未使用 usePaginatedData 的健康列表页 6 个
自建 nameCache 的页面 2 个AppointmentList、PointsOrderList
API 层文件 33 个(对象风格 8 个,函数风格 25 个)

2. 问题分析

2.1 组件膨胀分析

TOP 7 大组件:

文件 行数 职责混杂点
PluginCRUDPage.tsx 872 表格渲染 + 表单校验 + Drawer + 导入导出 + Timeline
PluginGraphPage.tsx 759 Canvas 绑定 + 数据加载 + 布局计算 + 动画控制
Organizations.tsx 622 三栏树形 + 组织 CRUD + 部门管理 + 人员分配
StatisticsDashboard.tsx 580 五个并行统计 API + 图表渲染 + 时间筛选
ArticleEditor.tsx 554 富文本编辑器 + 表单 + 标签选择 + 封面上传
FollowUpTaskList.tsx 547 列表 + 筛选面板 + 状态流转弹窗 + 批量操作
MainLayout.tsx 535 侧边栏 + 动态菜单 + 插件菜单注入 + Header

根因: React 组件未按"展示/容器/Hook"分层,状态逻辑和 UI 渲染耦合在同一文件中。

2.2 重复模式分析

模式一:错误处理

已有统一方案:client.ts 全局拦截器处理 401/403/500handleApiError() 处理业务错误,useApiRequest() hook 封装 try-catch。

实际情况:组件仍大量内联 catch (err) { message.error(...) }。原因useApiRequest 缺少 loading 状态返回,部分场景需要更细粒度的错误控制。

模式二:分页列表

已有 usePaginatedData<T>(fetchFn, pageSize) hook封装 data/total/page/loading/refresh。

未使用的健康列表页PatientList、AppointmentList、ConsultationList、FollowUpTaskList、OfflineEventList、PointsProductList。原因fetchFn 签名只支持 (page, pageSize, search)部分页面需要额外筛选参数status/dateRange/tag

模式三ID→名称缓存

已有 useHealthStore 提供 resolvePatientName(id) / getPatientName(id) + 自动去重加载。

AppointmentList.tsx 和 PointsOrderList.tsx 仍自建 useState<Record<string, string>> nameCache。原因store 未提供批量解析接口。

2.3 API 风格混用

  • 对象风格8 个文件):patientApi.list(), doctorApi.list()
  • 函数风格25 个文件):listAlerts(), acknowledgeAlert()

结论:不强制统一(改动量大、收益有限),新增 API 文件统一采用对象风格。

2.4 Bundle 体积分析

当前 manualChunks 仅拆分了 react/react-dom/antd/axios/zustand。未拆分的大型依赖

依赖 估算大小 (gzip) 使用范围
@ant-design/charts ~180KB 仅 StatisticsDashboard
@xyflow/react ~120KB 仅 PluginGraphPage
@wangeditor/editor ~200KB 仅 ArticleEditor

chunkSizeWarningLimit: 600 说明单个 chunk 已超过 Vite 默认 500KB 警告阈值。

3. 解决方案

3.1 组件拆分策略

统一采用 Container + Presentational + Hook 三层模式:

原组件 (500+ 行)
├── hooks/useXxxData.ts     — 数据获取、状态管理、业务逻辑
├── components/XxxTable.tsx — 纯展示表格
├── components/XxxForm.tsx  — 表单(含校验)
└── XxxPage.tsx             — 容器组件(组装 hooks + 子组件)

PluginCRUDPage.tsx (872行) — P0

拆分目标 类型 预估行数 职责
usePluginData.ts Hook ~120 CRUD 操作、导入导出逻辑
PluginTable.tsx 展示 ~150 表格列定义、行操作按钮
PluginForm.tsx 展示 ~180 新增/编辑表单 + Drawer
PluginImportExport.tsx 展示 ~100 导入导出面板
PluginTimeline.tsx 展示 ~80 操作历史 Timeline
PluginCRUDPage.tsx 容器 ~80 组装子组件

PluginGraphPage.tsx (759行) — P1

拆分目标 类型 预估行数 职责
useGraphLayout.ts Hook ~100 布局算法、节点位置计算
useGraphData.ts Hook ~80 数据加载、边/节点转换
GraphCanvas.tsx 展示 ~200 ReactFlow 渲染、节点样式
GraphToolbar.tsx 展示 ~60 工具栏(缩放/自动布局)
PluginGraphPage.tsx 容器 ~60 组装

Organizations.tsx (622行) — P1

拆分目标 类型 预估行数 职责
useOrgTree.ts Hook ~80 树数据加载、CRUD 操作
OrgTree.tsx 展示 ~120 左侧树形选择
OrgDetail.tsx 展示 ~150 右侧组织详情/编辑
DeptMemberList.tsx 展示 ~100 部门成员列表 + 分配
Organizations.tsx 容器 ~60 三栏布局组装

StatisticsDashboard.tsx (580行) — P1

拆分目标 类型 预估行数 职责
useStatsData.ts Hook ~100 五个统计 API 并行加载
PatientTrendChart.tsx 展示 ~80 患者趋势图
AppointmentStats.tsx 展示 ~80 预约统计图
OverviewCards.tsx 展示 ~60 概览卡片组
TimeRangeSelector.tsx 展示 ~40 时间范围选择
StatisticsDashboard.tsx 容器 ~50 组装

ArticleEditor.tsx (554行) — P2

拆分目标 类型 预估行数 职责
useArticleEditor.ts Hook ~100 文章加载/保存/发布逻辑
RichTextEditor.tsx 展示 ~150 WangEditor 封装
ArticleMetaForm.tsx 展示 ~120 标题/分类/标签/封面表单
ArticleEditor.tsx 容器 ~60 组装

FollowUpTaskList.tsx (547行) — P2

拆分目标 类型 预估行数 职责
useFollowUpTasks.ts Hook ~100 列表加载/筛选/状态流转
FollowUpTable.tsx 展示 ~120 表格 + 行操作
FollowUpFilter.tsx 展示 ~80 筛选面板
TaskStatusModal.tsx 展示 ~80 状态变更弹窗
FollowUpTaskList.tsx 容器 ~50 组装

MainLayout.tsx (535行) — P1

拆分目标 类型 预估行数 职责
useMenuBuilder.ts Hook ~100 菜单数据构建、插件菜单合并
AppSidebar.tsx 展示 ~120 侧边栏渲染
AppHeader.tsx 展示 ~80 顶部 Header
MainLayout.tsx 容器 ~60 布局骨架

3.2 重复模式统一方案

3.2.1 增强 useApiRequestP0

当前问题:缺少 loading 状态。增强为:

interface UseApiRequestReturn {
  execute: <T>(fn: () => Promise<T>, successMsg?: string) => Promise<T | null>;
  loading: boolean;
}

改动量:~10 行。已有调用点无需修改。

3.2.2 增强 usePaginatedDataP1

当前问题fetchFn 签名只支持 (page, pageSize, search)。增强为支持泛型筛选参数:

interface UsePaginatedDataOptions<T, F> {
  fetchFn: (page: number, pageSize: number, filters: F) => Promise<{ data: T[]; total: number }>;
  pageSize?: number;
  defaultFilters: F;
  autoFetch?: boolean;
}

改动量:~30 行。保持旧签名兼容函数重载6 个列表页渐进迁移。

3.2.3 增强 useHealthStore 批量解析P2

新增 batchResolvePatientNames(ids) / batchResolveDoctorNames(ids)。内部实现:去重 → 批量并发(限制并发数 5→ 写入缓存。

改动量stores/health.ts ~30 行。删除 AppointmentList/PointsOrderList 自建 nameCache 代码。

3.3 API 风格策略

不强制统一现有代码。新增规则:新建 API 文件统一采用对象风格修改已有文件时顺手迁移Boy Scout Rule

3.4 Bundle 优化方案

在 vite.config.ts 的 manualChunks 中增加重型依赖拆分:

if (id.includes('@ant-design/charts') || id.includes('@antv/')) return 'vendor-charts';
if (id.includes('@xyflow/react') || id.includes('@reactflow/')) return 'vendor-flow';
if (id.includes('@wangeditor/')) return 'vendor-editor';

配合路由级 React.lazy() 加载,使独立 chunk 仅在访问对应页面时下载。预期主 chunk 从 > 600KB 降至 < 400KBchunkSizeWarningLimit 可降至 500。

4. 实施步骤

Phase 任务 工期 优先级
1: 基础设施增强 增强 useApiRequest + usePaginatedData + manualChunks + 路由懒加载 1 天 P0-P1
2: 核心组件拆分 PluginCRUDPage + MainLayout + PluginGraphPage 2-3 天 P0-P1
3: 健康模块拆分 StatisticsDashboard + ArticleEditor + FollowUpTaskList 2 天 P1-P2
4: 重复模式迁移 6 个列表页迁移 usePaginatedData + 2 个 nameCache 迁移 2 天 P2
5: 验证回归 pnpm build 验证 + 功能回归 + ESLint max-lines-per-file 规则 1 天 P2

5. 风险与缓解

风险 缓解措施
拆分引入 re-render 性能退化 React.memo 包装展示组件DevTools Profiler 验证
usePaginatedData 泛型重构破坏现有调用点 保持旧签名兼容(函数重载),渐进迁移
拆分后导入路径变化导致循环依赖 每个拆分完成后立即 pnpm build 验证
Bundle 拆分过度导致请求数增加 HTTP/2 多路复用下影响有限
WangEditor 封装层与编辑器生命周期冲突 useRef 管理 editor 实例,严格 cleanup

不做的事情: 不重写现有组件;不强制统一 API 风格;不引入新状态管理库;不做 SSR/SSG。

成功指标:

指标 当前值 目标值
>400 行的组件数 14 <= 5
>500 行的组件数 7 0
主 chunk 体积 > 600KB < 400KB
usePaginatedData 覆盖率 ~30% > 80%
useApiRequest 覆盖率 ~20% > 60%