From b80dbbcb1340a5d02b2137efc9ff0e5d84c50647 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Thu, 17 Apr 2025 17:12:39 +0800
Subject: [PATCH] feat: 控制台相机控制
---
src/hooks/controlDrone/useManualControl.js | 55 +++-
src/api/drc.js | 2
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue | 52 ++-
src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue | 1
src/api/payload.js | 47 +++
src/components/ScaleRule.vue | 368 ++++++++++++++++++++++++++++++
src/components/CurrentTaskDetails/TaskDetailsLeft.vue | 117 ++++++++-
src/components/CurrentTaskDetails/CurrentTaskDetails.vue | 19
8 files changed, 583 insertions(+), 78 deletions(-)
diff --git a/src/api/drc.js b/src/api/drc.js
index fba2361..674f8c7 100644
--- a/src/api/drc.js
+++ b/src/api/drc.js
@@ -9,8 +9,6 @@
return resp.data
}
-// /control/api/v1/workspaces/82f6008b-2068-448c-9094-881e212f31c3/drc/connect
-
// 进入飞行控制 (建立drc连接&获取云控控制权)
export async function postDrcEnter (body,workspaceId) {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body)
diff --git a/src/api/payload.js b/src/api/payload.js
index ed91d52..61e91ea 100644
--- a/src/api/payload.js
+++ b/src/api/payload.js
@@ -1,12 +1,4 @@
import request from '@/axios'
-import { ElMessage } from 'element-plus'
-
-const API_PREFIX = '/drone-device-core/control/api/v1'
-
-// 获取负载控制权
-export async function postPayloadAuth(sn, body) {
- return await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)
-}
export const PayloadCommandsEnum = {
CameraModeSwitch: 'camera_mode_switch',
@@ -18,11 +10,36 @@
GimbalReset: 'gimbal_reset',
CameraAim: 'camera_aim',
};
+
+const API_PREFIX = '/drone-device-core/control/api/v1'
+
+// 获取负载控制权
+export async function postPayloadAuth(sn, body) {
+ return await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)
+}
+
// 发送负载名称
export async function postPayloadCommands(sn, body, config = {}) {
return await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body, config)
}
+// 获得有效载荷控制
+export async function getPayloadControlApi(params) {
+ return await request({
+ url:`${API_PREFIX}/devices/payload-control/requests`,
+ method:'get',
+ params
+ })
+}
+
+// 云台控制api
+export async function ptzControlApi(key,params) {
+ return await request({
+ url:`${API_PREFIX}/devices/payload-control/payload/${key}`,
+ method:'get',
+ params
+ })
+}
// 拍照和录像
export async function callPhotoAndVideoCmd(sn, type) {
@@ -33,11 +50,21 @@
}
+// 相机参数调整
+export async function cameraParamsChangeApi(data) {
+ return await request({
+ url:`/drone-device-core/payload-control/payload/zoom/level`,
+ method:'post',
+ data
+ })
+}
+
+
+
// 切换直播镜头
export const switchLivestream = (data) => {
- const url = `${API_PREFIX}/live/streams/switch`;
return request({
- url,
+ url:`${API_PREFIX}/live/streams/switch`,
method: 'post',
data,
});
diff --git a/src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue b/src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue
index aacaf7d..fd914f6 100644
--- a/src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue
+++ b/src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue
@@ -8,6 +8,7 @@
</div>
<div
v-for="item in list3"
+ :key="item.name"
:style="item.style"
class="operateBtn"
:title="item.name"
diff --git a/src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue b/src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue
index f4ba147..d78a55a 100644
--- a/src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue
+++ b/src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue
@@ -53,12 +53,12 @@
<el-icon class="btnIcon">
<Top />
</el-icon>
- <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_UP)" @mouseup="onMouseUp">C</div>
+ <div class="btn" @mousedown="onMouseDown(KeyCode.KEY_C)" @mouseup="onMouseUp">C</div>
</div>
</div>
<div class="btnGroupT">
<div class="btnItem">
- <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_DOWN)" @mouseup="onMouseUp">Z</div>
+ <div class="btn" @mousedown="onMouseDown(KeyCode.KEY_Z)" @mouseup="onMouseUp">Z</div>
<el-icon class="btnIcon">
<Bottom />
</el-icon>
@@ -66,7 +66,7 @@
</div>
</div>
</div>
-
+ <!-- 指南针-->
<div class="compass">
<ControlComPass />
</div>
@@ -83,7 +83,12 @@
</div>
<div class="ptzControlBtnBox">
<div class="ptzControlBtn b-r">
- <div v-for="(item, index) in list5" :style="item.style" class="ptzControlItem"></div>
+ <div
+ v-for="(item, index) in list5"
+ :style="item.style"
+ class="ptzControlItem"
+ @mousedown="onMouseDown(item.key)"
+ ></div>
<div
class="ptzControlItemIcon"
@@ -122,7 +127,7 @@
<script setup>
import ControlComPass from '../ControlComPass/ControlComPass.vue'
import { KeyCode, useManualControl } from '@/hooks/controlDrone/useManualControl'
-import { droneController, exitController, postDrc, postDrcExit, returnHome, returnHomeCancel } from '@/api/drc'
+import { droneController, exitController, postDrc, returnHome, returnHomeCancel } from '@/api/drc'
import { ElMessage } from 'element-plus'
import { useStore } from 'vuex'
import { UranusMqtt } from '@/mqtt'
@@ -133,23 +138,20 @@
ArrowUp,
Bottom,
CaretRight,
- CaretTop,
Minus,
Plus,
RefreshLeft,
RefreshRight,
} from '@element-plus/icons-vue'
-import controlCenterImg from '@/assets/images/taskManagement/taskIntermediateContent/controlCenter.png'
import BaseControl from '@/components/CurrentTaskDetails/ControlPanel/BaseControl.vue'
import EventBus from '@/event-bus'
-import dayjs from 'dayjs'
+import { getPayloadControlApi, ptzControlApi } from '@/api/payload'
const deviceOsdInfo = inject('deviceOsdInfo')
const taskDetails = inject('taskDetails')
const dockSn = inject('dockSn')
const droneSn = inject('droneSn')
-
const store = useStore()
let mqttState = null
@@ -157,7 +159,6 @@
const valueTime = ref('00:00:00')
let timer = null
let totalSeconds = 0
-
const workspace_id = computed(() => taskDetails?.value?.workspace_id)
const list1 = [
@@ -172,13 +173,13 @@
]
const speed = ref(5)
-provide('speed',speed)
+provide('speed', speed)
const list5 = [
- { name: '上', style: { top: '-70%' }, imgStyle: { top: '20%', left: '50%' } },
- { name: '右', style: { left: '70%' }, imgStyle: { right: '0', top: '50%' } },
- { name: '下', style: { top: '70%' }, imgStyle: { bottom: '0', left: '50%' } },
- { name: '左', style: { left: '-70%' }, imgStyle: { left: '20%', top: '50%' } },
+ { name: '上', key: KeyCode.ARROW_UP, operate: 'up', style: { top: '-70%' } },
+ { name: '右', key: KeyCode.ARROW_RIGHT, operate: 'right', style: { left: '70%' } },
+ { name: '下', key: KeyCode.ARROW_DOWN, operate: 'down', style: { top: '70%' } },
+ { name: '左', key: KeyCode.ARROW_LEFT, operate: 'left', style: { left: '-70%' } },
]
const list4 = [
@@ -193,8 +194,6 @@
{ name: '方向', value: '正北' },
],
]
-
-
const deviceTopicInfo = ref({
pubTopic: '',
@@ -247,6 +246,13 @@
.catch(e => {})
}
+// 获得有效载荷控制
+function getPayloadControl() {
+ getPayloadControlApi({ sn: dockSn.value }).then(res => {
+ ElMessage.success('成功获得有效载荷控制')
+ })
+}
+
// 手动控制
function control() {
if (!client_id.value) return ElMessage.error('无人机不在空中,不能进入指挥飞行模式。')
@@ -261,6 +267,7 @@
deviceTopicInfo.value.pubTopic = data.pub[0]
}
ElMessage.success('控制成功')
+ getPayloadControl()
isAutoControl.value = false
})
}
@@ -309,9 +316,10 @@
}
// useManualControl里面用的参数
-const paramsRef = computed(()=>({
- droneSn:droneSn.value,
- speed:speed.value,
+const paramsRef = computed(() => ({
+ droneSn: droneSn.value,
+ dockSn: dockSn.value,
+ speed: speed.value,
}))
watch(
@@ -320,7 +328,7 @@
if (workspace_id.value) {
await createConnect()
// 使用控制
- manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController,paramsRef)
+ manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController, paramsRef)
}
}
)
@@ -332,6 +340,7 @@
EventBus.on('controlPanel-onMouseDown', onMouseDown)
EventBus.on('controlPanel-timeStart', timeStart)
EventBus.on('controlPanel-timeStop', timeStop)
+ EventBus.on('controlPanel-getPayloadControl', getPayloadControl)
})
onBeforeUnmount(() => {
@@ -341,6 +350,7 @@
EventBus.off('controlPanel-onMouseDown', onMouseDown)
EventBus.off('controlPanel-timeStart', timeStart)
EventBus.off('controlPanel-timeStop', timeStop)
+ EventBus.off('controlPanel-getPayloadControl', getPayloadControl)
destroyConnect()
})
</script>
diff --git a/src/components/CurrentTaskDetails/CurrentTaskDetails.vue b/src/components/CurrentTaskDetails/CurrentTaskDetails.vue
index 649f760..8d51880 100644
--- a/src/components/CurrentTaskDetails/CurrentTaskDetails.vue
+++ b/src/components/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -18,12 +18,12 @@
<TaskDetailsRight v-if="isAutoControl" />
<template v-else>
<TaskDetailsHead />
- <TaskDetailsLeft />
</template>
+ <TaskDetailsLeft />
<!-- 控制面板,里面有方法需要立即执行,不可用v-if -->
<ControlPanel v-show="!isAutoControl" />
- <img alt="" :src="amplifyImg" class="amplify" @click="amplify" />
+ <img alt="" :src="amplifyImg" class="amplify" @click="isMaxMap = !isMaxMap" />
</div>
</el-dialog>
</template>
@@ -46,14 +46,12 @@
import { ElMessage } from 'element-plus'
import EventBus from '@/event-bus'
+
const isAutoControl = ref(true)
const lineQuality = ref(1) //1流畅,2标清
provide('isAutoControl', isAutoControl)
provide('lineQuality', lineQuality)
-const isShow = defineModel('show')
-const props = defineProps(['id'])
-const currentLiveUrl = ref('')
let taskDetails = ref({})
const deviceOsdInfo = ref({})
provide('taskDetails', taskDetails)
@@ -64,10 +62,12 @@
provide('dockSn', dockSn)
provide('droneSn', droneSn)
+const isShow = defineModel('show')
+const props = defineProps(['id'])
+const currentLiveUrl = ref('')
+const isTakeOff = ref(false)
const isMaxMap = ref(false)
-const amplify = () => {
- isMaxMap.value = !isMaxMap.value
-}
+let droneWebSocket //WS实例
// 机巢直播
const getDeviceLiveUrl = async () => {
@@ -80,8 +80,6 @@
const res = await liveStart(droneSn.value, lineQuality.value)
currentLiveUrl.value = res.data.data.rtcs_url
}
-
-const isTakeOff = ref(false)
// 设置当前直播地址
const setCurrentLiveUrl = async () => {
@@ -120,7 +118,6 @@
}
}
-let droneWebSocket
// 创建ws连接
const createWsConnect = () => {
const workspaceId = taskDetails.value.workspace_id
diff --git a/src/components/CurrentTaskDetails/TaskDetailsLeft.vue b/src/components/CurrentTaskDetails/TaskDetailsLeft.vue
index ce9f85d..812bd86 100644
--- a/src/components/CurrentTaskDetails/TaskDetailsLeft.vue
+++ b/src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -1,12 +1,18 @@
<template>
<div class="taskDetailsLeft">
<div class="title">负载控制</div>
- <div class="singleCol" @click="cameraType('wide', '广角')">广角相机</div>
- <div class="singleCol" @click="cameraType('zoom', '变焦')">变焦相机</div>
- <div class="singleCol" @click="cameraType('ir', '红外')">红外相机</div>
+ <div
+ class="singleCol"
+ v-for="item in list1"
+ @click="cameraType(item)"
+ :key="item.key"
+ :class="{ active: item.key === cameraParams.camera_type }"
+ >
+ {{ item.name }}相机
+ </div>
<div class="multiCol">
- <div>云台回中</div>
- <div>云台朝下</div>
+ <div @click="faceToCenter">云台回中</div>
+ <div @click="faceToDown">云台朝下</div>
</div>
<el-select class="qualityChange" v-model="lineQuality" @change="qualityChange">
<el-option v-for="item in qualityList" :key="item.id" :label="item.name" :value="item.id" />
@@ -19,17 +25,37 @@
<div>喊话</div>
<div>广播</div>
</div>
+
+ <div class="cameraZoom">
+ <el-slider
+ v-model="cameraParams.zoom_factor"
+ vertical
+ :max="cameraParams.camera_type === 'ir' ? 20 : 50"
+ @input="sliderChange"
+ />
+ </div>
</div>
</template>
<script setup>
import EventBus from '@/event-bus'
-import { callPhotoAndVideoCmd, switchLivestream } from '@/api/payload'
+import { callPhotoAndVideoCmd, cameraParamsChangeApi } from '@/api/payload'
import { ElMessage } from 'element-plus'
+import { throttle } from 'lodash'
const isRecording = ref(false)
+const list1 = ref([
+ { name: '广角', key: 'wide' },
+ { name: '变焦', key: 'zoom' },
+ { name: '红外', key: 'ir' },
+])
+const cameraParams = ref({
+ zoom_factor: 0,
+ camera_type: 'wide',
+})
const droneSn = inject('droneSn')
+const dockSn = inject('dockSn')
const lineQuality = inject('lineQuality')
const qualityList = [
{ name: '自适应', id: 0 },
@@ -39,14 +65,41 @@
{ name: '超清', id: 4 },
]
-// todo
-function cameraType(video_type, name) {
- switchLivestream({
- // video_id: this.videoId,
- video_type,
- }).then(res => {
- ElMessage.success(`切换为${name}相机成功`)
+function faceToCenter() {}
+
+function faceToDown() {}
+
+// 倍率调整
+function sliderChange(val) {
+ if (cameraParams.value.camera_type !== 'ir') {
+ cameraParams.value.camera_type = val > 1 ? 'zoom' : 'wide'
+ }
+ cameraParamsChangeThrottle()
+}
+
+// 修改相机参数-节流版
+const cameraParamsChangeThrottle = throttle(cameraParamsChange, 500)
+
+// 修改相机参数
+function cameraParamsChange() {
+ cameraParamsChangeApi({
+ sn: dockSn.value,
+ ...cameraParams.value,
})
+}
+
+function cameraType(row) {
+ cameraParams.value.camera_type = row.key
+ if (row.key === 'wide') {
+ cameraParams.value.zoom_factor = 0
+ }
+ if (row.key === 'zoom') {
+ cameraParams.value.zoom_factor = 5
+ }
+ if (row.key === 'ir') {
+ cameraParams.value.zoom_factor = 0
+ }
+ cameraParamsChange()
}
// 画质切换
@@ -100,17 +153,33 @@
align-items: center;
gap: 12px;
- .qualityChange{
+ .cameraZoom {
+ position: absolute;
+ padding: 16px 0;
+ left: 1600px;
+ top: -59px;
+ width: 112px;
+ height: 490px;
+ background: rgba(64, 64, 64, 0.15);
+ border-radius: 20px 20px 20px 20px;
+
+ .el-slider {
+ height: 100%;
+ }
+ }
+
+ .qualityChange {
width: 146px;
height: 40px;
background: rgba(74, 72, 72, 0.67);
- border:none!important;
- display:flex;
- justify-content:center;
+ border: none !important;
+ display: flex;
+ justify-content: center;
box-shadow: 0 0.4rem 7.2rem 0 rgba(0, 0, 0, 0.25);
border-radius: 0.8rem 0.8rem 0.8rem 0.8rem;
- :deep(){
- .el-select__wrapper{
+
+ :deep() {
+ .el-select__wrapper {
width: 100px;
height: 100%;
background: transparent;
@@ -118,13 +187,13 @@
border: none;
box-shadow: none;
}
- .el-select__selected-item{
+
+ .el-select__selected-item {
font-family: Segoe UI, Segoe UI;
color: #ffffff;
font-size: 14px;
}
}
-
}
.singleCol {
@@ -137,6 +206,12 @@
cursor: pointer;
background: rgba(74, 72, 72, 0.67);
+ &.active {
+ background: rgba(255, 255, 255, 0.78);
+ color: #3d3b3b;
+ border: 1px solid rgba(255, 255, 255, 0.99);
+ }
+
&:hover {
background: rgba(255, 255, 255, 0.78);
color: #3d3b3b;
diff --git a/src/components/ScaleRule.vue b/src/components/ScaleRule.vue
new file mode 100644
index 0000000..bb86e81
--- /dev/null
+++ b/src/components/ScaleRule.vue
@@ -0,0 +1,368 @@
+<template>
+ <div class="zoom-bar">
+ <div class="partial-wayline-zoom-scale">
+ <div id="zoom-scale" class="zoom-scale" @click="handleZoneClick">
+ <!-- 当前刻度指示器 -->
+ <div
+ class="current-scale"
+ :class="{ 'scale-z-index': isDragging }"
+ :style="{ bottom: currentScale + '%' }"
+ ></div>
+ <div class="current-scale drag" :style="{ bottom: currentScale + '%' }" @mousedown="startDrag"></div>
+
+ <!-- 刻度点 -->
+ <div
+ v-for="(point, index) in scalePoints"
+ :key="index"
+ class="point-container"
+ :class="{ optical: point.optical }"
+ :style="{ bottom: point.position + '%' }"
+ >
+ <span class="point"></span>
+ <span class="label">{{ point.label }}</span>
+ </div>
+
+ <!-- 刻度区域 -->
+ <div class="scale-container" @click.stop="handleZoneClick">
+ <div
+ v-for="(area, index) in scaleAreasWithWarning"
+ :key="index"
+ class="scale-area"
+ :class="{ optical: area.optical, warning: area.warning }"
+ :style="{ height: area.height + '%' }"
+ >
+ <div class="scale" v-for="n in area.scales" :key="n"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+const valueData = defineModel()
+
+const currentScale = ref(0)
+const isDragging = ref(false)
+const startY = ref(0)
+const startScale = ref(0)
+const scalePoints = ref([
+ { position: 0, label: '2X', optical: false },
+ { position: 11.1111, label: '5X', optical: true },
+ { position: 44.4444, label: '10X', optical: true },
+ { position: 77.7778, label: '20X', optical: true },
+ { position: 100, label: '200X', optical: false },
+])
+const scaleAreas = ref([
+ { height: 11.1111, scales: 3, optical: false },
+ { height: 33.3333, scales: 5, optical: true },
+ { height: 33.3333, scales: 5, optical: true },
+ { height: 22.2222, scales: 9, optical: false },
+])
+
+const isInWarningZone = computed(() => currentScale.value < 11.1111 || currentScale.value > 77.7778)
+const scaleAreasWithWarning = computed(() => {
+ // 计算每个区域的起始和结束位置
+ let currentPosition = 0
+ // 计算当前刻度是否在首尾区域范围内
+ const isInDangerZone = isInWarningZone.value
+ return scaleAreas.value.map((area, index) => {
+ currentPosition = currentPosition + area.height
+ // 检查是否是第一个或最后一个区域
+ const isFirstArea = index === scaleAreas.value.length - 1
+ const isLastArea = index === 0
+ return {
+ ...area,
+ warning: (isFirstArea || isLastArea) && isInDangerZone,
+ }
+ })
+})
+
+const currentXValue = computed(() => {
+ // 根据当前百分比计算实际的X值
+ if (currentScale.value <= 11.1111) {
+ // 2X-5X 区间
+ return Number(2 + (currentScale.value / 11.1111) * 3).toFixed(1)
+ } else if (currentScale.value <= 44.4444) {
+ // 5X-10X 区间
+ return Number(5 + ((currentScale.value - 11.1111) / 33.3333) * 5).toFixed(1)
+ } else if (currentScale.value <= 77.7778) {
+ // 10X-20X 区间
+ return Number(10 + ((currentScale.value - 44.4444) / 33.3333) * 10).toFixed(1)
+ } else {
+ // 20X-200X 区间
+ return Number(20 + ((currentScale.value - 77.7778) / 22.2222) * 180).toFixed(1)
+ }
+})
+
+function startDrag(e) {
+ e.preventDefault()
+ isDragging.value = true
+ startY.value = e.clientY
+ startScale.value = currentScale.value
+
+ // 添加全局事件监听
+ document.addEventListener('mousemove', onDrag)
+ document.addEventListener('mouseup', stopDrag)
+}
+
+function onDrag(e) {
+ if (!isDragging.value) return
+
+ e.preventDefault()
+ // 计算移动距离相对于容器高度的百分比
+ const container = document.getElementById('zoom-scale')
+ const containerHeight = container.offsetHeight
+ const deltaY = e.clientY - startY.value
+ const deltaPercent = (deltaY / containerHeight) * 100
+
+ // 更新刻度位置,并限制在0-100之间
+ let newScale = startScale.value - deltaPercent
+ newScale = Math.max(0, Math.min(100, newScale))
+ currentScale.value = newScale
+}
+
+function stopDrag() {
+ isDragging.value = false
+
+ // 移除全局事件监听
+ document.removeEventListener('mousemove', onDrag)
+ document.removeEventListener('mouseup', stopDrag)
+}
+
+function handleZoneClick(e) {
+ // 获取容器信息
+ const container = document.getElementById('zoom-scale')
+ const rect = container.getBoundingClientRect()
+
+ // 计算点击位置相对于容器底部的百分比
+ const clickY = e.clientY - rect.top
+ const containerHeight = rect.height
+ const percentage = ((containerHeight - clickY) / containerHeight) * 100
+
+ // 限制在0-100之间
+ currentScale.value = Math.max(0, Math.min(100, percentage))
+}
+
+function calculateScaleFromValue(value) {
+ const numValue = Number(value)
+ if (numValue <= 5) {
+ // 2X-5X 区间
+ return ((numValue - 2) / 3) * 11.1111
+ } else if (numValue <= 10) {
+ // 5X-10X 区间
+ return 11.1111 + ((numValue - 5) / 5) * 33.3333
+ } else if (numValue <= 20) {
+ // 10X-20X 区间
+ return 44.4444 + ((numValue - 10) / 10) * 33.3333
+ } else {
+ // 20X-200X 区间
+ return 77.7778 + ((numValue - 20) / 180) * 22.2222
+ }
+}
+
+watch(valueData, newValue => {
+ if (!isDragging.value) {
+ currentScale.value = calculateScaleFromValue(newValue)
+ }
+})
+
+const emit = defineEmits(['input', 'warning-change'])
+
+watch(
+ () => currentXValue.value,
+ newValue => {
+ valueData.value = newValue
+ emit('input', newValue)
+ },
+ { immediate: true }
+)
+
+watch(
+ () => isInWarningZone.value,
+ newValue => {
+ emit('warning-change', newValue)
+ },
+ { immediate: true }
+)
+</script>
+
+<style lang="scss">
+.zoom-bar {
+ background: linear-gradient(-90deg, rgba(0, 0, 0, 0.5) 36%, transparent 99%);
+ height: 300px;
+ padding: 4px 28px 4px 30px;
+ pointer-events: auto;
+ position: absolute;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+}
+
+.partial-wayline-zoom-scale {
+ -webkit-user-select: none;
+ user-select: none;
+ flex-direction: column;
+ height: 100%;
+ display: flex;
+ position: relative;
+
+ .zoom-scale {
+ cursor: pointer;
+ flex-direction: column;
+ flex: 1;
+ margin: 8px;
+ display: flex;
+ position: relative;
+
+ .scale-container {
+ flex-direction: column-reverse;
+ width: 4px;
+ height: 100%;
+ margin-left: 1px;
+ display: flex;
+ position: relative;
+
+ .scale-area {
+ flex-direction: column-reverse;
+ display: flex;
+
+ &.optical {
+ background: rgba(216, 216, 216, 0.3);
+ }
+
+ &.warning {
+ background: rgba(255, 153, 0, 0.5);
+ }
+
+ .scale {
+ border-top: 1px solid rgba(255, 255, 255, 0.5);
+ flex: 1;
+ width: 4px;
+
+ &:last-child {
+ border-top: none;
+ }
+ }
+ }
+ }
+ }
+
+ .current-scale {
+ background-color: #fff;
+ border-radius: 50%;
+ justify-content: center;
+ align-items: center;
+ width: 16px;
+ height: 16px;
+ margin-bottom: -8px;
+ margin-left: -5px;
+ display: flex;
+ position: absolute;
+ z-index: 10;
+ pointer-events: none;
+
+ &.drag {
+ z-index: 20;
+ background-color: transparent;
+ cursor: grab;
+ width: 16px;
+ height: 16px;
+ position: absolute;
+ padding: 10px;
+ margin: -10px;
+ box-sizing: content-box;
+ pointer-events: auto;
+
+ &:active {
+ cursor: grabbing;
+ }
+ }
+
+ &.scale-z-index {
+ z-index: 25;
+ }
+ }
+
+ .point-container {
+ align-items: flex-end;
+ display: flex;
+ position: absolute;
+ pointer-events: none;
+
+ .point {
+ z-index: 5;
+ background-color: #fff;
+ border-radius: 50%;
+ width: 6px;
+ height: 6px;
+ margin-bottom: -3px;
+ position: relative;
+ }
+
+ .label {
+ color: #fff;
+ font-size: 10px;
+ line-height: 18px;
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ transform: scale(0.8);
+ }
+
+ &.optical .point:after {
+ content: '';
+ background: rgba(216, 216, 216, 0.5);
+ border-radius: 50%;
+ width: 12px;
+ height: 12px;
+ position: absolute;
+ transform: translate(-3px, -3px);
+ }
+
+ &.warning .point:after {
+ content: '';
+ background: #f90;
+ border-radius: 50%;
+ width: 6px;
+ height: 6px;
+ position: absolute;
+ }
+ }
+
+ .input-number-wrapper {
+ width: 70px;
+ height: 28px;
+ margin: 8px 2px;
+
+ input {
+ height: 28px;
+ }
+ }
+}
+
+/* 刻度值显示框样式 */
+.scale-value-box {
+ position: absolute;
+ top: 50%;
+ right: 90px;
+ transform: translateY(-50%);
+ background: rgba(0, 0, 0, 0.5);
+ padding: 8px 12px;
+ border-radius: 4px;
+ color: #fff;
+ font-size: 14px;
+ line-height: 20px;
+ min-width: 60px;
+ text-align: center;
+
+ &.warning {
+ color: #ff9900;
+ }
+}
+
+.scale-value {
+ font-family: monospace;
+ font-weight: 500;
+}
+</style>
diff --git a/src/hooks/controlDrone/useManualControl.js b/src/hooks/controlDrone/useManualControl.js
index a2e0b97..4dc12ff 100644
--- a/src/hooks/controlDrone/useManualControl.js
+++ b/src/hooks/controlDrone/useManualControl.js
@@ -1,7 +1,8 @@
import { DRC_METHOD } from '@/const/drc.js'
import { useMqtt } from '@/hooks/controlDrone/useMqtt'
import { ElMessage } from 'element-plus'
-import { postPayloadCommands } from '@/api/payload'
+import { postPayloadCommands, ptzControlApi } from '@/api/payload'
+import { throttle } from 'lodash'
let myInterval
@@ -39,6 +40,7 @@
let genPortOne = true //是一代机场
const mqttHooks = useMqtt(mqttState,deviceTopicInfo)
let seq = 0
+ let throttledPtz = null
function handlePublish(params) {
const body = {
@@ -72,63 +74,90 @@
seq = 0
switch (keyCode) {
case 'KeyQ':
- if (activeCodeKey === keyCode) return
+ if (activeCodeKey.value === keyCode) return
let objQ = { yaw: 1024-W_SPEED }
if (genPortOne) {objQ = {w: -W_SPEED}}
handlePublish(objQ)
activeCodeKey.value = keyCode
break
case 'KeyW':
- if (activeCodeKey === keyCode) return
+ if (activeCodeKey.value === keyCode) return
let objW = { pitch: 1024+SPEED }
if (genPortOne) {objW = {x: SPEED}}
handlePublish(objW)
activeCodeKey.value = keyCode
break
case 'KeyE':
- if (activeCodeKey === keyCode) return
+ if (activeCodeKey.value === keyCode) return
let objE = { yaw: 1024+W_SPEED }
if (genPortOne) {objE = {w: W_SPEED}}
handlePublish(objE)
activeCodeKey.value = keyCode
break
case 'KeyA':
- if (activeCodeKey === keyCode) return
+ if (activeCodeKey.value === keyCode) return
let objA = { roll: 1024-SPEED }
if (genPortOne) {objA = {y: -SPEED}}
handlePublish(objA)
activeCodeKey.value = keyCode
break
case 'KeyS':
- if (activeCodeKey === keyCode) return
+ if (activeCodeKey.value === keyCode) return
let objS = { pitch: 1024-SPEED }
if (genPortOne) {objS = {x: -SPEED}}
handlePublish(objS)
activeCodeKey.value = keyCode
break
case 'KeyD':
- if (activeCodeKey === keyCode) return
+ if (activeCodeKey.value === keyCode) return
let objD = { roll: 1024+SPEED }
if (genPortOne) {objD = {y: SPEED}}
handlePublish(objD)
activeCodeKey.value = keyCode
break
- case 'ArrowUp':
+ case 'KeyZ':
if (activeCodeKey.value === keyCode) return
- handlePublish({ h: HEIGHT })
+ let objZ = { throttle: 1024-HEIGHT }
+ if (genPortOne) {objZ = {h: -HEIGHT}}
+ handlePublish(objZ)
activeCodeKey.value = keyCode
+ break
+ // case 'ArrowDown':
+ case 'KeyC':
+ if (activeCodeKey.value === keyCode) return
+ let objC = { throttle: 1024+HEIGHT }
+ if (genPortOne) {objC = {h: HEIGHT}}
+ handlePublish(objC)
+ activeCodeKey.value = keyCode
+ break
+ case 'ArrowUp':
+ ptzThrottle[0]()
break
case 'ArrowDown':
- if (activeCodeKey.value === keyCode) return
- handlePublish({ h: -HEIGHT })
- activeCodeKey.value = keyCode
+ ptzThrottle[1]()
break
-
+ case 'ArrowLeft':
+ ptzThrottle[2]()
+ break
+ case 'ArrowRight':
+ ptzThrottle[3]()
+ break
default:
break
}
}
+ // 云台控制节流list
+ const ptzThrottle = ['up','down','left','right'].map(key => throttle(()=>ptzControl(key), 500))
+
+ // 云台上下左右
+ function ptzControl(key) {
+ const {dockSn} = paramsRef.value
+ ptzControlApi(key, { sn: dockSn }).then(res => {
+
+ })
+ }
+
const handlePayloadTurn = (params) => {
handleClearInterval()
if (!this.myInterval) {
--
Gitblit v1.9.3