import { useCallback, useDeferredValue, useEffect, useMemo, useState } from 'react'; import { AlertTriangle, ChevronLeft, Clock3, Fuel, Maximize2, Minimize2, SlidersHorizontal } from 'lucide-react'; import HydrogenAmapCanvas from './HydrogenAmapCanvas'; import HydrogenHeatmapDetailPanel from './HydrogenHeatmapDetailPanel'; import HydrogenHeatmapFilters from './HydrogenHeatmapFilters'; import { fetchAmapConfig, fetchHydrogenHeatmapMeta, fetchHydrogenHeatmapPoints, fetchNearbyHydrogenStations } from './api'; import { recentDayRange } from '../../shared/date-range'; import type { AmapConfig, HydrogenHeatmapMeta, HydrogenHeatmapMetric, HydrogenHeatmapResponse, HydrogenNearbyResponse, HydrogenPayer } from './types'; // 接口返回数据水位前的兜底范围:近 30 天滚动窗口(不再写死历史区间)。 const FALLBACK_RANGE = recentDayRange(30); const integerFormat = new Intl.NumberFormat('zh-CN'); const kgFormat = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 1 }); function Metric({ value, label }: { value: string; label: string }) { return (

{value}

{label}

); } function metricName(metric: HydrogenHeatmapMetric) { if (metric === 'refuels') return '加氢频次'; if (metric === 'vehicles') return '车辆覆盖'; return '加氢量强度'; } function metricDescription(metric: HydrogenHeatmapMetric) { if (metric === 'refuels') return '每个站点累计有效加氢订单数;平方根平滑强度。'; if (metric === 'vehicles') return '每个站点按车牌去重统计车辆数;平方根平滑强度。'; return '每个站点累计有效加氢量(kg);对数平滑强度。'; } export default function HydrogenHeatmapModule() { const [meta, setMeta] = useState(null); const [config, setConfig] = useState(null); const [data, setData] = useState(null); const [nearby, setNearby] = useState(null); const [startDate, setStartDate] = useState(FALLBACK_RANGE.start); const [endDate, setEndDate] = useState(FALLBACK_RANGE.end); const [query, setQuery] = useState(''); const [payer, setPayer] = useState('all'); const [metric, setMetric] = useState('kg'); const [isFullscreen, setIsFullscreen] = useState(false); const [showFilters, setShowFilters] = useState(() => typeof window === 'undefined' || !window.matchMedia('(max-width: 767px)').matches); const [showDetails, setShowDetails] = useState(() => typeof window === 'undefined' || !window.matchMedia('(max-width: 767px)').matches); const [loading, setLoading] = useState(true); const [nearbyLoading, setNearbyLoading] = useState(false); const [error, setError] = useState(''); const deferredQuery = useDeferredValue(query.trim()); useEffect(() => { if (!isFullscreen) return undefined; const previousOverflow = document.body.style.overflow; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setIsFullscreen(false); }; document.body.style.overflow = 'hidden'; document.addEventListener('keydown', handleKeyDown); return () => { document.body.style.overflow = previousOverflow; document.removeEventListener('keydown', handleKeyDown); }; }, [isFullscreen]); useEffect(() => { const controller = new AbortController(); Promise.all([fetchHydrogenHeatmapMeta(controller.signal), fetchAmapConfig(controller.signal)]) .then(([nextMeta, nextConfig]) => { setMeta(nextMeta); setConfig(nextConfig); setStartDate(nextMeta.startDate); setEndDate(nextMeta.endDate); }) .catch((requestError) => { if (requestError?.name !== 'AbortError') setError('加氢热力图基础配置加载失败'); }); return () => controller.abort(); }, []); useEffect(() => { const controller = new AbortController(); const timer = window.setTimeout(() => { setLoading(true); setError(''); fetchHydrogenHeatmapPoints({ startDate, endDate, query: deferredQuery, payer, metric }, controller.signal) .then((response) => { setData(response); setNearby(null); }) .catch((requestError) => { if (requestError?.name !== 'AbortError') setError('加氢热力图数据加载失败,请稍后重试'); }) .finally(() => setLoading(false)); }, 220); return () => { window.clearTimeout(timer); controller.abort(); }; }, [deferredQuery, endDate, metric, payer, startDate]); const handleMapClick = useCallback((longitude: number, latitude: number) => { const controller = new AbortController(); setNearbyLoading(true); fetchNearbyHydrogenStations({ lng: longitude, lat: latitude, startDate, endDate, query: deferredQuery, payer, metric }, controller.signal) .then(setNearby) .catch((requestError) => { if (requestError?.name !== 'AbortError') setError('区域加氢站明细加载失败'); }) .finally(() => setNearbyLoading(false)); }, [deferredQuery, endDate, metric, payer, startDate]); const reset = () => { setStartDate(meta?.startDate || FALLBACK_RANGE.start); setEndDate(meta?.endDate || FALLBACK_RANGE.end); setQuery(''); setPayer('all'); setMetric('kg'); setNearby(null); }; const coverageText = meta ? `GPS覆盖 ${(meta.gpsCoverageRate * 100).toFixed(3)}% · 已排除 ${integerFormat.format(meta.excludedRefuelCount)} 笔缺坐标数据` : '正在核对站点 GPS 覆盖率'; const detail = useMemo(() => { if (nearby) { return { title: '选中区域', subtitle: `${nearby.center.lng.toFixed(3)}, ${nearby.center.lat.toFixed(3)} · 半径 ${nearby.radiusKm} 公里`, kg: nearby.kg, refuelCount: nearby.refuelCount, vehicleCount: nearby.vehicleCount, stationCount: nearby.stationCount, stations: nearby.topStations, selected: true, }; } return { title: deferredQuery ? '站点筛选结果' : '全国 · 当前范围', subtitle: `${payer === 'all' ? '' : `${payer === 'lingniu' ? '羚牛承担' : '客户承担'} · `}${startDate} ~ ${endDate}`, kg: data?.kg || 0, refuelCount: data?.refuelCount || 0, vehicleCount: data?.vehicleCount || 0, stationCount: data?.stationCount || 0, stations: data?.topStations || [], selected: false, }; }, [data, deferredQuery, endDate, nearby, payer, startDate]); return (

加氢站加氢热力图

加氢账本 × 加氢站 GPS · 上海时区

数据更新至 {meta?.endDate || FALLBACK_RANGE.end}

{config ? :
} {showFilters ? (
setShowFilters(false)} />
) : ( )} {!showDetails ? ( ) : null}
{metricName(metric)}{loading ? : null}

{metricDescription(metric)}

相对较低相对较高
{error ?
{error}
: null}
{showDetails ? ( setNearby(null)} onHide={() => setShowDetails(false)} /> ) : null}
); }