refactor(ui): polish semi mileage discovery

This commit is contained in:
lingniu
2026-07-19 16:25:48 +08:00
parent fa60bbb2c8
commit 40c210a192
3 changed files with 116 additions and 6 deletions

View File

@@ -122,6 +122,8 @@ test('defaults to seven days and supports today and yesterday shortcuts', async
await waitFor(() => expect(mocks.mileageStatistics).toHaveBeenCalled());
expect(mocks.mileageStatistics.mock.calls[0][0].get('dateFrom')).toBe(sevenDaysAgo);
expect(mocks.mileageStatistics.mock.calls[0][0].get('dateTo')).toBe(today);
expect(screen.getByRole('button', { name: '近 7 天' })).toHaveAttribute('aria-pressed', 'true');
expect(screen.getByRole('button', { name: '今天' })).toHaveAttribute('aria-pressed', 'false');
fireEvent.click(screen.getByRole('button', { name: '昨天' }));
await waitFor(() => {
@@ -129,6 +131,7 @@ test('defaults to seven days and supports today and yesterday shortcuts', async
expect(params.get('dateFrom')).toBe(yesterday);
expect(params.get('dateTo')).toBe(yesterday);
});
expect(screen.getByRole('button', { name: '昨天' })).toHaveAttribute('aria-pressed', 'true');
fireEvent.click(screen.getByRole('button', { name: '今天' }));
await waitFor(() => {
@@ -136,6 +139,7 @@ test('defaults to seven days and supports today and yesterday shortcuts', async
expect(params.get('dateFrom')).toBe(today);
expect(params.get('dateTo')).toBe(today);
});
expect(screen.getByRole('button', { name: '今天' })).toHaveAttribute('aria-pressed', 'true');
});
test('preserves the exact monitor return after a mileage date shortcut', async () => {
@@ -280,7 +284,7 @@ test('lets users disable mileage sources and persists the source priority', asyn
expect(screen.getByText('GPS 里程')).toBeInTheDocument();
expect(screen.getByText('GPS 里程').closest('.semi-tag')).toBeInTheDocument();
expect(screen.getAllByText('仪表盘里程')).toHaveLength(3);
expect(screen.getByRole('button', { name: /数据源.*GB32960 优先.*3\/3/ })).toHaveAttribute('title', '当前优先:国标 GB32960');
expect(screen.getByRole('button', { name: /数据源策略.*GB32960 优先.*3\/3/ })).toHaveAttribute('title', '当前优先:国标 GB32960');
fireEvent.click(screen.getByRole('switch', { name: '禁用 国标 GB32960' }));
fireEvent.click(screen.getByRole('button', { name: '上移 宇通 MQTT' }));
fireEvent.click(screen.getByRole('button', { name: '查询' }));

View File

@@ -59,6 +59,34 @@ function isSameRange(criteria: Pick<Criteria, 'dateFrom' | 'dateTo'>, range: { d
return criteria.dateFrom === range.dateFrom && criteria.dateTo === range.dateTo;
}
function MileageRangeShortcuts({
value,
ranges,
onChange
}: {
value: Pick<Criteria, 'dateFrom' | 'dateTo'>;
ranges: Array<{ label: string; range: { dateFrom: string; dateTo: string } }>;
onChange: (range: { dateFrom: string; dateTo: string }) => void;
}) {
return <div className="v2-mileage-ranges">
<span></span>
<ButtonGroup aria-label="快捷日期范围">
{ranges.map(({ label, range }) => {
const active = isSameRange(value, range);
return <Button
key={label}
theme={active ? 'solid' : 'light'}
htmlType="button"
aria-pressed={active}
onClick={() => onChange(range)}
>
{label}
</Button>;
})}
</ButtonGroup>
</div>;
}
export function mileageRangeContainsDate(
range: { dateFrom: string; dateTo: string },
date = localDate()
@@ -160,8 +188,18 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC
};
return <div className="v2-mileage-source-strategy">
<Button className="v2-mileage-source-trigger" theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-expanded={open} aria-controls="v2-mileage-source-strategy" title={`当前优先:${primarySource?.label ?? '未配置'}`} onClick={() => setOpen((current) => !current)}>
<span></span><em>{primarySource?.protocol ?? '未配置'} </em><b>{enabled.length}/3</b>
<Button
className="v2-mileage-source-trigger"
theme="light"
icon={<IconSetting />}
aria-label={`数据源策略,${primarySource?.protocol ?? '未配置'} 优先,${enabled.length}/3 已启用`}
aria-haspopup="dialog"
aria-expanded={open}
aria-controls="v2-mileage-source-strategy"
title={`当前优先:${primarySource?.label ?? '未配置'}`}
onClick={() => setOpen((current) => !current)}
>
<span></span><em>{primarySource?.protocol ?? '未配置'} </em><b>{enabled.length}/3</b>
</Button>
<WorkspaceSideSheet
className="v2-mileage-source-sidesheet"
@@ -545,6 +583,13 @@ export default function StatisticsPage() {
const sevenDayRange = defaultWindow(7);
const thirtyDayRange = defaultWindow(30);
const ninetyDayRange = defaultWindow(90);
const quickRanges = [
{ label: '今天', range: todayRange },
{ label: '昨天', range: yesterdayRange },
{ label: '近 7 天', range: sevenDayRange },
{ label: '近 30 天', range: thirtyDayRange },
{ label: '近 90 天', range: ninetyDayRange }
];
const refreshing = statistics.isFetching || mileage.isFetching || fleetVehicles.isFetching;
const resultsLoading = fleetVehicles.isLoading || mileage.isLoading || mileage.isPlaceholderData;
const exportPercent = exportProgress?.total
@@ -653,7 +698,7 @@ export default function StatisticsPage() {
</label>
<Button className="v2-primary-button v2-mileage-inline-submit" theme="solid" htmlType="submit"></Button>
<SourceStrategy value={draft.sources} onChange={(sources) => setDraft((current) => ({ ...current, sources }))} />
<div className="v2-mileage-ranges"><span></span><ButtonGroup aria-label="快捷日期范围"><Button theme={isSameRange(draft, todayRange) ? 'solid' : 'light'} onClick={() => setRange(todayRange)}></Button><Button theme={isSameRange(draft, yesterdayRange) ? 'solid' : 'light'} onClick={() => setRange(yesterdayRange)}></Button><Button theme={isSameRange(draft, sevenDayRange) ? 'solid' : 'light'} onClick={() => setRange(sevenDayRange)}> 7 </Button><Button theme={isSameRange(draft, thirtyDayRange) ? 'solid' : 'light'} onClick={() => setRange(thirtyDayRange)}> 30 </Button><Button theme={isSameRange(draft, ninetyDayRange) ? 'solid' : 'light'} onClick={() => setRange(ninetyDayRange)}> 90 </Button></ButtonGroup></div>
<MileageRangeShortcuts value={draft} ranges={quickRanges} onChange={setRange} />
</form>;
const filterValidation = validationError || criteriaError ? <p className="v2-mileage-validation" role="alert">{validationError || criteriaError}</p> : null;