From 2abd8e08f2148331e14bf1332e05f5d6302f82de Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Tue, 15 Apr 2025 20:27:56 +0800
Subject: [PATCH] feat: 当前任务详情35%
---
/dev/null | 54 ------
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue | 299 ++++++++++++++++++++++++++++++++----
src/hooks/controlDrone/useManualControl.js | 4
src/hooks/controlDrone/useMqtt.js | 19 -
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue | 9 +
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue | 4
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue | 86 ++++++++++
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue | 4
8 files changed, 368 insertions(+), 111 deletions(-)
diff --git a/src/hooks/controlDrone/useConnectDrone.js b/src/hooks/controlDrone/useConnectDrone.js
deleted file mode 100644
index 78faec6..0000000
--- a/src/hooks/controlDrone/useConnectDrone.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useStore } from 'vuex'
-import { postDrc } from '@/api/drc'
-import { UranusMqtt } from '@/mqtt'
-
-export function useConnectDrone() {
- const store = useStore()
-
-
- const deviceOsdInfo = inject('deviceOsdInfo')
- const taskDetails = inject('taskDetails')
-
-
- const mqttState = ref(null)
- const client_id = ref(null)
-
- onMounted(async ()=>{
- if (mqttState.value) return
-
- const workspace_id = taskDetails.value.way_lines[0].workspace_id
- const result = await postDrc({},workspace_id)
- if (result?.code === 0) {
- const { address, client_id, username, password, expire_time } = result.data
- // @TODO: 校验 expire_time
- mqttState.value = new UranusMqtt(address, {
- clientId: client_id,
- username,
- password,
- })
- mqttState.value?.initMqtt()
- mqttState.value?.on('onStatus', statusOptions => {
- // @TODO: 异常case
- })
-
- store.commit('SET_MQTT_STATE', mqttState.value)
- store.commit('SET_CLIENT_ID', client_id)
- }
- // @TODO: 认证失败case
- })
-
-
- onBeforeUnmount(() => {
- if (mqttState?.value) {
- mqttState.value?.destroyed()
- mqttState.value = null
- store.commit('SET_MQTT_STATE', null)
- store.commit('SET_CLIENT_ID', '')
- }
- })
-
- return {
- mqttState,
- client_id
- }
-}
diff --git a/src/hooks/controlDrone/useManualControl.js b/src/hooks/controlDrone/useManualControl.js
index 4f5ff74..91417c9 100644
--- a/src/hooks/controlDrone/useManualControl.js
+++ b/src/hooks/controlDrone/useManualControl.js
@@ -15,10 +15,10 @@
ARROW_DOWN: 'ArrowDown',
}
-export function useManualControl(deviceTopicInfo, isCurrentFlightController) {
+export function useManualControl(mqttState,deviceTopicInfo, isCurrentFlightController) {
const activeCodeKey = ref(null)
- const mqttHooks = useMqtt(deviceTopicInfo)
+ const mqttHooks = useMqtt(mqttState,deviceTopicInfo)
let seq = 0
function handlePublish(params) {
diff --git a/src/hooks/controlDrone/useMqtt.js b/src/hooks/controlDrone/useMqtt.js
index 2ee7e3d..caaff48 100644
--- a/src/hooks/controlDrone/useMqtt.js
+++ b/src/hooks/controlDrone/useMqtt.js
@@ -6,23 +6,18 @@
-export function useMqtt (deviceTopicInfo) {
+export function useMqtt (mqttState,deviceTopicInfo) {
let cacheSubscribeArr= []
- const store = useStore()
-
- const mqttState = computed(() => {
- return store.state.common.mqttState
- })
function publishMqtt (topic, body, ots) {
- mqttState.value?.publishMqtt(topic, JSON.stringify(body), ots)
+ mqttState?.publishMqtt(topic, JSON.stringify(body), ots)
}
function subscribeMqtt (topic, handleMessageMqtt) {
- mqttState.value?.subscribeMqtt(topic)
+ mqttState?.subscribeMqtt(topic)
const handler = handleMessageMqtt || onMessageMqtt
- mqttState.value?.on('onMessageMqtt', handler)
+ mqttState?.on('onMessageMqtt', handler)
cacheSubscribeArr.push({
topic,
callback: handler,
@@ -52,8 +47,8 @@
function unsubscribeDrc () {
// 销毁已订阅事件
cacheSubscribeArr.forEach(item => {
- mqttState.value?.off('onMessageMqtt', item.callback)
- mqttState.value?.unsubscribeMqtt(item.topic)
+ mqttState?.off('onMessageMqtt', item.callback)
+ mqttState?.unsubscribeMqtt(item.topic)
})
cacheSubscribeArr = []
}
@@ -87,7 +82,7 @@
},
}
const pingInterval = setInterval(() => {
- if (!mqttState.value) return
+ if (!mqttState) return
heartBeatSeq.value += 1
body.data.ts = new Date().getTime()
body.data.seq = heartBeatSeq.value
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue
index 2c35740..3ab40ef 100644
--- a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue
@@ -130,8 +130,8 @@
</script>
<style lang="scss" scoped>
.instrument-content {
- height: 245px;
- width: 280px;
+ height: 100%;
+ width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue
index a7f0956..5ef46bc 100644
--- a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue
@@ -1,32 +1,124 @@
<template>
<div class="pointControl">
+ <div class="manualControl"></div>
+
<div class="direction">
- <div class="blackBg directionUp">
- <el-button type="primary" @click="control">控制</el-button>
- <el-button type="primary" @click="cancelControl">取消控制</el-button>
- <el-button type="primary" ghost @mousedown="onMouseDown(KeyCode.KEY_Q)" @mouseup="onMouseUp">q</el-button>
+ <div class="boxTitle">
+ 飞
+ <br />
+ 行
+ <br />
+ 控
+ <br />
+ 制
+ <br />
+ 器
</div>
- <div class="blackBg directionDown"></div>
+ <div class="btnGroup">
+ <div class="btnGroupT">
+ <div class="btnItem" v-for="item in list1">
+ <el-icon class="btnIcon">
+ <component :is="item.icon" />
+ </el-icon>
+ <div class="btn" @mousedown="onMouseDown(item.key)" @mouseup="onMouseUp">{{ item.text }}</div>
+ </div>
+ </div>
+ <div class="btnGroupB">
+ <div class="btnItem" v-for="item in list2">
+ <div class="btn" @mousedown="onMouseDown(item.key)" @mouseup="onMouseUp">{{ item.text }}</div>
+ <el-icon class="btnIcon">
+ <component :is="item.icon" />
+ </el-icon>
+ </div>
+ </div>
+ </div>
+
+ <div class="speed">
+ <el-icon class="btnIcon">
+ <Plus />
+ </el-icon>
+ <div>5<br>m/s</div>
+ <el-icon class="btnIcon">
+ <Minus />
+ </el-icon>
+ </div>
+
+
+ <div class="upAndDown">
+ <div class="btnGroupT">
+ <div class="btnItem">
+ <el-icon class="btnIcon">
+ <Top />
+ </el-icon>
+ <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_UP)" @mouseup="onMouseUp">C</div>
+ </div>
+ </div>
+ <div class="btnGroupT">
+ <div class="btnItem">
+ <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_DOWN)" @mouseup="onMouseUp">Z</div>
+ <el-icon class="btnIcon">
+ <Bottom />
+ </el-icon>
+ </div>
+ </div>
+ </div>
+
</div>
- <ControlComPass />
+ <div class="compass">
+ <ControlComPass />
+ </div>
+
+ <div class="ptzControl">
+ <div>
+ 云
+ <br />
+ 台
+ <br />
+ 控
+ <br />
+ 制
+ </div>
+ <div></div>
+ </div>
</div>
</template>
<script setup>
import ControlComPass from './ControlComPass/ControlComPass.vue'
-import {
- KeyCode,
- useManualControl,
-} from '@/hooks/controlDrone/useManualControl'
-import { useMqtt } from '@/hooks/controlDrone/useMqtt'
-import { useConnectDrone } from '@/hooks/controlDrone/useConnectDrone'
-import { droneController, exitController, postDrcExit } from '@/api/drc'
+import { KeyCode, useManualControl } from '@/hooks/controlDrone/useManualControl'
+import { droneController, exitController, postDrc, postDrcExit } from '@/api/drc'
import { ElMessage } from 'element-plus'
import { useStore } from 'vuex'
+import { UranusMqtt } from '@/mqtt'
+import {
+ ArrowDown,
+ ArrowLeft,
+ ArrowRight,
+ ArrowUp,
+ Bottom, Minus,
+ Plus,
+ RefreshLeft,
+ RefreshRight,
+} from '@element-plus/icons-vue'
const deviceOsdInfo = inject('deviceOsdInfo')
const taskDetails = inject('taskDetails')
+const store = useStore()
+const workspace_id = computed(() => taskDetails.value.way_lines[0].workspace_id)
+const dock_sn = computed(() => taskDetails.value.device_sns[0])
+const list1 = [
+ { key: KeyCode.KEY_Q, text: 'Q', icon: RefreshLeft },
+ { key: KeyCode.KEY_W, text: 'W', icon: ArrowUp },
+ { key: KeyCode.KEY_E, text: 'E', icon: RefreshRight },
+]
+const list2 = [
+ { key: KeyCode.KEY_A, text: 'A', icon: ArrowLeft },
+ { key: KeyCode.KEY_S, text: 'S', icon: ArrowDown },
+ { key: KeyCode.KEY_D, text: 'D', icon: ArrowRight },
+]
+let mqttState = null
+const client_id = ref('')
const deviceTopicInfo = ref({
sn: deviceOsdInfo.value?.data?.sn,
@@ -36,29 +128,21 @@
const flightController = ref(false)
console.log('控制面板')
-// 连接无人机mqtt 成功获得有效控制
-useConnectDrone()
-
-// 订阅消息
-useMqtt(deviceTopicInfo.value)
-
-// 使用手动控制
-const { handleKeyup, handleEmergencyStop, resetControlState } = useManualControl(
- deviceTopicInfo.value,
- flightController
-)
+// 控制对象
+let manualControl = {}
function onMouseDown(type) {
- console.log('anxia')
- handleKeyup(type)
+ manualControl?.handleKeyup(type)
}
-const store = useStore()
-const clientId = computed(() => store.state.common.clientId)
-const dock_sn = computed(() => taskDetails.value.device_sns[0])
+function onMouseUp() {
+ console.log('弹起')
+ manualControl?.resetControlState()
+}
+// 取消手动控制
function cancelControl() {
- exitController({ client_id: clientId.value, dock_sn:dock_sn.value })
+ exitController({ client_id: client_id.value, dock_sn: dock_sn.value })
.then(res => {
flightController.value = false
deviceTopicInfo.value.subTopic = ''
@@ -68,11 +152,11 @@
.catch(e => {})
}
-// 控制
+// 手动控制
function control() {
- if (!clientId.value) return ElMessage.error('无人机不在空中,不能进入指挥飞行模式。')
+ if (!client_id.value) return ElMessage.error('无人机不在空中,不能进入指挥飞行模式。')
if (!dock_sn.value) return ElMessage.error('系统错误,未获取到dock_sn')
- droneController({ client_id: clientId.value, dock_sn:dock_sn.value }).then(res => {
+ droneController({ client_id: client_id.value, dock_sn: dock_sn.value }).then(res => {
flightController.value = true
const { data } = res.data
if (data.sub && data.sub?.length > 0) {
@@ -85,10 +169,35 @@
})
}
-function onMouseUp() {
- console.log('弹起')
- resetControlState()
+// 创建连接
+const createConnect = async () => {
+ const result = await postDrc({}, workspace_id.value)
+ if (result?.code === 0) {
+ const { address, client_id: clientId, username, password, expire_time } = result.data
+ mqttState = new UranusMqtt(address, { clientId, username, password })
+ mqttState?.initMqtt()
+ client_id.value = clientId
+ }
}
+
+// 销毁连接
+const destroyConnect = () => {
+ if (mqttState) {
+ mqttState?.destroyed()
+ mqttState = null
+ client_id.value = ''
+ }
+}
+
+onMounted(async () => {
+ await createConnect()
+ // 使用控制
+ manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController)
+})
+
+onBeforeUnmount(() => {
+ destroyConnect()
+})
</script>
<style scoped lang="scss">
@@ -97,19 +206,133 @@
align-items: center;
}
+
.pointControl {
position: absolute;
bottom: 0;
right: 0;
width: 1540px;
height: 217px;
- background: rgba(255, 255, 255, 0.3);
+ background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
+ backdrop-filter: blur(5px);
border-radius: 40px 0px 40px 40px;
display: flex;
justify-content: center;
- align-items: flex-end;
+ align-items: center;
color: white;
gap: 0 10px;
pointer-events: all;
+
+ .direction {
+ width: 476px;
+ height: 188px;
+ background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
+ border-radius: 40px 40px 40px 40px;
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ .boxTitle{
+ font-family: Segoe UI, Segoe UI;
+ font-weight: 400;
+ font-size: 14px;
+ color: #D2E8FA;
+ }
+
+ .btnGroup {
+ display: flex;
+ flex-direction: column;
+ gap: 10px 0;
+ .btnGroupT,
+ .btnGroupB {
+ width: 238px;
+ height: 73px;
+ }
+ }
+ .upAndDown{
+ display: flex;
+ flex-direction: column;
+ gap: 10px 0;
+ .btnGroupT,
+ .btnGroupB {
+ width: 58px;
+ height: 73px;
+ }
+ }
+ .speed{
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ width: 58px;
+ height: 155px;
+ background: #37393F;
+ box-shadow: 2px 4px 6px 0px rgba(0,13,26,0.42);
+ border-radius: 8px 8px 8px 8px;
+ text-align: center;
+ padding: 5px 0;
+ .btnIcon{
+ font-size: 20px;
+ }
+ }
+
+ }
+
+ .manualControl {
+ width: 188px;
+ height: 188px;
+ background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
+ border-radius: 40px 40px 40px 40px;
+ }
+
+ .ptzControl {
+ width: 406px;
+ height: 188px;
+ background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
+ border-radius: 40px 40px 40px 40px;
+ }
+
+ .compass {
+ width: 356px;
+ height: 188px;
+ background: rgba(157, 173, 189, 0.11);
+ background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
+ border-radius: 40px 40px 40px 40px;
+ }
+
+
+ .btnGroupT,
+ .btnGroupB {
+ background: #37393f;
+ box-shadow: 2px 4px 6px 0px rgba(0, 13, 26, 0.42);
+ border-radius: 8px 8px 8px 8px;
+ display: flex;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+ gap: 0 45px;
+
+ .btnItem {
+ .btnIcon {
+ font-size: 20px;
+
+ &:first-child {
+ margin-bottom: 5px;
+ }
+ }
+
+ .btn {
+ width: 35px;
+ height: 35px;
+ background: #222324;
+ line-height: 35px;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:first-child {
+ margin-bottom: 5px;
+ }
+ }
+ }
+ }
}
</style>
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
index a722ff8..2b3276d 100644
--- a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -10,14 +10,16 @@
>
<div class="content-container" v-if="isShow">
<TaskDetailsHead/>
+ <TaskDetailsLeft/>
<!-- 视频直播 -->
<div class="video-container">
<LiveVideo :videoUrl="currentLiveUrl" />
</div>
<!-- 展示地图 -->
<RealTimeMap class="realTimeMap" />
+ <ControlPanel />
+<!-- <ControlPanel v-if="deviceOsdInfo?.data?.sn" />-->
</div>
- <ControlPanel v-if="deviceOsdInfo?.data?.sn" />
</el-dialog>
</template>
@@ -34,6 +36,7 @@
import ControlPanel from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue'
import { KeyCode } from '@/hooks/controlDrone/useManualControl'
import TaskDetailsHead from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue'
+import TaskDetailsLeft from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue'
const isShow = defineModel('show')
const props = defineProps({
@@ -135,6 +138,7 @@
justify-content: space-between;
.el-dialog {
+ border-radius: 40px;
position: relative;
margin-top: 38px;
width: 1782px;
@@ -167,6 +171,8 @@
.content-container {
height: 100%;
width: 100%;
+ border-radius: 4rem;
+ overflow: hidden;
.video-container {
width: 100%;
@@ -183,6 +189,7 @@
height: 217px;
border-radius: 0px 20px 0px 40px;
border: 1px solid #62a1ff;
+ overflow: hidden;
}
}
</style>
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue
index c326e05..91ed227 100644
--- a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue
@@ -48,8 +48,8 @@
z-index: 5;
width: 100%;
height: 68px;
- background: rgba(255, 255, 255, 0.1); /* 半透明背景 */
- backdrop-filter: blur(3px);
+ background: rgb(0,0,0,.4); /* 半透明背景 */
+ backdrop-filter: blur(5px);
padding: 0 31px;
display: flex;
align-items: center;
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue
new file mode 100644
index 0000000..181fd79
--- /dev/null
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -0,0 +1,86 @@
+<template>
+ <div class="taskDetailsLeft">
+ <div class="title">负载控制</div>
+ <div class="singleCol">广角相机</div>
+ <div class="singleCol">变焦相机</div>
+ <div class="singleCol">红外相机</div>
+ <div class="multiCol">
+ <div>云台回中</div>
+ <div>云台朝下</div>
+ </div>
+ <div class="singleCol">画质</div>
+ <div class="multiCol">
+ <div>拍照</div>
+ <div>录像</div>
+ </div>
+ <div class="multiCol">
+ <div>喊话</div>
+ <div>广播</div>
+ </div>
+ </div>
+</template>
+
+<script setup></script>
+
+<style scoped lang="scss">
+.taskDetailsLeft {
+ z-index: 2;
+ position: absolute;
+ left: 18px;
+ top: 50%;
+ transform: translateY(-60%);
+ width: 178px;
+ height: 409px;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(5px);
+ border-radius: 20px 20px 20px 20px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ font-family: Segoe UI, Segoe UI;
+ font-weight: 400;
+ font-size: 14px;
+ color: #FFFFFF;
+ line-height: 18px;
+ text-align: center;
+ align-items: center;
+ gap: 12px;
+
+
+ .singleCol{
+ width: 146px;
+ height: 40px;
+ box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
+ border-radius: 8px 8px 8px 8px;
+
+ line-height: 40px;
+ cursor: pointer;
+ background: rgba(74,72,72,0.67);
+
+ &:hover{
+ background: rgba(255,255,255,0.78);
+ color: #3D3B3B;
+ border: 1px solid rgba(255,255,255,0.99);
+ }
+ }
+ .multiCol{
+ display: flex;
+ gap: 12px;
+ >div{
+ cursor: pointer;
+ width: 67px;
+ height: 40px;
+ background: rgba(74,72,72,0.67);
+ box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
+ border-radius: 8px 8px 8px 8px;
+ line-height: 40px;
+
+ &:hover{
+ background: rgba(255,255,255,0.78);
+ color: #3D3B3B;
+ border: 1px solid rgba(255,255,255,0.99);
+ }
+ }
+ }
+}
+</style>
--
Gitblit v1.9.3