feat(web): refine track and mileage workspaces
This commit is contained in:
@@ -55,9 +55,8 @@ test('renders only the desktop matrix with dates as columns and a period total',
|
||||
expect(screen.getAllByText('7/13').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('7/14').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('粤A12345').length).toBeGreaterThan(0);
|
||||
expect(screen.getByLabelText('里程查询工作区')).toHaveClass('v2-workspace-command-bar', 'v2-mileage-command-bar');
|
||||
expect(screen.getByRole('heading', { name: '车辆里程分析', level: 5 })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('里程查询工作区')).toHaveTextContent('GB32960 优先');
|
||||
expect(screen.queryByLabelText('里程查询工作区')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('heading', { name: '车辆里程分析' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '里程范围', level: 5 })).toBeInTheDocument();
|
||||
expect(screen.getByText('车牌、自然日与里程数据源策略')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-date-range.semi-datepicker')).toBeInTheDocument();
|
||||
@@ -74,7 +73,7 @@ test('renders only the desktop matrix with dates as columns and a period total',
|
||||
expect(screen.getByText('已绑定主车辆').closest('[role="listitem"]')).toHaveClass('is-primary', 'is-success');
|
||||
expect(screen.getByText('车辆每日里程').closest('.semi-card')).toHaveClass('v2-mileage-results');
|
||||
expect(view.container.querySelector('.v2-mileage-query-panel.semi-card')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-discovery-shell > .v2-mileage-command-bar')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-discovery-shell > .v2-mileage-command-bar')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-page > .v2-page-heading')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-summary')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
|
||||
expect(view.container.querySelector('.v2-mileage-summary .v2-workspace-metric-list')).toHaveAttribute('aria-label', '里程查询统计信息');
|
||||
|
||||
@@ -15,7 +15,6 @@ import { TablePagination } from '../shared/TablePagination';
|
||||
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
|
||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
||||
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||
@@ -705,15 +704,6 @@ export default function StatisticsPage() {
|
||||
return <div className="v2-mileage-page" ref={pageRef}>
|
||||
<MonitorReturnBar />
|
||||
<div className="v2-mileage-discovery-shell">
|
||||
<WorkspaceCommandBar
|
||||
className="v2-mileage-command-bar"
|
||||
ariaLabel="里程查询工作区"
|
||||
title="车辆里程分析"
|
||||
description="按自然日对比车辆行驶里程与协议来源"
|
||||
status={`${criteria.vehicles.length ? `${criteria.vehicles.length} 辆` : '全部授权车辆'} · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天`}
|
||||
statusColor={criteria.vehicles.length ? 'blue' : 'grey'}
|
||||
meta={<span>{criteria.sources.find((source) => source.enabled)?.protocol ?? '未配置'} 优先</span>}
|
||||
/>
|
||||
{mobileLayout ? <div className="v2-mileage-mobile-discovery">
|
||||
<MobileFilterToggle
|
||||
title="里程范围"
|
||||
|
||||
@@ -225,7 +225,7 @@ test('renders a map-first replay workspace and connects stop, event, and panel i
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '概览' }));
|
||||
expect(view.container.querySelectorAll('.v2-track-overview-section')).toHaveLength(3);
|
||||
expect(view.container.querySelectorAll('.v2-track-overview-section.semi-card')).toHaveLength(0);
|
||||
expect(view.container.querySelectorAll('.v2-track-overview-section.semi-card')).toHaveLength(3);
|
||||
expect(view.container.querySelector('.v2-track-overview-descriptions.semi-descriptions')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-track-source-list.semi-list')).toBeInTheDocument();
|
||||
expect(view.container.querySelectorAll('.v2-track-source-item.semi-list-item')).toHaveLength(1);
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { TrackPlaybackEvent, TrackPlaybackResponse, VehicleRow } from '../.
|
||||
import { protocolDisplayLabel } from '../domain/protocols';
|
||||
import { buildTrackRailSelection, compactTrackWindowLabel, downloadTrackCsv, formatDuration, sampledEventIndex, TRACK_ADDRESS_SETTLE_MS, trackAddressCoordinate, trackPlaybackInterval, trackPointMileageAvailable, type TrackAddressCoordinate } from '../domain/track';
|
||||
import { TrackMap } from '../map/TrackMap';
|
||||
import { InlineError, PanelEmpty, WorkspaceEmptyGuide } from '../shared/AsyncState';
|
||||
import { InlineError, PanelEmpty, PanelLoading, WorkspaceEmptyGuide } from '../shared/AsyncState';
|
||||
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
|
||||
import { ProtocolTag } from '../shared/ProtocolTag';
|
||||
import { SegmentedTabs } from '../shared/SegmentedTabs';
|
||||
@@ -210,7 +210,7 @@ function VehiclePicker({ value, onChange, onSelect }: { value: string; onChange:
|
||||
function OverviewPanel({ track }: { track: TrackPlaybackResponse }) {
|
||||
const distanceLabel = track.summary.distanceMethod === 'coordinates' ? 'GPS 轨迹累计' : track.summary.distanceMethod === 'odometer' ? '协议总里程差' : '行驶里程';
|
||||
return <div className="v2-track-overview-panel">
|
||||
<section className="v2-track-overview-section">
|
||||
<Card className="v2-track-overview-section v2-track-overview-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader variant="compact" title="行程概览" meta={<Tag color={track.sampled ? 'orange' : 'green'} type="light" size="small">{track.sampled ? '地图已抽稀' : '完整点集'}</Tag>} />
|
||||
<div className="v2-track-overview-card-body"><Descriptions className="v2-track-overview-descriptions" align="left" size="small" data={[
|
||||
{ key: '开始时间', value: dateTime(track.summary.startTime) },
|
||||
@@ -220,15 +220,15 @@ function OverviewPanel({ track }: { track: TrackPlaybackResponse }) {
|
||||
{ key: '平均 / 最高速度', value: `${number(track.summary.averageSpeedKmh)} / ${number(track.summary.maximumSpeedKmh)} km/h` },
|
||||
{ key: '停车 / 分段', value: `${track.summary.stopCount} / ${track.summary.segmentCount}` }
|
||||
]} /></div>
|
||||
</section>
|
||||
<section className="v2-track-overview-section v2-track-source-card">
|
||||
</Card>
|
||||
<Card className="v2-track-overview-section v2-track-overview-card v2-track-source-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader variant="compact" title="数据来源" meta={<Tag color="blue" type="light" size="small">{track.coverage.totalPoints.toLocaleString('zh-CN')} 个源点</Tag>} />
|
||||
<div className="v2-track-overview-card-body"><List className="v2-track-source-list">{track.sources.map((source) => <List.Item className="v2-track-source-item" key={source.protocol}><ProtocolTag className="v2-track-protocol-tag" protocol={source.protocol} compact /><strong>{source.pointCount.toLocaleString('zh-CN')} 点</strong><small>{time(source.startTime)}–{time(source.endTime)}</small></List.Item>)}</List></div>
|
||||
</section>
|
||||
<section className={`v2-track-overview-section v2-track-quality-card is-${track.quality.status}`}>
|
||||
</Card>
|
||||
<Card className={`v2-track-overview-section v2-track-overview-card v2-track-quality-card is-${track.quality.status}`} bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader variant="compact" title="轨迹质量" meta={<Tag color={track.quality.status === 'good' ? 'green' : 'orange'} type="light" size="small">{track.quality.status === 'good' ? '通过' : '需关注'}</Tag>} />
|
||||
<div className="v2-track-overview-card-body"><p>{track.quality.evidence}</p><small>无效 {track.quality.invalidCoordinatePoints} · 重复 {track.quality.duplicatePoints} · 漂移 {track.quality.driftPoints} · 大间隔 {track.quality.largeGapCount}</small></div>
|
||||
</section>
|
||||
</Card>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ export default function TrackPage() {
|
||||
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.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 }}>
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources[name]).not.toContain("from '../shared/PageHeader'");
|
||||
expect(corePageSources[name]).not.toContain('<PageHeader');
|
||||
}
|
||||
for (const name of ['VehiclePage', 'TrackPage', 'AccessPage', 'OperationsPage', 'StatisticsPage', 'UsersPage']) {
|
||||
for (const name of ['VehiclePage', 'TrackPage', 'AccessPage', 'OperationsPage', 'UsersPage']) {
|
||||
expect(corePageSources[name]).toContain("from '../shared/WorkspaceCommandBar'");
|
||||
expect(corePageSources[name]).toContain('<WorkspaceCommandBar');
|
||||
}
|
||||
@@ -446,7 +446,7 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-current-card"');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-empty-state"');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-playback-dock"');
|
||||
expect(corePageSources.TrackPage).toContain('<section className="v2-track-overview-section">');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-overview-section v2-track-overview-card"');
|
||||
expect(corePageSources.TrackPage).toContain('<Descriptions className="v2-track-overview-descriptions"');
|
||||
expect(corePageSources.TrackPage).toContain('<PanelEmpty className="v2-track-rail-empty"');
|
||||
expect(corePageSources.TrackPage).toContain('<List className="v2-track-evidence-list v2-track-stop-list"');
|
||||
@@ -462,12 +462,14 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.TrackPage).toContain("primaryAction={{ label: '查看地图'");
|
||||
expect(corePageSources.TrackPage).not.toContain('useSideSheetA11y(mobileLayout');
|
||||
expect(corePageSources.TrackPage).not.toContain('<Card className="v2-track-overview-card"><header><strong>行程概览</strong><Tag color={track.sampled ? \'orange\' : \'green\'} type="light" size="small">{track.sampled ? \'地图已抽稀\' : \'完整点集\'}</Tag></header><dl>');
|
||||
expect(corePageSources.TrackPage).toContain('<section className="v2-track-overview-section v2-track-source-card">');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-overview-section v2-track-overview-card v2-track-source-card"');
|
||||
expect(corePageSources.TrackPage).toContain('<List className="v2-track-source-list"');
|
||||
expect(corePageSources.TrackPage).toContain('<List.Item className="v2-track-source-item"');
|
||||
expect(corePageSources.TrackPage).not.toContain('<Card className="v2-track-source-item"');
|
||||
expect(corePageSources.TrackPage).toContain('<section className={`v2-track-overview-section v2-track-quality-card');
|
||||
expect(corePageSources.TrackPage).not.toContain('<section className={`v2-track-quality-card');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-overview-section v2-track-overview-card"');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className={`v2-track-overview-section v2-track-overview-card v2-track-quality-card');
|
||||
expect(corePageSources.TrackPage).toContain('<PanelLoading className="v2-track-loading"');
|
||||
expect(corePageSources.TrackPage).not.toContain('<span className="v2-spinner"');
|
||||
expect(corePageSources.TrackPage).not.toContain('<article key={source.protocol}');
|
||||
expect(corePageSources.TrackPage).not.toContain('<article className="v2-track-current-card"');
|
||||
expect(corePageSources.TrackPage).not.toContain('<footer className="v2-track-playback-dock"');
|
||||
@@ -476,6 +478,9 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.TrackPage).not.toContain('<input');
|
||||
expect(corePageSources.TrackPage).not.toContain('<button');
|
||||
expect(corePageSources.StatisticsPage).toContain('<Input');
|
||||
expect(corePageSources.StatisticsPage).not.toContain("from '../shared/WorkspaceCommandBar'");
|
||||
expect(corePageSources.StatisticsPage).not.toContain('<WorkspaceCommandBar');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('title="车辆里程分析"');
|
||||
expect(corePageSources.StatisticsPage).toContain('className="v2-mileage-query-panel"');
|
||||
expect(corePageSources.StatisticsPage).toContain('<WorkspaceMetricRail');
|
||||
expect(corePageSources.StatisticsPage).toContain('variant="queue"');
|
||||
|
||||
@@ -15033,7 +15033,7 @@
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 0 0 12px 12px;
|
||||
border-radius: 12px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -24142,6 +24142,23 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v2-track-loading.v2-state-surface {
|
||||
width: min(330px, calc(100% - 32px));
|
||||
min-height: 82px;
|
||||
height: auto;
|
||||
align-items: stretch;
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.v2-track-loading .v2-state-message {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.v2-track-loading .v2-state-skeleton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-track-detail-sidesheet .v2-workspace-config-summary {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -24234,7 +24251,7 @@
|
||||
.v2-track-current-card.semi-card {
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
width: min(404px, calc(100% - 350px));
|
||||
width: min(440px, calc(100% - 280px));
|
||||
border-radius: 13px;
|
||||
box-shadow: 0 14px 38px rgba(26, 47, 73, .14);
|
||||
}
|
||||
@@ -24252,6 +24269,12 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.v2-track-current-card > .semi-card-body > .v2-track-current-metrics > span:last-child strong {
|
||||
overflow: visible;
|
||||
font-size: clamp(13px, 1.1vw, 15px);
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.v2-track-current-card > .semi-card-body > .v2-track-current-evidence {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user