import { useEffect, useRef, useState } from 'react'; import L from 'leaflet'; interface CaseLocation { case_id: string; case_type: string; latitude: number; longitude: number; district: string; street: string; } const WUHAN_CENTER: [number, number] = [30.59, 114.31]; export function CaseLocationMap({ height = '400px' }: { height?: string }) { const mapRef = useRef(null); const mapInstanceRef = useRef(null); const layerRef = useRef(null); const [isLoading, setIsLoading] = useState(true); const [caseCount, setCaseCount] = useState(0); useEffect(() => { if (!mapRef.current || mapInstanceRef.current) return; const map = L.map(mapRef.current, { center: WUHAN_CENTER, zoom: 11, zoomControl: true, }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', maxZoom: 18, }).addTo(map); mapInstanceRef.current = map; layerRef.current = L.layerGroup().addTo(map); // Fetch case locations fetch('/api/geocoded/geocoded?limit=5000') .then((res) => res.json()) .then((data) => { const cases: CaseLocation[] = data.cases || []; const layer = layerRef.current; if (!layer) return; layer.clearLayers(); // Deduplicate by case_id to avoid overlapping markers const seen = new Set(); const unique: CaseLocation[] = []; for (const c of cases) { if (!seen.has(c.case_id)) { seen.add(c.case_id); unique.push(c); } } for (const c of unique) { if (!c.latitude || !c.longitude) continue; const color = c.case_type === 'inpatient' ? '#ef4444' : '#3b82f6'; const marker = L.circleMarker([c.latitude, c.longitude], { radius: 3, fillColor: color, fillOpacity: 0.6, color: color, weight: 1, }); marker.bindTooltip( `
${c.district} ${c.street}
类型: ${c.case_type === 'inpatient' ? '住院' : '门诊'}
`, { direction: 'top', offset: [0, -4] } ); marker.addTo(layer); } setCaseCount(unique.length); setIsLoading(false); // Fit bounds to case locations if (unique.length > 0) { const bounds = L.latLngBounds(unique.map((c) => [c.latitude, c.longitude])); map.fitBounds(bounds, { padding: [30, 30] }); } }) .catch(() => setIsLoading(false)); return () => { map.remove(); mapInstanceRef.current = null; }; }, []); return (
{isLoading && (
加载病例位置...
)} {!isLoading && (
{caseCount.toLocaleString()} 个病例位置 ● 住院 ● 门诊
)}
); }