docs: UI/UX 设计规格修订 — 补充 API 契约、组件接口、技术前置条件
- 新增范围与前置条件(antd 6.x、dayjs 初始化、目标目录) - 定义角色 code 映射表和判定逻辑 - 补充个人工作量 API 契约(GET /personal-stats) - 新增 DrawerForm/FilterBar 组件接口定义 - 补充 dayjs 集中初始化方案 - 明确数据层策略(统一 usePaginatedData) - 修正小程序:SVG 图标替代 emoji、sparkline 用 CSS/SVG - 标记输入指示器为后续迭代 - 明确预约日历为现有组件增量优化
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
> **日期**: 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 弹窗无法承载复杂录入,小程序快捷服务用纯文字图标辨识度低。
|
||||
@@ -23,7 +31,14 @@ HMS 健康管理平台前端当前存在"大而全"问题:仪表盘堆砌了 7
|
||||
| 管理员 | 管理中心 | 数据优先 | 5 KPI + 趋势图 + 医护工作量 + 透析/化验/预约 Tab |
|
||||
| 运营 | 运营中心 | 转化优先 | 积分发放/消费 + 热门文章 + 活动报名 + 排行 |
|
||||
|
||||
角色判定逻辑:从 JWT 中提取用户角色(`user.roles`),按优先级 doctor > nurse > admin > operator 匹配。
|
||||
角色判定逻辑:从 `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 医生视图布局
|
||||
|
||||
@@ -120,8 +135,39 @@ HMS 健康管理平台前端当前存在"大而全"问题:仪表盘堆砌了 7
|
||||
### 1.7 技术实现
|
||||
|
||||
- **组件**: 新建 `StatisticsDashboard/` 目录下 4 个角色组件 + 1 个路由组件
|
||||
- **API**: 复用现有 `useStatsData` hook,按角色选择性请求
|
||||
- **后端**: 需新增"当前用户工作量"接口(我的患者数、随访完成率等个人维度数据)
|
||||
- **API**: 复用现有 `useStatsData` hook 获取全局统计,新增个人维度请求
|
||||
- **后端**: 新增个人工作量 API
|
||||
|
||||
#### 1.7.1 个人工作量 API 契约
|
||||
|
||||
```
|
||||
GET /api/v1/health/dashboard/personal-stats
|
||||
Authorization: Bearer <jwt>
|
||||
|
||||
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,其余页面需迁移。
|
||||
|
||||
---
|
||||
|
||||
@@ -189,8 +235,19 @@ interface PageContainerProps {
|
||||
|
||||
### 2.5 共享工具
|
||||
|
||||
提取到 `utils/format.ts`:
|
||||
集中初始化 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();
|
||||
@@ -228,17 +285,52 @@ export const calcAge = (birthDate: string) => dayjs().diff(dayjs(birthDate), 'ye
|
||||
| 积分商品 | Modal → **Drawer** | 7→8 | 双列 + 图片上传 |
|
||||
| 文章编辑 | 独立页面(保持) | 富文本 | 全屏编辑器 |
|
||||
|
||||
### 3.3 表单分组规范
|
||||
### 3.3 DrawerForm 组件接口
|
||||
|
||||
中等/复杂表单必须分组,每组有标题:
|
||||
```typescript
|
||||
interface FormSection {
|
||||
title: string; // 分组标题
|
||||
fields: React.ReactNode; // 该分组内的表单项
|
||||
defaultCollapsed?: boolean; // 默认是否折叠
|
||||
}
|
||||
|
||||
**患者表单分组:**
|
||||
- 基本信息:姓名*、性别、出生日期、血型
|
||||
- 联系方式:手机号、身份证号、地址
|
||||
- 医疗信息:过敏史、备注
|
||||
- 紧急联系人:姓名、电话、关系(新增)
|
||||
interface DrawerFormProps {
|
||||
title: string; // 抽屉标题
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (values: Record<string, any>) => Promise<void>;
|
||||
initialValues?: Record<string, any>;
|
||||
loading?: boolean;
|
||||
width?: number | string; // 默认 640
|
||||
sections?: FormSection[]; // 分组模式
|
||||
children?: React.ReactNode; // 非分组模式,直接传入表单项
|
||||
columns?: 1 | 2; // 布局列数,默认 2
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 表单交互规范
|
||||
### 3.4 FilterBar 组件接口
|
||||
|
||||
```typescript
|
||||
// 组合模式:FilterBar 提供布局容器,筛选控件由各页面自行组合
|
||||
interface FilterBarProps {
|
||||
children: React.ReactNode; // 筛选控件(Input/Select/DatePicker 等)
|
||||
onReset?: () => void; // 重置按钮回调
|
||||
extra?: React.ReactNode; // 右侧额外操作(如导出按钮)
|
||||
}
|
||||
```
|
||||
|
||||
使用示例:
|
||||
```tsx
|
||||
<FilterBar onReset={handleReset}>
|
||||
<Input placeholder="搜索姓名/手机号" />
|
||||
<Select placeholder="状态" options={statusOptions} />
|
||||
<DatePicker.RangePicker />
|
||||
</FilterBar>
|
||||
```
|
||||
|
||||
### 3.5 表单分组规范
|
||||
|
||||
### 3.6 表单交互规范
|
||||
|
||||
- 所有表单字段编辑时必须完整回填(当前患者编辑缺失 allergy_history/notes)
|
||||
- 双列布局中相关字段同行(姓名+性别、出生日期+血型)
|
||||
@@ -254,14 +346,14 @@ export const calcAge = (birthDate: string) => dayjs().diff(dayjs(birthDate), 'ye
|
||||
**改动点:**
|
||||
- 顶部区域从浅色 → 深色渐变卡片,集成今日健康摘要(血压/心率/血糖三格)
|
||||
- 无数据时显示引导文案:"今天还没录入数据,点击开始" + 录入按钮
|
||||
- 快捷服务从纯文字图标 → emoji + 白色卡片(日常上报、预约挂号、在线咨询、AI报告)
|
||||
- 快捷服务从纯文字图标 → SVG 图标 + 白色卡片(日常上报、预约挂号、在线咨询、AI报告)。使用微信小程序内置 icon 组件或项目内 SVG 资源,不使用 emoji(跨设备渲染不一致)
|
||||
- 待办事项增加日期卡片视觉(左侧显示日期,右侧显示内容)
|
||||
- 新增"健康资讯"推荐区块(1-2 篇文章)
|
||||
- 提醒角标:顶部右侧显示未读消息/异常提醒数
|
||||
|
||||
### 4.2 健康数据 Hub
|
||||
|
||||
- 体征卡片增加参考范围文字和微型趋势 sparkline
|
||||
- 体征卡片增加参考范围文字和微型趋势 sparkline(使用纯 CSS/SVG 实现,不加载 ECharts 实例,避免小卡片内运行完整图表库)
|
||||
- 打卡入口合并到顶部(当前独立卡片)
|
||||
- 趋势链接优化为横向滚动卡片
|
||||
|
||||
@@ -274,13 +366,13 @@ export const calcAge = (birthDate: string) => dayjs().diff(dayjs(birthDate), 'ye
|
||||
### 4.4 预约流程
|
||||
|
||||
- 创建页增加医生排班实时日历视图
|
||||
- 选择日期后高亮可约时段,不可约时段灰显
|
||||
- 选择日期后高亮可约时段,不可约时段灰显(增量优化现有 `WeekCalendar` 组件:为 `available_count === 0` 的时段添加灰显样式,不足时显示"当日无空位"提示)
|
||||
- 预约详情页增加"修改预约"和"取消预约"操作
|
||||
|
||||
### 4.5 咨询聊天
|
||||
|
||||
- 消息按日期分组显示("今天"、"昨天"、具体日期)
|
||||
- 增加"对方正在输入"状态提示
|
||||
- 增加"对方正在输入"状态提示(需后端支持 WebSocket typing 事件,Phase 5 暂不实现,移至后续迭代)
|
||||
- 图片消息支持点击预览大图
|
||||
|
||||
### 4.6 积分商城
|
||||
|
||||
Reference in New Issue
Block a user