feat(platform): consolidate production vehicle data workflows

This commit is contained in:
lingniu
2026-07-15 23:26:29 +08:00
parent 6cddc0a43d
commit 3fabcf181a
59 changed files with 6849 additions and 3532 deletions

View File

@@ -1,7 +1,9 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { VehicleRealtimeRow } from '../../api/types';
import { api } from '../../api/client';
import MonitorPage from './MonitorPage';
const vehicles = [{
@@ -26,7 +28,18 @@ vi.mock('../map/FleetMap', () => ({
}));
vi.mock('../hooks/useMonitorData', () => ({
MAX_MONITOR_SEARCH_TERMS: 100,
MONITOR_REFRESH: { selected: 10_000, fleet: 15_000, summary: 30_000 },
parseMonitorSearchTerms: (value: string) => Array.from(new Set(value.split(/[\s,,、;]+/).map((item) => item.trim().toLocaleUpperCase()).filter(Boolean))).slice(0, 100),
monitorQueryParams: (filters: { keyword: string; protocol: string; status: string }, limit: number) => {
const params = new URLSearchParams({ limit: String(limit) });
const terms = Array.from(new Set(filters.keyword.split(/[\s,,、;]+/).map((item) => item.trim().toLocaleUpperCase()).filter(Boolean))).slice(0, 100);
if (terms.length === 1) params.set('keyword', terms[0]);
if (terms.length > 1) params.set('keywords', terms.join(','));
if (filters.protocol) params.set('protocol', filters.protocol);
if (filters.status) params.set('status', filters.status);
return params;
},
useMonitorData: () => ({
summary: { data: { totalVehicles: 2, onlineVehicles: 2, offlineVehicles: 0, drivingVehicles: 1, idleVehicles: 1, frameToday: 10 } },
vehicles: { data: { items: vehicles, total: 2 }, isError: false, isLoading: false, isFetching: false },
@@ -39,7 +52,8 @@ vi.mock('../hooks/useMonitorData', () => ({
afterEach(cleanup);
test('starts without a selection and supports expand, collapse, reselection, and clear', () => {
const view = render(<MemoryRouter><MonitorPage /></MemoryRouter>);
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const view = render(<QueryClientProvider client={queryClient}><MemoryRouter><MonitorPage /></MemoryRouter></QueryClientProvider>);
const workspace = view.container.querySelector('.v2-monitor-workspace')!;
const firstVehicle = screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ });
const secondVehicle = screen.getByRole('button', { name: /粤B67890 LTEST000000000002/ });
@@ -78,3 +92,45 @@ test('starts without a selection and supports expand, collapse, reselection, and
expect(workspace).toHaveClass('is-detail-open');
expect(secondVehicle).toHaveClass('is-selected');
});
test('switches to a lightweight realtime list and resolves addresses only on demand', async () => {
const row = vehicles[0];
vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [row], total: 1, limit: 50, offset: 0 });
const reverseGeocode = vi.spyOn(api, 'reverseGeocode').mockResolvedValue({ provider: 'AMap', longitude: 113.26, latitude: 23.13, formattedAddress: '广东省广州市天河区测试路' });
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={queryClient}><MemoryRouter><MonitorPage /></MemoryRouter></QueryClientProvider>);
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
expect(await screen.findByText('车辆实时列表')).toBeInTheDocument();
expect(screen.getByText('速度、里程和坐标实时刷新,文字地址按需解析')).toBeInTheDocument();
expect(await screen.findAllByText('粤A12345')).toHaveLength(2);
expect(screen.getAllByText('42')).toHaveLength(2);
expect(screen.getAllByText(/1,234/)).toHaveLength(2);
expect(screen.getAllByText(/113\.260000/)).toHaveLength(2);
expect(reverseGeocode).not.toHaveBeenCalled();
fireEvent.click(screen.getAllByRole('button', { name: '解析粤A12345位置' })[0]);
expect(await screen.findByText('广东省广州市天河区测试路')).toBeInTheDocument();
expect(reverseGeocode).toHaveBeenCalledTimes(1);
expect(screen.queryByText('综合状态')).not.toBeInTheDocument();
expect(screen.queryByText('三路正常')).not.toBeInTheDocument();
expect(screen.queryByTestId('fleet-map')).not.toBeInTheDocument();
});
test('pastes, deduplicates, and submits multiple plates as one batch search', async () => {
const vehicleRealtime = vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 });
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={queryClient}><MemoryRouter><MonitorPage /></MemoryRouter></QueryClientProvider>);
const input = screen.getByRole('textbox', { name: '搜索车辆' });
fireEvent.paste(input, { clipboardData: { getData: () => '粤a12345\n粤B67890粤A12345' } });
expect(input).toHaveValue('粤A12345粤B67890');
expect(screen.getByText('已识别 2 辆')).toBeInTheDocument();
expect(screen.getByText('正在批量筛选 2 辆')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
await waitFor(() => {
const params = vehicleRealtime.mock.calls[vehicleRealtime.mock.calls.length - 1]?.[0];
expect(params?.get('keywords')).toBe('粤A12345,粤B67890');
expect(params?.has('keyword')).toBe(false);
});
});