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) => {