refine Semi UI monitor vehicle inspector
This commit is contained in:
@@ -128,6 +128,12 @@ test('starts without a selection and supports expand, collapse, reselection, and
|
||||
expect(kpis?.querySelector('.v2-kpi:last-child')).toHaveClass('is-missing', 'is-support');
|
||||
expect(view.container.querySelector('.v2-event-strip')).toHaveClass('semi-card');
|
||||
expect(view.container.querySelector('.v2-monitor-live-tag')).toHaveClass('semi-tag');
|
||||
const liveStatus = screen.getByLabelText('实时数据状态');
|
||||
expect(liveStatus).toHaveTextContent('数据已同步');
|
||||
expect(liveStatus).toHaveTextContent('实时数据2 辆已载入');
|
||||
expect(liveStatus).toHaveTextContent('地图载荷0 个车辆点');
|
||||
expect(liveStatus).toHaveTextContent('智能刷新重点 10s · 车队 15s · 统计 30s');
|
||||
expect(liveStatus).toHaveTextContent('最近同步等待数据');
|
||||
expect(workspace).not.toHaveClass('is-detail-open');
|
||||
expect(workspace).not.toHaveClass('is-detail-collapsed');
|
||||
expect(firstVehicle).not.toHaveClass('is-selected');
|
||||
@@ -151,6 +157,7 @@ test('starts without a selection and supports expand, collapse, reselection, and
|
||||
expect(screen.getByRole('heading', { name, level: 5 })).toBeInTheDocument();
|
||||
}
|
||||
expect(screen.getByText('身份、车型与来源覆盖')).toBeInTheDocument();
|
||||
expect(screen.getByText('1/1 在线').closest('.semi-tag')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-report-summary')).toHaveTextContent('平台接收时间');
|
||||
expect(view.container.querySelector('.v2-report-summary')).toHaveTextContent('主协议JT808');
|
||||
expect(view.container.querySelector('.v2-metric-grid > div:first-child')).toHaveTextContent('速度42');
|
||||
|
||||
@@ -266,19 +266,20 @@ function VehicleDetailCard({
|
||||
<small>{vehicle.vin}</small>
|
||||
</div>
|
||||
<nav className="v2-detail-actions" aria-label="车辆快捷操作">
|
||||
{workflowLinks.map(([label, path]) => <Link key={label} to={withMonitorReturn(path, monitorReturn)}>{label}</Link>)}
|
||||
{workflowLinks.map(([label, path]) => <Link key={label} to={withMonitorReturn(path, monitorReturn)}><span>{label}</span><IconChevronRight aria-hidden="true" /></Link>)}
|
||||
</nav>
|
||||
</div>
|
||||
<section className="v2-detail-section v2-detail-report">
|
||||
<WorkspacePanelHeader
|
||||
variant="compact"
|
||||
title="最新上报"
|
||||
description="平台最新接收证据"
|
||||
meta={<Tag color={vehicle.online ? 'green' : 'grey'} type="light" size="small">{relativeFreshness(vehicle.lastSeen)}</Tag>}
|
||||
/>
|
||||
<div className="v2-report-summary">
|
||||
<span className="is-time"><small>平台接收时间</small><strong>{vehicle.lastSeen || '暂无上报'}</strong></span>
|
||||
<span><small>主协议</small><b>{vehicle.primaryProtocol || '未知'}</b></span>
|
||||
<span><small>接入来源</small><b>{accessSource || '待补充'}</b></span>
|
||||
<span className="is-time" title={vehicle.lastSeen || '暂无上报'}><small>平台接收时间</small><strong>{vehicle.lastSeen || '暂无上报'}</strong></span>
|
||||
<span title={vehicle.primaryProtocol || '未知'}><small>主协议</small><b>{vehicle.primaryProtocol || '未知'}</b></span>
|
||||
<span title={accessSource || '待补充'}><small>接入来源</small><b>{accessSource || '待补充'}</b></span>
|
||||
</div>
|
||||
<dl className="v2-detail-list">
|
||||
<div><dt>坐标</dt><dd><Button theme="borderless" type="tertiary" className="v2-detail-source-link" aria-label="查看全部位置来源" title="展开全部位置来源" onClick={() => setSourceEvidenceOpen(true)}>{hasRealtimeLocation(vehicle) ? `${vehicle.longitude.toFixed(6)}, ${vehicle.latitude.toFixed(6)}` : '—'}</Button></dd></div>
|
||||
@@ -293,12 +294,12 @@ function VehicleDetailCard({
|
||||
meta={<Tag color="blue" type="light" size="small">{vehicle.primaryProtocol || '未知协议'}</Tag>}
|
||||
/>
|
||||
<div className="v2-metric-grid">
|
||||
<div><small>速度</small><strong>{hasRealtimeSpeed(vehicle) ? formatNumber(vehicle.speedKmh, 1) : '—'}<em>{hasRealtimeSpeed(vehicle) ? 'km/h' : ''}</em></strong></div>
|
||||
<div><small>SOC</small><strong>{hasRealtimeSOC(vehicle) ? formatNumber(vehicle.socPercent, 1) : '—'}<em>{hasRealtimeSOC(vehicle) ? '%' : ''}</em></strong></div>
|
||||
<div><small>{mileageLabel}</small><Button theme="borderless" type="tertiary" className="v2-metric-source-link" aria-label="查看总里程全部来源" title="展开全部里程来源" onClick={() => setSourceEvidenceOpen(true)}><span>{hasRealtimeMileage(vehicle) ? formatNumber(vehicle.totalMileageKm, 1) : '—'}<em>{hasRealtimeMileage(vehicle) ? 'km' : ''}</em></span><IconChevronRight /></Button></div>
|
||||
<div><small>今日里程</small><Button theme="borderless" type="tertiary" className="v2-metric-source-link" aria-label="查看今日里程全部来源" title="展开全部里程来源" onClick={() => setSourceEvidenceOpen(true)}><span>{dailyMileage == null ? '—' : formatNumber(dailyMileage, 1)}<em>{dailyMileage == null ? '' : 'km'}</em></span><IconChevronRight /></Button></div>
|
||||
<div><small>状态</small><strong>{statusLabel(status)}</strong></div>
|
||||
<div className={(activeAlerts?.total ?? 0) > 0 ? 'has-alerts' : ''}><small>当前告警</small><strong>{formatNumber(activeAlerts?.total ?? 0)}<em>条</em></strong></div>
|
||||
<div className="is-speed"><small>速度</small><strong>{hasRealtimeSpeed(vehicle) ? formatNumber(vehicle.speedKmh, 1) : '—'}<em>{hasRealtimeSpeed(vehicle) ? 'km/h' : ''}</em></strong></div>
|
||||
<div className="is-soc"><small>SOC</small><strong>{hasRealtimeSOC(vehicle) ? formatNumber(vehicle.socPercent, 1) : '—'}<em>{hasRealtimeSOC(vehicle) ? '%' : ''}</em></strong></div>
|
||||
<div className="is-mileage"><small>{mileageLabel}</small><Button theme="borderless" type="tertiary" className="v2-metric-source-link" aria-label="查看总里程全部来源" title="展开全部里程来源" onClick={() => setSourceEvidenceOpen(true)}><span>{hasRealtimeMileage(vehicle) ? formatNumber(vehicle.totalMileageKm, 1) : '—'}<em>{hasRealtimeMileage(vehicle) ? 'km' : ''}</em></span><IconChevronRight /></Button></div>
|
||||
<div className="is-today"><small>今日里程</small><Button theme="borderless" type="tertiary" className="v2-metric-source-link" aria-label="查看今日里程全部来源" title="展开全部里程来源" onClick={() => setSourceEvidenceOpen(true)}><span>{dailyMileage == null ? '—' : formatNumber(dailyMileage, 1)}<em>{dailyMileage == null ? '' : 'km'}</em></span><IconChevronRight /></Button></div>
|
||||
<div className="is-status"><small>状态</small><strong>{statusLabel(status)}</strong></div>
|
||||
<div className={`is-alert${(activeAlerts?.total ?? 0) > 0 ? ' has-alerts' : ''}`}><small>当前告警</small><strong>{formatNumber(activeAlerts?.total ?? 0)}<em>条</em></strong></div>
|
||||
</div>
|
||||
{gbHighlights.length ? <div className="v2-gb-highlight-grid">{gbHighlights.map((item) => <div key={`${item.key}-${item.sourceField}`} title={item.description}><small>{item.label}</small><strong>{formatTelemetryValue(item.value, item.displayValue)}<em>{item.unit}</em></strong></div>)}</div> : null}
|
||||
</section>
|
||||
@@ -307,7 +308,7 @@ function VehicleDetailCard({
|
||||
variant="compact"
|
||||
title="车辆信息"
|
||||
description="身份、车型与来源覆盖"
|
||||
meta={`${vehicle.onlineSourceCount}/${vehicle.sourceCount} 在线`}
|
||||
meta={<Tag color={vehicle.onlineSourceCount ? 'green' : 'grey'} type="light" size="small">{vehicle.onlineSourceCount}/{vehicle.sourceCount} 在线</Tag>}
|
||||
/>
|
||||
<dl className="v2-detail-list">
|
||||
<div><dt>VIN</dt><dd>{vehicle.vin}</dd></div>
|
||||
@@ -426,6 +427,12 @@ export default function MonitorPage() {
|
||||
const driving = rows.filter((vehicle) => vehicleStatus(vehicle) === 'driving').length;
|
||||
const idle = rows.filter((vehicle) => vehicleStatus(vehicle) === 'idle').length;
|
||||
const offline = rows.filter((vehicle) => vehicleStatus(vehicle) === 'offline').length;
|
||||
const viewportLoad = mode === 'map'
|
||||
? (map.data?.clusters.length
|
||||
? `${map.data.clusters.length} 个聚合 · ${map.data.points.length} 个车辆点`
|
||||
: `${map.data?.points.length ?? 0} 个车辆点`)
|
||||
: `${realtimeListQuery.data?.items.length ?? 0} / ${realtimeListQuery.data?.total ?? 0} 辆`;
|
||||
const lastSyncAt = vehicles.dataUpdatedAt ? new Date(vehicles.dataUpdatedAt) : undefined;
|
||||
|
||||
return (
|
||||
<div className="v2-monitor-page">
|
||||
@@ -533,12 +540,17 @@ export default function MonitorPage() {
|
||||
) : null}
|
||||
</section> : <MonitorVehicleTable rows={visibleListRows} total={visibleListTotal} page={Math.floor(listOffset / listLimit) + 1} totalPages={Math.max(1, Math.ceil(visibleListTotal / listLimit))} limit={listLimit} loading={filterTransitionPending || realtimeListQuery.isFetching} onSelect={selectVehicle} onPage={(page) => setListOffset((page - 1) * listLimit)} onLimit={(next) => { setListLimit(next); setListOffset(0); }} />}
|
||||
|
||||
<Card className="v2-event-strip" bodyStyle={{ padding: 0 }}>
|
||||
<strong>实时数据状态</strong>
|
||||
<Tag className="v2-monitor-live-tag" color="green" type="light" size="small"><i className="is-online" />{vehicles.isFetching ? '正在刷新' : '实时车辆已同步'}</Tag>
|
||||
<span>{mode === 'map' ? `列表 ${visibleRows.length} 条 · 地图 ${map.data?.clusters.length ? `${map.data.clusters.length} 个聚合 + ${map.data.points.length} 个车辆点` : `${map.data?.points.length ?? 0} 个点`}` : `实时列表 ${realtimeListQuery.data?.items.length ?? 0} / ${realtimeListQuery.data?.total ?? 0} 辆`}</span>
|
||||
<span className="v2-refresh-cadence"><Tag color="blue" type="light" size="small">智能刷新</Tag>重点车辆 {MONITOR_REFRESH.selected / 1000} 秒 · 车队 {MONITOR_REFRESH.fleet / 1000} 秒 · 统计 {MONITOR_REFRESH.summary / 1000} 秒</span>
|
||||
<time>{new Date().toLocaleString('zh-CN', { hour12: false })}</time>
|
||||
<Card className="v2-event-strip" bodyStyle={{ padding: 0 }} aria-label="实时数据状态">
|
||||
<div className="v2-event-sync">
|
||||
<Tag className="v2-monitor-live-tag" color="green" type="light" size="small"><IconRefresh aria-hidden="true" />{vehicles.isFetching ? '正在同步' : '数据已同步'}</Tag>
|
||||
<span><strong>实时数据</strong><small>{visibleRows.length.toLocaleString('zh-CN')} 辆已载入</small></span>
|
||||
</div>
|
||||
<div className="v2-event-load"><small>{mode === 'map' ? '地图载荷' : '列表载荷'}</small><span>{viewportLoad}</span></div>
|
||||
<div className="v2-refresh-cadence">
|
||||
<Tag color="blue" type="light" size="small"><IconRefresh aria-hidden="true" />智能刷新</Tag>
|
||||
<span>重点 {MONITOR_REFRESH.selected / 1000}s · 车队 {MONITOR_REFRESH.fleet / 1000}s · 统计 {MONITOR_REFRESH.summary / 1000}s</span>
|
||||
</div>
|
||||
<time dateTime={lastSyncAt?.toISOString()}><span><small>最近同步</small><b>{lastSyncAt ? lastSyncAt.toLocaleTimeString('zh-CN', { hour12: false }) : '等待数据'}</b></span></time>
|
||||
</Card>
|
||||
{mobileEntryOpen ? <MobileEntry onClose={() => setMobileEntryOpen(false)} /> : null}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user