Files
hms/wiki/frontend.md
2026-05-21 12:25:41 +08:00

295 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Web 前端
updated: 2026-05-10
status: stable
tags: [frontend, react, antd, vite, spa]
---
# Web 前端
> 从 [[index]] 导航。关联: [[erp-server]] [[infrastructure]] [[erp-health]]
## 1. 设计决策
- **组件库优先** — Ant Design 6不自造轮子
- **状态集中** — Zustand 管理全局状态6 个 store
- **API 层分离** — HTTP 调用封装到 `src/api/`(含 health/ 和 ai/ 子目录),组件不直接 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 引用
- 支持 4 套主题切换:信任蓝 / 温润东方 / 深邃夜色 / 翡翠清雅
- 侧边栏使用 Ant Design `<Menu mode="inline">`,后端菜单树驱动,支持原生折叠动画 + 折叠 popover + 键盘导航 + ARIA
- 表单验证使用 Ant Design Form 的 validateRules
### 2.1 多主题系统4 套内置主题)
> 自 2026-04-28 起采用多主题架构。用户可在顶栏主题切换器中选择偏好主题,选择持久化到 localStorage。
#### 4 套主题视觉人格
| 主题 | 主色 | 背景 | 圆角 | 性格 |
|------|------|------|------|------|
| **信任蓝** (blue) | `#2563EB` | `#F8FAFC` 冷灰 | 10/12/6px | 专业·企业 |
| **温润东方** (warm) | `#C4623A` | `#F5F0EB` 暖米 | 12/16/8px | 温润·人文 |
| **深邃夜色** (dark) | `#60A5FA` | `#0F172A` 深蓝黑 | 10/12/6px | 护眼·专注 |
| **翡翠清雅** (emerald) | `#5B7A5E` | `#F4F7F4` 浅绿灰 | 10/14/8px | 清新·健康 |
#### 技术架构
- **CSS 变量层** — `:root` 默认为 blue`[data-theme='xxx']` 覆盖全部视觉 token`apps/web/src/index.css`
- **Ant Design 动态主题** — `ConfigProvider``theme` prop 按 ThemeName 选择不同配置(`apps/web/src/App.tsx`
- **Zustand 持久化** — `useAppStore().theme` + `localStorage('hms-theme')``apps/web/src/stores/app.ts`
- **暗色检测** — `useThemeMode()` hook 从 store 读取,不再比对色值(`apps/web/src/hooks/useThemeMode.ts`
#### 温润东方风详细 Token与小程序端共享
> 小程序端源文件:`apps/miniprogram/src/styles/variables.scss` + `mixins.scss`
| 角色 | CSS 变量 | 色值 |
|------|----------|------|
| 主色 | `--erp-primary` | `#C4623A` |
| 背景 | `--erp-bg-page` | `#F5F0EB` |
| 容器 | `--erp-bg-container` | `#FFFFFF` |
| 主文字 | `--erp-text-primary` | `#2D2A26` |
| 次文字 | `--erp-text-secondary` | `#7A756E` |
| 边框 | `--erp-border` | `#E8E2DC` |
| 成功 | `--erp-success` | `#5B7A5E` |
| 警告 | `--erp-warning` | `#C4873A` |
| 错误 | `--erp-error` | `#B54A4A` |
#### 禁止事项
- 禁止紫色渐变、禁止 emoji 作图标
- 禁止左侧彩色边框卡片标示状态(改用 tag 标签)
- 禁止无意义的渐变背景
- 禁止装饰性 icon 遍地配
## 3. 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `apps/web/src/main.tsx` | React 入口 |
| `apps/web/src/App.tsx` | 路由定义 + Ant Design 动态主题4 套) |
| `apps/web/src/layouts/MainLayout.tsx` | SaaS 后台管理布局 + ThemeSwitcher 集成 |
| `apps/web/src/stores/` | 4 个 Zustand store |
| `apps/web/src/components/ThemeSwitcher.tsx` | 主题选择下拉面板 |
| `apps/web/src/hooks/useThemeMode.ts` | 暗色模式检测(从 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` | 插件多视图页面 |
**健康管理路由25 条)**:
| 路径 | 页面 |
|------|------|
| `/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` | 咨询详情(含消息 + 导出) |
| `/health/articles` | 文章管理列表 |
| `/health/article-editor` | 文章编辑器(富文本) |
| `/health/article-categories` | 文章分类管理 |
| `/health/article-tags` | 文章标签管理 |
| `/health/points-rules` | 积分规则管理 |
| `/health/points-products` | 积分商品管理 |
| `/health/points-orders` | 积分订单列表 |
| `/health/statistics` | 统计概览(透析/化验/预约/体征上报率) |
| `/health/offline-events` | 线下活动管理 |
| `/health/ai-analysis` | AI 分析历史 |
| `/health/ai-prompts` | AI Prompt 管理 |
| `/health/ai-usage` | AI 用量统计 |
| `/health/media-library` | 媒体库管理(上传/文件夹/网格浏览) |
| `/health/banners` | 轮播图管理(表格+Drawer 表单+排序) |
### 健康模块共享组件13 个)
| 组件 | 用途 |
|------|------|
| `StatusTag` | 预约/随访/咨询状态标签(含单元测试) |
| `PatientSelect` | 患者搜索选择器(远程搜索) |
| `DoctorSelect` | 医护搜索选择器(远程搜索) |
| `CalendarView` | 排班日历视图 |
| `ExportButton` | 咨询记录导出按钮 |
| `VitalSignsChart` | 多指标趋势图:概览卡片条(5 指标 sparkline) + 点击展开详情折线图 |
| `VitalSignsTab` | 患者详情-体征标签页 |
| `LabReportsTab` | 患者详情-化验报告标签页 |
| `HealthRecordsTab` | 患者详情-健康档案标签页 |
| `FollowUpTab` | 患者详情-随访标签页 |
| `ImagePreview` | 图片预览组件 |
| `MediaPicker` | 媒体库选图组件(复用于轮播图选图、文章封面选图) |
| `resolveMediaUrl()` | 媒体 URL 工具函数(`src/utils/media.ts`):自动处理路径前缀 + JWT token 拼接 |
### 集成契约
| 方向 | 模块 | 接口 | 触发时机 |
|------|------|------|---------|
| 调用 → | [[erp-server]] | `/api/v1/*` REST | 所有数据操作 |
| 调用 → | [[erp-server]] | `ws://localhost:3000/ws/*` | WebSocket |
| 消费 ← | 插件系统 | `plugin.toml` schema | 动态生成插件页面 |
| 调用 → | [[erp-health]] | `/api/v1/health/*` | 健康模块所有数据操作 |
## 4. 代码逻辑
### 状态管理5 个 Zustand Store
| Store | 状态 |
|-------|------|
| `app.ts` | theme(blue/warm/dark/emerald), sidebarCollapsed, localStorage 持久化 |
| `auth.ts` | user, isAuthenticated, localStorage 持久化 |
| `health.ts` | 患者/医生姓名缓存与批量解析 |
| `message.ts` | unreadCount, recentMessages, SSE 实时推送连接, 请求去重 |
| `plugin.ts` | plugins 列表, 动态菜单, schema 缓存, 请求去重 |
### 健康模块 API 文件12 个)
| 文件 | 覆盖端点 |
|------|---------|
| `patients.ts` | 患者 CRUD + 标签 + 健康摘要 + 家庭成员 |
| `doctors.ts` | 医护档案 CRUD |
| `appointments.ts` | 预约 CRUD + 状态流转 |
| `healthData.ts` | 体征/化验/健康档案/趋势 |
| `followUp.ts` | 随访任务 + 记录 |
| `consultations.ts` | 咨询会话 + 消息 + 导出 |
| `articles.ts` | 健康文章 |
| `media.ts` | 媒体库 CRUD + 文件夹管理 + 批量操作 |
| `banners.ts` | 轮播图 CRUD + 排序 |
| `points.ts` | 积分系统 |
| `deviceReadings.ts` | 设备数据采集 |
| `alerts.ts` | 健康预警 |
### 前端单元测试36 个测试文件)
| 类别 | 覆盖范围 |
|------|---------|
| Store 测试 | auth, health, plugin, workbench, app, message |
| Hook 测试 | useThemeMode, useDebouncedValue, useCountUp 等 |
| API 契约测试 | 25+ API 模块 URL/Method/参数验证 |
| 组件测试 | StatusTag, 健康常量, 表达式求值 |
> ⚠️ **审计发现**Web 前端 225 个文件有 36 个测试文件 + 5 E2E spec测试覆盖率仍需持续提升。小程序完全无测试。详见 `docs/audits/07-test-coverage.md`。
### 2026-04-30 审计发现
| 发现 | 严重性 | 说明 |
|------|--------|------|
| 告警管理按钮永远不显示 | CRITICAL | AlertList.tsx 使用 `health.alert.manage`(单数),后端声明 `health.alerts.manage`复数AuthButton 隐藏 |
| AI 分析 SSE 无 UI 入口 | MEDIUM | 4 个 SSE 端点vital-signs/lab-report/health-trend/health-summary前端未调用 |
| SSE 重连无指数退避 | MEDIUM | 依赖浏览器原生 EventSource固定 3 秒间隔 |
| 前端测试极低 | MEDIUM | 163 文件仅 10 个测试 |
| 前端路由级权限控制缺失 | LOW | 健康模块路由无前端权限守卫,依赖后端 403 |
| AuthButton 覆盖率 26% | LOW | 13/50 声明权限码有 AuthButton其余依赖 API 403 |
### 插件页面系统
插件通过 `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)
http://localhost:5174/uploads/* → http://localhost:3000/* (媒体文件,需 JWT)
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<DataPoint>` 修复;前端 `extractData` 需同时处理 `ApiResponse` 包装和裸数组两种响应格式
- `IndicatorTimeseriesResp` 返回 `{ indicator, data: [...] }` 包装对象,不是裸数组 — `Array.isArray(res)` 为 false必须从 `res.data` 取值
## 6. 变更记录
| 日期 | 变更 |
|------|------|
| 2026-05-10 | **媒体库 + 轮播图管理**:新增 MediaLibrary 页面(上传/文件夹/网格浏览、BannerManage 页面(表格+Drawer+排序、MediaPicker 组件(复用于轮播图和文章封面选图);新增 `resolveMediaUrl()` 工具函数统一处理媒体 URLVite 代理新增 `/uploads`;健康路由 22→25 条,共享组件 11→13 个API 文件 10→12 个 |
| 2026-05-01 | 审计发现更新CRITICAL 权限码拼写错误alert→alerts、前端测试极低、AI SSE 无入口 |
| 2026-04-28 | UI/UX 重构 Phase 5小程序端 8 项优化):首页健康资讯+空状态引导、Hub sparkline bar+打卡合并、日常监测 3 分组折叠+异常高亮、预约时段灰显、咨询消息日期分组+图片预览、医护异常横幅+搜索、趋势图骨架屏 |
| 2026-04-28 | UI/UX 重构 Phase 44 个表单 Modal→DrawerForm患者 4 分组/预约 3 分组+排班校验/随访 2 分组/积分商品 2 分组) |
| 2026-04-28 | UI/UX 重构 Phase 310 个列表页统一迁移至 PageContainer + usePaginatedData + EntityName + 共享格式化工具,移除手动 isDark 处理 |
| 2026-04-28 | UI/UX 重构 Phase 2仪表盘角色自适应useDashboardRole + DoctorDashboard/NurseDashboard/AdminDashboard/OperatorDashboard删除快捷入口/积分排行/最近活动旧区块 |
| 2026-04-28 | UI/UX 重构 Phase 1提取 6 个共享组件PageContainer/EntityName/FilterBar/DrawerForm/FilterBar + dayjs/format 工具12 个文件 dayjs 导入统一 |
| 2026-04-28 | 多主题系统4 套主题blue/warm/dark/emerald+ CSS 变量 + Ant Design 动态主题 + ThemeSwitcher + useThemeMode 修复 |
| 2026-04-26 | 全面更新22 条健康路由(+12 内容/积分/统计/活动/AI、11 个共享组件、77 个 TSX 文件 |
| 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 节结构,更新为当前完整前端状态 |