feat(platform): link trajectory playback selection
This commit is contained in:
@@ -109,6 +109,10 @@ function timeValue(row?: HistoryLocationRow) {
|
||||
return Number.isFinite(ms) ? ms : undefined;
|
||||
}
|
||||
|
||||
function playbackPointId(row: HistoryLocationRow, index: number) {
|
||||
return `${row.vin || 'unknown'}-${row.deviceTime || row.serverTime || row.lastSeen || index}-${index}`;
|
||||
}
|
||||
|
||||
function formatDurationMinutes(value?: number, suffix = '') {
|
||||
if (!isFiniteNumber(value)) return '-';
|
||||
if (value < 1) return `${Math.round(value * 60)} 秒${suffix}`;
|
||||
@@ -444,9 +448,10 @@ export function History({
|
||||
const playbackRows = validLocations.length > 0 ? validLocations : locationItems;
|
||||
const currentPlaybackIndex = playbackRows.length === 0 ? -1 : Math.min(playbackIndex, playbackRows.length - 1);
|
||||
const currentPlayback = currentPlaybackIndex >= 0 ? playbackRows[currentPlaybackIndex] : undefined;
|
||||
const currentPlaybackPointId = currentPlayback ? playbackPointId(currentPlayback, currentPlaybackIndex) : undefined;
|
||||
const playbackAtEnd = playbackRows.length > 0 && currentPlaybackIndex >= playbackRows.length - 1;
|
||||
const playbackPoints: VehicleMapPoint[] = validLocations.map((row, index) => ({
|
||||
id: `${row.vin}-${row.deviceTime || row.serverTime || index}`,
|
||||
id: playbackPointId(row, index),
|
||||
label: row.plate || row.vin || `点 ${index + 1}`,
|
||||
longitude: row.longitude,
|
||||
latitude: row.latitude,
|
||||
@@ -539,6 +544,17 @@ export function History({
|
||||
setPlaybackPlaying(false);
|
||||
setPlaybackIndex((current) => Math.min(Math.max(current + delta, 0), playbackRows.length - 1));
|
||||
};
|
||||
const selectPlaybackPoint = (nextIndex: number) => {
|
||||
if (playbackRows.length === 0) return;
|
||||
setPlaybackPlaying(false);
|
||||
setPlaybackIndex(Math.min(Math.max(nextIndex, 0), playbackRows.length - 1));
|
||||
};
|
||||
const selectPlaybackMapPoint = (point: VehicleMapPoint) => {
|
||||
const index = playbackRows.findIndex((row, rowIndex) => playbackPointId(row, rowIndex) === point.id);
|
||||
if (index >= 0) {
|
||||
selectPlaybackPoint(index);
|
||||
}
|
||||
};
|
||||
const openPlaybackVehicle = () => {
|
||||
if (!currentPlayback || !canOpenVehicle(currentPlayback.vin)) return;
|
||||
onOpenVehicle(currentPlayback.vin, currentPlayback.protocol);
|
||||
@@ -648,6 +664,8 @@ export function History({
|
||||
<VehicleMap
|
||||
points={playbackPoints}
|
||||
mode="track"
|
||||
selectedId={currentPlaybackPointId}
|
||||
onPointSelect={selectPlaybackMapPoint}
|
||||
fallbackLabel="高德地图未配置,显示轨迹坐标预览"
|
||||
/>
|
||||
</div>
|
||||
@@ -663,7 +681,7 @@ export function History({
|
||||
<div className="vp-monitor-metric-value">{item.value}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="vp-playback-current">
|
||||
<div className="vp-playback-current">
|
||||
<div className="vp-map-service-queue-title">当前回放点</div>
|
||||
{currentPlayback ? (
|
||||
<>
|
||||
@@ -703,6 +721,16 @@ export function History({
|
||||
<Button size="small" disabled={currentPlaybackIndex >= playbackRows.length - 1} onClick={() => movePlayback(1)}>下一点</Button>
|
||||
<Button size="small" disabled={!canOpenVehicle(currentPlayback.vin)} onClick={openPlaybackVehicle}>回放点车辆服务</Button>
|
||||
</Space>
|
||||
<input
|
||||
className="vp-playback-range"
|
||||
aria-label="轨迹回放进度"
|
||||
type="range"
|
||||
min={0}
|
||||
max={Math.max(playbackRows.length - 1, 0)}
|
||||
step={1}
|
||||
value={currentPlaybackIndex}
|
||||
onChange={(event) => selectPlaybackPoint(Number(event.currentTarget.value))}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Typography.Text type="tertiary">暂无可回放位置点</Typography.Text>
|
||||
@@ -712,7 +740,13 @@ export function History({
|
||||
</div>
|
||||
<div className="vp-playback-timeline">
|
||||
{(validLocations.length > 0 ? validLocations : locationItems).slice(0, 8).map((row, index) => (
|
||||
<div key={`${row.deviceTime}-${index}`} className="vp-playback-step">
|
||||
<button
|
||||
key={`${row.deviceTime}-${index}`}
|
||||
type="button"
|
||||
className={`vp-playback-step ${index === currentPlaybackIndex ? 'vp-playback-step-active' : ''}`}
|
||||
aria-label={`选择轨迹点 ${index + 1}`}
|
||||
onClick={() => selectPlaybackPoint(index)}
|
||||
>
|
||||
<Tag color={index === 0 ? 'green' : index === Math.min((validLocations.length || locationItems.length), 8) - 1 ? 'orange' : 'blue'}>
|
||||
{index === 0 ? '起点' : index === Math.min((validLocations.length || locationItems.length), 8) - 1 ? '终点' : `点 ${index + 1}`}
|
||||
</Tag>
|
||||
@@ -720,7 +754,7 @@ export function History({
|
||||
<Typography.Text type="secondary">
|
||||
{formatNumber(row.speedKmh, ' km/h')} / {formatNumber(row.totalMileageKm, ' km')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
{locationItems.length === 0 ? (
|
||||
<Typography.Text type="secondary">当前筛选范围暂无轨迹点,请调整车辆、来源或时间范围。</Typography.Text>
|
||||
|
||||
Reference in New Issue
Block a user