feat(platform): add operations handoff summary

This commit is contained in:
lingniu
2026-07-04 19:57:33 +08:00
parent b0451982a1
commit 259601ed7b
2 changed files with 88 additions and 0 deletions

View File

@@ -489,6 +489,34 @@ export function Dashboard({
]
}
];
const copyOperationsHandoff = () => {
const unhealthyLinks = (summary?.linkHealth ?? []).filter((item) => item.status !== 'ok');
const priorityAction = serviceActionQueue[0];
const highPriorityLookupKey = highPriorityLookup?.key;
const highPriorityFilters = highPriorityEvidenceFilters;
const lines = [
'【车辆数据中台运营交接摘要】',
`在线车辆:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} / ${formatCount(serviceSummary?.totalVehicles)}`,
`今日活跃:${formatCount(summary?.activeToday)}`,
`今日帧量:${formatCount(summary?.frameToday)}`,
`实时地图有效定位:${commandLocatedCount.toLocaleString()}`,
`告警车辆:${formatCount(summary?.issueVehicles)}`,
`Kafka Lag${formatLag(summary?.kafkaLag)}`,
`高德地图:${amapConfigured ? '已配置' : '待配置'}`,
`链路健康:${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join('') : '正常'}`,
`优先动作:${priorityAction ? `${priorityAction.label} ${priorityAction.count.toLocaleString()}辆 - ${priorityAction.detail}` : '暂无待办'}`,
highPriorityIssue ? `最高告警:${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)} / ${highPriorityIssue.lastSeen || '-'}` : '最高告警:暂无',
highPriorityIssue ? `告警详情:${highPriorityIssue.detail || '-'}` : '',
`实时监控:${appURL(buildAppHash({ page: 'realtime', filters: { online: 'online' } }))}`,
`实时地图:${appURL(buildAppHash({ page: 'map', filters: { online: 'online' } }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: highPriorityFilters?.keyword, protocol: highPriorityFilters?.protocol, filters: highPriorityFilters }))}`,
`历史RAW${appURL(buildAppHash({ page: 'history-query', keyword: highPriorityFilters?.keyword, protocol: highPriorityFilters?.protocol, filters: { ...highPriorityFilters, tab: 'raw', includeFields: 'true' } }))}`,
`告警事件:${appURL(buildAppHash({ page: 'alert-events', filters: highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {} }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: highPriorityLookupKey, protocol: highPriorityIssue?.protocol }))}`,
`车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: highPriorityLookupKey, protocol: highPriorityIssue?.protocol }))}`
].filter(Boolean);
copyText(lines.join('\n'), '运营交接摘要');
};
return (
<div className="vp-page">
@@ -515,6 +543,7 @@ export function Dashboard({
</Tag>
<Button size="small" theme="light" type={(summary?.issueVehicles ?? 0) > 0 ? 'warning' : 'tertiary'} onClick={() => onOpenQuality()}></Button>
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
<Button size="small" theme="solid" type="primary" onClick={copyOperationsHandoff}></Button>
</Space>
</Card>
<Card bordered title="车辆服务作业台" style={{ marginBottom: 16 }}>

View File

@@ -40,6 +40,48 @@ test('exposes AMap operations shortcuts when map key is configured', async () =>
})
} as Response;
}
if (path.includes('/api/dashboard/summary')) {
return {
ok: true,
json: async () => ({
data: {
onlineVehicles: 0,
activeToday: 0,
frameToday: 0,
issueVehicles: 0,
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: 0,
boundVehicles: 0,
onlineVehicles: 0,
singleSourceVehicles: 0,
multiSourceVehicles: 0,
noDataVehicles: 0,
identityRequiredVehicles: 0,
archiveIncompleteVehicles: 0,
serviceStatuses: [],
protocols: [],
missingSources: [],
archiveMissingFields: []
},
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response;
}
if (path.includes('/api/mileage/summary')) {
return {
ok: true,
@@ -1170,6 +1212,23 @@ test('dashboard copies highest priority quality issue notification text', async
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('详情JT808 数据缺少 VIN 映射'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据http://localhost:3000/#/history?keyword=%E7%B2%A4A%E5%91%8A%E8%AD%A61&protocol=JT808&dateFrom=2026-07-03&dateTo=2026-07-04'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('RAW证据http://localhost:3000/#/history-query?keyword=%E7%B2%A4A%E5%91%8A%E8%AD%A61&protocol=JT808&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true'));
fireEvent.click(screen.getByRole('button', { name: '复制运营交接摘要' }));
await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台运营交接摘要】'));
});
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线车辆88 / 1,033'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('今日活跃96'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('今日帧量1,286,320'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('Kafka Lag0'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('最高告警P0 VIN 缺失 / 粤A告警1 / 13307795425 / 2026-07-03 20:12:10'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('实时监控http://localhost:3000/#/realtime?online=online'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('实时地图http://localhost:3000/#/map?online=online'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹回放http://localhost:3000/#/history?keyword=%E7%B2%A4A%E5%91%8A%E8%AD%A61&protocol=JT808&dateFrom=2026-07-03&dateTo=2026-07-04'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('历史RAWhttp://localhost:3000/#/history-query?keyword=%E7%B2%A4A%E5%91%8A%E8%AD%A61&protocol=JT808&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警事件http://localhost:3000/#/alert-events?issueType=VIN_MISSING'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务http://localhost:3000/#/detail?keyword=%E7%B2%A4A%E5%91%8A%E8%AD%A61&protocol=JT808'));
});
test('dashboard keeps core vehicle service metrics when preview requests fail', async () => {