feat(platform): show vehicle location posture
This commit is contained in:
@@ -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) => (
|
||||
|
||||
Reference in New Issue
Block a user