initial v1.3.0
Esse commit está contido em:
@@ -75,6 +75,7 @@
|
||||
"ant-design-vue/es/empty/style/css",
|
||||
"ant-design-vue/es/form/style/css",
|
||||
"ant-design-vue/es/image/style/css",
|
||||
"ant-design-vue/es/input-number/style/css",
|
||||
"ant-design-vue/es/input/style/css",
|
||||
"ant-design-vue/es/layout/style/css",
|
||||
"ant-design-vue/es/menu/style/css",
|
||||
@@ -94,7 +95,9 @@
|
||||
"ant-design-vue/es/tag/style/css",
|
||||
"ant-design-vue/es/tooltip/style/css",
|
||||
"ant-design-vue/es/tree/style/css",
|
||||
"ant-design-vue/es/upload/style/css",
|
||||
"axios",
|
||||
"lodash",
|
||||
"mitt",
|
||||
"moment",
|
||||
"reconnecting-websocket",
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="demo-app" class="demo-app">
|
||||
<div class="demo-app">
|
||||
<router-view />
|
||||
<!-- <div class="map-wrapper">
|
||||
<GMap/>
|
||||
@@ -26,9 +26,17 @@ export default defineComponent({
|
||||
.demo-app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.map-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
#demo-app {
|
||||
width: 100%;
|
||||
height: 100%
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request, { IWorkspaceResponse } from '/@/api/http/request'
|
||||
import { DeviceCmd } from '/@/types/device-cmd'
|
||||
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
|
||||
|
||||
const CMD_API_PREFIX = '/control/api/v1'
|
||||
|
||||
@@ -8,13 +8,19 @@ export interface SendCmdParams {
|
||||
device_cmd: DeviceCmd // 指令
|
||||
}
|
||||
|
||||
export interface PostSendCmdBody {
|
||||
action: DeviceCmdItemAction
|
||||
}
|
||||
/**
|
||||
* 发送机场控制指令
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
// /control/api/v1/devices/{dock_sn}/jobs/{service_identifier}
|
||||
export async function postSendCmd (params: SendCmdParams): Promise<IWorkspaceResponse<{}>> {
|
||||
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`)
|
||||
export async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> {
|
||||
const postBody = body || {}
|
||||
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, {
|
||||
...postBody
|
||||
})
|
||||
return resp.data
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import request, { IWorkspaceResponse } from '/@/api/http/request'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from '/@/types/device-setting'
|
||||
|
||||
const MNG_API_PREFIX = '/manage/api/v1'
|
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
|
||||
|
||||
export interface PutDevicePropsBody {
|
||||
night_lights_state?: NightLightsStateEnum;// 夜航灯开关
|
||||
height_limit?: number;// 限高设置
|
||||
distance_limit_status?: DistanceLimitStatus;// 限远开关
|
||||
obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置设备属性
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
// /manage/api/v1/devices/{{workspace_id}}/devices/{{device_sn}}/property
|
||||
export async function putDeviceProps (deviceSn: string, body: PutDevicePropsBody): Promise<IWorkspaceResponse<{}>> {
|
||||
const resp = await request.put(`${MNG_API_PREFIX}/devices/${workspaceId}/devices/${deviceSn}/property`, body)
|
||||
return resp.data
|
||||
}
|
||||
@@ -159,4 +159,10 @@ export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: st
|
||||
})
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const changeLivestreamLens = async function (body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/live/streams/switch`
|
||||
const result = await request.post(url, body)
|
||||
return result.data
|
||||
}
|
||||
+58
-16
@@ -1,14 +1,9 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import request, { IPage, IWorkspaceResponse } from '/@/api/http/request'
|
||||
const HTTP_PREFIX = '/wayline/api/v1'
|
||||
import request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'
|
||||
import { TaskType, TaskStatus, OutOfControlAction } from '/@/types/task'
|
||||
import { WaylineType } from '/@/types/wayline'
|
||||
|
||||
export interface CreatePlan {
|
||||
name: string,
|
||||
file_id: string,
|
||||
dock_sn: string,
|
||||
immediate: boolean,
|
||||
type: string,
|
||||
}
|
||||
const HTTP_PREFIX = '/wayline/api/v1'
|
||||
|
||||
// Get Wayline Files
|
||||
export const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
@@ -24,12 +19,11 @@ export const downloadWaylineFile = async function (workspaceId: string, waylineI
|
||||
if (result.data.type === 'application/json') {
|
||||
const reader = new FileReader()
|
||||
reader.onload = function (e) {
|
||||
let text = reader.result as string
|
||||
const text = reader.result as string
|
||||
const result = JSON.parse(text)
|
||||
message.error(result.message)
|
||||
}
|
||||
reader.readAsText(result.data, 'utf-8')
|
||||
return
|
||||
} else {
|
||||
return result.data
|
||||
}
|
||||
@@ -42,6 +36,17 @@ export const deleteWaylineFile = async function (workspaceId: string, waylineId:
|
||||
return result.data
|
||||
}
|
||||
|
||||
export interface CreatePlan {
|
||||
name: string,
|
||||
file_id: string,
|
||||
dock_sn: string,
|
||||
task_type: TaskType, // 任务类型
|
||||
wayline_type: WaylineType, // 航线类型
|
||||
execute_time?: number // 执行时间(毫秒)
|
||||
rth_altitude: number // 相对机场返航高度 20 - 500
|
||||
out_of_control_action: OutOfControlAction // 失控动作
|
||||
}
|
||||
|
||||
// Create Wayline Job
|
||||
export const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks`
|
||||
@@ -49,16 +54,53 @@ export const createPlan = async function (workspaceId: string, plan: CreatePlan)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
job_id: string,
|
||||
job_name: string,
|
||||
task_type: TaskType, // 任务类型
|
||||
file_id: string, // 航线文件id
|
||||
file_name: string, // 航线名称
|
||||
wayline_type: WaylineType, // 航线类型
|
||||
dock_sn: string,
|
||||
dock_name: string,
|
||||
workspace_id: string,
|
||||
username: string,
|
||||
execute_time: string,
|
||||
end_time: string,
|
||||
status: TaskStatus, // 任务状态
|
||||
progress: number, // 执行进度
|
||||
code: number, // 错误码
|
||||
rth_altitude: number // 相对机场返航高度 20 - 500
|
||||
out_of_control_action: OutOfControlAction // 失控动作
|
||||
}
|
||||
|
||||
// Get Wayline Jobs
|
||||
export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IWorkspaceResponse<any>> {
|
||||
export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IListWorkspaceResponse<Task>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Execute Wayline Job
|
||||
export const executeWaylineJobs = async function (workspaceId: string, plan_id: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${plan_id}`
|
||||
const result = await request.post(url)
|
||||
export interface DeleteTaskParams {
|
||||
job_id: string
|
||||
}
|
||||
|
||||
// 取消机场任务
|
||||
export async function deleteTask (workspaceId: string, params: DeleteTaskParams): Promise<IWorkspaceResponse<{}>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs`
|
||||
const result = await request.delete(url, {
|
||||
params: params
|
||||
})
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Upload Wayline file
|
||||
export const importKmzFile = async function (workspaceId: string, file: {}): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/file/upload`
|
||||
const result = await request.post(url, file, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
})
|
||||
return result.data
|
||||
}
|
||||
|
||||
+14
-12
@@ -182,12 +182,12 @@
|
||||
<a-row>
|
||||
<a-col span="12">
|
||||
<a-tooltip title="Network State">
|
||||
<span :style="deviceInfo.dock.network_state.quality === 2 ? 'color: #00ee8b' :
|
||||
deviceInfo.dock.network_state.quality === 1 ? 'color: yellow' : 'color: red'">
|
||||
<span v-if="deviceInfo.dock.network_state.type === 1"><SignalFilled /></span>
|
||||
<span :style="deviceInfo.dock.network_state?.quality === 2 ? 'color: #00ee8b' :
|
||||
deviceInfo.dock.network_state?.quality === 1 ? 'color: yellow' : 'color: red'">
|
||||
<span v-if="deviceInfo.dock.network_state?.type === 1"><SignalFilled /></span>
|
||||
<span v-else><GlobalOutlined /></span>
|
||||
</span>
|
||||
<span class="ml10" >{{ deviceInfo.dock.network_state.rate }} KB/S</span>
|
||||
<span class="ml10" >{{ deviceInfo.dock.network_state?.rate }} KB/S</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
@@ -199,13 +199,13 @@
|
||||
<a-col span="6">
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
<p>total: {{ deviceInfo.dock.storage.total }}</p>
|
||||
<p>used: {{ deviceInfo.dock.storage.used }}</p>
|
||||
<p>total: {{ deviceInfo.dock.storage?.total }}</p>
|
||||
<p>used: {{ deviceInfo.dock.storage?.used }}</p>
|
||||
</template>
|
||||
<span><FolderOpenOutlined /></span>
|
||||
<span class="ml10" v-if="deviceInfo.dock.storage.total > 0">
|
||||
<a-progress type="circle" :width="20" :percent="deviceInfo.dock.storage.used * 100/ deviceInfo.dock.storage.total"
|
||||
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.storage.used * 100 / deviceInfo.dock.storage.total > 80 ? 'red' : '#00ee8b' "/>
|
||||
<span class="ml10" v-if="deviceInfo.dock.storage?.total > 0">
|
||||
<a-progress type="circle" :width="20" :percent="deviceInfo.dock.storage?.used * 100/ deviceInfo.dock.storage?.total"
|
||||
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.storage?.used * 100 / deviceInfo.dock.storage?.total > 80 ? 'red' : '#00ee8b' "/>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
@@ -264,8 +264,8 @@
|
||||
</a-row>
|
||||
<a-row class="p5">
|
||||
<a-col span="24">
|
||||
<a-button type="primary" :disabled="controlPanelVisible" size="small" @click="dockDebugOnOff(osdVisible.gateway_sn, true)">
|
||||
远程调试
|
||||
<a-button type="primary" :disabled="controlPanelVisible" size="small" @click="setControlPanelVisible(true)">
|
||||
设备操作
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -589,6 +589,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) {
|
||||
deviceTsaUpdateHook.value.initMarker(EDeviceTypeName.Dock, EDeviceTypeName.Dock, data.currentSn, data.dockInfo[data.currentSn].longitude, data.dockInfo[data.currentSn].latitude)
|
||||
if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
|
||||
deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn]
|
||||
deviceInfo.device = data.deviceInfo[deviceInfo.dock.sub_device?.device_sn]
|
||||
@@ -839,6 +840,7 @@ export default defineComponent({
|
||||
EDockModeCode,
|
||||
controlPanelVisible,
|
||||
dockDebugOnOff,
|
||||
setControlPanelVisible,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -883,7 +885,7 @@ export default defineComponent({
|
||||
}
|
||||
.osd-panel {
|
||||
position: absolute;
|
||||
left: 350px;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
width: 480px;
|
||||
background: black;
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
<template>
|
||||
<div class="header">Task Plan Library</div>
|
||||
<div class="plan-panel-wrapper">
|
||||
<!-- <router-link :to=" '/' + ERouterName.CREATE_PLAN">
|
||||
<a-button type="primary">Create Plan</a-button>
|
||||
</router-link> -->
|
||||
<a-table class="plan-table" :columns="columns" :data-source="plansData.data" row-key="job_id"
|
||||
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
|
||||
<template #status="{ record }">
|
||||
<span v-if="taskProgressMap[record.bid]">
|
||||
<a-progress type="line" :percent="taskProgressMap[record.bid]?.progress?.percent"
|
||||
:status="taskProgressMap[record.bid]?.status.indexOf(ETaskStatus.FAILED) != -1 ? 'exception' : taskProgressMap[record.bid]?.status.indexOf(ETaskStatus.OK) != -1 ? 'success' : 'normal'">
|
||||
<template #format="percent">
|
||||
<a-tooltip :title="taskProgressMap[record.bid]?.status">
|
||||
<div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden; position: absolute; left: 5px; top: -12px;">
|
||||
{{ percent }}% {{ taskProgressMap[record.bid]?.status }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-progress>
|
||||
</span>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<span class="action-area">
|
||||
<a-popconfirm
|
||||
title="Are you sure execute this task?"
|
||||
ok-text="Yes"
|
||||
cancel-text="No"
|
||||
@confirm="executePlan(record.job_id)"
|
||||
>
|
||||
<a-button type="primary" size="small">Execute</a-button>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from '@vue/reactivity'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { TableState } from 'ant-design-vue/lib/table/interface'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { IPage } from '../api/http/type'
|
||||
import { executeWaylineJobs, getWaylineJobs } from '../api/wayline'
|
||||
import { getRoot } from '../root'
|
||||
import { useMyStore } from '../store'
|
||||
import { ELocalStorageKey, ERouterName } from '../types/enums'
|
||||
import router from '/@/router'
|
||||
import { ETaskStatus } from '/@/types/wayline'
|
||||
|
||||
const store = useMyStore()
|
||||
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
const root = getRoot()
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
const paginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Plan Name',
|
||||
dataIndex: 'job_name'
|
||||
},
|
||||
{
|
||||
title: 'Flight Route Name',
|
||||
dataIndex: 'file_name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Dock Name',
|
||||
dataIndex: 'dock_name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Creator',
|
||||
dataIndex: 'username',
|
||||
},
|
||||
{
|
||||
title: 'Updated',
|
||||
dataIndex: 'update_time'
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
key: 'status',
|
||||
width: 200,
|
||||
slots: { customRender: 'status' }
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Action',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
type Pagination = TableState['pagination']
|
||||
|
||||
interface TaskPlan {
|
||||
bid: string,
|
||||
job_id: string,
|
||||
job_name: string,
|
||||
file_name: string,
|
||||
dock_name: string,
|
||||
username: string,
|
||||
create_time: string,
|
||||
}
|
||||
|
||||
const plansData = reactive({
|
||||
data: [] as TaskPlan[]
|
||||
})
|
||||
|
||||
function createPlan () {
|
||||
root.$router.push('/' + ERouterName.CREATE_PLAN)
|
||||
}
|
||||
|
||||
const taskProgressMap = computed(() => store.state.taskProgressInfo)
|
||||
|
||||
onMounted(() => {
|
||||
getPlans()
|
||||
})
|
||||
|
||||
function getPlans () {
|
||||
getWaylineJobs(workspaceId, body).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
plansData.data = res.data.list
|
||||
paginationProp.total = res.data.pagination.total
|
||||
paginationProp.current = res.data.pagination.page
|
||||
})
|
||||
}
|
||||
|
||||
function refreshData (page: Pagination) {
|
||||
body.page = page?.current!
|
||||
body.page_size = page?.pageSize!
|
||||
getPlans()
|
||||
}
|
||||
|
||||
function executePlan (jobId: string) {
|
||||
executeWaylineJobs(workspaceId, jobId).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Executed Successfully')
|
||||
getPlans()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plan-panel-wrapper {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
.plan-table {
|
||||
background: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.action-area {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-align: start;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="device-log-detail-wrap">
|
||||
<div class="device-log-list">
|
||||
<div class="log-list-item">
|
||||
<a-button type="primary" class="download-btn" :disabled="!airportTableLogState.logList?.file_id" size="small" @click="onDownloadLog(airportTableLogState.logList.file_id)">
|
||||
<a-button type="primary" class="download-btn" :disabled="!airportTableLogState.logList?.file_id || !airportTableLogState.logList?.object_key" size="small" @click="onDownloadLog(airportTableLogState.logList.file_id)">
|
||||
下载机场日志
|
||||
</a-button>
|
||||
<a-table :columns="airportLogColumns"
|
||||
@@ -26,7 +26,7 @@
|
||||
</a-table>
|
||||
</div>
|
||||
<div class="log-list-item">
|
||||
<a-button type="primary" class="download-btn" :disabled="!droneTableLogState.logList?.file_id" size="small" @click="onDownloadLog(droneTableLogState.logList.file_id)">
|
||||
<a-button type="primary" class="download-btn" :disabled="!droneTableLogState.logList?.file_id || !droneTableLogState.logList?.object_key" size="small" @click="onDownloadLog(droneTableLogState.logList.file_id)">
|
||||
下载飞行器日志
|
||||
</a-button>
|
||||
<a-table :columns="droneLogColumns"
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div class="device-setting-wrapper">
|
||||
<div class="device-setting-header">设备属性设置</div>
|
||||
<div class="device-setting-box">
|
||||
<!-- 飞行器夜航灯 -->
|
||||
<div class="control-setting-item">
|
||||
<div class="control-setting-item-left">
|
||||
<div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}</div>
|
||||
<div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value }}</div>
|
||||
</div>
|
||||
<div class="control-setting-item-right">
|
||||
<DeviceSettingPopover
|
||||
:visible="deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].popConfirm.visible"
|
||||
:loading="deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].popConfirm.loading"
|
||||
@confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)"
|
||||
@cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)"
|
||||
>
|
||||
<template #formContent>
|
||||
<div class="form-content">
|
||||
<span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}:</span>
|
||||
<a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.nightLightsState" />
|
||||
</div>
|
||||
</template>
|
||||
<a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)">Edit</a>
|
||||
</DeviceSettingPopover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 限高 -->
|
||||
<div class="control-setting-item">
|
||||
<div class="control-setting-item-left">
|
||||
<div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}</div>
|
||||
<div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value }}</div>
|
||||
</div>
|
||||
<div class="control-setting-item-right">
|
||||
<DeviceSettingPopover
|
||||
:visible="deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].popConfirm.visible"
|
||||
:loading="deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].popConfirm.loading"
|
||||
@confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)"
|
||||
@cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)"
|
||||
>
|
||||
<template #formContent>
|
||||
<div class="form-content">
|
||||
<span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}:</span>
|
||||
<a-input-number v-model:value="deviceSettingFormModel.heightLimit" :min="20" :max="1500" />
|
||||
m
|
||||
</div>
|
||||
</template>
|
||||
<a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)">Edit</a>
|
||||
</DeviceSettingPopover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 限远 -->
|
||||
<div class="control-setting-item">
|
||||
<div class="control-setting-item-left">
|
||||
<div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}</div>
|
||||
<div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value }}</div>
|
||||
</div>
|
||||
<div class="control-setting-item-right">
|
||||
<DeviceSettingPopover
|
||||
:visible="deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].popConfirm.visible"
|
||||
:loading="deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].popConfirm.loading"
|
||||
@confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)"
|
||||
@cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)"
|
||||
>
|
||||
<template #formContent>
|
||||
<div class="form-content">
|
||||
<span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}:</span>
|
||||
<a-switch style="margin-right: 10px;" checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.distanceLimitStatus.state" />
|
||||
<a-input-number v-model:value="deviceSettingFormModel.distanceLimitStatus.distanceLimit" :min="15" :max="8000" />
|
||||
m
|
||||
</div>
|
||||
</template>
|
||||
<a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)">Edit</a>
|
||||
</DeviceSettingPopover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水平避障 -->
|
||||
<div class="control-setting-item">
|
||||
<div class="control-setting-item-left">
|
||||
<div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}</div>
|
||||
<div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value }}</div>
|
||||
</div>
|
||||
<div class="control-setting-item-right">
|
||||
<DeviceSettingPopover
|
||||
:visible="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].popConfirm.visible"
|
||||
:loading="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].popConfirm.loading"
|
||||
@confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)"
|
||||
@cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)"
|
||||
>
|
||||
<template #formContent>
|
||||
<div class="form-content">
|
||||
<span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}:</span>
|
||||
<a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.obstacleAvoidanceHorizon" />
|
||||
</div>
|
||||
</template>
|
||||
<a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)">Edit</a>
|
||||
</DeviceSettingPopover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 上视避障 -->
|
||||
<div class="control-setting-item">
|
||||
<div class="control-setting-item-left">
|
||||
<div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}</div>
|
||||
<div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value }}</div>
|
||||
</div>
|
||||
<div class="control-setting-item-right">
|
||||
<DeviceSettingPopover
|
||||
:visible="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].popConfirm.visible"
|
||||
:loading="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].popConfirm.loading"
|
||||
@confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)"
|
||||
@cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)"
|
||||
>
|
||||
<template #formContent>
|
||||
<div class="form-content">
|
||||
<span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}:</span>
|
||||
<a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.obstacleAvoidanceUpside" />
|
||||
</div>
|
||||
</template>
|
||||
<a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)">Edit</a>
|
||||
</DeviceSettingPopover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 下视避障 -->
|
||||
<div class="control-setting-item">
|
||||
<div class="control-setting-item-left">
|
||||
<div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}</div>
|
||||
<div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value }}</div>
|
||||
</div>
|
||||
<div class="control-setting-item-right">
|
||||
<DeviceSettingPopover
|
||||
:visible="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].popConfirm.visible"
|
||||
:loading="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].popConfirm.loading"
|
||||
@confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)"
|
||||
@cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)"
|
||||
>
|
||||
<template #formContent>
|
||||
<div class="form-content">
|
||||
<span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}:</span>
|
||||
<a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.obstacleAvoidanceDownside" />
|
||||
</div>
|
||||
</template>
|
||||
<a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)">Edit</a>
|
||||
</DeviceSettingPopover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref, watch } from 'vue'
|
||||
import { DeviceInfoType } from '/@/types/device'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { initDeviceSetting, initDeviceSettingFormModel, DeviceSettingKeyEnum } from '/@/types/device-setting'
|
||||
import { updateDeviceSettingInfoByOsd, updateDeviceSettingFormModelByOsd } from '/@/utils/device-setting'
|
||||
import { useDeviceSetting } from './useDeviceSetting'
|
||||
import DeviceSettingPopover from './DeviceSettingPopover.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
sn: string,
|
||||
deviceInfo: DeviceInfoType,
|
||||
}>()
|
||||
|
||||
const store = useMyStore()
|
||||
const deviceSetting = ref(cloneDeep(initDeviceSetting))
|
||||
const deviceSettingFormModelFromOsd = ref(cloneDeep(initDeviceSettingFormModel))
|
||||
const deviceSettingFormModel = ref(cloneDeep(initDeviceSettingFormModel)) // 真实使用的formModel
|
||||
|
||||
// 根据设备osd信息更新信息
|
||||
watch(() => props.deviceInfo, (value) => {
|
||||
updateDeviceSettingInfoByOsd(deviceSetting.value, value)
|
||||
updateDeviceSettingFormModelByOsd(deviceSettingFormModelFromOsd.value, value)
|
||||
// console.log('deviceInfo', value)
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: true
|
||||
})
|
||||
|
||||
function onShowPopConfirm (settingKey: DeviceSettingKeyEnum) {
|
||||
deviceSetting.value[settingKey].popConfirm.visible = true
|
||||
deviceSettingFormModel.value = cloneDeep(deviceSettingFormModelFromOsd.value)
|
||||
}
|
||||
|
||||
function onCancel (settingKey: DeviceSettingKeyEnum) {
|
||||
deviceSetting.value[settingKey].popConfirm.visible = false
|
||||
}
|
||||
|
||||
async function onConfirm (settingKey: DeviceSettingKeyEnum) {
|
||||
deviceSetting.value[settingKey].popConfirm.loading = true
|
||||
const body = genDevicePropsBySettingKey(settingKey, deviceSettingFormModel.value)
|
||||
await setDeviceProps(props.sn, body)
|
||||
deviceSetting.value[settingKey].popConfirm.loading = false
|
||||
deviceSetting.value[settingKey].popConfirm.visible = false
|
||||
}
|
||||
|
||||
// 更新设备属性
|
||||
const {
|
||||
genDevicePropsBySettingKey,
|
||||
setDeviceProps,
|
||||
} = useDeviceSetting()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.device-setting-wrapper{
|
||||
border-bottom: 1px solid #515151;
|
||||
|
||||
.device-setting-header{
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 10px 10px 0px;
|
||||
}
|
||||
|
||||
.device-setting-box{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
padding: 4px 10px;
|
||||
|
||||
.control-setting-item{
|
||||
width: 220px;
|
||||
height: 58px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #666;
|
||||
margin: 4px 0;
|
||||
padding: 0 8px;
|
||||
|
||||
.control-setting-item-left{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.item-label{
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<a-popover :visible="state.sVisible"
|
||||
trigger="click"
|
||||
v-bind="$attrs"
|
||||
:overlay-class-name="overlayClassName"
|
||||
placement="bottom"
|
||||
@visibleChange=";"
|
||||
v-on="$attrs">
|
||||
<template #content>
|
||||
<div class="title-content">
|
||||
</div>
|
||||
<slot name="formContent" />
|
||||
<div class="uranus-popconfirm-btns">
|
||||
<a-button size="sm"
|
||||
@click="onCancel">
|
||||
{{ cancelText || '取消'}}
|
||||
</a-button>
|
||||
<a-button size="sm"
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
class="confirm-btn"
|
||||
@click="onConfirm">
|
||||
{{ okText || '确定' }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="$slots.default">
|
||||
<slot></slot>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, defineEmits, reactive, watch, computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
visible?: boolean,
|
||||
loading?: Boolean,
|
||||
disabled?: Boolean,
|
||||
title?: String,
|
||||
okText?: String,
|
||||
cancelText?: String,
|
||||
width?: Number,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['cancel', 'confirm'])
|
||||
|
||||
const state = reactive({
|
||||
sVisible: false,
|
||||
loading: false,
|
||||
})
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
state.sVisible = val || false
|
||||
})
|
||||
|
||||
const loading = computed(() => {
|
||||
return props.loading
|
||||
})
|
||||
const okLabel = computed(() => {
|
||||
return props.loading ? '' : '确定'
|
||||
})
|
||||
|
||||
const overlayClassName = computed(() => {
|
||||
const classList = ['device-setting-popconfirm']
|
||||
return classList.join(' ')
|
||||
})
|
||||
|
||||
function onConfirm (e: Event) {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
emit('confirm', e)
|
||||
}
|
||||
|
||||
function onCancel (e: Event) {
|
||||
state.sVisible = false
|
||||
emit('cancel', e)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.device-setting-popconfirm {
|
||||
min-width: 300px;
|
||||
|
||||
.uranus-popconfirm-btns{
|
||||
display: flex;
|
||||
padding: 10px 0px;
|
||||
justify-content: flex-end;
|
||||
|
||||
.confirm-btn{
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-content{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.form-label{
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,24 +2,32 @@
|
||||
<div class="dock-control-panel">
|
||||
<!-- title -->
|
||||
<div class="dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between">
|
||||
<span>远程调试 {{ props.sn}}</span>
|
||||
<span>设备操作 {{ props.sn}}</span>
|
||||
<span @click="closeControlPanel">
|
||||
<CloseOutlined />
|
||||
</span>
|
||||
</div>
|
||||
<!-- setting -->
|
||||
<DeviceSettingBox :sn="props.sn" :deviceInfo="props.deviceInfo"></DeviceSettingBox>
|
||||
<!-- cmd -->
|
||||
<div class="control-cmd-wrapper">
|
||||
<div v-for="(cmdItem, index) in cmdList" :key="cmdItem.cmdKey" class="control-cmd-item">
|
||||
<div class="control-cmd-item-left">
|
||||
<div class="item-label">{{ cmdItem.label }}</div>
|
||||
<div class="item-status">{{ cmdItem.status }}</div>
|
||||
</div>
|
||||
<div class="control-cmd-item-right">
|
||||
<a-button :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)">
|
||||
{{ cmdItem.operateText }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-cmd-header">
|
||||
远程调试
|
||||
<a-switch class="debug-btn" checked-children="开" un-checked-children="关" v-model:checked="debugStatus" @change="onDeviceStatusChange"/>
|
||||
</div>
|
||||
<div class="control-cmd-box">
|
||||
<div v-for="(cmdItem, index) in cmdList" :key="cmdItem.cmdKey" class="control-cmd-item">
|
||||
<div class="control-cmd-item-left">
|
||||
<div class="item-label">{{ cmdItem.label }}</div>
|
||||
<div class="item-status">{{ cmdItem.status }}</div>
|
||||
</div>
|
||||
<div class="control-cmd-item-right">
|
||||
<a-button :disabled="!debugStatus || cmdItem.disabled" :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)">
|
||||
{{ cmdItem.operateText }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +43,7 @@ import { DeviceInfoType } from '/@/types/device'
|
||||
import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd'
|
||||
import DeviceSettingBox from './DeviceSettingBox.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
sn: string,
|
||||
@@ -71,14 +80,34 @@ function closeControlPanel () {
|
||||
}
|
||||
|
||||
// dock 控制指令
|
||||
const debugStatus = ref(false)
|
||||
|
||||
async function onDeviceStatusChange (status: boolean) {
|
||||
let result = false
|
||||
if (status) {
|
||||
result = await dockDebugOnOff(props.sn, true)
|
||||
} else {
|
||||
result = await dockDebugOnOff(props.sn, false)
|
||||
}
|
||||
if (!result) {
|
||||
if (status) {
|
||||
debugStatus.value = false
|
||||
} else {
|
||||
debugStatus.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
sendDockControlCmd,
|
||||
dockDebugOnOff
|
||||
} = useDockControl()
|
||||
|
||||
async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {
|
||||
const success = await sendDockControlCmd({
|
||||
sn: props.sn,
|
||||
cmd: cmdItem.cmdKey
|
||||
cmd: cmdItem.cmdKey,
|
||||
action: cmdItem.action
|
||||
}, true)
|
||||
if (success) {
|
||||
// updateDeviceSingleCmdInfo(cmdList.value[index])
|
||||
@@ -103,26 +132,39 @@ async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {
|
||||
}
|
||||
|
||||
.control-cmd-wrapper{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
padding: 4px 10px;
|
||||
.control-cmd-item{
|
||||
width: 220px;
|
||||
height: 58px;
|
||||
.control-cmd-header{
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 10px 10px 0px;
|
||||
|
||||
.debug-btn{
|
||||
margin-left: 10px;
|
||||
border:1px solid #585858;
|
||||
}
|
||||
}
|
||||
|
||||
.control-cmd-box{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #666;
|
||||
margin: 4px 0;
|
||||
padding: 0 8px;
|
||||
|
||||
.control-cmd-item-left{
|
||||
padding: 4px 10px;
|
||||
.control-cmd-item{
|
||||
width: 220px;
|
||||
height: 58px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #666;
|
||||
margin: 4px 0;
|
||||
padding: 0 8px;
|
||||
|
||||
.item-label{
|
||||
font-weight: 700;
|
||||
.control-cmd-item-left{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.item-label{
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { putDeviceProps, PutDevicePropsBody } from '/@/api/device-setting'
|
||||
import { DeviceSettingKeyEnum, DeviceSettingFormModel, ObstacleAvoidanceStatusEnum, NightLightsStateEnum, DistanceLimitStatusEnum } from '/@/types/device-setting'
|
||||
|
||||
export function useDeviceSetting () {
|
||||
// 生成参数
|
||||
function genDevicePropsBySettingKey (key: DeviceSettingKeyEnum, fromModel: DeviceSettingFormModel) {
|
||||
const body = {} as PutDevicePropsBody
|
||||
if (key === DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET) {
|
||||
body.night_lights_state = fromModel.nightLightsState ? NightLightsStateEnum.OPEN : NightLightsStateEnum.CLOSE
|
||||
} else if (key === DeviceSettingKeyEnum.HEIGHT_LIMIT_SET) {
|
||||
body.height_limit = fromModel.heightLimit
|
||||
} else if (key === DeviceSettingKeyEnum.DISTANCE_LIMIT_SET) {
|
||||
body.distance_limit_status = {}
|
||||
if (fromModel.distanceLimitStatus.state) {
|
||||
body.distance_limit_status.state = DistanceLimitStatusEnum.SET
|
||||
body.distance_limit_status.distance_limit = fromModel.distanceLimitStatus.distanceLimit
|
||||
} else {
|
||||
body.distance_limit_status.state = DistanceLimitStatusEnum.UNSET
|
||||
}
|
||||
} else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON) {
|
||||
body.obstacle_avoidance = {
|
||||
horizon: fromModel.obstacleAvoidanceHorizon ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE
|
||||
}
|
||||
} else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE) {
|
||||
body.obstacle_avoidance = {
|
||||
upside: fromModel.obstacleAvoidanceUpside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE
|
||||
}
|
||||
} else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE) {
|
||||
body.obstacle_avoidance = {
|
||||
downside: fromModel.obstacleAvoidanceDownside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// 设置设备属性
|
||||
async function setDeviceProps (sn: string, body: PutDevicePropsBody) {
|
||||
try {
|
||||
const { code, message: msg } = await putDeviceProps(sn, body)
|
||||
if (code === 0) {
|
||||
// message.success('指令发送成功')
|
||||
return true
|
||||
}
|
||||
throw (msg)
|
||||
} catch (e) {
|
||||
message.error('设备属性设置失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
genDevicePropsBySettingKey,
|
||||
setDeviceProps
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ref } from 'vue'
|
||||
import { postSendCmd } from '/@/api/device-cmd'
|
||||
import { DeviceCmd } from '/@/types/device-cmd'
|
||||
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
|
||||
|
||||
export function useDockControl () {
|
||||
const controlPanelVisible = ref(false)
|
||||
@@ -12,22 +12,30 @@ export function useDockControl () {
|
||||
|
||||
// 远程调试开关
|
||||
async function dockDebugOnOff (sn: string, on: boolean) {
|
||||
const success = await sendDockControlCmd({
|
||||
const result = await sendDockControlCmd({
|
||||
sn: sn,
|
||||
cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose
|
||||
}, false)
|
||||
if (success) {
|
||||
if (result) {
|
||||
setControlPanelVisible(on)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 发送指令
|
||||
async function sendDockControlCmd (params: {
|
||||
sn: string,
|
||||
cmd: DeviceCmd
|
||||
action?: DeviceCmdItemAction
|
||||
}, tip = true) {
|
||||
try {
|
||||
const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd })
|
||||
let body = undefined as any
|
||||
if (params.action !== undefined) {
|
||||
body = {
|
||||
action: params.action
|
||||
}
|
||||
}
|
||||
const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }, body)
|
||||
if (code === 0) {
|
||||
tip && message.success('指令发送成功')
|
||||
return true
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
<div id="player" style="width: 720px; height: 420px; border: 1px solid"></div>
|
||||
<p class="fz24">Live streaming source selection</p>
|
||||
<div class="flex-row flex-justify-center flex-align-center mt10">
|
||||
<template v-if="livePara.liveState && dronePara.isDockLive">
|
||||
<span class="mr10">Lens:</span>
|
||||
<a-radio-group v-model:value="dronePara.lensSelected" button-style="solid">
|
||||
<a-radio-button v-for="lens in dronePara.lensList" :key="lens" :value="lens">{{lens}}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-select
|
||||
style="width:150px"
|
||||
placeholder="Select Drone"
|
||||
@select="onDroneSelect"
|
||||
v-model:value="dronePara.droneSelected"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in dronePara.droneList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
@click="onDroneSelect(item)"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
@@ -19,12 +27,13 @@
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Camera"
|
||||
@select="onCameraSelect"
|
||||
v-model:value="dronePara.cameraSelected"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in dronePara.cameraList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
@click="onCameraSelect(item)"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
@@ -35,13 +44,13 @@
|
||||
@select="onVideoSelect"
|
||||
>
|
||||
<a-select-option
|
||||
class="ml10"
|
||||
v-for="item in dronePara.videoList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select> -->
|
||||
</template>
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
@@ -60,12 +69,16 @@
|
||||
Note: Obtain The Following Parameters From https://console.agora.io
|
||||
</p>
|
||||
<div class="flex-row flex-justify-center flex-align-center">
|
||||
<span class="mr10">AppId:</span>
|
||||
<a-input v-model:value="agoraPara.appid" placeholder="APP ID"></a-input>
|
||||
<span class="ml10">Token:</span>
|
||||
<a-input
|
||||
class="ml10"
|
||||
v-model:value="agoraPara.token"
|
||||
placeholder="Token"
|
||||
@change="encodeToken"
|
||||
></a-input>
|
||||
<span class="ml10">Channel:</span>
|
||||
<a-input
|
||||
class="ml10"
|
||||
v-model:value="agoraPara.channel"
|
||||
@@ -73,14 +86,15 @@
|
||||
></a-input>
|
||||
</div>
|
||||
<div class="mt20 flex-row flex-justify-center flex-align-center">
|
||||
<a-button type="primary" large @click="onStart">Play</a-button>
|
||||
<a-button v-if="livePara.liveState && dronePara.isDockLive" type="primary" large @click="onSwitch">Switch Lens</a-button>
|
||||
<a-button v-else type="primary" large @click="onStart">Play</a-button>
|
||||
<a-button class="ml20" type="primary" large @click="onStop"
|
||||
>Stop</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onUpdateQuality"
|
||||
>Update Clarity</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onRefresh"
|
||||
<a-button v-if="!livePara.liveState || !dronePara.isDockLive" class="ml20" type="primary" large @click="onRefresh"
|
||||
>Refresh Live Capacity</a-button
|
||||
>
|
||||
</div>
|
||||
@@ -91,8 +105,9 @@
|
||||
import AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { uuidv4 } from '../utils/uuid'
|
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config'
|
||||
import { getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
|
||||
import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
|
||||
import { getRoot } from '/@/root'
|
||||
|
||||
const root = getRoot()
|
||||
@@ -120,6 +135,12 @@ const clarityList = [
|
||||
}
|
||||
]
|
||||
|
||||
interface SelectOption {
|
||||
value: any,
|
||||
label: string,
|
||||
more?: any
|
||||
}
|
||||
|
||||
let agoraClient = {} as IAgoraRTCClient
|
||||
const agoraPara = reactive({
|
||||
appid: config.agoraAPPID,
|
||||
@@ -130,13 +151,16 @@ const agoraPara = reactive({
|
||||
})
|
||||
const dronePara = reactive({
|
||||
livestreamSource: [],
|
||||
droneList: [] as any[],
|
||||
cameraList: [] as any[],
|
||||
videoList: [] as any[],
|
||||
droneSelected: '',
|
||||
cameraSelected: '',
|
||||
videoSelected: '',
|
||||
claritySelected: 0
|
||||
droneList: [] as SelectOption[],
|
||||
cameraList: [] as SelectOption[],
|
||||
videoList: [] as SelectOption[],
|
||||
droneSelected: undefined as string | undefined,
|
||||
cameraSelected: undefined as string | undefined,
|
||||
videoSelected: undefined as string | undefined,
|
||||
claritySelected: 0,
|
||||
lensList: [] as string[],
|
||||
lensSelected: undefined as string | undefined,
|
||||
isDockLive: false
|
||||
})
|
||||
const livePara = reactive({
|
||||
url: '',
|
||||
@@ -144,14 +168,15 @@ const livePara = reactive({
|
||||
videoId: '',
|
||||
liveState: false
|
||||
})
|
||||
const nonSwitchable = 'normal'
|
||||
|
||||
const onRefresh = async () => {
|
||||
dronePara.droneList = []
|
||||
dronePara.cameraList = []
|
||||
dronePara.videoList = []
|
||||
dronePara.droneSelected = ''
|
||||
dronePara.cameraSelected = ''
|
||||
dronePara.videoSelected = ''
|
||||
dronePara.droneSelected = undefined
|
||||
dronePara.cameraSelected = undefined
|
||||
dronePara.videoSelected = undefined
|
||||
await getLiveCapacity({})
|
||||
.then(res => {
|
||||
if (res.code === 0) {
|
||||
@@ -166,18 +191,20 @@ const onRefresh = async () => {
|
||||
|
||||
if (dronePara.livestreamSource) {
|
||||
dronePara.livestreamSource.forEach((ele: any) => {
|
||||
dronePara.droneList.push({ label: ele.name + '-' + ele.sn, value: ele.sn })
|
||||
dronePara.droneList.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(error)
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onRefresh()
|
||||
agoraPara.token = encodeURIComponent(agoraPara.token)
|
||||
agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' })
|
||||
// Subscribe when a remote user publishes a stream
|
||||
agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => {
|
||||
@@ -191,7 +218,7 @@ onMounted(() => {
|
||||
const remoteVideoTrack = user.videoTrack!
|
||||
// Dynamically create a container in the form of a DIV element for playing the remote video track.
|
||||
const remotePlayerContainer: any = document.getElementById('player')
|
||||
// remotePlayerContainer.id = agoraPara.uid
|
||||
remotePlayerContainer.id = user.uid.toString()
|
||||
remoteVideoTrack.play(remotePlayerContainer)
|
||||
}
|
||||
})
|
||||
@@ -199,6 +226,10 @@ onMounted(() => {
|
||||
console.log('unpublish live:', user)
|
||||
message.info('unpublish live')
|
||||
})
|
||||
agoraClient.on('exception', async (e: any) => {
|
||||
console.log(e)
|
||||
message.error(e.msg)
|
||||
})
|
||||
})
|
||||
|
||||
const handleError = (err: any) => {
|
||||
@@ -207,7 +238,9 @@ const handleError = (err: any) => {
|
||||
const handleJoinChannel = (uid: any) => {
|
||||
agoraPara.uid = uid
|
||||
}
|
||||
|
||||
const encodeToken = (e: any) => {
|
||||
agoraPara.token = encodeURIComponent(agoraPara.token)
|
||||
}
|
||||
const onStart = async () => {
|
||||
const that = this
|
||||
console.log(
|
||||
@@ -222,13 +255,12 @@ const onStart = async () => {
|
||||
if (
|
||||
dronePara.droneSelected == null ||
|
||||
dronePara.cameraSelected == null ||
|
||||
dronePara.videoSelected == null ||
|
||||
dronePara.claritySelected == null
|
||||
) {
|
||||
message.warn('waring: not select live para!!!')
|
||||
return
|
||||
}
|
||||
agoraClient.setClientRole('audience', { level: 1 })
|
||||
agoraClient.setClientRole('audience', { level: 2 })
|
||||
if (agoraClient.connectionState === 'DISCONNECTED') {
|
||||
agoraClient
|
||||
.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
|
||||
@@ -236,11 +268,8 @@ const onStart = async () => {
|
||||
livePara.videoId =
|
||||
dronePara.droneSelected +
|
||||
'/' +
|
||||
dronePara.cameraSelected +
|
||||
'/' +
|
||||
dronePara.videoSelected
|
||||
dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0')
|
||||
console.log(agoraPara)
|
||||
agoraPara.token = encodeURIComponent(agoraPara.token)
|
||||
|
||||
livePara.url =
|
||||
'channel=' +
|
||||
@@ -259,6 +288,9 @@ const onStart = async () => {
|
||||
video_quality: dronePara.claritySelected
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
livePara.liveState = true
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -269,9 +301,8 @@ const onStop = async () => {
|
||||
livePara.videoId =
|
||||
dronePara.droneSelected +
|
||||
'/' +
|
||||
dronePara.cameraSelected +
|
||||
'/' +
|
||||
dronePara.videoSelected
|
||||
dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0')
|
||||
|
||||
stopLivestream({
|
||||
video_id: livePara.videoId
|
||||
}).then(res => {
|
||||
@@ -279,52 +310,52 @@ const onStop = async () => {
|
||||
message.success(res.message)
|
||||
}
|
||||
livePara.liveState = false
|
||||
dronePara.lensSelected = ''
|
||||
console.log('stop play livestream')
|
||||
})
|
||||
}
|
||||
const onDroneSelect = (val: any) => {
|
||||
dronePara.droneSelected = val
|
||||
if (dronePara.droneSelected) {
|
||||
const droneTemp = dronePara.livestreamSource
|
||||
dronePara.cameraList = []
|
||||
const onDroneSelect = (val: SelectOption) => {
|
||||
dronePara.cameraList = []
|
||||
dronePara.videoList = []
|
||||
dronePara.lensList = []
|
||||
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.cameras_list && drone.sn === dronePara.droneSelected) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
dronePara.cameraList.push({ label: ele.name, value: ele.index })
|
||||
})
|
||||
}
|
||||
})
|
||||
dronePara.cameraSelected = undefined
|
||||
dronePara.videoSelected = undefined
|
||||
dronePara.lensSelected = undefined
|
||||
dronePara.droneSelected = val.value
|
||||
if (!val.more) {
|
||||
return
|
||||
}
|
||||
val.more.forEach((ele: any) => {
|
||||
dronePara.cameraList.push({ label: ele.name, value: ele.index, more: ele.videos_list })
|
||||
})
|
||||
}
|
||||
const onCameraSelect = (val: any) => {
|
||||
dronePara.cameraSelected = val
|
||||
const onCameraSelect = (val: SelectOption) => {
|
||||
dronePara.cameraSelected = val.value
|
||||
dronePara.videoSelected = undefined
|
||||
dronePara.lensSelected = undefined
|
||||
dronePara.videoList = []
|
||||
dronePara.lensList = []
|
||||
if (!val.more) {
|
||||
return
|
||||
}
|
||||
|
||||
if (dronePara.cameraSelected) {
|
||||
const droneTemp = dronePara.livestreamSource
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.sn === dronePara.droneSelected) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
const camera = ele
|
||||
if (camera.index === dronePara.cameraSelected) {
|
||||
const videoListTemp = camera.videos_list
|
||||
dronePara.videoList = []
|
||||
videoListTemp.forEach((ele: any) => {
|
||||
dronePara.videoList.push({ label: ele.type, value: ele.index })
|
||||
})
|
||||
dronePara.videoSelected = dronePara.videoList[0]?.value
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
val.more.forEach((ele: any) => {
|
||||
dronePara.videoList.push({ label: ele.type, value: ele.index, more: ele.switch_video_types })
|
||||
})
|
||||
if (dronePara.videoList.length === 0) {
|
||||
return
|
||||
}
|
||||
const firstVideo: SelectOption = dronePara.videoList[0]
|
||||
dronePara.videoSelected = firstVideo.value
|
||||
dronePara.lensList = firstVideo.more
|
||||
dronePara.lensSelected = firstVideo.label
|
||||
dronePara.isDockLive = dronePara.lensList.length > 0
|
||||
}
|
||||
const onVideoSelect = (val: any) => {
|
||||
dronePara.videoSelected = val
|
||||
const onVideoSelect = (val: SelectOption) => {
|
||||
dronePara.videoSelected = val.value
|
||||
dronePara.lensList = val.more
|
||||
dronePara.lensSelected = val.label
|
||||
}
|
||||
const onClaritySelect = (val: any) => {
|
||||
dronePara.claritySelected = val
|
||||
@@ -343,6 +374,21 @@ const onUpdateQuality = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onSwitch = () => {
|
||||
if (dronePara.lensSelected === undefined || dronePara.lensSelected === nonSwitchable) {
|
||||
message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8)
|
||||
return
|
||||
}
|
||||
changeLivestreamLens({
|
||||
video_id: livePara.videoId,
|
||||
video_type: dronePara.lensSelected
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Switching live camera successfully.')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
class="mt20"
|
||||
></video>
|
||||
<p class="fz24">Live streaming source selection</p>
|
||||
|
||||
<div class="flex-row flex-justify-center flex-align-center mt10">
|
||||
<template v-if="liveState && isDockLive">
|
||||
<span class="mr10">Lens:</span>
|
||||
<a-radio-group v-model:value="lensSelected" button-style="solid">
|
||||
<a-radio-button v-for="lens in lensList" :key="lens" :value="lens">{{lens}}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-select
|
||||
style="width: 150px"
|
||||
placeholder="Select Live Type"
|
||||
@select="onLiveTypeSelect"
|
||||
v-model:value="livetypeSelected"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in liveTypeList"
|
||||
@@ -26,12 +35,13 @@
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Drone"
|
||||
@select="onDroneSelect"
|
||||
v-model:value="droneSelected"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in droneList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
@click="onDroneSelect(item)"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
@@ -39,12 +49,13 @@
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Camera"
|
||||
@select="onCameraSelect"
|
||||
v-model:value="cameraSelected"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in cameraList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
@click="onCameraSelect(item)"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
@@ -52,21 +63,23 @@
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Lens"
|
||||
@select="onVideoSelect"
|
||||
v-model:value="videoSelected"
|
||||
>
|
||||
<a-select-option
|
||||
class="ml10"
|
||||
v-for="item in videoList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
@click="onVideoSelect(item)"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select> -->
|
||||
</template>
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Clarity"
|
||||
@select="onClaritySelect"
|
||||
v-model:value="claritySelected"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in clarityList"
|
||||
@@ -85,14 +98,15 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt10 flex-row flex-justify-center flex-align-center">
|
||||
<a-button type="primary" large @click="onStart">Play</a-button>
|
||||
<a-button v-if="liveState && isDockLive" type="primary" large @click="onSwitch">Switch Lens</a-button>
|
||||
<a-button v-else type="primary" large @click="onStart">Play</a-button>
|
||||
<a-button class="ml20" type="primary" large @click="onStop"
|
||||
>Stop</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onUpdateQuality"
|
||||
>Update Clarity</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onRefresh"
|
||||
<a-button v-if="!liveState || !isDockLive" class="ml20" type="primary" large @click="onRefresh"
|
||||
>Refresh Live Capacity</a-button
|
||||
>
|
||||
</div>
|
||||
@@ -103,12 +117,18 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config'
|
||||
import { getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
|
||||
import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
|
||||
import { getRoot } from '/@/root'
|
||||
import jswebrtc from '/@/vendors/jswebrtc.min.js'
|
||||
const root = getRoot()
|
||||
|
||||
const liveTypeList = [
|
||||
interface SelectOption {
|
||||
value: any,
|
||||
label: string,
|
||||
more?: any
|
||||
}
|
||||
|
||||
const liveTypeList: SelectOption[] = [
|
||||
{
|
||||
value: 1,
|
||||
label: 'RTMP'
|
||||
@@ -122,7 +142,7 @@ const liveTypeList = [
|
||||
label: 'GB28181'
|
||||
}
|
||||
]
|
||||
const clarityList = [
|
||||
const clarityList: SelectOption[] = [
|
||||
{
|
||||
value: 0,
|
||||
label: 'Adaptive'
|
||||
@@ -152,12 +172,16 @@ const cameraList = ref()
|
||||
const videoList = ref()
|
||||
const droneSelected = ref()
|
||||
const cameraSelected = ref()
|
||||
const videoSeleted = ref()
|
||||
const claritySeleted = ref()
|
||||
const videoSelected = ref()
|
||||
const claritySelected = ref()
|
||||
const videoId = ref()
|
||||
const liveState = ref<boolean>(false)
|
||||
const livetypeSelected = ref()
|
||||
const rtspData = ref()
|
||||
const lensList = ref<string[]>([])
|
||||
const lensSelected = ref<String>()
|
||||
const isDockLive = ref(false)
|
||||
const nonSwitchable = 'normal'
|
||||
|
||||
const onRefresh = async () => {
|
||||
droneList.value = []
|
||||
@@ -165,7 +189,7 @@ const onRefresh = async () => {
|
||||
videoList.value = []
|
||||
droneSelected.value = null
|
||||
cameraSelected.value = null
|
||||
videoSeleted.value = null
|
||||
videoSelected.value = null
|
||||
await getLiveCapacity({})
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
@@ -178,16 +202,17 @@ const onRefresh = async () => {
|
||||
console.log('live_capacity:', resData)
|
||||
livestreamSource.value = resData
|
||||
|
||||
const temp: Array<{}> = []
|
||||
const temp: Array<SelectOption> = []
|
||||
if (livestreamSource.value) {
|
||||
livestreamSource.value.forEach((ele: any) => {
|
||||
temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn })
|
||||
temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list })
|
||||
})
|
||||
droneList.value = temp
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(error)
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
@@ -201,22 +226,22 @@ const onStart = async () => {
|
||||
livetypeSelected.value,
|
||||
droneSelected.value,
|
||||
cameraSelected.value,
|
||||
videoSeleted.value,
|
||||
claritySeleted.value
|
||||
videoSelected.value,
|
||||
claritySelected.value
|
||||
)
|
||||
const timestamp = new Date().getTime().toString()
|
||||
if (
|
||||
livetypeSelected.value == null ||
|
||||
droneSelected.value == null ||
|
||||
cameraSelected.value == null ||
|
||||
videoSeleted.value == null ||
|
||||
claritySeleted.value == null
|
||||
claritySelected.value == null
|
||||
) {
|
||||
message.warn('waring: not select live para!!!')
|
||||
return
|
||||
}
|
||||
videoId.value =
|
||||
droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value
|
||||
droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')
|
||||
|
||||
let liveURL = ''
|
||||
switch (livetypeSelected.value) {
|
||||
case 1: {
|
||||
@@ -241,9 +266,12 @@ const onStart = async () => {
|
||||
url: liveURL,
|
||||
video_id: videoId.value,
|
||||
url_type: livetypeSelected.value,
|
||||
video_quality: claritySeleted.value
|
||||
video_quality: claritySelected.value
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
if (livetypeSelected.value === 3) {
|
||||
const url = res.data.url
|
||||
const videoElement = videowebrtc.value
|
||||
@@ -259,7 +287,6 @@ const onStart = async () => {
|
||||
console.log('start play livestream')
|
||||
}
|
||||
})
|
||||
liveState.value = true
|
||||
}
|
||||
})
|
||||
} else if (livetypeSelected.value === 2) {
|
||||
@@ -281,10 +308,10 @@ const onStart = async () => {
|
||||
autoplay: true,
|
||||
onPlay: (obj: any) => {
|
||||
console.log('start play livestream')
|
||||
liveState.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
liveState.value = true
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
@@ -292,13 +319,15 @@ const onStart = async () => {
|
||||
}
|
||||
const onStop = () => {
|
||||
videoId.value =
|
||||
droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value
|
||||
droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')
|
||||
|
||||
stopLivestream({
|
||||
video_id: videoId.value
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.info(res.message)
|
||||
message.success(res.message)
|
||||
liveState.value = false
|
||||
lensSelected.value = undefined
|
||||
console.log('stop play livestream')
|
||||
}
|
||||
})
|
||||
@@ -311,10 +340,10 @@ const onUpdateQuality = () => {
|
||||
}
|
||||
setLivestreamQuality({
|
||||
video_id: videoId.value,
|
||||
video_quality: claritySeleted.value
|
||||
video_quality: claritySelected.value
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Set the clarity to ' + clarityList[claritySeleted.value].label)
|
||||
message.success('Set the clarity to ' + clarityList[claritySelected.value].label)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -322,54 +351,66 @@ const onUpdateQuality = () => {
|
||||
const onLiveTypeSelect = (val: any) => {
|
||||
livetypeSelected.value = val
|
||||
}
|
||||
const onDroneSelect = (val: any) => {
|
||||
droneSelected.value = val
|
||||
const temp: Array<{}> = []
|
||||
const onDroneSelect = (val: SelectOption) => {
|
||||
droneSelected.value = val.value
|
||||
const temp: Array<SelectOption> = []
|
||||
cameraList.value = []
|
||||
if (droneSelected.value) {
|
||||
const droneTemp = livestreamSource.value
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.cameras_list && drone.sn === droneSelected.value) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
console.info(ele)
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
temp.push({ label: ele.name, value: ele.index })
|
||||
})
|
||||
cameraList.value = temp
|
||||
}
|
||||
})
|
||||
cameraSelected.value = undefined
|
||||
videoSelected.value = undefined
|
||||
videoList.value = []
|
||||
lensList.value = []
|
||||
if (!val.more) {
|
||||
return
|
||||
}
|
||||
val.more.forEach((ele: any) => {
|
||||
temp.push({ label: ele.name, value: ele.index, more: ele.videos_list })
|
||||
})
|
||||
cameraList.value = temp
|
||||
}
|
||||
const onCameraSelect = (val: any) => {
|
||||
cameraSelected.value = val
|
||||
const result: Array<{}> = []
|
||||
if (cameraSelected.value) {
|
||||
const droneTemp = livestreamSource.value
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.sn === droneSelected.value) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
const camera = ele
|
||||
if (camera.index === cameraSelected.value) {
|
||||
const videoListTemp = camera.videos_list
|
||||
videoListTemp.forEach((ele: any) => {
|
||||
result.push({ label: ele.type, value: ele.index })
|
||||
})
|
||||
videoList.value = result
|
||||
videoSeleted.value = videoList.value[0]?.value
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
const onCameraSelect = (val: SelectOption) => {
|
||||
cameraSelected.value = val.value
|
||||
const result: Array<SelectOption> = []
|
||||
videoSelected.value = undefined
|
||||
videoList.value = []
|
||||
lensList.value = []
|
||||
if (!val.more) {
|
||||
return
|
||||
}
|
||||
|
||||
val.more.forEach((ele: any) => {
|
||||
result.push({ label: ele.type, value: ele.index, more: ele.switch_video_types })
|
||||
})
|
||||
videoList.value = result
|
||||
if (videoList.value.length === 0) {
|
||||
return
|
||||
}
|
||||
const firstVideo: SelectOption = videoList.value[0]
|
||||
videoSelected.value = firstVideo.value
|
||||
lensList.value = firstVideo.more
|
||||
lensSelected.value = firstVideo.label
|
||||
isDockLive.value = lensList.value.length > 0
|
||||
}
|
||||
const onVideoSelect = (val: any) => {
|
||||
videoSeleted.value = val
|
||||
const onVideoSelect = (val: SelectOption) => {
|
||||
videoSelected.value = val.value
|
||||
lensList.value = val.more
|
||||
lensSelected.value = val.label
|
||||
}
|
||||
const onClaritySelect = (val: any) => {
|
||||
claritySeleted.value = val
|
||||
claritySelected.value = val
|
||||
}
|
||||
const onSwitch = () => {
|
||||
if (lensSelected.value === undefined || lensSelected.value === nonSwitchable) {
|
||||
message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8)
|
||||
return
|
||||
}
|
||||
changeLivestreamLens({
|
||||
video_id: videoId.value,
|
||||
video_type: lensSelected.value
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Switching live camera successfully.')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+106
-29
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="plan">
|
||||
<div class="create-plan-wrapper">
|
||||
<div class="header">
|
||||
Create Plan
|
||||
</div>
|
||||
@@ -8,6 +8,7 @@
|
||||
<a-form-item label="Plan Name" name="name" :labelCol="{span: 24}">
|
||||
<a-input style="background: black;" placeholder="Please enter plan name" v-model:value="planBody.name"/>
|
||||
</a-form-item>
|
||||
<!-- 航线 -->
|
||||
<a-form-item label="Flight Route" :wrapperCol="{offset: 7}" name="file_id">
|
||||
<router-link
|
||||
:to="{name: 'select-plan'}"
|
||||
@@ -40,6 +41,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<!-- 设备 -->
|
||||
<a-form-item label="Device" :wrapperCol="{offset: 10}" v-model:value="planBody.dock_sn" name="dock_sn">
|
||||
<router-link
|
||||
:to="{name: 'select-plan'}"
|
||||
@@ -59,13 +61,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="Immediate">
|
||||
<a-switch v-model:checked="planBody.immediate">
|
||||
<template #checkedChildren><CheckOutlined /></template>
|
||||
<template #unCheckedChildren><CloseOutlined /></template>
|
||||
</a-switch>
|
||||
<!-- 任务类型 -->
|
||||
<a-form-item label="Plan Timer" class="plan-timer-form-item">
|
||||
<div style="white-space: nowrap;">
|
||||
<a-radio-group v-model:value="planBody.task_type" button-style="solid">
|
||||
<a-radio-button :value="TaskType.Immediate">Immediate</a-radio-button>
|
||||
<a-radio-button :value="TaskType.Single">Timed&One-Time</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item style="position: absolute; bottom: 0px; margin-bottom: 0; margin-left: -10px; width: 280px;">
|
||||
<!-- 执行时间 -->
|
||||
<a-form-item label="Start Time" v-if="planBody.task_type === TaskType.Single" name="select_execute_time">
|
||||
<a-date-picker
|
||||
v-model:value="planBody.select_execute_time"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
placeholder="Select Time"
|
||||
style="width: 280px;"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- RTH Altitude Relative to Dock -->
|
||||
<a-form-item label="RTH Altitude Relative to Dock (m)" :labelCol="{span: 24}" name="rth_altitude">
|
||||
<a-input v-model:value="planBody.rth_altitude" style="background: black !important;">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<!-- Lost Action -->
|
||||
<a-form-item label="Lost Action" :labelCol="{span: 24}" name="out_of_control_action">
|
||||
<div style="white-space: nowrap;">
|
||||
<a-radio-group v-model:value="planBody.out_of_control_action" button-style="solid">
|
||||
<a-radio-button v-for="action in OutOfControlActionOptions" :value="action.value" :key="action.value">
|
||||
{{ action.label }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item style="width: 280px;">
|
||||
<div class="footer">
|
||||
<a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel
|
||||
</a-button>
|
||||
@@ -88,14 +118,16 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'
|
||||
import { CheckOutlined, CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { ELocalStorageKey, ERouterName } from '/@/types'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { WaylineFile } from '/@/types/wayline'
|
||||
import { WaylineType, WaylineFile } from '/@/types/wayline'
|
||||
import { Device, EDeviceType } from '/@/types/device'
|
||||
import { createPlan, CreatePlan } from '/@/api/wayline'
|
||||
import { getRoot } from '/@/root'
|
||||
import { TaskType, OutOfControlActionOptions, OutOfControlAction } from '/@/types/task'
|
||||
import moment, { Moment } from 'moment'
|
||||
import { RuleObject } from 'ant-design-vue/es/form/interface'
|
||||
|
||||
const root = getRoot()
|
||||
const store = useMyStore()
|
||||
@@ -113,13 +145,16 @@ const dock = computed<Device>(() => {
|
||||
const disabled = ref(false)
|
||||
|
||||
const routeName = ref('')
|
||||
const planBody: UnwrapRef<CreatePlan> = reactive({
|
||||
const planBody = reactive({
|
||||
name: '',
|
||||
file_id: computed(() => store.state.waylineInfo.id),
|
||||
dock_sn: computed(() => store.state.dockInfo.device_sn),
|
||||
immediate: false,
|
||||
type: 'wayline'
|
||||
task_type: TaskType.Immediate,
|
||||
select_execute_time: undefined as Moment| undefined,
|
||||
rth_altitude: '',
|
||||
out_of_control_action: OutOfControlAction.ReturnToHome,
|
||||
})
|
||||
|
||||
const drawerVisible = ref(false)
|
||||
const valueRef = ref()
|
||||
const rules = {
|
||||
@@ -128,21 +163,42 @@ const rules = {
|
||||
{ max: 20, message: 'Length should be 1 to 20', trigger: 'blur' }
|
||||
],
|
||||
file_id: [{ required: true, message: 'Select Route' }],
|
||||
dock_sn: [{ required: true, message: 'Select Device' }]
|
||||
dock_sn: [{ required: true, message: 'Select Device' }],
|
||||
select_execute_time: [{ required: true, message: 'Select start time' }],
|
||||
rth_altitude: [
|
||||
{
|
||||
validator: async (rule: RuleObject, value: string) => {
|
||||
if (!/^[0-9]{1,}$/.test(value)) {
|
||||
throw new Error('RTH Altitude Relative Require number')
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
out_of_control_action: [{ required: true, message: 'Select Lost Action' }],
|
||||
}
|
||||
|
||||
function onSubmit () {
|
||||
valueRef.value.validate().then(() => {
|
||||
disabled.value = true
|
||||
createPlan(workspaceId, planBody)
|
||||
const createPlanBody = { ...planBody } as unknown as CreatePlan
|
||||
if (planBody.select_execute_time) {
|
||||
createPlanBody.execute_time = moment(planBody.select_execute_time).valueOf()
|
||||
}
|
||||
createPlanBody.rth_altitude = Number(createPlanBody.rth_altitude)
|
||||
if (wayline.value && wayline.value.template_types && wayline.value.template_types.length > 0) {
|
||||
createPlanBody.wayline_type = wayline.value.template_types[0]
|
||||
}
|
||||
// console.log('planBody', createPlanBody)
|
||||
createPlan(workspaceId, createPlanBody)
|
||||
.then(res => {
|
||||
message.success('Saved Successfully')
|
||||
setTimeout(() => {
|
||||
disabled.value = false
|
||||
}, 1500)
|
||||
}).finally(() => {
|
||||
closePlan()
|
||||
})
|
||||
}).catch((e: any) => {
|
||||
console.log('validate err', e)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -167,16 +223,16 @@ function selectDevice () {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.plan {
|
||||
.create-plan-wrapper {
|
||||
background-color: #232323;
|
||||
color: white;
|
||||
color: fff;
|
||||
padding-bottom: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
height: 53px;
|
||||
height: 52px;
|
||||
border-bottom: 1px solid #4f4f4f;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
@@ -184,30 +240,50 @@ function selectDevice () {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
height: calc(100% - 54px);
|
||||
overflow-y: auto;
|
||||
|
||||
form {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
form label, input {
|
||||
color: white;
|
||||
background-color: #232323;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ant-input-suffix {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.plan-timer-form-item {
|
||||
// flex-direction: column;
|
||||
|
||||
.ant-radio-button-wrapper{
|
||||
background-color: #232323;
|
||||
color: #fff;
|
||||
|
||||
&.ant-radio-button-wrapper-checked{
|
||||
background-color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #4f4f4f;
|
||||
min-height: 65px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
padding:10px 0;
|
||||
|
||||
button {
|
||||
width: 45%;
|
||||
color: white;
|
||||
color: #fff ;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wayline-panel {
|
||||
background: #3c3c3c;
|
||||
margin-left: auto;
|
||||
@@ -228,6 +304,7 @@ function selectDevice () {
|
||||
margin: 0px 10px 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: #3c3c3c;
|
||||
margin-left: auto;
|
||||
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="header">Task Plan Library</div>
|
||||
<div class="plan-panel-wrapper">
|
||||
<a-table class="plan-table" :columns="columns" :data-source="plansData.data" row-key="job_id"
|
||||
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
|
||||
<!-- 执行时间 -->
|
||||
<template #duration="{ record }">
|
||||
<div>
|
||||
<div>{{ formatTaskTime(record.execute_time) }}</div>
|
||||
<div>{{ formatTaskTime(record.end_time) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 任务类型 -->
|
||||
<template #taskType="{ record }">
|
||||
<div>{{ formatTaskType(record) }}</div>
|
||||
</template>
|
||||
<!-- 失控动作 -->
|
||||
<template #lostAction="{ record }">
|
||||
<div>{{ formatLostAction(record) }}</div>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="{ record }">
|
||||
<div>
|
||||
<div class="flex-display flex-align-center">
|
||||
<span class="circle-icon" :style="{backgroundColor: formatTaskStatus(record).color}"></span>
|
||||
{{ formatTaskStatus(record).text }}
|
||||
<a-tooltip v-if="!!record.code" placement="bottom" arrow-point-at-center >
|
||||
<template #title>
|
||||
<div>{{ getCodeMessage(record.code) }}</div>
|
||||
</template>
|
||||
<exclamation-circle-outlined class="ml5" :style="{color: commonColor.WARN, fontSize: '16px' }"/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div v-if="record.status === TaskStatus.Carrying">
|
||||
<a-progress :percent="record.progress || 0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #action="{ record }">
|
||||
<span class="action-area">
|
||||
<a-popconfirm
|
||||
v-if="record.status === TaskStatus.Wait"
|
||||
title="Are you sure you want to delete flight task?"
|
||||
ok-text="Yes"
|
||||
cancel-text="No"
|
||||
@confirm="onDeleteTask(record.job_id)"
|
||||
>
|
||||
<a-button type="primary" size="small">Delete</a-button>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from '@vue/reactivity'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { TableState } from 'ant-design-vue/lib/table/interface'
|
||||
import { onMounted } from 'vue'
|
||||
import { IPage } from '/@/api/http/type'
|
||||
import { deleteTask, getWaylineJobs, Task } from '/@/api/wayline'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { ELocalStorageKey } from '/@/types/enums'
|
||||
import { useFormatTask } from './use-format-task'
|
||||
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap } from '/@/types/task'
|
||||
import { useTaskProgressEvent } from './use-task-progress-event'
|
||||
import { getErrorMessage } from '/@/utils/error-code/index'
|
||||
import { commonColor } from '/@/utils/color'
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
const store = useMyStore()
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
const paginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Planned/Actual Time',
|
||||
dataIndex: 'duration',
|
||||
width: 180,
|
||||
slots: { customRender: 'duration' },
|
||||
},
|
||||
{
|
||||
title: 'Plan Name',
|
||||
dataIndex: 'job_name',
|
||||
width: 150,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
dataIndex: 'taskType',
|
||||
width: 120,
|
||||
slots: { customRender: 'taskType' },
|
||||
},
|
||||
{
|
||||
title: 'Flight Route Name',
|
||||
dataIndex: 'file_name',
|
||||
width: 150,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Dock Name',
|
||||
dataIndex: 'dock_name',
|
||||
width: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'RTH Altitude Relative to Dock (m)',
|
||||
dataIndex: 'rth_altitude',
|
||||
width: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Lost Action',
|
||||
dataIndex: 'out_of_control_action',
|
||||
width: 120,
|
||||
slots: { customRender: 'lostAction' },
|
||||
},
|
||||
{
|
||||
title: 'Creator',
|
||||
dataIndex: 'username',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
key: 'status',
|
||||
width: 200,
|
||||
slots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
width: 120,
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
type Pagination = TableState['pagination']
|
||||
|
||||
const plansData = reactive({
|
||||
data: [] as Task[]
|
||||
})
|
||||
|
||||
const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus } = useFormatTask()
|
||||
|
||||
// 设备任务执行进度更新
|
||||
function onTaskProgressWs (data: TaskProgressInfo) {
|
||||
const { bid, output } = data
|
||||
if (output) {
|
||||
const { status, progress } = output || {}
|
||||
const taskItem = plansData.data.find(task => task.job_id === bid)
|
||||
if (!taskItem) return
|
||||
if (status) {
|
||||
taskItem.status = TaskProgressWsStatusMap[status]
|
||||
// 执行中,更新进度
|
||||
if (status === TaskProgressStatus.Sent || status === TaskProgressStatus.inProgress) {
|
||||
taskItem.progress = progress?.percent || 0
|
||||
} else if ([TaskProgressStatus.Rejected, TaskProgressStatus.Canceled, TaskProgressStatus.Timeout, TaskProgressStatus.Failed, TaskProgressStatus.OK].includes(status)) {
|
||||
getPlans()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCodeMessage (code: number) {
|
||||
return getErrorMessage(code) + `(code: ${code})`
|
||||
}
|
||||
|
||||
useTaskProgressEvent(onTaskProgressWs)
|
||||
|
||||
onMounted(() => {
|
||||
getPlans()
|
||||
})
|
||||
|
||||
function getPlans () {
|
||||
getWaylineJobs(workspaceId, body).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
plansData.data = res.data.list
|
||||
paginationProp.total = res.data.pagination.total
|
||||
paginationProp.current = res.data.pagination.page
|
||||
})
|
||||
}
|
||||
|
||||
function refreshData (page: Pagination) {
|
||||
body.page = page?.current!
|
||||
body.page_size = page?.pageSize!
|
||||
getPlans()
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
async function onDeleteTask (jobId: string) {
|
||||
const { code } = await deleteTask(workspaceId, {
|
||||
job_id: jobId
|
||||
})
|
||||
if (code === 0) {
|
||||
message.success('Deleted successfully')
|
||||
getPlans()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plan-panel-wrapper {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
.plan-table {
|
||||
background: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.action-area {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.circle-icon {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 3px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-align: start;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'
|
||||
import { Task } from '/@/api/wayline'
|
||||
import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap } from '/@/types/task'
|
||||
|
||||
export function useFormatTask () {
|
||||
function formatTaskType (task: Task) {
|
||||
return TaskTypeMap[task.task_type] || DEFAULT_PLACEHOLDER
|
||||
}
|
||||
|
||||
function formatTaskTime (time: string) {
|
||||
return time || DEFAULT_PLACEHOLDER
|
||||
}
|
||||
|
||||
function formatLostAction (task: Task) {
|
||||
return OutOfControlActionMap[task.out_of_control_action] || DEFAULT_PLACEHOLDER
|
||||
}
|
||||
|
||||
function formatTaskStatus (task: Task) {
|
||||
const statusObj = {
|
||||
text: '',
|
||||
color: ''
|
||||
}
|
||||
const { status } = task
|
||||
statusObj.text = TaskStatusMap[status]
|
||||
statusObj.color = TaskStatusColor[status]
|
||||
return statusObj
|
||||
}
|
||||
|
||||
return {
|
||||
formatTaskType,
|
||||
formatTaskTime,
|
||||
formatLostAction,
|
||||
formatTaskStatus,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import EventBus from '/@/event-bus/'
|
||||
import { onMounted, onBeforeUnmount } from 'vue'
|
||||
import { TaskProgressInfo } from '/@/types/task'
|
||||
|
||||
export function useTaskProgressEvent (onTaskProgressWs: (data: TaskProgressInfo) => void): void {
|
||||
function handleTaskProgress (payload: any) {
|
||||
onTaskProgressWs(payload.data)
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
// console.log('payload', payload.data)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
EventBus.on('deviceTaskProgress', handleTaskProgress)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
EventBus.off('deviceTaskProgress', handleTaskProgress)
|
||||
})
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<template>
|
||||
<div class="panel-wrapper" draggable="true">
|
||||
<div class="header">Wayline Library</div>
|
||||
<a-button type="primary" style="margin-top:20px" @click="onRefresh"
|
||||
>Refresh</a-button
|
||||
>
|
||||
<a-table class="table" :columns="columns" :data-source="data">
|
||||
<template #name="{ text, record }">
|
||||
<a :href="record.preview_url">{{ text }}</a>
|
||||
</template>
|
||||
<template #action>
|
||||
<span class="action-area">
|
||||
action
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from '@vue/reactivity'
|
||||
import { onMounted } from 'vue'
|
||||
import { ELocalStorageKey } from '../types/enums'
|
||||
import { getWaylineFiles } from '/@/api/wayline'
|
||||
const columns = [
|
||||
{
|
||||
title: 'FileName',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
slots: { customRender: 'name' }
|
||||
},
|
||||
{
|
||||
title: 'TemplateType',
|
||||
dataIndex: 'template_type',
|
||||
key: 'template_type'
|
||||
},
|
||||
{
|
||||
title: 'Favorited',
|
||||
dataIndex: 'favorite',
|
||||
key: 'favorite'
|
||||
},
|
||||
{
|
||||
title: 'DroneType',
|
||||
dataIndex: 'drone_type',
|
||||
key: 'drone_type'
|
||||
},
|
||||
{
|
||||
title: 'PayloadType',
|
||||
dataIndex: 'payload_type',
|
||||
key: 'payload_type'
|
||||
},
|
||||
{
|
||||
title: 'User',
|
||||
dataIndex: 'user',
|
||||
key: 'user'
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
key: 'action',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
const data = ref([
|
||||
{
|
||||
key: '1',
|
||||
name: 'name1',
|
||||
template_type: '0',
|
||||
drone_type: '0-60-0',
|
||||
payload_type: 'PM320_DUAL',
|
||||
user: 'pilot',
|
||||
favorited: 'true'
|
||||
}
|
||||
])
|
||||
onMounted(() => {
|
||||
onRefresh()
|
||||
})
|
||||
const onRefresh = async () => {
|
||||
const wid: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)
|
||||
data.value = []
|
||||
const index = 1
|
||||
const res = await getWaylineFiles(wid, {
|
||||
page: 1, // 页数
|
||||
page_size: 9, // 每页大小
|
||||
order_by: 'update_time desc' // 排序, xxx_column_desc, xxx_column_asc, xxx_column(default asc)
|
||||
})
|
||||
console.log(res)
|
||||
res.data.list.forEach(ele => {
|
||||
data.value.push({
|
||||
key: index.toString(),
|
||||
name: ele.name,
|
||||
template_type: ele.template_types[0],
|
||||
drone_type: ele.drone_model_key,
|
||||
payload_type: ele.payload_model_keys[0],
|
||||
user: ele.user_name,
|
||||
favorite: ele.favorited.toString()
|
||||
})
|
||||
})
|
||||
console.log('wayline files:', data.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.panel-wrapper {
|
||||
width: 100%;
|
||||
.table {
|
||||
background: #fff;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 16px 24px;
|
||||
font-size: 20px;
|
||||
text-align: start;
|
||||
color: #000;
|
||||
}
|
||||
.action-area {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,9 @@
|
||||
import mitt, { Emitter } from 'mitt'
|
||||
|
||||
type Events = {
|
||||
deviceUpgrade: any;
|
||||
deviceLogUploadProgress: any
|
||||
deviceUpgrade: any; // 设备升级
|
||||
deviceLogUploadProgress: any // 设备日志上传
|
||||
deviceTaskProgress: any // 设备任务进度
|
||||
};
|
||||
|
||||
const emitter: Emitter<Events> = mitt<Events>()
|
||||
|
||||
@@ -3,18 +3,19 @@ import { getRoot } from '/@/root'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { getDeviceBySn } from '/@/api/manage'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dockIcon from '/@/assets/icons/dock.png'
|
||||
import rcIcon from '/@/assets/icons/rc.png'
|
||||
import droneIcon from '/@/assets/icons/drone.png'
|
||||
|
||||
export function deviceTsaUpdate () {
|
||||
const root = getRoot()
|
||||
const AMap = root.$aMap
|
||||
|
||||
const icons: {
|
||||
[key: string]: string
|
||||
} = {
|
||||
'sub-device': '/@/assets/icons/drone.png',
|
||||
'gateway': '/@/assets/icons/rc.png',
|
||||
'dock': '/@/assets/icons/dock.png'
|
||||
}
|
||||
const icons = new Map([
|
||||
['sub-device', droneIcon],
|
||||
['gateway', rcIcon],
|
||||
['dock', dockIcon]
|
||||
])
|
||||
const markers = store.state.markerInfo.coverMap
|
||||
const paths = store.state.markerInfo.pathMap
|
||||
|
||||
@@ -33,12 +34,17 @@ export function deviceTsaUpdate () {
|
||||
|
||||
function initIcon (type: string) {
|
||||
return new AMap.Icon({
|
||||
image: icons[type],
|
||||
imageSize: new AMap.Size(40, 40)
|
||||
image: icons.get(type),
|
||||
imageSize: new AMap.Size(40, 40),
|
||||
size: new AMap.Size(40, 40)
|
||||
})
|
||||
}
|
||||
|
||||
function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) {
|
||||
if (AMap === undefined) {
|
||||
location.reload()
|
||||
return
|
||||
}
|
||||
if (markers[sn]) {
|
||||
return
|
||||
}
|
||||
@@ -50,7 +56,6 @@ export function deviceTsaUpdate () {
|
||||
offset: [0, -20],
|
||||
})
|
||||
root.$map.add(markers[sn])
|
||||
|
||||
// markers[sn].on('moving', function (e: any) {
|
||||
// let path = paths[sn]
|
||||
// if (!path) {
|
||||
|
||||
@@ -252,6 +252,12 @@ onMounted(() => {
|
||||
apiPilot.onBackClickReg()
|
||||
apiPilot.onStopPlatform()
|
||||
|
||||
window.connectCallback = arg => {
|
||||
connectCallback(arg)
|
||||
}
|
||||
window.wsConnectCallback = arg => {
|
||||
wsConnectCallback(arg)
|
||||
}
|
||||
device.data.gateway_sn = apiPilot.getRemoteControllerSN()
|
||||
if (device.data.gateway_sn === EStatusValue.DISCONNECT.toString()) {
|
||||
message.warn('Data is not available, please restart the remote control.')
|
||||
@@ -296,12 +302,6 @@ onMounted(() => {
|
||||
bindParam.user_id = res.data.user_id
|
||||
bindParam.workspace_id = res.data.workspace_id
|
||||
})
|
||||
window.connectCallback = arg => {
|
||||
connectCallback(arg)
|
||||
}
|
||||
window.wsConnectCallback = arg => {
|
||||
wsConnectCallback(arg)
|
||||
}
|
||||
})
|
||||
|
||||
const connectCallback = async (arg: any) => {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div v-if="docksData.data.length !== 0">
|
||||
<div v-for="dock in docksData.data" :key="dock.device_sn">
|
||||
<div v-if="dock.children" class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
|
||||
<div v-if="dock?.children" class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
|
||||
<div class="title">
|
||||
<a-tooltip :title="dock.nickname">
|
||||
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
|
||||
|
||||
@@ -5,34 +5,42 @@
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="20">Task Plan Library</a-col>
|
||||
<a-col :span="2">
|
||||
<span v-if="!createPlanTip">
|
||||
<router-link :to="{ name: 'create-plan'}">
|
||||
<PlusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = true"/>
|
||||
<span v-if="taskRoute">
|
||||
<router-link :to="{ name: ERouterName.CREATE_PLAN}">
|
||||
<PlusOutlined class="route-icon"/>
|
||||
</router-link>
|
||||
</span>
|
||||
<span v-else>
|
||||
<router-link :to="{ name: 'task'}">
|
||||
<MinusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = false"/>
|
||||
<router-link :to="{ name: ERouterName.TASK}">
|
||||
<MinusOutlined class="route-icon"/>
|
||||
</router-link>
|
||||
</span>
|
||||
</a-col>
|
||||
<a-col :span="1"></a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div v-if="createPlanTip">
|
||||
<router-view />
|
||||
<div v-if="!taskRoute">
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined, MinusOutlined } from '@ant-design/icons-vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ERouterName } from '/@/types/enums'
|
||||
|
||||
const createPlanTip = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
const taskRoute = computed(() => {
|
||||
return route.name === ERouterName.TASK
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.route-icon {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,12 +3,24 @@
|
||||
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
|
||||
<a-row>
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="22">Flight Route Library</a-col>
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="15">Flight Route Library</a-col>
|
||||
<a-col :span="8" v-if="importVisible" class="flex-row flex-justify-end flex-align-center">
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="false"
|
||||
:before-upload="beforeUpload"
|
||||
:show-upload-list="false"
|
||||
:customRequest="uploadFile"
|
||||
>
|
||||
<a-button type="text" style="color: white;">
|
||||
<SelectOutlined />
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="height-100">
|
||||
<a-spin :spinning="loading" :delay="1000" tip="downloading" size="large">
|
||||
<a-spin :spinning="loading" :delay="300" tip="downloading" size="large">
|
||||
<div class="scrollbar uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll">
|
||||
<div v-for="wayline in waylinesData.data" :key="wayline.id">
|
||||
<div class="wayline-panel" style="padding-top: 5px;" @click="selectRoute(wayline)">
|
||||
@@ -72,14 +84,17 @@
|
||||
import { reactive } from '@vue/reactivity'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, onUpdated, ref } from 'vue'
|
||||
import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles } from '/@/api/wayline'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline'
|
||||
import { ELocalStorageKey, ERouterName } from '/@/types'
|
||||
import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue'
|
||||
import { EDeviceType } from '/@/types/device'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { WaylineFile } from '/@/types/wayline'
|
||||
import { downloadFile } from '/@/utils/common'
|
||||
import { IPage } from '/@/api/http/type'
|
||||
import { CURRENT_CONFIG } from '/@/api/http/config'
|
||||
import { load } from '@amap/amap-jsapi-loader'
|
||||
import { getRoot } from '/@/root'
|
||||
|
||||
const loading = ref(false)
|
||||
const store = useMyStore()
|
||||
@@ -93,19 +108,28 @@ const waylinesData = reactive({
|
||||
data: [] as WaylineFile[]
|
||||
})
|
||||
|
||||
const root = getRoot()
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
const deleteTip = ref(false)
|
||||
const deleteWaylineId = ref<string>('')
|
||||
const canRefresh = ref(true)
|
||||
const importVisible = ref<boolean>(root.$router.currentRoute.value.name === ERouterName.WAYLINE)
|
||||
|
||||
onMounted(() => {
|
||||
getWaylines()
|
||||
setTimeout(() => {
|
||||
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
|
||||
const parent = element?.parentNode as HTMLDivElement
|
||||
console.info(element, parent)
|
||||
// console.info(element.scrollHeight, parent.clientHeight)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
|
||||
const parent = element?.parentNode as HTMLDivElement
|
||||
setTimeout(() => {
|
||||
console.info(element, parent)
|
||||
if (element?.scrollHeight < parent?.clientHeight && pagination.total > waylinesData.data.length) {
|
||||
if (canRefresh.value) {
|
||||
pagination.page++
|
||||
@@ -130,6 +154,7 @@ function getWaylines () {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
waylinesData.data = []
|
||||
res.data.list.forEach((wayline: WaylineFile) => waylinesData.data.push(wayline))
|
||||
pagination.total = res.data.pagination.total
|
||||
pagination.page = res.data.pagination.page
|
||||
@@ -151,8 +176,7 @@ function deleteWayline () {
|
||||
deleteWaylineId.value = ''
|
||||
deleteTip.value = false
|
||||
pagination.total--
|
||||
waylinesData.data = []
|
||||
setTimeout(getWaylines, 500)
|
||||
getWaylines()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -175,12 +199,50 @@ function selectRoute (wayline: WaylineFile) {
|
||||
|
||||
function onScroll (e: any) {
|
||||
const element = e.srcElement
|
||||
console.info(element)
|
||||
if (element.scrollTop + element.clientHeight === element.scrollHeight && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
|
||||
pagination.page++
|
||||
getWaylines()
|
||||
}
|
||||
}
|
||||
|
||||
interface FileItem {
|
||||
uid: string;
|
||||
name?: string;
|
||||
status?: string;
|
||||
response?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
interface FileInfo {
|
||||
file: FileItem;
|
||||
fileList: FileItem[];
|
||||
}
|
||||
const fileList = ref<FileItem[]>([])
|
||||
|
||||
function beforeUpload (file: FileItem) {
|
||||
fileList.value = [file]
|
||||
loading.value = true
|
||||
return true
|
||||
}
|
||||
const uploadFile = async () => {
|
||||
console.info(loading.value)
|
||||
fileList.value.forEach(async (file: FileItem) => {
|
||||
const fileData = new FormData()
|
||||
fileData.append('file', file, file.name)
|
||||
await importKmzFile(workspaceId, fileData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
message.success(`${file.name} file uploaded successfully`)
|
||||
canRefresh.value = true
|
||||
getWaylines()
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
fileList.value = []
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -13,22 +13,22 @@
|
||||
<div class="media-wrapper" v-if="root.$route.name === ERouterName.MEDIA">
|
||||
<MediaPanel />
|
||||
</div>
|
||||
<div class="media-wrapper" v-if="root.$route.name === ERouterName.TASK">
|
||||
<div class="task-wrapper" v-if="root.$route.name === ERouterName.TASK">
|
||||
<TaskPanel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
import Sidebar from '/@/components/common/sidebar.vue'
|
||||
import MediaPanel from '/@/components/MediaPanel.vue'
|
||||
import TaskPanel from '/@/components/TaskPanel.vue'
|
||||
import TaskPanel from '/@/components/task/TaskPanel.vue'
|
||||
import GMap from '/@/components/GMap.vue'
|
||||
import { EBizCode, ERouterName } from '/@/types'
|
||||
import { getRoot } from '/@/root'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { useConnectWebSocket } from '/@/hooks/use-connect-websocket'
|
||||
import EventBus from '/@/event-bus'
|
||||
|
||||
const root = getRoot()
|
||||
const store = useMyStore()
|
||||
@@ -72,7 +72,7 @@ const messageHandler = async (payload: any) => {
|
||||
break
|
||||
}
|
||||
case EBizCode.FlightTaskProgress: {
|
||||
store.commit('SET_FLIGHT_TASK_PROGRESS', payload.data)
|
||||
EventBus.emit('deviceTaskProgress', payload)
|
||||
break
|
||||
}
|
||||
case EBizCode.DeviceHms: {
|
||||
@@ -112,42 +112,41 @@ useConnectWebSocket(messageHandler)
|
||||
|
||||
.project-app-wrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
transition: width 0.2s ease;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.left {
|
||||
width: 400px;
|
||||
display: flex;
|
||||
width: 335px;
|
||||
flex: 0 0 335px;
|
||||
background-color: #232323;
|
||||
float: left;
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
color: $text-white-basic;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.map-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
.map-wrapper{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
color: $text-white-basic;
|
||||
}
|
||||
.media-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
}
|
||||
.wayline-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
padding: 16px;
|
||||
|
||||
.media-wrapper,
|
||||
.task-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
import { ERouterName } from '/@/types/index'
|
||||
import CreatePlan from '../pages/page-web/projects/create-plan.vue'
|
||||
import CreatePlan from '/@/components/task/CreatePlan.vue'
|
||||
import WaylinePanel from '/@/pages/page-web/projects/wayline.vue'
|
||||
import DockPanel from '/@/pages/page-web/projects/dock.vue'
|
||||
import LiveAgora from '/@/components/livestream-agora.vue'
|
||||
|
||||
+17
-20
@@ -4,7 +4,7 @@ import { EDeviceTypeName } from '../types'
|
||||
import { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device'
|
||||
import { getLayers } from '/@/api/layer'
|
||||
import { LayerType } from '/@/types/mapLayer'
|
||||
import { ETaskStatus, TaskInfo, WaylineFile } from '/@/types/wayline'
|
||||
import { WaylineFile } from '/@/types/wayline'
|
||||
import { DevicesCmdExecuteInfo } from '/@/types/device-cmd'
|
||||
|
||||
const initStateFunc = () => ({
|
||||
@@ -81,11 +81,6 @@ const initStateFunc = () => ({
|
||||
dockInfo: {
|
||||
|
||||
} as Device,
|
||||
taskProgressInfo: {
|
||||
|
||||
} as {
|
||||
[bid: string]: TaskInfo
|
||||
},
|
||||
hmsInfo: {} as {
|
||||
[sn: string]: DeviceHms[]
|
||||
},
|
||||
@@ -113,16 +108,29 @@ const mutations: MutationTree<RootStateType> = {
|
||||
state.deviceState.currentType = EDeviceTypeName.Gateway
|
||||
},
|
||||
SET_DOCK_INFO (state, info) {
|
||||
if (Object.keys(info.host).length === 0) {
|
||||
return
|
||||
}
|
||||
if (!state.deviceState.dockInfo[info.sn]) {
|
||||
state.deviceState.dockInfo[info.sn] = info.host
|
||||
return
|
||||
}
|
||||
state.deviceState.currentSn = info.sn
|
||||
state.deviceState.currentType = EDeviceTypeName.Dock
|
||||
const dock = state.deviceState.dockInfo[info.sn]
|
||||
if (info.host.sdr && state.deviceState.dockInfo[info.sn]) {
|
||||
if (info.host.sdr) {
|
||||
dock.sdr = info.host.sdr
|
||||
dock.media_file_detail = info.host.media_file_detail
|
||||
return
|
||||
}
|
||||
const sdr = dock?.sdr
|
||||
const mediaFileDetail = dock?.media_file_detail
|
||||
if (info.host.job_number !== undefined) {
|
||||
if (info.host.drone_battery_maintenance_info) {
|
||||
dock.drone_battery_maintenance_info = info.host.drone_battery_maintenance_info
|
||||
}
|
||||
return
|
||||
}
|
||||
const sdr = dock.sdr
|
||||
const mediaFileDetail = dock.media_file_detail
|
||||
state.deviceState.dockInfo[info.sn] = info.host
|
||||
state.deviceState.dockInfo[info.sn].sdr = sdr
|
||||
state.deviceState.dockInfo[info.sn].media_file_detail = mediaFileDetail
|
||||
@@ -160,17 +168,6 @@ const mutations: MutationTree<RootStateType> = {
|
||||
SET_SELECT_DOCK_INFO (state, info) {
|
||||
state.dockInfo = info
|
||||
},
|
||||
SET_FLIGHT_TASK_PROGRESS (state, info) {
|
||||
const taskInfo: TaskInfo = info.output
|
||||
|
||||
if (taskInfo.status === ETaskStatus.OK || taskInfo.status === ETaskStatus.FAILED) {
|
||||
taskInfo.status = taskInfo.status.concat('(Code:').concat(info.result).concat(')')
|
||||
setTimeout(() => {
|
||||
delete state.taskProgressInfo[info.bid]
|
||||
}, 60000)
|
||||
}
|
||||
state.taskProgressInfo[info.bid] = info.output
|
||||
},
|
||||
SET_DEVICE_HMS_INFO (state, info) {
|
||||
const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn]
|
||||
state.hmsInfo[info.sn] = info.host.concat(hmsList ?? [])
|
||||
|
||||
@@ -36,3 +36,27 @@ export enum SupplementLightStateEnum {
|
||||
Close = 0, // 关闭
|
||||
Open = 1, // 打开
|
||||
}
|
||||
|
||||
// 机场声光报警状态
|
||||
export enum AlarmModeEnum {
|
||||
CLOSE = 0, // 关闭
|
||||
OPEN = 1, // 开启
|
||||
}
|
||||
|
||||
// 电池保养
|
||||
export enum BatteryStoreModeEnum {
|
||||
BATTERY_PLAN_STORE = 1, // 电池计划存储策略
|
||||
BATTERY_EMERGENCY_STORE = 2, // 电池应急存储策略
|
||||
}
|
||||
|
||||
// 飞行器电池保养
|
||||
export enum DroneBatteryStateEnum {
|
||||
NoMaintenanceRequired = 0, // 0-无需保养
|
||||
MaintenanceRequired = 1, // 1-待保养
|
||||
MaintenanceInProgress = 2, // 2-正在保养
|
||||
}
|
||||
|
||||
export enum DroneBatteryModeEnum {
|
||||
CLOSE = 0, // 关闭
|
||||
OPEN = 1, // 开启
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum } from '/@/types/airport-tsa';
|
||||
// 机场指令集
|
||||
export enum DeviceCmd {
|
||||
// 简单指令
|
||||
@@ -19,16 +20,24 @@ export enum DeviceCmd {
|
||||
PutterClose = 'putter_close', // 推杆闭合
|
||||
ChargeOpen = 'charge_open', // 打开充电
|
||||
ChargeClose = 'charge_close', // 关闭充电
|
||||
AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警
|
||||
BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养
|
||||
DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养
|
||||
|
||||
}
|
||||
|
||||
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum
|
||||
|
||||
export interface DeviceCmdItem{
|
||||
label: string, // 标题
|
||||
status: string, // 当前状态
|
||||
operateText: string, // 按钮文字
|
||||
cmdKey: DeviceCmd, // 请求指令
|
||||
oppositeCmdKey?: DeviceCmd, // 相反状态指令
|
||||
action?: DeviceCmdItemAction, // 参数
|
||||
func: string, // 处理函数
|
||||
loading: boolean // 按钮loading
|
||||
disabled?: boolean // 按钮disabled
|
||||
}
|
||||
|
||||
// 机场指令
|
||||
@@ -114,6 +123,34 @@ export const cmdList: DeviceCmdItem[] = [
|
||||
func: 'supplementLightStatus',
|
||||
loading: false,
|
||||
},
|
||||
{
|
||||
label: '机场声光报警',
|
||||
status: '关',
|
||||
operateText: '打开',
|
||||
cmdKey: DeviceCmd.AlarmStateSwitch,
|
||||
action: AlarmModeEnum.OPEN,
|
||||
func: 'alarmState',
|
||||
loading: false,
|
||||
},
|
||||
{
|
||||
label: '机场电池存储模式',
|
||||
status: '计划',
|
||||
operateText: '应急',
|
||||
cmdKey: DeviceCmd.BatteryStoreModeSwitch,
|
||||
action: BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE,
|
||||
func: 'batteryStoreMode',
|
||||
loading: false,
|
||||
},
|
||||
{
|
||||
label: '飞机电池保养',
|
||||
status: '--',
|
||||
operateText: '保养',
|
||||
cmdKey: DeviceCmd.DroneBatteryModeSwitch,
|
||||
action: DroneBatteryModeEnum.OPEN,
|
||||
func: 'droneBatteryMode',
|
||||
loading: false,
|
||||
disabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
export enum DeviceCmdStatusText {
|
||||
@@ -176,6 +213,32 @@ export enum DeviceCmdStatusText {
|
||||
DeviceSupplementLightCloseText = '关闭中...',
|
||||
DeviceSupplementLightCloseFailedText = '开',
|
||||
DeviceSupplementLightCloseBtnText = '打开',
|
||||
|
||||
AlarmStateOpenNormalText = '开',
|
||||
AlarmStateOpenText = '开启中...',
|
||||
AlarmStateOpenFailedText = '关',
|
||||
AlarmStateOpenBtnText = '关闭',
|
||||
|
||||
AlarmStateCloseNormalText = '关',
|
||||
AlarmStateCloseText = '关闭中...',
|
||||
AlarmStateCloseFailedText = '开',
|
||||
AlarmStateCloseBtnText = '打开',
|
||||
|
||||
BatteryStoreModePlanNormalText = '计划',
|
||||
BatteryStoreModePlanText = '切换中...',
|
||||
BatteryStoreModePlanFailedText = '应急',
|
||||
BatteryStoreModePlanBtnText = '应急',
|
||||
|
||||
BatteryStoreModeEmergencyNormalText = '应急',
|
||||
BatteryStoreModeEmergencyText = '切换中...',
|
||||
BatteryStoreModeEmergencyFailedText = '计划',
|
||||
BatteryStoreModeEmergencyBtnText = '计划',
|
||||
|
||||
DroneBatteryModeMaintenanceInProgressText = '保养中',
|
||||
DroneBatteryModeMaintenanceNotNeedText = '无需保养',
|
||||
DroneBatteryModeMaintenanceNeedText = '需保养',
|
||||
DroneBatteryModeOpenBtnText = '保养',
|
||||
DroneBatteryModeCloseBtnText = '关闭保养',
|
||||
}
|
||||
|
||||
// cmd ws 消息状态
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// 夜航灯开关
|
||||
export enum NightLightsStateEnum {
|
||||
CLOSE = 0, // 0-关闭
|
||||
OPEN = 1, // 1-打开
|
||||
}
|
||||
|
||||
// 限远开关
|
||||
export enum DistanceLimitStatusEnum {
|
||||
UNSET = 0, // 0-未设置
|
||||
SET = 1, // 1-已设置
|
||||
}
|
||||
|
||||
export interface DistanceLimitStatus {
|
||||
state?: DistanceLimitStatusEnum;
|
||||
distance_limit?: number; // 限远
|
||||
}
|
||||
|
||||
// 避障
|
||||
export enum ObstacleAvoidanceStatusEnum {
|
||||
CLOSE = 0, // 0-关闭
|
||||
OPEN = 1, // 1-开启
|
||||
}
|
||||
|
||||
export interface ObstacleAvoidance {
|
||||
horizon?: ObstacleAvoidanceStatusEnum;// 水平避障开关
|
||||
upside?: ObstacleAvoidanceStatusEnum;// 上行方向避障开关
|
||||
downside?: ObstacleAvoidanceStatusEnum;// 下行方向避障开关
|
||||
}
|
||||
|
||||
// 设备管理设置key
|
||||
export enum DeviceSettingKeyEnum {
|
||||
NIGHT_LIGHTS_MODE_SET = 'night_lights_state', // 夜航灯开关
|
||||
HEIGHT_LIMIT_SET = 'height_limit', // 限高设置
|
||||
DISTANCE_LIMIT_SET = 'distance_limit_status', // 限远开关
|
||||
OBSTACLE_AVOIDANCE_HORIZON = 'obstacle_avoidance_horizon', // 水平避障状态
|
||||
OBSTACLE_AVOIDANCE_UPSIDE = 'obstacle_avoidance_upside', // 上视避障状态
|
||||
OBSTACLE_AVOIDANCE_DOWNSIDE = 'obstacle_avoidance_downside', // 下视避障状态
|
||||
}
|
||||
|
||||
export type DeviceSettingType = Record<DeviceSettingKeyEnum, any>
|
||||
|
||||
export const initDeviceSetting = {
|
||||
[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET]:
|
||||
{
|
||||
label: '飞行器夜航灯',
|
||||
value: '',
|
||||
trueValue: NightLightsStateEnum.CLOSE,
|
||||
editable: false,
|
||||
popConfirm: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
// content: '为保证飞行器的作业安全,建议打开夜航灯',
|
||||
label: '飞行器夜航灯',
|
||||
},
|
||||
settingKey: DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET,
|
||||
},
|
||||
[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET]:
|
||||
{
|
||||
label: '限高',
|
||||
value: '',
|
||||
trueValue: 120,
|
||||
editable: false,
|
||||
popConfirm: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
// content: '限高:20 - 1500m',
|
||||
// info: '修改限高会影响当前机场的所有作业任务,建议确认作业情况后再进行修改',
|
||||
label: '限高',
|
||||
},
|
||||
settingKey: DeviceSettingKeyEnum.HEIGHT_LIMIT_SET,
|
||||
},
|
||||
[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET]:
|
||||
{
|
||||
label: '限远',
|
||||
value: '',
|
||||
trueValue: DistanceLimitStatusEnum.UNSET,
|
||||
// info: '限远(15 - 8000m)是约束飞行器相对机场的最大作业距离',
|
||||
editable: false,
|
||||
popConfirm: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
// content: '限远 (15- 8000m) 是约束飞行器相对机场的最大作业距离',
|
||||
// info: '修改限远会影响当前机场的所有作业任务,建议确认作业情况后再进行修改',
|
||||
label: '限远',
|
||||
|
||||
},
|
||||
settingKey: DeviceSettingKeyEnum.DISTANCE_LIMIT_SET,
|
||||
},
|
||||
[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON]:
|
||||
{
|
||||
label: '水平避障',
|
||||
value: '',
|
||||
trueValue: ObstacleAvoidanceStatusEnum.CLOSE,
|
||||
// info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置',
|
||||
editable: false,
|
||||
popConfirm: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
// content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启',
|
||||
label: '水平避障',
|
||||
|
||||
},
|
||||
settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON,
|
||||
},
|
||||
[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE]:
|
||||
{
|
||||
label: '上视避障',
|
||||
value: '',
|
||||
trueValue: ObstacleAvoidanceStatusEnum.CLOSE,
|
||||
// info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置',
|
||||
editable: false,
|
||||
popConfirm: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
// content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启',
|
||||
label: '上视避障',
|
||||
|
||||
},
|
||||
settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE,
|
||||
},
|
||||
[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE]:
|
||||
{
|
||||
label: '下视避障',
|
||||
value: '',
|
||||
trueValue: ObstacleAvoidanceStatusEnum.CLOSE,
|
||||
// info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置',
|
||||
editable: false,
|
||||
popConfirm: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
// content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启',
|
||||
label: '下视避障',
|
||||
|
||||
},
|
||||
settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE,
|
||||
},
|
||||
} as DeviceSettingType
|
||||
|
||||
export const initDeviceSettingFormModel = {
|
||||
nightLightsState: false, // 夜航灯开关
|
||||
heightLimit: 20, // 限高设置
|
||||
distanceLimitStatus: { state: false, distanceLimit: 15 }, // 限远开关
|
||||
obstacleAvoidanceHorizon: false, // 飞行器避障-水平开关设置
|
||||
obstacleAvoidanceUpside: false, // 飞行器避障-上视开关设置
|
||||
obstacleAvoidanceDownside: false, // 飞行器避障-下视开关设置
|
||||
}
|
||||
|
||||
export type DeviceSettingFormModel = typeof initDeviceSettingFormModel
|
||||
+17
-2
@@ -1,5 +1,6 @@
|
||||
import { commonColor } from '/@/utils/color'
|
||||
|
||||
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'
|
||||
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum } from './airport-tsa'
|
||||
export interface DeviceValue {
|
||||
key: string; // 'domain-type-subtype'
|
||||
domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种
|
||||
@@ -235,7 +236,11 @@ export interface DeviceOsd {
|
||||
landing_power: string,
|
||||
remain_flight_time: number,
|
||||
return_home_power: string,
|
||||
}
|
||||
},
|
||||
night_lights_state?: NightLightsStateEnum;// 夜航灯开关
|
||||
height_limit?: number;// 限高设置
|
||||
distance_limit_status?: DistanceLimitStatus;// 限远开关
|
||||
obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
|
||||
}
|
||||
|
||||
export interface DockOsd {
|
||||
@@ -294,6 +299,12 @@ export interface DockOsd {
|
||||
device_online_status: number,
|
||||
device_paired: number,
|
||||
},
|
||||
alarm_state?: AlarmModeEnum; // 机场声光报警状态
|
||||
battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式
|
||||
drone_battery_maintenance_info?: { // 飞行器电池保养信息
|
||||
maintenance_state: DroneBatteryStateEnum, // 保养状态
|
||||
maintenance_time_left: number, // 电池保养剩余时间(小时)
|
||||
}
|
||||
}
|
||||
|
||||
export enum EModeCode {
|
||||
@@ -343,6 +354,10 @@ export enum EDeviceType {
|
||||
H20N = '1-61-0' as any,
|
||||
DJI_Dock_Camera = '1-165-0' as any,
|
||||
L1 = '1-90742-0' as any,
|
||||
M3E = '0-77-0' as any,
|
||||
M3D = '0-77-1' as any,
|
||||
M3E_Camera = '1-66-0' as any,
|
||||
M3T_Camera = '1-67-0' as any,
|
||||
}
|
||||
|
||||
export enum EDockModeCode {
|
||||
|
||||
@@ -90,7 +90,7 @@ export enum EBizCode {
|
||||
MapElementDelete = 'map_element_delete',
|
||||
DeviceOnline = 'device_online',
|
||||
DeviceOffline = 'device_offline',
|
||||
FlightTaskProgress = 'flighttask_progress',
|
||||
FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度
|
||||
DeviceHms = 'device_hms',
|
||||
|
||||
// 设备指令
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { commonColor } from '/@/utils/color'
|
||||
|
||||
// 任务类型
|
||||
export enum TaskType {
|
||||
Immediate = 0, // 立即执行
|
||||
Single = 1, // 单次定时任务
|
||||
}
|
||||
|
||||
export const TaskTypeMap = {
|
||||
[TaskType.Immediate]: 'Immediate',
|
||||
[TaskType.Single]: 'Timed & One-Time',
|
||||
}
|
||||
|
||||
// 失控动作
|
||||
export enum OutOfControlAction {
|
||||
ReturnToHome = 0,
|
||||
Hover = 1,
|
||||
Land = 2,
|
||||
}
|
||||
|
||||
export const OutOfControlActionMap = {
|
||||
[OutOfControlAction.ReturnToHome]: 'Return to Home',
|
||||
[OutOfControlAction.Hover]: 'Hover',
|
||||
[OutOfControlAction.Land]: 'Land',
|
||||
}
|
||||
|
||||
export const OutOfControlActionOptions = [
|
||||
{ value: OutOfControlAction.ReturnToHome, label: OutOfControlActionMap[OutOfControlAction.ReturnToHome] },
|
||||
{ value: OutOfControlAction.Hover, label: OutOfControlActionMap[OutOfControlAction.Hover] },
|
||||
{ value: OutOfControlAction.Land, label: OutOfControlActionMap[OutOfControlAction.Land] },
|
||||
]
|
||||
|
||||
// 任务状态
|
||||
export enum TaskStatus {
|
||||
Wait = 1, // 待执行
|
||||
Carrying = 2, // 执行中
|
||||
Success = 3, // 完成
|
||||
CanCel = 4, // 取消
|
||||
Fail = 5, // 失败
|
||||
}
|
||||
|
||||
export const TaskStatusMap = {
|
||||
[TaskStatus.Wait]: 'To be performed',
|
||||
[TaskStatus.Carrying]: 'In progress',
|
||||
[TaskStatus.Success]: 'Task completed',
|
||||
[TaskStatus.CanCel]: 'Task canceled',
|
||||
[TaskStatus.Fail]: 'Task failed',
|
||||
}
|
||||
|
||||
export const TaskStatusColor = {
|
||||
[TaskStatus.Wait]: commonColor.BLUE,
|
||||
[TaskStatus.Carrying]: commonColor.BLUE,
|
||||
[TaskStatus.Success]: commonColor.NORMAL,
|
||||
[TaskStatus.CanCel]: commonColor.FAIL,
|
||||
[TaskStatus.Fail]: commonColor.FAIL,
|
||||
}
|
||||
|
||||
// 任务执行 ws 消息状态
|
||||
export enum TaskProgressStatus {
|
||||
Sent = 'sent', // 已下发
|
||||
inProgress = 'in_progress', // 执行中
|
||||
Paused = 'paused', // 暂停
|
||||
Rejected = 'rejected', // 拒绝
|
||||
Canceled = 'canceled', // 取消或终止
|
||||
Timeout = 'timeout', // 超时
|
||||
Failed = 'failed', // 失败
|
||||
OK = 'ok', // 上传成功
|
||||
}
|
||||
|
||||
// 任务进度消息
|
||||
export interface TaskProgressInfo {
|
||||
bid: string,
|
||||
output:{
|
||||
ext: {
|
||||
current_waypoint_index: number,
|
||||
media_count: number // 媒体文件
|
||||
},
|
||||
progress:{
|
||||
current_step: number,
|
||||
percent: number
|
||||
},
|
||||
status: TaskProgressStatus
|
||||
},
|
||||
result: number,
|
||||
}
|
||||
|
||||
// ws status => log status
|
||||
export const TaskProgressWsStatusMap = {
|
||||
[TaskProgressStatus.Sent]: TaskStatus.Carrying,
|
||||
[TaskProgressStatus.inProgress]: TaskStatus.Carrying,
|
||||
[TaskProgressStatus.Rejected]: TaskStatus.Fail,
|
||||
[TaskProgressStatus.OK]: TaskStatus.Success,
|
||||
[TaskProgressStatus.Failed]: TaskStatus.Fail,
|
||||
[TaskProgressStatus.Canceled]: TaskStatus.CanCel,
|
||||
[TaskProgressStatus.Timeout]: TaskStatus.Fail,
|
||||
[TaskProgressStatus.Paused]: TaskStatus.Wait,
|
||||
}
|
||||
+7
-22
@@ -1,30 +1,15 @@
|
||||
// 航线类型
|
||||
export enum WaylineType {
|
||||
NormalWaypointWayline = 0, // 普通航点航线
|
||||
AccurateReshootingWayline = 1 // 精准复拍航线
|
||||
}
|
||||
|
||||
export interface WaylineFile {
|
||||
id: string,
|
||||
name: string,
|
||||
drone_model_key: any,
|
||||
payload_model_keys: string[],
|
||||
template_types: number[],
|
||||
template_types: WaylineType[],
|
||||
update_time: number,
|
||||
user_name: string,
|
||||
}
|
||||
|
||||
export interface TaskExt {
|
||||
current_waypoint_index: number,
|
||||
media_count: number,
|
||||
}
|
||||
|
||||
export interface TaskProgress {
|
||||
current_step: number,
|
||||
percent: number,
|
||||
}
|
||||
|
||||
export interface TaskInfo {
|
||||
status: string,
|
||||
progress: TaskProgress,
|
||||
ext: TaskExt,
|
||||
}
|
||||
|
||||
export enum ETaskStatus {
|
||||
OK = 'ok',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
+125
-1
@@ -1,6 +1,7 @@
|
||||
import { DroneBatteryModeEnum, DroneBatteryStateEnum } from './../types/airport-tsa';
|
||||
import { DeviceInfoType } from '/@/types/device'
|
||||
import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
|
||||
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum } from '/@/types/airport-tsa'
|
||||
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum, AlarmModeEnum, BatteryStoreModeEnum } from '/@/types/airport-tsa'
|
||||
import { getBytesObject } from './bytes'
|
||||
import { DEFAULT_PLACEHOLDER } from './constants'
|
||||
|
||||
@@ -35,6 +36,12 @@ export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo:
|
||||
droneFormat(cmdItem, device)
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关
|
||||
getSupplementLightState(cmdItem, dock)
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 声光报警
|
||||
getAlarmState(cmdItem, dock)
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养
|
||||
getBatteryStoreMode(cmdItem, dock)
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
|
||||
getDroneBatteryMode(cmdItem, dock)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -166,6 +173,55 @@ function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any
|
||||
}
|
||||
}
|
||||
|
||||
// 声光报警
|
||||
function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {
|
||||
const alarmState = airportProperties?.alarm_state
|
||||
if (alarmState === AlarmModeEnum.CLOSE) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.AlarmStateCloseBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseNormalText
|
||||
cmdItem.action = AlarmModeEnum.OPEN
|
||||
} else if (alarmState === AlarmModeEnum.OPEN) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.AlarmStateOpenBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenNormalText
|
||||
cmdItem.action = AlarmModeEnum.CLOSE
|
||||
}
|
||||
}
|
||||
|
||||
// 机场电池模式
|
||||
function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) {
|
||||
const batteryStoreMode = airportProperties?.battery_store_mode
|
||||
if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModePlanBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanNormalText
|
||||
cmdItem.action = BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE
|
||||
} else if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModeEmergencyBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyNormalText
|
||||
cmdItem.action = BatteryStoreModeEnum.BATTERY_PLAN_STORE
|
||||
}
|
||||
}
|
||||
|
||||
// 飞行器电池保养
|
||||
function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) {
|
||||
const maintenanceState = airportProperties?.drone_battery_maintenance_info?.maintenance_state
|
||||
if (maintenanceState === DroneBatteryStateEnum.MaintenanceInProgress) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeCloseBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
|
||||
cmdItem.action = DroneBatteryModeEnum.CLOSE
|
||||
cmdItem.disabled = false
|
||||
} else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
|
||||
cmdItem.action = DroneBatteryModeEnum.OPEN
|
||||
cmdItem.disabled = true
|
||||
} else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) {
|
||||
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
|
||||
cmdItem.action = DroneBatteryModeEnum.OPEN
|
||||
cmdItem.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换指令
|
||||
* @param cmd
|
||||
@@ -344,6 +400,74 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.loading = false
|
||||
}
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 机场声光报警
|
||||
if (cmdItem.action === AlarmModeEnum.CLOSE) {
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
|
||||
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText
|
||||
cmdItem.loading = true
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
|
||||
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText
|
||||
cmdItem.loading = false
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.loading = false
|
||||
}
|
||||
} else if (cmdItem.action === AlarmModeEnum.OPEN) {
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
|
||||
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText
|
||||
cmdItem.loading = true
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
|
||||
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText
|
||||
cmdItem.loading = false
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.loading = false
|
||||
}
|
||||
}
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养
|
||||
if (cmdItem.action === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
|
||||
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText
|
||||
cmdItem.loading = true
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
|
||||
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText
|
||||
cmdItem.loading = false
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.loading = false
|
||||
}
|
||||
} else if (cmdItem.action === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
|
||||
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText
|
||||
cmdItem.loading = true
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
|
||||
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText
|
||||
cmdItem.loading = false
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.loading = false
|
||||
}
|
||||
}
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
|
||||
if (cmdItem.action === DroneBatteryModeEnum.OPEN) {
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
|
||||
// cmdItem.loading = true
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
|
||||
// cmdItem.loading = false
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
|
||||
// cmdItem.loading = false
|
||||
}
|
||||
} else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) {
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
|
||||
cmdItem.loading = true
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
|
||||
cmdItem.loading = false
|
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
|
||||
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
|
||||
cmdItem.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import { DeviceInfoType } from '/@/types/device'
|
||||
import { DeviceSettingType, DeviceSettingKeyEnum, DistanceLimitStatusEnum, ObstacleAvoidanceStatusEnum, DeviceSettingFormModel, NightLightsStateEnum } from '/@/types/device-setting'
|
||||
import { DEFAULT_PLACEHOLDER } from './constants'
|
||||
import { isNil } from 'lodash'
|
||||
|
||||
const Unit_M = ' m'
|
||||
|
||||
/**
|
||||
* 根据osd 更新信息
|
||||
* @param deviceSetting
|
||||
* @param deviceInfo
|
||||
* @returns
|
||||
*/
|
||||
export function updateDeviceSettingInfoByOsd (deviceSetting: DeviceSettingType, deviceInfo: DeviceInfoType) {
|
||||
const { device, dock, gateway } = deviceInfo || {}
|
||||
if (!deviceSetting) {
|
||||
return
|
||||
}
|
||||
// 夜航灯
|
||||
let nightLightsState = '' as any
|
||||
if (isNil(device?.night_lights_state)) {
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER
|
||||
nightLightsState = DEFAULT_PLACEHOLDER
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = true
|
||||
nightLightsState = device?.night_lights_state
|
||||
if (nightLightsState === NightLightsStateEnum.CLOSE) {
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '关闭'
|
||||
} else if (nightLightsState === NightLightsStateEnum.OPEN) {
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '开启'
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER
|
||||
}
|
||||
}
|
||||
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].trueValue = nightLightsState
|
||||
|
||||
// 限高
|
||||
let heightLimit = device?.height_limit as any
|
||||
if (isNil(heightLimit) || heightLimit === 0) {
|
||||
heightLimit = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = false
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = true
|
||||
}
|
||||
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].trueValue = heightLimit
|
||||
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value = heightLimit + Unit_M
|
||||
|
||||
// 限远
|
||||
let distanceLimitStatus = '' as any
|
||||
if (isNil(device?.distance_limit_status?.state)) {
|
||||
distanceLimitStatus = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = true
|
||||
distanceLimitStatus = device?.distance_limit_status?.state
|
||||
if (distanceLimitStatus === DistanceLimitStatusEnum.UNSET) {
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = '关闭'
|
||||
} else if (distanceLimitStatus === DistanceLimitStatusEnum.SET) {
|
||||
const distanceLimit = device?.distance_limit_status?.distance_limit
|
||||
if (distanceLimit) {
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = distanceLimit + Unit_M
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER
|
||||
}
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER
|
||||
}
|
||||
}
|
||||
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].trueValue = distanceLimitStatus
|
||||
|
||||
// 避障
|
||||
if (isNil(device?.obstacle_avoidance)) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER
|
||||
} else {
|
||||
const { horizon, upside, downside } = device.obstacle_avoidance || {}
|
||||
if (isNil(horizon)) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false
|
||||
if (horizon === ObstacleAvoidanceStatusEnum.CLOSE) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '关闭'
|
||||
} else if (horizon === ObstacleAvoidanceStatusEnum.OPEN) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '开启'
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER
|
||||
}
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = horizon
|
||||
}
|
||||
|
||||
if (isNil(upside)) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false
|
||||
if (upside === ObstacleAvoidanceStatusEnum.CLOSE) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '关闭'
|
||||
} else if (upside === ObstacleAvoidanceStatusEnum.OPEN) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '开启'
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER
|
||||
}
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = upside
|
||||
}
|
||||
|
||||
if (isNil(downside)) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false
|
||||
if (downside === ObstacleAvoidanceStatusEnum.CLOSE) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '关闭'
|
||||
} else if (downside === ObstacleAvoidanceStatusEnum.OPEN) {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '开启'
|
||||
} else {
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER
|
||||
}
|
||||
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = downside
|
||||
}
|
||||
}
|
||||
return deviceSetting
|
||||
}
|
||||
|
||||
// 更新formModel
|
||||
export function updateDeviceSettingFormModelByOsd (deviceSettingFormModelFromOsd: DeviceSettingFormModel, deviceInfo: DeviceInfoType) {
|
||||
const { device, dock, gateway } = deviceInfo || {}
|
||||
if (!deviceSettingFormModelFromOsd) {
|
||||
return
|
||||
}
|
||||
// 夜航灯
|
||||
const nightLightsState = device?.night_lights_state as any
|
||||
if (!isNil(nightLightsState) && nightLightsState === NightLightsStateEnum.OPEN) {
|
||||
deviceSettingFormModelFromOsd.nightLightsState = true
|
||||
} else {
|
||||
deviceSettingFormModelFromOsd.nightLightsState = false
|
||||
}
|
||||
|
||||
// 限高
|
||||
const heightLimit = device?.height_limit as any
|
||||
if (isNil(heightLimit) || heightLimit === 0) {
|
||||
deviceSettingFormModelFromOsd.heightLimit = 20
|
||||
} else {
|
||||
deviceSettingFormModelFromOsd.heightLimit = heightLimit
|
||||
}
|
||||
|
||||
// 限远
|
||||
const distanceLimitStatus = device?.distance_limit_status?.state as any
|
||||
if (!isNil(distanceLimitStatus) && distanceLimitStatus === DistanceLimitStatusEnum.SET) {
|
||||
deviceSettingFormModelFromOsd.distanceLimitStatus.state = true
|
||||
deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = device?.distance_limit_status?.distance_limit || 15
|
||||
} else {
|
||||
deviceSettingFormModelFromOsd.distanceLimitStatus.state = false
|
||||
deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = 15
|
||||
}
|
||||
|
||||
// 避障
|
||||
if (isNil(device?.obstacle_avoidance)) {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false
|
||||
} else {
|
||||
const { horizon, upside, downside } = device.obstacle_avoidance || {}
|
||||
if (!isNil(horizon) && horizon === ObstacleAvoidanceStatusEnum.OPEN) {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = true
|
||||
} else {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false
|
||||
}
|
||||
if (!isNil(upside) && upside === ObstacleAvoidanceStatusEnum.OPEN) {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = true
|
||||
} else {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false
|
||||
}
|
||||
if (!isNil(downside) && downside === ObstacleAvoidanceStatusEnum.OPEN) {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = true
|
||||
} else {
|
||||
deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false
|
||||
}
|
||||
}
|
||||
return deviceSettingFormModelFromOsd
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
export interface ErrorCode {
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据错误码翻译错误信息
|
||||
* @param code
|
||||
* @param errorMsg
|
||||
* @returns
|
||||
*/
|
||||
export function getErrorMessage (code: number, errorMsg?: string): string {
|
||||
const errorInfo = ERROR_CODE.find((item: ErrorCode) => item.code === code)
|
||||
return errorInfo ? errorInfo.msg : errorMsg || 'Server error'
|
||||
}
|
||||
|
||||
// 暂时只添加航线错误
|
||||
export const ERROR_CODE = [
|
||||
{
|
||||
code: 314001,
|
||||
msg: 'The issued route task url is empty',
|
||||
},
|
||||
{
|
||||
code: 314002,
|
||||
msg: 'The issued route task md5 is empty',
|
||||
},
|
||||
{
|
||||
code: 314003,
|
||||
msg: 'MissionID is invalid',
|
||||
},
|
||||
{
|
||||
code: 314004,
|
||||
msg: 'Failed to send flight route task from cloud',
|
||||
},
|
||||
{
|
||||
code: 314005,
|
||||
msg: 'Route md5 check failed',
|
||||
},
|
||||
{
|
||||
code: 314006,
|
||||
msg: 'Timeout waiting for aircraft to upload route (waiting for gs_state)',
|
||||
},
|
||||
{
|
||||
code: 314007,
|
||||
msg: 'Failed to upload route to aircraft',
|
||||
},
|
||||
{
|
||||
code: 314008,
|
||||
msg: 'Timeout waiting for the aircraft to enter the route executable state',
|
||||
},
|
||||
{
|
||||
code: 314009,
|
||||
msg: 'Failed to open route mission',
|
||||
},
|
||||
{
|
||||
code: 314010,
|
||||
msg: 'Route execution failed',
|
||||
},
|
||||
{
|
||||
code: 316001,
|
||||
msg: 'Failed to set alternate point',
|
||||
},
|
||||
{
|
||||
code: 316002,
|
||||
msg: 'Alternate safety transfer altitude equipment failed',
|
||||
},
|
||||
{
|
||||
code: 316003,
|
||||
msg: 'Failed to set takeoff altitude. Remarks: The default safe takeoff height of the aircraft set by the current DJI Dock is: 1.8',
|
||||
},
|
||||
{
|
||||
code: 316004,
|
||||
msg: 'Failed to set runaway behavior',
|
||||
},
|
||||
{
|
||||
code: 316005,
|
||||
msg: 'Aircraft RTK convergence failed',
|
||||
},
|
||||
{
|
||||
code: 316013,
|
||||
msg: 'DJI Dock Moved',
|
||||
},
|
||||
{
|
||||
code: 316015,
|
||||
msg: 'The aircraft RTK convergence position is too far from the DJI Dock',
|
||||
},
|
||||
{
|
||||
code: 316007,
|
||||
msg: 'Set parameter timeout while waiting for aircraft to be ready',
|
||||
},
|
||||
{
|
||||
code: 316008,
|
||||
msg: 'Failed to gain control of aircraft',
|
||||
},
|
||||
{
|
||||
code: 316009,
|
||||
msg: 'Aircraft power is low',
|
||||
},
|
||||
{
|
||||
code: 316010,
|
||||
msg: 'After power on, the aircraft is not connected for more than 2 minutes (flight control OSD reception timeout)',
|
||||
},
|
||||
{
|
||||
code: 316011,
|
||||
msg: 'Landing Position Offset',
|
||||
},
|
||||
|
||||
{
|
||||
code: 317001,
|
||||
msg: 'Failed to get the number of media files',
|
||||
},
|
||||
|
||||
{
|
||||
code: 319001,
|
||||
msg: 'The task center is not currently idle',
|
||||
},
|
||||
{
|
||||
code: 319002,
|
||||
msg: 'dronenest communication timeout',
|
||||
},
|
||||
{
|
||||
code: 319999,
|
||||
msg: 'Unknown error, e.g. restart after crash',
|
||||
},
|
||||
{
|
||||
code: 321000,
|
||||
msg: 'Route execution failed, unknown error',
|
||||
},
|
||||
{
|
||||
code: 321257,
|
||||
msg: 'The route has already started and cannot be started again',
|
||||
},
|
||||
{
|
||||
code: 321258,
|
||||
msg: 'The route cannot be interrupted in this state',
|
||||
},
|
||||
{
|
||||
code: 321259,
|
||||
msg: 'The route has not started and cannot end the route',
|
||||
},
|
||||
{
|
||||
code: 321513,
|
||||
msg: 'Reach the height limit',
|
||||
},
|
||||
{
|
||||
code: 321514,
|
||||
msg: 'Reach the limit',
|
||||
},
|
||||
{
|
||||
code: 321515,
|
||||
msg: 'Crossing the restricted flight zone',
|
||||
},
|
||||
{
|
||||
code: 321516,
|
||||
msg: 'Low limit',
|
||||
},
|
||||
|
||||
{
|
||||
code: 321517,
|
||||
msg: 'Obstacle Avoidance',
|
||||
},
|
||||
{
|
||||
code: 321769,
|
||||
msg: 'Weak GPS signal',
|
||||
},
|
||||
{
|
||||
code: 321770,
|
||||
msg: 'The current gear state cannot be executed, B control seizes the control, and the gear is switched',
|
||||
},
|
||||
{
|
||||
code: 321771,
|
||||
msg: 'The home point is not refreshed',
|
||||
},
|
||||
{
|
||||
code: 321772,
|
||||
msg: 'The current battery is too low to start the task',
|
||||
},
|
||||
{
|
||||
code: 321773,
|
||||
msg: 'Low battery return',
|
||||
},
|
||||
{
|
||||
code: 321776,
|
||||
msg: 'RTK not ready',
|
||||
},
|
||||
{
|
||||
code: 321778,
|
||||
msg: 'The aircraft is idling on the ground and is not allowed to start the route, thinking that the user is not ready.',
|
||||
},
|
||||
{
|
||||
code: 322282,
|
||||
msg: 'User interrupt (B control takeover)',
|
||||
},
|
||||
{
|
||||
code: 514100,
|
||||
msg: 'Command not supported',
|
||||
},
|
||||
{
|
||||
code: 514101,
|
||||
msg: 'Failed to close putter',
|
||||
},
|
||||
{
|
||||
code: 514102,
|
||||
msg: 'Failed to release putter',
|
||||
},
|
||||
{
|
||||
code: 514103,
|
||||
msg: 'Aircraft battery is low',
|
||||
},
|
||||
{
|
||||
code: 514104,
|
||||
msg: 'Failed to start charging',
|
||||
},
|
||||
{
|
||||
code: 514105,
|
||||
msg: 'Failed to stop charging',
|
||||
},
|
||||
{
|
||||
code: 514106,
|
||||
msg: 'Failed to restart the aircraft',
|
||||
},
|
||||
{
|
||||
code: 514107,
|
||||
msg: 'Failed to open hatch',
|
||||
},
|
||||
{
|
||||
code: 514108,
|
||||
msg: 'Failed to close hatch',
|
||||
},
|
||||
{
|
||||
code: 514109,
|
||||
msg: 'Failed to open the plane',
|
||||
},
|
||||
{
|
||||
code: 514110,
|
||||
msg: 'Failed to close the plane',
|
||||
},
|
||||
{
|
||||
code: 514111,
|
||||
msg: 'The aircraft failed to turn on the slow-rotating propeller in the cabin',
|
||||
},
|
||||
{
|
||||
code: 514112,
|
||||
msg: 'The aircraft failed to stop the slow-rotating propeller in the cabin',
|
||||
},
|
||||
{
|
||||
code: 514113,
|
||||
msg: 'Failed to establish wired connection with aircraft',
|
||||
},
|
||||
{
|
||||
code: 514114,
|
||||
msg: 'Get aircraft power status, command timed out, or return code is not 0',
|
||||
},
|
||||
{
|
||||
code: 514116,
|
||||
msg: 'The DJI Dock is busy and other control orders are being executed at the DJI Dock',
|
||||
},
|
||||
{
|
||||
code: 514117,
|
||||
msg: 'Check hatch status failed',
|
||||
},
|
||||
{
|
||||
code: 514118,
|
||||
msg: 'Check putter status failed',
|
||||
},
|
||||
{
|
||||
code: 514120,
|
||||
msg: 'DJI Dock and aircraft SDR connection failed',
|
||||
},
|
||||
{
|
||||
code: 514121,
|
||||
msg: 'Emergency stop state',
|
||||
},
|
||||
{
|
||||
code: 514122,
|
||||
msg: 'Failed to get the charging status of the aircraft (Failed to get the charging status, the flight mission can be executed, affecting charging and remote troubleshooting)',
|
||||
},
|
||||
{
|
||||
code: 514123,
|
||||
msg: 'Unable to power on due to low battery',
|
||||
},
|
||||
{
|
||||
code: 514124,
|
||||
msg: 'Failed to get battery information',
|
||||
},
|
||||
{
|
||||
code: 514125,
|
||||
msg: 'The battery is fully charged and cannot be charged',
|
||||
},
|
||||
{
|
||||
code: 514145,
|
||||
msg: 'Can not work while debugging on site',
|
||||
},
|
||||
{
|
||||
code: 514146,
|
||||
msg: 'Unable to work in remote debugging',
|
||||
},
|
||||
{
|
||||
code: 514147,
|
||||
msg: 'Unable to work in upgrade state',
|
||||
},
|
||||
{
|
||||
code: 514148,
|
||||
msg: 'Unable to execute new tasks in job state',
|
||||
},
|
||||
{
|
||||
code: 514150,
|
||||
msg: 'DJI Dock is automatically restarting',
|
||||
},
|
||||
]
|
||||
Referência em uma Nova Issue
Bloquear um usuário