feat(platform): show vehicle location posture

This commit is contained in:
lingniu
2026-07-04 13:58:35 +08:00
parent 1365ce2a30
commit e515a4251e
3 changed files with 167 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ import type { QualityIssueRow, RealtimeLocationRow, VehicleDetail as VehicleDeta
import { PageHeader } from '../components/PageHeader';
import { SourceStatusTags } from '../components/SourceStatusTags';
import { StatusTag } from '../components/StatusTag';
import { VehicleMap, type VehicleMapPoint } from '../components/VehicleMap';
import { buildAppHash } from '../domain/appRoute';
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
import { isLikelyVIN } from '../domain/vehicleLookup';
@@ -182,6 +183,21 @@ export function VehicleDetail({
const lastSeen = resolution?.lastSeen || summary?.lastSeen || latest?.lastSeen || '-';
const serviceStatus = detail?.serviceStatus;
const realtimeRows = detail?.realtime ?? [];
const locatedRealtimeRows = useMemo(() => realtimeRows.filter((row) => (
isFiniteNumber(row.longitude) &&
isFiniteNumber(row.latitude) &&
row.longitude !== 0 &&
row.latitude !== 0
)), [realtimeRows]);
const sourceOnlineByProtocol = useMemo(() => new Map((detail?.sourceStatus ?? []).map((source) => [source.protocol, source.online])), [detail?.sourceStatus]);
const vehicleMapPoints = useMemo<VehicleMapPoint[]>(() => locatedRealtimeRows.map((row) => ({
id: `${row.protocol || 'source'}-${row.lastSeen || row.vin}`,
label: row.protocol || row.vin,
longitude: row.longitude,
latitude: row.latitude,
online: sourceOnlineByProtocol.get(row.protocol) ?? true,
title: `${row.protocol || '来源'} / ${formatCompactNumber(row.speedKmh, ' km/h')}`
})), [locatedRealtimeRows, sourceOnlineByProtocol]);
const latestRealtimeByProtocol = useMemo(() => {
const next = new Map<string, RealtimeLocationRow>();
for (const row of realtimeRows) {
@@ -526,6 +542,41 @@ export function VehicleDetail({
</Card>
) : null}
{hasResolvedVIN && vehicleMapPoints.length > 0 ? (
<Card bordered title="车辆位置态势" style={{ marginTop: 16 }}>
<div className="vp-vehicle-map-layout">
<VehicleMap
points={vehicleMapPoints}
heightClassName="vp-vehicle-detail-map"
maxFallbackPoints={12}
fallbackLabel="高德地图未配置,显示车辆服务坐标预览"
/>
<div className="vp-vehicle-map-side">
<Space wrap>
<Tag color="blue">{locatedRealtimeRows.length.toLocaleString()} </Tag>
<Tag color={activeProtocol ? 'blue' : 'green'}>{scopeText}</Tag>
</Space>
{locatedRealtimeRows.map((row) => (
<div key={`${row.protocol}-${row.lastSeen}-${row.longitude}-${row.latitude}`} className="vp-vehicle-map-source">
<div className="vp-vehicle-map-source-main">
<Typography.Text strong>{row.protocol}</Typography.Text>
<Tag color={sourceOnlineByProtocol.get(row.protocol) === false ? 'orange' : 'green'}>
{row.protocol} / {formatCompactNumber(row.speedKmh, ' km/h')}
</Tag>
</div>
<Typography.Text type="secondary" size="small">
{row.longitude}, {row.latitude}
</Typography.Text>
<Typography.Text type="tertiary" size="small">
{formatCompactNumber(row.totalMileageKm, ' km')} / {row.lastSeen || '-'}
</Typography.Text>
</div>
))}
</div>
</div>
</Card>
) : null}
<Card bordered title="服务处置建议" style={{ marginTop: 16 }}>
<div className="vp-action-grid">
{serviceActions.map((item) => (