diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx
index 8df75e17..30240566 100644
--- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx
+++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx
@@ -2079,6 +2079,66 @@ export function Dashboard({
onClick: openTimeMonitorAlerts
}
];
+ const customerPrimaryFlowItems = [
+ {
+ label: '实时地图',
+ value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
+ detail: '先确认车辆在哪里、是否在线、是否有有效坐标。',
+ action: '打开地图',
+ color: 'green' as const,
+ onClick: () => onOpenMap({ online: 'online' })
+ },
+ {
+ label: '时间窗复盘',
+ value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
+ detail: '用同一时间范围串起轨迹、位置、里程、历史明细和导出。',
+ action: '查询导出',
+ color: 'blue' as const,
+ onClick: openTimeMonitorHistory
+ },
+ {
+ label: '里程统计',
+ value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
+ detail: '以车辆为对象核对区间里程、每日统计和 BI 口径。',
+ action: '统计查询',
+ color: 'blue' as const,
+ onClick: openTimeMonitorMileage
+ },
+ {
+ label: '告警通知',
+ value: `${formatCount(summary?.issueVehicles)} 告警`,
+ detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '无高优先级异常时可交付无异常说明。',
+ action: '通知处理',
+ color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
+ onClick: openTimeMonitorAlerts
+ }
+ ];
+ const operationsEvidenceItems = [
+ {
+ label: '链路健康',
+ value: unhealthyLinkCount > 0 ? `${formatCount(unhealthyLinkCount)} 异常` : '正常',
+ detail: '解释车辆实时、轨迹、统计或导出不可用时的链路证据。',
+ action: '查看告警',
+ color: unhealthyLinkCount > 0 ? 'orange' as const : 'green' as const,
+ onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
+ },
+ {
+ label: '消息积压',
+ value: formatLag(summary?.kafkaLag),
+ detail: '判断接收、解析和历史落地是否存在排队。',
+ action: '查看运维',
+ color: (summary?.kafkaLag ?? 0) > 0 ? 'orange' as const : 'green' as const,
+ onClick: () => onOpenQuality()
+ },
+ {
+ label: '缓存在线',
+ value: formatCount(opsHealth?.redisOnlineKeys),
+ detail: '辅助解释在线车辆数和实时状态是否可信。',
+ action: '缓存证据',
+ color: (opsHealth?.redisOnlineKeys ?? 0) > 0 ? 'blue' as const : 'orange' as const,
+ onClick: () => onOpenRealtime({ online: 'online' })
+ }
+ ];
return (
@@ -2216,6 +2276,60 @@ export function Dashboard({
))}
+
+
+
+
+ 客户主流程
+ 一个车辆服务
+
+ 默认展示车辆地图、时间窗、里程、导出和告警;三类数据源只作为二级证据。
+
+
+ {customerPrimaryFlowItems.map((item) => (
+
+ ))}
+
+
+
+
+
+ 运维证据层
+ 0 ? 'orange' : 'green'}>{unhealthyLinkCount > 0 ? `${formatCount(unhealthyLinkCount)} 异常` : '链路正常'}
+
+
+ 用于解释车辆服务不可用的链路、队列、缓存、存储和来源证据,不作为客户默认入口。
+
+
+
+ {operationsEvidenceItems.map((item) => (
+
+ ))}
+
+
+
{
+ window.history.replaceState(null, '', '/#/dashboard');
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
+ const path = String(input);
+ if (path.includes('/api/ops/health')) {
+ return {
+ ok: true,
+ json: async () => ({
+ data: {
+ linkHealth: [],
+ kafkaLag: 0,
+ activeConnections: 0,
+ capacityFindings: [],
+ redisOnlineKeys: 281,
+ tdengineWritable: true,
+ mysqlWritable: true,
+ runtime: {
+ requestTimeoutMs: 5000,
+ amapWebJsConfigured: true,
+ amapApiConfigured: true,
+ amapSecurityProxyEnabled: true,
+ amapSecurityCodeExposed: false
+ }
+ },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ }
+ if (path.includes('/api/dashboard/summary')) {
+ return {
+ ok: true,
+ json: async () => ({
+ data: {
+ onlineVehicles: 73,
+ activeToday: 4,
+ frameToday: 1286320,
+ issueVehicles: 7,
+ kafkaLag: 0,
+ protocols: [],
+ serviceStatuses: [],
+ linkHealth: []
+ },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ }
+ if (path.includes('/api/vehicle-service/summary')) {
+ return {
+ ok: true,
+ json: async () => ({
+ data: {
+ totalVehicles: 1033,
+ boundVehicles: 1024,
+ onlineVehicles: 208,
+ singleSourceVehicles: 391,
+ multiSourceVehicles: 181,
+ noDataVehicles: 461,
+ identityRequiredVehicles: 9,
+ serviceStatuses: [],
+ protocols: [],
+ missingSources: []
+ },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ }
+ return {
+ ok: true,
+ json: async () => ({
+ data: { items: [], total: 0, limit: 8, offset: 0 },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ });
+
+ render();
+
+ expect(await screen.findByText('客户主流程')).toBeInTheDocument();
+ expect(screen.getByText('默认展示车辆地图、时间窗、里程、导出和告警;三类数据源只作为二级证据。')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户主流程 实时地图 208 在线 打开地图' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户主流程 时间窗复盘 1 天窗口 查询导出' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户主流程 里程统计 1,033 车辆 统计查询' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户主流程 告警通知 7 告警 通知处理' })).toBeInTheDocument();
+ expect(screen.getByText('运维证据层')).toBeInTheDocument();
+ expect(screen.getByText('用于解释车辆服务不可用的链路、队列、缓存、存储和来源证据,不作为客户默认入口。')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '运维证据层 链路健康 正常 查看告警' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '运维证据层 消息积压 0 查看运维' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '运维证据层 缓存在线 281 缓存证据' })).toBeInTheDocument();
+});
+
test('dashboard guides customer custom time window service path', async () => {
window.history.replaceState(null, '', '/#/dashboard');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {