refactor(mileage): use fixed-column table on mobile

This commit is contained in:
lingniu
2026-07-16 13:07:43 +08:00
parent 4877c3fe8f
commit 6d6c9ce534
3 changed files with 20 additions and 49 deletions

View File

@@ -115,30 +115,17 @@ test('removes the previous mileage scope while a new date range is loading', asy
expect(screen.getAllByText('2026-07-10 至 2026-07-16').length).toBeGreaterThan(0);
});
test('mounts only the mobile mileage cards and removes the viewport listener', async () => {
test('uses one responsive mileage matrix without viewport listeners', async () => {
prepareData();
const addEventListener = vi.fn();
const removeEventListener = vi.fn();
vi.spyOn(window, 'matchMedia').mockReturnValue({
matches: true,
media: '(max-width: 680px)',
onchange: null,
addEventListener,
removeEventListener,
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(() => false)
} as unknown as MediaQueryList);
const view = renderPage('/statistics?vins=LTEST000000000001&dateFrom=2026-07-13&dateTo=2026-07-14');
expect(await screen.findByText('车辆每日里程')).toBeInTheDocument();
await waitFor(() => expect(view.container.querySelectorAll('.v2-mileage-mobile-list article')).toHaveLength(1));
expect(view.container.querySelector('.v2-mileage-table-wrap')).not.toBeInTheDocument();
expect(view.container.querySelectorAll('.v2-mileage-mobile-days > div')).toHaveLength(2);
expect(addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
view.unmount();
expect(removeEventListener).toHaveBeenCalledWith('change', expect.any(Function));
await waitFor(() => expect(view.container.querySelectorAll('.v2-mileage-table tbody tr')).toHaveLength(1));
expect(view.container.querySelector('.v2-mileage-table-wrap')).toBeInTheDocument();
expect(view.container.querySelectorAll('.v2-mileage-table th.is-date')).toHaveLength(2);
expect(view.container.querySelector('.v2-mileage-table th.is-plate')).toHaveTextContent('车牌');
expect(view.container.querySelector('.v2-mileage-table th.is-total')).toHaveTextContent('区间总里程');
expect(view.container.querySelector('.v2-mileage-mobile-list')).not.toBeInTheDocument();
});
test('supports searching and selecting license plates before querying exact VINs', async () => {

View File

@@ -15,7 +15,6 @@ const MAX_SELECTED_VEHICLES = 20;
const PAGE_SIZE = 20;
const EXPORT_VEHICLE_PAGE_SIZE = 2_000;
const EXPORT_VIN_BATCH_SIZE = 50;
const MOBILE_MILEAGE_QUERY = '(max-width: 680px)';
const MAX_MILEAGE_RANGE_DAYS = 366;
type VehicleOption = Pick<VehicleRow, 'vin' | 'plate'>;
@@ -223,18 +222,6 @@ function SummaryRail({ data, criteria, fleetTotal, loading }: { data?: MileageSt
type VehicleMileageMatrix = VehicleOption & { days: Map<string, number>; sources: Map<string, string>; totalMileageKm: number };
function useMobileMileageLayout() {
const [mobile, setMobile] = useState(() => window.matchMedia(MOBILE_MILEAGE_QUERY).matches);
useEffect(() => {
const media = window.matchMedia(MOBILE_MILEAGE_QUERY);
const update = () => setMobile(media.matches);
media.addEventListener('change', update);
update();
return () => media.removeEventListener('change', update);
}, []);
return mobile;
}
function rangeDates(dateFrom: string, dateTo: string) {
const dates: string[] = [];
const cursor = new Date(`${dateFrom}T00:00:00Z`);
@@ -252,8 +239,6 @@ function dateLabel(date: string) {
}
function MileageTable({ rows, dates }: { rows: VehicleMileageMatrix[]; dates: string[] }) {
const mobile = useMobileMileageLayout();
if (mobile) return <div className="v2-mileage-mobile-list">{rows.map((row) => <article key={row.vin}><header><div><strong>{row.plate || '未绑定'}</strong><span>{row.vin}</span></div><b>{formatKm(row.totalMileageKm)} km<small></small></b></header><div className="v2-mileage-mobile-days" aria-label={`${row.plate || row.vin}每日里程,左右滑动查看 ${dates.length}`}>{dates.map((date) => { const source = row.sources.get(date); return <div key={date} aria-label={`${dateLabel(date)}${row.days.has(date) ? `${formatKm(row.days.get(date))}公里` : '无数据'}${source ? `,来源${source}` : ''}`}><time>{dateLabel(date)}</time><strong>{row.days.has(date) ? `${formatKm(row.days.get(date))} km` : '—'}</strong>{source ? <small>{source === 'YUTONG_MQTT' ? 'YUTONG' : source}</small> : null}</div>; })}</div></article>)}</div>;
let maxDailyMileage = 1;
for (const row of rows) {
for (const mileage of row.days.values()) maxDailyMileage = Math.max(maxDailyMileage, mileage);