DiKongGanZhiPingTai/src/views/demo/workmanagement/flightcontrol/index.vue

1412 lines
39 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<!-- 飞行控制主容器 -->
<div class="flight-control">
<!-- 顶部状态栏 -->
<div class="flight-control-top">
<!-- 机场选择组件 -->
<SelectComponent @selectChange="changeSelect" />
<!-- 机场名称 -->
<div class="airport-name">
<img src="@/assets/images/flightcontrol/logo.png" alt="机场logo" />
{{ airportName }}
</div>
<!-- 机场状态 -->
<span class="airport-status" v-if="airporOsdVal.flighttask_step_code">
<template v-if="airporOsdVal.flighttask_step_code == 0">作业准备中</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 1">飞行作业中</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 2">作业后状态恢复</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 3">自定义飞行区更新中</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 4">地形障碍物更新中</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 5">任务空闲</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 255">飞行器异常</template>
<template v-else-if="airporOsdVal.flighttask_step_code == 256">未知状态</template>
</span>
<!-- 机场直播按钮 -->
<span class="airport-live" @click="getRtmpList">机场直播</span>
<!-- 机场环境信息 -->
<div class="airport-info">
<img src="@/assets/images/flightcontrol/temperature.png" alt="温度图标" />
<span class="airport-info-title">舱外温度</span>
<span>{{ airporOsdVal.temperature }}℃</span>
</div>
<div class="airport-info">
<img src="@/assets/images/flightcontrol/rainfall.png" alt="雨量图标" />
<span class="airport-info-title">雨量</span>
<span>
<template v-if="airporOsdVal.rainfall == 0">无雨</template>
<template v-else-if="airporOsdVal.rainfall == 1">小雨</template>
<template v-else-if="airporOsdVal.rainfall == 2">中雨</template>
<template v-else-if="airporOsdVal.rainfall == 3">大雨</template>
</span>
</div>
<div class="airport-info">
<img src="@/assets/images/flightcontrol/wind_speed.png" alt="风速图标" />
<span class="airport-info-title">风速</span>
<span>{{ airporOsdVal.wind_speed }}m/s</span>
</div>
<div class="airport-info">
<img src="@/assets/images/flightcontrol/rate.png" alt="网络图标" />
<span class="airport-info-title">网络</span>
<span>{{ airporOsdVal.rate }}KB/s</span>
</div>
</div>
<!-- 主内容区域 -->
<div class="flight-contain">
<!-- 飞行器控制盒打开按钮 -->
<div class="uavbox-open" @click="openUavBox" v-if="!uavBoxVisible">
<img src="@/assets/images/flightcontrol/uavbox-open.png" alt="打开飞行器控制盒" />
</div>
<!-- 地图容器 -->
<div class="map-container" :style="uavBoxVisible
? 'width:38vw;height:' + mapHeight + 'px'
: 'width:90vw;height:' + mapHeight + 'px'
">
<div class="map-container-content">
<!-- 地图组件 -->
<Map @flyToThere="flyToThere" :airPort="airPort" :uavTrack="uavTrack" :allAreaDataList="allAreaDataList"
@takeOff="takeOff" />
</div>
</div>
<!-- 飞行器控制盒 -->
<!-- <div class="uav-box" ref="uavBox" v-if="uavBoxVisible"> -->
<div class="uav-box" ref="uavBox" v-if="1">
<UavBox @closeUavBox="uavBoxVisible = false" @flyLog="logVisible = true" @flyTo="flyTobtn" @patrol="patrolbtn"
@taskSubmitted="handleTaskSubmitted" />
</div>
<!-- 飞行日志面板 -->
<div class="fly-log" v-if="logVisible">
<div class="fly-log-title">
<span>飞行日志</span>
<span @click="logVisible = false">
<CloseCircleOutlined />
</span>
</div>
<div class="fly-log-content">
<div>检查项</div>
<div class="fly-status">所有检查项均通过,飞行器将立即执行飞行任务。</div>
<div class="log-opstions" ref="myDiv">
<template v-for="(item, index) in flyOptions" :key="index">
<div class="log-item MinusCircleFilled" v-if="item.type == 'info'">
<MinusCircleFilled />
<span class="item-desc">{{ item.title }}{{ item.content }}</span>
</div>
<div class="log-item CheckCircleFilled" v-else-if="item.type == 'success'">
<CheckCircleFilled />
<span class="item-desc">{{ item.title }}{{ item.content }}</span>
</div>
<div class="log-item CloseCircleFilled" v-else-if="item.type == 'error'">
<CloseCircleFilled />
<span class="item-desc">{{ item.title }}{{ item.content }}</span>
</div>
</template>
</div>
</div>
</div>
<!-- 指点飞行面板 -->
<div class="fly-to-form" v-if="flyToVisible">
<div class="fly-to-title">
<span class="title-name">指点飞行</span>
<span class="title-icon" @click="flyToVisible = false">
<CloseOutlined />
</span>
</div>
<span class="form-desc">点击地图选择目标点,点击开始执行,飞行器将飞到指定地点。绘制路线仅供参考。</span>
<div class="form-content">
<div class="form-item">
<span>经度</span>
<div>
<a-input v-model:value="flyToData.points[0].longitude" />
</div>
</div>
<div class="form-item">
<span>纬度</span>
<div>
<a-input v-model:value="flyToData.points[0].latitude" />
</div>
</div>
<div class="form-item">
<span>高度</span>
<div>
<a-input v-model:value="flyToData.points[0].height" />
m
</div>
</div>
</div>
<div class="fly-to-button">
<span @click="stop">结束执行</span>
<span @click="execute">开始执行</span>
</div>
</div>
<!-- 机场直播面板 -->
<div class="airport-live-contain" v-show="liveVisible">
<div class="live-title">
<span title="刷新">
<img src="@/assets/images/flightcontrol/refresh.png" alt="刷新" @click="reloadLive" />
</span>
<span class="关闭">
<img src="@/assets/images/flightcontrol/close.png" alt="关闭" @click="closeLive" />
</span>
</div>
<div class="player">
<video id="player-container-id" :width="width" :height="height" preload="auto" playsinline webkit-playsinline>
</video>
</div>
</div>
<!-- 智能巡检面板 -->
<div class="patrol-btn" v-if="patrolVisible">
<Patrol @changePatrol="changePatrol" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
// 导入Vue核心模块
import { ref, computed, onMounted, watch, reactive, nextTick, onUnmounted } from 'vue';
// 导入自定义组件
import { SelectComponent, UavBox, Map, Patrol } from './index';
// 导入状态管理
import { airPortStore } from '@/store/modules/airport';
import { useUserStore } from '@/store/modules/user';
// 导入MQTT工具
import { getClient, createConnection, clientSubscribe, destroyConnection } from '@/utils/mqtt';
// 导入事件总线
import { EventBus } from '@/utils/eventBus';
// 导入API接口
import { GetWorkAreaList } from '@/api/demo/mediaLibrary';
import {
getDronePortInfoFromRedis,
saveHandFlyTask,
getLastHandFlyTask,
} from '@/api/workmanagement/droneDock';
import { getOperationLogList, isCanFly } from '@/api/workmanagement/airportMaintenance';
// 导入工具函数
import { WktToGeojson } from '@/components/MapboxMaps/src/WktGeojsonTransform';
import { buildGUID, uuid } from '@/utils/uuid';
import {
servicesTopic,
services_replyTopic,
errorName,
unServices_replyTopic,
} from '@/utils/debugging/remote';
import { servicesTopicReize } from '@/utils/debugging/events';
// 导入UI组件
import {
CloseCircleOutlined,
MinusCircleFilled,
CheckCircleFilled,
CloseCircleFilled,
CloseOutlined,
} from '@ant-design/icons-vue';
import { useMessage } from '@/hooks/web/useMessage';
// 导入第三方库
import axios from 'axios';
import TCPlayer from 'tcplayer.js';
import 'tcplayer.js/dist/tcplayer.min.css'; //引入插件和样式文件
import { io, Socket } from 'socket.io-client';
// 消息提示实例
const { createConfirm, createMessage } = useMessage();
// 飞行器控制盒引用
const uavBox = ref();
// 地图高度
const mapHeight = ref(document.documentElement.clientHeight - 124);
// 机场状态存储
const airPortStoreVal = airPortStore();
const airPortInfo = airPortStoreVal.getAirport;
const live_info = airPortStoreVal.getLiveInfo;
const uavInfo = airPortStoreVal.getUAV;
const typeId = airPortStoreVal.getTypeId;
// 直播相关
const liveCode = ref(airPortInfo.sn);
const airportName = ref(airPortInfo.name);
// 用户信息
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
// 机场OSD信息
const airporOsdVal = ref({
flighttask_step_code: 256, // 飞行任务步骤代码
temperature: 0, // 温度
rainfall: 0, // 雨量
wind_speed: 0, // 风速
rate: 0, // 网络速率
});
// 位置信息
const locationVal: any = ref({});
// 机场位置信息
const airPort = ref({
latitude: null, // 纬度
longitude: null, // 经度
height: null, // 高度
});
// 无人机轨迹
const uavTrack = ref({});
// 工作区域数据
const allAreaDataList: any = ref([]);
// 选择值
const changeSelectValue = ref();
// 飞行器控制盒可见性
const uavBoxVisible = ref(false);
// 飞行日志可见性
const logVisible = ref(false);
// 飞行日志选项
const flyOptions = ref(airPortStoreVal.getflyLog);
// 指点飞行可见性
const flyToVisible = ref(false);
// Socket相关
const socket = ref<Socket | null>(null);
const socketConnected = ref(false);
const socketUrl = ref('http://localhost:5000'); // Socket服务器地址
// 连接Socket服务
const connectSocket = () => {
try {
if (socket.value) {
socket.value.disconnect();
}
socket.value = io(socketUrl.value, {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.value.on('connect', () => {
console.log('Socket连接成功');
socketConnected.value = true;
airPortStoreVal.setflyLog({
type: 'info',
title: 'Socket连接',
content: 'Socket服务连接成功'
});
});
socket.value.on('disconnect', () => {
console.log('Socket断开连接');
socketConnected.value = false;
airPortStoreVal.setflyLog({
type: 'info',
title: 'Socket连接',
content: 'Socket服务断开连接'
});
});
socket.value.on('task_status_update', (data) => {
console.log('任务状态更新:', data);
airPortStoreVal.setflyLog({
type: 'info',
title: '任务状态更新',
content: `任务 ${data.task_id} 状态: ${data.status}`
});
});
socket.value.on('inference_result', (data) => {
console.log('推理结果:', data);
airPortStoreVal.setflyLog({
type: 'info',
title: '推理结果',
content: `任务 ${data.task_id} 模型 ${data.model_id} 推理完成`
});
});
socket.value.on('system_notification', (data) => {
console.log('系统通知:', data);
airPortStoreVal.setflyLog({
type: data.type === 'error' ? 'error' : 'info',
title: '系统通知',
content: data.message
});
});
socket.value.on('connect_error', (error) => {
console.error('Socket连接错误:', error);
socketConnected.value = false;
airPortStoreVal.setflyLog({
type: 'error',
title: 'Socket连接',
content: `Socket服务连接失败: ${error.message}`
});
});
} catch (error) {
console.error('连接Socket服务失败:', error);
airPortStoreVal.setflyLog({
type: 'error',
title: 'Socket连接',
content: `连接Socket服务失败: ${error.message}`
});
}
};
// 断开Socket连接
const disconnectSocket = () => {
if (socket.value) {
socket.value.disconnect();
socket.value = null;
socketConnected.value = false;
console.log('Socket连接已断开');
}
};
// 订阅任务
const subscribeTask = (taskId: string) => {
if (socket.value && socketConnected.value) {
socket.value.emit('subscribe_task', { task_id: taskId }, (response: any) => {
console.log('订阅任务响应:', response);
if (response.status === 'subscribed') {
airPortStoreVal.setflyLog({
type: 'info',
title: '任务订阅',
content: `成功订阅任务 ${taskId}`
});
} else {
airPortStoreVal.setflyLog({
type: 'error',
title: '任务订阅',
content: `订阅任务失败: ${response.message}`
});
}
});
} else {
airPortStoreVal.setflyLog({
type: 'error',
title: '任务订阅',
content: 'Socket未连接无法订阅任务'
});
}
};
// 处理任务提交事件
const handleTaskSubmitted = (data: any) => {
console.log('接收到任务提交事件:', data);
if (data.success && data.taskId) {
// 连接Socket服务
connectSocket();
// 延迟订阅任务确保Socket连接成功
setTimeout(() => {
subscribeTask(data.taskId);
}, 1000);
// 显示飞行日志面板
logVisible.value = true;
// 记录任务提交成功日志
airPortStoreVal.setflyLog({
type: 'success',
title: '任务提交',
content: `智能巡检任务提交成功已连接Socket服务监听任务状态`
});
}
};
// 指点飞行数据
const flyToData = reactive({
fly_to_id: buildGUID(),
max_speed: 10,
points: [{}],
});
// 直播可见性
const liveVisible = ref(false);
// 巡检可见性
const patrolVisible = ref(false);
// 飞行日志滚动容器引用
const myDiv = ref(null);
// 监听飞行日志变化,自动滚动到底部
watch(
() => flyOptions.value,
() => {
nextTick(() => {
if (myDiv.value) {
myDiv.value.scrollTop = myDiv.value.scrollHeight;
}
});
},
{ deep: true }
);
// 视频播放器实例
let player;
// 播放器尺寸
const width = ref(300);
const height = ref(200);
// MQTT连接状态
const connected = ref(false);
const connectCallback = () => {
connected.value = true;
};
// 机场MQTT主题
const topicUrl = computed(() => {
return 'thing/product/' + airPortInfo.sn + '/osd';
});
// 无人机MQTT主题
const topicUAVUrl = computed(() => {
return 'thing/product/' + uavInfo.sn + '/osd';
});
// 机场选择变更处理
const changeSelect = async (value?: any) => {
if (value) {
changeSelectValue.value = value;
airportName.value = value.airportName;
}
// 订阅机场消息
const topicUrl = 'thing/product/' + airPortInfo.sn + '/osd';
clientSubscribe(topicUrl, { qos: 1 });
// 获取机场信息
getDronePortInfoFromRedis({ key: value.airport }).then((res) => {
const rs = JSON.parse(res);
// 设置机场位置信息
airPort.value.latitude = rs.lat;
airPort.value.longitude = rs.lng;
airPort.value.height = rs.height;
// 如果设备在线,查询任务信息
if (rs.device_online_status == 1) {
getLastHandFlyTask({ sn: airPortInfo.sn }).then((res) => {
if (res.Status == 1 || res.Status == 6) {
// 设置任务ID
airPortStoreVal.setTaskId(res.Id);
// 获取操作日志
getOperationLogList({ taskid: res.Id, page: 1, limit: 1000 }).then((res) => {
const optiosns = [];
res.items.forEach((element) => {
optiosns.push(JSON.parse(element.data));
});
// 更新飞行日志
airPortStoreVal.setflyLog(optiosns, 'replace');
flyOptions.value = optiosns;
});
}
});
}
});
// 订阅飞行器消息
const topicUAVUrl = 'thing/product/' + uavInfo.sn + '/osd';
clientSubscribe(topicUAVUrl, { qos: 1 });
};
// 打开飞行器控制盒
const openUavBox = () => {
getDronePortInfoFromRedis({ key: airPortInfo.sn }).then((res) => {
const rs = JSON.parse(res);
if (rs.device_online_status == 1) {
// 显示飞行器控制盒
uavBoxVisible.value = true;
// 调整地图高度
nextTick(() => {
mapHeight.value = uavBox.value.clientHeight;
console.log(uavBox.value.clientHeight);
});
} else {
// 提示飞行器未开机
createMessage.warning('飞行器未开机');
}
});
};
// 地图点击定位
const flyToThere = (e) => {
// 存储位置信息
locationVal.value.lat = e._lat;
locationVal.value.lng = e._lng;
locationVal.value.alt = e._alt;
// 触发位置获取事件
EventBus.emit('obtainTheLocation', locationVal.value);
};
// 获取几何类型
function getGeomType(area) {
let geom = area.geom;
let radiusFlag = area.properties.indexOf('radius') > -1 ? true : false;
// 点类型
if (geom.indexOf('POINT') > -1 && !radiusFlag) {
return 'Point';
}
// 线类型
if (geom.indexOf('LINESTRING') > -1 && !radiusFlag) {
return 'Polyline';
}
// 多边形类型
if (geom.indexOf('POLYGON') > -1 && !radiusFlag) {
return 'Polygon';
}
// 圆形类型
if (geom.indexOf('POINT') > -1 && radiusFlag) {
return 'Circle';
}
}
// 飞行任务ID
const flight_id = ref(uuid(14, 14));
// 一键起飞
const takeOff = () => {
createConfirm({
iconType: 'info',
title: '一键起飞',
content: '确定要一键起飞吗?',
onOk: async () => {
// 显示飞行日志
logVisible.value = true;
// 起飞参数
const data = {
flight_id: flight_id.value,
security_takeoff_height: 100, // 安全起飞高度
rth_altitude: 115, // 返航高度
rth_mode: 1, // 返航模式
max_speed: 10, // 最大速度
commander_flight_mode: 1, // 指挥官飞行模式
rc_lost_action: 2, // 遥控器丢失动作
commander_mode_lost_action: 1, // 指挥官模式丢失动作
commander_flight_height: 115.0, // 指挥官飞行高度
flight_safety_advance_check: 1, // 飞行安全预先检查
target_height: Number(airPort.value.height) + 100, // 目标高度
target_latitude: Number(airPort.value.latitude), // 目标纬度
target_longitude: Number(airPort.value.longitude), // 目标经度
};
// 适配不同类型的机场
if (typeId == 'Dock 1') {
delete data.rth_mode;
delete data.flight_safety_advance_check;
}
// 构建请求参数
const querys = {
bid: buildGUID(),
data: data,
tid: buildGUID(),
timestamp: new Date().getTime(),
method: 'takeoff_to_point',
};
console.log(querys);
// 检查设备状态
getDronePortInfoFromRedis({ key: airPortInfo.sn }).then((res) => {
const rs = JSON.parse(res);
if (rs.device_online_status == 1) {
// 设备已开机,不能起飞
airPortStoreVal.setflyLog({
type: 'info',
title: '飞行器已开机',
content: '不能进行起飞操作',
});
} else {
// 检查是否可以飞行
isCanFly({
sn: airPortInfo.sn,
dock: airPortInfo.getTypeId,
userid: userInfo.id,
}).then((res) => {
console.log(res);
if (res.status) {
// 发送起飞指令
servicesTopic(querys);
services_replyTopic();
} else {
// 起飞被阻止
airPortStoreVal.setflyLog({
type: 'error',
title: '一键起飞阻止',
content: res.msg,
});
}
});
}
});
},
});
};
// 直播任务ID
const startTid = buildGUID();
// 获取并显示机场直播
const getRtmpList = () => {
// 显示直播面板
liveVisible.value = true;
liveCode.value = airPortInfo.sn;
// 查询直播流列表
axios.get(live_info.getUrl + 'api/v1/streams/').then((res) => {
console.log(res);
if (res.data.streams.length > 0) {
console.log(res.data.streams);
// 查找当前机场的直播流
const index = res.data.streams.findIndex((item) => item.url === `/live/` + airPortInfo.sn);
console.log(index);
if (index == -1) {
// 未找到直播流,启动直播
startLiveFun();
} else {
// 检查直播流是否活跃
if (res.data.streams[index].publish.active) {
// 播放直播
player.src(live_info.url + liveCode.value + '.flv');
player.play();
// 记录日志
airPortStoreVal.setflyLog({
type: 'success',
title: airPortInfo.name + '直播',
content: '已开启',
});
} else {
// 直播流未活跃,启动直播
startLiveFun();
}
}
} else {
// 无直播流,启动直播
startLiveFun();
}
});
};
// 启动直播
const startLiveFun = () => {
// 构建直播启动参数
const querys = {
bid: buildGUID(),
method: 'live_start_push',
tid: startTid,
timestamp: new Date().getTime(),
data: {
url_type: 1, // 0 = 自适应;如需 RTMP 改为 1GB28181 为 3WebRTC 为 4
url: live_info.rtmp + liveCode.value,
video_id: airPortInfo.video_id,
video_quality: 3, // 0=自适应1=流畅2=标清3=高清4=超清
},
};
console.log(querys);
// 发送直播启动指令
servicesTopic(querys);
services_replyTopic();
};
// 重新加载直播
const reloadLive = () => {
player.src(live_info.url + liveCode.value + '.flv');
player.play();
};
// 停止直播任务ID
const stopTid = buildGUID();
// 关闭直播
const closeLive = () => {
// 隐藏直播面板
liveVisible.value = false;
};
// 打开指点飞行面板
const flyTobtn = () => {
flyToVisible.value = true;
EventBus.emit('flyTobtn', '');
};
// 执行指点飞行
const execute = () => {
// 检查是否选择了目标点
if (
flyToData.points[0].latitude == null ||
flyToData.points[0].longitude == null ||
flyToData.points[0].height == null
) {
createMessage.warning('请先选择目标点');
return;
}
// 转换坐标类型为数字
flyToData.points[0].latitude = Number(flyToData.points[0].latitude);
flyToData.points[0].longitude = Number(flyToData.points[0].longitude);
flyToData.points[0].height = Number(flyToData.points[0].height);
// 构建指点飞行参数
const querys = {
bid: buildGUID(),
data: flyToData,
tid: buildGUID(),
timestamp: new Date().getTime(),
method: 'fly_to_point',
};
console.log(querys);
// 发送指点飞行指令
servicesTopic(querys);
};
// 停止指点飞行
const stop = () => {
// 构建停止指令参数
const querys = {
bid: buildGUID(),
data: {},
tid: buildGUID(),
timestamp: new Date().getTime(),
method: 'fly_to_point_stop',
};
console.log(querys);
// 发送停止指令
servicesTopic(querys);
};
// 打开智能巡检面板
const patrolbtn = () => {
patrolVisible.value = true;
};
// 关闭智能巡检面板
const changePatrol = () => {
patrolVisible.value = false;
};
// 监听机场选择变化,加载工作区域数据
watch(
() => changeSelectValue.value,
async () => {
// 获取工作区域列表
allAreaDataList.value = await GetWorkAreaList({
workspaceid: changeSelectValue.value.project,
});
// 处理工作区域数据
if (allAreaDataList.value.length > 0) {
allAreaDataList.value.forEach((area, index) => {
// 转换WKT为GeoJSON
let geomjson = WktToGeojson(area.geom);
// 更新区域数据
area = {
...area,
properties: JSON.parse(area.properties),
geomtype: getGeomType(area),
coordinates: geomjson.coordinates,
};
allAreaDataList.value[index] = area;
});
}
},
);
// 方向控制变化处理
const changeDRC = (val) => {
console.log(val);
};
// 键盘事件超时器
const timeout = ref();
const timeoutUp = ref();
// 键盘按下事件处理
const handleKeyDown = (event) => {
clearTimeout(timeout.value);
timeout.value = setTimeout(() => {
console.log('handleKeyDown', event.key);
changeDRC(event.key);
}, 500);
};
// 键盘释放事件处理
const handleKeyUp = (event) => {
clearTimeout(timeoutUp.value);
timeoutUp.value = setTimeout(() => {
console.log('handleKeyUp', event.key);
changeDRC('');
}, 500);
};
// 组件挂载时初始化
onMounted(() => {
console.log('index--onMounted');
console.log(getClient());
// 监听窗口大小变化,调整地图高度
window.addEventListener('resize', function () {
const pageHeight = document.documentElement.clientHeight;
console.log(pageHeight);
mapHeight.value = document.documentElement.clientHeight - 124;
});
// 初始化MQTT连接
if (!getClient() || !getClient().connected) {
createConnection(connectCallback);
}
// 监听键盘事件
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
// 初始化视频播放器
player = TCPlayer('player-container-id', {
sources: [
{
src: live_info.url + liveCode.value + '.flv', // 播放地址
},
],
licenseUrl: live_info.url + liveCode.value + '.flv', // license 地址,必传
autoplay: true, // 是否自动播放
});
// 监听位置获取事件
EventBus.on('obtainTheLocation', (val: any) => {
flyToData.points[0].latitude = val.lat.toFixed(6);
flyToData.points[0].longitude = val.lng.toFixed(6);
flyToData.points[0].height = val.alt.toFixed(2);
});
// 监听MQTT消息
setTimeout(() => {
getClient().on('message', (topic, message) => {
const rs = JSON.parse(message);
const rsData = rs.data;
// 处理机场消息
if (topic === topicUrl.value) {
if (rsData.flighttask_step_code) {
airporOsdVal.value.flighttask_step_code = rsData.flighttask_step_code;
}
if (rsData.temperature) {
airporOsdVal.value.temperature = rsData.temperature;
}
if (rsData.rainfall) {
airporOsdVal.value.rainfall = rsData.rainfall;
}
if (rsData.wind_speed) {
airporOsdVal.value.wind_speed = rsData.wind_speed;
}
if (rsData.network_state && rsData.network_state.rate) {
airporOsdVal.value.rate = rsData.network_state.rate;
}
}
// 处理无人机消息
if (topic == topicUAVUrl.value) {
if (rsData.latitude && rsData.longitude) {
uavTrack.value = rs.data;
}
}
// 处理一键起飞消息
if (rs.method == 'takeoff_to_point') {
console.log('一键起飞');
console.log(rs);
if (rsData.result == 0) {
// 保存飞行任务
saveHandFlyTask({
flightId: flight_id.value,
workspaceId: airPortStoreVal.getProject,
sn: airPortInfo.sn,
}).then((res) => {
if (res) {
airPortStoreVal.setTaskId(res.taskId);
// 记录起飞成功日志
airPortStoreVal.setflyLog({
type: 'success',
title: '一键起飞',
content: '成功',
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
// 延迟记录起飞中日志
setTimeout(() => {
airPortStoreVal.setflyLog({
type: 'success',
title: '一键起飞',
content: '正在起飞,请稍后',
});
}, 500);
}
});
} else {
// 记录起飞失败日志
airPortStoreVal.setflyLog({
type: 'error',
title: '一键起飞',
content: '失败,' + errorName(rsData.result),
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
}
}
// 处理机场直播消息
if (rs.method == 'live_start_push' && rs.tid == startTid) {
if (rsData.result == 0) {
// 开始直播
player.src(live_info.url + liveCode.value + '.flv');
player.play();
// 记录直播成功日志
airPortStoreVal.setflyLog({
type: 'success',
title: airPortInfo.name + '开始直播',
content: '成功',
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
} else if (rsData.result == 513003) {
// 直播已开启
player.src(live_info.url + liveCode.value + '.flv');
player.play();
airPortStoreVal.setflyLog({
type: 'success',
title: airPortInfo.name + '直播',
content: '已开启',
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
} else {
// 记录直播失败日志
airPortStoreVal.setflyLog({
type: 'error',
title: airPortInfo.name + '开始直播',
content: '失败,' + errorName(rsData.result),
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
}
} else if (rs.method == 'live_stop_push' && rs.tid == stopTid) {
if (rs.data.result == 0) {
// 记录停止直播成功日志
airPortStoreVal.setflyLog({
type: 'success',
title: airPortInfo.name + '停止直播',
content: '成功',
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
} else {
// 记录停止直播失败日志
airPortStoreVal.setflyLog({
type: 'error',
title: airPortInfo.name + '停止直播',
content: '失败,' + errorName(rs.data.result),
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
}
}
// 处理指点飞行消息
if (rs.method == 'fly_to_point') {
if (rs.data.result == 0) {
// 记录指点飞行成功日志
airPortStoreVal.setflyLog({
type: 'success',
title: '指点飞行',
content: '成功',
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
EventBus.emit('closeTranslation', null);
// 修改云台的角度至最下方
const querys = {
bid: buildGUID(),
method: 'camera_screen_drag',
tid: buildGUID(),
timestamp: new Date().getTime(),
data: {
payload_index: uavInfo.camera_index,
locked: true,
pitch_speed: -180,
yaw_speed: 0,
},
};
servicesTopicReize(querys);
} else {
// 记录指点飞行失败日志
airPortStoreVal.setflyLog({
type: 'error',
title: '指点飞行',
content: '失败,' + errorName(rs.data.result),
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
}
}
// 处理结束指点飞行消息
if (rs.method == 'fly_to_point_stop') {
if (rs.data.result == 0) {
// 记录结束指点飞行成功日志
airPortStoreVal.setflyLog({
type: 'success',
title: '结束指点飞行',
content: '成功',
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
} else {
// 记录结束指点飞行失败日志
airPortStoreVal.setflyLog({
type: 'error',
title: '结束指点飞行',
content: '失败,' + errorName(rs.data.result),
mark: rs.timestamp
? rs.method + '-' + rs.timestamp
: rs.method + '-' + new Date().getTime(),
});
}
}
});
}, 1000);
});
// 组件卸载时清理
onUnmounted(() => {
// 取消服务订阅
unServices_replyTopic();
// 清理相机设置
airPortStoreVal.setCamera('type', null);
airPortStoreVal.setCamera('name', null);
});
</script>
<style lang="less" scoped>
.flight-control {
background: #00152a;
.flight-control-top {
width: 100%;
background: #10203a;
height: 42px;
display: flex;
align-items: center;
font-size: 12px;
.airport-name {
color: #fff;
margin-left: 10px;
img {
width: 16px;
}
}
.airport-status {
margin-left: 10px;
padding: 4px 6px;
color: #f2762d;
border-radius: 20px;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
0px 7px 6px 0px rgba(28, 29, 34, 0.06),
inset 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
border: 1px solid #f2762d;
}
.airport-live {
margin-left: 10px;
padding: 4px 6px;
color: #fff;
border-radius: 20px;
background: #0377f6;
cursor: pointer;
}
.airport-info {
margin-left: 10px;
color: #fff;
.airport-info-title {
display: inline-block;
margin: 0 6px;
opacity: 0.6;
}
img {
width: 12px;
}
}
}
}
.flight-contain {
position: relative;
margin-top: 2px;
display: flex;
overflow: auto;
.uavbox-open {
position: absolute;
right: 0;
top: 0;
background: #10203a;
color: #fff;
z-index: 2;
padding: 6px;
cursor: pointer;
img {
width: 20px;
}
}
.map-container {
// width: calc(100% - 44px);
// height: 90vh;
}
.map-container-content {
width: 100%;
height: 100%;
}
.uav-box {
width: 62vw;
}
}
.fly-log {
position: absolute;
top: 20px;
left: 20px;
width: 15vw;
height: 50vh;
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
border: 1px solid #3662a5;
background: #00152a;
.fly-log-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
color: #fff;
border-bottom: 1px solid #4e5778;
span:last-child {
font-size: 20px;
cursor: pointer;
}
}
.fly-log-content {
width: 90%;
height: 40vh;
box-shadow:
0px 10px 30px 0px rgba(0, 0, 6, 0.15),
0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
border: 1px solid #3662a5;
margin: 20px auto;
background: #10203a;
padding: 10px 12px;
color: #fff;
.fly-status {
color: #868e9b;
font-size: 12px;
margin: 10px 0 20px 0;
}
.log-opstions {
font-size: 14px;
height: 29vh;
overflow: scroll;
}
.log-item {
padding: 5px 0;
.item-desc {
font-size: 14px;
color: #fff;
margin-left: 10px;
}
}
.MinusCircleFilled {
color: #737d8c;
font-size: 16px;
.item-desc {
color: #737d8c;
}
}
.CheckCircleFilled {
font-size: 16px;
color: #1aa053;
}
.CloseCircleFilled {
font-size: 16px;
color: #0e5528;
}
}
}
.fly-to-form {
position: absolute;
left: 16.6vw;
bottom: 90px;
width: 17vw;
height: 34vh;
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
border: 1px solid #3662a5;
background: #00152a;
color: #fff;
.fly-to-title {
display: flex;
align-items: center;
justify-content: space-between;
.title-name {
margin: 10px 0 0 10px;
}
.title-icon {
border: 1px solid #3662a5;
border-top: none;
border-right: none;
border-radius: 6px;
width: 26px;
height: 26px;
text-align: center;
line-height: 26px;
cursor: pointer;
}
}
.form-desc {
color: #f2762d;
font-size: 12px;
padding: 14px;
display: inline-block;
}
.form-content {
padding: 10px 16px;
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
div {
background: #263f66;
width: 60%;
border-radius: 6px;
border: 1px solid #3662a5;
height: 34px;
}
input {
background: #263f66;
width: 140px;
text-align: right;
color: #fff;
font-size: 12px;
border: none;
}
::v-deep .ant-input-number,
.ant-input-number-group-wrapper {
background: none;
width: 260px !important;
text-align: right;
color: #fff;
font-size: 12px;
}
::v-deep .ant-input-number-group-wrapper {
input {
background: none;
width: 260px !important;
text-align: right;
color: #fff;
font-size: 12px;
}
}
}
}
.fly-to-button {
display: flex;
align-items: center;
justify-content: space-around;
span {
width: 40%;
color: '#fff';
height: 34px;
border-radius: 6px;
text-align: center;
line-height: 34px;
cursor: pointer;
}
span:first-child {
background: rgba(207, 39, 48, 0.6);
}
span:last-child {
background: #0377f6;
}
}
}
.airport-live-contain {
position: absolute;
left: 16.6vw;
top: 20px;
width: 17vw;
height: 26vh;
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
border: 1px solid #3662a5;
background: #00152a;
color: #fff;
.live-title {
display: flex;
align-items: center;
justify-content: flex-end;
img {
width: 16px;
cursor: pointer;
}
span {
width: 30px;
height: 30px;
border: 1px solid #3662a5;
text-align: center;
}
}
}
.player {
display: flex;
justify-content: center;
align-items: center;
#player-container-id-live {
margin: 0 auto;
}
}
.patrol-btn {
position: absolute;
left: 32vw;
top: 4px;
width: 16vw;
height: 59vh;
box-shadow: 0px 10px 30px 0px rgba(0, 0, 6, 0.15);
border-radius: 6px;
border: 1px solid #3662a5;
background: #00152a;
color: #fff;
}
</style>
<style scoped>
body :global(.ant-message-notice) {
text-align: right;
}
</style>