diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index 3b5497c9..265e875a 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -2002,6 +2002,41 @@ export function Dashboard({ onClick: openTimeMonitorRaw } ]; + const deliveryScopeText = `${timeMonitorScopeValue.keyword || '全部车辆'} / ${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorScopeValue.dateFrom || '-'} 至 ${timeMonitorScopeValue.dateTo || '-'}`; + const customerDeliveryWorkbenchItems = [ + { + label: '轨迹回放', + value: `${formatCount(summary?.activeToday)} 活跃`, + detail: '回放同一时间窗内的路线、速度、停驶和里程断点。', + action: '回放轨迹', + color: 'blue' as const, + onClick: openTimeMonitorHistory + }, + { + label: '里程统计', + value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`, + detail: '核对区间里程、每日统计和 BI 展示口径。', + action: '统计查询', + color: 'green' as const, + onClick: openTimeMonitorMileage + }, + { + label: '历史明细', + value: `${formatCount(summary?.frameToday)} 帧`, + detail: '查询位置历史、原始记录和解析字段,作为客户证据。', + action: '历史导出', + color: 'blue' as const, + onClick: openTimeMonitorRaw + }, + { + 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 + } + ]; return (
@@ -2091,6 +2126,38 @@ export function Dashboard({
+
+
+ + 客户查询导出交付台 + {dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)} + {timeMonitorDeliveryStatus.label} + + + 先锁定车辆和时间窗,再选择轨迹、里程、历史明细或告警说明,最终交付一份客户能看懂的数据包。 + +
+ 当前交付范围 + {deliveryScopeText} +
+
+
+ {customerDeliveryWorkbenchItems.map((item) => ( + + ))} +
+
{ expect(screen.getByRole('button', { name: '调度运营任务 导出客户证据 1,286,320 帧 历史导出' })).toBeInTheDocument(); }); +test('dashboard frames history query and export as a customer delivery workbench', async () => { + 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: 0, + 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: 3, + 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.getByText('当前交付范围')).toBeInTheDocument(); + expect(screen.getAllByText('全部车辆 / 全部数据通道 / 2026-07-04 至 2026-07-05').length).toBeGreaterThan(0); + expect(screen.getByRole('button', { name: '客户查询导出交付台 轨迹回放 4 活跃 回放轨迹' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户查询导出交付台 里程统计 1,033 车辆 统计查询' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户查询导出交付台 历史明细 1,286,320 帧 历史导出' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户查询导出交付台 告警说明 7 告警 告警通知' })).toBeInTheDocument(); +}); + test('dashboard guides customer custom time window service path', async () => { window.history.replaceState(null, '', '/#/dashboard'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {