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

@@ -3,7 +3,7 @@ import {
IconAlarm, IconBox, IconCalendar, IconClock, IconCopy,
IconChevronRight, IconMapPin, IconSearch, IconTickCircle
} 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 { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
@@ -24,9 +24,9 @@ import { SegmentedTabs } from '../shared/SegmentedTabs';
import { VehicleSourceEvidencePanel } from '../shared/VehicleSourceEvidencePanel';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceSideSheet, type WorkspaceSideSheetBadgeColor } from '../shared/WorkspaceSideSheet';
import { monitorReturnFromParams, withMonitorReturn } from '../routing/monitorContext';
import { useMobileLayout } from '../hooks/useMobileLayout';
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
function fmt(value?: string) { return value?.trim() || '—'; }
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 inspectedPresentation = useMemo(() => inspectedValue ? telemetryValuePresentation(inspectedValue) : undefined, [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 valuesByProtocolCategory = new Map<string, LatestTelemetryResponse['values']>();
const sources = new Map<string, { protocol: string; endpoint?: string }>();
@@ -311,16 +314,40 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
</div>
{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>
<SideSheet
className="v2-telemetry-value-sidesheet"
<WorkspaceSideSheet
className="v2-telemetry-evidence-sidesheet"
variant="detail"
visible={!!inspectedValue}
ariaLabel="遥测字段详情"
placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 520}
height={mobileLayout ? 'min(88dvh, 780px)' : undefined}
aria-label="遥测字段详情"
title={inspectedValue ? <div className="v2-telemetry-value-title"><strong>{inspectedValue.label}</strong><span>{inspectedValue.sourceField}</span></div> : null}
onCancel={() => { setInspectedValue(undefined); setCopiedValue(false); }}
footer={<div className="v2-telemetry-value-footer"><span></span><Button theme="solid" onClick={() => { setInspectedValue(undefined); setCopiedValue(false); }}></Button></div>}
title={inspectedValue?.label}
description={inspectedValue?.sourceField}
badge={inspectedValue ? telemetryQualityLabel(inspectedValue.quality) : undefined}
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">
<Descriptions
@@ -328,12 +355,11 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
size="small"
row
data={[
{ key: '协议', value: <ProtocolTag className="v2-telemetry-detail-protocol" protocol={inspectedValue.protocol} compact /> },
{ key: '数据类型', value: telemetryValueTypeLabel(inspectedValue.valueType) },
{ 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.serverTime) }
{ key: '接收时间', value: formatTelemetryTime(inspectedValue.serverTime) },
{ key: '质量说明', value: inspectedValue.qualityReason || '未提供质量说明' }
]}
/>
<section>
@@ -347,9 +373,8 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
>{copiedValue ? '已复制' : '复制完整值'}</Button></header>
<pre>{inspectedDetail}</pre>
</section>
<footer><span></span><strong>{inspectedValue.sourceEndpoint || '协议默认来源'}</strong></footer>
</div> : null}
</SideSheet>
</WorkspaceSideSheet>
</Card>;
}