feat(platform): add parsed fields history view

This commit is contained in:
lingniu
2026-07-04 17:31:44 +08:00
parent 747894ce1d
commit 2d4b397acd
4 changed files with 221 additions and 16 deletions

View File

@@ -5335,6 +5335,83 @@ test('shows and clears current history filters while keeping vehicle scope', asy
expect(window.location.hash).toBe('#/history-query?keyword=VIN-HISTORY-001&tab=raw');
});
test('shows parsed field history as a flattened evidence table', async () => {
window.history.replaceState(null, '', '/#/history-query?keyword=VIN-FIELDS-001&protocol=GB32960&tab=fields&fields=gb32960.vehicle.speed_kmh');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
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/history/locations')) {
return {
ok: true,
json: async () => ({
data: { items: [], total: 0, limit: 10, offset: 0 },
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response;
}
if (path.includes('/api/history/raw-frames/query')) {
return {
ok: true,
json: async () => ({
data: {
items: [{
id: 'raw-field-001',
vin: 'VIN-FIELDS-001',
plate: '粤A字段1',
protocol: 'GB32960',
frameType: 'realtime',
rawSizeBytes: 256,
deviceTime: '2026-07-03 11:56:06',
serverTime: '2026-07-03 11:56:07',
parsedFields: {
'gb32960.vehicle.speed_kmh': 42,
'gb32960.vehicle.soc_percent': 78.4
}
}],
total: 1,
limit: 10,
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 />);
expect(await screen.findByRole('heading', { name: '历史查询' })).toBeInTheDocument();
expect(await screen.findByRole('tab', { name: '解析字段' })).toHaveAttribute('aria-selected', 'true');
expect(fetchMock).toHaveBeenCalledWith('/api/history/raw-frames/query', expect.objectContaining({
method: 'POST',
body: expect.stringContaining('"includeFields":true')
}));
expect(screen.getAllByText('gb32960.vehicle.speed_kmh').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('gb32960.vehicle.soc_percent')).toBeInTheDocument();
expect(screen.getByText('42')).toBeInTheDocument();
expect(screen.getByText('78.4')).toBeInTheDocument();
expect(screen.getByText('当前页 2 个字段')).toBeInTheDocument();
});
test('updates history hash when vehicle history filters are submitted', async () => {
window.history.replaceState(null, '', '/#/history');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
@@ -5898,7 +5975,7 @@ test('exports current raw frame page as csv with parsed fields', async () => {
render(<App />);
expect(await screen.findByText('raw-export-001')).toBeInTheDocument();
expect((await screen.findAllByText('raw-export-001')).length).toBeGreaterThanOrEqual(1);
fireEvent.click(screen.getByRole('button', { name: '导出 RAW 当前页 CSV' }));
expect(createObjectURL).toHaveBeenCalled();
@@ -7027,7 +7104,7 @@ test('opens same-day raw evidence from history location row', async () => {
expect(params.get('includeFields')).toBe('true');
expect(params.get('tab')).toBe('raw');
expect(await screen.findByRole('tab', { name: 'RAW 帧' })).toHaveAttribute('aria-selected', 'true');
expect(await screen.findByText('raw-from-location-001')).toBeInTheDocument();
expect((await screen.findAllByText('raw-from-location-001')).length).toBeGreaterThanOrEqual(1);
});
test('opens vehicle service from raw history rows with row source evidence', async () => {
@@ -7215,7 +7292,7 @@ test('opens same-day mileage statistics from raw history row', async () => {
render(<App />);
expect(await screen.findByText('raw-jt808-mileage-001')).toBeInTheDocument();
expect((await screen.findAllByText('raw-jt808-mileage-001')).length).toBeGreaterThanOrEqual(1);
fireEvent.click(screen.getByRole('button', { name: '核对里程' }));
await waitFor(() => {