feat(vehicle): live-update single vehicle map

This commit is contained in:
lingniu
2026-07-16 10:20:18 +08:00
parent e0e7e28762
commit 59f220885b
2 changed files with 84 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ import {
import { FormEvent, useMemo, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { LatestTelemetryResponse, QualityIssueRow, VehicleDetail, VehicleProfileSyncItem, VehicleProfileSyncResult } from '../../api/types';
import type { LatestTelemetryResponse, QualityIssueRow, VehicleDetail, VehicleProfileSyncItem, VehicleProfileSyncResult, VehicleRealtimeRow } from '../../api/types';
import { usePlatformSession } from '../auth/AuthGate';
import { canAdminister } from '../auth/session';
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
@@ -23,6 +23,7 @@ function issueTone(issue: QualityIssueRow) { return issue.severity === 'error' ?
function durationHours(seconds?: number | null) { return seconds == null ? '—' : `${formatZhNumber(seconds / 3600, 1)} 小时`; }
function localDateTime(value?: string) { return value ? value.slice(0, 16) : ''; }
const operationStatusLabels = { unknown: '待维护', active: '运营中', inactive: '停运', maintenance: '维保中', retired: '已退役' } as const;
const SINGLE_VEHICLE_REFRESH_MS = 10_000;
function ProfileSyncPanel({ onClose }: { onClose: () => void }) {
const [sourceSystem, setSourceSystem] = useState('');
@@ -170,9 +171,9 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
</section>;
}
function VehicleRecord({ detail, telemetry, telemetryPending, telemetryError, onUpdated }: { detail: VehicleDetail; telemetry?: LatestTelemetryResponse; telemetryPending: boolean; telemetryError?: string; onUpdated: () => void }) {
function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, telemetryError, onUpdated }: { detail: VehicleDetail; liveRealtime?: VehicleRealtimeRow; telemetry?: LatestTelemetryResponse; telemetryPending: boolean; telemetryError?: string; onUpdated: () => void }) {
const { session } = usePlatformSession();
const realtime = detail.realtimeSummary;
const realtime = liveRealtime ?? detail.realtimeSummary;
const identity = detail.identity;
const mapVehicles = realtime ? [realtime] : [];
const lastMileage = detail.mileage.items[0];
@@ -198,10 +199,20 @@ function VehicleRecord({ detail, telemetry, telemetryPending, telemetryError, on
export default function VehiclePage() {
const { vin } = useParams();
const query = useQuery({ queryKey: ['vehicle-detail', vin], enabled: Boolean(vin), queryFn: ({ signal }) => api.vehicleDetail(new URLSearchParams({ keyword: vin!, limit: '20' }), signal), gcTime: QUERY_MEMORY.summaryGcTime });
const resolvedVin = query.data?.lookupResolved ? query.data.vin : '';
const realtime = useQuery({
queryKey: ['vehicle-detail-realtime', resolvedVin],
queryFn: ({ signal }) => api.vehicleRealtime(new URLSearchParams({ keywords: resolvedVin, limit: '1', offset: '0' }), signal),
enabled: Boolean(resolvedVin),
staleTime: 5_000,
gcTime: QUERY_MEMORY.highVolumeGcTime,
refetchInterval: resolvedVin ? SINGLE_VEHICLE_REFRESH_MS : false,
...LIVE_QUERY_POLICY
});
const telemetry = useQuery({ queryKey: ['vehicle-latest-telemetry', vin], enabled: Boolean(vin), queryFn: ({ signal }) => api.latestTelemetry(vin!, signal), staleTime: 10_000, gcTime: QUERY_MEMORY.summaryGcTime, refetchInterval: 20_000, ...LIVE_QUERY_POLICY });
if (!vin) return <VehicleSearch />;
if (query.isPending) return <PageLoading />;
if (query.isError) return <div className="v2-page-error"><InlineError message={query.error instanceof Error ? query.error.message : '车辆档案加载失败'} onRetry={() => query.refetch()} /></div>;
if (!query.data.lookupResolved) return <section className="v2-not-found"><IconSearch size="extra-large" /><h2></h2><p>{vin}VIN </p><Link to="/vehicles"></Link></section>;
return <VehicleRecord detail={query.data} telemetry={telemetry.data} telemetryPending={telemetry.isPending} telemetryError={telemetry.isError ? (telemetry.error instanceof Error ? telemetry.error.message : '请求失败') : undefined} onUpdated={() => { void query.refetch(); void telemetry.refetch(); }} />;
return <VehicleRecord detail={query.data} liveRealtime={realtime.data?.items[0]} telemetry={telemetry.data} telemetryPending={telemetry.isPending} telemetryError={telemetry.isError ? (telemetry.error instanceof Error ? telemetry.error.message : '请求失败') : undefined} onUpdated={() => { void query.refetch(); void realtime.refetch(); void telemetry.refetch(); }} />;
}