| New file |
| | |
| | | <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> |