refactor(mileage): use fixed-column table on mobile
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1332,8 +1332,8 @@ button, a { -webkit-tap-highlight-color: transparent; }
|
||||
.v2-mileage-result-actions > button { height: 34px; flex: 0 0 auto; }
|
||||
.v2-mileage-export-progress { align-items: stretch; flex-direction: column; gap: 7px; padding: 9px 12px; }
|
||||
.v2-mileage-export-progress > span { min-width: 0; }
|
||||
.v2-mileage-table-wrap { display: none; }
|
||||
.v2-mileage-mobile-list { display: flex; flex-direction: column; }
|
||||
.v2-mileage-table-wrap { display: block; }
|
||||
.v2-mileage-mobile-list { display: none; }
|
||||
.v2-mileage-mobile-list article { border-bottom: 1px solid #e9eef5; padding: 14px 12px; }
|
||||
.v2-mileage-mobile-list article > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
|
||||
.v2-mileage-mobile-list article > header div { min-width: 0; }
|
||||
@@ -1934,16 +1934,15 @@ button, a { -webkit-tap-highlight-color: transparent; }
|
||||
.v2-mileage-results > header .v2-mileage-result-actions { gap: 6px; }
|
||||
.v2-mileage-results > header .v2-mileage-result-actions em { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.v2-mileage-result-actions > button { height: 32px; padding: 0 9px; font-size: 10px; }
|
||||
.v2-mileage-mobile-list { gap: 5px; background: #f4f7fb; padding: 5px; }
|
||||
.v2-mileage-mobile-list article { overflow: hidden; border: 1px solid #dfe6ef; border-radius: 8px; background: #fff; padding: 9px 10px 8px; box-shadow: 0 1px 4px rgba(23,39,62,.03); }
|
||||
.v2-mileage-mobile-list article > header { align-items: center; }
|
||||
.v2-mileage-mobile-list article > header strong { font-size: 14px; }
|
||||
.v2-mileage-mobile-list article > header span { margin-top: 1px; font-size: 8px; }
|
||||
.v2-mileage-mobile-list article > header b { color: #135fcb; font-size: 17px; letter-spacing: -.025em; }
|
||||
.v2-mileage-mobile-list article > header b small { margin-top: 1px; font-size: 8px; }
|
||||
.v2-mileage-mobile-days { grid-auto-columns: 70px; gap: 4px; margin-top: 7px; border-top: 1px solid #edf1f5; padding-top: 6px; padding-bottom: 1px; scroll-snap-type: x proximity; }
|
||||
.v2-mileage-mobile-days > div { min-height: 38px; border: 0; border-radius: 6px; background: #f6f8fb; padding: 5px 7px; scroll-snap-align: start; }
|
||||
.v2-mileage-mobile-days time { font-size: 8px; }
|
||||
.v2-mileage-mobile-days strong { margin-top: 3px; color: #26364b; font-size: 10px; }
|
||||
.v2-mileage-mobile-days small { display: none; }
|
||||
.v2-mileage-table-wrap { display: block; width: 100%; max-width: 100%; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: thin; }
|
||||
.v2-mileage-table { width: max-content; min-width: 100%; font-size: 10px; }
|
||||
.v2-mileage-table th { height: 38px; padding: 0 8px; font-size: 9px; }
|
||||
.v2-mileage-table td { height: 44px; padding: 0 8px; font-size: 10px; }
|
||||
.v2-mileage-table .is-vin { display: none; }
|
||||
.v2-mileage-table .is-plate { left: 0; width: 92px; min-width: 92px; max-width: 92px; border-right: 1px solid #d8e0ea; box-shadow: 5px 0 10px rgba(15,23,42,.035); }
|
||||
.v2-mileage-table td.is-plate strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.v2-mileage-table .is-date { width: 76px; min-width: 76px; }
|
||||
.v2-mileage-table .is-total { width: 104px; min-width: 104px; padding-right: 10px; }
|
||||
.v2-mileage-table .is-daily { font-size: 10px; }
|
||||
.v2-mileage-table .is-period { font-size: 11px; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user