From 16a776c2137f0a54509db82971822741faecec1a Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 28 Apr 2026 01:42:50 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20UI/UX=20=E9=87=8D=E6=9E=84=E5=AE=9E?= =?UTF-8?q?=E6=96=BD=E8=AE=A1=E5=88=92=20=E2=80=94=206=20Phase=2037=20Task?= =?UTF-8?q?=20=E5=88=86=E6=AD=A5=E8=AF=A6=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 基础组件提取 → Phase 2 仪表盘角色自适应 → Phase 3 列表页统一 → Phase 4 表单升级 Drawer → Phase 5 小程序重构 → Phase 6 验收 --- .../plans/2026-04-28-ui-ux-overhaul-plan.md | 1062 +++++++++++++++++ 1 file changed, 1062 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-28-ui-ux-overhaul-plan.md diff --git a/docs/superpowers/plans/2026-04-28-ui-ux-overhaul-plan.md b/docs/superpowers/plans/2026-04-28-ui-ux-overhaul-plan.md new file mode 100644 index 0000000..c852967 --- /dev/null +++ b/docs/superpowers/plans/2026-04-28-ui-ux-overhaul-plan.md @@ -0,0 +1,1062 @@ +# HMS UI/UX 全面重构 — 实施计划 + +> **日期**: 2026-04-28 | **依赖规格**: `docs/superpowers/specs/2026-04-28-ui-ux-overhaul-design.md` +> **总工期**: 12-17 天 | **6 Phase · 28 Task** + +## 前置条件 + +- [x] 设计规格已通过审查 +- [x] 角色代码映射已确认 +- [x] 技术栈:antd 6.x + dayjs + Zustand(Web)、Taro 4.2 + React 18(小程序) + +--- + +## Phase 1: 基础组件提取(1-2天) + +> 目标:建立共享基础设施,后续 Phase 全部依赖。 + +| # | Task | 产出文件 | 依赖 | +|---|------|---------|------| +| 1.1 | dayjs 集中初始化 | `apps/web/src/utils/dayjs.ts` | — | +| 1.2 | 日期/年龄格式化工具 | `apps/web/src/utils/format.ts` | 1.1 | +| 1.3 | EntityName 兜底组件 | `apps/web/src/components/EntityName.tsx` | — | +| 1.4 | FilterBar 筛选栏组件 | `apps/web/src/components/FilterBar.tsx` | — | +| 1.5 | PageContainer 页面容器 | `apps/web/src/components/PageContainer.tsx` | 1.2, 1.4 | +| 1.6 | DrawerForm 抽屉表单 | `apps/web/src/components/DrawerForm.tsx` | — | + +**验证**: `pnpm build` 通过 + 现有页面无回归 + +--- + +## Phase 2: 仪表盘重构(2-3天) + +> 目标:替换现有 7 区块仪表盘为 4 角色自适应视图。 + +| # | Task | 产出文件 | 依赖 | +|---|------|---------|------| +| 2.1 | 后端:个人工作量 API | Rust handler + service | — | +| 2.2 | 前端:角色判定 hook | `apps/web/src/hooks/useDashboardRole.ts` | — | +| 2.3 | 医生视图组件 | `StatisticsDashboard/DoctorDashboard.tsx` | 2.1, 2.2 | +| 2.4 | 护士视图组件 | `StatisticsDashboard/NurseDashboard.tsx` | 2.1, 2.2 | +| 2.5 | 管理员视图组件 | `StatisticsDashboard/AdminDashboard.tsx` | 2.2 | +| 2.6 | 运营视图组件 | `StatisticsDashboard/OperatorDashboard.tsx` | 2.2 | +| 2.7 | 路由组件 + 删除旧区块 | `StatisticsDashboard/index.tsx` | 2.3-2.6 | + +**验证**: 逐角色登录,确认各视图正确渲染 + +--- + +## Phase 3: 列表页统一(3-4天) + +> 目标:所有列表页迁移到 PageContainer,统一筛选/列/格式。 + +| # | Task | 产出文件 | 依赖 | +|---|------|---------|------| +| 3.1 | 患者管理页迁移 | `PatientList.tsx` | Phase 1 | +| 3.2 | 医生管理页迁移 | `DoctorList.tsx` | Phase 1 | +| 3.3 | 预约管理页迁移 | `AppointmentList.tsx` | Phase 1 | +| 3.4 | 随访任务页迁移 | `FollowUpTaskList.tsx` | Phase 1 | +| 3.5 | 咨询管理页迁移 | `ConsultationList.tsx` | Phase 1 | +| 3.6 | 积分(规则/商品/订单)迁移 | 3 个页面 | Phase 1 | +| 3.7 | 告警列表迁移 | `AlertList.tsx` | Phase 1 | +| 3.8 | 文章管理页迁移 | `ArticleList.tsx` | Phase 1 | + +**验证**: 逐页面对比截图,确认筛选/列/格式改进生效 + +--- + +## Phase 4: 表单升级(2-3天) + +> 目标:中等复杂度表单迁移到 DrawerForm,分组+双列布局。 + +| # | Task | 产出文件 | 依赖 | +|---|------|---------|------| +| 4.1 | 患者表单:Modal → Drawer + 分组 | `PatientList.tsx` 内表单 | 1.6, 3.1 | +| 4.2 | 预约表单:Drawer + 排班校验 | `AppointmentList.tsx` 内表单 | 1.6, 3.3 | +| 4.3 | 随访填写:Drawer + 完整回填 | `FollowUpTaskList.tsx` 内表单 | 1.6, 3.4 | +| 4.4 | 积分商品:Drawer + 图片上传 | 积分商品页内表单 | 1.6, 3.6 | + +**验证**: 每个表单创建/编辑完整流程测试 + +--- + +## Phase 5: 小程序重构(3-4天) + +> 目标:8 个页面按设计规格优化 UI/UX。 + +| # | Task | 产出文件 | 依赖 | +|---|------|---------|------| +| 5.1 | 患者首页重设计 | `pages/index/index.tsx` | — | +| 5.2 | 健康数据 Hub 优化 | `pages/health/index.tsx` | — | +| 5.3 | 日常监测表单分组 | `pages/daily-monitoring/index.tsx` | — | +| 5.4 | 预约流程优化 | `pages/appointment/create/index.tsx` | — | +| 5.5 | 咨询聊天优化 | `pages/consultation/detail/index.tsx` | — | +| 5.6 | 积分商城优化 | `pages/points/` 相关页面 | — | +| 5.7 | 医护工作台重设计 | `pages/doctor/index.tsx` | — | +| 5.8 | 趋势图表优化 | `components/TrendChart/` | — | + +**验证**: 小程序编译 + 真机预览 + +--- + +## Phase 6: 验收(1天) + +| # | Task | 范围 | +|---|------|------| +| 6.1 | 暗色模式全量测试 | Web 所有页面 + 小程序 | +| 6.2 | 响应式布局测试 | 1280/1440/1920 宽度 | +| 6.3 | 各角色权限测试 | 4 角色逐个登录验证 | +| 6.4 | 生产构建验证 | `pnpm build` + `cargo build --release` | + +--- + +(以下逐章展开详细实施步骤) + +--- + +## Phase 1 详细步骤:基础组件提取 + +### Task 1.1 — dayjs 集中初始化 + +**文件**: `apps/web/src/utils/dayjs.ts`(新建) + +**步骤**: +1. 创建 `utils/dayjs.ts`,导入 dayjs + relativeTime 插件 + zh-cn locale +2. 执行 `dayjs.extend(relativeTime)` 和 `dayjs.locale('zh-cn')` +3. 导出已初始化的 `dayjs` 实例 + +**代码骨架**: +```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 }; +export default dayjs; +``` + +**清理**: 全局搜索 `import dayjs from 'dayjs'` 和 `import relativeTime from 'dayjs/plugin/relativeTime'` 的独立初始化(已知 `AlertList.tsx` 第 13-14 行),替换为 `import { dayjs } from '@/utils/dayjs'`。 + +**验证**: `pnpm build` 通过 + +--- + +### Task 1.2 — 日期/年龄格式化工具 + +**文件**: `apps/web/src/utils/format.ts`(新建) + +**步骤**: +1. 从 `./dayjs` 导入已初始化的 dayjs +2. 导出 4 个函数:`formatDate`、`formatDateTime`、`formatRelative`、`calcAge` + +**代码骨架**: +```typescript +import { dayjs } from './dayjs'; + +export const formatDate = (v: string | null | undefined): string => + v ? dayjs(v).format('YYYY-MM-DD') : '--'; + +export const formatDateTime = (v: string | null | undefined): string => + v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '--'; + +export const formatRelative = (v: string | null | undefined): string => + v ? dayjs(v).fromNow() : '--'; + +export const calcAge = (birthDate: string | null | undefined): string => { + if (!birthDate) return '--'; + const age = dayjs().diff(dayjs(birthDate), 'year'); + return `${age}岁`; +}; +``` + +**验证**: `pnpm build` 通过 + +--- + +### Task 1.3 — EntityName 兜底组件 + +**文件**: `apps/web/src/components/EntityName.tsx`(新建) + +**步骤**: +1. 创建组件,接收 `name`、`id`、`fallbackLabel` props +2. name 存在时直接显示 +3. name 缺失时显示灰色 fallback 文字 + Tooltip 显示 ID + +**代码骨架**: +```typescript +import { Tooltip, Typography } from 'antd'; + +interface EntityNameProps { + name?: string | null; + id?: string; + fallbackLabel?: string; // 默认 "未知" +} + +export function EntityName({ name, id, fallbackLabel = '未知' }: EntityNameProps) { + if (name) return {name}; + return ( + + {fallbackLabel} + + ); +} +``` + +**验证**: `pnpm build` 通过 + +--- + +### Task 1.4 — FilterBar 筛选栏组件 + +**文件**: `apps/web/src/components/FilterBar.tsx`(新建) + +**步骤**: +1. 创建组合式筛选栏容器 +2. 左侧渲染 children(筛选控件),右侧渲染"重置"按钮 + extra 区域 +3. 使用 Ant Design Space + Flex 布局,支持暗色模式 + +**代码骨架**: +```typescript +import { Button, Flex, Space } from 'antd'; +import { ReloadOutlined } from '@ant-design/icons'; +import { useThemeMode } from '../hooks/useThemeMode'; + +interface FilterBarProps { + children: React.ReactNode; + onReset?: () => void; + extra?: React.ReactNode; +} + +export function FilterBar({ children, onReset, extra }: FilterBarProps) { + const isDark = useThemeMode(); + return ( + + {children} + + {onReset && } + {extra} + + + ); +} +``` + +**验证**: `pnpm build` 通过 + +--- + +### Task 1.5 — PageContainer 页面容器 + +**文件**: `apps/web/src/components/PageContainer.tsx`(新建) + +**步骤**: +1. 创建统一页面容器,集成标题栏 + FilterBar + 批量操作栏 + 表格插槽 +2. 暗色模式自动适配 +3. 批量操作栏在有选中项时显示,替换常规操作栏 + +**代码骨架**: +```typescript +import { Card, Flex, Space, Typography, Button } from 'antd'; +import { useThemeMode } from '../hooks/useThemeMode'; +import { FilterBar } from './FilterBar'; + +interface PageContainerProps { + title: string; + subtitle?: string; + filters?: React.ReactNode; + onResetFilters?: () => void; + filterExtra?: React.ReactNode; + actions?: React.ReactNode; + batchActions?: React.ReactNode; + selectedCount?: number; + onClearSelection?: () => void; + children: React.ReactNode; // 表格或其他内容 + loading?: boolean; +} + +export function PageContainer({ + title, subtitle, filters, onResetFilters, filterExtra, + actions, batchActions, selectedCount, onClearSelection, children, loading, +}: PageContainerProps) { + const isDark = useThemeMode(); + return ( +
+ {/* 标题栏 */} + +
+ {title} + {subtitle && {subtitle}} +
+ + {selectedCount ? batchActions : actions} + {selectedCount && onClearSelection && ( + + )} + +
+ {/* 筛选栏 */} + {filters && {filters}} + {/* 内容区 */} + + {children} + +
+ ); +} +``` + +**验证**: `pnpm build` 通过。可选:选取一个列表页临时引入 PageContainer,确认布局正确。 + +--- + +### Task 1.6 — DrawerForm 抽屉表单 + +**文件**: `apps/web/src/components/DrawerForm.tsx`(新建) + +**步骤**: +1. 创建 Ant Design Drawer + Form 组合组件 +2. 支持分组模式(sections)和非分组模式(children) +3. 双列网格布局(columns prop) +4. 编辑时完整回填(initialValues + form.setFieldsValue) + +**代码骨架**: +```typescript +import { Drawer, Form, Typography, Divider } from 'antd'; +import { useThemeMode } from '../hooks/useThemeMode'; + +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; + sections?: FormSection[]; + children?: React.ReactNode; + columns?: 1 | 2; +} + +export function DrawerForm({ + title, open, onClose, onSubmit, initialValues, + loading, width = 640, sections, children, columns = 2, +}: DrawerFormProps) { + const [form] = Form.useForm(); + const isDark = useThemeMode(); + + // initialValues 变化时回填 + React.useEffect(() => { form.resetFields(); if (initialValues) form.setFieldsValue(initialValues); }, [initialValues, form]); + + const handleSubmit = async () => { + const values = await form.validateFields(); + await onSubmit(values); + }; + + const gridStyle = columns === 2 ? { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 16px' } : {}; + + return ( + }> +
+ {sections ? sections.map((s, i) => ( +
+ {i > 0 && } + {s.title} +
{s.fields}
+
+ )) :
{children}
} +
+
+ ); +} +``` + +**验证**: `pnpm build` 通过 + +--- + +### Phase 1 整体验证 + +1. `cd apps/web && pnpm build` — 生产构建通过 +2. 启动后端 + 前端,确认现有页面无回归(不引入新组件,仅新增文件) +3. 提交:`feat(web): 提取 PageContainer/EntityName/DrawerForm/FilterBar 共享组件 + format 工具` + +--- + +## Phase 2 详细步骤:仪表盘重构 + +### Task 2.1 — 后端:个人工作量 API + +**文件**: +- `crates/erp-health/src/handlers/dashboard.rs`(新建或追加) +- `crates/erp-health/src/services/dashboard.rs`(新建或追加) +- `crates/erp-health/src/routes.rs`(追加路由) + +**步骤**: +1. 定义 `PersonalStatsResponse` 结构体(字段见规格 1.7.1) +2. 实现 `DashboardService::personal_stats(user_id, tenant_id)` 查询方法 +3. 数据来源:从 `patients`(doctor_id 筛选)、`follow_up_tasks`、`consultations`、`vital_signs` 表聚合计算 +4. 注册路由 `GET /api/v1/health/dashboard/personal-stats` +5. 添加 utoipa 注解 + +**SQL 聚合逻辑**: +```sql +-- 我的患者数 +SELECT COUNT(*) FROM patients WHERE doctor_id = $1 AND tenant_id = $2 AND deleted_at IS NULL; +-- 本月新增 +SELECT COUNT(*) FROM patients WHERE doctor_id = $1 AND tenant_id = $2 AND created_at >= $month_start; +-- 随访完成率 +SELECT COUNT(*) FILTER (WHERE status = 'completed') * 100.0 / COUNT(*) FROM follow_up_tasks WHERE ...; +-- 今日待随访 +SELECT COUNT(*) FROM follow_up_tasks WHERE assignee_id = $1 AND planned_date = CURRENT_DATE; +-- 待审核化验 +SELECT COUNT(*) FROM lab_reports WHERE reviewed_by IS NULL AND tenant_id = $2; +``` + +**验证**: `cargo test` + Swagger UI 调用返回正确数据 + +--- + +### Task 2.2 — 前端:角色判定 hook + +**文件**: `apps/web/src/hooks/useDashboardRole.ts`(新建) + +**步骤**: +1. 从 Zustand auth store 读取 `user.roles` +2. 按优先级 `doctor > nurse > admin > operator` 匹配 +3. 返回匹配的角色类型 `'doctor' | 'nurse' | 'admin' | 'operator'` + +**代码骨架**: +```typescript +import { useAuthStore } from '../stores/auth'; + +type DashboardRole = 'doctor' | 'nurse' | 'admin' | 'operator'; + +const ROLE_PRIORITY: DashboardRole[] = ['doctor', 'nurse', 'admin', 'operator']; + +export function useDashboardRole(): DashboardRole { + const user = useAuthStore(s => s.user); + if (!user?.roles?.length) return 'admin'; // 无角色默认管理员视图 + const codes = user.roles.map(r => r.code); + for (const role of ROLE_PRIORITY) { + if (codes.some(c => c === role || c.startsWith(role))) return role; + } + return 'admin'; +} +``` + +**验证**: `pnpm build` 通过 + +--- + +### Task 2.3 — 医生视图组件 + +**文件**: `apps/web/src/pages/health/StatisticsDashboard/DoctorDashboard.tsx`(新建) + +**步骤**: +1. 调用 `personal-stats` API + `useStatsData`(全局统计) +2. 布局:顶部问候 + 紧急提醒 → 中间双列(日程 + 化验/咨询)→ 底部统计卡 +3. 使用 Ant Design Card + Row/Col 布局 +4. 日程区显示今日预约时间线,当前时间高亮 +5. 化验审核列表显示待审核项(异常项红色标记) +6. 底部 4 个统计卡使用 `` 组件 + 趋势箭头 + +**数据源**: +- `personal-stats` API → 统计卡数据 +- 现有预约 API → 今日日程 +- 现有化验 API → 待审核列表 +- 现有咨询 API → 未读消息 + +**验证**: 以医生角色登录,确认日程/审核/消息/统计正确渲染 + +--- + +### Task 2.4 — 护士视图组件 + +**文件**: `apps/web/src/pages/health/StatisticsDashboard/NurseDashboard.tsx`(新建) + +**步骤**: +1. 布局:问候 + 异常提醒 → 双列(随访队列 + 上报率)→ 统计卡 +2. 异常提醒区:体征异常患者列表,每项可"通知医生" +3. 随访队列:按逾期/今日分组,显示患者名 + 随访类型 +4. 上报率:环形进度条 + 已上报/未上报人数 + 催报按钮 +5. 底部 3 个统计卡(今日预约/随访完成率/逾期随访) + +**数据源**: +- `personal-stats` API → 统计数据 +- 体征 API → 异常列表(按阈值筛选) +- 随访 API → 今日/逾期队列 + +**验证**: 以护士角色登录,确认队列/上报率/异常正确显示 + +--- + +### Task 2.5 — 管理员视图组件 + +**文件**: `apps/web/src/pages/health/StatisticsDashboard/AdminDashboard.tsx`(新建) + +**步骤**: +1. 布局:顶部 5 KPI 卡 → 中间双列(趋势图 + 工作量)→ 底部 Tab +2. KPI 卡:患者总数、本月预约、随访完成率、体征上报率、医护人数 +3. 趋势图:复用现有 ECharts 或轻量级图表(双线图:新增患者 + 预约数) +4. 工作量:横向条形图,显示各医护的患者数/随访完成率 +5. 底部 Tab:透析管理 / 化验报告 / 预约分析 / 体征数据(复用现有 HealthDataCenter 组件) + +**数据源**: +- `useStatsData` hook → 现有全局统计 +- `HealthDataCenter.tsx` → 底部 Tab 内容 + +**验证**: 以管理员角色登录,确认 KPI/图表/Tab 正确渲染 + +--- + +### Task 2.6 — 运营视图组件 + +**文件**: `apps/web/src/pages/health/StatisticsDashboard/OperatorDashboard.tsx`(新建) + +**步骤**: +1. 布局:顶部 4 KPI → 中间双列(积分排行 + 热门文章)→ 底部活动 +2. KPI 卡:积分发放/消费、文章发布、活动报名 +3. 积分排行:Top 5 列表(从现有 useStatsData.pointsStats 获取) +4. 热门文章:Top 3 列表(从文章 API 获取,按浏览量排序) +5. 底部活动:进行中的线下活动卡片 + +**数据源**: +- `useStatsData` hook → 积分统计 +- 文章 API → 热门文章 +- 活动 API → 进行中活动 + +**验证**: 以运营角色登录,确认积分/文章/活动正确渲染 + +--- + +### Task 2.7 — 路由组件 + 删除旧区块 + +**文件**: `apps/web/src/pages/health/StatisticsDashboard.tsx`(重写) + +**步骤**: +1. 导入 `useDashboardRole` hook +2. 根据 role 条件渲染对应的 Dashboard 组件 +3. 删除旧内容:快捷入口区块、积分排行 Top10、最近活动区块 +4. 保留:HealthDataCenter 迁移到管理员视图内部 + +**代码骨架**: +```typescript +import { useDashboardRole } from '../../hooks/useDashboardRole'; +import { DoctorDashboard } from './StatisticsDashboard/DoctorDashboard'; +import { NurseDashboard } from './StatisticsDashboard/NurseDashboard'; +import { AdminDashboard } from './StatisticsDashboard/AdminDashboard'; +import { OperatorDashboard } from './StatisticsDashboard/OperatorDashboard'; + +const DASHBOARD_MAP = { + doctor: DoctorDashboard, + nurse: NurseDashboard, + admin: AdminDashboard, + operator: OperatorDashboard, +}; + +export default function StatisticsDashboard() { + const role = useDashboardRole(); + const DashboardComponent = DASHBOARD_MAP[role]; + return ; +} +``` + +**验证**: 各角色登录切换,确认正确渲染对应视图 + +--- + +### Phase 2 整体验证 + +1. `cargo test` — 后端测试通过 +2. `pnpm build` — 前端构建通过 +3. 启动后端 + 前端,逐角色登录验证仪表盘内容 +4. 提交:`feat(web): 仪表盘角色自适应重构 + 后端个人工作量 API` + +--- + +## Phase 3 详细步骤:列表页统一 + +> 每个列表页迁移遵循统一模式: +> 1. 引入 PageContainer + FilterBar +> 2. 手动 fetch 逻辑迁移到 usePaginatedData +> 3. 筛选器补充至至少 3 个维度 +> 4. 列调整:UUID→姓名、日期格式化、合并列、增加 Checkbox +> 5. 暗色模式统一处理 + +### 迁移模板(每个页面遵循) + +```typescript +// 迁移前:手动 state + fetch +const [data, setData] = useState([]); +const [total, setTotal] = useState(0); +const [page, setPage] = useState(1); +const [loading, setLoading] = useState(false); +const fetchXxx = async (p = page) => { ... }; + +// 迁移后:usePaginatedData + PageContainer +const { data, total, page, loading, searchText, setSearchText, filters, setFilters, refresh } = + usePaginatedData(xxxApi.list, { pageSize: 20, defaultFilters: {...} }); + +return ( + + + +); +``` + +--- + +### Task 3.1 — 患者管理页迁移 + +**文件**: `apps/web/src/pages/health/PatientList.tsx` + +**改动清单**: +1. 手动 state → `usePaginatedData` with filters +2. 引入 `PageContainer` + `FilterBar` +3. 筛选器:搜索(姓名/手机/身份证) + 状态 + 性别 + 注册日期范围(当前只有搜索+状态,补充性别和日期范围) +4. 列调整: + - 新增 Checkbox 列 + - 合并姓名+来源 → 姓名列(`` 副标题显示手机号+来源) + - 合并认证状态 → 状态列 + - 出生日期 → 年龄(`calcAge(record.birth_date)`) + - 创建时间 → 最近就诊时间(`formatDateTime(record.last_visit_at)`) + - UUID 列删除(如有) +5. 暗色模式:移除手动 isDark 处理,由 PageContainer 统一 + +--- + +### Task 3.2 — 医生管理页迁移 + +**文件**: `apps/web/src/pages/health/DoctorList.tsx` + +**改动清单**: +1. 手动 state → `usePaginatedData` +2. 筛选器:搜索(姓名) + 科室 + 职称 + 在线状态 +3. 列调整:Checkbox + 姓名副标题显示科室+职称 + UUID→姓名 + 日期格式化 + +--- + +### Task 3.3 — 预约管理页迁移 + +**文件**: `apps/web/src/pages/health/AppointmentList.tsx` + +**改动清单**: +1. 手动 state → `usePaginatedData` +2. 筛选器:状态 + 日期范围 + 患者搜索 + 预约类型 +3. 列调整:Checkbox + patient_id → `` + doctor_id → `` + 日期格式化 +4. 状态操作保留现有下拉切换模式(已实现得好) + +--- + +### Task 3.4 — 随访任务页迁移 + +**文件**: `apps/web/src/pages/health/FollowUpTaskList.tsx` + +**改动清单**: +1. 手动 state → `usePaginatedData` +2. 筛选器:状态 + 计划日期范围 + 随访类型 + 执行人 +3. 列调整:patient_id → `` + assignee_id → `` + 日期格式化 +4. 执行人筛选:下拉列表从医护 API 获取 + +--- + +### Task 3.5 — 咨询管理页迁移 + +**文件**: `apps/web/src/pages/health/ConsultationList.tsx` + +**改动清单**: +1. 手动 state → `usePaginatedData` +2. 筛选器:状态 + 日期范围(当前可能缺少日期范围) +3. 列调整:日期格式化 + UUID→姓名 + +--- + +### Task 3.6 — 积分(规则/商品/订单)迁移 + +**文件**: +- `apps/web/src/pages/health/PointsRuleList.tsx` +- `apps/web/src/pages/health/PointsProductList.tsx` +- `apps/web/src/pages/health/PointsOrderList.tsx` + +**改动清单**(每个页面): +1. 引入 `PageContainer` + `FilterBar` +2. 筛选器: + - 积分规则:类型 + 状态 + - 积分商品:搜索(名称) + 类型 + 上架状态 + - 积分订单:状态 + 日期范围 +3. 列调整:日期格式化 + UUID→姓名 + +--- + +### Task 3.7 — 告警列表迁移 + +**文件**: `apps/web/src/pages/health/AlertList.tsx` + +**改动清单**: +1. 引入 `PageContainer` + `FilterBar` +2. 筛选器:状态 + 严重程度 + 规则名搜索 + 日期范围 +3. 列调整:patient_id → ``(可点击跳转患者详情) +4. 日期格式化:使用 `formatRelative()` + title 悬停绝对时间 +5. 替换组件内独立的 dayjs 导入为 `utils/dayjs` + +--- + +### Task 3.8 — 文章管理页迁移 + +**文件**: `apps/web/src/pages/health/ArticleList.tsx` + +**改动清单**: +1. 引入 `PageContainer` + `FilterBar` +2. 筛选器:搜索(标题) + 分类 + 状态 +3. 列调整:日期格式化 + Checkbox + +--- + +### Phase 3 整体验证 + +1. `pnpm build` — 前端构建通过 +2. 逐页面打开,确认: + - PageContainer 统一布局 + - 筛选器功能正常(搜索/下拉/日期范围) + - 列调整生效(无 UUID、日期格式化、EntityName 兜底) + - Checkbox 批量选择可用 + - 暗色模式正确 +3. 提交:`refactor(web): 列表页统一迁移至 PageContainer + 筛选/列/格式化规范` + +--- + +## Phase 4 详细步骤:表单升级 + +### Task 4.1 — 患者表单:Modal → Drawer + 分组 + +**文件**: `apps/web/src/pages/health/PatientList.tsx`(修改现有表单部分) + +**改动清单**: +1. 引入 `DrawerForm` 组件 +2. 创建表单改为 4 分组: + - 基本信息:姓名*、性别、出生日期、血型 + - 联系方式:手机号、身份证号、地址 + - 医疗信息:过敏史、备注 + - 紧急联系人:姓名、电话、关系(新增字段) +3. 字段数从 7 → 12,布局 columns=2 +4. 编辑模式完整回填:确保 `allergy_history`、`notes` 字段从 `initialValues` 正确填充 +5. 新增"紧急联系人"字段提交到后端(需确认 API 是否支持,如不支持先前端预留) + +**关键改动点**: +```typescript +// 旧:Modal + 垂直单列 + +
+ + ... + +
+ +// 新:DrawerForm + 分组双列 + setModalOpen(false)} + onSubmit={handleSubmit} initialValues={editingPatient ?? undefined} + columns={2} + sections={[ + { title: '基本信息', fields: <>{nameField}{genderField}{birthDateField}{bloodTypeField} }, + { title: '联系方式', fields: <>{phoneField}{idNumberField}{addressField} }, + { title: '医疗信息', fields: <>{allergyField}{notesField} }, + { title: '紧急联系人', fields: <>{emergencyNameField}{emergencyPhoneField}{emergencyRelationField} }, + ]} +/> +``` + +**验证**: 创建患者 → 全字段填写 → 提交成功;编辑患者 → 所有字段正确回填 → 修改提交 + +--- + +### Task 4.2 — 预约表单:Drawer + 排班校验 + +**文件**: `apps/web/src/pages/health/AppointmentList.tsx`(修改现有表单部分) + +**改动清单**: +1. 引入 `DrawerForm` 组件,columns=2 +2. 分组: + - 患者信息:患者选择、预约类型、日期 + - 医生信息:医生选择、时段、排班状态反馈 +3. 排班校验:选择医生+日期后,调用排班 API 显示可用时段 + - 有空位:显示绿色"可预约 X 位" + - 无空位:显示红色"已满"并禁用提交 +4. 修复当前问题:患者/医生 Select 从 Form.Item 外部受控改为 Form.Item 内部管理(解决校验问题) +5. 取消预约原因:从 Modal.confirm 改为独立 Drawer + +**关键改动点**: +```typescript +// 排班校验反馈组件 +function ScheduleStatus({ doctorId, date }: { doctorId: string; date: string }) { + const { data, loading } = useScheduleCheck(doctorId, date); + if (loading) return ; + if (!data?.available) return ; + return ; +} +``` + +**验证**: 创建预约 → 选医生+日期 → 看到排班状态 → 选择时段 → 提交成功 + +--- + +### Task 4.3 — 随访填写:Drawer + 完整回填 + +**文件**: `apps/web/src/pages/health/FollowUpTaskList.tsx`(修改现有表单部分) + +**改动清单**: +1. 填写记录从 Modal → `DrawerForm`,columns=1 +2. 分组: + - 基本信息:患者、计划日期、随访类型(只读展示) + - 填写内容:执行人、随访结果、备注 +3. 完整回填:编辑时加载已保存的随访记录数据 +4. 保留现有 Modal 用于简单操作(状态变更) + +**验证**: 创建随访任务 → 填写记录 → 保存 → 重新打开确认回填 + +--- + +### Task 4.4 — 积分商品:Drawer + 图片上传 + +**文件**: 积分商品页面(修改现有表单部分) + +**改动清单**: +1. 引入 `DrawerForm`,columns=2 +2. 分组: + - 基本信息:名称、类型、所需积分、库存数量 + - 展示:图片上传、描述、上架状态 +3. 图片上传:使用 Ant Design Upload 组件,支持预览和删除 +4. 字段数 7→8(新增描述字段,如 API 支持) + +**验证**: 创建商品 → 上传图片 → 提交 → 编辑确认回填 + +--- + +### Phase 4 整体验证 + +1. `pnpm build` — 前端构建通过 +2. 每个表单执行创建 + 编辑完整流程: + - 患者表单:12 字段分组双列,编辑回填 + - 预约表单:排班校验反馈,时段选择 + - 随访填写:Drawer 完整回填 + - 积分商品:图片上传 +3. 提交:`feat(web): 表单升级 — Modal→Drawer + 分组双列布局` + +--- + +## Phase 5 详细步骤:小程序重构 + +> 每个小程序页面独立修改,互不依赖,可按任意顺序或并行实施。 + +### Task 5.1 — 患者首页重设计 + +**文件**: +- `apps/miniprogram/src/pages/index/index.tsx` +- `apps/miniprogram/src/pages/index/index.scss` + +**改动清单**: +1. 顶部区域:浅色 → 深色渐变卡片 + - 集成今日健康摘要(血压/心率/血糖三格,无数据时显示引导文案) + - 右上角提醒角标(未读消息/异常提醒数) +2. 快捷服务:纯文字图标 → SVG 图标 + 白色卡片(日常上报、预约挂号、在线咨询、AI报告) + - 使用微信小程序 `` 组件或内嵌 SVG +3. 待办事项:增加日期卡片视觉(左侧日期圆圈 + 右侧内容) +4. 新增"健康资讯"推荐区块(1-2 篇文章卡片) +5. 空状态引导:"今天还没录入数据,点击开始" + 录入按钮 + +--- + +### Task 5.2 — 健康数据 Hub 优化 + +**文件**: +- `apps/miniprogram/src/pages/health/index.tsx` +- `apps/miniprogram/src/pages/health/index.scss` + +**改动清单**: +1. 体征卡片增加参考范围文字(如"正常: 90-140 mmHg") +2. 体征卡片增加微型趋势 sparkline(纯 CSS/SVG 实现,不加载 ECharts) +3. 打卡入口合并到顶部区域(当前独立卡片 → 顶部快捷操作栏) +4. 趋势链接优化为横向滚动卡片(而非当前列表) + +--- + +### Task 5.3 — 日常监测表单分组折叠 + +**文件**: +- `apps/miniprogram/src/pages/daily-monitoring/index.tsx` +- `apps/miniprogram/src/pages/daily-monitoring/index.scss` + +**改动清单**: +1. 8 个区块改为 3 组折叠: + - 晨间体征(收缩压/舒张压/心率) + - 晚间体征(收缩压/舒张压/心率) + - 其他(体重/血糖/出入量/体温/备注) +2. 异常值实时提示:输入超出参考范围时红框高亮 + 文字提醒 +3. 提交前校验摘要弹窗:如有异常值弹出确认("血压偏高 (160/95),确认提交?") +4. 参考范围常量定义在组件顶部或 utils + +--- + +### Task 5.4 — 预约流程优化 + +**文件**: +- `apps/miniprogram/src/pages/appointment/create/index.tsx` + +**改动清单**: +1. 增量优化现有 `WeekCalendar` 组件: + - 为 `available_count === 0` 的时段添加灰显样式 + - 不足时显示"当日无空位"提示 +2. 选择日期后高亮可约时段,不可约灰显 +3. 预约详情页增加"修改预约"和"取消预约"操作按钮(如尚未有) + +--- + +### Task 5.5 — 咨询聊天优化 + +**文件**: +- `apps/miniprogram/src/pages/consultation/detail/index.tsx` + +**改动清单**: +1. 消息按日期分组显示("今天"、"昨天"、具体日期) + - 在消息列表中插入日期分割线 +2. 图片消息支持点击预览大图(`Taro.previewImage`) +3. "对方正在输入"标记为后续迭代(需后端 WebSocket) + +--- + +### Task 5.6 — 积分商城优化 + +**文件**: `apps/miniprogram/src/pages/points/` 相关页面 + +**改动清单**: +1. 顶部积分余额大字显示 + 签到按钮 +2. 商品卡片增加库存提示("剩余 X 件")和兑换按钮 +3. 分类 Tab 保留但优化视觉(更紧凑的胶囊 Tab) + +--- + +### Task 5.7 — 医护工作台重设计 + +**文件**: +- `apps/miniprogram/src/pages/doctor/index.tsx` +- `apps/miniprogram/src/pages/doctor/index.scss` + +**改动清单**: +1. 顶部增加异常体征提醒横幅(与 Web 医生视图紧急提醒联动,同数据源) +2. 工作概览卡片改为 SVG 图标 + 数字 + 标签三层结构(替代当前中文字符"患"/"消"/"随") +3. 新增患者搜索入口(顶部搜索框) +4. 快捷操作增加"随访记录"、"排班查看"入口 + +--- + +### Task 5.8 — 趋势图表优化 + +**文件**: `apps/miniprogram/src/components/TrendChart/index.tsx` + +**改动清单**: +1. ECharts 懒加载:按需加载 EcCanvas 组件(`React.lazy` + Suspense) +2. 加载中显示骨架屏 +3. 异常值红点标记(数据点超出参考范围时红色标记 + 点击显示详情) +4. 时间范围选择器:7天/30天/90天 按钮组(替代当前可能只有单一时间范围) + +--- + +### Phase 5 整体验证 + +1. `cd apps/miniprogram && pnpm build` — 小程序构建通过 +2. 微信开发者工具中预览,逐页面确认改动 +3. 真机扫码预览,确认样式和交互 +4. 提交:`feat(miniprogram): 8 页面 UI/UX 重构 — 首页/健康/监测/预约/聊天/积分/医护/图表` + +--- + +## Phase 6 详细步骤:验收 + +### Task 6.1 — 暗色模式全量测试 + +**范围**: Web 所有已改动页面 + 小程序 + +**步骤**: +1. 切换到暗色主题 +2. 逐页面检查: + - PageContainer 背景色正确 + - DrawerForm 内容区可读 + - FilterBar 筛选控件可见 + - 表格行交替色正确 + - EntityName 灰色文字在暗背景上可读 +3. 小程序暗色模式(如支持):顶部卡片、表单、图表 + +**通过标准**: 无白块、无不可读文字、无颜色冲突 + +--- + +### Task 6.2 — 响应式布局测试 + +**步骤**: +1. 浏览器宽度调整为 1280px / 1440px / 1920px +2. 检查仪表盘布局在各宽度下的排列: + - 双列区域是否正确折叠 + - 统计卡是否响应式排列 + - 表格是否水平滚动 +3. 小程序:确认各页面在 iPhone SE / iPhone 15 Pro / iPad 下正常 + +**通过标准**: 无内容溢出、无重叠、无截断 + +--- + +### Task 6.3 — 各角色权限测试 + +**步骤**: +1. 创建 4 个测试账号,分别赋予 doctor / nurse / admin / operator 角色 +2. 逐角色登录 Web 端: + - 仪表盘显示对应角色视图 + - 各列表页筛选/操作按权限显示 + - 表单创建/编辑权限正确 +3. 逐角色登录小程序(医护端): + - 医护工作台显示对应内容 + - 操作权限正确 + +**通过标准**: 每个角色只看到授权内容,无 403 误报 + +--- + +### Task 6.4 — 生产构建验证 + +**步骤**: +1. `cd apps/web && pnpm build` — 无 warning/error +2. `cd apps/miniprogram && pnpm build` — 无 warning/error +3. `cargo build --release` — 后端构建通过 +4. 部署到测试环境,执行冒烟测试 + +**通过标准**: 所有构建零 error,冒烟测试关键流程通过 + +--- + +## 总结 + +| Phase | 工期 | Task 数 | 关键产出 | +|-------|------|---------|---------| +| Phase 1 | 1-2天 | 6 | PageContainer / EntityName / DrawerForm / FilterBar / format 工具 | +| Phase 2 | 2-3天 | 7 | 4 角色仪表盘 + 个人工作量 API | +| Phase 3 | 3-4天 | 8 | 8 个列表页统一迁移 | +| Phase 4 | 2-3天 | 4 | 4 个表单升级为 Drawer + 分组 | +| Phase 5 | 3-4天 | 8 | 8 个小程序页面重构 | +| Phase 6 | 1天 | 4 | 暗色/响应式/权限/构建验收 | +| **合计** | **12-17天** | **37** | | + +**依赖关系**: +- Phase 1 是所有后续 Phase 的基础 +- Phase 2(仪表盘)与 Phase 3(列表页)可并行 +- Phase 4(表单)依赖 Phase 3 完成对应页面的迁移 +- Phase 5(小程序)独立于 Web 端,可与 Phase 2-4 并行 +- Phase 6 必须在所有 Phase 完成后执行