diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index e9e98227..a10c1de9 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -1,3 +1,4 @@ +import { IconCopy } from '@douyinfe/semi-icons'; import { Button, Card, Form, Select, Space, Table, Tag, Toast } from '@douyinfe/semi-ui'; import { useEffect, useMemo, useState } from 'react'; import { api } from '../api/client'; @@ -32,6 +33,15 @@ function sourceEvidenceText(row: VehicleCoverageRow) { return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; } +async function copyVehicleShareURL() { + try { + await navigator.clipboard.writeText(`${window.location.origin}${window.location.pathname}${window.location.hash}`); + Toast.success('已复制筛选链接'); + } catch { + Toast.error('复制筛选链接失败'); + } +} + export function Vehicles({ onOpenVehicle, onFiltersChange, @@ -158,6 +168,7 @@ export function Vehicles({ + 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 2e6101b9..c10dc1f0 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -337,6 +337,69 @@ test('filters vehicle list from result summary actions', async () => { expect(window.location.hash).toBe('#/vehicles?online=online'); }); +test('copies shareable vehicle service filter link', async () => { + window.history.replaceState(null, '', '/#/vehicles?keyword=%E7%B2%A4A&coverage=multi&serviceStatus=degraded'); + const writeText = vi.fn(() => Promise.resolve()); + Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: { writeText } + }); + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + const path = String(input); + if (path.includes('/api/ops/health')) { + return { + ok: true, + json: async () => ({ + data: { linkHealth: [], kafkaLag: 0, redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/vehicles/coverage/summary')) { + return { + ok: true, + json: async () => ({ + data: { + totalVehicles: 181, + onlineVehicles: 73, + singleSourceVehicles: 0, + multiSourceVehicles: 181, + noDataVehicles: 12, + unboundVehicles: 9 + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/vehicles/coverage')) { + return { + ok: true, + json: async () => ({ + data: { items: [], total: 181, limit: 20, offset: 0 }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + return { + ok: true, + json: async () => ({ + data: { items: [], total: 0, limit: 10, offset: 0 }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + }); + + render(); + + fireEvent.click(await screen.findByRole('button', { name: /复制筛选链接/ })); + + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('/#/vehicles?keyword=%E7%B2%A4A&coverage=multi&serviceStatus=degraded')); +}); + test('shows resolved vehicle service status after topbar search', async () => { window.history.replaceState(null, '', '/#/dashboard'); const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {