feat(web): focus track empty workspace

This commit is contained in:
lingniu
2026-07-20 07:35:44 +08:00
parent 19c66dc1f6
commit 3c9eea527c
2 changed files with 9 additions and 6 deletions

View File

@@ -348,10 +348,14 @@ test('keeps the mobile track canvas focused when the viewport crosses the respon
test('keeps one clear mobile query entry before a vehicle is selected', async () => {
Object.defineProperty(window, 'matchMedia', { configurable: true, value: vi.fn(() => ({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() })) });
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks']}><TrackPage /></MemoryRouter></QueryClientProvider>);
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks']}><TrackPage /></MemoryRouter></QueryClientProvider>);
expect(await screen.findByRole('button', { name: /选择车辆/ })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: '打开轨迹详情' })).not.toBeInTheDocument();
expect(screen.queryByRole('toolbar', { name: '地图轨迹工具' })).not.toBeInTheDocument();
expect(screen.queryByRole('slider', { name: '轨迹播放进度' })).not.toBeInTheDocument();
expect(view.container.querySelector('.v2-track-playback-dock')).not.toBeInTheDocument();
expect(view.container.querySelector('.v2-workspace-empty-guide-eyebrow')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /选择车辆/ }));
expect(await screen.findByRole('dialog', { name: '轨迹查询与明细' })).toBeInTheDocument();
expect(screen.getByRole('list', { name: '轨迹查询与明细摘要' })).toHaveTextContent('查询状态待查询请先选择车辆');

View File

@@ -509,11 +509,11 @@ export default function TrackPage() {
<small>{number(track.summary.distanceKm)} km · {track.stops.length} </small>
</span>
</Button> : null}
<div className="v2-track-stage-tools" role="toolbar" aria-label="地图轨迹工具">
{track?.points.length ? <div className="v2-track-stage-tools" role="toolbar" aria-label="地图轨迹工具">
<Button theme="borderless" aria-label={follow ? '关闭车辆跟随' : '开启车辆跟随'} className={follow ? 'is-active' : ''} icon={<IconMapPin />} disabled={!points.length} onClick={() => setFollow((value) => !value)}>{follow ? '跟随车辆' : '自由浏览'}</Button>
<Button theme="borderless" aria-label={showStops ? '隐藏停留点' : '显示停留点'} className={showStops ? 'is-active' : ''} icon={showStops ? <IconEyeOpened /> : <IconEyeClosed />} disabled={!track?.stops.length} onClick={() => setShowStops((value) => !value)}></Button>
<Button theme="borderless" aria-label="导出轨迹 CSV" icon={<IconDownload />} disabled={!track?.points.length} onClick={() => track && downloadTrackCsv(track)}></Button>
</div>
</div> : null}
{track?.points.length ? <>
<Card className="v2-track-current-card" bodyStyle={{ padding: 0 }}>
@@ -533,7 +533,6 @@ export default function TrackPage() {
<p title={addressSettling ? undefined : addressQuery.data?.formattedAddress}>{playing ? '播放中,暂停地址解析' : addressSettling ? '位置已变化,等待地址解析…' : addressQuery.isFetching ? '地址解析中…' : addressQuery.data?.formattedAddress || `${current?.longitude.toFixed(6)}, ${current?.latitude.toFixed(6)}`}</p>
</Card>
</> : <Card className="v2-track-empty-state" bodyStyle={{ padding: 0 }}><WorkspaceEmptyGuide
eyebrow="轨迹回放"
icon={<IconMapPin />}
title="先选择车辆,再开始轨迹回放"
description="地图会呈现完整路径、停留点、事件点与逐帧播放位置。"
@@ -548,7 +547,7 @@ export default function TrackPage() {
{query.isFetching ? <PanelLoading className="v2-track-loading" compact title="正在生成轨迹" description="正在校验来源覆盖并整理停留与事件证据。" /> : null}
{query.isError ? <div className="v2-track-error"><InlineError message={query.error instanceof Error ? query.error.message : '轨迹查询失败'} onRetry={() => query.refetch()} /></div> : null}
<Card className="v2-track-playback-dock" bodyStyle={{ padding: 0 }}>
{track?.points.length ? <Card className="v2-track-playback-dock" bodyStyle={{ padding: 0 }}>
<div className="v2-track-dock-progress">
{track ? <SegmentRail track={track} /> : <div className="v2-track-segment-placeholder" />}
<Slider key={`track-progress-${points.length}`} className="v2-track-progress-slider" aria-label="轨迹播放进度" min={0} max={Math.max(0, points.length - 1)} step={1} value={boundedIndex} onChange={(value) => selectIndex(Number(value))} disabled={!points.length} showBoundary={false} tipFormatter={(value) => `${Number(value) + 1} / ${points.length} 个数据点`} />
@@ -563,7 +562,7 @@ export default function TrackPage() {
<label><span id="track-playback-speed-label"></span><Select aria-labelledby="track-playback-speed-label" value={playbackSpeed} onChange={(value) => setPlaybackSpeed(Number(value) as PlaybackSpeed)} optionList={speedOptions.map((speed) => ({ value: speed, label: `${speed}×` }))} /></label>
<Button theme="borderless" aria-label="回到起点" icon={<IconRefresh />} onClick={() => selectIndex(0)} disabled={!boundedIndex} />
</div>
</Card>
</Card> : null}
</section>
</div>;
}