From d96659a5ebd6a96dc94f1138202794ee8a474261 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Mon, 14 Apr 2025 19:26:33 +0800
Subject: [PATCH] feat: 控制按钮

---
 src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue                  |  200 ++++++++++++++++++
 src/assets/images/rightmapidentification.png                                                      |    0 
 src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue            |    6 
 src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue |  392 +++++++++++++++++++++++++++++++++++
 src/hooks/useMapAggregation/useMapAggregation.js                                                  |    4 
 src/assets/images/mapidentification.png                                                           |    0 
 src/directive/hold.js                                                                             |   34 +++
 src/assets/images/leftmapidentification.png                                                       |    0 
 8 files changed, 632 insertions(+), 4 deletions(-)

diff --git a/src/assets/images/leftmapidentification.png b/src/assets/images/leftmapidentification.png
new file mode 100644
index 0000000..1bd0b84
--- /dev/null
+++ b/src/assets/images/leftmapidentification.png
Binary files differ
diff --git a/src/assets/images/mapidentification.png b/src/assets/images/mapidentification.png
new file mode 100644
index 0000000..0f3ca48
--- /dev/null
+++ b/src/assets/images/mapidentification.png
Binary files differ
diff --git a/src/assets/images/rightmapidentification.png b/src/assets/images/rightmapidentification.png
new file mode 100644
index 0000000..cf8dc77
--- /dev/null
+++ b/src/assets/images/rightmapidentification.png
Binary files differ
diff --git a/src/directive/hold.js b/src/directive/hold.js
new file mode 100644
index 0000000..a68dd14
--- /dev/null
+++ b/src/directive/hold.js
@@ -0,0 +1,34 @@
+const hold = {
+	mounted(el, binding) {
+		let timer = null;
+		el.startHold = () => {
+			if (!timer) {
+				timer = setInterval(() => {
+					binding.value();
+				}, 50);
+			}
+		};
+
+		el.stopHold = () => {
+			clearInterval(timer);
+			timer = null;
+		};
+
+		el.addEventListener("mousedown", el.startHold);
+		el.addEventListener("mouseup", el.stopHold);
+		el.addEventListener("mouseleave", el.stopHold);
+		el.addEventListener("touchstart", el.startHold); // 适配触摸屏
+		el.addEventListener("touchend", el.stopHold);
+	},
+
+	// 绑定元素的父组件卸载前调用
+	beforeUnmount(el, binding) {
+		el.removeEventListener("mousedown", el.startHold);
+		el.removeEventListener("mouseup", el.stopHold);
+		el.removeEventListener("mouseleave", el.stopHold);
+		el.removeEventListener("touchstart", el.startHold);
+		el.removeEventListener("touchend", el.stopHold);
+	},
+}
+
+export default hold
diff --git a/src/hooks/useMapAggregation/useMapAggregation.js b/src/hooks/useMapAggregation/useMapAggregation.js
index 0027285..246ef47 100644
--- a/src/hooks/useMapAggregation/useMapAggregation.js
+++ b/src/hooks/useMapAggregation/useMapAggregation.js
@@ -123,8 +123,8 @@
   }
 
   // 获取事件聚合
-  function getMapEventCount (areaCode) {
-    return getMapEvents({ areaCode }).then(res => {
+  function getMapEventCount (area_code) {
+    return getMapEvents({ area_code,date_enum:'CURRENT_WEEK' }).then(res => {
       const resData = res?.data?.data
       if (resData?.data) {
         eventList = resData?.data
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue
new file mode 100644
index 0000000..2c35740
--- /dev/null
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlComPass/ControlComPass.vue
@@ -0,0 +1,392 @@
+<template>
+  <div class="instrument-content">
+    <div class="left-img" :data-text="`${attitude_pitch}°`">
+      <div class="scaleImg">
+        <p class="scale" :style="{ top: 45 + ScaleTop + 'px' }"></p>
+        <img src="@/assets/images/rightmapidentification.png" />
+      </div>
+    </div>
+    <div class="instrument-center">
+      <div class="compass-box" :data-text="`${prevRotate?.toFixed(2)}°`">
+        <div v-for="(item, index) in str" :key="index" class="scale"
+          :style="{ '--rotate': 30 * index - prevRotate + 'deg' }">
+          <span class="text">{{ item }}</span>
+        </div>
+      </div>
+      <div class="center-show">
+        <img src="@/assets/images/mapidentification.png" />
+      </div>
+      <div class="rotat-btn"></div>
+    </div>
+    <div class="right-img" :data-text="`${height}m`">
+      <div class="ident-arrow">
+        <img src="@/assets/images/leftmapidentification.png" />
+        <div class="arrow-box" :style="{ bottom: realHeight }">
+          <div class="arrow"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import _ from 'lodash';
+export default {
+  data () {
+    return {
+      str: ['W', 30, 33, 'N', 3, 6, 'E', 12, 15, 'S', 21, 24],
+      // 俯仰角度数
+      attitude_pitch: 0,
+      // 真空高度
+      height: 0,
+      // 旋转方向角度
+      prevRotate: 0,
+      dockHeight: 0,
+    }
+  },
+  watch: {
+    roamPoint: {
+      handler (val) {
+        // this.prevRotate = val.arrowHeading
+        // this.attitude_pitch = val.roll
+        // this.height = _.round(val.altitude,1)
+      },
+    },
+  },
+  computed: {
+    // roamPoint: vuexStateSimplify('pointsWayLine', 'roamPoint'),
+    ScaleTop () {
+      return (-this.attitude_pitch * 30) / 90
+    },
+    realHeight () {
+			return 0
+      // 无人机高度
+      const maxHeight = 240
+      // 真空高度
+      const vacuumHeight = 120
+      // 机场高度
+      const dockHeightConfig = {
+        'e3dea0f5-37f2-4d79-ae58-490af3228069': 14.7,
+        '4a574db8-4ad3-48f7-9f16-3edbcd8056e1': 54,
+        // 'f47ac10b-58cc-4372-a567-0e02b2c3d479': 81,
+      }
+      const workspaceId =
+        this.$store.state.drone.selectedWorkSpaceId ||
+        window.localStorage.getItem('bs_workspace_id')
+      const dockHeight = dockHeightConfig?.[workspaceId] || 1
+
+      let proportion = 0
+      // 处于真空高度和最大高度之间
+      if (this.height < maxHeight && this.height >= vacuumHeight) {
+        const height = this.height - vacuumHeight
+        const proport = height / maxHeight
+        proportion = Math.round(proport * 50 + 50)
+      }
+      // 小于等于真空高度计算
+      if (this.height < vacuumHeight && this.height >= dockHeight) {
+        const height = this.height - dockHeight
+        const copyVacuumHeight = vacuumHeight - dockHeight
+        const proport = height / copyVacuumHeight
+        proportion = Math.round(proport * 25 + 25)
+      }
+      // 小于机场高度计算
+      if (this.height < dockHeight && this.height >= 0) {
+        const proport = this.height / dockHeight
+        proportion = Math.round(proport * 25)
+      }
+      // 大于最大高度计算
+      if (this.height >= maxHeight) {
+        proportion = 95
+      }
+      return proportion + '%'
+      // const proportion = Math.ceil((this.height / maxHeight) * 100) || 50;
+    },
+  },
+  methods: {
+    getAngle (currentLngLat, targetLngLat) {
+      const { longitude: lng_a, latitude: lat_a } = currentLngLat
+      const { longitude: lng_b, latitude: lat_b } = targetLngLat
+      var a = ((90 - lat_b) * Math.PI) / 180
+      var b = ((90 - lat_a) * Math.PI) / 180
+      var AOC_BOC = ((lng_b - lng_a) * Math.PI) / 180
+      var cosc =
+        Math.cos(a) * Math.cos(b) +
+        Math.sin(a) * Math.sin(b) * Math.cos(AOC_BOC)
+      var sinc = Math.sqrt(1 - cosc * cosc)
+      var sinA = (Math.sin(a) * Math.sin(AOC_BOC)) / sinc
+      var A = (Math.asin(sinA) * 180) / Math.PI
+      var res = 0
+      if (lng_b > lng_a && lat_b > lat_a) res = A
+      else if (lng_b > lng_a && lat_b < lat_a) res = 180 - A
+      else if (lng_b < lng_a && lat_b < lat_a) res = 180 - A
+      else if (lng_b < lng_a && lat_b > lat_a) res = 360 + A
+      else if (lng_b > lng_a && lat_b == lat_a) res = 90
+      else if (lng_b < lng_a && lat_b == lat_a) res = 270
+      else if (lng_b == lng_a && lat_b > lat_a) res = 0
+      else if (lng_b == lng_a && lat_b < lat_a) res = 180
+      return res
+    },
+  },
+}
+</script>
+<style lang="scss" scoped>
+.instrument-content {
+  height: 245px;
+  width: 280px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  transform: translateY(20px);
+
+  .left-img,
+  .right-img {
+    position: relative;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    width: 40px;
+
+    img {
+      height: 100px;
+    }
+  }
+
+  .left-img {
+    .text {
+      position: absolute;
+      left: 0px;
+      top: 50px;
+      width: 60px;
+      text-align: center;
+    }
+
+    .scaleImg {
+      position: relative;
+      width: 100%;
+      height: 100px;
+
+      img {
+        margin-left: 25px;
+      }
+
+      .scale {
+        width: 10px;
+        height: 10px;
+        background-color: #1fa3f6;
+        position: absolute;
+        margin-left: 10px;
+
+        &::before {
+          content: '';
+          position: absolute;
+          width: 0;
+          height: 0;
+          top: 0px;
+          left: 10px;
+          border-top: solid 5px transparent;
+          border-left: solid 5px #1fa3f6;
+          border-bottom: solid 5px transparent;
+        }
+      }
+    }
+
+    &::before {
+      content: attr(data-text);
+      position: absolute;
+      right: 0px;
+      top: 50px;
+      font-size: 14px;
+      font-weight: bolder;
+      font-family: none;
+    }
+
+    &::after {
+      content: '俯仰角度';
+      position: absolute;
+      right: 0px;
+      text-align: right;
+      bottom: 30px;
+      font-size: 14px;
+      font-weight: bolder;
+      font-family: none;
+    }
+  }
+
+  .right-img {
+    &::before {
+      content: attr(data-text);
+      position: absolute;
+      left: 0px;
+      top: 50px;
+      font-size: 14px;
+      font-weight: bolder;
+      font-family: none;
+    }
+
+    &::after {
+      content: '真空高度';
+      position: absolute;
+      left: 0px;
+      bottom: 30px;
+      font-size: 14px;
+      font-weight: bolder;
+      font-family: none;
+    }
+
+    .ident-arrow {
+      position: relative;
+
+      .arrow-box {
+        position: absolute;
+        bottom: 0;
+        left: 20px;
+
+        .arrow {
+          position: relative;
+          width: 10px;
+          height: 10px;
+          background-color: #1fa3f6;
+
+          &::before {
+            content: '';
+            position: absolute;
+            left: -9px;
+            top: 0;
+
+            border: 5px solid transparent {
+              right: solid 5px #1fa3f6;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .instrument-center {
+    position: relative;
+
+    .compass-box {
+      width: 180px;
+      height: 180px;
+      border-radius: 50%;
+      position: relative;
+      border: 30px solid rgba($color: #323931, $alpha: 0.5);
+      box-shadow: 0 2px 12px 0 #158aff;
+      user-select: none;
+      transform: none !important;
+
+      .scale {
+        width: 135%;
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        font-weight: bold;
+        color: #c1c3c4;
+        text-align: left;
+        transform: translate(-50%, -50%) rotate(var(--rotate));
+
+        &:nth-child(3n - 2) {
+          color: #fff;
+          font-weight: bolder;
+          font-size: 20px;
+        }
+      }
+
+      .scale {
+        .text {
+          display: inline-block;
+          // rotate: -90deg;
+          transform: rotate(-90deg);
+          -ms-transform: rotate(-90deg);
+          -moz-transform: rotate(-90deg);
+          -webkit-transform: rotate(-90deg);
+          -o-transform: rotate(-90deg);
+        }
+      }
+
+      &::before {
+        content: '';
+        position: absolute;
+        top: -45px;
+        left: 50%;
+        transform: translateX(-50%);
+        width: 7px;
+        height: 15px;
+        background: #00ee8b;
+        margin: 0 auto;
+        box-shadow: 0 0 4px rgba(0, 0, 0, 0.5), -1px -1px 0 rgba(0, 0, 0, 0.5),
+          1px -1px 0 rgba(0, 0, 0, 0.5), -1px 1px 0 rgba(0, 0, 0, 0.5),
+          1px 1px 0 rgba(0, 0, 0, 0.5);
+      }
+
+      &::after {
+        content: attr(data-text);
+        position: absolute;
+        top: -60px;
+        font-size: 16px;
+        line-height: 16px;
+        font-weight: 600;
+        color: #00ee8b;
+        left: 50%;
+        transform: translateX(-50%);
+      }
+    }
+
+    .rotat-btn {
+      width: 16px;
+      height: 16px;
+      background-color: rgba($color: #1fa3f6, $alpha: 1);
+      position: absolute;
+      top: 10px;
+      left: 50%;
+      transform: translateX(-50%);
+
+      &::before {
+        content: '';
+        display: block;
+        width: 0;
+        height: 0;
+        border-left: 8px solid transparent;
+        border-right: 8px solid transparent;
+        border-bottom: 8px solid #1fa3f6;
+        position: absolute;
+        bottom: 100%;
+      }
+    }
+
+    .center-show {
+      width: 30px;
+      height: 40px;
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+
+      img {
+        width: 100%;
+        height: 100%;
+        transform: rotate(var(--rotate));
+        transition: all 0.5s linear;
+      }
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      width: 40px;
+      top: 50%;
+      left: 0;
+      z-index: 99;
+    }
+
+    &::before {
+      content: '';
+      position: absolute;
+      width: 40px;
+      top: 50%;
+      right: 0;
+      z-index: 99;
+    }
+  }
+}
+</style>
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue
new file mode 100644
index 0000000..a5abf27
--- /dev/null
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue
@@ -0,0 +1,200 @@
+<template>
+	<div class="pointControl">
+		<div class="direction">
+			<div class="blackBg directionUp">
+				<div v-for="item in upperRowButton" :key="item.text">
+					<el-icon :class="item.icon"></el-icon>
+					<div
+						:class="{ hotkeyBtn: true, activeKey: currentKey === item.text }"
+						@click="() => mousedown(item.text)"
+						v-hold="() => mousedown(item.text)"
+					>
+						{{ item.text }}
+					</div>
+				</div>
+			</div>
+			<div class="blackBg directionDown">
+				<div v-for="item in lowerRowButton" :key="item.text">
+					<el-icon :class="item.icon"></el-icon>
+					<div
+						:class="{ hotkeyBtn: true, activeKey: currentKey === item.text }"
+						@click="() => mousedown(item.text)"
+						v-hold="() => mousedown(item.text)"
+					>
+						{{ item.text }}
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<ControlComPass />
+
+		<div class="height-direction">
+			<div class="blackBg height-direction-btn">
+				<el-icon class="el-icon-top" />
+				<div :class="{ hotkeyBtn: true, activeKey: currentKey === 'C' }" v-hold="() => mousedown('C')">C</div>
+			</div>
+			<div class="blackBg height-direction-btn">
+				<div :class="{ hotkeyBtn: true, activeKey: currentKey === 'Z' }" v-hold="() => mousedown('Z')">Z</div>
+				<el-icon class="el-icon-bottom" />
+			</div>
+		</div>
+	</div>
+</template>
+<script setup>
+import vHold from '@/directive/hold'
+import ControlComPass from './ControlComPass/ControlComPass.vue'
+
+const lowerRowButton = [
+	{ icon: 'el-icon-arrow-left', text: 'A' },
+	{ icon: 'el-icon-arrow-down', text: 'S' },
+	{ icon: 'el-icon-arrow-right', text: 'D' },
+]
+const upperRowButton = [
+	{ icon: 'el-icon-refresh-left', text: 'Q' },
+	{ icon: 'el-icon-arrow-up', text: 'W' },
+	{ icon: 'el-icon-refresh-right', text: 'E' },
+]
+let currentKey = ref('')
+let keyReset = null
+let visible = false
+
+function mousedown(key) {
+	if (key === 'Q') {
+		console.log(6666666666)
+	}
+}
+
+function handleKeydown(e) {
+	currentKey.value = e.key.toUpperCase()
+	mousedown(e.key.toUpperCase())
+}
+
+function handleKeyup() {
+	currentKey.value = null
+}
+
+onMounted(() => {
+	window.addEventListener('keydown', handleKeydown)
+	window.addEventListener('keyup', handleKeyup)
+})
+onBeforeUnmount(() => {
+	window.removeEventListener('keydown', handleKeydown)
+	window.removeEventListener('keyup', handleKeyup)
+})
+</script>
+
+<style scoped lang="scss">
+.f-c {
+	display: flex;
+	align-items: center;
+}
+
+//变量
+$FSColor: #00ee8b;
+
+.hotkeyBtn {
+	background: #3c3c3c;
+	border-radius: 2px;
+	width: 24px;
+	height: 24px;
+	font-size: 14px;
+	line-height: 24px;
+	text-align: center;
+	color: #fff;
+	cursor: pointer;
+	-webkit-user-select: none;
+	user-select: none;
+
+	&:hover {
+		background: $FSColor;
+	}
+}
+
+.pointControl {
+	display: flex;
+	justify-content: center;
+	align-items: flex-end;
+	color: white;
+	gap: 0 10px;
+	pointer-events: all;
+	width: 100%;
+	height: 200px;
+
+	.activeKey {
+		background: $FSColor;
+	}
+
+	.blackBg {
+		background: rgba(0, 0, 0, 0.65);
+		padding: 5px 5px;
+		border-radius: 5px;
+	}
+
+	.direction {
+		display: flex;
+		flex-direction: column;
+		width: 100px;
+
+		.speedBox {
+			color: $FSColor;
+			font-size: 16px;
+			font-weight: 600;
+			margin: 5px 0;
+			gap: 0 5px;
+		}
+
+		.editBox {
+			display: flex;
+			justify-content: end;
+			gap: 0 5px;
+			margin-bottom: 5px;
+
+			.current-point {
+				font-size: 20px;
+				display: flex;
+				align-items: center;
+				color: $FSColor;
+			}
+
+			.editPointBtn {
+				&:hover {
+					cursor: pointer;
+					background: $FSColor;
+				}
+			}
+		}
+
+		.directionUp,
+		.directionDown {
+			display: flex;
+			justify-content: space-around;
+			text-align: center;
+			height: 55px;
+
+			> div {
+				display: flex;
+				flex-direction: column;
+				justify-content: space-between;
+			}
+		}
+	}
+
+	.height-direction {
+		width: 100px;
+		display: flex;
+		flex-direction: column;
+		align-items: end;
+		gap: 5px 0;
+
+		.height-direction-btn {
+			width: 35px;
+			height: 55px;
+			text-align: center;
+			display: flex;
+			flex-direction: column;
+			justify-content: space-between;
+		}
+	}
+}
+</style>
diff --git a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
index fba05d6..b4e363e 100644
--- a/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
+++ b/src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -16,6 +16,7 @@
 			<!-- 展示地图 -->
 			<RealTimeMap />
 		</div>
+		<ControlPanel/>
 	</el-dialog>
 </template>
 
@@ -29,6 +30,7 @@
 import { getWebsocketUrl } from '@/websocket/util/config'
 import { useConnectWebSocket } from '@/utils/websocket/connect-websocket'
 import { EBizCode } from '@/utils/staticData/enums'
+import ControlPanel from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue'
 
 const isShow = defineModel('show')
 const props = defineProps({
@@ -50,7 +52,7 @@
 // 机巢直播
 const getDeviceLiveUrl = async () => {
 	if (machineNestUrl.value) return machineNestUrl.value
-	const res = await liveStart(taskDetails.value.device_sns[0])
+	const res = await liveStart(taskDetails.value.device_sns[0],'')
 	machineNestUrl.value = res.data.data.rtcs_url
 	return machineNestUrl.value
 }
@@ -58,7 +60,7 @@
 // 无人机直播
 const getDockLiveUrl = async dockSn => {
 	if (dockLiveUrl.value) return dockLiveUrl.value
-	const res = await liveStart(dockSn)
+	const res = await liveStart(dockSn,'')
 	dockLiveUrl.value = res.data.data.rtcs_url
 	return dockLiveUrl.value
 }

--
Gitblit v1.9.3