feat(platform): copy trajectory playback summary

This commit is contained in:
lingniu
2026-07-04 13:35:28 +08:00
parent ff833bc065
commit d77754b0a0
2 changed files with 170 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
import { Button, Card, Form, Select, SideSheet, Space, Table, Tabs, Tag, Toast, Typography } from '@douyinfe/semi-ui';
import { IconRefresh, IconSearch } from '@douyinfe/semi-icons';
import { IconCopy, IconRefresh, IconSearch } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useState } from 'react';
import { api, type RawFrameQuery } from '../api/client';
import type { HistoryLocationRow, Page, RawFrameRow } from '../api/types';
@@ -125,6 +125,58 @@ function exportFileName(prefix: string, filters: HistoryFilters) {
return `${prefix}-${keyword}-${protocol}.csv`;
}
function trajectorySummaryText({
filters,
totalPoints,
validPointCount,
mileageDelta,
maxSpeed,
firstLocation,
lastLocation
}: {
filters: HistoryFilters;
totalPoints: number;
validPointCount: number;
mileageDelta?: number;
maxSpeed?: number;
firstLocation?: HistoryLocationRow;
lastLocation?: HistoryLocationRow;
}) {
const vehicle = filters.keyword?.trim() || '全部车辆';
const protocol = filters.protocol?.trim() || '全部来源';
const dateFrom = filters.dateFrom?.trim();
const dateTo = filters.dateTo?.trim();
const range = dateFrom || dateTo ? `${dateFrom || '-'}${dateTo || '-'}` : '全部时间';
const startTime = firstLocation?.deviceTime || firstLocation?.serverTime || '-';
const endTime = lastLocation?.deviceTime || lastLocation?.serverTime || '-';
return [
'【轨迹回放摘要】',
`车辆:${vehicle}`,
`数据来源:${protocol}`,
`查询范围:${range}`,
`轨迹点:${totalPoints.toLocaleString()},有效定位:${validPointCount.toLocaleString()}`,
`区间里程:${formatNumber(mileageDelta, ' km')}`,
`最高速度:${formatNumber(maxSpeed, ' km/h')}`,
`起点:${startTime}`,
`终点:${endTime}`,
`当前位置服务:${window.location.origin}${window.location.pathname}${window.location.hash}`
].join('\n');
}
async function copyText(value: string, label: string) {
const text = value.trim();
if (!text) {
Toast.warning(`${label}为空`);
return;
}
try {
await navigator.clipboard.writeText(text);
Toast.success(`已复制${label}`);
} catch {
Toast.error(`复制${label}失败`);
}
}
export function History({
initialVin,
initialProtocol,
@@ -299,6 +351,20 @@ export function History({
downloadCsv(exportFileName('raw-frames', filters), buildCsv(rawExportColumns, rawFrames.items));
Toast.success(`已导出 ${rawFrames.items.length} 条 RAW 帧`);
};
const copyTrajectorySummary = () => {
copyText(
trajectorySummaryText({
filters,
totalPoints: locations.total,
validPointCount: validLocations.length,
mileageDelta,
maxSpeed,
firstLocation,
lastLocation
}),
'轨迹摘要'
);
};
const movePlayback = (delta: number) => {
if (playbackRows.length === 0) return;
setPlaybackIndex((current) => Math.min(Math.max(current + delta, 0), playbackRows.length - 1));
@@ -388,7 +454,16 @@ export function History({
</Space>
</Card>
) : null}
<Card bordered title="轨迹回放作业台" style={{ marginTop: 16 }}>
<Card
bordered
title={(
<Space>
<span></span>
<Button size="small" theme="light" icon={<IconCopy />} onClick={copyTrajectorySummary}></Button>
</Space>
)}
style={{ marginTop: 16 }}
>
<div className="vp-playback-layout">
<div className="vp-playback-map">
<div className="vp-monitor-map-header">