refactor(ui): converge semi mobile vehicle record

This commit is contained in:
lingniu
2026-07-19 20:46:54 +08:00
parent f59148e260
commit 2b18a400b1
4 changed files with 268 additions and 28 deletions

View File

@@ -525,7 +525,11 @@ test('keeps protocol telemetry independent and explains mileage semantics', asyn
test('uses compact Semi telemetry evidence cards on mobile', async () => { test('uses compact Semi telemetry evidence cards on mobile', async () => {
layout.mobile = true; layout.mobile = true;
vi.spyOn(api, 'vehicleDetail').mockResolvedValue({ ...detail, sources: ['GB32960', 'JT808', 'YUTONG_MQTT'] }); vi.spyOn(api, 'vehicleDetail').mockResolvedValue({
...detail,
profile: { brandName: '测试品牌', modelName: '移动端完整车型名称' } as never,
sources: ['GB32960', 'JT808', 'YUTONG_MQTT']
});
vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [initialRealtime], total: 1, limit: 1, offset: 0 }); vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [initialRealtime], total: 1, limit: 1, offset: 0 });
vi.spyOn(api, 'latestTelemetry').mockResolvedValue({ vi.spyOn(api, 'latestTelemetry').mockResolvedValue({
vin: initialRealtime.vin, vin: initialRealtime.vin,
@@ -553,6 +557,7 @@ test('uses compact Semi telemetry evidence cards on mobile', async () => {
expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-cyan-light'); expect(document.querySelector('.v2-telemetry-detail-protocol')).toHaveClass('semi-tag-cyan-light');
expect(screen.getByRole('button', { name: '关闭遥测字段详情' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '关闭遥测字段详情' })).toBeInTheDocument();
expect(document.querySelector('.v2-vehicle-record-page')).toHaveClass('v2-vehicle-record-v3', 'is-mobile-layout'); expect(document.querySelector('.v2-vehicle-record-page')).toHaveClass('v2-vehicle-record-v3', 'is-mobile-layout');
expect(document.querySelector('.v2-identity-model strong')).toHaveAttribute('title', '测试品牌 / 移动端完整车型名称');
const mobileActions = screen.getByRole('group', { name: '车辆快捷操作' }); const mobileActions = screen.getByRole('group', { name: '车辆快捷操作' });
expect(mobileActions.querySelectorAll('.semi-button')).toHaveLength(3); expect(mobileActions.querySelectorAll('.semi-button')).toHaveLength(3);
expect(mobileActions).toHaveTextContent('切换车辆轨迹回放更多'); expect(mobileActions).toHaveTextContent('切换车辆轨迹回放更多');

View File

@@ -559,7 +559,7 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
</span>} </span>}
description={<span className="v2-identity-vin"><small>VIN</small><b>{detail.vin}</b><Button theme="borderless" aria-label="复制 VIN" title="复制 VIN" icon={<IconCopy />} onClick={() => navigator.clipboard?.writeText(detail.vin)} /></span>} description={<span className="v2-identity-vin"><small>VIN</small><b>{detail.vin}</b><Button theme="borderless" aria-label="复制 VIN" title="复制 VIN" icon={<IconCopy />} onClick={() => navigator.clipboard?.writeText(detail.vin)} /></span>}
meta={<span className="v2-identity-meta"> meta={<span className="v2-identity-meta">
<span className="v2-identity-model"><small> / </small><strong>{[detail.profile?.brandName, detail.profile?.modelName].filter(Boolean).join(' / ') || fmt(identity?.oem || realtime?.oem)}</strong></span> <span className="v2-identity-model"><small> / </small><strong title={[detail.profile?.brandName, detail.profile?.modelName].filter(Boolean).join(' / ') || fmt(identity?.oem || realtime?.oem)}>{[detail.profile?.brandName, detail.profile?.modelName].filter(Boolean).join(' / ') || fmt(identity?.oem || realtime?.oem)}</strong></span>
<span className="v2-identity-source-context"><small></small><span className="v2-identity-sources">{detail.sources.length ? detail.sources.map((source) => <ProtocolTag key={source} protocol={source} compact />) : <Tag color="grey" type="light" size="small"></Tag>}</span></span> <span className="v2-identity-source-context"><small></small><span className="v2-identity-sources">{detail.sources.length ? detail.sources.map((source) => <ProtocolTag key={source} protocol={source} compact />) : <Tag color="grey" type="light" size="small"></Tag>}</span></span>
</span>} </span>}
actions={<Suspense fallback={null}><VehicleActions actions={actions} mobile={mobileLayout} onSelect={navigate} /></Suspense>} actions={<Suspense fallback={null}><VehicleActions actions={actions} mobile={mobileLayout} onSelect={navigate} /></Suspense>}

View File

@@ -25886,3 +25886,236 @@
white-space: nowrap; white-space: nowrap;
} }
} }
/*
* Semi UI mobile vehicle record convergence.
* The first viewport keeps identity and four live facts intact, then brings
* the map forward by consolidating location, source evidence and section
* navigation into compact operational rows.
*/
@media (max-width: 680px) {
.v2-vehicle-record-v3.is-mobile-layout.v2-vehicle-record-page {
gap: 6px;
padding: 6px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card.semi-card,
.v2-vehicle-record-v3.is-mobile-layout .v2-source-evidence.semi-card,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-record-nav.semi-card,
.v2-vehicle-record-v3.is-mobile-layout .v2-single-map-card.semi-card {
border-color: #dce5ef;
border-radius: 10px;
box-shadow: 0 4px 16px rgba(31, 53, 80, .04);
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-workspace-command-copy {
min-height: 58px;
gap: 2px;
padding: 6px 9px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-primary {
gap: 6px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-plate {
min-height: 32px;
border-radius: 8px;
padding-inline: 8px;
font-size: 14px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-online-label.semi-tag {
min-height: 20px;
padding-inline: 6px;
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-vin {
gap: 4px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-vin small,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-vin b {
font-size: 9px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-vin .semi-button {
width: 24px;
min-width: 24px;
height: 24px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta {
grid-template-columns: minmax(0, 1fr) minmax(132px, auto);
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta > span {
min-height: 49px;
padding: 5px 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta small {
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta strong {
display: -webkit-box;
margin-top: 2px;
overflow: hidden;
font-size: 10px;
line-height: 1.25;
white-space: normal;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-sources {
flex-wrap: nowrap;
gap: 3px;
margin-top: 2px;
overflow: hidden;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-sources .semi-tag {
min-height: 18px;
padding-inline: 4px;
font-size: 7px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions {
gap: 5px;
padding: 5px 7px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions > .semi-button {
min-height: 36px;
border-radius: 8px;
padding: 2px 5px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions > .semi-button .semi-button-content-left {
font-size: 13px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions > .semi-button .semi-button-content-right {
font-size: 10px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-section > .v2-workspace-panel-header {
min-height: 38px;
padding: 4px 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-section .v2-live-report-meta {
max-width: 64vw;
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail.is-queue.has-context > .semi-card-body {
min-height: 96px;
grid-template-columns: minmax(0, 1fr);
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-list > span,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-action.semi-button,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-action .semi-button-content {
min-height: 48px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-list > span,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-action .semi-button-content {
column-gap: 5px;
padding: 4px 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-list > span > strong,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-action .semi-button-content > strong,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-list > span.is-secondary > strong,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-list > span.is-secondary .v2-workspace-metric-action strong {
overflow: visible;
font-size: 15px;
letter-spacing: -.025em;
line-height: 18px;
text-overflow: clip;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-list > span > em,
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-live-metric-rail .v2-workspace-metric-action .semi-button-content > em {
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card .v2-current-location {
min-height: 42px;
grid-template-columns: minmax(0, 1.14fr) minmax(124px, .86fr);
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card .v2-current-location > span {
min-height: 42px;
gap: 4px;
padding: 4px 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card .v2-current-location > span + span {
border-top: 0;
border-left: 1px solid #e4eaf2;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card .v2-current-location small {
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card .v2-current-location strong {
overflow: hidden;
font-size: 9px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-card .v2-current-address-action.semi-button {
min-height: 28px;
border-radius: 7px;
padding-inline: 6px;
font-size: 9px;
}
.v2-vehicle-record-v3.is-mobile-layout > .v2-source-evidence.semi-card .v2-source-evidence-trigger {
min-height: 44px;
padding: 5px 8px;
}
.v2-vehicle-record-v3.is-mobile-layout > .v2-source-evidence .v2-source-evidence-trigger > div {
gap: 1px;
}
.v2-vehicle-record-v3.is-mobile-layout > .v2-source-evidence .v2-source-evidence-trigger strong {
font-size: 11px;
}
.v2-vehicle-record-v3.is-mobile-layout > .v2-source-evidence .v2-source-evidence-trigger span {
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout > .v2-source-evidence .v2-source-evidence-trigger > .semi-button {
min-height: 30px;
border-radius: 7px;
padding-inline: 8px;
font-size: 9px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-record-nav .v2-workspace-panel-header {
min-height: 44px;
padding: 3px 5px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-section-nav > .semi-button {
min-height: 36px;
border-radius: 7px;
font-size: 8px;
}
.v2-vehicle-record-v3.is-mobile-layout .v2-single-map-card > .semi-card-body > .v2-workspace-panel-header {
min-height: 44px;
padding: 5px 9px;
}
}

View File

@@ -1,48 +1,50 @@
# 全局监控列表 Semi UI Design QA # 单车详情移动端 Semi UI Design QA
- source visual truth: 生产版本 `e944b797``/monitor?mode=list` - source visual truth: 生产版本 `f59148e2``/vehicles/LB9A32A26R0LS1489`
- source screenshots: - source screenshots:
- desktop: `/tmp/semi-continuous-audit-20260719/04-monitor-list-desktop.png` - directory desktop: `/tmp/semi-goal-audit-20260719-next/01-vehicle-directory-desktop.png`
- mobile: `/tmp/semi-continuous-audit-20260719/05-monitor-list-mobile.png` - detail desktop: `/tmp/semi-goal-audit-20260719-next/02-vehicle-detail-desktop.png`
- detail mobile: `/tmp/semi-goal-audit-20260719-next/03-vehicle-detail-mobile.png`
- implementation screenshots: - implementation screenshots:
- desktop: `/tmp/semi-continuous-audit-20260719/07-monitor-list-desktop-after.png` - detail mobile: `/tmp/semi-goal-audit-20260719-next/04-vehicle-detail-mobile-after.png`
- mobile: `/tmp/semi-continuous-audit-20260719/06-monitor-list-mobile-after.png` - detail desktop: `/tmp/semi-goal-audit-20260719-next/05-vehicle-detail-desktop-after.png`
- comparison evidence: - comparison evidence:
- mobile before/after: `/tmp/semi-continuous-audit-20260719/monitor-mobile-comparison.png` - mobile before/after: `/tmp/semi-goal-audit-20260719-next/vehicle-detail-mobile-comparison.png`
- viewport and state: - viewport and state:
- desktop `1440 × 900` - desktop `1440 × 900`
- mobile `390 × 844` - mobile `390 × 844`
- 使用管理员生产数据,共 `1,024` 辆授权车辆 - 使用管理员生产数据与同一辆车 `粤AGP3506`
- 列表视图、第一页、每页 `50`
## Fidelity Ledger ## Fidelity Ledger
| Surface | Required fidelity | Implementation evidence | Result | | Surface | Required fidelity | Implementation evidence | Result |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Monitoring workflow | 搜索、筛选、地图/列表切换、分页和实时刷新周期不能被视觉重构改变。 | 保留原查询、路由和轮询实现,只重组列表渲染与响应式样式。 | Preserved. | | Vehicle identity | 车牌、在线状态、VIN、品牌车型和三类协议来源必须完整。 | 保留原任务栏结构;车型允许两行且提供完整 `title`,协议标签保持 Semi Tag 色彩语义。 | Improved. |
| Fleet hierarchy | 车辆总数、当前在线、行驶和告警必须优先;今日上报与无实时位置属于辅助证据。 | 手机端主指标采用清晰的 `2 × 2` 网格;辅助指标独立放在底部并压缩大数。 | Improved. | | Live evidence | 速度、SOC、总里程和当日里程不能被截断或隐藏。 | 修正移动端带上下文指标轨道的网格优先级;四项指标获得完整宽度,实测全部 `clipped=false`。 | Fixed. |
| Vehicle evidence | 车牌、VIN、速度、当日里程、总里程、协议和位置必须在一眼可扫范围内。 | 手机卡片固定为身份、三项核心证据、位置三层;桌面继续使用连续 Semi Table。 | Improved. | | First viewport | 首屏必须尽快进入地图,不能被重复卡片和过高操作区占满。 | 身份、动作、位置、来源入口和章节导航收敛;地图顶部由 `652px` 提前至 `569px`。 | Improved. |
| Mobile density | 单屏车辆数需要显著增加,但不牺牲字段可辨认性。 | 卡片高度从约 `147px` 降至 `109px`;列表视口可容纳约 `4.05` 张卡片,截图中从约 `2.5` 辆提升到 `4` 辆。 | Improved. | | Location workflow | 当前坐标、按需地址解析和车辆实时跟随必须保留。 | 坐标与解析按钮合并成一行;未增加自动逆地理编码,地图和实时轮询逻辑未改。 | Preserved. |
| Realtime performance | 高频刷新不能让全部车辆卡片无差别重绘。 | 移动卡片使用字段级 `memo` 比较;不可见卡片继续使用 `content-visibility` 和固有尺寸占位。 | Improved. | | Source evidence | 总里程、当日里程和“查看全部来源”仍需打开同一证据面板。 | 实测总里程按钮打开 Semi Bottom SideSheet日期和来源证据可见关闭后恢复页面。 | Passed. |
| Address cost | 地理位置不得随列表刷新自动消耗逆地理编码 API。 | 经纬度始终可见;地址仍由用户点击“解析位置”后按需请求。 | Preserved. | | Section navigation | 实时位置、最近事件、实时遥测和车辆主档必须可达。 | 四个 Semi Button 保留可访问名称;点击实时位置后目标区域获得焦点。 | Passed. |
| Asset fidelity | 品牌和图标不得用临时图形替代。 | 沿用生产 Logo、Semi Icon、Card、Button、Table、Tag 与 Pagination。 | Preserved. | | Desktop stability | 移动端收敛不能造成桌面布局回退。 | `1440 × 900` 桌面截图保持原信息结构,横向溢出 `0px`。 | Preserved. |
| Asset fidelity | 品牌、地图和图标不得由临时资产代替。 | 继续使用生产 Logo、高德地图、Semi Icon、Card、Button、Tag 与 SideSheet。 | Preserved. |
## Interaction And Accessibility Checks ## Interaction And Accessibility Checks
- 手机端“筛选”可展开并可通过“收起”恢复列表 - 页面根节点保留“车牌 + 车辆档案”的可访问标签
- 地图/列表使用原有可访问 tab 语义,车辆卡片具有“车牌 + 实时数据”标签 - 总里程和当日里程继续以有名称的按钮进入来源证据
- “地图”和“解析位置”按钮均保留车辆级可访问名称 - 来源证据 SideSheet 可打开、读取并通过“关闭来源证据”退出
- `390px` 视口文档横向溢出为 `0px`,主指标和辅助指标无裁切 - 章节导航点击后执行平滑滚动并将焦点交给目标区域
- 移动卡片实测高度 `109px`,第一页仍渲染 `50` 辆并由内部列表滚动 - `390px` `1440px` 视口横向溢出均为 `0px`
- 控制台未发现本次改动产生的运行时异常;仅保留 Semi UI 在 React StrictMode 下既有的 `findDOMNode` 弃用警告。 - 生产数据下总里程、当日里程、速度和 SOC 均无视觉裁切;
- 控制台未发现本次改动产生的运行时异常。
## Comparison History ## Comparison History
### Pass 1 — fixed ### Pass 1 — fixed
- 生产手机端的“今日上报”和“无实时位置”数值被截断,信息层级与主要车队指标混在一起 - 原移动端指标轨道仍被通用 `has-context` 网格分成两列,隐藏的来源上下文占用一半宽度,导致总里程显示为 `80,27…`
- 生产车辆卡片纵向留白过多,`390 × 844` 首屏只能看到约 `2.5` - 当前修复将四项实时指标恢复到完整宽度的 `2 × 2` 网格,并压缩重复的纵向间距
- 已将辅助指标改成完整的紧凑数值,将每辆车的协议、里程与坐标收拢到固定证据网格 - 同视口并排对比确认地图在首屏获得约 `83px` 的额外可见空间,车型和里程信息完整
- 并排对比后未发现 P0、P1 或 P2 视觉与交互问题。 - 未发现 P0、P1 或 P2 视觉与交互问题。
final result: passed final result: passed