import { IconChevronLeft, IconFilter, IconList, IconMapPin, IconQrCode, IconRefresh, IconSearch } from '@douyinfe/semi-icons'; import { Button, Card, Input, Select, Spin, Table, Tag } from '@douyinfe/semi-ui'; import { useQuery } from '@tanstack/react-query'; import { lazy, memo, Suspense, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { api } from '../../api/client'; import type { Page, VehicleRealtimeRow } from '../../api/types'; import { FleetMap } from '../map/FleetMap'; import { EmptyState, InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState'; import { SegmentedTabs } from '../shared/SegmentedTabs'; import { TablePagination } from '../shared/TablePagination'; import { ProtocolTag } from '../shared/ProtocolTag'; import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; import { formatNumber, statusLabel, vehicleStatus } from '../domain/monitor'; import { useMobileLayout } from '../hooks/useMobileLayout'; import { MAX_MONITOR_SEARCH_TERMS, MONITOR_REFRESH, monitorFilterScope, monitorQueryParams, parseMonitorSearchTerms, useMonitorData, type MonitorViewport } from '../hooks/useMonitorData'; import { LIVE_QUERY_POLICY, QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy'; import { buildMonitorPath, parseMonitorRouteContext, withMonitorReturn } from '../routing/monitorContext'; const protocols = ['', 'GB32960', 'JT808', 'YUTONG_MQTT']; const statuses = ['', 'online', 'offline', 'driving', 'idle', 'no_location']; const EMPTY_VEHICLES: VehicleRealtimeRow[] = []; const MONITOR_VIEW_ITEMS = [ { key: 'map', label: '地图', icon: }, { key: 'list', label: '列表', icon: } ] as const; const VehicleDetailCard = lazy(() => import('./MonitorVehicleDetailCard')); const MonitorCoverageWarning = lazy(() => import('./MonitorCoverageWarning')); const BatchVehicleSearchDialog = lazy(() => import('./BatchVehicleSearchDialog')); type AddressCoordinate = { longitude: number; latitude: number; key: string }; function hasRealtimeLocation(vehicle: VehicleRealtimeRow) { if (vehicle.locationAvailable != null) return vehicle.locationAvailable; return Number.isFinite(vehicle.longitude) && Number.isFinite(vehicle.latitude) && Math.abs(vehicle.longitude) <= 180 && Math.abs(vehicle.latitude) <= 90 && (vehicle.longitude !== 0 || vehicle.latitude !== 0); } function hasRealtimeSpeed(vehicle: VehicleRealtimeRow) { return vehicle.speedAvailable ?? hasRealtimeLocation(vehicle); } function hasRealtimeMileage(vehicle: VehicleRealtimeRow) { return vehicle.mileageAvailable ?? hasRealtimeLocation(vehicle); } function hasTodayMileage(vehicle: VehicleRealtimeRow): vehicle is VehicleRealtimeRow & { todayMileageKm: number } { return vehicle.todayMileageAvailable === true && vehicle.todayMileageKm != null; } function vehicleProtocols(vehicle: VehicleRealtimeRow): string[] { const runtimeValue = (vehicle as VehicleRealtimeRow & { protocols?: unknown }).protocols; const normalized: string[] = []; const seen = new Set(); if (Array.isArray(runtimeValue)) { for (const value of runtimeValue) { if (typeof value !== 'string') continue; const protocol = value.trim(); if (!protocol || seen.has(protocol)) continue; seen.add(protocol); normalized.push(protocol); } } const primaryProtocol = typeof vehicle.primaryProtocol === 'string' ? vehicle.primaryProtocol.trim() : ''; if (!normalized.length && primaryProtocol) normalized.push(primaryProtocol); return normalized; } function vehiclePrimaryProtocol(vehicle: VehicleRealtimeRow, sourceProtocols = vehicleProtocols(vehicle)) { const primaryProtocol = typeof vehicle.primaryProtocol === 'string' ? vehicle.primaryProtocol.trim() : ''; return primaryProtocol || sourceProtocols[0] || ''; } function vehicleProtocolSignature(vehicle: VehicleRealtimeRow) { return vehicleProtocols(vehicle).join('|'); } function formatSupportCount(value: number) { const absolute = Math.abs(value); if (absolute >= 100_000_000) return `${Number((value / 100_000_000).toFixed(1))}亿`; if (absolute >= 10_000) return `${Number((value / 10_000).toFixed(1))}万`; return formatNumber(value); } function hasRealtimeSOC(vehicle: VehicleRealtimeRow) { return vehicle.socAvailable ?? (vehicle.primaryProtocol === 'GB32960' || vehicle.primaryProtocol === 'YUTONG_MQTT'); } function addressCoordinate(vehicle: VehicleRealtimeRow): AddressCoordinate | undefined { if (!hasRealtimeLocation(vehicle) || !Number.isFinite(vehicle.longitude) || !Number.isFinite(vehicle.latitude) || Math.abs(vehicle.longitude) > 180 || Math.abs(vehicle.latitude) > 90 || (vehicle.longitude === 0 && vehicle.latitude === 0)) return undefined; const longitude = Number(vehicle.longitude.toFixed(4)); const latitude = Number(vehicle.latitude.toFixed(4)); return { longitude, latitude, key: `${longitude.toFixed(4)},${latitude.toFixed(4)}` }; } const MonitorAddressCell = memo(function MonitorAddressCell({ vehicle }: { vehicle: VehicleRealtimeRow }) { const current = addressCoordinate(vehicle); const [requested, setRequested] = useState(); const addressQuery = useQuery({ queryKey: ['monitor', 'list-address', requested?.key], queryFn: ({ signal }) => api.reverseGeocode(new URLSearchParams({ longitude: requested!.longitude.toFixed(4), latitude: requested!.latitude.toFixed(4) }), signal), enabled: Boolean(requested), staleTime: 6 * 60 * 60_000, gcTime: QUERY_MEMORY.highVolumeGcTime, refetchOnWindowFocus: false, retry: 1 }); const moved = Boolean(current && requested && current.key !== requested.key); if (!current) return 暂无实时位置; if (!requested) return } aria-label={`解析${vehicle.plate || vehicle.vin}位置`} title="按需调用高德地址解析,不随实时数据刷新" onClick={() => setRequested(current)}>解析位置; if (addressQuery.isFetching && !addressQuery.data) return 解析中; if (addressQuery.isError) return void addressQuery.refetch()}>解析失败,重试; return {addressQuery.data?.formattedAddress || '暂无地址'}{addressQuery.dataUpdatedAt ? `解析于 ${new Date(addressQuery.dataUpdatedAt).toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' })}` : ''}{moved ? setRequested(current)}>位置已移动 · 更新 : null}; }); type MonitorMobileVehicleCardProps = { row: VehicleRealtimeRow; selected: boolean; onSelect: (vin: string) => void; }; const MonitorMobileVehicleCard = memo(function MonitorMobileVehicleCard({ row, selected, onSelect }: MonitorMobileVehicleCardProps) { const identity = row.plate || '未绑定车牌'; const location = hasRealtimeLocation(row); const sourceProtocols = vehicleProtocols(row); const primaryProtocol = vehiclePrimaryProtocol(row, sourceProtocols); return {identity}{row.vin} } aria-label={`在地图中定位${identity}`} onClick={() => onSelect(row.vin)}>定位 速度{hasRealtimeSpeed(row) ? <>{formatNumber(row.speedKmh, 1)} km/h> : '—'} 当日里程{hasTodayMileage(row) ? <>{formatNumber(row.todayMileageKm, 1)} km> : '—'} 总里程{hasRealtimeMileage(row) ? <>{formatNumber(row.totalMileageKm, 1)} km> : '—'} 协议来源{sourceProtocols.length > 1 ? {sourceProtocols.length} 路 : null} {location ? {row.longitude.toFixed(6)}, {row.latitude.toFixed(6)} : 暂无经纬度} ; }, (previous, next) => { const before = previous.row; const after = next.row; return previous.onSelect === next.onSelect && previous.selected === next.selected && before.vin === after.vin && before.plate === after.plate && before.speedAvailable === after.speedAvailable && before.speedKmh === after.speedKmh && before.todayMileageAvailable === after.todayMileageAvailable && before.todayMileageKm === after.todayMileageKm && before.mileageAvailable === after.mileageAvailable && before.totalMileageKm === after.totalMileageKm && before.primaryProtocol === after.primaryProtocol && vehicleProtocolSignature(before) === vehicleProtocolSignature(after) && before.locationAvailable === after.locationAvailable && before.longitude === after.longitude && before.latitude === after.latitude; }); function MonitorVehicleTable({ rows, total, page, totalPages, limit, loading, error, mobile, selectedVin, mobileScrollTop, onMobileScroll, onSelect, onPage, onLimit }: { rows: VehicleRealtimeRow[]; total: number; page: number; totalPages: number; limit: number; loading: boolean; error: boolean; mobile: boolean; selectedVin: string; mobileScrollTop: number; onMobileScroll: (scrollTop: number) => void; onSelect: (vin: string) => void; onPage: (page: number) => void; onLimit: (limit: number) => void }) { const mobileCardsRef = useRef(null); const columns = useMemo(() => [ { title: '车辆', dataIndex: 'plate', width: 210, className: 'v2-monitor-table-vehicle', render: (_value: string, row: VehicleRealtimeRow) => onSelect(row.vin)}>{row.plate || '未绑定车牌'}{row.vin} }, { title: '速度', dataIndex: 'speedKmh', width: 120, render: (_value: number, row: VehicleRealtimeRow) => <>{hasRealtimeSpeed(row) ? formatNumber(row.speedKmh, 1) : '—'}{hasRealtimeSpeed(row) ? km/h : null}> }, { title: '当日里程', dataIndex: 'todayMileageKm', width: 140, render: (_value: number, row: VehicleRealtimeRow) => <>{hasTodayMileage(row) ? formatNumber(row.todayMileageKm, 1) : '—'}{hasTodayMileage(row) ? km : null}> }, { title: '总里程', dataIndex: 'totalMileageKm', width: 170, render: (_value: number, row: VehicleRealtimeRow) => <>{hasRealtimeMileage(row) ? formatNumber(row.totalMileageKm, 1) : '—'}{hasRealtimeMileage(row) ? km : null}> }, { title: '协议来源', dataIndex: 'primaryProtocol', width: 150, render: (_value: string, row: VehicleRealtimeRow) => { const sourceProtocols = vehicleProtocols(row); return {sourceProtocols.length > 1 ? +{sourceProtocols.length - 1} 路 : null}; } }, { title: '经纬度', dataIndex: 'longitude', width: 190, render: (_value: number, row: VehicleRealtimeRow) => hasRealtimeLocation(row) ? {row.longitude.toFixed(6)}{row.latitude.toFixed(6)} : — }, { title: '地理位置', dataIndex: 'vin', render: (_value: string, row: VehicleRealtimeRow) => } ], [onSelect, selectedVin]); useLayoutEffect(() => { if (!mobile || !mobileCardsRef.current) return; mobileCardsRef.current.scrollTop = mobileScrollTop; }, [mobile, mobileScrollTop, selectedVin]); return {!mobile ? row ? ({ className: row.vin === selectedVin ? 'is-selected' : '' }) : ({})} />{loading ? : null}{!loading && !error && !rows.length ? } title="暂无符合条件的车辆" description="调整搜索、协议或在线状态筛选后重试。" /> : null} : null} {mobile ? onMobileScroll(event.currentTarget.scrollTop)}>{rows.map((row) => )}{loading && !rows.length ? : null}{!loading && !error && !rows.length ? } title="暂无符合条件的车辆" description="调整搜索、协议或在线状态筛选后重试。" /> : null} : null} ; } function MobileEntry({ mobile, onClose }: { mobile: boolean; onClose: () => void }) { const [qr, setQr] = useState(''); const [error, setError] = useState(''); const [copyState, setCopyState] = useState<'idle' | 'copied' | 'error'>('idle'); const [attempt, setAttempt] = useState(0); const url = `${window.location.origin}/monitor`; useEffect(() => { if (copyState === 'idle') return; const timer = window.setTimeout(() => setCopyState('idle'), 1_800); return () => window.clearTimeout(timer); }, [copyState]); useEffect(() => { let cancelled = false; setQr(''); setError(''); void import('qrcode') .then(({ default: QRCode }) => QRCode.toDataURL(url, { width: 240, margin: 2, color: { dark: '#122033', light: '#ffffff' } })) .then((value) => { if (!cancelled) setQr(value); }) .catch(() => { if (!cancelled) setError('二维码生成失败,请检查浏览器环境后重试'); }); return () => { cancelled = true; }; }, [attempt, url]); const copyURL = async () => { try { if (!navigator.clipboard?.writeText) throw new Error('clipboard unavailable'); await navigator.clipboard.writeText(url); setCopyState('copied'); } catch { setCopyState('error'); } }; const qrState = qr ? '已生成' : error ? '生成失败' : '生成中'; const copyLabel = copyState === 'copied' ? '已复制访问地址' : copyState === 'error' ? '复制失败,重试' : '复制访问地址'; return } badge="账号鉴权" badgeColor="blue" summaryItems={[ { label: '入口页面', value: '全局监控', detail: '移动端响应式布局', tone: 'primary' }, { label: '凭证安全', value: '不含 Token', detail: '扫码后仍需账号鉴权', tone: 'success' }, { label: '二维码状态', value: qrState, detail: error ? '可在下方重新生成' : '仅在打开入口时生成', tone: error ? 'danger' : qr ? 'success' : 'neutral' } ]} footerNote={url} secondaryActions={[{ label: '关闭', onClick: onClose }]} primaryAction={{ label: copyLabel, loading: !qr && !error, disabled: !qr && !error, onClick: () => void copyURL() }} onCancel={onClose} > 扫码后使用现有账号鉴权,不在二维码中保存 Token。 {qr ? : error ? {error} setAttempt((value) => value + 1)}>重新生成 : } {url} {copyLabel} ; } const VehicleRow = memo(function VehicleRow({ vehicle, selected, onSelect }: { vehicle: VehicleRealtimeRow; selected: boolean; onSelect: (vin: string) => void }) { const status = vehicleStatus(vehicle); return ( onSelect(vehicle.vin)}> {vehicle.plate || '未绑定车牌'}{vehicle.vin} {formatNumber(vehicle.speedKmh, 1)} km/h{statusLabel(status)} ); }); const MemoFleetMap = memo(FleetMap); export default function MonitorPage() { const [routeParams, setRouteParams] = useSearchParams(); const navigate = useNavigate(); const pageRef = useRef(null); const initialContextRef = useRef(parseMonitorRouteContext(routeParams)); const initialContext = initialContextRef.current; const mobileLayout = useMobileLayout(760); const [mode, setMode] = useState<'map' | 'list'>(initialContext.mode); const [mobileEntryOpen, setMobileEntryOpen] = useState(false); const [listOffset, setListOffset] = useState(initialContext.listOffset); const [listLimit, setListLimit] = useState(initialContext.listLimit); const [keyword, setKeyword] = useState(initialContext.keyword); const [batchSearchOpen, setBatchSearchOpen] = useState(false); const [filtersCollapsed, setFiltersCollapsed] = useState(true); const deferredKeyword = useDeferredValue(keyword); const searchTerms = useMemo(() => parseMonitorSearchTerms(keyword), [keyword]); const [protocol, setProtocol] = useState(initialContext.protocol); const [status, setStatus] = useState(initialContext.status); const [selectedVin, setSelectedVin] = useState(initialContext.selectedVin); const [detailOpen, setDetailOpen] = useState(initialContext.detailOpen); const mobileAutoSelectionRef = useRef(''); const mobileInitialFleetReadyRef = useRef(false); const listReturnPendingRef = useRef(false); const listScrollTopRef = useRef(0); const [viewport, setViewport] = useState(initialContext.viewport); const updateViewport = useCallback((next: MonitorViewport) => { setViewport((current) => current.zoom === next.zoom && current.bounds === next.bounds ? current : next); }, []); const filters = useMemo(() => ({ keyword: deferredKeyword, protocol, status }), [deferredKeyword, protocol, status]); const monitorReturn = useMemo(() => buildMonitorPath({ mode, keyword, protocol, status, selectedVin, detailOpen, viewport, listOffset, listLimit }), [detailOpen, keyword, listLimit, listOffset, mode, protocol, selectedVin, status, viewport]); useEffect(() => { const next = new URL(monitorReturn, 'https://vehicle-platform.invalid').searchParams; if (next.toString() !== routeParams.toString()) setRouteParams(next, { replace: true }); }, [monitorReturn, routeParams, setRouteParams]); const listScope = useMemo(() => monitorFilterScope(filters), [filters]); const listParams = useMemo(() => { const params = monitorQueryParams(filters, listLimit); params.set('offset', String(listOffset)); params.set('sort', 'identity'); return params; }, [filters, listLimit, listOffset]); const trackedVin = mode === 'map' ? selectedVin : ''; const { summary, vehicles, map, selectedVehicle } = useMonitorData(filters, viewport, trackedVin, mode === 'map', mode === 'map'); const realtimeListQuery = useQuery>({ queryKey: ['monitor', 'vehicle-list', listScope, listLimit, listOffset], queryFn: ({ signal }) => api.vehicleRealtime(listParams, signal), enabled: mode === 'list', placeholderData: retainPreviousPageWithinScope>(listScope, 2), refetchInterval: mode === 'list' ? MONITOR_REFRESH.fleet : false, gcTime: QUERY_MEMORY.highVolumeGcTime, ...LIVE_QUERY_POLICY }); const rows = useMemo(() => { const data = vehicles.data?.items ?? []; if (status === 'driving' || status === 'idle') return data.filter((vehicle) => vehicleStatus(vehicle) === status); return data; }, [status, vehicles.data?.items]); const listRows = realtimeListQuery.data?.items ?? EMPTY_VEHICLES; const activeBatchRows = mode === 'map' ? rows : listRows; const activeBatchQuery = mode === 'map' ? vehicles : realtimeListQuery; const filterTransitionPending = keyword !== deferredKeyword || activeBatchQuery.isLoading; const batchSearchPending = searchTerms.length > 1 && filterTransitionPending; const batchMatch = useMemo(() => { const identities = new Set(); for (const vehicle of activeBatchRows) { identities.add(vehicle.vin.toLocaleUpperCase()); if (vehicle.plate) identities.add(vehicle.plate.toLocaleUpperCase()); } const missing = searchTerms.filter((term) => !identities.has(term)); return { matched: searchTerms.length - missing.length, missing }; }, [activeBatchRows, searchTerms]); const visibleRows = filterTransitionPending ? [] : rows; const batchStatus = batchSearchPending ? `正在查找 ${searchTerms.length} 辆` : batchMatch.missing.length ? `已找到 ${batchMatch.matched}/${searchTerms.length} · 未找到 ${batchMatch.missing.length} 辆` : `已找到 ${batchMatch.matched}/${searchTerms.length}`; const batchStatusTitle = batchMatch.missing.length ? `未找到:${batchMatch.missing.join('、')}` : searchTerms.join('、'); const visibleListRows = filterTransitionPending ? EMPTY_VEHICLES : listRows; const visibleListTotal = filterTransitionPending ? 0 : (realtimeListQuery.data?.total ?? 0); const selected = selectedVin ? rows.find((vehicle) => vehicle.vin === selectedVin) ?? selectedVehicle.data?.items[0] : undefined; const resetMonitorScroll = useCallback(() => { const scrollOwner = pageRef.current?.closest('.v2-content'); if (scrollOwner) scrollOwner.scrollTop = 0; }, []); useLayoutEffect(() => { if (!mobileLayout) return; resetMonitorScroll(); const frame = window.requestAnimationFrame(resetMonitorScroll); return () => window.cancelAnimationFrame(frame); }, [detailOpen, filtersCollapsed, mobileLayout, resetMonitorScroll, selectedVin]); useLayoutEffect(() => { if (!mobileLayout) { mobileInitialFleetReadyRef.current = false; return; } if (!vehicles.data || mobileInitialFleetReadyRef.current) return; mobileInitialFleetReadyRef.current = true; resetMonitorScroll(); const frame = window.requestAnimationFrame(resetMonitorScroll); return () => window.cancelAnimationFrame(frame); }, [mobileLayout, resetMonitorScroll, vehicles.data]); useLayoutEffect(() => { resetMonitorScroll(); }, [resetMonitorScroll, status]); const selectVehicle = useCallback((vin: string, origin: 'map' | 'list' = 'map') => { listReturnPendingRef.current = mobileLayout && origin === 'list'; resetMonitorScroll(); setSelectedVin(vin); setDetailOpen(true); setMode('map'); }, [mobileLayout, resetMonitorScroll]); const selectListVehicle = useCallback((vin: string) => selectVehicle(vin, 'list'), [selectVehicle]); const clearSelection = useCallback(() => { listReturnPendingRef.current = false; setSelectedVin(''); setDetailOpen(false); }, []); const applyStatusFilter = useCallback((nextStatus: string) => { setStatus(nextStatus); setListOffset(0); clearSelection(); }, [clearSelection]); const selectMapVehicle = useCallback((vehicle: VehicleRealtimeRow) => selectVehicle(vehicle.vin), [selectVehicle]); const collapseDetail = useCallback(() => { setDetailOpen(false); if (mobileLayout && listReturnPendingRef.current) { listReturnPendingRef.current = false; resetMonitorScroll(); setMode('list'); } }, [mobileLayout, resetMonitorScroll]); const clearDetail = useCallback(() => { const returnToList = mobileLayout && listReturnPendingRef.current; clearSelection(); if (returnToList) { resetMonitorScroll(); setMode('list'); } }, [clearSelection, mobileLayout, resetMonitorScroll]); const expandDetail = useCallback(() => setDetailOpen(true), []); const changeMonitorMode = useCallback((nextMode: 'map' | 'list') => { listReturnPendingRef.current = false; resetMonitorScroll(); setMode(nextMode); if (nextMode === 'list') setDetailOpen(false); }, [resetMonitorScroll]); useEffect(() => { const term = searchTerms.length === 1 ? searchTerms[0] : ''; if (!mobileLayout || !term || filterTransitionPending) { if (!term) mobileAutoSelectionRef.current = ''; return; } const exactVehicle = rows.find((vehicle) => vehicle.vin.toLocaleUpperCase() === term || vehicle.plate?.toLocaleUpperCase() === term); if (!exactVehicle || mobileAutoSelectionRef.current === term) return; mobileAutoSelectionRef.current = term; setFiltersCollapsed(true); selectVehicle(exactVehicle.vin); }, [filterTransitionPending, mobileLayout, rows, searchTerms, selectVehicle]); const openVehicleList = useCallback(() => changeMonitorMode('list'), [changeMonitorMode]); const clearFilters = useCallback(() => { setKeyword(''); setProtocol(''); setStatus(''); setListOffset(0); clearSelection(); }, [clearSelection]); const activeFilterCount = Number(Boolean(keyword.trim())) + Number(Boolean(protocol)) + Number(Boolean(status)); const driving = rows.filter((vehicle) => vehicleStatus(vehicle) === 'driving').length; const idle = rows.filter((vehicle) => vehicleStatus(vehicle) === 'idle').length; const offline = rows.filter((vehicle) => vehicleStatus(vehicle) === 'offline').length; const totalVehicleCount = summary.data?.truncated ? vehicles.data?.total ?? summary.data.totalVehicles : summary.data?.totalVehicles ?? vehicles.data?.total ?? rows.length; const onlineVehicleCount = summary.data?.onlineVehicles ?? rows.length - offline; const offlineVehicleCount = summary.data?.offlineVehicles ?? offline; const drivingVehicleCount = summary.data?.drivingVehicles ?? driving; const idleVehicleCount = summary.data?.idleVehicles ?? idle; const alertVehicleCount = summary.data?.alertDataAvailable ? summary.data.alertVehicles : undefined; const monitorCoverageTruncated = Boolean(summary.data?.truncated || map.data?.truncated); const processedVehicleCount = summary.data?.totalVehicles ?? visibleRows.length; const monitorMetrics: WorkspaceQueueMetricRailItem[] = [ { label: activeFilterCount ? '筛选结果' : '车辆总数', value: <>{formatNumber(totalVehicleCount)}辆>, note: activeFilterCount ? `${activeFilterCount} 个条件` : '授权车辆', tone: 'primary', emphasis: 'primary', active: !status, ariaLabel: status ? '清除状态筛选' : '查看全部状态车辆', onClick: () => applyStatusFilter('') }, { label: '当前在线', value: <>{formatNumber(onlineVehicleCount)}辆>, note: '当前可用', tone: 'success', emphasis: 'primary', active: status === 'online', ariaLabel: `筛选当前在线车辆,共 ${formatNumber(onlineVehicleCount)} 辆`, onClick: () => applyStatusFilter('online') }, { label: '行驶车辆', value: <>{formatNumber(drivingVehicleCount)}辆>, note: '实时行驶', tone: 'primary', emphasis: 'primary', active: status === 'driving', ariaLabel: `筛选行驶车辆,共 ${formatNumber(drivingVehicleCount)} 辆`, onClick: () => applyStatusFilter('driving') }, { label: '当前离线', value: <>{formatNumber(offlineVehicleCount)}辆>, note: '无在线来源', tone: 'neutral', emphasis: 'secondary', active: status === 'offline', ariaLabel: `筛选当前离线车辆,共 ${formatNumber(offlineVehicleCount)} 辆`, onClick: () => applyStatusFilter('offline') }, { label: '静止车辆', value: <>{formatNumber(idleVehicleCount)}辆>, note: '在线静止', tone: 'success', emphasis: 'secondary', active: status === 'idle', ariaLabel: `筛选静止车辆,共 ${formatNumber(idleVehicleCount)} 辆`, onClick: () => applyStatusFilter('idle') }, { label: '告警车辆', value: <>{alertVehicleCount == null ? '—' : formatNumber(alertVehicleCount)}辆>, note: alertVehicleCount == null ? '数据未开放' : '需要关注', tone: alertVehicleCount ? 'danger' : 'neutral', emphasis: 'primary', ariaLabel: alertVehicleCount == null ? '打开事件中心' : `打开事件中心核对告警车辆,共 ${formatNumber(alertVehicleCount)} 辆`, onClick: () => navigate(withMonitorReturn('/alerts?status=unprocessed', monitorReturn)) } ]; const mapCoverage = map.data?.mode === 'provinces' ? `${formatNumber(map.data.total)} / ${formatNumber(totalVehicleCount)} 辆有定位 · 省级聚合` : map.data?.mode === 'clusters' ? `${formatNumber(map.data.total)} / ${formatNumber(totalVehicleCount)} 辆有定位 · ${map.data.clusters.length} 个聚合` : `${formatNumber(map.data?.total ?? 0)} / ${formatNumber(totalVehicleCount)} 辆有定位 · ${map.data?.points.length ?? 0} 个车辆点`; const viewportLoad = mode === 'map' ? mapCoverage : `${realtimeListQuery.data?.items.length ?? 0} / ${realtimeListQuery.data?.total ?? 0} 辆`; const lastSyncAt = vehicles.dataUpdatedAt ? new Date(vehicles.dataUpdatedAt) : undefined; return ( 1 ? ' is-batch' : ''}`}> } value={keyword} onChange={(value) => { setKeyword(value); setListOffset(0); clearSelection(); }} onPaste={(event) => { const pastedTerms = parseMonitorSearchTerms(event.clipboardData.getData('text')); if (pastedTerms.length <= 1) return; event.preventDefault(); setKeyword(pastedTerms.join(',')); setListOffset(0); clearSelection(); }} placeholder="车牌 / VIN;可批量粘贴车牌" suffix={ setBatchSearchOpen(true)}>批量} /> {searchTerms.length > 1 ? {batchSearchPending ? `已识别 ${searchTerms.length} 辆` : `已找到 ${batchMatch.matched}/${searchTerms.length}`} : null} 协议 { setProtocol(String(value)); setListOffset(0); clearSelection(); }} aria-labelledby="monitor-protocol-filter-label" optionList={protocols.map((item) => ({ value: item, label: item || '全部协议' }))} /> 在线状态 applyStatusFilter(String(value))} aria-labelledby="monitor-status-filter-label" optionList={statuses.map((item) => ({ value: item, label: item === 'no_location' ? '无实时位置' : item ? statusLabel(item as never) : '全部状态' }))} /> } disabled={activeFilterCount === 0} onClick={clearFilters}>清空 {mobileLayout ? } aria-controls="monitor-filter-fields" aria-expanded={!filtersCollapsed} aria-label={`${filtersCollapsed ? '展开' : '收起'}车辆筛选,当前 ${activeFilterCount} 个条件`} onClick={() => setFiltersCollapsed((value) => !value)} > {filtersCollapsed ? '筛选' : '收起'}{activeFilterCount ? {activeFilterCount} : null} : 自动生效 {activeFilterCount ? {activeFilterCount} : null} } {!mobileLayout ? } aria-label="打开手机端入口" onClick={() => setMobileEntryOpen(true)}>手机端 : null} {batchSearchOpen ? setBatchSearchOpen(false)} onApply={(value) => { setKeyword(value); setListOffset(0); clearSelection(); setBatchSearchOpen(false); }} /> : null} 今日上报{formatSupportCount(summary.data?.frameToday ?? 0)}条数据活跃度 applyStatusFilter('no_location')}>无实时位置{formatNumber(summary.data?.noLocationVehicles ?? 0)}辆辅助排查 } /> {vehicles.isError ? vehicles.refetch()} /> : null} {mode === 'list' && realtimeListQuery.isError ? realtimeListQuery.refetch()} /> : null} {mode === 'map' && monitorCoverageTruncated ? : null} {mode === 'map' ? 车辆列表{batchSearchPending ? '查询中' : `${formatNumber(vehicles.data?.total ?? visibleRows.length)} 辆`} 1 ? batchStatusTitle : undefined}>{searchTerms.length > 1 ? batchStatus : deferredKeyword ? `正在筛选“${deferredKeyword}”` : '最新上报优先'} {vehicles.isLoading || batchSearchPending ? {batchSearchPending ? `正在查找 ${searchTerms.length} 辆车` : '加载车辆'} : null} {!vehicles.isLoading && !vehicles.isError && !batchSearchPending && visibleRows.length === 0 ? : null} {visibleRows.map((vehicle) => )} {selected && detailOpen ? ( }> ) : null} {selected && !detailOpen ? ( {selected.plate || '未绑定车牌'} ) : null} : { listScrollTopRef.current = scrollTop; }} onSelect={selectListVehicle} onPage={(page) => { listScrollTopRef.current = 0; setListOffset((page - 1) * listLimit); }} onLimit={(next) => { listScrollTopRef.current = 0; setListLimit(next); setListOffset(0); }} />} {vehicles.isFetching ? '正在同步' : '数据已同步'} 实时数据{visibleRows.length.toLocaleString('zh-CN')} 辆已载入 {mode === 'map' ? '地图覆盖' : '列表载荷'}{viewportLoad} 智能刷新 重点 {MONITOR_REFRESH.selected / 1000}s · 车队 {MONITOR_REFRESH.fleet / 1000}s · 统计 {MONITOR_REFRESH.summary / 1000}s 最近同步{lastSyncAt ? lastSyncAt.toLocaleTimeString('zh-CN', { hour12: false }) : '等待数据'} {mobileEntryOpen ? setMobileEntryOpen(false)} /> : null} ); }
{row.longitude.toFixed(6)}, {row.latitude.toFixed(6)}
{row.longitude.toFixed(6)}{row.latitude.toFixed(6)}
扫码后使用现有账号鉴权,不在二维码中保存 Token。
{url}