Initial commit: HealthManager full-stack health management platform

Backend: .NET 10 + PostgreSQL + EF Core + JWT + SignalR
Frontend patient: React 19 + TypeScript + Vite (mobile H5)
Frontend doctor: React 19 + TypeScript + Vite (desktop web)
This commit is contained in:
MingNian
2026-05-20 16:18:56 +08:00
commit 435af55c4a
215 changed files with 18595 additions and 0 deletions

View File

@@ -0,0 +1,591 @@
# 健康管家(健康管家)— 技术审核报告
> **项目名称**health-manager-demo
> **中文名称**:健康管家 · 心脏健康管理
> **定位**PCI 术后患者心脏健康管理移动端 Web 应用
> **审核日期**2026-05-19
> **审核范围**全部源代码87 个文件)
---
## 目录
1. [项目概述](#1-项目概述)
2. [技术栈](#2-技术栈)
3. [目录结构与文件说明](#3-目录结构与文件说明)
4. [架构分析](#4-架构分析)
5. [Bug 清单](#5-bug-清单)
6. [代码质量问题](#6-代码质量问题)
7. [安全性问题](#7-安全性问题)
8. [性能问题](#8-性能问题)
9. [无障碍Accessibility问题](#9-无障碍accessibility问题)
10. [改进建议](#10-改进建议)
---
## 1. 项目概述
### 1.1 这是什么项目?
这是一个**心脏健康管理 App 的前端原型**,目标用户是做过 **PCI冠状动脉支架植入手术**的患者。它用网页技术React模拟了一个手机 App包含以下功能模块
| 模块 | 功能 |
|------|------|
| 登录/注册 | 手机号 + 短信验证码登录 |
| 首页看板 | 健康概览(血压、心率)、快捷入口、健康小贴士 |
| 健康数据 | 手动录入血压/心率/血糖/血氧/体重/步数,查看趋势图,健康日历 |
| 设备绑定 | 模拟蓝牙设备绑定(血压计、手表、体脂秤等) |
| 用药管理 | 药品列表、服药记录、用药依从性统计 |
| 在线问诊 | 医生列表、科室筛选、图文聊天 |
| 报告解读 | 上传/查看医疗报告、AI 解读 |
| 复查管理 | 术后复查预约记录 |
| 运动饮食 | 运动/饮食推荐、运动/饮食打卡 |
| 通知中心 | 系统通知列表 |
| 个人中心 | 个人资料、设置、隐私政策、关于 |
### 1.2 重要前提
**这是一个纯前端 Demo没有后端服务。** 所有数据用户、健康记录、医生列表、药品等都是写在前端代码里的假数据Mock Data。点击交互看似"正常",但数据不会真正保存到任何服务器,刷新页面后所有修改都会丢失。
---
## 2. 技术栈
| 类别 | 技术 | 版本 | 用途 |
|------|------|------|------|
| 语言 | TypeScript | ~6.0.2 | 类型安全 |
| 框架 | React | ^19.2.6 | UI 构建 |
| 构建工具 | Vite | ^8.0.12 | 开发/打包 |
| 路由 | react-router-dom | ^7.15.1 | 页面跳转 |
| 状态管理 | zustand | ^5.0.13 | 全局状态(登录状态、通知) |
| 图表 | echarts + echarts-for-react | ^6.0.0 | 折线图、柱状图、饼图 |
| 动画 | framer-motion | ^12.39.0 | 页面切换动画 |
| 日期 | dayjs | ^1.11.20 | 日期格式化 |
| 样式 | CSS Modules + CSS 变量 | Vite 内置 | 组件级样式隔离 |
| 代码检查 | ESLint | ^10.3.0 | 代码规范 |
### 2.1 技术要点说明
- **React 19**:最新版 React支持 Suspense、Server Components 等新特性(本项目未使用)
- **zustand**:比 Redux 更轻量的状态管理库,本项目用它管理登录状态和通知,登录状态会持久化到 localStorage
- **echarts**Apache 出品的图表库,本项目用它画趋势图、饼图、柱状图
- **CSS Modules**:每个组件有自己的 `.module.css` 文件,样式不会互相污染
- **framer-motion**:给页面切换加过渡动画
- **dayjs**:比 moment.js 轻量 97% 的日期处理库
---
## 3. 目录结构与文件说明
```
haruite-medical-demo/
├── index.html # 应用入口 HTML设置 viewport、标题、PWA meta
├── package.json # 项目配置名称、依赖、npm 脚本)
├── vite.config.ts # Vite 构建配置(路径别名、端口、插件)
├── tsconfig.json # TypeScript 根配置(引用子配置)
├── tsconfig.app.json # 应用代码 TS 配置JSX、路径别名
├── tsconfig.node.json # Node 端 TS 配置vite.config.ts 用)
├── eslint.config.js # ESLint 代码检查规则(扁平配置)
├── README.md # 项目说明
├── public/
│ ├── favicon.svg # 浏览器标签页图标
│ └── icons.svg # 社交媒体图标Bluesky、Discord、GitHub、X
└── src/
├── main.tsx # 应用启动入口,渲染 <App /> 到 <div id="root">
├── App.tsx # 根组件,初始化路由
├── assets/styles/ # 全局样式
│ ├── variables.css # CSS 变量(颜色、间距、圆角、阴影、字体、层级)
│ ├── reset.css # CSS 重置(统一各浏览器默认样式)
│ └── global.css # 全局样式(页面布局、滚动容器、过渡动画)
├── types/ # TypeScript 类型定义
│ ├── index.ts # 统一导出所有类型
│ ├── user.ts # User、LoginRequest、RegisterRequest
│ ├── health.ts # HealthRecord、HealthStats、MeasurementType
│ ├── device.ts # Device、DeviceType、DeviceStatus
│ ├── medication.ts # Medication、MedicationRecord、MedicationAdherence
│ ├── consultation.ts # Doctor、Consultation、ConsultationMessage
│ ├── report.ts # Report、ReportFinding、ReportResult
│ ├── followup.ts # FollowUp、FollowUpStatus
│ ├── exercise-diet.ts # FoodItem、ExerciseRecord、DietRecord
│ ├── notification.ts # Notification、NotificationGroup
│ └── calendar.ts # CalendarMarker、CalendarDay
├── utils/ # 工具函数
│ ├── constants.ts # 常量(测量类型、导航项、药品列表、健康提示)
│ ├── format.ts # 格式化(日期、数字、血压风险等级)
│ ├── storage.ts # localStorage 封装(带错误处理)
│ └── validator.ts # 表单验证(手机号、验证码、必填、数字范围)
├── hooks/ # 自定义 React Hooks
│ ├── useAuth.ts # 登录状态 HookuseAuthStore 的封装)
│ └── useCountdown.ts # 倒计时 Hook短信验证码重发倒计时
├── stores/ # 全局状态管理zustand
│ ├── auth.store.ts # 登录状态用户信息、token、登录/注册/登出)
│ └── notification.store.ts # 通知状态(通知列表、未读数)
├── services/ # API 服务层
│ ├── api.ts # API 请求封装(目前直接返回 Mock 数据)
│ ├── auth.service.ts # 登录/注册/短信/个人资料
│ ├── health.service.ts # 健康记录 CRUD + 统计分析
│ ├── device.service.ts # 设备扫描/绑定/解绑
│ ├── medication.service.ts # 药品 CRUD + 用药记录 + 依从性
│ ├── consultation.service.ts # 问诊(医生列表、咨询、消息)
│ ├── report.service.ts # 报告上传/查看/解读
│ ├── followup.service.ts # 复查预约 CRUD
│ ├── exercise-diet.service.ts # 运动/饮食记录 + 推荐
│ └── notification.service.ts # 通知列表/已读/未读数
├── mock/ # Mock 假数据
│ ├── index.ts # Mock 基础设施延迟模拟、响应格式、ID 生成)
│ ├── users.ts # 假用户数据1 个用户)
│ ├── health-records.ts # 假健康记录91 天 × 6 种测量类型)
│ ├── devices.ts # 假设备数据4 已绑定 + 2 可扫描)
│ ├── medications.ts # 假药品 + 30 天服药记录
│ ├── consultations.ts # 假医生8 人)+ 问诊记录
│ ├── reports.ts # 假医疗报告5 份)
│ ├── followups.ts # 假复查记录3 条)
│ ├── exercise-diet.ts # 假运动/饮食记录14 天)
│ └── notifications.ts # 假通知8 条)
├── router/ # 路由配置
│ ├── index.tsx # 全部路由定义
│ └── AuthGuard.tsx # 登录守卫(未登录自动跳转登录页)
├── components/ # 可复用组件
│ ├── common/ # 通用 UI 组件
│ │ ├── Badge.tsx/css # 角标/红点
│ │ ├── Button.tsx/css # 按钮(主要/次要/线框/文字 + 加载态)
│ │ ├── Card.tsx/css # 卡片容器
│ │ ├── Empty.tsx/css # 空状态占位
│ │ ├── Input.tsx/css # 输入框(带标签和错误提示)
│ │ └── Toast.tsx/css # 轻提示(全局 toast 通知)
│ ├── layout/ # 布局组件
│ │ ├── AppLayout.tsx/css # 主布局(顶部 + 内容区 + 底部 TabBar
│ │ ├── StackLayout.tsx # 堆栈页面布局(无底栏,仅 <Outlet />
│ │ ├── PageHeader.tsx/css # 页面顶栏(返回按钮 + 标题 + 右侧操作)
│ │ └── TabBar.tsx/css # 底部导航栏(首页/健康/服务/我的)
│ └── charts/ # 图表组件
│ ├── BarChart.tsx # 柱状图echarts
│ ├── LineChart.tsx # 折线图(支持双线 + 警戒线)
│ └── PieChart.tsx # 饼图/环形图echarts
└── pages/ # 页面组件
├── auth/ # 登录/注册
├── home/ # 首页 + 设备绑定
├── health/ # 健康数据Hub、记录列表、录入、趋势图、日历
├── medication/ # 用药管理(列表、编辑、详情)
├── services/ # 服务Hub、医生列表、聊天、报告、复查
├── exercise-diet/ # 运动饮食
├── notifications/ # 通知列表
└── profile/ # 个人中心、设置、静态页面
```
---
## 4. 架构分析
### 4.1 整体架构
```
┌─────────────────────────────────────────────┐
│ Pages页面
│ 每个页面是一个 React 组件,负责 UI 渲染 │
└──────────────┬──────────────────────────────┘
│ 调用
┌──────────────▼──────────────────────────────┐
│ Services服务层
│ 封装业务逻辑,每个域一个文件 │
└──────────────┬──────────────────────────────┘
│ 调用
┌──────────────▼──────────────────────────────┐
│ Mock模拟数据层
│ 返回假数据,模拟后端 API 响应 │
└─────────────────────────────────────────────┘
```
### 4.2 状态管理架构
```
┌──────────────────────┐ ┌──────────────────────┐
│ auth.store.ts │ │ notification.store.ts │
│ (zustand + persist) │ │ (zustand) │
│ │ │ │
│ - user │ │ - notifications[] │
│ - token │ │ - unreadCount │
│ - isAuthenticated │ │ - loading │
│ - login() │ │ - fetchNotifications│
│ - register() │ │ - markRead() │
│ - logout() │ │ - markAllRead() │
│ - updateProfile() │ │ │
└──────────────────────┘ └──────────────────────┘
```
### 4.3 路由架构
```
/ (root)
├── /login → LoginPage登录页
├── /register → RegisterPage注册页
├── [AuthGuard] ← 登录验证
│ ├── [AppLayout] ← 带底部 TabBar 的布局
│ │ ├── /home → HomePage首页
│ │ ├── /health → HealthHubPage健康 Hub
│ │ ├── /services → ServicesHubPage服务 Hub
│ │ └── /profile → ProfilePage我的
│ │
│ └── [StackLayout] ← 无底部栏的布局
│ ├── /home/device-binding → 设备绑定
│ ├── /health/records → 健康记录列表
│ ├── /health/records/add → 手动录入
│ ├── /health/trends/:type → 趋势图
│ ├── /health/calendar → 健康日历
│ ├── /health/medications → 用药列表
│ ├── /services/consultation → 医生列表
│ ├── /services/consultation/chat/:doctorId → 聊天
│ ├── /services/reports → 报告列表
│ ├── /services/follow-ups → 复查列表
│ ├── /profile/settings → 设置
│ └── /notifications → 通知列表
```
---
## 5. Bug 清单
### 5.1 严重 Bug会导致崩溃或白屏
#### 🔴 BUG-001ProfilePage 在未登录时崩溃
**文件**`src/pages/profile/ProfilePage.tsx:45`
**问题**:当 `user``null` 时,`user?.medicalHistory.join(',')` 会抛出 `TypeError`
**原因**:可选链 `?.` 只在 `user``null/undefined` 时短路,但 `medicalHistory` 本身也可能是 `undefined``.join()``undefined` 上调用直接报错。
```tsx
// ❌ 错误
{user?.medicalHistory.join(',')}
// ✅ 正确
{user?.medicalHistory?.join(',') || '无'}
```
---
#### 🔴 BUG-002ChatPage 缺少 doctorId 时崩溃
**文件**`src/pages/services/ChatPage.tsx:42`
**问题**`doctorId!` 使用了 TypeScript 的非空断言。如果用户直接访问 `/services/consultation/chat/`(没有 doctorId页面直接崩溃。
```tsx
// ❌ 错误
await consultationService.sendMessage(doctorId!, consultation.id, text);
// ✅ 正确
if (!doctorId) { toast('医生信息缺失'); return; }
```
---
#### 🔴 BUG-003FollowUpEditPage 服务调用失败时按钮永久 Loading
**文件**`src/pages/services/FollowUpEditPage.tsx:19-24`
**问题**`handleSubmit` 没有 `try/catch`。如果 `addFollowUp()` 抛出异常,`setLoading(false)` 永远不会执行,按钮永远显示"提交中..."
```tsx
// ❌ 错误
const handleSubmit = async () => {
setLoading(true);
await followupService.addFollowUp({...}); // 抛异常就卡死
setLoading(false);
};
// ✅ 正确
const handleSubmit = async () => {
setLoading(true);
try {
await followupService.addFollowUp({...});
} catch {
toast('提交失败');
} finally {
setLoading(false);
}
};
```
---
#### 🔴 BUG-004路由修复前 — 所有子页面白屏(已修复)
**文件**`src/router/index.tsx:61`
**问题(已修复)**:第二个路由组渲染 `<AuthGuard>{null}</AuthGuard>`,没有 `<Outlet />`,导致 20+ 个堆栈页面全部白屏。
**修复**:将其改为 `<AuthGuard><StackLayout /></AuthGuard>``StackLayout` 内部渲染 `<Outlet />`
---
### 5.2 中等问题(功能不正常,但不会崩溃)
#### 🟡 BUG-005健康趋势分析始终显示"稳定"
**文件**`src/services/health.service.ts:51-54`
**问题**`getLatestStats()` 中对 `weekRecords`(对象数组)做了 `.reduce((a,b) => a+b, 0)`,而不是对数值数组。这导致 `olderAvg``newerAvg` 始终是 `NaN`,趋势永远返回 `'stable'`
```tsx
// ❌ 错误 — weekRecords 是 HealthRecord 对象数组,不能相加
const olderAvg = olderValues.reduce((a, b) => a + b, 0) / olderValues.length;
// ✅ 正确 — 应该用 values数值数组
const olderAvg = olderValues.reduce((a, b) => a + b, 0) / olderValues.length;
```
---
#### 🟡 BUG-006健康日历使用写死的假数据
**文件**`src/pages/health/HealthCalendarPage.tsx:7-9`
**问题**:直接从 `mock/` 目录 import 假数据,不经过 service 层。无论用户录入了多少健康数据、吃了什么药,日历永远显示同样的假数据。
```tsx
// ❌ 绕过 service 层,直接引用 mock
import { mockHealthRecords } from '@/mock/health-records';
import { mockMedicationRecords } from '@/mock/medications';
// ✅ 应该通过 service 获取
healthService.getRecords({...})
medicationService.getMedicationRecords(...)
```
---
#### 🟡 BUG-007复查列表卡片点击跳转到错误页面
**文件**`src/pages/services/FollowUpListPage.tsx:39`
**问题**:点击复查卡片跳转到 `/health/medications`(用药列表),而不是复查详情页。明显是复制粘贴错误。
---
#### 🟡 BUG-008用药详情页获取全部数据而非按 ID 查询
**文件**`src/pages/medication/MedicationDetailPage.tsx:17-19`
**问题**:请求**所有**药品和**所有**服药记录,然后在客户端 `.find()` 筛选。如果药品数量很大,这会造成不必要的性能开销。应该按 ID 精确查询。
---
#### 🟡 BUG-009手动录入页 Loading 状态永远不会激活
**文件**`src/pages/health/ManualEntryPage.tsx:24`
**问题**`loading` 状态声明了但从未设成 `true`,用户快速点击提交按钮可以重复提交。
---
#### 🟡 BUG-010报告上传失败时仍显示"上传成功"
**文件**`src/pages/services/ReportUploadPage.tsx:21`
**问题**`handleSubmit` 没有 `try/catch`,不管 `uploadReport()` 是否成功,都会显示"上传成功"并返回上一页。
---
#### 🟡 BUG-011短信验证码从未真正发送
**文件**`src/pages/auth/LoginPage.tsx``RegisterPage.tsx`
**问题**:点击"发送验证码"直接 toast"验证码已发送",只启动了倒计时,实际上没有调用任何 API。smsCode 参数在 `auth.service.ts` 中标记为 `_smsCode`(未使用)。
---
#### 🟡 BUG-012AuthGuard 在页面刷新时会闪到登录页
**文件**`src/router/AuthGuard.tsx`
**问题**zustand 的 `persist` 中间件是异步从 localStorage 恢复数据的。在恢复完成前,`isAuthenticated``false`,导致 AuthGuard 短暂显示登录页,等数据恢复后又跳回来。用户体验很差。
---
#### 🟡 BUG-013useCountdown 内存泄漏
**文件**`src/hooks/useCountdown.ts`
**问题**
1. 组件卸载时 `setInterval` 不会被清除,持续在后台运行
2. 多次调用 `start()` 会创建多个并发的倒计时,倒计时速度翻倍
---
### 5.3 低严重度(小问题)
- **mock/devices.ts**:小米体脂秤的设备类型写成了 `smartwatch`,应该是 `scale`(但 `DeviceType` 联合类型里没有 `scale`
- **mock/notifications.ts:80**:存在乱码字符 `"已完<E5B7B2><E5AE8C><EFBFBD>状态"`,应为 `"已完成状态"`
- **utils/format.ts**`formatRelative` 对未来的日期显示"刚刚",逻辑不对
- **stores/auth.store.ts**`register()` 接收 `code` 参数但不传给 service
- **types/health.ts** vs **utils/constants.ts**`MeasurementType` 类型在两个文件中分别定义,可能不同步
---
## 6. 代码质量问题
### 6.1 系统性问题
| 问题 | 影响范围 | 说明 |
|------|----------|------|
| **所有页面缺少 `.catch()` 错误处理** | 几乎所有页面 | Promise 被静默吞掉,出错时用户看不到任何反馈 |
| **所有页面缺少 Loading 状态** | 几乎所有页面 | 数据加载时显示空状态,然后突然出现内容,体验差 |
| **所有页面缺少 Error 状态** | 几乎所有页面 | API 调用失败时没有错误提示和重试按钮 |
| **大量 inline 箭头函数** | 所有页面 | `onClick={() => ...}` 每次渲染创建新函数,影响 memo 优化 |
| **没有 Error Boundary** | 全局 | 任何一个组件崩溃都会导致整个页面白屏 |
| **URL 参数无验证** | 多个页面 | `as MeasurementType` 等方式把 URL 参数直接断言为类型,无运行时校验 |
| **缺少 `<form>` 元素** | 所有表单页面 | 用 `<div>` + `onClick` 代替 `<form>` + `onSubmit`,失去原生表单行为 |
| **确认框用 `window.confirm()`** | ProfilePage | 阻塞主线程,移动端体验差,样式不可定制 |
### 6.2 Mock 数据问题
| 问题 | 说明 |
|------|------|
| **用户 ID 硬编码 `'u001'`** | 所有 service 文件都写死 `userId: 'u001'`,无法支持多用户 |
| **Mock 数据被可变修改** | 所有"写"操作直接修改模块级数组,测试之间状态会互相污染 |
| **无法模拟错误** | `mockApiResponse` 永远返回 `code: 200`,无法测试错误处理流程 |
| **健康记录时间不真实** | 所有记录时间戳都相同08:00:00步数是 20:00:00 |
| **饮食记录只有早餐** | `i % 3 === 0` 过滤导致只生成大约 5 条早餐记录,无午餐/晚餐 |
| **ChatPage 里有硬编码的假回复** | 生产代码里写了 `getDoctorReply()`,用随机选择的模板回复 |
### 6.3 类型安全问题
- `PieChart.tsx` 中 tooltip 用 `as Record<string, unknown>[]` 强制转换,丢失类型检查
- 多处 URL 参数用 `as` 断言跳过类型校验
- `ChatPage` 用了非空断言 `doctorId!`
- `main.tsx` 用了非空断言 `document.getElementById('root')!`
---
## 7. 安全性问题
| 等级 | 问题 | 说明 |
|------|------|------|
| 🔴 高 | Token 存 localStorage | 登录 token 通过 zustand persist 存 localStorage任何同源 JS 都能读取XSS 攻击面) |
| 🟡 中 | 短信验证码无服务端校验 | `sendSmsCode` 接受 `_phone` 但从未真正验证,客户端倒计时可绕过 |
| 🟡 中 | 无 CSRF 保护 | Mock 层没有实现任何防护,后续接入真实后端需补充 |
| 🟡 中 | LineChart tooltip XSS 隐患 | tooltip HTML 用模板字符串拼接数据值,若数据含 `<` 等字符可能被注入 |
| 🟢 低 | 用户隐私数据明文存储 | `User` 类型包含手机号、生日、病史,存 localStorage 无加密 |
---
## 8. 性能问题
| 等级 | 问题 | 文件 | 说明 |
|------|------|------|------|
| 🔴 高 | 用药详情获取全部数据 | MedicationDetailPage | 请求所有药品和记录后在客户端 filter应服务端按 ID 查 |
| 🔴 高 | 健康统计每次重新计算 | health.service.ts | `getLatestStats` 遍历全部记录做统计,无缓存 |
| 🟡 中 | 多次重复 `diff` 调用 | format.ts | `formatRelative` 中 3 次调用 `dayjs.diff()`,可一次完成 |
| 🟡 中 | Inline 函数导致无效渲染 | 全项目 | 所有 `onClick={() => ...}` 在每次 render 创建新函数引用 |
| 🟡 中 | 数组浅拷贝开销 | 各 service | 每次读取数据都 `[...mockArray]` 创建新数组 |
| 🟢 低 | 咨询消息全量加载 | 类型定义 | `Consultation``messages` 是完整数组,没有分页 |
---
## 9. 无障碍Accessibility问题
### 9.1 重大问题
| 问题 | 文件 | 说明 |
|------|------|------|
| **通知设置页开关不可交互** | `staticPages.tsx:19-21` | 开关是纯装饰性 `<div>`,无 `onClick`、无 `role="switch"`、无 `aria-checked` |
| **报告上传区域不可交互** | `ReportUploadPage.tsx:39-43` | 上传区域是 `<div>`,没有 `<input type="file">`,没有 `role="button"`,没有键盘事件 |
| **Input 组件 label 不关联 input** | `Input.tsx:11` | `<label>` 没有 `htmlFor` 属性,点击标签不会聚焦输入框 |
| **Input 组件错误不关联 input** | `Input.tsx:13` | 错误信息没有 `aria-describedby`,屏幕阅读器不会读错误 |
### 9.2 普适性问题
| 问题 | 影响范围 |
|------|----------|
| Emoji 图标无 `aria-label` | 所有使用 `💓💊👨‍⚕️📋📅🏃🔔💙` 等 emoji 做图标的地方 |
| Tab 切换无 `role="tab"` / `aria-selected` | 所有 Tab 切换页面 |
| 导航按钮无 `aria-label` | 所有快捷操作按钮 |
| 健康日历无键盘导航 | `HealthCalendarPage` — 没有 `role="grid"`、没有 `tabindex` |
| 聊天消息无 `aria-live` | `ChatPage` — 新消息不会被屏幕阅读器自动播报 |
| `<button>` 缺少 `type="button"` | 多处按钮默认 `type="submit"`,若被 `<form>` 包裹可能触发意外提交 |
---
## 10. 改进建议
### 10.1 立即修复(影响核心功能)
1. **修复 ProfilePage 崩溃**`user?.medicalHistory?.join(',')`
2. **修复健康趋势永远显示"稳定"**`health.service.ts` 改用数值数组计算
3. **修复健康日历写死假数据** → 通过 service 层获取数据
4. **修复复查列表跳转错误** → 改为正确路径
5. **修复 ChatPage 非空断言** → 加 guard 判断
6. **修复 FollowUpEditPage try/catch** → 加异常处理
### 10.2 短期优化(提升体验)
7. 所有数据获取加 `.catch()` 错误处理
8. 所有异步页面加 Loading 骨架屏
9. 加 Error Boundary 防止全局崩溃
10. 修复 `useCountdown` 的内存泄漏
11. 修复 AuthGuard 的 hydration 闪烁问题
12.`<form>` + `onSubmit` 替代 `<div>` + `onClick`
13. URL 参数加运行时校验
### 10.3 中期改进(架构升级)
14. 统一 `MeasurementType` 定义(从 constants 派生,类型文件引用)
15. 重构 Toast 组件为 Context/Provider 模式
16. Service 层抽离接口,方便后续替换为真实 HTTP 调用
17. Mock 层加入错误模拟能力
18. 图表组件的无障碍替代文本
19. 移除生产代码中的假数据(日历、问诊回复等)
### 10.4 长期规划(生产就绪)
20. 接入真实后端 APIHTTP Client 替换 Mock 层)
21. Token 改用 httpOnly Cookie 存储
22. 添加短信验证码服务端校验 + 发送频率限制
23. TypeScript 严格模式(`strict: true`
24. 单元测试 + E2E 测试
25. PWA 离线支持Service Worker
26. i18n 国际化(目前中文硬编码)
27. CI/CD 自动构建部署
---
## 附录 A修复前后路由对比
```tsx
// ❌ 修复前 — 第二个路由组渲染 null所有子页面白屏
{
path: '/',
element: <AuthGuard>{null}</AuthGuard>,
children: [ /* 20+ 页面全部无法渲染 */ ],
}
// ✅ 修复后 — 新增 StackLayout 组件渲染 <Outlet />
{
path: '/',
element: (
<AuthGuard>
<StackLayout /> // 只渲染 <Outlet />,无底部栏
</AuthGuard>
),
children: [ /* 正常渲染 */ ],
}
```
## 附录 B关键文件依赖关系图
```
main.tsx
└── App.tsx
└── router/index.tsx
├── AuthGuard.tsx → hooks/useAuth.ts → stores/auth.store.ts
├── AppLayout.tsx → TabBar.tsx
├── StackLayout.tsx (新增)
└── pages/** (25 个页面组件)
└── services/*.service.ts
└── mock/index.ts → mock/*.ts
```
---
> **审核结论**:项目架构基本合理,组件拆分清晰,类型定义完整。已修复一个导致 20+ 页面白屏的路由 Bug。剩余主要问题是缺少错误处理、Loading 状态、以及部分页面存在假数据硬编码。建议优先处理 10.1 节中的 6 个严重 Bug。