feat: unify vehicle live metric rail
This commit is contained in:
@@ -83,12 +83,13 @@ test('polls lightweight single-vehicle realtime data and passes its report inter
|
||||
expect(screen.getByLabelText('单车任务栏')).toHaveClass('v2-workspace-command-bar', 'v2-vehicle-record-command');
|
||||
expect(screen.getByLabelText('单车任务栏')).toHaveTextContent('粤A12345');
|
||||
expect(screen.getByLabelText('单车任务栏')).toHaveTextContent('LTEST000000000001');
|
||||
expect(screen.getByRole('list', { name: '车辆实时指标' })).toHaveClass('v2-live-grid');
|
||||
expect(commandCard?.querySelectorAll('.v2-live-grid > [role="listitem"]')).toHaveLength(6);
|
||||
expect(commandCard?.querySelector('.v2-live-grid > div:first-child')).toHaveTextContent('速度');
|
||||
expect(commandCard?.querySelector('.v2-live-grid > div:nth-child(2)')).toHaveTextContent('SOC');
|
||||
expect(commandCard?.querySelector('.v2-live-grid > div:nth-child(4)')).toHaveTextContent('当日里程');
|
||||
expect(commandCard?.querySelector('.v2-live-grid > div:nth-child(5)')).toHaveTextContent('推荐来源JT/T 808');
|
||||
expect(screen.getByRole('list', { name: '车辆实时指标' })).toHaveClass('v2-workspace-metric-list');
|
||||
expect(commandCard?.querySelector('.v2-vehicle-live-metric-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
|
||||
expect(commandCard?.querySelectorAll('.v2-workspace-metric-list > [role="listitem"]')).toHaveLength(5);
|
||||
expect(commandCard?.querySelector('.v2-workspace-metric-list > span:first-child')).toHaveTextContent('速度');
|
||||
expect(commandCard?.querySelector('.v2-workspace-metric-list > span:nth-child(2)')).toHaveTextContent('SOC');
|
||||
expect(commandCard?.querySelector('.v2-workspace-metric-list > span:nth-child(4)')).toHaveTextContent('当日里程');
|
||||
expect(commandCard?.querySelector('.v2-vehicle-live-source-context')).toHaveTextContent('推荐来源JT/T 808');
|
||||
expect(commandCard?.querySelector('.v2-live-primary-protocol')).toHaveClass('v2-protocol-tag', 'semi-tag-cyan-light');
|
||||
expect(commandCard?.querySelector('.v2-identity-sources .v2-protocol-tag')).toHaveClass('semi-tag-cyan-light');
|
||||
expect(screen.getByLabelText('粤A12345 车辆档案')).toHaveFocus();
|
||||
@@ -112,9 +113,9 @@ test('polls lightweight single-vehicle realtime data and passes its report inter
|
||||
expect(locationSource).toHaveClass('semi-button', 'v2-map-source-link');
|
||||
expect(locationSource.closest('.semi-card')).toHaveClass('v2-single-map-card');
|
||||
const totalMileageSource = screen.getByRole('button', { name: '查看总里程全部来源' });
|
||||
expect(totalMileageSource).toHaveClass('semi-button', 'v2-live-source-link');
|
||||
expect(totalMileageSource.closest('.semi-card')).toHaveClass('v2-live-overview');
|
||||
expect(totalMileageSource.closest('.semi-card')).toBe(commandCard);
|
||||
expect(totalMileageSource).toHaveClass('semi-button', 'v2-workspace-metric-action');
|
||||
expect(totalMileageSource.closest('.semi-card')).toHaveClass('v2-vehicle-live-metric-rail');
|
||||
expect(totalMileageSource.closest('.v2-live-overview')).toBe(commandCard);
|
||||
fireEvent.click(locationSource);
|
||||
await waitFor(() => expect(sourceEvidence).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
@@ -445,7 +446,7 @@ test('shows unavailable live fields as dashes instead of fabricated zeroes', asy
|
||||
|
||||
const liveOverview = (await screen.findByText('最新上报')).closest('.semi-card')!;
|
||||
for (const label of ['速度', 'SOC', '总里程', '当日里程']) {
|
||||
const metric = Array.from(liveOverview.querySelectorAll('.v2-live-grid > div')).find((item) => item.querySelector('small')?.textContent === label);
|
||||
const metric = Array.from(liveOverview.querySelectorAll('.v2-workspace-metric-list > [role="listitem"]')).find((item) => item.querySelector('small')?.textContent === label);
|
||||
expect(metric).toHaveTextContent('—');
|
||||
expect(metric).not.toHaveTextContent('88.8');
|
||||
expect(metric).not.toHaveTextContent(/^0/);
|
||||
@@ -562,11 +563,11 @@ test('uses compact Semi telemetry evidence cards on mobile', async () => {
|
||||
expect(document.querySelector('.v2-identity-sources .semi-tag-blue-light')).toHaveTextContent('GB/T 32960');
|
||||
expect(document.querySelector('.v2-identity-sources .semi-tag-cyan-light')).toHaveTextContent('JT/T 808');
|
||||
expect(document.querySelector('.v2-identity-sources .semi-tag-violet-light')).toHaveTextContent('宇通 MQTT');
|
||||
expect(document.querySelector('.v2-vehicle-command-card .v2-live-grid')).toHaveAttribute('aria-label', '车辆实时指标');
|
||||
expect(document.querySelectorAll('.v2-vehicle-command-card .v2-live-grid > [role="listitem"]')).toHaveLength(6);
|
||||
expect(document.querySelector('.v2-live-metric.is-speed')).toHaveTextContent('速度');
|
||||
expect(document.querySelector('.v2-live-metric.is-today-mileage')).toHaveTextContent('当日里程');
|
||||
expect(document.querySelector('.v2-live-metric.is-source-status')).toHaveTextContent('来源状态');
|
||||
expect(document.querySelector('.v2-vehicle-command-card .v2-workspace-metric-list')).toHaveAttribute('aria-label', '车辆实时指标');
|
||||
expect(document.querySelectorAll('.v2-vehicle-command-card .v2-workspace-metric-list > [role="listitem"]')).toHaveLength(5);
|
||||
expect(document.querySelector('.v2-vehicle-command-card .v2-workspace-metric-list > span:first-child')).toHaveTextContent('速度');
|
||||
expect(document.querySelector('.v2-vehicle-command-card .v2-workspace-metric-list > span:nth-child(4)')).toHaveTextContent('当日里程');
|
||||
expect(document.querySelector('.v2-vehicle-command-card .v2-workspace-metric-list > span:nth-child(5)')).toHaveTextContent('来源状态');
|
||||
fireEvent.click(screen.getByRole('button', { name: '历史数据' }));
|
||||
expect(await screen.findByTestId('mobile-history-route')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ProtocolTag } from '../shared/ProtocolTag';
|
||||
import { SegmentedTabs } from '../shared/SegmentedTabs';
|
||||
import { VehicleSourceEvidencePanel } from '../shared/VehicleSourceEvidencePanel';
|
||||
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
||||
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { WorkspaceSideSheet, type WorkspaceSideSheetBadgeColor } from '../shared/WorkspaceSideSheet';
|
||||
import { monitorReturnFromParams, withMonitorReturn } from '../routing/monitorContext';
|
||||
@@ -466,6 +467,46 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
...(hasMenu(session, 'statistics') ? [{ key: 'statistics', label: '里程查询', icon: <IconClock />, to: withMonitorReturn(`/statistics?vins=${encodeURIComponent(detail.vin)}`, monitorReturn), type: 'tertiary' as const }] : []),
|
||||
...(hasMenu(session, 'alerts') ? [{ key: 'alerts', label: '告警事件', icon: <IconAlarm />, to: `/alerts?vin=${encodeURIComponent(detail.vin)}`, type: 'tertiary' as const }] : [])
|
||||
];
|
||||
const liveMetrics: WorkspaceQueueMetricRailItem[] = [
|
||||
{
|
||||
label: '速度',
|
||||
value: <>{availableMetric(realtime?.speedKmh, realtime?.speedAvailable)}<em className="v2-vehicle-live-unit">km/h</em></>,
|
||||
note: '实时车速',
|
||||
tone: 'primary',
|
||||
emphasis: 'primary'
|
||||
},
|
||||
{
|
||||
label: 'SOC',
|
||||
value: <>{availableMetric(realtime?.socPercent, realtime?.socAvailable)}<em className="v2-vehicle-live-unit">%</em></>,
|
||||
note: '剩余电量',
|
||||
tone: 'success',
|
||||
emphasis: 'secondary'
|
||||
},
|
||||
{
|
||||
label: '总里程',
|
||||
value: <>{availableMetric(realtime?.totalMileageKm, realtime?.mileageAvailable)}<em className="v2-vehicle-live-unit">km</em></>,
|
||||
note: <span className="v2-vehicle-live-action-note">推荐口径<IconChevronRight /></span>,
|
||||
emphasis: 'secondary',
|
||||
ariaLabel: '查看总里程全部来源',
|
||||
onClick: () => setSourceEvidenceOpen(true)
|
||||
},
|
||||
{
|
||||
label: '当日里程',
|
||||
value: <>{availableMetric(realtime?.todayMileageKm, realtime?.todayMileageAvailable)}<em className="v2-vehicle-live-unit">km</em></>,
|
||||
note: <span className="v2-vehicle-live-action-note">今日累计<IconChevronRight /></span>,
|
||||
tone: 'primary',
|
||||
emphasis: 'primary',
|
||||
ariaLabel: '查看当日里程全部来源',
|
||||
onClick: () => setSourceEvidenceOpen(true)
|
||||
},
|
||||
{
|
||||
label: '来源状态',
|
||||
value: <>{realtime?.onlineSourceCount ?? 0}<em className="v2-vehicle-live-unit">/ {detail.sourceStatus.length}</em></>,
|
||||
note: '在线来源 / 全部',
|
||||
tone: (realtime?.onlineSourceCount ?? 0) > 0 ? 'success' : 'neutral',
|
||||
emphasis: 'secondary'
|
||||
}
|
||||
];
|
||||
useLayoutEffect(() => {
|
||||
const root = recordRootRef.current;
|
||||
if (!root) return;
|
||||
@@ -529,14 +570,17 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
description="关键运行指标与推荐数据来源"
|
||||
meta={<span className="v2-live-report-meta"><i className={realtime?.online ? 'is-online' : ''} />{realtime?.online ? '实时在线' : '当前离线'} · <IconClock /><PlatformTime value={realtime?.lastSeen || identity?.lastSeen} /></span>}
|
||||
/>
|
||||
<div className="v2-live-grid" role="list" aria-label="车辆实时指标">
|
||||
<div className="v2-live-metric is-speed" role="listitem"><small>速度</small><strong>{availableMetric(realtime?.speedKmh, realtime?.speedAvailable)}<em>km/h</em></strong><span>实时车速</span></div>
|
||||
<div className="v2-live-metric is-soc" role="listitem"><small>SOC</small><strong>{availableMetric(realtime?.socPercent, realtime?.socAvailable)}<em>%</em></strong><span>剩余电量</span></div>
|
||||
<div className="v2-live-metric is-mileage" role="listitem"><small>总里程</small><Button theme="borderless" type="tertiary" className="v2-live-source-link" title="展开全部里程来源" aria-label="查看总里程全部来源" onClick={() => setSourceEvidenceOpen(true)}><span>{availableMetric(realtime?.totalMileageKm, realtime?.mileageAvailable)}<em>km</em></span><IconChevronRight /></Button><span>推荐口径</span></div>
|
||||
<div className="v2-live-metric is-today-mileage" role="listitem"><small>当日里程</small><Button theme="borderless" type="tertiary" className="v2-live-source-link" title="展开全部里程来源" aria-label="查看当日里程全部来源" onClick={() => setSourceEvidenceOpen(true)}><span>{availableMetric(realtime?.todayMileageKm, realtime?.todayMileageAvailable)}<em>km</em></span><IconChevronRight /></Button><span>今日累计</span></div>
|
||||
<div className="v2-live-metric is-source" role="listitem"><small>推荐来源</small><ProtocolTag protocol={realtime?.primaryProtocol} className="v2-live-primary-protocol" /><span title={realtime?.locationSource}>{protocolSourceLabel(realtime?.locationSource) || '当前最优来源'}</span></div>
|
||||
<div className="v2-live-metric is-source-status" role="listitem"><small>来源状态</small><strong>{realtime?.onlineSourceCount ?? 0}<em> / {detail.sourceStatus.length}</em></strong><span>在线来源 / 全部</span></div>
|
||||
</div>
|
||||
<WorkspaceMetricRail
|
||||
variant="queue"
|
||||
className="v2-vehicle-live-metric-rail"
|
||||
ariaLabel="车辆实时指标"
|
||||
items={liveMetrics}
|
||||
context={<span className="v2-vehicle-live-source-context">
|
||||
<small>推荐来源</small>
|
||||
<strong><ProtocolTag protocol={realtime?.primaryProtocol} className="v2-live-primary-protocol" compact /></strong>
|
||||
<em title={realtime?.locationSource}>{protocolSourceLabel(realtime?.locationSource) || '当前最优来源'}</em>
|
||||
</span>}
|
||||
/>
|
||||
<CurrentVehicleAddress vehicle={realtime} fallback={identity?.locationText} />
|
||||
</section>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user