From 7f9d327f38a045751db31a756ed68cb0aa902b3c Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 04:18:20 +0800 Subject: [PATCH] feat(platform): expose single-source vehicles --- vehicle-data-platform/apps/web/src/pages/Dashboard.tsx | 1 + vehicle-data-platform/apps/web/src/pages/Vehicles.tsx | 1 + vehicle-data-platform/apps/web/src/test/App.test.tsx | 9 ++++++--- vehicle-data-platform/docs/product-spec.md | 2 ++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index 281b2e48..5a83147a 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -102,6 +102,7 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on const kpis: Array<{ label: string; value: string; filters: Record }> = [ { label: '总车辆', value: formatCount(serviceSummary?.totalVehicles), filters: {} }, { label: '在线车辆', value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles), filters: { online: 'online' } }, + { label: '单源车辆', value: formatCount(serviceSummary?.singleSourceVehicles), filters: { coverage: 'single' } }, { label: '多源车辆', value: formatCount(serviceSummary?.multiSourceVehicles), filters: { coverage: 'multi' } }, { label: '身份未绑定', value: formatCount(serviceSummary?.identityRequiredVehicles), filters: { serviceStatus: 'identity_required' } } ]; diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index c78ff706..9ee68fa6 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -50,6 +50,7 @@ export function Vehicles({ const items: Array<{ label: string; value: string; filters: Record }> = [ { label: '过滤车辆', value: (summary?.totalVehicles ?? pagination.total).toLocaleString(), filters: {} }, { label: '在线车辆', value: (summary?.onlineVehicles ?? 0).toLocaleString(), filters: { online: 'online' } }, + { label: '单源车辆', value: (summary?.singleSourceVehicles ?? 0).toLocaleString(), filters: { coverage: 'single' } }, { label: '多源车辆', value: (summary?.multiSourceVehicles ?? 0).toLocaleString(), filters: { coverage: 'multi' } }, { label: '待绑定', value: (summary?.unboundVehicles ?? 0).toLocaleString(), filters: { bindingStatus: 'unbound' } } ]; 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 86ef606c..21cd2d00 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -117,6 +117,8 @@ test('dashboard renders vehicle service summary metrics', async () => { expect(await screen.findByText('总车辆')).toBeInTheDocument(); expect(screen.getByText('1,033')).toBeInTheDocument(); + expect(screen.getByText('单源车辆')).toBeInTheDocument(); + expect(screen.getByText('391')).toBeInTheDocument(); expect(screen.getByText('多源车辆')).toBeInTheDocument(); expect(screen.getByText('181')).toBeInTheDocument(); expect(screen.getByText('身份未绑定')).toBeInTheDocument(); @@ -169,12 +171,12 @@ test('opens vehicle list filtered by service summary KPI', async () => { render(); - fireEvent.click(await screen.findByRole('button', { name: /多源车辆/ })); + fireEvent.click(await screen.findByRole('button', { name: /单源车辆/ })); await waitFor(() => { - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicles/coverage?limit=20&offset=0&coverage=multi'), undefined); + expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicles/coverage?limit=20&offset=0&coverage=single'), undefined); }); - expect(window.location.hash).toBe('#/vehicles?coverage=multi'); + expect(window.location.hash).toBe('#/vehicles?coverage=single'); }); test('shows vehicle service result summary on vehicle list filters', async () => { @@ -258,6 +260,7 @@ test('shows vehicle service result summary on vehicle list filters', async () => expect(screen.getByText('过滤车辆')).toBeInTheDocument(); expect(screen.getByText('73')).toBeInTheDocument(); expect(screen.getByText('在线车辆')).toBeInTheDocument(); + expect(screen.getByText('单源车辆')).toBeInTheDocument(); expect(screen.getByText('待绑定')).toBeInTheDocument(); expect(screen.getByText('VIN-MULTI-001')).toBeInTheDocument(); expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1); diff --git a/vehicle-data-platform/docs/product-spec.md b/vehicle-data-platform/docs/product-spec.md index 53d6daa1..03c6dfab 100644 --- a/vehicle-data-platform/docs/product-spec.md +++ b/vehicle-data-platform/docs/product-spec.md @@ -26,6 +26,8 @@ Vehicle detail and top search context should expose one source-consistency concl 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. +Dashboard and vehicle-list summary cards should expose both `单源车辆` and `多源车辆`. Single-source vehicles are operationally important because they cannot be cross-checked across sources, so they must be one-click filter targets rather than hidden behind the generic coverage filter. + ## Interaction Rules - Tables are the default data surface.