From 1d552a3bad95caae4af15322df28e6240b797693 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Thu, 13 Aug 2026 15:32:06 +0800
Subject: [PATCH] feat: app视频封面图
---
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 201 insertions(+), 35 deletions(-)
diff --git a/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue b/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
index 8fb9ea7..c9a7ef1 100644
--- a/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
+++ b/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
@@ -1,13 +1,41 @@
<template>
- <el-dialog class="ztzf-page-map-view-dialog" v-model="visible" :show-close="false"
- :close-on-click-modal="false" @closed="handleClosed" destroy-on-close>
+ <el-dialog
+ class="command-page-map-view-dialog"
+ v-model="visible"
+ :show-close="false"
+ :close-on-click-modal="false"
+ @closed="handleClosed"
+ destroy-on-close
+ >
<div class="dialog-container">
<div class="left-container">
- <div class="leftMap ztzf-cesium" id="leftMapContainer"></div>
+ <CommonCesiumMap
+ ref="mapRef"
+ class="leftMap command-cesium"
+ :active="visible"
+ :flat-mode="false"
+ :terrain="true"
+ :layer-mode="4"
+ :boundary="false"
+ :list="list"
+ @point-change="handlePointChange"
+ >
+ <template #timeline-prefix>
+ <el-icon class="track-play-btn" :size="30" @click.stop="handlePlayClick">
+ <VideoPause v-if="isPlaying" />
+ <VideoPlay v-else />
+ </el-icon>
+ </template>
+ </CommonCesiumMap>
</div>
<div class="right-container">
- <div class="header">{{ dialogTitle }}</div>
- <div class="dialog-container">
+ <div class="header">
+ <span>{{ dialogTitle }}</span>
+
+ <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
+ </div>
+
+ <div class="content">
<div class="detail-title">轨迹信息</div>
<el-row>
<el-col :span="24">
@@ -32,9 +60,9 @@
</el-col>
</el-row>
<div class="detail-title">轨迹点</div>
- <div class="ztzf-table-container">
- <div class="ztzf-table-content">
- <el-table class="ztzf-data-cockpit-table" :data="list">
+ <div class="command-table-container">
+ <div class="command-table-content">
+ <el-table ref="pointTableRef" class="command-table" :data="list" :row-class-name="getRowClassName">
<el-table-column type="index" width="60" label="序号" />
<el-table-column label="经纬度">
<template #default="{ row }">{{ row.longitude }},{{ row.latitude }}</template>
@@ -45,7 +73,9 @@
</div>
</div>
<div class="footer">
- <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">
+ {{ dialogReadonly ? '关闭' : '取消' }}
+ </el-button>
</div>
</div>
</div>
@@ -53,12 +83,15 @@
</template>
<script setup>
-import { computed, nextTick, ref } from 'vue'
+import { Close, VideoPause, VideoPlay } from '@element-plus/icons-vue'
+
+import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { fwDroneFlightRecordDetailApi } from '@/views/recordManage/historyTracks/fwDroneFlightRecord'
import { fwDroneFlightRecordDetailPageApi } from '@/views/recordManage/historyTracks/flyTrajectory'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
import * as Cesium from 'cesium'
-import { flyVisual } from '@/utils/cesium/mapUtil'
+import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
+import droneIcon from '@/assets/images/dataCockpit/map/drone.png'
const initForm = () => ({
droneName: '',
@@ -68,11 +101,11 @@
stayDuration: '',
})
let list = ref([])
-const emit = defineEmits(['success'])
const formData = ref(initForm()) // 表单数据
const visible = ref(false) // 弹框显隐
const dialogMode = ref('add') // 弹框模式
const dialogReadonly = computed(() => dialogMode.value === 'view')
+const mapRef = ref(null)
const dialogTitle = computed(() => {
if (dialogMode.value === 'edit') {
return '编辑'
@@ -84,12 +117,12 @@
})
// 关闭弹框
-function handleCancel () {
+function handleCancel() {
visible.value = false
}
// 加载详情
-async function loadDetail () {
+async function loadDetail() {
if (!formData.value.id) return
const res = await fwDroneFlightRecordDetailApi({ id: formData.value.id })
const res1 = await fwDroneFlightRecordDetailPageApi({ flightRecordId: formData.value.id })
@@ -98,52 +131,145 @@
formData.value = res?.data?.data ?? {}
}
+let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
+ color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
+ directionColor: new Cesium.Color(1, 1, 1, 1),
+ outlineColor: new Cesium.Color(1, 1, 1, 1),
+ outlineWidth: 0,
+ speed: 5,
+})
+
+const MOCK_FLIGHT_HEIGHT = 120
+
// 角度转c3
-function degreesToC3 ({ longitude, latitude, height = 0 }) {
+function degreesToC3({ longitude, latitude, height = 0 }) {
return Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
}
// 渲染航线
-function drawFlightPath () {
- const positions = list.value.map(p => degreesToC3(p))
+function drawFlightPath() {
+ const positions = list.value.map(p => degreesToC3({ ...p, height: MOCK_FLIGHT_HEIGHT }))
+ if (!positions.length) return
droneLineEntity && geometricSource.entities.remove(droneLineEntity)
- const color = Cesium.Color.fromCssColorString('#00D690')
droneLineEntity = geometricSource.entities.add({
polyline: {
+ width: 4,
positions: positions,
- width: 3,
- material: new Cesium.PolylineOutlineMaterialProperty({ color }),
+ material: arrowLineMaterialProperty,
clampToGround: false,
},
})
- viewer.flyTo(droneLineEntity)
+ startDroneEntity && geometricSource.entities.remove(startDroneEntity)
+ startDroneEntity = geometricSource.entities.add({
+ position: positions[positions.length - 1],
+ billboard: {
+ image: droneIcon,
+ width: 40,
+ height: 40,
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
+ heightReference: Cesium.HeightReference.NONE,
+ },
+ })
+ viewer.flyTo(droneLineEntity, { duration: 0 })
}
// 关闭后重置
-function handleClosed () {
+function handleClosed() {
+ stopTrackPlay()
formData.value = initForm()
list.value = []
+ selectedPointIndex.value = 0
viewer = null
geometricSource = null
droneLineEntity = null
+ startDroneEntity = null
}
-let viewer, geometricSource, droneLineEntity
-function initMap () {
- if (viewer) return
- const publicCesiumInstance = new PublicCesium({
- dom: 'leftMapContainer',
- flatMode: false,
- terrain: true,
- layerMode: 4,
- boundary: false,
+// 当前选中的轨迹点索引(与地图时间轴联动)
+const selectedPointIndex = ref(0)
+const pointTableRef = ref(null)
+const isPlaying = ref(false)
+const PLAY_INTERVAL = 1000
+let playTimer = null
+let playIndex = 0
+
+// 停止轨迹播放
+function stopTrackPlay() {
+ if (playTimer) {
+ clearTimeout(playTimer)
+ playTimer = null
+ }
+ isPlaying.value = false
+}
+
+// 播放下一个轨迹点
+function playNextPoint() {
+ if (!list.value.length || playIndex >= list.value.length) {
+ stopTrackPlay()
+ return
+ }
+ mapRef.value?.clickWeekTime?.(list.value[playIndex], playIndex, {
+ animate: true,
+ duration: PLAY_INTERVAL,
})
- viewer = publicCesiumInstance.getViewer()
+ playIndex += 1
+ playTimer = setTimeout(() => {
+ playTimer = null
+ if (playIndex >= list.value.length) {
+ stopTrackPlay()
+ return
+ }
+ playNextPoint()
+ }, PLAY_INTERVAL)
+}
+
+// 切换轨迹播放状态
+function handlePlayClick() {
+ if (isPlaying.value) {
+ stopTrackPlay()
+ return
+ }
+ if (!list.value.length) return
+ mapRef.value?.clickWeekTime?.(list.value[0], 0)
+ if (list.value.length === 1) return
+ playIndex = 1
+ isPlaying.value = true
+ playNextPoint()
+}
+
+// 地图上点击轨迹点后,同步高亮表格对应行,并滚动到该行
+function handlePointChange({ index } = {}) {
+ if (typeof index !== 'number') return
+ selectedPointIndex.value = index
+ scrollToSelectedRow()
+}
+
+// 表格行较多时自动滚动到选中行
+async function scrollToSelectedRow() {
+ await nextTick()
+ const rowEl = pointTableRef.value?.$el?.querySelector('.selected-row')
+ rowEl?.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
+}
+
+function getRowClassName({ row, rowIndex }) {
+ const classNames = []
+ if (row?.isAlarm === 1) classNames.push('alarm-row')
+ if (rowIndex === selectedPointIndex.value) classNames.push('selected-row')
+ return classNames.join(' ')
+}
+
+let viewer, geometricSource, droneLineEntity, startDroneEntity
+function initMap() {
+ if (viewer) return
+ const map = mapRef.value?.getMap?.()
+ if (!map?.viewer) return
+ viewer = map.viewer
geometricSource = new Cesium.CustomDataSource('geometricSource')
viewer.dataSources.add(geometricSource)
}
// 打开弹框
-async function open ({ mode, row } = {}) {
+async function open({ mode, row } = {}) {
+ stopTrackPlay()
dialogMode.value = mode || 'add'
visible.value = true
await nextTick()
@@ -157,6 +283,46 @@
}
defineExpose({ open })
+
+// 卸载时清理播放定时器
+onBeforeUnmount(() => {
+ stopTrackPlay()
+})
</script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.left-container {
+ min-width: 0;
+ overflow: hidden;
+}
+
+.right-container {
+ position: relative;
+ flex-shrink: 0;
+ z-index: 1;
+ background: #1a1a2a;
+}
+
+:deep(.alarm-row) {
+ background: #2b2b4c !important;
+
+ td {
+ background-color: transparent !important;
+ }
+}
+
+// 与地图时间轴联动的选中行高亮
+:deep(.selected-row) {
+ background: #14335f !important;
+
+ td {
+ background-color: transparent !important;
+ color: #4faaff !important;
+ }
+}
+
+.track-play-btn {
+ color: #ffffff;
+ cursor: pointer;
+}
+</style>
--
Gitblit v1.9.3