From 801a9851ea54d6d0c98ee65360445a288d129de4 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 18 Jul 2026 17:37:29 +0800 Subject: [PATCH] refine Semi UI access evidence --- .../apps/web/src/v2/pages/AccessPage.test.tsx | 14 +- .../apps/web/src/v2/pages/AccessPage.tsx | 33 +++- .../apps/web/src/v2/styles/workspace.css | 147 ++++++++++++++++++ 3 files changed, 186 insertions(+), 8 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx index 8bdd08c7..cf295813 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx @@ -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(); diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx index 8eb538bf..aa266f00 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx @@ -157,13 +157,22 @@ const ConnectionState = memo(function ConnectionState({ row }: { row: AccessVehi function ProtocolCoverage({ summary }: { summary?: AccessSummary }) { return
+ 真实来源覆盖 {PROTOCOLS.map((protocol) => { const actual = summary?.protocols.find((item) => item.name === protocol); - return {protocol}{(actual?.total ?? 0).toLocaleString('zh-CN')} 辆; + return {compactProtocolLabel(protocol)}{(actual?.total ?? 0).toLocaleString('zh-CN')} 辆; })}
; } +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 {sheet ? null : } onClick={onClose} aria-label="关闭车辆接入详情" />} />} -
{row.connectionState === 'healthy' ? '接入结论' : '优先核对'}{accessIssueSummary(row)}{row.actualProtocols.length ? `已发现 ${row.actualProtocols.join(' / ')} ${row.actualProtocols.length} 个真实来源` : '当前没有可用的真实来源证据'}
+
{row.connectionState === 'healthy' ? '接入结论' : '优先核对'}{accessIssueSummary(row)}{row.actualProtocols.length ? `已发现 ${row.actualProtocols.join(' / ')} ${row.actualProtocols.length} 个真实来源` : '当前没有可用的真实来源证据'}处置建议 · {accessRecommendation(row)}
- {mobile ?
- 品牌 / 车型{[row.oem, row.model].filter(Boolean).join(' / ') || '未维护'} - 真实来源{row.actualProtocols.length ? row.actualProtocols.join(' / ') : '尚无来源'} - 资料状态{row.masterDataIssues.length ? row.masterDataIssues.join(';') : '已维护'} -
: + 已接入{health.connected} / {PROTOCOLS.length} + 当前在线{health.online} + 异常来源{health.attention} + 资料状态{row.masterDataIssues.length ? `${row.masterDataIssues.length} 项待维护` : '已维护'} + + {mobile ?
车辆档案{[row.oem, row.model].filter(Boolean).join(' / ') || '品牌与车型未维护'}
: small { + color: #8794a6; + font-size: 10px; + font-weight: 600; + white-space: nowrap; +} + +.v2-access-protocol-coverage > span { + display: inline-flex; + min-height: 27px; + align-items: center; + gap: 5px; + border: 1px solid #e0e7f0; + border-radius: 7px; + background: #f8fafd; + padding: 4px 7px; + white-space: nowrap; +} + +.v2-access-protocol-coverage b { + color: #50627a; + font-size: 10px; +} + +.v2-access-protocol-coverage em { + color: #78879a; + font-size: 10px; + font-style: normal; + font-variant-numeric: tabular-nums; +} + .v2-access-table-v3.semi-card, .v2-access-table-v3 > .semi-card-body { height: 100%; @@ -9452,10 +9490,101 @@ line-height: 1.45; } +.v2-access-inspector-focus b { + margin-top: 3px; + color: #526c91; + font-size: 10px; + font-weight: 650; + line-height: 1.45; +} + +.v2-access-inspector-focus.is-degraded b, +.v2-access-inspector-focus.is-incomplete b { + color: #9c640f; +} + +.v2-access-inspector-focus.is-offline b, +.v2-access-inspector-focus.is-not_connected b { + color: #a45048; +} + .v2-access-inspector-focus .v2-access-connection-tag.semi-tag { flex: 0 0 auto; } +.v2-access-health-strip { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin: 10px 12px 0; + overflow: hidden; + border: 1px solid #e0e7ef; + border-radius: 10px; + background: #fff; +} + +.v2-access-health-strip > span { + display: grid; + min-width: 0; + gap: 5px; + padding: 10px 11px; +} + +.v2-access-health-strip > span + span { + border-left: 1px solid #e8edf3; +} + +.v2-access-health-strip small { + color: #8794a6; + font-size: 9px; + white-space: nowrap; +} + +.v2-access-health-strip strong { + overflow: hidden; + color: #34475f; + font-size: 15px; + line-height: 1; + text-overflow: ellipsis; + white-space: nowrap; + font-variant-numeric: tabular-nums; +} + +.v2-access-health-strip strong.is-good { + color: #169b62; +} + +.v2-access-health-strip strong.is-danger { + color: #d1493f; +} + +.v2-access-health-strip strong.is-warning { + color: #b86c0d; +} + +.v2-access-mobile-profile { + display: flex; + min-height: 42px; + align-items: center; + gap: 10px; + border-bottom: 1px solid #e6ebf2; + background: #fff; + padding: 8px 12px; +} + +.v2-access-mobile-profile small { + flex: 0 0 auto; + color: #8794a6; + font-size: 10px; +} + +.v2-access-mobile-profile strong { + overflow: hidden; + color: #40536b; + font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; +} + .v2-access-inspector-focus + .v2-access-inspector-summary.semi-descriptions { padding-top: 8px; } @@ -9875,6 +10004,24 @@ font-size: 13px; } + .v2-access-health-strip { + margin: 7px 9px 0; + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .v2-access-health-strip > span { + gap: 4px; + padding: 8px 6px; + } + + .v2-access-health-strip small { + font-size: 9px; + } + + .v2-access-health-strip strong { + font-size: 12px; + } + .v2-ops-page { height: auto; min-height: 100%;