新增 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 个预存失败)。
166 lines
5.6 KiB
TypeScript
166 lines
5.6 KiB
TypeScript
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>
|
||
);
|
||
}
|