feat(platform): persist history filters in route
This commit is contained in:
@@ -26,6 +26,9 @@ export default function App() {
|
|||||||
const [realtimeFilters, setRealtimeFilters] = useState<Record<string, string>>(
|
const [realtimeFilters, setRealtimeFilters] = useState<Record<string, string>>(
|
||||||
initialRoute.page === 'realtime' ? realtimeFiltersFromRoute(initialRoute) : {}
|
initialRoute.page === 'realtime' ? realtimeFiltersFromRoute(initialRoute) : {}
|
||||||
);
|
);
|
||||||
|
const [historyFilters, setHistoryFilters] = useState<Record<string, string>>(
|
||||||
|
initialRoute.page === 'history' ? historyFiltersFromRoute(initialRoute) : {}
|
||||||
|
);
|
||||||
const [qualityFilters, setQualityFilters] = useState<Record<string, string>>(
|
const [qualityFilters, setQualityFilters] = useState<Record<string, string>>(
|
||||||
initialRoute.page === 'quality' ? qualityFiltersFromRoute(initialRoute) : {}
|
initialRoute.page === 'quality' ? qualityFiltersFromRoute(initialRoute) : {}
|
||||||
);
|
);
|
||||||
@@ -104,6 +107,9 @@ export default function App() {
|
|||||||
if (route.page === 'realtime') {
|
if (route.page === 'realtime') {
|
||||||
setRealtimeFilters(realtimeFiltersFromRoute(route));
|
setRealtimeFilters(realtimeFiltersFromRoute(route));
|
||||||
}
|
}
|
||||||
|
if (route.page === 'history') {
|
||||||
|
setHistoryFilters(historyFiltersFromRoute(route));
|
||||||
|
}
|
||||||
if (route.page === 'quality') {
|
if (route.page === 'quality') {
|
||||||
setQualityFilters(qualityFiltersFromRoute(route));
|
setQualityFilters(qualityFiltersFromRoute(route));
|
||||||
}
|
}
|
||||||
@@ -128,6 +134,10 @@ export default function App() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (page === 'history' || page === 'mileage') {
|
if (page === 'history' || page === 'mileage') {
|
||||||
|
if (page === 'history') {
|
||||||
|
replaceHistoryHash(historyFilters);
|
||||||
|
return;
|
||||||
|
}
|
||||||
replaceHash(page, analysisVin, activeProtocol);
|
replaceHash(page, analysisVin, activeProtocol);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,6 +182,26 @@ export default function App() {
|
|||||||
replaceHash('realtime', keyword, protocol, restFilters);
|
replaceHash('realtime', keyword, protocol, restFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateHistoryFilters = (filters: Record<string, unknown> = {}, tab = historyTab) => {
|
||||||
|
const nextFilters = normalizeHistoryFilterValues(filters);
|
||||||
|
setHistoryFilters(nextFilters);
|
||||||
|
if (nextFilters.keyword) {
|
||||||
|
setAnalysisVin(nextFilters.keyword);
|
||||||
|
}
|
||||||
|
setActiveProtocol(nextFilters.protocol ?? '');
|
||||||
|
setHistoryTab(tab);
|
||||||
|
replaceHistoryHash(nextFilters, tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceHistoryHash = (filters: Record<string, string> = {}, tab = historyTab) => {
|
||||||
|
const { keyword, protocol, ...restFilters } = filters;
|
||||||
|
const routeFilters = { ...restFilters };
|
||||||
|
if (tab && tab !== 'location') {
|
||||||
|
routeFilters.tab = tab;
|
||||||
|
}
|
||||||
|
replaceHash('history', keyword ?? analysisVin, protocol ?? activeProtocol, routeFilters);
|
||||||
|
};
|
||||||
|
|
||||||
const replaceQualityHash = (filters: Record<string, string> = {}) => {
|
const replaceQualityHash = (filters: Record<string, string> = {}) => {
|
||||||
const keyword = filters.keyword;
|
const keyword = filters.keyword;
|
||||||
const protocol = filters.protocol;
|
const protocol = filters.protocol;
|
||||||
@@ -218,6 +248,7 @@ export default function App() {
|
|||||||
setAnalysisVin(nextVin);
|
setAnalysisVin(nextVin);
|
||||||
setActiveProtocol(nextProtocol);
|
setActiveProtocol(nextProtocol);
|
||||||
setHistoryTab('location');
|
setHistoryTab('location');
|
||||||
|
setHistoryFilters({ keyword: nextVin, protocol: nextProtocol });
|
||||||
setActivePage('history');
|
setActivePage('history');
|
||||||
replaceHash('history', nextVin, nextProtocol);
|
replaceHash('history', nextVin, nextProtocol);
|
||||||
};
|
};
|
||||||
@@ -231,6 +262,7 @@ export default function App() {
|
|||||||
setAnalysisVin(nextVin);
|
setAnalysisVin(nextVin);
|
||||||
setActiveProtocol(nextProtocol);
|
setActiveProtocol(nextProtocol);
|
||||||
setHistoryTab('raw');
|
setHistoryTab('raw');
|
||||||
|
setHistoryFilters({ keyword: nextVin, protocol: nextProtocol });
|
||||||
setActivePage('history');
|
setActivePage('history');
|
||||||
replaceHash('history', nextVin, nextProtocol, { tab: 'raw' });
|
replaceHash('history', nextVin, nextProtocol, { tab: 'raw' });
|
||||||
};
|
};
|
||||||
@@ -263,7 +295,7 @@ export default function App() {
|
|||||||
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} onOpenHistory={openHistoryForVehicle} onOpenRaw={openRawForVehicle} onOpenMileage={openMileageForVehicle} onOpenVehicles={openVehicles} onQueryChange={updateVehicleDetailQuery} />,
|
||||||
history: <History initialVin={analysisVin} initialProtocol={activeProtocol} initialTab={historyTab} onOpenVehicle={openVehicle} />,
|
history: <History initialVin={analysisVin} initialProtocol={activeProtocol} initialTab={historyTab} initialFilters={historyFilters} onFiltersChange={updateHistoryFilters} onOpenVehicle={openVehicle} />,
|
||||||
mileage: <Mileage initialVin={analysisVin} initialProtocol={activeProtocol} onOpenVehicle={openVehicle} />,
|
mileage: <Mileage initialVin={analysisVin} initialProtocol={activeProtocol} 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} />
|
||||||
};
|
};
|
||||||
@@ -300,6 +332,32 @@ function realtimeFiltersFromRoute(route: ReturnType<typeof parseAppHash>): Recor
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function historyFiltersFromRoute(route: ReturnType<typeof parseAppHash>): Record<string, string> {
|
||||||
|
return normalizeHistoryFilterValues({
|
||||||
|
...(route.keyword ? { keyword: route.keyword } : {}),
|
||||||
|
...(route.protocol ? { protocol: route.protocol } : {}),
|
||||||
|
...(route.filters?.dateFrom ? { dateFrom: route.filters.dateFrom } : {}),
|
||||||
|
...(route.filters?.dateTo ? { dateTo: route.filters.dateTo } : {}),
|
||||||
|
...(route.filters?.includeFields ? { includeFields: route.filters.includeFields } : {}),
|
||||||
|
...(route.filters?.fields ? { fields: route.filters.fields } : {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHistoryFilterValues(filters: Record<string, unknown> = {}): Record<string, string> {
|
||||||
|
const normalized: Record<string, string> = {};
|
||||||
|
for (const key of ['keyword', 'protocol', 'dateFrom', 'dateTo', 'fields'] as const) {
|
||||||
|
const value = String(filters[key] ?? '').trim();
|
||||||
|
if (value) {
|
||||||
|
normalized[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const includeFields = filters.includeFields;
|
||||||
|
if (includeFields === true || includeFields === 'true') {
|
||||||
|
normalized.includeFields = 'true';
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
function serviceStatusFromOverview(overview: VehicleServiceOverview): VehicleServiceStatus {
|
function serviceStatusFromOverview(overview: VehicleServiceOverview): VehicleServiceStatus {
|
||||||
const sourceCount = overview.sourceCount;
|
const sourceCount = overview.sourceCount;
|
||||||
const onlineSourceCount = overview.onlineSourceCount;
|
const onlineSourceCount = overview.onlineSourceCount;
|
||||||
|
|||||||
@@ -9,7 +9,19 @@ export type AppRoute = {
|
|||||||
filters?: Record<string, string>;
|
filters?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterKeys = ['coverage', 'serviceStatus', 'online', 'bindingStatus', 'missingProtocol', 'issueType', 'tab'] as const;
|
const filterKeys = [
|
||||||
|
'coverage',
|
||||||
|
'serviceStatus',
|
||||||
|
'online',
|
||||||
|
'bindingStatus',
|
||||||
|
'missingProtocol',
|
||||||
|
'issueType',
|
||||||
|
'tab',
|
||||||
|
'dateFrom',
|
||||||
|
'dateTo',
|
||||||
|
'includeFields',
|
||||||
|
'fields'
|
||||||
|
] as const;
|
||||||
|
|
||||||
export function parseAppHash(hash: string): AppRoute {
|
export function parseAppHash(hash: string): AppRoute {
|
||||||
const normalized = hash.trim().replace(/^#\/?/, '');
|
const normalized = hash.trim().replace(/^#\/?/, '');
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type HistoryFilters = {
|
|||||||
dateFrom?: string;
|
dateFrom?: string;
|
||||||
dateTo?: string;
|
dateTo?: string;
|
||||||
fields?: string;
|
fields?: string;
|
||||||
includeFields?: boolean;
|
includeFields?: boolean | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultFilters: HistoryFilters = {
|
const defaultFilters: HistoryFilters = {
|
||||||
@@ -39,19 +39,37 @@ function normalizeHistoryTab(value?: string): HistoryTabKey {
|
|||||||
return value === 'raw' ? 'raw' : 'location';
|
return value === 'raw' ? 'raw' : 'location';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isIncludeFieldsEnabled(value?: boolean | string) {
|
||||||
|
return value === true || value === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeInitialFilters(initialVin: string, initialProtocol?: string, initialFilters: Record<string, string> = {}): HistoryFilters {
|
||||||
|
return {
|
||||||
|
...defaultFilters,
|
||||||
|
keyword: initialVin || defaultFilters.keyword,
|
||||||
|
protocol: initialProtocol,
|
||||||
|
...initialFilters,
|
||||||
|
includeFields: isIncludeFieldsEnabled(initialFilters.includeFields)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function History({
|
export function History({
|
||||||
initialVin,
|
initialVin,
|
||||||
initialProtocol,
|
initialProtocol,
|
||||||
initialTab,
|
initialTab,
|
||||||
|
initialFilters = {},
|
||||||
|
onFiltersChange,
|
||||||
onOpenVehicle
|
onOpenVehicle
|
||||||
}: {
|
}: {
|
||||||
initialVin: string;
|
initialVin: string;
|
||||||
initialProtocol?: string;
|
initialProtocol?: string;
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
|
initialFilters?: Record<string, string>;
|
||||||
|
onFiltersChange?: (filters: HistoryFilters, tab?: HistoryTabKey) => void;
|
||||||
onOpenVehicle: (vin: string, protocol?: string) => void;
|
onOpenVehicle: (vin: string, protocol?: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const initialFilters = { ...defaultFilters, keyword: initialVin || defaultFilters.keyword, protocol: initialProtocol };
|
const initialHistoryFilters = mergeInitialFilters(initialVin, initialProtocol, initialFilters);
|
||||||
const [filters, setFilters] = useState<HistoryFilters>(initialFilters);
|
const [filters, setFilters] = useState<HistoryFilters>(initialHistoryFilters);
|
||||||
const [activeTab, setActiveTab] = useState<HistoryTabKey>(normalizeHistoryTab(initialTab));
|
const [activeTab, setActiveTab] = useState<HistoryTabKey>(normalizeHistoryTab(initialTab));
|
||||||
const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
|
const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
|
||||||
const [rawFrames, setRawFrames] = useState<Page<RawFrameRow>>(defaultPage);
|
const [rawFrames, setRawFrames] = useState<Page<RawFrameRow>>(defaultPage);
|
||||||
@@ -67,7 +85,7 @@ export function History({
|
|||||||
if (nextFilters.protocol?.trim()) params.set('protocol', nextFilters.protocol.trim());
|
if (nextFilters.protocol?.trim()) params.set('protocol', nextFilters.protocol.trim());
|
||||||
if (nextFilters.dateFrom?.trim()) params.set('dateFrom', nextFilters.dateFrom.trim());
|
if (nextFilters.dateFrom?.trim()) params.set('dateFrom', nextFilters.dateFrom.trim());
|
||||||
if (nextFilters.dateTo?.trim()) params.set('dateTo', nextFilters.dateTo.trim());
|
if (nextFilters.dateTo?.trim()) params.set('dateTo', nextFilters.dateTo.trim());
|
||||||
if (raw && nextFilters.includeFields) params.set('includeFields', 'true');
|
if (raw && isIncludeFieldsEnabled(nextFilters.includeFields)) params.set('includeFields', 'true');
|
||||||
if (raw && nextFilters.fields?.trim()) params.set('fields', nextFilters.fields.trim());
|
if (raw && nextFilters.fields?.trim()) params.set('fields', nextFilters.fields.trim());
|
||||||
return params;
|
return params;
|
||||||
};
|
};
|
||||||
@@ -78,7 +96,7 @@ export function History({
|
|||||||
if (nextFilters.protocol?.trim()) query.protocol = nextFilters.protocol.trim();
|
if (nextFilters.protocol?.trim()) query.protocol = nextFilters.protocol.trim();
|
||||||
if (nextFilters.dateFrom?.trim()) query.dateFrom = nextFilters.dateFrom.trim();
|
if (nextFilters.dateFrom?.trim()) query.dateFrom = nextFilters.dateFrom.trim();
|
||||||
if (nextFilters.dateTo?.trim()) query.dateTo = nextFilters.dateTo.trim();
|
if (nextFilters.dateTo?.trim()) query.dateTo = nextFilters.dateTo.trim();
|
||||||
if (nextFilters.includeFields) query.includeFields = true;
|
if (isIncludeFieldsEnabled(nextFilters.includeFields)) query.includeFields = true;
|
||||||
const fields = splitFields(nextFilters.fields);
|
const fields = splitFields(nextFilters.fields);
|
||||||
if (fields.length > 0) query.fields = fields;
|
if (fields.length > 0) query.fields = fields;
|
||||||
return query;
|
return query;
|
||||||
@@ -116,24 +134,32 @@ export function History({
|
|||||||
includeFields: Boolean(values.includeFields)
|
includeFields: Boolean(values.includeFields)
|
||||||
};
|
};
|
||||||
setFilters(nextFilters);
|
setFilters(nextFilters);
|
||||||
|
onFiltersChange?.(nextFilters, activeTab);
|
||||||
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
||||||
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
const nextFilters = { ...defaultFilters, keyword: initialVin || defaultFilters.keyword, protocol: initialProtocol };
|
const nextFilters = mergeInitialFilters(initialVin, initialProtocol, {});
|
||||||
setFilters(nextFilters);
|
setFilters(nextFilters);
|
||||||
|
onFiltersChange?.(nextFilters, activeTab);
|
||||||
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
||||||
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextFilters = { ...defaultFilters, keyword: initialVin || defaultFilters.keyword, protocol: initialProtocol };
|
const nextFilters = mergeInitialFilters(initialVin, initialProtocol, initialFilters);
|
||||||
setFilters(nextFilters);
|
setFilters(nextFilters);
|
||||||
setActiveTab(normalizeHistoryTab(initialTab));
|
setActiveTab(normalizeHistoryTab(initialTab));
|
||||||
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
||||||
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
||||||
}, [initialVin, initialProtocol, initialTab]);
|
}, [initialVin, initialProtocol, initialTab, JSON.stringify(initialFilters)]);
|
||||||
|
|
||||||
|
const changeTab = (key: string) => {
|
||||||
|
const nextTab = normalizeHistoryTab(String(key));
|
||||||
|
setActiveTab(nextTab);
|
||||||
|
onFiltersChange?.(filters, nextTab);
|
||||||
|
};
|
||||||
|
|
||||||
const rawFieldCount = useMemo(() => Object.keys(selectedRaw?.parsedFields ?? {}).length, [selectedRaw]);
|
const rawFieldCount = useMemo(() => Object.keys(selectedRaw?.parsedFields ?? {}).length, [selectedRaw]);
|
||||||
const currentVehicleKeyword = filters.keyword?.trim() ?? '';
|
const currentVehicleKeyword = filters.keyword?.trim() ?? '';
|
||||||
@@ -156,7 +182,7 @@ export function History({
|
|||||||
<Typography.Text type="tertiary">历史位置和 RAW 帧按当前车辆与来源范围查询。</Typography.Text>
|
<Typography.Text type="tertiary">历史位置和 RAW 帧按当前车辆与来源范围查询。</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<Card bordered>
|
<Card bordered>
|
||||||
<Form key={`${filters.keyword ?? ''}-${filters.protocol ?? ''}-${filters.includeFields ? 'fields' : 'light'}`} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
|
<Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
|
||||||
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 260 }} />
|
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 260 }} />
|
||||||
<Form.Select field="protocol" label="数据来源" placeholder="全部来源" style={{ width: 190 }}>
|
<Form.Select field="protocol" label="数据来源" placeholder="全部来源" style={{ width: 190 }}>
|
||||||
<Select.Option value="GB32960">GB32960</Select.Option>
|
<Select.Option value="GB32960">GB32960</Select.Option>
|
||||||
@@ -182,7 +208,7 @@ export function History({
|
|||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
<Card bordered style={{ marginTop: 16 }}>
|
<Card bordered style={{ marginTop: 16 }}>
|
||||||
<Tabs activeKey={activeTab} onChange={(key) => setActiveTab(normalizeHistoryTab(String(key)))}>
|
<Tabs activeKey={activeTab} onChange={(key) => changeTab(String(key))}>
|
||||||
<Tabs.TabPane tab="位置历史" itemKey="location">
|
<Tabs.TabPane tab="位置历史" itemKey="location">
|
||||||
<Table
|
<Table
|
||||||
rowKey="deviceTime"
|
rowKey="deviceTime"
|
||||||
|
|||||||
@@ -1776,6 +1776,51 @@ test('applies protocol from shareable history hash to API requests', async () =>
|
|||||||
expect(screen.getByText('当前来源:JT808')).toBeInTheDocument();
|
expect(screen.getByText('当前来源:JT808')).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) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: { items: [], total: 0, limit: 10, offset: 0 },
|
||||||
|
traceId: 'trace-test',
|
||||||
|
timestamp: 1783094400000
|
||||||
|
})
|
||||||
|
} as Response;
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<App />);
|
||||||
|
|
||||||
|
await screen.findByRole('heading', { name: '历史数据' });
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('VIN / 车牌 / 手机号'), { target: { value: '粤AG18312' } });
|
||||||
|
fireEvent.click(screen.getByText('全部来源'));
|
||||||
|
fireEvent.click(await screen.findByText('JT808'));
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('2026-07-03 00:00:00'), { target: { value: '2026-07-01 00:00:00' } });
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('2026-07-03 23:59:59'), { target: { value: '2026-07-01 23:59:59' } });
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'search 查询' }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(window.location.hash).toBe('#/history?keyword=%E7%B2%A4AG18312&protocol=JT808&dateFrom=2026-07-01+00%3A00%3A00&dateTo=2026-07-01+23%3A59%3A59');
|
||||||
|
});
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/history/locations?'), undefined);
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('dateFrom=2026-07-01+00%3A00%3A00'), undefined);
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith('/api/history/raw-frames/query', expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
body: expect.stringContaining('"dateFrom":"2026-07-01 00:00:00"')
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
test('shows vehicle and source scope on mileage hash', async () => {
|
test('shows vehicle and source scope on mileage hash', async () => {
|
||||||
window.history.replaceState(null, '', '/#/mileage?keyword=VIN-MILEAGE-001&protocol=GB32960');
|
window.history.replaceState(null, '', '/#/mileage?keyword=VIN-MILEAGE-001&protocol=GB32960');
|
||||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user