From 43e1f2d3af6b23413fc92773929bffc680bb54da Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Wed, 22 Jul 2026 18:13:04 +0800
Subject: [PATCH] feat:优化历史轨迹
---
applications/drone-command/src/components/map-container/common-cesium-map.vue | 202 +++++++++++++++++++++++++++++++++++++-------------
1 files changed, 150 insertions(+), 52 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 723a663..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
@@ -5,23 +5,36 @@
<div class="timeline-prefix">
<slot name="timeline-prefix"></slot>
</div>
- <div class="card-content">
- <div class="card-item" v-for="(item, index) in props.list" @click="clickWeekTime(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
+ 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 class="time-bottom">{{ item.createTime.slice(11) }}</div>
</div>
-
</div>
</div>
- <div class="time-line"></div>
</template>
</div>
</template>
@@ -91,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 = {
@@ -144,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)
@@ -165,8 +253,12 @@
// 与航线相同的高度,让标记悬浮在轨迹线上,而不是贴地
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,
@@ -212,6 +304,7 @@
})
onBeforeUnmount(() => {
+ stopPositionAnimation()
if (removeCameraListener) removeCameraListener()
removeJaAdminBoundary(viewer, adminBoundarySources)
adminBoundarySources = null
@@ -320,11 +413,9 @@
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;
@@ -342,20 +433,43 @@
.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 {
@@ -394,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: 70%;
- // position: fixed;
- left: 15%;
+ left: 41px;
+ right: 41px;
bottom: 36px;
- 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