From 5cb250945709b7d3a7cf164274ddb1e71b5562e0 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 03:29:53 +0800 Subject: [PATCH] feat(platform): clarify customer service entry --- vehicle-data-platform/apps/web/src/App.tsx | 3 - .../apps/web/src/pages/Dashboard.tsx | 118 +++++++++++++++--- .../apps/web/src/styles/global.css | 67 ++++++++++ .../apps/web/src/test/App.test.tsx | 13 +- 4 files changed, 181 insertions(+), 20 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/App.tsx b/vehicle-data-platform/apps/web/src/App.tsx index 5a8c4651..29d41e94 100644 --- a/vehicle-data-platform/apps/web/src/App.tsx +++ b/vehicle-data-platform/apps/web/src/App.tsx @@ -429,9 +429,6 @@ export default function App() { const nextFilters = normalizeMileageFilterValues(filters); const nextVin = nextFilters.keyword?.trim() || analysisVin; const nextProtocol = nextFilters.protocol?.trim() ?? activeProtocol; - if (!nextVin) { - return; - } setAnalysisVin(nextVin); setActiveProtocol(nextProtocol); setMileageFilters({ ...nextFilters, keyword: nextVin, protocol: nextProtocol }); diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index 450ebca3..43e7b8f1 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -1215,6 +1215,73 @@ export function Dashboard({ onSecondary: () => exportDashboardSnapshot() } ]; + const customerServicePathItems = [ + { + title: '找车', + value: formatCount(serviceSummary?.totalVehicles), + detail: '按 VIN、车牌、手机号或 OEM 进入车辆服务,协议来源只作为证据。', + action: '车辆中心', + color: 'blue' as const, + onClick: () => onOpenVehicles({}) + }, + { + title: '看位置', + value: `${commandLocatedCount.toLocaleString()} 有定位`, + detail: '优先打开实时地图,判断车辆是否在线、坐标是否有效、是否存在断链。', + action: '实时地图', + color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const, + onClick: () => onOpenMap({ online: 'online' }) + }, + { + title: '复盘时间', + value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo), + detail: '同一个时间窗贯穿轨迹回放、里程统计、RAW 证据和告警复盘。', + action: '轨迹回放', + color: 'blue' as const, + onClick: openTimeMonitorHistory + }, + { + title: '交付数据', + value: `${formatCount(summary?.frameToday)} 今日数据`, + detail: '按车辆、时间、字段裁剪导出位置历史、原始记录和字段明细。', + action: '数据导出', + color: 'blue' as const, + onClick: openTimeMonitorRaw + }, + { + title: '闭环异常', + value: `${formatCount(summary?.issueVehicles)} 告警`, + detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警,保持日常巡检。', + action: '告警事件', + color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, + onClick: () => onOpenQuality() + } + ]; + const copyCustomerServiceGuide = () => { + const scope = timeMonitorScope(); + const rawFilters = { ...scope, tab: 'raw', includeFields: 'true' }; + const lines = [ + '【车辆客户服务说明】', + '服务对象:车辆,不是协议数据源。', + `车辆规模:${formatCount(serviceSummary?.totalVehicles)},在线车辆:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)},有效定位:${commandLocatedCount.toLocaleString()}`, + `当前时间窗:${timeMonitorSummary}(${dayRangeText(scope.dateFrom, scope.dateTo)})`, + `告警车辆:${formatCount(summary?.issueVehicles)},最高优先级:${highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无'}`, + '', + '客户服务路径:', + '1. 找车:先按 VIN、车牌、手机号或 OEM 锁定车辆。', + '2. 看位置:打开实时地图确认车辆在线、坐标和最新上报。', + '3. 复盘时间:同一时间窗查看轨迹、里程和告警。', + '4. 交付数据:按车辆、时间、字段导出位置历史、RAW 和字段明细。', + '5. 闭环异常:告警事件进入通知和处置流程。', + `车辆中心:${appURL(buildAppHash({ page: 'vehicles' }))}`, + `实时地图:${appURL(buildAppHash({ page: 'map', filters: { online: 'online' } }))}`, + `轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`, + `里程统计:${appURL(buildAppHash({ page: 'mileage', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`, + `数据导出:${appURL(buildAppHash({ page: 'history-query', keyword: scope.keyword, protocol: scope.protocol, filters: rawFilters }))}`, + `告警事件:${appURL(buildAppHash({ page: 'alert-events', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}` + ]; + copyText(lines.join('\n'), '客户服务说明'); + }; return (
@@ -1350,22 +1417,41 @@ export function Dashboard({ ))}
- - {vehicleServiceOnlineText(serviceSummary, summary)} - - 有效定位 {commandLocatedCount.toLocaleString()} - - 今日活跃 {formatCount(summary?.activeToday)} - - 今日数据 {formatCount(summary?.frameToday)} - - 0 ? 'orange' : 'green'}> - {formatCount(summary?.issueVehicles)} 告警事件 - - - - - +
+
+ + 客户服务路径 + {vehicleServiceOnlineText(serviceSummary, summary)} + 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} 告警事件 + + 从车辆出发,完成监控、复盘、统计、导出和告警闭环 + + 客户看到的是车辆服务结果;32960、808、MQTT 只在需要追溯时作为证据通道出现。 + + + + + + + +
+
+ {customerServicePathItems.map((item) => ( + + ))} +
+
{ expect(screen.getByText('导出证据数据')).toBeInTheDocument(); expect(screen.getByText('闭环告警通知')).toBeInTheDocument(); expect(screen.getByText('车辆服务入口')).toBeInTheDocument(); + expect(screen.getByText('客户服务路径')).toBeInTheDocument(); + expect(screen.getByText('从车辆出发,完成监控、复盘、统计、导出和告警闭环')).toBeInTheDocument(); + expect(screen.getByText('客户看到的是车辆服务结果;32960、808、MQTT 只在需要追溯时作为证据通道出现。')).toBeInTheDocument(); + expect(screen.getByText('找车')).toBeInTheDocument(); + expect(screen.getByText('看位置')).toBeInTheDocument(); + expect(screen.getAllByText('复盘时间').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('交付数据')).toBeInTheDocument(); + expect(screen.getByText('闭环异常')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务入口 找车 车辆中心' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务入口 看位置 实时地图' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '复制客户服务说明' })).toBeInTheDocument(); expect(screen.getByText('自定义时间监控')).toBeInTheDocument(); expect(screen.getByText('应用时间窗')).toBeInTheDocument(); expect(screen.getByText('重置最近一天')).toBeInTheDocument(); @@ -821,7 +832,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => { expect(screen.getByText('客户时间窗复盘包')).toBeInTheDocument(); expect(screen.getByText('客户问某辆车、某段时间发生了什么时,直接按这个时间窗串起轨迹、里程、历史数据、告警通知和地图定位。')).toBeInTheDocument(); expect(screen.getByText('车辆范围')).toBeInTheDocument(); - expect(screen.getByText('复盘时间')).toBeInTheDocument(); + expect(screen.getAllByText('复盘时间').length).toBeGreaterThanOrEqual(1); expect(screen.getByText('统计证据')).toBeInTheDocument(); expect(screen.getByText('时间窗服务核对')).toBeInTheDocument(); expect(screen.getByText('交付给客户前,先确认范围、轨迹、导出和异常闭环四件事都能讲清楚。')).toBeInTheDocument();