import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { Truck, Filter, ChevronDown, Maximize2, Minimize2, RotateCcw, ArrowUp, ArrowDown, ChevronsUp, Download, Check, CalendarDays, RefreshCw, } from 'lucide-react'; import { BarChart, Bar, ResponsiveContainer, Tooltip, ReferenceLine, XAxis } from 'recharts'; import type { MonitoringVehicle, MonitoringStats, MonitoringFilters } from './types'; import { fetchMonitoring } from './api'; import Blur from '../../components/Blur'; import PlateMultiSelect from './PlateMultiSelect'; import { exportMileageXlsx } from './xlsx-export'; import VehicleDetailModal from './VehicleDetailModal'; const HIGH_MILEAGE_ALERT_TARGETS = new Set([ '交投40辆4.5T普货', '交投190辆4.5T冷链车', ]); const HIGH_MILEAGE_ALERT_KM = 800; function defaultMileageDate(): string { const now = new Date(); if (now.getHours() < 5) now.setDate(now.getDate() - 1); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; } function parseOneOsTime(value: string | null | undefined): number | null { if (!value) return null; let normalized = value.trim().replace(' ', 'T'); if (!/(?:Z|[+-]\d{2}:?\d{2})$/i.test(normalized)) normalized += '+08:00'; const timestamp = Date.parse(normalized); return Number.isFinite(timestamp) ? timestamp : null; } function formatAbsoluteTime(value: string | null | undefined): string | null { const timestamp = parseOneOsTime(value); if (timestamp === null) return null; return new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }).format(new Date(timestamp)); } function vehicleStatisticTime(vehicle: MonitoringVehicle, nowMs: number): { label: string; title: string; color: string; } { const primaryTime = vehicle.dataTime || vehicle.updatedAt || vehicle.calculatedAt; const timestamp = parseOneOsTime(primaryTime); if (timestamp === null) { return { label: vehicle.isDataSynced ? '统计时间待接口' : '无有效数据时间', title: 'OneOS 当前未返回车辆级数据时间', color: vehicle.isDataSynced ? 'text-amber-500' : 'text-slate-500', }; } const deltaSeconds = Math.floor((nowMs - timestamp) / 1000); let label: string; if (deltaSeconds < -60) label = '时间异常'; else if (deltaSeconds < 5) label = '刚刚'; else if (deltaSeconds < 60) label = `${deltaSeconds}秒前`; else if (deltaSeconds < 3600) label = `${Math.floor(deltaSeconds / 60)}分钟前`; else if (deltaSeconds < 86400) label = `${Math.floor(deltaSeconds / 3600)}小时前`; else label = `${Math.floor(deltaSeconds / 86400)}天前`; const absoluteDataTime = formatAbsoluteTime(vehicle.dataTime); const absoluteCalculatedAt = formatAbsoluteTime(vehicle.calculatedAt); const absoluteUpdatedAt = formatAbsoluteTime(vehicle.updatedAt); const title = [ absoluteDataTime ? `数据时间:${absoluteDataTime}` : null, absoluteCalculatedAt ? `计算时间:${absoluteCalculatedAt}` : null, absoluteUpdatedAt ? `更新时间:${absoluteUpdatedAt}` : null, ].filter(Boolean).join('\n'); const ageSeconds = Math.max(0, deltaSeconds); const color = ageSeconds <= 300 ? 'text-emerald-500' : ageSeconds <= 3600 ? 'text-blue-500' : ageSeconds <= 86400 ? 'text-amber-500' : 'text-rose-500'; return { label, title, color }; } function normalizeRangeLabel(start: string, end: string): string { if (!start && !end) return '最新数据'; if (start && end && start === end) return start; return `${start || end} 至 ${end || start}`; } function fmtDate(date: Date): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } type RangePreset = 'today' | 'yesterday' | 'thisWeek' | 'thisMonth' | 'last7' | 'last15'; function getRangePreset(preset: RangePreset): { start: string; end: string } { const end = new Date(`${defaultMileageDate()}T00:00:00`); const start = new Date(end); if (preset === 'yesterday') { start.setDate(start.getDate() - 1); end.setDate(end.getDate() - 1); } else if (preset === 'thisWeek') { const day = start.getDay() || 7; start.setDate(start.getDate() - day + 1); } else if (preset === 'thisMonth') { start.setDate(1); } else if (preset === 'last7') { start.setDate(start.getDate() - 6); } else if (preset === 'last15') { start.setDate(start.getDate() - 14); } return { start: fmtDate(start), end: fmtDate(end) }; } const SearchableSelect = ({ options, value, onChange, placeholder }: { options: string[], value: string, onChange: (val: string) => void, placeholder: string }) => { const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(''); const filtered = useMemo(() => { if (!search) return options; return options.filter(opt => opt.toLowerCase().includes(search.toLowerCase())); }, [options, search]); return (
setIsOpen(true)} onChange={(e) => setSearch(e.target.value)} onBlur={() => { // Delay to allow clicking an option setTimeout(() => setIsOpen(false), 200); }} />
{isOpen && (
{ onChange('All'); setSearch(''); setIsOpen(false); }} > 无限制
{filtered.map((opt: string) => (
{ onChange(opt); setSearch(''); setIsOpen(false); }} > {opt === '__EMPTY__' ? '无值' : opt}
))} {filtered.length === 0 && (
无匹配项
)}
)}
); }; const BatchMultiSelect = ({ options, selected, onChange, placeholder, }: { options: string[], selected: string[], onChange: (val: string[]) => void, placeholder: string }) => { const rootRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(''); const selectedSet = useMemo(() => new Set(selected), [selected]); const filtered = useMemo(() => { if (!search) return options; return options.filter(opt => opt.toLowerCase().includes(search.toLowerCase())); }, [options, search]); const label = selected.length === 0 ? placeholder : selected.length === options.length ? '全部批次' : selected.length === 1 ? selected[0] : `已选 ${selected.length} 个批次`; const toggle = (opt: string) => { if (selectedSet.has(opt)) { onChange(selected.filter(item => item !== opt)); } else { onChange([...selected, opt]); } }; useEffect(() => { if (!isOpen) return; const handlePointerDown = (event: PointerEvent) => { const target = event.target; if (target instanceof Node && !rootRef.current?.contains(target)) { setIsOpen(false); setSearch(''); } }; document.addEventListener('pointerdown', handlePointerDown); return () => document.removeEventListener('pointerdown', handlePointerDown); }, [isOpen]); return (
{isOpen && (
setSearch(e.target.value)} />
{filtered.map((opt: string) => { const checked = selectedSet.has(opt); return ( ); })} {filtered.length === 0 && (
无匹配项
)}
)}
); }; export default function MonitoringView() { const [searchTerm, setSearchTerm] = useState(''); const [filterDept, setFilterDept] = useState('All'); const [sortBy, setSortBy] = useState<'today' | 'total'>('today'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [isFilterOpen, setIsFilterOpen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [fullscreenVehicles, setFullscreenVehicles] = useState([]); const [fullscreenStats, setFullscreenStats] = useState({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }); const [fullscreenRefresh, setFullscreenRefresh] = useState(0); const [fullscreenLoading, setFullscreenLoading] = useState(false); // New filters from image const [filterPlates, setFilterPlates] = useState([]); const [filterCustomer, setFilterCustomer] = useState('All'); const [filterProject, setFilterProject] = useState('All'); const [filterEntity, setFilterEntity] = useState('All'); const [filterRentStatus, setFilterRentStatus] = useState('All'); const [filterPlatePrefix, setFilterPlatePrefix] = useState('All'); const [filterTargetNames, setFilterTargetNames] = useState([]); const [filterRegion, setFilterRegion] = useState('All'); const [filterBrands, setFilterBrands] = useState([]); const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' }); const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' }); const [exporting, setExporting] = useState(false); const [detailVehicle, setDetailVehicle] = useState(null); const [rangeStart, setRangeStart] = useState(defaultMileageDate); const [rangeEnd, setRangeEnd] = useState(defaultMileageDate); const [vehicles, setVehicles] = useState([]); const [stats, setStats] = useState({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }); const [filterOptions, setFilterOptions] = useState({ departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [], platePrefixes: [], targetNames: [], regions: [], brands: [] }); const [rangeDailyTotals, setRangeDailyTotals] = useState<{ date: string; totalKm: number }[]>([]); const [effectiveRange, setEffectiveRange] = useState<{ start: string; end: string }>(() => ({ start: defaultMileageDate(), end: defaultMileageDate() })); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [pageLoading, setPageLoading] = useState(true); const [manualRefreshing, setManualRefreshing] = useState(false); const [lastRefreshedAt, setLastRefreshedAt] = useState(null); const [showBackToTop, setShowBackToTop] = useState(false); const [relativeNow, setRelativeNow] = useState(() => Date.now()); const PAGE_SIZE = 50; useEffect(() => { const timer = setInterval(() => setRelativeNow(Date.now()), 5000); return () => clearInterval(timer); }, []); const departments = filterOptions.departments; const plateNumbers = filterOptions.plates; const rangeLabel = normalizeRangeLabel(effectiveRange.start, effectiveRange.end); const isRangeMode = !!effectiveRange.start && !!effectiveRange.end && effectiveRange.start !== effectiveRange.end; const averageDailyKm = rangeDailyTotals.length > 0 ? rangeDailyTotals.reduce((sum, item) => sum + item.totalKm, 0) / rangeDailyTotals.length : 0; const applyRangePreset = useCallback((preset: RangePreset) => { const range = getRangePreset(preset); setRangeStart(range.start); setRangeEnd(range.end); }, []); const isHighMileageAlert = useCallback((v: MonitoringVehicle) => { const inAlertTarget = v.targetNames?.some(name => HIGH_MILEAGE_ALERT_TARGETS.has(name)) || filterTargetNames.some(name => HIGH_MILEAGE_ALERT_TARGETS.has(name)); return inAlertTarget && Math.max(0, v.dailyKm || 0) >= HIGH_MILEAGE_ALERT_KM; }, [filterTargetNames]); // 加载首页数据 const loadFirstPage = useCallback((showPageLoading = true) => { if (showPageLoading) setPageLoading(true); return fetchMonitoring({ sortBy, sortOrder, limit: PAGE_SIZE, page: 1, search: searchTerm || undefined, dept: filterDept !== 'All' ? filterDept : undefined, customer: filterCustomer !== 'All' ? filterCustomer : undefined, project: filterProject !== 'All' ? filterProject : undefined, entity: filterEntity !== 'All' ? filterEntity : undefined, rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined, platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined, targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined, region: filterRegion !== 'All' ? filterRegion : undefined, plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined, mileageMin: appliedMileageRange.min || undefined, mileageMax: appliedMileageRange.max || undefined, startDate: rangeStart || undefined, endDate: rangeEnd || undefined, brands: filterBrands.length > 0 ? filterBrands : undefined, }).then(d => { setVehicles(d.vehicles); setStats(d.stats); setFilterOptions(d.filters); setRangeDailyTotals(d.rangeDailyTotals || []); setEffectiveRange(d.dateRange || { start: rangeStart, end: rangeEnd }); setTotal(d.total); setPage(1); setHasMore(d.page < d.totalPages); setLastRefreshedAt(new Date()); }).catch(() => {}).finally(() => { if (showPageLoading) setPageLoading(false); }); }, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, filterBrands]); const handleManualRefresh = useCallback(async () => { if (manualRefreshing) return; setManualRefreshing(true); try { await loadFirstPage(false); } finally { setManualRefreshing(false); } }, [loadFirstPage, manualRefreshing]); // 加载更多 const loadMore = useCallback(() => { if (loadingMore || !hasMore) return; const nextPage = page + 1; setLoadingMore(true); fetchMonitoring({ sortBy, sortOrder, limit: PAGE_SIZE, page: nextPage, search: searchTerm || undefined, dept: filterDept !== 'All' ? filterDept : undefined, customer: filterCustomer !== 'All' ? filterCustomer : undefined, project: filterProject !== 'All' ? filterProject : undefined, entity: filterEntity !== 'All' ? filterEntity : undefined, rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined, platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined, targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined, region: filterRegion !== 'All' ? filterRegion : undefined, plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined, mileageMin: appliedMileageRange.min || undefined, mileageMax: appliedMileageRange.max || undefined, startDate: rangeStart || undefined, endDate: rangeEnd || undefined, brands: filterBrands.length > 0 ? filterBrands : undefined, }).then(d => { setVehicles(prev => [...prev, ...d.vehicles]); setPage(nextPage); setHasMore(nextPage < d.totalPages); }).catch(() => {}).finally(() => setLoadingMore(false)); }, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, page, loadingMore, hasMore, filterBrands]); // 筛选/排序变化时重新加载 useEffect(() => { loadFirstPage(); }, [loadFirstPage]); // 区域级联:plate 选项收窄后,剔除已选但已不属于该区域的车牌 useEffect(() => { if (filterPlates.length === 0) return; const valid = new Set(filterOptions.plates); const next = filterPlates.filter(p => valid.has(p)); if (next.length !== filterPlates.length) setFilterPlates(next); }, [filterOptions.plates, filterPlates]); // 下载当前筛选结果为 xlsx const handleDownload = useCallback(async () => { if (exporting) return; setExporting(true); try { const d = await fetchMonitoring({ sortBy, sortOrder, limit: 9999, page: 1, search: searchTerm || undefined, dept: filterDept !== 'All' ? filterDept : undefined, customer: filterCustomer !== 'All' ? filterCustomer : undefined, project: filterProject !== 'All' ? filterProject : undefined, entity: filterEntity !== 'All' ? filterEntity : undefined, rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined, platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined, targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined, region: filterRegion !== 'All' ? filterRegion : undefined, plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined, mileageMin: appliedMileageRange.min || undefined, mileageMax: appliedMileageRange.max || undefined, startDate: rangeStart || undefined, endDate: rangeEnd || undefined, brands: filterBrands.length > 0 ? filterBrands : undefined, }); exportMileageXlsx(d.vehicles, { startDate: d.dateRange?.start || rangeStart, endDate: d.dateRange?.end || rangeEnd, sortBy }); } catch (err) { console.error('export failed', err); } finally { setExporting(false); } }, [exporting, sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, filterBrands]); // 每分钟自动刷新 useEffect(() => { const timer = setInterval(() => { void loadFirstPage(false); }, 60 * 1000); return () => clearInterval(timer); }, [loadFirstPage]); // 触底检测:用 IntersectionObserver 监听哨兵元素 const loadMoreRef = useRef(loadMore); loadMoreRef.current = loadMore; const sentinelRef = useRef(null); useEffect(() => { const sentinel = sentinelRef.current; if (!sentinel) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { loadMoreRef.current(); } }, { rootMargin: '200px' } ); observer.observe(sentinel); return () => observer.disconnect(); }, []); // 回到顶部按钮:用 IntersectionObserver 检测顶部哨兵是否离开视口 const topSentinelRef = useRef(null); useEffect(() => { const el = topSentinelRef.current; if (!el) return; const observer = new IntersectionObserver( ([entry]) => setShowBackToTop(!entry.isIntersecting), ); observer.observe(el); return () => observer.disconnect(); }, []); const scrollToTop = () => { window.scrollTo(0, 0); document.documentElement.scrollTop = 0; document.body.scrollTop = 0; }; const filteredVehicles = vehicles; const toggleFullscreen = () => { setIsFullscreen(!isFullscreen); }; // 全屏时加载全部数据(无分页),筛选变化时重新加载 useEffect(() => { if (!isFullscreen) return; setFullscreenLoading(true); fetchMonitoring({ sortBy, sortOrder, limit: 9999, page: 1, search: searchTerm || undefined, dept: filterDept !== 'All' ? filterDept : undefined, customer: filterCustomer !== 'All' ? filterCustomer : undefined, rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined, platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined, targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined, region: filterRegion !== 'All' ? filterRegion : undefined, plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined, startDate: rangeStart || undefined, endDate: rangeEnd || undefined, brands: filterBrands.length > 0 ? filterBrands : undefined, }).then(d => { setFullscreenVehicles(d.vehicles); setFullscreenStats(d.stats); setFilterOptions(d.filters); }).catch(() => {}).finally(() => setFullscreenLoading(false)); }, [isFullscreen, sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, rangeStart, rangeEnd, fullscreenRefresh, filterBrands]); // 全屏时禁止背景滚动 useEffect(() => { if (isFullscreen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [isFullscreen]); // 检测是否在小程序 webview 中(微信/抖音/支付宝等),且当前是竖屏 // 小程序 webview 无法调用系统旋转 API,只能用 CSS rotate 强制横屏 const forceLandscape = useMemo(() => { if (typeof window === 'undefined') return false; const ua = navigator.userAgent || ''; const isMiniProgram = /miniProgram/i.test(ua) || /toutiaomicroapp/i.test(ua) || /AlipayClient/i.test(ua) || (window as any).__wxjs_environment === 'miniprogram'; const isPortrait = window.innerHeight > window.innerWidth; return isMiniProgram && isPortrait; }, [isFullscreen]); return ( <> {/* 顶部哨兵:离开视口时显示回到顶部按钮 */}
{/* Fullscreen Landscape View Overlay */} {isFullscreen && ( {/* Top bar: compact inline KPI */}

全屏监控

区间 {Math.round(fullscreenStats.totalToday).toLocaleString()} km | 累计 {Math.round(fullscreenStats.totalAll).toLocaleString()} km | 车辆 {fullscreenStats.vehicleCount} | {(fullscreenStats.vehicleCount > 0 ? (sortBy === 'today' ? fullscreenStats.totalToday : fullscreenStats.totalAll) / fullscreenStats.vehicleCount : 0).toFixed(0)} km
{/* Batch selector + legend */}
{filterOptions.targetNames.map(n => ( ))}
在线 离线 未对接
{/* Table Area */}
{fullscreenLoading && (
加载中...
)} {fullscreenVehicles.map((v) => { const highMileageAlert = isHighMileageAlert(v); const statisticTime = vehicleStatisticTime(v, relativeNow); return ( ); })}
状态
车牌号 {filterPlates.length === 0 ? '全部' : `已选 ${filterPlates.length}`}
客户 { const val = e.target.value.trim(); setFilterCustomer(val || 'All'); e.target.value = ''; }} onKeyDown={(e) => { if (e.key === 'Enter') { const val = (e.target as HTMLInputElement).value.trim(); setFilterCustomer(val || 'All'); (e.target as HTMLInputElement).value = ''; } }} />
品牌 { const val = e.target.value.trim(); if (val) { setFilterBrands(filterBrands.includes(val) ? filterBrands.filter(b => b !== val) : [...filterBrands, val]); } e.target.value = ''; }} onKeyDown={(e) => { if (e.key === 'Enter') { const val = (e.target as HTMLInputElement).value.trim(); if (val) { setFilterBrands(filterBrands.includes(val) ? filterBrands.filter(b => b !== val) : [...filterBrands, val]); } (e.target as HTMLInputElement).value = ''; } }} />
租赁状态
部门
{ if (sortBy === 'today') { setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc'); } else { setSortBy('today'); setSortOrder('desc'); } }} >
区间里程 {sortBy === 'today' && ( sortOrder === 'desc' ? : )}
{ if (sortBy === 'total') { setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc'); } else { setSortBy('total'); setSortOrder('desc'); } }} >
累计里程 {sortBy === 'total' && ( sortOrder === 'desc' ? : )}
{v.plate}
{statisticTime.label}
{v.customer || '-'} {v.brand || '-'} {v.rentStatus || '-'} {v.department || '-'} {(v.isDataSynced || v.totalKm != null) ? <>{Math.max(0, v.dailyKm || 0).toLocaleString()} km : 未对接} {v.totalKm != null ? <>{v.totalKm.toLocaleString()} km : 未对接}
)}

里程看板

数据监控 • OneOS 实时查询
{lastRefreshedAt ? lastRefreshedAt.toLocaleTimeString('zh-CN', { hour12: false }) : '--:--:--'}
{([ ['today', '今天'], ['yesterday', '昨天'], ['thisWeek', '本周'], ['thisMonth', '本月'], ['last15', '近15天'], ] as Array<[RangePreset, string]>).map(([preset, label]) => { const range = getRangePreset(preset); const active = rangeStart === range.start && rangeEnd === range.end; return ( ); })} {rangeLabel}
{/* Expandable Filter Panel */} {isFilterOpen && (
{/* Date range */}
setRangeStart(e.target.value)} /> setRangeEnd(e.target.value)} />
{([ ['today', '今天'], ['yesterday', '昨天'], ['thisWeek', '本周'], ['thisMonth', '本月'], ['last7', '近7天'], ['last15', '近15天'], ] as Array<[RangePreset, string]>).map(([preset, label]) => ( ))}
{/* Department */}
{/* Customer */}
{/* Brand multi-select */}
{/* Project */}
{/* Rent Status */}
{/* Entity */}
{/* Plate Prefix */}
{/* Mileage Range */}
setFilterMileageRange(prev => ({ ...prev, min: e.target.value }))} />
setFilterMileageRange(prev => ({ ...prev, max: e.target.value }))} />
)}
{/* Active Filter Tags */} {(() => { const tags: { label: string; onClear: () => void }[] = []; if (filterTargetNames.length > 0) tags.push({ label: filterTargetNames.length === filterOptions.targetNames.length ? '批次: 全部批次' : `批次: ${filterTargetNames.length === 1 ? filterTargetNames[0] : `${filterTargetNames[0]} 等${filterTargetNames.length}`}`, onClear: () => setFilterTargetNames([]) }); if (filterRegion !== 'All') tags.push({ label: `区域: ${filterRegion}`, onClear: () => setFilterRegion('All') }); if (filterPlates.length > 0) tags.push({ label: `车牌: ${filterPlates.length === 1 ? filterPlates[0] : `${filterPlates[0]} 等${filterPlates.length}`}`, onClear: () => setFilterPlates([]) }); if (filterRentStatus !== 'All') tags.push({ label: `状态: ${filterRentStatus}`, onClear: () => setFilterRentStatus('All') }); if (filterDept !== 'All') tags.push({ label: `部门: ${filterDept === '__EMPTY__' ? '无值' : filterDept}`, onClear: () => setFilterDept('All') }); if (filterCustomer !== 'All') tags.push({ label: `客户: ${filterCustomer === '__EMPTY__' ? '无值' : filterCustomer}`, onClear: () => setFilterCustomer('All') }); if (filterBrands.length > 0) tags.push({ label: `品牌: ${filterBrands.length === 1 ? filterBrands[0] : `${filterBrands[0]} 等${filterBrands.length}`}`, onClear: () => setFilterBrands([]) }); if (filterProject !== 'All') tags.push({ label: `项目: ${filterProject}`, onClear: () => setFilterProject('All') }); if (filterEntity !== 'All') tags.push({ label: `主体: ${filterEntity}`, onClear: () => setFilterEntity('All') }); if (searchTerm) tags.push({ label: `搜索: ${searchTerm}`, onClear: () => setSearchTerm('') }); if (appliedMileageRange.min) tags.push({ label: `里程≥${appliedMileageRange.min}`, onClear: () => { setFilterMileageRange(prev => ({ ...prev, min: '' })); setAppliedMileageRange(prev => ({ ...prev, min: '' })); } }); if (appliedMileageRange.max) tags.push({ label: `里程≤${appliedMileageRange.max}`, onClear: () => { setFilterMileageRange(prev => ({ ...prev, max: '' })); setAppliedMileageRange(prev => ({ ...prev, max: '' })); } }); if (filterPlatePrefix !== 'All') tags.push({ label: `车牌段: ${filterPlatePrefix}`, onClear: () => setFilterPlatePrefix('All') }); if (rangeStart || rangeEnd) tags.push({ label: `区间: ${normalizeRangeLabel(rangeStart, rangeEnd)}`, onClear: () => { const today = defaultMileageDate(); setRangeStart(today); setRangeEnd(today); } }); if (tags.length === 0) return null; const clearAll = () => { setFilterDept('All'); setFilterCustomer('All'); setFilterRentStatus('All'); setFilterProject('All'); setFilterEntity('All'); setFilterPlates([]); setSearchTerm(''); setFilterPlatePrefix('All'); setFilterTargetNames([]); setFilterRegion('All'); setFilterMileageRange({ min: '', max: '' }); setAppliedMileageRange({ min: '', max: '' }); setFilterBrands([]); const today = defaultMileageDate(); setRangeStart(today); setRangeEnd(today); }; return (
已选条件 {tags.map((tag, i) => ( {tag.label} ))}
); })()} {/* Sticky header: KPI + 清单标题 */}
{sortBy === 'today' ? (isRangeMode ? '区间' : '当日') : '累计'}总里程
{pageLoading ?
: <>{Math.round(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()} km}
{rangeLabel}
平均单车
{pageLoading ?
: (stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)}
km/台
监控台数
{pageLoading ?
: stats.vehicleCount}
{pageLoading ? (
) : isRangeMode ? (
区间走势
{rangeDailyTotals.length} 天 · km
{rangeDailyTotals.length === 0 ? (
暂无趋势
) : ( [`${Number(value ?? 0).toLocaleString('zh-CN', { maximumFractionDigits: 2 })} km`, '当日里程']} labelFormatter={(label) => `日期 ${String(label)}`} contentStyle={{ borderRadius: 10, borderColor: '#e2e8f0', fontSize: 11 }} cursor={{ fill: 'rgba(37, 99, 235, 0.06)' }} /> {averageDailyKm > 0 && } )}
日均
{Math.round(averageDailyKm).toLocaleString()}
km
) : (
单日概览
{rangeLabel} · 单位 km
当日总计
{Math.round(stats.totalToday).toLocaleString()}
日均单车
{stats.vehicleCount > 0 ? Math.round(stats.totalToday / stats.vehicleCount).toLocaleString() : 0}
)}
车辆详情清单 {total} 条
车牌 / 数据状态 客户 / 归属 {isRangeMode ? '区间里程' : '当日里程'} 累计里程
{/* Vehicle List */}
{pageLoading && (
{Array.from({ length: 6 }).map((_, i) => (
))}
)}
{filteredVehicles.map((v) => { const highMileageAlert = isHighMileageAlert(v); const statisticTime = vehicleStatisticTime(v, relativeNow); return ( setDetailVehicle(v)} >
{v.plate} {v.isOnline ? '在线' : '离线'}
{statisticTime.label}
{v.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''} {v.customer || '-'}
{v.customer || '-'}
{[v.rentStatus, v.department?.replace('业务', ''), v.project].filter(Boolean).join(' · ') || '暂无归属信息'}
{Math.max(0, v.dailyKm || 0).toLocaleString()} km
{v.totalKm != null ? v.totalKm.toLocaleString() : '-'} km
{!v.isDataSynced && v.totalKm == null && (
)}
{(v.isDataSynced || v.totalKm != null) ? <>{Math.max(0, v.dailyKm || 0).toLocaleString()} km : 未对接}
{v.totalKm != null ? `${v.totalKm.toLocaleString()} km` : '未对接'}
); })}
{filteredVehicles.length === 0 && !loadingMore && (

未找到匹配数据

)} {/* 加载更多提示 */} {loadingMore && (
加载中...
)} {!hasMore && filteredVehicles.length > 0 && (
已加载全部 {total} 条
)} {/* 哨兵元素:进入视口时触发加载更多 */}
setDetailVehicle(null)} /> {/* 回到顶部按钮 */} {showBackToTop && ( )} ); }