| | |
| | | <template> |
| | | <div class="pointControl"> |
| | | <div class="manualControl"></div> |
| | | |
| | | <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 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="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 class="speed"> |
| | | <el-icon class="btnIcon"> |
| | | <Plus /> |
| | | </el-icon> |
| | | <div>5<br>m/s</div> |
| | | <el-icon class="btnIcon"> |
| | | <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> |
| | | |
| | | <ControlComPass /> |
| | | <div class="compass"> |
| | | <ControlComPass /> |
| | | </div> |
| | | |
| | | <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 class="ptzControl"> |
| | | <div> |
| | | 云 |
| | | <br /> |
| | | 台 |
| | | <br /> |
| | | 控 |
| | | <br /> |
| | | 制 |
| | | </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> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import vHold from '@/directive/hold' |
| | | import ControlComPass from './ControlComPass/ControlComPass.vue' |
| | | import { KeyCode, useManualControl } from '@/hooks/controlDrone/useManualControl' |
| | | import { droneController, exitController, postDrc, postDrcExit } from '@/api/drc' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useStore } from 'vuex' |
| | | import { UranusMqtt } from '@/mqtt' |
| | | import { |
| | | ArrowDown, |
| | | ArrowLeft, |
| | | ArrowRight, |
| | | ArrowUp, |
| | | Bottom, Minus, |
| | | Plus, |
| | | RefreshLeft, |
| | | RefreshRight, |
| | | } from '@element-plus/icons-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 deviceOsdInfo = inject('deviceOsdInfo') |
| | | const taskDetails = inject('taskDetails') |
| | | const store = useStore() |
| | | const workspace_id = computed(() => taskDetails.value.way_lines[0].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 upperRowButton = [ |
| | | { icon: 'el-icon-refresh-left', text: 'Q' }, |
| | | { icon: 'el-icon-arrow-up', text: 'W' }, |
| | | { icon: 'el-icon-refresh-right', text: 'E' }, |
| | | 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 }, |
| | | ] |
| | | let currentKey = ref('') |
| | | let keyReset = null |
| | | let visible = false |
| | | |
| | | function mousedown(key) { |
| | | if (key === 'Q') { |
| | | console.log(6666666666) |
| | | let mqttState = null |
| | | const client_id = ref('') |
| | | |
| | | const deviceTopicInfo = ref({ |
| | | sn: deviceOsdInfo.value?.data?.sn, |
| | | pubTopic: '', |
| | | subTopic: '', |
| | | }) |
| | | const flightController = ref(false) |
| | | console.log('控制面板') |
| | | |
| | | // 控制对象 |
| | | let manualControl = {} |
| | | |
| | | function onMouseDown(type) { |
| | | manualControl?.handleKeyup(type) |
| | | } |
| | | |
| | | function onMouseUp() { |
| | | console.log('弹起') |
| | | 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('控制成功') |
| | | }) |
| | | } |
| | | |
| | | // 创建连接 |
| | | 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 |
| | | } |
| | | } |
| | | |
| | | function handleKeydown(e) { |
| | | currentKey.value = e.key.toUpperCase() |
| | | mousedown(e.key.toUpperCase()) |
| | | // 销毁连接 |
| | | const destroyConnect = () => { |
| | | if (mqttState) { |
| | | mqttState?.destroyed() |
| | | mqttState = null |
| | | client_id.value = '' |
| | | } |
| | | } |
| | | |
| | | function handleKeyup() { |
| | | currentKey.value = null |
| | | } |
| | | |
| | | onMounted(() => { |
| | | window.addEventListener('keydown', handleKeydown) |
| | | window.addEventListener('keyup', handleKeyup) |
| | | onMounted(async () => { |
| | | await createConnect() |
| | | // 使用控制 |
| | | manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener('keydown', handleKeydown) |
| | | window.removeEventListener('keyup', handleKeyup) |
| | | destroyConnect() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | 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 { |
| | | position: absolute; |
| | | bottom: 0; |
| | | right: 0; |
| | | width: 1540px; |
| | | height: 217px; |
| | | background: rgb(0, 0, 0, 0.4); /* 半透明背景 */ |
| | | backdrop-filter: blur(5px); |
| | | border-radius: 40px 0px 40px 40px; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: flex-end; |
| | | align-items: center; |
| | | 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 { |
| | | width: 476px; |
| | | height: 188px; |
| | | background: rgb(0, 0, 0, 0.4); /* 半透明背景 */ |
| | | border-radius: 40px 40px 40px 40px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 100px; |
| | | |
| | | .speedBox { |
| | | color: $FSColor; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | margin: 5px 0; |
| | | gap: 0 5px; |
| | | align-items: center; |
| | | justify-content: space-around; |
| | | .boxTitle{ |
| | | font-family: Segoe UI, Segoe UI; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #D2E8FA; |
| | | } |
| | | |
| | | .editBox { |
| | | .btnGroup { |
| | | 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; |
| | | } |
| | | flex-direction: column; |
| | | gap: 10px 0; |
| | | .btnGroupT, |
| | | .btnGroupB { |
| | | width: 238px; |
| | | height: 73px; |
| | | } |
| | | } |
| | | |
| | | .directionUp, |
| | | .directionDown { |
| | | .upAndDown{ |
| | | display: flex; |
| | | justify-content: space-around; |
| | | text-align: center; |
| | | height: 55px; |
| | | |
| | | > div { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | flex-direction: column; |
| | | gap: 10px 0; |
| | | .btnGroupT, |
| | | .btnGroupB { |
| | | width: 58px; |
| | | height: 73px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | .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; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | .manualControl { |
| | | width: 188px; |
| | | height: 188px; |
| | | background: rgb(0, 0, 0, 0.4); /* 半透明背景 */ |
| | | border-radius: 40px 40px 40px 40px; |
| | | } |
| | | |
| | | .ptzControl { |
| | | width: 406px; |
| | | height: 188px; |
| | | background: rgb(0, 0, 0, 0.4); /* 半透明背景 */ |
| | | border-radius: 40px 40px 40px 40px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |