From 4c390d209b45d516fbe2f7552d26048687c83972 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Wed, 05 Aug 2026 14:29:25 +0800
Subject: [PATCH] feat:tab切换太快的时候内容不对应

---
 applications/drone-command/src/components/map-container/common-cesium-map.vue |  233 +++++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 172 insertions(+), 61 deletions(-)

diff --git a/applications/drone-command/src/components/map-container/common-cesium-map.vue b/applications/drone-command/src/components/map-container/common-cesium-map.vue
index 9969d7e..f1c9be7 100644
--- a/applications/drone-command/src/components/map-container/common-cesium-map.vue
+++ b/applications/drone-command/src/components/map-container/common-cesium-map.vue
@@ -1,26 +1,41 @@
 <template>
 	<div :id="mapId" class="common-cesium-map" @contextmenu.prevent>
-		<div class="card">
-			<div class="card-content">
-				<div class="card-item" v-for="(item, index) in props.list" @click="clickWeekTime(item, index)">
-					<!-- <div class="time-top" :class="index === selectedImgIndex ? 'active' : ''">
-						{{ item.createTime }}
-					</div> -->
-					<div class="time-point">
-						<img
-							class="active"
-							v-if="index === selectedImgIndex"
-							src="@/assets/images/common/point-active.png"
-							alt=""
-						/>
-						<img v-else src="@/assets/images/common/point.png" alt="" />
-					</div>
-					<div class="time-bottom">{{ item.createTime.slice(11) }}</div>
+		<template v-if="props.list && props.list.length">
+			<div class="card">
+				<div class="timeline-prefix">
+					<slot name="timeline-prefix"></slot>
 				</div>
-				
+				<div
+					ref="timelineRef"
+					class="card-content"
+					:class="{ 'is-dragging': timelineDragging }"
+					@mousedown="handleTimelineMouseDown"
+					@mousemove="handleTimelineMouseMove"
+					@mouseup="stopTimelineDrag"
+					@mouseleave="stopTimelineDrag"
+				>
+					<div class="timeline-track">
+						<div class="time-line"></div>
+						<div
+							class="card-item"
+							v-for="(item, index) in props.list"
+							@click="handleTimelineItemClick(item, index)"
+						>
+							<div class="time-point">
+								<img
+									class="active"
+									v-if="index === selectedImgIndex"
+									src="@/assets/images/common/point-active.png"
+									alt=""
+								/>
+								<img v-else src="@/assets/images/common/point.png" alt="" />
+							</div>
+							<div class="time-bottom">{{ item.createTime.slice(11) }}</div>
+						</div>
+					</div>
+				</div>
 			</div>
-		</div>
-		<div class="time-line"></div>
+		</template>
 	</div>
 </template>
 
@@ -89,6 +104,11 @@
 
 const emit = defineEmits(['ready', 'height-change', 'stage-change', 'point-change'])
 const mapId = props.domId || `common-cesium-map-${Math.random().toString(36).slice(2, 10)}`
+const timelineRef = ref(null)
+const timelineDragging = ref(false)
+let timelineStartX = 0
+let timelineStartScrollLeft = 0
+let timelineDidDrag = false
 let viewInstance = null
 let viewer = null
 const ADMIN_BOUNDARY_NAMES = {
@@ -142,19 +162,89 @@
 const selectedImgIndex = ref(0)
 // 航线高度(与 TrajectoryDiaLog 里 MOCK_FLIGHT_HEIGHT 保持一致),让标记悬浮在轨迹线上
 const FLIGHT_HEIGHT = 120
+const POSITION_MOVE_DURATION = 1000
 // 当前经纬度位置标记
 let positionEntity = null
+let positionAnimationFrame = null
+
+// 停止位置图标移动动画
+const stopPositionAnimation = () => {
+	if (positionAnimationFrame !== null) {
+		cancelAnimationFrame(positionAnimationFrame)
+		positionAnimationFrame = null
+	}
+}
+
+// 平滑移动位置图标
+const animatePositionMarker = (targetPosition, duration) => {
+	const currentPosition = positionEntity?.position?.getValue(viewer.clock.currentTime, new Cesium.Cartesian3())
+	if (!currentPosition) {
+		positionEntity.position = targetPosition
+		return
+	}
+	stopPositionAnimation()
+	const startTime = performance.now()
+	const nextPosition = new Cesium.Cartesian3()
+
+	// 更新位置图标移动进度
+	const updatePosition = currentTime => {
+		const progress = Math.min((currentTime - startTime) / duration, 1)
+		Cesium.Cartesian3.lerp(currentPosition, targetPosition, progress, nextPosition)
+		positionEntity.position.setValue(nextPosition)
+		viewer.scene.requestRender()
+		if (progress < 1) {
+			positionAnimationFrame = requestAnimationFrame(updatePosition)
+		} else {
+			positionAnimationFrame = null
+		}
+	}
+	positionAnimationFrame = requestAnimationFrame(updatePosition)
+}
+
 // 点击周期
-const clickWeekTime = (item, index) => {
+const clickWeekTime = (item, index, options = {}) => {
 	// clickImgSrc.value = item.url
 	selectedImgIndex.value = index
-	showPositionMarker(item)
+	showPositionMarker(item, options)
 	// 通知父组件当前选中的轨迹点,用于右侧表格同步高亮
 	emit('point-change', { item, index })
 }
 
+// 开始拖动时间轴
+const handleTimelineMouseDown = event => {
+	if (event.button !== 0) return
+	timelineDragging.value = true
+	timelineDidDrag = false
+	timelineStartX = event.clientX
+	timelineStartScrollLeft = timelineRef.value?.scrollLeft ?? 0
+}
+
+// 横向拖动时间轴
+const handleTimelineMouseMove = event => {
+	if (!timelineDragging.value || !timelineRef.value) return
+	const offsetX = event.clientX - timelineStartX
+	if (Math.abs(offsetX) > 3) timelineDidDrag = true
+	if (!timelineDidDrag) return
+	timelineRef.value.scrollLeft = timelineStartScrollLeft - offsetX
+	event.preventDefault()
+}
+
+// 停止拖动时间轴
+const stopTimelineDrag = () => {
+	timelineDragging.value = false
+	setTimeout(() => {
+		timelineDidDrag = false
+	}, 0)
+}
+
+// 点击时间轴节点
+const handleTimelineItemClick = (item, index) => {
+	if (timelineDidDrag) return
+	clickWeekTime(item, index)
+}
+
 // 在地图上通过经纬度显示位置图标
-const showPositionMarker = item => {
+const showPositionMarker = (item, { animate = false, duration = POSITION_MOVE_DURATION } = {}) => {
 	if (!viewer) return
 	const longitude = Number(item?.longitude)
 	const latitude = Number(item?.latitude)
@@ -163,15 +253,19 @@
 	// 与航线相同的高度,让标记悬浮在轨迹线上,而不是贴地
 	const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, FLIGHT_HEIGHT)
 	if (positionEntity) {
-		// 已存在则移动到新的位置
-		positionEntity.position = position
+		if (animate) {
+			animatePositionMarker(position, duration)
+		} else {
+			stopPositionAnimation()
+			positionEntity.position = position
+		}
 	} else {
 		positionEntity = viewer.entities.add({
 			position,
 			billboard: {
 				image: positionIcon,
-				width: 32,
-				height: 40,
+				width: 36,
+				height: 36,
 				verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
 				disableDepthTestDistance: Number.POSITIVE_INFINITY,
 			},
@@ -210,6 +304,7 @@
 })
 
 onBeforeUnmount(() => {
+	stopPositionAnimation()
 	if (removeCameraListener) removeCameraListener()
 	removeJaAdminBoundary(viewer, adminBoundarySources)
 	adminBoundarySources = null
@@ -305,7 +400,7 @@
 	})
 }
 
-defineExpose({ getMap, getViewer, getPublicCesium, flyToPoint, setAdminBoundaryVisible, zoomToAdminBoundary })
+defineExpose({ getMap, getViewer, getPublicCesium, flyToPoint, clickWeekTime, setAdminBoundaryVisible, zoomToAdminBoundary })
 </script>
 
 <style scoped lang="scss">
@@ -318,31 +413,63 @@
 		bottom: 0px;
 		width: 100%;
 		height: 67px;
+		box-sizing: border-box;
 		background: rgba(0, 0, 0, 0.42);
-		border-radius: 0px 0px 20px 20px;
-		padding-left: 6px;
-		// padding-right: 6px;
-		// border: 1px solid red;
+		border-radius: 0;
 
 		color: #d5d5d5;
 		font-size: 14px;
 
+		.timeline-prefix {
+			position: absolute;
+			left: 7.5%;
+			top: 50%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			z-index: 2;
+			transform: translate(-50%, -50%);
+		}
+
 		.card-content {
 			display: flex;
-			justify-content: center;
 			position: absolute;
-			
-			width: calc(100% - 6px - 6px);
+			left: 15%;
+			width: 70%;
 			height: 100%;
-			overflow-x: scroll;
+			overflow-x: auto;
 			overflow-y: hidden;
 			white-space: nowrap;
+			cursor: grab;
+			scrollbar-width: none;
+
+			&::-webkit-scrollbar {
+				display: none;
+			}
+
+			&.is-dragging {
+				cursor: grabbing;
+				user-select: none;
+			}
+
+			.timeline-track {
+				position: relative;
+				display: flex;
+				flex: 0 0 auto;
+				height: 100%;
+				margin: 0 auto;
+			}
+
 			.card-item {
+				position: relative;
+				flex: 0 0 82px;
+				width: 82px;
+				text-align: center;
 				cursor: pointer;
 			}
 
-			& > div {
-				position: relative;
+			&.is-dragging .card-item {
+				cursor: grabbing;
 			}
 
 			.time-top {
@@ -381,36 +508,20 @@
 			}
 
 			.time-bottom {
-				// border: 1px solid red;
 				margin-top: 44px;
-				margin-left: 8px;
-				&:first-child {
-					margin-left: 0;
-				}
 			}
 
-			// .time-line {
-			// 	// position: absolute;
-			// 	// top: 35px;
-			// 	// width: 100%;
-			// 	position: fixed;
-			// 	bottom: 96px;
-			// 	width: 94%;
-			// 	border: 1px solid rgba(237, 237, 237, 0.6);
-			// 	z-index: 0;
-			// }
-		}
-		
-	}
-	.time-line {
+			.time-line {
 				position: absolute;
-				// top: 35px;
-				width: 100%;
-				// position: fixed;
+				left: 41px;
+				right: 41px;
 				bottom: 36px;
-				width: 100%;
-				border: 1px solid rgba(237, 237, 237, 0.6);
+				height: 1px;
+				background: rgba(237, 237, 237, 0.6);
 				z-index: 0;
+				pointer-events: none;
 			}
+		}
+	}
 }
 </style>

--
Gitblit v1.9.3