Files
soft/frontend-patient/技术报告.md
MingNian 435af55c4a 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)
2026-05-20 16:18:56 +08:00

592 lines
28 KiB
Markdown
Raw Permalink 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.

# 健康管家(健康管家)— 技术审核报告
> **项目名称**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。