feat(web): add vehicle directory views

This commit is contained in:
lingniu
2026-07-20 07:58:45 +08:00
parent 3c9eea527c
commit 19b187d942
4 changed files with 185 additions and 21 deletions

View File

@@ -196,8 +196,10 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
const view = render(<QueryClientProvider client={client}><MemoryRouter initialEntries={['/vehicles']} future={ROUTER_FUTURE}><Routes><Route path="/vehicles" element={<VehiclePage />} /><Route path="/vehicles/:vin" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
expect(await screen.findByRole('heading', { name: '车辆快速定位', level: 5 })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '查找车辆', level: 5 })).toBeInTheDocument();
expect(screen.getByLabelText('查车操作')).toHaveClass('v2-workspace-command-bar', 'v2-vehicle-command-bar');
expect(screen.getByRole('tablist', { name: '车辆目录视图' })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: '全部车辆' })).toHaveAttribute('aria-selected', 'true');
expect(screen.getByRole('heading', { name: '车辆定位', level: 5 }).closest('.semi-card')).toHaveClass('v2-vehicle-query-panel', 'v2-workspace-filter-panel');
const input = screen.getByRole('textbox', { name: '搜索车辆' });
expect(input).toHaveAttribute('aria-expanded', 'false');
@@ -210,10 +212,10 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
const directoryMetrics = screen.getByRole('list', { name: '车辆目录摘要' });
expect(directoryMetrics.closest('.v2-vehicle-directory-metric-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
expect(Array.from(directoryMetrics.querySelectorAll(':scope > [role="listitem"] > small')).map((item) => item.textContent)).toEqual(['授权车辆', '本页在线', '多源车辆']);
expect(Array.from(directoryMetrics.querySelectorAll(':scope > [role="listitem"] > small')).map((item) => item.textContent)).toEqual(['授权车辆', '本页在线', '本页多源']);
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(1)')).toHaveTextContent('授权车辆2辆账号范围');
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(2)')).toHaveTextContent('本页在线1辆本页 1 辆');
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(3)')).toHaveTextContent('多源车辆0辆2+ 协议来源');
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(3)')).toHaveTextContent('本页多源0辆2+ 协议来源');
expect(screen.getByText('第 1 / 1 页').closest('.semi-tag')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '刷新车辆目录' })).toHaveClass('semi-button');
expect(view.container.querySelector('.v2-vehicle-directory-table.semi-table-wrapper')).toBeInTheDocument();
@@ -280,6 +282,8 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi
expect(view.container.querySelector('.v2-vehicle-directory-table')).not.toBeInTheDocument();
expect(view.container.querySelector('.v2-vehicle-query-panel')).not.toBeInTheDocument();
expect(screen.queryByLabelText('查车操作')).not.toBeInTheDocument();
expect(screen.getByRole('tablist', { name: '车辆目录视图' })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: '全部车辆' })).toHaveAttribute('aria-selected', 'true');
const toggle = screen.getByRole('button', { name: '打开车辆搜索10 辆授权车辆' });
expect(toggle).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(toggle);
@@ -310,6 +314,50 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi
expect(screen.getByRole('heading', { name: '授权车辆', level: 5 })).toBeInTheDocument();
});
test('filters the authorized directory with one consistent Semi view switcher', async () => {
const candidate = {
vin: initialRealtime.vin,
plate: initialRealtime.plate,
phone: '',
oem: '测试品牌',
protocols: ['JT808', 'GB32960'],
missingProtocols: [],
sourceStatus: [],
sourceCount: 2,
onlineSourceCount: 2,
online: true,
lastSeen: initialRealtime.lastSeen,
locationText: '广东省广州市',
bindingScore: 100,
bindingStatus: 'bound'
};
const vehicles = vi.spyOn(api, 'vehicleCoverage').mockResolvedValue({ items: [candidate], total: 1, limit: 10, offset: 0 });
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter initialEntries={['/vehicles']} future={ROUTER_FUTURE}><Routes><Route path="/vehicles" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
await screen.findByText(initialRealtime.plate);
const viewTabs = screen.getByRole('tablist', { name: '车辆目录视图' });
expect(viewTabs).toHaveTextContent('全部车辆当前在线当前离线多源车辆');
fireEvent.click(screen.getByRole('tab', { name: '当前在线' }));
await waitFor(() => expect(vehicles.mock.calls[vehicles.mock.calls.length - 1]?.[0]?.get('online')).toBe('online'));
expect(screen.getByRole('tab', { name: '当前在线' })).toHaveAttribute('aria-selected', 'true');
expect(screen.getByRole('heading', { name: '当前在线', level: 5 })).toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: '当前离线' }));
await waitFor(() => expect(vehicles.mock.calls[vehicles.mock.calls.length - 1]?.[0]?.get('online')).toBe('offline'));
fireEvent.click(screen.getByRole('tab', { name: '多源车辆' }));
await waitFor(() => expect(vehicles.mock.calls[vehicles.mock.calls.length - 1]?.[0]?.get('coverage')).toBe('multi'));
fireEvent.click(screen.getByRole('tab', { name: '全部车辆' }));
expect(screen.getByRole('tab', { name: '全部车辆' })).toHaveAttribute('aria-selected', 'true');
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
expect(vehicles.mock.calls[0]?.[0]?.has('online')).toBe(false);
expect(vehicles.mock.calls[0]?.[0]?.has('coverage')).toBe(false);
});
test('keeps a partial vehicle query in the directory instead of navigating to an invalid detail route', async () => {
const candidate = {
vin: initialRealtime.vin,