From 115bd56702e2ce09b7a7c1e6bb5e93e2f6174cdf Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Wed, 16 Apr 2025 20:50:13 +0800
Subject: [PATCH] Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard

---
 src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue |  604 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 604 insertions(+), 0 deletions(-)

diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue
new file mode 100644
index 0000000..d11d3d6
--- /dev/null
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue
@@ -0,0 +1,604 @@
+<template>
+	<div class="pointControl">
+		<BaseControl />
+
+		<div class="direction">
+			<div class="boxTitle">
+				飞
+				<br />
+				行
+				<br />
+				控
+				<br />
+				制
+				<br />
+				器
+			</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" @click="speed = speed + 1">
+					<Plus />
+				</el-icon>
+				<div>
+					{{ speed }}
+					<br />
+					m/s
+				</div>
+				<el-icon class="btnIcon" @click="speed = speed - 1">
+					<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>
+
+		<div class="compass">
+			<ControlComPass />
+		</div>
+
+		<div class="ptzControlBox">
+			<div class="boxTitle">
+				云
+				<br />
+				台
+				<br />
+				控
+				<br />
+				制
+			</div>
+			<div class="ptzControlBtnBox">
+				<div class="ptzControlBtn b-r">
+					<div v-for="(item, index) in list5" :style="item.style" class="ptzControlItem"></div>
+
+					<div
+						class="ptzControlItemIcon"
+						v-for="(item, index) in list5"
+						:style="{ transform: `rotate(${index * 90}deg)` }"
+					>
+						<el-icon>
+							<CaretRight />
+						</el-icon>
+					</div>
+					<div class="circles1 b-r">
+						<div class="circles2 b-r">
+							<div class="circles3 b-r">
+								<div class="circles4 b-r"></div>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="videoBox" v-if="valueTime !== '00:00:00'">
+					<div class="videoName">录像</div>
+					<div class="videoPoint"></div>
+					<div class="videoTime">{{ valueTime }}</div>
+				</div>
+			</div>
+
+			<div class="divider"></div>
+			<div v-for="arr in list4" class="info">
+				<div v-for="item in arr" class="infoItem">
+					<div class="infoName">{{ item.name }}</div>
+					<div class="infoValue">{{ item.value }}</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+<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 { ElMessage } from 'element-plus'
+import { useStore } from 'vuex'
+import { UranusMqtt } from '@/mqtt'
+import {
+	ArrowDown,
+	ArrowLeft,
+	ArrowRight,
+	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 '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/BaseControl.vue'
+import EventBus from '@/event-bus'
+import dayjs from 'dayjs'
+
+const deviceOsdInfo = inject('deviceOsdInfo')
+const taskDetails = inject('taskDetails')
+const store = useStore()
+const workspace_id = computed(() => taskDetails?.value?.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 },
+]
+
+const speed = ref(5)
+
+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%' } },
+]
+
+const list4 = [
+	[
+		{ name: '焦距倍数', value: '0' },
+		{ name: '俯仰角度', value: '0.0°' },
+		{ name: '横向角度', value: '0.0°' },
+	],
+	[
+		{ name: '储存', value: '64.5G' },
+		{ name: '方向', value: '正北' },
+		{ name: '方向', value: '正北' },
+	],
+]
+
+let mqttState = null
+const client_id = ref('')
+const valueTime = ref('00:00:00')
+let timer = null
+let totalSeconds = 0
+
+const deviceTopicInfo = ref({
+	sn: deviceOsdInfo.value?.data?.sn,
+	pubTopic: '',
+	subTopic: '',
+})
+const flightController = ref(false)
+// 控制对象
+let manualControl = {}
+const sn = computed(() => deviceOsdInfo?.value?.data?.sn)
+const isAutoControl = inject('isAutoControl')
+
+const timeStart = () => {
+	stop() // 避免重复启动
+	timer = setInterval(() => {
+		totalSeconds++
+		const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
+		const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0')
+		const secs = String(totalSeconds % 60).padStart(2, '0')
+		valueTime.value = `${hours}:${minutes}:${secs}`
+	}, 1000)
+}
+
+const timeStop = () => {
+	if (timer) {
+		clearInterval(timer)
+		timer = null
+		totalSeconds = 0
+		valueTime.value = '00:00:00'
+	}
+}
+
+// 按下操作
+function onMouseDown(type) {
+	manualControl?.handleKeyup(type, { sn: sn.value, speed: speed.value })
+}
+
+// 弹起操作
+function onMouseUp() {
+	manualControl?.resetControlState()
+}
+
+// 取消手动控制
+function cancelControl() {
+	exitController({ client_id: client_id.value, dock_sn: dock_sn.value })
+		.then(res => {
+			flightController.value = false
+			deviceTopicInfo.value.subTopic = ''
+			deviceTopicInfo.value.pubTopic = ''
+			ElMessage.success('退出飞行控制成功')
+		})
+		.catch(e => {})
+}
+
+// 手动控制
+function control() {
+	if (!client_id.value) return ElMessage.error('无人机不在空中,不能进入指挥飞行模式。')
+	if (!dock_sn.value) return ElMessage.error('系统错误,未获取到dock_sn')
+	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) {
+			deviceTopicInfo.value.subTopic = data.sub[0]
+		}
+		if (data.pub && data.pub?.length > 0) {
+			deviceTopicInfo.value.pubTopic = data.pub[0]
+		}
+		ElMessage.success('控制成功')
+		isAutoControl.value = false
+	})
+}
+
+// 返航
+function onBackDock() {
+	returnHome(dock_sn.value).then(res => {
+		ElMessage.success('返航操作成功')
+	})
+}
+
+// 取消返航
+function cancelBackDock() {
+	returnHomeCancel({ client_id: this.clientId, dock_sn: this.sn }).then(res => {
+		ElMessage.success('取消返航成功')
+	})
+}
+
+// 创建mqtt连接
+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 = ''
+	}
+}
+
+// 返航或取消返航
+const returnOrCancelReturn = () => {
+	if (deviceOsdInfo.value?.data?.host?.mode_code === 9) {
+		cancelBackDock()
+	} else {
+		onBackDock()
+	}
+}
+
+watch(
+	() => workspace_id.value,
+	async () => {
+		if (workspace_id.value) {
+			await createConnect()
+			// 使用控制
+			manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController)
+		}
+	}
+)
+
+onMounted(async () => {
+	EventBus.on('controlPanel-control', control)
+	EventBus.on('controlPanel-cancelControl', cancelControl)
+	EventBus.on('controlPanel-returnOrCancelReturn', returnOrCancelReturn)
+	EventBus.on('controlPanel-onMouseDown', onMouseDown)
+	EventBus.on('controlPanel-timeStart', timeStart)
+	EventBus.on('controlPanel-timeStop', timeStop)
+})
+
+onBeforeUnmount(() => {
+	EventBus.off('controlPanel-control', control)
+	EventBus.off('controlPanel-cancelControl', cancelControl)
+	EventBus.off('controlPanel-returnOrCancelReturn', returnOrCancelReturn)
+	EventBus.off('controlPanel-onMouseDown', onMouseDown)
+	EventBus.off('controlPanel-timeStart', timeStart)
+	EventBus.off('controlPanel-timeStop', timeStop)
+	destroyConnect()
+})
+</script>
+
+<style scoped lang="scss">
+.f-c {
+	display: flex;
+	align-items: center;
+}
+
+.boxTitle {
+	font-family: Segoe UI, Segoe UI;
+	font-weight: 400;
+	font-size: 14px;
+	color: #d2e8fa;
+}
+
+.b-r {
+	border-radius: 50%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.pointControl {
+	position: absolute;
+	bottom: 0;
+	right: 0;
+	width: 1540px;
+	height: 217px;
+	background: linear-gradient(196deg, rgba(23, 23, 23, 0.11) 0%, rgba(6, 6, 6, 0.11) 100%);
+	backdrop-filter: blur(5px);
+	border-radius: 40px 0px 40px 40px;
+	display: flex;
+	justify-content: center;
+	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-evenly;
+
+		.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;
+			}
+		}
+	}
+
+	.ptzControlBox {
+		width: 406px;
+		height: 188px;
+		background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
+		border-radius: 40px 40px 40px 40px;
+		display: flex;
+		align-items: center;
+		justify-content: space-evenly;
+
+		.ptzControlBtnBox {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+
+			.ptzControlBtn {
+				position: relative;
+				overflow: hidden;
+				width: 154px;
+				height: 154px;
+				background: linear-gradient(180deg, #818181 0%, #222222 100%);
+				box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
+
+				.ptzControlItemIcon {
+					pointer-events: none;
+					width: 75px;
+					display: flex;
+					justify-content: right;
+					font-size: 18px;
+					position: absolute;
+				}
+
+				.ptzControlItem {
+					position: absolute;
+					width: 100%;
+					height: 100%;
+					transform: rotate(45deg);
+
+					&:hover {
+						cursor: pointer;
+						box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.3);
+					}
+				}
+
+				.circles1 {
+					width: 130px;
+					height: 130px;
+					background: linear-gradient(360deg, #282828 23%, #3a3a3a 70%, #3c3a3a 95%);
+					box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
+
+					.circles2 {
+						width: 79px;
+						height: 79px;
+						background: linear-gradient(180deg, #484848 0%, #3f3f3f 100%);
+						box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25), inset -1px -2px 4px 0px rgba(255, 255, 255, 0.25);
+						border: 1px solid rgba(0, 0, 0, 0.2);
+
+						.circles3 {
+							width: 42px;
+							height: 42px;
+							background: rgba(0, 0, 0, 0.31);
+							z-index: 1;
+
+							.circles4 {
+								width: 23px;
+								height: 23px;
+								background: #ffffff;
+								box-shadow: 2px 4px 6px 0px rgba(35, 37, 39, 0.26);
+							}
+						}
+					}
+				}
+			}
+
+			.videoBox {
+				display: flex;
+				align-items: center;
+				font-family: Segoe UI, Segoe UI;
+				margin-top: 5px;
+
+				.videoName {
+					font-weight: 400;
+					font-size: 12px;
+					color: rgba(210, 232, 250, 0.57);
+				}
+
+				.videoPoint {
+					width: 3px;
+					height: 3px;
+					background: #12ff7f;
+					border-radius: 50%;
+					margin: 0 5px;
+				}
+
+				.videoTime {
+					font-weight: 400;
+					font-size: 16px;
+					color: #ffffff;
+					line-height: 15px;
+				}
+			}
+		}
+
+		.divider {
+			position: absolute;
+			transform: translateX(90px);
+			width: 0;
+			height: 137px;
+			border: 1px solid rgba(255, 255, 255, 0.07);
+		}
+
+		.info {
+			display: flex;
+			flex-direction: column;
+			gap: 7px 0;
+			width: 70px;
+
+			.infoName {
+				height: 25px;
+				font-family: Segoe UI, Segoe UI, serif;
+				font-weight: 400;
+				font-size: 11px;
+				color: rgba(210, 232, 250, 0.57);
+				line-height: 15px;
+			}
+
+			.infoValue {
+				height: 25px;
+				font-family: Segoe UI, Segoe UI, serif;
+				font-weight: 400;
+				font-size: 16px;
+				color: #ffffff;
+				line-height: 15px;
+			}
+		}
+	}
+
+	.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>

--
Gitblit v1.9.3