Files
hms/apps/miniprogram/src/pages/doctor/alerts/index.tsx
iven 1fd2c7a533 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 个预存失败)。
2026-05-15 01:13:01 +08:00

166 lines
5.6 KiB
TypeScript
Raw 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.
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { View, Text, ScrollView } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { usePageData } from '@/hooks/usePageData';
import { listAlerts, type Alert } from '@/services/doctor/alerts';
import Loading from '@/components/Loading';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { safeNavigateTo } from '@/utils/navigate';
import './index.scss';
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
info: { label: '提示', className: 'alert-severity--info' },
warning: { label: '警告', className: 'alert-severity--warning' },
critical: { label: '严重', className: 'alert-severity--critical' },
urgent: { label: '紧急', className: 'alert-severity--urgent' },
};
const STATUS_MAP: Record<string, { label: string; className: string }> = {
pending: { label: '待处理', className: 'alert-status--pending' },
acknowledged: { label: '已确认', className: 'alert-status--acknowledged' },
resolved: { label: '已恢复', className: 'alert-status--resolved' },
dismissed: { label: '已忽略', className: 'alert-status--dismissed' },
};
const STATUS_TABS = [
{ value: '', label: '全部' },
{ value: 'pending', label: '待处理' },
{ value: 'acknowledged', label: '已确认' },
{ value: 'resolved', label: '已恢复' },
];
export default function AlertList() {
const modeClass = useElderClass();
const [alerts, setAlerts] = useState<Alert[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState('');
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const mountedRef = useRef(false);
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
const loadAlerts = useCallback(async () => {
setLoading(true);
try {
const res = await listAlerts({
status: activeTab || undefined,
page,
page_size: 20,
});
setAlerts(res.data || []);
setTotal(res.total || 0);
} catch {
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {
setLoading(false);
}
}, [activeTab, page]);
const { trigger } = usePageData(loadAlerts);
// tab/page 变化时重新加载(跳过首次 mount由 usePageData 的 useDidShow 处理)
useEffect(() => {
if (mountedRef.current) {
trigger();
}
mountedRef.current = true;
}, [page, activeTab, trigger]);
const handleTabChange = (value: string) => {
setActiveTab(value);
setPage(1);
};
const handleAlertClick = (alert: Alert) => {
safeNavigateTo(`/pages/doctor/alerts/detail/index?id=${alert.id}`);
};
const formatTime = (dateStr: string) => {
const d = new Date(dateStr);
const now = new Date();
const diff = now.getTime() - d.getTime();
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return '刚刚';
if (minutes < 60) return `${minutes}分钟前`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}小时前`;
return d.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
};
if (loading && alerts.length === 0) return <Loading />;
return (
<ScrollView scrollY className={`alert-list-page ${modeClass}`}>
<View className='alert-list-header'>
<Text className='alert-list-title'></Text>
<Text className='alert-list-count'> {total} </Text>
</View>
<View className='alert-tabs'>
{STATUS_TABS.map((tab) => (
<Text
key={tab.value}
className={`alert-tab ${activeTab === tab.value ? 'alert-tab--active' : ''}`}
onClick={() => handleTabChange(tab.value)}
>
{tab.label}
</Text>
))}
</View>
{alerts.length === 0 ? (
<EmptyState description='暂无告警' />
) : (
<View className='alert-cards'>
{alerts.map((alert) => {
const severity = SEVERITY_MAP[alert.severity] ?? SEVERITY_MAP.info;
const status = STATUS_MAP[alert.status] ?? STATUS_MAP.pending;
return (
<View
key={alert.id}
className='alert-card'
onClick={() => handleAlertClick(alert)}
>
<View className='alert-card__header'>
<Text className={`alert-severity ${severity.className}`}>
{severity.label}
</Text>
<Text className={`alert-status ${status.className}`}>
{status.label}
</Text>
</View>
<Text className='alert-card__title'>{alert.title}</Text>
<View className='alert-card__footer'>
<Text className='alert-card__time'>{formatTime(alert.created_at)}</Text>
</View>
</View>
);
})}
</View>
)}
{total > 20 && (
<View className='alert-pagination'>
<Text
className={`alert-pagination__btn ${page <= 1 ? 'disabled' : ''}`}
onClick={() => page > 1 && setPage(page - 1)}
>
</Text>
<Text className='alert-pagination__info'>
{page} / {totalPages}
</Text>
<Text
className={`alert-pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
onClick={() => page < totalPages && setPage(page + 1)}
>
</Text>
</View>
)}
</ScrollView>
);
}