docs: 5 份实施计划 — 性能/安全/事件/前端/可观测性
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

对应 5 份设计规格,共 75 个 Task:

1. 性能优化 (12 Task) — 批量INSERT/N+1内联name/合并COUNT/按需重绘/chunk拆分
2. 安全纵深防御 (8 Task) — RLS/行级数据范围/Redis session_key/审计哈希链
3. 事件驱动架构 (10 Task) — 11个缺失事件补发/LISTEN+NOTIFY/schema版本化
4. 前端工程化 (10 Task) — hook统一/组件拆分/Bundle优化
5. 可观测性运维 (10 Task) — 深度健康检查/Prometheus/OTel/生产Docker/告警
This commit is contained in:
iven
2026-04-27 08:00:50 +08:00
parent 215fb35e0e
commit b410fa9f78
5 changed files with 809 additions and 0 deletions

View File

@@ -0,0 +1,290 @@
# 前端工程化改进实施计划
> 设计规格: `docs/superpowers/specs/2026-04-26-frontend-engineering-design.md`
> 日期: 2026-04-26 | 状态: draft | 总周期: 7 天
---
## Phase 1: 重复模式统一Day 1-2
### Task 1: 增强 useApiRequest hook统一错误处理
**目标**: 补齐 loading 状态,消除组件内联 `catch (err) { message.error(...) }` 模式。
**涉及文件**:
- 修改: `apps/web/src/hooks/useApiRequest.ts`
**详细步骤**:
1.`useApiRequest` 返回值中新增 `loading: boolean` 状态:
```typescript
interface UseApiRequestReturn {
execute: <T>(fn: () => Promise<T>, successMsg?: string) => Promise<T | null>;
loading: boolean;
}
```
2. `execute` 内部在调用前 `setLoading(true)`finally 中 `setLoading(false)`
3. 保持现有调用点无需修改 — 返回值是对象解构,新增字段不影响旧代码
4. 选取 3 个健康模块页面PatientList、AppointmentList、FollowUpTaskList迁移为使用 `execute` + `loading`
**验收标准**:
- `pnpm build` 通过
- 3 个迁移页面的 catch 块不再有内联 `message.error`,统一走 `handleApiError`
- loading 状态正确绑定到页面按钮/Spin 组件
---
### Task 2: 增强 usePaginatedData hook健康模块页面迁移
**目标**: 支持泛型筛选参数,迁移 6 个健康列表页使用统一 hook。
**涉及文件**:
- 修改: `apps/web/src/hooks/usePaginatedData.ts`
- 修改: `apps/web/src/pages/health/PatientList.tsx`
- 修改: `apps/web/src/pages/health/OfflineEventList.tsx`
- 修改: `apps/web/src/pages/health/PointsProductList.tsx`
**详细步骤**:
1. 增强 hook 签名为泛型筛选:
```typescript
function usePaginatedData<T, F = string>(
fetchFn: (page: number, pageSize: number, filters: F) => Promise<{ data: T[]; total: number }>,
options?: { pageSize?: number; defaultFilters: F; autoFetch?: boolean }
): { data, total, page, loading, filters, setFilters, refresh }
```
2. 函数重载保持旧 `(fetchFn, pageSize?)` 签名兼容
3. 新增 `filters` / `setFilters` 状态,`fetchFn` 调用时传入当前 filters
4. 迁移 PatientList按 status/name/gender 筛选)和 OfflineEventList按 status/dateRange 筛选)
**验收标准**:
- 旧调用点(不传 filters行为不变
- PatientList 和 OfflineEventList 筛选功能正常,代码行数各减少 15-25 行
- `pnpm build` 通过
---
### Task 3: 移除 nameCache统一用 useHealthStore
**目标**: 消除 AppointmentList 和 PointsOrderList 自建的 `useState<Record<string, string>>` nameCache。
**涉及文件**:
- 修改: `apps/web/src/stores/health.ts`
- 修改: `apps/web/src/pages/health/AppointmentList.tsx`
- 修改: `apps/web/src/pages/health/PointsOrderList.tsx`
**详细步骤**:
1.`useHealthStore` 新增批量解析方法:
- `batchResolvePatientNames(ids: string[]): Promise<Record<string, string>>`
- `batchResolveDoctorNames(ids: string[]): Promise<Record<string, string>>`
2. 内部实现:去重 → 过滤已缓存 → 并发加载(限制 5 并发)→ 写入缓存并返回
3. 在 AppointmentList 中移除 nameCache state改用 store 方法
4. 在 PointsOrderList 中同样迁移
**验收标准**:
- 两个页面无 `useState<Record<string, string>>` nameCache 代码
- 患者姓名/医生姓名在列表中正确显示
- `pnpm build` 通过
---
## Phase 2: 大组件拆分Day 3-5
### Task 4: PluginCRUDPage 拆分为 CRUDTable/CRUDForm/DetailDrawer/ImportExport
**目标**: 将 872 行的 PluginCRUDPage.tsx 拆为容器 + 展示组件。
**涉及文件**:
- 新增: `apps/web/src/pages/plugins/components/CRUDTable.tsx` (~150 行)
- 新增: `apps/web/src/pages/plugins/components/CRUDForm.tsx` (~180 行)
- 新增: `apps/web/src/pages/plugins/components/DetailDrawer.tsx` (~80 行)
- 新增: `apps/web/src/pages/plugins/components/ImportExport.tsx` (~100 行)
- 新增: `apps/web/src/pages/plugins/hooks/usePluginData.ts` (~120 行)
- 修改: `apps/web/src/pages/plugins/PluginCRUDPage.tsx` (缩减至 ~80 行)
**详细步骤**:
1. 创建 `hooks/usePluginData.ts`:提取 CRUD 操作、导入导出逻辑、Drawer 可见性状态
2. 创建 `CRUDTable.tsx`:表格列定义 + 行操作按钮props 接收 data/onDelete/onEdit/onDetail
3. 创建 `CRUDForm.tsx`:新增/编辑表单 + Drawer包含校验规则
4. 创建 `DetailDrawer.tsx`:详情展示 + 操作历史 Timeline
5. 创建 `ImportExport.tsx`:导入面板 + 导出按钮
6. 改写 `PluginCRUDPage.tsx` 为容器组件:调用 usePluginData hook组装子组件
**验收标准**:
- `pnpm build` 通过
- 插件 CRUD 所有功能正常(新增、编辑、删除、详情、导入、导出)
- PluginCRUDPage.tsx <= 100 行,无子组件超过 200 行
---
### Task 5: PluginGraphPage 抽取 useGraphCanvas hook
**目标**: 将 759 行的 PluginGraphPage.tsx 拆为 hook + 展示组件。
**涉及文件**:
- 新增: `apps/web/src/pages/plugins/hooks/useGraphLayout.ts` (~100 行)
- 新增: `apps/web/src/pages/plugins/hooks/useGraphData.ts` (~80 行)
- 新增: `apps/web/src/pages/plugins/components/GraphCanvas.tsx` (~200 行)
- 新增: `apps/web/src/pages/plugins/components/GraphToolbar.tsx` (~60 行)
- 修改: `apps/web/src/pages/plugins/PluginGraphPage.tsx` (缩减至 ~60 行)
**详细步骤**:
1. `useGraphData.ts`:数据加载、边/节点格式转换、字段映射
2. `useGraphLayout.ts`Dagre/elkjs 布局算法、节点位置计算、自动布局触发
3. `GraphCanvas.tsx`ReactFlow 渲染、自定义节点样式、拖拽交互
4. `GraphToolbar.tsx`:缩放控制、自动布局、布局方向切换
5. 容器组件组装以上模块
**验收标准**:
- 插件关系图页面正常渲染和交互
- 拖拽节点、自动布局、缩放功能正常
- `pnpm build` 通过
---
### Task 6: Organizations.tsx 抽象 TreeEntityManager
**目标**: 将 622 行的 Organizations.tsx 按三层模式拆分。
**涉及文件**:
- 新增: `apps/web/src/pages/system/hooks/useOrgTree.ts` (~80 行)
- 新增: `apps/web/src/pages/system/components/OrgTree.tsx` (~120 行)
- 新增: `apps/web/src/pages/system/components/OrgDetail.tsx` (~150 行)
- 新增: `apps/web/src/pages/system/components/DeptMemberList.tsx` (~100 行)
- 修改: `apps/web/src/pages/system/Organizations.tsx` (缩减至 ~60 行)
**详细步骤**:
1. `useOrgTree.ts`树数据加载、CRUD 操作、选中节点状态
2. `OrgTree.tsx`左侧树形选择DirectoryTree + 搜索 + 右键菜单)
3. `OrgDetail.tsx`:右侧组织详情/编辑表单
4. `DeptMemberList.tsx`:部门成员列表 + 人员分配 Modal
5. 容器组件三栏布局组装
**验收标准**:
- 组织管理 CRUD 功能正常(新增/编辑/删除组织、部门、人员分配)
- 树形选择、搜索过滤正常
- `pnpm build` 通过
---
### Task 7: StatisticsDashboard 拆分为独立卡片组件
**目标**: 将 580 行的 StatisticsDashboard.tsx 拆为 hook + 独立图表卡片。
**涉及文件**:
- 新增: `apps/web/src/pages/health/hooks/useStatsData.ts` (~100 行)
- 新增: `apps/web/src/pages/health/components/PatientTrendChart.tsx` (~80 行)
- 新增: `apps/web/src/pages/health/components/AppointmentStats.tsx` (~80 行)
- 新增: `apps/web/src/pages/health/components/OverviewCards.tsx` (~60 行)
- 新增: `apps/web/src/pages/health/components/TimeRangeSelector.tsx` (~40 行)
- 修改: `apps/web/src/pages/health/StatisticsDashboard.tsx` (缩减至 ~50 行)
**详细步骤**:
1. `useStatsData.ts`:五个统计 API 并行加载、loading/error 状态、时间范围变更触发刷新
2. `PatientTrendChart.tsx`:患者趋势折线图(@ant-design/charts Line
3. `AppointmentStats.tsx`:预约统计饼图/柱状图
4. `OverviewCards.tsx`概览数字卡片组Statistic + Card
5. `TimeRangeSelector.tsx`:日期范围选择 + 快捷选项近7天/近30天/近90天
6. 容器组件组装,布局使用 Row + Col
**验收标准**:
- 统计仪表板页面渲染正常,图表数据正确
- 时间范围切换触发数据刷新
- `pnpm build` 通过
---
## Phase 3: Bundle 优化Day 6-7
### Task 8: vite.config.ts manualChunks 拆分重型依赖
**目标**: 将 @ant-design/charts、@xyflow/react@wangeditor/editor 拆为独立 chunk降低主 chunk 体积。
**涉及文件**:
- 修改: `apps/web/vite.config.ts`
**详细步骤**:
1.`manualChunks` 配置中新增三条规则:
```typescript
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';
```
2. 对应页面添加路由级 `React.lazy()`
- `StatisticsDashboard``lazy(() => import('./health/StatisticsDashboard'))`
- `PluginGraphPage``lazy(() => import('./plugins/PluginGraphPage'))`
- `ArticleEditor``lazy(() => import('./health/ArticleEditor'))`
3.`chunkSizeWarningLimit` 从 600 降至 500
4. 运行 `pnpm build` 对比拆分前后各 chunk 大小
**验收标准**:
- 主 chunk 体积 < 400KBgzip 前约 600KB 以内)
- `vendor-charts``vendor-flow``vendor-editor` 独立生成
- `pnpm build` 无警告
- 统计仪表板、插件关系图、文章编辑器页面功能正常(懒加载无闪烁)
---
### Task 9: columns 配置 useMemo 化
**目标**: 消除 PluginCRUDPage 和健康模块列表页的 columns 重复创建,减少不必要的 re-render。
**涉及文件**:
- 修改: `apps/web/src/pages/plugins/components/CRUDTable.tsx`Phase 2 Task 4 产物)
- 修改: `apps/web/src/pages/health/PatientList.tsx`
- 修改: `apps/web/src/pages/health/AppointmentList.tsx`
- 修改: `apps/web/src/pages/health/FollowUpTaskList.tsx`
**详细步骤**:
1. 在每个列表页中,将 `columns` 数组定义包裹在 `useMemo`
2. 依赖项包含 columns 中引用的回调函数(如 onDelete、onEdit
3. 确保回调函数通过 `useCallback` 缓存,避免 useMemo 失效
4. 使用 React DevTools Profiler 验证翻页/筛选时减少不必要渲染
**验收标准**:
- 列表翻页时 Table 组件不因 columns 引用变化触发全量渲染
- 所有列表页功能正常(排序、筛选、操作按钮)
- `pnpm build` 通过
---
### Task 10: API 层新代码统一为对象风格
**目标**: 确认新增 API 文件采用对象风格(`xxxApi.list()` 而非 `listXxx()`),修改已有文件时顺手迁移。
**涉及文件**:
- 修改: `apps/web/src/api/health/` 下近期新增的 API 文件(如 `alerts.ts``deviceReadings.ts`
**详细步骤**:
1. 审计 `apps/web/src/api/` 下所有文件,标记函数风格的文件清单
2. 近期新增的文件alerts、deviceReadings 等)统一改为对象风格:
```typescript
export const alertApi = {
list: (params) => client.get('/alerts', { params }),
acknowledge: (id) => client.post(`/alerts/${id}/acknowledge`),
};
```
3. 更新引用处的 import页面组件中的调用方式
4. 旧文件不强制迁移,仅记录待迁移清单
**验收标准**:
- `alerts.ts``deviceReadings.ts` 为对象风格导出
- 对应页面功能正常
- `pnpm build` 通过
---
## 执行原则
1. **每 Task 完成后立即提交** — 不积压,保持可追溯
2. **先基础设施后拆分** — Phase 1 的 hook 增强完成后再做 Phase 2 组件拆分
3. **每步验证** — 每个 Task 完成后 `pnpm build` 验证,拆分任务额外验证页面功能
4. **渐进迁移** — 重复模式统一采用渐进策略,不一次性全量迁移