diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx
index 5f381cf9..8a49d0c9 100644
--- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx
+++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx
@@ -728,6 +728,53 @@ export function VehicleDetail({
]
}
];
+ const customerVehicleActionBar = [
+ {
+ title: '看当前位置',
+ value: formatLocation(latest),
+ detail: '确认车辆当前位置、在线状态、最后上报和地图可用性。',
+ action: '实时地图',
+ color: online ? 'green' as const : hasResolvedVIN ? 'orange' as const : 'grey' as const,
+ disabled: !hasResolvedVIN,
+ onClick: () => onOpenRealtime(resolvedVIN, activeProtocol)
+ },
+ {
+ title: '回放轨迹',
+ value: `${formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0)} 条`,
+ detail: '按同一车辆进入轨迹回放,复核位置、速度和里程断点。',
+ action: '轨迹回放',
+ color: (overview?.historyCount ?? detail?.history?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
+ disabled: !hasResolvedVIN,
+ onClick: () => onOpenHistory(resolvedVIN, activeProtocol)
+ },
+ {
+ title: '核对里程',
+ value: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条`,
+ detail: '进入统计查询,核对区间里程、日里程和统计闭合。',
+ action: '统计查询',
+ color: (overview?.mileageCount ?? detail?.mileage?.total ?? 0) > 0 ? 'green' as const : 'grey' as const,
+ disabled: !hasResolvedVIN,
+ onClick: () => onOpenMileage(resolvedVIN, activeProtocol)
+ },
+ {
+ title: '导出证据',
+ value: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 条`,
+ detail: '查询历史数据和字段证据,作为客户复盘与导出依据。',
+ action: '历史导出',
+ color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
+ disabled: !hasResolvedVIN,
+ onClick: () => onOpenRaw(resolvedVIN, activeProtocol)
+ },
+ {
+ title: '告警通知',
+ value: `${qualityCount.toLocaleString()} 项`,
+ detail: priorityQualityIssue ? `${qualityIssueLabel(priorityQualityIssue.issueType)} / ${priorityQualityIssue.lastSeen || '-'}` : '暂无待通知告警。',
+ action: '告警闭环',
+ color: qualityCount > 0 ? 'orange' as const : 'green' as const,
+ disabled: !hasResolvedVIN && qualityCount <= 0,
+ onClick: () => onOpenQuality?.(qualityFiltersForCurrentVehicle())
+ }
+ ];
const serviceRunbookSteps = [
{
title: '确认实时状态',
@@ -1188,6 +1235,32 @@ export function VehicleDetail({
客户打开一辆车时,先看当前位置、最后上报、速度、SOC、总里程和告警,再进入轨迹回放、统计查询、数据导出和告警闭环。
+
+
+
+ 客户单车操作台
+ 围绕这辆车直接完成看车、回放、统计、导出和告警通知。
+
+
单车服务
+
+
+ {customerVehicleActionBar.map((item) => (
+
+ ))}
+
+
{[
{ label: '当前位置', value: formatLocation(latest) },
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index 2ca162cb..eb878001 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -7162,6 +7162,94 @@ button.vp-realtime-command-item:focus-visible {
line-height: 22px;
}
+.vp-single-vehicle-action-bar {
+ padding: 12px;
+ border: 1px solid rgba(22, 100, 255, 0.16);
+ border-radius: var(--vp-radius);
+ background: rgba(255, 255, 255, 0.86);
+ display: grid;
+ gap: 10px;
+}
+
+.vp-single-vehicle-action-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.vp-single-vehicle-action-head > div {
+ display: grid;
+ gap: 2px;
+}
+
+.vp-single-vehicle-action-head strong {
+ color: var(--vp-text);
+ font-size: 15px;
+ line-height: 21px;
+}
+
+.vp-single-vehicle-action-head span {
+ color: var(--vp-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.vp-single-vehicle-action-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 8px;
+}
+
+.vp-single-vehicle-action-item {
+ min-height: 132px;
+ padding: 10px;
+ border: 1px solid var(--vp-border);
+ border-radius: 8px;
+ background: #fbfcff;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ text-align: left;
+ display: grid;
+ gap: 7px;
+ align-content: start;
+}
+
+.vp-single-vehicle-action-item:hover,
+.vp-single-vehicle-action-item:focus-visible {
+ border-color: rgba(22, 100, 255, 0.45);
+ box-shadow: 0 0 0 3px rgba(22, 100, 255, 0.06);
+ outline: none;
+}
+
+.vp-single-vehicle-action-item:disabled {
+ cursor: not-allowed;
+ opacity: 0.62;
+}
+
+.vp-single-vehicle-action-item strong {
+ color: var(--vp-text);
+ font-size: 15px;
+ line-height: 20px;
+ font-weight: 700;
+ word-break: break-word;
+}
+
+.vp-single-vehicle-action-item span {
+ color: var(--vp-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.vp-single-vehicle-action-item em {
+ color: var(--vp-primary);
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 18px;
+}
+
.vp-vehicle-live-facts {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
@@ -8293,6 +8381,7 @@ button.vp-realtime-command-item:focus-visible {
.vp-customer-delivery-grid,
.vp-vehicle-live-view .semi-card-body,
.vp-vehicle-live-main,
+ .vp-single-vehicle-action-grid,
.vp-vehicle-live-facts,
.vp-vehicle-archive-summary-main,
.vp-vehicle-archive-summary-grid,
diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx
index f44a9ddc..a6dce00d 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -12029,6 +12029,13 @@ test('copies vehicle service diagnostic summary', async () => {
expect(await screen.findByText('车辆服务结论')).toBeInTheDocument();
expect(screen.getByText('单车 Live View')).toBeInTheDocument();
expect(screen.getByText('客户打开一辆车时,先看当前位置、最后上报、速度、SOC、总里程和告警,再进入轨迹回放、统计查询、数据导出和告警闭环。')).toBeInTheDocument();
+ expect(screen.getByText('客户单车操作台')).toBeInTheDocument();
+ expect(screen.getByText('围绕这辆车直接完成看车、回放、统计、导出和告警通知。')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户单车操作台 看当前位置 113.2, 23.1 实时地图' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户单车操作台 回放轨迹 12 条 轨迹回放' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户单车操作台 核对里程 3 条 统计查询' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户单车操作台 导出证据 34 条 历史导出' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户单车操作台 告警通知 1 项 告警闭环' })).toBeInTheDocument();
expect(screen.getByText('当前位置')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '单车实时视图 实时位置 查看实时' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '单车实时视图 数据导出 历史导出' })).toBeInTheDocument();