refactor(mp): 架构重构 — usePageData 统一数据加载 + Store 解耦 + 大页面拆分

新增 usePageData hook(useDidShow 节流 + usePullDownRefresh + loadingRef 防重入 + enabled 条件守卫),
44/58 页面迁移接入,消灭 4 种数据加载模式并存。

- 新增 hooks/usePageData.ts — 统一页面数据加载生命周期
- 新增 stores/index.ts — resetAllStores() 解耦 auth↔health store 依赖
- 新增 pages/index/useHomeData.ts — 首页数据 hook(424→282 行)
- 新增 pages/health/useHealthData.ts — 健康页数据 hook(422→254 行)
- 44 个页面迁移到 usePageData(9 患者端 + 15 医生端 + 20 子包)
- auth store logout 不再直接导入 health store

构建通过,测试 74/75(1 个预存失败)。
This commit is contained in:
iven
2026-05-15 01:13:01 +08:00
parent 0f58af245d
commit 1fd2c7a533
52 changed files with 791 additions and 664 deletions

View File

@@ -0,0 +1,149 @@
import { useState, useMemo, useRef } from 'react';
import { useHealthStore } from '@/stores/health';
import { useAuthStore } from '@/stores/auth';
import { usePageData } from '@/hooks/usePageData';
import { trackPageView } from '@/services/analytics';
import * as appointmentApi from '@/services/appointment';
import * as followupApi from '@/services/followup';
import { listPendingSuggestions, type AiSuggestionItem } from '@/services/ai-analysis';
import { notificationService } from '@/services/notification';
export interface ReminderItem {
id: string;
text: string;
type: 'ai' | 'appointment' | 'followup';
tag: string;
}
function buildSuggestionText(s: AiSuggestionItem): string {
const riskMap: Record<string, string> = { high: '高风险', medium: '中风险', low: '低风险' };
const typeMap: Record<string, string> = {
vital_sign_anomaly: '体征异常',
lab_result_anomaly: '化验异常',
medication_adherence: '用药提醒',
lifestyle: '生活建议',
};
const risk = riskMap[s.risk_level] || '';
const type = typeMap[s.suggestion_type] || '健康建议';
return `${type}:发现${risk}指标,建议关注`;
}
export function useHomeData() {
const user = useAuthStore((s) => s.user);
const currentPatient = useAuthStore((s) => s.currentPatient);
const todaySummary = useHealthStore((s) => s.todaySummary);
const loading = useHealthStore((s) => s.loading);
const refreshToday = useHealthStore((s) => s.refreshToday);
const [reminders, setReminders] = useState<ReminderItem[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [remindersLoading, setRemindersLoading] = useState(false);
const fetchData = async () => {
const patientId = useAuthStore.getState().currentPatient?.id;
if (!patientId) return;
refreshToday();
loadReminders(patientId);
loadUnread();
trackPageView('home');
};
const { trigger, refresh } = usePageData(fetchData, {
throttleMs: 5000,
enablePullDown: true,
enabled: !!user,
});
const loadUnread = async () => {
try {
const res = await notificationService.getUnreadCount() as { data?: { count?: number } | number };
const d = res.data;
setUnreadCount(typeof d === 'object' && d ? (d.count ?? 0) : 0);
} catch { /* ignore */ }
};
const loadReminders = async (patientId: string) => {
setRemindersLoading(true);
try {
const items: ReminderItem[] = [];
const [apptRes, taskRes, suggestRes] = await Promise.allSettled([
appointmentApi.listAppointments(patientId, 1),
followupApi.listTasks(patientId, 'pending'),
listPendingSuggestions(),
]);
if (suggestRes.status === 'fulfilled') {
for (const s of suggestRes.value.data.slice(0, 1)) {
items.push({ id: s.id, text: buildSuggestionText(s), type: 'ai', tag: 'AI 建议' });
}
}
if (apptRes.status === 'fulfilled') {
for (const a of apptRes.value.data.slice(0, 1)) {
if (a.status === 'pending' || a.status === 'confirmed') {
items.push({
id: a.id,
text: `${a.appointment_date} ${a.start_time}${a.doctor_name || '医护'} ${a.department || '门诊'}`,
type: 'appointment',
tag: '预约',
});
}
}
}
if (taskRes.status === 'fulfilled') {
for (const t of taskRes.value.data.slice(0, 1)) {
items.push({
id: t.id,
text: `${t.follow_up_type} · 截止 ${t.planned_date}`,
type: 'followup',
tag: '随访',
});
}
}
setReminders(items.slice(0, 3));
} catch {
setReminders([]);
} finally {
setRemindersLoading(false);
}
};
const summary = todaySummary || {};
const indicators = [!!summary.blood_pressure, !!summary.heart_rate, !!summary.blood_sugar, !!summary.weight];
const completedCount = indicators.filter(Boolean).length;
const progressPercent = Math.round((completedCount / 4) * 100);
const indicatorCapsules = useMemo(() => [
{ label: '血压', done: !!summary.blood_pressure },
{ label: '心率', done: !!summary.heart_rate },
{ label: '血糖', done: !!summary.blood_sugar },
{ label: '体重', done: !!summary.weight },
], [summary.blood_pressure, summary.heart_rate, summary.blood_sugar, summary.weight]);
const healthItems = useMemo(() => [
{ label: '血压', value: summary.blood_pressure ? `${summary.blood_pressure.systolic}/${summary.blood_pressure.diastolic}` : '—', unit: 'mmHg', status: summary.blood_pressure?.status, indicator: 'systolic_bp_morning' },
{ label: '心率', value: summary.heart_rate ? `${summary.heart_rate.value}` : '—', unit: 'bpm', status: summary.heart_rate?.status, indicator: 'heart_rate' },
{ label: '血糖', value: summary.blood_sugar ? `${summary.blood_sugar.value}` : '—', unit: 'mmol/L', status: summary.blood_sugar?.status, indicator: 'blood_sugar' },
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '—', unit: 'kg', status: summary.weight?.status, indicator: 'weight' },
], [summary.blood_pressure, summary.heart_rate, summary.blood_sugar, summary.weight]);
const hour = new Date().getHours();
const greeting = hour < 12 ? '上午好' : hour < 18 ? '下午好' : '晚上好';
const displayName = user?.display_name || currentPatient?.name || user?.username || (user?.phone ? `${user.phone.slice(-4)}` : '') || '用户';
return {
user,
currentPatient,
todaySummary: summary,
loading,
reminders,
unreadCount,
remindersLoading,
indicatorCapsules,
healthItems,
completedCount,
progressPercent,
greeting,
displayName,
trigger,
refresh,
};
}