无人机系统

This commit is contained in:
Teo
2025-05-14 18:31:25 +08:00
parent c92f2091d9
commit 3e67c83ced
232 changed files with 16610 additions and 7 deletions

View File

@ -7,6 +7,10 @@ VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = 'http://192.168.110.8:8899'
# 无人机接口地址
VITE_APP_BASE_DRONE_API = 'http://192.168.110.8:9136'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'

View File

@ -210,6 +210,7 @@
</div>
</div>
<script src="./src/assets/sdk/YJEarth.min.js"></script>
<script src="./src/utils/reconnecting-websocket.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -41,6 +41,9 @@
"js-cookie": "3.0.5",
"js-md5": "^0.8.3",
"jsencrypt": "3.3.2",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"mitt": "^3.0.1",
"nprogress": "0.2.0",
"ol": "^10.5.0",

430
src/api/air/index.ts Normal file
View File

@ -0,0 +1,430 @@
import { request } from '../../utils/requset2.js';
// 开关空调
export function airConditionerModeSwitch(data) {
return request({
url: '/dj/remote/debug/airConditionerModeSwitch',
method: 'post',
data
});
}
// 生成航线文件
export function waypoint(data) {
return request({
url: '/dj/router/waypoint',
method: 'post',
data
});
}
// 创建任务 /dj/task/create
export function taskCreate(data) {
return request({
url: '/dj/missions/create',
method: 'post',
data
});
}
// 下发任务 /dj/flightTaskPrepare
export function flightTaskPrepare(data) {
return request({
url: '/dj/flightTaskPrepare',
method: 'post',
data
});
}
// 执行任务
export function flightTaskExecute(data) {
return request({
url: '/dj/flightTaskExecute',
method: 'post',
data
});
}
// 取消任务
export function flightTaskUndo(data) {
return request({
url: '/dj/flightTaskUndo',
method: 'post',
data
});
}
// 恢复航线
export function flightTaskRecovery(data) {
return request({
url: '/dj/flightTaskRecovery',
method: 'post',
data
});
}
// 恢复航线
export function flightTaskPause(data) {
return request({
url: '/dj/flightTaskPause',
method: 'post',
data
});
}
// 一键返航
export function returnHome(data) {
return request({
url: '/dj/returnHome',
method: 'post',
data
});
}
// 无参飞行指令
export function noDataFlight(data) {
return request({
url: '/dj/cmdFly/noDataFlight',
method: 'post',
data
});
}
// 有参飞行指令
export function hasDataFlight(data) {
return request({
url: '/dj/cmdFly/hasDataFlight',
method: 'post',
data
});
}
// 开启指令
export function drcModeEnte(data) {
return request({
url: '/dj/cmdFly/drcModeEnter',
method: 'post',
data
});
}
// 查询航线文件信息列表
export function listPaths(query) {
return request({
url: '/system/paths/list',
method: 'get',
params: query
});
}
// 删除航线文件信息
export function delPaths(id) {
return request({
url: '/system/paths/' + id,
method: 'delete'
});
}
// 查询任务列表
export function listTasks(query) {
return request({
url: '/system/tasks/list',
method: 'get',
params: query
});
}
// 查询无人机任务列表
export function listMissions(query) {
return request({
url: '/system/missions/list',
method: 'get',
params: query
});
}
// 删除无人机任务
export function delMissions(id) {
return request({
url: '/system/missions/' + id,
method: 'delete'
});
}
// 远程调试
export function remoteDebug(data) {
return request({
url: '/dj/remote/debug/service',
method: 'post',
data
});
}
// 增强图传
export function sdrWorkmodeSwitch(data) {
return request({
url: '/dj/remote/debug/sdrWorkmodeSwitch',
method: 'post',
data
});
}
// 查询视频设备列表
export function listInfo(query) {
return request({
url: '/system/info/list',
method: 'get',
params: query
});
}
// 登录
export function login(data) {
return request({
url: '/login',
headers: {
isToken: false,
repeatSubmit: false
},
method: 'post',
data: data
});
}
// 获取文件
export function getFileByflightId(data) {
return request({
url: '/dj/getFileByflightId',
method: 'post',
data
});
}
// 机场列表
export function droneList(data) {
return request({
url: '/system/drone/list',
method: 'get',
data
});
}
// 监听网关
export function gatewaysAdd(data) {
return request({
url: '/dj/gateways/add',
method: 'post',
data
});
}
// 关闭网关
export function gatewaysRemove(data) {
return request({
url: '/dj/gateways/remove',
method: 'delete',
data
});
}
// 添加航线
export function routerAdd(data) {
return request({
url: '/dj/router/add',
method: 'post',
data
});
}
// 复制航线 /dj/router/copy/{id}
export function routerCopy(id) {
return request({
url: '/dj/router/copy/' + id,
method: 'post'
});
}
// 重命名文件
export function routerRename(data) {
return request({
url: '/dj/router/rename',
method: 'post',
data
});
}
// 获取文件
export function takeoffList(data) {
return request({
url: '/system/takeoff/list',
method: 'get',
data
});
}
// 切换直播流
export function liveChange(data) {
return request({
url: '/dj/live/change',
method: 'post',
data
});
}
// 设置清晰度 /dj/live/quality
export function liveChangeQuality(data) {
return request({
url: '/dj/live/quality',
method: 'post',
data
});
}
// 开启直播
export function liveStart(data) {
return request({
url: '/dj/live/start',
method: 'post',
data
});
}
// 关闭直播
export function liveStop(data) {
return request({
url: '/dj/live/stop',
method: 'post',
data
});
}
// 静音模式开关
export function propertySet(data) {
return request({
url: '/property/set',
method: 'post',
data
});
}
// 直播相机切换
export function cameraChange(data) {
return request({
url: '/dj/live/camera/change',
method: 'post',
data
});
}
// 新建计划
export function taskAdd(data) {
return request({
url: '/dj/task/add',
method: 'post',
data
});
}
// 计划列表
export function taskList(data) {
return request({
url: '/dj/task/list',
method: 'get',
params: data
});
}
// 新暂停
export function flightTaskPauseNew(data) {
return request({
url: '/dj/task/flightTaskPause',
method: 'post',
data
});
}
// 新恢复
export function flightTaskRecoveryNew(data) {
return request({
url: '/dj/task/flightTaskRecovery',
method: 'post',
data
});
}
// 取消任务
export function flightTaskUndoNew(data) {
return request({
url: '/dj/task/flightTaskUndo',
method: 'post',
data
});
}
// 一键返航 post
export function returnHomeNew(data) {
return request({
url: '/dj/task/returnHome',
method: 'post',
data
});
}
// 删除
export function delMissionsNew(id) {
return request({
url: '/system/new/missions/' + id,
method: 'delete'
});
}
// drc飞行控制 /dj/cmdFly/droneControl
export function droneControl(data) {
return request({
url: '/dj/cmdFly/droneControl',
method: 'post',
data
});
}
// drc急停 /dj/cmdFly/droneEmergencyStop
export function droneEmergencyStop(data) {
return request({
url: '/dj/cmdFly/droneEmergencyStop',
method: 'post',
data
});
}
// 取消返航 /dj/returnHomeCancel
export function returnHomeCancel(data) {
return request({
url: '/dj/returnHomeCancel',
method: 'post',
data
});
}
// 导入航线
export function returnImport(data) {
return request({
url: '/dj/router/import',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
// DRC心跳
export function heartBeat(data) {
return request({
url: '/dj/cmdFly/heartBeat',
method: 'post',
data
});
}
// AI识别
export function aiInfo(data) {
return request({
url: '/minio/ai/info',
method: 'get',
params: data
});
}
// 当前最新任务
export function getLatest(data) {
return request({
url: '/dj/task/getLatest',
method: 'get',
params: data
});
}

View File

@ -0,0 +1,118 @@
import { request } from '../../utils/requset2';
// 获取文件列表
export function getFiles(data) {
return request({
url: '/minio/file',
method: 'get',
data,
params: data
});
}
// 获取ai文件列表
export function getAiFiles(data) {
return request({
url: '/minio/ai/file',
method: 'get',
data,
params: data
});
}
// 获取文件信息
export function getInfo(data) {
return request({
url: '/minio/info',
method: 'get',
data,
params: data,
timeout: 30000
});
}
// 获取ai文件信息
export function getAiInfo(data) {
return request({
url: '/minio/ai/info',
method: 'get',
data,
params: data,
timeout: 30000
});
}
// 下载文件
export function fileDownload(params, onProgress) {
return request({
url: '/minio/download',
method: 'post',
params,
responseType: 'blob',
timeout: 0,
// header: {
// "Content-Length": Buffer.byteLength(JSON.stringify(params)),
// },
onDownloadProgress: (progressEvent) => {
if (onProgress && typeof onProgress === 'function') {
const { loaded, total } = progressEvent;
console.log('progressEvent', progressEvent);
const percentage = Math.round((loaded / total) * 100); // 计算百分比
onProgress(percentage); // 调用传入的 onProgress 回调函数
}
}
});
}
// 删除文件
export function deleteFile(data) {
return request({
url: '/minio/deleteBatch',
method: 'delete',
data,
timeout: 0
});
}
// 删除ai文件
export function deleteAiFile(data) {
return request({
url: '/minio/ai/deleteBatch',
method: 'delete',
data,
timeout: 0
});
}
// 批量下载文件或目录
export function downloadBatch(data, onProgress) {
return request({
url: '/minio/downloadBatch',
method: 'post',
responseType: 'blob',
data,
timeout: 0,
onDownloadProgress: (progressEvent) => {
if (onProgress && typeof onProgress === 'function') {
const { loaded, total } = progressEvent;
console.log('progressEvent', progressEvent);
const percentage = Math.round((loaded / total) * 100); // 计算百分比
onProgress(percentage); // 调用传入的 onProgress 回调函数
}
}
});
}
// 获取某个目录下面的所有文件的URL列表
export function getUrlList(data) {
return request({
url: '/minio/urlList',
method: 'post',
data,
params: data,
timeout: 0
});
}
// 获取某个目录下面的所有文件的URL列表
export function getAiUrlList(data) {
return request({
url: '/minio/ai/urlList',
method: 'post',
data,
params: data,
timeout: 0
});
}

View File

@ -9,7 +9,8 @@ import {
projectNewsVO,
safetyInspectionVO,
projectNewsDetailVO,
weatherVO
weatherVO,
safetyDayVO
} from './type';
/**
* 查询大屏质量信息
@ -120,3 +121,16 @@ export const getweatherList = (id?: string): AxiosPromise<weatherVO[]> => {
method: 'get'
});
};
/**
* 查询项目安全天数
* @param query
* @returns {*}
*/
export const getSafetyDay = (id?: string): AxiosPromise<safetyDayVO> => {
return request({
url: '/project/project/safetyDay/' + id,
method: 'get'
});
};

View File

@ -86,3 +86,7 @@ export interface weatherVO {
sunRise: string;
sunSet: string;
}
export interface safetyDayVO {
safetyDay: string;
}

View File

@ -0,0 +1,57 @@
<template>
<video class="rtc_media_player" width="100%" height="100%" autoplay muted playsinline ref="rtcMediaPlayer"></video>
</template>
<script>
export default {
name: "SrsPlayer",
data() {
return {
webrtc: null, // Instance of SRS SDK
sessionId: null,
simulatorUrl: null,
playerVisible: false,
url: ""
};
},
methods: {
hideInfo() {
document.querySelector(".alert").style.display = "none";
},
async startPlay(url) {
this.url = url;
this.playerVisible = true;
if (this.webrtc) {
this.webrtc.close();
}
this.webrtc = new SrsRtcWhipWhepAsync();
this.$refs.rtcMediaPlayer.srcObject = this.webrtc.stream;
console.log('stream tracks:', this.webrtc.stream.getTracks());
try {
const session = await this.webrtc.play(url);
console.log('after play, stream tracks:', this.webrtc.stream.getTracks());
this.sessionId = session.sessionid;
this.simulatorUrl = `${session.simulator}?drop=1&username=${session.sessionid}`;
} catch (error) {
console.error("Error playing stream:", error);
this.webrtc.close();
this.playerVisible = false;
}
},
},
beforeDestroy() {
// Cleanup the SDK instance on component destroy
if (window[this.url]) {
window[this.url].close();
window[this.url] = null
}
},
};
</script>
<style scoped>
.rtc_media_player {
width: 100%;
height: 100%;
}
</style>

View File

@ -46,11 +46,21 @@ VXETable.config({
zIndex: 999999
});
//本地保存飞机配置
import { setLocal } from './utils';
setLocal('dockair', 'http://192.168.110.24:9136');
setLocal('aiurl', 'http://192.168.110.23:8000');
setLocal('host', '192.168.110.199');
setLocal('rtmpport', '1935');
setLocal('rtcport', '1985');
setLocal('dockSocketUrl', 'ws://192.168.110.8:9136/websocket');
// 修改 el-dialog 默认点击遮照为不关闭
/*import { ElDialog } from 'element-plus';
ElDialog.props.closeOnClickModal.default = false;*/
// **main.js**
import { vue3ScrollSeamless } from 'vue3-scroll-seamless';
import bus from './utils/bus';
const app = createApp(App);
@ -62,6 +72,7 @@ app.use(print);
app.use(i18n);
app.use(VXETable);
app.use(plugins);
app.use(bus);
app.component('vue3ScrollSeamless', vue3ScrollSeamless);
// 自定义指令
directive(app);

View File

@ -98,6 +98,11 @@ export const constantRoutes: RouteRecordRaw[] = [
path: '/gisHome',
component: () => import('@/views/gisHome/index.vue'),
hidden: true
},
{
path: '/drone',
component: () => import('@/views/drone/index.vue'),
hidden: true
}
];

View File

@ -0,0 +1,42 @@
// stores/useAirStore.js
import { getLocal } from '@/utils';
import { defineStore } from 'pinia';
export const useAirStore = defineStore('air', {
state: () => ({
debugValue: false, // 远程调试模式
HatchCoverValue: false, // 舱盖
AerocraftValue: false, // 飞行器
gateWay: JSON.parse(getLocal('airGateway')) || null, //
queryParams: {
target_height: 100, //目标点高度
rth_altitude: 100, //返航高度
commander_flight_height: 100, //指点飞行高度
rc_lost_action: 2, //遥控器失控动作
rth_mode: 1, //返航模式设置值
commander_mode_lost_action: 1, //指点飞行失控动作
commander_flight_mode: 1, //指点飞行模式设置值
max_speed: 12, //一键起飞的飞行过程中能达到的最大速度
security_takeoff_height: 100, //安全起飞高度
target_latitude: 0, //目标点纬度
target_longitude: 0, //目标点经度
height: 100
}
}),
actions: {
UP_debugValue(payload) {
localStorage.setItem('debugValue', JSON.stringify(payload));
},
UP_HatchCoverValue(payload) {
localStorage.setItem('HatchCoverValue', JSON.stringify(payload));
},
UP_AerocraftValue(payload) {
localStorage.setItem('AerocraftValue', JSON.stringify(payload));
},
SET_GATEWAY(gateWay) {
this.gateWay = gateWay;
localStorage.setItem('airGateway', JSON.stringify(gateWay));
}
}
});

View File

@ -1,3 +1,5 @@
import { getLocal } from '.';
const TokenKey = 'Admin-Token';
const tokenStorage = useStorage<null | string>(TokenKey, null);
@ -7,3 +9,27 @@ export const getToken = () => tokenStorage.value;
export const setToken = (access_token: string) => (tokenStorage.value = access_token);
export const removeToken = () => (tokenStorage.value = null);
export const getDockSocketUrl = () => {
return getLocal('dockSocketUrl');
};
export const getHost = () => {
return getLocal('host');
};
export const getRtmpPort = () => {
return getLocal('rtmpPort');
};
export const getRtcPort = () => {
return getLocal('rtcPort');
};
export const getDockAir = () => {
return getLocal('dockAir');
};
export const getAiUrl = () => {
return getLocal('aiUrl');
};

22
src/utils/bus.ts Normal file
View File

@ -0,0 +1,22 @@
import mitt from 'mitt';
const emitter = mitt();
export default {
install(app) {
// 发送事件
app.config.globalProperties.$sendChanel = (event, payload) => {
emitter.emit(event, payload);
};
// 监听事件(返回取消函数)
app.config.globalProperties.$recvChanel = (event, handler) => {
emitter.on(event, handler);
// 可选:返回解绑函数,方便使用
return () => {
emitter.off(event, handler);
};
};
}
};

View File

@ -1,5 +1,5 @@
import { parseTime } from '@/utils/ruoyi';
import $cache from '@/plugins/cache';
/**
* 表格时间格式化
*/
@ -15,6 +15,14 @@ export const formatDate = (cellValue: string) => {
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
};
export const setLocal = (key, value) => {
return $cache.local.set(key, value);
};
export const getLocal = (key) => {
return $cache.local.get(key);
};
/**
* @param {number} time
* @param {string} option
@ -316,3 +324,5 @@ export const removeClass = (ele: HTMLElement, cls: string) => {
export const isExternal = (path: string) => {
return /^(https?:|http?:|mailto:|tel:)/.test(path);
};
export { parseTime };

40
src/utils/moveDiv.ts Normal file
View File

@ -0,0 +1,40 @@
export function setMove(downId, moveID) {
let moveDiv = document.getElementById(downId)
moveDiv.style.cssText += ';cursor:move;'
let informationBox = document.getElementById(moveID)
const sty = (() => {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr];
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr];
}
})()
moveDiv.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - moveDiv.offsetLeft;
const disY = e.clientY - moveDiv.offsetTop;
// 获取到的值带px 正则匹配替换
let styL = sty(informationBox, 'left');
let styT = sty(informationBox, 'top');
// 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
} else {
styL = +styL.replace(/\px/g, '');
styT = +styT.replace(/\px/g, '');
}
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX;
let top = e.clientY - disY;
// 移动当前元素
informationBox.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
}

View File

@ -0,0 +1,382 @@
// MIT License:
//
// Copyright (c) 2010-2012, Joe Walnes
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/**
* This behaves like a WebSocket in every way, except if it fails to connect,
* or it gets disconnected, it will repeatedly poll until it successfully connects
* again.
*
* It is API compatible, so when you have:
* ws = new WebSocket('ws://....');
* you can replace with:
* ws = new ReconnectingWebSocket('ws://....');
*
* The event stream will typically look like:
* onconnecting
* onopen
* onmessage
* onmessage
* onclose // lost connection
* onconnecting
* onopen // sometime later...
* onmessage
* onmessage
* etc...
*
* It is API compatible with the standard WebSocket API, apart from the following members:
*
* - `bufferedAmount`
* - `extensions`
* - `binaryType`
*
* Latest version: https://github.com/joewalnes/reconnecting-websocket/
* - Joe Walnes
*
* Syntax
* ======
* var socket = new ReconnectingWebSocket(url, protocols, options);
*
* Parameters
* ==========
* url - The url you are connecting to.
* protocols - Optional string or array of protocols.
* options - See below
*
* Options
* =======
* Options can either be passed upon instantiation or set after instantiation:
*
* var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });
*
* or
*
* var socket = new ReconnectingWebSocket(url);
* socket.debug = true;
* socket.reconnectInterval = 4000;
*
* debug
* - Whether this instance should log debug messages. Accepts true or false. Default: false.
*
* automaticOpen
* - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
*
* reconnectInterval
* - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.
*
* maxReconnectInterval
* - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.
*
* reconnectDecay
* - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.
*
* timeoutInterval
* - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.
*
*/
// (function (global, factory) {
// if (typeof define === 'function' && define.amd) {
// define([], factory);
// } else if (typeof module !== 'undefined' && module.exports){
// module.exports = factory();
// } else {
// global.ReconnectingWebSocket = factory();
// }
// })(this, function () {
//
// if (!('WebSocket' in window)) {
// return;
// }
function ReconnectingWebSocket(url, protocols, options) {
// Default settings
var settings = {
/** Whether this instance should log debug messages. */
debug: false,
/** Whether or not the websocket should attempt to connect immediately upon instantiation. */
automaticOpen: true,
/** The number of milliseconds to delay before attempting to reconnect. */
reconnectInterval: 1000,
/** The maximum number of milliseconds to delay a reconnection attempt. */
maxReconnectInterval: 30000,
/** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
reconnectDecay: 1.5,
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
timeoutInterval: 2000,
/** The maximum number of reconnection attempts to make. Unlimited if null. */
maxReconnectAttempts: null,
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
binaryType: 'blob'
}
if (!options) {
options = {};
}
// Overwrite and define settings with options if they exist.
for (var key in settings) {
if (typeof options[key] !== 'undefined') {
this[key] = options[key];
} else {
this[key] = settings[key];
}
}
// These should be treated as read-only properties
/** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
this.url = url;
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
this.reconnectAttempts = 0;
/**
* The current state of the connection.
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
* Read only.
*/
this.readyState = WebSocket.CONNECTING;
/**
* A string indicating the name of the sub-protocol the server selected; this will be one of
* the strings specified in the protocols parameter when creating the WebSocket object.
* Read only.
*/
this.protocol = null;
// Private state variables
var self = this;
var ws;
var forcedClose = false;
var timedOut = false;
var eventTarget = document.createElement('div');
// Wire up "on*" properties as event handlers
eventTarget.addEventListener('open', function (event) {
self.onopen(event);
});
eventTarget.addEventListener('close', function (event) {
self.onclose(event);
});
eventTarget.addEventListener('connecting', function (event) {
self.onconnecting(event);
});
eventTarget.addEventListener('message', function (event) {
self.onmessage(event);
});
eventTarget.addEventListener('error', function (event) {
self.onerror(event);
});
// Expose the API required by EventTarget
this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
/**
* This function generates an event that is compatible with standard
* compliant browsers and IE9 - IE11
*
* This will prevent the error:
* Object doesn't support this action
*
* http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
* @param s String The name that the event should use
* @param args Object an optional object that the event will use
*/
function generateEvent(s, args) {
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent(s, false, false, args);
return evt;
};
this.open = function (reconnectAttempt) {
ws = new WebSocket(self.url, protocols || []);
ws.binaryType = this.binaryType;
if (reconnectAttempt) {
if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
return;
}
} else {
eventTarget.dispatchEvent(generateEvent('connecting'));
this.reconnectAttempts = 0;
}
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
}
var localWs = ws;
var timeout = setTimeout(function () {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
}
timedOut = true;
localWs.close();
timedOut = false;
}, self.timeoutInterval);
ws.onopen = function (event) {
clearTimeout(timeout);
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onopen', self.url);
}
self.protocol = ws.protocol;
self.readyState = WebSocket.OPEN;
self.reconnectAttempts = 0;
var e = generateEvent('open');
e.isReconnect = reconnectAttempt;
reconnectAttempt = false;
eventTarget.dispatchEvent(e);
};
ws.onclose = function (event) {
clearTimeout(timeout);
ws = null;
if (forcedClose) {
self.readyState = WebSocket.CLOSED;
eventTarget.dispatchEvent(generateEvent('close'));
} else {
self.readyState = WebSocket.CONNECTING;
var e = generateEvent('connecting');
e.code = event.code;
e.reason = event.reason;
e.wasClean = event.wasClean;
eventTarget.dispatchEvent(e);
if (!reconnectAttempt && !timedOut) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onclose', self.url);
}
eventTarget.dispatchEvent(generateEvent('close'));
}
var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
setTimeout(function () {
self.reconnectAttempts++;
self.open(true);
}, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
}
};
ws.onmessage = function (event) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
}
var e = generateEvent('message');
e.data = event.data;
eventTarget.dispatchEvent(e);
};
ws.onerror = function (event) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
}
eventTarget.dispatchEvent(generateEvent('error'));
};
}
// Whether or not to create a websocket upon instantiation
if (this.automaticOpen == true) {
this.open(false);
}
/**
* Transmits data to the server over the WebSocket connection.
*
* @param data a text string, ArrayBuffer or Blob to send to the server.
*/
this.send = function (data) {
if (ws) {
if (self.debug || ReconnectingWebSocket.debugAll) {
console.debug('ReconnectingWebSocket', 'send', self.url, data);
}
return ws.send(data);
} else {
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
}
};
/**
* Closes the WebSocket connection or connection attempt, if any.
* If the connection is already CLOSED, this method does nothing.
*/
this.close = function (code, reason) {
// Default CLOSE_NORMAL code
if (typeof code == 'undefined') {
code = 1000;
}
forcedClose = true;
if (ws) {
ws.close(code, reason);
}
};
/**
* Additional public API method to refresh the connection if still open (close, re-open).
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
*/
this.refresh = function () {
if (ws) {
ws.close();
}
};
}
/**
* An event listener to be called when the WebSocket connection's readyState changes to OPEN;
* this indicates that the connection is ready to send and receive data.
*/
ReconnectingWebSocket.prototype.onopen = function (event) {
};
/** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
ReconnectingWebSocket.prototype.onclose = function (event) {
};
/** An event listener to be called when a connection begins being attempted. */
ReconnectingWebSocket.prototype.onconnecting = function (event) {
};
/** An event listener to be called when a message is received from the server. */
ReconnectingWebSocket.prototype.onmessage = function (event) {
};
/** An event listener to be called when an error occurs. */
ReconnectingWebSocket.prototype.onerror = function (event) {
};
/**
* Whether all instances of ReconnectingWebSocket should log debug messages.
* Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
*/
ReconnectingWebSocket.debugAll = false;
ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
ReconnectingWebSocket.OPEN = WebSocket.OPEN;
ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
// return ReconnectingWebSocket;
// });

55
src/utils/requset2.ts Normal file
View File

@ -0,0 +1,55 @@
import $modal from '@/plugins/modal';
import axios from 'axios';
// 创建一个 Axios 实例并设置默认配置
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_DRONE_API,
timeout: 5000
});
// 请求拦截器,添加 token 到每个请求头
axiosInstance.interceptors.request.use(
(config) => {
let air = localStorage.getItem('airToken');
let token = '';
if (air) {
token = JSON.parse(air).access_token; // 假设 token 存储在 localStorage 中
}
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 处理消息显示
function showMessage(message, type = 'error') {
$modal.msg({
message: message,
type: type,
duration: 5 * 1000
});
}
export function request(params) {
// 如果有取消请求的需求,可以在此处添加 CancelToken
// const source = axios.CancelToken.source();
// params.cancelToken = source.token;
// 确保 URL 是完整的
// if (!params.url.startsWith(process.env.AIR)) {
// params.url = process.env.AIR;
// }
return axiosInstance(params).then((res) => {
if (res.data.code === 200) {
return res.data;
} else {
if (!res.data.code) return res.data;
showMessage(res.data.message || res.data.msg);
return Promise.reject(res.data);
}
});
}

View File

@ -0,0 +1,370 @@
<template>
<div class="air_advancedSet uav_box">
<div class="header">
<div class="logo">
<img class="title" src="../../images/airsetup.png" alt="" />
<div class="switch" @click="save" title="返回航线设置"></div>
</div>
</div>
<div class="content">
<!-- 拍照设置 -->
<div class="photograph">
<div class="content_header">拍照设置</div>
<div class="btns flex space_between">
<el-checkbox-group v-model="checkboxGroup1" :min="1" class="flex space_between">
<el-checkbox-button v-for="city in cities" :label="city.label" :key="city.value">{{ city.label
}}</el-checkbox-button>
</el-checkbox-group>
</div>
<div class="flex space_between">
<div>智能低光</div>
<el-switch v-model="value" active-color="rgba(0, 255, 255, 1)" inactive-color="rgba(0, 255, 255, 0.2)"
:active-value="true" :inactive-value="false">
</el-switch>
</div>
</div>
<!-- 爬升模式 -->
<div class="climb">
<el-radio-group v-model="radio1" class="flex space_between">
<el-radio-button v-for="item in climbs" :label="item"></el-radio-button>
</el-radio-group>
<div class="flex space_between">
<div class="left_img">
<svg-icon icon-class="climb" style="width: 240px;height: 160px;vertical-align: middle;"></svg-icon>
</div>
<div class="right_value">
<plusResduce :column="true" unit="m" :max="10000" :min="10"></plusResduce>
</div>
</div>
</div>
<!-- 航线高度模式 -->
<div class="routeAltitude">
<div class="content_header">拍照设置</div>
<el-radio-group v-model="radio2" class="flex space_between">
<el-radio-button v-for="item in routeAltitude" :label="item"></el-radio-button>
</el-radio-group>
<div class="flex space_between">
<div class="left_img">
<svg-icon icon-class="height1" style="width: 240px;height: 160px;vertical-align: middle;"></svg-icon>
</div>
<div class="right_value">
<plusResduce :column="true" unit="m"></plusResduce>
</div>
</div>
</div>
<!-- 全局航线速度 -->
<div class="routeSpeed">
<div class="content_header">全局航线速度</div>
<myProgress progressType="num" :max="15" :min="1"></myProgress>
</div>
<!-- 高级设置 -->
<div class="setting">
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item name="1">
<template slot="title">
高级设置
</template>
<el-divider></el-divider>
<myProgress progressType="num" :max="15" :min="1"></myProgress>
<!-- 航点类型 -->
<div class="subtitle">
<div class="f14 fw500">航点类型</div>
<el-select v-model="value2" placeholder="请选择">
<el-option v-for="item in waypointType" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<!-- 飞行器偏航角模式 -->
<div class="subtitle">
<div class="f14 fw500">飞行器偏航角模式</div>
<el-select v-model="value3" placeholder="请选择">
<el-option v-for="item in yawAngleMode" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<!-- 完成动作 -->
<div class="subtitle">
<div class="f14 fw500">完成动作</div>
<el-select v-model="value4" placeholder="请选择">
<el-option v-for="item in completeAction" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</div>
</template>
<script>
import plusResduce from "../component/plusReduce/index.vue";
import myProgress from "../component/progress/index.vue";
import {
waypointType,
yawAngleMode,
completeAction,
} from "../../utils/options";
export default {
name: "air_advancedSet",
components: { plusResduce, myProgress },
data() {
return {
value: false,
cities: [
{
label: "广角照片",
value: "wide",
},
{
label: "变焦照片",
value: "zoom",
},
{
label: "红外照片",
value: "ir",
},
],
checkboxGroup1: ["广角照片", "变焦照片", "红外照片"],
climbs: ["垂直爬升", "倾斜爬升"],
radio1: "垂直爬升",
routeAltitude: ["绝对高度", "相对起飞点高度", "相对地形高度"],
radio2: "绝对高度",
activeNames: [],
waypointType: waypointType,
yawAngleMode: yawAngleMode,
completeAction: completeAction,
value2: "2", //航点类型
value3: "1", //偏航角模式
value4: "1", //完成动作
};
},
mounted() { },
methods: {
handleChange(val) {
// console.log(val);
},
// 保存
save() {
this.$emit("save");
},
},
};
</script>
<style lang="scss">
.air_advancedSet {
left: 20px;
user-select: none;
.header {
margin-bottom: 10px;
.logo {
position: relative;
text-align: center;
.title {
width: 380px;
height: 32px;
}
}
.switch {
position: absolute;
top: 5px;
right: 10px;
width: 16px;
height: 16px;
z-index: 20;
background: url("../../images/switch.png");
background-size: cover;
cursor: pointer;
}
.switch:hover {
background: url("../../images/switchh.png");
background-size: cover;
}
}
.content {
height: calc(100% - 36px);
color: #fff;
overflow: auto;
padding-right: 5px;
.content_header {
font-family: "alimamashuheiti";
font-size: 14px;
font-weight: 700;
margin-bottom: 10px;
}
.photograph {
height: 120px;
padding: 15px 15px 0 15px;
background-color: rgba(0, 0, 0, 0.5);
}
.btns {
margin: 10px 0;
.el-checkbox-group {
width: 100%;
}
.el-checkbox-button:last-child .el-checkbox-button__inner,
.el-checkbox-button:first-child .el-checkbox-button__inner {
border-radius: 5px;
box-shadow: none;
}
.el-checkbox-button.is-checked .el-checkbox-button__inner {
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
color: rgba(0, 255, 255, 1);
}
.el-checkbox-button__inner {
background: rgba(0, 255, 255, 0.2);
color: rgba(0, 255, 255, 0.8);
border: 1px solid rgba(0, 255, 255, 0.5);
margin-right: 8px;
border-radius: 5px;
padding: 8px 15px;
box-shadow: none;
}
}
.climb,
.routeAltitude,
.routeSpeed {
margin-top: 15px;
padding: 15px 15px 0 15px;
background-color: rgba(0, 0, 0, 0.5);
.el-radio-group {
width: 100%;
}
.el-radio-button {
flex: 1;
}
.el-radio-button__inner {
padding: 8px 15px;
width: 100%;
background: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 0, 0, 0);
color: #fff;
}
.el-radio-button__orig-radio:checked+.el-radio-button__inner {
box-shadow: none;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
}
.el-radio-button:first-child .el-radio-button__inner,
.el-radio-button:last-child .el-radio-button__inner {
border-radius: 0;
}
.left_img {
padding: 15px 0;
}
}
.routeSpeed {
padding-bottom: 5px;
}
.setting {
margin-top: 15px;
padding: 10px;
background-color: rgba(0, 0, 0, 0.5);
position: relative;
z-index: 10;
.el-select {
width: 100%;
margin: 10px 0;
}
.el-input__inner {
background-color: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 255, 255, 0.5);
color: #fff;
}
.el-select .el-input__inner:focus {
border-color: rgba(0, 255, 255, 0.5);
}
// .el-select-dropdown__item.hover,
// .el-select-dropdown__item:hover,
.el-divider--horizontal {
margin: 10px 0;
}
.el-divider {
background: rgba(204, 204, 204, 0.2);
}
.el-collapse-item__header {
background-color: transparent !important;
font-family: "alimamashuheiti";
font-size: 14px;
}
.el-collapse-item__content {
color: #fff;
padding-bottom: 0;
}
.el-collapse-item__wrap {
border-bottom: none;
}
}
}
/* 修改垂直滚动条 */
.content::-webkit-scrollbar {
width: 8px;
/* 修改宽度 */
}
/* 修改滚动条轨道背景色 */
.content::-webkit-scrollbar-track {
background-color: rgba(0, 255, 255, 0.1);
border-radius: 10px;
}
/* 修改滚动条滑块颜色 */
.content::-webkit-scrollbar-thumb {
background-color: rgba(0, 255, 255, 0.3);
border-radius: 10px;
}
}
.el-select-dropdown {
background-color: rgba(0, 0, 0, 1);
border-color: rgba(0, 255, 255, 0.5);
}
.el-select-dropdown__item.hover,
.el-select-dropdown__item:hover {
background-color: rgba(0, 255, 255, 0.5);
}
.el-select-dropdown__item {
color: #fff;
}
.el-select-dropdown__item.selected {
color: rgba(0, 255, 255, 1);
}
</style>

View File

@ -0,0 +1,750 @@
import { waypoint } from "@/api/air";
export default {
data() {
return {
leftShow: false,
pzDisabled: false,
keywordShow: false,
setupShow: false,
selectId: null,
inputShow: false,
selectObj: {
label: "",
params: {},
type: "",
}, // 选中的动作
selectIndexF: null,
selectIndexS: null,
checkboxGroup1: ["visable", "ir"],
checkboxGroup2: ["wide", "ir", "zoom"],
startPoint: null,
pohoto: [
{
label: "可见光",
value: "visable",
},
{
label: "红外照片",
value: "ir",
},
],
pohoto1: [
{
label: "广角照片",
value: "wide",
},
{
label: "红外照片",
value: "ir",
},
{
label: "变焦照片",
value: "zoom",
},
],
dropdownList: [
{
label: "新增航点(前)",
value: 1,
type: "before",
},
{
label: "新增航点(后)",
value: 2,
type: "after",
},
{
label: "删除",
value: 3,
},
],
controls: [
{
url: require("../../../images/control_start.png"),
label: "开始录像",
value: "startRecord",
svg: "start_record",
params: {
fileSuffix: "123456",
payloadPositionIndex: 0,
useGlobalPayloadLensIndex: 1, //是否使用全局 1为使用全局 0不使用全局
payloadLensIndex: "visable,ir",
},
},
{
url: require("../../../images/control_suspend.png"),
label: "停止录像",
value: "stopRecord",
svg: "stop_record",
params: {
payloadPositionIndex: 0,
},
},
// {
// url: require("../../../images/control_Isochronous.png"),
// label: "开始等时间隔拍照",
// value: "takePhoto",
// svg: "interval_time",
// params: {
// payloadPositionIndex: 0,
// useGlobalPayloadLensIndex: 1,
// payloadLensIndex: "visable,ir",
// },
// actionTrigger: {
// actionTriggerType: "multipleTiming",
// actionTriggerParam: 10,
// },
// },
// {
// url: require("../../../images/control_equidistant.png"),
// label: "开始等距间隔拍照",
// value: "takePhoto",
// svg: "interval_distance",
// params: {
// payloadPositionIndex: 0,
// useGlobalPayloadLensIndex: 1,
// payloadLensIndex: "visable,ir",
// },
// actionTrigger: {
// actionTriggerType: "multipleDistance",
// actionTriggerParam: 10,
// },
// },
// {
// url: require("../../../images/control_interval.png"),
// label: "结束间隔拍照",
// value: "",
// svg: "interval_stop",
// },
{
url: require("../../../images/control_hover.png"),
label: "悬停",
value: "hover",
svg: "hover",
params: {
hoverTime: 10,
},
},
{
url: require("../../../images/control_aircraft.png"),
label: "飞行器偏航角",
value: "rotateYaw",
svg: "drone_yaw",
params: {
aircraftHeading: 0,
aircraftPathMode: "counterClockwise",
},
},
// {
// url: require("../../../images/control_yaw.png"),
// label: "云台偏航角",
// value: "gimbalRotate",
// svg: "action_gimbal_pitch",
// params: {
// gimbalHeadingYawBase: "north",
// gimbalRotateMode: "absoluteAngle",
// gimbalPitchRotateEnable: 1,
// gimbalPitchRotateAngle: 0,
// gimbalRollRotateEnable: 0,
// gimbalRollRotateAngle: 0,
// gimbalYawRotateEnable: 0,
// gimbalYawRotateAngle: 0,
// gimbalRotateTimeEnable: 0,
// gimbalRotateTime: 0,
// payloadPositionIndex: 0,
// },
// },
{
url: require("../../../images/control_pitch.png"),
label: "云台俯仰角",
value: "gimbalRotate",
svg: "action_gimbal_pitch",
params: {
gimbalHeadingYawBase: "north",
gimbalRotateMode: "absoluteAngle",
gimbalPitchRotateEnable: 1,
gimbalPitchRotateAngle: 0, // 变化的值
gimbalRollRotateEnable: 0,
gimbalRollRotateAngle: 0,
gimbalYawRotateEnable: 0,
gimbalYawRotateAngle: 0,
gimbalRotateTimeEnable: 0,
gimbalRotateTime: 0,
payloadPositionIndex: 0,
},
},
{
url: require("../../../images/control_photograph.png"),
label: "拍照",
value: "takePhoto",
svg: "take_photo",
params: {
payloadPositionIndex: 0,
useGlobalPayloadLensIndex: 1,
payloadLensIndex: "wide,zoom,ir",
},
},
{
url: require("../../../images/control_zoom.png"),
label: "相机变焦",
value: "zoom",
svg: "camera_zoom",
params: {
focalLength: 24,
isUseFocalFactor: 0,
payloadPositionIndex: 0,
},
},
{
url: require("../../../images/control_panorama.png"),
label: "全景拍照",
value: "panoShot",
svg: "pano_shot",
params: {
payloadPositionIndex: 0,
useGlobalPayloadLensIndex: 0,
payloadLensIndex: "visable,ir",
panoShotSubMode: "panoShot_360",
},
},
],
points: [],
obj: {
type: 0, // 航线类型
imageFormat: "wide,zoom,ir,narrow_band,visable", // 拍照配置
estimateTime: "", // 预计执行时间 sdk
photoNum: 0, // 航点拍照数量 sdk
waylineLen: "", // 航线长度 sdk
flag: "",
// 0-91-1
droneInfo: {
// 无人机的型号及其子类型
droneEnumValue: "91",
droneSubEnumValue: "1",
},
// 3-2-0
payloadInfo: {
// 负载类型和子类型以及负载的位置
payloadEnumValue: "81",
payloadPositionIndex: "0",
payloadSubEnumValue: "0",
},
placemarkList: [], // 经纬度
globalHeight: 120, // 相对起飞点高度
autoFlightSpeed: 10, // 全局航线速度
globalShootHeight: 60,
// remark: undefined,
// imageFormat: '',
takeOffSecurityHeight: 100, //安全起飞高度
globalTransitionalSpeed: 10, //飞向首航点速度
takeOffRefPoint: {
lng: undefined,
lat: undefined,
alt: undefined,
},
},
networkState: {},
length: 0,
time: 0,
gensui: true,
inputDiv: false,
};
},
mounted() {
this.createTongBu();
// let points = JSON.parse(localStorage.getItem("routePoints"));
let pointsAndTakeOff = JSON.parse(this.routeLine.points);
console.log(
"pointsAndTakeOff",
this.routeLine,
this.routeLine.normalHeight
);
// console.log("points", points);
if (pointsAndTakeOff) {
this.points = pointsAndTakeOff.points;
console.log(".takeOffRefPoint", pointsAndTakeOff);
// return;
let takeOff = pointsAndTakeOff.takeOffRefPoint.split(",");
this.obj.takeOffRefPoint = {
lng: takeOff[1] - 0,
lat: takeOff[0] - 0,
alt: takeOff[2] - 0,
};
console.log("this.obj.takeOffRefPoint", this.obj.takeOffRefPoint);
if (!window.airLine) {
console.log("this.points", this.routeLine.normalHeight);
this.renderRouter(this.points);
}
if (!window.PointEntity) {
this.renderStart(this.obj.takeOffRefPoint);
}
} else {
this.drawStart();
}
},
computed: {
getPhotoNum() {
let photoNum = 0;
this.points.forEach((item) => {
if (item.actions.length > 0) {
item.actions.forEach((item1) => {
if (item1.type == "takePhoto" || item1.type == "panoShot") {
photoNum++;
}
});
}
});
return photoNum;
},
},
methods: {
// 创建地球
createTongBu() {
window.EarthTongbu = new YJ.YJEarth("tongbuEarth");
YJ.Global.setKeyboardEventActive(window.EarthTongbu, false);
const obj = {
compass: false, //罗盘
legend: false, //比例尺
info: false, //信息栏
frame: false, //刷新率
};
let viewer = window.EarthTongbu.viewer;
YJ.Global.CesiumContainer(window.EarthTongbu, obj);
viewer.terrainProvider = new Cesium.createWorldTerrain();
let imageryProvider = new Cesium.ArcGisMapServerImageryProvider({
url:
"https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
});
viewer.imageryLayers.addImageryProvider(imageryProvider);
viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;
viewer.scene.screenSpaceCameraController.enableTilt = false;
},
// 跟随航线
genliuhangxian() {
this.gensui = !this.gensui;
if (this.gensui) {
this.selectObj.params.useGlobalPayloadLensIndex = 1;
} else {
this.selectObj.params.useGlobalPayloadLensIndex = 0;
}
},
// genliuhangxian
genliuhangxian2() {
this.selectObj.params.useGlobalPayloadLensIndex = 1;
},
// 保存成功
saveSuucess() {
this.inputDiv = false;
this.$message.success("保存成功");
},
// 删除成功
deleteSuucess() {
this.$message.success("删除成功");
this.inputShow = false;
this.selectObj.params.fileSuffix = "";
},
changeGroup() {
this.selectObj.params.payloadLensIndex = this.checkboxGroup1.join();
},
changeGroup1() {
this.selectObj.params.payloadLensIndex = this.checkboxGroup2.join();
},
interval_distance(value) {
let svg = this.selectObj.svg;
if (svg == "interval_time" || svg == "interval_distance") {
this.selectObj.actionTrigger.actionTriggerParam = value;
}
if (svg == "hover") {
console.log("hoverTime", this.selectObj.params.hoverTime);
this.selectObj.params.hoverTime = value;
}
},
//
hpr(value) {
let svg = this.selectObj.svg;
if (svg == "drone_yaw") {
this.selectObj.params.aircraftHeading = value;
let h = value == 0 ? 0 : value;
window.airLine.frustum.updateFrustumHPR(
h,
window.airLine.frustum.pitch,
window.airLine.frustum.roll,
true,
"alone"
);
}
if (svg == "action_gimbal_pitch") {
function mapValue(x, a1, b1, a2, b2) {
// 线性映射公式
return a2 + ((x - a1) / (b1 - a1)) * (b2 - a2);
}
// 原始区间和目标区间
const range1 = [35, -90]; // 原始区间
const range2 = [60, 180]; // 目标区间
let mappedValue = mapValue(
value,
range1[0],
range1[1],
range2[0],
range2[1]
);
this.selectObj.params.gimbalPitchRotateAngle = value;
let p = mappedValue == 0 ? 90 : mappedValue;
// return;
let hh = Cesium.Math.toDegrees(window.airLine.frustum.hpr.heading);
window.airLine.frustum.updateFrustumHPR(hh, p, 0, true, "alone");
}
if (svg == "camera_zoom") {
this.selectObj.params.focalLength = value * 24;
}
},
closeSetup() {
this.setupShow = false;
this.selectIndexS = null;
},
beforeHandleDropMenu(dropId, airLine) {
return {
dropId: dropId,
airLine: airLine,
};
},
handleDropMenu(command) {
const { dropId, airLine } = command;
if (dropId == 1 || dropId == 2) {
let draw = new YJ.Draw.DrawPoint(window.Earth1);
draw.start((err, params) => {
if (params != undefined) {
let position = {
...params,
actions: [],
};
if (dropId == 1) {
if (window.airLine) {
this.points.splice(this.selectId, 0, position);
window.airLine.addPoint(this.points);
}
}
if (dropId == 2) {
if (window.airLine) {
if (this.selectId == this.points.length - 1) {
this.points.push(position);
} else {
this.points.splice(this.selectId + 1, 0, position);
}
window.airLine.addPoint(this.points);
}
}
}
});
}
if (dropId == 3) {
const index = this.points.findIndex((obj) => obj.lng == airLine.lng);
this.points = this.points.filter((obj) => obj.lng !== airLine.lng);
if (window.airLine) {
window.airLine.delPosition(index, this);
}
}
},
// 新增航点
addPoint() {
let draw = new YJ.Draw.DrawPoint(window.Earth1);
draw.start((err, params) => {
if (params != undefined) {
let position = {
...params,
actions: [],
};
this.points.push(position);
window.airLine.addPoint(this.points);
this.save(() => {}, false);
}
});
},
// 选中
select(index) {
this.selectId = index;
let item = this.points[index];
let obj = item.actions[0];
if (obj) {
this.openSet(index, 0, obj);
} else {
this.setupShow = false;
this.selectIndexS = null;
}
if (window.airLine) {
//判断动作组中有value为rotateYaw、gimbalRotate时就不执行window.airLine.updateFrustumPosition(index);
let flag = item.actions.some(
(obj) => obj.value == "rotateYaw" || obj.value == "gimbalRotate"
);
if (!flag) {
window.airLine.updateFrustumPosition(index);
}
}
},
add() {},
more() {
this.leftShow = !this.leftShow;
},
// 打开高级设置
senior() {
this.save(() => {
this.$emit("seniorSet");
});
},
// 返回
back() {
if (this.points.length > 0) {
this.save(() => {
if (airLine) {
window.airLine.remove();
window.airLine = null;
}
if (window.PointEntity) {
window.PointEntity.remove();
window.PointEntity = null;
}
this.$emit("back");
}, false);
} else {
this.$emit("back");
}
this.$changeComponentShow(".bottomTabs", true);
},
// 绘制航线
draw() {
let draw = new YJ.Draw.DrawPolyline(window.Earth1, {
tipText: "请选择航点(左键确定,右键结束)",
});
draw.start((err, positions) => {
if (positions.length == 0) {
return;
}
positions.forEach((el) => {
el.actions = [];
return el;
});
this.points = [...positions];
this.renderRouter(positions);
});
},
// 绘制起飞点
drawStart() {
let draw = new YJ.Draw.DrawTakeOff(window.Earth1, {
tipText: "选择参考起飞点(左键确定)",
});
draw.start((err, params, flag) => {
if (params != undefined) {
if (!flag) {
this.renderStart(params);
this.obj.takeOffRefPoint = window.PointEntity.options.positions;
} else {
this.obj.takeOffRefPoint = window.airportEntity.options.positions;
}
this.draw();
}
});
},
// 渲染参考起飞点
renderStart(positions) {
let option = {
positions,
label: {
text: "参考起飞点",
fontSize: 20,
},
billboard: {
image: "/static/sdk/img/start.png",
},
};
window.PointEntity = new YJ.Obj.BillboardObject(window.Earth1, option);
window.PointEntity.picking = false;
window.PointEntity.onClick = () => {
setTimeout(() => {
window.PointEntity.positionEditing = true;
}, 100);
};
},
// 渲染航线
renderRouter(positions) {
console.log("globalPointHeight", this.routeLine.globalPointHeight);
let airLine = new YJ.Obj.newAirLine(
{
positions,
image: "/static/img/定位.png",
saveFun: this.save,
selectFun: this.select,
normalHeight: this.routeLine.globalPointHeight,
airHeight: this.routeLine.height,
},
window.Earth1.viewer,
window.EarthTongbu.viewer
);
window.airLine = airLine;
window.airLine.flyTo();
this.length = airLine.countLength();
this.time = airLine.countTime();
// console.log("airLine", airLine);
},
// 添加动作到航点
onClick(item) {
console.log("item", item);
// 添加动作时,
// console.log("this.selectId, item", this.selectId, item);
if (this.selectId === null) {
this.$message.warning("请选择航点,再进行动作添加");
return;
}
let selectItem = this.points[this.selectId];
selectItem.actions.push({
label: item.label,
type: item.value,
params: item.params,
svg: item.svg,
actionTrigger: item.actionTrigger,
});
// 打开当前动作设置
// this.openSet(this.selectId, selectItem.actions.length - 1, item);
// console.log("this.points", this.points);
},
// 点击动作图标
openSet(indexF, indexS, item) {
this.selectIndexF = indexF;
this.selectIndexS = indexS;
this.selectObj = item;
console.log("this.selectObj", this.selectObj);
if (
this.selectObj.type == "takePhoto" ||
this.selectObj.type == "startRecord"
) {
this.checkboxGroup2 = this.selectObj.params.payloadLensIndex.split(",");
if (this.selectObj.params.fileSuffix) {
this.inputDiv = false;
this.inputShow = true;
} else {
this.inputShow = false;
this.inputDiv = true;
}
}
this.setupShow = true;
},
// 删除动作
delAction() {
let curAction = this.points[this.selectIndexF];
curAction.actions.splice(this.selectIndexS, 1);
this.setupShow = false;
// console.log(
// "curActioncurActioncurActioncurAction",
// curAction,
// this.selectIndexS
// );
},
// 在保存之前判断每个点中得动作组,如果前一个动作组中有开始录像,后面的动作组中有全景拍照的动作,就提示用户该航线因为全景拍照的动作处于录像过程中,无法执行,并返回当前有问题的航点的索引
saveBefore() {
let flag = true;
let indexf = null;
this.points.forEach((item, index) => {
if (item.actions.length > 0) {
let startRecord = item.actions.find(
(obj) => obj.type == "startRecord"
);
if (startRecord) {
let nextAction = this.points[index + 1];
if (nextAction) {
let panoShot = nextAction.actions.find(
(obj) => obj.type == "panoShot"
);
if (panoShot) {
flag = false;
indexf = index + 1;
}
}
}
}
});
return {
flag: !flag,
index: indexf,
};
},
// 保存操作
save(cb = null, flag = true) {
let before = this.saveBefore();
if (before.flag) {
this.$message.warning(
`${before.index +
1}#航点的全景拍照动作处于录像过程中,无法执行。请删除该动作后再保存`
);
return;
}
let positions = window.airLine.getNewPositions();
let newPoints = [];
this.points.forEach((item1, index1) => {
let obj = {
actions: item1.actions,
};
positions.forEach((item, index) => {
if (index == index1) {
obj.lng = item.lng;
obj.lat = item.lat;
obj.alt = item.alt - this.routeLine.globalPointHeight;
obj.longitude = item.lng;
obj.latitude = item.lat;
obj.altitude =
item.alt -
this.routeLine.height +
this.routeLine.globalPointHeight;
}
});
newPoints.push(obj);
});
this.points = newPoints;
this.obj.placemarkList = newPoints;
this.obj.id = this.routeLine.id;
if (typeof this.obj.takeOffRefPoint == "object") {
this.obj.takeOffRefPoint =
this.obj.takeOffRefPoint.lat +
"," +
this.obj.takeOffRefPoint.lng +
"," +
this.obj.takeOffRefPoint.alt;
} else {
this.obj.takeOffRefPoint = this.obj.takeOffRefPoint;
}
let obj = {
points: newPoints,
takeOffRefPoint: this.obj.takeOffRefPoint,
};
if (!obj.points[0].lng) {
this.obj.points = [];
this.takeOffRefPoint = "";
window.PointEntity.remove();
window.PointEntity = null;
}
this.obj.points = JSON.stringify(obj);
waypoint(this.obj).then((res) => {
if (flag) {
this.$message.success("操作成功");
}
if (typeof cb == "function") {
cb();
}
});
},
// 航线航点移动
towardsLeft() {},
up() {},
towardsRight() {},
left() {},
after() {},
right() {},
},
};

View File

@ -0,0 +1,627 @@
<template>
<div class="air_list uav_box">
<div class="header">
<div class="logo">
<img class="title" src="../../images/airlist.png" alt="" />
<el-tooltip class="item" effect="dark" content="导入航线" placement="top">
<div class="importPath" @click="importPath"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="添加" placement="top">
<div class="add" @click="add"></div>
</el-tooltip>
</div>
<div class="search">
<el-row>
<el-col :span="17">
<el-input placeholder="请输入内容" v-model="queryParam.fileName">
<i slot="suffix" class="el-input__icon el-icon-search" @click="search"></i>
</el-input>
</el-col>
<el-col :span="7">
<div class="date flex">
<span style="margin-right: 5px">时间排序</span>
<div class="flex arrow">
<i class="el-icon-caret-top" @click="order(0)"></i>
<i class="el-icon-caret-bottom" @click="order(1)"></i>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
<el-divider></el-divider>
<div class="content">
<div v-for="item in airList" :key="item.id" class="air_item">
<div class="top flex">
<el-tooltip effect="dark" :content="item.fileName" placement="top-start">
<div class="text">
{{ item.fileName }}
</div>
</el-tooltip>
<div class="flex" style="align-items: center">
<div v-if="item.isImport == 'false'" class="edit" @click="edit(item)"></div>
<el-dropdown @command="handleDropMenu">
<div class="more_icon"></div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="beforeHandleDropMenu(drop.value, item)" v-for="drop in dropdownList" :key="drop.value">{{
drop.label
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="middle m10">
<img src="../../images/wurenji.png" alt="" />
<span>{{ item.deviceType }}</span>
</div>
<div class="bottom m10">
<span class="f14">更新时间</span>
<span class="f12">{{ parseTime(item.updatedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</div>
</div>
</div>
<div class="pagination"></div>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="1"
:pager-count="5"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
layout="total, sizes, prev, pager, next"
:total="total"
>
</el-pagination>
<myDialog ref="createHX" class="createHX" :title="title" @close="close">
<el-divider></el-divider>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="航线名称" prop="value1" style="margin-top: 10px; margin-bottom: 20px">
<el-input placeholder="请输入名称" v-model.trim="form.value1" clearable></el-input>
</el-form-item>
<el-form-item v-if="globalPointHeightShow" label="全局航线高度" prop="value2" style="margin-top: 10px; margin-bottom: 20px">
<el-input placeholder="请输入全局航线高度(2~3000)" v-model.trim.number="form.value2" clearable></el-input>
</el-form-item>
</el-form>
<div class="btns">
<el-button size="small" @click="submit">确定</el-button>
<el-button size="small" @click="cancel">取消</el-button>
</div>
</myDialog>
<input type="file" id="fileInput" style="display: none" accept=".kmz" @change="uploadFile" />
</div>
</template>
<script>
import myDialog from '../component/dialog/index.vue';
import { listPaths, routerAdd, routerRename, routerCopy, delPaths, listInfo, returnImport } from '@/api/air';
import { useAirStore } from '@/store/modules/drone';
import { parseTime } from '@/utils';
export default {
name: 'airList',
components: {
myDialog
},
data() {
return {
value: '',
form: {
value1: '',
value2: ''
},
rules: {
value1: [{ required: true, message: '请输入名称', trigger: 'blur' }],
value2: [
{ required: true, message: '请输入全局航线高度', trigger: 'blur', type: 'number' },
{ min: 2, max: 3000, message: '请输入2~3000之间的数字', trigger: 'blur', type: 'number' }
]
},
airList: [],
curId: '',
title: '创建新航线',
parseTime: parseTime,
queryParam: {
pageNum: 1,
pageSize: 10,
fileName: '',
order: 1
},
dropdownList: [
{
label: '重命名',
value: 1
},
{
label: '复制',
value: 2
},
{
label: '下载',
value: 3
},
{
label: '删除',
value: 4
}
],
gateWay: useAirStore().gateWay,
info: {},
networkState: {},
total: 0,
height: 100,
globalPointHeightShow: true
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getAirRouteList();
this.getList();
},
deep: true
}
},
mounted() {
this.getAirRouteList();
this.getList();
this.$recvChanel('websocketBus', (data) => {
if (data.businessType == 'osd3') {
this.networkState = { ...data.data };
}
});
},
methods: {
handleSizeChange(val) {
this.queryParam.pageSize = val;
this.getAirRouteList();
},
handleCurrentChange(val) {
this.queryParam.pageNum = val;
this.getAirRouteList();
},
uploadFile(event) {
let files = event.target.files;
if (files && files[0]) {
const file = files[0];
const formData = new FormData();
formData.append('file', file);
formData.append('flag', this.gateWay.gateway);
returnImport(formData).then((res) => {
if (res.code == 200) {
this.$message.success('上传成功');
this.getAirRouteList();
}
});
}
},
// 导入航线
importPath() {
document.getElementById('fileInput').click();
},
// 判断是否有地形
getList() {
listInfo({
pageNum: 1,
pageSize: 10,
gateway: this.gateWay.gateway,
type: '飞机'
}).then((res) => {
this.info = res.rows[0];
// console.log("this.info", this.info);
});
},
// 搜索
search() {
this.getAirRouteList();
},
// 获取航线列表数据
getAirRouteList() {
this.queryParam.flag = this.gateWay.gateway;
listPaths(this.queryParam).then((res) => {
let list = res.rows || [];
this.airList = list;
this.total = res.total;
});
},
beforeHandleDropMenu(dropId, airLine) {
return {
dropId: dropId,
airLine: airLine
};
},
handleDropMenu(command) {
// console.log("command", command);
const { dropId, airLine } = command;
if (dropId == 1) {
this.title = '重命名';
this.form.value1 = airLine.fileName;
this.curId = airLine.id;
this.globalPointHeightShow = false;
this.$refs.createHX.open();
}
if (dropId == 2) {
routerCopy(airLine.id).then((res) => {
if (res.code == 200) {
this.$message.success('复制成功');
this.getAirRouteList();
}
});
}
if (dropId == 3) {
if (airLine.fileUrl) {
let link = document.createElement('a');
link.style.display = 'none';
link.href = airLine.fileUrl;
// decodeURIComponent
link.setAttribute('download', airLine.fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link); //下载完成移除元素
} else {
this.$message.warning('请编辑后再下载');
}
}
if (dropId == 4) {
this.$confirm('此操作将永久删除航线数据,是否继续?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
delPaths(airLine.id).then(() => {
this.$message.success('删除成功');
if (window.airLine) {
window.airLine.remove();
window.airLine = null;
}
// 若当前项之前为选中状态清除
this.getAirRouteList();
});
})
.catch(() => {});
}
},
// 创建航线
add() {
this.title = '创建新航线';
this.globalPointHeightShow = true;
this.$refs.createHX.open();
},
// 升降序
order(order) {
this.queryParam.order = order;
this.getAirRouteList();
},
// 确认
submit() {
this.$refs['form'].validate((valid) => {
if (valid) {
if (this.title == '创建新航线') {
routerAdd({
filename: this.form.value1,
remark: this.info.remark,
flag: this.gateWay.gateway,
globalPointHeight: this.form.value2
}).then((res) => {
// console.log(res);
if (res.code == 200) {
this.$message.success('创建成功');
this.getAirRouteList();
}
});
}
if (this.title == '重命名') {
routerRename({
id: this.curId,
newFileName: this.form.value1
}).then((res) => {
if (res.code == 200) {
this.$message.success('重命名成功');
this.getAirRouteList();
}
});
}
this.form.value1 = '';
this.$refs.createHX.close();
} else {
console.log('error submit!!');
return false;
}
});
},
close() {
this.form.value1 = '';
this.form.value2 = '';
// this.$refs.createHX.close();
},
// 取消
cancel() {
this.form.value1 = '';
this.form.value2 = '';
this.$refs.createHX.close();
},
hasTerrain() {
return window.Earth1.viewer.terrainProvider.availability;
},
// 编辑
edit(item) {
let hasTerrain = window.Earth1.viewer.terrainProvider.availability;
if (!hasTerrain) {
this.$message.warning('请先添加地形文件(.pnk,再进行操作');
return;
}
this.$emit('routeEdit', item, this.networkState);
}
}
};
</script>
<style lang="scss">
.air_list {
left: 20px;
.el-pager li {
min-width: 18px;
}
.el-pager li.btn-quicknext,
.el-pager li.btn-quickprev {
color: #fff;
}
.header {
.logo {
position: relative;
text-align: center;
.title {
width: 380px;
height: 32px;
}
.add {
position: absolute;
top: 5px;
right: 10px;
width: 16px;
height: 16px;
z-index: 20;
background: url('../../images/add.png');
background-size: cover;
cursor: pointer;
}
.add:hover {
background: url('../../images/addh.png');
background-size: cover;
}
.importPath {
position: absolute;
top: 5px;
right: 40px;
width: 16px;
height: 16px;
z-index: 20;
background: url('../../images/importpath.png');
background-size: cover;
cursor: pointer;
}
.importPath:hover {
background: url('../../images/importpathh.png');
background-size: cover;
}
}
.search {
margin-top: 16px;
.el-input__inner {
background-color: rgba(0, 0, 0, 0.5);
height: 32px;
line-height: 32px;
border-color: rgba(0, 255, 255, 0.5);
}
.el-input__prefix,
.el-input__suffix {
top: -2px;
}
.date {
height: 32px;
color: #fff;
align-items: center;
justify-content: center;
}
.arrow {
flex-direction: column;
font-size: 12px;
i {
cursor: pointer;
}
}
}
}
.el-divider--horizontal {
margin: 10px 0;
}
.el-divider {
background: rgba(204, 204, 204, 0.2);
}
.content {
height: calc(100% - 130px);
color: #fff;
overflow: auto;
padding-right: 5px;
.air_item {
background: rgba(0, 0, 0, 0.5);
height: 113px;
border-radius: 2px;
padding: 16px;
margin-bottom: 10px;
.top {
justify-content: space-between;
.text {
font-family: 'alimamashuheiti';
width: 200px;
overflow: hidden;
white-space: nowrap;
/* 确保文本在一行内显示允许设置overflow属性 */
text-overflow: ellipsis;
/* 使用省略符号表示文本溢出 */
}
}
.middle {
img {
width: 14px;
height: 14px;
}
}
.more_icon {
height: 20px;
width: 20px;
margin: 0 6px 0 10px;
cursor: pointer;
background: url('../../images/more1.png');
background-size: cover;
}
.more_icon:hover {
background: url('../../images/more2.png');
background-size: cover;
}
.edit {
width: 18px;
height: 18px;
cursor: pointer;
background: url('../../images/edit.png');
background-size: cover;
}
.edit:hover {
background: url('../../images/edith.png');
background-size: cover;
}
.middle,
.bottom {
font-family: sans-serif;
color: rgba(230, 247, 255, 1);
}
}
}
.pagination {
.el-pager li {
width: 20px;
}
}
/* 修改垂直滚动条 */
.content::-webkit-scrollbar {
width: 8px;
/* 修改宽度 */
}
/* 修改滚动条轨道背景色 */
.content::-webkit-scrollbar-track {
background-color: rgba(0, 255, 255, 0.1);
border-radius: 10px;
}
/* 修改滚动条滑块颜色 */
.content::-webkit-scrollbar-thumb {
background-color: rgba(0, 255, 255, 0.3);
border-radius: 10px;
}
.createHX {
.el-input__inner {
background-color: rgba(0, 0, 0, 0.5);
height: 32px;
border-color: rgba(0, 255, 255, 0.5);
width: 100%;
color: #fff;
}
}
.btns {
text-align: center;
.el-button {
background: rgba(0, 255, 255, 0.2);
color: #fff;
border-color: rgba(0, 255, 255, 0.5);
}
.el-button:focus,
.el-button:hover {
border-color: rgba(0, 255, 255, 1);
}
}
}
.el-dropdown-menu {
background: rgba(0, 0, 0, 1);
border: 1px solid rgba(0, 255, 255, 0.5);
color: #fff;
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: rgba(0, 255, 255, 1);
background-color: transparent;
}
.el-dropdown-menu__item {
color: #fff;
}
}
.el-popper .popper__arrow,
.el-popper .popper__arrow::after {
display: none;
}
.el-message-box {
background: linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%), rgba(0, 0, 0, 0.3);
border: none;
.el-message-box__content,
.el-message-box__title {
color: #fff;
}
.el-button {
border-radius: 4px;
background: rgba(0, 255, 255, 0.2);
color: #fff;
border: 1px solid rgba(0, 255, 255, 0.5);
}
.el-button:hover {
border-color: rgba(0, 255, 255, 1);
}
}
.el-form-item__label {
color: #fff;
}
</style>

View File

@ -0,0 +1,881 @@
<template>
<div class="airPointSetup uav_box">
<div class="header">
<div class="logo">
<img class="title" src="../../images/airsetup.png" alt="" />
<div class="item">
<div class="back com" @click="back" title="返回"></div>
</div>
<div class="item">
<div class="save com" @click="save" title="保存"></div>
</div>
<div class="item">
<div class="switch com" @click="senior" title="高级设置"></div>
</div>
<!-- <el-tooltip class="item" effect="dark" content="返回" placement="top">
</el-tooltip>
<el-tooltip class="item" effect="dark" content="保存" placement="top">
</el-tooltip>
<el-tooltip class="item" effect="dark" content="高级设置" placement="top">
</el-tooltip> -->
</div>
</div>
<div class="title">
<img src="../../images/title.png" alt="" />
<span>航点列表</span>
</div>
<el-divider></el-divider>
<!-- 航线信息 -->
<div class="info">
<el-row>
<el-col :span="10">
<div class="info_item">
<div>航线长度</div>
<div>{{ length }} m</div>
</div>
</el-col>
<el-col :span="7">
<div class="info_item">
<div>航点</div>
<div>{{ points.length }}</div>
</div>
</el-col>
<el-col :span="7">
<div class="info_item">
<div>照片</div>
<div>{{ getPhotoNum }}</div>
</div>
</el-col>
</el-row>
</div>
<el-divider></el-divider>
<!-- 航点列表 -->
<div class="content">
<div v-for="(item, index) in points" :key="index" @click="select(index, item)" class="point_item"
:class="{ active: selectId == index }">
<div class="left">
<img src="../../images/point.png" alt="" />
<span style="margin: 0 5px;">{{ index + 1 }}</span>
</div>
<div v-if="item.actions.length > 0" class="middle">
<div v-for="(item1, index1) in item.actions" :key="index1" @click.stop="openSet(index, index1, item1)"
style="display: inline-block;margin: 0 3px;cursor: pointer;">
<svg-icon :icon-class="item1.svg" style="width: 16px;height: 16px;" :style="selectIndexS == index1 && selectIndexF == index
? 'fill:rgba(0, 255, 255, 1)'
: 'fill:#fff'
"></svg-icon>
</div>
</div>
<div class="right">
<el-dropdown @command="handleDropMenu" trigger="click">
<div class="more_icon"></div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="beforeHandleDropMenu(drop.value, item)" v-for="drop in dropdownList"
:key="drop.value">{{ drop.label }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
<!-- 操作仓 -->
<div v-show="keywordShow" class="keyword flex">
<div class="left mr20">
<div class="top">
<div class="flex space_between">
<div class="img_box">
<img src="../../images/left.png" alt="" />
</div>
<div class="img_box">
<img style="width: 16px;height: 20px;" src="../../images/top.png" alt="" />
</div>
<div class="img_box">
<img src="../../images/right.png" alt="" />
</div>
</div>
<div class="flex space_between ">
<div class="item_bottom" @mousedown="towardsLeft">Q</div>
<div class="item_bottom" @mousedown="up">W</div>
<div class="item_bottom" @mousedown="towardsRight">E</div>
</div>
</div>
<div class="bottom">
<div class="flex space_between ">
<div class="img_box">
<img src="../../images/left_f.png" alt="" />
</div>
<div class="img_box">
<img src="../../images/after_f.png" alt="" />
</div>
<div class="img_box">
<img src="../../images/right_f.png" alt="" />
</div>
</div>
<div class="flex space_between ">
<div class="item_bottom" @mousedown="left">A</div>
<div class="item_bottom" @mousedown="after">S</div>
<div class="item_bottom" @mousedown="right">D</div>
</div>
</div>
</div>
<div class="middle mr20">
<div class="middle_item">
<div class="titel">经度</div>
<div class="value">106.25467890 °</div>
</div>
<div class="middle_item">
<div class="titel">维度</div>
<div class="value">36.25467890 °</div>
</div>
<div class="middle_item">
<div class="titel">高度</div>
<div class="value">90 m</div>
</div>
</div>
<div class="right flex">
<img src="../../images/top_r.png" alt="" />
<div class="item_right">C</div>
<div class="alt flex space_between">
<div class="f30">96.3</div>
<div class="f14 text_algin_right">
<div>ALT</div>
<div>m</div>
</div>
</div>
<div class="f12 asl text_algin_right">581 ASL</div>
<img src="../../images/bottom_r.png" alt="" />
<div class="item_right">Z</div>
</div>
</div>
<!-- 动作 -->
<div class="control">
<div class="right">
<div v-if="points.length > 0" class="control_item" @click="addPoint">
<span>新增航点</span>
<img src="../../images/control_add.png" alt="" />
</div>
<div class="control_item" @click="more">
<span>更多</span>
<img src="../../images/control_more.png" alt="" />
</div>
</div>
<div v-show="leftShow" class="left">
<div class="control_item" v-for="item in controls" :key="item.label" @click="onClick(item)">
<span>{{ item.label }}</span>
<img :src="item.url" alt="" />
</div>
</div>
</div>
<!-- 动作设置 -->
<div v-show="setupShow" class="setup">
<div class="setup_header flex space_between">
<div class="setup_header_left">
<svg-icon icon-class="camera" style="width: 20px;height: 16px;vertical-align: middle;"></svg-icon>
<span class="f14 header_title">{{ selectObj.label }}</span>
</div>
<div class="setup_header_right flex ai_center">
<svg-icon icon-class="left" style="width: 18px;height: 10px;"></svg-icon>
{{ selectIndexF + 1 }}-{{ selectIndexS + 1 }}
<svg-icon icon-class="right" style="width: 18px;height: 10px;color: red;"></svg-icon>
<div class="del" @click="delAction"></div>
</div>
<div class="close" @click="closeSetup"></div>
</div>
<el-divider></el-divider>
<!-- 开始录像 -->
<div v-if="selectObj.svg == 'start_record'" class="setup_content">
<div class="flex space_between">
<div>DJI_YYYYMMDDhhmm_XXX_</div>
<div v-if="!selectObj.params.fileSuffix" class="edit" @click="inputShow = true, inputDiv = true"></div>
</div>
<div v-if="inputShow" class="flex wuyongBox">
<el-input v-if="inputDiv" v-model="selectObj.params.fileSuffix"></el-input>
<div style="flex: 1;" v-if="!inputDiv">{{ selectObj.params.fileSuffix }}</div>
<i class="el-icon-edit wuyong-icon" v-if="!inputDiv" @click="inputDiv = true"></i>
<i class="el-icon-check wuyong-icon" v-if="inputDiv" style="color: green;" @click="saveSuucess"></i>
<i class="el-icon-close wuyong-icon" v-if="inputDiv" style="color: red;" @click="deleteSuucess"></i>
</div>
<div class="flex mt15 space_between">
<div class="btns">
<el-checkbox-group :disabled="!selectObj.params.useGlobalPayloadLensIndex" v-model="checkboxGroup1" :min="1"
:max="2" @change="changeGroup">
<el-checkbox-button v-for="item in pohoto" :label="item.value" :key="item.value">{{ item.label
}}</el-checkbox-button>
</el-checkbox-group>
</div>
<el-button size="small" :class="{ bugen: selectObj.params.useGlobalPayloadLensIndex == 1 }"
@click="genliuhangxian">
<svg-icon icon-class="fly" style="width: 18px;height: 10px;"></svg-icon>跟随航线</el-button>
</div>
</div>
<!-- 停止录像 -->
<!-- 开始等时隔拍照 -->
<div v-if="selectObj.svg == 'interval_time'" class="setup_content">
<div class="flex space_between">
<div>间隔时间</div>
</div>
<div>
<plusReduce :defValue="selectObj.actionTrigger.actionTriggerParam" @value="interval_distance" :max="30"
:min="1">
</plusReduce>
</div>
<div class="flex mt15 space_between">
<div>
<el-button size="small" :disabled="pzDisabled">可见光</el-button>
<el-button size="small" :disabled="pzDisabled">红外照片</el-button>
</div>
<el-button size="small" :class="{ bugen: selectObj.params.useGlobalPayloadLensIndex == 1 }">
<svg-icon icon-class="fly" style="width: 18px;height: 10px;"></svg-icon>跟随航线</el-button>
</div>
</div>
<!-- 拍照 -->
<div v-if="selectObj.svg == 'take_photo'" class="setup_content">
<div class="flex space_between">
<div>DJI_YYYYMMDDhhmm_XXX_</div>
<div v-if="!selectObj.params.fileSuffix" class="edit" @click="inputShow = true, inputDiv = true"></div>
</div>
<div v-if="inputShow" class="flex wuyongBox">
<el-input v-if="inputDiv" v-model="selectObj.params.fileSuffix"></el-input>
<div style="flex: 1;" v-if="!inputDiv">{{ selectObj.params.fileSuffix }}</div>
<i class="el-icon-edit wuyong-icon" v-if="!inputDiv" @click="inputDiv = true"></i>
<i class="el-icon-check wuyong-icon" v-if="inputDiv" style="color: green;" @click="saveSuucess"></i>
<i class="el-icon-close wuyong-icon" v-if="inputDiv" style="color: red;" @click="deleteSuucess"></i>
</div>
<div class="flex mt15 space_between">
<div class="btnss">
<el-checkbox-group :disabled="!selectObj.params.useGlobalPayloadLensIndex" v-model="checkboxGroup2" :min="1"
@change="changeGroup1">
<el-checkbox-button v-for="item in pohoto1" :label="item.value" :key="item.value">{{ item.label
}}</el-checkbox-button>
</el-checkbox-group>
<!-- <el-button size="small" :disabled="pzDisabled">可见光</el-button>
<el-button size="small" :disabled="pzDisabled">红外照片</el-button> -->
</div>
<el-button size="small" :class="{ bugen: selectObj.params.useGlobalPayloadLensIndex == 1 }"
@click="genliuhangxian">
<svg-icon icon-class="fly" style="width: 18px;height: 10px;"></svg-icon>跟随航线</el-button>
</div>
</div>
<!-- 开始等距隔拍照 -->
<div v-if="selectObj.svg == 'interval_distance'" class="setup_content">
<div class="flex space_between">
<div>间隔时间</div>
</div>
<div>
<plusReduce @value="interval_distance" :defValue="selectObj.actionTrigger.actionTriggerParam" unit="m"
:max="100" :min="1"></plusReduce>
</div>
<div class="flex mt15 space_between">
<div>
<el-button size="small" :disabled="pzDisabled">可见光</el-button>
<el-button size="small" :disabled="pzDisabled">红外照片</el-button>
</div>
<el-button size="small" @click="pzDisabled = !pzDisabled">
<svg-icon icon-class="fly" style="width: 18px;height: 10px;"></svg-icon>跟随航线</el-button>
</div>
</div>
<!-- 悬停 -->
<div v-if="selectObj.svg == 'hover'" class="setup_content">
<div class="flex space_between">
<div>间隔时间</div>
</div>
<div>
<plusReduce @value="interval_distance" :defValue="Number(selectObj.params.hoverTime)" :max="1800" :min="1">
</plusReduce>
</div>
</div>
<!-- 飞行器偏航角 -->
<div v-if="selectObj.svg == 'drone_yaw'" class="setup_content">
<div class="flex space_between">
<div>飞行器偏航角</div>
<div class="lan">{{ selectObj.params.aircraftHeading }}°</div>
</div>
<div>
<myProgress :defValue="Number(selectObj.params.aircraftHeading)" @value="hpr" :sliderMax="180"
:sliderMin="-180">
</myProgress>
</div>
<div class="flex mt15 space_between"></div>
</div>
<!-- 云台俯仰角 -->
<div v-if="selectObj.svg == 'action_gimbal_pitch'" class="setup_content">
<div class="flex space_between">
<div>云台俯仰角</div>
<div class="lan">{{ selectObj.params.gimbalPitchRotateAngle }}°</div>
</div>
<div>
<myProgress :defValue="Number(selectObj.params.gimbalPitchRotateAngle)" @value="hpr" :sliderMax="30"
:sliderMin="-90"></myProgress>
</div>
<div class="flex mt15 space_between"></div>
</div>
<!-- 变焦 camera_zoom -->
<div v-if="selectObj.svg == 'camera_zoom'" class="setup_content">
<div class="flex space_between">
<div>变焦</div>
<div class="lan">
{{ Number(selectObj.params.focalLength) / 24 }}
<span style="font-size: 24px;color: #fff;">X</span>
</div>
</div>
<div>
<myProgress :defValue="Number(selectObj.params.focalLength) / 24" @value="hpr" :sliderMax="56" :sliderMin="1"
unit="X"></myProgress>
</div>
<div class="flex mt15 space_between"></div>
</div>
</div>
<div id="tongbuEarth"></div>
</div>
</template>
<script>
import plusReduce from "../component/plusReduce/index.vue";
import myProgress from "../component/progress/index.vue";
import mixin from "./js/pointSetup";
export default {
name: "airSetup",
components: { plusReduce, myProgress },
props: {
routeLine: {
type: Object,
default: () => { },
},
},
mixins: [mixin],
};
</script>
<style lang="scss">
.airPointSetup {
color: #fff;
left: 20px;
.header {
.logo {
position: relative;
text-align: center;
.title {
width: 380px;
height: 32px;
}
.com {
position: absolute;
top: 5px;
width: 16px;
height: 16px;
z-index: 20;
cursor: pointer;
}
.back {
right: 70px;
background: url("../../images/back.png");
background-size: cover;
}
.back:hover {
background: url("../../images/back_h.png");
background-size: cover;
}
.save {
right: 40px;
background: url("../../images/save.png");
background-size: cover;
}
.save:hover {
background: url("../../images/saveh.png");
background-size: cover;
}
.switch {
right: 10px;
background: url("../../images/switch.png");
background-size: cover;
}
.switch:hover {
background: url("../../images/switchh.png");
background-size: cover;
}
}
.search {
.el-input__inner {
background-color: rgba(0, 0, 0, 0.5);
height: 32px;
border-color: rgba(0, 255, 255, 0.5);
}
.date {
height: 32px;
color: #fff;
align-items: center;
justify-content: center;
}
.arrow {
flex-direction: column;
font-size: 12px;
}
}
}
.title {
height: 24px;
color: #fff;
display: flex;
align-items: center;
margin-top: 8px;
img {
width: 20px;
height: 16px;
margin-right: 10px;
}
}
.info {
.info_item {
div {
text-align: center;
line-height: 24px;
}
div:last-child {
font-family: "ddin";
}
}
}
.content {
height: calc(100% - 154px);
color: #fff;
overflow: auto;
padding-right: 5px;
.point_item {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 40px;
// margin-bottom: 10px;
border-bottom: 1px solid rgba(204, 204, 204, 0.2);
background: transparent;
.left {
min-width: 50px;
margin-left: 15px;
img {
width: 12px;
height: 16px;
}
}
.middle {
flex: 1;
align-items: center;
img {
width: 12px;
height: 16px;
margin: 0 5px;
}
}
.right {
.more_icon {
height: 20px;
width: 20px;
margin: 0 6px 0 10px;
cursor: pointer;
background: url("../../images/more1.png");
background-size: cover;
}
.more_icon:hover {
background: url("../../images/more2.png");
background-size: cover;
}
}
}
.active {
background: linear-gradient(180deg,
rgba(0, 255, 255, 0) 0%,
rgba(0, 255, 255, 0.5) 100%);
}
.point_item:hover {
background: linear-gradient(180deg,
rgba(0, 255, 255, 0) 0%,
rgba(0, 255, 255, 0.5) 100%);
}
}
/* 修改垂直滚动条 */
.content::-webkit-scrollbar {
width: 8px;
/* 修改宽度 */
}
/* 修改滚动条轨道背景色 */
.content::-webkit-scrollbar-track {
background-color: rgba(0, 255, 255, 0.1);
border-radius: 10px;
}
/* 修改滚动条滑块颜色 */
.content::-webkit-scrollbar-thumb {
background-color: rgba(0, 255, 255, 0.3);
border-radius: 10px;
}
.el-divider--horizontal {
margin: 10px 0;
}
.el-divider {
background: rgba(204, 204, 204, 0.2);
}
.text_align_center {
text-align: center;
}
.keyword {
position: fixed;
left: 50%;
bottom: 6%;
transform: translate(-50%, -8%);
.left {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.top,
.bottom {
width: 157px;
height: 80px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 8px;
padding: 10px;
.img_box {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
img {
width: 20px;
height: 18px;
}
}
.item_bottom {
width: 30px;
height: 30px;
line-height: 30px;
border-radius: 2px;
background: rgba(60, 60, 60, 1);
text-align: center;
}
}
.bottom {
.img_box:nth-child(1) {
img {
width: 12px;
height: 18px;
}
}
.img_box:nth-child(2) {
img {
width: 18px;
height: 12px;
}
}
.img_box:nth-child(3) {
img {
width: 12px;
height: 18px;
}
}
}
.middle {
min-width: 145px;
border-radius: 8px;
padding: 16px;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
justify-content: space-between;
.middle_item {
.title {
font-size: 16px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
}
.value {
font-family: "ddin";
font-size: 18px;
font-weight: 700;
color: rgba(255, 255, 255, 1);
}
}
}
.right {
min-width: 120px;
border-radius: 8px;
padding: 10px 16px;
background-color: rgba(0, 0, 0, 0.5);
flex-direction: column;
align-items: center;
justify-content: space-between;
img {
width: 16px;
height: 20px;
}
.item_right {
width: 30px;
height: 30px;
line-height: 30px;
border-radius: 2px;
background: rgba(60, 60, 60, 1);
text-align: center;
}
.alt,
.asl {
font-family: "ddin";
}
.alt {
color: rgba(0, 255, 255, 1);
}
.asl {
width: 100%;
}
}
.middle,
.right {
height: 200px;
}
}
.control {
position: fixed;
right: 2%;
top: 35%;
// background-color: transparent;
.control_item {
text-align: right;
margin-bottom: 20px;
span {
font-size: 14px;
font-weight: 700;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000,
1px 1px 0 #000;
/* 文字阴影,模拟边框效果 */
}
img {
width: 30px;
height: 30px;
vertical-align: middle;
}
}
.left {
position: absolute;
right: 120px;
top: 0;
min-width: 200px;
}
}
// 设置
.setup {
position: fixed;
top: 10%;
right: 5%;
min-width: 400px;
min-height: 100px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 8px;
border: 1px solid rgba(0, 255, 255, 0.5);
padding: 30px 12px 12px 16px;
.close {
width: 15px;
height: 15px;
position: absolute;
top: 8px;
right: 12px;
cursor: pointer;
background: url("../../images/close1.png");
background-size: cover;
}
.setup_header_left {
.header_title {
font-weight: 500;
color: rgba(0, 255, 255, 1);
}
}
.setup_header_right {
.del {
width: 14px;
height: 14px;
background: url("../../images/del.png");
background-size: cover;
cursor: pointer;
margin: 0 0 0 8px;
}
.del:hover {
background: url("../../images/delh.png");
background-size: cover;
}
}
.setup_content {
margin: 10px 0;
.wuyongBox {
align-items: center;
margin-top: 10px;
.wuyong-icon {
font-weight: bold;
margin: 0 5px;
cursor: pointer;
}
}
.bugen {
// background: rgba(0, 255, 255, 0.2) !important;
// color: rgba(0, 255, 255, 0.8) !important;
// border: 1px solid rgba(0, 255, 255, 0.5) !important;
background: rgb(255 255 255 / 20%) !important;
color: rgb(255 255 255 / 80%) !important;
border: 1px solid rgb(107 107 107 / 50%) !important;
}
.el-checkbox-group {
width: 100%;
}
.el-checkbox-button:last-child .el-checkbox-button__inner,
.el-checkbox-button:first-child .el-checkbox-button__inner {
border-radius: 5px;
box-shadow: none;
}
.el-checkbox-button.is-checked .el-checkbox-button__inner {
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
color: rgba(0, 255, 255, 1);
}
.el-checkbox-button__inner {
// background: rgba(0, 255, 255, 0.2);
// color: rgba(0, 255, 255, 0.8);
// border: 1px solid rgba(0, 255, 255, 0.5);
background: rgb(255 255 255 / 20%);
color: rgb(255 255 255 / 80%);
border: 1px solid rgb(107 107 107 / 50%);
margin-right: 8px;
border-radius: 5px;
padding: 8px 15px;
box-shadow: none;
}
.el-input__inner {
background-color: transparent;
border-color: aqua;
height: 30px;
line-height: 30px;
color: #fff;
}
.edit {
width: 14px;
height: 14px;
background: url("../../images/edit.png");
background-size: cover;
cursor: pointer;
}
.el-button.is-disabled,
.el-button.is-disabled:focus,
.el-button.is-disabled:hover {
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 0.5);
color: rgba(255, 255, 255, 0.8);
}
.el-button {
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
color: rgba(0, 255, 255, 1);
}
.lan {
color: rgba(0, 255, 255, 1);
font-size: 30px;
font-family: "ddin";
}
}
}
}
.el-dropdown-menu {
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(0, 255, 255, 0.5);
color: #fff;
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: rgba(0, 255, 255, 1);
background-color: transparent;
}
}
.el-popper .popper__arrow,
.el-popper .popper__arrow::after {
display: none;
}
#tongbuEarth {
width: 300px;
height: 200px;
position: fixed;
right: 0;
bottom: 0;
background: aqua;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div class="bottom_tabs flex ai_center center">
<div v-for="item in tabs" class="item " :class="{ active: item.id == selectId }" @click="select(item)">
<div class="text" :class="{ active: item.id == selectId }">
{{ item.label }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
tabs: [
{
label: "无人机",
id: 1,
},
{
label: "航线库",
id: 2,
},
{
label: "计划库",
id: 3,
},
{
label: "媒体库",
id: 4,
},
],
selectId: 1,
};
},
created() { },
mounted() { },
methods: {
select(item) {
let airGateway = JSON.parse(localStorage.getItem('airGateway'))
if (!airGateway) {
this.$message.warning("请先选择无人机");
return;
}
this.selectId = item.id;
this.$emit("select", item);
},
},
};
</script>
<style lang="scss" scoped>
.bottom_tabs {
width: 50%;
height: 80px;
position: absolute;
z-index: 10;
left: 25%;
bottom: 2%;
// background: rgba(0, 0, 0, 0.5);
// border: 1px solid rgba(0, 255, 255, 0.5);
.item {
position: relative;
width: 136px;
height: 40px;
line-height: 45px;
background: url("../../images/tab.png");
background-size: cover;
font-family: "alimamashuheiti";
text-align: center;
color: aliceblue;
cursor: pointer;
.text {
background: linear-gradient(180deg,
#ffffff,
#efffff,
#00ffff);
/* 标准语法 */
-webkit-background-clip: text;
/* Chrome, Safari */
background-clip: text;
-webkit-text-fill-color: transparent;
/* Chrome, Safari */
color: transparent;
/* 其他浏览器 */
}
.text:hover,
.active {
background: linear-gradient(180deg,
#ffffff,
#fff8eb,
#ffa600);
/* 标准语法 */
-webkit-background-clip: text;
/* Chrome, Safari */
background-clip: text;
-webkit-text-fill-color: transparent;
/* Chrome, Safari */
color: transparent;
/* 其他浏览器 */
}
}
.item:hover,
.active {
background: url("../../images/tab_h.png");
background-size: cover;
}
.item::after {
// content: " 🔗";
position: absolute;
left: 50%;
bottom: -16px;
/* 初始设置为不可见 */
opacity: 0;
transition: opacity 0.3s ease;
transform: translate(-50%);
content: "";
display: block;
width: 22px;
/* 设置图片宽度 */
height: 16px;
/* 设置图片高度 */
background-image: url("../../images/hover.png");
/* 设置图片路径 */
background-size: cover;
/* 背景图片覆盖整个元素 */
background-position: center;
/* 背景图片居中 */
}
.item:hover::after {
opacity: 1;
}
.active::after {
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="dialog" id="my_dialog" v-if="dialogShow">
<div class="header" id="my_dialog_header">
<span class="title">{{ title }}</span>
<div class="close" @click="close">
<div class="sector"></div>
<img src="../../../images/close.png" alt="" />
</div>
</div>
<slot></slot>
</div>
</template>
<script>
import { setMove } from '@/utils/moveDiv.ts';
export default {
name: 'dialog',
props: {
width: {
type: String,
default: ''
},
title: {
type: String,
default: '默认'
}
},
data() {
return {
dialogShow: false
};
},
mounted() {},
methods: {
close() {
this.$emit('close', false);
this.dialogShow = false;
},
open() {
this.dialogShow = true;
this.$nextTick(() => {
setMove('my_dialog_header', 'my_dialog');
});
}
}
};
</script>
<style lang="scss" scoped>
.dialog::before {
content: '';
width: 100px;
height: 6px;
background: aqua;
position: absolute;
top: -6px;
left: -2px;
clip-path: polygon(0% 0%, 90% 0%, 100% 100%, 0% 100%);
}
.dialog {
position: fixed;
top: 50%;
left: 50%;
z-index: 100;
width: 515px;
transform: translate(-50%, -50%);
padding: 10px;
background: linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%), rgba(0, 0, 0, 0.6);
border: 1.5px solid rgba(0, 255, 255, 1);
color: #fff;
.header {
height: 40px;
padding: 10px;
.title {
font-family: 'alimamashuheiti';
font-size: 18px;
font-weight: 700;
text-shadow: 0px 0px 9px rgba(20, 118, 255, 1);
}
.close {
cursor: pointer;
}
.sector {
position: absolute;
top: -30px;
right: -30px;
width: 0;
height: 0;
border: 30px solid transparent;
/* 边框宽度和颜色可以调整 */
border-bottom-color: rgba(0, 255, 255, 0.5);
/* 底边的颜色 */
border-radius: 50%;
/* 将边框变为圆形 */
transform: rotate(45deg);
/* 旋转45度可根据需要调整角度 */
}
img {
position: absolute;
top: 5px;
right: 5px;
width: 14px;
height: 14px;
}
}
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<div class="plusReduce flex" :class="{ flex_column: column }">
<div class="left flex" :class="{ flex_column: column }" @click="calculate">
<span :class="{ disabled: active }">-100</span>
<span :class="{ disabled: active }">-10</span>
<span v-show="!column" :class="{ disabled: active }">-1</span>
</div>
<div class="middle">{{ value }} {{ unit }}</div>
<div class="right flex" :class="{ flex_column: column }" @click="calculate">
<span v-show="!column">+1</span>
<span>+10</span>
<span>+100</span>
</div>
</div>
</template>
<script>
export default {
name: "plusReduce",
props: {
defValue: {
type: Number || String,
default: 0,
},
unit: {
type: String,
default: "s",
},
max: {
type: Number,
default: 10,
},
min: {
type: Number,
default: 1,
},
column: {
type: Boolean,
default: false,
},
},
data() {
return {
active: false,
value: 1,
};
},
watch: {
value(newVal) {
if (newVal == this.min) {
this.active = true;
} else {
this.active = false;
}
},
defValue(newVal) {
this.value = newVal;
},
},
mounted() {
this.value = this.defValue;
console.log('this.value', this.value);
},
methods: {
calculate(e) {
console.log(e.target.innerHTML);
if (this.value >= 0 && e.target.innerHTML.length < 10) {
this.value += Number(e.target.innerText);
if (this.value <= this.min) {
this.value = this.min;
}
if (this.value >= this.max) {
this.value = this.max;
}
}
this.$emit("value", this.value);
},
},
};
</script>
<style lang="scss" scoped>
.plusReduce {
align-items: center;
margin: 10px 0;
span {
display: inline-block;
padding: 6px 7px;
background: rgba(60, 60, 60, 1);
border-radius: 3px;
margin: 5px;
cursor: pointer;
}
.disabled {
background: rgba(60, 60, 60, 0.5);
color: rgba(255, 255, 255, 0.5);
cursor: not-allowed;
pointer-events: none;
}
// :not(.disabled)
span:hover {
background: rgba(0, 255, 255, 0.5);
}
.middle {
flex: 1;
text-align: center;
}
}
.flex_column {
flex-direction: column;
}
.flex {
display: flex;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div class="plusReduce flex">
<div class="left" @click="calculate(-1)">
<svg-icon icon-class="jian" style="width: 20px;height: 20px;cursor: pointer;"></svg-icon>
</div>
<div class="middle">
<el-slider v-if="progressType == 'progress'" style="width: 90%;" v-model="value" :max="sliderMax" :min="sliderMin"
:show-tooltip="false" @input="change"></el-slider>
<div v-if="progressType == 'num'">
<span class="num">{{ value2 }}</span>
m/s
</div>
</div>
<div class="right" @click="calculate(1)">
<svg-icon icon-class="jia" style="width: 20px;height: 20px;cursor: pointer;"></svg-icon>
</div>
</div>
</template>
<script>
export default {
name: "plusReduce",
props: {
defValue: {
type: Number,
default: 0,
},
unit: {
type: String,
default: "s",
},
sliderMax: {
type: Number,
default: 0,
},
sliderMin: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 10,
},
min: {
type: Number,
default: 1,
},
progressType: {
type: String,
default: "progress",
},
},
data() {
return {
active: false,
value: 1,
value2: 10,
};
},
watch: {
value(newVal) {
if (newVal == this.min) {
this.active = true;
} else {
this.active = false;
}
},
defValue(newVal) {
this.value = newVal;
this.value2 = newVal;
},
},
mounted() {
this.value = this.defValue;
this.value2 = this.defValue;
},
methods: {
calculate(num) {
if (this.progressType == "progress") {
if (this.value >= this.sliderMin || this.value < this.sliderMax) {
this.value += Number(num);
if (this.value < this.sliderMin) {
this.value = this.sliderMin;
}
if (this.value > this.sliderMax) {
this.value = this.sliderMax;
}
}
this.$emit("value", this.value);
} else {
if (this.value2 >= this.min || this.value2 < this.max) {
this.value2 += num;
if (this.value2 < this.min) {
this.value2 = this.min;
}
if (this.value2 > this.max) {
this.value2 = this.max;
}
this.$emit("value", this.value2);
}
}
},
change(value) {
this.$emit("value", value);
},
},
};
</script>
<style lang="scss">
.plusReduce {
align-items: center;
margin: 10px 0;
.middle {
flex: 1;
display: flex;
justify-content: center;
.el-slider__bar {
background-color: rgba(0, 255, 255, 1);
}
.el-slider__button {
border: none;
}
.num {
font-family: "ddin";
font-size: 30px;
font-weight: 700;
color: rgba(0, 255, 255, 1);
}
}
.disabled {
background: rgba(60, 60, 60, 0.5);
color: rgba(255, 255, 255, 0.5);
cursor: not-allowed;
pointer-events: none;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@ -0,0 +1,37 @@
<template>
<div class="flyerimg">
<img style="width: 100%;height: 100%;" src="./1.jpg" alt="" />
</div>
</template>
<script>
export default {
name: 'flyer',
data() {
return {
};
},
mounted() {
},
methods: {
},
};
</script>
<style>
.flyerimg {
position: absolute;
left: 50%;
top: 50%;
z-index: 20;
transform: translate(-50%, -50%);
width: 80%;
height: 75%;
color: #fff;
}
</style>

View File

@ -0,0 +1,447 @@
<template>
<div class="airport-information-wrapper">
<ModuleItem :title="'飞行器信息详情'" :subTitle="'Aircraft Information Details'">
<div class="airport-information-content">
<div class="airport-info-wrapper">
<div style="display: flex">
<div class="airport-left">
<img src="../../../../images/home/aircraft.png" alt="" />
</div>
<div class="airport-center">
<div>设备型号 {{ fxqinfo.deviceType || '----' }}</div>
<div>
设备项目呼号 {{ fxqinfo.businessName || '----' }}
<img @click="onview" src="../../../../images/home/local-point.png" alt="" />
</div>
</div>
</div>
<!--
<div class="airport-right">
{{ specialHandling("drone_in_dock")
}}{{ specialHandling("device_online_status") }}
</div> -->
</div>
<div class="airport-list-wrapper">
<div v-for="item in airportsList" :key="item.name" class="airport-list-item">
<div class="airport-list-top">
<img v-if="item.img" :src="item.img" alt="" />
<div>{{ item.data }}{{ item.unit }}</div>
</div>
<div class="airport-list-bottom">
<div>{{ item.name }}</div>
</div>
</div>
</div>
</div>
</ModuleItem>
</div>
</template>
<script>
import ModuleItem from '../ModuleItem/index.vue';
import { listInfo } from '@/api/air';
import { debounce } from '@/utils/index';
import satellite from '../../../../images/home/satellite.png';
import paizhao from '../../../../images/home/paizhao.png';
import pzsyl from '../../../../images/home/pzsyl.png';
import { useAirStore } from '@/store/modules/drone';
export default {
name: 'AirportInformationDetails',
components: { ModuleItem },
props: {},
data() {
return {
airportsList: [
{
name: 'RTK',
data: 0,
unit: '',
key: 'rtk_number',
img: satellite
},
{
name: '拍照状态',
data: '空闲',
unit: '',
key: 'photo_state',
img: paizhao
},
{
name: '拍照剩余数量',
data: '0',
unit: '张',
key: 'remain_photo_num',
img: pzsyl
},
{
name: '电池剩余总量',
data: '0',
unit: '%',
key: 'capacity_percent',
img: null
},
{
name: '剩余飞行时间',
data: '00:00:00',
key: 'remain_flight_time',
img: null
},
{
name: '返航所需电量',
data: '0',
unit: '%',
key: 'return_home_power',
img: null
},
{
name: '内存总容量',
data: '0',
unit: 'GB',
key: 'total',
img: null
},
{
name: '内存已使用容量',
data: '0',
unit: 'GB',
key: 'used',
img: null
},
{
name: '飞行高度',
data: '0',
unit: 'm',
key: 'elevation',
img: null
}
],
osd4: null,
osd4Map: new Map(),
dataMap: new Map(),
dataAll: {},
info: {},
fxqinfo: {},
gateWay: useAirStore().gateWay,
flag: true,
timer: null,
time: ''
};
},
// computed: {
// // 映射 Vuex 中的状态到当前组件的计算属性
// ...mapState({
// gateWay: (state) => state.gateWay,
// }),
// },
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getList();
},
deep: true
}
},
methods: {
// 机场位置
onview: debounce(function () {
this.$sendChanel('onViewUAV');
}, 500),
getList() {
listInfo({
pageNum: 1,
pageSize: 10,
gateway: this.gateWay.gateway
}).then((res) => {
// console.log("list-------", res);
// 暂时写死
this.info = res.rows[0];
this.fxqinfo = res.rows[1];
});
},
dataTreat(info) {
// console.log(456);
if (info.businessType !== 'osd4') {
this[info.businessType] = info.data;
Object.assign(this.dataAll, info.data);
// this.osd3.
} else {
this.osd4 = info.data;
// console.log("this.dataAll--------", this.dataAll);
}
function flattenObjectToMap(obj, map = new Map()) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
flattenObjectToMap(value, map);
} else {
map.set(key, value);
}
}
}
return map;
}
this.osd4Map = flattenObjectToMap(this.osd4, this.osd4Map);
this.dataMap = flattenObjectToMap(this.dataAll, this.dataMap);
// console.log("this.dataAll--------", this.dataAll);
// 需要特殊处理的字段
const spcial = ['remain_flight_time', 'photo_state', 'remain_photo_num', 'total', 'used'];
// 特殊处理
this.airportsList.forEach((item) => {
if (!spcial.includes(item.key)) {
item.data = this.osd4Map.get(item.key) || 0;
} else {
item.data = this.specialHandling(item.key);
}
});
},
specialHandling(key) {
function kbToGb(kb) {
return kb / (1024 * 1024);
}
switch (key) {
case 'remain_flight_time':
const val = this.osd4Map.get(key);
if (!this.osd4Map.get(key)) {
return 0 + '时' + 0 + '分' + 0 + '秒';
}
//返回分钟
let h = parseInt(val / 60 / 60);
let miu = parseInt(h % 60);
let s = val % 60;
return h + '时' + miu + '分' + s + '秒';
case 'photo_state':
if (!this.osd4Map.get('cameras')) {
return '空闲';
}
const photoState = this.osd4Map.get('cameras')[0][key] == 0 ? '空闲' : '拍照中';
return photoState ? photoState : '空闲';
case 'remain_photo_num':
if (!this.osd4Map.get('cameras')) {
return 0;
}
const remain_photo_num = this.osd4Map.get('cameras')[0][key];
return remain_photo_num ? remain_photo_num : 0;
case 'total':
if (!this.osd4Map.get(key)) {
return 0;
}
let b = kbToGb(this.osd4Map.get(key));
const total = b.toFixed(2);
return total ? total : 0;
case 'used':
if (!this.osd4Map.get(key)) {
return 0;
}
let a = kbToGb(this.osd4Map.get(key));
const used = a.toFixed(2);
return used ? used : 0;
case 'device_online_status':
return String(this.dataMap.get(key)) === '1' ? '开机' : '关机';
case 'drone_in_dock':
return String(this.dataMap.get(key)) === '1' ? '舱内' : '舱外';
default:
break;
}
},
restet() {
// console.log(123);
this.airportsList = [
{
name: 'RTK',
data: 0,
unit: '',
key: 'rtk_number',
img: satellite
},
{
name: '拍照状态',
data: '空闲',
unit: '',
key: 'photo_state',
img: paizhao
},
{
name: '拍照剩余数量',
data: '0',
unit: '张',
key: 'remain_photo_num',
img: pzsyl
},
{
name: '电池剩余总量',
data: '0',
unit: '%',
key: 'capacity_percent',
img: null
},
{
name: '剩余飞行时间',
data: '00:00:00',
key: 'remain_flight_time',
img: null
},
{
name: '返航所需电量',
data: '0',
unit: '%',
key: 'return_home_power',
img: null
},
{
name: '内存总容量',
data: '0',
unit: 'GB',
key: 'total',
img: null
},
{
name: '内存已使用容量',
data: '0',
unit: 'GB',
key: 'used',
img: null
},
{
name: '飞行高度',
data: '0',
unit: 'm',
key: 'elevation',
img: null
}
];
},
onReset() {
let that = this;
that.timer = setInterval(() => {
const currentTimestamp = Date.now(); // 获取当前时间戳
const elapsed = currentTimestamp - that.time; // 计算差值
if (elapsed > 2000) {
that.restet(); // 如果大于2000毫秒则执行方法
}
}, 2000);
}
},
// 定时器
created() {
this.$recvChanel('liveBus', (bool) => {
setTimeout(() => {
this.restet();
}, 2000);
});
this.$recvChanel('airInfoBus', (bool) => {
this.flag = bool;
});
this.$recvChanel('websocketBus', (airdata) => {
// if (airdata.businessType == 'osd3') {
// this.dataTreat(airdata);
// }
if (airdata.businessType == 'osd4' && this.flag) {
this.dataTreat(airdata);
this.time = new Date().getTime();
}
if (airdata.businessType == 'osd4' && !this.flag) {
this.restet();
}
});
},
mounted() {
if (this.gateWay) {
this.getList();
}
this.onReset();
}
};
</script>
<style lang="scss" scoped>
.airport-information-wrapper {
width: 100%;
height: 40%;
position: relative;
.airport-information-content {
height: 100%;
width: 100%;
// background-color: blueviolet;
.airport-info-wrapper {
padding: 0 2%;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid rgba(0, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.5);
margin: 5px 0;
.airport-left {
transform: translateY(5px);
margin-right: 10px;
}
.airport-center {
font-size: 12px;
color: rgba(207, 255, 255, 1);
display: flex;
flex-direction: column;
justify-content: center;
> div:nth-of-type(2) {
display: flex;
align-items: center;
margin-top: 5px;
img {
margin-left: 5px;
cursor: pointer;
}
}
}
.airport-right {
display: flex;
align-items: center;
color: rgba(241, 108, 85, 1);
}
}
.airport-list-wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* 创建3列每列等宽 */
gap: 15px;
/* 设置列和行之间的间距 */
padding: 10px 0;
.airport-list-item {
font-size: 14px;
.airport-list-top {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4px;
img {
width: 14px;
height: 14px;
margin-right: 4px;
}
}
.airport-list-bottom {
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
</style>

View File

@ -0,0 +1,485 @@
<template>
<div class="airport-information-wrapper">
<ModuleItem :title="'无人机机场信息详情'" :subTitle="'Airport Information Details'">
<div class="airport-information-content">
<div class="airport-info-wrapper">
<div style="display: flex">
<div class="airport-left">
<img src="../../../../images/home/cabin.png" alt="" />
</div>
<div class="airport-center">
<!-- {{ gateway.deviceType }} -->
<div>机舱型号{{ info.deviceType || '----' }}</div>
<div>
<!-- {{ gateway.businessName }} -->
机舱项目呼号{{ info.businessName || '----' }}
<!-- 防止重复点击,使用once修饰符 -->
<img @click="onview" src="../../../../images/home/local-point.png" alt="" />
</div>
</div>
</div>
<div class="airport-right">
{{ specialHandling('flighttask_step_code') }}
</div>
</div>
<div class="airport-list-wrapper">
<div v-for="item in airportsList" :key="item.name" class="airport-list-item">
<div class="airport-list-top">
<img v-if="item.img" :src="item.img" alt="" />
<div>{{ item.data }}{{ item.unit }}</div>
</div>
<div class="airport-list-bottom">
<div>{{ item.name }}</div>
</div>
</div>
</div>
</div>
</ModuleItem>
</div>
</template>
<script>
import ModuleItem from '../ModuleItem/index.vue';
import dayjs from 'dayjs';
import { listInfo } from '@/api/air';
import { debounce } from '@/utils/index';
import satellite from '../../../../images/home/satellite.png';
import calibration from '../../../../images/home/calibration.png';
import recharge from '../../../../images/home/recharge.png';
import file from '../../../../images/home/file.png';
import uav from '../../../../images/home/uav.png';
import uavLocal from '../../../../images/home/uav-local.png';
import { useAirStore } from '@/store/modules/drone';
export default {
name: 'AirportInformationDetails',
components: { ModuleItem },
data() {
return {
airportsList: [
{
name: '机场搜星',
key: 'gps_number',
data: 0,
unit: '',
img: satellite
},
{
name: '标定状态',
data: '已标定',
key: 'is_calibration',
unit: '',
img: calibration
},
{
name: '机场存储',
data: '0/0',
unit: 'GB',
key: 'storage',
img: null
},
{
name: '以太网网络',
data: '0',
unit: 'KB/s',
key: 'rate',
img: null
},
{
name: '风速',
data: '0',
unit: 'm/s',
key: 'wind_speed',
img: null
},
{
name: '降雨量',
data: '0',
unit: 'mm',
key: 'rainfall',
img: null
},
{
name: '充电状态',
data: '空闲',
unit: '',
key: 'state',
img: recharge
},
{
name: '待上传数',
data: '0',
unit: '',
img: file,
key: 'remain_upload'
},
{
name: '舱内温度',
data: '0',
unit: '℃',
key: 'temperature',
img: null
},
{
name: '舱外温度',
data: '0',
unit: '℃',
key: 'environment_temperature',
img: null
},
{
name: '舱内湿度',
data: '0',
unit: '%RH',
key: 'humidity',
img: null
},
{
name: '运行时长',
data: '0天0时0分',
unit: '',
key: 'acc_time',
img: null
},
{
name: '飞行器状态',
data: '关机',
unit: '',
key: 'device_online_status',
img: uav
},
{
name: '飞行器位置',
data: '舱内',
unit: '',
key: 'drone_in_dock',
img: uavLocal
}
],
osd1: null,
osd2: null,
osd3: null,
dataMap: new Map(),
dataAll: {},
info: {},
gateway: useAirStore().gateWay
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateway = newValue;
this.getList();
},
deep: true
}
},
methods: {
dataTreat(info) {
if (['osd1', 'osd2', 'osd3'].includes(info.businessType)) {
this[info.businessType] = info.data;
Object.assign(this.dataAll, info.data);
}
// console.log("osd1", this.osd1);
// console.log("osd2", this.osd2);
// console.log("osd3", this.osd3);
// console.log("数据", this.dataAll);
function flattenObjectToMap(obj, map = new Map()) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
flattenObjectToMap(value, map);
} else {
map.set(key, value);
}
}
}
return map;
}
this.dataMap = flattenObjectToMap(this.dataAll, this.dataMap);
// console.log("dataMap", this.dataMap);
// 需要特殊处理的字段
const spcial = ['is_calibration', 'storage', 'rate', 'state', 'acc_time', 'device_online_status', 'drone_in_dock'];
// 特殊处理
this.airportsList.forEach((item) => {
if (!spcial.includes(item.key)) {
item.data = this.dataMap.get(item.key);
} else {
item.data = this.specialHandling(item.key);
}
});
},
specialHandling(key) {
switch (key) {
case 'flighttask_step_code':
const flighttaskObj = {
'0': '作业准备中',
'1': '飞行作业中',
'2': '作业后状态恢复',
'3': '自定义飞行区更新中',
'4': '地形障碍物更新中',
'5': '任务空闲',
'255': '飞行器异常',
'256': '未知状态'
};
return flighttaskObj[String(this.dataMap.get(key))];
case 'is_calibration':
return String(this.dataMap.get(key)) === '1' ? '已标定' : '未标定';
case 'storage':
function kbToGb(kb) {
return kb / (1024 * 1024);
}
let a = kbToGb(this.dataMap.get('used')) || 0;
let b = kbToGb(this.dataMap.get('total')) || 0;
const used = a.toFixed(2);
const total = b.toFixed(2);
return `${used}/${total}`;
case 'rate':
let c = this.dataMap.get(key) || 0;
return c.toFixed(2);
case 'state':
return String(this.dataMap.get(key)) === '1' ? '充电中' : '空闲';
case 'acc_time':
function formatTime(seconds) {
const days = Math.floor(seconds / (3600 * 24));
const hours = Math.floor((seconds % (3600 * 24)) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${days}${hours}${minutes}`;
}
const timeFormatter = formatTime(dayjs(this.dataMap.get(key)));
return timeFormatter;
case 'device_online_status':
return String(this.dataMap.get(key)) === '1' ? '开机' : '关机';
case 'drone_in_dock':
return String(this.dataMap.get(key)) === '1' ? '舱内' : '舱外';
default:
break;
}
},
// 获取设备列表
getList() {
listInfo({
pageNum: 1,
pageSize: 10,
gateway: this.gateway.gateway
}).then((res) => {
this.number = res.rows.length;
this.info = res.rows[0];
console.log('this.infothis.infothis.infothis.info', this.info);
});
},
// 机场位置
// 使用防抖处理点击事件,避免短时间内重复触发
onview: debounce(function () {
this.$sendChanel('onView');
}, 500), // 500ms内只执行一次
restet() {
this.airportsList = [
{
name: '机场搜星',
key: 'gps_number',
data: 0,
unit: '',
img: satellite
},
{
name: '标定状态',
data: '已标定',
key: 'is_calibration',
unit: '',
img: calibration
},
{
name: '机场存储',
data: '0/0',
unit: 'GB',
key: 'storage',
img: null
},
{
name: '以太网网络',
data: '0',
unit: 'KB/s',
key: 'rate',
img: null
},
{
name: '风速',
data: '0',
unit: 'm/s',
key: 'wind_speed',
img: null
},
{
name: '降雨量',
data: '0',
unit: 'mm',
key: 'rainfall',
img: null
},
{
name: '充电状态',
data: '空闲',
unit: '',
key: 'state',
img: recharge
},
{
name: '待上传数',
data: '0',
unit: '',
img: file,
key: 'remain_upload'
},
{
name: '舱内温度',
data: '0',
unit: '℃',
key: 'temperature',
img: null
},
{
name: '舱外温度',
data: '0',
unit: '℃',
key: 'environment_temperature',
img: null
},
{
name: '舱内湿度',
data: '0',
unit: '%RH',
key: 'humidity',
img: null
},
{
name: '运行时长',
data: '0天0时0分',
unit: '',
key: 'acc_time',
img: null
},
{
name: '飞行器状态',
data: '关机',
unit: '',
key: 'device_online_status',
img: uav
},
{
name: '飞行器位置',
data: '舱内',
unit: '',
key: 'drone_in_dock',
img: uavLocal
}
];
}
},
mounted() {
if (this.gateway) {
this.getList();
}
this.$recvChanel('liveBus', (bool) => {
this.restet();
});
this.$recvChanel('websocketBus', (data) => {
this.dataTreat(data);
});
}
};
</script>
<style lang="scss" scoped>
.airport-information-wrapper {
width: 100%;
height: 32%;
position: relative;
margin-bottom: 15px;
.airport-information-content {
height: 100%;
width: 100%;
// background-color: blueviolet;
.airport-info-wrapper {
padding: 0 2%;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid rgba(0, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.5);
margin: 5px 0;
.airport-left {
margin-right: 10px;
}
.airport-center {
// transform: translateX(-40px);
font-size: 12px;
color: rgba(207, 255, 255, 1);
display: flex;
flex-direction: column;
// justify-content: space-between;
justify-content: center;
> div:nth-of-type(2) {
display: flex;
align-items: center;
margin-top: 5px;
img {
margin-left: 5px;
cursor: pointer;
}
}
}
.airport-right {
display: flex;
align-items: center;
color: rgba(27, 248, 195, 1);
}
}
.airport-list-wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
/* 创建4列每列等宽 */
// gap: 6px; /* 设置列和行之间的间距 */
grid-gap: 10px 6px;
.airport-list-item {
font-size: 14px;
.airport-list-top {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4px;
img {
width: 14px;
height: 14px;
margin-right: 4px;
}
div:first-child {
font-family: 'ddin';
}
}
.airport-list-bottom {
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<div class="airport-debug-list-item" :style="{ width: cusWidth ? cusWidth : '100%' }">
<div class="debug-top" :style="{ minHeight: showBottom ? '70%' : '100%' }">
<div class="debug-l">
<div class="debug-l-img">
<img :src="imgUrl" alt="" />
</div>
<div class="debug-l-text">
<div>{{ name }}</div>
<el-tooltip :content="status" placement="bottom">
<div class="debug-l-status">{{ status }}</div>
</el-tooltip>
</div>
</div>
<div class="debug-r">
<slot name="btn"></slot>
</div>
</div>
<div v-if="showBottom" class="debug-bottom">
<div class="debug-bottom-wrapper">
<slot name="progress"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: "DebugItem",
props: {
imgUrl: {
type: String,
},
status: {
type: String,
},
name: {
type: String,
},
cusWidth: {
type: String,
default: `100%`,
},
showBottom: {
type: Boolean,
default: false,
},
},
data() {
return {};
},
methods: {},
created() { },
mounted() { },
};
</script>
<style lang="scss" scoped>
.airport-debug-list-item {
font-size: 14px;
border: 1px solid rgba(0, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.5);
// height: 10vh;
padding: 20px 0;
width: 100%;
.debug-top {
padding: 0 3%;
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
min-height: 70%;
.debug-l {
display: flex;
align-items: center;
width: 70%;
.debug-l-img {
width: 24px;
height: 24px;
margin-right: 10px;
img {
width: 24px;
height: 24px;
}
}
.debug-l-text {
>div {
margin: 4px 0;
}
.debug-l-status {
width: 2.8vw;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.debug-r {
// width: 20%;
}
}
.debug-bottom {
padding: 0 2%;
transform: translateY(10px);
width: 100%;
display: flex;
align-items: center;
.debug-bottom-wrapper {
width: 100%;
}
}
}
</style>

View File

@ -0,0 +1,811 @@
<template>
<div class="airport-debug-wrapper">
<ModuleItem :title="'机场远程调试'" :subTitle="'Airport Remote Debugging'">
<template slot="titleOption">
<el-switch v-model="debugSwitch" active-color="#00FFFFFF" inactive-color="#CCCCCC33" @change="changeSwitch"> </el-switch>
</template>
<div class="airport-debug-content">
<div class="airport-debug-list-wrapper">
<div class="debug-item-box">
<DebugItem :name="jcxt.name" :imgUrl="jcxt.img" :status="jcxt.data" :showBottom="true">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(jcxt.key)" size="small">
{{ jcxt.btnStatus }}
</el-button>
</div>
</template>
<template slot="progress">
<el-progress :percentage="jcxt.progress" :show-text="false" :color="'#00FFFFFF'" :define-back-color="'#CCCCCC33'"></el-progress>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<DebugItem :name="cg.name" :imgUrl="cg.img" :status="cg.data" :showBottom="true">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(cg.key)" size="small">
{{ specialHandling(cg.key) !== '打开' ? '打开' : '关闭' }}
</el-button>
</div>
</template>
<template slot="progress">
<el-progress :percentage="cg.progress" :show-text="false" :color="'#00FFFFFF'" :define-back-color="'#CCCCCC33'"></el-progress>
</template>
</DebugItem>
</div>
<div v-if="number == 2" class="debug-item-box">
<DebugItem :name="tg.name" :imgUrl="tg.img" :status="tg.data" :showBottom="true">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(tg.key)" size="small">
{{ specialHandling(tg.key) !== '打开' ? '打开' : '关闭' }}
</el-button>
</div>
</template>
<template slot="progress">
<el-progress :percentage="tg.progress" :show-text="false" :color="'#00FFFFFF'" :define-back-color="'#CCCCCC33'"></el-progress>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<DebugItem :name="jyms.name" :imgUrl="jyms.img" :status="jyms.data">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(jyms.key)" size="small">
{{ jyms.btnStatus }}
</el-button>
</div>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<DebugItem :name="jccc.name" :imgUrl="jccc.img" :status="jccc.data">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(jccc.key)" size="small">
{{ jccc.btnStatus }}
</el-button>
</div>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<DebugItem :name="zqtc.name" :imgUrl="zqtc.img" :status="zqtc.data">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(zqtc.key)" size="small">
{{ dataMap.get(zqtc.key) == 0 ? '开启' : '关闭' }}
</el-button>
</div>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<!-- 我需要页面刚进来时status显示开机或者关机而不是开机中或者关机中这个状态来自dataTreat -->
<DebugItem
:name="fxq.name"
:imgUrl="fxq.img"
:status="
fxqLoading ? (fxqMethod == 'drone_open' ? '开机中' : '关机中') : fxqMethod ? (fxqMethod == 'drone_open' ? '开机' : '关机') : fxq.data
"
:showBottom="true"
>
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(fxq.key)" size="small">
<span v-show="!fxqLoading">{{ fxq.data == '开机' ? '关机' : '开机' }}</span>
<img v-show="fxqLoading" class="myLoading" src="../../../../images/loading.png" alt="" />
</el-button>
</div>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<DebugItem :name="kt.name" :imgUrl="kt.img" :status="kt.data" class="custom-width">
<template slot="btn">
<div class="debug-btn-groups">
<el-button v-for="item in kt.btns" :key="item.id" @click="handleBtnClick(item.id, true)" size="mini">
{{ item.name }}
</el-button>
</div>
</template>
</DebugItem>
</div>
<div class="debug-item-box">
<DebugItem :name="cd.name" :imgUrl="cd.img" :status="cd.data">
<template slot="btn">
<div class="debug-btn-group">
<el-button @click="handleBtnClick(cd.key)" size="small">
{{ dataAll.drone_charge_state.state == 0 ? '开启' : '关闭' }}
</el-button>
</div>
</template>
<template slot="progress">
<el-progress :percentage="cd.progress" :show-text="false" :color="'#00FFFFFF'" :define-back-color="'#CCCCCC33'"></el-progress>
</template>
</DebugItem>
</div>
</div>
</div>
</ModuleItem>
</div>
</template>
<script>
import { useAirStore } from '@/store/modules/drone';
import ModuleItem from '../ModuleItem/index.vue';
import DebugItem from './DebugItem.vue';
import { remoteDebug, listInfo, propertySet, sdrWorkmodeSwitch, airConditionerModeSwitch } from '@/api/air';
import debugJcxt from '../../../../images/home/debug-jcxt.png';
import debugCg from '../../../../images/home/debug-cg.png';
import debugTg from '../../../../images/home/debug-tg.png';
import debugJyms from '../../../../images/home/debug-jyms.png';
import debugJccc from '../../../../images/home/debug-jccc.png';
import debugZqtc from '../../../../images/home/debug-zqtc.png';
import debugFxq from '../../../../images/home/debug-fxq.png';
import debugKt from '../../../../images/home/debug-kt.png';
export default {
name: 'AirportRemoteDebugging',
components: { ModuleItem, DebugItem },
data() {
return {
debugSwitch: false,
// 机场系统
jcxt: {
name: '机场系统',
data: '工作中',
img: debugJcxt,
key: 'flighttask_step_code',
btnStatus: '重启',
progress: 0,
progressMethod: ['device_reboot']
},
cg: {
name: '舱盖',
data: '关',
img: debugCg,
key: 'cover_state',
btnStatus: '开启',
progress: 0,
progressMethod: ['cover_open', 'cover_close']
},
tg: {
name: '推杆',
data: '闭合',
img: debugTg,
key: 'putter_state',
btnStatus: '关闭',
progress: 0,
progressMethod: ['putter_open', 'putter_close']
},
jyms: {
name: '静音模式',
data: '未开启',
img: debugJyms,
key: 'silent_mode',
btnStatus: '开启'
},
jccc: {
name: '机场存储',
data: '0/0 GB',
img: debugJccc,
key: 'storage',
btnStatus: '格式化'
},
zqtc: {
name: '增强图传',
data: '未开启',
img: debugZqtc,
key: 'link_workmode',
btnStatus: '开启'
},
fxq: {
name: '飞行器',
data: '关机',
img: debugFxq,
key: 'device_online_status',
progress: 0,
progressMethod: ['drone_close'],
btnStatus: '开启'
},
kt: {
name: '空调',
data: '空闲中',
img: debugKt,
key: 'air_conditioner_state',
btns: [
{ name: '空闲', id: '0' },
{ name: '制冷', id: '1' },
{ name: '制热', id: '2' },
{ name: '除湿', id: '3' }
]
},
cd: {
name: '充电',
data: '未充电',
img: debugTg,
key: 'drone_charge_state',
btnStatus: '开启',
progress: 0,
progressMethod: ['charge_open', 'charge_close']
},
osd1: null,
osd2: null,
osd3: null,
osd4: null,
dataMap: new Map(),
dataAll: {
drone_charge_state: {
state: 0
}
},
info: {},
gateWay: useAirStore().gateWay,
number: 0,
fxqLoading: false,
fxqMethod: ''
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getDeviceNumber();
if (newValue) {
// 重置飞行器状态相关数据
this.fxqLoading = false;
this.fxqMethod = '';
this.fxq.data = '关机'; // 设置默认状态为关机
// 清空数据映射
this.dataMap = new Map();
this.dataAll = {
drone_charge_state: {
state: 0
}
};
}
},
deep: true
}
},
methods: {
// 获取设备列表
dataTreat(info) {
if (info.businessType !== 'osd4') {
if (['osd1', 'osd2', 'osd3'].includes(info.businessType)) {
this[info.businessType] = info.data;
Object.assign(this.dataAll, info.data);
} else {
// console.log("this[info.businessType]", info);
this.debuggingProgress(info.businessType, info.data);
}
} else {
this.osd4 = info.data;
}
//
if (this.dataMap.get('device_online_status') == '1' && this.fxqMethod == 'drone_open') {
this.fxqLoading = false;
this.fxqMethod = 'drone_open';
} else if (this.dataMap.get('device_online_status') == '0' && this.fxqMethod == 'drone_close') {
this.fxqLoading = false;
this.fxqMethod = 'drone_close';
if (window.airModel) {
window.airModel.remove();
window.airModel = null;
return;
}
} else if (this.dataMap.get('device_online_status') == '0') {
if (window.airModel) {
window.airModel.remove();
window.airModel = null;
return;
}
}
function flattenObjectToMap(obj, map = new Map()) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
flattenObjectToMap(value, map);
} else {
map.set(key, value);
}
}
}
return map;
}
this.dataMap = flattenObjectToMap(this.dataAll, this.dataMap);
this.jcxt.data = this.specialHandling(this.jcxt.key);
this.cg.data = this.specialHandling(this.cg.key);
this.jyms.data = this.specialHandling(this.jyms.key);
this.tg.data = this.specialHandling(this.tg.key);
this.jccc.data = this.specialHandling(this.jccc.key);
this.zqtc.data = this.specialHandling(this.zqtc.key);
this.fxq.data = this.specialHandling(this.fxq.key);
this.kt.data = this.specialHandling(this.kt.key);
this.cd.data = this.dataAll.drone_charge_state.state == 0 ? '未充电' : '充电中';
// console.log("this.dataMap", this.dataMap);
// console.log("this.jccc.data", this.dataMap.get(this.jccc.key));
if (this.dataMap.get('mode_code') && this.dataMap.get('mode_code') === 2) {
this.debugSwitch = true;
} else {
this.debugSwitch = false;
}
// console.log("mode_code", this.dataMap.get("mode_code"));
},
specialHandling(key) {
switch (key) {
case 'flighttask_step_code':
const flighttaskObj = {
'0': '作业准备中',
'1': '飞行作业中',
'2': '作业后状态恢复',
'3': '自定义飞行区更新中',
'4': '地形障碍物更新中',
'5': '任务空闲',
'255': '飞行器异常',
'256': '未知状态'
};
// console.log("this.dataMap.get(key)", this.dataMap.get(key));
return flighttaskObj[String(this.dataMap.get(key))];
case 'storage':
function kbToGb(kb) {
return kb / (1024 * 1024);
}
let a = kbToGb(this.dataMap.get('used')) || 0;
let b = kbToGb(this.dataMap.get('total')) || 0;
const used = a.toFixed(2);
const total = b.toFixed(2);
return `${used}/${total}GB`;
case 'cover_state':
const coverStateObj = {
'0': '关闭',
'1': '打开',
'2': '半开',
'3': '舱盖状态异常'
};
return coverStateObj[String(this.dataMap.get(key))];
case 'silent_mode':
const silentModeObj = {
'0': '非静音模式',
'1': '静音模式'
};
return silentModeObj[String(this.dataMap.get(key))];
case 'putter_state':
const putterStateObj = {
'0': '关闭',
'1': '打开',
'2': '半开',
'3': '推杆状态异常'
};
return putterStateObj[String(this.dataMap.get(key))];
case 'link_workmode':
const linkWorkmodeObj = {
'0': 'SDR 模式',
'1': '4G 融合模式'
};
return linkWorkmodeObj[String(this.dataMap.get(key))];
case 'device_online_status':
const deviceOnlineObj = {
'0': '关机',
'1': '开机'
};
return deviceOnlineObj[String(this.dataMap.get(key))];
case 'air_conditioner_state':
const airConditionerObj = {
'0': '空闲模式',
'1': '制冷模式',
'2': '制热模式',
'3': '除湿模式',
'4': '制冷退出模式',
'5': '制热退出模式',
'6': '除湿退出模式',
'7': '制冷准备模式',
'8': '制热准备模式',
'9': '除湿准备模式'
};
return airConditionerObj[String(this.dataMap.get(key))];
case 'drone_charge_state':
const chargeObj = {
'0': '空闲',
'1': '充电中'
};
return chargeObj[String(this.dataMap.get(key).state)];
default:
break;
}
},
handleBtnClick(key, isAirConditioner = false) {
// console.log("key", key);
switch (key) {
case 'flighttask_step_code':
this.airportRestart();
break;
case 'cover_state':
this.hatch();
break;
case 'putter_state':
this.putterStateChange();
break;
case 'silent_mode':
this.silentModeChange();
break;
case 'storage':
this.storageFormat();
break;
case 'link_workmode':
this.linkWorkmodeChange();
break;
case 'device_online_status':
this.AircraftBoot();
break;
case 'drone_charge_state':
this.chargeState();
break;
default:
// 空调
if (isAirConditioner) {
this.airConditioner(key);
}
break;
}
},
// 远程调试开关
changeSwitch(status) {
let air = localStorage.getItem('airGateway');
if (!air) {
this.$message.error('请先选择机场');
this.debugSwitch = false;
return;
}
this.debugSwitch = status;
const { gateway } = this.gateWay;
const params = { gateway, method: '' };
if (this.debugSwitch) {
params.method = 'debug_mode_open';
} else {
params.method = 'debug_mode_close';
}
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
// console.log("this.debugSwitch", this.debugSwitch);
},
// 机场重启
airportRestart() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const method = 'device_reboot';
const params = { gateway, method };
this.$sendChanel('liveBus', true);
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
},
// 飞行器开机
AircraftBoot() {
if (!this.debugSwitch) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const method = Number(this.dataMap.get('device_online_status')) === 0 ? 'drone_open' : 'drone_close';
const params = { gateway, method };
this.fxqLoading = true;
this.fxqMethod = method;
if (method == 'drone_close') {
this.$sendChanel('liveBus', false);
}
this.$sendChanel('airInfoBus', method == 'drone_open' ? true : false);
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
},
// 舱盖开关
hatch() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const method = Number(this.dataMap.get('cover_state')) === 0 ? 'cover_open' : 'cover_close';
const params = { gateway, method };
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
},
// 推杆开关
putterStateChange() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const method = Number(this.dataMap.get('putter_state')) === 0 ? 'putter_open' : 'putter_close';
const params = { gateway, method };
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
},
// 静音模式开关
silentModeChange() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const status = Number(this.dataMap.get('silent_mode')) === 0 ? 1 : 0;
if (status === 0) {
this.jyms.btnStatus = '开启';
} else {
this.jyms.btnStatus = '关闭';
}
const params = { gateway, params: { silent_mode: status } };
propertySet(params).then((res) => {
this.$message.success(res.msg);
});
},
// 增强图传
linkWorkmodeChange() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
// const method = "sdr_workmode_switch";
// const params = { gateway, method };
// remoteDebug(params).then((res) => {});
const action = this.dataMap.get('link_workmode') == 1 ? 0 : 1;
const params = { gateway, action };
sdrWorkmodeSwitch(params).then((res) => {
const { code, msg } = res;
if (code == 200) {
this.$message.success('操作成功');
} else {
this.$message.error(msg);
}
});
},
// 空调
airConditioner(key) {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const params = { gateway, action: key };
airConditionerModeSwitch(params).then((res) => {
// console.log("空调-----------", res);
this.$message.success(res.msg);
});
},
// 机场存储格式化
storageFormat() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const params = { gateway, method: 'device_format' };
this.$confirm('此操作将清除机场存储数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'customMessageBox',
confirmButtonClass: 'customConfirm',
cancelButtonClass: 'customCancel'
}).then(() => {
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
});
},
chargeState() {
if (!this.gateWay) {
this.$message.error('机场已退出远程调试模式,无法执行当前操作');
return;
}
const { gateway } = this.gateWay;
const method = Number(this.dataAll.drone_charge_state.state) === 0 ? 'charge_open' : 'charge_close';
const params = { gateway, method };
remoteDebug(params).then((res) => {
this.$message.success(res.msg);
});
},
// 调试进度
debuggingProgress(key, data) {
if (this.cg.progressMethod.includes(key)) {
this.cg.progress = data.output.progress.percent;
} else if (this.jcxt.progressMethod.includes(key)) {
this.jcxt.progress = data.output.progress.percent;
} else if (this.fxq.progressMethod.includes(key)) {
this.fxq.progress = data.output.progress.percent;
} else if (this.tg.progressMethod.includes(key)) {
this.tg.progress = data.output.progress.percent;
} else if (this.cd.progressMethod.includes(key)) {
this.cd.progress = data.output.progress.percent;
}
},
// 获取数量
getDeviceNumber() {
const gateway = this.gateWay.gateway;
// 获取首位数字
const firstDigit = parseInt(gateway.charAt(0));
// 判断是4还是7
if (firstDigit === 4) {
// 是4返回4
this.number = 2;
} else {
this.number = 1;
}
}
},
created() {
this.$recvChanel('websocketBus', (data) => {
// console.log("收到的数据222", data);
this.dataTreat(data);
});
if (this.gateWay) {
this.getDeviceNumber();
}
},
mounted() {}
};
</script>
<style lang="scss">
@keyframes identifier {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.airport-debug-wrapper {
width: 100%;
height: 32%;
margin-bottom: 15px;
position: relative;
.airport-debug-content {
width: 100%;
height: 100%;
padding: 0 2%;
.airport-debug-list-wrapper {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 创建2列每列等宽 */
gap: 12px;
/* 设置列和行之间的间距 */
overflow-y: auto;
.debug-item-box {
.myLoading {
width: 10px;
height: 10px;
animation: identifier 1s linear infinite;
}
.custom-width {
// width: 130% !important;
// display: flex;
// justify-content: space-around;
// background-color: aquamarine;
.debug-l {
width: 50%;
}
.debug-r {
width: 50%;
}
}
.debug-btn-group {
display: flex;
justify-content: flex-end;
.el-button--mini {
padding: 5px !important;
}
}
.debug-btn-groups {
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 创建2列每列等宽 */
gap: 2px;
/* 设置列和行之间的间距 */
// padding: 2%;
.el-button--mini {
padding: 3px !important;
}
}
.el-button {
pointer-events: all;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 0.5);
color: #fff;
margin: 0 !important;
}
}
}
}
}
.customMessageBox {
background: linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%), rgba(0, 0, 0, 0.6);
border: 1.5px solid rgba(0, 255, 255, 1);
.el-message-box__message,
.el-message-box__title,
.el-message-box__close {
color: #fff;
&:hover {
color: #fff;
}
}
}
.customConfirm,
.customCancel {
border-radius: 4px !important;
background: rgba(0, 255, 255, 0.2) !important;
border: 1px solid rgba(0, 255, 255, 1) !important;
color: #fff;
&:hover {
color: #fff;
}
}
/* 整个滚动条 */
::-webkit-scrollbar {
width: 8px;
/* 滚动条的宽度 */
height: 8px;
/* 滚动条的高度(横向滚动条) */
}
/* 滑块部分 */
::-webkit-scrollbar-thumb {
background-color: rgba(0, 255, 255, 0.5);
/* 滑块的背景颜色 */
border-radius: 10px;
/* 滑块的圆角 */
border: none;
/* 滑块的边框 */
background-clip: content-box;
/* 背景裁剪 */
}
/* 滑块在悬停状态下的样式 */
::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 255, 255, 0.7);
/* 悬停时滑块的颜色 */
}
/* 滚动条轨道 */
::-webkit-scrollbar-track {
background-color: transparent;
/* 轨道的背景颜色 */
border-radius: 10px;
/* 轨道的圆角 */
}
</style>

View File

@ -0,0 +1,80 @@
<template>
<div class="module-item-container">
<div class="module-item-title">
<div class="module-item-title-left">
<div class="module-item-title-left-main">{{ title }}</div>
<div class="module-item-title-left-sub">{{ subTitle }}</div>
</div>
<div class="module-item-title-right">
<slot name="titleOption"></slot>
</div>
</div>
<div class="module-item-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "ModuleItem",
props: ["title", "subTitle"],
computed: {},
data() {
return {};
},
created() { },
mounted() { },
};
</script>
<style lang="scss" scoped>
.module-item-container {
width: 100%;
height: 100%;
color: #fff;
.module-item-title {
height: 40px;
width: 100%;
margin-bottom: 15px;
background: url("../../../../images/home/module-item-title.png") no-repeat;
background-size: 100% 100%;
display: flex;
padding-left: 8%;
justify-content: space-between;
align-items: center;
.module-item-title-left {
display: flex;
align-items: flex-end;
&-main {
font-size: 18px;
font-weight: 700;
background: linear-gradient(180deg,
rgba(255, 255, 255, 1) 40%,
rgba(0, 255, 255, 1) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-right: 10px;
font-family: "alimamashuheiti";
}
&-sub {
font-size: 14px;
font-weight: 500;
}
}
.module-item-title-right {
// background-color: aqua;
}
}
.module-item-content {
width: 100%;
height: calc(100% - 50px);
}
}
</style>

View File

@ -0,0 +1,755 @@
<template>
<div class="cabin-live-wrapper">
<ModuleItem :title="'无人机机场监控直播'" :subTitle="'Monitor live streaming'">
<!-- <button @click="startRecording" :disabled="isRecording">开始录制</button>
<button @click="stopRecording" :disabled="!isRecording">停止录制</button>
<a ref="downloadLink" style="display: none;">下载视频</a> -->
<div
:id="'cabin-live-video_' + info.cameraIndex"
ref="monitorLiveVideo"
:class="[screen.currentStatus ? 'cabin-live-content-full' : 'cabin-live-content']"
>
<div class="cabin-live-title">
<div class="cabin-live-title-left">机舱SN:{{ info.sn || '----' }}</div>
<div class="cabin-live-title-right">
<!-- <el-tooltip :content="backlight.tips" placement="bottom"> -->
<div class="bgd cabin-live-title-right-wrapper">
<img :src="backlight.img" @click="changeBacklight" :title="backlight.tips" />
</div>
<!-- </el-tooltip> -->
<el-dropdown v-if="number == 1" size="mini" placement="bottom" trigger="click" :hide-on-click="false" @visible-change="visibleDropdown">
<!-- <el-tooltip :content="monitor.tips" placement="bottom"> -->
<div class="monitor cabin-live-title-right-wrapper">
<img :src="monitor.img" :title="monitor.tips" />
</div>
<!-- </el-tooltip> -->
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in monitor.list" :key="item.name" :class="{ active: monitor.current === item.index }">
<span @click="monitorCheck(item)">{{ item.name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown size="mini" trigger="click" placement="bottom" :hide-on-click="false" @visible-change="visibleDropdown">
<!-- <el-tooltip :content="set.tips"> -->
<div class="monitor cabin-live-title-right-wrapper">
<img :src="set.img" :title="set.tips" />
</div>
<!-- </el-tooltip> -->
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in set.list" :key="item.name" :class="{ active: set.current === item.index }">
<span @click="setCheck(item)">{{ item.name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- <el-tooltip :content="reset.tips" placement="bottom"> -->
<div class="bgd cabin-live-title-right-wrapper">
<img :src="reset.img" @click="handleReset" :title="reset.tips" />
</div>
<!-- </el-tooltip> -->
<!-- <el-tooltip :content="screen.tips" placement="bottom"> -->
<div class="bgd cabin-live-title-right-wrapper">
<img :src="screen.img" @click="makeFullScreen(`cabin-live-video_${info.cameraIndex}`)" :title="screen.tips" />
</div>
<!-- </el-tooltip> -->
<!-- <el-tooltip :content="switchs.tips" placement="bottom"> -->
<div class="bgd cabin-live-title-right-wrapper">
<img :src="switchs.img" @click="handleOpen" :title="switchs.tips" />
</div>
<!-- </el-tooltip> -->
</div>
</div>
<div class="cabin-live-video">
<!-- <video ref="sourceVideo" v-show="switchs.currentStatus && !switchs.loading" class="cabin-live-video-item"
style="position: absolute;width: 100%;height: 100%;object-fit: fill;"
:id="'video-webrtc_' + info.cameraIndex" autoplay muted width="100%" height="100%"></video> -->
<div v-show="switchs.currentStatus && !switchs.loading" class="cabin-live-video-item">
<webrtc :ref="'video-webrtc_' + info.cameraIndex"></webrtc>
</div>
<div v-show="!switchs.currentStatus && !switchs.loading" class="live-stop">
<img src="../../../../images/home/live-stop.png" alt="" />
<span>机舱已经停止直播</span>
</div>
<div v-show="switchs.loading" class="live-loading">
<img src="../../../../images/home/loading.png" alt="" />
<span>加载中请稍侯</span>
</div>
</div>
</div>
</ModuleItem>
</div>
</template>
<script>
import ModuleItem from '../ModuleItem/index.vue';
import { listInfo, liveStart, liveStop, remoteDebug, liveChangeQuality, cameraChange } from '@/api/air';
import webrtc from '@/components/webrtc';
import { useAirStore } from '@/store/modules/drone';
import { getHost, getRtmpPort, getRtcPort } from '@/utils/auth';
import uavBgd from '../../../../images/home/uav-bgd.png';
import uavMonitor from '../../../../images/home/uav-monitor.png';
import uavSet from '../../../../images/home/uav-set.png';
import uavReset from '../../../../images/home/uav-reset.png';
import uavScreen from '../../../../images/home/uav-screen.png';
import uavClose from '../../../../images/home/uav-close.png';
import uavBgdOpen from '../../../../images/home/uav-bgd-open.png';
import uavScreenEsc from '../../../../images/home/uav-screen-esc.png';
import _ from 'lodash';
export default {
name: 'MonitorLiveStreaming',
components: { ModuleItem, webrtc },
data() {
return {
videoUrl: '',
flvPlayer: null,
info: {},
gateWay: useAirStore().gateWay,
time1: null,
time2: null,
//背光灯
backlight: {
light: false, // 是否开启
img: uavBgd,
tips: '打开背光灯',
key: 'supplement_light_open'
},
// 摄像头
monitor: {
img: uavMonitor,
list: [
{ name: '舱内', active: true, index: 0 },
{ name: '舱外', active: false, index: 1 }
],
tips: '摄像头',
key: 'supplement_light_open',
current: null
},
// 设置
set: {
img: uavSet,
list: [
{ name: '自适应', active: true, index: 0 },
{ name: '流畅', active: false, index: 1 },
{ name: '标清', active: false, index: 2 },
{ name: '高清', active: false, index: 3 },
{ name: '超清', active: false, index: 4 }
],
tips: '设置',
current: null
},
// 重置
reset: {
img: uavReset,
tips: '刷新',
key: 'device_reboot'
},
// 全屏切换
screen: {
img: uavScreen,
tips: '全屏切换',
currentStatus: false
},
//开关
switchs: {
img: uavClose,
tips: '开启直播',
currentStatus: false,
loading: false
},
number: 0,
isRecording: false,
mediaRecorder: null,
recordedChunks: [],
canvas: null,
canvasStream: null,
drawInterval: null
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getList();
this.getDeviceNumber();
},
deep: true
}
},
methods: {
// 背光灯
changeBacklight() {
this.backlight.light = !this.backlight.light;
if (this.backlight.light) {
this.backlight.img = uavBgdOpen;
this.backlight.key = 'supplement_light_open';
this.backlight.tips = '关闭背光灯';
this.backlight.msg = '已为你开启背光灯';
} else {
this.backlight.img = uavBgd;
this.backlight.key = 'supplement_light_close';
this.backlight.tips = '打开背光灯';
this.backlight.msg = '已为关闭背光灯';
}
const { gateway } = this.info;
let method = this.backlight.key;
const params = { gateway, method };
remoteDebug(params)
.then((res) => {
this.$message.success(this.backlight.msg);
})
.finally(() => {
this.handleFullscreenChange();
});
},
// 摄像头
monitorCheck(data) {
console.log('data', data);
this.monitor.current = data.index;
this.monitor.list.forEach((item) => {
if (this.monitor.current === item.index) {
item.active = true;
} else {
item.active = false;
}
});
const { gateway } = this.info;
const params = {
gateway,
cameraIndex: this.gateWay.cameraIndex,
cameraPosition: data.index,
sn: this.gateWay.sn,
videoIndex: this.gateWay.videoIndex
};
cameraChange(params)
.then((res) => {
this.$message.success(res.data.msg);
})
.finally(() => {
this.handleFullscreenChange();
});
console.log('this.monitor', this.monitor.list);
},
// // 开始录制
// startRecording() {
// this.isRecording = true;
// this.recordedChunks = [];
// // 获取 video 元素
// const sourceVideo = this.$refs.sourceVideo;
// console.log('sourceVideo', sourceVideo);
// // 创建 canvas 并设置大小
// this.canvas = document.createElement("canvas");
// this.canvas.width = sourceVideo.videoWidth;
// this.canvas.height = sourceVideo.videoHeight;
// const ctx = this.canvas.getContext("2d");
// // 定时将 video 内容绘制到 canvas
// const drawToCanvas = () => {
// if (!sourceVideo.paused && !sourceVideo.ended) {
// ctx.drawImage(sourceVideo, 0, 0, this.canvas.width, this.canvas.height);
// this.drawInterval = requestAnimationFrame(drawToCanvas);
// }
// };
// drawToCanvas();
// // 获取 canvas 的 MediaStream 并创建 MediaRecorder
// this.canvasStream = this.canvas.captureStream();
// this.mediaRecorder = new MediaRecorder(this.canvasStream, { mimeType: "video/webm" });
// // 处理录制的数据块
// this.mediaRecorder.ondataavailable = (event) => {
// if (event.data.size > 0) {
// this.recordedChunks.push(event.data);
// }
// };
// // 录制完成后生成下载链接
// this.mediaRecorder.onstop = () => {
// const blob = new Blob(this.recordedChunks, { type: "video/webm" });
// console.log('blobblobblobblob', blob);
// const url = URL.createObjectURL(blob);
// const downloadLink = document.createElement("a");
// downloadLink.href = url;
// downloadLink.download = String(new Date().getTime()) + ".webm";
// downloadLink.click();
// };
// // 开始录制
// this.mediaRecorder.start();
// },
// stopRecording() {
// this.isRecording = false;
// // 停止录制和绘制
// if (this.mediaRecorder) {
// this.mediaRecorder.stop();
// }
// if (this.drawInterval) {
// cancelAnimationFrame(this.drawInterval);
// }
// },
// 设置
setCheck(data) {
console.log('data', data);
this.set.current = data.index;
const { gateway, sn, cameraIndex, videoIndex } = this.info;
const params = {
gateway,
sn,
cameraIndex,
videoIndex,
videoQuality: this.set.current
};
liveChangeQuality(params)
.then((res) => {
this.$message.success(res.data.msg);
this.set.list.forEach((item) => {
if (this.set.current === item.index) {
item.active = true;
} else {
item.active = false;
}
});
})
.finally(() => {
this.handleFullscreenChange();
});
},
// 刷新
handleReset() {
if (this.switchs.currentStatus) {
this.switchs.loading = true;
this.debouncehandleReset();
}
},
debouncehandleReset: _.debounce(async function () {
{
if (this.switchs.currentStatus) {
await this.stopLive();
const res = await this.startLive();
this.$message.success(res.msg);
this.switchs.currentStatus = true;
this.switchs.loading = false;
this.handleFullscreenChange();
}
}
}, 600),
// 电源
handleOpen() {
this.switchs.loading = true;
// 关闭就不显示加载动画
this.switchs.currentStatus && (this.switchs.loading = false);
this.debounceHandleOpen();
},
// 加个防抖不准频繁请求解决频繁请求关不了的问题
debounceHandleOpen: _.debounce(function () {
if (this.switchs.currentStatus) {
this.stopLive()
.then((res) => {
this.switchs.currentStatus = false;
this.switchs.tips = '开启直播';
this.$message.success(res.msg);
this.switchs.loading = false;
})
.catch((err) => {
console.log('err', err);
this.switchs.loading = false;
})
.finally(() => {
this.switchs.loading = false;
this.handleFullscreenChange();
});
} else {
this.startLive()
.then((res) => {
console.log('res', res);
this.switchs.currentStatus = true;
this.switchs.tips = '关闭直播';
this.$message.success(res.msg);
this.switchs.loading = false;
})
.catch((err) => {
console.log('err', err);
this.switchs.loading = false;
})
.finally(() => {
console.log('finally');
this.switchs.loading = false;
this.handleFullscreenChange();
});
}
}, 600),
makeFullScreen(id) {
const videoElement = document.getElementById(id);
if (!this.screen.currentStatus) {
this.screen.img = uavScreenEsc;
this.screen.currentStatus = true;
if (videoElement.requestFullscreen) {
videoElement.requestFullscreen();
} else if (videoElement.mozRequestFullScreen) {
// Firefox
videoElement.mozRequestFullScreen();
} else if (videoElement.webkitRequestFullscreen) {
// Chrome, Safari and Opera
videoElement.webkitRequestFullscreen();
} else if (videoElement.msRequestFullscreen) {
// IE/Edge
videoElement.msRequestFullscreen();
}
} else {
this.screen.img = uavScreen;
if (document.fullscreenElement) {
document
.exitFullscreen()
.then(() => {
this.screen.currentStatus = false;
clearInterval(this.time1);
clearInterval(this.time2);
})
.catch((err) => {
console.error(`Failed to exit full screen: ${err}`);
});
}
}
},
// 用于解决全屏下下拉菜单不显示的问题
visibleDropdown(flag) {
this.time1 = setTimeout(() => {
if (document.querySelector('.el-dropdown-menu')) {
this.$refs.monitorLiveVideo.appendChild(document.querySelectorAll('.el-dropdown-menu')[1]);
this.$refs.monitorLiveVideo.appendChild(document.querySelectorAll('.el-dropdown-menu')[0]);
}
}, 200);
},
// 获取设备列表
getList() {
listInfo({
pageNum: 1,
pageSize: 10,
gateway: this.gateWay.gateway,
type: '机场'
}).then((res) => {
console.log('list-------', res);
this.info = res.rows[0];
});
},
// 开启直播
startLive() {
return new Promise((resovle, reject) => {
if (this.info) {
const { cameraIndex, gateway, sn, videoIndex } = this.info;
liveStart({
cameraIndex,
definition: 0,
gateway,
sn,
videoIndex,
host: getHost(),
rtmpPort: getRtmpPort(),
rtcPort: getRtcPort()
})
.then((res) => {
// this.playFlv(
// this.videoUrl,
// "video-webrtc_" + this.info.cameraIndex,
// this.info.cameraIndex
// );
// webrtc
if (res.code == 200) {
this.videoUrl = res.data.url;
this.$refs['video-webrtc_' + cameraIndex].startPlay(res.data.url);
resovle(res);
}
resovle(res);
this.handleFullscreenChange();
})
.catch((err) => {
reject(err);
this.handleFullscreenChange();
});
}
});
},
// 关闭直播
stopLive() {
return new Promise((resovle, reject) => {
if (this.info) {
const { cameraIndex, gateway, sn, videoIndex } = this.info;
liveStop({
cameraIndex,
definition: 0,
gateway,
sn,
videoIndex
})
.then((res) => {
console.log('res-------', res);
this.flvDestroy();
resovle(res);
this.handleFullscreenChange();
})
.catch((err) => {
reject(err);
this.handleFullscreenChange();
});
}
});
},
// 播放flv视频
playFlv(flv, videoId) {
if (flvjs.isSupported()) {
var videoElement = document.getElementById(videoId);
this.flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
hasAudio: false,
url: flv,
enableStashBuffer: true, //
enableWorker: true,
autoCleanupSourceBuffer: true //自动清除缓存
});
this.$nextTick(() => {
this.flvPlayer.attachMediaElement(videoElement);
this.flvPlayer.load();
this.flvPlayer.play();
});
}
},
// 关闭flv
flvDestroy() {
if (this.flvPlayer) {
try {
this.flvPlayer.pause(); // 暂停播放数据流
this.flvPlayer.unload(); // 取消数据流加载
this.flvPlayer.destroy(); // 销毁播放实例
// flvPlayer.off("error"); // 移除错误处理程序
this.flvPlayer.detachMediaElement(); // 将播放实例从节点中取出
this.flvPlayer = null;
} catch (error) {}
}
},
// 判断全屏添加消息提示
handleFullscreenChange() {
console.log('全屏---------');
// 检查是否处于全屏模式
if (document.fullscreenElement) {
console.log('进入全屏模式');
// 执行你的代码
if (document.querySelector('.el-message')) {
this.$refs.monitorLiveVideo.appendChild(document.querySelectorAll('.el-message')[0]);
}
}
},
// 我需要通过获取this.gateWay.gateway(7CTDM4S00B459L)中的首位数字来判断当前设备的数量
getDeviceNumber() {
const gateway = this.gateWay.gateway;
// 获取首位数字
const firstDigit = parseInt(gateway.charAt(0));
// 判断是4还是7
if (firstDigit === 4) {
// 是4返回4
this.number = 2;
} else {
this.number = 1;
}
}
},
mounted() {
if (this.gateWay) {
this.getList();
this.getDeviceNumber();
}
this.$recvChanel('liveBus', (flag) => {
if (flag && this.switchs.currentStatus) {
this.switchs.currentStatus = false;
this.switchs.tips = '开启直播';
}
});
this.$nextTick(() => {
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
this.screen.currentStatus = false;
this.screen.img = uavScreen;
}
});
});
},
beforeDestroy() {
// 在组件销毁前移除事件监听
document.removeEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
this.screen.currentStatus = false;
this.screen.img = uavScreen;
}
});
}
};
</script>
<style lang="scss">
.cabin-live-wrapper {
width: 100%;
height: 30%;
margin-bottom: 15px;
position: relative;
.cabin-live-content {
width: 100%;
height: 100%;
padding: 10px;
background: url('../../../../images/home/UAV-bg.png') no-repeat;
background-size: 100% 100%;
.cabin-live-title {
height: 24px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
&-left {
font-size: 14px;
}
&-right {
display: flex;
align-items: center;
justify-content: center;
&-wrapper {
width: 16px;
height: 16px;
margin: 0 3px;
cursor: pointer;
img {
height: 100%;
width: 100%;
}
}
}
}
.cabin-live-video {
margin-top: 10px;
height: calc(100% - 34px);
width: 100%;
position: relative;
clip-path: polygon(0 0, 97% 0, 100% 7%, 100% 100%, 3% 100%, 0 93%);
border: 1px solid rgba(0, 255, 255, 1);
.cabin-live-video-item {
height: 100%;
width: 100%;
}
.live-stop {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #000;
flex-direction: column;
size: 12px;
}
.live-loading {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
size: 12px;
> img {
height: 50px;
height: 50px;
}
}
}
}
// 全屏
.cabin-live-content-full {
width: 100%;
height: 100%;
padding: 10px;
.cabin-live-title {
height: 24px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
top: 0;
left: 0;
&-left {
font-size: 14px;
}
&-right {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 0;
&-wrapper {
width: 16px;
height: 16px;
margin: 0 3px;
cursor: pointer;
img {
height: 100%;
width: 100%;
}
}
}
}
.cabin-live-video {
margin-top: 10px;
height: calc(100% - 34px);
width: 100%;
position: relative;
.cabin-live-video-item {
height: 100%;
width: 100%;
}
.live-stop {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #000;
flex-direction: column;
size: 12px;
}
.live-loading {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
size: 12px;
> img {
height: 50px;
height: 50px;
}
}
}
}
}
.active {
background: rgba(0, 255, 255, 0.2);
color: rgba(0, 255, 255, 1);
}
</style>

View File

@ -0,0 +1,784 @@
<template>
<div class="cabin-live-wrapper">
<ModuleItem :title="'无人机机场监控直播'" :subTitle="'Monitor live streaming'">
<div
:id="'cabin-live-video_' + info.cameraIndex"
ref="monitorLiveVideo"
:class="[screen.currentStatus ? 'cabin-live-content-full' : 'cabin-live-content']"
>
<div class="cabin-live-title">
<div class="cabin-live-title-left">机舱SN:{{ info.sn || '----' }}</div>
<div class="cabin-live-title-right">
<el-tooltip :content="backlight.tips" placement="bottom">
<div class="bgd cabin-live-title-right-wrapper">
<img :src="backlight.img" @click="changeBacklight" />
</div>
</el-tooltip>
<!-- -->
<el-dropdown v-if="number == 2" size="mini" trigger="click" :hide-on-click="false" @visible-change="visibleDropdown">
<el-tooltip :content="monitor.tips" placement="bottom">
<div class="monitor cabin-live-title-right-wrapper">
<img :src="monitor.img" />
</div>
</el-tooltip>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in monitor.list" :key="item.name" :class="{ active: monitor.current === item.index }">
<span @click="monitorCheck(item)">{{ item.name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown size="mini" trigger="click" :hide-on-click="false" @visible-change="visibleDropdown">
<el-tooltip :content="set.tips" placement="bottom">
<div class="monitor cabin-live-title-right-wrapper">
<img :src="set.img" />
</div>
</el-tooltip>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in set.list" :key="item.name" :class="{ active: set.current === item.index }">
<span @click="setCheck(item)">{{ item.name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- <div class="monitor cabin-live-title-right-wrapper" :ref="'screenFull'">
<el-tooltip :content="set.tips" placement="bottom">
<img :src="set.img" @click="showDropDown()" />
</el-tooltip>
<div v-show="set.show" class="monitor-live-dropdown-menu">
<span v-for="itm in set.list" :key="itm.value" class="dropdown-item"
:class="{ 'dropdown-active': itm.active }" @click="setCheck(itm)">
{{ itm.name }}
</span>
</div>
</div> -->
<el-tooltip :content="reset.tips" placement="bottom">
<div class="bgd cabin-live-title-right-wrapper">
<img :src="reset.img" @click="handleReset" />
</div>
</el-tooltip>
<el-tooltip :content="screen.tips" placement="bottom">
<div class="bgd cabin-live-title-right-wrapper">
<img :src="screen.img" @click="makeFullScreen(`cabin-live-video_${info.cameraIndex}`)" />
</div>
</el-tooltip>
<el-tooltip :content="switchs.tips" placement="bottom">
<div class="bgd cabin-live-title-right-wrapper">
<img :src="switchs.img" @click="handleOpen" />
</div>
</el-tooltip>
</div>
</div>
<div class="cabin-live-video">
<video
v-show="switchs.currentStatus && !switchs.loading"
class="cabin-live-video-item"
style="position: absolute; width: 100%; height: 100%; object-fit: fill"
:id="'video-webrtc_' + info.cameraIndex"
autoplay
muted
width="100%"
height="100%"
></video>
<div v-show="!switchs.currentStatus && !switchs.loading" class="live-stop">
<img :src="require('../../../../images/home/live-stop.png')" alt="" />
<span>机舱已经停止直播</span>
</div>
<div v-show="switchs.loading" class="live-loading">
<img :src="require('../../../../images/home/loading.png')" alt="" />
<span>加载中请稍侯</span>
</div>
</div>
</div>
</ModuleItem>
</div>
</template>
<script>
import ModuleItem from '../ModuleItem/index.vue';
import { listInfo, liveStart, liveStop, remoteDebug, liveChangeQuality, cameraChange } from '@/api/air';
import _ from 'lodash';
import { getHost, getRtmpPort, getRtcPort } from '@/utils/auth';
import { useAirStore } from '@/store/modules/drone';
export default {
name: 'MonitorLiveStreaming',
components: { ModuleItem },
data() {
return {
videoUrl: '',
flvPlayer: null,
info: {},
gateWay: useAirStore().gateWay,
time1: null,
time2: null,
number: 0,
//背光灯
backlight: {
light: false, // 是否开启
img: require('../../../../images/home/uav-bgd.png'),
tips: '打开补光灯',
key: 'supplement_light_open',
msg: '补光灯已开启'
},
// 摄像头
monitor: {
img: require('../../../../images/home/uav-monitor.png'),
list: [
{ name: '舱外', active: false, index: 1 },
{ name: '舱内', active: true, index: 0 }
],
tips: '摄像头',
key: 'supplement_light_open',
current: 0
},
// 设置
set: {
show: false,
img: require('../../../../images/home/uav-set.png'),
list: [
{ name: '自适应', active: true, index: 0 },
{ name: '流畅', active: false, index: 1 },
{ name: '标清', active: false, index: 2 },
{ name: '高清', active: false, index: 3 },
{ name: '超清', active: false, index: 4 }
],
tips: '设置',
current: 0
},
// 重置
reset: {
img: require('../../../../images/home/uav-reset.png'),
tips: '刷新',
key: 'device_reboot'
},
// 全屏切换
screen: {
img: require('../../../../images/home/uav-screen.png'),
tips: '全屏切换',
currentStatus: false
},
//开关
switchs: {
img: require('../../../../images/home/uav-close.png'),
tips: '开启直播',
currentStatus: false,
loading: false
}
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getList();
},
deep: true
}
},
methods: {
showDropDown() {
this.set.show = true;
},
// 关闭直播
stopLives() {
if (this.flvPlayer) {
this.stopLive();
}
},
// 背光灯
changeBacklight() {
this.backlight.light = !this.backlight.light;
if (this.backlight.light) {
this.backlight.img = require('../../../../images/home/uav-bgd-open.png');
this.backlight.key = 'supplement_light_open';
this.backlight.tips = '关闭补光灯';
this.backlight.msg = '补光灯已开启';
} else {
this.backlight.img = require('../../../../images/home/uav-bgd.png');
this.backlight.key = 'supplement_light_close';
this.backlight.tips = '打开补光灯';
this.backlight.msg = '补光灯已关闭';
}
const { gateway } = this.info;
let method = this.backlight.key;
const params = { gateway, method };
remoteDebug(params)
.then((res) => {
console.log(res);
console.log(this.backlight.msg);
this.$message.success(this.backlight.msg);
})
.finally(() => {
this.handleFullscreenChange();
});
},
// 摄像头
monitorCheck(data) {
// console.log("data", data);
this.monitor.current = data.index;
this.monitor.list.forEach((item) => {
if (this.monitor.current === item.index) {
item.active = true;
} else {
item.active = false;
}
});
const { gateway } = this.info;
const params = {
gateway,
cameraIndex: this.gateWay.cameraIndex,
cameraPosition: data.index,
sn: this.gateWay.sn,
videoIndex: this.gateWay.videoIndex
};
cameraChange(params)
.then((res) => {
this.$message.success(res.msg);
})
.finally(() => {
this.handleFullscreenChange();
});
// console.log("this.monitor", this.monitor.list);
},
// 设置
setCheck(data) {
// console.log("data", data);
this.set.show = false;
this.set.current = data.index;
const { gateway, sn, cameraIndex, videoIndex } = this.info;
const params = {
gateway,
sn,
cameraIndex,
videoIndex,
videoQuality: this.set.current
};
liveChangeQuality(params)
.then((res) => {
this.set.list.forEach((item) => {
if (this.set.current === item.index) {
item.active = true;
this.$message.success(res.msg);
} else {
item.active = false;
}
});
})
.finally(() => {
this.handleFullscreenChange();
});
},
// 刷新
handleReset() {
// if (this.switchs.currentStatus) {
this.switchs.loading = true;
this.debouncehandleReset();
// }
},
debouncehandleReset: _.debounce(async function () {
{
if (this.switchs.currentStatus) {
// await this.stopLive();
const res = await this.startLive();
this.$message.success(res.msg);
this.switchs.currentStatus = true;
this.switchs.loading = false;
this.handleFullscreenChange();
}
}
}, 600),
// 电源
handleOpen() {
this.switchs.loading = true;
// 关闭就不显示加载动画
this.switchs.currentStatus && (this.switchs.loading = false);
this.debounceHandleOpen();
},
// 加个防抖不准频繁请求解决频繁请求关不了的问题
debounceHandleOpen: _.debounce(function () {
if (this.switchs.currentStatus) {
this.stopLive()
.then((res) => {
this.switchs.currentStatus = false;
this.switchs.tips = '开启直播';
this.$message.success(res.msg);
this.switchs.loading = false;
})
.catch((err) => {
// console.log("err", err);
this.switchs.loading = false;
})
.finally(() => {
this.switchs.loading = false;
this.handleFullscreenChange();
});
} else {
this.startLive()
.then((res) => {
// console.log("res", res);
this.switchs.currentStatus = true;
this.switchs.tips = '关闭直播';
this.$message.success(res.msg);
this.switchs.loading = false;
})
.catch((err) => {
// console.log("err", err);
this.switchs.loading = false;
})
.finally(() => {
// console.log("finally");
this.switchs.loading = false;
this.handleFullscreenChange();
});
}
}, 600),
makeFullScreen(id) {
this.$nextTick(() => {
const videoElement = document.getElementById(id);
if (!this.screen.currentStatus) {
this.screen.img = require('../../../../images/home/uav-screen-esc.png');
this.screen.currentStatus = true;
if (videoElement.requestFullscreen) {
videoElement.requestFullscreen();
} else if (videoElement.mozRequestFullScreen) {
// Firefox
videoElement.mozRequestFullScreen();
} else if (videoElement.webkitRequestFullscreen) {
// Chrome, Safari and Opera
videoElement.webkitRequestFullscreen();
} else if (videoElement.msRequestFullscreen) {
// IE/Edge
videoElement.msRequestFullscreen();
}
} else {
this.screen.img = require('../../../../images/home/uav-screen.png');
if (document.fullscreenElement) {
document
.exitFullscreen()
.then(() => {
this.screen.currentStatus = false;
clearInterval(this.time1);
clearInterval(this.time2);
})
.catch((err) => {
console.error(`Failed to exit full screen: ${err}`);
});
}
}
});
},
// 用于解决全屏下下拉菜单不显示的问题
visibleDropdown() {
// this.time1 = setTimeout(() => {
// // console.log("有执行吗", this.$refs.monitorLiveVideo);
// // console.log("el-dropdown", document.querySelector(".el-dropdown-menu"));
// if (document.querySelector(".el-dropdown-menu")) {
// this.$refs.monitorLiveVideo.appendChild(
// document.querySelectorAll(".el-dropdown-menu")[0]
// );
// this.$refs.monitorLiveVideo.appendChild(
// document.querySelectorAll(".el-dropdown-menu")[1]
// );
// }
// }, 200);
},
// 获取设备列表
getList() {
listInfo({
pageNum: 1,
pageSize: 10,
gateway: this.gateWay.gateway,
type: '机场'
}).then((res) => {
// console.log("list-------", res);
this.info = res.rows[0];
});
},
// 获取数量
getNumber() {
listInfo({
pageNum: 1,
pageSize: 10,
gateway: this.gateWay.gateway
}).then((res) => {
// console.log("list-------", res);
this.number = res.rows.length;
});
},
// 开启直播
startLive() {
return new Promise((resovle, reject) => {
if (this.info) {
const { cameraIndex, gateway, sn, videoIndex } = this.info;
liveStart({
cameraIndex,
definition: 0,
gateway,
sn,
videoIndex,
host: getHost(),
rtmpPort: getRtmpPort(),
rtcPort: getRtcPort()
})
.then((res) => {
this.videoUrl = res.data.url;
this.playFlv(this.videoUrl, 'video-webrtc_' + this.info.cameraIndex, this.info.cameraIndex);
resovle(res);
this.handleFullscreenChange();
})
.catch((err) => {
reject(err);
this.handleFullscreenChange();
});
}
});
},
// 关闭直播
stopLive() {
return new Promise((resovle, reject) => {
if (this.info) {
const { cameraIndex, gateway, sn, videoIndex } = this.info;
liveStop({
cameraIndex,
definition: 0,
gateway,
sn,
videoIndex
})
.then((res) => {
// console.log("res-------", res);
this.flvDestroy();
resovle(res);
this.handleFullscreenChange();
})
.catch((err) => {
reject(err);
this.handleFullscreenChange();
});
}
});
},
// 播放flv视频
playFlv(flv, videoId) {
if (flvjs.isSupported()) {
var videoElement = document.getElementById(videoId);
this.flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
hasAudio: false,
url: flv,
enableStashBuffer: true, //
enableWorker: true,
autoCleanupSourceBuffer: true //自动清除缓存
});
this.$nextTick(() => {
this.flvPlayer.attachMediaElement(videoElement);
this.flvPlayer.load();
this.flvPlayer.play();
});
}
},
// 关闭flv
flvDestroy() {
if (this.flvPlayer) {
try {
this.flvPlayer.pause(); // 暂停播放数据流
this.flvPlayer.unload(); // 取消数据流加载
this.flvPlayer.destroy(); // 销毁播放实例
// flvPlayer.off("error"); // 移除错误处理程序
this.flvPlayer.detachMediaElement(); // 将播放实例从节点中取出
this.flvPlayer = null;
} catch (error) {}
}
},
// 判断全屏添加消息提示
handleFullscreenChange() {
// console.log("全屏---------");
// 检查是否处于全屏模式
if (document.fullscreenElement) {
// console.log("进入全屏模式");
// 执行你的代码
if (document.querySelector('.el-message')) {
this.$refs.monitorLiveVideo.appendChild(document.querySelectorAll('.el-message')[0]);
}
}
},
handleClickOutside(event) {
const dropdown = this.$refs.screenFull;
// console.log("dropdown", dropdown);
setTimeout(() => {
if (!dropdown.contains(event.target)) {
this.set.show = false; // 点击外部区域时隐藏下拉框
}
}, 200);
}
},
mounted() {
if (this.gateWay) {
this.getList();
this.getNumber();
}
this.$recvChanel('liveBus', (flag) => {
if (flag && this.switchs.currentStatus) {
this.switchs.currentStatus = false;
this.switchs.tips = '开启直播';
}
});
// this.$recvChanel("flyerChange", () => {
// this.stopLives()
// });
// document.addEventListener("click", this.handleClickOutside);
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
this.screen.currentStatus = false;
this.screen.img = require('../../../../images/home/uav-screen.png');
}
});
},
beforeDestroy() {
// 在组件销毁前移除事件监听
document.removeEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
this.screen.currentStatus = false;
this.screen.img = require('../../../../images/home/uav-screen.png');
}
});
}
};
</script>
<style lang="scss">
.cabin-live-wrapper {
width: 100%;
height: 30%;
margin-bottom: 15px;
position: relative;
.cabin-live-content {
width: 100%;
height: 100%;
padding: 10px;
background: url('../../../../images/home/UAV-bg.png') no-repeat;
background-size: 100% 100%;
.cabin-live-title {
height: 24px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 10;
&-left {
font-size: 14px;
}
&-right {
display: flex;
align-items: center;
justify-content: center;
&-wrapper {
width: 16px;
height: 16px;
margin: 0 3px;
cursor: pointer;
// position: relative;
img {
height: 100%;
width: 100%;
}
}
.monitor-live-dropdown-menu {
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.5);
border: 1.5px solid rgba(0, 255, 255, 1);
backdrop-filter: blur(2px);
border-radius: 5px;
margin: 0;
width: 60px;
.dropdown-item {
padding: 5px;
display: inline-block;
cursor: pointer;
width: 100%;
text-align: center;
line-height: 24px;
font-size: 12px;
&:hover {
background: rgba(0, 255, 255, 0.2);
color: rgba(0, 255, 255, 1);
}
}
.dropdown-active {
background: rgba(0, 255, 255, 0.2);
color: rgba(0, 255, 255, 1);
}
}
}
}
.cabin-live-video {
margin-top: 10px;
height: calc(100% - 34px);
width: 100%;
position: relative;
// clip-path: polygon(0 0, 97% 0, 100% 7%, 100% 100%, 3% 100%, 0 93%);
border: 1px solid rgba(0, 255, 255, 1);
.cabin-live-video-item {
height: 100%;
width: 100%;
}
.live-stop {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #000;
flex-direction: column;
size: 12px;
}
.live-loading {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
size: 12px;
> img {
height: 50px;
height: 50px;
}
}
}
}
// 全屏
.cabin-live-content-full {
width: 100%;
height: 100%;
padding: 10px;
.cabin-live-title {
height: 24px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
top: 0;
left: 0;
&-left {
font-size: 14px;
}
&-right {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 0;
&-wrapper {
width: 16px;
height: 16px;
margin: 0 3px;
cursor: pointer;
position: relative;
img {
height: 100%;
width: 100%;
}
}
// .monitor-live-dropdown-menu {
// display: flex;
// flex-direction: column;
// background: rgba(0, 0, 0, 0.5);
// border: 1.5px solid rgba(0, 255, 255, 1);
// backdrop-filter: blur(2px);
// border-radius: 5px;
// margin: 0;
// width: 60px;
// .dropdown-item {
// padding: 5px;
// display: inline-block;
// cursor: pointer;
// width: 100%;
// text-align: center;
// line-height: 24px;
// font-size: 12px;
// &:hover {
// background: rgba(0, 255, 255, 0.2);
// color: rgba(0, 255, 255, 1);
// }
// }
// .dropdown-active {
// background: rgba(0, 255, 255, 0.2);
// color: rgba(0, 255, 255, 1);
// }
// }
}
}
.cabin-live-video {
margin-top: 10px;
height: calc(100% - 34px);
width: 100%;
position: relative;
.cabin-live-video-item {
height: 100%;
width: 100%;
}
.live-stop {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #000;
flex-direction: column;
size: 12px;
}
.live-loading {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
size: 12px;
> img {
height: 50px;
height: 50px;
}
}
}
}
}
.active {
background: rgba(0, 255, 255, 0.2);
color: rgba(0, 255, 255, 1);
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="home_left uav_box">
<MonitorLiveStreaming></MonitorLiveStreaming>
<AirportInformationDetails></AirportInformationDetails>
<AirportRemoteDebugging></AirportRemoteDebugging>
<!-- <div v-for="(url, index) in urls" :key="index">
<webrtc :ref="`webrtc-${index}`"></webrtc>
<button @click="startPlay(url, index)">播放</button>
</div> -->
</div>
</template>
<script>
import MonitorLiveStreaming from './components/MonitorLiveStreaming/index.vue';
import AirportInformationDetails from './components/AirportInformationDetails/index.vue';
import AirportRemoteDebugging from './components/AirportRemoteDebugging/index.vue';
// import webrtc from "@/components/webrtc";
export default {
components: {
MonitorLiveStreaming,
AirportInformationDetails,
AirportRemoteDebugging
// webrtc,
},
data() {
return {
urls: [
'http://121.37.237.116:28453/rtc/v1/whep/?app=live&stream=4SEDL9C001X8GE4SEDL9C001X8GE165-0-7normal-0',
'http://121.37.237.116:28453/rtc/v1/whep/?app=live&stream=4SEDL9C001X8GE1581F5BMD238V00172JR39-0-7normal-0',
'http://121.37.237.116:28453/rtc/v1/whep/?app=live&stream=4SEDL9C001X8GE1581F5BMD238V00172JR53-0-0normal-0'
]
};
},
methods: {
startPlay(url, index) {
// console.log(this.$refs[`webrtc-${index}`][0]);
this.$refs[`webrtc-${index}`][0].startPlay(url);
}
},
created() {},
mounted() {}
};
</script>
<style lang="scss" scoped>
.home_left {
left: 20px;
width: 450px;
height: 100vh;
position: absolute;
z-index: 2;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<div class="home_right uav_box">
<!-- <el-button @click="chongdian">充电</el-button> -->
<AircraftMonitoringLiveBroadcast></AircraftMonitoringLiveBroadcast>
<AircraftInformationDetails></AircraftInformationDetails>
</div>
</template>
<script>
import AircraftMonitoringLiveBroadcast from './components/AircraftMonitoringLiveBroadcast/index.vue';
import AircraftInformationDetails from './components/AircraftInformationDetails/index.vue';
import { useAirStore } from '@/store/modules/drone';
// import { remoteDebug } from "@/api/air";
export default {
components: {
AircraftMonitoringLiveBroadcast,
AircraftInformationDetails
},
data() {
return {
gateWay: useAirStore().gateWay
};
},
watch: {
// // 监听 Vuex 中的状态变化
// "useAirStore().gateWay": {
// handler(newValue, oldVal) {
// this.gateWay = newValue;
// },
// deep: true,
// },
},
created() {},
mounted() {},
methods: {
// chongdian() {
// let obj = {
// gateway: this.gateWay.gateway,
// method: "charge_open",
// };
// // this.$store.commit("UP_debugValue", val);
// this.droneDebug(obj);
// },
// // 调试模式
// droneDebug(obj, val) {
// remoteDebug(obj).then((res) => {
// const { code, data, msg } = res;
// if (code == 200) {
// this.$message.success("操作成功");
// } else {
// this.$message.error(msg);
// this[val] = !this[val];
// }
// });
// },
}
};
</script>
<style lang="scss">
.home_right {
position: absolute;
right: 20px;
width: 450px;
top: 0px;
z-index: 2;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<div class="home-container" ref="monitorLiveVideo">
<homeLeft></homeLeft>
<homeRight></homeRight>
</div>
</template>
<script>
import homeLeft from "./homeLeft.vue";
import homeRight from "./homeRight.vue";
export default {
components: { homeLeft, homeRight },
data() {
return {};
},
methods: {},
created() { },
mounted() { },
};
</script>
<style lang="scss" scoped>
.home-container {
height: 100%;
width: 100%;
}
</style>

View File

@ -0,0 +1,264 @@
<template>
<div class="medium-container">
<div class="medium-wrapper">
<div class="medium-menu-container">
<div class="medium-title">媒体库</div>
<div class="medium-info">
<div class="medium-info-title">项目空间使用情况</div>
<div v-for="item in mediumInfoList" :key="item.name" class="medium-info-item">
<div class="medium-info-item-left">
<div class="medium-img-box">
<img :src="item.img" alt="" />
</div>
<div class="medium-num-box">
<div>{{ item.name }}</div>
<div>{{ item.count }} 文件</div>
</div>
</div>
<div class="medium-info-item-right">
{{ item.storage }}
</div>
</div>
<div v-for="item in aiInfoList" :key="item.name" class="medium-info-item">
<div class="medium-info-item-left">
<div class="medium-img-box">
<img :src="item.img" alt="" />
</div>
<div class="medium-num-box">
<div>{{ item.name }}</div>
<div>{{ item.count }} 文件</div>
</div>
</div>
<div class="medium-info-item-right">
{{ item.storage }}
</div>
</div>
</div>
<!-- <div class="medium-info medium-storage">
<div class="medium-info-item">
<div class="medium-info-item-left">
<div class="medium-img-box">
<img src="../../images/medium/medium-storage.png" alt="" />
</div>
<div class="medium-num-box">
剩余可用空间
</div>
</div>
<div class="medium-info-item-right">
50 G
</div>
</div>
</div> -->
</div>
<div class="medium-main-container">
<el-tabs class="myTABS" v-model="activeName">
<el-tab-pane label="无人机" name="first">
<MediumHome></MediumHome>
</el-tab-pane>
<el-tab-pane label="AI识别" name="second">
<mediumHomeAi></mediumHomeAi>
</el-tab-pane>
</el-tabs>
</div>
</div>
<Preview></Preview>
</div>
</template>
<script>
import MediumHome from './mediumHome.vue';
import mediumHomeAi from './mediumHomeAi.vue';
import { getInfo } from '@/api/fileMangement';
import { aiInfo } from '@/api/air';
import Preview from './preview.vue';
import { useAirStore } from '@/store/modules/drone';
export default {
components: { MediumHome, Preview, mediumHomeAi },
data() {
return {
info: {},
gateway: {},
mediumHomeVisible: false,
previewVisible: false,
previewImage: '',
previewTitle: '',
activeName: 'first',
mediumInfoList: [
{
img: require('../../images/medium/medium-img.png'),
name: '照片',
count: 0,
storage: '0'
},
{
img: require('../../images/medium/medium-video.png'),
name: '视频',
count: 0,
storage: '0'
}
],
aiInfoList: [
{
img: require('../../images/medium/medium-video.png'),
name: 'AI',
count: 0,
storage: '0'
}
],
gateWay: useAirStore().gateWay
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getFileInfo();
},
deep: true
}
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
},
// 文件信息概况
async getFileInfo() {
const { gateway } = this.gateWay;
const res = await getInfo({ gateway });
// console.log("文件信息概况", res);
this.mediumInfoList[0].count = res.data.totalImageCount;
this.mediumInfoList[0].storage = res.data.totalImageSize;
this.mediumInfoList[1].count = res.data.totalVideoCount;
this.mediumInfoList[1].storage = res.data.totalVideoSize;
},
// ai信息
async getAiInfo() {
const { gateway } = this.gateWay;
const res = await aiInfo({ gateway });
this.aiInfoList[0].count = res.data.totalImageCount;
this.aiInfoList[0].storage = res.data.totalImageSize;
}
},
created() {},
mounted() {
this.getFileInfo();
this.getAiInfo();
this.$recvChanel('mediumDel', (bool) => {
this.getFileInfo();
this.getAiInfo();
});
}
};
</script>
<style lang="scss">
.medium-container {
// height: 100%;
// width: 100%;
color: #fff;
// background-color: rgb(224, 137, 22);
// position: absolute;
display: flex;
justify-content: center;
align-items: center;
.medium-wrapper {
position: absolute;
top: 15%;
margin: auto;
width: 80%;
height: 75%;
background-color: antiquewhite;
display: flex;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(2px);
.medium-menu-container {
width: 20%;
height: 100%;
.medium-title {
font-size: 18px;
border-bottom: 1px solid rgba(204, 204, 204, 0.2);
line-height: 60px;
padding-left: 20px;
}
.medium-info {
padding: 0 20px;
border-bottom: 1px solid rgba(204, 204, 204, 0.2);
.medium-info-title {
line-height: 60px;
font-size: 16px;
font-weight: 400;
color: rgba(255, 255, 255, 1);
}
.medium-info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.medium-info-item-left {
display: flex;
.medium-img-box {
width: 40px;
height: 40px;
border-radius: 8px;
background: rgba(0, 0, 0, 1);
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
margin-right: 10px;
img {
width: 24px;
height: 20px;
}
}
.medium-num-box {
font-size: 12px;
display: flex;
flex-direction: column;
justify-content: space-around;
}
}
.medium-info-item-right {
height: 40px;
display: flex;
align-items: center;
font-size: 14px;
}
}
}
.medium-storage {
border-bottom: none;
padding-top: 20px;
img {
width: 20px !important;
height: 20px;
}
}
}
.medium-main-container {
width: 80%;
height: 100%;
background-color: #f6f8fa;
overflow: hidden;
.el-tabs__item {
padding-left: 20px !important;
}
}
}
}
</style>

View File

@ -0,0 +1,620 @@
<template>
<div class="medium-home-container">
<div class="medium-home-main">
<div class="medium-home-main-top">
<div class="medium-breadcrumb">
<div v-for="item in paths" :key="item" class="breadcrumb-item" @click="changePath(item)">
{{ item || '全部文件' }}
</div>
</div>
</div>
<div class="medium-home-top-btn">
<div>
<el-button size="small" @click="handleDelete(false)" type="danger"> 删除 </el-button>
<el-button size="small" @click="handleDownload(false)" type="success"> 下载 </el-button>
</div>
<div class="medium-check">
<div class="check-item">已选/全部</div>
<div class="check-item">{{ selectNum }}/{{ fileList.length }}</div>
</div>
</div>
<div class="medium-home-main-table">
<el-table
ref="multipleTable"
:data="fileList"
tooltip-effect="dark"
:default-sort="{ prop: 'creatTime' }"
stripe
size="small"
height="600"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column prop="fileName" label="文件名称">
<template slot-scope="scope">
<div
class="file-name-wrapper"
style="display: flex; align-items: center; cursor: pointer"
@click="handleSelectFile(scope.row.path, scope.row)"
>
<img :src="scope.row.img" alt="" style="width: 16px; height: 16px; transform: translateY(-1px); margin-right: 2px" />
<div class="file-name">{{ scope.row.name ? scope.row.name : scope.row.fileName }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="80" show-overflow-tooltip> </el-table-column>
<el-table-column prop="creatTime" label="创建时间" sortable="custom">
<template slot-scope="scope">{{ scope.row.creatTime }}</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="120">
<template slot-scope="scope">
<div class="opration-btns">
<el-tooltip v-if="scope.row.size !== 'N/A'" class="item" content="下载" effect="dark">
<span class="opration-btn" style="display: inline-block; cursor: pointer" @click="handleDownload(scope.row)">
<img :src="require('../../images/medium/down-load.png')" srcset="" />
</span>
</el-tooltip>
<el-tooltip v-if="scope.row.size !== 'N/A'" class="item" content="预览" effect="dark">
<span class="opration-btn" style="display: inline-block; cursor: pointer" @click="handlePreview(scope.row)">
<img :src="require('../../images/medium/prview-icon.png')" srcset="" />
</span>
</el-tooltip>
<el-tooltip class="item" content="删除" effect="dark">
<span class="opration-btn" style="display: inline-block; cursor: pointer" @click="handleDelete(scope.row)">
<img :src="require('../../images/medium/delete.png')" srcset="" />
</span>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="shade" v-if="loading">
<el-progress type="circle" :percentage="percentage" :format="percentageFun"></el-progress>
<!-- <div
style="white-space: pre-line;text-align: center;color: aqua;color: aqua; display: flex; flex-direction: column;position: relative;">
<img src="../../images/loadingdow.gif" alt="">
<span style="display: inline-block;position: absolute;left: 50%;top:50%;transform: translate(-50%, -50%);">{{
this.text }}</span>
</div> -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import JSZip from 'jszip';
import { listInfo } from '@/api/air';
import { getFiles, fileDownload, deleteFile, downloadBatch, getUrlList } from '@/api/fileMangement';
import Preview from './preview.vue';
import { useAirStore } from '@/store/modules/drone';
export default {
components: { Preview },
data() {
return {
percentage: 0,
info: {},
paths: [''],
fileList: [],
totle: 0,
selectNum: 0,
length: 0,
sn: '',
currentPath: '',
searchName: '', // 搜索关键字
dateTime: [new Date(), new Date()], // 初始时间范围为今日至今日
typeOptions: [
{
value: '选项1',
label: '选项1'
},
{
value: '选项2',
label: '选项2'
}
],
typeValue: '',
loadOptions: [
{
value: '选项1',
label: '选项1'
},
{
value: '选项2',
label: '选项2'
}
],
loadValue: '',
selectList: [],
loading: false,
gateWay: useAirStore().gateWay,
text: ''
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getFileList(this.gateWay.gateway);
},
deep: true
}
},
methods: {
percentageFun(percentage) {
let text = '';
if (percentage == 99) {
text = `${percentage}%\n 正在打包中`;
} else {
text = `${percentage}%\n 下载中`;
}
return text;
},
handleSortChange({ column, prop, order }) {
this.fileList.sort((a, b) => {
if (a.excludeSort || b.excludeSort) {
// 如果有数据被标记为排除排序,则直接返回原始顺序
return 0;
}
if (order === 'ascending') {
return a[prop] > b[prop] ? 1 : -1;
} else if (order === 'descending') {
return a[prop] < b[prop] ? 1 : -1;
}
return 0;
});
},
handleSelectionChange(datas) {
// console.log("datas: ", datas);
this.selectList = datas;
this.selectNum = datas.length;
},
// 获取文件列表
async getFileList(gateway, path = '') {
this.fileList = [];
const list = await getFiles({ gateway, itemPath: path });
// console.log("文件列表1-------", list);
this.paths = [
'',
...Object.keys(list)[0]
.split('/')
.filter((path) => path !== '')
.slice(0, -1)
];
this.fileList = Object.keys(list).map((item) => {
const pathList = item.split('/').filter((path) => path !== '');
let img = require('../../images/medium/files-icon.png');
let type = 'file';
// 文件缩略图
if (list[item].size !== 'N/A') {
type = item.split('.')[1];
if (item.split('.')[1] !== 'mp4') {
img = list[item].download_url;
} else {
img = require('../../images/medium/video-icon.png');
}
}
return {
name: list[item].missionName,
fileName: pathList[pathList.length - 1],
size: list[item].size,
creatTime: list[item].creation_time,
path: item,
img,
type,
url: list[item].download_url,
fileLength: list[item].length,
excludeSort: pathList[pathList.length - 1] == '视频录制' ? true : false
};
});
console.log(this.fileList);
this.totle = this.fileList.length;
this.length = this.fileList.fileLength;
},
// 文件夹点击进入文件夹
handleSelectFile(path, data) {
console.log('path11111111111', path);
if (data && data.size !== 'N/A') {
// console.log("文件------------");
} else {
this.currentPath = path;
const { gateway } = this.gateWay;
this.getFileList(gateway, path);
}
},
// 路径变化
changePath(path) {
if (path) {
// console.log("currentPath", this.currentPath);
this.currentPath = this.currentPath.split(path)[0] + path;
// console.log("path", path);
// 跳转到该路径
this.handleSelectFile(this.currentPath);
} else {
this.handleSelectFile('');
}
},
// 删除
handleDelete(row) {
// console.log("删除---------", row);
let params = { itemPaths: [row.path], gateway: this.gateWay.gateway };
if (!row) {
if (this.selectList.length === 0) {
this.$message.warning('请先选择要删除的文件');
this.loading = false;
return;
}
const pathList = this.selectList.map((item) => item.path);
params = { itemPaths: pathList, gateway: this.gateWay.gateway };
}
this.$confirm('此操作将删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'customMessageBox',
confirmButtonClass: 'customConfirm',
cancelButtonClass: 'customCancel'
}).then(() => {
deleteFile(params).then((res) => {
this.$message.success(res.msg);
this.$sendChanel('mediumDel');
// 过滤掉删除的文件
this.fileList = this.fileList.filter((file) => {
return !params.itemPaths.includes(file.path);
});
if (this.fileList.length === 0) {
// 如果列表为空跳转到根路径
this.handleSelectFile('');
}
});
});
},
// 下载
handleDownload(row) {
this.loading = true;
// console.log("下载---------", row);
let params = { itemPath: row.path, gateway: this.gateWay.gateway };
if (!row) {
if (this.selectList.length === 0) {
this.$message.warning('请先选择要下载的文件');
this.loading = false;
return;
}
let pathList = [];
// 定义是否是文件夹
let isFile = false;
// console.log("this.selectList", this.selectList);
this.selectList.forEach((item) => {
if (item.type === 'file') {
isFile = true;
}
});
if (isFile) {
const { gateway } = this.gateWay;
const promises = this.selectList.map((item) => {
const params = { gateway, itemPath: item.path };
return getUrlList(params).then((res) => {
console.log('res', res);
pathList.push(...res); // 将结果合并到 pathList
});
});
// 等待所有异步操作完成后,再执行下载操作
Promise.all(promises)
.then(() => {
// console.log("pathList1", pathList);
// 确保 pathList 有值后再调用下载方法
console.log('pathList', pathList);
this.downloadAndZipFiles(pathList);
})
.catch((error) => {
console.error('获取文件路径列表时出错:', error);
});
} else {
pathList = this.selectList.map((item) => item.url);
// console.log("pathList2", pathList);
// 并行下载文件并压缩为 zip
this.downloadAndZipFiles(pathList);
}
} else {
this.downloadAndZipFiles([row.url], row);
}
},
async downloadAndZipFiles(fileUrls, file) {
try {
const zip = new JSZip();
const fileSizeMap = new Map();
let fileTotal = 0;
if (file) {
fileTotal = file.fileLength;
} else {
this.selectList.forEach((item) => {
fileTotal += item.fileLength;
});
}
// console.log("file-----", fileTotal);
// 并行下载所有文件
const downloadPromises = fileUrls.map((url) =>
axios({
url: url,
method: 'get',
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const { loaded, total } = progressEvent;
fileSizeMap.set(url, loaded);
const totalFileSize = [...fileSizeMap.values()].reduce((acc, value) => acc + value, 0);
const progress = Math.round((totalFileSize / fileTotal) * 100); // 计算下载进度百分比
// console.log("progressEvent", progressEvent);
// console.log("overallProgress", progress);
this.percentage = progress;
if (progress == 100) {
this.percentage = progress * 0.99;
}
// if (progress == 100) {
// this.text = `${99}%\n 正在打包中`;
// } else {
// this.text = `${progress}%\n 下载中`
// }
}
}).then((response) => {
// console.log("Response:", response); // 在这里打印 response 对象
// console.log("url:", url); // 在这里打印 response 对象
const regex = /\/(DJI_[^\/]+\/DJI_[^\/]+\.[^\/?]+)(?=\?)/;
// 提取匹配的部分
const match = url.match(regex);
let extractedString = 'undefined';
if (match) {
extractedString = match[1]; // 捕获的字符串
}
// 打印文件名和文件数据
const fileName = file ? file.fileName : extractedString; // 获取文件名
const fileData = response.data; // 保存文件数据
// 获取文件大小:使用 Blob 的 size 属性
const fileSize = fileData.size; // 单位为字节bytes
// console.log("File size:", fileSize);
// 返回一个对象,继续后续操作
return { fileName, fileData };
})
);
// 等待所有文件下载完成
const downloadedFiles = await Promise.all(downloadPromises);
if (file) {
this.percentage = 100;
this.selectList = [];
// console.log("Downloading", downloadedFiles);
const url = window.URL.createObjectURL(downloadedFiles[0].fileData);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', downloadedFiles[0].fileName); // 设置下载的文件名
document.body.appendChild(link);
link.click();
link.remove();
this.loading = false;
this.percentage = 0; // 下载完进度百分比清零
return;
}
// 将每个文件添加到 zip 中
downloadedFiles.forEach(({ fileName, fileData }) => {
// console.log("File Name:", fileName);
zip.file(fileName, fileData); // 添加文件到 zip
});
// 生成 zip 文件并触发下载
const zipBlob = await zip.generateAsync({ type: 'blob' });
const url = window.URL.createObjectURL(zipBlob);
const link = document.createElement('a');
link.href = url;
const temp = new Date().getTime();
link.setAttribute('download', `文件${temp}.zip`); // 设置下载的文件名
document.body.appendChild(link);
link.click();
link.remove();
this.loading = false;
this.percentage = 0; // 下载完进度百分比清零
} catch (error) {
// console.error("Error downloading or zipping files:", error);
this.loading = false;
this.percentage = 0; // 下载完进度百分比清零
}
},
// 预览
handlePreview(row) {
const list = this.fileList;
// console.log("预览---------", row);
// console.log("list---------", list);
const index = list.findIndex((item) => item === row);
const data = { list, detail: row, index };
this.$sendChanel('previewBus', data);
}
},
created() {},
mounted() {
this.getFileList(this.gateWay.gateway);
}
};
</script>
<style lang="scss">
.medium-home-container {
height: 100%;
width: 100%;
position: relative;
background-color: antiquewhite;
.medium-home-search-wrapper {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
> .el-date-editor {
width: 38%;
}
> .el-select {
width: 18%;
}
> .el-input {
width: 18%;
}
> .el-button {
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
img {
width: 20px;
height: 20px;
}
}
}
.medium-home-main {
height: calc(100%);
background: #fff;
.medium-home-main-top {
display: flex;
justify-content: space-between;
line-height: 40px;
padding: 0 16px;
font-size: 12px;
width: 100%;
.medium-breadcrumb {
display: flex;
.breadcrumb-item {
// margin-right: 20px;
color: #909399;
cursor: pointer;
&::after {
content: '/';
display: inline-block;
margin: 0 3px;
}
}
.breadcrumb-item:last-child {
color: #000;
&::after {
content: '';
display: inline-block;
}
}
}
.file-name-wrapper {
display: flex;
.file-img-wrapper {
height: 20px !important;
width: 20px !important;
img {
width: 100%;
height: 100%;
}
}
}
.el-table {
.el-checkbox {
margin-left: 8px !important;
}
}
}
.medium-home-top-btn {
width: 100%;
height: 40px;
padding: 0 16px;
display: flex;
align-items: center;
font-size: 12px;
justify-content: space-between;
.medium-check {
display: flex;
align-items: center;
color: #000;
}
}
.medium-home-main-table {
overflow-y: auto;
height: calc(100% - 80px);
width: 100%;
}
.el-checkbox__input {
margin-left: 8px !important;
}
}
.shade {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
.el-progress__text {
color: aqua !important;
white-space: pre-line;
line-height: 20px;
}
}
.el-picker-panel {
background: rgba(255, 255, 255, 1) !important;
color: #606266 !important;
filter: drop-shadow(2px 4px 6px black);
}
.opration-btns {
display: flex;
justify-content: start;
align-items: center;
// background-color: #606266;
.opration-btn {
width: 16px;
height: 16px;
display: flex;
justify-content: center;
align-items: center;
margin-left: 10px;
cursor: pointer;
img {
width: 12px;
height: 12px;
}
&:hover {
// background: #e6e6e6;
}
}
}
}
</style>

View File

@ -0,0 +1,609 @@
<template>
<div class="medium-home-container">
<div class="medium-home-main">
<div class="medium-home-main-top">
<div class="medium-breadcrumb">
<div v-for="item in paths" :key="item" class="breadcrumb-item" @click="changePath(item)">
{{ item || '全部文件' }}
</div>
</div>
</div>
<div class="medium-home-top-btn">
<div>
<el-button size="small" @click="handleDelete(false)" type="danger"> 删除 </el-button>
<el-button size="small" @click="handleDownload(false)" type="success"> 下载 </el-button>
</div>
<div class="medium-check">
<div class="check-item">已选/全部</div>
<div class="check-item">{{ selectNum }}/{{ totle }}</div>
</div>
</div>
<div class="medium-home-main-table">
<el-table
ref="multipleTable"
:data="fileList"
tooltip-effect="dark"
stripe
size="small"
height="600"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column prop="fileName" label="文件名称">
<template slot-scope="scope">
<div
class="file-name-wrapper"
style="display: flex; align-items: center; cursor: pointer"
@click="handleSelectFile(scope.row.path, scope.row)"
>
<img :src="scope.row.img" alt="" style="width: 16px; height: 16px; transform: translateY(-1px); margin-right: 2px" />
<div class="file-name">{{ scope.row.name ? scope.row.name : scope.row.fileName }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="80" show-overflow-tooltip> </el-table-column>
<el-table-column prop="creatTime" label="创建时间">
<template slot-scope="scope">{{ scope.row.creatTime }}</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="120">
<template slot-scope="scope">
<div class="opration-btns">
<el-tooltip v-if="scope.row.size !== 'N/A'" class="item" content="下载" effect="dark">
<!-- v-if="scope.row.size !== 'N/A'" -->
<span class="opration-btn" style="display: inline-block; cursor: pointer" @click="handleDownload(scope.row)">
<img :src="require('../../images/medium/down-load.png')" srcset="" />
</span>
</el-tooltip>
<el-tooltip v-if="scope.row.size !== 'N/A'" class="item" content="预览" effect="dark">
<span class="opration-btn" style="display: inline-block; cursor: pointer" @click="handlePreview(scope.row)">
<img :src="require('../../images/medium/prview-icon.png')" srcset="" />
</span>
</el-tooltip>
<el-tooltip class="item" content="删除" effect="dark">
<span class="opration-btn" style="display: inline-block; cursor: pointer" @click="handleDelete(scope.row)">
<img :src="require('../../images/medium/delete.png')" srcset="" />
</span>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="shade" v-if="loading">
<el-progress type="circle" :percentage="percentage"></el-progress>
<!-- <div
style="white-space: pre-line;text-align: center;color: aqua;color: aqua; display: flex; flex-direction: column;">
<img src="../../images/loadingdow.gif" alt="">
<span style="display: inline-block;position: absolute;left: 50%;top:50%;transform: translate(-50%, -50%);">{{
this.text }}</span>
</div> -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import JSZip from 'jszip';
import { getAiFiles, deleteAiFile, getAiUrlList } from '@/api/fileMangement';
import Preview from './preview.vue';
import { useAirStore } from '@/store/modules/drone';
export default {
components: { Preview },
data() {
return {
percentage: 0,
info: {},
paths: [''],
fileList: [
// {
// fileName: item,
// size: list[item].size,
// creatTime: list[item].creation_time,
// path:''
// img: ''
// },
],
totle: 0,
selectNum: 0,
length: 0,
sn: '',
currentPath: '',
searchName: '', // 搜索关键字
dateTime: [new Date(), new Date()], // 初始时间范围为今日至今日
typeOptions: [
{
value: '选项1',
label: '选项1'
},
{
value: '选项2',
label: '选项2'
}
],
typeValue: '',
loadOptions: [
{
value: '选项1',
label: '选项1'
},
{
value: '选项2',
label: '选项2'
}
],
loadValue: '',
selectList: [],
loading: false,
gateWay: useAirStore().gateWay,
text: ''
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateWay = newValue;
this.getFileList(this.gateWay.gateway);
},
deep: true
}
},
methods: {
handleSelectionChange(datas) {
// console.log("datas: ", datas);
this.selectList = datas;
this.selectNum = datas.length;
},
isEmptyObject(obj) {
return Object.keys(obj).length === 0;
},
// 获取文件列表
async getFileList(gateway, path = '') {
this.fileList = [];
const list = await getAiFiles({ gateway, itemPath: path });
// console.log("文件列表1-------", list);
if (Object.keys(list).length === 0) {
return;
}
this.paths = [
'',
...Object.keys(list)[0]
.split('/')
.filter((path) => path !== '')
.slice(0, -1)
];
this.fileList = Object.keys(list).map((item) => {
const pathList = item.split('/').filter((path) => path !== '');
let img = require('../../images/medium/files-icon.png');
let type = 'file';
// console.log("item----------", item);
// console.log("list----------", list[item]);
// 文件缩略图
if (list[item].size !== 'N/A') {
type = item.split('.')[1];
if (item.split('.')[1] !== 'mp4') {
img = list[item].download_url;
} else {
img = require('../../images/medium/video-icon.png');
}
}
return {
name: list[item].missionName,
fileName: pathList[pathList.length - 1],
size: list[item].size,
creatTime: list[item].creation_time,
path: item,
img,
type,
url: list[item].download_url,
fileLength: list[item].length
};
});
this.totle = this.fileList.length;
this.length = this.fileList.fileLength;
// console.log("文件列表2-------", this.fileList);
},
// 文件夹点击进入文件夹
handleSelectFile(path, data) {
console.log('path11111111111', path);
if (data && data.size !== 'N/A') {
// console.log("文件------------");
} else {
this.currentPath = path;
const { gateway } = this.gateWay;
this.getFileList(gateway, path);
}
},
// 路径变化
changePath(path) {
if (path) {
// console.log("currentPath", this.currentPath);
this.currentPath = this.currentPath.split(path)[0] + path;
// console.log("path", path);
// 跳转到该路径
this.handleSelectFile(this.currentPath);
} else {
this.handleSelectFile('');
}
},
// 删除
handleDelete(row) {
// console.log("删除---------", row);
let params = { itemPaths: [row.path], gateway: this.gateWay.gateway };
if (!row) {
if (this.selectList.length === 0) {
this.$message.warning('请先选择要删除的文件');
this.loading = false;
return;
}
const pathList = this.selectList.map((item) => item.path);
params = { itemPaths: pathList, gateway: this.gateWay.gateway };
}
this.$confirm('此操作将删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'customMessageBox',
confirmButtonClass: 'customConfirm',
cancelButtonClass: 'customCancel'
}).then(() => {
deleteAiFile(params).then((res) => {
this.$message.success(res.msg);
this.$sendChanel('mediumDel');
// 过滤掉删除的文件
this.fileList = this.fileList.filter((file) => {
return !params.itemPaths.includes(file.path);
});
if (this.fileList.length === 0) {
// 如果列表为空跳转到根路径
this.handleSelectFile('');
}
});
});
},
// 下载
handleDownload(row) {
this.loading = true;
// console.log("下载---------", row);
let params = { itemPath: row.path, gateway: this.gateWay.gateway };
if (!row) {
if (this.selectList.length === 0) {
this.$message.warning('请先选择要下载的文件');
this.loading = false;
return;
}
let pathList = [];
// 定义是否是文件夹
let isFile = false;
// console.log("this.selectList", this.selectList);
this.selectList.forEach((item) => {
if (item.type === 'file') {
isFile = true;
}
});
if (isFile) {
const { gateway } = this.gateWay;
// this.selectList.forEach((item) => {
// const params = { gateway, itemPath: item.path };
// getUrlList(params).then((res) => {
// pathList.push(...res);
// });
// });
// console.log("pathList1", pathList);
// // 并行下载文件并压缩为 zip
// this.downloadAndZipFiles(pathList);
const promises = this.selectList.map((item) => {
const params = { gateway, itemPath: item.path };
return getAiUrlList(params).then((res) => {
pathList.push(...res); // 将结果合并到 pathList
});
});
// 等待所有异步操作完成后,再执行下载操作
Promise.all(promises)
.then(() => {
// console.log("pathList1", pathList);
// 确保 pathList 有值后再调用下载方法
this.downloadAndZipFiles(pathList);
})
.catch((error) => {
console.error('获取文件路径列表时出错:', error);
});
} else {
pathList = this.selectList.map((item) => item.url);
// console.log("pathList2", pathList);
// 并行下载文件并压缩为 zip
this.downloadAndZipFiles(pathList);
}
} else {
this.downloadAndZipFiles([row.url], row);
}
},
async downloadAndZipFiles(fileUrls, file) {
try {
const zip = new JSZip();
const fileSizeMap = new Map();
let fileTotal = 0;
if (file) {
fileTotal = file.fileLength;
} else {
this.selectList.forEach((item) => {
fileTotal += item.fileLength;
});
}
// console.log("file-----", fileTotal);l
// 并行下载所有文件
const downloadPromises = fileUrls.map((url) =>
axios({
url: url,
method: 'get',
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const { loaded, total } = progressEvent;
fileSizeMap.set(url, loaded);
const totalFileSize = [...fileSizeMap.values()].reduce((acc, value) => acc + value, 0);
const progress = Math.round((totalFileSize / fileTotal) * 100); // 计算下载进度百分比
// console.log("progressEvent", progressEvent);
// console.log("overallProgress", progress);
// this.percentage = progress; // 更新进度百分比
if (progress == 100) {
this.text = `${99}%\n 正在打包中`;
} else {
this.text = `${progress}%\n 下载中`;
}
}
}).then((response) => {
console.log('Response:', response); // 在这里打印 response 对象
console.log('url:', url); // 在这里打印 response 对象
console.log('flie:', file); // 在这里打印 response 对象
const regex = /\/(DJI_[^\/]+\/DJI_[^\/]+\.[^\/?]+)(?=\?)/;
// 提取匹配的部分
const match = url.match(regex);
let extractedString = response.data.size + '.jpg';
if (match) {
extractedString = match[1]; // 捕获的字符串
}
// 打印文件名和文件数据
const fileName = file ? file.fileName : extractedString; // 获取文件名
const fileData = response.data; // 保存文件数据
// 获取文件大小:使用 Blob 的 size 属性
const fileSize = fileData.size; // 单位为字节bytes
// console.log("File size:", fileSize);
// 返回一个对象,继续后续操作
return { fileName, fileData };
})
);
// 等待所有文件下载完成
const downloadedFiles = await Promise.all(downloadPromises);
if (file) {
// console.log("Downloading", downloadedFiles);
const url = window.URL.createObjectURL(downloadedFiles[0].fileData);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', downloadedFiles[0].fileName); // 设置下载的文件名
document.body.appendChild(link);
link.click();
link.remove();
this.loading = false;
this.percentage = 0; // 下载完进度百分比清零
return;
}
// 将每个文件添加到 zip 中
downloadedFiles.forEach(({ fileName, fileData }) => {
// console.log("File Name:", fileName);
zip.file(fileName, fileData); // 添加文件到 zip
});
// 生成 zip 文件并触发下载
const zipBlob = await zip.generateAsync({ type: 'blob' });
const url = window.URL.createObjectURL(zipBlob);
const link = document.createElement('a');
link.href = url;
const temp = new Date().getTime();
link.setAttribute('download', `文件${temp}.zip`); // 设置下载的文件名
document.body.appendChild(link);
link.click();
link.remove();
this.loading = false;
this.percentage = 0; // 下载完进度百分比清零
} catch (error) {
// console.error("Error downloading or zipping files:", error);
this.loading = false;
this.percentage = 0; // 下载完进度百分比清零
}
},
// 预览
handlePreview(row) {
const list = this.fileList;
// console.log("预览---------", row);
// console.log("list---------", list);
const index = list.findIndex((item) => item === row);
const data = { list, detail: row, index };
this.$sendChanel('previewBus', data);
}
},
created() {},
mounted() {
this.getFileList(this.gateWay.gateway);
}
};
</script>
<style lang="scss">
.medium-home-container {
height: 100%;
width: 100%;
position: relative;
background-color: antiquewhite;
.medium-home-search-wrapper {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
> .el-date-editor {
width: 38%;
}
> .el-select {
width: 18%;
}
> .el-input {
width: 18%;
}
> .el-button {
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
img {
width: 20px;
height: 20px;
}
}
}
.medium-home-main {
height: calc(100%);
background: #fff;
.medium-home-main-top {
display: flex;
justify-content: space-between;
line-height: 40px;
padding: 0 16px;
font-size: 12px;
width: 100%;
.medium-breadcrumb {
display: flex;
.breadcrumb-item {
// margin-right: 20px;
color: #909399;
cursor: pointer;
&::after {
content: '/';
display: inline-block;
margin: 0 3px;
}
}
.breadcrumb-item:last-child {
color: #000;
&::after {
content: '';
display: inline-block;
}
}
}
.file-name-wrapper {
display: flex;
.file-img-wrapper {
height: 20px !important;
width: 20px !important;
img {
width: 100%;
height: 100%;
}
}
}
.el-table {
.el-checkbox {
margin-left: 8px !important;
}
}
}
.medium-home-top-btn {
width: 100%;
height: 40px;
padding: 0 16px;
display: flex;
align-items: center;
font-size: 12px;
justify-content: space-between;
.medium-check {
display: flex;
align-items: center;
color: #000;
}
}
.medium-home-main-table {
overflow-y: auto;
height: calc(100% - 80px);
width: 100%;
}
.el-checkbox__input {
margin-left: 8px !important;
}
}
.shade {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
.el-progress__text {
color: aqua !important;
white-space: pre-line;
line-height: 20px;
}
}
.el-picker-panel {
background: rgba(255, 255, 255, 1) !important;
color: #606266 !important;
filter: drop-shadow(2px 4px 6px black);
}
.opration-btns {
display: flex;
justify-content: start;
align-items: center;
// background-color: #606266;
.opration-btn {
width: 16px;
height: 16px;
display: flex;
justify-content: center;
align-items: center;
margin-left: 10px;
cursor: pointer;
img {
width: 12px;
height: 12px;
}
&:hover {
// background: #e6e6e6;
}
}
}
}
</style>

View File

@ -0,0 +1,272 @@
<template>
<div v-if="show" class="preview-container">
<div class="preview-top">
<div class="preview-t-l">
<div class="preview-item-wrapper">
<img v-if="detail.type !== 'mp4'" :src="detail.url" alt="" class="preview-item" />
<video v-else :src="detail.url" controls crossorigin="anonymous" class="preview-item"></video>
<div class="arrows arrow-l" @click="check(current - 1)">
<img :src="require('../../images/medium/arrow.png')" />
</div>
<div class="arrows arrow-r" @click="check(current + 1)">
<img :src="require('../../images/medium/arrow.png')" />
</div>
</div>
</div>
<div class="preview-t-r">
<div class="preview-close" @click="show = false">
<img :src="require('../../images/medium/close.png')" />
</div>
<div class="preview-t-infos">
<div class="preview-t-info">
<span class="preview-t-info-text label">文件名</span>
<span class="preview-t-info-text">{{ detail.fileName }}</span>
</div>
<div class="preview-t-info">
<span class="preview-t-info-text label">文件大小</span>
<span class="preview-t-info-text">{{ detail.size }}</span>
</div>
<div class="preview-t-info">
<span class="preview-t-info-text label">创建时间</span>
<span class="preview-t-info-text">{{ detail.creatTime }}</span>
</div>
</div>
</div>
</div>
<div class="preview-bottom">
<div class="preview-list-wrapper scroll-container">
<div v-for="(item, index) in list" :key="item.url" class="preview-list-item"
:class="{ active: current === index }" @click="check(index)">
<img :src="item.img" alt="" srcset="" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {},
data() {
return {
show: false,
list: [
{
fileName: "测试1",
size: "3M",
creatTime: "2024",
path: "ua/",
img: require("../../images/medium/medium-video.png"),
type: "mp4",
url:
"https://vdept3.bdstatic.com/mda-qh0g1z9fvz28wwe9/cae_h264/1722574521989748719/mda-qh0g1z9fvz28wwe9.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1726726486-0-0-bdef95510c480bcaa1f3c311508a59de&bcevod_channel=searchbox_feed&pd=1&cr=0&cd=0&pt=3&logid=0886315216&vid=10817113278196649776&klogid=0886315216&abtest=",
},
{
fileName: "测试2",
size: "3M",
creatTime: "2024",
path: "ua/",
img: require("../../images/medium/medium-img.png"),
url:
"//lf-web-assets.juejin.cn/obj/juejin-web/xitu_juejin_web/e08da34488b114bd4c665ba2fa520a31.svg",
type: "img",
},
],
detail: {
fileName: "测试1",
size: "3M",
creatTime: "2024",
path: "ua/",
img: require("../../images/medium/medium-video.png"),
type: "mp4",
url:
"https://vdept3.bdstatic.com/mda-qh0g1z9fvz28wwe9/cae_h264/1722574521989748719/mda-qh0g1z9fvz28wwe9.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1726726486-0-0-bdef95510c480bcaa1f3c311508a59de&bcevod_channel=searchbox_feed&pd=1&cr=0&cd=0&pt=3&logid=0886315216&vid=10817113278196649776&klogid=0886315216&abtest=",
},
current: 0,
};
},
methods: {
check(idx) {
// const index = this.list.findIndex((item) => item.url === this.detail.url);
// console.log("index", index);
// console.log("list", this.list);
if (idx < 0) {
this.detail = this.list[this.list.length - 1];
this.current = this.list.length - 1;
} else if (idx > this.list.length - 1) {
this.detail = this.list[0];
this.current = 0;
} else {
this.detail = this.list[idx];
this.current = idx;
}
},
},
created() {
this.$recvChanel("previewBus", (data) => {
// console.log("收到的数据222", data);
this.list = data.list;
this.detail = data.detail;
this.current = data.index;
this.show = true;
});
},
mounted() {
// this.detail = this.list[0];
// const container = document.querySelector(".scroll-container");
// container.addEventListener("wheel", (event) => {
// event.preventDefault(); // 阻止默认纵向滚动
// // 横向滚动父容器中的内容
// container.scrollLeft += event.deltaY;
// });
},
};
</script>
<style lang="scss">
.preview-container {
height: 100vh;
width: 100vw;
position: fixed;
// top: -20%;
// left: -12%;
top: 0;
left: 0;
z-index: 111;
background-color: rgba($color: #000000, $alpha: 0.5);
.preview-top {
width: 100%;
height: 85%;
display: flex;
background-color: rgba(240, 248, 255, 0.281);
.preview-t-l {
width: 80%;
height: 100%;
background-color: #000000;
.preview-item-wrapper {
width: 100%;
height: 100%;
position: relative;
.arrows {
position: absolute;
top: 50%;
width: 50px;
height: 50px;
background-color: rgba(0, 0, 0, 0.4);
cursor: pointer;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
img {
width: 30px;
height: 30px;
}
}
.arrow-l {
left: 40px;
}
.arrow-r {
right: 40px;
transform: rotate(180deg);
}
.preview-item {
width: 100%;
height: 100%;
}
}
}
.preview-t-r {
width: 20%;
height: 100%;
background-color: #1c1c1c;
position: relative;
.preview-close {
position: absolute;
top: 10px;
right: 10px;
>img {
height: 20px;
width: 20px;
cursor: pointer;
}
}
.preview-t-infos {
height: 100%;
width: 100%;
padding: 15% 0 0 5%;
font-size: 12px;
.preview-t-info {
width: 100%;
display: flex;
// &:nth-child(1) {
// width: 25%;
// text-align: justify;
// background-color: aqua;
// }
.preview-t-info-text {
margin-right: 15px;
margin-bottom: 15px;
}
.label {
width: 18%;
text-align: justify;
}
}
}
}
}
.preview-bottom {
width: 100%;
height: 15%;
background-color: #000000;
display: flex;
align-items: center;
justify-content: center;
margin: auto;
.preview-list-wrapper {
height: 80px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
overflow-x: auto;
.preview-list-item {
display: inline-block;
height: 60px;
width: 100px;
border: 2px solid rgb(255, 255, 255);
margin: 0 5px;
cursor: pointer;
img {
height: 100%;
width: 100%;
}
}
.active {
border: 2px solid rgb(0, 140, 255);
}
}
}
}
</style>

View File

@ -0,0 +1,130 @@
<template>
<div class="flyHistroy">
<span class="flyHistroy_name" @click="back">返回</span>
<span class="flyHistroy_name">{{ data.missionName }}</span>
<div class="flyHistroy_bottom">
<div class="bottom_item">
<div class="title">飞行器</div>
<div class="content">M3TD-庆福广场</div>
</div>
<div class="bottom_item">
<div class="title">任务开始时间</div>
<div class="content">{{ data.createdAt }}</div>
</div>
<div class="bottom_item">
<div class="title">任务结束时间</div>
<div class="content">
{{ parseTime(data.lastTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</div>
</div>
<div class="bottom_item">
<div class="title">轨迹最后更新时间</div>
<div class="content">
{{ parseTime(data.lastTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</div>
</div>
<div class="bottom_item">
<div class="title">实际飞行距离</div>
<div class="content">{{ length }}m</div>
</div>
<div class="bottom_item">
<div class="title">实际飞行时长</div>
<div class="content">{{ time }}</div>
</div>
</div>
</div>
</template>
<script>
import { parseTime } from '@/utils/index.ts';
export default {
name: 'flyHistroy',
props: {
data: {
type: Object,
default: () => {}
}
},
data() {
return {
points: [],
length: '',
time: ''
};
},
// 监听data
// watch: {
// data: function handle(newVal, oldVal) {
// let pointss = JSON.parse(newVal);
// this.renderRouter(pointss.points);
// }
// },
mounted() {
let pointss = JSON.parse(this.data.points);
this.renderRouter(pointss.points);
},
methods: {
parseTime(val) {
return parseTime(val);
},
renderRouter(positions) {
let airLine = new YJ.Obj.newAirLine(
{
positions,
frustumShow: false,
keyboard: false
},
window.Earth1.viewer
);
window.airLine = airLine;
this.length = airLine.countLength();
this.time = airLine.countTime();
window.airLine.flyTo();
// console.log("airLine", airLine);
},
// 返回计划库
back() {
this.$emit('back');
}
}
};
</script>
<style lang="scss">
.flyHistroy {
position: absolute;
top: 8%;
left: 20px;
z-index: 20;
color: #fff;
&_name {
border-radius: 8px;
padding: 8px 16px;
background: linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%), rgba(0, 0, 0, 0.6);
backdrop-filter: blur(2px);
}
&_bottom {
position: fixed;
bottom: 10%;
left: 30px;
height: 80px;
width: 60%;
display: flex;
justify-content: space-between;
.title {
font-size: 16px;
margin-bottom: 10px;
font-weight: 400;
}
.content {
font-size: 18px;
color: rgba(0, 255, 255, 1);
font-weight: 600;
}
}
}
</style>

View File

@ -0,0 +1,837 @@
<template>
<div class="planLank flex">
<div class="planLank_left">
<div class="header flex space_between ai_center">
<span class="text">计划库</span>
<div class="add" @click="add">
<img src="../../images/add_plan.png" alt="" />
<span class="f16">新建</span>
</div>
</div>
<div class="content">
<div class="title">
<span>{{ gateway.deviceType }}</span>
<span>{{ gateway.businessName }}</span>
</div>
<div class="card">
<div class="card_top flex">
<div class="card_top_left flex ai_center">
<div>
<img src="../../images/time.png" alt="" />
<span>待执行</span>
</div>
<div>
{{ listObj.timer ? listObj.timer : '' }}
</div>
</div>
<div class="card_top_right flex">
<span>{{ listObj.missionName ? listObj.missionName : '暂无' }}</span>
</div>
</div>
<div class="card_bottom flex">
<!-- <div class="card_bottom_top">
<span>{{ gateway.deviceType }}</span>
<span>{{ gateway.businessName }}</span>
</div> -->
<div class="card_bottom_bottom">
<div class="flex ai_center mr10 mb10">
<img src="../../images/eq.png" alt="" />
<span class="status">设备{{ filterModeCode(mode.flighttask_step_code) }}</span>
</div>
<div class="flex ai_center mb10">
<img src="../../images/air.png" alt="" />
<span
>{{ filterDroneInDock(networkState.drone_in_dock) }}
{{ filterDeviceOnlineStatus(networkState.sub_device.device_online_status) }}</span
>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="planLank_right">
<div class="header_form">
<el-form :inline="true" size="small" class="demo-form-inline">
<el-form-item>
<el-date-picker
v-model="time"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="changeTime"
>
</el-date-picker>
</el-form-item>
<el-form-item>
<el-select v-model="tableForm.type" @change="getQuestRecordList" placeholder="所有类型">
<el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
</el-select>
<template #label></template>
</el-form-item>
<el-form-item>
<el-select v-model="tableForm.state" @change="getQuestRecordList" placeholder="全部状态">
<el-option v-for="item in states" :label="item" :value="item"></el-option>
</el-select>
<template #label></template>
</el-form-item>
<el-form-item>
<el-input v-model.trim="tableForm.name" @change="getQuestRecordList" clearable placeholder="按航线或者计划名称搜索">
<i style="cursor: pointer" slot="suffix" class="el-input__icon el-icon-search" @click="search"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="refresh" icon="el-icon-refresh-right"></el-button>
</el-form-item>
</el-form>
</div>
<div class="table">
<el-table :data="tableData.data" style="width: 100%">
<el-table-column prop="createdAt" label="创建时间" width="180" align="center"> </el-table-column>
<el-table-column prop="state" label="执行状态" width="180" align="center"> </el-table-column>
<el-table-column prop="type" label="类型" align="center">
<template slot-scope="scope">
<span>{{ scope.row.type == 0 ? '立即' : '定时' }}</span>
</template>
</el-table-column>
<el-table-column prop="missionName" label="计划名称" align="center"> </el-table-column>
<el-table-column prop="fileName" label="航线名称" align="center"> </el-table-column>
<!-- <el-table-column prop="realPhotoNum" label="实际上传" align="center">
</el-table-column> -->
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<div>
<el-tooltip v-if="['执行中', '暂停'].includes(scope.row.state)" class="item" content="航线暂停" effect="dark">
<span style="display: inline-block; cursor: pointer" @click="handlerClick(1, scope.row)">
<img src="../../images/zanting.png" alt="" />
</span>
</el-tooltip>
<el-tooltip v-if="['执行中', '暂停'].includes(scope.row.state)" class="item" content="航线恢复" effect="dark">
<span style="display: inline-block; cursor: pointer" @click="handlerClick(2, scope.row)">
<img src="../../images/huifu.png" alt="" />
</span>
</el-tooltip>
<el-tooltip v-if="['执行中', '暂停'].includes(scope.row.state)" class="item" content="一键返航" effect="dark">
<span style="display: inline-block; cursor: pointer" @click="handlerClick(3, scope.row)">
<img src="../../images/fanhang.png" alt="" />
</span>
</el-tooltip>
<el-tooltip v-if="['执行成功'].includes(scope.row.state)" class="item" content="飞行记录" effect="dark">
<span style="display: inline-block; cursor: pointer" @click="handlerClick(4, scope.row)">
<img src="../../images/flyh.png" alt="" />
</span>
</el-tooltip>
<el-tooltip class="item" content="删除" effect="dark">
<span style="display: inline-block; cursor: pointer" @click="handlerClick(5, scope.row)">
<img src="../../images/shanchu.png" alt="" />
</span>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="progress">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="tableForm.pageNum"
:page-sizes="[10, 20, 30, 40]"
:page-size="tableForm.pageSize"
layout="prev, pager, next,sizes"
:total="tableData.total"
>
</el-pagination>
</div>
</div>
<myDialog ref="createPL" class="createPL" :title="title">
<div class="form">
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="110px" class="demo-ruleForm">
<el-form-item label="计划名称" prop="missionName">
<el-input v-model="form.missionName" placeholder="请输入计划名称" clearable></el-input>
</el-form-item>
<el-form-item label="执行航线" prop="fileId">
<el-select style="width: 100%" v-model="form.fileId" filterable placeholder="请选择活动区域">
<el-option v-for="item in airList" :label="item.fileName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="任务精度" prop="waylinePrecisionType">
<el-radio-group style="width: 100%" v-model="form.waylinePrecisionType">
<el-radio-button v-for="dict in waylinePrecisionTypeOptions" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="任务策略" prop="type">
<el-radio-group style="width: 100%" v-model="form.type">
<el-radio-button :label="0">立即</el-radio-button>
<el-radio-button :label="1">单次定时</el-radio-button>
<!-- <el-radio-button :label="2">重复定时</el-radio-button> -->
</el-radio-group>
</el-form-item>
<el-form-item label="返航高度模式" prop="returnAltitudeMode">
<el-radio-group style="width: 50%" v-model="form.returnAltitudeMode">
<el-radio-button v-for="dict in rthModeOptions" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.type == 1" label="执行时间" prop="timer">
<el-date-picker value-format="yyyy-MM-dd HH:mm:ss" v-model="form.timer" type="datetime" placeholder="选择日期时间"> </el-date-picker>
</el-form-item>
<el-form-item v-if="form.type == 2" label="执行时间" prop="execTime">
<el-date-picker v-model="form.execTime" type="datetime" placeholder="选择日期时间"> </el-date-picker>
</el-form-item>
<el-form-item label="航线飞行中失联" prop="outOfControlAction">
<el-radio-group style="width: 100%" v-model="form.outOfControlAction">
<el-radio-button v-for="dict in exitWaylineOptions" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<div>
<div>返航高度(相对机场返航高度)</div>
<div style="display: flex; justify-content: center">
<plusReduce :defValue="form.returnAltitude" @value="height" style="width: 80%" :max="1500" :min="20" unit="m"> </plusReduce>
</div>
</div>
<el-form-item label="失联动作" prop="flightControlAction">
<!-- 失控动作类型 -->
<el-radio-group style="width: 100%" v-model="form.flightControlAction">
<el-radio-button v-for="dict in outOfControlOptions" :label="dict.value">{{ dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<div class="btns">
<el-button size="small" @click="submit">确定</el-button>
<el-button size="small" @click="cancel">取消</el-button>
</div>
</myDialog>
</div>
</template>
<script>
import myDialog from '../component/dialog/index.vue';
import plusReduce from '../component/plusReduce/index.vue';
import {
listPaths,
taskList,
taskAdd,
getLatest,
flightTaskPauseNew,
flightTaskRecoveryNew,
flightTaskUndoNew,
delMissionsNew,
returnHomeNew
} from '@/api/air';
import { getLocal, parseTime } from '@/utils/';
import { exitWaylineOptions, waylinePrecisionTypeOptions, outOfControlOptions, rthModeOptions } from '../../utils/options.js';
import { useAirStore } from '@/store/modules/drone';
export default {
name: 'planLank',
components: {
myDialog,
plusReduce
},
data() {
return {
parseTime: parseTime,
rthModeOptions: rthModeOptions,
outOfControlOptions: outOfControlOptions,
exitWaylineOptions: exitWaylineOptions,
waylinePrecisionTypeOptions: waylinePrecisionTypeOptions,
rules: {
missionName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
fileId: [{ required: true, message: '请选择任务航线', trigger: 'blur' }],
timer: [{ required: true, message: '请选择时间', trigger: 'blur' }]
},
formInline: {
region: '',
value: '',
user: ''
},
options: [
{
label: '全部',
value: ''
},
{
label: '立即',
value: 0
},
{
label: '定时',
value: 1
}
],
time: [],
states: ['全部', '取消或终止', '失败', '执行中', '定时待执行', '执行成功', '部分完成', '暂停', '拒绝', '已下发', '超时'],
form: {
missionName: '',
// executionTime: "",
timer: null,
type: 0,
fileId: '',
returnAltitude: 100,
returnAltitudeMode: 0,
outOfControlAction: 1,
flightControlAction: 0,
waylinePrecisionType: 1,
gateway: '',
timeStamp: ''
},
value1: '',
value2: '',
title: '新建计划',
queryParam: {
pageNum: 1,
pageSize: 10000000,
fileName: '',
order: 1,
gateway: ''
},
tableForm: {
pageNum: 1,
pageSize: 20,
gateway: ''
},
airList: [],
networkState: {
mode_code: 0,
drone_in_dock: 1,
sub_device: {
device_online_status: 0
}
},
mode: {},
gateway: useAirStore().gateWay,
tableData: {
data: [],
total: 0
},
time: [],
listObj: {}
};
},
watch: {
// 监听 Vuex 中的状态变化
'useAirStore().gateWay': {
handler(newValue, oldVal) {
this.gateway = newValue;
console.log('newValue', newValue);
this.getQuestRecordList();
this.getNewestTimer();
},
deep: true
}
},
mounted() {
this.allApi();
this.$recvChanel('websocketBus', (data) => {
if (data.businessType == 'osd3') {
this.networkState = { ...data.data };
// console.log(this.networkState);
}
if (data.businessType == 'osd2') {
this.mode = { ...data.data };
}
});
this.form.gateway = this.gateway.gateway;
this.queryParam.flag = this.gateway.gateway;
},
methods: {
allApi() {
this.getAirRouteList();
this.getQuestRecordList();
this.getNewestTimer();
},
search() {
if (this.time.length > 0) {
this.tableForm.startTime = this.time[0];
this.tableForm.endTime = this.time[1];
}
this.getQuestRecordList();
},
changeTime(value) {
this.tableForm.startTime = value[0];
this.tableForm.endTime = value[1];
this.getQuestRecordList();
},
handlerClick(num, item) {
console.log(num, item);
if (num == 1) {
this.pauseAirLine(item);
}
if (num == 2) {
this.recoverAirLine(item);
}
if (num == 3) {
this.onBackHome(item);
}
if (num == 4) {
this.$emit('openHistroy', item);
}
if (num == 5) {
this.onDelQuest(item);
}
},
// delMissions
// 航线暂停
pauseAirLine(row) {
let obj = { gateway: this.gateway.gateway, taskId: row.id };
flightTaskPauseNew(obj).then((res) => {
this.$message.success('暂停成功');
this.getQuestRecordList();
});
},
// 航线恢复
recoverAirLine(row) {
let obj = { gateway: this.gateway.gateway, taskId: row.id };
flightTaskRecoveryNew(obj).then((res) => {
this.$message.success('恢复成功');
this.getQuestRecordList();
});
},
// 一键航航
onBackHome(row) {
let obj = { gateway: this.gateway.gateway, taskId: row.id };
returnHomeNew(obj).then((res) => {
this.$message.success('返航成功');
this.getQuestRecordList();
});
},
// 删除 delMissions
onDelQuest(row) {
this.$confirm('确认删除该任务信息?', '温馨提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delMissionsNew(row.id).then((res) => {
if (res.code == 200) {
this.$message.success('删除成功');
this.getQuestRecordList();
this.getNewestTimer();
}
});
});
},
refresh() {
this.tableForm = {
pageNum: 1,
pageSize: 10,
gateway: this.gateway.gateway
};
this.getQuestRecordList();
},
// 获取任务历史记录数据
getQuestRecordList(flag = false) {
this.loading = true;
this.tableForm.gateway = this.gateway.gateway;
taskList(this.tableForm).then((res) => {
this.loading = false;
if (res.code == 200) {
console.log(res.rows);
let list = res.rows || [];
this.tableData.data = list;
this.tableData.total = res.total;
if (flag) {
this.$sendChanel('TaskIssuedSuccessfully', list[0].flightId);
}
}
});
},
// 最新的定时任务
getNewestTimer() {
let obj = { gateway: this.gateway.gateway };
getLatest(obj).then((res) => {
if (res.code == 200) {
this.listObj = res.data || {};
}
});
},
getAirRouteList() {
listPaths(this.queryParam).then((res) => {
let list = res.rows || [];
this.airList = list;
});
},
filterDeviceOnlineStatus(val) {
let obj = { '0': '关机', '1': '开机' };
return obj[val];
},
filterDroneInDock(val) {
let obj = { '0': '舱外', '1': '舱内' };
return obj[val];
},
filterModeCode(val) {
let obj = {
'0': '作业准备中',
'1': '飞行作业中',
'2': '作业后状态恢复',
'3': '自定义飞行区更新中',
'4': '地形障碍物更新中',
'5': '任务空闲',
'255': '飞行器异常',
'256': '未知状态'
};
return obj[val];
},
height(value) {
this.form.height = value;
},
add() {
this.$refs.createPL.open();
},
submit() {
// console.log("this.form", this.form);
this.form.timeStamp = Date.now();
const { mode_code } = this.networkState;
if (mode_code == 2) {
this.$message.warning('请退出调试模式');
return;
}
// 判断returnAltitude是否有值
if (this.form.returnAltitude == '') {
return this.$message.warning('请设置返航高度');
}
// 判断this.mode.flighttask_step_code是否为2
if (this.mode.flighttask_step_code !== 5) {
let text = this.filterModeCode(this.mode.flighttask_step_code);
this.$message.warning(text);
return;
}
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
taskAdd(this.form).then((res) => {
// console.log(res);
if (res.code == 200) {
this.$message.success('新建成功');
this.getQuestRecordList(true);
this.getNewestTimer();
this.cancel();
this.reset();
}
});
} else {
return false;
}
});
},
cancel() {
this.$refs.createPL.close();
this.reset();
},
handleSizeChange(val) {
this.tableForm.pageSize = val;
this.getQuestRecordList();
},
handleCurrentChange(val) {
this.tableForm.pageNum = val;
this.getQuestRecordList();
},
reset() {
this.form = {
missionName: '',
timer: null,
type: 0,
fileId: '',
returnAltitude: 100,
returnAltitudeMode: 0,
outOfControlAction: 1,
flightControlAction: 0,
waylinePrecisionType: 0,
gateway: this.gateway.gateway,
timeStamp: ''
};
}
}
};
</script>
<style lang="scss">
.planLank {
position: absolute;
left: 50%;
top: 50%;
z-index: 20;
transform: translate(-50%, -50%);
width: 80%;
height: 75%;
color: #fff;
&_left {
width: 320px;
background-color: rgba(0, 0, 0, 0.6);
.header {
box-sizing: border-box;
height: 60px;
padding: 0 20px;
border-bottom: 1px solid rgba(204, 204, 204, 0.2);
.text {
font-size: 18px;
font-family: 'alimamashuheiti';
}
.add {
cursor: pointer;
img {
width: 13px;
height: 12px;
}
}
}
.content {
padding: 0 20px;
.title {
margin: 20px 0;
font-weight: 400;
}
.card {
border: 1px solid rgb(0, 255, 255, 0.5);
border-radius: 4px;
&_top {
border-bottom: 1px solid rgba(204, 204, 204, 0.2);
margin-bottom: 15px;
&_left {
flex-direction: column;
justify-content: space-evenly;
align-items: center;
font-size: 12px;
height: 44px;
background-color: rgba(0, 255, 255, 0.5);
padding: 0 5px;
img {
width: 12px;
height: 12px;
}
}
&_right {
padding: 0 20px;
align-items: center;
font-family: 'alimamashuheiti';
}
}
&_bottom {
flex-direction: column;
padding: 0 15px;
&_top {
margin-bottom: 15px;
}
&_bottom {
color: #1bf8c3;
}
img {
width: 16px;
height: 16px;
margin-right: 5px;
}
}
}
}
}
&_right {
flex: 1;
padding: 10px;
background-color: #f7f9fa;
overflow: hidden;
.table {
height: 650px;
overflow-y: auto;
background-color: #fff;
padding: 10px;
width: 100%;
.el-form-item--mini.el-form-item,
.el-form-item--small.el-form-item {
margin-bottom: 10px;
}
.el-table__header-wrapper table,
.el-table__body-wrapper table {
width: 100% !important;
}
.el-table__body,
.el-table__footer,
.el-table__header {
table-layout: auto;
}
.el-table__empty-block,
.el-table__body {
width: 100% !important;
}
.el-pagination .btn-next,
.el-pagination .btn-prev {
background: center center no-repeat #fff !important;
background-size: 16px !important;
cursor: pointer;
margin: 0;
color: #303133;
}
.el-pagination .el-pagination__total,
.el-pagination .el-pagination__jump,
.el-pagination .number {
color: #000 !important;
}
.el-pagination .el-pager li.active {
color: #409eff !important;
}
img {
width: 14px;
height: 14px;
vertical-align: middle;
}
}
.progress {
padding-top: 5px;
text-align: right;
background-color: #fff;
.el-pagination .el-pagination__total,
.el-pagination .el-pagination__jump,
.el-pagination .number {
color: #606266 !important;
}
.el-pagination .el-pager li.active {
color: #267aff !important;
}
}
}
.createPL {
.el-input__inner {
background-color: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 255, 255, 0.5);
width: 100%;
color: #fff;
}
.el-form-item__label {
color: #fff;
}
.el-row {
line-height: 40px;
margin: 10px 0;
}
.el-radio-group {
display: flex;
.el-radio-button {
flex: 1;
.el-radio-button__inner {
width: 100%;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border-color: rgba(0, 255, 255, 0.5);
-webkit-box-shadow: -1px 0 0 0 rgba(0, 255, 255, 0.5);
box-shadow: -1px 0 0 0 rgba(0, 255, 255, 0.5);
}
.el-radio-button__orig-radio:checked + .el-radio-button__inner {
color: rgba(0, 255, 255, 1);
border-color: rgba(0, 255, 255, 1);
}
}
}
}
.btns {
text-align: center;
.el-button {
background: rgba(0, 255, 255, 0.2);
color: #fff;
border-color: rgba(0, 255, 255, 0.5);
}
.el-button:focus,
.el-button:hover {
border-color: rgba(0, 255, 255, 1);
}
}
}
.el-picker-panel {
background: #fff !important;
color: #606266 !important;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
filter: none;
.el-picker-panel__footer .el-button--default {
color: #606266 !important;
}
.el-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff !important;
color: #606266 !important;
font-size: 14px;
border-radius: 4px;
}
.el-button.is-plain:focus,
.el-button.is-plain:hover {
background: #fff !important;
border-color: #409eff !important;
color: #409eff !important;
}
.el-button.is-disabled,
.el-button.is-disabled:focus,
.el-button.is-disabled:hover {
color: #c0c4cc !important;
cursor: not-allowed;
background-image: none;
background-color: #fff !important;
border-color: #ebeef5 !important;
}
}
.el-message-box {
background: linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%), rgba(0, 0, 0, 0.3);
border: none;
.el-message-box__content,
.el-message-box__title {
color: #fff;
}
.el-button {
border-radius: 4px;
background: rgba(0, 255, 255, 0.2);
color: #fff;
border: 1px solid rgba(0, 255, 255, 0.5);
}
.el-button:hover {
border-color: rgba(0, 255, 255, 1);
}
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<div class="uavselect">
<el-select v-model="value" @change="onChange" placeholder="请选择" size="mini">
<el-option v-for="item in options" :key="item.gateway" :label="item.businessName || item.gateway" :value="item.gateway"> </el-option>
</el-select>
</div>
</template>
<script>
import { listInfo } from '@/api/air';
import { setLocal, getLocal } from '@/utils';
import { gatewaysRemove, gatewaysAdd } from '@/api/air';
import { useAirStore } from '@/store/modules/drone';
export default {
data() {
return {
options: [],
value: '无人机',
parma: {
pageNum: 1,
pageSize: 100,
type: '机场'
}
};
},
watch: {
value: {
handler(newV, oldV) {
if (newV) {
this.$emit('showAirListAndAttr');
}
}
}
},
mounted() {
this.getDrone();
this.value = JSON.parse(getLocal('airGateway')).gateway;
},
methods: {
// 保存sn gateway
onChange(value) {
// console.log("valuevalue", value);
let obj = this.options.filter((item) => item.gateway == value)[0];
setLocal('airGateway', JSON.stringify(obj));
useAirStore().SET_GATEWAY(obj);
this.$emit('showAirListAndAttr');
},
// 获取机场列表
getDrone() {
listInfo(this.parma).then((res) => {
if (res.code == 200) {
let list = res.rows || [];
this.options = list;
}
});
}
}
};
</script>
<style lang="scss">
.uavselect {
position: fixed;
top: 5%;
left: 1%;
z-index: 1999;
.el-input__inner {
background-color: transparent;
border-color: rgba(0, 255, 255, 0.5);
color: #fff;
}
}
</style>

View File

@ -0,0 +1,26 @@
import { request } from "@/utils/requset2";
//进入指令飞行控制模式
export function drcModeEnter(data) {
return request({
url: "/dj/cmdFly/drcModeEnter",
method: "post",
data,
});
}
//有参数的指令飞行
export function hasDataFlight(data) {
return request({
url: "/dj/cmdFly/hasDataFlight",
method: "post",
data,
});
}
//无参数的指令飞行
export function noDataFlight(data) {
return request({
url: "/dj/cmdFly/noDataFlight",
method: "post",
data,
});
}

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="15.52001953125" viewBox="0 0 16 15.52001953125" fill="none">
<path d="M15.9466 14.3674L11.9464 9.45645C11.8964 9.40634 11.8464 9.35623 11.7964 9.35623C11.7464 9.35623 11.6964 9.30611 11.6964 9.30611L10.4964 9.40634C10.5464 9.10566 10.5464 8.75488 10.4464 8.4041C10.2464 7.85286 9.89637 7.45197 9.39637 7.20141L10.1464 0.386176C10.1964 -0.014721 9.64637 -0.165056 9.49637 0.23584L7.19632 6.14906C7.14632 6.19917 7.14632 6.24928 7.14632 6.3495C7.14632 6.39962 7.14632 6.44973 7.19632 6.44973L7.69633 7.20141C6.84632 7.6023 6.34631 8.50432 6.54631 9.40634L0.196191 12.2126C-0.153816 12.3629 -0.00381303 12.9142 0.396195 12.8641L6.69631 11.8618C6.74632 11.8618 6.79632 11.8117 6.84632 11.8117C6.89632 11.8117 6.89632 11.7616 6.94632 11.7115L7.44633 10.7092C7.94634 11.0099 8.54635 11.1101 9.14636 10.9097C9.34636 10.8596 9.49637 10.7594 9.64637 10.6591L15.3965 14.8685C15.7465 15.1191 16.1466 14.7182 15.9466 14.3674ZM9.44637 9.45645C9.29636 9.70701 9.04636 9.95757 8.74635 10.0077L8.54635 10.0077C8.29634 10.0077 8.09634 9.90746 7.89634 9.80723C7.64633 9.60678 7.54633 9.35623 7.54633 9.00544C7.54633 8.55443 7.89634 8.15354 8.29634 8.05331C8.39635 8.0032 8.44635 8.0032 8.54635 8.0032C8.84635 8.0032 9.09636 8.15354 9.29636 8.35398C9.44637 8.50432 9.54637 8.75488 9.54637 9.00544C9.54637 9.20589 9.49637 9.35623 9.44637 9.45645ZM13.4964 8.50432C13.4964 9.10566 13.3964 9.70701 13.1964 10.2582L14.2465 11.5612C14.6965 10.609 14.9465 9.60678 14.9465 8.50432C14.9465 5.4976 13.0964 2.992 10.4464 1.93965L10.3464 3.49312C12.1964 4.39514 13.4964 6.29939 13.4964 8.50432ZM2.89624 10.5088C2.64624 9.85734 2.49623 9.20589 2.49623 8.50432C2.49623 7.6023 2.74624 6.70029 3.09625 5.94861L1.69622 5.34726C1.24621 6.29939 0.996206 7.40186 0.996206 8.50432C0.996206 9.40634 1.19621 10.3084 1.49622 11.1101L2.89624 10.5088ZM7.89634 2.992L8.49635 1.48864L8.04634 1.48864C6.84632 1.48864 5.7463 1.78931 4.79628 2.29043L4.84628 3.99424C5.6963 3.3929 6.74632 3.04211 7.89634 2.992ZM7.99634 14.0166C6.64631 14.0166 5.44629 13.5155 4.49627 12.7137L2.64624 13.0144C3.94626 14.5178 5.8463 15.52 7.99634 15.52C9.84637 15.52 11.5464 14.7683 12.7964 13.5656L11.5464 12.6636C10.5964 13.5155 9.34636 14.0166 7.99634 14.0166ZM4.19627 1.93965L0.996206 4.49536L4.49627 5.99872L4.19627 1.93965Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M12.16 11.52L12.568 11.112L12.568 15.432C12.568 15.7481 12.8279 16 13.144 16C13.4601 16 13.712 15.7481 13.712 15.432L13.712 11.112L14.128 11.52C14.3512 11.7432 14.7128 11.7432 14.936 11.52C15.0485 11.4075 15.104 11.2664 15.104 11.12C15.104 10.9736 15.0485 10.8227 14.936 10.712L13.544 9.328L13.528 9.304L13.52 9.296L13.504 9.288L13.488 9.28L13.48 9.272L13.464 9.264L13.456 9.256L13.44 9.248L13.432 9.24L13.416 9.232L13.408 9.224L13.392 9.216L13.384 9.216L13.368 9.208L13.36 9.2L13.344 9.2L13.328 9.192L13.32 9.192L13.304 9.184L13.296 9.184L13.28 9.176L13.264 9.176L13.248 9.168L13.24 9.168L13.224 9.168L13.216 9.168L13.192 9.16L13.184 9.16L13.168 9.16L13.112 9.16L13.096 9.16L13.08 9.16L13.064 9.168L13.048 9.168L13.032 9.168L13.024 9.168L13.008 9.176L13 9.176L12.984 9.184L12.968 9.184L12.952 9.192L12.944 9.192L12.928 9.2L12.92 9.2L12.904 9.208L12.888 9.216L12.88 9.216L12.864 9.224L12.856 9.232L12.84 9.24L12.832 9.248L12.816 9.256L12.808 9.264L12.792 9.272L12.784 9.28L12.768 9.288L12.76 9.296L12.752 9.304L12.728 9.328L11.344 10.712C11.1208 10.9352 11.1208 11.2968 11.344 11.52C11.5744 11.7414 11.9368 11.7432 12.16 11.52ZM10.304 3.432C10.304 3.11593 10.0521 2.856 9.736 2.856L2.888 2.856C2.57193 2.856 2.32 3.11593 2.32 3.432C2.32 3.74807 2.57193 4 2.888 4L9.736 4C10.0503 4 10.304 3.74807 10.304 3.432ZM10.304 6.864C10.304 6.54793 10.0521 6.288 9.736 6.288L2.888 6.288C2.57193 6.288 2.32 6.54793 2.32 6.864C2.32 7.18007 2.57193 7.432 2.888 7.432L9.736 7.432C10.0503 7.432 10.304 7.18007 10.304 6.864ZM2.888 9.72C2.57193 9.72 2.32 9.97193 2.32 10.288C2.32 10.6041 2.57193 10.864 2.888 10.864L6.312 10.864C6.62807 10.864 6.88 10.6041 6.88 10.288C6.88 9.97193 6.62807 9.72 6.312 9.72L2.888 9.72ZM8.584 13.16L2.32 13.16C2.00571 13.16 1.64 12.9081 1.64 12.592L1.64 2.176C1.64 1.85993 1.89371 1.6 2.208 1.6L10.4 1.6C10.7143 1.6 10.968 1.85993 10.968 2.176L10.968 7.432C10.968 7.74807 11.6857 8 12 8C12.3143 8 12.6 7.74807 12.6 7.432L12.6 1.144C12.6 0.511857 12.0864 0 11.456 0L1.144 0C0.513643 0 0 0.513643 0 1.144L0 13.72C0 14.3521 0.513643 14.864 1.144 14.864L8.584 14.864C8.89829 14.864 9.136 14.3321 9.136 14.016C9.136 13.7017 8.90007 13.16 8.584 13.16ZM10.856 13.44C10.5399 13.44 10.288 13.6999 10.288 14.016L10.288 15.152C10.288 15.4681 10.5399 15.728 10.856 15.728C11.1721 15.728 11.432 15.4681 11.432 15.152L11.432 14.016C11.432 13.6999 11.1721 13.44 10.856 13.44ZM9.712 10.856C10.0281 10.856 10.28 10.5961 10.28 10.28L10.28 9.712C10.28 9.39593 10.0281 9.144 9.712 9.144C9.39593 9.144 9.136 9.39593 9.136 9.712L9.136 10.28C9.136 10.5961 9.39771 10.856 9.712 10.856ZM14.856 13.16L14.856 14.304C14.856 14.6201 15.1159 14.872 15.432 14.872C15.7481 14.872 16 14.6201 16 14.304L16 13.16C16 12.8439 15.7481 12.584 15.432 12.584C15.1159 12.584 14.856 12.8439 14.856 13.16Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15.919921875" height="16" viewBox="0 0 15.919921875 16" fill="none">
<path d="M15.1112 6.9457C14.7718 6.9457 14.4865 6.71375 14.4053 6.39967L14.4042 6.39988C14.363 6.23191 14.3153 6.06583 14.2609 5.90166C14.2065 5.73748 14.1458 5.57572 14.0786 5.41639C13.7466 4.6337 13.2723 3.93128 12.6703 3.32739C12.0666 2.7235 11.3643 2.24916 10.5817 1.91892C10.3134 1.80506 10.0378 1.71088 9.75837 1.63459C9.39315 1.59066 9.11 1.27987 9.11 0.902734C9.11 0.495495 9.44008 0.165363 9.84724 0.165363C9.95397 0.165363 10.0553 0.188238 10.1469 0.229059C12.9246 0.987503 15.1006 3.19988 15.8076 5.99827C15.8185 6.03353 15.8268 6.06944 15.8324 6.10597C15.838 6.14248 15.8407 6.17922 15.8407 6.21617C15.8408 6.61908 15.5141 6.9457 15.1112 6.9457ZM0 7.97245C0 12.4058 3.59352 16 8.02615 16C11.9404 16 15.1997 13.1977 15.9083 9.49084L15.906 9.49002C15.915 9.43909 15.92 9.38673 15.92 9.33319C15.92 8.87028 15.5633 8.49098 15.11 8.45452L15.1103 8.44802L8.00791 7.99434L8.00791 0.962189C8.0103 0.935711 8.01172 0.908938 8.01172 0.881822C8.01172 0.394809 7.61697 0 7.13004 0C7.0789 5.78452e-05 7.02814 0.00452013 6.97776 0.0133868C3.04116 0.526739 0 3.89402 0 7.97245ZM1.97552 10.5285C1.63259 9.72027 1.45929 8.85913 1.45929 7.97245C1.45929 7.08577 1.63259 6.22828 1.9737 5.42006C2.30387 4.63919 2.77631 3.93678 3.37827 3.33289C3.98023 2.73083 4.6807 2.25647 5.46143 1.92442C5.81349 1.773 6.1783 1.65623 6.54861 1.57048L6.54861 8.45897L6.54961 8.45963L6.55082 8.6708C6.55075 8.6748 6.55052 8.67873 6.55052 8.68273C6.55052 9.08825 6.87784 9.4172 7.28264 9.41997L7.28252 9.42023L7.42858 9.42094L7.42858 9.42091L7.91486 9.45208L14.3175 9.86075C13.9892 10.9591 13.3762 11.957 12.5226 12.7598C11.3005 13.9074 9.70435 14.5405 8.02615 14.5405C7.13962 14.5405 6.27863 14.3671 5.47238 14.026C4.68983 13.6957 3.98753 13.2214 3.38374 12.6175C2.77995 12.0136 2.30569 11.3112 1.97552 10.5285Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14.3203125" height="16" viewBox="0 0 14.3203125 16" fill="none">
<path d="M13.9947 3.10876L10.0045 0.103786C9.91533 0.0362046 9.80931 0 9.69847 0L0.402396 0.00241378C0.197584 0.00241378 0 0.144818 0 0.407905L0 15.3435C0 15.679 0.289147 16 0.657809 16L13.9634 16C14.1754 16 14.32 15.8962 14.32 15.6573L14.32 3.5046C14.3176 3.34289 14.1248 3.20531 13.9947 3.10876ZM10.1563 1.75713L10.1563 4.76211C10.1563 4.88037 10.0985 4.98416 9.96834 4.98416L3.6553 4.98416C3.48181 4.98416 3.45772 4.89244 3.45772 4.75487L3.45772 1.25268L9.44306 1.25268L10.1563 1.75713ZM13.1032 14.3298C13.1032 14.588 12.9466 14.7642 12.696 14.7642L1.5879 14.7642C1.39272 14.7666 1.26261 14.6242 1.26261 14.4142L1.26261 1.53266C1.26261 1.36129 1.42164 1.25509 1.63127 1.25509C1.81199 1.25509 2.0409 1.25509 2.29872 1.25509L2.30113 5.85066C2.30113 6.0172 2.40474 6.09443 2.56136 6.09443L10.9008 6.09202C11.0647 6.09443 11.1587 5.97134 11.1538 5.78549L11.1563 2.52225L13.1032 3.98009L13.1032 14.3298ZM2.66738 10.5307L7.21422 10.5307C7.37566 10.5307 7.48409 10.4269 7.48409 10.2797C7.48409 10.1542 7.48409 9.79454 7.48409 9.65938C7.48409 9.50008 7.31542 9.3987 7.16602 9.3987L2.63365 9.3987C2.4457 9.3987 2.35173 9.51697 2.35173 9.65938C2.35173 9.81385 2.35173 10.1831 2.35173 10.2724C2.35173 10.4173 2.46498 10.5307 2.66738 10.5307ZM10.8189 9.3987L8.52983 9.3987C8.33225 9.3987 8.19009 9.51214 8.19009 9.67144C8.19009 9.78488 8.19009 10.13 8.19009 10.2459C8.19009 10.4414 8.34189 10.5331 8.51779 10.5331L10.79 10.5331C10.9924 10.5331 11.1563 10.3907 11.1563 10.2145C11.1563 10.0649 11.1563 9.9297 11.1563 9.75833C11.1587 9.56524 11.0261 9.3987 10.8189 9.3987ZM5.28175 12.3964L2.6722 12.3964C2.47221 12.3964 2.34932 12.5002 2.34932 12.7175C2.34932 12.8623 2.34932 13.0119 2.34932 13.135C2.34932 13.3498 2.47943 13.4826 2.67943 13.4826L5.22392 13.4826C5.41428 13.4826 5.58776 13.3547 5.58776 13.1447C5.58776 12.983 5.58776 12.8647 5.58776 12.7512C5.58776 12.5533 5.42632 12.3964 5.28175 12.3964ZM10.7996 12.3964L6.57086 12.3964C6.35641 12.3964 6.22871 12.5533 6.22871 12.7368C6.22871 12.843 6.22871 13.0385 6.22871 13.1543C6.22871 13.304 6.36364 13.4826 6.57809 13.4826L10.7707 13.4826C10.9852 13.4826 11.1563 13.3305 11.1563 13.1109C11.1563 13.0119 11.1563 12.9009 11.1563 12.7512C11.1587 12.5485 11.031 12.3964 10.7996 12.3964ZM8.83344 4.23593C9.00934 4.23593 9.1515 4.0887 9.1515 3.96078L9.1515 2.23261C9.1515 2.05401 9.05271 1.95505 8.90332 1.95505C8.78043 1.95505 8.30334 1.95505 8.19732 1.95505C8.04792 1.95505 7.97564 2.06366 7.97564 2.2302L7.97564 3.96561C7.97564 4.12008 8.12744 4.23835 8.26237 4.23835C8.37803 4.23593 8.70332 4.23593 8.83344 4.23593Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="12.6400146484375" viewBox="0 0 16 12.6400146484375" fill="none">
<path d="M13.52 12.64L2.48 12.64C1.12 12.64 0 11.52 0 10.16L0 2.48C0 1.12001 1.12 0 2.48 0L13.52 0C14.88 0 16 1.12001 16 2.48L16 10.16C16 11.52 14.88 12.64 13.52 12.64ZM2.8 1.6C2.16 1.6 1.6 2.16001 1.6 2.8L1.68 9.92C1.68 10.56 2.24 11.12 2.88 11.12L13.2 11.12C13.84 11.12 14.4 10.56 14.4 9.92L14.32 2.8C14.32 2.16001 13.76 1.6 13.12 1.6L2.8 1.6ZM6.4 9.84C4.48 9.84 2.88 8.24 2.88 6.32C2.88 4.4 4.48 2.8 6.4 2.8C8.32 2.8 9.92 4.4 9.92 6.32C10 8.24 8.39999 9.84 6.4 9.84ZM6.4 4C5.11999 4 4.08 5.04 4.08 6.32C4.08 7.6 5.11999 8.64 6.4 8.64C7.67999 8.64 8.72 7.6 8.72 6.32C8.72 5.04 7.67999 4 6.4 4ZM13.04 3.6L11.76 3.6C11.52 3.6 11.28 3.36 11.28 3.12L11.28 2.96C11.28 2.72 11.52 2.48 11.76 2.48L13.04 2.48C13.28 2.48 13.52 2.72 13.52 2.96L13.52 3.12C13.52 3.36 13.28 3.6 13.04 3.6Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 981 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,199 @@
<template>
<div class="left_detail">
<div>
<el-tooltip class="item" effect="dark" content="搜星数量" placement="top-start">
<div>
<img src="./icons/satellite.svg" alt="" />
<span>{{ info.position_state.rtk_number }}RTK</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="电池剩余电量" placement="top-start">
<div>
<img src="./icons/battery.svg" alt="" />
<span>{{ info.battery.capacity_percent }}%</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="返航所需电量" placement="top-start">
<div>
<img src="./icons/quantity.svg" alt="" />
<span>{{ info.battery.return_home_power }}%</span>
</div>
</el-tooltip>
</div>
<div>
<el-tooltip class="item" effect="dark" content="内存总容量" placement="top-start">
<div>
<img src="./icons/memory.svg" alt="" />
<span>{{ (info.storage.total / (1024 * 1024)).toFixed(2) }}GB</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="内存已使用容量" placement="top-start">
<div>
<img src="./icons/big.svg" alt="" />
<span>{{ (info.storage.used / (1024 * 1024)).toFixed(2) }}GB</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="飞行高度" placement="top-start">
<div>
<img src="./icons/plane.svg" alt="" />
<span>{{ info.elevation.toFixed(2) }}m</span>
</div>
</el-tooltip>
</div>
<div>
<el-tooltip class="item" effect="dark" content="拍照状态" placement="top-start">
<div>
<img src="./icons/picture.svg" alt="" />
<span>{{
info.cameras && info.cameras[0]
? (info.cameras[0].photo_state == 0 ? "空闲" : "拍照中")
: "-"
}}</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="拍照剩余数量" placement="top-start">
<div>
<img src="./icons/Remaining.svg" alt="" />
<span>{{ info.cameras && info.cameras[0] ? info.cameras[0].remain_photo_num : "-" }}</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="剩余飞行时间" placement="top-start">
<div>
<img src="./icons/FlightTime.svg" alt="" />
<span>{{ filterTime(info.battery.remain_flight_time) }}</span>
</div>
</el-tooltip>
</div>
<div>
<el-tooltip class="item" effect="dark" content="垂直速度" placement="top-start">
<div>
<span>VS {{ info.vertical_speed.toFixed(2) }}</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="相对起飞点高度" placement="top-start">
<div>
<span>ALT {{ Number(info.elevation).toFixed(2) }}</span>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="海拔高度" placement="top-start">
<div>
<span>ASL {{ (Number(info.elevation) + Number(info.height)).toFixed(2) }}</span>
</div>
</el-tooltip>
</div>
<div>
<el-tooltip class="item" effect="dark" content="水平速度" placement="top-start">
<div>
<span>HS {{ info.horizontal_speed.toFixed(2) }}</span>
</div>
</el-tooltip>
<div>&nbsp;</div>
<div>&nbsp;</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
info: {
//飞行器数据信息
cameras: [{ remain_photo_num: 0, photo_state: 0 }],
horizontal_speed: 0,
position_state: { rtk_number: 0 },
vertical_speed: 0,
horizontal_speed: 0,
elevation: 0,
height: 0,
wind_direction: "0",
battery: {
capacity_percent: 0,
remain_flight_time: 0,
return_home_power: 0,
},
wind_speed: 0,
storage: { total: 0, used: 0 },
},
};
},
created() { },
mounted() {
this.$recvChanel("websocketBus", (res) => {
if (res.businessType == "osd4") {
this.info = res.data || {}
this.time = new Date().getTime()
}
});
this.onReset()
},
methods: {
restet() {
this.info = {
cameras: [{ remain_photo_num: 0, photo_state: 0 }],
horizontal_speed: 0,
position_state: { rtk_number: 0 },
vertical_speed: 0,
horizontal_speed: 0,
elevation: 0,
height: 0,
wind_direction: "0",
battery: {
capacity_percent: 0,
remain_flight_time: 0,
return_home_power: 0,
},
wind_speed: 0,
storage: { total: 0, used: 0 },
};
},
onReset() {
let that = this
that.timer = setInterval(() => {
const currentTimestamp = Date.now(); // 获取当前时间戳
const elapsed = currentTimestamp - that.time; // 计算差值
if (elapsed > 2000) {
that.restet();
}
}, 2000);
},
filterTime(val) {
//返回分钟
let miu = parseInt(val / 60);
let s = val % 60;
return miu + "分" + s + "秒";
},
},
};
</script>
<style lang="scss" scoped>
.left_detail {
display: flex;
align-items: center;
padding: 20px 30px 0 80px;
justify-content: space-between;
height: 100%;
>div {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
font-size: 14px;
>div {
display: flex;
// height: 100%;
align-items: center;
color: #fff;
font-weight: 600;
>img {
width: 20px;
margin-right: 6px;
height: 20px;
}
}
}
}
</style>

View File

@ -0,0 +1,282 @@
<template>
<div class="drone_video_box" id="drone_video_box" @wheel="debouncedWheel" @dblclick="onDetailPost">
<div class="shutter" :id="'shutter' + type"></div>
<!-- <video :class="{ video_drone: true }" :id="'drone_video-webrtc_' + droneObj.cameraIndex" autoplay muted width="100%"
height="100%"></video> -->
<div :id="'drone_video-webrtc_' + droneObj.cameraIndex" :class="{ video_drone: true }" style="width: 100%; height: 100%">
<webrtc :ref="'drone_video-webrtc_' + droneObj.cameraIndex" width="100%" height="100%"></webrtc>
</div>
<myLoading ref="myLoadingRef"></myLoading>
</div>
</template>
<script>
// 加载中
import myLoading from '../myLoading/index.vue';
import { hasDataFlight } from '@/api/air';
import webrtc from '@/components/webrtc';
import { getDockAir } from '@/utils/auth';
export default {
components: {
myLoading,
webrtc
},
props: {
type: {
type: Number,
default: 1 //1、无人机 2、机场
},
typeLabel: {
type: String,
default: '广角' //1、无人机 2、机场
}
},
data() {
return {
droneObj: {},
videoShow: false,
flvPlayer: null,
camera_type: 'wide',
videoUrl: '',
aiUrl: ''
};
},
mounted() {
this.$recvChanel('camera_type', (type) => {
this.camera_type = type;
});
this.$recvChanel('video_width', (height) => {
this.getInfo(this.droneObj);
let div = document.getElementById('drone_video_box');
div.style.height = height + 'px';
let div2 = document.getElementById('drone_video-webrtc_' + this.droneObj.cameraIndex);
div2.style.height = height + 'px';
});
},
beforeDestroy() {
this.getVideoEnd(this.droneObj);
},
methods: {
debouncedWheel(event) {
// 滚轮滚动
// 如果是机场视频和广角类型 不需要滚动
if (!(this.type == 2 || this.typeLabel == '广角')) {
this.$emit('wheelScale', event);
}
},
// 拍照效果
setShutter() {
const shutter = document.getElementById('shutter' + this.type);
shutter.classList.add('active');
// 动画结束后移除类,以便可以重新触发动画
setTimeout(() => {
shutter.classList.remove('active');
}, 300); // 动画时长
},
onDetailPost(e) {
if (this.type == 2) {
// 机场视频不需要点击
return;
}
// 获取坐标
let div = document.getElementById('drone_video_box');
// let div = document.getElementById("drone_video-webrtc_" + this.droneObj.cameraIndex);
let rect = div.getBoundingClientRect();
let width = rect.width;
let height = rect.height;
let x, y;
if (width && height && e.offsetX && e.offsetY) {
x = Number(e.offsetX / width);
y = Number(e.offsetY / height);
let obj = {
gateway: this.droneObj.gateway,
method: 'camera_aim',
params: {
camera_type: this.camera_type,
locked: true,
payload_index: this.droneObj.cameraIndex,
x,
y
}
};
hasDataFlight(obj).then((res) => {
if (res.code == 200) {
this.$message.success('操作成功');
}
});
}
},
// 数据获取并播放
getInfo(data) {
this.$refs.myLoadingRef.statusLoading(true);
this.droneObj = data;
console.log('this.droneObj', this.droneObj);
this.$nextTick(() => {
this.getVideo(data);
});
},
// 视频结束
getVideoEnd(data, cb) {
if (this.flvPlayer) {
axios({
// url: process.env.DOCKAIR + "/dj/live/stop",
url: getDockAir() + '/dj/live/stop',
method: 'post',
data
}).then((res) => {
if (res.data.code !== 200) {
this.$message.error(res.data.msg);
} else {
this.videoShow = false;
try {
this.destroyVideo();
if (cb) cb();
} catch (error) {}
}
});
} else {
if (cb) cb();
}
},
// 销毁
destroyVideo() {
this.flvPlayer.pause(); // 暂停播放数据流
this.flvPlayer.unload(); // 取消数据流加载
this.flvPlayer.destroy(); // 销毁播放实例
this.flvPlayer.detachMediaElement(); // 将播放实例从节点中取出
this.flvPlayer = null;
},
// 开始视频调用
getVideo(data1) {
if (this.flvPlayer) {
this.destroyVideo();
}
axios({
// url: process.env.DOCKAIR + "/dj/live/start",
url: getDockAir() + '/dj/live/start',
method: 'post',
data: data1
}).then((res) => {
const { code, data, msg } = res.data;
if (code == 200) {
// this.playFlv(data.url, "drone_video-webrtc_" + data1.cameraIndex, data1.cameraIndex)
setTimeout(() => {
this.$refs['drone_video-webrtc_' + data1.cameraIndex].startPlay(data.url);
this.videoUrl = data.url;
this.aiUrl = data.aiUrl;
}, 1000);
this.$refs.myLoadingRef.statusLoading(false);
} else {
this.$refs.myLoadingRef.statusLoading(false);
this.$message.error(msg);
}
});
},
// 播放flv视频
playFlv(flv, videoId) {
if (flvjs.isSupported()) {
var videoElement = document.getElementById(videoId);
this.flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
hasAudio: false,
url: flv,
enableStashBuffer: true,
enableWorker: true,
autoCleanupSourceBuffer: true //自动清除缓存
});
this.$nextTick(() => {
this.flvPlayer.attachMediaElement(videoElement);
this.flvPlayer.load();
this.flvPlayer.play();
this.$refs.myLoadingRef.statusLoading(false);
// this.handleDelay(this.flvPlayer);
});
this.flvPlayer.on('error', () => {
console.log('播放错误');
});
// videoElement.addEventListener("error", () => {
// console.log("播放错误");
// // 重新播放
// // flvPlayer.attachMediaElement(videoElement);
// // flvPlayer.load();
// // flvPlayer.play();
// });
}
},
// 重新播放加载
onPlay() {
if (this.flvPlayer) {
this.flvPlayer.load();
this.flvPlayer.play();
}
},
// 处理延迟
handleDelay(flvPlayer) {
let timer = setInterval(() => {
if (!flvPlayer) return;
if (flvPlayer.buffered.length) {
let end = flvPlayer.buffered.end(0); //获取当前buffered值
let diff = end - flvPlayer.currentTime; //获取buffered与currentTime的差值
if (diff >= 2) {
//如果差值大于等于0.5 手动跳帧 这里可根据自身需求来定
flvPlayer.currentTime = flvPlayer.buffered.end(0) - 1.5; //手动跳帧
}
}
}, 1000); //1000毫秒执行一次
return timer;
}
}
};
</script>
<style lang="scss" scoped>
.drone_video_box {
width: 960px;
height: 720px;
position: relative;
overflow: hidden;
.shutter {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
transform: translateY(-100%);
opacity: 0;
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.shutter.active {
transform: translateY(0);
opacity: 1;
}
@keyframes shutterAnimation {
0% {
transform: translateY(-100%);
opacity: 0;
}
50% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-100%);
opacity: 0;
}
}
.video_drone {
// width: 100%;
background-color: rgba(0, 0, 0, 1);
// object-fit: fill;
// height: 100%;
}
}
</style>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View File

@ -0,0 +1,187 @@
<template>
<div class="flight_setup">
<div class="left_in">
<div>任务剩余距离</div>
<div>~</div>
<div>任务剩余时长</div>
<div>~</div>
<div>飞行作业模式</div>
<!-- <div></div> -->
<!-- display: flex;justify-content: center; -->
<div v-if="taskId" style="" @click="stopLine">
<div class="hangxian">
<span>{{ showPath ? '暂停航线' : '恢复航线' }}</span>
</div>
</div>
</div>
<div class="tool_flight">
<div @click="onFightTool">
<img src="./icons/icon1.svg" alt="" />
<span>飞行设置</span>
</div>
<div @click="onHomewardVoyage">
<img src="./icons/icon2.svg" alt="" />
<span>{{ showCancel ? '返航' : '取消返航' }}</span>
</div>
<div @click="onScram">
<img src="./icons/icon3.svg" alt="" />
<span>急停</span>
</div>
</div>
</div>
</template>
<script>
import { droneControl, droneEmergencyStop, flightTaskPauseNew, flightTaskRecoveryNew, drcModeEnte } from '@/api/air';
import { useAirStore } from '@/store/modules/drone';
export default {
data() {
return {
showCancel: true,
height: 0,
gateway: useAirStore().gateWay,
showPath: true,
taskId: ''
};
},
mounted() {
this.$recvChanel('TaskIssuedSuccessfully', (taskId) => {
this.taskId = taskId;
});
this.$recvChanel('showCancel', (showCancel) => {
this.showCancel = !showCancel;
});
},
methods: {
onHomewardVoyage() {
if (this.showCancel) {
// 飞机返航
this.$emit('showTips', true);
} else {
// 取消返航
this.$emit('showTipss', true);
}
},
// 飞行设置
onFightTool() {
this.$emit('onTakeOff', true);
},
// 暂停航线
stopLine() {
if (this.showPath) {
this.showPath = false;
// 暂停航线
this.pauseAirLine();
} else {
this.showPath = true;
// 恢复航线
this.recoverAirLine();
}
},
// 急停
onScram() {
// 无参数接口调用
droneEmergencyStop({ gateway: this.gateway.gateway }).then((res) => {
if (res.code == 200) {
this.$message.success('操作成功');
}
});
},
// 航线暂停
pauseAirLine() {
let obj = { gateway: this.gateway.gateway, taskId: this.taskId };
flightTaskPauseNew(obj).then((res) => {
this.$message.success('暂停成功');
});
},
// 航线恢复
recoverAirLine() {
let obj = { gateway: this.gateway.gateway, taskId: this.taskId };
flightTaskRecoveryNew(obj).then((res) => {
this.$message.success('恢复成功');
});
}
}
};
</script>
<style lang="scss" scoped>
.flight_setup {
width: 100%;
height: 100%;
position: relative;
padding: 20px;
box-sizing: border-box;
display: flex;
> div {
width: 50%;
}
.left_in {
color: #fff;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
> div {
font-size: 14px;
}
.hangxian {
text-align: center;
border: 1px solid rgb(255, 255, 255, 0.8);
width: 60%;
padding: 6px 0;
color: rgb(255, 255, 255, 0.8);
border-radius: 5px;
}
.hangxian:hover {
color: #fff;
border-color: #fff;
}
}
.tool_flight {
> div {
cursor: pointer;
margin-left: 30px;
border-radius: 4px;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
padding: 8px 0px;
display: flex;
width: 150px;
color: rgba(0, 255, 255, 1);
place-items: center;
justify-content: center;
margin-bottom: 8px;
> img {
width: 18px;
margin-right: 10px;
}
}
& > div:last-child {
background: linear-gradient(180deg, rgba(241, 108, 85, 0.2) 0%, rgba(241, 108, 85, 0) 100%), rgba(0, 0, 0, 0.5);
border: 1px solid rgba(241, 108, 85, 1);
color: rgb(241, 108, 85);
}
}
}
.flight_setup::before {
content: '';
display: block;
position: absolute;
top: 0;
left: 50%;
width: 1px;
height: 100%;
background: url('./icons/line.png') no-repeat;
background-size: 100% 100%;
}
</style>

View File

@ -0,0 +1,203 @@
<template>
<div class="graduated_scale" id="graduated_scale" @wheel="debouncedWheel">
<span class="max">{{ max }}X</span>
<div v-for="(item, i) of labelList" :key="i">
<div class="text">{{ item.count }}X</div>
<div class="boxs">
<div
v-for="(items, index) of item.arr"
:key="index"
:style="'height:' + (1 / item.arr.length) * 100 + '%'"
@click="onSelect(items)"
>
<div :class="valueScale == items ? 'select' : ''"></div>
</div>
</div>
</div>
</div>
</template>
<script>
let timeout;
export default {
props: {
valueScale: {
type: Number,
default: 2,
},
typeLabel: {
type: String,
default: "变焦",
},
},
data() {
return {
list: [
//变焦
{ count: 2, arr: [2, 3, 4, 5, 6].reverse() },
{ count: 7, arr: [7, 8, 9, 10, 11, 12].reverse() },
{
count: 14,
arr: [
14,
16,
18,
20,
22,
24,
26,
28,
30,
32,
34,
38,
43,
46,
50,
53,
56,
].reverse(),
},
].reverse(),
// 红外
listInfrared: [
//变焦
{ count: 2, arr: [2, 3, 4, 5].reverse() },
{ count: 6, arr: [7, 8, 9, 10, 11, 12].reverse() },
{ count: 13, arr: [13, 14, 15, 16, 17, 18, 19, 20].reverse() },
].reverse(),
labelList: [], //当前应该显示的倍速
detailList: [],
max: 56, //获取最大值 默认
index: 0, //鼠标滚轮滚动时 改变下标 获取对应的值(倍数大小)
};
},
watch: {
typeLabel(newlog) {
this.setInfo(newlog);
},
},
mounted() {
this.setInfo(this.typeLabel);
},
methods: {
onSelect(item) {
this.index = this.detailList.findIndex((items) => {
return items == item;
});
this.$emit("getScale", item);
},
handleWheel(event) {
// 处理滚轮事件的逻辑
if (event.deltaY > 0) {
this.index--;
if (this.index <= 0) {
this.index = 0;
}
} else {
this.index++;
if (this.index >= this.detailList.length) {
this.index = this.detailList.length - 1;
}
}
},
// 300 毫秒的防抖时间
debouncedWheel(event) {
// 清除之前的定时器
clearTimeout(timeout);
// 设置一个新的定时器
timeout = setTimeout(() => {
this.$emit("getScale", this.detailList[this.index]);
}, 200); // 200毫秒后判断为停止
this.handleWheel(event);
},
// 数据处理
setInfo(label) {
this.detailList = [];
if (label == "变焦") {
this.labelList = this.list;
this.list.forEach((element) => {
this.detailList.push(...element.arr);
});
} else {
//红外
this.labelList = this.listInfrared;
this.listInfrared.forEach((element) => {
this.detailList.push(...element.arr);
});
}
this.detailList.sort((a, b) => a - b);
this.index = this.detailList.findIndex(
(item) => item === this.valueScale
);
this.max = this.detailList[this.detailList.length - 1];
},
},
};
</script>
<style lang="scss" scoped>
.graduated_scale {
width: 70px;
height: 180px;
background: rgba(0, 0, 0, 0.6);
position: absolute;
color: #fff;
bottom: 70px;
right: 20px;
padding: 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
.max {
position: absolute;
top: 6px;
left: 10px;
}
> div {
height: 33%;
color: #fff;
font-size: 14px;
display: flex;
> div {
height: 100%;
}
.text {
display: flex;
justify-content: flex-end;
flex-direction: column;
width: 32px;
}
.boxs {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
> div {
width: 100%;
display: grid;
place-items: center;
> div {
position: relative;
background-color: #fff;
display: inline-block;
width: 10px;
height: 1px;
}
.select::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #fff;
}
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12.96 18.4869C13.2 18.2469 13.44 17.9931 13.44 17.5131C13.44 17.0331 13.2 16.8069 12.96 16.5669C12.72 16.3269 12.48 16.0869 12 16.0869C11.52 16.0869 11.28 16.3269 11.04 16.5669C10.8 16.8069 10.56 17.0331 10.56 17.5131C10.56 17.9931 10.8 18.2469 11.04 18.4869C11.28 18.7269 11.52 18.7269 12 18.9669C12.48 18.7269 12.72 18.7269 12.96 18.4869ZM12 24C8.64 24 5.76686 22.8069 3.60686 20.4069C1.20686 18.2469 0 15.36 0 12C0 8.64 1.20686 5.76686 3.60686 3.60686C5.76686 1.20686 8.64 0 12 0C15.36 0 18.2469 1.20686 20.4069 3.60686C22.8069 5.76686 24 8.64 24 12C24 15.36 22.8069 18.2469 20.4069 20.4069C18.2469 22.8069 15.36 24 12 24ZM10.56 6.95314L11.04 13.6869C11.04 13.9269 11.2869 14.16 11.2869 14.4C11.5269 14.4 11.76 14.6469 12 14.6469C12.24 14.6469 12.4869 14.4 12.7269 14.4C12.7269 14.16 12.96 13.9269 12.96 13.6869L13.44 6.95314C13.68 6.47314 13.4469 6 13.2069 5.76C12.9669 5.28 12.48 5.04686 12 5.04686C11.52 5.04686 11.0469 5.28 10.8069 5.76C10.5669 6 10.32 6.47314 10.56 6.95314Z" fill-rule="evenodd" fill="#FFA145" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,126 @@
<template>
<div class="tips" v-if="showTip">
<div class="header_tips">
<span class="title"> 提示 </span>
<img src="./icons/close.png" @click="onClose" alt="" srcset="" />
</div>
<div class="content">
<img src="./icons/warn.svg" alt="" srcset="" />
<span> {{ obj.text }}</span>
</div>
<div class="footer_bottom">
<div class="cl" @click="onSubmit"> </div>
<div @click="onClose"> </div>
</div>
</div>
</template>
<script>
import { returnHome, returnHomeCancel } from '@/api/air';
export default {
props: {
Drone: {
type: Object,
default: () => ({})
},
obj: {
type: Object,
default: () => ({})
}
},
data() {
return { showTip: false };
},
mounted() {},
methods: {
openTip() {
this.showTip = true;
},
onClose() {
this.showTip = false;
},
// 返航
cancelHome(obj) {
returnHomeCancel(obj).then((res) => {
this.onClose();
this.$message.success('取消成功');
});
},
subHome(obj) {
returnHome(obj).then((res) => {
this.onClose();
this.$message.success('返航成功');
});
},
onSubmit() {
let obj = { gateway: this.Drone.gateway };
if (this.obj.flag) {
this.subHome(obj);
} else {
this.cancelHome(obj);
}
this.$sendChanel('showCancel', this.obj.flag);
}
}
};
</script>
<style lang="scss" scoped>
.tips {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 500px;
opacity: 1;
background: linear-gradient(180deg, rgb(33 33 33 / 70%) 0%, rgb(9 156 156 / 55%) 100%), #0000;
padding: 20px 20px 0;
box-sizing: border-box;
.header_tips {
display: flex;
justify-content: space-between;
align-items: center;
.title {
font-size: 16px;
color: #fff;
}
img {
width: 12px;
cursor: pointer;
}
}
.content {
height: 60px;
display: flex;
align-items: center;
color: #fff;
font-size: 16px;
> img {
width: 18px;
margin-right: 10px;
}
}
.footer_bottom {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: flex-end;
> div {
border-radius: 4px;
cursor: pointer;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
padding: 10px 16px;
margin-right: 16px;
color: rgba(0, 255, 255, 1);
}
}
}
</style>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,98 @@
<template>
<div class="right_detail">
<div class="left_info">
<div>
<span>智能规划最佳飞行路线</span>
<div>
<el-switch v-model="value" inactive-color="#ff4949" active-color="rgba(0, 255, 255, 1)"> </el-switch>
</div>
</div>
<div>
<span>飞行作业高(ALT)</span>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.commander_flight_height += v)"
:valueNumber="queryParams.commander_flight_height"
@change="(b) => (queryParams.commander_flight_height = b)"
>
</inputNumber>
</div>
</div>
<div>
<span>返航高(ALT)</span>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.rth_altitude += v)"
:valueNumber="queryParams.rth_altitude"
@change="(b) => (queryParams.rth_altitude = b)"
>
</inputNumber>
</div>
</div>
<div>
<span>失联动作</span>
<div class="lost_action">
<el-select v-model="queryParams.rc_lost_action" placeholder="请选择">
<el-option v-for="item in lostAction" :key="item.id" :label="item.label" :value="item.id"> </el-option>
</el-select>
</div>
</div>
<div>
<span>目标点高度(AGL)</span>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.height += v)"
:valueNumber="queryParams.height"
@change="(b) => (queryParams.height = b)"
></inputNumber>
</div>
</div>
</div>
<div class="take_off" @click="onTakeOff">
<img src="./icons/fight.svg" alt="" srcset="" />
</div>
</div>
</template>
<script>
// 输入框
import inputNumber from '../inputNumber/index.vue';
import { lostAction } from '../../index.js';
import { useAirStore } from '@/store/modules/drone';
export default {
components: { inputNumber },
data() {
return {
value: true,
queryParams: useAirStore().queryParams,
// queryParams: {
// target_height: 100, //目标点高度
// rth_altitude: 100, //返航高度
// security_takeoff_height: 100, //安全起飞高度
// rc_lost_action: 2, //遥控器失控动作
// commander_flight_height: 100, //指点飞行高度
// },
value4: 1,
lostAction
};
},
methods: {
onTakeOff() {
// 一键起飞
this.$emit('onTakeOff', false);
}
}
};
</script>
<style lang="scss" scoped>
@import '../../style/comm.scss';
</style>

View File

@ -0,0 +1,68 @@
<template>
<div class="type_controls">
<div
v-for="(item, i) of controls"
:key="i"
:class="item.id == selet_id ? 'select' : ''"
v-show="item.id != selet_id"
@click="onControl(item)"
>
{{ item.label }}
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 2,
},
},
data() {
return {
controls: [
{ id: 1, label: "变焦", value: 2, type: "zoom" }, //value:倍速
{ id: 2, label: "红外", value: 2, type: "ir" }, //value:倍速
{ id: 3, label: "广角", value: 0, type: "wide" },
],
selet_id: 3,
};
},
watch: {
value(val) {},
},
methods: {
onControl(item) {
this.selet_id = item.id;
this.$emit("updateType", item);
},
},
};
</script>
<style lang="scss" scoped>
.type_controls {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
> div {
width: 48px;
height: 28px;
opacity: 1;
border-radius: 4px;
background: rgba(0, 255, 255, 0.2);
display: grid;
place-items: center;
border: 1px solid rgba(255, 255, 255, 0.5);
color: #fff;
margin-bottom: 6px;
cursor: pointer;
}
.select {
color: rgba(0, 255, 255);
border: 1px solid rgba(0, 255, 255, 0.5);
}
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div class="type_value">
{{ typeLabel }}<span v-if="typeLabel != '广角'"> {{ value }}X</span>
</div>
</template>
<script>
export default {
props: {
typeLabel: {
type: String,
default: "广角",
},
value: {
type: Number,
default: 2,
},
},
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="scss" scoped>
.type_value {
position: absolute;
left: 50%;
top: 50px;
transform: translateX(-50%);
color: rgba(27, 248, 195, 1);
border: 1px solid rgba(27, 248, 195, 1);
font-size: 14px;
border-radius: 4px;
padding: 6px 20px;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<div class="articulation" v-show="showArticulation">
<span v-for="(item, i) of articulationList" :key="i" :style="select_id == item.id ? 'color: aqua;' : ''"
@click="setArticulation(item.id)">{{ item.label }}</span>
</div>
</template>
<script>
export default {
props: {
videoObj: {
type: Object,
default: () => ({}),
},
type: {
type: String,
default: "AerodromeVideoRef", //AerodromeVideoRef:机场视频 DroneVideoRef无人机视频
},
},
data() {
return {
articulationList: [
{ id: 0, label: "自适应" },
{ id: 1, label: "流畅" },
{ id: 2, label: "标清" },
{ id: 3, label: "高清" },
{ id: 4, label: "超清" },
],
select_id: 0,
showArticulation: false,
};
},
methods: {
openCloseShow(flag) {
this.showArticulation = !this.showArticulation;
},
// 清晰度调整
setArticulation(definition) {
this.showArticulation = false;
// 多次点击
if (this.select_id == definition) return;
// console.log("调用");
this.select_id = definition;
this.videoObj.definition = definition;
this.$emit("updateDefinition", this.videoObj, this.type);
},
},
};
</script>
<style lang="scss" scoped>
.articulation {
position: absolute;
top: 24px;
width: 60px;
right: 20px;
background-color: #404a5dcc;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
padding: 6px;
z-index: 999;
>span {
margin: 5px 0;
cursor: pointer;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View File

@ -0,0 +1,285 @@
<template>
<div class="dialog_control" id="my_dialog_control" v-if="dialogShow">
<div class="header" id="my_dialog_header_control">
<span class="title">飞行前检查</span>
<div class="close" @click="close">
<div class="sector"></div>
<img src="./icons/close.png" alt="" />
</div>
</div>
<div class="right_detail">
<div class="left_info left_box">
<div>
<span>安全起飞高度</span>
<div>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.security_takeoff_height += v)"
:valueNumber="queryParams.security_takeoff_height"
@change="(b) => (queryParams.security_takeoff_height = b)"
></inputNumber>
</div>
</div>
</div>
<div>
<span>飞行作业高(ALT)</span>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.commander_flight_height += v)"
:valueNumber="queryParams.commander_flight_height"
@change="(b) => (queryParams.commander_flight_height = b)"
></inputNumber>
</div>
</div>
<div>
<span>返航高(ALT)</span>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.rth_altitude += v)"
:valueNumber="queryParams.rth_altitude"
@change="(b) => (queryParams.rth_altitude = b)"
>
</inputNumber>
</div>
</div>
<div>
<span>失联动作</span>
<div class="lost_action">
<el-select v-model="queryParams.rc_lost_action" placeholder="请选择">
<el-option
v-for="item in lostAction"
:key="item.id"
:label="item.label"
:value="item.id"
@change="(b) => (queryParams.rc_lost_action = b)"
>
</el-option>
</el-select>
</div>
</div>
<div>
<span>目标点高度(AGL)</span>
<div>
<inputNumber
:min="0"
:max="10"
uuit="m"
@updateInput="(v) => (queryParams.height += v)"
:valueNumber="queryParams.height"
@change="(b) => (queryParams.height = b)"
>
</inputNumber>
</div>
</div>
</div>
</div>
<div class="footer_bottom">
<div class="cl" @click="onClose">取消</div>
<div @click="onCarryOut" v-if="!flag">立即执行</div>
</div>
</div>
</template>
<script>
import { setMove } from '@/utils/moveDiv';
// 输入框
import inputNumber from '../inputNumber/index.vue';
import { lostAction } from '../../index.js';
import { hasDataFlight, noDataFlight } from '../../api/index';
import { useAirStore } from '@/store/modules/drone';
export default {
name: 'dialogControl',
components: {
inputNumber
},
props: {
Drone: {
type: Object,
default: () => ({})
}
},
data() {
return {
dialogShow: false,
lostAction,
height: 100,
queryParams: useAirStore().queryParams,
// queryParams: {
// target_height: 100, //目标点高度
// rth_altitude: 100, //返航高度
// commander_flight_height: 100, //指点飞行高度
// rc_lost_action: 2, //遥控器失控动作
// rth_mode: 1, //返航模式设置值
// commander_mode_lost_action: 1, //指点飞行失控动作
// commander_flight_mode: 1, //指点飞行模式设置值
// max_speed: 12, //一键起飞的飞行过程中能达到的最大速度
// security_takeoff_height: 100, //安全起飞高度
// target_latitude: 0, //目标点纬度
// target_longitude: 0, //目标点经度
// },
value: true,
networkState: {},
flag: false //是否可以执行
};
},
mounted() {
this.$recvChanel('websocketBus', (data) => {
if (data.businessType == 'osd3') {
this.networkState = { ...data.data };
}
});
},
methods: {
onClose() {
this.dialogShow = false;
},
// 执行飞行操作
onCarryOut() {
this.queryParams.target_latitude = this.networkState.latitude;
this.queryParams.target_longitude = this.networkState.longitude;
let obj = {
params: {
...this.queryParams,
target_height: this.networkState.height + this.queryParams.height
},
gateway: this.Drone.gateway,
method: 'takeoff_to_point'
};
console.log('objobj', this.height, obj);
// return;
// 关闭调试模式
// debug_mode_close
noDataFlight({
gateway: this.Drone.gateway,
method: 'debug_mode_close'
}).then((res) => {
if (res.code == 200) {
setTimeout(() => {
this.setHasDataFlight(obj);
}, 1000);
} else {
this.$message.error(res.msg);
}
});
},
openDialog(flag) {
this.flag = flag;
this.queryParams.target_height = 100;
this.dialogShow = true;
this.$nextTick(() => {
setMove('my_dialog_header_control', 'my_dialog_control');
});
},
close() {
this.dialogShow = false;
},
// 有参数接口调用
setHasDataFlight(data) {
hasDataFlight(data).then((res) => {
if (res.code == 200) {
this.close();
this.$message.success('操作成功');
this.$emit('onflight', true);
}
});
}
}
};
</script>
<style lang="scss">
@import '../../style/comm.scss';
.dialog_control::before {
content: '';
width: 100px;
height: 6px;
background: aqua;
position: absolute;
top: -6px;
left: -2px;
clip-path: polygon(0% 0%, 90% 0%, 100% 100%, 0% 100%);
}
.dialog_control {
position: fixed;
top: 50%;
left: 50%;
width: 480px;
transform: translate(-50%, -50%);
padding: 10px;
background: linear-gradient(180deg, rgba(0, 255, 255, 0.2) 0%, rgba(0, 255, 255, 0) 100%), rgba(0, 0, 0, 0.6);
border: 1.5px solid rgba(0, 255, 255, 1);
color: #fff;
.header {
height: 40px;
padding: 10px;
.title {
font-family: 'alimamashuheiti';
font-size: 18px;
font-weight: 700;
text-shadow: 0px 0px 9px rgba(20, 118, 255, 1);
}
.close {
cursor: pointer;
}
.sector {
position: absolute;
top: -30px;
right: -30px;
width: 0;
height: 0;
border: 30px solid transparent;
/* 边框宽度和颜色可以调整 */
border-bottom-color: rgba(0, 255, 255, 0.5);
/* 底边的颜色 */
border-radius: 50%;
/* 将边框变为圆形 */
transform: rotate(45deg);
/* 旋转45度可根据需要调整角度 */
}
img {
position: absolute;
top: 5px;
right: 5px;
width: 14px;
height: 14px;
}
}
.footer_bottom {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: flex-end;
> div {
cursor: pointer;
border-radius: 4px;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 1);
padding: 10px 16px;
margin-right: 16px;
color: rgba(0, 255, 255, 1);
}
.cl {
border: 1px solid rgba(0, 255, 255, 0.5);
}
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div class="input_number">
<el-input class="custom-number-input" type="number" v-model.trim.number="value" @change="change">
<div slot="suffix" class="button_right">
<div>
<span @click="onAdd(1)">
</span>
<span @click="onAdd(-1)">
</span>
</div>
<span>{{ uuit }}</span>
</div>
</el-input>
</div>
</template>
<script>
export default {
props: {
uuit: {
type: String,
default: "m",
},
valueNumber: {
type: Number,
default: 0,
},
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 10,
},
},
data() {
return {
value: 0,
};
},
watch: {
valueNumber: {
handler(newValue) {
this.value = newValue;
},
immediate: true,
},
},
methods: {
onAdd(valueNumber) {
this.value += valueNumber;
if (this.value < this.min) {
this.value = this.min;
}
if (this.value > this.max) {
this.value = this.max;
}
this.$emit("updateInput", valueNumber);
},
change(value) {
this.$emit("change", Number(value));
}
},
};
</script>
<style lang="scss">
.input_number {
.custom-number-input input::-webkit-inner-spin-button,
.custom-number-input input::-webkit-outer-spin-button {
appearance: none;
margin: 0;
}
.el-input__inner {
height: 30px;
background: #f0f8ff00;
color: white;
border: 1px solid rgba(0, 255, 255, 0.5);
width: 90px;
}
.el-input {
width: 90px;
}
.button_right {
display: flex;
color: #fff;
font-size: 14px;
align-items: center;
height: 30px;
padding-right: 3px;
>div {
font-size: 9px;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 6px;
>span {
cursor: pointer;
}
&>span:hover {
color: #c7c7c7;
}
}
}
}
</style>

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" fill="rgba(255, 255, 255, 1)" d="M0 12C0 5.37254 5.37254 0 12 0C18.6275 0 24 5.37254 24 12C24 18.6275 18.6275 24 12 24C5.37254 24 0 18.6275 0 12ZM1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12ZM5.54546 12C5.54546 15.5647 8.43525 18.4545 12 18.4545C15.5647 18.4545 18.4545 15.5647 18.4545 12C18.4545 8.43525 15.5647 5.54546 12 5.54546C8.43525 5.54546 5.54546 8.43525 5.54546 12ZM6.54546 12C6.54546 15.0124 8.98755 17.4545 12 17.4545C15.0124 17.4545 17.4545 15.0124 17.4545 12C17.4545 8.98755 15.0124 6.54546 12 6.54546C8.98755 6.54546 6.54546 8.98755 6.54546 12Z">
</path>
<circle cx="11.999658584594727" cy="12.000024795532227" r="9.142236709594727" fill="#FFFFFF" >
</circle>
</svg>

After

Width:  |  Height:  |  Size: 917 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.005859375" height="13.300048828125" viewBox="0 0 16.005859375 13.300048828125" fill="none">
<path d="M12.0061 10.64L1.50609 10.64C-0.174573 10.64 0.00609375 9.3002 0.00609375 7.65L0.00609375 2.99C0.00609375 1.34013 -0.174573 0 1.50609 0L12.0061 0C13.6868 0 13.3361 1.34013 13.3361 2.99L13.3361 7.65C13.3361 9.3002 13.6868 10.64 12.0061 10.64ZM3.05609 2C2.11076 2 2.00609 2.7433 2.00609 3.66L2.00609 7.65C2.00609 8.5667 2.11076 8.65 3.05609 8.65L10.2961 8.65C11.2418 8.65 11.0061 8.5667 11.0061 7.65L11.0061 3.66C11.0061 2.7433 11.2418 2 10.2961 2L3.05609 2ZM16.0061 8.65L13.3361 6.99L13.3361 3.66L16.0061 2L16.0061 8.65ZM12.6761 13.3L0.676094 13.3C0.665177 13.3 0.646983 13.3 0.636094 13.3C0.625192 13.3 0.616954 13.3 0.606094 13.3C0.595246 13.3 0.586885 13.29 0.576094 13.29C0.565302 13.29 0.556802 13.29 0.546094 13.29C0.5354 13.29 0.516677 13.28 0.506094 13.28C0.49551 13.28 0.486537 13.27 0.476094 13.27C0.46565 13.27 0.456358 13.26 0.446094 13.26C0.435817 13.26 0.426177 13.25 0.416094 13.25C0.40601 13.25 0.386094 13.24 0.386094 13.24L0.356094 13.22L0.326094 13.21L0.306094 13.19L0.276094 13.17L0.246094 13.15L0.226094 13.13L0.206094 13.11L0.176094 13.08L0.156094 13.06L0.136094 13.03L0.116094 13L0.0960938 12.98L0.0860938 12.95L0.0660938 12.92C0.0660938 12.92 0.0560937 12.9001 0.0560937 12.89C0.0560937 12.8799 0.0460938 12.8702 0.0460938 12.86C0.0460938 12.8498 0.0360937 12.8404 0.0360937 12.83C0.0360937 12.8196 0.0260937 12.8106 0.0260937 12.8C0.0260937 12.7894 0.0160937 12.7707 0.0160937 12.76C0.0160937 12.7493 0.0160937 12.7408 0.0160937 12.73C0.0160937 12.7192 0.00609375 12.7108 0.00609375 12.7C0.00609375 12.6892 0.00609375 12.6809 0.00609375 12.67C0.00609375 12.6591 0.00609375 12.6509 0.00609375 12.64C0.00609375 12.6291 0.00609375 12.6109 0.00609375 12.6C0.00609375 12.5891 0.00609375 12.5808 0.00609375 12.57C0.00609375 12.5592 0.0160937 12.5508 0.0160937 12.54C0.0160937 12.5292 0.0160937 12.5207 0.0160937 12.51C0.0160937 12.4993 0.0260937 12.4806 0.0260937 12.47C0.0260937 12.4594 0.0360937 12.4504 0.0360937 12.44C0.0360937 12.4296 0.0460938 12.4202 0.0460938 12.41C0.0460938 12.3998 0.0560937 12.3901 0.0560937 12.38C0.0560937 12.3699 0.0660938 12.35 0.0660938 12.35L0.0860938 12.32L0.0960938 12.29L0.116094 12.27L0.136094 12.24L0.156094 12.21L0.176094 12.19L0.206094 12.16L0.226094 12.14L0.246094 12.12L0.276094 12.1L0.306094 12.08L0.326094 12.06L0.356094 12.05L0.386094 12.03C0.386094 12.03 0.40601 12.02 0.416094 12.02C0.426177 12.02 0.435817 12.01 0.446094 12.01C0.456358 12.01 0.46565 12 0.476094 12C0.486537 12 0.49551 11.99 0.506094 11.99C0.516677 11.99 0.5354 11.98 0.546094 11.98C0.556802 11.98 0.565302 11.98 0.576094 11.98C0.586885 11.98 0.595246 11.97 0.606094 11.97C0.616954 11.97 0.625192 11.97 0.636094 11.97C0.646983 11.97 0.665177 11.97 0.676094 11.97L12.6761 11.97C12.687 11.97 12.6952 11.97 12.7061 11.97C12.717 11.97 12.7252 11.97 12.7361 11.97C12.7469 11.97 12.7553 11.98 12.7661 11.98C12.7769 11.98 12.7954 11.98 12.8061 11.98C12.8168 11.98 12.8255 11.99 12.8361 11.99C12.8467 11.99 12.8557 12 12.8661 12C12.8765 12 12.8858 12.01 12.8961 12.01C12.9064 12.01 12.916 12.02 12.9261 12.02C12.9362 12.02 12.9561 12.03 12.9561 12.03L12.9861 12.05L13.0161 12.06L13.0461 12.08L13.0661 12.1L13.0961 12.12L13.1161 12.14L13.1461 12.16L13.1661 12.19L13.1861 12.21L13.2061 12.24L13.2261 12.27L13.2461 12.29L13.2561 12.32L13.2761 12.35C13.2761 12.35 13.2861 12.3699 13.2861 12.38C13.2861 12.3901 13.2961 12.3998 13.2961 12.41C13.2961 12.4202 13.3061 12.4296 13.3061 12.44C13.3061 12.4504 13.3161 12.4594 13.3161 12.47C13.3161 12.4806 13.3261 12.4993 13.3261 12.51C13.3261 12.5207 13.3361 12.5292 13.3361 12.54C13.3361 12.5508 13.3361 12.5592 13.3361 12.57C13.3361 12.5808 13.3361 12.5891 13.3361 12.6C13.3361 12.6109 13.3361 12.6291 13.3361 12.64C13.3361 12.6509 13.3361 12.6591 13.3361 12.67C13.3361 12.6809 13.3361 12.6892 13.3361 12.7C13.3361 12.7108 13.3361 12.7192 13.3361 12.73C13.3361 12.7408 13.3261 12.7493 13.3261 12.76C13.3261 12.7707 13.3161 12.7894 13.3161 12.8C13.3161 12.8106 13.3061 12.8196 13.3061 12.83C13.3061 12.8404 13.2961 12.8498 13.2961 12.86C13.2961 12.8702 13.2861 12.8799 13.2861 12.89C13.2861 12.9001 13.2761 12.92 13.2761 12.92L13.2561 12.95L13.2461 12.98L13.2261 13L13.2061 13.03L13.1861 13.06L13.1661 13.08L13.1461 13.11L13.1161 13.13L13.0961 13.15L13.0661 13.17L13.0461 13.19L13.0161 13.21L12.9861 13.22L12.9561 13.24C12.9561 13.24 12.9362 13.25 12.9261 13.25C12.916 13.25 12.9064 13.26 12.8961 13.26C12.8858 13.26 12.8765 13.27 12.8661 13.27C12.8557 13.27 12.8467 13.28 12.8361 13.28C12.8255 13.28 12.8168 13.29 12.8061 13.29C12.7954 13.29 12.7769 13.29 12.7661 13.29C12.7553 13.29 12.7469 13.3 12.7361 13.3C12.7252 13.3 12.717 13.3 12.7061 13.3C12.6952 13.3 12.687 13.3 12.6761 13.3Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.005859375" height="13.300048828125" viewBox="0 0 16.005859375 13.300048828125" fill="none">
<path d="M12.0061 10.64L1.50609 10.64C-0.174573 10.64 0.00609375 9.3002 0.00609375 7.65L0.00609375 2.99C0.00609375 1.34013 -0.174573 0 1.50609 0L12.0061 0C13.6868 0 13.3361 1.34013 13.3361 2.99L13.3361 7.65C13.3361 9.3002 13.6868 10.64 12.0061 10.64ZM3.05609 2C2.11076 2 2.00609 2.7433 2.00609 3.66L2.00609 7.65C2.00609 8.5667 2.11076 8.65 3.05609 8.65L10.2961 8.65C11.2418 8.65 11.0061 8.5667 11.0061 7.65L11.0061 3.66C11.0061 2.7433 11.2418 2 10.2961 2L3.05609 2ZM16.0061 8.65L13.3361 6.99L13.3361 3.66L16.0061 2L16.0061 8.65ZM12.6761 13.3L0.676094 13.3C0.665177 13.3 0.646983 13.3 0.636094 13.3C0.625192 13.3 0.616954 13.3 0.606094 13.3C0.595246 13.3 0.586885 13.29 0.576094 13.29C0.565302 13.29 0.556802 13.29 0.546094 13.29C0.5354 13.29 0.516677 13.28 0.506094 13.28C0.49551 13.28 0.486537 13.27 0.476094 13.27C0.46565 13.27 0.456358 13.26 0.446094 13.26C0.435817 13.26 0.426177 13.25 0.416094 13.25C0.40601 13.25 0.386094 13.24 0.386094 13.24L0.356094 13.22L0.326094 13.21L0.306094 13.19L0.276094 13.17L0.246094 13.15L0.226094 13.13L0.206094 13.11L0.176094 13.08L0.156094 13.06L0.136094 13.03L0.116094 13L0.0960938 12.98L0.0860938 12.95L0.0660938 12.92C0.0660938 12.92 0.0560937 12.9001 0.0560937 12.89C0.0560937 12.8799 0.0460938 12.8702 0.0460938 12.86C0.0460938 12.8498 0.0360937 12.8404 0.0360937 12.83C0.0360937 12.8196 0.0260937 12.8106 0.0260937 12.8C0.0260937 12.7894 0.0160937 12.7707 0.0160937 12.76C0.0160937 12.7493 0.0160937 12.7408 0.0160937 12.73C0.0160937 12.7192 0.00609375 12.7108 0.00609375 12.7C0.00609375 12.6892 0.00609375 12.6809 0.00609375 12.67C0.00609375 12.6591 0.00609375 12.6509 0.00609375 12.64C0.00609375 12.6291 0.00609375 12.6109 0.00609375 12.6C0.00609375 12.5891 0.00609375 12.5808 0.00609375 12.57C0.00609375 12.5592 0.0160937 12.5508 0.0160937 12.54C0.0160937 12.5292 0.0160937 12.5207 0.0160937 12.51C0.0160937 12.4993 0.0260937 12.4806 0.0260937 12.47C0.0260937 12.4594 0.0360937 12.4504 0.0360937 12.44C0.0360937 12.4296 0.0460938 12.4202 0.0460938 12.41C0.0460938 12.3998 0.0560937 12.3901 0.0560937 12.38C0.0560937 12.3699 0.0660938 12.35 0.0660938 12.35L0.0860938 12.32L0.0960938 12.29L0.116094 12.27L0.136094 12.24L0.156094 12.21L0.176094 12.19L0.206094 12.16L0.226094 12.14L0.246094 12.12L0.276094 12.1L0.306094 12.08L0.326094 12.06L0.356094 12.05L0.386094 12.03C0.386094 12.03 0.40601 12.02 0.416094 12.02C0.426177 12.02 0.435817 12.01 0.446094 12.01C0.456358 12.01 0.46565 12 0.476094 12C0.486537 12 0.49551 11.99 0.506094 11.99C0.516677 11.99 0.5354 11.98 0.546094 11.98C0.556802 11.98 0.565302 11.98 0.576094 11.98C0.586885 11.98 0.595246 11.97 0.606094 11.97C0.616954 11.97 0.625192 11.97 0.636094 11.97C0.646983 11.97 0.665177 11.97 0.676094 11.97L12.6761 11.97C12.687 11.97 12.6952 11.97 12.7061 11.97C12.717 11.97 12.7252 11.97 12.7361 11.97C12.7469 11.97 12.7553 11.98 12.7661 11.98C12.7769 11.98 12.7954 11.98 12.8061 11.98C12.8168 11.98 12.8255 11.99 12.8361 11.99C12.8467 11.99 12.8557 12 12.8661 12C12.8765 12 12.8858 12.01 12.8961 12.01C12.9064 12.01 12.916 12.02 12.9261 12.02C12.9362 12.02 12.9561 12.03 12.9561 12.03L12.9861 12.05L13.0161 12.06L13.0461 12.08L13.0661 12.1L13.0961 12.12L13.1161 12.14L13.1461 12.16L13.1661 12.19L13.1861 12.21L13.2061 12.24L13.2261 12.27L13.2461 12.29L13.2561 12.32L13.2761 12.35C13.2761 12.35 13.2861 12.3699 13.2861 12.38C13.2861 12.3901 13.2961 12.3998 13.2961 12.41C13.2961 12.4202 13.3061 12.4296 13.3061 12.44C13.3061 12.4504 13.3161 12.4594 13.3161 12.47C13.3161 12.4806 13.3261 12.4993 13.3261 12.51C13.3261 12.5207 13.3361 12.5292 13.3361 12.54C13.3361 12.5508 13.3361 12.5592 13.3361 12.57C13.3361 12.5808 13.3361 12.5891 13.3361 12.6C13.3361 12.6109 13.3361 12.6291 13.3361 12.64C13.3361 12.6509 13.3361 12.6591 13.3361 12.67C13.3361 12.6809 13.3361 12.6892 13.3361 12.7C13.3361 12.7108 13.3361 12.7192 13.3361 12.73C13.3361 12.7408 13.3261 12.7493 13.3261 12.76C13.3261 12.7707 13.3161 12.7894 13.3161 12.8C13.3161 12.8106 13.3061 12.8196 13.3061 12.83C13.3061 12.8404 13.2961 12.8498 13.2961 12.86C13.2961 12.8702 13.2861 12.8799 13.2861 12.89C13.2861 12.9001 13.2761 12.92 13.2761 12.92L13.2561 12.95L13.2461 12.98L13.2261 13L13.2061 13.03L13.1861 13.06L13.1661 13.08L13.1461 13.11L13.1161 13.13L13.0961 13.15L13.0661 13.17L13.0461 13.19L13.0161 13.21L12.9861 13.22L12.9561 13.24C12.9561 13.24 12.9362 13.25 12.9261 13.25C12.916 13.25 12.9064 13.26 12.8961 13.26C12.8858 13.26 12.8765 13.27 12.8661 13.27C12.8557 13.27 12.8467 13.28 12.8361 13.28C12.8255 13.28 12.8168 13.29 12.8061 13.29C12.7954 13.29 12.7769 13.29 12.7661 13.29C12.7553 13.29 12.7469 13.3 12.7361 13.3C12.7252 13.3 12.717 13.3 12.7061 13.3C12.6952 13.3 12.687 13.3 12.6761 13.3Z" fill="#00FFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="15.800048828125" viewBox="0 0 20 15.800048828125" fill="none">
<path d="M16.9 15.8L3.1 15.8C1.4 15.8 0 14.4 0 12.7L0 3.1C0 1.40001 1.4 0 3.1 0L16.9 0C18.6 0 20 1.40001 20 3.1L20 12.7C20 14.4 18.6 15.8 16.9 15.8ZM3.5 2C2.7 2 2 2.70001 2 3.5L2.1 12.4C2.1 13.2 2.8 13.9 3.6 13.9L16.5 13.9C17.3 13.9 18 13.2 18 12.4L17.9 3.5C17.9 2.70001 17.2 2 16.4 2L3.5 2ZM8 12.3C5.6 12.3 3.6 10.3 3.6 7.9C3.6 5.5 5.6 3.5 8 3.5C10.4 3.5 12.4 5.5 12.4 7.9C12.5 10.3 10.5 12.3 8 12.3ZM8 5C6.39999 5 5.1 6.29999 5.1 7.9C5.1 9.50001 6.39999 10.8 8 10.8C9.59998 10.8 10.9 9.50001 10.9 7.9C10.9 6.29999 9.59998 5 8 5ZM16.3 4.5L14.7 4.5C14.4 4.5 14.1 4.2 14.1 3.9L14.1 3.7C14.1 3.4 14.4 3.1 14.7 3.1L16.3 3.1C16.6 3.1 16.9 3.4 16.9 3.7L16.9 3.9C16.9 4.2 16.6 4.5 16.3 4.5Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 885 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="15.800048828125" viewBox="0 0 20 15.800048828125" fill="none">
<path d="M16.9 15.8L3.1 15.8C1.4 15.8 0 14.4 0 12.7L0 3.1C0 1.40001 1.4 0 3.1 0L16.9 0C18.6 0 20 1.40001 20 3.1L20 12.7C20 14.4 18.6 15.8 16.9 15.8ZM3.5 2C2.7 2 2 2.70001 2 3.5L2.1 12.4C2.1 13.2 2.8 13.9 3.6 13.9L16.5 13.9C17.3 13.9 18 13.2 18 12.4L17.9 3.5C17.9 2.70001 17.2 2 16.4 2L3.5 2ZM8 12.3C5.6 12.3 3.6 10.3 3.6 7.9C3.6 5.5 5.6 3.5 8 3.5C10.4 3.5 12.4 5.5 12.4 7.9C12.5 10.3 10.5 12.3 8 12.3ZM8 5C6.39999 5 5.1 6.29999 5.1 7.9C5.1 9.50001 6.39999 10.8 8 10.8C9.59998 10.8 10.9 9.50001 10.9 7.9C10.9 6.29999 9.59998 5 8 5ZM16.3 4.5L14.7 4.5C14.4 4.5 14.1 4.2 14.1 3.9L14.1 3.7C14.1 3.4 14.4 3.1 14.7 3.1L16.3 3.1C16.6 3.1 16.9 3.4 16.9 3.7L16.9 3.9C16.9 4.2 16.6 4.5 16.3 4.5Z" fill="#00FFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 885 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="14.5" viewBox="0 0 20 14.5" fill="none">
<path d="M0 1.81249L0 12.6875C0 13.6889 0.745453 14.5 1.66637 14.5L18.3336 14.5C19.2536 14.5 20 13.6889 20 12.6875L20 1.81249C20 0.81108 19.2546 0 18.3336 0L1.66637 0C0.746363 0 0 0.81108 0 1.81249ZM1.73455 8.06563C1.78424 8.41182 1.88484 8.66617 2.03636 8.82868C2.18789 8.99181 2.39274 9.07337 2.65091 9.07337C2.92364 9.07337 3.1297 9.00088 3.26909 8.85587C3.40728 8.71086 3.47636 8.54171 3.47636 8.34838C3.47636 8.22452 3.44727 8.11879 3.38909 8.0312C3.33092 7.9442 3.22909 7.86866 3.08363 7.80463C2.98426 7.76113 2.75789 7.68441 2.40454 7.57444C1.95 7.43366 1.63121 7.26026 1.44819 7.05425C1.19 6.76545 1.06091 6.41322 1.06091 5.99757C1.06091 5.72992 1.12151 5.47948 1.24273 5.24628C1.36394 5.01308 1.53879 4.83575 1.76728 4.71432C1.99515 4.59227 2.2709 4.53125 2.59453 4.53125C3.12181 4.53125 3.51848 4.67594 3.78454 4.96533C4.05122 5.25414 4.1909 5.6399 4.20362 6.12263L3.35636 6.16974C3.32001 5.89967 3.24213 5.70545 3.12273 5.58704C3.00332 5.46862 2.82424 5.4094 2.58546 5.4094C2.33879 5.4094 2.14575 5.47285 2.00636 5.59972C1.91666 5.68066 1.87181 5.78941 1.87181 5.92597C1.87181 6.04982 1.91393 6.15617 1.99818 6.24497C2.10484 6.35674 2.36455 6.47365 2.77726 6.59569C3.1894 6.71772 3.49455 6.8437 3.69273 6.9736C3.89031 7.1041 4.04485 7.28233 4.15637 7.50828C4.26848 7.73424 4.32454 8.01306 4.32454 8.34474C4.32454 8.64623 4.25787 8.92838 4.12453 9.19119C3.9906 9.45399 3.80122 9.64915 3.55637 9.77663C3.31274 9.90471 3.00818 9.96875 2.64273 9.96875C2.11183 9.96875 1.70394 9.81529 1.41908 9.50837C1.13424 9.20147 0.964243 8.75409 0.90909 8.16622L1.73455 8.06563ZM4.44 4.62097L5.70908 4.62097L6.4709 8.2052L7.22455 4.62097L8.49727 4.62097L8.49727 9.32441L9.9609 4.62097L10.8582 4.62097L12.4055 9.45671L12.4055 4.62097L14.1909 4.62097C14.6394 4.62097 14.9655 4.6681 15.1691 4.76234C15.3727 4.85658 15.5358 5.02455 15.6582 5.26622C15.6952 5.33993 15.7267 5.41697 15.7527 5.49732L15.7527 4.62188L19.0909 4.62188L19.0909 5.51L17.8473 5.51L17.8473 9.87632L17 9.87632L17 5.51L15.7582 5.51C15.8139 5.68882 15.8418 5.88336 15.8418 6.09362C15.8418 6.48815 15.7494 6.81379 15.5646 7.07056C15.3791 7.32734 15.1021 7.48925 14.7336 7.55631C14.9167 7.69044 15.0682 7.83755 15.1882 7.99766C15.307 8.15777 15.4676 8.44202 15.67 8.85043L16.1827 9.8754L15.1691 9.8754L14.5555 8.73172C14.3379 8.32329 14.1891 8.06592 14.1091 7.95959C14.0285 7.85327 13.9437 7.78045 13.8546 7.74118C13.7643 7.70191 13.6218 7.68228 13.4273 7.68228L13.2546 7.68228L13.2546 9.8754L11.6182 9.8754L11.2518 8.68187L9.57273 8.68187L9.22637 9.8754L7.70909 9.8754L7.70909 5.73929L6.87454 9.8754L6.05817 9.8754L5.22727 5.73929L5.22727 9.8754L4.44 9.8754L4.44 4.62188L4.44 4.62097ZM13.8818 6.84219L13.2546 6.84219L13.2546 5.51L13.9154 5.51C14.2597 5.51 14.4661 5.51603 14.5345 5.52812C14.6721 5.55651 14.7788 5.6251 14.8545 5.73385C14.9297 5.8426 14.9673 5.98548 14.9673 6.1625C14.9673 6.31957 14.9385 6.45098 14.8809 6.55672C14.8239 6.66184 14.7449 6.73586 14.6436 6.77874C14.5424 6.82165 14.2885 6.8431 13.8818 6.8431L13.8818 6.84219ZM10.4 5.84532L10.9782 7.79556L9.83272 7.79556L10.4 5.84621L10.4 5.84532Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="14.5" viewBox="0 0 20 14.5" fill="none">
<path d="M0 1.81249L0 12.6875C0 13.6889 0.745453 14.5 1.66637 14.5L18.3336 14.5C19.2536 14.5 20 13.6889 20 12.6875L20 1.81249C20 0.81108 19.2546 0 18.3336 0L1.66637 0C0.746363 0 0 0.81108 0 1.81249ZM1.73455 8.06563C1.78424 8.41182 1.88484 8.66617 2.03636 8.82868C2.18789 8.99181 2.39274 9.07337 2.65091 9.07337C2.92364 9.07337 3.1297 9.00088 3.26909 8.85587C3.40728 8.71086 3.47636 8.54171 3.47636 8.34838C3.47636 8.22452 3.44727 8.11879 3.38909 8.0312C3.33092 7.9442 3.22909 7.86866 3.08363 7.80463C2.98426 7.76113 2.75789 7.68441 2.40454 7.57444C1.95 7.43366 1.63121 7.26026 1.44819 7.05425C1.19 6.76545 1.06091 6.41322 1.06091 5.99757C1.06091 5.72992 1.12151 5.47948 1.24273 5.24628C1.36394 5.01308 1.53879 4.83575 1.76728 4.71432C1.99515 4.59227 2.2709 4.53125 2.59453 4.53125C3.12181 4.53125 3.51848 4.67594 3.78454 4.96533C4.05122 5.25414 4.1909 5.6399 4.20362 6.12263L3.35636 6.16974C3.32001 5.89967 3.24213 5.70545 3.12273 5.58704C3.00332 5.46862 2.82424 5.4094 2.58546 5.4094C2.33879 5.4094 2.14575 5.47285 2.00636 5.59972C1.91666 5.68066 1.87181 5.78941 1.87181 5.92597C1.87181 6.04982 1.91393 6.15617 1.99818 6.24497C2.10484 6.35674 2.36455 6.47365 2.77726 6.59569C3.1894 6.71772 3.49455 6.8437 3.69273 6.9736C3.89031 7.1041 4.04485 7.28233 4.15637 7.50828C4.26848 7.73424 4.32454 8.01306 4.32454 8.34474C4.32454 8.64623 4.25787 8.92838 4.12453 9.19119C3.9906 9.45399 3.80122 9.64915 3.55637 9.77663C3.31274 9.90471 3.00818 9.96875 2.64273 9.96875C2.11183 9.96875 1.70394 9.81529 1.41908 9.50837C1.13424 9.20147 0.964243 8.75409 0.90909 8.16622L1.73455 8.06563ZM4.44 4.62097L5.70908 4.62097L6.4709 8.2052L7.22455 4.62097L8.49727 4.62097L8.49727 9.32441L9.9609 4.62097L10.8582 4.62097L12.4055 9.45671L12.4055 4.62097L14.1909 4.62097C14.6394 4.62097 14.9655 4.6681 15.1691 4.76234C15.3727 4.85658 15.5358 5.02455 15.6582 5.26622C15.6952 5.33993 15.7267 5.41697 15.7527 5.49732L15.7527 4.62188L19.0909 4.62188L19.0909 5.51L17.8473 5.51L17.8473 9.87632L17 9.87632L17 5.51L15.7582 5.51C15.8139 5.68882 15.8418 5.88336 15.8418 6.09362C15.8418 6.48815 15.7494 6.81379 15.5646 7.07056C15.3791 7.32734 15.1021 7.48925 14.7336 7.55631C14.9167 7.69044 15.0682 7.83755 15.1882 7.99766C15.307 8.15777 15.4676 8.44202 15.67 8.85043L16.1827 9.8754L15.1691 9.8754L14.5555 8.73172C14.3379 8.32329 14.1891 8.06592 14.1091 7.95959C14.0285 7.85327 13.9437 7.78045 13.8546 7.74118C13.7643 7.70191 13.6218 7.68228 13.4273 7.68228L13.2546 7.68228L13.2546 9.8754L11.6182 9.8754L11.2518 8.68187L9.57273 8.68187L9.22637 9.8754L7.70909 9.8754L7.70909 5.73929L6.87454 9.8754L6.05817 9.8754L5.22727 5.73929L5.22727 9.8754L4.44 9.8754L4.44 4.62188L4.44 4.62097ZM13.8818 6.84219L13.2546 6.84219L13.2546 5.51L13.9154 5.51C14.2597 5.51 14.4661 5.51603 14.5345 5.52812C14.6721 5.55651 14.7788 5.6251 14.8545 5.73385C14.9297 5.8426 14.9673 5.98548 14.9673 6.1625C14.9673 6.31957 14.9385 6.45098 14.8809 6.55672C14.8239 6.66184 14.7449 6.73586 14.6436 6.77874C14.5424 6.82165 14.2885 6.8431 13.8818 6.8431L13.8818 6.84219ZM10.4 5.84532L10.9782 7.79556L9.83272 7.79556L10.4 5.84621L10.4 5.84532Z" fill="#00FFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM2.20272 12.648L2.57472 13.56C2.57472 13.56 2.99692 13.848 3.30672 13.848L4.05072 13.596C4.21656 13.422 4.30272 13.216 4.30272 12.984C4.30272 12.8353 4.26455 12.7051 4.19472 12.6C4.12492 12.4956 3.99727 12.4008 3.82272 12.324C3.70348 12.2718 3.43074 12.18 3.00672 12.048C2.46127 11.8791 2.08636 11.6712 1.86672 11.424C1.55689 11.0774 1.39872 10.6628 1.39872 10.164C1.39872 9.84282 1.46927 9.54384 1.61472 9.264C1.76017 8.98416 1.97653 8.76172 2.25072 8.616C2.52416 8.46954 2.84636 8.4 3.23472 8.4C3.86746 8.4 4.34345 8.58072 4.66272 8.928C4.98274 9.27456 5.15146 9.72872 5.16672 10.308L4.15872 10.368C4.1151 10.0439 4.014 9.81409 3.87072 9.672C3.72744 9.52991 3.52127 9.456 3.23472 9.456C2.93872 9.456 2.70598 9.53176 2.53872 9.684C2.43108 9.78114 2.37072 9.91614 2.37072 10.08C2.37072 10.2286 2.42562 10.3454 2.52672 10.452C2.65471 10.5861 2.96747 10.7375 3.46272 10.884C3.95729 11.0304 4.31692 11.1721 4.55472 11.328C4.79182 11.4846 4.9849 11.7049 5.11872 11.976C5.25326 12.2472 5.31072 12.574 5.31072 12.972C5.31072 13.3338 5.23073 13.6766 5.07072 13.992C4.91 14.3074 4.69255 14.547 4.39872 14.7C4.10638 14.8537 3.73326 14.928 3.29472 14.928C2.65764 14.928 2.17255 14.7443 1.83072 14.376C1.48891 14.0077 1.2849 13.4735 1.21872 12.768L2.20272 12.648ZM5.45472 8.508L6.97872 8.508L7.89072 12.816L8.79072 8.508L10.3267 8.508L10.3267 14.148L12.0787 8.508L13.1587 8.508L15.0187 14.316L15.0187 8.508L17.1547 8.508C17.6929 8.508 18.0864 8.56291 18.3307 8.676C18.5752 8.78909 18.7718 8.998 18.9187 9.288C18.9631 9.37644 18.9954 9.46758 19.0267 9.564L19.0267 8.508L23.0347 8.508L23.0347 9.576L21.5467 9.576L21.5467 14.82L20.5267 14.82L20.5267 9.576L19.0387 9.576C19.1057 9.79058 19.1347 10.0197 19.1347 10.272C19.1347 10.7454 19.0206 11.1399 18.7987 11.448C18.5761 11.7561 18.2449 11.9555 17.8027 12.036C18.0223 12.1969 18.2107 12.3719 18.3547 12.564C18.4973 12.7561 18.6878 13.0939 18.9307 13.584L19.5427 14.82L18.3307 14.82L17.5987 13.44C17.3376 12.9499 17.1547 12.6436 17.0587 12.516C16.962 12.3884 16.8536 12.2992 16.7467 12.252C16.6384 12.2048 16.4762 12.18 16.2427 12.18L16.0267 12.18L16.0267 14.82L14.0707 14.82L13.6267 13.38L11.6107 13.38L11.2027 14.82L9.37872 14.82L9.37872 9.852L8.37072 14.82L7.39872 14.82L6.40272 9.852L6.40272 14.82L5.45472 14.82L5.45472 8.508ZM18.0944 10.3561C18.0944 9.54013 17.5754 9.54012 16.0184 9.54012L16.0184 11.1721C17.6088 11.1721 18.0944 11.1721 18.0944 10.3561ZM13.3058 12.3138L12.5978 9.9978L11.9258 12.3138L13.3058 12.3138Z" fill-rule="evenodd" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M0 12C0 18.6275 5.37254 24 12 24C18.6275 24 24 18.6275 24 12C24 5.37255 18.6275 7.62939e-06 12 7.62939e-06C5.37254 7.62939e-06 0 5.37255 0 12ZM17.4545 12C17.4545 15.0124 15.0124 17.4545 12 17.4545C8.98755 17.4545 6.54546 15.0124 6.54546 12C6.54546 8.98756 8.98755 6.54547 12 6.54547C15.0124 6.54547 17.4545 8.98756 17.4545 12Z" fill="#E55D5D" >
</path>
<circle cx="12" cy="24" transform="rotate(-90 6 18)" r="6" fill="#FFFFFF" >
</circle>
</svg>

After

Width:  |  Height:  |  Size: 598 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" fill="rgba(255, 255, 255, 1)" d="M12 24C5.37254 24 0 18.6275 0 12C0 5.37255 5.37254 7.62939e-06 12 7.62939e-06C18.6275 7.62939e-06 24 5.37255 24 12C24 18.6275 18.6275 24 12 24ZM12 23C5.92487 23 1 18.0751 1 12C1 5.92488 5.92487 1.00001 12 1.00001C18.0751 1.00001 23 5.92488 23 12C23 18.0751 18.0751 23 12 23ZM12 18.4545C15.5647 18.4545 18.4545 15.5647 18.4545 12C18.4545 8.43526 15.5647 5.54547 12 5.54547C8.43525 5.54547 5.54546 8.43526 5.54546 12C5.54546 15.5647 8.43525 18.4545 12 18.4545ZM12 17.4545C15.0124 17.4545 17.4545 15.0124 17.4545 12C17.4545 8.98756 15.0124 6.54547 12 6.54547C8.98755 6.54547 6.54546 8.98756 6.54546 12C6.54546 15.0124 8.98755 17.4545 12 17.4545Z">
</path>
<circle cx="11.999658584594727" cy="30.284326553344727" transform="rotate(-90 2.857421875 21.14208984375)" r="9.142236709594727" fill="#E55D5D" >
</circle>
</svg>

After

Width:  |  Height:  |  Size: 1018 B

View File

@ -0,0 +1,305 @@
<template>
<div class="mode_control">
<div class="tool_control" v-for="(item, i) of toolControl" @click="onTool(item)" :key="i" v-show="i == 0 || !showView">
<img :src="item.icon" alt="" srcset="" />
</div>
<div v-if="showView" class="progress_box">
<el-progress type="circle" :width="26" :stroke-width="4" :percentage="percentage"></el-progress>
</div>
<div class="box_control" v-show="showControl">
<div @click="onControl(item)" v-for="(item, i) of modeControls" :key="i">
<img v-if="item.id == select_mode" :src="item.iconB" alt="" srcset="" />
<img v-else :src="item.icon" alt="" srcset="" />
<span :style="item.id == select_mode ? 'color: rgba(0, 255, 255, 1);' : ''">{{ item.label }}</span>
</div>
</div>
</div>
</template>
<script>
import { hasDataFlight } from '../../api/index';
import icon3 from './icons/icon3.svg';
import icon1 from './icons/icon1.svg';
import icon3_en from './icons/icon3_en.svg';
import icon2 from './icons/icon2.svg';
import icon2_en from './icons/icon2_en.svg';
import start_record from './icons/start_record.svg';
import icon4_en from './icons/icon4_en.svg';
import icon4 from './icons/icon4.svg';
import power from './icons/power.svg';
import icon5_en from './icons/icon5_en.svg';
import icon5 from './icons/icon5.svg';
import record from './icons/record.svg';
export default {
props: {
Drone: {
type: Object,
default: () => ({})
},
camera_type: {
type: String,
default: ''
}
},
data() {
return {
toolControl: [
{ id: 1, icon: icon3 },
{ id: 2, icon: icon1, play: false }
],
modeControls: [
{
id: 0,
label: '单拍',
icon: icon3,
iconB: icon3_en,
iconC: icon1
},
{
id: 1,
label: '录像',
icon: icon2,
iconB: icon2_en,
iconC: start_record
},
{
id: 2,
label: '智能低光',
icon: icon4,
iconB: icon4_en,
iconC: power
},
{
id: 3,
label: '全景',
icon: icon5,
iconB: icon5_en,
iconC: icon1
}
],
select_mode: 0,
showControl: false,
showView: false, //全景拍照中
percentage: 0, //进度
elevation: 0 //飞行高度
};
},
mounted() {
this.$recvChanel('websocketBus', (res) => {
if (res.businessType == 'camera_photo_take_progress') {
if (res.data && res.data.output) {
// 获取上报进度
this.percentage = res.data.output.progress.percent;
if (this.percentage == 100 && this.showView) {
this.showView = false;
this.$message.success('全景拍照完成');
}
}
}
if (res.businessType == 'osd4') {
this.elevation = res.data.elevation || 0; //飞行高度
}
});
},
methods: {
onTool(item) {
// 选中模式
if (this.showView) {
//正在全景拍照中
this.$message.success('正在全景拍照中,请稍等');
return;
}
if (item.id == 1) {
this.showControl = true;
} else {
// 录像状态
if (this.select_mode == 1) {
if (this.play) {
// 结束录像
item.icon = start_record;
this.videoRecord('camera_recording_stop', false, '结束录像');
} else {
// 开始录像
this.videoRecord('camera_recording_start', true, '开始录像');
item.icon = record;
}
this.play = !this.play;
}
// 单拍
if (this.select_mode == 0) {
this.singleBeat('camera_photo_take');
}
// 智能灯光
if (this.select_mode == 2) {
this.power();
}
// 全景
if (this.select_mode == 3) {
// 当还没有飞行时不可拍全景
if (this.elevation > 0) {
if (this.camera_type !== 'wide') {
this.$message.warning('请切换到广角模式再进行全景拍照!');
} else {
this.panoramicView();
}
} else {
this.$message.warning('请起飞之后再进行全景拍照!');
}
}
}
},
// 开始全景拍照
panoramicView() {
let obj = {
params: {
payload_index: this.Drone.cameraIndex
},
gateway: this.Drone.gateway,
method: 'camera_photo_take'
};
hasDataFlight(obj).then((res) => {
if (res.code == 200) {
this.showView = true;
}
});
},
// 单拍
singleBeat(method) {
let obj = {
params: {
payload_index: this.Drone.cameraIndex
},
gateway: this.Drone.gateway,
method
};
hasDataFlight(obj).then((res) => {
if (res.code == 200) {
this.$emit('setShutter', true);
this.$message.success('拍照成功');
}
});
},
// 智能灯光
power() {
// console.log("智能低光");
},
// 录像
videoRecord(method, flag, label) {
let obj = {
params: {
payload_index: this.Drone.cameraIndex
},
gateway: this.Drone.gateway,
method
};
this.$emit('cameraRecording', flag);
this.setHasDataFlight(obj, true, label);
},
// 切换工具
onControl(item) {
// 切换模式
this.ToggleCamera(item.id);
this.select_mode = item.id;
this.toolControl[0].icon = item.icon;
this.toolControl[1].icon = item.iconC;
this.showControl = false;
const heightMap = {
0: 720,
1: 540
};
const height = heightMap[item.id] || 0; // 默认值为0可以根据需要修改
if ([0, 1].includes(item.id)) {
this.$sendChanel('video_width', height);
}
},
// 切换模式
ToggleCamera(value, flag = true) {
if (value == this.select_mode && flag) return;
let obj = {
params: {
camera_mode: value,
payload_index: this.Drone.cameraIndex
},
gateway: this.Drone.gateway,
method: 'camera_mode_switch'
};
this.setHasDataFlight(obj, flag);
},
// 有参数接口调用
setHasDataFlight(data, flag, label = '操作成功') {
hasDataFlight(data).then((res) => {
if (res.code == 200) {
this.$message.success(label);
}
});
}
}
};
</script>
<style lang="scss">
.progress_box {
border-radius: 50%;
place-items: center;
display: grid;
.el-progress--circle .el-progress__text,
.el-progress--dashboard .el-progress__text {
color: #fff !important;
font-size: 8px !important;
}
}
.mode_control {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
width: 36px;
height: 75px;
display: flex;
flex-direction: column;
align-items: center;
background: rgba(0, 0, 0, 0.5);
justify-content: space-around;
.tool_control {
cursor: pointer;
> img {
width: 24px;
height: 24px;
}
}
.box_control {
position: absolute;
right: 120%;
top: -40%;
width: 70px;
height: 200px;
background: rgba(0, 0, 0, 0.5);
padding: 4px 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
> div {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
font-size: 14px;
cursor: pointer;
> img {
width: 20px;
height: 20px;
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,46 @@
<template>
<div class="my_loading" v-show="showLoading">
<img src="./icons/loading2.png" alt="" srcset="" />
<span>正在加载数据</span>
</div>
</template>
<script>
export default {
data() {
return { showLoading: false };
},
methods: {
statusLoading(flag) {
this.showLoading = flag;
},
},
};
</script>
<style lang="scss" scoped>
@keyframes identifier {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.my_loading {
display: grid;
place-items: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
> img {
width: 50px;
height: 50px;
animation: identifier 1s linear infinite;
}
> span {
color: #fff;
}
}
</style>

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="10" viewBox="0 0 16 10" fill="none">
<path d="M0.351074 7.85577L7.19283 0.365385C7.39462 0.134616 7.69251 1.52624e-15 8 1.46975e-15C8.3075 1.41326e-15 8.60538 0.134616 8.80717 0.365385L15.6489 7.85577C15.9852 8.21154 16.091 8.72115 15.918 9.18269C15.745 9.63461 15.3318 9.95192 14.8418 10L1.15825 10C0.668179 9.96154 0.254983 9.64423 0.0820169 9.18269C-0.0909481 8.72115 0.0147524 8.21154 0.351074 7.85577Z" fill="#FFFFFF" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 544 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,369 @@
<template>
<div class="ptz_control" @mouseleave="handleComponentLeave">
<div>
<div class="boxs" v-for="(item, i) of pztList" :key="i" @click="onControlReset(item.id)" :class="item.id == select_id ? 'select' : ''">
<img :src="item.icon" alt="" srcset="" />
<span>{{ item.label }}</span>
</div>
</div>
<div class="boxs_ptz">
<div class="ptz_dis">
<div
class="boxs"
v-for="(item, i) of controlList1"
:key="i"
@mousedown="startControl(item)"
@mouseup="stopControl"
@mouseleave="handleMouseLeave"
:class="item.id == select_id ? 'select' : ''"
>
<img :src="item.icon" alt="" srcset="" />
<span>{{ item.label }}</span>
</div>
</div>
<div class="ptz_dis1">
<div
class="boxs"
v-for="(item, i) of controlList"
:key="i"
@mousedown="startControl(item)"
@mouseup="stopControl"
@mouseleave="handleMouseLeave"
:class="item.id == select_id ? 'select' : ''"
>
<img :src="item.icon" alt="" srcset="" />
<span>{{ item.label }}</span>
</div>
</div>
</div>
<!-- <el-button @click="sendCommand"></el-button>
<el-button></el-button>
<el-button></el-button>
<el-button></el-button> -->
</div>
</template>
<script>
import { hasDataFlight } from '../../api/index';
import { getDockAir } from '@/utils/auth';
import ptz1 from './icons/ptz1.svg';
import ptz from './icons/ptz.svg';
import btn from './icons/btn.svg';
const validKeys = ['w', 'a', 's', 'd', 'c', 'z'];
export default {
props: {
Drone: {
type: Object,
default: () => ({})
}
},
data() {
return {
pztList: [
{
id: 0,
label: '云台回中',
icon: ptz
},
{ id: 1, label: '云台向下', icon: ptz1 }
],
controlList1: [
{
id: 2,
label: '云台向上',
icon: btn,
type: 'vertical',
verticalValue: 180
}
],
controlList: [
{
id: 6,
label: '云台向左',
icon: btn,
type: 'horizontal',
verticalValue: -180
},
{
id: 3,
label: '云台回下',
icon: btn,
type: 'vertical',
verticalValue: -180
},
{
id: 4,
label: '云台向右',
icon: btn,
type: 'horizontal',
verticalValue: 180
}
],
select_id: 1,
isSelect: true, //是否可以点击
intervalId: null,
obj: {
gateway: null,
x: 12, //17~-17 前后
y: 0, //17~-17 左右
h: 0, //5~-4 上下
w: 0
},
controlInterval: null
};
},
mounted() {
this.addLis();
},
methods: {
// 鼠标按下离开当前div停止
handleMouseLeave(event) {
// 检查鼠标按键是否仍处于按下状态
if (event.buttons !== 0) {
// buttons 属性为0表示没有按键被按下
this.stopControl();
}
},
// 鼠标按下离开当前 ptz_control div停止
handleComponentLeave() {
// 停止所有控制操作
this.stopControl();
this.select_id = null;
},
// flight_authority_grab
onControlReset(id) {
this.select_id = id;
// 无参数接口调用
let obj = {
params: {
payload_index: this.Drone.cameraIndex,
reset_mode: id
},
gateway: this.Drone.gateway,
method: 'gimbal_reset'
};
this.setHasDataFlight(obj);
},
startControl(item) {
this.select_id = item.id;
// 先执行一次
this.onControl(item);
// 设置定时器持续执行
this.controlInterval = setInterval(() => {
this.onControl(item);
}, 100);
},
stopControl() {
if (this.controlInterval) {
clearInterval(this.controlInterval);
this.controlInterval = null;
}
// 重置选中状态
this.select_id = null;
},
onControl(item) {
let obj = {
locked: false,
payload_index: this.Drone.cameraIndex
};
if (item.type == 'horizontal') {
//水平
this.obj.pitch_speed = 0;
obj.yaw_speed = Cesium.Math.toRadians(item.verticalValue);
obj.pitch_speed = 0;
} else {
//垂直
this.obj.yaw_speed = 0;
obj.pitch_speed = Cesium.Math.toRadians(item.verticalValue);
obj.yaw_speed = 0;
}
let obj2 = {
params: obj,
gateway: this.Drone.gateway,
method: 'camera_screen_drag'
};
this.setHasDataFlight(obj2);
},
// 有参数接口调用
setHasDataFlight(data) {
if (!this.isSelect) return;
hasDataFlight(data).then((res) => {
if (res.code == 200) {
}
this.isSelect = true;
});
},
control() {
console.log(this.obj, droneControl);
// droneControl(this.obj).then(res => {
// console.log(234);
// })
},
sendCommand() {
// 这里放置发送指令的逻辑
this.obj.gateway = this.Drone.gateway; // 确保使用正确的上下文
// let url = process.env.DOCKAIR + '/dj/cmdFly/droneControl'; // 注意这里的url是否正确
let url = getDockAir() + '/dj/cmdFly/droneControl'; // 注意这里的url是否正确
axios
.post(url, this.obj) // 直接使用url和数据
.then((res) => {
console.log(res);
})
.catch((error) => {
console.error('发送请求失败:', error);
});
},
// 添加监听
addLis() {
// 定义事件处理函数
document.addEventListener('keydown', this.handleKeydown);
document.addEventListener('keyup', this.handleKeyup);
},
handleKeydown(event) {
// 检查是否是有效按键
if (validKeys.includes(event.key.toLowerCase())) {
this.obj.x = 0;
this.obj.y = 0;
this.obj.h = 0;
this.obj.w = 0;
switch (event.key.toLowerCase()) {
case 'w':
this.obj.x = 12; // W按键时x值为12
break;
case 's':
this.obj.x = -12; // S按键时x值为-12
break;
case 'a':
this.obj.y = -12; // A按键时y值为12
break;
case 'd':
this.obj.y = 12; // D按键时y值为-12
break;
case 'z':
this.obj.h = -2; // Z按键时h值为-2
break;
case 'c':
this.obj.h = 5; // C按键时h值为5
break;
default:
break;
}
// 如果没有正在发送请求,则启动定时器
if (!this.intervalId) {
this.intervalId = setInterval(() => {
// 在这里发送请求
console.log(`发送请求: 按下了 ${event.key.toUpperCase()}`, this.obj);
this.obj.gateway = this.Drone.gateway; // 确保使用正确的上下文
// let url = process.env.DOCKAIR + '/dj/cmdFly/droneControl'; // 注意这里的url是否正确
let url = getDockAir() + '/dj/cmdFly/droneControl'; // 注意这里的url是否正确
console.log(url, this.obj);
axios
.post(url, this.obj) // 直接使用url和数据
.then((res) => {})
.catch((error) => {
console.log('发送请求失败:', error);
});
}, 150);
}
}
},
handleKeyup(event) {
// 如果抬起的按键是有效按键,停止定时器
if (validKeys.includes(event.key.toLowerCase()) && this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
},
// 移除事件监听的函数
removeEventListeners() {
document.removeEventListener('keydown', this.handleKeydown);
document.removeEventListener('keyup', this.handleKeyup);
// 如果定时器存在,清除定时器
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
console.log('事件监听器已移除');
}
}
};
</script>
<style lang="scss" scoped>
.ptz_control {
position: absolute;
left: 0px;
bottom: 0px;
display: flex;
align-items: center;
> div {
margin-right: 20px;
.boxs {
height: 50px;
display: flex;
align-items: center;
flex-direction: column;
color: #fff;
border-radius: 4px;
background: rgba(0, 255, 255, 0.1);
box-sizing: border-box;
border: 1px solid rgba(0, 255, 255, 0.1);
padding: 4px;
font-size: 12px;
margin-bottom: 10px;
width: 58px;
cursor: pointer;
> img {
width: 16px;
height: 16px;
margin-bottom: 6px;
}
}
.boxs:hover {
border: 1px solid rgba(0, 255, 255, 0.5);
}
.select {
// border: 1px solid rgba(0, 255, 255, 0.5);
}
}
.boxs_ptz {
width: 220px;
.ptz_dis {
width: 100%;
display: flex;
justify-content: center;
}
.ptz_dis1 {
display: flex;
justify-content: space-between;
> div:nth-child(1) > img {
transform: rotate(-90deg);
}
> div:nth-child(2) > img {
transform: rotate(180deg);
}
> div:nth-child(3) > img {
transform: rotate(90deg);
}
}
}
}
</style>

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M8 0C3.58169 0 0 3.58169 0 8C0 12.4183 3.58169 16 8 16C12.4183 16 16 12.4183 16 8C16 3.58169 12.4183 0 8 0ZM8 11.6364C5.9917 11.6364 4.36364 10.0083 4.36364 8C4.36364 5.9917 5.9917 4.36364 8 4.36364C10.0083 4.36364 11.6364 5.9917 11.6364 8C11.6364 10.0083 10.0083 11.6364 8 11.6364Z" fill="#E55D5D" >
</path>
<circle cx="8" cy="8" r="4" fill="#FFFFFF" >
</circle>
</svg>

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

View File

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12" fill="none">
<g clip-path="url(#clip-path-41_1528)">
<path d="M6.98714 1.73933C7.14377 1.78066 7.30021 1.69136 7.34114 1.53533L7.64714 0.383334C7.68807 0.227162 7.5989 0.0702258 7.44314 0.029334C7.28651 -0.011993 7.12466 0.0773075 7.08314 0.233334L6.77714 1.38533C6.73621 1.54136 6.83109 1.69801 6.98714 1.73933ZM4.61714 1.53533C4.65836 1.69136 4.81509 1.78051 4.97114 1.73933C5.12719 1.69786 5.22236 1.54137 5.18114 1.38533L4.87514 0.233334C4.83392 0.0773063 4.67119 -0.0119928 4.51514 0.029334C4.35879 0.0702258 4.26992 0.227307 4.31114 0.383334L4.61714 1.53533ZM8.91314 2.63333C9.05111 2.71803 9.23053 2.6745 9.31514 2.53733L9.93914 1.52333C10.0233 1.38587 9.98111 1.20573 9.84314 1.12133C9.70517 1.03709 9.52575 1.07987 9.44114 1.21733L8.81714 2.23133C8.73327 2.36865 8.77545 2.54909 8.91314 2.63333ZM2.64314 2.53733C2.72761 2.67451 2.90745 2.71801 3.04514 2.63333C3.18282 2.54908 3.22575 2.3688 3.14114 2.23133L2.51714 1.21733C2.43268 1.07972 2.25283 1.03694 2.11514 1.12133C1.97775 1.20573 1.93453 1.38587 2.01914 1.52333L2.64314 2.53733ZM1.93514 4.59533C2.01816 4.457 1.97183 4.27614 1.83314 4.19333L0.813141 3.58733C0.674299 3.50512 0.493578 3.5507 0.411141 3.68933C0.328708 3.82796 0.374733 4.00883 0.513141 4.09133L1.53314 4.69733C1.67198 4.77955 1.85299 4.73381 1.93514 4.59533ZM11.5471 3.68933C11.4644 3.55071 11.284 3.50525 11.1451 3.58733L10.1251 4.19333C9.9863 4.27612 9.9404 4.457 10.0231 4.59533C10.1056 4.73396 10.2803 4.77969 10.4191 4.69733L11.4451 4.09133C11.584 4.00897 11.6293 3.82796 11.5471 3.68933ZM11.0371 2.20733L10.4011 1.57133L8.40314 3.57533C7.74278 2.95789 6.88725 2.60933 5.97914 2.60933C5.03025 2.60933 4.13607 2.98897 3.46514 3.65933C2.79393 4.3297 2.42714 5.22514 2.42714 6.17333C2.42714 6.80615 2.59337 7.42471 2.91314 7.96733C3.0974 8.2804 3.20036 8.44605 3.38114 8.60933L1.37114 10.6253L2.00114 11.2613L3.74714 9.51533L3.74714 11.1233C3.74714 11.5743 4.63334 12.0173 5.08514 12.0173L6.87314 12.0173C7.32481 12.0173 8.20246 11.4801 8.21114 11.1233C8.19667 10.9664 8.21114 8.87933 8.21114 8.87933C8.67264 8.57975 8.76515 8.44838 9.04514 7.97333C9.36608 7.43014 9.53114 6.80729 9.53114 6.17333C9.53114 5.48818 9.33941 4.82912 8.97914 4.26533L11.0371 2.20733ZM3.86714 7.38533C3.62272 6.97032 3.49514 6.49536 3.49514 6.01133C3.49514 5.28644 3.78001 4.71779 4.29314 4.20533C4.80657 3.69274 5.2534 3.58733 5.97914 3.58733C6.98717 3.58733 7.15201 3.94874 7.65314 4.40333L4.20914 7.84133C4.081 7.70154 3.9639 7.54962 3.86714 7.38533ZM7.28714 10.8773C7.28714 10.9848 7.20289 11.0753 7.09514 11.0753L4.86314 11.0753C4.7551 11.0753 4.66514 10.9848 4.66514 10.8773L4.66514 10.1453L7.28714 10.1453L7.28714 10.8773ZM8.17514 7.43333C7.93666 7.8366 7.59638 8.17671 7.19114 8.41133L7.04714 8.49533L7.04714 9.29333L4.76114 9.29333L4.76114 8.62133L4.68314 8.57333L8.21114 5.11133C8.42795 5.50605 8.55314 5.59634 8.55314 6.05933C8.55314 6.54395 8.42013 7.01789 8.17514 7.43333Z" fill="#FFFFFF" >
</path>
</g>
<defs>
<clipPath id="clip-path-41_1528">
<path d="M0 12L12 12L12 0L0 0L0 12Z" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show More