feat(platform): copy trajectory playback summary
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user