feat: unify track and telemetry workbenches

This commit is contained in:
lingniu
2026-07-19 06:29:55 +08:00
parent 4bd1ad8a85
commit f2fe5404d9
6 changed files with 146 additions and 25 deletions

View File

@@ -254,7 +254,7 @@ test('renders a map-first replay workspace and connects stop, event, and panel i
await waitFor(() => expect(client.getQueryCache().findAll({ queryKey: ['track-address'] })).toHaveLength(0)); await waitFor(() => expect(client.getQueryCache().findAll({ queryKey: ['track-address'] })).toHaveLength(0));
}); });
test('uses a Semi bottom SideSheet for mobile track criteria and evidence', async () => { test('uses the shared Semi editor SideSheet for mobile track criteria and evidence', async () => {
Object.defineProperty(window, 'matchMedia', { configurable: true, value: vi.fn(() => ({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() })) }); 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 } } }); const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks?keyword=LTEST000000000001&dateFrom=2026-07-15T00:00&dateTo=2026-07-15T23:59']}><TrackPage /></MemoryRouter></QueryClientProvider>); const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks?keyword=LTEST000000000001&dateFrom=2026-07-15T00:00&dateTo=2026-07-15T23:59']}><TrackPage /></MemoryRouter></QueryClientProvider>);
@@ -270,6 +270,7 @@ test('uses a Semi bottom SideSheet for mobile track criteria and evidence', asyn
return element; return element;
}); });
expect(sheet).toHaveClass('semi-sidesheet-inner'); expect(sheet).toHaveClass('semi-sidesheet-inner');
expect(document.querySelector('.v2-track-detail-sidesheet')).toHaveClass('v2-workspace-editor-sidesheet', 'semi-sidesheet-bottom');
expect(document.querySelector('.v2-track-detail-sidesheet .v2-track-rail')).toBeInTheDocument(); expect(document.querySelector('.v2-track-detail-sidesheet .v2-track-rail')).toBeInTheDocument();
expect(document.querySelector('.v2-track-detail-sidesheet .v2-track-query-summary')).not.toBeInTheDocument(); expect(document.querySelector('.v2-track-detail-sidesheet .v2-track-query-summary')).not.toBeInTheDocument();
expect(screen.getByLabelText('轨迹查询任务栏')).toHaveTextContent('粤A12345'); expect(screen.getByLabelText('轨迹查询任务栏')).toHaveTextContent('粤A12345');

View File

@@ -3,7 +3,7 @@ import {
IconChevronLeft, IconChevronRight, IconClose, IconDownload, IconEyeClosed, IconChevronLeft, IconChevronRight, IconClose, IconDownload, IconEyeClosed,
IconEyeOpened, IconList, IconMapPin, IconPause, IconPlay, IconRefresh, IconSearch IconEyeOpened, IconList, IconMapPin, IconPause, IconPlay, IconRefresh, IconSearch
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { Button, Card, Descriptions, Empty, Input, List, Select, SideSheet, Slider, Tag } from '@douyinfe/semi-ui'; import { Button, Card, Descriptions, Empty, Input, List, Select, Slider, Tag } from '@douyinfe/semi-ui';
import { FormEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FormEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client'; import { api } from '../../api/client';
@@ -19,10 +19,10 @@ import { VehicleCandidateList } from '../shared/VehicleCandidateList';
import { mergeVehicleCandidates } from '../shared/vehicleCandidates'; import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
import { QUERY_MEMORY } from '../queryPolicy'; import { QUERY_MEMORY } from '../queryPolicy';
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext'; import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
import { useMobileLayout } from '../hooks/useMobileLayout'; import { useMobileLayout } from '../hooks/useMobileLayout';
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
const speedOptions = [0.5, 1, 2, 4] as const; const speedOptions = [0.5, 1, 2, 4] as const;
type PlaybackSpeed = (typeof speedOptions)[number]; type PlaybackSpeed = (typeof speedOptions)[number];
@@ -309,7 +309,6 @@ export default function TrackPage() {
const [queryCollapsed, setQueryCollapsed] = useState(() => Boolean(criteria.keyword)); const [queryCollapsed, setQueryCollapsed] = useState(() => Boolean(criteria.keyword));
const animationRef = useRef<number>(); const animationRef = useRef<number>();
const lastFrameRef = useRef(0); const lastFrameRef = useRef(0);
useSideSheetA11y(mobileLayout && !railCollapsed, '.v2-track-detail-sidesheet', 'v2-track-detail-sheet', '轨迹查询与明细', '关闭轨迹查询与明细');
const params = useMemo(() => { const params = useMemo(() => {
const next = new URLSearchParams({ keyword: criteria.keyword, maxPoints: '1600' }); const next = new URLSearchParams({ keyword: criteria.keyword, maxPoints: '1600' });
@@ -394,19 +393,31 @@ export default function TrackPage() {
}; };
const progress = points.length > 1 ? boundedIndex / (points.length - 1) * 100 : 0; const progress = points.length > 1 ? boundedIndex / (points.length - 1) * 100 : 0;
const trackRail = <TrackRail draft={draft} track={track} loading={query.isFetching} activeStopIndexes={activeStopIndexes} activeEventIndexes={activeEventIndexes} tab={panelTab} queryCollapsed={queryCollapsed} onDraft={setDraft} onSubmit={submit} onTab={setPanelTab} onSelectIndex={selectRailIndex} onToggleQuery={() => setQueryCollapsed((value) => !value)} onCollapse={collapseRail} />; const trackRail = <TrackRail draft={draft} track={track} loading={query.isFetching} activeStopIndexes={activeStopIndexes} activeEventIndexes={activeEventIndexes} tab={panelTab} queryCollapsed={queryCollapsed} onDraft={setDraft} onSubmit={submit} onTab={setPanelTab} onSelectIndex={selectRailIndex} onToggleQuery={() => setQueryCollapsed((value) => !value)} onCollapse={collapseRail} />;
const mobileSheetTitle = track?.plate || draft.keyword.trim() || '轨迹查询与明细';
const mobileSheetDescription = track
? `${number(track.summary.distanceKm)} km · ${track.stops.length} 个停留 · ${track.events.length} 个事件`
: '选择车辆和时间范围';
const mobileSheetBadge = query.isFetching
? '读取中'
: track
? `${track.points.length.toLocaleString('zh-CN')}`
: '待查询';
return <div className={`v2-track-page${mobileLayout ? ' is-mobile-layout' : ''}${railCollapsed ? ' is-rail-collapsed' : ''}${monitorReturn ? ' has-monitor-return' : ''}`}> return <div className={`v2-track-page${mobileLayout ? ' is-mobile-layout' : ''}${railCollapsed ? ' is-rail-collapsed' : ''}${monitorReturn ? ' has-monitor-return' : ''}`}>
<MonitorReturnBar /> <MonitorReturnBar />
{mobileLayout ? <SideSheet {mobileLayout ? <WorkspaceSideSheet
className="v2-track-detail-sidesheet" className="v2-track-detail-sidesheet"
variant="editor"
visible={!railCollapsed} visible={!railCollapsed}
ariaLabel="轨迹查询与明细"
placement="bottom" placement="bottom"
height={mobileTrackSheetHeight(track, queryCollapsed, panelTab)} height={mobileTrackSheetHeight(track, queryCollapsed, panelTab)}
aria-label="轨迹查询与明细" title={mobileSheetTitle}
title={<div className="v2-track-detail-sheet-title"><strong></strong><span>{track ? `${number(track.summary.distanceKm)} km · ${track.stops.length} 个停留 · ${track.events.length} 个事件` : '选择车辆和时间范围'}</span></div>} description={mobileSheetDescription}
footer={null} badge={mobileSheetBadge}
badgeColor={query.isFetching ? 'blue' : track ? 'green' : 'grey'}
onCancel={collapseRail} onCancel={collapseRail}
>{trackRail}</SideSheet> : trackRail} >{trackRail}</WorkspaceSideSheet> : trackRail}
<section className="v2-track-stage"> <section className="v2-track-stage">
<TrackMap points={points} stops={track?.stops ?? []} activeIndex={boundedIndex} showStops={showStops} follow={follow} followDurationMs={playing ? Math.max(40, trackPlaybackInterval(playbackSpeed) - 10) : 180} onSelectIndex={selectIndex} onFollowChange={setFollow} /> <TrackMap points={points} stops={track?.stops ?? []} activeIndex={boundedIndex} showStops={showStops} follow={follow} followDurationMs={playing ? Math.max(40, trackPlaybackInterval(playbackSpeed) - 10) : 180} onSelectIndex={selectIndex} onFollowChange={setFollow} />

View File

@@ -495,10 +495,17 @@ test('keeps protocol telemetry independent and explains mileage semantics', asyn
expect(complexValue).toHaveTextContent('3.213.223.23'); expect(complexValue).toHaveTextContent('3.213.223.23');
fireEvent.click(complexValue); fireEvent.click(complexValue);
expect(await screen.findByRole('dialog', { name: '遥测字段详情' })).toBeInTheDocument(); expect(await screen.findByRole('dialog', { name: '遥测字段详情' })).toBeInTheDocument();
expect(document.querySelector('.v2-telemetry-evidence-sidesheet')).toHaveClass('v2-workspace-detail-sidesheet');
expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-blue-light'); expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-blue-light');
expect(document.querySelector('.v2-telemetry-value-meta')).toHaveTextContent('数据类型列表'); expect(document.querySelector('.v2-telemetry-value-meta')).toHaveTextContent('数据类型列表');
const detailSummary = screen.getByRole('list', { name: '遥测字段详情摘要' });
expect(detailSummary).toHaveTextContent('协议来源');
expect(detailSummary).toHaveTextContent('10.0.0.1:8080');
expect(detailSummary).toHaveTextContent('完整值');
expect(detailSummary).toHaveTextContent('4 个值');
expect(detailSummary).toHaveTextContent('数据质量');
expect(detailSummary).toHaveTextContent('正常');
expect(document.querySelector('.v2-telemetry-value-detail pre')).toHaveTextContent('3.24'); expect(document.querySelector('.v2-telemetry-value-detail pre')).toHaveTextContent('3.24');
expect(document.querySelector('.v2-telemetry-value-detail > footer strong')).toHaveTextContent('10.0.0.1:8080');
fireEvent.click(screen.getByRole('tab', { name: /JT\/T 808/ })); fireEvent.click(screen.getByRole('tab', { name: /JT\/T 808/ }));
expect(screen.getByRole('tab', { name: /JT\/T 808/, selected: true })).toBeInTheDocument(); expect(screen.getByRole('tab', { name: /JT\/T 808/, selected: true })).toBeInTheDocument();
expect(screen.getByText('GPS 总里程')).toBeInTheDocument(); expect(screen.getByText('GPS 总里程')).toBeInTheDocument();
@@ -530,7 +537,7 @@ test('uses compact Semi telemetry evidence cards on mobile', async () => {
expect(screen.getByRole('button', { name: '查看 GPS 速度 字段详情' })).toHaveClass('v2-telemetry-mobile-action'); expect(screen.getByRole('button', { name: '查看 GPS 速度 字段详情' })).toHaveClass('v2-telemetry-mobile-action');
fireEvent.click(screen.getByRole('button', { name: '查看 GPS 速度 字段详情' })); fireEvent.click(screen.getByRole('button', { name: '查看 GPS 速度 字段详情' }));
expect(await screen.findByRole('dialog', { name: '遥测字段详情' })).toBeInTheDocument(); expect(await screen.findByRole('dialog', { name: '遥测字段详情' })).toBeInTheDocument();
expect(document.querySelector('.v2-telemetry-value-sidesheet')).toHaveClass('semi-sidesheet-bottom'); expect(document.querySelector('.v2-telemetry-evidence-sidesheet')).toHaveClass('v2-workspace-detail-sidesheet', 'semi-sidesheet-bottom');
expect(document.querySelector('.v2-telemetry-value-meta')).toHaveTextContent('数据类型数值'); expect(document.querySelector('.v2-telemetry-value-meta')).toHaveTextContent('数据类型数值');
expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-cyan-light'); expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-cyan-light');
expect(screen.getByRole('button', { name: '关闭遥测字段详情' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '关闭遥测字段详情' })).toBeInTheDocument();

View File

@@ -3,7 +3,7 @@ import {
IconAlarm, IconBox, IconCalendar, IconClock, IconCopy, IconAlarm, IconBox, IconCalendar, IconClock, IconCopy,
IconChevronRight, IconMapPin, IconSearch, IconTickCircle IconChevronRight, IconMapPin, IconSearch, IconTickCircle
} from '@douyinfe/semi-icons'; } from '@douyinfe/semi-icons';
import { Button, Card, Descriptions, Empty, Input, List, Select, SideSheet, Table, Tag } from '@douyinfe/semi-ui'; import { Button, Card, Descriptions, Empty, Input, List, Select, Table, Tag } from '@douyinfe/semi-ui';
import { FormEvent, lazy, Suspense, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { FormEvent, lazy, Suspense, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { api } from '../../api/client'; import { api } from '../../api/client';
@@ -24,9 +24,9 @@ import { SegmentedTabs } from '../shared/SegmentedTabs';
import { VehicleSourceEvidencePanel } from '../shared/VehicleSourceEvidencePanel'; import { VehicleSourceEvidencePanel } from '../shared/VehicleSourceEvidencePanel';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceSideSheet, type WorkspaceSideSheetBadgeColor } from '../shared/WorkspaceSideSheet';
import { monitorReturnFromParams, withMonitorReturn } from '../routing/monitorContext'; import { monitorReturnFromParams, withMonitorReturn } from '../routing/monitorContext';
import { useMobileLayout } from '../hooks/useMobileLayout'; import { useMobileLayout } from '../hooks/useMobileLayout';
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
function fmt(value?: string) { return value?.trim() || '—'; } function fmt(value?: string) { return value?.trim() || '—'; }
function metric(value: number | undefined) { return Number.isFinite(value as number) ? formatZhNumber(value!, 1) : '—'; } function metric(value: number | undefined) { return Number.isFinite(value as number) ? formatZhNumber(value!, 1) : '—'; }
@@ -245,7 +245,10 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
const [copiedValue, setCopiedValue] = useState(false); const [copiedValue, setCopiedValue] = useState(false);
const inspectedPresentation = useMemo(() => inspectedValue ? telemetryValuePresentation(inspectedValue) : undefined, [inspectedValue]); const inspectedPresentation = useMemo(() => inspectedValue ? telemetryValuePresentation(inspectedValue) : undefined, [inspectedValue]);
const inspectedDetail = useMemo(() => inspectedValue ? telemetryValueDetail(inspectedValue) : '', [inspectedValue]); const inspectedDetail = useMemo(() => inspectedValue ? telemetryValueDetail(inspectedValue) : '', [inspectedValue]);
useSideSheetA11y(!!inspectedValue, '.v2-telemetry-value-sidesheet', 'v2-telemetry-value-detail', '遥测字段详情', '关闭遥测字段详情'); const closeInspection = () => {
setInspectedValue(undefined);
setCopiedValue(false);
};
const indexed = useMemo(() => { const indexed = useMemo(() => {
const valuesByProtocolCategory = new Map<string, LatestTelemetryResponse['values']>(); const valuesByProtocolCategory = new Map<string, LatestTelemetryResponse['values']>();
const sources = new Map<string, { protocol: string; endpoint?: string }>(); const sources = new Map<string, { protocol: string; endpoint?: string }>();
@@ -311,16 +314,40 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
</div> </div>
{state} {state}
<footer><b></b>{indexed.sources.map((source) => <span key={`${source.protocol}-${source.endpoint ?? ''}`} title={source.endpoint}>{protocolDisplayLabel(source.protocol)}</span>)}<small> {data?.scannedFrames ?? 0} · {formatTelemetryTime(data?.asOf)}</small></footer> <footer><b></b>{indexed.sources.map((source) => <span key={`${source.protocol}-${source.endpoint ?? ''}`} title={source.endpoint}>{protocolDisplayLabel(source.protocol)}</span>)}<small> {data?.scannedFrames ?? 0} · {formatTelemetryTime(data?.asOf)}</small></footer>
<SideSheet <WorkspaceSideSheet
className="v2-telemetry-value-sidesheet" className="v2-telemetry-evidence-sidesheet"
variant="detail"
visible={!!inspectedValue} visible={!!inspectedValue}
ariaLabel="遥测字段详情"
placement={mobileLayout ? 'bottom' : 'right'} placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 520} width={mobileLayout ? undefined : 520}
height={mobileLayout ? 'min(88dvh, 780px)' : undefined} height={mobileLayout ? 'min(88dvh, 780px)' : undefined}
aria-label="遥测字段详情" title={inspectedValue?.label}
title={inspectedValue ? <div className="v2-telemetry-value-title"><strong>{inspectedValue.label}</strong><span>{inspectedValue.sourceField}</span></div> : null} description={inspectedValue?.sourceField}
onCancel={() => { setInspectedValue(undefined); setCopiedValue(false); }} badge={inspectedValue ? telemetryQualityLabel(inspectedValue.quality) : undefined}
footer={<div className="v2-telemetry-value-footer"><span></span><Button theme="solid" onClick={() => { setInspectedValue(undefined); setCopiedValue(false); }}></Button></div>} badgeColor={inspectedValue ? telemetryQualityColor(inspectedValue.quality) as WorkspaceSideSheetBadgeColor : 'grey'}
summaryItems={inspectedValue ? [
{
label: '协议来源',
value: <ProtocolTag className="v2-telemetry-detail-protocol" protocol={inspectedValue.protocol} compact />,
detail: inspectedValue.sourceEndpoint || '协议默认来源',
tone: 'primary'
},
{
label: '完整值',
value: inspectedPresentation?.summary || '—',
detail: inspectedValue.unit ? `单位 ${inspectedValue.unit}` : '无单位'
},
{
label: '数据质量',
value: telemetryQualityLabel(inspectedValue.quality),
detail: `${formatZhNumber(inspectedValue.freshnessSeconds, 0)}s · ${inspectedValue.qualityReason || '未提供说明'}`,
tone: inspectedValue.quality === 'good' ? 'success' : inspectedValue.quality === 'stale' ? 'warning' : 'danger'
}
] : []}
footerNote="完整值按需渲染,不影响实时遥测刷新。"
primaryAction={{ label: '完成', onClick: closeInspection }}
onCancel={closeInspection}
> >
{inspectedValue ? <div className="v2-telemetry-value-detail"> {inspectedValue ? <div className="v2-telemetry-value-detail">
<Descriptions <Descriptions
@@ -328,12 +355,11 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
size="small" size="small"
row row
data={[ data={[
{ key: '协议', value: <ProtocolTag className="v2-telemetry-detail-protocol" protocol={inspectedValue.protocol} compact /> },
{ key: '数据类型', value: telemetryValueTypeLabel(inspectedValue.valueType) }, { key: '数据类型', value: telemetryValueTypeLabel(inspectedValue.valueType) },
{ key: '单位', value: inspectedValue.unit || '—' }, { key: '单位', value: inspectedValue.unit || '—' },
{ key: '质量', value: <Tag className="v2-telemetry-detail-quality" color={telemetryQualityColor(inspectedValue.quality)} type="light" size="small">{telemetryQualityLabel(inspectedValue.quality)}</Tag> },
{ key: '设备时间', value: formatTelemetryTime(inspectedValue.deviceTime) }, { key: '设备时间', value: formatTelemetryTime(inspectedValue.deviceTime) },
{ key: '接收时间', value: formatTelemetryTime(inspectedValue.serverTime) } { key: '接收时间', value: formatTelemetryTime(inspectedValue.serverTime) },
{ key: '质量说明', value: inspectedValue.qualityReason || '未提供质量说明' }
]} ]}
/> />
<section> <section>
@@ -347,9 +373,8 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
>{copiedValue ? '已复制' : '复制完整值'}</Button></header> >{copiedValue ? '已复制' : '复制完整值'}</Button></header>
<pre>{inspectedDetail}</pre> <pre>{inspectedDetail}</pre>
</section> </section>
<footer><span></span><strong>{inspectedValue.sourceEndpoint || '协议默认来源'}</strong></footer>
</div> : null} </div> : null}
</SideSheet> </WorkspaceSideSheet>
</Card>; </Card>;
} }

View File

@@ -95,6 +95,10 @@ describe('V2 production entry', () => {
expect(corePageSources.VehiclePage).toContain('<List.Item className="v2-telemetry-mobile-item"'); expect(corePageSources.VehiclePage).toContain('<List.Item className="v2-telemetry-mobile-item"');
expect(corePageSources.VehiclePage).toContain('className="v2-telemetry-mobile-action"'); expect(corePageSources.VehiclePage).toContain('className="v2-telemetry-mobile-action"');
expect(corePageSources.VehiclePage).toContain("placement={mobileLayout ? 'bottom' : 'right'}"); expect(corePageSources.VehiclePage).toContain("placement={mobileLayout ? 'bottom' : 'right'}");
expect(corePageSources.VehiclePage).toContain("from '../shared/WorkspaceSideSheet'");
expect(corePageSources.VehiclePage).toContain('<WorkspaceSideSheet');
expect(corePageSources.VehiclePage).toContain('className="v2-telemetry-evidence-sidesheet"');
expect(corePageSources.VehiclePage).not.toContain('useSideSheetA11y(!!inspectedValue');
expect(corePageSources.VehiclePage).toContain("item?.sourceEndpoint ?? ''"); expect(corePageSources.VehiclePage).toContain("item?.sourceEndpoint ?? ''");
expect(corePageSources.VehiclePage).toContain('const columns = useMemo(() => ['); expect(corePageSources.VehiclePage).toContain('const columns = useMemo(() => [');
expect(corePageSources.VehiclePage).toContain('<List'); expect(corePageSources.VehiclePage).toContain('<List');
@@ -368,6 +372,10 @@ describe('V2 production entry', () => {
expect(corePageSources.TrackPage).toContain('<List className="v2-track-evidence-list v2-track-event-list"'); expect(corePageSources.TrackPage).toContain('<List className="v2-track-evidence-list v2-track-event-list"');
expect(corePageSources.TrackPage).toContain('<List.Item className="v2-track-evidence-item"'); expect(corePageSources.TrackPage).toContain('<List.Item className="v2-track-evidence-item"');
expect(corePageSources.TrackPage).toContain('<Empty image={<IconMapPin />} title="先选择车辆,再开始轨迹回放"'); expect(corePageSources.TrackPage).toContain('<Empty image={<IconMapPin />} title="先选择车辆,再开始轨迹回放"');
expect(corePageSources.TrackPage).toContain("from '../shared/WorkspaceSideSheet'");
expect(corePageSources.TrackPage).toContain('<WorkspaceSideSheet');
expect(corePageSources.TrackPage).toContain('variant="editor"');
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).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('<section className="v2-track-overview-section v2-track-source-card">');
expect(corePageSources.TrackPage).toContain('<List className="v2-track-source-list"'); expect(corePageSources.TrackPage).toContain('<List className="v2-track-source-list"');

View File

@@ -21129,6 +21129,75 @@
box-shadow: none; box-shadow: none;
} }
.v2-track-detail-sidesheet.v2-workspace-editor-sidesheet .v2-workspace-config-body,
.v2-track-detail-sidesheet.v2-workspace-editor-sidesheet .v2-workspace-config-content,
.v2-track-detail-sidesheet.v2-workspace-editor-sidesheet .v2-track-rail {
height: 100%;
}
.v2-track-detail-sidesheet.v2-workspace-editor-sidesheet .v2-workspace-config-title small {
font-variant-numeric: tabular-nums;
}
.v2-telemetry-evidence-sidesheet .v2-workspace-config-content {
padding: 10px;
}
.v2-telemetry-evidence-sidesheet .v2-workspace-config-summary > span:first-child strong {
overflow: visible;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-detail-protocol.semi-tag {
min-height: 22px;
margin: 0;
border-radius: 999px;
font-size: 9px;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-detail {
gap: 10px;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-meta.semi-descriptions,
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-detail > section {
border-color: #dce5ef;
border-radius: 11px;
box-shadow: 0 7px 20px rgba(31, 53, 80, .045);
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-detail pre {
min-height: 220px;
max-height: none;
overflow: visible;
}
@media (max-width: 680px) {
.v2-telemetry-evidence-sidesheet .v2-workspace-config-content {
padding: 8px;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-meta .semi-descriptions-item {
min-width: 112px;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-detail > section > header {
min-height: 50px;
align-items: center;
flex-direction: row;
padding: 8px 10px 8px 12px;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-detail > section > header .semi-button {
width: auto;
min-width: 108px;
}
.v2-telemetry-evidence-sidesheet .v2-telemetry-value-detail pre {
min-height: 260px;
font-size: 9px;
}
}
.v2-history-export-sidesheet .v2-export-jobs.semi-card { .v2-history-export-sidesheet .v2-export-jobs.semi-card {
min-height: 100%; min-height: 100%;
border-color: #dce5ef; border-color: #dce5ef;