feat(platform-web): query raw frames with post body
This commit is contained in:
39
vehicle-data-platform/apps/web/src/api/client.test.ts
Normal file
39
vehicle-data-platform/apps/web/src/api/client.test.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { afterEach, expect, test, vi } from 'vitest';
|
||||||
|
import { api } from './client';
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rawFramesQuery posts structured JSON instead of URL query strings', async () => {
|
||||||
|
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: { items: [], total: 0, limit: 20, offset: 0 },
|
||||||
|
traceId: 'trace-test',
|
||||||
|
timestamp: 1783094400000
|
||||||
|
})
|
||||||
|
} as Response);
|
||||||
|
|
||||||
|
await api.rawFramesQuery({
|
||||||
|
keyword: '粤AG18312',
|
||||||
|
protocol: 'GB32960',
|
||||||
|
fields: ['gb32960.vehicle.speed_kmh'],
|
||||||
|
includeFields: true,
|
||||||
|
limit: 20,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith('/api/history/raw-frames/query', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
keyword: '粤AG18312',
|
||||||
|
protocol: 'GB32960',
|
||||||
|
fields: ['gb32960.vehicle.speed_kmh'],
|
||||||
|
includeFields: true,
|
||||||
|
limit: 20,
|
||||||
|
offset: 0
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,6 +16,18 @@ import type {
|
|||||||
VehicleRow
|
VehicleRow
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
export type RawFrameQuery = {
|
||||||
|
keyword?: string;
|
||||||
|
vin?: string;
|
||||||
|
protocol?: string;
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
fields?: string[];
|
||||||
|
includeFields?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
|
|
||||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
const response = await fetch(path, init);
|
const response = await fetch(path, init);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -34,6 +46,11 @@ export const api = {
|
|||||||
realtimeLocations: (params = new URLSearchParams()) => request<Page<RealtimeLocationRow>>(`/api/realtime/locations?${params.toString()}`),
|
realtimeLocations: (params = new URLSearchParams()) => request<Page<RealtimeLocationRow>>(`/api/realtime/locations?${params.toString()}`),
|
||||||
historyLocations: (params = new URLSearchParams()) => request<Page<HistoryLocationRow>>(`/api/history/locations?${params.toString()}`),
|
historyLocations: (params = new URLSearchParams()) => request<Page<HistoryLocationRow>>(`/api/history/locations?${params.toString()}`),
|
||||||
rawFrames: (params = new URLSearchParams()) => request<Page<RawFrameRow>>(`/api/history/raw-frames?${params.toString()}`),
|
rawFrames: (params = new URLSearchParams()) => request<Page<RawFrameRow>>(`/api/history/raw-frames?${params.toString()}`),
|
||||||
|
rawFramesQuery: (query: RawFrameQuery) => request<Page<RawFrameRow>>('/api/history/raw-frames/query', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(query)
|
||||||
|
}),
|
||||||
mileageSummary: (params = new URLSearchParams()) => request<MileageSummary>(`/api/mileage/summary?${params.toString()}`),
|
mileageSummary: (params = new URLSearchParams()) => request<MileageSummary>(`/api/mileage/summary?${params.toString()}`),
|
||||||
dailyMileage: (params = new URLSearchParams()) => request<Page<DailyMileageRow>>(`/api/mileage/daily?${params.toString()}`),
|
dailyMileage: (params = new URLSearchParams()) => request<Page<DailyMileageRow>>(`/api/mileage/daily?${params.toString()}`),
|
||||||
qualitySummary: (params = new URLSearchParams()) => request<QualitySummary>(`/api/quality/summary?${params.toString()}`),
|
qualitySummary: (params = new URLSearchParams()) => request<QualitySummary>(`/api/quality/summary?${params.toString()}`),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Button, Card, Form, Select, SideSheet, Space, Table, Tabs, Toast, Typography } from '@douyinfe/semi-ui';
|
import { Button, Card, Form, Select, SideSheet, Space, Table, Tabs, Toast, Typography } from '@douyinfe/semi-ui';
|
||||||
import { IconRefresh, IconSearch } from '@douyinfe/semi-icons';
|
import { IconRefresh, IconSearch } from '@douyinfe/semi-icons';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { api } from '../api/client';
|
import { api, type RawFrameQuery } from '../api/client';
|
||||||
import type { HistoryLocationRow, Page, RawFrameRow } from '../api/types';
|
import type { HistoryLocationRow, Page, RawFrameRow } from '../api/types';
|
||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
|
|
||||||
@@ -26,6 +26,13 @@ function canOpenVehicle(vin?: string) {
|
|||||||
return Boolean(value && value !== 'unknown');
|
return Boolean(value && value !== 'unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitFields(value?: string) {
|
||||||
|
return (value ?? '')
|
||||||
|
.split(/[\n,]/)
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
export function History({ initialVin, onOpenVehicle }: { initialVin: string; onOpenVehicle: (vin: string) => void }) {
|
export function History({ initialVin, onOpenVehicle }: { initialVin: string; onOpenVehicle: (vin: string) => void }) {
|
||||||
const [filters, setFilters] = useState<HistoryFilters>({ ...defaultFilters, keyword: initialVin || defaultFilters.keyword });
|
const [filters, setFilters] = useState<HistoryFilters>({ ...defaultFilters, keyword: initialVin || defaultFilters.keyword });
|
||||||
const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
|
const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
|
||||||
@@ -47,6 +54,18 @@ export function History({ initialVin, onOpenVehicle }: { initialVin: string; onO
|
|||||||
return params;
|
return params;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildRawQuery = (nextFilters: HistoryFilters, limit: number, offset: number): RawFrameQuery => {
|
||||||
|
const query: RawFrameQuery = { limit, offset };
|
||||||
|
if (nextFilters.keyword?.trim()) query.keyword = nextFilters.keyword.trim();
|
||||||
|
if (nextFilters.protocol?.trim()) query.protocol = nextFilters.protocol.trim();
|
||||||
|
if (nextFilters.dateFrom?.trim()) query.dateFrom = nextFilters.dateFrom.trim();
|
||||||
|
if (nextFilters.dateTo?.trim()) query.dateTo = nextFilters.dateTo.trim();
|
||||||
|
if (nextFilters.includeFields) query.includeFields = true;
|
||||||
|
const fields = splitFields(nextFilters.fields);
|
||||||
|
if (fields.length > 0) query.fields = fields;
|
||||||
|
return query;
|
||||||
|
};
|
||||||
|
|
||||||
const loadLocations = (nextFilters = filters, page = locationPagination.currentPage, pageSize = locationPagination.pageSize) => {
|
const loadLocations = (nextFilters = filters, page = locationPagination.currentPage, pageSize = locationPagination.pageSize) => {
|
||||||
setLoadingLocations(true);
|
setLoadingLocations(true);
|
||||||
api.historyLocations(buildParams(nextFilters, pageSize, (page - 1) * pageSize, false))
|
api.historyLocations(buildParams(nextFilters, pageSize, (page - 1) * pageSize, false))
|
||||||
@@ -60,7 +79,7 @@ export function History({ initialVin, onOpenVehicle }: { initialVin: string; onO
|
|||||||
|
|
||||||
const loadRawFrames = (nextFilters = filters, page = rawPagination.currentPage, pageSize = rawPagination.pageSize) => {
|
const loadRawFrames = (nextFilters = filters, page = rawPagination.currentPage, pageSize = rawPagination.pageSize) => {
|
||||||
setLoadingRaw(true);
|
setLoadingRaw(true);
|
||||||
api.rawFrames(buildParams(nextFilters, pageSize, (page - 1) * pageSize, true))
|
api.rawFramesQuery(buildRawQuery(nextFilters, pageSize, (page - 1) * pageSize))
|
||||||
.then((nextPage) => {
|
.then((nextPage) => {
|
||||||
setRawFrames(nextPage);
|
setRawFrames(nextPage);
|
||||||
setRawPagination({ currentPage: page, pageSize });
|
setRawPagination({ currentPage: page, pageSize });
|
||||||
|
|||||||
Reference in New Issue
Block a user