diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx index e48d9399..bb74642f 100644 --- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx +++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx @@ -46,6 +46,12 @@ function formatSeconds(value?: number) { return `${Math.round(value)} 秒`; } +function sourceServiceRole(source: VehicleSourceStatus, primaryProtocol?: string) { + if (source.protocol === primaryProtocol) return { label: '主来源', color: 'blue' as const }; + if (source.online) return { label: '在线证据', color: 'green' as const }; + return { label: '离线证据', color: 'grey' as const }; +} + async function copyVehicleServiceURL() { try { await navigator.clipboard.writeText(`${window.location.origin}${window.location.pathname}${window.location.hash}`); @@ -305,57 +311,72 @@ export function VehicleDetail({ -
- {detail.sourceStatus.map((source) => ( -
-
- - {source.protocol} - - - {source.lastSeen || '无上报'} -
- - {sourceCapabilities.map((item) => ( - - {item.label}{source[item.key] ? '有' : '无'} - - ))} - -
+ ( + {row.protocol} + ) + }, + { + title: '车辆服务角色', + width: 130, + render: (_: unknown, row: VehicleSourceStatus) => { + const role = sourceServiceRole(row, summary?.primaryProtocol); + return {role.label}; + } + }, + { title: '在线', width: 100, render: (_: unknown, row: VehicleSourceStatus) => }, + { title: '最后时间', dataIndex: 'lastSeen', width: 190, render: (value?: string) => value || '无上报' }, + ...sourceCapabilities.map((item) => ({ + title: item.label, + width: 90, + render: (_: unknown, row: VehicleSourceStatus) => {row[item.key] ? '有' : '无'} + })), + { + title: '操作', + width: 330, + render: (_: unknown, row: VehicleSourceStatus) => ( - - - ))} - + ) + } + ]} + /> ) : ( 暂未查询到该车辆的数据来源覆盖。 @@ -377,24 +398,6 @@ export function VehicleDetail({ ]} /> - - -
}, - { title: '最后时间', dataIndex: 'lastSeen', width: 190 }, - { title: '实时', width: 90, render: (_: unknown, row: VehicleSourceStatus) => {row.hasRealtime ? '有' : '无'} }, - { title: '历史', width: 90, render: (_: unknown, row: VehicleSourceStatus) => {row.hasHistory ? '有' : '无'} }, - { title: 'RAW', width: 90, render: (_: unknown, row: VehicleSourceStatus) => {row.hasRaw ? '有' : '无'} }, - { title: '里程', width: 90, render: (_: unknown, row: VehicleSourceStatus) => {row.hasMileage ? '有' : '无'} } - ]} - /> - ) : null} diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index a98ba7f7..f12a60a8 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -318,41 +318,10 @@ body { word-break: break-all; } -.vp-source-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px; -} - .vp-source-toolbar { margin-bottom: 12px; } -.vp-source-card { - min-height: 86px; - padding: 12px; - border: 1px solid var(--vp-border); - border-radius: var(--vp-radius); - background: #fbfcff; -} - -.vp-source-card-active { - border-color: rgba(22, 100, 255, 0.5); - background: rgba(22, 100, 255, 0.06); -} - -.vp-source-card-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 12px; -} - -.vp-source-card-actions { - margin-top: 12px; -} - .vp-realtime-strip { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); 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 fc9857b5..c523f0fc 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -1865,6 +1865,10 @@ test('switches vehicle detail to a source from the coverage cards', async () => render(); expect(await screen.findByText('VIN001')).toBeInTheDocument(); + expect(screen.getByText('车辆服务角色')).toBeInTheDocument(); + expect(screen.getByText('主来源')).toBeInTheDocument(); + expect(screen.getByText('在线证据')).toBeInTheDocument(); + expect(screen.getByText('离线证据')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: '仅看 JT808' })); await waitFor(() => {