diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx
index e3523d95..b51f4f03 100644
--- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx
+++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx
@@ -513,6 +513,29 @@ function mapCustomerPackageText({
].filter(Boolean).join('\n');
}
+function selectedMapVehicleHandoffText(row: VehicleRealtimeRow, protocol: string) {
+ const vin = row.vin || '';
+ const vehicle = `${row.plate || '-'} / ${vin || '-'} / ${protocol || row.primaryProtocol || '-'}`;
+ const position = isValidCoordinate(row) ? `${row.longitude},${row.latitude}` : '无有效坐标';
+ return [
+ '【地图选中车辆交接卡】',
+ `车辆:${vehicle}`,
+ `服务状态:${vehicleServiceStatus(row).label}`,
+ `在线状态:${row.online ? '在线' : '离线'}`,
+ `最后上报:${row.lastSeen || '-'}`,
+ `位置:${position}`,
+ `速度:${row.speedKmh ?? '-'} km/h;SOC:${row.socPercent ?? '-'}%;总里程:${row.totalMileageKm ?? '-'} km`,
+ `问题:${realtimeIssueLabels(row).join(';')}`,
+ `建议动作:${realtimeIssueAction(row)}`,
+ `车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: vin, protocol }))}`,
+ `实时地图:${appURL(buildAppHash({ page: 'map', keyword: vin, protocol }))}`,
+ `轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: vin, protocol }))}`,
+ `统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: vin, protocol }))}`,
+ `历史数据:${appURL(buildAppHash({ page: 'history-query', keyword: vin, protocol, filters: { tab: 'raw', includeFields: 'true' } }))}`,
+ `告警通知:${appURL(buildAppHash({ page: 'alert-events', keyword: vin, protocol }))}`
+ ].join('\n');
+}
+
function mapCustomerDecisionText({
filters,
rows,
@@ -1065,6 +1088,13 @@ export function Realtime({
filters: { tab: 'raw', includeFields: 'true' }
});
};
+ const copySelectedMapVehicleHandoff = () => {
+ if (!selectedMapRow || !canOpenVehicle(selectedMapRow.vin)) {
+ Toast.warning('请先选择可交接的车辆');
+ return;
+ }
+ copyText(selectedMapVehicleHandoffText(selectedMapRow, selectedVehicleProtocol), '选中车辆交接卡');
+ };
const mapVehicleWorkItems = [
{
title: '车辆服务',
@@ -2213,6 +2243,17 @@ export function Realtime({
历史导出
{selectedMapRow ? selectedVehicleProtocol || selectedMapRow.primaryProtocol || '全部通道' : '等待选择'}
+
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index a0df9714..647dc736 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -6177,7 +6177,7 @@ button.vp-realtime-command-item:focus-visible {
.vp-map-selected-service-actions {
min-width: 0;
display: grid;
- grid-template-columns: repeat(5, minmax(0, 1fr));
+ grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 10px;
}
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 58d9385f..2fd9fb8a 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -10771,6 +10771,15 @@ test('shows realtime freshness status for recently updated and stale vehicles',
expect(screen.getByRole('button', { name: '地图选中车辆 轨迹复盘 打开轨迹' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '地图选中车辆 统计查询 统计查询' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '地图选中车辆 数据导出 历史导出' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '地图选中车辆 交接卡 复制交接' })).toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: '地图选中车辆 交接卡 复制交接' }));
+ await waitFor(() => {
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【地图选中车辆交接卡】'));
+ });
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:粤A超时1 / VIN-STALE-001 / JT808'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('问题:'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('天未更新;JT808 离线'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-STALE-001&protocol=JT808'));
fireEvent.click(screen.getByRole('button', { name: /复制地图监控包/ }));
await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户地图监控包】'));