From f93c8d6ccc153f1ea0e8f6207bed595ad3bd17ee Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 07:34:18 +0800 Subject: [PATCH] feat(platform): unify source status tags --- .../web/src/components/SourceStatusTags.tsx | 35 +++++++++++++++++++ .../apps/web/src/pages/Dashboard.tsx | 7 ++-- .../apps/web/src/pages/Vehicles.tsx | 18 ++-------- .../apps/web/src/test/App.test.tsx | 9 +++++ 4 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 vehicle-data-platform/apps/web/src/components/SourceStatusTags.tsx diff --git a/vehicle-data-platform/apps/web/src/components/SourceStatusTags.tsx b/vehicle-data-platform/apps/web/src/components/SourceStatusTags.tsx new file mode 100644 index 00000000..4ad18e9f --- /dev/null +++ b/vehicle-data-platform/apps/web/src/components/SourceStatusTags.tsx @@ -0,0 +1,35 @@ +import { Space, Tag } from '@douyinfe/semi-ui'; +import type { VehicleSourceStatus } from '../api/types'; + +function sourceStatusLabel(source: Pick) { + if (source.online) { + return { text: '在线', color: 'green' as const }; + } + if (source.hasRealtime || source.lastSeen) { + return { text: '离线', color: 'orange' as const }; + } + return { text: '未接入', color: 'grey' as const }; +} + +export function SourceStatusTags({ + sourceStatus, + protocols, + lastSeen +}: { + sourceStatus?: VehicleSourceStatus[]; + protocols: string[]; + lastSeen: string; +}) { + const sources = sourceStatus?.length + ? sourceStatus + : protocols.map((protocol) => ({ protocol, online: false, hasRealtime: true, lastSeen })); + + return ( + + {sources.map((source) => { + const status = sourceStatusLabel(source); + return {source.protocol} {status.text}; + })} + + ); +} diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index 95974e10..fb184e92 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { api } from '../api/client'; import type { DashboardSummary, LinkHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types'; import { PageHeader } from '../components/PageHeader'; +import { SourceStatusTags } from '../components/SourceStatusTags'; import { StatusTag } from '../components/StatusTag'; import { qualityIssueVehicleLookup } from '../domain/vehicleLookup'; @@ -324,11 +325,9 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on { title: 'VIN', dataIndex: 'vin', width: 190 }, { title: '来源证据', - width: 240, + width: 300, render: (_: unknown, row: VehicleCoverageRow) => ( - - {row.protocols.map((protocol) => {protocol})} - + ) }, { diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index 06d92c6b..986d54cb 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -5,6 +5,7 @@ import { api } from '../api/client'; import type { VehicleCoverageRow, VehicleCoverageSummary } from '../api/types'; import { DataEmpty } from '../components/DataEmpty'; import { PageHeader } from '../components/PageHeader'; +import { SourceStatusTags } from '../components/SourceStatusTags'; import { StatusTag } from '../components/StatusTag'; function vehicleServiceStatus(row: VehicleCoverageRow) { @@ -33,16 +34,6 @@ function sourceEvidenceText(row: VehicleCoverageRow) { return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; } -function sourceStatusLabel(source: { online: boolean; hasRealtime: boolean; lastSeen: string }) { - if (source.online) { - return { text: '在线', color: 'green' as const }; - } - if (source.hasRealtime || source.lastSeen) { - return { text: '离线', color: 'orange' as const }; - } - return { text: '未接入', color: 'grey' as const }; -} - function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record) => void) { const consistency = row.sourceConsistency; if (!consistency) { @@ -160,12 +151,7 @@ export function Vehicles({ title: '来源证据', width: 300, render: (_: unknown, row: VehicleCoverageRow) => ( - - {(row.sourceStatus?.length ? row.sourceStatus : row.protocols.map((protocol) => ({ protocol, online: false, hasRealtime: true, lastSeen: row.lastSeen }))).map((source) => { - const status = sourceStatusLabel(source); - return {source.protocol} {status.text}; - })} - + ) }, { diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index a11a84f4..024f5595 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -714,6 +714,12 @@ test('shows row service status in dashboard vehicle previews', async () => { phone: '', oem: 'G7s', protocols: ['GB32960', 'JT808'], + missingProtocols: ['YUTONG_MQTT'], + sourceStatus: [ + { protocol: 'GB32960', online: true, lastSeen: '2026-07-03 20:12:10', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false }, + { protocol: 'JT808', online: false, lastSeen: '2026-07-03 20:10:00', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false }, + { protocol: 'YUTONG_MQTT', online: false, lastSeen: '', hasRealtime: false, hasHistory: false, hasRaw: false, hasMileage: false } + ], sourceCount: 2, onlineSourceCount: 1, online: true, @@ -791,6 +797,9 @@ test('shows row service status in dashboard vehicle previews', async () => { expect(await screen.findByText('VIN-COVERAGE-WARN')).toBeInTheDocument(); expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('GB32960 在线')).toBeInTheDocument(); + expect(screen.getByText('JT808 离线')).toBeInTheDocument(); + expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument(); expect(screen.getAllByText('1/2 来源在线').length).toBeGreaterThanOrEqual(1); expect(screen.getByText('覆盖部分离线')).toBeInTheDocument(); expect(screen.getByText('VIN-REALTIME-OFFLINE')).toBeInTheDocument();