refine Semi UI access evidence

This commit is contained in:
lingniu
2026-07-18 17:37:29 +08:00
parent 82d78926c6
commit 801a9851ea
3 changed files with 186 additions and 8 deletions

View File

@@ -57,6 +57,11 @@ test('removes old access rows immediately when the vehicle filter scope changes'
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
}
expect(screen.getByRole('navigation', { name: '接入差异筛选' })).toHaveClass('v2-access-status-tabs');
const protocolCoverage = screen.getByLabelText('真实协议来源概览');
expect(protocolCoverage).toHaveTextContent('真实来源覆盖');
expect(protocolCoverage).toHaveTextContent('32960');
expect(protocolCoverage).toHaveTextContent('808');
expect(protocolCoverage).toHaveTextContent('宇通');
expect(screen.getByRole('columnheader', { name: '差异摘要' })).toBeInTheDocument();
expect(screen.queryByRole('columnheader', { name: '真实来源' })).not.toBeInTheDocument();
expect(screen.getByText('已接来源状态正常')).toBeInTheDocument();
@@ -92,6 +97,12 @@ test('removes old access rows immediately when the vehicle filter scope changes'
expect(within(detailDialog).getByText('旧接入车牌 · OLDVIN')).toBeInTheDocument();
expect(within(detailDialog).getByText('接入结论')).toBeInTheDocument();
expect(within(detailDialog).getByText('已接来源状态正常')).toBeInTheDocument();
expect(within(detailDialog).getByText('处置建议 · 无需处理,持续监测来源上报')).toBeInTheDocument();
const healthOverview = within(detailDialog).getByRole('group', { name: '接入健康概览' });
expect(healthOverview).toHaveTextContent('已接入1 / 3');
expect(healthOverview).toHaveTextContent('当前在线1');
expect(healthOverview).toHaveTextContent('异常来源0');
expect(healthOverview).toHaveTextContent('资料状态已维护');
expect(detailDialog.querySelector('.v2-access-inspector-header')).not.toBeInTheDocument();
fireEvent.change(screen.getByRole('textbox', { name: '车辆' }), { target: { value: 'NEWVIN' } });
fireEvent.click(screen.getByRole('button', { name: '查询' }));
@@ -195,7 +206,8 @@ test('renders mobile access vehicles as selectable Semi cards', async () => {
expect(within(dialog).getByRole('button', { name: '关闭车辆接入详情' })).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-inspector-v3.semi-card')).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-inspector-focus.semi-card')).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-inspector-summary-grid')).toHaveAttribute('aria-label', '车辆接入概览');
expect(within(dialog).getByRole('group', { name: '接入健康概览' })).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-mobile-profile')).toHaveTextContent('车辆档案测试品牌 / 测试车型');
expect(within(dialog).getByText('协议证据')).toBeInTheDocument();
expect(within(dialog).getByText('1 / 3 已接入').closest('.semi-tag')).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-protocol-details.semi-card-group')).toBeInTheDocument();

View File

@@ -157,13 +157,22 @@ const ConnectionState = memo(function ConnectionState({ row }: { row: AccessVehi
function ProtocolCoverage({ summary }: { summary?: AccessSummary }) {
return <div className="v2-access-protocol-coverage" aria-label="真实协议来源概览">
<small></small>
{PROTOCOLS.map((protocol) => {
const actual = summary?.protocols.find((item) => item.name === protocol);
return <span key={protocol}><b>{protocol}</b><em>{(actual?.total ?? 0).toLocaleString('zh-CN')} </em></span>;
return <span key={protocol}><b>{compactProtocolLabel(protocol)}</b><em>{(actual?.total ?? 0).toLocaleString('zh-CN')} </em></span>;
})}
</div>;
}
function accessRecommendation(row: AccessVehicleRow) {
if (row.connectionState === 'healthy') return '无需处理,持续监测来源上报';
if (row.connectionState === 'incomplete') return '补齐品牌、车型或来源标识';
if (row.connectionState === 'not_connected') return '核对车端设备与接入映射';
if (row.connectionState === 'offline') return '优先核对设备供电与接入链路';
return '优先核对离线或延迟异常来源';
}
const AccessVehicleTable = memo(function AccessVehicleTable({ rows, selectedVIN, onSelect }: { rows: AccessVehicleRow[]; selectedVIN: string; onSelect: (vin: string) => void }) {
const columns = useMemo(() => [
{
@@ -205,6 +214,14 @@ const AccessVehicleTable = memo(function AccessVehicleTable({ rows, selectedVIN,
function VehicleInspector({ row, onClose, sheet = false, mobile = false }: { row: AccessVehicleRow; onClose: () => void; sheet?: boolean; mobile?: boolean }) {
const protocols = useMemo(() => [...PROTOCOLS].sort((left, right) => protocolPriority(statusByProtocol(row, left)) - protocolPriority(statusByProtocol(row, right))), [row]);
const health = useMemo(() => {
const connected = row.protocolStatuses.filter((item) => item.connected);
return {
connected: connected.length,
online: connected.filter((item) => item.onlineState === 'online').length,
attention: connected.filter((item) => protocolNeedsAttention(item)).length
};
}, [row.protocolStatuses]);
const [expandedProtocol, setExpandedProtocol] = useState(() => protocols.find((protocol) => protocolNeedsAttention(statusByProtocol(row, protocol))) || '');
const toggleProtocol = useCallback((protocol: string) => {
setExpandedProtocol((current) => current === protocol ? '' : protocol);
@@ -213,14 +230,16 @@ function VehicleInspector({ row, onClose, sheet = false, mobile = false }: { row
return <Card className={`v2-access-inspector-v3${sheet ? ' is-sheet' : ''}`} bodyStyle={{ padding: 0 }}>
{sheet ? null : <WorkspacePanelHeader className="v2-access-inspector-header" title={row.plate || '未绑定车牌'} description={row.vin} actions={<Button theme="borderless" icon={<IconClose />} onClick={onClose} aria-label="关闭车辆接入详情" />} />}
<Card className={`v2-access-inspector-focus is-${row.connectionState}`} bodyStyle={{ padding: 0 }}>
<div><small>{row.connectionState === 'healthy' ? '接入结论' : '优先核对'}</small><strong>{accessIssueSummary(row)}</strong><span>{row.actualProtocols.length ? `已发现 ${row.actualProtocols.join(' / ')} ${row.actualProtocols.length} 个真实来源` : '当前没有可用的真实来源证据'}</span></div>
<div><small>{row.connectionState === 'healthy' ? '接入结论' : '优先核对'}</small><strong>{accessIssueSummary(row)}</strong><span>{row.actualProtocols.length ? `已发现 ${row.actualProtocols.join(' / ')} ${row.actualProtocols.length} 个真实来源` : '当前没有可用的真实来源证据'}</span><b> · {accessRecommendation(row)}</b></div>
<ConnectionTag row={row} />
</Card>
{mobile ? <div className="v2-access-inspector-summary-grid" role="group" aria-label="车辆接入概览">
<span><small> / </small><strong>{[row.oem, row.model].filter(Boolean).join(' / ') || '未维护'}</strong></span>
<span><small></small><strong>{row.actualProtocols.length ? row.actualProtocols.join(' / ') : '尚无来源'}</strong></span>
<span><small></small><strong>{row.masterDataIssues.length ? row.masterDataIssues.join('') : '已维护'}</strong></span>
</div> : <Descriptions className="v2-access-inspector-summary" align="left" size="small" data={[
<div className="v2-access-health-strip" role="group" aria-label="接入健康概览">
<span><small></small><strong>{health.connected} / {PROTOCOLS.length}</strong></span>
<span><small>线</small><strong className="is-good">{health.online}</strong></span>
<span><small></small><strong className={health.attention ? 'is-danger' : ''}>{health.attention}</strong></span>
<span><small></small><strong className={row.masterDataIssues.length ? 'is-warning' : ''}>{row.masterDataIssues.length ? `${row.masterDataIssues.length} 项待维护` : '已维护'}</strong></span>
</div>
{mobile ? <div className="v2-access-mobile-profile"><small></small><strong>{[row.oem, row.model].filter(Boolean).join(' / ') || '品牌与车型未维护'}</strong></div> : <Descriptions className="v2-access-inspector-summary" align="left" size="small" data={[
{ key: '品牌 / 车型', value: [row.oem, row.model].filter(Boolean).join(' / ') || '未维护' },
{ key: '真实接入来源', value: row.actualProtocols.length ? row.actualProtocols.join(' / ') : '尚无来源' },
{ key: '资料状态', value: row.masterDataIssues.length ? row.masterDataIssues.join('') : '已维护' }