refactor: unify guided workspace empty states
This commit is contained in:
@@ -205,7 +205,7 @@ test('uses one focused Semi bottom SideSheet for the complete mobile history que
|
||||
mocks.historyExports.mockResolvedValue([]);
|
||||
renderPage('/history');
|
||||
|
||||
await screen.findByText('选择车辆开始查询');
|
||||
await screen.findByText('选择车辆,开始核验历史证据');
|
||||
const trigger = screen.getByRole('button', { name: '修改查询范围:请选择车辆' });
|
||||
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(screen.queryByRole('button', { name: '展开趋势' })).not.toBeInTheDocument();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';
|
||||
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||
import { IconCalendar, IconChevronRight, IconClose, IconDownload, IconMapPin, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Empty, Input, Progress, Select, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
|
||||
import { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -8,7 +8,7 @@ import type { HistoryDataResponse, HistoryDataRow, HistoryExportJob, HistoryExpo
|
||||
import { buildHistorySeriesPanels, formatExportFileSize, formatHistoryValue, formatSeriesGrain, historyExportPollInterval, parseHistoryKeywords } from '../domain/history';
|
||||
import { downloadBlob } from '../domain/download';
|
||||
import { formatShanghaiDateTime } from '../domain/formatters';
|
||||
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||
import { InlineError, PanelEmpty, PanelLoading, WorkspaceEmptyGuide } from '../shared/AsyncState';
|
||||
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
||||
@@ -593,11 +593,26 @@ export default function HistoryPage() {
|
||||
{dataQuery.isPending && keywords.length
|
||||
? <PanelLoading className="v2-history-loading" title="正在加载当前筛选范围的历史数据" description="按车辆、时间和协议整理可追溯证据。" />
|
||||
: !resultRows.length
|
||||
? <PanelEmpty className="v2-history-empty"
|
||||
title={keywords.length ? '当前条件没有历史记录' : '选择车辆开始查询'}
|
||||
description={keywords.length ? '调整车辆、时间或数据来源后重试。' : '支持车牌或 VIN,最多可同时查询 5 台车辆。'}
|
||||
action={mobileLayout ? <Button className="v2-history-empty-action" theme="solid" icon={<IconSearch />} aria-label={keywords.length ? '调整查询范围' : '选择车辆并查询'} onClick={() => setFiltersCollapsed(false)}>{keywords.length ? '调整查询范围' : '选择车辆并查询'}</Button> : null}
|
||||
/>
|
||||
? keywords.length
|
||||
? <PanelEmpty
|
||||
className="v2-history-empty"
|
||||
title="当前条件没有历史记录"
|
||||
description="调整车辆、时间或数据来源后重试。"
|
||||
action={<Button className="v2-history-empty-action" theme="light" icon={<IconSearch />} aria-label="调整查询范围" onClick={() => setFiltersCollapsed(false)}>调整查询范围</Button>}
|
||||
/>
|
||||
: <WorkspaceEmptyGuide
|
||||
className="v2-history-empty"
|
||||
eyebrow="从一个明确的车辆范围开始"
|
||||
icon={<IconSearch />}
|
||||
title="选择车辆,开始核验历史证据"
|
||||
description="一次最多查询 5 台车辆,结果会保留协议来源、质量说明与 RAW 证据。"
|
||||
steps={[
|
||||
{ icon: <IconSearch />, title: '选择车辆', description: '支持车牌、VIN 与批量粘贴' },
|
||||
{ icon: <IconCalendar />, title: '确认时间', description: '按上海时区精确到分钟' },
|
||||
{ icon: <IconMapPin />, title: '核验数据', description: '位置、RAW 与日里程统一检索' }
|
||||
]}
|
||||
action={<Button className="v2-history-empty-action" theme="solid" icon={<IconSearch />} aria-label="选择车辆并查询" onClick={() => setFiltersCollapsed(false)}>选择车辆并查询</Button>}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
{keywords.length ? <footer><TablePagination page={page} totalPages={totalPages} info={`共 ${(result?.total ?? 0).toLocaleString('zh-CN')} 条`} onPageChange={(next) => setOffset((next - 1) * limit)} pageSize={mobileLayout ? undefined : limit} onPageSizeChange={(next) => { setLimit(next); setOffset(0); }} pageSizeOptions={mobileLayout ? undefined : [{ value: 20, label: '20 条/页' }, { value: 50, label: '50 条/页' }, { value: 100, label: '100 条/页' }]} /></footer> : null}
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { TrackPlaybackEvent, TrackPlaybackResponse, VehicleRow } from '../.
|
||||
import { protocolDisplayLabel } from '../domain/protocols';
|
||||
import { buildTrackRailSelection, downloadTrackCsv, formatDuration, sampledEventIndex, TRACK_ADDRESS_SETTLE_MS, trackAddressCoordinate, trackPlaybackInterval, trackPointMileageAvailable, type TrackAddressCoordinate } from '../domain/track';
|
||||
import { TrackMap } from '../map/TrackMap';
|
||||
import { InlineError } from '../shared/AsyncState';
|
||||
import { InlineError, WorkspaceEmptyGuide } from '../shared/AsyncState';
|
||||
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
|
||||
import { ProtocolTag } from '../shared/ProtocolTag';
|
||||
import { SegmentedTabs } from '../shared/SegmentedTabs';
|
||||
@@ -513,7 +513,18 @@ export default function TrackPage() {
|
||||
</div>
|
||||
<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 }}><Empty image={<IconMapPin />} title="先选择车辆,再开始轨迹回放" description="地图会显示完整路径、停留点、事件点和播放位置。"><Button theme="solid" icon={<IconSearch />} onClick={() => setRailCollapsed(false)}>选择车辆</Button></Empty></Card>}
|
||||
</> : <Card className="v2-track-empty-state" bodyStyle={{ padding: 0 }}><WorkspaceEmptyGuide
|
||||
eyebrow="轨迹回放"
|
||||
icon={<IconMapPin />}
|
||||
title="先选择车辆,再开始轨迹回放"
|
||||
description="地图会呈现完整路径、停留点、事件点与逐帧播放位置。"
|
||||
steps={[
|
||||
{ icon: <IconSearch />, title: '选择车辆', description: '车牌或 VIN 精确定位' },
|
||||
{ icon: <IconMapPin />, title: '确认来源', description: '自动选择最佳定位来源' },
|
||||
{ icon: <IconPlay />, title: '播放核验', description: '逐点查看速度、里程与事件' }
|
||||
]}
|
||||
action={<Button theme="solid" icon={<IconSearch />} onClick={() => setRailCollapsed(false)}>选择车辆</Button>}
|
||||
/></Card>}
|
||||
|
||||
{query.isFetching ? <div className="v2-track-loading"><span className="v2-spinner" />正在读取并加工历史轨迹</div> : null}
|
||||
{query.isError ? <div className="v2-track-error"><InlineError message={query.error instanceof Error ? query.error.message : '轨迹查询失败'} onRetry={() => query.refetch()} /></div> : null}
|
||||
|
||||
Reference in New Issue
Block a user