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