feat(platform): add vehicle detail realtime shortcut
This commit is contained in:
@@ -288,6 +288,20 @@ export default function App() {
|
|||||||
replaceHash('history', nextVin, nextProtocol, { tab: 'raw' });
|
replaceHash('history', nextVin, nextProtocol, { tab: 'raw' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openRealtimeForVehicle = (vin: string, protocol?: string) => {
|
||||||
|
const nextVin = vin.trim();
|
||||||
|
const nextProtocol = protocol?.trim() ?? activeProtocol;
|
||||||
|
if (!nextVin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nextFilters = { keyword: nextVin, protocol: nextProtocol };
|
||||||
|
setAnalysisVin(nextVin);
|
||||||
|
setActiveProtocol(nextProtocol);
|
||||||
|
setRealtimeFilters(nextFilters);
|
||||||
|
setActivePage('realtime');
|
||||||
|
replaceRealtimeHash(nextFilters);
|
||||||
|
};
|
||||||
|
|
||||||
const openMileageForVehicle = (vin: string, protocol?: string) => {
|
const openMileageForVehicle = (vin: string, protocol?: string) => {
|
||||||
const nextVin = vin.trim();
|
const nextVin = vin.trim();
|
||||||
const nextProtocol = protocol?.trim() ?? activeProtocol;
|
const nextProtocol = protocol?.trim() ?? activeProtocol;
|
||||||
@@ -316,7 +330,7 @@ export default function App() {
|
|||||||
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => navigatePage('quality')} onOpenVehicles={openVehicles} />,
|
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => navigatePage('quality')} onOpenVehicles={openVehicles} />,
|
||||||
vehicles: <Vehicles onOpenVehicle={openVehicle} onFiltersChange={updateVehicleFilters} initialFilters={vehicleFilters} />,
|
vehicles: <Vehicles onOpenVehicle={openVehicle} onFiltersChange={updateVehicleFilters} initialFilters={vehicleFilters} />,
|
||||||
realtime: <Realtime onOpenVehicle={openVehicle} onFiltersChange={updateRealtimeFilters} initialFilters={realtimeFilters} />,
|
realtime: <Realtime onOpenVehicle={openVehicle} onFiltersChange={updateRealtimeFilters} initialFilters={realtimeFilters} />,
|
||||||
detail: <VehicleDetail vin={activeVin} protocol={activeProtocol} onOpenHistory={openHistoryForVehicle} onOpenRaw={openRawForVehicle} onOpenMileage={openMileageForVehicle} onOpenVehicles={openVehicles} onQueryChange={updateVehicleDetailQuery} />,
|
detail: <VehicleDetail vin={activeVin} protocol={activeProtocol} onOpenRealtime={openRealtimeForVehicle} onOpenHistory={openHistoryForVehicle} onOpenRaw={openRawForVehicle} onOpenMileage={openMileageForVehicle} onOpenVehicles={openVehicles} onQueryChange={updateVehicleDetailQuery} />,
|
||||||
history: <History initialVin={analysisVin} initialProtocol={activeProtocol} initialTab={historyTab} initialFilters={historyFilters} onFiltersChange={updateHistoryFilters} onOpenVehicle={openVehicle} />,
|
history: <History initialVin={analysisVin} initialProtocol={activeProtocol} initialTab={historyTab} initialFilters={historyFilters} onFiltersChange={updateHistoryFilters} onOpenVehicle={openVehicle} />,
|
||||||
mileage: <Mileage initialVin={analysisVin} initialProtocol={activeProtocol} initialFilters={mileageFilters} onFiltersChange={updateMileageFilters} onOpenVehicle={openVehicle} />,
|
mileage: <Mileage initialVin={analysisVin} initialProtocol={activeProtocol} initialFilters={mileageFilters} onFiltersChange={updateMileageFilters} onOpenVehicle={openVehicle} />,
|
||||||
quality: <Quality onOpenVehicle={openVehicle} onHealthLoaded={(health) => setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} onFiltersChange={updateQualityFilters} initialFilters={qualityFilters} />
|
quality: <Quality onOpenVehicle={openVehicle} onHealthLoaded={(health) => setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} onFiltersChange={updateQualityFilters} initialFilters={qualityFilters} />
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ async function copyVehicleServiceURL() {
|
|||||||
export function VehicleDetail({
|
export function VehicleDetail({
|
||||||
vin,
|
vin,
|
||||||
protocol,
|
protocol,
|
||||||
|
onOpenRealtime,
|
||||||
onOpenHistory,
|
onOpenHistory,
|
||||||
onOpenRaw,
|
onOpenRaw,
|
||||||
onOpenMileage,
|
onOpenMileage,
|
||||||
@@ -75,6 +76,7 @@ export function VehicleDetail({
|
|||||||
}: {
|
}: {
|
||||||
vin: string;
|
vin: string;
|
||||||
protocol?: string;
|
protocol?: string;
|
||||||
|
onOpenRealtime: (vin: string, protocol?: string) => void;
|
||||||
onOpenHistory: (vin: string, protocol?: string) => void;
|
onOpenHistory: (vin: string, protocol?: string) => void;
|
||||||
onOpenRaw: (vin: string, protocol?: string) => void;
|
onOpenRaw: (vin: string, protocol?: string) => void;
|
||||||
onOpenMileage: (vin: string, protocol?: string) => void;
|
onOpenMileage: (vin: string, protocol?: string) => void;
|
||||||
@@ -226,6 +228,7 @@ export function VehicleDetail({
|
|||||||
<Button icon={<IconSearch />} htmlType="submit" theme="solid" type="primary">查询车辆</Button>
|
<Button icon={<IconSearch />} htmlType="submit" theme="solid" type="primary">查询车辆</Button>
|
||||||
<Button icon={<IconRefresh />} onClick={() => load(query)} loading={loading}>刷新</Button>
|
<Button icon={<IconRefresh />} onClick={() => load(query)} loading={loading}>刷新</Button>
|
||||||
<Button icon={<IconCopy />} onClick={copyVehicleServiceURL}>复制服务链接</Button>
|
<Button icon={<IconCopy />} onClick={copyVehicleServiceURL}>复制服务链接</Button>
|
||||||
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRealtime(resolvedVIN, activeProtocol)}>查看实时</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenHistory(resolvedVIN, activeProtocol)}>查看历史</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenHistory(resolvedVIN, activeProtocol)}>查看历史</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRaw(resolvedVIN, activeProtocol)}>查看 RAW</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRaw(resolvedVIN, activeProtocol)}>查看 RAW</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenMileage(resolvedVIN, activeProtocol)}>查看里程</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenMileage(resolvedVIN, activeProtocol)}>查看里程</Button>
|
||||||
|
|||||||
@@ -3236,6 +3236,82 @@ test('opens raw history with the currently selected vehicle detail source', asyn
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('opens realtime with the currently selected vehicle detail source', async () => {
|
||||||
|
window.history.replaceState(null, '', '/#/detail?keyword=VIN001');
|
||||||
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||||
|
const path = String(input);
|
||||||
|
if (path.includes('/api/vehicle-service')) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: {
|
||||||
|
vin: 'VIN001',
|
||||||
|
lookupKey: 'VIN001',
|
||||||
|
lookupResolved: true,
|
||||||
|
resolution: {
|
||||||
|
lookupKey: 'VIN001',
|
||||||
|
resolved: true,
|
||||||
|
vin: 'VIN001',
|
||||||
|
plate: '粤AG18312',
|
||||||
|
phone: '13307795425',
|
||||||
|
oem: 'G7s',
|
||||||
|
protocols: ['GB32960', 'JT808'],
|
||||||
|
online: true,
|
||||||
|
lastSeen: '2026-07-03 20:12:10'
|
||||||
|
},
|
||||||
|
realtimeSummary: {
|
||||||
|
vin: 'VIN001',
|
||||||
|
plate: '粤AG18312',
|
||||||
|
phone: '13307795425',
|
||||||
|
oem: 'G7s',
|
||||||
|
protocols: ['GB32960', 'JT808'],
|
||||||
|
sourceCount: 2,
|
||||||
|
onlineSourceCount: 2,
|
||||||
|
online: true,
|
||||||
|
primaryProtocol: 'GB32960',
|
||||||
|
longitude: 113.2,
|
||||||
|
latitude: 23.1,
|
||||||
|
speedKmh: 30,
|
||||||
|
socPercent: 78,
|
||||||
|
totalMileageKm: 100,
|
||||||
|
lastSeen: '2026-07-03 20:12:10'
|
||||||
|
},
|
||||||
|
sources: ['GB32960', 'JT808'],
|
||||||
|
sourceStatus: [
|
||||||
|
{ protocol: 'GB32960', online: true, lastSeen: '2026-07-03 20:12:10', hasRealtime: true, hasHistory: true, hasRaw: true, hasMileage: true },
|
||||||
|
{ protocol: 'JT808', online: true, lastSeen: '2026-07-03 20:12:08', hasRealtime: true, hasHistory: true, hasRaw: true, hasMileage: true }
|
||||||
|
],
|
||||||
|
realtime: [],
|
||||||
|
history: { items: [], total: 0, limit: 20, offset: 0 },
|
||||||
|
raw: { items: [], total: 0, limit: 10, offset: 0 },
|
||||||
|
mileage: { items: [], total: 0, limit: 20, offset: 0 },
|
||||||
|
quality: { items: [], total: 0, 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(<App />);
|
||||||
|
|
||||||
|
fireEvent.click(await screen.findByRole('button', { name: '仅看 JT808' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '查看实时' }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(window.location.hash).toBe('#/realtime?keyword=VIN001&protocol=JT808');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('opens source-specific history directly from vehicle detail source card', async () => {
|
test('opens source-specific history directly from vehicle detail source card', async () => {
|
||||||
window.history.replaceState(null, '', '/#/detail?keyword=VIN001');
|
window.history.replaceState(null, '', '/#/detail?keyword=VIN001');
|
||||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user