Showing preview only (660K chars total). Download the full file or copy to clipboard to get everything.
Repository: dji-sdk/Cloud-API-Demo-Web
Branch: main
Commit: bfcf3ee998b8
Files: 166
Total size: 603.0 KB
Directory structure:
gitextract_ex2lupb9/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .gitignore copy
├── .npmrc
├── LICENSE
├── README.md
├── index.html
├── package.json
├── src/
│ ├── App.vue
│ ├── antd.ts
│ ├── api/
│ │ ├── device-cmd/
│ │ │ └── index.ts
│ │ ├── device-log/
│ │ │ └── index.ts
│ │ ├── device-setting/
│ │ │ └── index.ts
│ │ ├── device-upgrade/
│ │ │ └── index.ts
│ │ ├── drc.ts
│ │ ├── drone-control/
│ │ │ ├── drone.ts
│ │ │ └── payload.ts
│ │ ├── flight-area/
│ │ │ └── index.ts
│ │ ├── http/
│ │ │ ├── config.ts
│ │ │ ├── request.ts
│ │ │ └── type.ts
│ │ ├── http.ts
│ │ ├── layer.ts
│ │ ├── manage.ts
│ │ ├── media.ts
│ │ ├── pilot-bridge.ts
│ │ └── wayline.ts
│ ├── components/
│ │ ├── GMap.vue
│ │ ├── LayersTree.vue
│ │ ├── MediaPanel.vue
│ │ ├── common/
│ │ │ ├── sidebar.vue
│ │ │ └── topbar.vue
│ │ ├── devices/
│ │ │ ├── DeviceFirmwareStatus.vue
│ │ │ ├── device-hms/
│ │ │ │ └── DeviceHmsDrawer.vue
│ │ │ ├── device-log/
│ │ │ │ ├── DeviceLogDetailModal.vue
│ │ │ │ ├── DeviceLogUploadModal.vue
│ │ │ │ ├── DeviceLogUploadRecordDrawer.vue
│ │ │ │ ├── use-device-log-upload-detail.ts
│ │ │ │ └── use-device-log-upload-progress-event.ts
│ │ │ └── device-upgrade/
│ │ │ ├── DeviceFirmwareUpgrade.vue
│ │ │ ├── DeviceFirmwareUpgradeModal.vue
│ │ │ ├── use-device-upgrade-event.ts
│ │ │ └── use-device-upgrade.ts
│ │ ├── flight-area/
│ │ │ ├── FlightAreaActionIcon.vue
│ │ │ ├── FlightAreaDevicePanel.vue
│ │ │ ├── FlightAreaIcon.vue
│ │ │ ├── FlightAreaItem.vue
│ │ │ ├── FlightAreaPanel.vue
│ │ │ ├── FlightAreaSyncPanel.vue
│ │ │ ├── use-flight-area-drone-location-event.ts
│ │ │ ├── use-flight-area-sync-progress-event.ts
│ │ │ ├── use-flight-area-update.ts
│ │ │ └── use-flight-area.ts
│ │ ├── g-map/
│ │ │ ├── DeviceSettingBox.vue
│ │ │ ├── DeviceSettingPopover.vue
│ │ │ ├── DockControlPanel.vue
│ │ │ ├── DroneControlInfoPanel.vue
│ │ │ ├── DroneControlPanel.vue
│ │ │ ├── DroneControlPopover.vue
│ │ │ ├── use-connect-mqtt.ts
│ │ │ ├── use-device-setting.ts
│ │ │ ├── use-dock-control.ts
│ │ │ ├── use-drone-control-mqtt-event.ts
│ │ │ ├── use-drone-control-ws-event.ts
│ │ │ ├── use-drone-control.ts
│ │ │ ├── use-manual-control.ts
│ │ │ ├── use-mqtt.ts
│ │ │ └── use-payload-control.ts
│ │ ├── livestream-agora.vue
│ │ ├── livestream-others.vue
│ │ ├── svgIcon.vue
│ │ ├── task/
│ │ │ ├── CreatePlan.vue
│ │ │ ├── TaskPanel.vue
│ │ │ ├── use-format-task.ts
│ │ │ └── use-task-ws-event.ts
│ │ └── workspace/
│ │ ├── DividerLine.vue
│ │ └── Title.vue
│ ├── constants/
│ │ ├── index.ts
│ │ ├── map.ts
│ │ └── mock-layers.ts
│ ├── directives/
│ │ ├── drag-window.ts
│ │ └── index.ts
│ ├── env.d.ts
│ ├── event-bus/
│ │ └── index.ts
│ ├── hooks/
│ │ ├── use-connect-websocket.ts
│ │ ├── use-g-map-cover.ts
│ │ ├── use-g-map-tsa.ts
│ │ ├── use-g-map.ts
│ │ ├── use-map-tool.ts
│ │ └── use-mouse-tool.ts
│ ├── main.ts
│ ├── mqtt/
│ │ ├── config.ts
│ │ └── index.ts
│ ├── pages/
│ │ ├── page-pilot/
│ │ │ ├── pilot-bind.vue
│ │ │ ├── pilot-home.vue
│ │ │ ├── pilot-index.vue
│ │ │ ├── pilot-liveshare.vue
│ │ │ └── pilot-media.vue
│ │ └── page-web/
│ │ ├── home.vue
│ │ ├── index.vue
│ │ └── projects/
│ │ ├── Firmwares.vue
│ │ ├── devices.vue
│ │ ├── dock.vue
│ │ ├── flight-area.vue
│ │ ├── layer.vue
│ │ ├── livestream.vue
│ │ ├── media.vue
│ │ ├── members.vue
│ │ ├── task.vue
│ │ ├── tsa.vue
│ │ ├── wayline.vue
│ │ └── workspace.vue
│ ├── plugins/
│ │ └── svgBuilder.ts
│ ├── root.ts
│ ├── router/
│ │ └── index.ts
│ ├── shims-mqtt.d.ts
│ ├── shims-vue.d.ts
│ ├── store/
│ │ └── index.ts
│ ├── styles/
│ │ ├── common.scss
│ │ ├── flex.style.scss
│ │ ├── fonts.scss
│ │ ├── index.scss
│ │ ├── reset.scss
│ │ └── variables.scss
│ ├── types/
│ │ ├── airport-tsa.ts
│ │ ├── device-cmd.ts
│ │ ├── device-firmware.ts
│ │ ├── device-log.ts
│ │ ├── device-setting.ts
│ │ ├── device.ts
│ │ ├── drc.ts
│ │ ├── drone-control.ts
│ │ ├── enums.ts
│ │ ├── flight-area.ts
│ │ ├── index.ts
│ │ ├── live-stream.ts
│ │ ├── map-enum.ts
│ │ ├── map.d.ts
│ │ ├── mapLayer.ts
│ │ ├── task.ts
│ │ └── wayline.ts
│ ├── use-common-components.ts
│ ├── utils/
│ │ ├── bytes.ts
│ │ ├── color.ts
│ │ ├── common.ts
│ │ ├── constants.ts
│ │ ├── data-process.ts
│ │ ├── device-cmd.ts
│ │ ├── device-setting.ts
│ │ ├── download.ts
│ │ ├── error-code/
│ │ │ └── index.ts
│ │ ├── genjson.ts
│ │ ├── layer-tree.ts
│ │ ├── logger.ts
│ │ ├── map-layer-utils.ts
│ │ ├── storage.ts
│ │ ├── time.ts
│ │ └── uuid.ts
│ ├── vendors/
│ │ ├── coordtransform.js
│ │ └── srs.sdk.js
│ ├── vite-env.d.ts
│ └── websocket/
│ ├── index.ts
│ └── util/
│ └── config.ts
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
/src/vendors/**
================================================
FILE: .eslintrc.js
================================================
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true
},
extends: ['standard', 'plugin:vue/vue3-essential'],
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser'
},
plugins: ['vue', '@typescript-eslint'],
rules: {
'comma-dangle': 'off',
'import/no-absolute-path': 'off',
'no-unused-vars': 'off',
camelcase: 'off',
'no-redeclare': 'off',
'vue/no-unused-components': 'off'
}
}
================================================
FILE: .gitignore
================================================
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules/
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
# .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.history
/coverage
/backup
node_modules
================================================
FILE: .gitignore copy
================================================
node_modules
.DS_Store
dist
dist-ssr
*.local
================================================
FILE: .npmrc
================================================
registry=https://registry.npmmirror.com/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 DJI-SDK
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# 关于DJI Cloud API Demo 终止维护公告
发布日期:2025年4月10日
1.项目终止维护说明
即日起,大疆创新(DJI)将停止对DJI Cloud API Demo (地址:https://github.com/dji-sdk/Cloud-API-Demo-Web、https://github.com/dji-sdk/DJI-Cloud-API-Demo)示例项目的更新与技术支持。
该项目作为官方提供的云端集成参考实现,旨在辅助开发者理解API调用逻辑。并非生产级解决方案,可能存在未修复的安全隐患(如数据泄露、未授权访问等)。请避免在生产环境中直接使用Demo中的代码,若直接使用我们强烈建议您启动安全自查,或避免将基于该Demo的服务暴露于公网环境。
2.免责声明
因直接使用Demo代码导致的业务损失、数据风险或第三方纠纷,DJI将不承担任何责任。
3.后续支持
如有疑问,请联系DJI开发者支持团队(邮箱:developer@dji.com)或访问大疆开发者社区获取最新技术资源。
感谢您一直以来的理解与支持!
# DJI Cloud API
## What is the DJI Cloud API?
The launch of the Cloud API mainly solves the problem of developers reinventing the wheel. For developers who do not need in-depth customization of APP, they can directly use DJI Pilot2 to communicate with the third cloud platform, and developers can focus on the development and implementation of cloud service interfaces.
## Docker
If you don't want to install the development environment, you can try deploying with docker. [Click the link to download.](https://terra-sz-hc1pro-cloudapi.oss-cn-shenzhen.aliyuncs.com/c0af9fe0d7eb4f35a8fe5b695e4d0b96/docker/cloud_api_sample_docker.zip)
## Usage
For more documentation, please visit the [DJI Developer Documentation](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
## Latest Release
Cloud API 1.10.0 was released on 7 April 2024. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
## License
Cloud API is MIT-licensed. Please refer to the LICENSE file for more information.
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>demo-web</title>
</head>
<body>
<div id="demo-app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "demo-web",
"version": "0.0.1",
"scripts": {
"serve": "vite",
"build:test": "vite build --mode stag",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --fix"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@ant-design/icons-vue": "^6.0.1",
"@vitejs/plugin-legacy": "^1.6.2",
"agora-rtc-sdk-ng": "^4.12.1",
"ant-design-vue": "^2.2.8",
"axios": "^0.21.1",
"eventemitter3": "^5.0.0",
"mitt": "^3.0.0",
"mqtt": "^4.3.7",
"query-string": "^7.0.1",
"reconnecting-websocket": "^4.4.0",
"vconsole": "^3.8.1",
"vite-plugin-components": "^0.13.3",
"vite-plugin-importer": "^0.2.5",
"vite-plugin-optimize-persist": "^0.1.2",
"vite-plugin-package-config": "^0.1.1",
"vue": "^3.2.26",
"vue-cookies": "^1.7.4",
"vue-i18n": "^9.1.6",
"vue-router": "4",
"vuex": "^4.0.2"
},
"devDependencies": {
"@types/node": "^16.3.2",
"@types/urlencode": "^1.1.2",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"@vitejs/plugin-vue": "^1.2.4",
"@vue/compiler-sfc": "^3.0.5",
"eslint": "^7.30.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.13.0",
"rollup-plugin-external-globals": "^0.6.1",
"sass": "^1.35.1",
"typescript": "^4.5.4",
"vite": "^2.4.0",
"vite-plugin-eslint": "^1.3.0",
"vite-plugin-style-import": "^1.0.1",
"vite-plugin-svg-icons": "^1.0.5",
"vite-plugin-vconsole": "^1.1.0",
"vue-tsc": "^0.0.24"
},
"license": "ISC",
"vite": {
"optimizeDeps": {
"include": [
"@amap/amap-jsapi-loader",
"@ant-design/icons-vue",
"@vue/reactivity",
"agora-rtc-sdk-ng",
"ant-design-vue",
"ant-design-vue/es",
"ant-design-vue/es/avatar/style/css",
"ant-design-vue/es/breadcrumb/style/css",
"ant-design-vue/es/button/style/css",
"ant-design-vue/es/checkbox/style/css",
"ant-design-vue/es/col/style/css",
"ant-design-vue/es/collapse/style/css",
"ant-design-vue/es/date-picker/style/css",
"ant-design-vue/es/divider/style/css",
"ant-design-vue/es/drawer/style/css",
"ant-design-vue/es/dropdown/style/css",
"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",
"ant-design-vue/es/message/style/css",
"ant-design-vue/es/modal/style/css",
"ant-design-vue/es/pagination/style/css",
"ant-design-vue/es/popconfirm/style/css",
"ant-design-vue/es/popover/style/css",
"ant-design-vue/es/progress/style/css",
"ant-design-vue/es/radio/style/css",
"ant-design-vue/es/row/style/css",
"ant-design-vue/es/select/style/css",
"ant-design-vue/es/space/style/css",
"ant-design-vue/es/spin/style/css",
"ant-design-vue/es/switch/style/css",
"ant-design-vue/es/table/style/css",
"ant-design-vue/es/tag/style/css",
"ant-design-vue/es/time-picker/style/css",
"ant-design-vue/es/tooltip/style/css",
"ant-design-vue/es/tree/style/css",
"ant-design-vue/es/upload/style/css",
"axios",
"eventemitter3",
"lodash",
"mitt",
"moment",
"mqtt",
"mqtt/dist/mqtt.min",
"reconnecting-websocket",
"vconsole",
"vue",
"vue-router",
"vuex"
]
}
}
}
================================================
FILE: src/App.vue
================================================
<template>
<div class="demo-app">
<router-view />
<!-- <div class="map-wrapper">
<GMap/>
</div> -->
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from 'vue'
import { useMyStore } from './store'
import GMap from '/@/components/GMap.vue'
export default defineComponent({
name: 'App',
components: { GMap },
setup () {
const store = useMyStore()
return {}
}
})
</script>
<style lang="scss" scoped>
.demo-app {
width: 100%;
height: 100%;
.map-wrapper {
height: 100%;
width: 100%;
}
}
</style>
<style lang="scss">
#demo-app {
width: 100%;
height: 100%
}
</style>
================================================
FILE: src/antd.ts
================================================
// import Icon from '@ant-design/icons-vue'
import * as antDesign from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
import { App } from 'vue'
import svgIcon from '/@/components/svgIcon.vue'
export const antComponents = {
install (app: App): void {
app.use(antDesign)
// app.component('Icon', Icon)
app.component('svg-icon', svgIcon)
}
}
================================================
FILE: src/api/device-cmd/index.ts
================================================
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
const CMD_API_PREFIX = '/control/api/v1'
export interface SendCmdParams {
dock_sn: string, // 机场cn
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, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> {
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, body)
return resp.data
}
================================================
FILE: src/api/device-log/index.ts
================================================
import request, { IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'
import { DeviceValue, DOMAIN } from '/@/types/device'
import { DeviceLogUploadStatusEnum } from '/@/types/device-log'
import { ELocalStorageKey } from '/@/types'
import { CURRENT_CONFIG } from '/@/api/http/config'
const MNG_API_PREFIX = '/manage/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export interface GetDeviceUploadLogListParams {
device_sn: string,
page: number,
page_size: number,
begin_time?: number, // 开始时间
end_time?: number, // 结束时间
status?: DeviceLogUploadStatusEnum, // 日志上传状态
logs_information?: string // 搜索内容
}
export interface BriefDeviceInfo {
sn: string,
device_model: DeviceValue,
device_callsign: string
}
export interface DeviceLogProgressInfo{
device_sn: string,
device_model_domain: DOMAIN,
progress: number, // 进度
result: number, // 上传结果
upload_rate: number, // 上传速率
status: DeviceLogUploadStatusEnum // 上传状态
}
export interface DeviceLogItem {
boot_index: number, // 日志id
start_time: number, // 日志开始时间
end_time: number, // 日志结束时间
size: number // 日志大小
}
export interface DeviceLogFileInfo {
device_sn: string,
module: DOMAIN,
result: number,
object_key: string,
file_id: string,
list: DeviceLogItem[]
}
export interface DeviceLogFileListInfo {
files: DeviceLogFileInfo[]
}
export interface GetDeviceUploadLogListRsp {
logs_id: string, // 记录id
happen_time: string, // 发生时间
user_name: string, // 用户
logs_information: string, // 异常描述
create_time: string, // 上传时间
status:DeviceLogUploadStatusEnum, // 日志上传状态
device_topo:{ // 设备topo
hosts: BriefDeviceInfo[],
parents: BriefDeviceInfo[]
},
logs_progress: DeviceLogProgressInfo[], // 日志上传进度
device_logs: DeviceLogFileListInfo // 设备日志
}
/**
* 获取设备上传日志列表信息
* @param params
* @returns
*/
export async function getDeviceUploadLogList (params: GetDeviceUploadLogListParams): Promise<IListWorkspaceResponse<GetDeviceUploadLogListRsp>> {
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs-uploaded`, {
params: params
})
return resp.data
}
export interface GetDeviceLogListParams{
device_sn: string,
domain: DOMAIN[]
}
/**
* 获取设备日志列表信息
* @param params
* @returns
*/
export async function getDeviceLogList (params: GetDeviceLogListParams): Promise<IWorkspaceResponse<DeviceLogFileListInfo>> {
const domain = params.domain ? params.domain : []
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs`, {
params: {
domain_list: domain.join(',')
}
})
return resp.data
}
export interface UploadDeviceLogBody {
device_sn: string
happen_time: string // 发生时间
logs_information: string // 异常描述
files:{
list: DeviceLogItem[],
device_sn: string,
module: DOMAIN
}[]
}
/**
* 上传设备日志
* @param body
* @returns
*/
export async function postDeviceUpgrade (body: UploadDeviceLogBody): Promise<IWorkspaceResponse<{}>> {
const resp = await request.post(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs`, body)
return resp.data
}
export type DeviceLogUploadAction = 'cancel'
export interface CancelDeviceLogUploadBody {
device_sn: string
status: DeviceLogUploadAction
module_list: DOMAIN[]
}
// 取消上传
export async function cancelDeviceLogUpload (body: CancelDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> {
const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs`
const result = await request.delete(url, {
data: body
})
return result.data
}
export interface DeleteDeviceLogUploadBody {
device_sn: string
logs_id: string
}
// 取消上传
export async function deleteDeviceLogUpload (body: DeleteDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> {
const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs/${body.logs_id}`
const result = await request.delete(url, {
data: body
})
return result.data
}
export interface GetUploadDeviceLogUrlParams{
logs_id: string,
file_id: string,
}
// export interface GetUploadDeviceLogRsp{
// url: string
// }
/**
* 获取设备上传日志url
* @param params
* @returns
*/
export async function getUploadDeviceLogUrl (params: GetUploadDeviceLogUrlParams): Promise<IWorkspaceResponse<string>> {
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/logs/${params.logs_id}/url/${params.file_id}`)
return resp.data
}
================================================
FILE: src/api/device-setting/index.ts
================================================
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
}
================================================
FILE: src/api/device-upgrade/index.ts
================================================
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { DeviceFirmwareTypeEnum } from '/@/types/device'
const MNG_API_PREFIX = '/manage/api/v1'
export interface GetDeviceUpgradeInfoParams {
device_name: string
}
export interface GetDeviceUpgradeInfoRsp {
device_name: string
product_version: string
release_note: string
released_time: string
}
/**
* 获取设备升级信息
* @param params
* @returns
*/
export async function getDeviceUpgradeInfo (params: GetDeviceUpgradeInfoParams): Promise<IWorkspaceResponse<GetDeviceUpgradeInfoRsp[]>> {
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/firmware-release-notes/latest`, {
params: params
})
return resp.data
}
export interface UpgradeDeviceInfo {
device_name: string,
sn: string,
product_version: string,
firmware_upgrade_type: DeviceFirmwareTypeEnum // 1-普通升级,2-一致性升级
}
export type DeviceUpgradeBody = UpgradeDeviceInfo[]
/**
* 设备升级
* @param workspace_id
* @param body
* @returns
*/
export async function postDeviceUpgrade (workspace_id: string, body: DeviceUpgradeBody): Promise<IWorkspaceResponse<{}>> {
const resp = await request.post(`${MNG_API_PREFIX}/devices/${workspace_id}/devices/ota`, body)
return resp.data
}
================================================
FILE: src/api/drc.ts
================================================
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { ELocalStorageKey } from '/@/types'
// DRC 链路
const DRC_API_PREFIX = '/control/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export interface PostDrcBody {
client_id?: string // token过期时,用于续期则必填
expire_sec?: number // 过期时间,单位秒,默认3600
}
export interface DrcParams {
address: string
username: string
password: string
client_id: string
expire_time: number // 过期时间
enable_tls: boolean // 是否开启tls
}
// 获取 mqtt 连接认证
export async function postDrc (body: PostDrcBody): Promise<IWorkspaceResponse<DrcParams>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/connect`, body)
return resp.data
}
export interface DrcEnterBody {
client_id: string
dock_sn: string
expire_sec?: number // 过期时间,单位秒,默认3600
device_info?: {
osd_frequency?: number
hsi_frequency?: number
}
}
export interface DrcEnterResp {
sub: string[] // 需要订阅接收的topic
pub: string[] // 推送的topic地址
}
// 进入飞行控制 (建立drc连接&获取云控控制权)
export async function postDrcEnter (body: DrcEnterBody): Promise<IWorkspaceResponse<DrcEnterResp>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body)
return resp.data
}
export interface DrcExitBody {
client_id: string
dock_sn: string
}
// 退出飞行控制 (退出drc连接&退出云控控制权)
export async function postDrcExit (body: DrcExitBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/exit`, body)
return resp.data
}
================================================
FILE: src/api/drone-control/drone.ts
================================================
import request, { IWorkspaceResponse } from '/@/api/http/request'
// import { ELocalStorageKey } from '/@/types'
const API_PREFIX = '/control/api/v1'
// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
// 获取飞行控制权
export async function postFlightAuth (sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/flight`)
return resp.data
}
export enum WaylineLostControlActionInCommandFlight {
CONTINUE = 0,
EXEC_LOST_ACTION = 1
}
export enum LostControlActionInCommandFLight {
HOVER = 0, // 悬停
Land = 1, // 着陆
RETURN_HOME = 2, // 返航
}
export enum ERthMode {
SMART = 0,
SETTING = 1
}
export enum ECommanderModeLostAction {
CONTINUE = 0,
EXEC_LOST_ACTION = 1
}
export enum ECommanderFlightMode {
SMART = 0,
SETTING = 1
}
export interface PointBody {
latitude: number;
longitude: number;
height: number;
}
export interface PostFlyToPointBody {
max_speed: number,
points: PointBody[]
}
// 飞向目标点
export async function postFlyToPoint (sn: string, body: PostFlyToPointBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`, body)
return resp.data
}
// 停止飞向目标点
export async function deleteFlyToPoint (sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.delete(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`)
return resp.data
}
export interface PostTakeoffToPointBody{
target_height: number;
target_latitude: number;
target_longitude: number;
security_takeoff_height: number; // 安全起飞高
max_speed: number; // flyto过程中能达到的最大速度, 单位m/s 跟飞机档位有关
rc_lost_action: LostControlActionInCommandFLight; // 失控行为
rth_altitude: number; // 返航高度
exit_wayline_when_rc_lost: WaylineLostControlActionInCommandFlight;
rth_mode: ERthMode;
commander_mode_lost_action: ECommanderModeLostAction;
commander_flight_mode: ECommanderFlightMode;
commander_flight_height: number;
}
// 一键起飞
export async function postTakeoffToPoint (sn: string, body: PostTakeoffToPointBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/takeoff-to-point`, body)
return resp.data
}
================================================
FILE: src/api/drone-control/payload.ts
================================================
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { CameraType, CameraMode } from '/@/types/live-stream'
import { GimbalResetMode } from '/@/types/drone-control'
// import { ELocalStorageKey } from '/@/types'
const API_PREFIX = '/control/api/v1'
// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
export interface PostPayloadAuthBody {
payload_index: string
}
// 获取负载控制权
export async function postPayloadAuth (sn: string, body: PostPayloadAuthBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)
return resp.data
}
// TODO: 画面拖动控制
export enum PayloadCommandsEnum {
CameraModeSwitch = 'camera_mode_switch',
CameraPhotoTake = 'camera_photo_take',
CameraRecordingStart = 'camera_recording_start',
CameraRecordingStop = 'camera_recording_stop',
CameraFocalLengthSet = 'camera_focal_length_set',
GimbalReset = 'gimbal_reset',
CameraAim = 'camera_aim'
}
export interface PostCameraModeBody {
payload_index: string
camera_mode: CameraMode
}
export interface PostCameraPhotoBody {
payload_index: string
}
export interface PostCameraRecordingBody {
payload_index: string
}
export interface DeleteCameraRecordingParams {
payload_index: string
}
export interface PostCameraFocalLengthBody {
payload_index: string,
camera_type: CameraType,
zoom_factor: number
}
export interface PostGimbalResetBody{
payload_index: string,
reset_mode: GimbalResetMode,
}
export interface PostCameraAimBody{
payload_index: string,
camera_type: CameraType,
locked: boolean,
x: number,
y: number,
}
export type PostPayloadCommandsBody = {
cmd: PayloadCommandsEnum.CameraModeSwitch,
data: PostCameraModeBody
} | {
cmd: PayloadCommandsEnum.CameraPhotoTake,
data: PostCameraPhotoBody
} | {
cmd: PayloadCommandsEnum.CameraRecordingStart,
data: PostCameraRecordingBody
} | {
cmd: PayloadCommandsEnum.CameraRecordingStop,
data: DeleteCameraRecordingParams
} | {
cmd: PayloadCommandsEnum.CameraFocalLengthSet,
data: PostCameraFocalLengthBody
} | {
cmd: PayloadCommandsEnum.GimbalReset,
data: PostGimbalResetBody
} | {
cmd: PayloadCommandsEnum.CameraAim,
data: PostCameraAimBody
}
// 发送负载名称
export async function postPayloadCommands (sn: string, body: PostPayloadCommandsBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body)
return resp.data
}
================================================
FILE: src/api/flight-area/index.ts
================================================
import request from '../http/request'
import { IWorkspaceResponse } from '../http/type'
import { EFlightAreaType, ESyncStatus, FlightAreaContent } from './../../types/flight-area'
import { ELocalStorageKey } from '/@/types/enums'
import { GeojsonCoordinate } from '/@/utils/genjson'
export interface GetFlightArea {
area_id: string,
name: string,
type: EFlightAreaType,
content: FlightAreaContent,
status: boolean,
username: string,
create_time: number,
update_time: number,
}
export interface PostFlightAreaBody {
id: string,
name: string,
type: EFlightAreaType,
content: {
properties: {
color: string,
clampToGround: boolean,
},
geometry: {
type: string,
coordinates: GeojsonCoordinate | GeojsonCoordinate[][],
radius?: number,
}
}
}
export interface FlightAreaStatus {
sync_code: number,
sync_status: ESyncStatus,
sync_msg: string,
}
export interface GetDeviceStatus {
device_sn: string,
nickname?: string,
device_name?: string,
online?: boolean,
flight_area_status: FlightAreaStatus,
}
const MAP_API_PREFIX = '/map/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export async function getFlightAreaList (): Promise<IWorkspaceResponse<GetFlightArea[]>> {
const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-areas`)
return resp.data
}
export async function changeFlightAreaStatus (area_id: string, status: boolean): Promise<IWorkspaceResponse<any>> {
const resp = await request.put(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`, { status })
return resp.data
}
export async function saveFlightArea (body: PostFlightAreaBody): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area`, body)
return resp.data
}
export async function deleteFlightArea (area_id: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.delete(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`)
return resp.data
}
export async function syncFlightArea (device_sn: string[]): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/sync`, { device_sn })
return resp.data
}
export async function getDeviceStatus (): Promise<IWorkspaceResponse<GetDeviceStatus[]>> {
const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/device-status`)
return resp.data
}
================================================
FILE: src/api/http/config.ts
================================================
export const CURRENT_CONFIG = {
// license
appId: 'Please enter the app id.', // You need to go to the development website to apply.
appKey: 'Please enter the app key.', // You need to go to the development website to apply.
appLicense: 'Please enter the app license.', // You need to go to the development website to apply.
// http
baseURL: 'Please enter the backend access address prefix.', // This url must end with "/". Example: 'http://192.168.1.1:6789/'
websocketURL: 'Please enter the WebSocket access address.', // Example: 'ws://192.168.1.1:6789/api/v1/ws'
// livestreaming
// RTMP Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.
rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'
// GB28181 Note:If you don't know what these parameters mean, you can go to Pilot2 and select the GB28181 page in the cloud platform. Where the parameters same as these parameters.
gbServerIp: 'Please enter the server ip.',
gbServerPort: 'Please enter the server port.',
gbServerId: 'Please enter the server id.',
gbAgentId: 'Please enter the agent id',
gbPassword: 'Please enter the agent password',
gbAgentPort: 'Please enter the local port.',
gbAgentChannel: 'Please enter the channel.',
// RTSP
rtspUserName: 'Please enter the username.',
rtspPassword: 'Please enter the password.',
rtspPort: '8554',
// Agora
agoraAPPID: 'Please enter the agora app id.',
agoraToken: 'Please enter the agora temporary token.',
agoraChannel: 'Please enter the agora channel.',
// map
// You can apply on the AMap website.
amapKey: 'Please enter the amap key.',
}
================================================
FILE: src/api/http/request.ts
================================================
import axios from 'axios'
import { uuidv4 } from '/@/utils/uuid'
import { CURRENT_CONFIG } from './config'
import { message } from 'ant-design-vue'
import router from '/@/router'
import { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums'
export * from './type'
const REQUEST_ID = 'X-Request-Id'
function getAuthToken () {
return localStorage.getItem(ELocalStorageKey.Token)
}
const instance = axios.create({
// withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
// timeout: 12000,
})
instance.interceptors.request.use(
config => {
config.headers[ELocalStorageKey.Token] = getAuthToken()
// config.headers[REQUEST_ID] = uuidv4()
config.baseURL = CURRENT_CONFIG.baseURL
return config
},
error => {
return Promise.reject(error)
},
)
instance.interceptors.response.use(
response => {
console.info('URL: ' + response.config.baseURL + response.config.url, '\nData: ', response.data, '\nResponse:', response)
if (response.data.code && response.data.code !== 0) {
message.error(response.data.message)
}
return response
},
err => {
const requestId = err?.config?.headers && err?.config?.headers[REQUEST_ID]
if (requestId) {
console.info(REQUEST_ID, ':', requestId)
}
console.info('url: ', err?.config?.url, `【${err?.config?.method}】 \n>>>> err: `, err)
let description = '-'
if (err.response?.data && err.response.data.message) {
description = err.response.data.message
}
if (err.response?.data && err.response.data.result) {
description = err.response.data.result.message
}
// @See: https://github.com/axios/axios/issues/383
if (!err.response || !err.response.status) {
message.error('The network is abnormal, please check the backend service and try again')
return
}
if (err.response?.status !== 200) {
message.error(`ERROR_CODE: ${err.response?.status}`)
}
// if (err.response?.status === 403) {
// // window.location.href = '/'
// }
if (err.response?.status === 401) {
console.error(err.response)
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag))
switch (flag) {
case EUserType.Web:
router.push(ERouterName.PROJECT)
break
case EUserType.Pilot:
router.push(ERouterName.PILOT)
break
}
}
return Promise.reject(err)
},
)
export default instance
================================================
FILE: src/api/http/type.ts
================================================
export interface IResult {
code: number;
message: string;
}
export interface IPage {
page: number;
total: number;
page_size: number;
}
export interface IListWorkspaceResponse<T> {
code: number;
message: string;
data: {
list: T[];
pagination: IPage;
};
}
// Workspace
export interface IWorkspaceResponse<T> {
code: number;
data: T;
message: string;
}
export type IStatus = 'WAITING' | 'DOING' | 'SUCCESS' | 'FAILED';
export interface CommonListResponse<T> extends IResult {
data: {
list: T[];
pagination: IPage;
};
}
export interface CommonResponse<T> extends IResult {
data: T
}
================================================
FILE: src/api/http.ts
================================================
/**
* 职责声明:
* 1.提供一个 单一的 axios 实例(方面进行统一拦截)
* 2.允许调用方定制自己的配置(例如拦截器等),而不影响其他实例
*
* 暴露 API:
* 1.一个统一的 axios 实例: singleAxiosInstance(绑定了统一的拦截器)
* 2.创建 axios 实例的方法 createAxiosInstance,并在参数中允许配置是否绑定统一拦截器
* 3.对外暴露统一拦截器绑定方案,允许外界进行定制: bindCommonRequestInterceptors、bindCommonResponseInterceptors
*/
import Axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
// 统一的 request 拦截器
export function bindCommonRequestInterceptors (instance: AxiosInstance): void {
instance.interceptors.request.use(config => {
return config
})
}
// Unified response interceptor
export function bindCommonResponseInterceptors (instance: AxiosInstance): void {
instance.interceptors.response.use(config => {
return config
}, err => {
return Promise.reject(err)
})
}
export function createAxiosInstance (config?: AxiosRequestConfig, commonInterceptorConf: { request?: boolean, response?: boolean } = {}): AxiosInstance {
const instance = Axios.create(config)
// Binding a unified interceptor, binding by default
commonInterceptorConf.request !== false && bindCommonRequestInterceptors(instance)
commonInterceptorConf.response !== false && bindCommonResponseInterceptors(instance)
return instance
}
const singleAxios = createAxiosInstance({}, { request: true, response: false })
export default singleAxios
================================================
FILE: src/api/layer.ts
================================================
import { ELocalStorageKey } from '../types/enums'
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { mapLayers } from '/@/constants/mock-layers'
import { elementGroupsReq, PostElementsBody, PutElementsBody } from '/@/types/mapLayer'
const PREFIX = '/map/api/v1'
const workspace_id = localStorage.getItem(ELocalStorageKey.WorkspaceId)
type UnknownResponse = Promise<IWorkspaceResponse<unknown>>
// get elements group
// export const getLayers = async (reqParams: elementGroupsReq): UnknownResponse => {
// const url = `${PREFIX}/workspaces/${workspace_id}/element_groups`
// const result = await request.get(url, {
// params: {
// group_id: reqParams.groupId,
// is_distributed: reqParams.isDistributed
// },
// })
// return result.data
// }
export const getLayers = async (reqParams: elementGroupsReq): UnknownResponse => {
return mapLayers
}
// Get elements groups request
export const getElementGroupsReq = async (body: elementGroupsReq): Promise<IWorkspaceResponse<any>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + '/element-groups'
const result = await request.get(url, body)
return result.data
}
// add element
export const postElementsReq = async (pid: string, body: PostElementsBody): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups/${pid}/elements`
const result = await request.post(url, body)
return result.data
}
// Update map element request
export const updateElementsReq = async (id: string, body: PutElementsBody): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/elements/${id}`
const result = await request.put(url, body)
return result.data
}
// Delete map element
export const deleteElementReq = async (id: string, body: {}): Promise<any> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/elements/${id}`
const result = await request.delete(url, body)
return result.data
}
// Delete layer elements
export const deleteLayerEleReq = async (id: string, body: {}): Promise<any> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups/${id}/elements`
const result = await request.delete(url, body)
return result.data
}
================================================
FILE: src/api/manage.ts
================================================
import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request'
import { Device } from '/@/types/device'
const HTTP_PREFIX = '/manage/api/v1'
// login
export interface LoginBody {
username: string,
password: string,
flag: number,
}
export interface BindBody {
device_sn: string,
user_id: string,
workspace_id: string,
domain?: string
}
export interface HmsQueryBody {
sns: string[],
children_sn: string,
device_sn: string,
language: string,
level: number | string,
begin_time: number,
end_time: number,
message: string,
domain: number,
}
export const login = async function (body: LoginBody): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/login`
const result = await request.post(url, body)
return result.data
}
// Refresh Token
export const refreshToken = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/token/refresh`
const result = await request.post(url, body)
return result.data
}
// Get Platform Info
export const getPlatformInfo = async function (): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/current`
const result = await request.get(url)
return result.data
}
// Get User Info
export const getUserInfo = async function (): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/users/current`
const result = await request.get(url)
return result.data
}
// Get Device Topo
export const getDeviceTopo = async function (workspace_id: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices`
const result = await request.get(url)
return result.data
}
// Get Livestream Capacity
export const getLiveCapacity = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/capacity`
const result = await request.get(url, body)
return result.data
}
// Start Livestream
export const startLivestream = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/start`
const result = await request.post(url, body)
return result.data
}
// Stop Livestream
export const stopLivestream = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/stop`
const result = await request.post(url, body)
return result.data
}
// Update Quality
export const setLivestreamQuality = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/update`
const result = await request.post(url, body)
return result.data
}
export const getAllUsersInfo = async function (wid: string, body: IPage): Promise<CommonListResponse<any>> {
const url = `${HTTP_PREFIX}/users/${wid}/users?&page=${body.page}&page_size=${body.page_size}`
const result = await request.get(url)
return result.data
}
export const updateUserInfo = async function (wid: string, user_id: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/users/${wid}/users/${user_id}`
const result = await request.put(url, body)
return result.data
}
export const bindDevice = async function (body: BindBody): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${body.device_sn}/binding`
const result = await request.post(url, body)
return result.data
}
export const unbindDevice = async function (device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${device_sn}/unbinding`
const result = await request.delete(url)
return result.data
}
export const getDeviceBySn = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`
const result = await request.get(url)
return result.data
}
/**
* 获取绑定设备信息
* @param workspace_id
* @param body
* @param domain
* @returns
*/
export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: number): Promise<IListWorkspaceResponse<Device>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}`
const result = await request.get(url)
return result.data
}
export const updateDevice = async function (body: {}, workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`
const result = await request.put(url, body)
return result.data
}
export const getUnreadDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`
const result = await request.get(url)
return result.data
}
export const updateDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`
const result = await request.put(url)
return result.data
}
export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: string, pagination: IPage): Promise<IListWorkspaceResponse<any>> {
let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +
`&level=${body.level ?? ''}&begin_time=${body.begin_time ?? ''}&end_time=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}`
body.sns.forEach((sn: string) => {
if (sn !== '') {
url = url.concat(`&device_sn=${sn}`)
}
})
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
}
export const getFirmwares = async function (workspace_id: string, page: IPage, body: FirmwareQueryParam): Promise<IListWorkspaceResponse<Firmware>> {
const url = `${HTTP_PREFIX}/workspaces/${workspace_id}/firmwares?page=${page.page}&page_size=${page.page_size}` +
`&device_name=${body.device_name}&product_version=${body.product_version}&status=${body.firmware_status ?? ''}`
const result = await request.get(url)
return result.data
}
export const importFirmareFile = async function (workspaceId: string, param: FormData): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/file/upload`
const result = await request.post(url, param)
return result.data
}
export const changeFirmareStatus = async function (workspaceId: string, firmwareId: string, param: {status: boolean}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/${firmwareId}`
const result = await request.put(url, param)
return result.data
}
================================================
FILE: src/api/media.ts
================================================
import { message } from 'ant-design-vue'
import request, { IPage, IWorkspaceResponse } from '/@/api/http/request'
const HTTP_PREFIX = '/media/api/v1'
// Get Media Files
export const getMediaFiles = async function (wid: string, pagination: IPage): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/files/${wid}/files?page=${pagination.page}&page_size=${pagination.page_size}`
const result = await request.get(url)
return result.data
}
// Download Media File
export const downloadMediaFile = async function (workspaceId: string, fileId: string): Promise<any> {
const url = `${HTTP_PREFIX}/files/${workspaceId}/file/${fileId}/url`
const result = await request.get(url, { responseType: 'blob' })
if (result.data.type === 'application/json') {
const reader = new FileReader()
reader.onload = function (e) {
const text = reader.result as string
const result = JSON.parse(text)
message.error(result.message)
}
reader.readAsText(result.data, 'utf-8')
} else {
return result.data
}
}
================================================
FILE: src/api/pilot-bridge.ts
================================================
import { message } from 'ant-design-vue'
import { EComponentName, EPhotoType, ERouterName } from '../types'
import { CURRENT_CONFIG } from './http/config'
import { EVideoPublishType, LiveStreamStatus } from '../types/live-stream'
import { getRoot } from '/@/root'
const root = getRoot()
export const components = new Map()
declare let window:any
interface JsResponse{
code:number,
message:string,
data:any
}
export interface ThingParam {
host: string,
username: string,
password: string,
connectCallback: string
}
export interface LiveshareParam {
videoPublishType: string, // video-on-demand、video-by-manual、video-demand-aux-manual
statusCallback: string
}
export interface MapParam {
userName: string,
elementPreName: string
}
export interface WsParam {
host: string,
token: string,
connectCallback: string
}
export interface ApiParam {
host: string,
token: string
}
export interface MediaParam {
autoUploadPhoto: boolean, // 是否自动上传图片, 非必需
autoUploadPhotoType: number, // 自动上传的照片类型,0:原图, 1:缩略图, 非必需
autoUploadVideo: boolean // 是否自动上传视频, 非必需
}
function returnBool (response: string): boolean {
const res: JsResponse = JSON.parse(response)
const isError = errorHint(res)
if (JSON.stringify(res.data) !== '{}') {
return isError && res.data
}
return isError
}
function returnString (response: string): string {
const res: JsResponse = JSON.parse(response)
return errorHint(res) ? res.data : ''
}
function returnNumber (response: string): number {
const res: JsResponse = JSON.parse(response)
return errorHint(res) ? res.data : -1
}
function errorHint (response: JsResponse): boolean {
if (response.code !== 0) {
message.error(response.message)
console.error(response.message)
return false
}
return true
}
export default {
init (): Map<EComponentName, any> {
const thingParam: ThingParam = {
host: '',
connectCallback: '',
username: '',
password: ''
}
components.set(EComponentName.Thing, thingParam)
const liveshareParam: LiveshareParam = {
videoPublishType: EVideoPublishType.VideoDemandAuxManual,
statusCallback: 'liveStatusCallback'
}
components.set(EComponentName.Liveshare, liveshareParam)
const mapParam: MapParam = {
userName: '',
elementPreName: 'PILOT'
}
components.set(EComponentName.Map, mapParam)
const wsParam: WsParam = {
host: CURRENT_CONFIG.websocketURL,
token: '',
connectCallback: 'wsConnectCallback'
}
components.set(EComponentName.Ws, wsParam)
const apiParam: ApiParam = {
host: '',
token: ''
}
components.set(EComponentName.Api, apiParam)
components.set(EComponentName.Tsa, {})
const mediaParam: MediaParam = {
autoUploadPhoto: true,
autoUploadPhotoType: EPhotoType.Preview,
autoUploadVideo: true
}
components.set(EComponentName.Media, mediaParam)
components.set(EComponentName.Mission, {})
return components
},
getComponentParam (key:EComponentName): any {
return components.get(key)
},
setComponentParam (key:EComponentName, value:any) {
components.set(key, value)
},
loadComponent (name:string, param:any):string {
return returnString(window.djiBridge.platformLoadComponent(name, JSON.stringify(param)))
},
unloadComponent (name:string) :string {
return returnString(window.djiBridge.platformUnloadComponent(name))
},
isComponentLoaded (module:string): boolean {
return returnBool(window.djiBridge.platformIsComponentLoaded(module))
},
setWorkspaceId (uuid:string):string {
return returnString(window.djiBridge.platformSetWorkspaceId(uuid))
},
setPlatformMessage (platformName:string, title:string, desc:string): boolean {
return returnBool(window.djiBridge.platformSetInformation(platformName, title, desc))
},
getRemoteControllerSN () :string {
return returnString(window.djiBridge.platformGetRemoteControllerSN())
},
getAircraftSN ():string {
return returnString(window.djiBridge.platformGetAircraftSN())
},
stopwebview ():string {
return returnString(window.djiBridge.platformStopSelf())
},
setLogEncryptKey (key:string):string {
return window.djiBridge.platformSetLogEncryptKey(key)
},
clearLogEncryptKey ():string {
return window.djiBridge.platformClearLogEncryptKey()
},
getLogPath ():string {
return returnString(window.djiBridge.platformGetLogPath())
},
platformVerifyLicense (appId:string, appKey:string, appLicense:string): boolean {
return returnBool(window.djiBridge.platformVerifyLicense(appId, appKey, appLicense))
},
isPlatformVerifySuccess (): boolean {
return returnBool(window.djiBridge.platformIsVerified())
},
isAppInstalled (pkgName: string): boolean {
return returnBool(window.djiBridge.platformIsAppInstalled(pkgName))
},
getVersion (): string {
return window.djiBridge.platformGetVersion()
},
// thing
thingGetConnectState (): boolean {
return returnBool(window.djiBridge.thingGetConnectState())
},
thingGetConfigs (): ThingParam {
const thingParam = JSON.parse(window.djiBridge.thingGetConfigs())
return thingParam.code === 0 ? JSON.parse(thingParam.data) : {}
},
// api
getToken () : string {
return returnString(window.djiBridge.apiGetToken())
},
setToken (token:string):string {
return returnString(window.djiBridge.apiSetToken(token))
},
getHost (): string {
return returnString(window.djiBridge.apiGetHost())
},
// liveshare
/**
*
* @param type
* video-on-demand: 服务器点播,依赖于thing模块,具体的点播命令参见设备物模型的直播服务
* video-by-manual:手动点播,配置好直播类型参数之后,在图传页面可修改直播参数,停止直播
* video-demand-aux-manual: 混合模式,支持服务器点播,以及图传页面修改直播参数,停止直播
*/
setVideoPublishType (type:string): boolean {
return returnBool(window.djiBridge.liveshareSetVideoPublishType(type))
},
/**
*
* @returns
* type: liveshare type, 0:unknown, 1:agora, 2:rtmp, 3:rtsp, 4:gb28181
*/
getLiveshareConfig (): string {
return returnString(window.djiBridge.liveshareGetConfig())
},
setLiveshareConfig (type:number, params:string):string {
return window.djiBridge.liveshareSetConfig(type, params)
},
setLiveshareStatusCallback (callbackFunc:string) :string {
return window.djiBridge.liveshareSetStatusCallback(callbackFunc)
},
getLiveshareStatus (): LiveStreamStatus {
return JSON.parse(JSON.parse(window.djiBridge.liveshareGetStatus()).data)
},
startLiveshare (): boolean {
return returnBool(window.djiBridge.liveshareStartLive())
},
stopLiveshare (): boolean {
return returnBool(window.djiBridge.liveshareStopLive())
},
// WebSocket
wsGetConnectState (): boolean {
return returnBool(window.djiBridge.wsGetConnectState())
},
wsConnect (host: string, token: string, callback: string): string {
return window.djiBridge.wsConnect(host, token, callback)
},
wsDisconnect (): string {
return window.djiBridge.wsConnect()
},
wsSend (message: string): string {
return window.djiBridge.wsSend(message)
},
// media
setAutoUploadPhoto (auto:boolean):string {
return window.djiBridge.mediaSetAutoUploadPhoto(auto)
},
getAutoUploadPhoto (): boolean {
return returnBool(window.djiBridge.mediaGetAutoUploadPhoto())
},
setUploadPhotoType (type:number):string {
return window.djiBridge.mediaSetUploadPhotoType(type)
},
getUploadPhotoType (): number {
return returnNumber(window.djiBridge.mediaGetUploadPhotoType())
},
setAutoUploadVideo (auto:boolean):string {
return window.djiBridge.mediaSetAutoUploadVideo(auto)
},
getAutoUploadVideo (): boolean {
return returnBool(window.djiBridge.mediaGetAutoUploadVideo())
},
setDownloadOwner (rcIndex:number):string {
return window.djiBridge.mediaSetDownloadOwner(rcIndex)
},
getDownloadOwner (): number {
return returnNumber(window.djiBridge.mediaGetDownloadOwner())
},
onBackClickReg () {
window.djiBridge.onBackClick = () => {
if (root.$router.currentRoute.value.path === '/' + ERouterName.PILOT_HOME) {
return false
} else {
history.go(-1)
return true
}
}
},
onStopPlatform () {
window.djiBridge.onStopPlatform = () => {
localStorage.clear()
}
}
}
================================================
FILE: src/api/wayline.ts
================================================
import { message } from 'ant-design-vue'
import request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'
import { TaskType, TaskStatus, OutOfControlAction } from '/@/types/task'
import { WaylineType } from '/@/types/wayline'
const HTTP_PREFIX = '/wayline/api/v1'
// Get Wayline Files
export const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${wid}/waylines?order_by=${body.order_by}&page=${body.page}&page_size=${body.page_size}`
const result = await request.get(url)
return result.data
}
// Download Wayline File
export const downloadWaylineFile = async function (workspaceId: string, waylineId: string): Promise<any> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}/url`
const result = await request.get(url, { responseType: 'blob' })
if (result.data.type === 'application/json') {
const reader = new FileReader()
reader.onload = function (e) {
const text = reader.result as string
const result = JSON.parse(text)
message.error(result.message)
}
reader.readAsText(result.data, 'utf-8')
} else {
return result.data
}
}
// Delete Wayline File
export const deleteWaylineFile = async function (workspaceId: string, waylineId: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}`
const result = await request.delete(url)
return result.data
}
export interface CreatePlan {
name: string,
file_id: string,
dock_sn: string,
task_type: TaskType, // 任务类型
wayline_type: WaylineType, // 航线类型
task_days: number[] // 执行任务的日期(秒)
task_periods: number[][] // 执行任务的时间点(秒)
rth_altitude: number // 相对机场返航高度 20 - 500
out_of_control_action: OutOfControlAction // 失控动作
min_battery_capacity?: number, // The minimum battery capacity of aircraft.
min_storage_capacity?: number, // The minimum storage capacity of dock and aircraft.
}
// Create Wayline Job
export const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks`
const result = await request.post(url, plan)
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,
begin_time: string,
end_time: string,
execute_time: string,
completed_time: string,
status: TaskStatus, // 任务状态
progress: number, // 执行进度
code: number, // 错误码
rth_altitude: number // 相对机场返航高度 20 - 500
out_of_control_action: OutOfControlAction // 失控动作
media_count: number // 媒体数量
uploading:boolean // 是否正在上传媒体
uploaded_count: number // 已上传媒体数量
}
// Get Wayline Jobs
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
}
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
}
export enum UpdateTaskStatus {
Suspend = 0, // 暂停
Resume = 1, // 恢复
}
export interface UpdateTaskStatusBody {
job_id: string
status: UpdateTaskStatus
}
// 更新机场任务状态
export async function updateTaskStatus (workspaceId: string, body: UpdateTaskStatusBody): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${body.job_id}`
const result = await request.put(url, {
status: body.status
})
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
}
// 媒体立即上传
export const uploadMediaFileNow = async function (workspaceId: string, jobId: string): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${jobId}/media-highest`
const result = await request.post(url)
return result.data
}
================================================
FILE: src/components/GMap.vue
================================================
<template>
<div class="g-map-wrapper">
<!-- 地图区域 -->
<div id="g-container" :style="{ width: '100%', height: '100%' }" />
<!-- 绘制面板 -->
<div
class="g-action-panel"
:style="{ right: drawVisible ? '316px' : '16px' }"
>
<div :class="state.currentType === 'pin' ? 'g-action-item selection' : 'g-action-item'" @click="draw('pin', true)">
<a><a-image :src="pin" :preview="false" /></a>
</div>
<div :class="state.currentType === 'polyline' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polyline', true)">
<a><LineOutlined :rotate="135" class="fz20"/></a>
</div>
<div :class="state.currentType === 'polygon' && !state.isFlightArea ? 'g-action-item selection' : 'g-action-item'" @click="draw('polygon', true)">
<a><BorderOutlined class="fz18" /></a>
</div>
<FlightAreaActionIcon class="g-action-item mt10" :class="{'selection': mouseMode && state.isFlightArea}" @select-action="selectFlightAreaAction" @click="selectFlightAreaAction"/>
<div v-if="mouseMode" class="g-action-item" @click="draw('off', false)">
<a style="color: red;"><CloseOutlined /></a>
</div>
</div>
<!-- 飞机OSD -->
<div v-if="osdVisible.visible && !osdVisible.is_dock" v-drag-window class="osd-panel fz12">
<div class="drag-title pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 18%;">
<span>{{ osdVisible.callsign }}</span>
<span><a class="fz16" style="color: white;" @click="() => osdVisible.visible = false"><CloseOutlined /></a></span>
</div>
<div style="height: 82%;">
<div class="flex-column flex-align-center flex-justify-center" style="margin-top: -5px; padding-top: 25px; float: left; width: 60px; background: #2d2d2d;">
<a-tooltip :title="osdVisible.model">
<div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
<span><a-image :src="M30" :preview="false"/></span>
<span>{{ osdVisible.model }}</span>
</div>
</a-tooltip>
</div>
<div class="osd">
<a-row>
<a-col span="16" :style="deviceInfo.device.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">{{ EModeCode[deviceInfo.device.mode_code] }}</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Signal strength">
<span>HD</span>
<span class="ml10">{{ deviceInfo.gateway?.transmission_signal_quality }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="RC Battery Level">
<span><ThunderboltOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.gateway && deviceInfo.gateway.capacity_percent !== str ? deviceInfo.gateway?.capacity_percent + ' %' : deviceInfo.gateway?.capacity_percent }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Drone Battery Level">
<span><ThunderboltOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device.battery.capacity_percent + ' %' : deviceInfo.device.battery.capacity_percent }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-tooltip title="RTK Fixed">
<a-col span="6" class="flex-row flex-align-center flex-justify-start">
<span>Fixed</span>
<span class="ml10 circle" :style="deviceInfo.device.position_state.is_fixed === 1 ? 'backgroud: rgb(25,190,107);' : ' background: red;'"></span>
</a-col>
</a-tooltip>
<a-col span="6">
<a-tooltip title="GPS">
<span>GPS</span>
<span class="ml10">{{ deviceInfo.device.position_state.gps_number }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="RTK">
<span><TrademarkOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.device.position_state.rtk_number }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Flight Mode">
<span><ControlOutlined class="fz16" /></span>
<span class="ml10">{{ EGear[deviceInfo.device.gear] }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Altitude above sea level">
<span>ASL</span>
<span class="ml10">{{ deviceInfo.device.height === str ? str : deviceInfo.device.height.toFixed(2) + ' m'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Altitude above takeoff level">
<span>ALT</span>
<span class="ml10">{{ deviceInfo.device.elevation === str ? str : deviceInfo.device.elevation.toFixed(2) + ' m' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Distance to Home Point">
<span>H</span>
<span class="ml10">{{ deviceInfo.device.home_distance === str ? str : deviceInfo.device.home_distance.toFixed(2) + ' m' }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Horizontal Speed">
<span>H.S</span>
<span class="ml10">{{ deviceInfo.device.horizontal_speed === str ? str : deviceInfo.device.horizontal_speed.toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Vertical Speed">
<span>V.S</span>
<span class="ml10">{{ deviceInfo.device.vertical_speed === str ? str : deviceInfo.device.vertical_speed.toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Wind Speed">
<span>W.S</span>
<span class="ml10">{{ deviceInfo.device.wind_speed === str ? str : (deviceInfo.device.wind_speed / 10).toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
</a-row>
</div>
</div>
<div class="battery-slide" v-if="deviceInfo.device.battery.remain_flight_time !== 0">
<div style="background: #535759;" class="width-100"></div>
<div class="capacity-percent" :style="{ width: deviceInfo.device.battery.capacity_percent + '%'}"></div>
<div class="return-home" :style="{ width: deviceInfo.device.battery.return_home_power + '%'}"></div>
<div class="landing" :style="{ width: deviceInfo.device.battery.landing_power + '%'}"></div>
<div class="white-point" :style="{ left: deviceInfo.device.battery.landing_power + '%'}"></div>
<div class="battery" :style="{ left: deviceInfo.device.battery.capacity_percent + '%' }">
{{ Math.floor(deviceInfo.device.battery.remain_flight_time / 60) }}:
{{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}
</div>
</div>
</div>
<!-- 机场OSD -->
<div v-if="osdVisible.visible && osdVisible.is_dock" v-drag-window class="osd-panel fz12">
<div class="drag-title fz16 pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 10%;">
<span>{{ osdVisible.gateway_callsign }}</span>
</div>
<span><a style="color: white; position: absolute; top: 5px; right: 5px;" @click="() => osdVisible.visible = false"><CloseOutlined /></a></span>
<!-- 机场 -->
<div class ="flex-display" style="border-bottom: 1px solid #515151;">
<div class="flex-column flex-align-stretch flex-justify-center" style="width: 60px; background: #2d2d2d;">
<a-tooltip :title="osdVisible.model">
<div class="flex-column flex-align-center flex-justify-center" style="width: 90%;">
<span><RobotFilled style="font-size: 48px;"/></span>
<span class="mt10">Dock</span>
</div>
</a-tooltip>
</div>
<div class="osd flex-1" style="flex: 1">
<a-row>
<a-col span="16" :style="deviceInfo.dock.basic_osd?.mode_code === EDockModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">
{{ EDockModeCode[deviceInfo.dock.basic_osd?.mode_code] }}</a-col>
</a-row>
<a-row>
<a-col span="12">
<a-tooltip title="Accumulated Running Time">
<span><HistoryOutlined /></span>
<span class="ml10">
<span v-if="deviceInfo.dock.work_osd?.acc_time >= 2592000"> {{ Math.floor(deviceInfo.dock.work_osd?.acc_time / 2592000) }}m </span>
<span v-if="(deviceInfo.dock.work_osd?.acc_time % 2592000) >= 86400"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000) / 86400) }}d </span>
<span v-if="(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) >= 3600"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) / 3600) }}h </span>
<span v-if="(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) >= 60"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) / 60) }}min </span>
<span>{{ Math.floor(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600 % 60) }} s</span>
</span>
</a-tooltip>
</a-col>
<a-col span="12">
<a-tooltip title="Activation time">
<span><FieldTimeOutlined /></span>
<span class="ml10">{{ new Date((deviceInfo.dock.work_osd?.activation_time ?? 0) * 1000).toLocaleString() }}
</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Network State">
<span :style="qualityStyle">
<span v-if="deviceInfo.dock.basic_osd?.network_state?.type === NetworkStateTypeEnum.FOUR_G"><SignalFilled /></span>
<span v-else><GlobalOutlined /></span>
</span>
<span class="ml10" >{{ deviceInfo.dock.basic_osd?.network_state?.rate }} kb/s</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="The total number of times the dock has performed missions.">
<span><CarryOutOutlined /></span>
<span class="ml10" >{{ deviceInfo.dock.work_osd?.job_number }} </span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Media File Remain Upload">
<span><CloudUploadOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.dock.link_osd?.media_file_detail?.remain_upload }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip>
<template #title>
<p>total: {{ deviceInfo.dock.basic_osd?.storage?.total }}</p>
<p>used: {{ deviceInfo.dock.basic_osd?.storage?.used }}</p>
</template>
<span><FolderOpenOutlined /></span>
<span class="ml10" v-if="deviceInfo.dock.basic_osd?.storage?.total > 0">
<a-progress type="circle" :width="20" :percent="deviceInfo.dock.basic_osd?.storage?.used * 100/ deviceInfo.dock.basic_osd?.storage?.total"
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.basic_osd?.storage?.used * 100 / deviceInfo.dock.basic_osd?.storage?.total > 80 ? 'red' : '#00ee8b' "/>
</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Wind Speed">
<span>W.S</span>
<span class="ml10">{{ (deviceInfo.dock.basic_osd?.wind_speed ?? str) + ' m/s'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Rainfall">
<span>🌧</span>
<span class="ml10">{{ RainfallEnum[deviceInfo.dock.basic_osd?.rainfall] }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Environment Temperature">
<span>°C</span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.environment_temperature }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Dock Temperature">
<span>°C</span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.temperature }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Dock Humidity">
<span>💦</span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.humidity }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Working Voltage">
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 16px; text-align: center; float: left;">V</span>
<span class="ml10">{{ (deviceInfo.dock.work_osd?.working_voltage ?? str) + ' mV' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Working Current">
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center; float: left;" >A</span>
<span class="ml10">{{ (deviceInfo.dock.work_osd?.working_current ?? str) + ' mA' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Drone in dock">
<span><RocketOutlined /></span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.drone_in_dock }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row class="p5">
<a-col span="24">
<a-button type="primary" :disabled="dockControlPanelVisible" size="small" @click="setDockControlPanelVisible(true)">
Actions
</a-button>
</a-col>
</a-row>
<!-- 机场控制面板 -->
<DockControlPanel v-if="dockControlPanelVisible" :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" @close-control-panel="onCloseControlPanel">
</DockControlPanel>
</div>
</div>
<!-- 飞机-->
<div class ="flex-display">
<div class="flex-column flex-align-stretch flex-justify-center" style="width: 60px; background: #2d2d2d;">
<a-tooltip :title="osdVisible.model">
<div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
<span><a-image :src="M30" :preview="false"/></span>
<span>M30</span>
</div>
</a-tooltip>
</div>
<div class="osd flex-1">
<a-row>
<a-col span="16" :style="!deviceInfo.device || deviceInfo.device?.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">
{{ !deviceInfo.device ? EModeCode[EModeCode.Disconnected] : EModeCode[deviceInfo.device?.mode_code] }}</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Upward Quality">
<span><SignalFilled /><ArrowUpOutlined style="font-size: 9px; vertical-align: top;" /></span>
<span class="ml10">{{ deviceInfo.dock.link_osd?.sdr?.up_quality }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Downward Quality">
<span><SignalFilled /><ArrowDownOutlined style="font-size: 9px; vertical-align: top;" /></span>
<span class="ml10">{{ deviceInfo.dock.link_osd?.sdr?.down_quality }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Drone Battery Level">
<span><ThunderboltOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.device && deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device?.battery.capacity_percent + ' %' : str }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip>
<template #title>
<p>total: {{ deviceInfo.device?.storage?.total }}</p>
<p>used: {{ deviceInfo.device?.storage?.used }}</p>
</template>
<span><FolderOpenOutlined /></span>
<span class="ml10" v-if="deviceInfo.device?.storage?.total > 0">
<a-progress type="circle" :width="20" :percent="deviceInfo.device?.storage?.used * 100/ deviceInfo.device?.storage?.total"
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.device?.storage?.used * 100 / deviceInfo.device?.storage?.total > 80 ? 'red' : '#00ee8b' "/>
</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-tooltip title="RTK Fixed">
<a-col span="6" class="flex-row flex-align-center flex-justify-start">
<span>Fixed</span>
<span class="ml10 circle" :style="deviceInfo.device?.position_state.is_fixed === 1 ? 'backgroud: rgb(25,190,107);' : ' background: red;'"></span>
</a-col>
</a-tooltip>
<a-col span="6">
<a-tooltip title="GPS">
<span>GPS</span>
<span class="ml10">{{ deviceInfo.device ? deviceInfo.device.position_state.gps_number : str }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="RTK">
<span><TrademarkOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.device ? deviceInfo.device.position_state.rtk_number : str }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Flight Mode">
<span><ControlOutlined class="fz16" /></span>
<span class="ml10">{{ deviceInfo.device ? EGear[deviceInfo.device?.gear] : str }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Altitude above sea level">
<span>ASL</span>
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.height === str ? str : deviceInfo.device?.height.toFixed(2) + ' m'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Altitude above takeoff level">
<span>ALT</span>
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.elevation === str ? str : deviceInfo.device?.elevation.toFixed(2) + ' m' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Distance to Home Point">
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center; display: block; float: left;" >H</span>
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.home_distance === str ? str : deviceInfo.device?.home_distance.toFixed(2) + ' m' }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Horizontal Speed">
<span>H.S</span>
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device?.horizontal_speed === str ? str : deviceInfo.device?.horizontal_speed.toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Vertical Speed">
<span>V.S</span>
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.vertical_speed === str ? str : deviceInfo.device?.vertical_speed.toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Wind Speed">
<span>W.S</span>
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.wind_speed === str ? str : (deviceInfo.device?.wind_speed / 10).toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
</a-row>
</div>
</div>
<div class="battery-slide" v-if="deviceInfo.device && deviceInfo.device.battery.remain_flight_time !== 0" style="border: 1px solid red">
<div style="background: #535759;" class="width-100"></div>
<div class="capacity-percent" :style="{ width: deviceInfo.device.battery.capacity_percent + '%'}"></div>
<div class="return-home" :style="{ width: deviceInfo.device.battery.return_home_power + '%'}"></div>
<div class="landing" :style="{ width: deviceInfo.device.battery.landing_power + '%'}"></div>
<div class="white-point" :style="{ left: deviceInfo.device.battery.landing_power + '%'}"></div>
<div class="battery" :style="{ left: deviceInfo.device.battery.capacity_percent + '%' }">
{{ Math.floor(deviceInfo.device.battery.remain_flight_time / 60) }}:
{{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}
</div>
</div>
<!-- 飞行指令 -->
<DroneControlPanel :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" :payloads="osdVisible.payloads"></DroneControlPanel>
</div>
<!-- liveview -->
<div class="liveview" v-if="livestreamOthersVisible" v-drag-window >
<div style="height: 40px; width: 100%" class="drag-title"></div>
<a style="position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;" @click="closeLivestreamOthers"><CloseOutlined /></a>
<LivestreamOthers />
</div>
<div class="liveview" v-if="livestreamAgoraVisible" v-drag-window >
<div style="height: 40px; width: 100%" class="drag-title"></div>
<a style="position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;" @click="closeLivestreamAgora"><CloseOutlined /></a>
<LivestreamAgora />
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'
import {
generateLineContent,
generatePointContent,
generatePolyContent
} from '../utils/map-layer-utils'
import { postElementsReq } from '/@/api/layer'
import { MapDoodleType, MapElementEnum } from '/@/constants/map'
import { useGMapManage } from '/@/hooks/use-g-map'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import { useMouseTool } from '/@/hooks/use-mouse-tool'
import { getApp, getRoot } from '/@/root'
import { useMyStore } from '/@/store'
import { GeojsonCoordinate } from '/@/types/map'
import { MapDoodleEnum } from '/@/types/map-enum'
import { PostElementsBody } from '/@/types/mapLayer'
import { uuidv4 } from '/@/utils/uuid'
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'
import { deviceTsaUpdate } from '/@/hooks/use-g-map-tsa'
import {
DeviceOsd, DeviceStatus, DockOsd, EGear, EModeCode, GatewayOsd, EDockModeCode,
NetworkStateQualityEnum, NetworkStateTypeEnum, RainfallEnum, DroneInDockEnum
} from '/@/types/device'
import pin from '/@/assets/icons/pin-2d8cf0.svg'
import M30 from '/@/assets/icons/m30.png'
import {
BorderOutlined, LineOutlined, CloseOutlined, ControlOutlined, TrademarkOutlined, ArrowDownOutlined,
ThunderboltOutlined, SignalFilled, GlobalOutlined, HistoryOutlined, CloudUploadOutlined, RocketOutlined,
FieldTimeOutlined, CloudOutlined, CloudFilled, FolderOpenOutlined, RobotFilled, ArrowUpOutlined, CarryOutOutlined
} from '@ant-design/icons-vue'
import { EDeviceTypeName } from '../types'
import DockControlPanel from './g-map/DockControlPanel.vue'
import { useDockControl } from './g-map/use-dock-control'
import DroneControlPanel from './g-map/DroneControlPanel.vue'
import { useConnectMqtt } from './g-map/use-connect-mqtt'
import LivestreamOthers from './livestream-others.vue'
import LivestreamAgora from './livestream-agora.vue'
import FlightAreaActionIcon from './flight-area/FlightAreaActionIcon.vue'
import { EFlightAreaType } from '../types/flight-area'
import { useFlightArea } from './flight-area/use-flight-area'
import { useFlightAreaDroneLocationEvent } from './flight-area/use-flight-area-drone-location-event'
export default defineComponent({
components: {
BorderOutlined,
LineOutlined,
CloseOutlined,
ControlOutlined,
TrademarkOutlined,
ThunderboltOutlined,
SignalFilled,
GlobalOutlined,
HistoryOutlined,
CloudUploadOutlined,
FieldTimeOutlined,
CloudOutlined,
CloudFilled,
FolderOpenOutlined,
RobotFilled,
ArrowUpOutlined,
ArrowDownOutlined,
DockControlPanel,
DroneControlPanel,
CarryOutOutlined,
RocketOutlined,
LivestreamOthers,
LivestreamAgora,
FlightAreaActionIcon,
},
name: 'GMap',
props: {},
setup () {
const useMouseToolHook = useMouseTool()
const useGMapManageHook = useGMapManage()
const deviceTsaUpdateHook = deviceTsaUpdate()
const root = getRoot()
const mouseMode = ref(false)
const store = useMyStore()
const state = reactive({
currentType: '',
coverIndex: 0,
isFlightArea: false,
})
const str: string = '--'
const deviceInfo = reactive({
gateway: {
capacity_percent: str,
transmission_signal_quality: str,
} as GatewayOsd,
dock: {
} as DockOsd,
device: {
gear: -1,
mode_code: EModeCode.Disconnected,
height: str,
home_distance: str,
horizontal_speed: str,
vertical_speed: str,
wind_speed: str,
wind_direction: str,
elevation: str,
position_state: {
gps_number: str,
is_fixed: 0,
rtk_number: str
},
battery: {
capacity_percent: str,
landing_power: str,
remain_flight_time: 0,
return_home_power: str,
},
latitude: 0,
longitude: 0,
} as DeviceOsd
})
const shareId = computed(() => {
return store.state.layerBaseInfo.share
})
const defaultId = computed(() => {
return store.state.layerBaseInfo.default
})
const drawVisible = computed(() => {
return store.state.drawVisible
})
const livestreamOthersVisible = computed(() => {
return store.state.livestreamOthersVisible
})
const livestreamAgoraVisible = computed(() => {
return store.state.livestreamAgoraVisible
})
const osdVisible = computed(() => {
return store.state.osdVisible
})
const qualityStyle = computed(() => {
if (deviceInfo.dock.basic_osd?.network_state?.type === NetworkStateTypeEnum.ETHERNET ||
(deviceInfo.dock.basic_osd?.network_state?.quality || 0) > NetworkStateQualityEnum.FAIR) {
return 'color: #00ee8b'
}
if ((deviceInfo.dock.basic_osd?.network_state?.quality || 0) === NetworkStateQualityEnum.FAIR) {
return 'color: yellow'
}
return 'color: red'
})
watch(() => store.state.deviceStatusEvent,
data => {
if (Object.keys(data.deviceOnline).length !== 0) {
deviceTsaUpdateHook.initMarker(data.deviceOnline.domain, data.deviceOnline.device_callsign, data.deviceOnline.sn)
store.state.deviceStatusEvent.deviceOnline = {} as DeviceStatus
}
if (Object.keys(data.deviceOffline).length !== 0) {
deviceTsaUpdateHook.removeMarker(data.deviceOffline.sn)
if ((data.deviceOffline.sn === osdVisible.value.sn) || (osdVisible.value.is_dock && data.deviceOffline.sn === osdVisible.value.gateway_sn)) {
osdVisible.value.visible = false
store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
}
store.state.deviceStatusEvent.deviceOffline = {}
}
},
{
deep: true
}
)
watch(() => store.state.deviceState, data => {
if (data.currentType === EDeviceTypeName.Gateway && data.gatewayInfo[data.currentSn]) {
const coordinate = wgs84togcj02(data.gatewayInfo[data.currentSn].longitude, data.gatewayInfo[data.currentSn].latitude)
deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])
if (osdVisible.value.visible && osdVisible.value.gateway_sn !== '') {
deviceInfo.gateway = data.gatewayInfo[osdVisible.value.gateway_sn]
}
}
if (data.currentType === EDeviceTypeName.Aircraft && data.deviceInfo[data.currentSn]) {
const coordinate = wgs84togcj02(data.deviceInfo[data.currentSn].longitude, data.deviceInfo[data.currentSn].latitude)
deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])
if (osdVisible.value.visible && osdVisible.value.sn !== '') {
deviceInfo.device = data.deviceInfo[osdVisible.value.sn]
}
}
if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) {
const coordinate = wgs84togcj02(data.dockInfo[data.currentSn].basic_osd?.longitude, data.dockInfo[data.currentSn].basic_osd?.latitude)
deviceTsaUpdateHook.initMarker(EDeviceTypeName.Dock, EDeviceTypeName[EDeviceTypeName.Dock], data.currentSn, coordinate[0], coordinate[1])
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.basic_osd.sub_device?.device_sn ?? osdVisible.value.sn]
}
}
}, {
deep: true
})
watch(
() => store.state.wsEvent,
newData => {
const useGMapCoverHook = useGMapCover()
const event = newData
let exist = false
if (Object.keys(event.mapElementCreat).length !== 0) {
console.log(event.mapElementCreat)
const ele = event.mapElementCreat
store.state.Layers.forEach(layer => {
layer.elements.forEach(e => {
if (e.id === ele.id) {
exist = true
console.log('true')
}
})
})
if (exist === false) {
setLayers({
id: ele.id,
name: ele.name,
resource: ele.resource
})
updateCoordinates('wgs84-gcj02', ele)
const data = { id: ele.id, name: ele.name }
if (MapElementEnum.PIN === ele.resource?.type) {
useGMapCoverHook.init2DPin(
ele.name,
ele.resource.content.geometry.coordinates,
ele.resource.content.properties.color,
data
)
} else if (MapElementEnum.LINE === ele.resource?.type) {
useGMapCoverHook.initPolyline(
ele.name,
ele.resource.content.geometry.coordinates,
ele.resource.content.properties.color,
data)
} else if (MapElementEnum.POLY === ele.resource?.type) {
useGMapCoverHook.initPolygon(
ele.name,
ele.resource.content.geometry.coordinates,
ele.resource.content.properties.color,
data)
}
}
store.state.wsEvent.mapElementCreat = {}
}
if (Object.keys(event.mapElementUpdate).length !== 0) {
console.log(event.mapElementUpdate)
console.log('该功能还未实现,请开发商自己增加')
store.state.wsEvent.mapElementUpdate = {}
}
if (Object.keys(event.mapElementDelete).length !== 0) {
console.log(event.mapElementDelete)
console.log('该功能还未实现,请开发商自己增加')
store.state.wsEvent.mapElementDelete = {}
}
},
{
deep: true
}
)
function draw (type: MapDoodleType, bool: boolean, flightAreaType?: EFlightAreaType) {
state.currentType = type
mouseMode.value = bool
state.isFlightArea = !!flightAreaType
useMouseToolHook.mouseTool(type, getDrawCallback, flightAreaType)
}
// dock 控制面板
const {
dockControlPanelVisible,
setDockControlPanelVisible,
onCloseControlPanel,
} = useDockControl()
// 连接或断开drc
useConnectMqtt()
onMounted(() => {
const app = getApp()
useGMapManageHook.globalPropertiesConfig(app)
})
const { getDrawFlightAreaCallback, onFlightAreaDroneLocationWs } = useFlightArea()
useFlightAreaDroneLocationEvent(onFlightAreaDroneLocationWs)
function selectFlightAreaAction ({ type, isCircle }: { type: EFlightAreaType, isCircle: boolean }) {
draw(isCircle ? MapDoodleEnum.CIRCLE : MapDoodleEnum.POLYGON, true, type)
}
function getDrawCallback ({ obj }: { obj : any }) {
if (state.isFlightArea) {
getDrawFlightAreaCallback(obj)
return
}
switch (state.currentType) {
case MapDoodleEnum.PIN:
postPinPositionResource(obj)
break
case MapDoodleEnum.POLYLINE:
postPolylineResource(obj)
break
case MapDoodleEnum.POLYGON:
postPolygonResource(obj)
break
default:
break
}
}
async function postPinPositionResource (obj) {
const req = getPinPositionResource(obj)
setLayers(req)
const coordinates = req.resource.content.geometry.coordinates
updateCoordinates('gcj02-wgs84', req);
(req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2])
const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name })
store.state.coverMap[req.id] = [obj]
}
async function postPolylineResource (obj) {
const req = getPolylineResource(obj)
setLayers(req)
updateCoordinates('gcj02-wgs84', req)
const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name })
store.state.coverMap[req.id] = [obj]
}
async function postPolygonResource (obj) {
const req = getPoygonResource(obj)
setLayers(req)
updateCoordinates('gcj02-wgs84', req)
const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name })
store.state.coverMap[req.id] = [obj]
}
function getPinPositionResource (obj) {
const position = obj.getPosition()
const resource = generatePointContent(position)
const name = obj._originOpts.title
const id = uuidv4()
return {
id,
name,
resource
}
}
function getPolylineResource (obj) {
const path = obj.getPath()
const resource = generateLineContent(path)
const { name, id } = getBaseInfo(obj._opts)
return {
id,
name,
resource
}
}
function getPoygonResource (obj) {
const path = obj.getPath()
const resource = generatePolyContent(path)
const { name, id } = getBaseInfo(obj._opts)
return {
id,
name,
resource
}
}
function getBaseInfo (obj) {
const name = obj.title
const id = uuidv4()
return { name, id }
}
function setLayers (resource: PostElementsBody) {
const layers = store.state.Layers
const layer = layers.find(item => item.id.includes(shareId.value))
// layer.id = 'private_layer' + uuidv4()
// layer?.elements.push(resource)
if (layer?.elements) {
;(layer?.elements as any[]).push(resource)
}
console.log('layers', layers)
store.commit('SET_LAYER_INFO', layers)
}
function closeLivestreamOthers () {
store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', false)
}
function closeLivestreamAgora () {
store.commit('SET_LIVESTREAM_AGORA_VISIBLE', false)
}
function updateCoordinates (transformType: string, element: any) {
const geoType = element.resource?.content.geometry.type
const type = element.resource?.type as number
if (element.resource) {
if (MapElementEnum.PIN === type) {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate
if (transformType === 'wgs84-gcj02') {
const transResult = wgs84togcj02(
coordinates[0],
coordinates[1]
) as GeojsonCoordinate
element.resource.content.geometry.coordinates = transResult
} else if (transformType === 'gcj02-wgs84') {
const transResult = gcj02towgs84(
coordinates[0],
coordinates[1]
) as GeojsonCoordinate
element.resource.content.geometry.coordinates = transResult
}
} else if (MapElementEnum.LINE === type) {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
}
element.resource.content.geometry.coordinates = coordinates
} else if (MapElementEnum.POLY === type) {
const coordinates = element.resource?.content.geometry
.coordinates[0] as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
}
element.resource.content.geometry.coordinates = [coordinates]
}
}
}
return {
draw,
mouseMode,
drawVisible,
livestreamOthersVisible,
livestreamAgoraVisible,
osdVisible,
pin,
state,
M30,
deviceInfo,
EGear,
EModeCode,
str,
EDockModeCode,
dockControlPanelVisible,
setDockControlPanelVisible,
onCloseControlPanel,
NetworkStateTypeEnum,
NetworkStateQualityEnum,
RainfallEnum,
DroneInDockEnum,
closeLivestreamOthers,
closeLivestreamAgora,
qualityStyle,
selectFlightAreaAction,
}
}
})
</script>
<style lang="scss" scoped>
.g-map-wrapper {
height: 100%;
width: 100%;
.g-action-panel {
position: absolute;
top: 16px;
right: 16px;
.g-action-item {
width: 28px;
height: 28px;
background: white;
color: $primary;
border-radius: 2px;
line-height: 28px;
text-align: center;
margin-bottom: 2px;
}
.g-action-item:hover {
border: 1px solid $primary;
border-radius: 2px;
}
}
.selection {
border: 1px solid $primary;
border-radius: 2px;
}
// antd button 光晕
&:deep(.ant-btn){
&::after {
display: none;
}
}
}
.osd-panel {
position: absolute;
margin-left: 10px;
left: 0;
top: 10px;
width: 480px;
background: #000;
color: #fff;
border-radius: 2px;
opacity: 0.8;
}
.osd > div:not(.dock-control-panel) {
margin-top: 5px;
padding-left: 5px;
}
.circle {
border-radius: 50%;
width: 10px;
height: 10px;
}
.battery-slide {
.capacity-percent {
background: #00ee8b;
}
.return-home {
background: #ff9f0a;
}
.landing {
background: #f5222d;
}
.white-point {
width: 4px;
height: 4px;
border-radius: 50%;
background: white;
bottom: -0.5px;
}
.battery {
background: #141414;
color: #00ee8b;
margin-top: -10px;
height: 20px;
width: auto;
border-left: 1px solid #00ee8b;
padding: 0 5px;
}
}
.battery-slide > div {
position: absolute;
min-height: 2px;
border-radius: 2px;
}
.liveview {
position: absolute;
color: #fff;
z-index: 1;
left: 0;
margin-left: 10px;
top: 10px;
text-align: center;
width: 800px;
height: 720px;
background: #232323;
}
</style>
================================================
FILE: src/components/LayersTree.vue
================================================
<template>
<span>
<a-tree
draggable
:defaultExpandAll="true"
class="device-map-layers"
@drop="onDrop"
v-bind="$attrs"
>
<a-tree-node
:title="layer.name"
:id="layer.id"
v-for="layer in getTreeData"
:key="layer.id"
>
<!-- <template #title>
{{layer.name}}
</template> -->
<template v-if="layer.elements">
<a-tree-node
v-for="resource in layer.elements"
:id="getLayerTreeKey('resource', resource.id)"
:key="getLayerTreeKey('resource', resource.id)"
>
<template #title>
{{ resource.name }}
</template>
</a-tree-node>
</template>
</a-tree-node>
</a-tree>
</span>
</template>
<script lang="ts" setup>
import { computed, defineProps, PropType, reactive } from 'vue'
import { useMyStore } from '/@/store'
import { DropEvent, mapLayer } from '/@/types/mapLayer'
import { getLayerTreeKey } from '/@/utils/layer-tree'
const store = useMyStore()
const props = defineProps({
layerData: Array as PropType<mapLayer[]>
})
const state = reactive({
checkedKeys: [] as string[],
expandedKeys: [] as string[]
})
const getTreeData = computed(() => {
// console.log('props.treeData', JSON.parse(JSON.stringify(props.layerData)))
return JSON.parse(JSON.stringify(props.layerData))
})
const shareId = computed(() => {
return store.state.layerBaseInfo.share
})
const defaultId = computed(() => {
return store.state.layerBaseInfo.default
})
async function onDrop ({ node, dragNode, dropPosition, dropToGap }: DropEvent) {
let _treeData = props.layerData || []
let dragKey = dragNode.eventKey
dragKey = dragKey.replaceAll('resource__', '')
const dropPos = node.pos.split('-')
let dropKey =
node.eventKey.includes(shareId.value) ||
node.eventKey.includes(defaultId.value)
? node.eventKey
: node.$parent.eventKey
if (!dragKey || !dropKey) return
dropKey = dropKey.replaceAll('resource__', '')
const loop = (data: mapLayer[], key: string, callback: Function) => {
data.forEach((item, index, arr) => {
if (item.id === key) {
return callback(item, index, arr)
}
if (item.elements) {
return loop(item.elements, key, callback)
}
})
}
const data = [..._treeData] as mapLayer[]
// Find dragObject
let dragObj = {} as mapLayer
loop(data, dragKey, (item: mapLayer, index: number, arr: mapLayer[]) => {
arr.splice(index, 1)
dragObj = item
})
if (!dropToGap) {
// Drop on the content
loop(data, dropKey, (item: mapLayer) => {
item.elements = item.elements || []
// where to insert 示例添加到尾部,可以是随意位置
item.elements.push(dragObj)
})
}
_treeData = data
// console.log('_treeData', _treeData)
}
</script>
<style lang="scss">
$antPrefix: 'ant';
.device-map-layers.#{$antPrefix}-tree {
color: #fff;
.#{$antPrefix}-tree-checkbox:not(.#{$antPrefix}-tree-checkbox-checked)
.#{$antPrefix}-tree-checkbox-inner {
background-color: unset;
}
.anticon {
font-size: 16px;
}
// 第一个层级的 li,有左边距 16px
> li {
padding-left: 16px;
padding-right: 16px;
}
li {
display: flex;
flex-wrap: wrap;
align-items: center;
padding-top: 0;
padding-bottom: 0;
&:first-child {
padding-top: 4px;
}
&.#{$antPrefix}-tree-treenode-disabled
> .#{$antPrefix}-tree-node-content-wrapper {
height: 20px;
span {
color: #fff;
}
}
> ul {
width: 100%;
}
.#{$antPrefix}-tree-switcher {
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
.#{$antPrefix}-tree-checkbox {
z-index: 1;
}
.#{$antPrefix}-tree-checkbox:hover::after,
.#{$antPrefix}-tree-checkbox-wrapper:hover
.#{$antPrefix}-tree-checkbox::after {
visibility: collapse;
}
.#{$antPrefix}-tree-title {
display: block;
}
.#{$antPrefix}-tree-node-content-wrapper {
color: #fff;
width: calc(100% - 46px);
flex: 1;
box-sizing: content-box;
height: 20px;
min-width: 0; // 解决文字溢出不会省略的问题
padding-right: 0;
&:not([draggable='true']) {
border-top: 2px transparent solid;
border-bottom: 2px transparent solid;
}
&:hover {
background-color: transparent;
}
> span {
&::before {
// position: absolute;
// right: 0;
// left: 0;
height: 28px;
transition: all 0.3s;
content: '';
}
// 进度条组件需要相对最外层定位,进度条组件的position不能设置为relative
> *:not(.progress-wrapper) {
position: relative;
z-index: 1;
}
}
&.#{$antPrefix}-tree-node-selected {
background-color: transparent;
color: #2d8cf0;
> span {
&::before {
background-color: #4f4f4f;
}
}
}
}
}
span.#{$antPrefix}-tree-switcher.#{$antPrefix}-tree-switcher_open
.#{$antPrefix}-tree-switcher-icon {
transform: rotate(0deg) !important;
}
span.#{$antPrefix}-tree-switcher.#{$antPrefix}-tree-switcher_close
.#{$antPrefix}-tree-switcher-icon {
transform: rotate(0deg) !important;
}
}
</style>
================================================
FILE: src/components/MediaPanel.vue
================================================
<template>
<div class="header">Media Files</div>
<a-spin :spinning="loading" :delay="1000" tip="downloading" size="large">
<div class="media-panel-wrapper">
<a-table class="media-table" :columns="columns" :data-source="mediaData.data" row-key="fingerprint"
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
<template v-for="col in ['name', 'path']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<a v-if="col === 'name'">{{ text }}</a>
<span v-else>{{ text }}</span>
</a-tooltip>
</template>
<template #original="{ text }">
{{ text }}
</template>
<template #action="{ record }">
<a-tooltip title="download">
<a class="fz18" @click="downloadMedia(record)"><DownloadOutlined /></a>
</a-tooltip>
</template>
</a-table>
</div>
</a-spin>
</template>
<script setup lang="ts">
import { ref } from '@vue/reactivity'
import { TableState } from 'ant-design-vue/lib/table/interface'
import { onMounted, reactive } from 'vue'
import { IPage } from '../api/http/type'
import { ELocalStorageKey } from '../types/enums'
import { downloadFile } from '../utils/common'
import { downloadMediaFile, getMediaFiles } from '/@/api/media'
import { DownloadOutlined } from '@ant-design/icons-vue'
import { message, Pagination } from 'ant-design-vue'
import { load } from '@amap/amap-jsapi-loader'
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const loading = ref(false)
const columns = [
{
title: 'File Name',
dataIndex: 'file_name',
ellipsis: true,
slots: { customRender: 'name' }
},
{
title: 'File Path',
dataIndex: 'file_path',
ellipsis: true,
slots: { customRender: 'path' }
},
// {
// title: 'FileSize',
// dataIndex: 'size',
// },
{
title: 'Drone',
dataIndex: 'drone'
},
{
title: 'Payload Type',
dataIndex: 'payload'
},
{
title: 'Original',
dataIndex: 'is_original',
slots: { customRender: 'original' }
},
{
title: 'Created',
dataIndex: 'create_time'
},
{
title: 'Action',
slots: { customRender: 'action' }
}
]
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
})
type Pagination = TableState['pagination']
interface MediaFile {
fingerprint: string,
drone: string,
payload: string,
is_original: string,
file_name: string,
file_path: string,
create_time: string,
file_id: string,
}
const mediaData = reactive({
data: [] as MediaFile[]
})
onMounted(() => {
getFiles()
})
function getFiles () {
getMediaFiles(workspaceId, body).then(res => {
mediaData.data = res.data.list
paginationProp.total = res.data.pagination.total
paginationProp.current = res.data.pagination.page
console.info(mediaData.data[0])
})
}
function refreshData (page: Pagination) {
body.page = page?.current!
body.page_size = page?.pageSize!
getFiles()
}
function downloadMedia (media: MediaFile) {
loading.value = true
downloadMediaFile(workspaceId, media.file_id).then(res => {
if (!res) {
return
}
const data = new Blob([res])
downloadFile(data, media.file_name)
}).finally(() => {
loading.value = false
})
}
</script>
<style lang="scss" scoped>
.media-panel-wrapper {
width: 100%;
padding: 16px;
.media-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>
================================================
FILE: src/components/common/sidebar.vue
================================================
<template>
<div class="demo-project-sidebar-wrapper flex-justify-between">
<div>
<router-link
v-for="item in options"
:key="item.key"
:to="item.path"
:class="{
'menu-item': true,
selected: selectedRoute(item),
}"
>
<a-tooltip :title="item.label" placement="right">
<Icon class="fz20" style="width: 50px;" :icon="item.icon"/>
</a-tooltip>
</router-link>
</div>
<div class="mb20 flex-display flex-column flex-align-center flex-justify-between">
<a-tooltip title="Back to home" placement="right">
<a @click="goHome"> <Icon icon="ImportOutlined" style="font-size: 22px; color: white"/></a>
</a-tooltip>
</div>
</div>
</template>
<script lang="ts">
import { createVNode, defineComponent } from 'vue'
import { getRoot } from '/@/root'
import * as icons from '@ant-design/icons-vue'
import { ERouterName } from '/@/types'
interface IOptions {
key: number
label: string
path:
| string
| {
path: string
query?: any
}
icon: string
}
const Icon = (props: {icon: string}) => {
return createVNode((icons as any)[props.icon])
}
export default defineComponent({
components: {
Icon,
},
name: 'Sidebar',
setup () {
const root = getRoot()
const options = [
{ key: 0, label: 'Tsa', path: '/' + ERouterName.TSA, icon: 'TeamOutlined' },
{ key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },
{ key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
{ key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
{ key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
{ key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
{ key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
]
function selectedRoute (item: IOptions) {
const path = typeof item.path === 'string' ? item.path : item.path.path
return root.$route.path?.indexOf(path) === 0
}
function goHome () {
root.$router.push('/' + ERouterName.MEMBERS)
}
return {
options,
selectedRoute,
goHome,
}
}
})
</script>
<style scoped lang="scss">
.demo-project-sidebar-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 50px;
border-right: 1px solid #4f4f4f;
color: $text-white-basic;
// flex: 1;
overflow: hidden;
.menu-item {
width: 100%;
padding: 16px 0px;
display: flex;
flex-direction: column;
align-items: center;
color: $text-white-basic;
cursor: pointer;
&.selected {
background-color: #101010;
color: $primary;
}
&.disabled {
pointer-events: none;
opacity: 0.45;
}
}
.filling {
flex: 1;
}
.setting-icon {
font-size: 24px;
margin-bottom: 24px;
color: $text-white-basic;
}
}
.ant-tooltip-open {
border: 0;
}
</style>
================================================
FILE: src/components/common/topbar.vue
================================================
<template>
<div class="width-100 flex-row flex-justify-between flex-align-center" style="height: 60px;">
<div class="height-100">
<a-avatar :size="40" shape="square" :src="cloudapi" />
<span class="ml10 fontBold">{{ workspaceName }}</span>
</div>
<a-space class="fz16 height-100" size="large">
<router-link
v-for="item in options"
:key="item.key"
:to="item.path"
:class="{
'menu-item': true,
}">
<span @click="selectedRoute(item.path)" :style="selected === item.path ? 'color: #2d8cf0;' : 'color: white'">{{ item.label }}</span>
</router-link>
</a-space>
<div class="height-100 fz16 flex-row flex-justify-between flex-align-center">
<a-dropdown>
<div class="height-100">
<span class="fz20 mt20" style="border: 2px solid white; border-radius: 50%; display: inline-flex;"><UserOutlined /></span>
<span class="ml10 mr10" style="float: right;">{{ username }}</span>
</div>
<template #overlay>
<a-menu theme="dark" class="flex-column flex-justify-between flex-align-center">
<a-menu-item>
<span class="mr10" style="font-size: 16px;"><ExportOutlined /></span>
<span @click="logout">Log Out</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</template>
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import { defineComponent, onMounted, ref } from 'vue'
import { getRoot } from '/@/root'
import { getPlatformInfo } from '/@/api/manage'
import { ELocalStorageKey, ERouterName } from '/@/types'
import { UserOutlined, ExportOutlined } from '@ant-design/icons-vue'
import cloudapi from '/@/assets/icons/cloudapi.png'
const root = getRoot()
interface IOptions {
key: number
label: string
path:
| string
| {
path: string
query?: any
}
icon: string
}
const username = ref(localStorage.getItem(ELocalStorageKey.Username))
const workspaceName = ref('')
const options = [
{ key: 0, label: ERouterName.WORKSPACE.charAt(0).toUpperCase() + ERouterName.WORKSPACE.substr(1), path: '/' + ERouterName.WORKSPACE },
{ key: 1, label: ERouterName.MEMBERS.charAt(0).toUpperCase() + ERouterName.MEMBERS.substr(1), path: '/' + ERouterName.MEMBERS },
{ key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES },
{ key: 3, label: ERouterName.FIRMWARES.charAt(0).toUpperCase() + ERouterName.FIRMWARES.substr(1), path: '/' + ERouterName.FIRMWARES },
]
const selected = ref<string>(root.$route.path)
onMounted(() => {
getPlatformInfo().then(res => {
workspaceName.value = res.data.workspace_name
})
})
function selectedRoute (path: string) {
selected.value = path
}
const logout = () => {
localStorage.clear()
root.$router.push(ERouterName.PROJECT)
}
</script>
<style lang="scss" scoped>
@import '/@/styles/index.scss';
.fontBold {
font-weight: 500;
font-size: 18px;
}
</style>
================================================
FILE: src/components/devices/DeviceFirmwareStatus.vue
================================================
<template>
<div>
<span class="status-tag pointer">
<a-popconfirm
:title="getTitle()"
ok-text="Yes"
cancel-text="No"
placement="left"
@confirm="onFirmwareStatusClick(firmware)"
>
<a-tag :color="firmware.firmware_status ? commonColor.NORMAL : commonColor.FAIL"
:class="firmware.firmware_status ? 'border-corner ' : 'status-disable border-corner'">
{{ getText(firmware.firmware_status) }}
</a-tag>
</a-popconfirm>
</span>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, watch, computed } from 'vue'
import { changeFirmareStatus } from '/@/api/manage'
import { ELocalStorageKey } from '/@/types'
import { Firmware, FirmwareStatusEnum } from '/@/types/device-firmware'
import { commonColor } from '/@/utils/color'
const props = defineProps<{
firmware: Firmware
}>()
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
function getTitle () {
return `Are you sure to set this firmware to ${getText(!props.firmware.firmware_status)}?`
}
function getText (status: boolean) {
return status ? FirmwareStatusEnum.TRUE : FirmwareStatusEnum.FALSE
}
function onFirmwareStatusClick (record: Firmware) {
changeFirmareStatus(workspaceId, record.firmware_id, { status: !record.firmware_status }).then((res) => {
if (res.code === 0) {
record.firmware_status = !record.firmware_status
}
})
}
</script>
<style lang="scss" scoped>
.status-disable{
opacity: 0.4;
}
.border-corner {
border-radius: 3px;
}
.pointer {
cursor: pointer;
}
</style>
================================================
FILE: src/components/devices/device-hms/DeviceHmsDrawer.vue
================================================
<template>
<a-drawer
title="Hms Info"
placement="right"
v-model:visible="sVisible"
@update:visible="onVisibleChange"
:destroyOnClose="true"
:width="800">
<div class="flex-row flex-align-center">
<div style="width: 240px;">
<a-range-picker
v-model:value="time"
format="YYYY-MM-DD"
:placeholder="['Start Time', 'End Time']"
@change="onTimeChange"/>
</div>
<div class="ml5">
<a-select
style="width: 150px"
v-model:value="param.level"
@select="onLevelSelect">
<a-select-option
v-for="item in levels"
:key="item.label"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</div>
<div class="ml5">
<a-select
v-model:value="param.domain"
:disabled="!param.children_sn || !param.device_sn"
style="width: 150px"
@select="onDeviceTypeSelect">
<a-select-option
v-for="item in deviceTypes"
:key="item.label"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</div>
<div class="ml5">
<a-input-search
v-model:value="param.message"
placeholder="input search message"
style="width: 200px"
@search="getHms"/>
</div>
</div>
<div>
<a-table :columns="hmsColumns" :scroll="{ x: '100%', y: 600 }" :data-source="hmsData.data" :pagination="hmsPaginationProp" @change="refreshHmsData" row-key="hms_id"
:rowClassName="rowClassName" :loading="loading">
<template #time="{ record }">
<div>{{ record.create_time }}</div>
<div :style="record.update_time ? '' : record.level === EHmsLevel.CAUTION ? 'color: orange;' :
record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'">{{ record.update_time ?? 'It is happening...' }}</div>
</template>
<template #level="{ text }">
<div class="flex-row flex-align-center">
<div :class="text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'" style="width: 10px; height: 10px; border-radius: 50%;"></div>
<div style="margin-left: 3px;">{{ EHmsLevel[text] }}</div>
</div>
</template>
<template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<div >{{ text }}</div>
</a-tooltip>
</template>
<template #domain="{text}">
<a-tooltip :title="EDeviceTypeName[text]">
<div >{{ EDeviceTypeName[text] }}</div>
</a-tooltip>
</template>
</a-table>
</div>
</a-drawer>
</template>
<!-- 暂时只抽取该组件 -->
<script lang="ts" setup>
import { watchEffect, reactive, ref, defineProps, defineEmits, watch } from 'vue'
import { getDeviceHms, HmsQueryBody } from '/@/api/manage'
import moment, { Moment } from 'moment'
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
import { Device, DeviceHms } from '/@/types/device'
import { IPage } from '/@/api/http/type'
import { EDeviceTypeName, EHmsLevel, ELocalStorageKey } from '/@/types'
const props = defineProps<{
visible: boolean,
device: null | Device,
}>()
const emit = defineEmits(['update:visible', 'ok', 'cancel'])
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
// 健康状态
const sVisible = ref(false)
watch(props, () => {
sVisible.value = props.visible
// 显示弹框时,获取设备hms信息
if (props.visible) {
showHms()
}
})
function onVisibleChange (sVisible: boolean) {
setVisible(sVisible)
}
function setVisible (v: boolean, e?: Event) {
sVisible.value = v
emit('update:visible', v, e)
}
const loading = ref(false)
const hmsColumns: ColumnProps[] = [
{ title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } },
{ title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } },
{ title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },
{ title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
{ title: 'Hms Message', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
]
interface DeviceHmsData {
data: DeviceHms[]
}
const hmsData = reactive<DeviceHmsData>({
data: []
})
type Pagination = TableState['pagination']
const hmsPaginationProp = reactive({
pageSizeOptions: ['20', '50', '100'],
showQuickJumper: true,
showSizeChanger: true,
pageSize: 50,
current: 1,
total: 0
})
// 获取分页信息
function getPaginationBody () {
return {
page: hmsPaginationProp.current,
page_size: hmsPaginationProp.pageSize
} as IPage
}
function showHms () {
const dock = props.device
if (!dock) return
if (dock.domain === EDeviceTypeName.Dock) {
getDeviceHmsBySn(dock.device_sn, dock.children?.[0].device_sn ?? '')
}
if (dock.domain === EDeviceTypeName.Aircraft) {
param.domain = EDeviceTypeName.Aircraft
getDeviceHmsBySn('', dock.device_sn)
}
}
function refreshHmsData (page: Pagination) {
hmsPaginationProp.current = page?.current!
hmsPaginationProp.pageSize = page?.pageSize!
getHms()
}
const param = reactive<HmsQueryBody>({
sns: [],
device_sn: '',
children_sn: '',
language: 'en',
begin_time: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0),
end_time: new Date().setHours(23, 59, 59, 999),
domain: -1,
level: '',
message: ''
})
const levels = [
{
label: 'All',
value: ''
}, {
label: EHmsLevel[0],
value: EHmsLevel.NOTICE
}, {
label: EHmsLevel[1],
value: EHmsLevel.CAUTION
}, {
label: EHmsLevel[2],
value: EHmsLevel.WARN
}
]
const deviceTypes = [
{
label: 'All',
value: -1
}, {
label: EDeviceTypeName[EDeviceTypeName.Aircraft],
value: EDeviceTypeName.Aircraft
}, {
label: EDeviceTypeName[EDeviceTypeName.Dock],
value: EDeviceTypeName.Dock
}
]
const rowClassName = (record: any, index: number) => {
const className = []
if ((index & 1) === 0) {
className.push('table-striped')
}
if (record.domain !== EDeviceTypeName.Dock) {
className.push('child-row')
}
return className.toString().replaceAll(',', ' ')
}
const time = ref([moment(param.begin_time), moment(param.end_time)])
function getHms () {
loading.value = true
getDeviceHms(param, workspaceId, getPaginationBody())
.then(res => {
hmsPaginationProp.total = res.data.pagination.total
hmsPaginationProp.current = res.data.pagination.page
hmsData.data = res.data.list
hmsData.data.forEach(hms => {
hms.domain = hms.sn === param.children_sn ? EDeviceTypeName.Aircraft : EDeviceTypeName.Dock
})
loading.value = false
}).catch(_err => {
loading.value = false
})
}
function getDeviceHmsBySn (sn: string, childSn: string) {
param.device_sn = sn
param.children_sn = childSn
param.sns = [param.device_sn, param.children_sn]
getHms()
}
function onTimeChange (newTime: [Moment, Moment]) {
param.begin_time = newTime[0].valueOf()
param.end_time = newTime[1].valueOf()
getHms()
}
function onDeviceTypeSelect (val: number) {
param.sns = [param.device_sn, param.children_sn]
if (val === EDeviceTypeName.Dock) {
param.sns = [param.device_sn, '']
}
if (val === EDeviceTypeName.Aircraft) {
param.sns = ['', param.children_sn]
}
getHms()
}
function onLevelSelect (val: number) {
param.level = val
getHms()
}
</script>
================================================
FILE: src/components/devices/device-log/DeviceLogDetailModal.vue
================================================
<template>
<a-modal
title="日志上传详情"
v-model:visible="sVisible"
width="900px"
:footer="null"
@update:visible="onVisibleChange">
<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 || !airportTableLogState.logList?.object_key" size="small" @click="onDownloadLog(airportTableLogState.logList.file_id)">
下载机场日志
</a-button>
<a-table :columns="airportLogColumns"
:scroll="{ x: '100%', y: 600 }"
:data-source="airportTableLogState.logList?.list"
rowKey="boot_index"
:pagination = "false"
>
<template #log_time="{record}">
<div>{{getLogTime(record)}}</div>
</template>
<template #size="{record}">
<div>{{getLogSize(record.size)}}</div>
</template>
</a-table>
</div>
<div class="log-list-item">
<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"
:scroll="{ x: '100%', y: 600 }"
:data-source="droneTableLogState.logList?.list"
rowKey="boot_index"
:pagination = "false"
>
<template #log_time="{record}">
<div>{{getLogTime(record)}}</div>
</template>
<template #size="{record}">
<div>{{getLogSize(record.size)}}</div>
</template>
</a-table>
</div>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
import { IPage } from '/@/api/http/type'
import { DOMAIN } from '/@/types/device'
import { DeviceLogFileInfo, GetDeviceUploadLogListRsp, getUploadDeviceLogUrl } from '/@/api/device-log'
import { useDeviceLogUploadDetail } from './use-device-log-upload-detail'
import { download } from '/@/utils/download'
const props = defineProps<{
visible: boolean,
deviceLog: null | GetDeviceUploadLogListRsp,
}>()
const emit = defineEmits(['update:visible'])
const sVisible = ref(false)
watchEffect(() => {
sVisible.value = props.visible
if (props.visible) {
classifyDeviceLog()
}
})
function onVisibleChange (sVisible: boolean) {
setVisible(sVisible)
}
function setVisible (v: boolean, e?: Event) {
sVisible.value = v
emit('update:visible', v, e)
}
// 表格
const airportLogColumns: ColumnProps[] = [
{ title: '机场日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } },
{ title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } },
]
const droneLogColumns: ColumnProps[] = [
{ title: '飞行器日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } },
{ title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } },
]
const airportTableLogState = reactive({
logList: {} as DeviceLogFileInfo,
})
const droneTableLogState = reactive({
logList: {} as DeviceLogFileInfo,
})
function classifyDeviceLog () {
if (!props.deviceLog) return
const { device_logs } = props.deviceLog
const { files } = device_logs || {}
if (files && files.length > 0) {
files.forEach(file => {
if (file.module === DOMAIN.DOCK) {
airportTableLogState.logList = file
} else if (file.module === DOMAIN.DRONE) {
droneTableLogState.logList = file
}
})
}
}
const { getLogTime, getLogSize } = useDeviceLogUploadDetail()
async function onDownloadLog (fileId: string) {
const { data } = await getUploadDeviceLogUrl({
file_id: fileId,
logs_id: props.deviceLog?.logs_id || ''
})
if (data) {
download(data)
// download('https:/github.com/dji-sdk/Mobile-SDK-Android-V5/archive/refs/heads/dev-sdk-main.zip')
}
}
</script>
<style lang="scss" scoped>
.device-log-detail-wrap{
.device-log-list{
display: flex;
justify-content: space-between;
padding: 8px 0;
.log-list-item{
width: 420px;
.download-btn{
margin-bottom: 10px;
}
}
}
}
</style>
>
================================================
FILE: src/components/devices/device-log/DeviceLogUploadModal.vue
================================================
<template>
<a-modal
title="设备日志上传"
v-model:visible="sVisible"
width="900px"
:footer="null"
@update:visible="onVisibleChange">
<div class="device-log-upload-wrap">
<div class="page-action-row">
<a-button type="primary" :disabled="deviceLogUploadBtnDisabled" @click="uploadDeviceLog">上传日志</a-button>
</div>
<div class="device-log-list">
<div class="log-list-item">
<a-table :columns="airportLogColumns"
:scroll="{ x: '100%', y: 600 }"
:data-source="airportTableLogState.logList?.list"
:loading="airportTableLogState.tableLoading"
:row-selection="airportTableLogState.rowSelection"
rowKey="boot_index"
:pagination = "false">
<template #log_time="{record}">
<div>{{getLogTime(record)}}</div>
</template>
<template #size="{record}">
<div>{{getLogSize(record.size)}}</div>
</template>
</a-table>
</div>
<div class="log-list-item">
<a-table :columns="droneLogColumns"
:scroll="{ x: '100%', y: 600 }"
:data-source="droneTableLogState.logList?.list"
:loading="droneTableLogState.tableLoading"
:row-selection="droneTableLogState.rowSelection"
rowKey="boot_index"
:pagination = "false">
<template #log_time="{record}">
<div>{{getLogTime(record)}}</div>
</template>
<template #size="{record}">
<div>{{getLogSize(record.size)}}</div>
</template>
</a-table>
</div>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { watchEffect, reactive, ref, computed, defineProps, defineEmits } from 'vue'
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
import { IPage } from '/@/api/http/type'
import { Device, DOMAIN } from '/@/types/device'
import { getDeviceLogList, postDeviceUpgrade, DeviceLogFileInfo, UploadDeviceLogBody, DeviceLogItem } from '/@/api/device-log'
import { message } from 'ant-design-vue'
import { useDeviceLogUploadDetail } from './use-device-log-upload-detail'
const props = defineProps<{
visible: boolean,
device: null | Device,
}>()
const emit = defineEmits(['update:visible', 'upload-log-ok'])
const sVisible = ref(false)
watchEffect(() => {
sVisible.value = props.visible
// 显示弹框时,获取设备日志信息
if (props.visible) {
getDeviceLogInfo()
}
})
function onVisibleChange (sVisible: boolean) {
setVisible(sVisible)
if (!sVisible) {
resetTableLogState()
}
}
function setVisible (v: boolean, e?: Event) {
sVisible.value = v
emit('update:visible', v, e)
}
// 表格
const airportLogColumns: ColumnProps[] = [
{ title: '机场日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } },
{ title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } },
]
const droneLogColumns: ColumnProps[] = [
{ title: '飞行器日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } },
{ title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } },
]
const airportTableLogState = reactive({
logList: {} as DeviceLogFileInfo,
tableLoading: false,
selectRow: [],
rowSelection: {
columnWidth: 15,
selectedRowKeys: [] as number[],
onChange: (selectedRowKeys:number[], selectedRows: []) => {
airportTableLogState.rowSelection.selectedRowKeys = selectedRowKeys
airportTableLogState.selectRow = selectedRows
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
},
}
})
function resetTableLogState () {
airportTableLogState.logList = {} as DeviceLogFileInfo
airportTableLogState.selectRow = []
airportTableLogState.tableLoading = false
}
const droneTableLogState = reactive({
logList: {} as DeviceLogFileInfo,
tableLoading: false,
selectRow: [],
rowSelection: {
columnWidth: 15,
selectedRowKeys: [] as number[],
onChange: (selectedRowKeys: number[], selectedRows: []) => {
droneTableLogState.rowSelection.selectedRowKeys = selectedRowKeys
droneTableLogState.selectRow = selectedRows
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
},
}
})
const deviceLogUploadBtnDisabled = computed(() => {
return (airportTableLogState.rowSelection.selectedRowKeys && airportTableLogState.rowSelection.selectedRowKeys.length <= 0) &&
(droneTableLogState.rowSelection.selectedRowKeys && droneTableLogState.rowSelection.selectedRowKeys.length <= 0)
})
// 获取设备内日志
async function getDeviceLogInfo () {
airportTableLogState.tableLoading = true
droneTableLogState.tableLoading = true
try {
const { code, data } = await getDeviceLogList({
device_sn: props.device?.device_sn || '',
domain: [DOMAIN.DOCK, DOMAIN.DRONE]
})
if (code === 0) {
const { files } = data
if (files && files.length > 0) {
files.forEach(file => {
if (file.module === DOMAIN.DOCK) {
airportTableLogState.logList = file
} else if (file.module === DOMAIN.DRONE) {
droneTableLogState.logList = file
}
})
}
}
} catch (err) {
}
airportTableLogState.tableLoading = false
droneTableLogState.tableLoading = false
}
// 日志上传
async function uploadDeviceLog () {
const body = {
device_sn: props.device?.device_sn || '',
files: [] as any
} as UploadDeviceLogBody
if (airportTableLogState.selectRow && airportTableLogState.selectRow.length > 0) {
body.files.push({
list: airportTableLogState.selectRow,
device_sn: airportTableLogState.logList.device_sn,
module: airportTableLogState.logList.module
})
}
if (droneTableLogState.selectRow && droneTableLogState.selectRow.length > 0) {
body.files.push({
list: droneTableLogState.selectRow,
device_sn: droneTableLogState.logList.device_sn,
module: droneTableLogState.logList.module
})
}
const { code } = await postDeviceUpgrade(body)
if (code === 0) {
message.success('日志上传任务执行成功')
emit('upload-log-ok')
setVisible(false)
}
}
const { getLogTime, getLogSize } = useDeviceLogUploadDetail()
</script>
<style lang="scss" scoped>
.device-log-upload-wrap{
.device-log-list{
display: flex;
justify-content: space-between;
padding: 8px 0;
.log-list-item{
width: 420px;
}
}
}
</style>
================================================
FILE: src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue
================================================
<template>
<a-drawer
title="设备日志上传记录"
placement="right"
v-model:visible="sVisible"
@update:visible="onVisibleChange"
:width="800">
<!-- 设备日志上传记录 -->
<div class="device-log-upload-record-wrap">
<div class="page-action-row">
<a-button type="primary" @click="onUploadDeviceLog">上传日志</a-button>
</div>
<div class="device-log-upload-list">
<a-table :columns="deviceLogUploadListColumns"
:scroll="{ x: '100%', y: 600 }"
:data-source="deviceUploadLogState.uploadLogList"
:loading="deviceUploadLogState.loading"
:pagination="deviceUploadLogState.paginationProp"
@change="onDeviceUploadLogTableChange"
rowKey="logs_id">
<!-- 设备类型 -->
<template #device_type="{ record }">
<div>
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key]}}</div>
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key]}}</div>
</div>
</template>
<!-- 设备sn -->
<template #device_sn="{ record }">
<div>
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ getDeviceInfo(record).parents[0].sn }}</div>
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ getDeviceInfo(record).hosts[0].sn }}</div>
</div>
</template>
<!-- 上传状态 -->
<template #status="{ record }">
<div>
<div>
<span class="circle-icon" :style="{backgroundColor: getDeviceLogUploadStatus(record).color}"></span>
{{ getDeviceLogUploadStatus(record).text }}
</div>
<div v-if="record.status === DeviceLogUploadStatusEnum.Uploading">
<a-progress :percent="getLogProgress(record)" />
</div>
</div>
</template>
<!-- 操作 -->
<template #action="{ record }">
<div class="row-action">
<a-tooltip title="查看详情">
<FileTextOutlined @click="showDeviceLogDetail(record)"/>
</a-tooltip>
<span v-if="record.status === DeviceLogUploadStatusEnum.Uploading">
<a-tooltip title="取消">
<StopOutlined @click="onCancelUploadDeviceLog(record)"/>
</a-tooltip>
</span>
<span v-else>
<a-tooltip title="删除">
<DeleteOutlined @click="onDeleteUploadDeviceLog(record)"/>
</a-tooltip>
</span>
</div>
</template>
</a-table>
</div>
</div>
</a-drawer>
<!-- 设备日志上传弹框 -->
<DeviceLogUploadModal
v-model:visible="deviceLogUploadModalVisible"
:device="props.device"
@upload-log-ok="onUploadLogOk"
></DeviceLogUploadModal>
<!-- 设备日志上传详情弹框 -->
<DeviceLogDetailModal
v-model:visible="deviceLogDetailModalVisible"
:deviceLog="currentDeviceLog"
></DeviceLogDetailModal>
</template>
<script lang="ts" setup>
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
import { IPage } from '/@/api/http/type'
import { Device, DOMAIN, DEVICE_NAME } from '/@/types/device'
import DeviceLogUploadModal from './DeviceLogUploadModal.vue'
import DeviceLogDetailModal from './DeviceLogDetailModal.vue'
import { getDeviceUploadLogList, GetDeviceUploadLogListRsp, cancelDeviceLogUpload, deleteDeviceLogUpload } from '/@/api/device-log'
import { StopOutlined, DeleteOutlined, FileTextOutlined } from '@ant-design/icons-vue'
import { DeviceLogUploadStatusEnum, DeviceLogUploadStatusMap, DeviceLogUploadStatusColor, DeviceLogUploadInfo, DeviceLogUploadWsStatusMap, DeviceLogProgressInfo } from '/@/types/device-log'
import { useDeviceLogUploadProgressEvent } from './use-device-log-upload-progress-event'
import { Modal } from 'ant-design-vue'
const props = defineProps<{
visible: boolean,
device: null | Device,
}>()
const emit = defineEmits(['update:visible'])
const sVisible = ref(false)
watchEffect(() => {
sVisible.value = props.visible
// 显示弹框时,获取设备日志上传记录信息
if (props.visible) {
getDeviceUploadLogInfo()
}
})
function onVisibleChange (sVisible: boolean) {
setVisible(sVisible)
}
function setVisible (v: boolean, e?: Event) {
sVisible.value = v
emit('update:visible', v, e)
}
// 日志列表
const deviceLogUploadListColumns: ColumnProps[] = [
{ title: '上传时间', dataIndex: 'create_time', width: 100 },
{ title: '设备型号', dataIndex: 'device_type', width: 80, slots: { customRender: 'device_type' } },
{ title: '设备SN', dataIndex: 'device_sn', width: 120, slots: { customRender: 'device_sn' } },
{ title: '上传状态', dataIndex: 'status', width: 120, slots: { customRender: 'status' } },
{ title: '操作', dataIndex: 'actions', width: 80, slots: { customRender: 'action' } },
]
const deviceUploadLogState = reactive({
uploadLogList: [] as GetDeviceUploadLogListRsp[],
loading: false,
paginationProp: {
pageSizeOptions: ['20', '50', '100'],
showQuickJumper: true,
showSizeChanger: true,
pageSize: 50,
current: 1,
total: 0
}
})
// 获取上传的设备日志
async function getDeviceUploadLogInfo () {
deviceUploadLogState.loading = true
try {
const { code, data } = await getDeviceUploadLogList({
device_sn: props.device?.device_sn || '',
page: deviceUploadLogState.paginationProp.current,
page_size: deviceUploadLogState.paginationProp.pageSize
})
if (code === 0) {
deviceUploadLogState.uploadLogList = data.list
deviceUploadLogState.paginationProp.total = data.pagination.total
deviceUploadLogState.paginationProp.current = data.pagination.page
deviceUploadLogState.paginationProp.pageSize = data.pagination.page_size
}
deviceUploadLogState.loading = false
} catch (error) {
deviceUploadLogState.loading = false
}
}
type Pagination = TableState['pagination']
// 获取设备信息
function getDeviceInfo (deviceLogItem: GetDeviceUploadLogListRsp) {
const { device_topo: deviceTopo } = deviceLogItem
return deviceTopo
}
// 获取上传状态
function getDeviceLogUploadStatus (deviceLogItem: GetDeviceUploadLogListRsp) {
const statusObj = {
color: '',
text: ''
}
const { status } = deviceLogItem
statusObj.color = DeviceLogUploadStatusColor[status]
statusObj.text = DeviceLogUploadStatusMap[status]
return statusObj
}
// 获取上传进度
function getLogProgress (deviceLogItem: GetDeviceUploadLogListRsp) {
let percent = 0
const { logs_progress } = deviceLogItem
if (logs_progress && logs_progress.length > 0) {
logs_progress.forEach(log => {
percent += (log.progress || 0)
})
percent = percent / logs_progress.length
}
return Math.floor(percent)
}
// 设备日志上传进度更新
function onDeviceLogUploadWs (data: DeviceLogUploadInfo) {
const { sn, output } = data
if (output) {
const { files, status, logs_id: logId } = output || {}
const deviceLogItem = deviceUploadLogState.uploadLogList.find(log => log.logs_id === logId)
if (!deviceLogItem) return
if (status) {
deviceLogItem.status = DeviceLogUploadWsStatusMap[status]
}
if (files && files.length > 0) {
const logsProgress = [] as DeviceLogProgressInfo[]
files.forEach(file => {
logsProgress.push({
...file,
status: DeviceLogUploadWsStatusMap[file.status]
})
})
deviceLogItem.logs_progress = logsProgress
}
}
}
useDeviceLogUploadProgressEvent(onDeviceLogUploadWs)
// 搜索
async function onDeviceUploadLogTableChange (page: Pagination) {
deviceUploadLogState.paginationProp.current = page?.current || 1
deviceUploadLogState.paginationProp.pageSize = page?.pageSize || 20
await getDeviceUploadLogInfo()
}
// 查看上传设备日志详情
const deviceLogDetailModalVisible = ref(false)
const currentDeviceLog = ref({} as GetDeviceUploadLogListRsp)
function showDeviceLogDetail (deviceLogItem: GetDeviceUploadLogListRsp) {
if (!deviceLogItem) return
currentDeviceLog.value = deviceLogItem
deviceLogDetailModalVisible.value = true
}
// 取消上传设备日志
async function onCancelUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) {
Modal.confirm({
title: '取消日志上传',
content: '您确认取消设备日志上传吗?',
okType: 'danger',
onOk () {
cancelDeviceLogUploadOk()
},
})
}
async function cancelDeviceLogUploadOk () {
const { code } = await cancelDeviceLogUpload({
device_sn: props.device?.device_sn || '',
module_list: [DOMAIN.DOCK, DOMAIN.DRONE],
status: 'cancel'
})
if (code === 0) {
await getDeviceUploadLogInfo()
}
}
// 删除上传的设备日志
function onDeleteUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) {
Modal.confirm({
title: '删除上传日志',
content: '您确认删除该条已上传设备日志吗?',
okType: 'danger',
onOk () {
deleteUploadDeviceLogOk(deviceLogItem)
},
})
}
async function deleteUploadDeviceLogOk (deviceLogItem: GetDeviceUploadLogListRsp) {
const { code } = await deleteDeviceLogUpload({
device_sn: props.device?.device_sn || '',
logs_id: deviceLogItem.logs_id
})
if (code === 0) {
await getDeviceUploadLogInfo()
}
}
// 上传日志
const deviceLogUploadModalVisible = ref(false)
function onUploadDeviceLog () {
deviceLogUploadModalVisible.value = true
}
function onUploadLogOk () {
// 刷新列表
getDeviceUploadLogInfo()
}
</script>
<style lang="scss" scoped>
.device-log-upload-record-wrap{
.page-action-row{
display: flex;
justify-content: space-between;
width: 100%;
}
.device-log-upload-list{
padding: 20px 0 10px;
}
.circle-icon {
display: inline-block;
width: 12px;
height: 12px;
margin-right: 3px;
border-radius: 50%;
vertical-align: middle;
flex-shrink: 0;
}
.row-action{
color: #2d8cf0;
& > span{
margin-right: 10px;
}
}
}
</style>
================================================
FILE: src/components/devices/device-log/use-device-log-upload-detail.ts
================================================
import { DeviceLogItem } from '/@/api/device-log'
import { bytesToSize } from '/@/utils/bytes'
import { formatUnixTime } from '/@/utils/time'
import {
DATE_FORMAT_MINUTE
} from '/@/utils/constants'
export function useDeviceLogUploadDetail () {
function getLogTime (deviceLog: DeviceLogItem): string {
const startTime = formatUnixTime(deviceLog.start_time, DATE_FORMAT_MINUTE)
const endTime = formatUnixTime(deviceLog.end_time, DATE_FORMAT_MINUTE)
return `${startTime} — ${endTime}`
}
function getLogSize (size: number) {
return bytesToSize(size)
}
return {
getLogTime,
getLogSize
}
}
================================================
FILE: src/components/devices/device-log/use-device-log-upload-progress-event.ts
================================================
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { DeviceLogUploadInfo } from '/@/types/device-log'
export function useDeviceLogUploadProgressEvent (onDeviceLogUploadWs: (data: DeviceLogUploadInfo) => void): void {
function handleDeviceLogUploadProgress (payload: any) {
onDeviceLogUploadWs(payload.data)
// eslint-disable-next-line no-unused-expressions
// console.log('payload', payload.data)
}
onMounted(() => {
EventBus.on('deviceLogUploadProgress', handleDeviceLogUploadProgress)
})
onBeforeUnmount(() => {
EventBus.off('deviceLogUploadProgress', handleDeviceLogUploadProgress)
})
}
================================================
FILE: src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue
================================================
<template>
<div class="firmware_upgrade_wrap">
<!-- 版本 -->
<span class="version"> {{ device.firmware_version }}</span>
<!-- tag -->
<span v-if="getTagStatus(device)"
class="status-tag pointer">
<a-tag class="pointer"
:color="getFirmwareTag(device.firmware_status).color"
@click="deviceUpgrade(device)">
{{ getFirmwareTag(device.firmware_status).text }}
</a-tag>
</span>
<!-- 进度 -->
<span v-if="device.firmware_status === DeviceFirmwareStatusEnum.DuringUpgrade">
{{ `${device.firmware_progress}`}}
</span>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, watch, computed } from 'vue'
import { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareStatusColor } from '/@/types/device'
const props = defineProps<{
device: Device,
}>()
const emit = defineEmits(['device-upgrade'])
const needUpgrade = computed(() => {
return props.device.firmware_status === DeviceFirmwareStatusEnum.ConsistencyUpgrade ||
props.device.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded
})
function getTagStatus (record: Device) {
return record.firmware_status && record.firmware_status !== DeviceFirmwareStatusEnum.None
}
function getFirmwareTag (status: DeviceFirmwareStatusEnum) {
return {
text: DeviceFirmwareStatus[status] || '',
color: DeviceFirmwareStatusColor[status] || ''
}
}
function deviceUpgrade (record: Device) {
if (!needUpgrade.value) return
emit('device-upgrade', record)
}
</script>
<style lang="scss" scoped>
.firmware_upgrade_wrap{
.status-tag{
margin-left: 10px;
}
.pointer {
cursor: pointer;
}
}
</style>
================================================
FILE: src/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue
================================================
<template>
<a-modal :visible="sVisible"
:title="title"
:closable="false"
centered
@update:visible="onVisibleChange"
@cancel="onCancel"
@ok="onConfirm">
<div>
升级固件版本: {{ deviceUpgradeInfo?.product_version }}
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, Ref, watchEffect } from 'vue'
import { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareTypeEnum } from '/@/types/device'
import { getDeviceUpgradeInfo, GetDeviceUpgradeInfoRsp, DeviceUpgradeBody } from '/@/api/device-upgrade'
const props = defineProps<{
visible: boolean,
title: string,
device: null | Device,
}>()
const emit = defineEmits(['update:visible', 'ok', 'cancel'])
const deviceUpgradeInfo:Ref<GetDeviceUpgradeInfoRsp> = ref({} as GetDeviceUpgradeInfoRsp)
const sVisible = ref(false)
watchEffect(() => {
sVisible.value = props.visible
// 显示弹框时,获取设备升级信息
if (props.visible) {
initDeviceUpgradeInfo()
}
})
function onVisibleChange (sVisible: boolean) {
setVisible(sVisible)
}
function setVisible (v: boolean, e?: Event) {
sVisible.value = v
emit('update:visible', v, e)
}
// 获取设备升级信息
async function initDeviceUpgradeInfo () {
if (!props.device?.device_name) {
return
}
const { code, data } = await getDeviceUpgradeInfo({ device_name: props.device?.device_name })
if (code === 0) {
deviceUpgradeInfo.value = data && data[0]
}
}
// 提交
function checkConfirm () {
if (!deviceUpgradeInfo.value.product_version) {
return false
}
if (!props.device) {
return false
}
if (props.device.firmware_status !== DeviceFirmwareStatusEnum.ToUpgraded && props.device.firmware_status !== DeviceFirmwareStatusEnum.ConsistencyUpgrade) {
return false
}
return true
}
function onConfirm (e: Event) {
if (!checkConfirm()) {
return
}
setVisible(false, e)
emit('ok', [{
device_name: props.device?.device_name,
sn: props.device?.device_sn,
product_version: deviceUpgradeInfo.value.product_version,
firmware_upgrade_type: props.device?.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded ? DeviceFirmwareTypeEnum.ToUpgraded : DeviceFirmwareTypeEnum.ConsistencyUpgrade // 1-普通升级,2-一致性升级
}] as DeviceUpgradeBody, e)
}
function onCancel (e: Event) {
setVisible(false, e)
emit('cancel', e)
}
</script>
<style lang="scss" scoped>
</style>
================================================
FILE: src/components/devices/device-upgrade/use-device-upgrade-event.ts
================================================
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
export function useDeviceUpgradeEvent (onDeviceUpgradeWs: (payload: DeviceCmdExecuteInfo) => void): void {
function handleDeviceUpgrade (payload: any) {
onDeviceUpgradeWs(payload.data)
// eslint-disable-next-line no-unused-expressions
// console.log('payload', payload.data)
}
onMounted(() => {
EventBus.on('deviceUpgrade', handleDeviceUpgrade)
})
onBeforeUnmount(() => {
EventBus.off('deviceUpgrade', handleDeviceUpgrade)
})
}
================================================
FILE: src/components/devices/device-upgrade/use-device-upgrade.ts
================================================
import { Ref, ref } from 'vue'
import { Device } from '/@/types/device'
import { postDeviceUpgrade, DeviceUpgradeBody } from '/@/api/device-upgrade'
export function useDeviceFirmwareUpgrade (workspaceId: string) {
const deviceFirmwareUpgradeModalVisible = ref(false)
const selectedDevice: Ref<null | Device> = ref(null)
function setDeviceFirmwareUpgradeModalVisible (visible: boolean) {
deviceFirmwareUpgradeModalVisible.value = visible
}
function setSelectedDevice (device: null | Device) {
selectedDevice.value = device
}
// 点击设备升级
function onDeviceUpgrade (record: Device) {
if (!record) {
return
}
setSelectedDevice(record)
setDeviceFirmwareUpgradeModalVisible(true)
}
// 确认设备升级
async function onUpgradeDeviceOk (deviceUpgradeBody: DeviceUpgradeBody) {
const { code } = await postDeviceUpgrade(workspaceId, deviceUpgradeBody)
if (code === 0) {
// setDeviceFirmwareUpgradeModalVisible(false)
}
}
return {
deviceFirmwareUpgradeModalVisible,
setDeviceFirmwareUpgradeModalVisible,
selectedDevice,
setSelectedDevice,
onDeviceUpgrade,
onUpgradeDeviceOk,
}
}
================================================
FILE: src/components/flight-area/FlightAreaActionIcon.vue
================================================
<template>
<div @click="selectCurrent">
<a-dropdown class="height-100 width-100 icon-panel">
<FlightAreaIcon :type="actionMap[selectedKey].type" :is-circle="actionMap[selectedKey].isCircle" :hide-title="true"/>
<template #overlay>
<a-menu @click="selectAction" mode="vertical-right" :selectedKeys="[selectedKey]">
<a-menu-item v-for="(v, k) in actionMap" :key="k">
<FlightAreaIcon :type="v.type" :is-circle="v.isCircle"/>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</template>
<script lang="ts" setup>
import { ref, defineEmits } from 'vue'
import { EFlightAreaType } from '../../types/flight-area'
import FlightAreaIcon from './FlightAreaIcon.vue'
const emit = defineEmits(['select-action', 'click'])
const actionMap: Record<string, { type: EFlightAreaType, isCircle: boolean}> = {
1: {
type: EFlightAreaType.DFENCE,
isCircle: true,
},
2: {
type: EFlightAreaType.DFENCE,
isCircle: false,
},
3: {
type: EFlightAreaType.NFZ,
isCircle: true,
},
4: {
type: EFlightAreaType.NFZ,
isCircle: false,
},
}
const selectedKey = ref<string>('1')
const selectAction = (item: any) => {
selectedKey.value = item.key
emit('select-action', actionMap[item.key])
}
const selectCurrent = () => {
emit('click', actionMap[selectedKey.value])
}
</script>
<style lang="scss">
.icon-panel {
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>
================================================
FILE: src/components/flight-area/FlightAreaDevicePanel.vue
================================================
<template>
<div class="flight-area-device-panel">
<Title title="Choose Synchronous Devices">
<div style="position: absolute; right: 10px;">
<a style="color: white;" @click="closePanel"><CloseOutlined /></a>
</div>
</Title>
<div class="scrollbar">
<div id="data" v-if="data.length !== 0">
<div v-for="dock in data" :key="dock.device_sn">
<div class="pt5 panel flex-row" @click="selectDock(dock)" :style="{opacity: selectedDocksMap[dock.device_sn] ? 1 : 0.5 }">
<div style="width: 88%">
<div class="title">
<RobotFilled class="fz20"/>
<a-tooltip :title="dock.nickname">
<div class="pr10 ml5" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
</a-tooltip>
</div>
<div class="ml10 mr10 pr5 pl5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
<div>
Custom Flight Area
</div>
<div>
<div v-if="!dock.status">
<a-tooltip title="Dock offline">
<ApiOutlined />
</a-tooltip>
</div>
<div v-else-if="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZED">
<a-tooltip title="Data synced">
<CheckCircleTwoTone twoToneColor="#28d445"/>
</a-tooltip>
</div>
<div v-else-if="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING
|| deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC">
<a-tooltip title="To be synced">
<SyncOutlined spin />
</a-tooltip>
</div>
<div v-else>
<a-tooltip :title="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_msg || 'No synchronization'">
<ExclamationCircleTwoTone twoToneColor="#e70102" />
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="box" v-if="selectedDocksMap[dock.device_sn]">
<CheckOutlined />
</div>
</div>
</div>
<DividerLine style="position: absolute; bottom: 68px;" />
<div class="flex-row flex-justify-between footer">
<a-button class="mr10" @click="closePanel">Cancel
</a-button>
<a-button type="primary" :disabled="confirmDisabled" @click="syncDeviceFlightArea">Sync
</a-button>
</div>
</div>
<div v-else>
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { CloseOutlined, RobotFilled, CheckOutlined, ApiOutlined, CheckCircleTwoTone, SyncOutlined, ExclamationCircleTwoTone } from '@ant-design/icons-vue'
import Title from '/@/components/workspace/Title.vue'
import { defineEmits, onMounted, ref, defineProps, computed } from 'vue'
import { getBindingDevices } from '/@/api/manage'
import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
import { IPage } from '/@/api/http/type'
import { Device } from '/@/types/device'
import DividerLine from '../workspace/DividerLine.vue'
import { message } from 'ant-design-vue'
import { GetDeviceStatus, syncFlightArea } from '/@/api/flight-area'
import { ESyncStatus } from '/@/types/flight-area'
const props = defineProps<{
data: GetDeviceStatus[]
}>()
const emit = defineEmits(['closePanel'])
const closePanel = () => {
emit('closePanel', false)
}
const confirmDisabled = ref(false)
const deviceStatusMap = computed(() => props.data.reduce((obj: Record<string, GetDeviceStatus>, val: GetDeviceStatus) => {
obj[val.device_sn] = val
return obj
}, {} as Record<string, GetDeviceStatus>))
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
const body: IPage = {
page: 1,
total: 0,
page_size: 10,
}
const data = ref<Device[]>([])
const selectedDocksMap = ref<Record<string, boolean>>({})
const getDocks = async () => {
await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
if (res.code !== 0) {
return
}
data.value.push(...res.data.list)
body.page = res.data.pagination.page
body.page_size = res.data.pagination.page_size
body.total = res.data.pagination.total
})
}
const selectDock = (dock: Device) => {
if (!dock.status) {
message.info(`Dock(${dock.nickname}) is offline.`)
return
}
if (deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING ||
deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC) {
message.info('The dock is synchronizing.')
return
}
selectedDocksMap.value[dock.device_sn] = !selectedDocksMap.value[dock.device_sn]
}
onMounted(() => {
getDocks()
const key = setInterval(() => {
if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page) {
clearInterval(key)
return
}
body.page++
getDocks()
}, 1000)
})
const syncDeviceFlightArea = () => {
const keys = Object.keys(selectedDocksMap.value)
if (keys.length === 0) {
message.warn('Please select the docks that need to be synchronized.')
return
}
confirmDisabled.value = true
Object.keys(selectedDocksMap.value).forEach(k => {
const device = deviceStatusMap.value[k]
if (device) {
device.flight_area_status = { sync_code: 0, sync_status: ESyncStatus.WAIT_SYNC, sync_msg: '' }
}
})
syncFlightArea(keys).then(res => {
if (res.code === 0) {
message.success('The devices are synchronizing...')
selectedDocksMap.value = {}
}
}).finally(() => setTimeout(() => {
confirmDisabled.value = false
}, 3000))
}
</script>
<style lang="scss" scoped>
.flight-area-device-panel {
position: absolute;
left: 285px;
width: 280px;
height: 100vh;
float: right;
top: 0;
z-index: 1000;
color: white;
background: #282828;
.footer {
position: absolute;
width: 100%;
bottom: 10px;
padding: 10px;
button {
width: 45%;
border: 0;
}
}
.scrollbar {
overflow-y: auto;
height: calc(100vh - 150px);
}
.box {
font-size: 22px;
line-height: 60px;
}
}
</style>
================================================
FILE: src/components/flight-area/FlightAreaIcon.vue
================================================
<template>
<div class="flex-row flex-align-center">
<div class="shape" :class="type" :style="isCircle ? 'border-radius: 50%;' : ''"></div>
<div class="ml5" v-if="!hideTitle">{{ FlightAreaTypeTitleMap[type][isCircle ? EGeometryType.CIRCLE : EGeometryType.POLYGON] }}</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import { EFlightAreaType, EGeometryType, FlightAreaTypeTitleMap } from '../../types/flight-area'
const props = defineProps<{
type: EFlightAreaType,
isCircle: boolean,
hideTitle?: boolean
}>()
</script>
<style lang="scss">
.nfz {
border-color: red;
}
.dfence {
border-color: $tag-green;
}
.shape {
width: 16px;
height: 16px;
border-width: 3px;
border-style: solid;
}
</style>
================================================
FILE: src/components/flight-area/FlightAreaItem.vue
================================================
<template>
<div class="panel" style="padding-top: 5px;" :class="{disable: !flightArea.status}">
<div class="title">
<a-tooltip :title="flightArea.name">
<div class="pr10" style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ flightArea.name }}</div>
</a-tooltip>
</div>
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
<span class="mr10">Update at {{ formatDateTime(flightArea.update_time).toLocaleString() }}</span>
</div>
<div class="flex-row flex-justify-between flex-align-center ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<FlightAreaIcon :type="flightArea.type" :isCircle="EGeometryType.CIRCLE === flightArea.content.geometry.type"/>
<div class="mr10 operate">
<a-popconfirm v-if="flightArea.status" title="Is it determined to disable the current area?" okText="Disable" @confirm="changeAreaStatus(false)">
<stop-outlined />
</a-popconfirm>
<a-popconfirm v-else @confirm="changeAreaStatus(true)" title="Is it determined to enable the current area?" okText="Enable" >
<check-circle-outlined />
</a-popconfirm>
<EnvironmentFilled class="ml10" @click="clickLocation"/>
<a-popconfirm title="Is it determined to delete the current area?" okText="Delete" okType="danger" @confirm="deleteArea">
<delete-outlined class="ml10" />
</a-popconfirm>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, reactive, defineEmits, computed } from 'vue'
import { GetFlightArea, changeFlightAreaStatus } from '../../api/flight-area'
import FlightAreaIcon from './FlightAreaIcon.vue'
import { formatDateTime } from '../../utils/time'
import { EGeometryType } from '../../types/flight-area'
import { StopOutlined, CheckCircleOutlined, DeleteOutlined, EnvironmentFilled } from '@ant-design/icons-vue'
const props = defineProps<{
data: GetFlightArea
}>()
const emit = defineEmits(['delete', 'update', 'location'])
const flightArea = computed(() => props.data)
const changeAreaStatus = (status: boolean) => {
changeFlightAreaStatus(props.data.area_id, status).then(res => {
if (res.code === 0) {
flightArea.value.status = status
emit('update', flightArea)
}
})
}
const deleteArea = () => {
emit('delete', flightArea.value.area_id)
}
const clickLocation = () => {
emit('location', flightArea.value.area_id)
}
</script>
<style lang="scss" scoped>
.panel {
background: #3c3c3c;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
height: 90px;
width: 95%;
font-size: 13px;
border-radius: 2px;
cursor: pointer;
.title {
display: flex;
flex-direction: row;
align-items: center;
height: 30px;
font-weight: bold;
margin: 0px 10px 0 10px;
}
.operate > *{
font-size: 16px;
}
}
.disable {
opacity: 50%;
}
</style>
================================================
FILE: src/components/flight-area/FlightAreaPanel.vue
================================================
<template>
<div class="flight-area-panel">
<div v-if="data.length === 0">
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
<div v-else v-for="area in flightAreaList" :key="area.area_id">
<FlightAreaItem :data="area" @delete="deleteArea" @update="updateArea" @location="clickLocation(area)"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, computed } from 'vue'
import FlightAreaItem from './FlightAreaItem.vue'
import { GetFlightArea } from '/@/api/flight-area'
const emit = defineEmits(['deleteArea', 'updateArea', 'locationArea'])
const props = defineProps<{
data: GetFlightArea[]
}>()
const flightAreaList = computed(() => props.data)
const deleteArea = (areaId: string) => {
emit('deleteArea', areaId)
}
const updateArea = (area: GetFlightArea) => {
emit('updateArea', area)
}
const clickLocation = (area: GetFlightArea) => {
emit('locationArea', area)
}
</script>
<style lang="scss" scoped>
.flight-area-panel {
overflow-y: auto;
height: calc(100vh - 150px);
}
</style>
================================================
FILE: src/components/flight-area/FlightAreaSyncPanel.vue
================================================
<template>
<div class="flight-area-sync-panel p10 flex-row flex-align-center" >
<RobotFilled class="fz30" twoToneColor="red" fill="#00ff00"/>
<div class="ml20 mr10 flex-column" @click="switchPanel">
<div class="fz18">Sync Across Devices</div>
<div v-if="syncDevicesCount > 0"><a-spin /> Syncing to {{ syncDevicesCount }} devices</div>
</div>
<RightOutlined class="fz18" @click="switchPanel"/>
<FlightAreaDevicePanel v-if="visible" @close-panel="closePanel" :data="syncDevices"/>
</div>
</template>
<script lang="ts" setup>
import { RobotFilled, RightOutlined } from '@ant-design/icons-vue'
import FlightAreaDevicePanel from '/@/components/flight-area/FlightAreaDevicePanel.vue'
import { computed, onMounted, ref, watch } from 'vue'
import { GetDeviceStatus, getDeviceStatus } from '/@/api/flight-area'
import { ESyncStatus, FlightAreaSyncProgress } from '/@/types/flight-area'
import { useFlightAreaSyncProgressEvent } from './use-flight-area-sync-progress-event'
const visible = ref(false)
const syncDevices = ref<GetDeviceStatus[]>([])
const syncDevicesCount = computed(() => syncDevices.value.filter(device =>
device.flight_area_status.sync_status === ESyncStatus.SYNCHRONIZING || device.flight_area_status.sync_status === ESyncStatus.WAIT_SYNC).length)
const getAllDeviceStatus = () => {
getDeviceStatus().then(res => {
if (res.code === 0) {
syncDevices.value = res.data
}
})
}
onMounted(() => {
getAllDeviceStatus()
})
const switchPanel = () => {
visible.value = !visible.value
}
const closePanel = (val: boolean) => {
visible.value = val
}
const handleSyncProgress = (data: FlightAreaSyncProgress) => {
let has = false
const status = { sync_code: data.result, sync_status: data.status, sync_msg: data.message }
syncDevices.value.forEach(device => {
if (data.sn === device.device_sn) {
device.flight_area_status = status
has = true
}
})
if (!has) {
syncDevices.value.push({ device_sn: data.sn, flight_area_status: status })
}
}
useFlightAreaSyncProgressEvent(handleSyncProgress)
</script>
<style lang="scss" scoped>
.flight-area-sync-panel {
height: 70px;
cursor: pointer;
}
</style>
================================================
FILE: src/components/flight-area/use-flight-area-drone-location-event.ts
================================================
import { FlightAreasDroneLocation } from '/@/types/flight-area'
import { CommonHostWs } from '/@/websocket'
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
export function useFlightAreaDroneLocationEvent (onFlightAreaDroneLocationWs: (data: CommonHostWs<FlightAreasDroneLocation>) => void): void {
function handleDroneLocationEvent (data: any) {
onFlightAreaDroneLocationWs(data.data)
}
onMounted(() => {
EventBus.on('flightAreasDroneLocationWs', handleDroneLocationEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasDroneLocationWs', handleDroneLocationEvent)
})
}
================================================
FILE: src/components/flight-area/use-flight-area-sync-progress-event.ts
================================================
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { FlightAreaSyncProgress } from '/@/types/flight-area'
export function useFlightAreaSyncProgressEvent (onFlightAreaSyncProgressWs: (data: FlightAreaSyncProgress) => void): void {
function handleSyncProgressEvent (data: FlightAreaSyncProgress) {
onFlightAreaSyncProgressWs(data)
}
onMounted(() => {
EventBus.on('flightAreasSyncProgressWs', handleSyncProgressEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasSyncProgressWs', handleSyncProgressEvent)
})
}
================================================
FILE: src/components/flight-area/use-flight-area-update.ts
================================================
import { EFlightAreaUpdate, FlightAreaUpdate, FlightAreasDroneLocation } from '/@/types/flight-area'
import { CommonHostWs } from '/@/websocket'
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
function doNothing (data: FlightAreaUpdate) {
}
export function useFlightAreaUpdateEvent (addFunc = doNothing, deleteFunc = doNothing, updateFunc = doNothing): void {
function handleDroneLocationEvent (data: FlightAreaUpdate) {
switch (data.operation) {
case EFlightAreaUpdate.ADD:
addFunc(data)
break
case EFlightAreaUpdate.UPDATE:
updateFunc(data)
break
case EFlightAreaUpdate.DELETE:
deleteFunc(data)
break
}
}
onMounted(() => {
EventBus.on('flightAreasUpdateWs', handleDroneLocationEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasUpdateWs', handleDroneLocationEvent)
})
}
================================================
FILE: src/components/flight-area/use-flight-area.ts
================================================
import { message, notification } from 'ant-design-vue'
import { MapDoodleEnum } from '/@/types/map-enum'
import { getRoot } from '/@/root'
import { PostFlightAreaBody, saveFlightArea } from '/@/api/flight-area'
import { generateCircleContent, generatePolyContent } from '/@/utils/map-layer-utils'
import { GeojsonCoordinate } from '/@/utils/genjson'
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform.js'
import { uuidv4 } from '/@/utils/uuid'
import { CommonHostWs } from '/@/websocket'
import { FlightAreasDroneLocation } from '/@/types/flight-area'
import rootStore from '/@/store'
import { h } from 'vue'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import moment from 'moment'
import { DATE_FORMAT } from '/@/utils/constants'
export function useFlightArea () {
const root = getRoot()
const store = rootStore
const coverMap = store.state.coverMap
let useGMapCoverHook = useGMapCover()
const MIN_RADIUS = 10
function checkCircle (obj: any): boolean {
if (obj.getRadius() < MIN_RADIUS) {
message.error(`The radius must be greater than ${MIN_RADIUS}m.`)
root.$map.remove(obj)
return false
}
return true
}
function checkPolygon (obj: any): boolean {
const path: any[][] = obj.getPath()
if (path.length < 3) {
message.error('The path of the polygon cannot be crossed.')
root.$map.remove(obj)
return false
}
// root.$aMap.GeometryUtil.doesLineLineIntersect()
return true
}
function setExtData (obj: any) {
let ext = obj.getExtData()
const id = uuidv4()
const name = `${ext.type}-${moment().format(DATE_FORMAT)}`
ext = Object.assign({}, ext, { id, name })
obj.setExtData(ext)
return ext
}
function createFlightArea (obj: any) {
const ext = obj.getExtData()
const data = {
id: ext.id,
type: ext.type,
name: ext.name,
}
let coordinates: GeojsonCoordinate | GeojsonCoordinate[][]
let content
switch (ext.mapType) {
case 'circle':
content = generateCircleContent(obj.getCenter(), obj.getRadius())
coordinates = getWgs84(content.geometry.coordinates as GeojsonCoordinate)
break
case 'polygon':
content = generatePolyContent(obj.getPath()).content
coordinates = [getWgs84(content.geometry.coordinates[0] as GeojsonCoordinate[])]
break
default:
message.error(`Invalid type: ${obj.mapType}`)
root.$map.remove(obj)
return
}
content.geometry.coordinates = coordinates
saveFlightArea(Object.assign({}, data, { content }) as PostFlightAreaBody).then(res => {
if (res.code !== 0) {
useGMapCoverHook.removeCoverFromMap(ext.id)
}
}).finally(() => root.$map.remove(obj))
}
function getDrawFlightAreaCallback (obj: any) {
useGMapCoverHook = useGMapCover()
const ext = setExtData(obj)
switch (ext.mapType) {
case MapDoodleEnum.CIRCLE:
if (!checkCircle(obj)) {
return
}
break
case MapDoodleEnum.POLYGON:
if (!checkPolygon(obj)) {
return
}
break
default:
break
}
createFlightArea(obj)
}
const getWgs84 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {
if (coordinate[0] instanceof Array) {
return (coordinate as GeojsonCoordinate[]).map(c => gcj02towgs84(c[0], c[1])) as T
}
return gcj02towgs84(coordinate[0], coordinate[1])
}
const getGcj02 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {
if (coordinate[0] instanceof Array) {
return (coordinate as GeojsonCoordinate[]).map(c => wgs84togcj02(c[0], c[1])) as T
}
return wgs84togcj02(coordinate[0], coordinate[1])
}
const onFlightAreaDroneLocationWs = (data: CommonHostWs<FlightAreasDroneLocation>) => {
const nearArea = data.host.drone_locations.filter(val => !val.is_in_area)
const inArea = data.host.drone_locations.filter(val => val.is_in_area)
notification.warning({
key: `flight-area-${data.sn}`,
message: `Drone(${data.sn}) flight area information`,
description: h('div',
[
h('div', [
h('span', { class: 'fz18' }, 'In the flight area: '),
h('ul', [
...inArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))
])
]),
h('div', [
h('span', { class: 'fz18' }, 'Near the flight area: '),
h('ul', [
...nearArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))
])
])
]),
duration: null,
style: {
width: '420px',
marginTop: '-8px',
marginLeft: '-28px',
}
})
}
return {
getDrawFlightAreaCallback,
getGcj02,
getWgs84,
onFlightAreaDroneLocationWs,
}
}
================================================
FILE: src/components/g-map/DeviceSettingBox.vue
================================================
<template>
<div class="device-setting-wrapper">
<div class="device-setting-header">Device Property Set</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 './use-device-setting'
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>
================================================
FILE: src/components/g-map/DeviceSettingPopover.vue
================================================
<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>
================================================
FILE: src/components/g-map/DockControlPanel.vue
================================================
<template>
<div class="dock-control-panel">
<!-- title -->
<div class="dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between">
<span>Device Control<span class="fz12 pl15">{{ props.sn}}</span></span>
<span @click="closeControlPanel">
<CloseOutlined />
</span>
</div>
<!-- setting -->
<DeviceSettingBox :sn="props.sn" :deviceInfo="props.deviceInfo"></DeviceSettingBox>
<!-- cmd -->
<div class="control-cmd-wrapper">
<div class="control-cmd-header">
Device Remote Debug
<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>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, ref, watch } from 'vue'
import {
CloseOutlined
} from '@ant-design/icons-vue'
import { useDockControl } from './use-dock-control'
import { DeviceInfoType, EDockModeCode } 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,
deviceInfo: DeviceInfoType,
}>()
const store = useMyStore()
const initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem))
const cmdList = ref(initCmdList)
// 根据机场指令执行状态更新信息
watch(() => store.state.devicesCmdExecuteInfo, (devicesCmdExecuteInfo) => {
if (props.sn && devicesCmdExecuteInfo[props.sn]) {
updateDeviceCmdInfoByExecuteInfo(cmdList.value, devicesCmdExecuteInfo[props.sn])
}
}, {
immediate: true,
deep: true,
})
// 根据设备osd信息更新信息
watch(() => props.deviceInfo, (value) => {
updateDeviceCmdInfoByOsd(cmdList.value, value)
// console.log('deviceInfo', value)
}, {
immediate: true,
deep: true
})
// dock 控制指令
const debugStatus = ref(props.deviceInfo.dock?.basic_osd?.mode_code === EDockModeCode.Remote_Debugging)
const emit = defineEmits(['close-control-panel'])
function closeControlPanel () {
emit('close-control-panel', props.sn, debugStatus.value)
}
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,
action: cmdItem.action
}, true)
if (success) {
// updateDeviceSingleCmdInfo(cmdList.value[index])
}
}
</script>
<style lang='scss' scoped>
.dock-control-panel{
position: absolute;
left: calc(100% + 10px);
top: 0px;
width: 480px;
padding: 0 !important;
background: #000;
color: #fff;
border-radius: 2px;
.dock-control-panel-header{
border-bottom: 1px solid #515151;
}
.control-cmd-wrapper{
.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;
flex-wrap: wrap;
justify-content: space-between;
padding: 4px 10px;
.control-cmd-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-cmd-item-left{
display: flex;
flex-direction: column;
.item-label{
font-weight: 700;
}
}
}
}
}
}
</style>
================================================
FILE: src/components/g-map/DroneControlInfoPanel.vue
================================================
<template>
<div class="drone-control-info-wrap">
<a-textarea v-model:value="info" placeholder="drc info" :rows="5" disabled/>
</div>
</template>
<script lang="ts" setup>
import { ref, defineProps, watch } from 'vue'
const props = defineProps<{
message?: string,
}>()
const info = ref('')
watch(() => props.message, message => {
info.value = message || ''
}, {
immediate: true
})
// const emit = defineEmits(['cancel', 'confirm'])
</script>
<style lang="scss" scoped>
.drone-control-info-wrap {
&::v-deep{
textarea.ant-input {
background-color: #000;
color: #fff;
white-space: pre-wrap;
gitextract_ex2lupb9/ ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .gitignore copy ├── .npmrc ├── LICENSE ├── README.md ├── index.html ├── package.json ├── src/ │ ├── App.vue │ ├── antd.ts │ ├── api/ │ │ ├── device-cmd/ │ │ │ └── index.ts │ │ ├── device-log/ │ │ │ └── index.ts │ │ ├── device-setting/ │ │ │ └── index.ts │ │ ├── device-upgrade/ │ │ │ └── index.ts │ │ ├── drc.ts │ │ ├── drone-control/ │ │ │ ├── drone.ts │ │ │ └── payload.ts │ │ ├── flight-area/ │ │ │ └── index.ts │ │ ├── http/ │ │ │ ├── config.ts │ │ │ ├── request.ts │ │ │ └── type.ts │ │ ├── http.ts │ │ ├── layer.ts │ │ ├── manage.ts │ │ ├── media.ts │ │ ├── pilot-bridge.ts │ │ └── wayline.ts │ ├── components/ │ │ ├── GMap.vue │ │ ├── LayersTree.vue │ │ ├── MediaPanel.vue │ │ ├── common/ │ │ │ ├── sidebar.vue │ │ │ └── topbar.vue │ │ ├── devices/ │ │ │ ├── DeviceFirmwareStatus.vue │ │ │ ├── device-hms/ │ │ │ │ └── DeviceHmsDrawer.vue │ │ │ ├── device-log/ │ │ │ │ ├── DeviceLogDetailModal.vue │ │ │ │ ├── DeviceLogUploadModal.vue │ │ │ │ ├── DeviceLogUploadRecordDrawer.vue │ │ │ │ ├── use-device-log-upload-detail.ts │ │ │ │ └── use-device-log-upload-progress-event.ts │ │ │ └── device-upgrade/ │ │ │ ├── DeviceFirmwareUpgrade.vue │ │ │ ├── DeviceFirmwareUpgradeModal.vue │ │ │ ├── use-device-upgrade-event.ts │ │ │ └── use-device-upgrade.ts │ │ ├── flight-area/ │ │ │ ├── FlightAreaActionIcon.vue │ │ │ ├── FlightAreaDevicePanel.vue │ │ │ ├── FlightAreaIcon.vue │ │ │ ├── FlightAreaItem.vue │ │ │ ├── FlightAreaPanel.vue │ │ │ ├── FlightAreaSyncPanel.vue │ │ │ ├── use-flight-area-drone-location-event.ts │ │ │ ├── use-flight-area-sync-progress-event.ts │ │ │ ├── use-flight-area-update.ts │ │ │ └── use-flight-area.ts │ │ ├── g-map/ │ │ │ ├── DeviceSettingBox.vue │ │ │ ├── DeviceSettingPopover.vue │ │ │ ├── DockControlPanel.vue │ │ │ ├── DroneControlInfoPanel.vue │ │ │ ├── DroneControlPanel.vue │ │ │ ├── DroneControlPopover.vue │ │ │ ├── use-connect-mqtt.ts │ │ │ ├── use-device-setting.ts │ │ │ ├── use-dock-control.ts │ │ │ ├── use-drone-control-mqtt-event.ts │ │ │ ├── use-drone-control-ws-event.ts │ │ │ ├── use-drone-control.ts │ │ │ ├── use-manual-control.ts │ │ │ ├── use-mqtt.ts │ │ │ └── use-payload-control.ts │ │ ├── livestream-agora.vue │ │ ├── livestream-others.vue │ │ ├── svgIcon.vue │ │ ├── task/ │ │ │ ├── CreatePlan.vue │ │ │ ├── TaskPanel.vue │ │ │ ├── use-format-task.ts │ │ │ └── use-task-ws-event.ts │ │ └── workspace/ │ │ ├── DividerLine.vue │ │ └── Title.vue │ ├── constants/ │ │ ├── index.ts │ │ ├── map.ts │ │ └── mock-layers.ts │ ├── directives/ │ │ ├── drag-window.ts │ │ └── index.ts │ ├── env.d.ts │ ├── event-bus/ │ │ └── index.ts │ ├── hooks/ │ │ ├── use-connect-websocket.ts │ │ ├── use-g-map-cover.ts │ │ ├── use-g-map-tsa.ts │ │ ├── use-g-map.ts │ │ ├── use-map-tool.ts │ │ └── use-mouse-tool.ts │ ├── main.ts │ ├── mqtt/ │ │ ├── config.ts │ │ └── index.ts │ ├── pages/ │ │ ├── page-pilot/ │ │ │ ├── pilot-bind.vue │ │ │ ├── pilot-home.vue │ │ │ ├── pilot-index.vue │ │ │ ├── pilot-liveshare.vue │ │ │ └── pilot-media.vue │ │ └── page-web/ │ │ ├── home.vue │ │ ├── index.vue │ │ └── projects/ │ │ ├── Firmwares.vue │ │ ├── devices.vue │ │ ├── dock.vue │ │ ├── flight-area.vue │ │ ├── layer.vue │ │ ├── livestream.vue │ │ ├── media.vue │ │ ├── members.vue │ │ ├── task.vue │ │ ├── tsa.vue │ │ ├── wayline.vue │ │ └── workspace.vue │ ├── plugins/ │ │ └── svgBuilder.ts │ ├── root.ts │ ├── router/ │ │ └── index.ts │ ├── shims-mqtt.d.ts │ ├── shims-vue.d.ts │ ├── store/ │ │ └── index.ts │ ├── styles/ │ │ ├── common.scss │ │ ├── flex.style.scss │ │ ├── fonts.scss │ │ ├── index.scss │ │ ├── reset.scss │ │ └── variables.scss │ ├── types/ │ │ ├── airport-tsa.ts │ │ ├── device-cmd.ts │ │ ├── device-firmware.ts │ │ ├── device-log.ts │ │ ├── device-setting.ts │ │ ├── device.ts │ │ ├── drc.ts │ │ ├── drone-control.ts │ │ ├── enums.ts │ │ ├── flight-area.ts │ │ ├── index.ts │ │ ├── live-stream.ts │ │ ├── map-enum.ts │ │ ├── map.d.ts │ │ ├── mapLayer.ts │ │ ├── task.ts │ │ └── wayline.ts │ ├── use-common-components.ts │ ├── utils/ │ │ ├── bytes.ts │ │ ├── color.ts │ │ ├── common.ts │ │ ├── constants.ts │ │ ├── data-process.ts │ │ ├── device-cmd.ts │ │ ├── device-setting.ts │ │ ├── download.ts │ │ ├── error-code/ │ │ │ └── index.ts │ │ ├── genjson.ts │ │ ├── layer-tree.ts │ │ ├── logger.ts │ │ ├── map-layer-utils.ts │ │ ├── storage.ts │ │ ├── time.ts │ │ └── uuid.ts │ ├── vendors/ │ │ ├── coordtransform.js │ │ └── srs.sdk.js │ ├── vite-env.d.ts │ └── websocket/ │ ├── index.ts │ └── util/ │ └── config.ts ├── tsconfig.json └── vite.config.ts
SYMBOL INDEX (496 symbols across 89 files)
FILE: src/antd.ts
method install (line 8) | install (app: App): void {
FILE: src/api/device-cmd/index.ts
constant CMD_API_PREFIX (line 4) | const CMD_API_PREFIX = '/control/api/v1'
type SendCmdParams (line 6) | interface SendCmdParams {
type PostSendCmdBody (line 11) | interface PostSendCmdBody {
function postSendCmd (line 20) | async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBod...
FILE: src/api/device-log/index.ts
constant MNG_API_PREFIX (line 7) | const MNG_API_PREFIX = '/manage/api/v1'
type GetDeviceUploadLogListParams (line 11) | interface GetDeviceUploadLogListParams {
type BriefDeviceInfo (line 21) | interface BriefDeviceInfo {
type DeviceLogProgressInfo (line 27) | interface DeviceLogProgressInfo{
type DeviceLogItem (line 36) | interface DeviceLogItem {
type DeviceLogFileInfo (line 43) | interface DeviceLogFileInfo {
type DeviceLogFileListInfo (line 52) | interface DeviceLogFileListInfo {
type GetDeviceUploadLogListRsp (line 56) | interface GetDeviceUploadLogListRsp {
function getDeviceUploadLogList (line 76) | async function getDeviceUploadLogList (params: GetDeviceUploadLogListPar...
type GetDeviceLogListParams (line 83) | interface GetDeviceLogListParams{
function getDeviceLogList (line 93) | async function getDeviceLogList (params: GetDeviceLogListParams): Promis...
type UploadDeviceLogBody (line 103) | interface UploadDeviceLogBody {
function postDeviceUpgrade (line 119) | async function postDeviceUpgrade (body: UploadDeviceLogBody): Promise<IW...
type DeviceLogUploadAction (line 124) | type DeviceLogUploadAction = 'cancel'
type CancelDeviceLogUploadBody (line 126) | interface CancelDeviceLogUploadBody {
function cancelDeviceLogUpload (line 133) | async function cancelDeviceLogUpload (body: CancelDeviceLogUploadBody): ...
type DeleteDeviceLogUploadBody (line 141) | interface DeleteDeviceLogUploadBody {
function deleteDeviceLogUpload (line 147) | async function deleteDeviceLogUpload (body: DeleteDeviceLogUploadBody): ...
type GetUploadDeviceLogUrlParams (line 155) | interface GetUploadDeviceLogUrlParams{
function getUploadDeviceLogUrl (line 169) | async function getUploadDeviceLogUrl (params: GetUploadDeviceLogUrlParam...
FILE: src/api/device-setting/index.ts
constant MNG_API_PREFIX (line 5) | const MNG_API_PREFIX = '/manage/api/v1'
type PutDevicePropsBody (line 8) | interface PutDevicePropsBody {
function putDeviceProps (line 21) | async function putDeviceProps (deviceSn: string, body: PutDevicePropsBod...
FILE: src/api/device-upgrade/index.ts
constant MNG_API_PREFIX (line 4) | const MNG_API_PREFIX = '/manage/api/v1'
type GetDeviceUpgradeInfoParams (line 6) | interface GetDeviceUpgradeInfoParams {
type GetDeviceUpgradeInfoRsp (line 10) | interface GetDeviceUpgradeInfoRsp {
function getDeviceUpgradeInfo (line 22) | async function getDeviceUpgradeInfo (params: GetDeviceUpgradeInfoParams)...
type UpgradeDeviceInfo (line 29) | interface UpgradeDeviceInfo {
type DeviceUpgradeBody (line 36) | type DeviceUpgradeBody = UpgradeDeviceInfo[]
function postDeviceUpgrade (line 44) | async function postDeviceUpgrade (workspace_id: string, body: DeviceUpgr...
FILE: src/api/drc.ts
constant DRC_API_PREFIX (line 5) | const DRC_API_PREFIX = '/control/api/v1'
type PostDrcBody (line 8) | interface PostDrcBody {
type DrcParams (line 13) | interface DrcParams {
function postDrc (line 23) | async function postDrc (body: PostDrcBody): Promise<IWorkspaceResponse<D...
type DrcEnterBody (line 28) | interface DrcEnterBody {
type DrcEnterResp (line 38) | interface DrcEnterResp {
function postDrcEnter (line 44) | async function postDrcEnter (body: DrcEnterBody): Promise<IWorkspaceResp...
type DrcExitBody (line 49) | interface DrcExitBody {
function postDrcExit (line 55) | async function postDrcExit (body: DrcExitBody): Promise<IWorkspaceRespon...
FILE: src/api/drone-control/drone.ts
constant API_PREFIX (line 4) | const API_PREFIX = '/control/api/v1'
function postFlightAuth (line 8) | async function postFlightAuth (sn: string): Promise<IWorkspaceResponse<n...
type WaylineLostControlActionInCommandFlight (line 12) | enum WaylineLostControlActionInCommandFlight {
type LostControlActionInCommandFLight (line 16) | enum LostControlActionInCommandFLight {
type ERthMode (line 21) | enum ERthMode {
type ECommanderModeLostAction (line 25) | enum ECommanderModeLostAction {
type ECommanderFlightMode (line 29) | enum ECommanderFlightMode {
type PointBody (line 33) | interface PointBody {
type PostFlyToPointBody (line 38) | interface PostFlyToPointBody {
function postFlyToPoint (line 44) | async function postFlyToPoint (sn: string, body: PostFlyToPointBody): Pr...
function deleteFlyToPoint (line 50) | async function deleteFlyToPoint (sn: string): Promise<IWorkspaceResponse...
type PostTakeoffToPointBody (line 55) | interface PostTakeoffToPointBody{
function postTakeoffToPoint (line 71) | async function postTakeoffToPoint (sn: string, body: PostTakeoffToPointB...
FILE: src/api/drone-control/payload.ts
constant API_PREFIX (line 6) | const API_PREFIX = '/control/api/v1'
type PostPayloadAuthBody (line 9) | interface PostPayloadAuthBody {
function postPayloadAuth (line 14) | async function postPayloadAuth (sn: string, body: PostPayloadAuthBody): ...
type PayloadCommandsEnum (line 20) | enum PayloadCommandsEnum {
type PostCameraModeBody (line 30) | interface PostCameraModeBody {
type PostCameraPhotoBody (line 35) | interface PostCameraPhotoBody {
type PostCameraRecordingBody (line 39) | interface PostCameraRecordingBody {
type DeleteCameraRecordingParams (line 43) | interface DeleteCameraRecordingParams {
type PostCameraFocalLengthBody (line 47) | interface PostCameraFocalLengthBody {
type PostGimbalResetBody (line 53) | interface PostGimbalResetBody{
type PostCameraAimBody (line 58) | interface PostCameraAimBody{
type PostPayloadCommandsBody (line 66) | type PostPayloadCommandsBody = {
function postPayloadCommands (line 90) | async function postPayloadCommands (sn: string, body: PostPayloadCommand...
FILE: src/api/flight-area/index.ts
type GetFlightArea (line 7) | interface GetFlightArea {
type PostFlightAreaBody (line 18) | interface PostFlightAreaBody {
type FlightAreaStatus (line 35) | interface FlightAreaStatus {
type GetDeviceStatus (line 41) | interface GetDeviceStatus {
constant MAP_API_PREFIX (line 49) | const MAP_API_PREFIX = '/map/api/v1'
function getFlightAreaList (line 53) | async function getFlightAreaList (): Promise<IWorkspaceResponse<GetFligh...
function changeFlightAreaStatus (line 58) | async function changeFlightAreaStatus (area_id: string, status: boolean)...
function saveFlightArea (line 63) | async function saveFlightArea (body: PostFlightAreaBody): Promise<IWorks...
function deleteFlightArea (line 68) | async function deleteFlightArea (area_id: string): Promise<IWorkspaceRes...
function syncFlightArea (line 73) | async function syncFlightArea (device_sn: string[]): Promise<IWorkspaceR...
function getDeviceStatus (line 78) | async function getDeviceStatus (): Promise<IWorkspaceResponse<GetDeviceS...
FILE: src/api/http.ts
function bindCommonRequestInterceptors (line 15) | function bindCommonRequestInterceptors (instance: AxiosInstance): void {
function bindCommonResponseInterceptors (line 22) | function bindCommonResponseInterceptors (instance: AxiosInstance): void {
function createAxiosInstance (line 30) | function createAxiosInstance (config?: AxiosRequestConfig, commonInterce...
FILE: src/api/http/config.ts
constant CURRENT_CONFIG (line 1) | const CURRENT_CONFIG = {
FILE: src/api/http/request.ts
constant REQUEST_ID (line 9) | const REQUEST_ID = 'X-Request-Id'
function getAuthToken (line 10) | function getAuthToken () {
FILE: src/api/http/type.ts
type IResult (line 1) | interface IResult {
type IPage (line 6) | interface IPage {
type IListWorkspaceResponse (line 12) | interface IListWorkspaceResponse<T> {
type IWorkspaceResponse (line 21) | interface IWorkspaceResponse<T> {
type IStatus (line 27) | type IStatus = 'WAITING' | 'DOING' | 'SUCCESS' | 'FAILED';
type CommonListResponse (line 29) | interface CommonListResponse<T> extends IResult {
type CommonResponse (line 36) | interface CommonResponse<T> extends IResult {
FILE: src/api/layer.ts
constant PREFIX (line 5) | const PREFIX = '/map/api/v1'
type UnknownResponse (line 7) | type UnknownResponse = Promise<IWorkspaceResponse<unknown>>
FILE: src/api/manage.ts
constant HTTP_PREFIX (line 5) | const HTTP_PREFIX = '/manage/api/v1'
type LoginBody (line 8) | interface LoginBody {
type BindBody (line 13) | interface BindBody {
type HmsQueryBody (line 19) | interface HmsQueryBody {
FILE: src/api/media.ts
constant HTTP_PREFIX (line 3) | const HTTP_PREFIX = '/media/api/v1'
FILE: src/api/pilot-bridge.ts
type JsResponse (line 10) | interface JsResponse{
type ThingParam (line 16) | interface ThingParam {
type LiveshareParam (line 23) | interface LiveshareParam {
type MapParam (line 28) | interface MapParam {
type WsParam (line 33) | interface WsParam {
type ApiParam (line 39) | interface ApiParam {
type MediaParam (line 44) | interface MediaParam {
function returnBool (line 50) | function returnBool (response: string): boolean {
function returnString (line 59) | function returnString (response: string): string {
function returnNumber (line 64) | function returnNumber (response: string): number {
function errorHint (line 69) | function errorHint (response: JsResponse): boolean {
method init (line 79) | init (): Map<EComponentName, any> {
method getComponentParam (line 120) | getComponentParam (key:EComponentName): any {
method setComponentParam (line 123) | setComponentParam (key:EComponentName, value:any) {
method loadComponent (line 126) | loadComponent (name:string, param:any):string {
method unloadComponent (line 129) | unloadComponent (name:string) :string {
method isComponentLoaded (line 132) | isComponentLoaded (module:string): boolean {
method setWorkspaceId (line 135) | setWorkspaceId (uuid:string):string {
method setPlatformMessage (line 138) | setPlatformMessage (platformName:string, title:string, desc:string): boo...
method getRemoteControllerSN (line 141) | getRemoteControllerSN () :string {
method getAircraftSN (line 144) | getAircraftSN ():string {
method stopwebview (line 147) | stopwebview ():string {
method setLogEncryptKey (line 150) | setLogEncryptKey (key:string):string {
method clearLogEncryptKey (line 153) | clearLogEncryptKey ():string {
method getLogPath (line 156) | getLogPath ():string {
method platformVerifyLicense (line 159) | platformVerifyLicense (appId:string, appKey:string, appLicense:string): ...
method isPlatformVerifySuccess (line 162) | isPlatformVerifySuccess (): boolean {
method isAppInstalled (line 165) | isAppInstalled (pkgName: string): boolean {
method getVersion (line 168) | getVersion (): string {
method thingGetConnectState (line 173) | thingGetConnectState (): boolean {
method thingGetConfigs (line 177) | thingGetConfigs (): ThingParam {
method getToken (line 183) | getToken () : string {
method setToken (line 186) | setToken (token:string):string {
method getHost (line 189) | getHost (): string {
method setVideoPublishType (line 201) | setVideoPublishType (type:string): boolean {
method getLiveshareConfig (line 210) | getLiveshareConfig (): string {
method setLiveshareConfig (line 214) | setLiveshareConfig (type:number, params:string):string {
method setLiveshareStatusCallback (line 218) | setLiveshareStatusCallback (callbackFunc:string) :string {
method getLiveshareStatus (line 221) | getLiveshareStatus (): LiveStreamStatus {
method startLiveshare (line 224) | startLiveshare (): boolean {
method stopLiveshare (line 227) | stopLiveshare (): boolean {
method wsGetConnectState (line 231) | wsGetConnectState (): boolean {
method wsConnect (line 234) | wsConnect (host: string, token: string, callback: string): string {
method wsDisconnect (line 237) | wsDisconnect (): string {
method wsSend (line 240) | wsSend (message: string): string {
method setAutoUploadPhoto (line 244) | setAutoUploadPhoto (auto:boolean):string {
method getAutoUploadPhoto (line 247) | getAutoUploadPhoto (): boolean {
method setUploadPhotoType (line 250) | setUploadPhotoType (type:number):string {
method getUploadPhotoType (line 253) | getUploadPhotoType (): number {
method setAutoUploadVideo (line 256) | setAutoUploadVideo (auto:boolean):string {
method getAutoUploadVideo (line 259) | getAutoUploadVideo (): boolean {
method setDownloadOwner (line 262) | setDownloadOwner (rcIndex:number):string {
method getDownloadOwner (line 265) | getDownloadOwner (): number {
method onBackClickReg (line 268) | onBackClickReg () {
method onStopPlatform (line 278) | onStopPlatform () {
FILE: src/api/wayline.ts
constant HTTP_PREFIX (line 6) | const HTTP_PREFIX = '/wayline/api/v1'
type CreatePlan (line 39) | interface CreatePlan {
type Task (line 60) | interface Task {
type DeleteTaskParams (line 92) | interface DeleteTaskParams {
function deleteTask (line 97) | async function deleteTask (workspaceId: string, params: DeleteTaskParams...
type UpdateTaskStatus (line 105) | enum UpdateTaskStatus {
type UpdateTaskStatusBody (line 109) | interface UpdateTaskStatusBody {
function updateTaskStatus (line 115) | async function updateTaskStatus (workspaceId: string, body: UpdateTaskSt...
FILE: src/components/devices/device-log/use-device-log-upload-detail.ts
function useDeviceLogUploadDetail (line 8) | function useDeviceLogUploadDetail () {
FILE: src/components/devices/device-log/use-device-log-upload-progress-event.ts
function useDeviceLogUploadProgressEvent (line 5) | function useDeviceLogUploadProgressEvent (onDeviceLogUploadWs: (data: De...
FILE: src/components/devices/device-upgrade/use-device-upgrade-event.ts
function useDeviceUpgradeEvent (line 5) | function useDeviceUpgradeEvent (onDeviceUpgradeWs: (payload: DeviceCmdEx...
FILE: src/components/devices/device-upgrade/use-device-upgrade.ts
function useDeviceFirmwareUpgrade (line 5) | function useDeviceFirmwareUpgrade (workspaceId: string) {
FILE: src/components/flight-area/use-flight-area-drone-location-event.ts
function useFlightAreaDroneLocationEvent (line 6) | function useFlightAreaDroneLocationEvent (onFlightAreaDroneLocationWs: (...
FILE: src/components/flight-area/use-flight-area-sync-progress-event.ts
function useFlightAreaSyncProgressEvent (line 5) | function useFlightAreaSyncProgressEvent (onFlightAreaSyncProgressWs: (da...
FILE: src/components/flight-area/use-flight-area-update.ts
function doNothing (line 6) | function doNothing (data: FlightAreaUpdate) {
function useFlightAreaUpdateEvent (line 8) | function useFlightAreaUpdateEvent (addFunc = doNothing, deleteFunc = doN...
FILE: src/components/flight-area/use-flight-area.ts
function useFlightArea (line 17) | function useFlightArea () {
FILE: src/components/g-map/use-connect-mqtt.ts
type StatusOptions (line 14) | type StatusOptions = {
function useConnectMqtt (line 24) | function useConnectMqtt () {
FILE: src/components/g-map/use-device-setting.ts
function useDeviceSetting (line 5) | function useDeviceSetting () {
FILE: src/components/g-map/use-dock-control.ts
function useDockControl (line 6) | function useDockControl () {
FILE: src/components/g-map/use-drone-control-mqtt-event.ts
function useDroneControlMqttEvent (line 11) | function useDroneControlMqttEvent (sn: string) {
FILE: src/components/g-map/use-drone-control-ws-event.ts
type UseDroneControlWsEventParams (line 8) | interface UseDroneControlWsEventParams {
function useDroneControlWsEvent (line 11) | function useDroneControlWsEvent (sn: string, payloadSn: string, funcs?: ...
FILE: src/components/g-map/use-drone-control.ts
function useDroneControl (line 5) | function useDroneControl () {
FILE: src/components/g-map/use-manual-control.ts
type KeyCode (line 19) | enum KeyCode {
function useManualControl (line 30) | function useManualControl (deviceTopicInfo: DeviceTopicInfo, isCurrentFl...
FILE: src/components/g-map/use-mqtt.ts
type DeviceTopicInfo (line 18) | interface DeviceTopicInfo{
type MessageMqtt (line 24) | type MessageMqtt = (topic: string, payload: Buffer, packet: IPublishPack...
function useMqtt (line 26) | function useMqtt (deviceTopicInfo: DeviceTopicInfo) {
FILE: src/components/g-map/use-payload-control.ts
function usePayloadControl (line 13) | function usePayloadControl () {
FILE: src/components/task/use-format-task.ts
function useFormatTask (line 6) | function useFormatTask () {
FILE: src/components/task/use-task-ws-event.ts
type UseTaskWsEventParams (line 6) | interface UseTaskWsEventParams {
function useTaskWsEvent (line 12) | function useTaskWsEvent (funcs: UseTaskWsEventParams): void {
FILE: src/constants/map.ts
type MapElementColor (line 2) | enum MapElementColor {
type MapDoodleColor (line 12) | enum MapDoodleColor {
type MapElementEnum (line 18) | enum MapElementEnum {
type MapDoodleType (line 23) | type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' | 'circle'
FILE: src/directives/drag-window.ts
function useDragWindowDirective (line 3) | function useDragWindowDirective (app: App): void {
FILE: src/directives/index.ts
function useDirectives (line 4) | function useDirectives (app: App): void {
FILE: src/env.d.ts
type ImportMetaEnv (line 4) | interface ImportMetaEnv {
FILE: src/event-bus/index.ts
type Events (line 3) | type Events = {
FILE: src/hooks/use-connect-websocket.ts
function useConnectWebSocket (line 10) | function useConnectWebSocket (messageHandler: MessageHandler) {
FILE: src/hooks/use-g-map-cover.ts
function useGMapCover (line 12) | function useGMapCover () {
FILE: src/hooks/use-g-map-tsa.ts
function deviceTsaUpdate (line 10) | function deviceTsaUpdate () {
FILE: src/hooks/use-g-map.ts
function useGMapManage (line 5) | function useGMapManage () {
FILE: src/hooks/use-map-tool.ts
function useMapTool (line 4) | function useMapTool () {
FILE: src/hooks/use-mouse-tool.ts
function useMouseTool (line 9) | function useMouseTool () {
FILE: src/mqtt/config.ts
constant OPTIONS (line 6) | const OPTIONS: IClientOptions = {
FILE: src/mqtt/index.ts
class UranusMqtt (line 15) | class UranusMqtt extends EventEmitter {
method constructor (line 21) | constructor (url?: string, options?: IClientOptions) {
FILE: src/plugins/svgBuilder.ts
function svgFind (line 10) | function svgFind(e) {
method transformIndexHtml (line 43) | transformIndexHtml (dom: String) {
FILE: src/root.ts
type ComponentCustomProperties (line 3) | interface ComponentCustomProperties {
function createInstance (line 12) | function createInstance (App: any): VueApp {
function getRoot (line 18) | function getRoot (): ComponentCustomProperties {
function getApp (line 22) | function getApp (): VueApp {
FILE: src/store/index.ts
type RootStateType (line 98) | type RootStateType = ReturnType<typeof initStateFunc>
method SET_LAYER_INFO (line 103) | SET_LAYER_INFO (state, info) {
method SET_DEVICE_INFO (line 106) | SET_DEVICE_INFO (state, info) {
method SET_GATEWAY_INFO (line 111) | SET_GATEWAY_INFO (state, info) {
method SET_DOCK_INFO (line 116) | SET_DOCK_INFO (state, info) {
method SET_DRAW_VISIBLE_INFO (line 138) | SET_DRAW_VISIBLE_INFO (state, bool) {
method SET_LIVESTREAM_OTHERS_VISIBLE (line 141) | SET_LIVESTREAM_OTHERS_VISIBLE (state, bool) {
method SET_LIVESTREAM_AGORA_VISIBLE (line 144) | SET_LIVESTREAM_AGORA_VISIBLE (state, bool) {
method SET_MAP_ELEMENT_CREATE (line 147) | SET_MAP_ELEMENT_CREATE (state, info) {
method SET_MAP_ELEMENT_UPDATE (line 150) | SET_MAP_ELEMENT_UPDATE (state, info) {
method SET_MAP_ELEMENT_DELETE (line 153) | SET_MAP_ELEMENT_DELETE (state, info) {
method SET_DEVICE_ONLINE (line 156) | SET_DEVICE_ONLINE (state, info) {
method SET_DEVICE_OFFLINE (line 159) | SET_DEVICE_OFFLINE (state, info) {
method SET_OSD_VISIBLE_INFO (line 168) | SET_OSD_VISIBLE_INFO (state, info) {
method SET_SELECT_WAYLINE_INFO (line 171) | SET_SELECT_WAYLINE_INFO (state, info) {
method SET_SELECT_DOCK_INFO (line 174) | SET_SELECT_DOCK_INFO (state, info) {
method SET_DEVICE_HMS_INFO (line 177) | SET_DEVICE_HMS_INFO (state, info) {
method SET_DEVICES_CMD_EXECUTE_INFO (line 181) | SET_DEVICES_CMD_EXECUTE_INFO (state, info) { // 保存设备指令ws消息推送
method SET_MQTT_STATE (line 200) | SET_MQTT_STATE (state, mqttState) {
method SET_CLIENT_ID (line 203) | SET_CLIENT_ID (state, clientId) {
method getAllElement (line 209) | async getAllElement ({ commit }) {
method updateElement (line 217) | updateElement ({ state }, content: {type: 'is_check' | 'is_select', id: ...
method setLayerInfo (line 226) | setLayerInfo ({ state }, layers) {
method getLayerInfo (line 243) | getLayerInfo ({ state }, id:string) {
type AllStateStoreTypes (line 261) | type AllStateStoreTypes = RootStateType & {
function useMyStore (line 265) | function useMyStore<T = AllStateStoreTypes> () {
FILE: src/types/airport-tsa.ts
type AirportStorage (line 2) | interface AirportStorage {
type CoverStateEnum (line 8) | enum CoverStateEnum {
type PutterStateEnum (line 16) | enum PutterStateEnum {
type ChargeStateEnum (line 24) | enum ChargeStateEnum {
type DroneChargeState (line 29) | interface DroneChargeState {
type SupplementLightStateEnum (line 35) | enum SupplementLightStateEnum {
type AlarmModeEnum (line 41) | enum AlarmModeEnum {
type BatteryStoreModeEnum (line 47) | enum BatteryStoreModeEnum {
type DroneBatteryStateEnum (line 53) | enum DroneBatteryStateEnum {
type DroneBatteryModeEnum (line 59) | enum DroneBatteryModeEnum {
type FourGLinkStateEnum (line 65) | enum FourGLinkStateEnum {
type SdrLinkStateEnum (line 71) | enum SdrLinkStateEnum {
type LinkWorkModeEnum (line 77) | enum LinkWorkModeEnum {
FILE: src/types/device-cmd.ts
type DeviceCmd (line 3) | enum DeviceCmd {
type DeviceCmdItemAction (line 30) | type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneB...
type DeviceCmdItem (line 32) | interface DeviceCmdItem{
type DeviceCmdStatusText (line 176) | enum DeviceCmdStatusText {
type DeviceCmdExecuteStatus (line 275) | enum DeviceCmdExecuteStatus {
type DeviceCmdExecuteInfo (line 284) | interface DeviceCmdExecuteInfo {
type DevicesCmdExecuteInfo (line 304) | interface DevicesCmdExecuteInfo {
FILE: src/types/device-firmware.ts
type Firmware (line 1) | interface Firmware {
type FirmwareStatusEnum (line 13) | enum FirmwareStatusEnum {
type FirmwareQueryParam (line 19) | interface FirmwareQueryParam {
type FirmwareUploadParam (line 25) | interface FirmwareUploadParam {
type DeviceNameEnum (line 31) | enum DeviceNameEnum {
FILE: src/types/device-log.ts
type DeviceLogUploadStatusEnum (line 5) | enum DeviceLogUploadStatusEnum {
type DeviceLogUploadStatus (line 27) | enum DeviceLogUploadStatus {
type DeviceLogUploadInfo (line 38) | interface DeviceLogUploadInfo {
FILE: src/types/device-setting.ts
type NightLightsStateEnum (line 2) | enum NightLightsStateEnum {
type DistanceLimitStatusEnum (line 8) | enum DistanceLimitStatusEnum {
type DistanceLimitStatus (line 13) | interface DistanceLimitStatus {
type ObstacleAvoidanceStatusEnum (line 19) | enum ObstacleAvoidanceStatusEnum {
type ObstacleAvoidance (line 24) | interface ObstacleAvoidance {
type DeviceSettingKeyEnum (line 31) | enum DeviceSettingKeyEnum {
type DeviceSettingType (line 40) | type DeviceSettingType = Record<DeviceSettingKeyEnum, any>
type DeviceSettingFormModel (line 148) | type DeviceSettingFormModel = typeof initDeviceSettingFormModel
FILE: src/types/device.ts
type DeviceValue (line 6) | interface DeviceValue {
type DOMAIN (line 14) | enum DOMAIN {
type DRONE_TYPE (line 22) | enum DRONE_TYPE {
type PAYLOAD_TYPE (line 31) | enum PAYLOAD_TYPE {
type RC_TYPE (line 53) | enum RC_TYPE {
type DOCK_TYPE (line 60) | enum DOCK_TYPE {
type DEVICE_SUB_TYPE (line 66) | enum DEVICE_SUB_TYPE {
constant DEVICE_MODEL_KEY (line 74) | const DEVICE_MODEL_KEY = {
constant DEVICE_NAME (line 114) | const DEVICE_NAME = {
type ControlSource (line 155) | enum ControlSource {
type PayloadInfo (line 160) | interface PayloadInfo {
type OnlineDevice (line 170) | interface OnlineDevice {
type DeviceFirmwareTypeEnum (line 185) | enum DeviceFirmwareTypeEnum {
type DeviceFirmwareStatusEnum (line 191) | enum DeviceFirmwareStatusEnum {
type Device (line 212) | interface Device {
type DeviceStatus (line 228) | interface DeviceStatus {
type OSDVisible (line 240) | interface OSDVisible {
type GatewayOsd (line 251) | interface GatewayOsd {
type OsdCameraLiveview (line 258) | interface OsdCameraLiveview {
type DeviceOsdCamera (line 264) | interface DeviceOsdCamera {
type DeviceOsd (line 275) | interface DeviceOsd {
type NetworkStateTypeEnum (line 305) | enum NetworkStateTypeEnum {
type NetworkStateQualityEnum (line 310) | enum NetworkStateQualityEnum {
type RainfallEnum (line 319) | enum RainfallEnum {
type DroneInDockEnum (line 326) | enum DroneInDockEnum {
type DockBasicOsd (line 330) | interface DockBasicOsd {
type DrcStateEnum (line 388) | enum DrcStateEnum {
type DockLinkOsd (line 393) | interface DockLinkOsd {
type MaintainStatus (line 419) | interface MaintainStatus {
type DockWorkOsd (line 426) | interface DockWorkOsd {
type DockOsd (line 447) | interface DockOsd {
type EModeCode (line 453) | enum EModeCode {
type EGear (line 471) | enum EGear {
type EDockModeCode (line 484) | enum EDockModeCode {
type DeviceHms (line 493) | interface DeviceHms {
type DeviceInfoType (line 510) | interface DeviceInfoType {
FILE: src/types/drc.ts
type DRC_METHOD (line 1) | enum DRC_METHOD {
type DroneControlProtocol (line 11) | interface DroneControlProtocol {
type DRCOsdInfo (line 20) | interface DRCOsdInfo {
type DRCHsiInfo (line 34) | interface DRCHsiInfo {
type LiveViewDelayItem (line 56) | interface LiveViewDelayItem {
type DRCDelayTimeInfo (line 62) | interface DRCDelayTimeInfo {
type DrcResponseInfo (line 67) | interface DrcResponseInfo {
FILE: src/types/drone-control.ts
type ControlSourceChangeType (line 4) | enum ControlSourceChangeType {
type ControlSourceChangeInfo (line 10) | interface ControlSourceChangeInfo {
type FlyToPointMessage (line 17) | interface FlyToPointMessage {
type TakeoffToPointMessage (line 24) | interface TakeoffToPointMessage {
type DrcModeExitNotifyMessage (line 31) | interface DrcModeExitNotifyMessage {
type DrcStatusNotifyMessage (line 38) | interface DrcStatusNotifyMessage {
type GimbalResetMode (line 71) | enum GimbalResetMode {
FILE: src/types/enums.ts
type ERouterName (line 1) | enum ERouterName {
type EStorageKey (line 27) | enum EStorageKey {
type EStatusValue (line 33) | enum EStatusValue {
type ELiveStatusValue (line 39) | enum ELiveStatusValue {
type EComponentName (line 45) | enum EComponentName {
type ELocalStorageKey (line 56) | enum ELocalStorageKey {
type EPhotoType (line 69) | enum EPhotoType {
type EDownloadOwner (line 75) | enum EDownloadOwner {
type EUserType (line 81) | enum EUserType {
type EBizCode (line 86) | enum EBizCode {
type EDeviceTypeName (line 134) | enum EDeviceTypeName {
type EHmsLevel (line 140) | enum EHmsLevel {
FILE: src/types/flight-area.ts
type EFlightAreaType (line 3) | enum EFlightAreaType {
type EGeometryType (line 8) | enum EGeometryType {
type EFlightAreaUpdate (line 13) | enum EFlightAreaUpdate {
type ESyncStatus (line 19) | enum ESyncStatus {
type GeojsonCircle (line 27) | interface GeojsonCircle {
type DroneLocation (line 40) | interface DroneLocation {
type FlightAreasDroneLocation (line 46) | interface FlightAreasDroneLocation {
type FlightAreaContent (line 50) | type FlightAreaContent = GeojsonCircle | GeojsonPolygon
type FlightAreaUpdate (line 52) | interface FlightAreaUpdate {
type FlightAreaSyncProgress (line 64) | interface FlightAreaSyncProgress {
FILE: src/types/live-stream.ts
type LiveStreamStatus (line 2) | interface LiveStreamStatus {
type GB28181Param (line 14) | interface GB28181Param {
type RTSPParam (line 24) | interface RTSPParam {
type LiveConfigParam (line 30) | interface LiveConfigParam {
type EVideoPublishType (line 35) | enum EVideoPublishType {
type ELiveTypeValue (line 41) | enum ELiveTypeValue {
type ELiveTypeName (line 49) | enum ELiveTypeName {
type CameraMode (line 57) | enum CameraMode {
type VideoType (line 63) | enum VideoType {
type CameraType (line 71) | enum CameraType {
type VideoListItem (line 88) | interface VideoListItem {
type CameraListItem (line 94) | interface CameraListItem {
type DeviceListItem (line 107) | interface DeviceListItem {
FILE: src/types/map-enum.ts
type MapDoodleEnum (line 1) | enum MapDoodleEnum {
FILE: src/types/map.d.ts
type MapGeographicPosition (line 2) | interface MapGeographicPosition {
type LayerType (line 7) | enum LayerType {
type pinAMapPosition (line 12) | interface pinAMapPosition {
type ResourceStatus (line 19) | enum ResourceStatus {
type GeojsonCoordinate (line 23) | type GeojsonCoordinate = [number, number, number?]
type GeojsonLine (line 25) | interface GeojsonLine {
type GeojsonPolygon (line 37) | interface GeojsonPolygon {
type GeojsonPoint (line 48) | interface GeojsonPoint {
type GeojsonFeature (line 59) | type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint
type ResourceObjectBasic (line 61) | interface ResourceObjectBasic {
type PinResource (line 67) | interface PinResource extends ResourceObjectBasic {
type ResourceObject (line 72) | type ResourceObject = PinResource
type LayerElevationLoadStatus (line 73) | enum LayerElevationLoadStatus {
type LayerResource (line 78) | interface LayerResource {
type Layer (line 88) | interface Layer {
FILE: src/types/mapLayer.ts
type mapLayerStyle (line 3) | interface mapLayerStyle {
type mapLayerChildren (line 7) | interface mapLayerChildren {
type mapLayerChildrenObj (line 13) | interface mapLayerChildrenObj {
type DropEvent (line 21) | interface DropEvent {
type mapLayer (line 33) | interface mapLayer {
type elementGroupsReq (line 41) | interface elementGroupsReq{
type PostElementsBody (line 45) | interface PostElementsBody {
type Color (line 65) | interface Color {
type GeoType (line 72) | enum GeoType {
type ResourceStatus (line 77) | enum ResourceStatus {
type LayerElevationLoadStatus (line 82) | enum LayerElevationLoadStatus {
type PutElementsBody (line 86) | interface PutElementsBody {
type LayerType (line 93) | enum LayerType {
FILE: src/types/task.ts
type TaskType (line 4) | enum TaskType {
type OutOfControlAction (line 23) | enum OutOfControlAction {
type TaskStatus (line 42) | enum TaskStatus {
type TaskProgressStatus (line 71) | enum TaskProgressStatus {
type TaskProgressInfo (line 83) | interface TaskProgressInfo {
type MediaStatus (line 112) | enum MediaStatus { // 媒体上传进度
type MediaStatusProgressInfo (line 134) | interface MediaStatusProgressInfo {
type TaskMediaHighestPriorityProgressInfo (line 141) | interface TaskMediaHighestPriorityProgressInfo {
FILE: src/types/wayline.ts
type WaylineType (line 2) | enum WaylineType {
type WaylineFile (line 7) | interface WaylineFile {
FILE: src/use-common-components.ts
method install (line 8) | install (app: App): void {
FILE: src/utils/bytes.ts
function bytesToSize (line 10) | function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix =...
function getBytesObject (line 27) | function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fi...
function bytesToSizeWithMinUnit (line 63) | function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1):...
FILE: src/utils/common.ts
function downloadFile (line 6) | function downloadFile (data: Blob, fileName: string) {
FILE: src/utils/constants.ts
constant DEFAULT_PLACEHOLDER (line 2) | const DEFAULT_PLACEHOLDER = '--' // 默认占位符
constant DATE_FORMAT (line 5) | const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
constant DATE_FORMAT_MINUTE (line 6) | const DATE_FORMAT_MINUTE = 'YYYY-MM-DD HH:mm'
constant DATE_FORMAT_DAY (line 7) | const DATE_FORMAT_DAY = 'YYYY-MM-DD'
constant TIME_FORMAT (line 8) | const TIME_FORMAT = 'HH:mm:ss'
constant TIME_FORMAT_MINUTE (line 9) | const TIME_FORMAT_MINUTE = 'HH:mm'
constant DATE_FORMAT_MM (line 10) | const DATE_FORMAT_MM = 'MM-DD HH:mm'
constant SIZES (line 12) | const SIZES = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
constant BYTE_SIZES (line 13) | const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
constant PAGE_SIZE_OPTIONS (line 14) | const PAGE_SIZE_OPTIONS = ['20', '50', '100']
constant PAGE_SIZE (line 15) | const PAGE_SIZE = 50
FILE: src/utils/data-process.ts
function formatPhoneNum (line 1) | function formatPhoneNum (phoneNum: string | number) {
FILE: src/utils/device-cmd.ts
function updateDeviceCmdInfoByOsd (line 14) | function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo:...
function getDroneState (line 52) | function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) {
function getCoverState (line 69) | function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {
function getPutterState (line 88) | function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {
function getChargeState (line 106) | function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {
function deviceFormat (line 126) | function deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) {
function droneFormat (line 133) | function droneFormat (cmdItem: DeviceCmdItem, droneProperties: any) {
function getAirportStorage (line 144) | function getAirportStorage (storage: AirportStorage) {
function getBytes (line 156) | function getBytes (bytes: number, index: number, fixed = 1) {
function getSupplementLightState (line 161) | function getSupplementLightState (cmdItem: DeviceCmdItem, airportPropert...
function getAlarmState (line 179) | function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {
function getBatteryStoreMode (line 193) | function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties:...
function getDroneBatteryMode (line 207) | function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties:...
function getSdrWorkNode (line 228) | function getSdrWorkNode (cmdItem: DeviceCmdItem, airportProperties: any) {
function exchangeDeviceCmd (line 245) | function exchangeDeviceCmd (cmdItem: DeviceCmdItem) {
function updateDeviceCmdInfoByExecuteInfo (line 276) | function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], dev...
function isExecuteFailed (line 521) | function isExecuteFailed (status: DeviceCmdExecuteStatus) {
FILE: src/utils/device-setting.ts
function updateDeviceSettingInfoByOsd (line 14) | function updateDeviceSettingInfoByOsd (deviceSetting: DeviceSettingType,...
function updateDeviceSettingFormModelByOsd (line 138) | function updateDeviceSettingFormModelByOsd (deviceSettingFormModelFromOs...
FILE: src/utils/download.ts
function urlToImage (line 6) | function urlToImage (url: string) {
type CompressImageData (line 15) | interface CompressImageData {
function compressImage (line 19) | function compressImage (imgToCompress: HTMLImageElement, targetWidth: nu...
function download (line 71) | function download (url: string, fileName = ''): void {
FILE: src/utils/error-code/index.ts
type ErrorCode (line 1) | interface ErrorCode {
function getErrorMessage (line 12) | function getErrorMessage (code: number, errorMsg?: string): string {
constant ERROR_CODE (line 18) | const ERROR_CODE = [
FILE: src/utils/genjson.ts
type GeojsonCoordinate (line 5) | type GeojsonCoordinate = [number, number, number?]
type GeojsonLine (line 7) | interface GeojsonLine {
type GeojsonPolygon (line 19) | interface GeojsonPolygon {
type GeojsonPoint (line 30) | interface GeojsonPoint {
type GeojsonCircle (line 42) | interface GeojsonCircle {
type GeojsonFeature (line 55) | type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint | Geoj...
function geographic2Coordinate (line 57) | function geographic2Coordinate (position: MapGeographicPosition): Geojso...
function generateLine (line 63) | function generateLine (coordinates: MapGeographicPosition[], properties:...
function generatePolygon (line 74) | function generatePolygon (coordinates: MapGeographicPosition[], properti...
function generatePoint (line 85) | function generatePoint (position: MapGeographicPosition, properties: Geo...
function generateCircle (line 96) | function generateCircle (position: MapGeographicPosition, properties: Ge...
FILE: src/utils/layer-tree.ts
type LayerTreeType (line 2) | type LayerTreeType = (typeof layerTreeTypes)[number]
function getLayerTreeKey (line 5) | function getLayerTreeKey (type: LayerTreeType, id: number | string) {
function isLayerTreeKey (line 9) | function isLayerTreeKey (key: string, type?: LayerTreeType) {
function getIdFromLayerTreeKey (line 17) | function getIdFromLayerTreeKey (key: string) {
FILE: src/utils/logger.ts
function consoleLog (line 6) | function consoleLog (...args: Parameters<typeof console.log>) {
function consoleWarn (line 12) | function consoleWarn (...args: Parameters<typeof console.warn>) {
function consoleError (line 18) | function consoleError (...args: Parameters<typeof console.error>) {
function testEnvLog (line 24) | function testEnvLog (...args: Parameters<typeof console.log>) {
FILE: src/utils/map-layer-utils.ts
function getPinPosition (line 4) | function getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographic...
function generatePointContent (line 8) | function generatePointContent (pinAMapPosition: pinAMapPosition) {
function getLieOrPolyPosition (line 18) | function getLieOrPolyPosition (mapPosition: pinAMapPosition[]):MapGeogra...
function generateLineContent (line 25) | function generateLineContent (mapPosition: pinAMapPosition[]) {
function generatePolyContent (line 36) | function generatePolyContent (mapPosition: pinAMapPosition[]) {
function generateCircleContent (line 46) | function generateCircleContent (pinAMapPosition: pinAMapPosition, radius...
FILE: src/utils/storage.ts
function getStorageData (line 6) | function getStorageData (key: EStorageKey, parse?: boolean): any {
function clearStorageData (line 21) | function clearStorageData (key: EStorageKey | EStorageKey[]) {
method save (line 34) | save (key: EStorageKey, value: string) {
FILE: src/utils/time.ts
function formatDateTime (line 8) | function formatDateTime (time: string | number, format = DATE_FORMAT) {
function formatUnixTime (line 13) | function formatUnixTime (time: number, format = DATE_FORMAT): string {
FILE: src/utils/uuid.ts
function uuidv4 (line 1) | function uuidv4 () {
FILE: src/vendors/coordtransform.js
function wgs84togcj02 (line 16) | function wgs84togcj02(lng, lat) {
function gcj02towgs84 (line 41) | function gcj02towgs84(lng, lat) {
function transformlat (line 61) | function transformlat(lng, lat) {
function transformlng (line 69) | function transformlng(lng, lat) {
function out_of_china (line 83) | function out_of_china(lng, lat) {
FILE: src/vendors/srs.sdk.js
function SrsError (line 10) | function SrsError(name, message) {
function SrsRtcPublisherAsync (line 20) | function SrsRtcPublisherAsync() {
function SrsRtcPlayerAsync (line 275) | function SrsRtcPlayerAsync() {
function SrsRtcWhipWhepAsync (line 515) | function SrsRtcWhipWhepAsync() {
function SrsRtcFormatSenders (line 653) | function SrsRtcFormatSenders(senders, kind) {
FILE: src/websocket/index.ts
type WebSocketOptions (line 5) | interface WebSocketOptions {
type MessageHandler (line 11) | interface MessageHandler {
type CommonHostWs (line 15) | interface CommonHostWs<T> {
class ConnectWebSocket (line 24) | class ConnectWebSocket {
method constructor (line 30) | constructor (url: string) {
method initSocket (line 37) | initSocket () {
method _onOpen (line 60) | _onOpen () {
method _onClose (line 64) | _onClose () {
method _onError (line 68) | _onError () {
method registerMessageHandler (line 72) | registerMessageHandler (messageHandler: MessageHandler) {
method _onMessage (line 76) | _onMessage (msg: MessageEvent) {
method close (line 86) | close () {
FILE: src/websocket/util/config.ts
function getWebsocketUrl (line 4) | function getWebsocketUrl () {
Condensed preview — 166 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (664K chars).
[
{
"path": ".eslintignore",
"chars": 16,
"preview": "/src/vendors/**\n"
},
{
"path": ".eslintrc.js",
"chars": 484,
"preview": "module.exports = {\n env: {\n browser: true,\n commonjs: true,\n es2021: true,\n node: true\n },\n extends: ['st"
},
{
"path": ".gitignore",
"chars": 244,
"preview": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\nnode_modules/\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n"
},
{
"path": ".gitignore copy",
"chars": 45,
"preview": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n"
},
{
"path": ".npmrc",
"chars": 40,
"preview": "registry=https://registry.npmmirror.com/"
},
{
"path": "LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2022 DJI-SDK\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "README.md",
"chars": 1523,
"preview": "# 关于DJI Cloud API Demo 终止维护公告\n\n发布日期:2025年4月10日\n\n1.项目终止维护说明\n即日起,大疆创新(DJI)将停止对DJI Cloud API Demo (地址:https://github.com/dj"
},
{
"path": "index.html",
"chars": 342,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" href=\"/favicon.ico\" />\n <"
},
{
"path": "package.json",
"chars": 3829,
"preview": "{\n \"name\": \"demo-web\",\n \"version\": \"0.0.1\",\n \"scripts\": {\n \"serve\": \"vite\",\n \"build:test\": \"vite build --mode s"
},
{
"path": "src/App.vue",
"chars": 654,
"preview": "<template>\n <div class=\"demo-app\">\n <router-view />\n <!-- <div class=\"map-wrapper\">\n <GMap/>\n </div> -->\n"
},
{
"path": "src/antd.ts",
"chars": 364,
"preview": "// import Icon from '@ant-design/icons-vue'\nimport * as antDesign from 'ant-design-vue'\nimport 'ant-design-vue/dist/antd"
},
{
"path": "src/api/device-cmd/index.ts",
"chars": 701,
"preview": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { DeviceCmd, DeviceCmdItemAction } from '/@/typ"
},
{
"path": "src/api/device-log/index.ts",
"chars": 4561,
"preview": "import request, { IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'\nimport { DeviceValue, DOMAIN }"
},
{
"path": "src/api/device-setting/index.ts",
"chars": 946,
"preview": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { ELocalStorageKey } from '/@/types'\nimport { N"
},
{
"path": "src/api/device-upgrade/index.ts",
"chars": 1237,
"preview": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { DeviceFirmwareTypeEnum } from '/@/types/devic"
},
{
"path": "src/api/drc.ts",
"chars": 1607,
"preview": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { ELocalStorageKey } from '/@/types'\n\n// DRC 链路"
},
{
"path": "src/api/drone-control/drone.ts",
"chars": 2245,
"preview": "import request, { IWorkspaceResponse } from '/@/api/http/request'\n// import { ELocalStorageKey } from '/@/types'\n\nconst "
},
{
"path": "src/api/drone-control/payload.ts",
"chars": 2510,
"preview": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { CameraType, CameraMode } from '/@/types/live-"
},
{
"path": "src/api/flight-area/index.ts",
"chars": 2544,
"preview": "import request from '../http/request'\nimport { IWorkspaceResponse } from '../http/type'\nimport { EFlightAreaType, ESyncS"
},
{
"path": "src/api/http/config.ts",
"chars": 1745,
"preview": "export const CURRENT_CONFIG = {\n\n // license\n appId: 'Please enter the app id.', // You need to go to the development "
},
{
"path": "src/api/http/request.ts",
"chars": 2473,
"preview": "import axios from 'axios'\nimport { uuidv4 } from '/@/utils/uuid'\nimport { CURRENT_CONFIG } from './config'\nimport { mess"
},
{
"path": "src/api/http/type.ts",
"chars": 611,
"preview": "export interface IResult {\n code: number;\n message: string;\n}\n\nexport interface IPage {\n page: number;\n total: number;\n "
},
{
"path": "src/api/http.ts",
"chars": 1325,
"preview": "/**\n * 职责声明:\n * 1.提供一个 单一的 axios 实例(方面进行统一拦截)\n * 2.允许调用方定制自己的配置(例如拦截器等),而不影响其他实例\n *\n * 暴露 API:\n * 1.一个统一的 axios 实例: sing"
},
{
"path": "src/api/layer.ts",
"chars": 2286,
"preview": "import { ELocalStorageKey } from '../types/enums'\nimport request, { IWorkspaceResponse } from '/@/api/http/request'\nimpo"
},
{
"path": "src/api/manage.ts",
"chars": 7084,
"preview": "import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'\nimport request, { CommonLis"
},
{
"path": "src/api/media.ts",
"chars": 1043,
"preview": "import { message } from 'ant-design-vue'\nimport request, { IPage, IWorkspaceResponse } from '/@/api/http/request'\nconst "
},
{
"path": "src/api/pilot-bridge.ts",
"chars": 8296,
"preview": "import { message } from 'ant-design-vue'\nimport { EComponentName, EPhotoType, ERouterName } from '../types'\nimport { CUR"
},
{
"path": "src/api/wayline.ts",
"chars": 4695,
"preview": "import { message } from 'ant-design-vue'\nimport request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/"
},
{
"path": "src/components/GMap.vue",
"chars": 42451,
"preview": "<template>\n <div class=\"g-map-wrapper\">\n <!-- 地图区域 -->\n <div id=\"g-container\" :style=\"{ width: '100%', height: '1"
},
{
"path": "src/components/LayersTree.vue",
"chars": 5364,
"preview": "<template>\n <span>\n <a-tree\n draggable\n :defaultExpandAll=\"true\"\n class=\"device-map-layers\"\n @dr"
},
{
"path": "src/components/MediaPanel.vue",
"chars": 3874,
"preview": "<template>\n <div class=\"header\">Media Files</div>\n <a-spin :spinning=\"loading\" :delay=\"1000\" tip=\"downloading\" size=\"l"
},
{
"path": "src/components/common/sidebar.vue",
"chars": 3120,
"preview": "<template>\n <div class=\"demo-project-sidebar-wrapper flex-justify-between\">\n <div>\n <router-link\n v-for=\"ite"
},
{
"path": "src/components/common/topbar.vue",
"chars": 3082,
"preview": "<template>\n <div class=\"width-100 flex-row flex-justify-between flex-align-center\" style=\"height: 60px;\">\n <div clas"
},
{
"path": "src/components/devices/DeviceFirmwareStatus.vue",
"chars": 1615,
"preview": "<template>\n<div>\n <span class=\"status-tag pointer\">\n <a-popconfirm\n :title=\"getTitle()\"\n ok-text=\"Yes\"\n "
},
{
"path": "src/components/devices/device-hms/DeviceHmsDrawer.vue",
"chars": 8002,
"preview": "<template>\n <a-drawer\n title=\"Hms Info\"\n placement=\"right\"\n v-model:visible=\"sVisible\"\n @update:visible=\"on"
},
{
"path": "src/components/devices/device-log/DeviceLogDetailModal.vue",
"chars": 4563,
"preview": "<template>\n <a-modal\n title=\"日志上传详情\"\n v-model:visible=\"sVisible\"\n width=\"900px\"\n :footer=\"null\"\n @update"
},
{
"path": "src/components/devices/device-log/DeviceLogUploadModal.vue",
"chars": 6618,
"preview": "<template>\n <a-modal\n title=\"设备日志上传\"\n v-model:visible=\"sVisible\"\n width=\"900px\"\n :footer=\"null\"\n @update"
},
{
"path": "src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue",
"chars": 10208,
"preview": "<template>\n <a-drawer\n title=\"设备日志上传记录\"\n placement=\"right\"\n v-model:visible=\"sVisible\"\n @update:visible=\"on"
},
{
"path": "src/components/devices/device-log/use-device-log-upload-detail.ts",
"chars": 625,
"preview": "import { DeviceLogItem } from '/@/api/device-log'\nimport { bytesToSize } from '/@/utils/bytes'\nimport { formatUnixTime }"
},
{
"path": "src/components/devices/device-log/use-device-log-upload-progress-event.ts",
"chars": 667,
"preview": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { DeviceLogUploadInfo } fro"
},
{
"path": "src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue",
"chars": 1681,
"preview": "<template>\n<div class=\"firmware_upgrade_wrap\">\n <!-- 版本 -->\n <span class=\"version\"> {{ device.firmware_version }}</spa"
},
{
"path": "src/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue",
"chars": 2436,
"preview": "<template>\n<a-modal :visible=\"sVisible\"\n :title=\"title\"\n :closable=\"false\"\n centered\n @u"
},
{
"path": "src/components/devices/device-upgrade/use-device-upgrade-event.ts",
"chars": 632,
"preview": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { DeviceCmdExecuteInfo, Dev"
},
{
"path": "src/components/devices/device-upgrade/use-device-upgrade.ts",
"chars": 1162,
"preview": "import { Ref, ref } from 'vue'\nimport { Device } from '/@/types/device'\nimport { postDeviceUpgrade, DeviceUpgradeBody } "
},
{
"path": "src/components/flight-area/FlightAreaActionIcon.vue",
"chars": 1527,
"preview": "<template>\n <div @click=\"selectCurrent\">\n <a-dropdown class=\"height-100 width-100 icon-panel\">\n <FlightAreaIc"
},
{
"path": "src/components/flight-area/FlightAreaDevicePanel.vue",
"chars": 6664,
"preview": "<template>\n <div class=\"flight-area-device-panel\">\n <Title title=\"Choose Synchronous Devices\">\n <div style=\"pos"
},
{
"path": "src/components/flight-area/FlightAreaIcon.vue",
"chars": 783,
"preview": "<template>\n <div class=\"flex-row flex-align-center\">\n <div class=\"shape\" :class=\"type\" :style=\"isCircle ? 'border-ra"
},
{
"path": "src/components/flight-area/FlightAreaItem.vue",
"chars": 2905,
"preview": "<template>\n <div class=\"panel\" style=\"padding-top: 5px;\" :class=\"{disable: !flightArea.status}\">\n <div class=\"title\""
},
{
"path": "src/components/flight-area/FlightAreaPanel.vue",
"chars": 1102,
"preview": "<template>\n <div class=\"flight-area-panel\">\n <div v-if=\"data.length === 0\">\n <a-empty :image-style=\"{ height: '"
},
{
"path": "src/components/flight-area/FlightAreaSyncPanel.vue",
"chars": 2198,
"preview": "<template>\n <div class=\"flight-area-sync-panel p10 flex-row flex-align-center\" >\n <RobotFilled class=\"fz30\" twoToneC"
},
{
"path": "src/components/flight-area/use-flight-area-drone-location-event.ts",
"chars": 639,
"preview": "import { FlightAreasDroneLocation } from '/@/types/flight-area'\nimport { CommonHostWs } from '/@/websocket'\nimport Event"
},
{
"path": "src/components/flight-area/use-flight-area-sync-progress-event.ts",
"chars": 583,
"preview": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { FlightAreaSyncProgress } "
},
{
"path": "src/components/flight-area/use-flight-area-update.ts",
"chars": 915,
"preview": "import { EFlightAreaUpdate, FlightAreaUpdate, FlightAreasDroneLocation } from '/@/types/flight-area'\nimport { CommonHost"
},
{
"path": "src/components/flight-area/use-flight-area.ts",
"chars": 5062,
"preview": "import { message, notification } from 'ant-design-vue'\nimport { MapDoodleEnum } from '/@/types/map-enum'\nimport { getRoo"
},
{
"path": "src/components/g-map/DeviceSettingBox.vue",
"chars": 11480,
"preview": "<template>\n <div class=\"device-setting-wrapper\">\n <div class=\"device-setting-header\">Device Property Set</div>\n <"
},
{
"path": "src/components/g-map/DeviceSettingPopover.vue",
"chars": 2115,
"preview": "<template>\n <a-popover :visible=\"state.sVisible\"\n trigger=\"click\"\n v-bind=\"$attrs\"\n "
},
{
"path": "src/components/g-map/DockControlPanel.vue",
"chars": 4568,
"preview": "<template>\n<div class=\"dock-control-panel\">\n <!-- title -->\n <div class=\"dock-control-panel-header fz16 pl5 pr5 flex-a"
},
{
"path": "src/components/g-map/DroneControlInfoPanel.vue",
"chars": 646,
"preview": "<template>\n<div class=\"drone-control-info-wrap\">\n <a-textarea v-model:value=\"info\" placeholder=\"drc info\" :rows=\"5\" dis"
},
{
"path": "src/components/g-map/DroneControlPanel.vue",
"chars": 28936,
"preview": "<template>\n <div class=\"drone-control-wrapper\">\n <div class=\"drone-control-header\">Drone Flight Control</div>\n <d"
},
{
"path": "src/components/g-map/DroneControlPopover.vue",
"chars": 2232,
"preview": "<template>\n <a-popover :visible=\"state.sVisible\"\n trigger=\"click\"\n v-bind=\"$attrs\"\n "
},
{
"path": "src/components/g-map/use-connect-mqtt.ts",
"chars": 1708,
"preview": "\nimport {\n ref,\n watch,\n computed,\n onUnmounted,\n} from 'vue'\nimport { useMyStore } from '/@/store'\nimport { postDrc"
},
{
"path": "src/components/g-map/use-device-setting.ts",
"chars": 2341,
"preview": "import { message } from 'ant-design-vue'\nimport { putDeviceProps, PutDevicePropsBody } from '/@/api/device-setting'\nimpo"
},
{
"path": "src/components/g-map/use-dock-control.ts",
"chars": 1551,
"preview": "import { message } from 'ant-design-vue'\nimport { ref } from 'vue'\nimport { postSendCmd } from '/@/api/device-cmd'\nimpor"
},
{
"path": "src/components/g-map/use-drone-control-mqtt-event.ts",
"chars": 1980,
"preview": "import { ref, onMounted, onBeforeUnmount } from 'vue'\nimport EventBus from '/@/event-bus/'\nimport {\n DRC_METHOD,\n DRCH"
},
{
"path": "src/components/g-map/use-drone-control-ws-event.ts",
"chars": 3366,
"preview": "import { message, notification } from 'ant-design-vue'\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport Even"
},
{
"path": "src/components/g-map/use-drone-control.ts",
"chars": 1103,
"preview": "import { ref } from 'vue'\nimport { postFlyToPoint, PostFlyToPointBody, deleteFlyToPoint, postTakeoffToPoint, PostTakeoff"
},
{
"path": "src/components/g-map/use-manual-control.ts",
"chars": 4093,
"preview": "import {\n ref,\n onUnmounted,\n watch,\n Ref,\n} from 'vue'\nimport { message } from 'ant-design-vue'\nimport {\n DRC_METH"
},
{
"path": "src/components/g-map/use-mqtt.ts",
"chars": 3368,
"preview": "import {\n ref,\n reactive,\n computed,\n watch,\n onUnmounted,\n} from 'vue'\nimport {\n IClientPublishOptions,\n IPublis"
},
{
"path": "src/components/g-map/use-payload-control.ts",
"chars": 3134,
"preview": "import { message } from 'ant-design-vue'\nimport {\n postPayloadAuth,\n postPayloadCommands,\n PayloadCommandsEnum,\n Pos"
},
{
"path": "src/components/livestream-agora.vue",
"chars": 11562,
"preview": "<template>\n <div class=\"flex-column flex-justify-start flex-align-center\">\n <div id=\"player\" style=\"width: 720px; he"
},
{
"path": "src/components/livestream-others.vue",
"chars": 11957,
"preview": "<template>\n <div class=\"flex-column flex-justify-start flex-align-center\">\n <video\n :style=\"{ width: '720px', h"
},
{
"path": "src/components/svgIcon.vue",
"chars": 891,
"preview": "<template>\n <svg :class=\"svgClass\" :aria-hidden=\"true\" :style=\"{color: color, width:computedWidth, height:computedWidth"
},
{
"path": "src/components/task/CreatePlan.vue",
"chars": 15716,
"preview": "<template>\n <div class=\"create-plan-wrapper\">\n <div class=\"header\">\n Create Plan\n </div>\n <div class=\"con"
},
{
"path": "src/components/task/TaskPanel.vue",
"chars": 10347,
"preview": "<template>\n <div class=\"header\">Task Plan Library</div>\n <div class=\"plan-panel-wrapper\">\n <a-table class=\"plan-tab"
},
{
"path": "src/components/task/use-format-task.ts",
"chars": 2373,
"preview": "import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'\nimport { Task } from '/@/api/wayline'\nimport { TaskStatusColor,"
},
{
"path": "src/components/task/use-task-ws-event.ts",
"chars": 1306,
"preview": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { TaskProgressInfo, MediaSt"
},
{
"path": "src/components/workspace/DividerLine.vue",
"chars": 242,
"preview": "<template>\n <Divider class=\"divider\" />\n</template>\n<script lang=\"ts\" setup>\nimport { Divider } from 'ant-design-vue'\n\n"
},
{
"path": "src/components/workspace/Title.vue",
"chars": 494,
"preview": "<template>\n <div style=\"height: 40px; line-height: 50px; font-weight: 450;\">\n <a-row>\n <a-col :span=\"1\"></a-col"
},
{
"path": "src/constants/index.ts",
"chars": 469,
"preview": "import { CURRENT_CONFIG } from '/@/api/http/config'\n\nexport const AMapConfig = {\n key: CURRENT_CONFIG.amapKey,\n versio"
},
{
"path": "src/constants/map.ts",
"chars": 491,
"preview": "\nexport enum MapElementColor {\n Blue = '#2D8CF0',\n Green = '#19BE6B',\n Yellow = '#FFBB00',\n Red = '#E23C39',"
},
{
"path": "src/constants/mock-layers.ts",
"chars": 3510,
"preview": "export const mapLayers = {\n code: 0,\n message: 'success',\n data: {\n list: [{\n id: 'private_layer',\n name"
},
{
"path": "src/directives/drag-window.ts",
"chars": 1208,
"preview": "import { nextTick, App } from 'vue'\n\nexport default function useDragWindowDirective (app: App): void {\n app.directive('"
},
{
"path": "src/directives/index.ts",
"chars": 159,
"preview": "import { App } from 'vue'\nimport useDragWindowDirective from './drag-window'\n\nexport function useDirectives (app: App): "
},
{
"path": "src/env.d.ts",
"chars": 275,
"preview": "// Environment variable definition\n// https://cn.vitejs.dev/guide/env-and-mode.html#env-files\n\ninterface ImportMetaEnv {"
},
{
"path": "src/event-bus/index.ts",
"chars": 397,
"preview": "import mitt, { Emitter } from 'mitt'\n\ntype Events = {\n deviceUpgrade: any; // 设备升级\n deviceLogUploadProgress: any // 设备"
},
{
"path": "src/hooks/use-connect-websocket.ts",
"chars": 570,
"preview": "import { onMounted, onUnmounted } from 'vue'\nimport ReconnectingWebSocket from 'reconnecting-websocket'\nimport ConnectWe"
},
{
"path": "src/hooks/use-g-map-cover.ts",
"chars": 9704,
"preview": "import { EFlightAreaType } from '../types/flight-area'\nimport pin19be6b from '/@/assets/icons/pin-19be6b.svg'\nimport pin"
},
{
"path": "src/hooks/use-g-map-tsa.ts",
"chars": 2765,
"preview": "import store from '/@/store'\nimport { getRoot } from '/@/root'\nimport { ELocalStorageKey, EDeviceTypeName } from '/@/typ"
},
{
"path": "src/hooks/use-g-map.ts",
"chars": 951,
"preview": "import AMapLoader from '@amap/amap-jsapi-loader'\nimport { App, reactive } from 'vue'\nimport { AMapConfig } from '/@/cons"
},
{
"path": "src/hooks/use-map-tool.ts",
"chars": 342,
"preview": "import { GeojsonCoordinate } from '../utils/genjson'\nimport { getRoot } from '/@/root'\n\nexport function useMapTool () {\n"
},
{
"path": "src/hooks/use-mouse-tool.ts",
"chars": 3644,
"preview": "import { reactive } from 'vue'\nimport pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'\nimport { MapDoodleType } from '/@/"
},
{
"path": "src/main.ts",
"chars": 520,
"preview": "import App from './App.vue'\nimport router from './router'\nimport { antComponents } from './antd'\nimport { CommonComponen"
},
{
"path": "src/mqtt/config.ts",
"chars": 273,
"preview": "\nimport {\n IClientOptions,\n} from 'mqtt'\n\nexport const OPTIONS: IClientOptions = {\n clean: true, // true: 清除会话, false:"
},
{
"path": "src/mqtt/index.ts",
"chars": 2588,
"preview": "import EventEmitter from 'eventemitter3'\nimport {\n OPTIONS,\n} from './config'\nimport {\n connect,\n MqttClient,\n IClie"
},
{
"path": "src/pages/page-pilot/pilot-bind.vue",
"chars": 1855,
"preview": "<template>\n <a-layout class=\"flex-display\" style=\"height: 100vh; background-color: white;\">\n <div class=\"height100 wid"
},
{
"path": "src/pages/page-pilot/pilot-home.vue",
"chars": 18247,
"preview": "<template>\n <a-layout class=\"page\">\n <a-layout-sider class=\"left\" width=\"40%\" style=\"border-radius: 4px;\">\n <di"
},
{
"path": "src/pages/page-pilot/pilot-index.vue",
"chars": 4654,
"preview": "<template>\n <div class=\"login flex-column flex-justify-center flex-align-center m0 b0\">\n <a-image\n style=\"width"
},
{
"path": "src/pages/page-pilot/pilot-liveshare.vue",
"chars": 11384,
"preview": "<template>\n <div class=\"width100 flex-column flex-justify-start flex-align-start\" style=\"background-color: white;\">\n\n"
},
{
"path": "src/pages/page-pilot/pilot-media.vue",
"chars": 3281,
"preview": "<template>\n <a-layout>\n <div class=\"width100 flex-column flex-justify-start flex-align-start\" style=\"background-color:"
},
{
"path": "src/pages/page-web/home.vue",
"chars": 1458,
"preview": "<template>\n <a-layout class=\"width-100 flex-display\" style=\"height: 100vh\">\n <a-layout-header class=\"header\">\n "
},
{
"path": "src/pages/page-web/index.vue",
"chars": 2610,
"preview": "<template>\n <div\n class=\"login flex-column flex-justify-center flex-align-center m0 b0\">\n <a-image\n style=\"w"
},
{
"path": "src/pages/page-web/projects/Firmwares.vue",
"chars": 10300,
"preview": "\n<template>\n <div class=\"ml20 mt20 mr20 flex-row flex-align-center flex-justify-between\">\n <div class=\"flex-row\">\n "
},
{
"path": "src/pages/page-web/projects/devices.vue",
"chars": 14115,
"preview": "\n<template>\n <a-menu v-model:selectedKeys=\"current\" mode=\"horizontal\" @select=\"select\">\n <a-menu-item :key=\"EDeviceT"
},
{
"path": "src/pages/page-web/projects/dock.vue",
"chars": 4027,
"preview": "<template>\n <div class=\"height-100\">\n <div style=\"height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f;"
},
{
"path": "src/pages/page-web/projects/flight-area.vue",
"chars": 3390,
"preview": "<template>\n <div class=\"project-flight-area-wrapper height-100\">\n <a-spin :spinning=\"loading\" :delay=\"300\" tip=\"load"
},
{
"path": "src/pages/page-web/projects/layer.vue",
"chars": 13540,
"preview": "<template>\n <div class=\"project-layer-wrapper height-100\">\n <div style=\"height: 50px; line-height: 50px; border-bott"
},
{
"path": "src/pages/page-web/projects/livestream.vue",
"chars": 2192,
"preview": "<template>\n <div class=\"flex-column flex-justify-start flex-align-center\">\n <router-link\n style=\"width: 90%; ma"
},
{
"path": "src/pages/page-web/projects/media.vue",
"chars": 143,
"preview": "<template>\n <div class=\"project-media-wrapper\">\n </div>\n</template>\n\n<script lang=\"ts\" setup>\n</script>\n\n<style lang=\""
},
{
"path": "src/pages/page-web/projects/members.vue",
"chars": 5349,
"preview": "\n<template>\n <div class=\"table flex-display flex-column\">\n <a-table :columns=\"columns\" :data-source=\"data.member\" :p"
},
{
"path": "src/pages/page-web/projects/task.vue",
"chars": 1185,
"preview": "<template>\n <div>\n <div style=\"height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;\""
},
{
"path": "src/pages/page-web/projects/tsa.vue",
"chars": 21220,
"preview": "<template>\n <div class=\"project-tsa-wrapper \">\n <div>\n <a-row>\n <a-col :span=\"1\"></a-col>\n <a-col"
},
{
"path": "src/pages/page-web/projects/wayline.vue",
"chars": 8741,
"preview": "<template>\n <div class=\"project-wayline-wrapper height-100\">\n <a-spin :spinning=\"loading\" :delay=\"300\" tip=\"download"
},
{
"path": "src/pages/page-web/projects/workspace.vue",
"chars": 4237,
"preview": "<template>\n <div class=\"project-app-wrapper\">\n <div class=\"left\">\n <Sidebar />\n <div class=\"main-content u"
},
{
"path": "src/plugins/svgBuilder.ts",
"chars": 1602,
"preview": "import { readFileSync, readdirSync } from 'fs'\n\nlet idPerfix = ''\nconst svgTitle = /<svg([^>+].*?)>/\nconst clearHeightWi"
},
{
"path": "src/root.ts",
"chars": 562,
"preview": "import { createApp, ComponentCustomProperties, App as VueApp } from 'vue'\ndeclare module '@vue/runtime-core' {\n interfa"
},
{
"path": "src/router/index.ts",
"chars": 4125,
"preview": "import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'\nimport { ERouterName } from '/@/types/index'"
},
{
"path": "src/shims-mqtt.d.ts",
"chars": 82,
"preview": "declare module 'mqtt/dist/mqtt.min' {\n import MQTT from 'mqtt'\n export = MQTT\n}\n"
},
{
"path": "src/shims-vue.d.ts",
"chars": 142,
"preview": "declare module '*.vue' {\n import { DefineComponent } from 'vue'\n const component: DefineComponent<{}, {}, any>\n expor"
},
{
"path": "src/store/index.ts",
"chars": 7103,
"preview": "import { InjectionKey } from 'vue'\nimport { ActionTree, createStore, GetterTree, MutationTree, Store, StoreOptions, useS"
},
{
"path": "src/styles/common.scss",
"chars": 642,
"preview": "\nhtml, body, #app, #my-app {\n height: 100%;\n overflow: hidden;\n}\n\nbody {\n background-color: #f7f9fa;\n -webkit-font-s"
},
{
"path": "src/styles/flex.style.scss",
"chars": 4096,
"preview": ".flex-display {\n display: flex;\n}\n\n.flex-column {\n @extend .flex-display;\n flex-direction: column;\n}\n\n.flex-row {\n @"
},
{
"path": "src/styles/fonts.scss",
"chars": 947,
"preview": "$font-family-sans-serif: 'Open Sans', BlinkMacSystemFont, 'Segoe UI', Roboto,\n 'Helvetica Neue', Helvetica, 'PingFang S"
},
{
"path": "src/styles/index.scss",
"chars": 74,
"preview": "@import './common.scss';\n@import 'flex.style.scss';\n@import 'fonts.scss';\n"
},
{
"path": "src/styles/reset.scss",
"chars": 1498,
"preview": "html,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\na,\nabbr,\nacronym,\naddress,\nbi"
},
{
"path": "src/styles/variables.scss",
"chars": 1955,
"preview": "$main-text-color: #000;\n$header-height: 52px;\n// Auxiliary color\n$info: #1fa3f6;\n$success: #28d445;\n$danger: #e70102;\n$a"
},
{
"path": "src/types/airport-tsa.ts",
"chars": 1333,
"preview": "// 机场存储容量:总容量(单位:KB)、已使用(单位:KB)\nexport interface AirportStorage {\n total: number, // 单位:KB\n used: number\n}\n\n// 舱盖状态\nex"
},
{
"path": "src/types/device-cmd.ts",
"chars": 8182,
"preview": "import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum, LinkWorkModeEnum } from '/@/types/airport-tsa'\n// 机场"
},
{
"path": "src/types/device-firmware.ts",
"chars": 740,
"preview": "export interface Firmware {\n firmware_id: string\n file_name: string\n product_version: string\n file_size: number\n de"
},
{
"path": "src/types/device-log.ts",
"chars": 1990,
"preview": "import { DOMAIN } from '/@/types/device'\nimport { commonColor } from '/@/utils/color'\n\n// 日志上传状态\nexport enum DeviceLogUp"
},
{
"path": "src/types/device-setting.ts",
"chars": 3997,
"preview": "// 夜航灯开关\nexport enum NightLightsStateEnum {\n CLOSE = 0, // 0-关闭\n OPEN = 1, // 1-打开\n}\n\n// 限远开关\nexport enum DistanceLimi"
},
{
"path": "src/types/device.ts",
"chars": 12126,
"preview": "import { commonColor } from '/@/utils/color'\nimport { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } fro"
},
{
"path": "src/types/drc.ts",
"chars": 2479,
"preview": "export enum DRC_METHOD {\n HEART_BEAT = 'heart_beat',\n DRONE_CONTROL = 'drone_control', // 飞行控制-虚拟摇杆\n DRONE_EMERGENCY_"
},
{
"path": "src/types/drone-control.ts",
"chars": 2324,
"preview": "import { ControlSource } from './device'\nimport { ECommanderModeLostAction, ERthMode, LostControlActionInCommandFLight, "
},
{
"path": "src/types/enums.ts",
"chars": 3629,
"preview": "export enum ERouterName {\n ELEMENT = 'element',\n PROJECT = 'project',\n HOME = 'home',\n TSA = 'tsa',\n LAYE"
},
{
"path": "src/types/flight-area.ts",
"chars": 1626,
"preview": "import { GeojsonCoordinate, GeojsonPolygon } from '../utils/genjson'\n\nexport enum EFlightAreaType {\n NFZ = 'nfz',\n DFE"
},
{
"path": "src/types/index.ts",
"chars": 24,
"preview": "export * from './enums'\n"
},
{
"path": "src/types/live-stream.ts",
"chars": 2803,
"preview": "\nexport interface LiveStreamStatus {\n audioBitRate: number,\n dropRate: number,\n fps: number,\n jitter: number"
},
{
"path": "src/types/map-enum.ts",
"chars": 141,
"preview": "export enum MapDoodleEnum {\n PIN = 'pin',\n POLYLINE = 'polyline',\n POLYGON = 'polygon',\n Close = 'off',\n "
},
{
"path": "src/types/map.d.ts",
"chars": 1718,
"preview": "\nexport interface MapGeographicPosition {\n longitude: number;\n latitude: number;\n height?: number;\n}\nexport enum LayerTy"
},
{
"path": "src/types/mapLayer.ts",
"chars": 1559,
"preview": "import { MapElementEnum } from '/@/constants/map'\n\nexport interface mapLayerStyle {\n background: string\n}\n\nexport inter"
},
{
"path": "src/types/task.ts",
"chars": 3695,
"preview": "import { commonColor } from '/@/utils/color'\n\n// 任务类型\nexport enum TaskType {\n Immediate = 0, // 立即执行\n Timed = 1, // 单次"
},
{
"path": "src/types/wayline.ts",
"chars": 314,
"preview": "// 航线类型\nexport enum WaylineType {\n NormalWaypointWayline = 0, // 普通航点航线\n AccurateReshootingWayline = 1 // 精准复拍航线\n}\n\nex"
},
{
"path": "src/use-common-components.ts",
"chars": 281,
"preview": "import { App, DefineComponent } from 'vue'\n\nconst components: Record<string, DefineComponent<{}, {}, any>> = {\n\n}\n\nexpor"
},
{
"path": "src/utils/bytes.ts",
"chars": 2279,
"preview": "import { DEFAULT_PLACEHOLDER, SIZES as byteSizes, BYTE_SIZES } from './constants'\n\n/**\n * 转换字节数为单位B,KB,GB...\n * 保留一位小数\n "
},
{
"path": "src/utils/color.ts",
"chars": 183,
"preview": "export const commonColor = {\n WARN: '#FF9900', // 黄色\n FAIL: '#E02020', // 红色\n WHITE: '#FFFFFF', // 白色\n NORMAL: '#19B"
},
{
"path": "src/utils/common.ts",
"chars": 284,
"preview": "/**\n * 下载文件\n * @param data\n * @param fileName\n */\nexport function downloadFile (data: Blob, fileName: string) {\n const "
},
{
"path": "src/utils/constants.ts",
"chars": 560,
"preview": "\nexport const DEFAULT_PLACEHOLDER = '--' // 默认占位符\n\n// 全局日期格式\nexport const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'\nexport con"
},
{
"path": "src/utils/data-process.ts",
"chars": 148,
"preview": "export function formatPhoneNum (phoneNum: string | number) {\n const str = String(phoneNum)\n return str.substring(0, 3)"
},
{
"path": "src/utils/device-cmd.ts",
"chars": 25418,
"preview": "import { DroneBatteryModeEnum, DroneBatteryStateEnum, LinkWorkModeEnum } from './../types/airport-tsa'\nimport { DeviceIn"
},
{
"path": "src/utils/device-setting.ts",
"chars": 9313,
"preview": "import { DeviceInfoType } from '/@/types/device'\nimport { DeviceSettingType, DeviceSettingKeyEnum, DistanceLimitStatusEn"
},
{
"path": "src/utils/download.ts",
"chars": 2069,
"preview": "/**\n * 加载图片\n * @param url\n * @returns\n */\nexport function urlToImage (url: string) {\n return new Promise<HTMLImageEleme"
},
{
"path": "src/utils/error-code/index.ts",
"chars": 6218,
"preview": "export interface ErrorCode {\n code: number;\n msg: string;\n}\n\n/**\n * 根据错误码翻译错误信息\n * @param code\n * @param errorMsg\n * @"
},
{
"path": "src/utils/genjson.ts",
"chars": 2375,
"preview": "import {\n MapGeographicPosition,\n} from '/@/types/map'\n\nexport type GeojsonCoordinate = [number, number, number?]\n\nexpo"
},
{
"path": "src/utils/layer-tree.ts",
"chars": 548,
"preview": "const layerTreeTypes = ['layer', 'resource'] as const\ntype LayerTreeType = (typeof layerTreeTypes)[number]\nconst Spliter"
},
{
"path": "src/utils/logger.ts",
"chars": 908,
"preview": "\n/**\n * Used for log printing in a non-production environment\n * @param args\n */\nexport function consoleLog (...args: Pa"
},
{
"path": "src/utils/map-layer-utils.ts",
"chars": 1780,
"preview": "import { pinAMapPosition, MapGeographicPosition, Layer, LayerType, LayerElevationLoadStatus } from '/@/types/map'\nimport"
},
{
"path": "src/utils/storage.ts",
"chars": 1019,
"preview": "import { EStorageKey } from '/@/types/enums'\nimport { consoleWarn } from './logger'\n\nfunction getStorageData (key: EStor"
},
{
"path": "src/utils/time.ts",
"chars": 456,
"preview": "import {\n DATE_FORMAT,\n DEFAULT_PLACEHOLDER\n} from '/@/utils/constants'\nimport moment, { Moment } from 'moment'\n\n// 时间"
},
{
"path": "src/utils/uuid.ts",
"chars": 221,
"preview": "export function uuidv4 () {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n const r "
},
{
"path": "src/vendors/coordtransform.js",
"chars": 2939,
"preview": "/**\n * Conversion between the coordinates of the National Bureau of Survey and Measurement (Mars coordinates, GCJ02) and"
},
{
"path": "src/vendors/srs.sdk.js",
"chars": 23683,
"preview": "\n//\n// Copyright (c) 2013-2021 Winlin\n//\n// SPDX-License-Identifier: MIT\n//\n\n'use strict';\n\nfunction SrsError(name, mess"
},
{
"path": "src/vite-env.d.ts",
"chars": 38,
"preview": "/// <reference types=\"vite/client\" />\n"
},
{
"path": "src/websocket/index.ts",
"chars": 2003,
"preview": "import { message } from 'ant-design-vue'\nimport ReconnectingWebSocket from 'reconnecting-websocket'\nimport { EBizCode } "
},
{
"path": "src/websocket/util/config.ts",
"chars": 320,
"preview": "import { ELocalStorageKey } from '/@/types/enums'\nimport { CURRENT_CONFIG } from '/@/api/http/config'\n\nexport function g"
},
{
"path": "tsconfig.json",
"chars": 535,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"esnext\",\n \"module\": \"esnext\",\n \"moduleResolution\": \"node\",\n \"strict\": t"
},
{
"path": "vite.config.ts",
"chars": 2061,
"preview": "import vue from '@vitejs/plugin-vue'\n// config alias\nimport path from 'path'\nimport { ConfigEnv, defineConfig, UserConfi"
}
]
About this extraction
This page contains the full source code of the dji-sdk/Cloud-API-Demo-Web GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 166 files (603.0 KB), approximately 169.6k tokens, and a symbol index with 496 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.