--- title: Web 前端 updated: 2026-04-25 status: stable tags: [frontend, react, antd, vite, spa] --- # Web 前端 > 从 [[index]] 导航。关联: [[erp-server]] [[infrastructure]] [[erp-health]] ## 1. 设计决策 - **组件库优先** — Ant Design 6,不自造轮子 - **状态集中** — Zustand 管理全局状态(4 个 store) - **API 层分离** — HTTP 调用封装到 `src/api/`(28 个文件),组件不直接 fetch - **代理开发** — Vite 代理 `/api` 到后端 3000 端口 - **HashRouter** — 不需要服务端 fallback 配置,部署更稳健 - **懒加载** — 除 Login 外所有页面使用 `lazy()` 按需加载 ### 版本(以实际 package.json 为准) React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8.0.4 / TypeScript 6.0.2 ## 2. UI 规范 ### SaaS 后台布局 经典 SaaS 后台管理布局(响应式,支持移动端): ```text ┌─────────────────────────────────────────────┐ │ LOGO 搜索... 🔔 5 👤 Admin ▾ │ ← 顶部导航栏 ├─────────┬───────────────────────────────────┤ │ 📊 首页 │ │ │ 👥 用户 │ 主内容区域 │ │ 🔐 权限 │ (多标签页切换) │ │ 📋 流程 │ │ │ 💬 消息 │ │ │ ⚙️ 设置 │ │ │─────────│ │ │ 📦 进销存│ │ │ 🏭 生产 │ │ │ 💰 财务 │ │ │─────────│ │ │ ▸ 更多 │ │ └─────────┴───────────────────────────────────┘ ``` ### UI 规则 - 使用 Ant Design 组件库,不自造轮子 - 中文优先,所有文案通过 i18n key 引用 - 支持暗色/亮色主题切换 - 侧边栏按模块分组:基础模块 / 行业模块 - 表单验证使用 Ant Design Form 的 validateRules ## 3. 关键文件 + 数据流 ### 核心文件 | 文件 | 职责 | |------|------| | `apps/web/src/main.tsx` | React 入口 | | `apps/web/src/App.tsx` | 路由定义(公开 + 受保护) | | `apps/web/src/layouts/MainLayout.tsx` | SaaS 后台管理布局 | | `apps/web/src/stores/` | 4 个 Zustand store | | `apps/web/src/api/` | 28 个 API 服务文件(含 7 个健康模块 API) | | `apps/web/vite.config.ts` | Vite 配置 + API 代理 | > 微信小程序(患者端)是独立前端项目,详见 [[miniprogram]] ### 路由结构 **公开**: `/login` **受保护(MainLayout 包裹)**: | 路径 | 页面 | |------|------| | `/` | 首页 | | `/users`, `/roles`, `/organizations` | 用户/角色/组织管理 | | `/workflow` | 工作流 | | `/messages` | 消息中心 | | `/settings` | 系统设置 | | `/plugins/admin`, `/plugins/market` | 插件管理/市场 | | `/plugins/:pluginId/:entityName` | 插件 CRUD(动态生成) | | `/plugins/:pluginId/tabs|tree|graph|dashboard|kanban/:name` | 插件多视图页面 | **健康管理路由(10 条)**: | 路径 | 页面 | |------|------| | `/health/patients` | 患者列表 | | `/health/patients/:id` | 患者详情(5 个标签页:基本信息/体征/化验/健康档案/随访) | | `/health/patient-tags` | 患者标签管理 | | `/health/doctors` | 医护管理 | | `/health/schedules` | 排班管理(含日历视图) | | `/health/appointments` | 预约管理(含状态流转) | | `/health/follow-up-tasks` | 随访任务列表 | | `/health/follow-up-records` | 随访记录 | | `/health/consultations` | 咨询会话列表 | | `/health/consultations/:id` | 咨询详情(含消息 + 导出) | ### 健康模块共享组件(12 个) | 组件 | 用途 | |------|------| | `StatusTag` | 预约/随访/咨询状态标签(含单元测试) | | `PatientSelect` | 患者搜索选择器(远程搜索) | | `DoctorSelect` | 医护搜索选择器(远程搜索) | | `CalendarView` | 排班日历视图 | | `ExportButton` | 咨询记录导出按钮 | | `VitalSignsChart` | 多指标趋势图:概览卡片条(5 指标 sparkline) + 点击展开详情折线图 | | `VitalSignsTab` | 患者详情-体征标签页 | | `LabReportsTab` | 患者详情-化验报告标签页 | | `HealthRecordsTab` | 患者详情-健康档案标签页 | | `FollowUpTab` | 患者详情-随访标签页 | | `ImagePreview` | 图片预览组件 | ### 集成契约 | 方向 | 模块 | 接口 | 触发时机 | |------|------|------|---------| | 调用 → | [[erp-server]] | `/api/v1/*` REST | 所有数据操作 | | 调用 → | [[erp-server]] | `ws://localhost:3000/ws/*` | WebSocket | | 消费 ← | 插件系统 | `plugin.toml` schema | 动态生成插件页面 | | 调用 → | [[erp-health]] | `/api/v1/health/*` | 健康模块所有数据操作 | ## 4. 代码逻辑 ### 状态管理(4 个 Zustand Store) | Store | 状态 | |-------|------| | `app.ts` | theme(light/dark), sidebarCollapsed | | `auth.ts` | user, isAuthenticated, localStorage 持久化 | | `message.ts` | unreadCount, recentMessages, 请求去重 | | `plugin.ts` | plugins 列表, 动态菜单, schema 缓存, 请求去重 | ### 健康模块 API 文件(7 个) | 文件 | 覆盖端点 | |------|---------| | `patients.ts` | 患者 CRUD + 标签 + 健康摘要 + 家庭成员 | | `doctors.ts` | 医护档案 CRUD | | `appointments.ts` | 预约 CRUD + 状态流转 | | `healthData.ts` | 体征/化验/健康档案/趋势 | | `followUp.ts` | 随访任务 + 记录 | | `consultations.ts` | 咨询会话 + 消息 + 导出 | | `articles.ts` | 健康文章 | ### 前端单元测试(3 个) | 文件 | 测试内容 | |------|---------| | `constants/health.test.ts` | 健康常量定义验证 | | `hooks/useThemeMode.test.ts` | 暗色模式 hook | | `pages/health/components/StatusTag.test.tsx` | 状态标签渲染 | ### 插件页面系统 插件通过 `plugin.toml` schema 声明页面,前端根据 schema 动态生成: - `PluginCRUDPage` — 标准列表+表单 - `PluginTabsPage` — 标签页切换 - `PluginTreePage` — 树形展示 - `PluginGraphPage` — 关系图谱 - `PluginKanbanPage` — 看板视图 - `PluginDashboardPage` — 仪表盘 ⚡ **不变量**: 插件菜单由 `plugin.ts` store 从 API 动态获取,不硬编码 ⚡ **不变量**: API client 在请求前 30s 检查 token 过期,提前刷新避免 401 ⚡ **不变量**: DatePicker 返回 dayjs 对象,提交前必须 `.format('YYYY-MM-DD')` 转字符串 ### 代理配置 ``` http://localhost:5174/api/* → http://localhost:3000/* (API) ws://localhost:5174/ws/* → ws://localhost:3000/* (WebSocket) ``` ## 5. 活跃问题 + 陷阱 ⚠️ Ant Design 6 废弃 `destroyOnClose`,应使用 `destroyOnHidden` ⚠️ Ant Design 6 废弃 API 警告(`valueStyle`/`Spin tip`/`trailColor`)已在历史版本中修复 ⚠️ `antd.setScaleParam` 强制回流 64ms — antd 内部问题,无法直接修复 ### 历史教训 - DatePicker 提交 dayjs 对象而非字符串 → 后端 422 `birth_date trailing input` — 必须调用 `.format('YYYY-MM-DD')` - 预约表单医护字段标为可选但后端必填 → 400 `doctor_id is required` — 医护为必填(CAS 排班需要) - 趋势图后端 DTO `Vec<(NaiveDate, f64)>` 序列化为 JSON 数组 `[[date, value]]` 而非对象 `[{date, value}]` → 改用 `Vec` 修复;前端 `extractData` 需同时处理 `ApiResponse` 包装和裸数组两种响应格式 - `IndicatorTimeseriesResp` 返回 `{ indicator, data: [...] }` 包装对象,不是裸数组 — `Array.isArray(res)` 为 false,必须从 `res.data` 取值 ## 6. 变更记录 | 日期 | 变更 | |------|------| | 2026-04-26 | 从 CLAUDE.md 迁移:UI 布局规范(§8) | | 2026-04-26 | VitalSignsChart 重设计:概览卡片条 + 点击展开详情图,5 指标独立 Y 轴 | | 2026-04-25 | 全面更新:10 条健康路由、12 个共享组件、7 个健康 API 文件、3 个单元测试 | | 2026-04-24 | 添加小程序交叉引用 | | 2026-04-23 | 重构为 5 节结构,更新为当前完整前端状态 |