Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
| | |
| | | <meta name='apple-mobile-web-app-capable' content='yes'> |
| | | <meta name='apple-mobile-web-app-status-bar-style' content='black'> |
| | | <meta name='format-detection' content='telephone=no'> |
| | | <link rel='icon' href='/favicon.png' /> |
| | | <link rel="icon" href='/favicon.png' /> |
| | | <link rel='stylesheet' href='/fonts/font.css'> |
| | | <link rel='stylesheet' href='/iconfont/index.css'> |
| | | <link rel='stylesheet' href='/iconfont/avue/iconfont.css'> |
| | |
| | | } |
| | | |
| | | // 取消返航 |
| | | export async function returnHomeCancel(sn) { |
| | | export async function returnHomeCancel(data) { |
| | | return request({ |
| | | url: `/drone-device-core/dp/home/${sn}/drc/returnHomeCancel`, |
| | | url: `/drone-device-core/dp/home/${data.dock_sn}/drc/returnHomeCancel`, |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | |
| | | |
| | | &:hover { |
| | | cursor: pointer; |
| | | //background: radial-gradient(circle, #5d5c5e 0%, #514f52 50%, #3d3b3d 100%); |
| | | box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.3); |
| | | } |
| | | } |
| | |
| | | </div> |
| | | |
| | | <div class="instrument-center"> |
| | | <div class="compass-box" :style="{ transform: `rotate(${props?.options?.yawAngle || 0}deg)` }"> |
| | | <div class="compass-box" :style="compassStyle"> |
| | | <div v-for="(item, index) in str" :key="index" class="scale" :style="{ '--rotate': 30 * index + 'deg' }"> |
| | | <span class="text">{{ item }}</span> |
| | | </div> |
| | |
| | | } |
| | | }) |
| | | |
| | | const compassStyle = computed(() => { |
| | | return { transform: `rotate(${props?.options?.yawAngle || 0}deg)` } |
| | | }) |
| | | |
| | | const trueAltitudeStyle = computed(() => { |
| | | const trueAltitude = props?.options?.trueAltitude || 0 |
| | | // 将 [-240,240] 映射到 [0%, 100%] |
| | | const percentage = (((trueAltitude + 240) / 480) * 100).toFixed(2) |
| | | // 将 [0,240] 映射到 [0%, 100%] |
| | | const percentage = ((trueAltitude / 240) * 100).toFixed(2) |
| | | return { |
| | | bottom: `${percentage}%`, |
| | | } |
| | |
| | | </div> |
| | | |
| | | <div class="speed"> |
| | | <el-icon class="btnIcon" @click="speed = speed + 1"> |
| | | <el-icon class="btnIcon" @click="speed = speed === 15 ? 15 : speed + 1"> |
| | | <Plus /> |
| | | </el-icon> |
| | | <div> |
| | |
| | | <br /> |
| | | m/s |
| | | </div> |
| | | <el-icon class="btnIcon" @click="speed = speed - 1"> |
| | | <el-icon class="btnIcon" @click="speed = speed === 0 ? 0 : speed - 1"> |
| | | <Minus /> |
| | | </el-icon> |
| | | </div> |
| | |
| | | <div class="ptzControlBtnBox"> |
| | | <div class="ptzControlBtn b-r"> |
| | | <div |
| | | v-for="(item, index) in list5" |
| | | v-for="(item, index) in ptzBtns" |
| | | :style="item.style" |
| | | class="ptzControlItem" |
| | | @mousedown="onMouseDown(item.key)" |
| | |
| | | |
| | | <div |
| | | class="ptzControlItemIcon" |
| | | v-for="(item, index) in list5" |
| | | v-for="(item, index) in ptzBtns" |
| | | :style="{ transform: `rotate(${index * 90}deg)` }" |
| | | > |
| | | <el-icon> |
| | |
| | | </div> |
| | | |
| | | <div class="divider"></div> |
| | | <div v-for="arr in baseInfo" class="info"> |
| | | <div v-for="arr in baseInfoList" class="info"> |
| | | <div v-for="item in arr" class="infoItem"> |
| | | <div class="infoName">{{ item.name }}</div> |
| | | <div class="infoValue">{{ item.value + (item.unit || '') }}</div> |
| | |
| | | const taskDetails = inject('taskDetails') |
| | | const dockSn = inject('dockSn') |
| | | const droneSn = inject('droneSn') |
| | | |
| | | const trueAltitude = inject('trueAltitude') |
| | | const client_id = inject('client_id') |
| | | |
| | | const deviceTopicInfo = ref({ |
| | | pubTopic: '', |
| | | subTopic: '', |
| | | }) |
| | | const flightController = ref(false) |
| | | // 控制对象 |
| | | let manualControl = {} |
| | | const isAutoControl = inject('isAutoControl') |
| | | |
| | | const compassOptions = computed(() => { |
| | | return { |
| | |
| | | }) |
| | | |
| | | let mqttState = null |
| | | const client_id = ref('') |
| | | const valueTime = ref('00:00:00') |
| | | let timer = null |
| | | let totalSeconds = 0 |
| | |
| | | const speed = ref(5) |
| | | provide('speed', speed) |
| | | |
| | | const list5 = [ |
| | | const ptzBtns = [ |
| | | { 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 baseInfo = computed(() => { |
| | | const usedStorage = dock_osd_host.value?.storage?.used || baseInfo.value?.[1]?.[1]?.value || 0 |
| | | const zoom_factor = device_osd_host.value?.cameras?.[0]?.zoom_factor || 0 |
| | | const usedStorageGB = _.round(usedStorage / 1024 / 1024, 2) |
| | | |
| | | return [ |
| | | [ |
| | | { name: '焦距倍数', value: zoom_factor }, |
| | | { name: '俯仰角度', value: pitchAngle.value.angle, unit: '°' }, |
| | | { name: '横向角度', value: yawAngle.value.angle, unit: '°' }, |
| | | ], |
| | | [ |
| | | { name: '储存', value: usedStorageGB, unit: 'G' }, |
| | | { name: '方向', value: pitchAngle.value.direction }, |
| | | { name: '方向', value: yawAngle.value.direction }, |
| | | ], |
| | | ] |
| | | }) |
| | | const baseInfoList = ref([ |
| | | [ |
| | | { name: '焦距倍数', value: 0 }, |
| | | { name: '俯仰角度', value: 0, unit: '°' }, |
| | | { name: '横向角度', value: 0, unit: '°' }, |
| | | ], |
| | | [ |
| | | { name: '储存', value: 0, unit: 'G' }, |
| | | { name: '方向', value: 0 }, |
| | | { name: '方向', value: 0 }, |
| | | ], |
| | | ]) |
| | | |
| | | const pitchAngle = computed(() => { |
| | | const { payloads } = device_osd_host?.value || {} |
| | |
| | | direction, |
| | | } |
| | | }) |
| | | |
| | | const yawAngle = computed(() => { |
| | | let { payloads, attitude_head } = device_osd_host?.value || {} |
| | | const gimbal_yaw = payloads?.[0]?.gimbal_yaw || 0 |
| | |
| | | if ((yaw > -2 && yaw < 2) || parseInt(attitude_head) === parseInt(gimbal_yaw)) { |
| | | result = attitude_head < 0 ? 180 + (180 + attitude_head) : attitude_head |
| | | } |
| | | const roundResult = Math.round(result); |
| | | let direction = ''; |
| | | const roundResult = Math.round(result) |
| | | let direction = '' |
| | | for (const item of directionMap) { |
| | | if (roundResult >= item.min && roundResult <= item.max) { |
| | | direction = item.value; |
| | | break; |
| | | direction = item.value |
| | | break |
| | | } |
| | | } |
| | | return { |
| | |
| | | } |
| | | }) |
| | | |
| | | const deviceTopicInfo = ref({ |
| | | pubTopic: '', |
| | | subTopic: '', |
| | | }) |
| | | const flightController = ref(false) |
| | | // 控制对象 |
| | | let manualControl = {} |
| | | const isAutoControl = inject('isAutoControl') |
| | | const baseInfoChange = () => { |
| | | const newUsedStorage = dock_osd_host.value?.storage?.used |
| | | const zoom_factor = device_osd_host.value?.cameras?.[0]?.zoom_factor || 0 |
| | | const usedStorageGB = _.round(newUsedStorage / 1024 / 1024, 2) |
| | | baseInfoList.value[0][0].value = _.round(zoom_factor, 0) |
| | | baseInfoList.value[0][1].value = pitchAngle.value.angle |
| | | baseInfoList.value[0][2].value = yawAngle.value.angle |
| | | if (newUsedStorage !== undefined) baseInfoList.value[1][0].value = usedStorageGB |
| | | baseInfoList.value[1][1].value = pitchAngle.value.direction |
| | | baseInfoList.value[1][2].value = yawAngle.value.direction |
| | | } |
| | | |
| | | watch( |
| | | wsInfo, |
| | | () => { |
| | | baseInfoChange() |
| | | }, |
| | | { immediate: true, deep: true } |
| | | ) |
| | | |
| | | const timeStart = () => { |
| | | stop() // 避免重复启动 |
| | |
| | | }) |
| | | } |
| | | |
| | | const isBackDock = ref(false) |
| | | |
| | | // 返航 |
| | | function onBackDock() { |
| | | returnHome(dockSn?.value).then(res => { |
| | | ElMessage.success('返航操作成功') |
| | | }) |
| | | async function onBackDock() { |
| | | await returnHome(dockSn?.value) |
| | | ElMessage.success('返航操作成功') |
| | | isBackDock.value = true |
| | | } |
| | | |
| | | // 取消返航 |
| | | function cancelBackDock() { |
| | | returnHomeCancel(dockSn?.value).then(res => { |
| | | ElMessage.success('取消返航成功') |
| | | }) |
| | | async function cancelBackDock() { |
| | | await returnHomeCancel({ dock_sn: dockSn?.value, client_id: client_id.value }) |
| | | ElMessage.success('取消返航成功') |
| | | isBackDock.value = false |
| | | } |
| | | |
| | | // 创建mqtt连接 |
| | |
| | | |
| | | // 返航或取消返航 |
| | | const returnOrCancelReturn = () => { |
| | | if (device_osd_host?.value?.mode_code === 9) { |
| | | cancelBackDock() |
| | | } else { |
| | | onBackDock() |
| | | } |
| | | isBackDock.value ? cancelBackDock() : onBackDock() |
| | | } |
| | | |
| | | // useManualControl里面用的参数 |
| | |
| | | right: 0; |
| | | width: 1400px; |
| | | height: 217px; |
| | | background: linear-gradient(196deg, rgba(23, 23, 23, 0.11) 0%, rgba(6, 6, 6, 0.11) 100%); |
| | | background: rgba(31, 31, 31, 0.15); |
| | | backdrop-filter: blur(5px); |
| | | border-radius: 40px 0px 40px 40px; |
| | | display: flex; |
| | |
| | | box-shadow: 2px 4px 6px 0px rgba(0, 13, 26, 0.42); |
| | | border-radius: 8px 8px 8px 8px; |
| | | text-align: center; |
| | | padding: 5px 0; |
| | | padding: 10px 0; |
| | | |
| | | .btnIcon { |
| | | font-size: 20px; |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | .divider { |
| | | position: absolute; |
| | | transform: translateX(90px); |
| | | transform: translateX(95px); |
| | | width: 0; |
| | | height: 137px; |
| | | border: 1px solid rgba(255, 255, 255, 0.07); |
| | |
| | | * @Author: shuishen 1109946754@qq.com |
| | | * @Date: 2025-04-19 13:13:15 |
| | | * @LastEditors: shuishen 1109946754@qq.com |
| | | * @LastEditTime: 2025-04-19 14:15:02 |
| | | * @LastEditTime: 2025-04-19 15:04:15 |
| | | * @FilePath: \command-center-dashboard\src\components\CurrentTaskDetails\CurrentTaskDetails.vue |
| | | * @Description: |
| | | * |
| | |
| | | <TaskDetailsHead /> |
| | | <TaskDetailsLeft /> |
| | | </template> |
| | | |
| | | <!-- 控制面板,里面有方法需要立即执行,不可用v-if --> |
| | | <!-- <ControlPanel />--> |
| | | <ControlPanel v-show="!isAutoControl" /> |
| | | <img alt="" :src="amplifyImg" class="amplify" @click="isMaxMap = !isMaxMap" /> |
| | | </div> |
| | |
| | | const trueAltitude = ref('') // 真实高度 |
| | | const isAiLive = ref(false) // 是ai直播 |
| | | const video_id = ref('') // 直播视频id |
| | | |
| | | const isShow = defineModel('show') // 是否显示当前任务详情 |
| | | const props = defineProps(['id']) |
| | | const currentLiveUrl = ref('') // 当前直播地址 |
| | |
| | | let { taskDetails, workspace_id, getTaskDetails } = useTaskDetails(getDeviceLiveUrl) |
| | | let { wsInfo, removeWS } = useDroneWS(workspace_id) //ws信息,是一个ref对象 |
| | | |
| | | provide('wsInfo', wsInfo) |
| | | provide('deviceOsdInfo', deviceOsdInfo) |
| | | provide('dockOsdInfo', wsInfo?.value?.dock_osd) |
| | | provide('dockSn', dockSn) |
| | | provide('droneSn', droneSn) |
| | | provide('isAutoControl', isAutoControl) |
| | | provide('lineQuality', lineQuality) |
| | | provide('taskDetailsViewer', taskDetailsViewer) |
| | | provide('taskDetails', taskDetails) |
| | | provide('trueAltitude', trueAltitude) |
| | | provide('isAiLive', isAiLive) |
| | | provide('video_id', video_id) |
| | | provide('client_id', client_id) |
| | | |
| | | watch( |
| | | wsInfo, |
| | | () => { |
| | |
| | | }, |
| | | { deep: true } |
| | | ) |
| | | provide('wsInfo', wsInfo) |
| | | |
| | | provide('isAutoControl', isAutoControl) |
| | | provide('lineQuality', lineQuality) |
| | | provide('taskDetailsViewer', taskDetailsViewer) |
| | | provide('taskDetails', taskDetails) |
| | | provide('deviceOsdInfo', deviceOsdInfo) |
| | | provide('dockOsdInfo', wsInfo?.value?.dock_osd) |
| | | provide('dockSn', dockSn) |
| | | provide('droneSn', droneSn) |
| | | provide('trueAltitude', trueAltitude) |
| | | provide('isAiLive', isAiLive) |
| | | provide('video_id', video_id) |
| | | |
| | | const getAiLiveUrl = async () => { |
| | | const res = await getLiveAiLinkApi({ |
| | |
| | | } |
| | | |
| | | // 获取无人机直播url |
| | | async function getDroneLiveUrl() { |
| | | async function getDroneLiveUrl(reset = false) { |
| | | currentLiveUrl.value = '' |
| | | await nextTick() |
| | | const res = await liveStart(droneSn.value, lineQuality.value) |
| | | currentLiveUrl.value = res.data.data.rtcs_url |
| | | video_id.value = res.data.data.video_id |
| | | isAiLive.value = false |
| | | reset && ElMessage.success('刷新成功') |
| | | } |
| | | |
| | | // 无人机直播画质切换 |
| | |
| | | position: absolute; |
| | | left: 340px; |
| | | bottom: 183px; |
| | | width: 22px; |
| | | height: 22px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | { index: 4, title: '经度', value: 0, unit: '°' }, |
| | | { index: 5, title: '纬度', value: 0, unit: '°' }, |
| | | { index: 6, title: '风速', value: 0, unit: 'M/s' }, |
| | | { index: 7, title: '4G信号', value: 0, unit: '' }, |
| | | { index: 8, title: 'SDR信号', value: 0, unit: '' }, |
| | | { index: 7, title: '4G信号', value: '-', unit: '' }, |
| | | { index: 8, title: 'SDR信号', value: '-', unit: '' }, |
| | | { index: 9, title: 'GPS搜星数', value: 0, unit: '' }, |
| | | { index: 10, title: 'RTK搜星数', value: 0, unit: '' }, |
| | | { index: 11, title: '距离机场', value: 0, unit: 'M' }, |
| | |
| | | } |
| | | |
| | | function refreshLive() { |
| | | EventBus.emit('CurrentTaskDetails-getDroneLiveUrl') |
| | | EventBus.emit('CurrentTaskDetails-getDroneLiveUrl',true) |
| | | } |
| | | |
| | | function getFlightStatisticsFun() { |
| | |
| | | :max="cameraParams.camera_type === 'ir' ? 200 : 200" |
| | | @change="sliderChange" |
| | | /> |
| | | <div class="cameraZoomText">{{cameraParams.zoom_factor}}X</div> |
| | | </div> |
| | | </div> |
| | | <!-- 广播列表 --> |
| | |
| | | |
| | | <script setup> |
| | | import EventBus from '@/event-bus' |
| | | import { |
| | | callPhotoAndVideoCmd, |
| | | cameraParamsChangeApi, |
| | | ptzResetModeApi, |
| | | startVoice, |
| | | import { |
| | | callPhotoAndVideoCmd, |
| | | cameraParamsChangeApi, |
| | | ptzResetModeApi, |
| | | startVoice, |
| | | stayAwayRiver, |
| | | getVoiceFile, |
| | | playAudio, |
| | |
| | | } |
| | | } |
| | | const tableList = ref([]); |
| | | // 分页相关 |
| | | // 分页相关 |
| | | const searchParams = ref({ |
| | | sn: droneSn.value, |
| | | name: '', |
| | |
| | | |
| | | .cameraZoom { |
| | | position: absolute; |
| | | padding: 16px 0; |
| | | padding: 20px 0 15px 0; |
| | | left: 1600px; |
| | | top: -59px; |
| | | width: 112px; |
| | | width: 90px; |
| | | height: 490px; |
| | | background: rgba(64, 64, 64, 0.15); |
| | | background: rgba(0, 0, 0, 0.4); |
| | | backdrop-filter: blur(5rem); |
| | | border-radius: 20px 20px 20px 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .el-slider { |
| | | height: 100%; |
| | | flex: 1; |
| | | } |
| | | .cameraZoomText{ |
| | | font-family: Segoe UI, Segoe UI; |
| | | font-weight: 400; |
| | | font-size: 22px; |
| | | color: #ffffff; |
| | | margin-top: 20px; |
| | | } |
| | | } |
| | | |
| | |
| | | bottom: 10px; |
| | | height: 32px; |
| | | display: flex; |
| | | |
| | | |
| | | :deep(.number) { |
| | | color: #EDEDED; |
| | | } |
| | |
| | | () => { |
| | | list.value.forEach(item => { |
| | | item.value = taskDetails?.value?.[item.field] || '' |
| | | if (item.name === '任务频次') { |
| | | const { rep_rule_type = '', rep_rule_val = '' } = taskDetails?.value || {} |
| | | item.value = rep_rule_type + ' -- ' + rep_rule_val |
| | | } |
| | | }) |
| | | }, |
| | | { |
| | |
| | | deep: true, |
| | | } |
| | | ) |
| | | |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | width: 297px; |
| | | height: 1002px; |
| | | background: rgba(31, 31, 31, 0.15); |
| | | backdrop-filter: blur(0.5rem); |
| | | border-radius: 0px 40px 40px 0px; |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | .itemValue { |
| | | font-weight: bold; |
| | | color: #ffffff; |
| | | word-break: break-all; /* 强制在任意字符断行 */ |
| | | white-space: normal; /* 允许正常换行 */ |
| | | } |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | <DeviceJobDetails |
| | | v-if="isShowDeviceJobDetails" |
| | | v-model:show="isShowDeviceJobDetails" |
| | | :wayLineJodInfoId="wayLineJodInfoId"/> |
| | | :wayLineJodInfoId="rowData.id"/> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | }); |
| | | const jobListData = ref([]); |
| | | const total = ref(0); |
| | | let wayLineJodInfoId = ref('') |
| | | let isShowDeviceJobDetails = ref(false); |
| | | let isShowCurrentTaskDetails = ref(false); |
| | | |
| | |
| | | let rowData = ref({}); |
| | | const handleDetail = (row) => { |
| | | if (row.device_sns.length === 1){ |
| | | console.log(row) |
| | | rowData.value = row? row : {}; |
| | | if (row.status === 2 || row.status === 1){ |
| | | rowData.value = row? row : {}; |
| | | isShowCurrentTaskDetails.value = true; |
| | | } else{ |
| | | wayLineJodInfoId.value = row.id |
| | | isShowDeviceJobDetails.value = true |
| | | } |
| | | }else{ |