Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
13 files modified
1 files added
| | |
| | | return resp.data |
| | | } |
| | | |
| | | // /control/api/v1/workspaces/82f6008b-2068-448c-9094-881e212f31c3/drc/connect |
| | | |
| | | // 进入飞行控制 (建立drc连接&获取云控控制权) |
| | | export async function postDrcEnter (body,workspaceId) { |
| | | const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body) |
| | |
| | | import request from '@/axios' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | const API_PREFIX = '/drone-device-core/control/api/v1' |
| | | |
| | | // 获取负载控制权 |
| | | export async function postPayloadAuth(sn, body) { |
| | | return await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body) |
| | | } |
| | | |
| | | export const PayloadCommandsEnum = { |
| | | CameraModeSwitch: 'camera_mode_switch', |
| | |
| | | GimbalReset: 'gimbal_reset', |
| | | CameraAim: 'camera_aim', |
| | | }; |
| | | |
| | | const API_PREFIX = '/drone-device-core/control/api/v1' |
| | | |
| | | // 获取负载控制权 |
| | | export async function postPayloadAuth(sn, body) { |
| | | return await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body) |
| | | } |
| | | |
| | | // 发送负载名称 |
| | | export async function postPayloadCommands(sn, body, config = {}) { |
| | | return await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body, config) |
| | | } |
| | | |
| | | // 获得有效载荷控制 |
| | | export async function getPayloadControlApi(params) { |
| | | return await request({ |
| | | url:`${API_PREFIX}/devices/payload-control/requests`, |
| | | method:'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // 云台控制api |
| | | export async function ptzControlApi(key,params) { |
| | | return await request({ |
| | | url:`${API_PREFIX}/devices/payload-control/payload/${key}`, |
| | | method:'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // 拍照和录像 |
| | | export async function callPhotoAndVideoCmd(sn, type) { |
| | |
| | | } |
| | | |
| | | |
| | | // 相机参数调整 |
| | | export async function cameraParamsChangeApi(data) { |
| | | return await request({ |
| | | url:`/drone-device-core/payload-control/payload/zoom/level`, |
| | | method:'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | |
| | | |
| | | // 切换直播镜头 |
| | | export const switchLivestream = (data) => { |
| | | const url = `${API_PREFIX}/live/streams/switch`; |
| | | return request({ |
| | | url, |
| | | url:`${API_PREFIX}/live/streams/switch`, |
| | | method: 'post', |
| | | data, |
| | | }); |
| | |
| | | end-placeholder="结束日期" |
| | | @change="change" |
| | | disabled |
| | | :clearable="false" |
| | | /> |
| | | <div class="time-card"> |
| | | <div |
| | | class="card-item" |
| | | :class="item === checked ? 'active' : ''" |
| | | v-for="(item, index) in timeList" |
| | | v-for="(item, index) in timeList" :key="index" |
| | | @click="timeClick(item,index)" |
| | | > |
| | | {{ timeListStr[index] }} |
| | |
| | | position: relative; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | :deep(.el-date-editor .el-range__close-icon--hidden){ |
| | | width: 0 !important; |
| | | |
| | | } |
| | | :deep(.el-date-editor .el-range-input){ |
| | | width: 48% !important; |
| | | font-size: 13px !important; |
| | | } |
| | | :deep(.el-date-editor) { |
| | | box-shadow: none; |
| | | width: 0; |
| | | flex: 1; |
| | | margin-right: 4px; |
| | | // margin-right: 4px; |
| | | |
| | | background: rgba(0, 15, 34, 0.5); |
| | | border-radius: 0px 0px 0px 0px; |
| | |
| | | </div> |
| | | <div |
| | | v-for="item in list3" |
| | | :key="item.name" |
| | | :style="item.style" |
| | | class="operateBtn" |
| | | :title="item.name" |
| | |
| | | <el-icon class="btnIcon"> |
| | | <Top /> |
| | | </el-icon> |
| | | <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_UP)" @mouseup="onMouseUp">C</div> |
| | | <div class="btn" @mousedown="onMouseDown(KeyCode.KEY_C)" @mouseup="onMouseUp">C</div> |
| | | </div> |
| | | </div> |
| | | <div class="btnGroupT"> |
| | | <div class="btnItem"> |
| | | <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_DOWN)" @mouseup="onMouseUp">Z</div> |
| | | <div class="btn" @mousedown="onMouseDown(KeyCode.KEY_Z)" @mouseup="onMouseUp">Z</div> |
| | | <el-icon class="btnIcon"> |
| | | <Bottom /> |
| | | </el-icon> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 指南针--> |
| | | <div class="compass"> |
| | | <ControlComPass /> |
| | | </div> |
| | |
| | | </div> |
| | | <div class="ptzControlBtnBox"> |
| | | <div class="ptzControlBtn b-r"> |
| | | <div v-for="(item, index) in list5" :style="item.style" class="ptzControlItem"></div> |
| | | <div |
| | | v-for="(item, index) in list5" |
| | | :style="item.style" |
| | | class="ptzControlItem" |
| | | @mousedown="onMouseDown(item.key)" |
| | | ></div> |
| | | |
| | | <div |
| | | class="ptzControlItemIcon" |
| | |
| | | <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 { droneController, exitController, postDrc, returnHome, returnHomeCancel } from '@/api/drc' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useStore } from 'vuex' |
| | | import { UranusMqtt } from '@/mqtt' |
| | |
| | | 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 '@/components/CurrentTaskDetails/ControlPanel/BaseControl.vue' |
| | | import EventBus from '@/event-bus' |
| | | import dayjs from 'dayjs' |
| | | import { getPayloadControlApi, ptzControlApi } from '@/api/payload' |
| | | |
| | | const deviceOsdInfo = inject('deviceOsdInfo') |
| | | const taskDetails = inject('taskDetails') |
| | | const dockSn = inject('dockSn') |
| | | const droneSn = inject('droneSn') |
| | | |
| | | |
| | | const store = useStore() |
| | | let mqttState = null |
| | |
| | | const valueTime = ref('00:00:00') |
| | | let timer = null |
| | | let totalSeconds = 0 |
| | | |
| | | |
| | | const workspace_id = computed(() => taskDetails?.value?.workspace_id) |
| | | const list1 = [ |
| | |
| | | ] |
| | | |
| | | const speed = ref(5) |
| | | provide('speed',speed) |
| | | provide('speed', speed) |
| | | |
| | | 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%' } }, |
| | | { name: '上', key: KeyCode.ARROW_UP, operate: 'up', style: { top: '-70%' } }, |
| | | { name: '右', key: KeyCode.ARROW_RIGHT, operate: 'right', style: { left: '70%' } }, |
| | | { name: '下', key: KeyCode.ARROW_DOWN, operate: 'down', style: { top: '70%' } }, |
| | | { name: '左', key: KeyCode.ARROW_LEFT, operate: 'left', style: { left: '-70%' } }, |
| | | ] |
| | | |
| | | const list4 = [ |
| | |
| | | { name: '方向', value: '正北' }, |
| | | ], |
| | | ] |
| | | |
| | | |
| | | |
| | | const deviceTopicInfo = ref({ |
| | | pubTopic: '', |
| | |
| | | .catch(e => {}) |
| | | } |
| | | |
| | | // 获得有效载荷控制 |
| | | function getPayloadControl() { |
| | | getPayloadControlApi({ sn: dockSn.value }).then(res => { |
| | | ElMessage.success('成功获得有效载荷控制') |
| | | }) |
| | | } |
| | | |
| | | // 手动控制 |
| | | function control() { |
| | | if (!client_id.value) return ElMessage.error('无人机不在空中,不能进入指挥飞行模式。') |
| | |
| | | deviceTopicInfo.value.pubTopic = data.pub[0] |
| | | } |
| | | ElMessage.success('控制成功') |
| | | getPayloadControl() |
| | | isAutoControl.value = false |
| | | }) |
| | | } |
| | |
| | | } |
| | | |
| | | // useManualControl里面用的参数 |
| | | const paramsRef = computed(()=>({ |
| | | droneSn:droneSn.value, |
| | | speed:speed.value, |
| | | const paramsRef = computed(() => ({ |
| | | droneSn: droneSn.value, |
| | | dockSn: dockSn.value, |
| | | speed: speed.value, |
| | | })) |
| | | |
| | | watch( |
| | |
| | | if (workspace_id.value) { |
| | | await createConnect() |
| | | // 使用控制 |
| | | manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController,paramsRef) |
| | | manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController, paramsRef) |
| | | } |
| | | } |
| | | ) |
| | |
| | | EventBus.on('controlPanel-onMouseDown', onMouseDown) |
| | | EventBus.on('controlPanel-timeStart', timeStart) |
| | | EventBus.on('controlPanel-timeStop', timeStop) |
| | | EventBus.on('controlPanel-getPayloadControl', getPayloadControl) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | |
| | | EventBus.off('controlPanel-onMouseDown', onMouseDown) |
| | | EventBus.off('controlPanel-timeStart', timeStart) |
| | | EventBus.off('controlPanel-timeStop', timeStop) |
| | | EventBus.off('controlPanel-getPayloadControl', getPayloadControl) |
| | | destroyConnect() |
| | | }) |
| | | </script> |
| | |
| | | <TaskDetailsRight v-if="isAutoControl" /> |
| | | <template v-else> |
| | | <TaskDetailsHead /> |
| | | <TaskDetailsLeft /> |
| | | </template> |
| | | <TaskDetailsLeft /> |
| | | |
| | | <!-- 控制面板,里面有方法需要立即执行,不可用v-if --> |
| | | <ControlPanel v-show="!isAutoControl" /> |
| | | <img alt="" :src="amplifyImg" class="amplify" @click="amplify" /> |
| | | <img alt="" :src="amplifyImg" class="amplify" @click="isMaxMap = !isMaxMap" /> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import EventBus from '@/event-bus' |
| | | |
| | | |
| | | const isAutoControl = ref(true) |
| | | const lineQuality = ref(1) //1流畅,2标清 |
| | | provide('isAutoControl', isAutoControl) |
| | | provide('lineQuality', lineQuality) |
| | | |
| | | const isShow = defineModel('show') |
| | | const props = defineProps(['id']) |
| | | const currentLiveUrl = ref('') |
| | | let taskDetails = ref({}) |
| | | const deviceOsdInfo = ref({}) |
| | | provide('taskDetails', taskDetails) |
| | |
| | | provide('dockSn', dockSn) |
| | | provide('droneSn', droneSn) |
| | | |
| | | const isShow = defineModel('show') |
| | | const props = defineProps(['id']) |
| | | const currentLiveUrl = ref('') |
| | | const isTakeOff = ref(false) |
| | | const isMaxMap = ref(false) |
| | | const amplify = () => { |
| | | isMaxMap.value = !isMaxMap.value |
| | | } |
| | | let droneWebSocket //WS实例 |
| | | |
| | | // 机巢直播 |
| | | const getDeviceLiveUrl = async () => { |
| | |
| | | const res = await liveStart(droneSn.value, lineQuality.value) |
| | | currentLiveUrl.value = res.data.data.rtcs_url |
| | | } |
| | | |
| | | const isTakeOff = ref(false) |
| | | |
| | | // 设置当前直播地址 |
| | | const setCurrentLiveUrl = async () => { |
| | |
| | | } |
| | | } |
| | | |
| | | let droneWebSocket |
| | | // 创建ws连接 |
| | | const createWsConnect = () => { |
| | | const workspaceId = taskDetails.value.workspace_id |
| | |
| | | <template> |
| | | <div class="taskDetailsLeft"> |
| | | <div class="title">负载控制</div> |
| | | <div class="singleCol" @click="cameraType('wide', '广角')">广角相机</div> |
| | | <div class="singleCol" @click="cameraType('zoom', '变焦')">变焦相机</div> |
| | | <div class="singleCol" @click="cameraType('ir', '红外')">红外相机</div> |
| | | <div |
| | | class="singleCol" |
| | | v-for="item in list1" |
| | | @click="cameraType(item)" |
| | | :key="item.key" |
| | | :class="{ active: item.key === cameraParams.camera_type }" |
| | | > |
| | | {{ item.name }}相机 |
| | | </div> |
| | | <div class="multiCol"> |
| | | <div>云台回中</div> |
| | | <div>云台朝下</div> |
| | | <div @click="faceToCenter">云台回中</div> |
| | | <div @click="faceToDown">云台朝下</div> |
| | | </div> |
| | | <el-select class="qualityChange" v-model="lineQuality" @change="qualityChange"> |
| | | <el-option v-for="item in qualityList" :key="item.id" :label="item.name" :value="item.id" /> |
| | |
| | | <div>喊话</div> |
| | | <div>广播</div> |
| | | </div> |
| | | |
| | | <div class="cameraZoom"> |
| | | <el-slider |
| | | v-model="cameraParams.zoom_factor" |
| | | vertical |
| | | :max="cameraParams.camera_type === 'ir' ? 20 : 50" |
| | | @input="sliderChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import EventBus from '@/event-bus' |
| | | import { callPhotoAndVideoCmd, switchLivestream } from '@/api/payload' |
| | | import { callPhotoAndVideoCmd, cameraParamsChangeApi } from '@/api/payload' |
| | | import { ElMessage } from 'element-plus' |
| | | import { throttle } from 'lodash' |
| | | |
| | | const isRecording = ref(false) |
| | | |
| | | const list1 = ref([ |
| | | { name: '广角', key: 'wide' }, |
| | | { name: '变焦', key: 'zoom' }, |
| | | { name: '红外', key: 'ir' }, |
| | | ]) |
| | | const cameraParams = ref({ |
| | | zoom_factor: 0, |
| | | camera_type: 'wide', |
| | | }) |
| | | const droneSn = inject('droneSn') |
| | | const dockSn = inject('dockSn') |
| | | const lineQuality = inject('lineQuality') |
| | | const qualityList = [ |
| | | { name: '自适应', id: 0 }, |
| | |
| | | { name: '超清', id: 4 }, |
| | | ] |
| | | |
| | | // todo |
| | | function cameraType(video_type, name) { |
| | | switchLivestream({ |
| | | // video_id: this.videoId, |
| | | video_type, |
| | | }).then(res => { |
| | | ElMessage.success(`切换为${name}相机成功`) |
| | | function faceToCenter() {} |
| | | |
| | | function faceToDown() {} |
| | | |
| | | // 倍率调整 |
| | | function sliderChange(val) { |
| | | if (cameraParams.value.camera_type !== 'ir') { |
| | | cameraParams.value.camera_type = val > 1 ? 'zoom' : 'wide' |
| | | } |
| | | cameraParamsChangeThrottle() |
| | | } |
| | | |
| | | // 修改相机参数-节流版 |
| | | const cameraParamsChangeThrottle = throttle(cameraParamsChange, 500) |
| | | |
| | | // 修改相机参数 |
| | | function cameraParamsChange() { |
| | | cameraParamsChangeApi({ |
| | | sn: dockSn.value, |
| | | ...cameraParams.value, |
| | | }) |
| | | } |
| | | |
| | | function cameraType(row) { |
| | | cameraParams.value.camera_type = row.key |
| | | if (row.key === 'wide') { |
| | | cameraParams.value.zoom_factor = 0 |
| | | } |
| | | if (row.key === 'zoom') { |
| | | cameraParams.value.zoom_factor = 5 |
| | | } |
| | | if (row.key === 'ir') { |
| | | cameraParams.value.zoom_factor = 0 |
| | | } |
| | | cameraParamsChange() |
| | | } |
| | | |
| | | // 画质切换 |
| | |
| | | align-items: center; |
| | | gap: 12px; |
| | | |
| | | .qualityChange{ |
| | | .cameraZoom { |
| | | position: absolute; |
| | | padding: 16px 0; |
| | | left: 1600px; |
| | | top: -59px; |
| | | width: 112px; |
| | | height: 490px; |
| | | background: rgba(64, 64, 64, 0.15); |
| | | border-radius: 20px 20px 20px 20px; |
| | | |
| | | .el-slider { |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .qualityChange { |
| | | width: 146px; |
| | | height: 40px; |
| | | background: rgba(74, 72, 72, 0.67); |
| | | border:none!important; |
| | | display:flex; |
| | | justify-content:center; |
| | | border: none !important; |
| | | display: flex; |
| | | justify-content: center; |
| | | box-shadow: 0 0.4rem 7.2rem 0 rgba(0, 0, 0, 0.25); |
| | | border-radius: 0.8rem 0.8rem 0.8rem 0.8rem; |
| | | :deep(){ |
| | | .el-select__wrapper{ |
| | | |
| | | :deep() { |
| | | .el-select__wrapper { |
| | | width: 100px; |
| | | height: 100%; |
| | | background: transparent; |
| | |
| | | border: none; |
| | | box-shadow: none; |
| | | } |
| | | .el-select__selected-item{ |
| | | |
| | | .el-select__selected-item { |
| | | font-family: Segoe UI, Segoe UI; |
| | | color: #ffffff; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | .singleCol { |
| | |
| | | cursor: pointer; |
| | | background: rgba(74, 72, 72, 0.67); |
| | | |
| | | &.active { |
| | | background: rgba(255, 255, 255, 0.78); |
| | | color: #3d3b3b; |
| | | border: 1px solid rgba(255, 255, 255, 0.99); |
| | | } |
| | | |
| | | &:hover { |
| | | background: rgba(255, 255, 255, 0.78); |
| | | color: #3d3b3b; |
| New file |
| | |
| | | <template> |
| | | <div class="zoom-bar"> |
| | | <div class="partial-wayline-zoom-scale"> |
| | | <div id="zoom-scale" class="zoom-scale" @click="handleZoneClick"> |
| | | <!-- 当前刻度指示器 --> |
| | | <div |
| | | class="current-scale" |
| | | :class="{ 'scale-z-index': isDragging }" |
| | | :style="{ bottom: currentScale + '%' }" |
| | | ></div> |
| | | <div class="current-scale drag" :style="{ bottom: currentScale + '%' }" @mousedown="startDrag"></div> |
| | | |
| | | <!-- 刻度点 --> |
| | | <div |
| | | v-for="(point, index) in scalePoints" |
| | | :key="index" |
| | | class="point-container" |
| | | :class="{ optical: point.optical }" |
| | | :style="{ bottom: point.position + '%' }" |
| | | > |
| | | <span class="point"></span> |
| | | <span class="label">{{ point.label }}</span> |
| | | </div> |
| | | |
| | | <!-- 刻度区域 --> |
| | | <div class="scale-container" @click.stop="handleZoneClick"> |
| | | <div |
| | | v-for="(area, index) in scaleAreasWithWarning" |
| | | :key="index" |
| | | class="scale-area" |
| | | :class="{ optical: area.optical, warning: area.warning }" |
| | | :style="{ height: area.height + '%' }" |
| | | > |
| | | <div class="scale" v-for="n in area.scales" :key="n"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | const valueData = defineModel() |
| | | |
| | | const currentScale = ref(0) |
| | | const isDragging = ref(false) |
| | | const startY = ref(0) |
| | | const startScale = ref(0) |
| | | const scalePoints = ref([ |
| | | { position: 0, label: '2X', optical: false }, |
| | | { position: 11.1111, label: '5X', optical: true }, |
| | | { position: 44.4444, label: '10X', optical: true }, |
| | | { position: 77.7778, label: '20X', optical: true }, |
| | | { position: 100, label: '200X', optical: false }, |
| | | ]) |
| | | const scaleAreas = ref([ |
| | | { height: 11.1111, scales: 3, optical: false }, |
| | | { height: 33.3333, scales: 5, optical: true }, |
| | | { height: 33.3333, scales: 5, optical: true }, |
| | | { height: 22.2222, scales: 9, optical: false }, |
| | | ]) |
| | | |
| | | const isInWarningZone = computed(() => currentScale.value < 11.1111 || currentScale.value > 77.7778) |
| | | const scaleAreasWithWarning = computed(() => { |
| | | // 计算每个区域的起始和结束位置 |
| | | let currentPosition = 0 |
| | | // 计算当前刻度是否在首尾区域范围内 |
| | | const isInDangerZone = isInWarningZone.value |
| | | return scaleAreas.value.map((area, index) => { |
| | | currentPosition = currentPosition + area.height |
| | | // 检查是否是第一个或最后一个区域 |
| | | const isFirstArea = index === scaleAreas.value.length - 1 |
| | | const isLastArea = index === 0 |
| | | return { |
| | | ...area, |
| | | warning: (isFirstArea || isLastArea) && isInDangerZone, |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const currentXValue = computed(() => { |
| | | // 根据当前百分比计算实际的X值 |
| | | if (currentScale.value <= 11.1111) { |
| | | // 2X-5X 区间 |
| | | return Number(2 + (currentScale.value / 11.1111) * 3).toFixed(1) |
| | | } else if (currentScale.value <= 44.4444) { |
| | | // 5X-10X 区间 |
| | | return Number(5 + ((currentScale.value - 11.1111) / 33.3333) * 5).toFixed(1) |
| | | } else if (currentScale.value <= 77.7778) { |
| | | // 10X-20X 区间 |
| | | return Number(10 + ((currentScale.value - 44.4444) / 33.3333) * 10).toFixed(1) |
| | | } else { |
| | | // 20X-200X 区间 |
| | | return Number(20 + ((currentScale.value - 77.7778) / 22.2222) * 180).toFixed(1) |
| | | } |
| | | }) |
| | | |
| | | function startDrag(e) { |
| | | e.preventDefault() |
| | | isDragging.value = true |
| | | startY.value = e.clientY |
| | | startScale.value = currentScale.value |
| | | |
| | | // 添加全局事件监听 |
| | | document.addEventListener('mousemove', onDrag) |
| | | document.addEventListener('mouseup', stopDrag) |
| | | } |
| | | |
| | | function onDrag(e) { |
| | | if (!isDragging.value) return |
| | | |
| | | e.preventDefault() |
| | | // 计算移动距离相对于容器高度的百分比 |
| | | const container = document.getElementById('zoom-scale') |
| | | const containerHeight = container.offsetHeight |
| | | const deltaY = e.clientY - startY.value |
| | | const deltaPercent = (deltaY / containerHeight) * 100 |
| | | |
| | | // 更新刻度位置,并限制在0-100之间 |
| | | let newScale = startScale.value - deltaPercent |
| | | newScale = Math.max(0, Math.min(100, newScale)) |
| | | currentScale.value = newScale |
| | | } |
| | | |
| | | function stopDrag() { |
| | | isDragging.value = false |
| | | |
| | | // 移除全局事件监听 |
| | | document.removeEventListener('mousemove', onDrag) |
| | | document.removeEventListener('mouseup', stopDrag) |
| | | } |
| | | |
| | | function handleZoneClick(e) { |
| | | // 获取容器信息 |
| | | const container = document.getElementById('zoom-scale') |
| | | const rect = container.getBoundingClientRect() |
| | | |
| | | // 计算点击位置相对于容器底部的百分比 |
| | | const clickY = e.clientY - rect.top |
| | | const containerHeight = rect.height |
| | | const percentage = ((containerHeight - clickY) / containerHeight) * 100 |
| | | |
| | | // 限制在0-100之间 |
| | | currentScale.value = Math.max(0, Math.min(100, percentage)) |
| | | } |
| | | |
| | | function calculateScaleFromValue(value) { |
| | | const numValue = Number(value) |
| | | if (numValue <= 5) { |
| | | // 2X-5X 区间 |
| | | return ((numValue - 2) / 3) * 11.1111 |
| | | } else if (numValue <= 10) { |
| | | // 5X-10X 区间 |
| | | return 11.1111 + ((numValue - 5) / 5) * 33.3333 |
| | | } else if (numValue <= 20) { |
| | | // 10X-20X 区间 |
| | | return 44.4444 + ((numValue - 10) / 10) * 33.3333 |
| | | } else { |
| | | // 20X-200X 区间 |
| | | return 77.7778 + ((numValue - 20) / 180) * 22.2222 |
| | | } |
| | | } |
| | | |
| | | watch(valueData, newValue => { |
| | | if (!isDragging.value) { |
| | | currentScale.value = calculateScaleFromValue(newValue) |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['input', 'warning-change']) |
| | | |
| | | watch( |
| | | () => currentXValue.value, |
| | | newValue => { |
| | | valueData.value = newValue |
| | | emit('input', newValue) |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | |
| | | watch( |
| | | () => isInWarningZone.value, |
| | | newValue => { |
| | | emit('warning-change', newValue) |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .zoom-bar { |
| | | background: linear-gradient(-90deg, rgba(0, 0, 0, 0.5) 36%, transparent 99%); |
| | | height: 300px; |
| | | padding: 4px 28px 4px 30px; |
| | | pointer-events: auto; |
| | | position: absolute; |
| | | top: 50%; |
| | | right: 0; |
| | | transform: translateY(-50%); |
| | | } |
| | | |
| | | .partial-wayline-zoom-scale { |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | display: flex; |
| | | position: relative; |
| | | |
| | | .zoom-scale { |
| | | cursor: pointer; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | margin: 8px; |
| | | display: flex; |
| | | position: relative; |
| | | |
| | | .scale-container { |
| | | flex-direction: column-reverse; |
| | | width: 4px; |
| | | height: 100%; |
| | | margin-left: 1px; |
| | | display: flex; |
| | | position: relative; |
| | | |
| | | .scale-area { |
| | | flex-direction: column-reverse; |
| | | display: flex; |
| | | |
| | | &.optical { |
| | | background: rgba(216, 216, 216, 0.3); |
| | | } |
| | | |
| | | &.warning { |
| | | background: rgba(255, 153, 0, 0.5); |
| | | } |
| | | |
| | | .scale { |
| | | border-top: 1px solid rgba(255, 255, 255, 0.5); |
| | | flex: 1; |
| | | width: 4px; |
| | | |
| | | &:last-child { |
| | | border-top: none; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .current-scale { |
| | | background-color: #fff; |
| | | border-radius: 50%; |
| | | justify-content: center; |
| | | align-items: center; |
| | | width: 16px; |
| | | height: 16px; |
| | | margin-bottom: -8px; |
| | | margin-left: -5px; |
| | | display: flex; |
| | | position: absolute; |
| | | z-index: 10; |
| | | pointer-events: none; |
| | | |
| | | &.drag { |
| | | z-index: 20; |
| | | background-color: transparent; |
| | | cursor: grab; |
| | | width: 16px; |
| | | height: 16px; |
| | | position: absolute; |
| | | padding: 10px; |
| | | margin: -10px; |
| | | box-sizing: content-box; |
| | | pointer-events: auto; |
| | | |
| | | &:active { |
| | | cursor: grabbing; |
| | | } |
| | | } |
| | | |
| | | &.scale-z-index { |
| | | z-index: 25; |
| | | } |
| | | } |
| | | |
| | | .point-container { |
| | | align-items: flex-end; |
| | | display: flex; |
| | | position: absolute; |
| | | pointer-events: none; |
| | | |
| | | .point { |
| | | z-index: 5; |
| | | background-color: #fff; |
| | | border-radius: 50%; |
| | | width: 6px; |
| | | height: 6px; |
| | | margin-bottom: -3px; |
| | | position: relative; |
| | | } |
| | | |
| | | .label { |
| | | color: #fff; |
| | | font-size: 10px; |
| | | line-height: 18px; |
| | | position: absolute; |
| | | top: -6px; |
| | | left: 10px; |
| | | transform: scale(0.8); |
| | | } |
| | | |
| | | &.optical .point:after { |
| | | content: ''; |
| | | background: rgba(216, 216, 216, 0.5); |
| | | border-radius: 50%; |
| | | width: 12px; |
| | | height: 12px; |
| | | position: absolute; |
| | | transform: translate(-3px, -3px); |
| | | } |
| | | |
| | | &.warning .point:after { |
| | | content: ''; |
| | | background: #f90; |
| | | border-radius: 50%; |
| | | width: 6px; |
| | | height: 6px; |
| | | position: absolute; |
| | | } |
| | | } |
| | | |
| | | .input-number-wrapper { |
| | | width: 70px; |
| | | height: 28px; |
| | | margin: 8px 2px; |
| | | |
| | | input { |
| | | height: 28px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /* 刻度值显示框样式 */ |
| | | .scale-value-box { |
| | | position: absolute; |
| | | top: 50%; |
| | | right: 90px; |
| | | transform: translateY(-50%); |
| | | background: rgba(0, 0, 0, 0.5); |
| | | padding: 8px 12px; |
| | | border-radius: 4px; |
| | | color: #fff; |
| | | font-size: 14px; |
| | | line-height: 20px; |
| | | min-width: 60px; |
| | | text-align: center; |
| | | |
| | | &.warning { |
| | | color: #ff9900; |
| | | } |
| | | } |
| | | |
| | | .scale-value { |
| | | font-family: monospace; |
| | | font-weight: 500; |
| | | } |
| | | </style> |
| | |
| | | <div class="num">{{ dataObj.device_num }}</div> |
| | | </div> |
| | | <div class="info-item"> |
| | | 任务数: |
| | | 任务次数: |
| | | <div class="num">{{ dataObj.jobNum }}</div> |
| | | </div> |
| | | </div> |
| | |
| | | const areaCode = props.data.region_code; |
| | | dataObj.value.region_name = props.data.region_name; |
| | | const res = await getDeviceInfoNum({ areaCode }); |
| | | console.log('首页地图弹框',res); |
| | | |
| | | const resJob = await getTotalJobNum({ areaCode }); |
| | | dataObj.value.jobNum = resJob.data.data; |
| | | dataObj.value.device_num = res.data.data.device_num; |
| | |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | |
| | | .content { |
| | | padding: 0px 23px 25px 15px; |
| | | } |
| | | .info { |
| | | height: 34px; |
| | | font-family: Source Han Sans CN, Source Han Sans CN, serif; |
| | |
| | | line-height: 19px; |
| | | display: flex; |
| | | align-items: center; |
| | | padding-left: 15px; |
| | | gap: 0 20px; |
| | | margin: 18px 0; |
| | | justify-content: space-between; |
| | | |
| | | .info-item { |
| | | display: flex; |
| | |
| | | color: #ffffff; |
| | | line-height: 23px; |
| | | text-shadow: 0px 0px 12px rgba(68, 105, 255, 0.67); |
| | | padding-left: 15px; |
| | | |
| | | margin-bottom: 14px; |
| | | } |
| | | |
| | | .status-list { |
| | | display: flex; |
| | | justify-content: space-evenly; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .status-item { |
| | |
| | | import { DRC_METHOD } from '@/const/drc.js' |
| | | import { useMqtt } from '@/hooks/controlDrone/useMqtt' |
| | | import { ElMessage } from 'element-plus' |
| | | import { postPayloadCommands } from '@/api/payload' |
| | | import { postPayloadCommands, ptzControlApi } from '@/api/payload' |
| | | import { throttle } from 'lodash' |
| | | |
| | | let myInterval |
| | | |
| | |
| | | let genPortOne = true //是一代机场 |
| | | const mqttHooks = useMqtt(mqttState,deviceTopicInfo) |
| | | let seq = 0 |
| | | let throttledPtz = null |
| | | |
| | | function handlePublish(params) { |
| | | const body = { |
| | |
| | | seq = 0 |
| | | switch (keyCode) { |
| | | case 'KeyQ': |
| | | if (activeCodeKey === keyCode) return |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objQ = { yaw: 1024-W_SPEED } |
| | | if (genPortOne) {objQ = {w: -W_SPEED}} |
| | | handlePublish(objQ) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'KeyW': |
| | | if (activeCodeKey === keyCode) return |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objW = { pitch: 1024+SPEED } |
| | | if (genPortOne) {objW = {x: SPEED}} |
| | | handlePublish(objW) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'KeyE': |
| | | if (activeCodeKey === keyCode) return |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objE = { yaw: 1024+W_SPEED } |
| | | if (genPortOne) {objE = {w: W_SPEED}} |
| | | handlePublish(objE) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'KeyA': |
| | | if (activeCodeKey === keyCode) return |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objA = { roll: 1024-SPEED } |
| | | if (genPortOne) {objA = {y: -SPEED}} |
| | | handlePublish(objA) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'KeyS': |
| | | if (activeCodeKey === keyCode) return |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objS = { pitch: 1024-SPEED } |
| | | if (genPortOne) {objS = {x: -SPEED}} |
| | | handlePublish(objS) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'KeyD': |
| | | if (activeCodeKey === keyCode) return |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objD = { roll: 1024+SPEED } |
| | | if (genPortOne) {objD = {y: SPEED}} |
| | | handlePublish(objD) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'ArrowUp': |
| | | case 'KeyZ': |
| | | if (activeCodeKey.value === keyCode) return |
| | | handlePublish({ h: HEIGHT }) |
| | | let objZ = { throttle: 1024-HEIGHT } |
| | | if (genPortOne) {objZ = {h: -HEIGHT}} |
| | | handlePublish(objZ) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | // case 'ArrowDown': |
| | | case 'KeyC': |
| | | if (activeCodeKey.value === keyCode) return |
| | | let objC = { throttle: 1024+HEIGHT } |
| | | if (genPortOne) {objC = {h: HEIGHT}} |
| | | handlePublish(objC) |
| | | activeCodeKey.value = keyCode |
| | | break |
| | | case 'ArrowUp': |
| | | ptzThrottle[0]() |
| | | break |
| | | case 'ArrowDown': |
| | | if (activeCodeKey.value === keyCode) return |
| | | handlePublish({ h: -HEIGHT }) |
| | | activeCodeKey.value = keyCode |
| | | ptzThrottle[1]() |
| | | break |
| | | |
| | | case 'ArrowLeft': |
| | | ptzThrottle[2]() |
| | | break |
| | | case 'ArrowRight': |
| | | ptzThrottle[3]() |
| | | break |
| | | default: |
| | | break |
| | | } |
| | | } |
| | | |
| | | // 云台控制节流list |
| | | const ptzThrottle = ['up','down','left','right'].map(key => throttle(()=>ptzControl(key), 500)) |
| | | |
| | | // 云台上下左右 |
| | | function ptzControl(key) { |
| | | const {dockSn} = paramsRef.value |
| | | ptzControlApi(key, { sn: dockSn }).then(res => { |
| | | |
| | | }) |
| | | } |
| | | |
| | | const handlePayloadTurn = (params) => { |
| | | handleClearInterval() |
| | | if (!this.myInterval) { |
| | |
| | | <div class="name">总任务数</div> |
| | | </div> |
| | | <div class="status"> |
| | | <div class="item" v-for="(item, index) in list"> |
| | | <div class="item" v-for="(item, index) in list" :key="index"> |
| | | <div> |
| | | <div :style="{ color: item.color }" class="value"> |
| | | {{ jobStatistics[item.field] }} |
| | |
| | | ? 'distributed' |
| | | : scope.row.status === 3 |
| | | ? 'finish ' |
| | | |
| | | |
| | | : 'fail ' |
| | | " |
| | | > |
| | | : scope.row.status === 5 |
| | | ? 'fail ' |
| | | : ' '"> |
| | | {{ |
| | | scope.row.status === 1 |
| | | ? '待执行' |
| | |
| | | ? '执行中' |
| | | : scope.row.status === 3 |
| | | ? '已执行' |
| | | |
| | | : '执行失败' |
| | | : scope.row.status === 5 |
| | | ? '执行失败' |
| | | : '' |
| | | }} |
| | | </span> |
| | | </template> |
| | |
| | | </el-table> |
| | | </div> |
| | | <!-- 分页 --> |
| | | <div style="display: flex; justify-content: center;margin:15px 0;"> |
| | | <div style="display: flex; justify-content: center; margin: 15px 0"> |
| | | <el-pagination |
| | | class="ztzf-pagination" |
| | | v-model:current-page="pageParams.current" |
| | |
| | | { label: '待执行', value: 1 }, |
| | | { label: '执行中', value: 2 }, |
| | | { label: '已完成', value: 3 }, |
| | | |
| | | { label: '执行失败', value: 4 }, |
| | | |
| | | { label: '执行失败', value: 5 }, |
| | | ] |
| | | // 设备页面参数 |
| | | const devicePageParams = ref({ |
| | |
| | | } |
| | | // 获取任务列表 |
| | | const getJobList = () => { |
| | | // 事件状态:0 =待处理,1=待分拨,2=待处理,3=处理中,4=已完成 5=已完结 |
| | | // 事件状态:0 =待处理,1=待分拨,2=待处理,3=处理中,4=已完成 5=已完结 |
| | | jobList(taskDetailParams).then(res => { |
| | | if (res.data.code !== 0) return |
| | | taskDetailData.value = res.data.data.records |
| | | total.value = res.data.data.total |
| | | |
| | | }) |
| | | } |
| | | // 机巢列表数据 |
| | |
| | | } |
| | | |
| | | // 任务状态颜色 |
| | | // 待执行 |
| | | .pending { |
| | | color: #e36913; |
| | | color: #ffe17e; |
| | | } |
| | | // 执行中 |
| | | .distributed { |
| | | color: #ffc398; |
| | | color: #FFA768; |
| | | } |
| | | // 已执行 |
| | | .finish { |
| | | color: #afd9fb; |
| | | color: #8effac; |
| | | } |
| | | .cancel { |
| | | color: #11c4ff; |
| | | } |
| | | |
| | | // 执行失败 |
| | | .fail { |
| | | color: #8cfea7; |
| | | color: #ff8e8e; |
| | | } |
| | | </style> |
| | | |
| | |
| | | padding-top: 6px; |
| | | margin-top: 10px; |
| | | |
| | | height: 120px; |
| | | height: 100px; |
| | | } |
| | | img { |
| | | width: 45px; |
| | |
| | | // 作业中 |
| | | .atcive { |
| | | color: #ffa768; |
| | | background: linear-gradient(90deg, rgba(12, 45, 92, 1) 0%, #154671 50%, rgba(12, 45, 92, 1) 100%), |
| | | linear-gradient(90deg, rgba(12, 45, 92, 1) 0%, rgba(12, 45, 92, 1) 50%, rgba(12, 45, 92, 1) 100%); |
| | | |
| | | } |
| | | .numbering { |
| | | font-size: 12px; |
| | |
| | | color: #fff; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | background: linear-gradient( 90deg, rgba(71,157,255,0) 0%, rgba(71, 157, 255, 0.12) 50%, rgba(71,157,255,0) 100%); |
| | | .left { |
| | | .left-t { |
| | | height: 24px; |