diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx
index 80837226..b73a5129 100644
--- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx
+++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx
@@ -450,6 +450,34 @@ export function VehicleDetail({
}
return actions;
}, [activeProtocol, detail?.sourceStatus, displayLookupKey, hasResolvedVIN, missingProtocols, onOpenQuality, onOpenRealtime, onOpenVehicles, qualityCount, resolvedVIN]);
+ const copyVehicleOperationsSummary = () => {
+ if (!detail) {
+ Toast.warning('当前没有可复制的运营摘要');
+ return;
+ }
+ const vehicleName = [archivePlate, displayVIN].filter((item) => item && item !== '-').join(' / ') || displayLookupKey;
+ const actionLines = serviceActions.map((item, index) => `${index + 1}. ${item.label}:${item.description}`);
+ const lines = [
+ '【车辆服务运营摘要】',
+ `车辆:${vehicleName}`,
+ `当前范围:${scopeText}`,
+ `服务状态:${serviceStatus?.title ?? serviceConclusion.status}`,
+ `业务影响:${serviceConclusion.risk}`,
+ `建议动作:${serviceConclusion.nextStep}`,
+ `在线来源:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`,
+ `定位来源:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount}` : '0'}`,
+ `质量问题:${qualityCount.toLocaleString()} 项`,
+ `数据规模:历史 ${overview?.historyCount ?? detail.history.total ?? 0} / RAW ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0}`,
+ `下一步清单:${actionLines.length > 0 ? '' : '暂无明确处置项,持续观察实时状态。'}`,
+ ...actionLines,
+ `车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
+ `实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
+ `轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
+ `RAW证据:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
+ `里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
+ ];
+ copyText(lines.join('\n'), '运营摘要');
+ };
const detailWorkbench = [
{
title: '实时定位',
@@ -581,6 +609,7 @@ export function VehicleDetail({
} onClick={() => load(query)} loading={loading}>刷新
} onClick={copyVehicleServiceURL}>复制服务链接
} onClick={copyVehicleDiagnosticSummary}>复制诊断摘要
+ } onClick={copyVehicleOperationsSummary}>复制运营摘要
} onClick={exportVehicleReport}>导出档案 CSV
diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx
index 87161d9f..b9652a6e 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -10556,6 +10556,28 @@ test('copies vehicle service diagnostic summary', async () => {
expect(copied).toContain('轨迹:http://localhost:3000/#/history?keyword=VIN-SUMMARY-001&protocol=JT808');
expect(copied).toContain('RAW:http://localhost:3000/#/history-query?keyword=VIN-SUMMARY-001&protocol=JT808&tab=raw&includeFields=true');
expect(copied).toContain('里程:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
+
+ fireEvent.click(screen.getByRole('button', { name: /复制运营摘要/ }));
+ await waitFor(() => {
+ expect(writeText.mock.calls.some((call) => String(call[0]).includes('【车辆服务运营摘要】'))).toBe(true);
+ });
+ const operationsCopied = String(writeText.mock.lastCall?.[0] ?? '');
+ expect(operationsCopied).toContain('【车辆服务运营摘要】');
+ expect(operationsCopied).toContain('车辆:粤A诊断1 / VIN-SUMMARY-001');
+ expect(operationsCopied).toContain('当前范围:单一来源:JT808');
+ expect(operationsCopied).toContain('服务状态:来源不完整');
+ expect(operationsCopied).toContain('在线来源:1/2');
+ expect(operationsCopied).toContain('定位来源:2');
+ expect(operationsCopied).toContain('质量问题:1 项');
+ expect(operationsCopied).toContain('下一步清单:');
+ expect(operationsCopied).toContain('补齐 YUTONG_MQTT 来源');
+ expect(operationsCopied).toContain('排查 GB32960 离线');
+ expect(operationsCopied).toContain('处理质量问题 1 项');
+ expect(operationsCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
+ expect(operationsCopied).toContain('实时监控:http://localhost:3000/#/realtime?keyword=VIN-SUMMARY-001&protocol=JT808');
+ expect(operationsCopied).toContain('轨迹回放:http://localhost:3000/#/history?keyword=VIN-SUMMARY-001&protocol=JT808');
+ expect(operationsCopied).toContain('RAW证据:http://localhost:3000/#/history-query?keyword=VIN-SUMMARY-001&protocol=JT808&tab=raw&includeFields=true');
+ expect(operationsCopied).toContain('里程统计:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
});
test('exports vehicle detail service dossier as csv', async () => {