refine Semi UI vehicle directory
This commit is contained in:
@@ -181,9 +181,11 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
|
||||
expect(view.container.querySelector('.v2-vehicle-discovery-shell')).toContainElement(screen.getByLabelText('查车操作'));
|
||||
expect(screen.getByRole('button', { name: `打开 ${initialRealtime.vin} 车辆档案` })).toBeInTheDocument();
|
||||
fireEvent.focus(input);
|
||||
expect(input).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(input).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
||||
fireEvent.change(input, { target: { value: initialRealtime.plate } });
|
||||
await waitFor(() => expect(vehicles).toHaveBeenCalledTimes(2));
|
||||
expect(input).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(screen.getByRole('heading', { name: '匹配车辆', level: 5 })).toBeInTheDocument();
|
||||
const option = await screen.findByRole('option', { name: `${initialRealtime.plate} ${initialRealtime.vin} JT808 选择` });
|
||||
expect(view.container.querySelector('#v2-vehicle-search-options.v2-vehicle-candidate-list')).toBeInTheDocument();
|
||||
@@ -237,7 +239,49 @@ test('defaults to a compact mobile vehicle directory and keeps eight recent vehi
|
||||
expect(input).toBeVisible();
|
||||
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
||||
fireEvent.focus(input);
|
||||
expect(view.container.querySelector('.v2-vehicle-search-options')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
||||
fireEvent.change(input, { target: { value: items[0].plate } });
|
||||
await waitFor(() => expect(view.container.querySelector('.v2-vehicle-search-options')).toBeInTheDocument());
|
||||
expect(view.container.querySelector('.v2-vehicle-search-page')).toHaveClass('has-candidates');
|
||||
fireEvent.click(screen.getByRole('button', { name: /查询车辆/ }));
|
||||
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-vehicle-search-page')).not.toHaveClass('has-candidates');
|
||||
expect(screen.getByRole('heading', { name: '匹配车辆', level: 5 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('keeps a partial vehicle query in the directory instead of navigating to an invalid detail route', async () => {
|
||||
const candidate = {
|
||||
vin: initialRealtime.vin,
|
||||
plate: initialRealtime.plate,
|
||||
phone: '13800000000',
|
||||
oem: '测试品牌',
|
||||
protocols: ['JT808'],
|
||||
missingProtocols: [],
|
||||
sourceStatus: [],
|
||||
sourceCount: 1,
|
||||
onlineSourceCount: 1,
|
||||
online: true,
|
||||
lastSeen: initialRealtime.lastSeen,
|
||||
locationText: '广东省广州市',
|
||||
bindingScore: 100,
|
||||
bindingStatus: 'bound'
|
||||
};
|
||||
vi.spyOn(api, 'vehicleCoverage').mockResolvedValue({ items: [candidate], total: 1, limit: 10, offset: 0 });
|
||||
const vehicleDetail = vi.spyOn(api, 'vehicleDetail');
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
|
||||
const view = render(<QueryClientProvider client={client}><MemoryRouter initialEntries={['/vehicles']} future={ROUTER_FUTURE}><Routes><Route path="/vehicles" element={<VehiclePage />} /><Route path="/vehicles/:vin" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
|
||||
|
||||
await screen.findByRole('heading', { name: '授权车辆目录', level: 5 });
|
||||
const input = screen.getByRole('textbox', { name: '搜索车辆' });
|
||||
fireEvent.change(input, { target: { value: '粤A' } });
|
||||
expect(await screen.findByRole('listbox')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /查询车辆/ }));
|
||||
|
||||
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '匹配车辆', level: 5 })).toBeInTheDocument();
|
||||
expect(vehicleDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('paginates the complete desktop authorized vehicle directory', async () => {
|
||||
|
||||
@@ -4,12 +4,12 @@ import { Button, Card, Empty, Input, Table, Tag } from '@douyinfe/semi-ui';
|
||||
import { type FormEvent, lazy, Suspense, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
import type { VehicleCoverageRow } from '../../api/types';
|
||||
import type { Page, VehicleCoverageRow } from '../../api/types';
|
||||
import { usePlatformSession } from '../auth/AuthGate';
|
||||
import { canAdminister } from '../auth/session';
|
||||
import { formatZhNumber } from '../domain/formatters';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { QUERY_MEMORY } from '../queryPolicy';
|
||||
import { QUERY_MEMORY, queryScopeKey, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
import { TablePagination } from '../shared/TablePagination';
|
||||
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
|
||||
@@ -54,15 +54,16 @@ export default function VehicleSearchWorkspace() {
|
||||
const closeTimerRef = useRef<number>();
|
||||
const deferredKeyword = useDeferredValue(keyword.trim());
|
||||
const pageSize = mobileLayout ? 8 : 10;
|
||||
const directoryScope = useMemo(() => queryScopeKey({ keyword: deferredKeyword }), [deferredKeyword]);
|
||||
const candidateParams = useMemo(() => {
|
||||
const params = new URLSearchParams({ limit: String(pageSize), offset: String((page - 1) * pageSize) });
|
||||
if (deferredKeyword) params.set('keyword', deferredKeyword);
|
||||
return params;
|
||||
}, [deferredKeyword, page, pageSize]);
|
||||
const candidates = useQuery({
|
||||
queryKey: ['vehicle-directory', candidateParams.toString()],
|
||||
const candidates = useQuery<Page<VehicleCoverageRow>>({
|
||||
queryKey: ['vehicle-directory', directoryScope, pageSize, page],
|
||||
queryFn: ({ signal }) => api.vehicleCoverage(candidateParams, signal),
|
||||
placeholderData: (previous) => previous,
|
||||
placeholderData: retainPreviousPageWithinScope<Page<VehicleCoverageRow>>(directoryScope),
|
||||
staleTime: 30_000,
|
||||
gcTime: QUERY_MEMORY.optionGcTime
|
||||
});
|
||||
@@ -80,7 +81,7 @@ export default function VehicleSearchWorkspace() {
|
||||
}, [mobileLayout]);
|
||||
const openCandidates = () => {
|
||||
window.clearTimeout(closeTimerRef.current);
|
||||
setCandidatesOpen(true);
|
||||
setCandidatesOpen(Boolean(keyword.trim()));
|
||||
};
|
||||
const closeCandidates = () => {
|
||||
window.clearTimeout(closeTimerRef.current);
|
||||
@@ -99,7 +100,8 @@ export default function VehicleSearchWorkspace() {
|
||||
};
|
||||
const submit = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
openVehicle(keyword);
|
||||
setCandidatesOpen(false);
|
||||
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
||||
};
|
||||
const vehicleScope = candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权` : '授权范围';
|
||||
return <section className={`v2-vehicle-search-page${syncOpen ? ' has-sync-panel' : ''}${candidatesOpen ? ' has-candidates' : ''}`}>
|
||||
@@ -132,7 +134,7 @@ export default function VehicleSearchWorkspace() {
|
||||
aria-expanded={candidatesOpen}
|
||||
prefix={<IconSearch />}
|
||||
value={keyword}
|
||||
onChange={(value) => { setKeyword(value); setPage(1); setCandidatesOpen(true); }}
|
||||
onChange={(value) => { setKeyword(value); setPage(1); setCandidatesOpen(Boolean(value.trim())); }}
|
||||
onFocus={openCandidates}
|
||||
onBlur={closeCandidates}
|
||||
placeholder="输入车牌 / VIN / 终端手机号"
|
||||
@@ -140,7 +142,7 @@ export default function VehicleSearchWorkspace() {
|
||||
/>
|
||||
</div>
|
||||
<Button theme="solid" htmlType="submit" icon={<IconArrowRight />} iconPosition="right">查询车辆</Button>
|
||||
{candidatesOpen ? <VehicleCandidateList
|
||||
{candidatesOpen && deferredKeyword ? <VehicleCandidateList
|
||||
id="v2-vehicle-search-options"
|
||||
className="v2-vehicle-search-options"
|
||||
items={options}
|
||||
|
||||
@@ -2356,6 +2356,10 @@
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.v2-vehicle-search-page.has-candidates > .v2-vehicle-recent-card {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-recent-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user