import { useEffect, useState, useRef } from 'react'; import type { ExamClient, ExamModalTab } from '../../data/mockData'; import { EXAM_TAGS } from '../../data/mockData'; import { getTodayExamProgress } from '../../api'; import { Badge, Button, Card, CardContent, CardHeader, InfoCard, Input } from '../ui'; import { cls } from '../../utils/cls'; interface ExamSectionProps { filteredClients: ExamClient[]; selectedExamClient: ExamClient | undefined; examFilterTags: Set<(typeof EXAM_TAGS)[number]>; onFilterChange: (tag: (typeof EXAM_TAGS)[number]) => void; onOpenModal: (id: string, tab: ExamModalTab) => void; searchValue: string; onSearchChange: (value: string) => void; loading?: boolean; } const INITIAL_LOAD_COUNT = 9; // 初始加载数量(3列 x 3行) const LOAD_MORE_COUNT = 9; // 每次加载更多时的数量 export const ExamSection = ({ filteredClients, selectedExamClient, examFilterTags, onFilterChange, onOpenModal, searchValue, onSearchChange, loading = false, }: ExamSectionProps) => { const [progressStats, setProgressStats] = useState([ { label: '预约人数', value: 0 }, { label: '已签到', value: 0 }, { label: '体检中', value: 0 }, { label: '用餐', value: 0 }, ]); const [displayCount, setDisplayCount] = useState(INITIAL_LOAD_COUNT); const observerTarget = useRef(null); const [isLoadingMore, setIsLoadingMore] = useState(false); // 防抖:内部输入值 const [debouncedInputValue, setDebouncedInputValue] = useState(searchValue); const debounceTimerRef = useRef(null); useEffect(() => { getTodayExamProgress({}) .then((res) => { const d = res.Data; setProgressStats([ { label: '预约人数', value: Number(d?.today_appointment_count ?? 0) }, { label: '已签到', value: Number(d?.today_signin_count ?? 0) }, { label: '体检中', value: Number(d?.today_in_exam_count ?? 0) }, { label: '用餐', value: Number(d?.today_meal_count ?? 0) }, ]); }) .catch((err) => { console.error('获取今日体检进度失败', err); }); }, []); // 防抖:当输入值变化时,延迟 0.5 秒后调用 onSearchChange useEffect(() => { if (debounceTimerRef.current) { window.clearTimeout(debounceTimerRef.current); } debounceTimerRef.current = window.setTimeout(() => { onSearchChange(debouncedInputValue); }, 500); return () => { if (debounceTimerRef.current) { window.clearTimeout(debounceTimerRef.current); } }; }, [debouncedInputValue, onSearchChange]); // 当外部 searchValue 变化时(比如清空搜索),同步内部值(仅在值确实不同时更新) useEffect(() => { if (searchValue !== debouncedInputValue && searchValue === '') { // 只在外部清空搜索时同步,避免用户输入时被覆盖 setDebouncedInputValue(''); } }, [searchValue]); // 当 filteredClients 变化时,重置显示数量 useEffect(() => { setDisplayCount(INITIAL_LOAD_COUNT); }, [filteredClients.length, searchValue, examFilterTags]); // 懒加载:使用 Intersection Observer 监听底部元素 useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !loading && !isLoadingMore) { const hasMore = displayCount < filteredClients.length; if (hasMore) { setIsLoadingMore(true); // 模拟加载延迟,让用户看到 loading 效果 setTimeout(() => { setDisplayCount((prev) => Math.min(prev + LOAD_MORE_COUNT, filteredClients.length)); setIsLoadingMore(false); }, 0); } } }, { root: null, rootMargin: '100px', threshold: 0.1, } ); const currentTarget = observerTarget.current; if (currentTarget) { observer.observe(currentTarget); } return () => { if (currentTarget) { observer.unobserve(currentTarget); } }; }, [displayCount, filteredClients.length, loading, isLoadingMore]); const displayedClients = filteredClients.slice(0, displayCount); const hasMore = displayCount < filteredClients.length; return (
今日体检进度
{progressStats.map(({ label, value }) => ( ))}
setDebouncedInputValue(e.target.value)} className='text-sm flex-1' /> {debouncedInputValue && ( )}
体检客户列表
{EXAM_TAGS.map((tag) => ( ))}
{loading && filteredClients.length === 0 ? (
加载中...
) : filteredClients.length === 0 ? (
暂无体检客户数据
请尝试调整筛选条件或搜索关键词
) : ( <>
{displayedClients.map((client) => { const addonCount = client.addonCount || 0; const openModal = (tab: ExamModalTab) => onOpenModal(client.id, tab); return (
openModal('detail')} className={cls( 'text-left p-3 rounded-2xl border bg-white hover:bg-gray-50 flex flex-col gap-1 cursor-pointer', selectedExamClient?.id === client.id && 'border-gray-900 bg-gray-50', )} >
{client.name} {client.familyDoctorName && 家医:{client.familyDoctorName}} {client.level}
套餐:{client.packageName}
渠道:{client.channel ?? ''}
状态:{(client as any).physical_exam_status_name ?? '未知状态'} 已耗时:{client.elapsed}
{/* */}
); })}
{/* 懒加载触发区域和 loading */} {(hasMore || isLoadingMore) && (
{isLoadingMore && (
加载更多...
)}
)} )}
); };