From 0b53abf63d49dae7c8b5bb07461ab8aacf608edd Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 04:15:42 +0800 Subject: [PATCH] feat(platform): label source evidence --- .../apps/web/src/pages/Dashboard.tsx | 14 +++++++++----- .../apps/web/src/pages/Vehicles.tsx | 8 ++++++-- .../apps/web/src/test/App.test.tsx | 4 ++++ vehicle-data-platform/docs/product-spec.md | 2 ++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index b9f46b18..281b2e48 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -51,6 +51,10 @@ function rowServiceStatus(row: { serviceStatus?: { title: string; severity: stri return { label: '服务正常', color: 'green' as const }; } +function sourceEvidenceText(row: { onlineSourceCount: number; sourceCount: number }) { + return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; +} + export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { onOpenVehicle: (vin: string) => void; onOpenQuality: () => void; onOpenVehicles: (filters?: Record) => void }) { const [summary, setSummary] = useState(null); const [serviceSummary, setServiceSummary] = useState(null); @@ -250,7 +254,7 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on { title: '车牌', dataIndex: 'plate', width: 110 }, { title: 'VIN', dataIndex: 'vin', width: 190 }, { - title: '来源', + title: '来源证据', width: 240, render: (_: unknown, row: VehicleCoverageRow) => ( @@ -259,9 +263,9 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on ) }, { - title: '覆盖', - width: 110, - render: (_: unknown, row: VehicleCoverageRow) => `${row.onlineSourceCount}/${row.sourceCount}` + title: '证据覆盖', + width: 130, + render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row) }, { title: '服务状态', @@ -303,7 +307,7 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on { title: '车牌', dataIndex: 'plate' }, { title: 'VIN', dataIndex: 'vin' }, { - title: '来源', + title: '来源证据', render: (_: unknown, row: VehicleRealtimeRow) => ( {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 8a9aaf5c..c78ff706 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -28,6 +28,10 @@ function vehicleServiceStatus(row: VehicleCoverageRow) { return { label: '服务正常', color: 'green' as const }; } +function sourceEvidenceText(row: VehicleCoverageRow) { + return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; +} + export function Vehicles({ onOpenVehicle, onFiltersChange, @@ -100,7 +104,7 @@ export function Vehicles({ } }, { - title: '来源', + title: '来源证据', width: 240, render: (_: unknown, row: VehicleCoverageRow) => ( @@ -108,7 +112,7 @@ export function Vehicles({ ) }, - { title: '覆盖', width: 90, render: (_: unknown, row: VehicleCoverageRow) => `${row.onlineSourceCount}/${row.sourceCount}` }, + { title: '证据覆盖', width: 120, render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row) }, { title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => }, { title: '最后在线', dataIndex: 'lastSeen', width: 170 }, { title: '绑定', width: 90, render: (_: unknown, row: VehicleCoverageRow) => {row.bindingStatus === 'bound' ? '已绑定' : '未绑定'} }, 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 45f1c45b..86ef606c 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -260,6 +260,8 @@ test('shows vehicle service result summary on vehicle list filters', async () => expect(screen.getByText('在线车辆')).toBeInTheDocument(); expect(screen.getByText('待绑定')).toBeInTheDocument(); expect(screen.getByText('VIN-MULTI-001')).toBeInTheDocument(); + expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('1/2 来源在线')).toBeInTheDocument(); }); test('filters vehicle list from result summary actions', async () => { @@ -551,6 +553,8 @@ test('shows row service status in dashboard vehicle previews', async () => { render(); expect(await screen.findByText('VIN-COVERAGE-WARN')).toBeInTheDocument(); + expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('1/2 来源在线').length).toBeGreaterThanOrEqual(1); expect(screen.getByText('覆盖部分离线')).toBeInTheDocument(); expect(screen.getByText('VIN-REALTIME-OFFLINE')).toBeInTheDocument(); expect(screen.getByText('实时车辆离线')).toBeInTheDocument(); diff --git a/vehicle-data-platform/docs/product-spec.md b/vehicle-data-platform/docs/product-spec.md index 705bc776..53d6daa1 100644 --- a/vehicle-data-platform/docs/product-spec.md +++ b/vehicle-data-platform/docs/product-spec.md @@ -24,6 +24,8 @@ Vehicle lists should expose a vehicle-level service status derived from binding Vehicle detail and top search context should expose one source-consistency conclusion, such as `来源一致`, `部分来源离线`, or `里程差异偏大`. This diagnosis comes from `sourceConsistency` and turns GB32960, JT808, and Yutong MQTT into explainable evidence under one vehicle service rather than three competing realtime products. +Vehicle service lists and dashboard previews should label protocol coverage as `来源证据`. The row should make the vehicle service coverage explicit, for example `2/3 来源在线`, while protocol tags remain compact evidence labels underneath the same vehicle row. + ## Interaction Rules - Tables are the default data surface.