forked from drone/command-center-dashboard

shuishen
2025-04-16 115bd56702e2ce09b7a7c1e6bb5e93e2f6174cdf
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue
New file
@@ -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>