feat(monitor): preserve investigation context
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { IconChevronLeft, IconChevronRight, IconClose, IconFilter, IconList, IconMapPin, IconQrCode, IconRefresh, IconSearch } from '@douyinfe/semi-icons';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { memo, useCallback, useDeferredValue, useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { memo, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
import type { Page, VehicleRealtimeRow } from '../../api/types';
|
||||
import { FleetMap } from '../map/FleetMap';
|
||||
@@ -10,6 +10,7 @@ import { VehicleSourceEvidencePanel } from '../shared/VehicleSourceEvidencePanel
|
||||
import { formatNumber, relativeFreshness, statusLabel, vehicleStatus } from '../domain/monitor';
|
||||
import { MAX_MONITOR_SEARCH_TERMS, MONITOR_REFRESH, monitorFilterScope, monitorQueryParams, parseMonitorSearchTerms, useMonitorData, useMonitorVehicleCard, 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'];
|
||||
@@ -178,10 +179,12 @@ const MemoFleetMap = memo(FleetMap);
|
||||
|
||||
function VehicleDetailCard({
|
||||
vehicle,
|
||||
monitorReturn,
|
||||
onCollapse,
|
||||
onClear
|
||||
}: {
|
||||
vehicle: VehicleRealtimeRow;
|
||||
monitorReturn: string;
|
||||
onCollapse: () => void;
|
||||
onClear: () => void;
|
||||
}) {
|
||||
@@ -204,9 +207,10 @@ function VehicleDetailCard({
|
||||
<small>{vehicle.vin}</small>
|
||||
</div>
|
||||
<div className="v2-detail-actions">
|
||||
<Link to={`/vehicles/${encodeURIComponent(vehicle.vin)}`}>单车详情</Link>
|
||||
<Link to={`/tracks?vin=${encodeURIComponent(vehicle.vin)}`}>轨迹回放</Link>
|
||||
<Link to={`/history?vin=${encodeURIComponent(vehicle.vin)}`}>历史数据</Link>
|
||||
<Link to={withMonitorReturn(`/vehicles/${encodeURIComponent(vehicle.vin)}`, monitorReturn)}>单车详情</Link>
|
||||
<Link to={withMonitorReturn(`/tracks?vin=${encodeURIComponent(vehicle.vin)}`, monitorReturn)}>轨迹回放</Link>
|
||||
<Link to={withMonitorReturn(`/history?vin=${encodeURIComponent(vehicle.vin)}`, monitorReturn)}>历史数据</Link>
|
||||
<Link to={withMonitorReturn(`/statistics?vins=${encodeURIComponent(vehicle.vin)}`, monitorReturn)}>里程查询</Link>
|
||||
</div>
|
||||
<section>
|
||||
<h3>车辆信息</h3>
|
||||
@@ -246,23 +250,33 @@ function VehicleDetailCard({
|
||||
}
|
||||
|
||||
export default function MonitorPage() {
|
||||
const [mode, setMode] = useState<'map' | 'list'>('map');
|
||||
const [routeParams, setRouteParams] = useSearchParams();
|
||||
const initialContextRef = useRef(parseMonitorRouteContext(routeParams));
|
||||
const initialContext = initialContextRef.current;
|
||||
const [mode, setMode] = useState<'map' | 'list'>(initialContext.mode);
|
||||
const [mobileEntryOpen, setMobileEntryOpen] = useState(false);
|
||||
const [listOffset, setListOffset] = useState(0);
|
||||
const [listLimit, setListLimit] = useState(50);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [listOffset, setListOffset] = useState(initialContext.listOffset);
|
||||
const [listLimit, setListLimit] = useState(initialContext.listLimit);
|
||||
const [keyword, setKeyword] = useState(initialContext.keyword);
|
||||
const [batchSearchOpen, setBatchSearchOpen] = useState(false);
|
||||
const deferredKeyword = useDeferredValue(keyword);
|
||||
const searchTerms = useMemo(() => parseMonitorSearchTerms(keyword), [keyword]);
|
||||
const [protocol, setProtocol] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
const [selectedVin, setSelectedVin] = useState('');
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
const [viewport, setViewport] = useState<MonitorViewport>({ zoom: 5, bounds: '' });
|
||||
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 [viewport, setViewport] = useState<MonitorViewport>(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);
|
||||
@@ -394,10 +408,12 @@ export default function MonitorPage() {
|
||||
onSelect={selectMapVehicle}
|
||||
onSelectVin={selectVehicle}
|
||||
onViewportChange={updateViewport}
|
||||
initialViewport={initialContext.hasViewport ? initialContext.viewport : undefined}
|
||||
/>
|
||||
{selected && detailOpen ? (
|
||||
<VehicleDetailCard
|
||||
vehicle={selected}
|
||||
monitorReturn={monitorReturn}
|
||||
onCollapse={collapseDetail}
|
||||
onClear={clearSelection}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user