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