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