Browse Source

v1.3.0-beta1

v1.3.0-beta1
sean.zhou 2 years ago
parent
commit
1810c61f5a
  1. 6
      src/api/manage.ts
  2. 59
      src/api/wayline.ts
  3. 1
      src/components/GMap.vue
  4. 182
      src/components/TaskPanel.vue
  5. 169
      src/components/livestream-agora.vue
  6. 164
      src/components/livestream-others.vue
  7. 81
      src/components/task/CreatePlan.vue
  8. 233
      src/components/task/TaskPanel.vue
  9. 30
      src/components/task/use-format-task.ts
  10. 19
      src/components/task/use-task-progress-event.ts
  11. 123
      src/components/wayline-panel.vue
  12. 5
      src/event-bus/index.ts
  13. 23
      src/hooks/use-g-map-tsa.ts
  14. 2
      src/pages/page-pilot/pilot-home.vue
  15. 2
      src/pages/page-web/projects/dock.vue
  16. 26
      src/pages/page-web/projects/task.vue
  17. 5
      src/pages/page-web/projects/workspace.vue
  18. 2
      src/router/index.ts
  19. 18
      src/store/index.ts
  20. 2
      src/types/enums.ts
  21. 78
      src/types/task.ts
  22. 29
      src/types/wayline.ts
  23. 310
      src/utils/error-code/index.ts
  24. 5
      tsconfig.json

6
src/api/manage.ts

@ -160,3 +160,9 @@ export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: st
const result = await request.get(url) const result = await request.get(url)
return result.data 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
}

59
src/api/wayline.ts

@ -1,14 +1,9 @@
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import request, { IPage, IWorkspaceResponse } from '/@/api/http/request' import request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'
const HTTP_PREFIX = '/wayline/api/v1' import { TaskType, TaskStatus } from '/@/types/task'
import { WaylineType } from '/@/types/wayline'
export interface CreatePlan { const HTTP_PREFIX = '/wayline/api/v1'
name: string,
file_id: string,
dock_sn: string,
immediate: boolean,
type: string,
}
// Get Wayline Files // Get Wayline Files
export const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> { 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') { if (result.data.type === 'application/json') {
const reader = new FileReader() const reader = new FileReader()
reader.onload = function (e) { reader.onload = function (e) {
let text = reader.result as string const text = reader.result as string
const result = JSON.parse(text) const result = JSON.parse(text)
message.error(result.message) message.error(result.message)
} }
reader.readAsText(result.data, 'utf-8') reader.readAsText(result.data, 'utf-8')
return
} else { } else {
return result.data return result.data
} }
@ -42,6 +36,15 @@ export const deleteWaylineFile = async function (workspaceId: string, waylineId:
return result.data return result.data
} }
export interface CreatePlan {
name: string,
file_id: string,
dock_sn: string,
task_type: TaskType, // 任务类型
wayline_type: WaylineType, // 航线类型
execute_time?: number // 执行时间(毫秒)
}
// Create Wayline Job // Create Wayline Job
export const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> { export const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks` const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks`
@ -49,16 +52,40 @@ export const createPlan = async function (workspaceId: string, plan: CreatePlan)
return result.data 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, // 错误码
}
// Get Wayline Jobs // 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 url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}`
const result = await request.get(url) const result = await request.get(url)
return result.data return result.data
} }
// Execute Wayline Job export interface DeleteTaskParams {
export const executeWaylineJobs = async function (workspaceId: string, plan_id: string): Promise<IWorkspaceResponse<any>> { job_id: string
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${plan_id}` }
const result = await request.post(url)
// 取消机场任务
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 return result.data
} }

1
src/components/GMap.vue

@ -589,6 +589,7 @@ export default defineComponent({
} }
} }
if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) { 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 !== '') { if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn] deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn]
deviceInfo.device = data.deviceInfo[deviceInfo.dock.sub_device?.device_sn] deviceInfo.device = data.deviceInfo[deviceInfo.dock.sub_device?.device_sn]

182
src/components/TaskPanel.vue

@ -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>

169
src/components/livestream-agora.vue

@ -3,15 +3,23 @@
<div id="player" style="width: 720px; height: 420px; border: 1px solid"></div> <div id="player" style="width: 720px; height: 420px; border: 1px solid"></div>
<p class="fz24">Live streaming source selection</p> <p class="fz24">Live streaming source selection</p>
<div class="flex-row flex-justify-center flex-align-center mt10"> <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 <a-select
style="width:150px" style="width:150px"
placeholder="Select Drone" placeholder="Select Drone"
@select="onDroneSelect" v-model:value="dronePara.droneSelected"
> >
<a-select-option <a-select-option
v-for="item in dronePara.droneList" v-for="item in dronePara.droneList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
@click="onDroneSelect(item)"
>{{ item.label }}</a-select-option >{{ item.label }}</a-select-option
> >
</a-select> </a-select>
@ -19,12 +27,13 @@
class="ml10" class="ml10"
style="width:150px" style="width:150px"
placeholder="Select Camera" placeholder="Select Camera"
@select="onCameraSelect" v-model:value="dronePara.cameraSelected"
> >
<a-select-option <a-select-option
v-for="item in dronePara.cameraList" v-for="item in dronePara.cameraList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
@click="onCameraSelect(item)"
>{{ item.label }}</a-select-option >{{ item.label }}</a-select-option
> >
</a-select> </a-select>
@ -35,13 +44,13 @@
@select="onVideoSelect" @select="onVideoSelect"
> >
<a-select-option <a-select-option
class="ml10"
v-for="item in dronePara.videoList" v-for="item in dronePara.videoList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
>{{ item.label }}</a-select-option >{{ item.label }}</a-select-option
> >
</a-select> --> </a-select> -->
</template>
<a-select <a-select
class="ml10" class="ml10"
style="width:150px" style="width:150px"
@ -60,12 +69,16 @@
Note: Obtain The Following Parameters From https://console.agora.io Note: Obtain The Following Parameters From https://console.agora.io
</p> </p>
<div class="flex-row flex-justify-center flex-align-center"> <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> <a-input v-model:value="agoraPara.appid" placeholder="APP ID"></a-input>
<span class="ml10">Token:</span>
<a-input <a-input
class="ml10" class="ml10"
v-model:value="agoraPara.token" v-model:value="agoraPara.token"
placeholder="Token" placeholder="Token"
@change="encodeToken"
></a-input> ></a-input>
<span class="ml10">Channel:</span>
<a-input <a-input
class="ml10" class="ml10"
v-model:value="agoraPara.channel" v-model:value="agoraPara.channel"
@ -73,14 +86,15 @@
></a-input> ></a-input>
</div> </div>
<div class="mt20 flex-row flex-justify-center flex-align-center"> <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" <a-button class="ml20" type="primary" large @click="onStop"
>Stop</a-button >Stop</a-button
> >
<a-button class="ml20" type="primary" large @click="onUpdateQuality" <a-button class="ml20" type="primary" large @click="onUpdateQuality"
>Update Clarity</a-button >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 >Refresh Live Capacity</a-button
> >
</div> </div>
@ -91,8 +105,9 @@
import AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng' import AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { onMounted, reactive } from 'vue' import { onMounted, reactive } from 'vue'
import { uuidv4 } from '../utils/uuid'
import { CURRENT_CONFIG as config } from '/@/api/http/config' 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 { getRoot } from '/@/root'
const root = getRoot() const root = getRoot()
@ -120,6 +135,12 @@ const clarityList = [
} }
] ]
interface SelectOption {
value: any,
label: string,
more?: any
}
let agoraClient = {} as IAgoraRTCClient let agoraClient = {} as IAgoraRTCClient
const agoraPara = reactive({ const agoraPara = reactive({
appid: config.agoraAPPID, appid: config.agoraAPPID,
@ -130,13 +151,16 @@ const agoraPara = reactive({
}) })
const dronePara = reactive({ const dronePara = reactive({
livestreamSource: [], livestreamSource: [],
droneList: [] as any[], droneList: [] as SelectOption[],
cameraList: [] as any[], cameraList: [] as SelectOption[],
videoList: [] as any[], videoList: [] as SelectOption[],
droneSelected: '', droneSelected: undefined as string | undefined,
cameraSelected: '', cameraSelected: undefined as string | undefined,
videoSelected: '', videoSelected: undefined as string | undefined,
claritySelected: 0 claritySelected: 0,
lensList: [] as string[],
lensSelected: undefined as string | undefined,
isDockLive: false
}) })
const livePara = reactive({ const livePara = reactive({
url: '', url: '',
@ -144,14 +168,15 @@ const livePara = reactive({
videoId: '', videoId: '',
liveState: false liveState: false
}) })
const nonSwitchable = 'normal'
const onRefresh = async () => { const onRefresh = async () => {
dronePara.droneList = [] dronePara.droneList = []
dronePara.cameraList = [] dronePara.cameraList = []
dronePara.videoList = [] dronePara.videoList = []
dronePara.droneSelected = '' dronePara.droneSelected = undefined
dronePara.cameraSelected = '' dronePara.cameraSelected = undefined
dronePara.videoSelected = '' dronePara.videoSelected = undefined
await getLiveCapacity({}) await getLiveCapacity({})
.then(res => { .then(res => {
if (res.code === 0) { if (res.code === 0) {
@ -166,18 +191,20 @@ const onRefresh = async () => {
if (dronePara.livestreamSource) { if (dronePara.livestreamSource) {
dronePara.livestreamSource.forEach((ele: any) => { 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 => { .catch(error => {
message.error(error)
console.error(error) console.error(error)
}) })
} }
onMounted(() => { onMounted(() => {
onRefresh() onRefresh()
agoraPara.token = encodeURIComponent(agoraPara.token)
agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' }) agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' })
// Subscribe when a remote user publishes a stream // Subscribe when a remote user publishes a stream
agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => { agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => {
@ -191,7 +218,7 @@ onMounted(() => {
const remoteVideoTrack = user.videoTrack! const remoteVideoTrack = user.videoTrack!
// Dynamically create a container in the form of a DIV element for playing the remote video track. // Dynamically create a container in the form of a DIV element for playing the remote video track.
const remotePlayerContainer: any = document.getElementById('player') const remotePlayerContainer: any = document.getElementById('player')
// remotePlayerContainer.id = agoraPara.uid remotePlayerContainer.id = user.uid.toString()
remoteVideoTrack.play(remotePlayerContainer) remoteVideoTrack.play(remotePlayerContainer)
} }
}) })
@ -199,6 +226,10 @@ onMounted(() => {
console.log('unpublish live:', user) console.log('unpublish live:', user)
message.info('unpublish live') message.info('unpublish live')
}) })
agoraClient.on('exception', async (e: any) => {
console.error(e)
message.error(e.msg)
})
}) })
const handleError = (err: any) => { const handleError = (err: any) => {
@ -207,7 +238,9 @@ const handleError = (err: any) => {
const handleJoinChannel = (uid: any) => { const handleJoinChannel = (uid: any) => {
agoraPara.uid = uid agoraPara.uid = uid
} }
const encodeToken = (e: any) => {
agoraPara.token = encodeURIComponent(agoraPara.token)
}
const onStart = async () => { const onStart = async () => {
const that = this const that = this
console.log( console.log(
@ -222,13 +255,12 @@ const onStart = async () => {
if ( if (
dronePara.droneSelected == null || dronePara.droneSelected == null ||
dronePara.cameraSelected == null || dronePara.cameraSelected == null ||
dronePara.videoSelected == null ||
dronePara.claritySelected == null dronePara.claritySelected == null
) { ) {
message.warn('waring: not select live para!!!') message.warn('waring: not select live para!!!')
return return
} }
agoraClient.setClientRole('audience', { level: 1 }) agoraClient.setClientRole('audience', { level: 2 })
if (agoraClient.connectionState === 'DISCONNECTED') { if (agoraClient.connectionState === 'DISCONNECTED') {
agoraClient agoraClient
.join(agoraPara.appid, agoraPara.channel, agoraPara.token) .join(agoraPara.appid, agoraPara.channel, agoraPara.token)
@ -236,11 +268,8 @@ const onStart = async () => {
livePara.videoId = livePara.videoId =
dronePara.droneSelected + dronePara.droneSelected +
'/' + '/' +
dronePara.cameraSelected + dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0')
'/' +
dronePara.videoSelected
console.log(agoraPara) console.log(agoraPara)
agoraPara.token = encodeURIComponent(agoraPara.token)
livePara.url = livePara.url =
'channel=' + 'channel=' +
@ -259,6 +288,9 @@ const onStart = async () => {
video_quality: dronePara.claritySelected video_quality: dronePara.claritySelected
}) })
.then(res => { .then(res => {
if (res.code !== 0) {
return
}
livePara.liveState = true livePara.liveState = true
}) })
.catch(err => { .catch(err => {
@ -266,12 +298,6 @@ const onStart = async () => {
}) })
} }
const onStop = async () => { const onStop = async () => {
livePara.videoId =
dronePara.droneSelected +
'/' +
dronePara.cameraSelected +
'/' +
dronePara.videoSelected
stopLivestream({ stopLivestream({
video_id: livePara.videoId video_id: livePara.videoId
}).then(res => { }).then(res => {
@ -279,52 +305,52 @@ const onStop = async () => {
message.success(res.message) message.success(res.message)
} }
livePara.liveState = false livePara.liveState = false
dronePara.lensSelected = ''
console.log('stop play livestream') console.log('stop play livestream')
}) })
} }
const onDroneSelect = (val: any) => { const onDroneSelect = (val: SelectOption) => {
dronePara.droneSelected = val
if (dronePara.droneSelected) {
const droneTemp = dronePara.livestreamSource
dronePara.cameraList = [] dronePara.cameraList = []
dronePara.videoList = []
dronePara.lensList = []
droneTemp.forEach((ele: any) => { dronePara.cameraSelected = undefined
const drone = ele dronePara.videoSelected = undefined
if (drone.cameras_list && drone.sn === dronePara.droneSelected) { dronePara.lensSelected = undefined
const cameraListTemp = drone.cameras_list dronePara.droneSelected = val.value
cameraListTemp.forEach((ele: any) => { if (!val.more) {
dronePara.cameraList.push({ label: ele.name, value: ele.index }) return
})
} }
val.more.forEach((ele: any) => {
dronePara.cameraList.push({ label: ele.name, value: ele.index, more: ele.videos_list })
}) })
} }
} const onCameraSelect = (val: SelectOption) => {
const onCameraSelect = (val: any) => { dronePara.cameraSelected = val.value
dronePara.cameraSelected = val dronePara.videoSelected = undefined
dronePara.lensSelected = undefined
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 = [] dronePara.videoList = []
videoListTemp.forEach((ele: any) => { dronePara.lensList = []
dronePara.videoList.push({ label: ele.type, value: ele.index }) if (!val.more) {
}) return
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) => { const onVideoSelect = (val: SelectOption) => {
dronePara.videoSelected = val dronePara.videoSelected = val.value
dronePara.lensList = val.more
dronePara.lensSelected = val.label
} }
const onClaritySelect = (val: any) => { const onClaritySelect = (val: any) => {
dronePara.claritySelected = val dronePara.claritySelected = val
@ -343,6 +369,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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

164
src/components/livestream-others.vue

@ -8,11 +8,20 @@
class="mt20" class="mt20"
></video> ></video>
<p class="fz24">Live streaming source selection</p> <p class="fz24">Live streaming source selection</p>
<div class="flex-row flex-justify-center flex-align-center mt10"> <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 <a-select
style="width: 150px" style="width: 150px"
placeholder="Select Live Type" placeholder="Select Live Type"
@select="onLiveTypeSelect" @select="onLiveTypeSelect"
v-model:value="livetypeSelected"
> >
<a-select-option <a-select-option
v-for="item in liveTypeList" v-for="item in liveTypeList"
@ -26,12 +35,13 @@
class="ml10" class="ml10"
style="width:150px" style="width:150px"
placeholder="Select Drone" placeholder="Select Drone"
@select="onDroneSelect" v-model:value="droneSelected"
> >
<a-select-option <a-select-option
v-for="item in droneList" v-for="item in droneList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
@click="onDroneSelect(item)"
>{{ item.label }}</a-select-option >{{ item.label }}</a-select-option
> >
</a-select> </a-select>
@ -39,12 +49,13 @@
class="ml10" class="ml10"
style="width:150px" style="width:150px"
placeholder="Select Camera" placeholder="Select Camera"
@select="onCameraSelect" v-model:value="cameraSelected"
> >
<a-select-option <a-select-option
v-for="item in cameraList" v-for="item in cameraList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
@click="onCameraSelect(item)"
>{{ item.label }}</a-select-option >{{ item.label }}</a-select-option
> >
</a-select> </a-select>
@ -52,21 +63,23 @@
class="ml10" class="ml10"
style="width:150px" style="width:150px"
placeholder="Select Lens" placeholder="Select Lens"
@select="onVideoSelect" v-model:value="videoSelected"
> >
<a-select-option <a-select-option
class="ml10"
v-for="item in videoList" v-for="item in videoList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
@click="onVideoSelect(item)"
>{{ item.label }}</a-select-option >{{ item.label }}</a-select-option
> >
</a-select> --> </a-select> -->
</template>
<a-select <a-select
class="ml10" class="ml10"
style="width:150px" style="width:150px"
placeholder="Select Clarity" placeholder="Select Clarity"
@select="onClaritySelect" @select="onClaritySelect"
v-model:value="claritySelected"
> >
<a-select-option <a-select-option
v-for="item in clarityList" v-for="item in clarityList"
@ -85,14 +98,15 @@
</p> </p>
</div> </div>
<div class="mt10 flex-row flex-justify-center flex-align-center"> <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" <a-button class="ml20" type="primary" large @click="onStop"
>Stop</a-button >Stop</a-button
> >
<a-button class="ml20" type="primary" large @click="onUpdateQuality" <a-button class="ml20" type="primary" large @click="onUpdateQuality"
>Update Clarity</a-button >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 >Refresh Live Capacity</a-button
> >
</div> </div>
@ -103,12 +117,18 @@
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { onMounted, reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { CURRENT_CONFIG as config } from '/@/api/http/config' 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 { getRoot } from '/@/root'
import jswebrtc from '/@/vendors/jswebrtc.min.js' import jswebrtc from '/@/vendors/jswebrtc.min.js'
const root = getRoot() const root = getRoot()
const liveTypeList = [ interface SelectOption {
value: any,
label: string,
more?: any
}
const liveTypeList: SelectOption[] = [
{ {
value: 1, value: 1,
label: 'RTMP' label: 'RTMP'
@ -122,7 +142,7 @@ const liveTypeList = [
label: 'GB28181' label: 'GB28181'
} }
] ]
const clarityList = [ const clarityList: SelectOption[] = [
{ {
value: 0, value: 0,
label: 'Adaptive' label: 'Adaptive'
@ -152,12 +172,16 @@ const cameraList = ref()
const videoList = ref() const videoList = ref()
const droneSelected = ref() const droneSelected = ref()
const cameraSelected = ref() const cameraSelected = ref()
const videoSeleted = ref() const videoSelected = ref()
const claritySeleted = ref() const claritySelected = ref()
const videoId = ref() const videoId = ref()
const liveState = ref<boolean>(false) const liveState = ref<boolean>(false)
const livetypeSelected = ref() const livetypeSelected = ref()
const rtspData = ref() const rtspData = ref()
const lensList = ref<string[]>([])
const lensSelected = ref<String>()
const isDockLive = ref(false)
const nonSwitchable = 'normal'
const onRefresh = async () => { const onRefresh = async () => {
droneList.value = [] droneList.value = []
@ -165,7 +189,7 @@ const onRefresh = async () => {
videoList.value = [] videoList.value = []
droneSelected.value = null droneSelected.value = null
cameraSelected.value = null cameraSelected.value = null
videoSeleted.value = null videoSelected.value = null
await getLiveCapacity({}) await getLiveCapacity({})
.then(res => { .then(res => {
console.log(res) console.log(res)
@ -178,16 +202,17 @@ const onRefresh = async () => {
console.log('live_capacity:', resData) console.log('live_capacity:', resData)
livestreamSource.value = resData livestreamSource.value = resData
const temp: Array<{}> = [] const temp: Array<SelectOption> = []
if (livestreamSource.value) { if (livestreamSource.value) {
livestreamSource.value.forEach((ele: any) => { 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 droneList.value = temp
} }
} }
}) })
.catch(error => { .catch(error => {
message.error(error)
console.error(error) console.error(error)
}) })
} }
@ -201,22 +226,22 @@ const onStart = async () => {
livetypeSelected.value, livetypeSelected.value,
droneSelected.value, droneSelected.value,
cameraSelected.value, cameraSelected.value,
videoSeleted.value, videoSelected.value,
claritySeleted.value claritySelected.value
) )
const timestamp = new Date().getTime().toString() const timestamp = new Date().getTime().toString()
if ( if (
livetypeSelected.value == null || livetypeSelected.value == null ||
droneSelected.value == null || droneSelected.value == null ||
cameraSelected.value == null || cameraSelected.value == null ||
videoSeleted.value == null || claritySelected.value == null
claritySeleted.value == null
) { ) {
message.warn('waring: not select live para!!!') message.warn('waring: not select live para!!!')
return return
} }
videoId.value = videoId.value =
droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')
let liveURL = '' let liveURL = ''
switch (livetypeSelected.value) { switch (livetypeSelected.value) {
case 1: { case 1: {
@ -241,9 +266,12 @@ const onStart = async () => {
url: liveURL, url: liveURL,
video_id: videoId.value, video_id: videoId.value,
url_type: livetypeSelected.value, url_type: livetypeSelected.value,
video_quality: claritySeleted.value video_quality: claritySelected.value
}) })
.then(res => { .then(res => {
if (res.code !== 0) {
return
}
if (livetypeSelected.value === 3) { if (livetypeSelected.value === 3) {
const url = res.data.url const url = res.data.url
const videoElement = videowebrtc.value const videoElement = videowebrtc.value
@ -259,7 +287,6 @@ const onStart = async () => {
console.log('start play livestream') console.log('start play livestream')
} }
}) })
liveState.value = true
} }
}) })
} else if (livetypeSelected.value === 2) { } else if (livetypeSelected.value === 2) {
@ -281,24 +308,23 @@ const onStart = async () => {
autoplay: true, autoplay: true,
onPlay: (obj: any) => { onPlay: (obj: any) => {
console.log('start play livestream') console.log('start play livestream')
liveState.value = true
} }
}) })
} }
liveState.value = true
}) })
.catch(err => { .catch(err => {
console.error(err) console.error(err)
}) })
} }
const onStop = () => { const onStop = () => {
videoId.value =
droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value
stopLivestream({ stopLivestream({
video_id: videoId.value video_id: videoId.value
}).then(res => { }).then(res => {
if (res.code === 0) { if (res.code === 0) {
message.info(res.message) message.success(res.message)
liveState.value = false liveState.value = false
lensSelected.value = undefined
console.log('stop play livestream') console.log('stop play livestream')
} }
}) })
@ -311,10 +337,10 @@ const onUpdateQuality = () => {
} }
setLivestreamQuality({ setLivestreamQuality({
video_id: videoId.value, video_id: videoId.value,
video_quality: claritySeleted.value video_quality: claritySelected.value
}).then(res => { }).then(res => {
if (res.code === 0) { 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 +348,66 @@ const onUpdateQuality = () => {
const onLiveTypeSelect = (val: any) => { const onLiveTypeSelect = (val: any) => {
livetypeSelected.value = val livetypeSelected.value = val
} }
const onDroneSelect = (val: any) => { const onDroneSelect = (val: SelectOption) => {
droneSelected.value = val droneSelected.value = val.value
const temp: Array<{}> = [] const temp: Array<SelectOption> = []
cameraList.value = [] cameraList.value = []
if (droneSelected.value) { cameraSelected.value = undefined
const droneTemp = livestreamSource.value videoSelected.value = undefined
droneTemp.forEach((ele: any) => { videoList.value = []
const drone = ele lensList.value = []
if (drone.cameras_list && drone.sn === droneSelected.value) { if (!val.more) {
const cameraListTemp = drone.cameras_list return
console.info(ele)
cameraListTemp.forEach((ele: any) => {
temp.push({ label: ele.name, value: ele.index })
})
cameraList.value = temp
} }
val.more.forEach((ele: any) => {
temp.push({ label: ele.name, value: ele.index, more: ele.videos_list })
}) })
cameraList.value = temp
} }
const onCameraSelect = (val: SelectOption) => {
cameraSelected.value = val.value
const result: Array<SelectOption> = []
videoSelected.value = undefined
videoList.value = []
lensList.value = []
if (!val.more) {
return
} }
const onCameraSelect = (val: any) => {
cameraSelected.value = val val.more.forEach((ele: any) => {
const result: Array<{}> = [] result.push({ label: ele.type, value: ele.index, more: ele.switch_video_types })
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 videoList.value = result
videoSeleted.value = videoList.value[0]?.value 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: SelectOption) => {
videoSelected.value = val.value
lensList.value = val.more
lensSelected.value = val.label
} }
const onClaritySelect = (val: any) => {
claritySelected.value = val
} }
const onVideoSelect = (val: any) => { const onSwitch = () => {
videoSeleted.value = val if (lensSelected.value === undefined || lensSelected.value === nonSwitchable) {
message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8)
return
} }
const onClaritySelect = (val: any) => { changeLivestreamLens({
claritySeleted.value = val video_id: videoId.value,
video_type: lensSelected.value
}).then(res => {
if (res.code === 0) {
message.success('Switching live camera successfully.')
}
})
} }
</script> </script>

81
src/pages/page-web/projects/create-plan.vue → src/components/task/CreatePlan.vue

@ -1,5 +1,5 @@
<template> <template>
<div class="plan"> <div class="create-plan-wrapper">
<div class="header"> <div class="header">
Create Plan Create Plan
</div> </div>
@ -8,6 +8,7 @@
<a-form-item label="Plan Name" name="name" :labelCol="{span: 24}"> <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-input style="background: black;" placeholder="Please enter plan name" v-model:value="planBody.name"/>
</a-form-item> </a-form-item>
<!-- 航线 -->
<a-form-item label="Flight Route" :wrapperCol="{offset: 7}" name="file_id"> <a-form-item label="Flight Route" :wrapperCol="{offset: 7}" name="file_id">
<router-link <router-link
:to="{name: 'select-plan'}" :to="{name: 'select-plan'}"
@ -40,6 +41,7 @@
</div> </div>
</div> </div>
</a-form-item> </a-form-item>
<!-- 设备 -->
<a-form-item label="Device" :wrapperCol="{offset: 10}" v-model:value="planBody.dock_sn" name="dock_sn"> <a-form-item label="Device" :wrapperCol="{offset: 10}" v-model:value="planBody.dock_sn" name="dock_sn">
<router-link <router-link
:to="{name: 'select-plan'}" :to="{name: 'select-plan'}"
@ -59,12 +61,24 @@
</div> </div>
</div> </div>
</a-form-item> </a-form-item>
<a-form-item label="Immediate"> <!-- 任务类型 -->
<a-switch v-model:checked="planBody.immediate"> <a-form-item label="Plan Timer" class="plan-timer-form-item">
<template #checkedChildren><CheckOutlined /></template> <div style="white-space: nowrap;">
<template #unCheckedChildren><CloseOutlined /></template> <a-radio-group v-model:value="planBody.task_type" button-style="solid">
</a-switch> <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 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"
/>
</a-form-item> </a-form-item>
<!-- 操作 -->
<a-form-item style="position: absolute; bottom: 0px; margin-bottom: 0; margin-left: -10px; width: 280px;"> <a-form-item style="position: absolute; bottom: 0px; margin-bottom: 0; margin-left: -10px; width: 280px;">
<div class="footer"> <div class="footer">
<a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel <a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel
@ -88,14 +102,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue' import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'
import { CheckOutlined, CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue' import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { ELocalStorageKey, ERouterName } from '/@/types' import { ELocalStorageKey, ERouterName } from '/@/types'
import { useMyStore } from '/@/store' import { useMyStore } from '/@/store'
import { WaylineFile } from '/@/types/wayline' import { WaylineType, WaylineFile } from '/@/types/wayline'
import { Device, EDeviceType } from '/@/types/device' import { Device, EDeviceType } from '/@/types/device'
import { createPlan, CreatePlan } from '/@/api/wayline' import { createPlan, CreatePlan } from '/@/api/wayline'
import { getRoot } from '/@/root' import { getRoot } from '/@/root'
import { TaskType } from '/@/types/task'
import moment, { Moment } from 'moment'
import { message } from 'ant-design-vue'
const root = getRoot() const root = getRoot()
const store = useMyStore() const store = useMyStore()
@ -113,13 +129,14 @@ const dock = computed<Device>(() => {
const disabled = ref(false) const disabled = ref(false)
const routeName = ref('') const routeName = ref('')
const planBody: UnwrapRef<CreatePlan> = reactive({ const planBody = reactive({
name: '', name: '',
file_id: computed(() => store.state.waylineInfo.id), file_id: computed(() => store.state.waylineInfo.id),
dock_sn: computed(() => store.state.dockInfo.device_sn), dock_sn: computed(() => store.state.dockInfo.device_sn),
immediate: false, task_type: TaskType.Immediate,
type: 'wayline' select_execute_time: undefined as Moment| undefined
}) })
const drawerVisible = ref(false) const drawerVisible = ref(false)
const valueRef = ref() const valueRef = ref()
const rules = { const rules = {
@ -128,21 +145,31 @@ const rules = {
{ max: 20, message: 'Length should be 1 to 20', trigger: 'blur' } { max: 20, message: 'Length should be 1 to 20', trigger: 'blur' }
], ],
file_id: [{ required: true, message: 'Select Route' }], 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' }]
} }
function onSubmit () { function onSubmit () {
valueRef.value.validate().then(() => { valueRef.value.validate().then(() => {
disabled.value = true 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()
}
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 => { .then(res => {
message.success('Saved Successfully')
setTimeout(() => { setTimeout(() => {
disabled.value = false disabled.value = false
}, 1500) }, 1500)
}).finally(() => { }).finally(() => {
closePlan() closePlan()
}) })
}).catch((e: any) => {
console.log('validate err', e)
}) })
} }
@ -167,14 +194,14 @@ function selectDevice () {
</script> </script>
<style lang="scss"> <style lang="scss">
.create-plan-wrapper {
.plan {
background-color: #232323; background-color: #232323;
color: white; color: white;
padding-bottom: 0; padding-bottom: 0;
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.header { .header {
height: 53px; height: 53px;
border-bottom: 1px solid #4f4f4f; border-bottom: 1px solid #4f4f4f;
@ -184,15 +211,31 @@ function selectDevice () {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.content { .content {
height: 100%; height: 100%;
form { form {
margin: 10px; margin: 10px;
} }
form label, input { form label, input {
color: white; background-color: #232323;
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 { .footer {
display: flex; display: flex;
align-items: center; align-items: center;
@ -208,6 +251,7 @@ function selectDevice () {
} }
} }
} }
.wayline-panel { .wayline-panel {
background: #3c3c3c; background: #3c3c3c;
margin-left: auto; margin-left: auto;
@ -228,6 +272,7 @@ function selectDevice () {
margin: 0px 10px 0 10px; margin: 0px 10px 0 10px;
} }
} }
.panel { .panel {
background: #3c3c3c; background: #3c3c3c;
margin-left: auto; margin-left: auto;

233
src/components/task/TaskPanel.vue

@ -0,0 +1,233 @@
<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 #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: 200,
slots: { customRender: 'duration' },
},
{
title: 'Plan Name',
dataIndex: 'job_name',
width: 150,
ellipsis: true
},
{
title: 'Type',
dataIndex: 'taskType',
width: 150,
slots: { customRender: 'taskType' },
},
{
title: 'Flight Route Name',
dataIndex: 'file_name',
width: 150,
ellipsis: true
},
{
title: 'Dock Name',
dataIndex: 'dock_name',
width: 150,
ellipsis: true
},
{
title: 'Creator',
dataIndex: 'username',
width: 150,
},
{
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, 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>

30
src/components/task/use-format-task.ts

@ -0,0 +1,30 @@
import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'
import { Task } from '/@/api/wayline'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap } 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 formatTaskStatus (task: Task) {
const statusObj = {
text: '',
color: ''
}
const { status } = task
statusObj.text = TaskStatusMap[status]
statusObj.color = TaskStatusColor[status]
return statusObj
}
return {
formatTaskType,
formatTaskTime,
formatTaskStatus,
}
}

19
src/components/task/use-task-progress-event.ts

@ -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)
})
}

123
src/components/wayline-panel.vue

@ -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>

5
src/event-bus/index.ts

@ -1,8 +1,9 @@
import mitt, { Emitter } from 'mitt' import mitt, { Emitter } from 'mitt'
type Events = { type Events = {
deviceUpgrade: any; deviceUpgrade: any; // 设备升级
deviceLogUploadProgress: any deviceLogUploadProgress: any // 设备日志上传
deviceTaskProgress: any // 设备任务进度
}; };
const emitter: Emitter<Events> = mitt<Events>() const emitter: Emitter<Events> = mitt<Events>()

23
src/hooks/use-g-map-tsa.ts

@ -3,18 +3,19 @@ import { getRoot } from '/@/root'
import { ELocalStorageKey } from '/@/types' import { ELocalStorageKey } from '/@/types'
import { getDeviceBySn } from '/@/api/manage' import { getDeviceBySn } from '/@/api/manage'
import { message } from 'ant-design-vue' 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 () { export function deviceTsaUpdate () {
const root = getRoot() const root = getRoot()
const AMap = root.$aMap const AMap = root.$aMap
const icons: { const icons = new Map([
[key: string]: string ['sub-device', droneIcon],
} = { ['gateway', rcIcon],
'sub-device': '/@/assets/icons/drone.png', ['dock', dockIcon]
'gateway': '/@/assets/icons/rc.png', ])
'dock': '/@/assets/icons/dock.png'
}
const markers = store.state.markerInfo.coverMap const markers = store.state.markerInfo.coverMap
const paths = store.state.markerInfo.pathMap const paths = store.state.markerInfo.pathMap
@ -33,13 +34,14 @@ export function deviceTsaUpdate () {
function initIcon (type: string) { function initIcon (type: string) {
return new AMap.Icon({ return new AMap.Icon({
image: icons[type], image: icons.get(type),
imageSize: new AMap.Size(40, 40) imageSize: new AMap.Size(40, 40),
size: new AMap.Size(40, 40)
}) })
} }
function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) { function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) {
if (markers[sn]) { if (markers[sn] || AMap === undefined) {
return return
} }
markers[sn] = new AMap.Marker({ markers[sn] = new AMap.Marker({
@ -50,7 +52,6 @@ export function deviceTsaUpdate () {
offset: [0, -20], offset: [0, -20],
}) })
root.$map.add(markers[sn]) root.$map.add(markers[sn])
// markers[sn].on('moving', function (e: any) { // markers[sn].on('moving', function (e: any) {
// let path = paths[sn] // let path = paths[sn]
// if (!path) { // if (!path) {

2
src/pages/page-pilot/pilot-home.vue

@ -285,7 +285,7 @@ onMounted(() => {
// thing // thing
const param: ThingParam = { const param: ThingParam = {
host: res.data.mqtt_addr, host: res.data.mqtt_addr,
username: res.data.mqtt_username, username: res.data.mqtt.username,
password: res.data.mqtt_password, password: res.data.mqtt_password,
connectCallback: 'connectCallback' connectCallback: 'connectCallback'
} }

2
src/pages/page-web/projects/dock.vue

@ -9,7 +9,7 @@
</div> </div>
<div v-if="docksData.data.length !== 0"> <div v-if="docksData.data.length !== 0">
<div v-for="dock in docksData.data" :key="dock.device_sn"> <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"> <div class="title">
<a-tooltip :title="dock.nickname"> <a-tooltip :title="dock.nickname">
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div> <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>

26
src/pages/page-web/projects/task.vue

@ -5,21 +5,21 @@
<a-col :span="1"></a-col> <a-col :span="1"></a-col>
<a-col :span="20">Task Plan Library</a-col> <a-col :span="20">Task Plan Library</a-col>
<a-col :span="2"> <a-col :span="2">
<span v-if="!createPlanTip"> <span v-if="taskRoute">
<router-link :to="{ name: 'create-plan'}"> <router-link :to="{ name: ERouterName.CREATE_PLAN}">
<PlusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = true"/> <PlusOutlined class="route-icon"/>
</router-link> </router-link>
</span> </span>
<span v-else> <span v-else>
<router-link :to="{ name: 'task'}"> <router-link :to="{ name: ERouterName.TASK}">
<MinusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = false"/> <MinusOutlined class="route-icon"/>
</router-link> </router-link>
</span> </span>
</a-col> </a-col>
<a-col :span="1"></a-col> <a-col :span="1"></a-col>
</a-row> </a-row>
</div> </div>
<div v-if="createPlanTip"> <div v-if="!taskRoute">
<router-view/> <router-view/>
</div> </div>
</div> </div>
@ -27,12 +27,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PlusOutlined, MinusOutlined } from '@ant-design/icons-vue' 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> </script>
<style lang="scss"> <style lang="scss">
.route-icon {
color: #fff;
font-size: 16px;
}
</style> </style>

5
src/pages/page-web/projects/workspace.vue

@ -23,12 +23,13 @@
import Sidebar from '/@/components/common/sidebar.vue' import Sidebar from '/@/components/common/sidebar.vue'
import MediaPanel from '/@/components/MediaPanel.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 GMap from '/@/components/GMap.vue'
import { EBizCode, ERouterName } from '/@/types' import { EBizCode, ERouterName } from '/@/types'
import { getRoot } from '/@/root' import { getRoot } from '/@/root'
import { useMyStore } from '/@/store' import { useMyStore } from '/@/store'
import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' import { useConnectWebSocket } from '/@/hooks/use-connect-websocket'
import EventBus from '/@/event-bus'
const root = getRoot() const root = getRoot()
const store = useMyStore() const store = useMyStore()
@ -72,7 +73,7 @@ const messageHandler = async (payload: any) => {
break break
} }
case EBizCode.FlightTaskProgress: { case EBizCode.FlightTaskProgress: {
store.commit('SET_FLIGHT_TASK_PROGRESS', payload.data) EventBus.emit('deviceTaskProgress', payload)
break break
} }
case EBizCode.DeviceHms: { case EBizCode.DeviceHms: {

2
src/router/index.ts

@ -1,6 +1,6 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { ERouterName } from '/@/types/index' 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 WaylinePanel from '/@/pages/page-web/projects/wayline.vue'
import DockPanel from '/@/pages/page-web/projects/dock.vue' import DockPanel from '/@/pages/page-web/projects/dock.vue'
import LiveAgora from '/@/components/livestream-agora.vue' import LiveAgora from '/@/components/livestream-agora.vue'

18
src/store/index.ts

@ -4,7 +4,7 @@ import { EDeviceTypeName } from '../types'
import { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device' import { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device'
import { getLayers } from '/@/api/layer' import { getLayers } from '/@/api/layer'
import { LayerType } from '/@/types/mapLayer' import { LayerType } from '/@/types/mapLayer'
import { ETaskStatus, TaskInfo, WaylineFile } from '/@/types/wayline' import { WaylineFile } from '/@/types/wayline'
import { DevicesCmdExecuteInfo } from '/@/types/device-cmd' import { DevicesCmdExecuteInfo } from '/@/types/device-cmd'
const initStateFunc = () => ({ const initStateFunc = () => ({
@ -81,11 +81,6 @@ const initStateFunc = () => ({
dockInfo: { dockInfo: {
} as Device, } as Device,
taskProgressInfo: {
} as {
[bid: string]: TaskInfo
},
hmsInfo: {} as { hmsInfo: {} as {
[sn: string]: DeviceHms[] [sn: string]: DeviceHms[]
}, },
@ -160,17 +155,6 @@ const mutations: MutationTree<RootStateType> = {
SET_SELECT_DOCK_INFO (state, info) { SET_SELECT_DOCK_INFO (state, info) {
state.dockInfo = 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) { SET_DEVICE_HMS_INFO (state, info) {
const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn] const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn]
state.hmsInfo[info.sn] = info.host.concat(hmsList ?? []) state.hmsInfo[info.sn] = info.host.concat(hmsList ?? [])

2
src/types/enums.ts

@ -90,7 +90,7 @@ export enum EBizCode {
MapElementDelete = 'map_element_delete', MapElementDelete = 'map_element_delete',
DeviceOnline = 'device_online', DeviceOnline = 'device_online',
DeviceOffline = 'device_offline', DeviceOffline = 'device_offline',
FlightTaskProgress = 'flighttask_progress', FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度
DeviceHms = 'device_hms', DeviceHms = 'device_hms',
// 设备指令 // 设备指令

78
src/types/task.ts

@ -0,0 +1,78 @@
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 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,
}

29
src/types/wayline.ts

@ -1,30 +1,15 @@
// 航线类型
export enum WaylineType {
NormalWaypointWayline = 0, // 普通航点航线
AccurateReshootingWayline = 1 // 精准复拍航线
}
export interface WaylineFile { export interface WaylineFile {
id: string, id: string,
name: string, name: string,
drone_model_key: any, drone_model_key: any,
payload_model_keys: string[], payload_model_keys: string[],
template_types: number[], template_types: WaylineType[],
update_time: number, update_time: number,
user_name: string, 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'
}

310
src/utils/error-code/index.ts

@ -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',
},
]

5
tsconfig.json

@ -24,7 +24,6 @@
"src/**/*.ts", "src/**/*.ts",
"src/**/*.d.ts", "src/**/*.d.ts",
"src/**/*.tsx", "src/**/*.tsx",
"src/**/*.vue", "src/**/*.vue"
"src/vendors/coordtransform.js" , "src/vendors/coordtransform.js" ]
]
} }
Loading…
Cancel
Save