perf(tracks): coalesce address lookups

This commit is contained in:
lingniu
2026-07-16 06:22:37 +08:00
parent 41e2b1ab97
commit e6d26faad9
4 changed files with 52 additions and 9 deletions

View File

@@ -7,7 +7,7 @@ import { FormEvent, memo, useCallback, useEffect, useMemo, useRef, useState } fr
import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { TrackPlaybackEvent, TrackPlaybackResponse, VehicleRow } from '../../api/types';
import { buildTrackRailSelection, downloadTrackCsv, formatDuration, sampledEventIndex, trackPlaybackInterval } from '../domain/track';
import { buildTrackRailSelection, downloadTrackCsv, formatDuration, sampledEventIndex, TRACK_ADDRESS_SETTLE_MS, trackAddressCoordinate, trackPlaybackInterval, type TrackAddressCoordinate } from '../domain/track';
import { TrackMap } from '../map/TrackMap';
import { InlineError } from '../shared/AsyncState';
import { QUERY_MEMORY } from '../queryPolicy';
@@ -221,19 +221,21 @@ export default function TrackPage() {
const railSelection = useMemo(() => buildTrackRailSelection(track), [track]);
const activeStopIndexes = railSelection.stopIndexesByPoint[boundedIndex] ?? EMPTY_INDEXES;
const activeEventIndexes = railSelection.eventIndexesByPoint[boundedIndex] ?? EMPTY_INDEXES;
const [addressPoint, setAddressPoint] = useState<{ longitude: number; latitude: number }>();
const currentAddressPoint = useMemo(() => trackAddressCoordinate(current?.longitude, current?.latitude), [current?.latitude, current?.longitude]);
const [addressPoint, setAddressPoint] = useState<TrackAddressCoordinate>();
useEffect(() => {
if (playing || !current) return;
const timer = window.setTimeout(() => setAddressPoint({ longitude: current.longitude, latitude: current.latitude }), 360);
if (playing || !currentAddressPoint) return;
const timer = window.setTimeout(() => setAddressPoint(currentAddressPoint), TRACK_ADDRESS_SETTLE_MS);
return () => window.clearTimeout(timer);
}, [current?.latitude, current?.longitude, playing]);
}, [currentAddressPoint?.key, playing]);
const addressQuery = useQuery({
queryKey: ['track-address', addressPoint?.longitude.toFixed(6), addressPoint?.latitude.toFixed(6)],
queryKey: ['track-address', addressPoint?.key],
enabled: Boolean(addressPoint), staleTime: 60 * 60 * 1000,
queryFn: ({ signal }) => api.reverseGeocode(new URLSearchParams({ longitude: addressPoint!.longitude.toFixed(6), latitude: addressPoint!.latitude.toFixed(6) }), signal),
queryFn: ({ signal }) => api.reverseGeocode(new URLSearchParams({ longitude: addressPoint!.longitude.toFixed(4), latitude: addressPoint!.latitude.toFixed(4) }), signal),
gcTime: QUERY_MEMORY.highVolumeGcTime
});
const addressSettling = Boolean(!playing && currentAddressPoint && currentAddressPoint.key !== addressPoint?.key);
useEffect(() => { setActiveIndex(0); setPlaying(false); setFollow(true); setAddressPoint(undefined); if (track?.points.length) setRailCollapsed(window.matchMedia('(max-width: 700px)').matches); }, [track?.asOf]);
useEffect(() => {
@@ -299,7 +301,7 @@ export default function TrackPage() {
<article className="v2-track-current-card">
<header><div><strong>{track.plate || track.vin}</strong><span>{dateTime(current?.deviceTime)}</span></div><b>{number(progress, 0)}%</b></header>
<div><span><small></small><strong>{number(current?.speedKmh ?? 0)}<em> km/h</em></strong></span><span><small></small><strong>{direction(current?.directionDeg)}</strong></span><span><small>SOC</small><strong>{current?.socAvailable ? `${number(current.socPercent)}%` : '—'}</strong></span></div>
<p title={addressQuery.data?.formattedAddress}>{playing ? '播放中,暂停地址解析' : addressQuery.isFetching ? '地址解析中…' : addressQuery.data?.formattedAddress || `${current?.longitude.toFixed(6)}, ${current?.latitude.toFixed(6)}`}</p>
<p title={addressSettling ? undefined : addressQuery.data?.formattedAddress}>{playing ? '播放中,暂停地址解析' : addressSettling ? '位置已变化,等待地址解析…' : addressQuery.isFetching ? '地址解析中…' : addressQuery.data?.formattedAddress || `${current?.longitude.toFixed(6)}, ${current?.latitude.toFixed(6)}`}</p>
</article>
</> : <div className="v2-track-empty-state"><IconMapPin /><strong></strong><p></p><button type="button" onClick={() => setRailCollapsed(false)}><IconSearch /></button></div>}