【类 型】:feat
【原 因】:飞机操作模块 添加视频显示功能 【过 程】:地图 视频 大小屏幕切换 显示隐藏等 【影 响】: # 类型 包含: # feat:新功能(feature) # fix:修补bug # docs:文档(documentation) # style: 格式(不影响代码运行的变动) # refactor:重构(即不是新增功能,也不是修改bug的代码变动) # test:增加测试 # chore:构建过程或辅助工具的变动
This commit is contained in:
parent
48f5d54f55
commit
2ce3e8c27e
@ -2,7 +2,7 @@
|
|||||||
<div class="w-100 h-100 mainBox">
|
<div class="w-100 h-100 mainBox">
|
||||||
<!-- 弹出框 -->
|
<!-- 弹出框 -->
|
||||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="320px" top="30vh" @close="closeCallback"
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="320px" top="30vh" @close="closeCallback"
|
||||||
@open="openCallback">
|
@open="openCallback" :append-to-body="true">
|
||||||
<!-- 起飞设置弹出框 -->
|
<!-- 起飞设置弹出框 -->
|
||||||
<template v-if="dialogItem == 'takeoffBox'">
|
<template v-if="dialogItem == 'takeoffBox'">
|
||||||
<div class="flex mse mac">
|
<div class="flex mse mac">
|
||||||
|
@ -24,7 +24,9 @@ export default {
|
|||||||
takeoffLonLats: [], // 航线 第一个航点 起点 最后一个航点
|
takeoffLonLats: [], // 航线 第一个航点 起点 最后一个航点
|
||||||
isflow: false, // 飞机经纬度变化时是否跟随飞机
|
isflow: false, // 飞机经纬度变化时是否跟随飞机
|
||||||
currentStyleIndex: 0, // 当前选中的地图样式索引
|
currentStyleIndex: 0, // 当前选中的地图样式索引
|
||||||
guidedMarker: null // 指点飞行地图标记
|
guidedMarker: null, // 指点飞行地图标记
|
||||||
|
styleSwitcherControlRef: null, // 地图样式切换控件引用
|
||||||
|
fullscreenControlRef: null // 全屏控件引用(enableZoom)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@ -85,6 +87,38 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
/** 动态开关地图样式切换控件 */
|
||||||
|
enableSwitch (val) {
|
||||||
|
if (!this.map) return
|
||||||
|
if (val) {
|
||||||
|
if (!this.styleSwitcherControlRef) {
|
||||||
|
const control = new MapboxStyleSwitcherControl(this.mapStyleList, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex)
|
||||||
|
this.map.addControl(control, 'top-right')
|
||||||
|
this.styleSwitcherControlRef = control
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.styleSwitcherControlRef) {
|
||||||
|
try { this.map.removeControl(this.styleSwitcherControlRef) } catch (e) { /* noop */ }
|
||||||
|
this.styleSwitcherControlRef = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 动态开关全屏控件(enableZoom) */
|
||||||
|
enableZoom (val) {
|
||||||
|
if (!this.map) return
|
||||||
|
if (val) {
|
||||||
|
if (!this.fullscreenControlRef) {
|
||||||
|
const control = new CustomFullscreenControl(this.handleResize)
|
||||||
|
this.map.addControl(control, 'top-right')
|
||||||
|
this.fullscreenControlRef = control
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.fullscreenControlRef) {
|
||||||
|
try { this.map.removeControl(this.fullscreenControlRef) } catch (e) { /* noop */ }
|
||||||
|
this.fullscreenControlRef = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async mounted () {
|
async mounted () {
|
||||||
// 等待 异步 加载 后端地图源 地址
|
// 等待 异步 加载 后端地图源 地址
|
||||||
@ -211,18 +245,19 @@ export default {
|
|||||||
|
|
||||||
// 地图比例
|
// 地图比例
|
||||||
if (this.enblueScale) {
|
if (this.enblueScale) {
|
||||||
this.map.addControl(new mapboxgl.ScaleControl(), 'bottom-right')
|
this.map.addControl(new mapboxgl.ScaleControl(), 'bottom-left')
|
||||||
}
|
}
|
||||||
// 全屏 并再之后 刷新地图
|
// 全屏 并再之后 刷新地图
|
||||||
if (this.enableZoom) {
|
if (this.enableZoom) {
|
||||||
this.map.addControl(new CustomFullscreenControl(this.handleResize), 'top-right')
|
this.fullscreenControlRef = new CustomFullscreenControl(this.handleResize)
|
||||||
|
this.map.addControl(this.fullscreenControlRef, 'top-right')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 地图样式选择控件
|
// 地图样式选择控件
|
||||||
// 自定义地图样式列表
|
// 自定义地图样式列表
|
||||||
if (this.enableSwitch) {
|
if (this.enableSwitch) {
|
||||||
const styleSwitcherControl = new MapboxStyleSwitcherControl(this.mapStyleList, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex)
|
this.styleSwitcherControlRef = new MapboxStyleSwitcherControl(this.mapStyleList, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex)
|
||||||
this.map.addControl(styleSwitcherControl, 'top-right')
|
this.map.addControl(this.styleSwitcherControlRef, 'top-right')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 飞机跟随
|
// 飞机跟随
|
||||||
|
@ -1,258 +1,116 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mainBox flex column no-select">
|
<div class="video-container" :style="containerStyle">
|
||||||
<!-- 心跳 ps:黑色只网络通 绿色飞控有效心跳-->
|
<video
|
||||||
<div class="flex">
|
ref="video"
|
||||||
<div class="tag flex mac mc iconfont" :class="[heartIconClass, usefulHeartClass]"></div>
|
class="video-player"
|
||||||
</div>
|
:style="{ objectFit: fit }"
|
||||||
<!-- 解锁状态 -->
|
autoplay
|
||||||
<div class="flex">
|
:controls="showControls"
|
||||||
<div class="tag flex mac mc iconfont" :class="isUnlock ? 'icon-jiesuo' : 'icon-suoding'"></div>
|
playsinline
|
||||||
</div>
|
muted
|
||||||
<!-- 飞机模式 -->
|
></video>
|
||||||
<div class="flex">
|
<div class="overlay">
|
||||||
<div v-if="getPlaneMode" class="plane-mode p-l-5 p-r-5 mc mac">
|
<div class="overlay-inner">
|
||||||
<font class="plane-mode-text">{{ getPlaneMode }}</font>
|
<slot name="content"></slot>
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-moshixuanze">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 卫星 -->
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="satCount" class="plane-mode p-l-5 p-r-5 mc mac">
|
|
||||||
<font class="plane-mode-text">{{ fixType }} {{ satCount }}颗</font>
|
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-weixing">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 电池电压 -->
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="voltagBattery" class="plane-mode p-l-5 p-r-5 mc mac">
|
|
||||||
<font class="plane-mode-text">{{ voltagBattery }}V</font>
|
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-dianya1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 电池电流 -->
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="currentBattery" class="plane-mode p-l-5 p-r-5 mc mac">
|
|
||||||
<font class="plane-mode-text">{{ currentBattery }}A</font>
|
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-dianliu">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 飞机高度 -->
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="positionAlt" class="plane-mode p-l-5 p-r-5 mc mac">
|
|
||||||
<font class="plane-mode-text">{{ positionAlt }}米</font>
|
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-gaodu">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 飞机对地速度 -->
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="groundSpeed" class="plane-mode p-l-5 p-r-5 mc mac">
|
|
||||||
<font class="plane-mode-text">{{ groundSpeed }}米/秒</font>
|
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-sudu">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 飞机载重 钩子状态 -->
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="loadweight" class="plane-mode p-l-5 p-r-5 mc mac">
|
|
||||||
<font class="plane-mode-text" v-if="hookstatus || loadweight">
|
|
||||||
{{ hookstatus || '' }} {{ loadweight ? loadweight + '克' : '' }}
|
|
||||||
</font>
|
|
||||||
</div>
|
|
||||||
<div class="tag flex mac mc iconfont icon-mianxingdiaogou">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PlaneStatus',
|
name: 'VideoBox',
|
||||||
|
props: {
|
||||||
|
fit: {
|
||||||
|
type: String,
|
||||||
|
default: 'contain',
|
||||||
|
validator: v => ['contain', 'cover', 'fill', 'none', 'scale-down'].includes(v)
|
||||||
|
},
|
||||||
|
showControls: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
/* 心跳 */
|
url: 'http://82.156.122.87:80/rtc/v1/whep/?app=live&stream=083AF27BB2D0',
|
||||||
heartAnimation: false // 控制心跳动画图标
|
sdk: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
|
||||||
plane: {
|
|
||||||
type: Object,
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
// 飞机在线状态
|
containerStyle () {
|
||||||
online () {
|
const toCssSize = v => (typeof v === 'number' ? `${v}px` : v)
|
||||||
if (this.plane && this.plane.planeState) {
|
return {
|
||||||
return this.plane.planeState.online
|
width: toCssSize(this.width),
|
||||||
|
height: toCssSize(this.height)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
},
|
|
||||||
// 心跳随机数
|
|
||||||
heartRandom () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.heartRandom
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
heartIconClass () {
|
|
||||||
if (!this.online) {
|
|
||||||
return 'icon-xinsui offline'
|
|
||||||
}
|
|
||||||
return this.heartAnimation ? 'icon-heart online' : 'icon-heart1 online'
|
|
||||||
},
|
|
||||||
usefulHeartClass () {
|
|
||||||
return this.online && this.plane.planeState.heartBeat ? 'useful-heart' : ''
|
|
||||||
},
|
|
||||||
// 解锁状态
|
|
||||||
isUnlock () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.isUnlock
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
// 飞机模式
|
|
||||||
getPlaneMode () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.getPlaneMode
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 卫星数
|
|
||||||
satCount () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.satCount
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 定位状态
|
|
||||||
fixType () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.fixType
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 电池电压
|
|
||||||
voltagBattery () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.voltagBattery
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 电池电流
|
|
||||||
currentBattery () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
if (Number(this.plane.planeState.currentBattery) > 0) {
|
|
||||||
return this.plane.planeState.currentBattery
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 飞机高度
|
|
||||||
positionAlt () {
|
|
||||||
if (this.plane && this.plane.planeState && this.plane.planeState.position.length > 0) {
|
|
||||||
const posLen = this.plane.planeState.position.length
|
|
||||||
return this.plane.planeState.position[posLen - 1][2]
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 飞机对地速度
|
|
||||||
groundSpeed () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.groundSpeed
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 钩子状态
|
|
||||||
hookstatus () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.hookstatus
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// 飞机载重
|
|
||||||
loadweight () {
|
|
||||||
if (this.plane && this.plane.planeState) {
|
|
||||||
return this.plane.planeState.loadweight
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
mounted () {
|
||||||
heartRandom: {
|
this.startPlay()
|
||||||
handler () {
|
|
||||||
// 心跳动画
|
|
||||||
this.heartAnimation = true
|
|
||||||
setTimeout(() => {
|
|
||||||
this.heartAnimation = false
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async startPlay () {
|
||||||
},
|
if (this.sdk) {
|
||||||
created () {
|
this.sdk.close()
|
||||||
},
|
this.sdk = null
|
||||||
destroyed () {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sdk = new window.SrsRtcWhipWhepAsync()
|
||||||
|
this.$refs.video.srcObject = this.sdk.stream
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session = await this.sdk.play(this.url)
|
||||||
|
console.log('播放成功,session:', session)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('播放失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/styles/theme.scss";
|
@import "@/styles/theme.scss";
|
||||||
|
|
||||||
.mainBox {
|
.video-container {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
width: 29px;
|
height: 100%;
|
||||||
top: 40px;
|
|
||||||
left: 10px;
|
|
||||||
z-index: 90;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainBox .flex:not(:first-child) {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
height: 29px;
|
|
||||||
min-width: 29px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plane-mode {
|
|
||||||
position: absolute;
|
|
||||||
left: 29px;
|
|
||||||
height: 29px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top-right-radius: 4px;
|
justify-content: center;
|
||||||
border-bottom-right-radius: 4px;
|
align-items: center;
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.video-player {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plane-mode-text {
|
/* 覆盖层:承载插槽内容,叠在视频之上 */
|
||||||
display: inline-block;
|
.overlay {
|
||||||
white-space: nowrap;
|
position: absolute;
|
||||||
/* 防止内容换行 */
|
inset: 0;
|
||||||
|
z-index: 90; /* 与各叠加控件的 z-index 习惯保持一致 */
|
||||||
|
pointer-events: none; /* 默认透传,避免遮挡视频区域的交互 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.useful-heart {
|
/* 让插槽内容的绝对定位以 .overlay 为参照,不做默认偏移 */
|
||||||
color: $success-color;
|
.overlay-inner {
|
||||||
|
position: static; /* 不改变定位上下文,让内部绝对定位元素参照 .overlay */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: auto; /* 允许控件交互 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,16 +1,70 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-100">
|
<div class="h-100 stage">
|
||||||
<!-- 地图组件 -->
|
<!-- 主视图与画中画容器 -->
|
||||||
<map-box ref="mapbox" :enableShowNofly="true" :enableGuided="true" :enableFollow="true"
|
<!-- MapBox 容器:作为主视图或小窗显示(始终挂载) -->
|
||||||
:enblueScale="!$store.state.app.isWideScreen" @longPress="handleLongPress" @map-ready="onMapReady">
|
<div
|
||||||
|
v-show="mainView === 'map' || (mainView === 'video' && pipVisible)"
|
||||||
|
class="stage-item map"
|
||||||
|
:class="{ main: mainView === 'map', pip: mainView === 'video' && pipVisible }"
|
||||||
|
:style="pipCssVars"
|
||||||
|
>
|
||||||
|
<map-box
|
||||||
|
ref="mapbox"
|
||||||
|
:enableShowNofly="true"
|
||||||
|
:enableGuided="true"
|
||||||
|
:enableFollow="true"
|
||||||
|
:enableSwitch="mainView === 'map'"
|
||||||
|
:enableZoom="mainView === 'map'"
|
||||||
|
:enblueScale="!$store.state.app.isWideScreen"
|
||||||
|
@longPress="handleLongPress"
|
||||||
|
@map-ready="onMapReady"
|
||||||
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-show="mapReady">
|
<!-- 仅在地图为主视图时渲染叠加控件(小窗时不渲染) -->
|
||||||
|
<div v-if="mapReady && mainView === 'map'">
|
||||||
<BatteryStatus :plane="plane" />
|
<BatteryStatus :plane="plane" />
|
||||||
<PlaneStatus :plane="plane" />
|
<PlaneStatus :plane="plane" />
|
||||||
<ControllerTabs :plane="plane" @mapXOffset="mapXOffset" @makeRoute="makeRoute" @clearRoute="clearRoute" />
|
<ControllerTabs :plane="plane" @mapXOffset="mapXOffset" @makeRoute="makeRoute" @clearRoute="clearRoute" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</map-box>
|
</map-box>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- VideoBox 容器:作为主视图或小窗显示(始终挂载) -->
|
||||||
|
<div
|
||||||
|
v-show="mainView === 'video' || (mainView === 'map' && pipVisible)"
|
||||||
|
class="stage-item video"
|
||||||
|
:class="{ main: mainView === 'video', pip: mainView === 'map' && pipVisible }"
|
||||||
|
:style="pipCssVars"
|
||||||
|
>
|
||||||
|
<video-box
|
||||||
|
ref="videobox"
|
||||||
|
fit="cover"
|
||||||
|
:show-controls="false"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<!-- 仅在视频为主视图时渲染叠加控件(小窗时不渲染) -->
|
||||||
|
<div v-if="mainView === 'video'">
|
||||||
|
<BatteryStatus :plane="plane" />
|
||||||
|
<PlaneStatus :plane="plane" />
|
||||||
|
<ControllerTabs :plane="plane" @mapXOffset="mapXOffset" @makeRoute="makeRoute" @clearRoute="clearRoute" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</video-box>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 顶部工具条:切换主视图 / 显示/隐藏 小窗 -->
|
||||||
|
<div class="stage-toolbar top-right">
|
||||||
|
<el-button size="mini" @click="toggleMainView">
|
||||||
|
{{ mainView === 'map' ? '切到视频' : '切到地图' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button size="mini" @click="pipVisible = !pipVisible">
|
||||||
|
{{ pipVisible ? '隐藏小窗' : '显示小窗' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 弹出框 -->
|
<!-- 弹出框 -->
|
||||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="320px" top="30vh" @close="closeCallback"
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="320px" top="30vh" @close="closeCallback"
|
||||||
@open="openCallback">
|
@open="openCallback">
|
||||||
@ -46,6 +100,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MapBox from '@/components/MapBox'
|
import MapBox from '@/components/MapBox'
|
||||||
|
import VideoBox from '@/components/VideoBox'
|
||||||
import ControllerTabs from '@/components/ControllerTabs'
|
import ControllerTabs from '@/components/ControllerTabs'
|
||||||
import BatteryStatus from '@/components/BatteryStatus'
|
import BatteryStatus from '@/components/BatteryStatus'
|
||||||
import PlaneStatus from '@/components/PlaneStatus'
|
import PlaneStatus from '@/components/PlaneStatus'
|
||||||
@ -56,6 +111,11 @@ export default {
|
|||||||
name: 'Planes',
|
name: 'Planes',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
// 视图与画中画状态
|
||||||
|
mainView: 'map', // 'map' | 'video'
|
||||||
|
pipVisible: true,
|
||||||
|
pipPos: 'top-right', // 预留:'top-right'|'top-left'|'bottom-right'|'bottom-left'
|
||||||
|
pipSize: { w: 400, h: 225 }, // 16:9 小窗尺寸
|
||||||
dialogTitle: '', // 弹出框 标题
|
dialogTitle: '', // 弹出框 标题
|
||||||
dialogItem: '', // 弹出框 项目类型判断
|
dialogItem: '', // 弹出框 项目类型判断
|
||||||
dialogVisible: false, // 弹出框 显隐
|
dialogVisible: false, // 弹出框 显隐
|
||||||
@ -69,11 +129,17 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MapBox,
|
MapBox,
|
||||||
|
VideoBox,
|
||||||
ControllerTabs,
|
ControllerTabs,
|
||||||
BatteryStatus,
|
BatteryStatus,
|
||||||
PlaneStatus
|
PlaneStatus
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
pipCssVars () {
|
||||||
|
const w = typeof this.pipSize.w === 'number' ? `${this.pipSize.w}px` : this.pipSize.w
|
||||||
|
const h = typeof this.pipSize.h === 'number' ? `${this.pipSize.h}px` : this.pipSize.h
|
||||||
|
return { '--pip-w': w, '--pip-h': h }
|
||||||
|
},
|
||||||
plane () {
|
plane () {
|
||||||
if (this.$store.state.airList.length > 0) {
|
if (this.$store.state.airList.length > 0) {
|
||||||
return this.$store.state.airList.find(plane => plane.id === this.planesId)
|
return this.$store.state.airList.find(plane => plane.id === this.planesId)
|
||||||
@ -112,6 +178,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** 切换主视图 */
|
||||||
|
toggleMainView () {
|
||||||
|
this.mainView = this.mainView === 'map' ? 'video' : 'map'
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// 主从切换后,通知地图自适应
|
||||||
|
if (this.$refs.mapbox && typeof this.$refs.mapbox.handleResize === 'function') {
|
||||||
|
this.$refs.mapbox.handleResize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
/** 弹出框 关闭事件回调 */
|
/** 弹出框 关闭事件回调 */
|
||||||
closeCallback () {
|
closeCallback () {
|
||||||
if (this.dialogItem === 'guidedBox' && this.isReserveGuidedMaker === false) { // 关闭点飞窗口时
|
if (this.dialogItem === 'guidedBox' && this.isReserveGuidedMaker === false) { // 关闭点飞窗口时
|
||||||
@ -144,10 +220,10 @@ export default {
|
|||||||
}
|
}
|
||||||
this.guidedAlt = height
|
this.guidedAlt = height
|
||||||
|
|
||||||
// ✅ 构造单架飞机的位置数组
|
// 构造单架飞机的位置数组
|
||||||
const planeCoords = [[center.lon, center.lat]]
|
const planeCoords = [[center.lon, center.lat]]
|
||||||
|
|
||||||
// ✅ 复用现有方法绘制引导线
|
// 复用现有方法绘制引导线
|
||||||
this.$refs.mapbox.drawGuidedLines({
|
this.$refs.mapbox.drawGuidedLines({
|
||||||
centerPoint: { lng: center.lon, lat: center.lat },
|
centerPoint: { lng: center.lon, lat: center.lat },
|
||||||
endPoint: { lng: lonLat.lon, lat: lonLat.lat },
|
endPoint: { lng: lonLat.lon, lat: lonLat.lat },
|
||||||
@ -214,7 +290,10 @@ export default {
|
|||||||
* @param {*} y 正数向上移动 负数向下移动
|
* @param {*} y 正数向上移动 负数向下移动
|
||||||
*/
|
*/
|
||||||
mapXOffset (x, y) {
|
mapXOffset (x, y) {
|
||||||
|
// 仅在地图为主视图时,才执行地图偏移
|
||||||
|
if (this.mainView === 'map' && this.$refs.mapbox && typeof this.$refs.mapbox.mapXOffset === 'function') {
|
||||||
this.$refs.mapbox.mapXOffset(x, y)
|
this.$refs.mapbox.mapXOffset(x, y)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @description: 发布 mqtt 信息
|
* @description: 发布 mqtt 信息
|
||||||
@ -304,4 +383,66 @@ export default {
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.stage {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.stage-item {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all .2s ease;
|
||||||
|
}
|
||||||
|
.stage-item.main {
|
||||||
|
inset: 0;
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.stage-item.pip {
|
||||||
|
width: var(--pip-w, 400px);
|
||||||
|
height: var(--pip-h, 225px);
|
||||||
|
z-index: 20;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
/* 小窗位置:在右上基础上下移28px、左移43px */
|
||||||
|
.stage-item.pip {
|
||||||
|
top: 40px; /* 12 + 28 */
|
||||||
|
right: 55px; /* 12 + 43 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具条也移动到相应位置(与小窗同样偏移) */
|
||||||
|
.stage-toolbar.top-right {
|
||||||
|
top: 40px; /* 12 + 28 */
|
||||||
|
right: 55px; /* 12 + 43 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 30;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当地图为主视图时,避免叠加控件大面积拦截地图交互 */
|
||||||
|
/* 仅对 map 主视图容器内的控件做定向约束 */
|
||||||
|
.stage .map :deep(.mainBox) {
|
||||||
|
pointer-events: none; /* 默认不拦截,让地图可拖拽 */
|
||||||
|
}
|
||||||
|
.stage .map :deep(.mainBox .taButGroup),
|
||||||
|
.stage .map :deep(.mainBox .tabContent),
|
||||||
|
.stage .map :deep(.el-dialog),
|
||||||
|
.stage .map :deep(.el-button),
|
||||||
|
.stage .map :deep(.el-slider),
|
||||||
|
.stage .map :deep(.el-input),
|
||||||
|
.stage .map :deep(.el-select) {
|
||||||
|
pointer-events: auto; /* 仅在具体控件上启用交互 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保电量条、状态面板只占据自身区域,且可交互 */
|
||||||
|
.stage .map :deep(.batteryBar),
|
||||||
|
.stage .map :deep(.plane-mode),
|
||||||
|
.stage .map :deep(.mainBox .tag) {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user