refine Semi UI telemetry details

This commit is contained in:
lingniu
2026-07-18 22:28:00 +08:00
parent 76d35f54c5
commit b465d1ff7a
5 changed files with 126 additions and 16 deletions

View File

@@ -484,7 +484,9 @@ test('keeps protocol telemetry independent and explains mileage semantics', asyn
expect(complexValue).toHaveTextContent('4 个值');
expect(complexValue).toHaveTextContent('3.213.223.23');
fireEvent.click(complexValue);
expect(await screen.findByRole('dialog', { name: '遥测字段完整数据' })).toBeInTheDocument();
expect(await screen.findByRole('dialog', { name: '遥测字段详情' })).toBeInTheDocument();
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-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/ }));
@@ -515,6 +517,13 @@ test('uses compact Semi telemetry evidence cards on mobile', async () => {
expect(document.querySelector('.v2-telemetry-table')).not.toBeInTheDocument();
expect(document.querySelector('.v2-telemetry-mobile-list.semi-list')).toBeInTheDocument();
expect(document.querySelector('.v2-telemetry-mobile-item.semi-list-item')).toHaveTextContent('36km/h正常');
expect(screen.getByRole('button', { name: '查看 GPS 速度 字段详情' })).toHaveClass('v2-telemetry-mobile-action');
fireEvent.click(screen.getByRole('button', { name: '查看 GPS 速度 字段详情' }));
expect(await screen.findByRole('dialog', { name: '遥测字段详情' })).toBeInTheDocument();
expect(document.querySelector('.v2-telemetry-value-sidesheet')).toHaveClass('semi-sidesheet-bottom');
expect(document.querySelector('.v2-telemetry-value-meta')).toHaveTextContent('数据类型数值');
expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-cyan-light');
expect(screen.getByRole('button', { name: '关闭遥测字段详情' })).toBeInTheDocument();
expect(document.querySelector('.v2-vehicle-record-page')).toHaveClass('v2-vehicle-record-v3', 'is-mobile-layout');
expect(screen.getByRole('group', { name: '车辆快捷操作' }).querySelectorAll('.semi-button')).toHaveLength(5);
expect(screen.getByRole('group', { name: '车辆快捷操作' })).toHaveTextContent('切换车辆轨迹回放历史数据里程查询告警事件');

View File

@@ -11,7 +11,7 @@ import type { LatestTelemetryResponse, LatestTelemetryValue, QualityIssueRow, Ve
import { usePlatformSession } from '../auth/AuthGate';
import { canAdminister, hasMenu } from '../auth/session';
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
import { formatTelemetryTime, formatTelemetryValue, telemetryQualityLabel } from '../domain/telemetry';
import { formatTelemetryTime, formatTelemetryValue, telemetryQualityColor, telemetryQualityLabel, telemetryValueTypeLabel } from '../domain/telemetry';
import { formatZhNumber } from '../domain/formatters';
import { protocolDisplayLabel, protocolSourceLabel } from '../domain/protocols';
import { isValidAMapCoordinate } from '../../integrations/amap';
@@ -201,6 +201,9 @@ function telemetryValueDetail(item: LatestTelemetryValue) {
function TelemetryValueCell({ item, onInspect }: { item: LatestTelemetryValue; onInspect?: (item: LatestTelemetryValue) => void }) {
const presentation = telemetryValuePresentation(item);
if (presentation.complex) {
if (!onInspect) {
return <div className="v2-telemetry-value is-complex is-static"><strong>{presentation.summary}</strong><small>{presentation.preview}</small></div>;
}
return <div className="v2-telemetry-value is-complex"><Button
className="v2-telemetry-value-trigger"
theme="borderless"
@@ -215,8 +218,7 @@ function TelemetryValueCell({ item, onInspect }: { item: LatestTelemetryValue; o
}
function TelemetryQualityCell({ item }: { item: LatestTelemetryValue }) {
const color = item.quality === 'good' ? 'green' : item.quality === 'stale' ? 'orange' : 'red';
return <div className="v2-telemetry-quality"><Tag color={color} type="light" size="small">{telemetryQualityLabel(item.quality)}</Tag><small title={item.qualityReason}>{item.qualityReason || '未提供质量说明'} · {formatZhNumber(item.freshnessSeconds, 0)}s</small></div>;
return <div className="v2-telemetry-quality"><Tag color={telemetryQualityColor(item.quality)} type="light" size="small">{telemetryQualityLabel(item.quality)}</Tag><small title={item.qualityReason}>{item.qualityReason || '未提供质量说明'} · {formatZhNumber(item.freshnessSeconds, 0)}s</small></div>;
}
function TelemetryTimeCell({ item }: { item: LatestTelemetryValue }) {
@@ -239,7 +241,7 @@ 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(Boolean(inspectedValue), '.v2-telemetry-value-sidesheet', 'v2-telemetry-value-detail', '遥测字段完整数据', '关闭遥测字段完整数据');
useSideSheetA11y(Boolean(inspectedValue), '.v2-telemetry-value-sidesheet', 'v2-telemetry-value-detail', '遥测字段详情', '关闭遥测字段详情');
const indexed = useMemo(() => {
const valuesByProtocolCategory = new Map<string, LatestTelemetryResponse['values']>();
const sources = new Map<string, { protocol: string; endpoint?: string }>();
@@ -277,9 +279,12 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
? <Empty className="v2-telemetry-empty" title="暂无可展示字段" description={`该协议最近 ${data?.scannedFrames ?? 0} 帧没有可展示的标量遥测。`} />
: mobileLayout
? <List className="v2-telemetry-mobile-list" dataSource={visibleMetrics} split={false} renderItem={(item) => <List.Item className="v2-telemetry-mobile-item" key={telemetryRowKey(item)}>
<header><TelemetryFieldCell item={item} /><TelemetryValueCell item={item} onInspect={setInspectedValue} /></header>
<header><TelemetryFieldCell item={item} /><TelemetryValueCell item={item} /></header>
<div><TelemetryQualityCell item={item} /><TelemetryTimeCell item={item} /></div>
<TelemetrySourceCell item={item} />
<footer className="v2-telemetry-mobile-source">
<TelemetrySourceCell item={item} />
<Button className="v2-telemetry-mobile-action" theme="borderless" type="primary" size="small" icon={<IconChevronRight />} iconPosition="right" aria-label={`查看 ${item.label} 字段详情`} onClick={() => setInspectedValue(item)}></Button>
</footer>
</List.Item>} />
: <div className="v2-telemetry-table-wrap"><Table
className="v2-telemetry-table"
@@ -305,7 +310,10 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
<SideSheet
className="v2-telemetry-value-sidesheet"
visible={Boolean(inspectedValue)}
width={520}
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>}
@@ -316,10 +324,10 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
size="small"
row
data={[
{ key: '协议', value: protocolDisplayLabel(inspectedValue.protocol) },
{ key: '数据类型', value: inspectedValue.valueType || '—' },
{ key: '协议', value: <ProtocolTag className="v2-telemetry-detail-protocol" protocol={inspectedValue.protocol} compact /> },
{ key: '数据类型', value: telemetryValueTypeLabel(inspectedValue.valueType) },
{ key: '单位', value: inspectedValue.unit || '—' },
{ key: '质量', value: telemetryQualityLabel(inspectedValue.quality) },
{ 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) }
]}