# HMS 前端 UI/UX 全面重构设计规格 > **日期**: 2026-04-28 | **状态**: 待审核 | **范围**: Web 管理后台 + 微信小程序 ## 范围与前置条件 - **Web 端改动目标**: `apps/web/`(React 19 SPA),不涉及 `apps/desktop/`(Tauri 桌面壳) - **小程序改动目标**: `apps/miniprogram/`(Taro 4.2 + React 18) - **UI 框架**: Ant Design 6.x(已确认 `antd ^6.3.5`),使用 `styles.body` 替代已弃用的 `bodyStyle` - **状态管理**: Zustand stores(auth、theme 等) - **日期库**: dayjs ^1.11.20,需集中初始化 `relativeTime` 插件 + `zh-cn` locale ## Context HMS 健康管理平台前端当前存在"大而全"问题:仪表盘堆砌了 7 个信息区块但缺乏行动导向,22 个列表页面各自为政(3 种容器样式、3 种日期格式、暗色模式覆盖不完整),表单全用 Modal 弹窗无法承载复杂录入,小程序快捷服务用纯文字图标辨识度低。 本次重构目标:对标国内外一流医疗/SaaS 产品(MyChart、丁香医生、Linear、Stripe),按角色自适应原则重新设计信息架构,统一 CRUD 页面模式,建立表单三级容器策略。 --- ## 一、Web 仪表盘重构 ### 1.1 角色自适应策略 仪表盘根据当前用户角色显示不同视图: | 角色 | 视图名称 | 核心原则 | 主要内容 | |------|---------|---------|---------| | 医生 | 今日工作台 | 行动优先 | 日程时间线 + 紧急提醒 + 化验审核 + 咨询消息 | | 护士 | 随访监控台 | 监控优先 | 体征异常列表 + 随访队列 + 上报率 + 今日预约 | | 管理员 | 管理中心 | 数据优先 | 5 KPI + 趋势图 + 医护工作量 + 透析/化验/预约 Tab | | 运营 | 运营中心 | 转化优先 | 积分发放/消费 + 热门文章 + 活动报名 + 排行 | 角色判定逻辑:从 `UserInfo.roles: RoleInfo[]` 中提取角色 code(`roles.map(r => r.code)`),按优先级 `doctor > nurse > admin > operator` 匹配第一个命中。多角色用户取最高优先级角色对应的仪表盘。`RoleInfo` 结构:`{ id, name, code, description?, is_system }`。 | 仪表盘角色 code | 匹配的 RBAC 角色 code | |----------------|---------------------| | `doctor` | `doctor` 或任何以 `doctor` 为前缀的 code | | `nurse` | `nurse` 或任何以 `nurse` 为前缀的 code | | `admin` | `admin`、`super_admin` | | `operator` | `operator`、`content_manager` | ### 1.2 医生视图布局 ``` ┌─────────────────────────────────────────────────┐ │ 问候语 + 今日摘要(X个预约 · X条待处理) 🔍 🔔 │ ├─────────────────────────────────────────────────┤ │ [紧急] 张明华 血压 180/110 │ [注意] 2份化验待审 │ ├─────────────────────┬───────────────────────────┤ │ │ 化验审核 (2待审) │ │ 今日日程 │ ├ 张明华 — 血常规 3项异常│ │ ├ 09:30 张明华 ←当前│ └ 陈建国 — 肝功能 正常 │ │ ├ 10:00 李秀英 ├───────────────────────────┤ │ ├ 11:00 陈建国 │ 咨询消息 (5未读) │ │ └ 14:00 赵丽芳 │ ├ 王小明 10分钟前 │ │ │ └ 赵丽芳 1小时前 │ ├─────────┬──────┬──────────┬────────────────────┤ │ 我的患者│随访率│ 本月咨询 │ 体征上报率 │ │ 186 │ 92% │ 47 │ 78% │ │ ↑12新增 │ ↑5% │ 5待回复 │ ↓3% │ └─────────┴──────┴──────────┴────────────────────┘ ``` ### 1.3 护士视图布局 ``` ┌─────────────────────────────────────────────────┐ │ 问候 + 今日待随访12人 · 体征异常5人 │ ├─────────────────────────────────────────────────┤ │ 体征异常提醒 (5人异常) 查看全部 → │ │ ├ 张明华 血压 180/110 · 上报08:30 [高危] 通知医生→│ │ └ 刘美华 血糖 12.5 · 上报07:45 [偏高] 通知医生→│ ├─────────────────────┬───────────────────────────┤ │ 今日随访队列 (12人) │ 今日体征上报 │ │ [逾期] 王小明 血压 │ 78% │ │ [逾期] 陈小红 用药 │ 142已上报 │ 40未上报 │ │ [今日] 刘美华 用药 │ 催报未上报患者 → │ │ [今日] 赵丽芳 透析 │ │ ├─────────┬──────────────┬────────────────────────┤ │ 今日预约│ 随访完成率 │ 逾期随访 │ │ 6 │ 85% │ 8 │ └─────────┴──────────────┴────────────────────────┘ ``` ### 1.4 管理员视图布局 ``` ┌─────────────────────────────────────────────────┐ │ 管理中心 · 数据概览 [📅本月▾] [⬇️导出] │ ├───────┬───────┬───────┬───────┬─────────────────┤ │患者总数│本月预约│随访完成│体征上报│医护人数 │ │ 1,284 │ 356 │ 86% │ 72% │ 24 │ │ ↑8.2% │ ↓3.1% │ ↑5% │ ↓2% │ 3医21护 │ ├─────────────────────────┬───────────────────────┤ │ 患者增长 & 预约趋势图 │ 医护工作量 │ │ [双线图: 新增+预约] │ 王主任 186 ████████ 92%│ │ │ 陈医生 142 ██████ 70%│ │ │ 李护士 28 随访 █████ 85%│ ├─────────────────────────┴───────────────────────┤ │ [透析管理] 化验报告 预约分析 体征数据 │ │ 总记录 423 │ 并发症 2.3% │ 均超滤 2100ml │ 均时长245min │ └─────────────────────────────────────────────────┘ ``` ### 1.5 运营视图布局 ``` ┌─────────────────────────────────────────────────┐ │ 运营中心 · 积分、内容、活动 [📅本月▾] │ ├──────────┬──────────┬──────────┬────────────────┤ │ 积分发放 │ 积分消费 │ 文章发布 │ 活动报名 │ │ 12,450 │ 8,320 │ 18 │ 86 │ │ ↑15% │ 消费率66.8%│ 阅读4520│ 3场进行中 │ ├─────────────────────┬───────────────────────────┤ │ 积分消费排行 │ 热门文章 │ │ 1 赵丽芳 3,200分 │ 高血压管理指南 阅读1,230 │ │ 2 王小明 2,800分 │ 透析饮食注意 阅读980 │ │ 3 刘美华 2,150分 │ 春季过敏预防 阅读756 │ ├─────────────────────┴───────────────────────────┤ │ 线下活动 │ │ [高血压讲座 5/15 30/50人] [透析培训 5/22 18/30] │ └─────────────────────────────────────────────────┘ ``` ### 1.6 砍掉的内容 | 当前区块 | 处置 | 原因 | |---------|------|------| | 快捷入口(8个图标) | 删除 | 与左侧导航完全重复 | | 积分排行 Top10 | 移至运营视图 | 医护不需要看积分排行 | | 最近活动 | 删除 | 复用了排行 columns,未展示真正活动 | | 健康数据中心(嵌套 Card>Card) | 拆分到管理员视图 | 4 面板数据医护无需关注 | ### 1.7 技术实现 - **组件**: 新建 `StatisticsDashboard/` 目录下 4 个角色组件 + 1 个路由组件 - **API**: 复用现有 `useStatsData` hook 获取全局统计,新增个人维度请求 - **后端**: 新增个人工作量 API #### 1.7.1 个人工作量 API 契约 ``` GET /api/v1/health/dashboard/personal-stats Authorization: Bearer Response 200: { "success": true, "data": { "my_patients": 186, // 我的患者数 "new_patients_this_month": 12, // 本月新增患者 "follow_up_rate": 0.92, // 随访完成率 "consultations_this_month": 47, // 本月咨询数 "pending_consultations": 5, // 待回复咨询 "vital_signs_report_rate": 0.78, // 体征上报率(患者维度) "today_appointments": 6, // 今日预约数 "overdue_follow_ups": 8, // 逾期随访数 "today_follow_ups": 12, // 今日待随访数 "abnormal_vital_signs": 5, // 体征异常患者数 "vital_signs_reported": 142, // 已上报人数 "vital_signs_total": 182, // 应上报总人数 "pending_lab_reviews": 2 // 待审核化验数 } } ``` #### 1.7.2 数据层策略 所有列表页必须统一使用 `usePaginatedData` hook 作为数据层(已有 `apps/web/src/hooks/usePaginatedData.ts`)。`PageContainer` 接收 `usePaginatedData` 返回值作为 props,避免两套分页/筛选系统并存。目前仅 `VitalSignsTab`、`LabReportsTab`、`HealthRecordsTab`、`FollowUpTab` 使用了该 hook,其余页面需迁移。 --- ## 二、列表页统一规范 ### 2.1 统一 PageContainer 组件 提取共享组件 `components/PageContainer.tsx`,解决 3 种容器样式 + 暗色模式缺失: ```typescript interface PageContainerProps { title: string; subtitle?: string; filters?: React.ReactNode; // 筛选栏 actions?: React.ReactNode; // 右侧操作按钮 batchActions?: React.ReactNode; // 批量操作(选中时显示) selectedCount?: number; // 选中数量 onSelectAll?: () => void; onClearSelection?: () => void; } ``` ### 2.2 统一筛选栏规范 每个列表页至少 3 个筛选维度: | 页面 | 筛选维度 | |------|---------| | 患者管理 | 搜索(姓名/手机/身份证) + 状态 + 性别 + 注册日期范围 | | 医生管理 | 搜索(姓名) + 科室 + 职称 + 在线状态 | | 预约管理 | 状态 + 日期范围 + 患者搜索 + 预约类型 | | 随访任务 | 状态 + 计划日期范围 + 随访类型 + 执行人 | | 咨询管理 | 状态 + 日期范围 | | 积分规则 | 类型 + 状态 | | 积分商品 | 搜索(名称) + 类型 + 上架状态 | | 积分订单 | 状态 + 日期范围 | | 告警列表 | 状态 + 严重程度 + 规则名搜索 + 日期范围 | | 文章管理 | 搜索(标题) + 分类 + 状态 | ### 2.3 统一表格列设计原则 - **UUID 永远不暴露给用户**:后端必须返回 name 字段,前端兜底显示"未知患者" - **日期格式统一**:全部使用 `dayjs(v).format('YYYY-MM-DD HH:mm')`,告警等时间敏感页面用 `fromNow()` + title 悬停绝对时间 - **实体列合并**:姓名列集成副标题(手机号/来源),状态列合并认证状态 - **出生日期 → 年龄**:列表页显示"42岁"比"1984-03-15"更实用 - **增加 Checkbox 列**:支持批量选择和操作(批量导出/删除) ### 2.4 各页面列调整 **患者管理** (9列 → 8列): - 合并:姓名 + 来源 → 姓名列(副标题显示手机号 + 来源) - 合并:认证状态 → 状态列 - 变更:出生日期 → 年龄 - 变更:创建时间 → 最近就诊时间 - 新增:Checkbox 列 **告警列表** (7列 → 6列): - 变更:Patient ID → 患者姓名(可点击跳转) - 新增:严重程度筛选 + 日期范围筛选 **随访任务** (7列 → 7列): - 变更:患者 ID → 患者姓名 - 变更:执行人 ID → 执行人姓名 - 新增:日期范围 + 类型 + 执行人筛选 ### 2.5 共享工具 集中初始化 dayjs(`utils/dayjs.ts`): ```typescript import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import 'dayjs/locale/zh-cn'; dayjs.extend(relativeTime); dayjs.locale('zh-cn'); export { dayjs }; ``` 提取到 `utils/format.ts`(依赖 `utils/dayjs.ts`): ```typescript import { dayjs } from './dayjs'; export const formatDate = (v: string) => dayjs(v).format('YYYY-MM-DD'); export const formatDateTime = (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm'); export const formatRelative = (v: string) => dayjs(v).fromNow(); export const calcAge = (birthDate: string) => dayjs().diff(dayjs(birthDate), 'year'); ``` 提取到 `components/EntityName.tsx`: ```typescript // 统一处理 name 缺失的兜底显示 // name 存在 → 显示 name // name 缺失 → 显示 "未知患者" + tooltip 显示 ID ``` --- ## 三、表单设计规范 ### 3.1 三级容器策略 | 复杂度 | 容器 | 布局 | 适用场景 | |--------|------|------|---------| | 简单 (≤6字段) | Modal 弹窗 | 垂直单列 | 状态变更、简单编辑、分配 | | 中等 (7-12字段) | Drawer 抽屉 | 分组 + 双列网格 | 创建/编辑主实体 | | 复杂 (>12字段) | 独立页面 | 分步/分组 + 双列 | 完整建档、文章编辑 | ### 3.2 各页面表单容器决策 | 页面 | 当前 → 改进 | 字段数 | 布局 | |------|------------|--------|------| | 患者管理 | Modal → **Drawer** | 7→12 | 分组双列(基本/联系/医疗/紧急联系人) | | 医生管理 | Modal(保持) | 6 | 双列网格 | | 预约管理 | Modal → **Drawer** + 支持编辑 | 6→7 | 双列 + 排班校验反馈 | | 随访填写 | 3 Modal → Modal + **Drawer** | 5 | 填写记录用 Drawer | | 积分商品 | Modal → **Drawer** | 7→8 | 双列 + 图片上传 | | 文章编辑 | 独立页面(保持) | 富文本 | 全屏编辑器 | ### 3.3 DrawerForm 组件接口 ```typescript interface FormSection { title: string; // 分组标题 fields: React.ReactNode; // 该分组内的表单项 defaultCollapsed?: boolean; // 默认是否折叠 } interface DrawerFormProps { title: string; // 抽屉标题 open: boolean; onClose: () => void; onSubmit: (values: Record) => Promise; initialValues?: Record; loading?: boolean; width?: number | string; // 默认 640 sections?: FormSection[]; // 分组模式 children?: React.ReactNode; // 非分组模式,直接传入表单项 columns?: 1 | 2; // 布局列数,默认 2 } ``` ### 3.4 FilterBar 组件接口 ```typescript // 组合模式:FilterBar 提供布局容器,筛选控件由各页面自行组合 interface FilterBarProps { children: React.ReactNode; // 筛选控件(Input/Select/DatePicker 等) onReset?: () => void; // 重置按钮回调 extra?: React.ReactNode; // 右侧额外操作(如导出按钮) } ``` 使用示例: ```tsx