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
================================================
demo-web
================================================
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
================================================
================================================
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`, body)
return resp.data
}
// 停止飞向目标点
export async function deleteFlyToPoint (sn: string): Promise> {
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> {
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> {
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> {
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> {
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> {
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> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area`, body)
return resp.data
}
export async function deleteFlightArea (area_id: string): Promise> {
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> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/sync`, { device_sn })
return resp.data
}
export async function getDeviceStatus (): Promise> {
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 {
code: number;
message: string;
data: {
list: T[];
pagination: IPage;
};
}
// Workspace
export interface IWorkspaceResponse {
code: number;
data: T;
message: string;
}
export type IStatus = 'WAITING' | 'DOING' | 'SUCCESS' | 'FAILED';
export interface CommonListResponse extends IResult {
data: {
list: T[];
pagination: IPage;
};
}
export interface CommonResponse 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>
// 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> => {
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> => {
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> => {
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 => {
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 => {
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> {
const url = `${HTTP_PREFIX}/login`
const result = await request.post(url, body)
return result.data
}
// Refresh Token
export const refreshToken = async function (body: {}): Promise> {
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> {
const url = `${HTTP_PREFIX}/workspaces/current`
const result = await request.get(url)
return result.data
}
// Get User Info
export const getUserInfo = async function (): Promise> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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 {
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 {
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> {
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 {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${jobId}/media-highest`
const result = await request.post(url)
return result.data
}
================================================
FILE: src/components/GMap.vue
================================================
{{ EModeCode[deviceInfo.device.mode_code] }}
HD
{{ deviceInfo.gateway?.transmission_signal_quality }}
{{ deviceInfo.gateway && deviceInfo.gateway.capacity_percent !== str ? deviceInfo.gateway?.capacity_percent + ' %' : deviceInfo.gateway?.capacity_percent }}
{{ deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device.battery.capacity_percent + ' %' : deviceInfo.device.battery.capacity_percent }}
Fixed
GPS
{{ deviceInfo.device.position_state.gps_number }}
{{ deviceInfo.device.position_state.rtk_number }}
{{ EGear[deviceInfo.device.gear] }}
ASL
{{ deviceInfo.device.height === str ? str : deviceInfo.device.height.toFixed(2) + ' m'}}
ALT
{{ deviceInfo.device.elevation === str ? str : deviceInfo.device.elevation.toFixed(2) + ' m' }}
H
{{ deviceInfo.device.home_distance === str ? str : deviceInfo.device.home_distance.toFixed(2) + ' m' }}
H.S
{{ deviceInfo.device.horizontal_speed === str ? str : deviceInfo.device.horizontal_speed.toFixed(2) + ' m/s'}}
V.S
{{ deviceInfo.device.vertical_speed === str ? str : deviceInfo.device.vertical_speed.toFixed(2) + ' m/s'}}
W.S
{{ deviceInfo.device.wind_speed === str ? str : (deviceInfo.device.wind_speed / 10).toFixed(2) + ' m/s'}}
{{ 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 }}
{{ osdVisible.gateway_callsign }}
osdVisible.visible = false">
{{ EDockModeCode[deviceInfo.dock.basic_osd?.mode_code] }}
{{ Math.floor(deviceInfo.dock.work_osd?.acc_time / 2592000) }}m
{{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000) / 86400) }}d
{{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) / 3600) }}h
{{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) / 60) }}min
{{ Math.floor(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600 % 60) }} s
{{ new Date((deviceInfo.dock.work_osd?.activation_time ?? 0) * 1000).toLocaleString() }}
{{ deviceInfo.dock.basic_osd?.network_state?.rate }} kb/s
{{ deviceInfo.dock.work_osd?.job_number }}
{{ deviceInfo.dock.link_osd?.media_file_detail?.remain_upload }}
total: {{ deviceInfo.dock.basic_osd?.storage?.total }}
used: {{ deviceInfo.dock.basic_osd?.storage?.used }}
W.S
{{ (deviceInfo.dock.basic_osd?.wind_speed ?? str) + ' m/s'}}
🌧
{{ RainfallEnum[deviceInfo.dock.basic_osd?.rainfall] }}
°C
{{ deviceInfo.dock.basic_osd?.environment_temperature }}
°C
{{ deviceInfo.dock.basic_osd?.temperature }}
💦
{{ deviceInfo.dock.basic_osd?.humidity }}
V
{{ (deviceInfo.dock.work_osd?.working_voltage ?? str) + ' mV' }}
A
{{ (deviceInfo.dock.work_osd?.working_current ?? str) + ' mA' }}
{{ deviceInfo.dock.basic_osd?.drone_in_dock }}
Actions
{{ !deviceInfo.device ? EModeCode[EModeCode.Disconnected] : EModeCode[deviceInfo.device?.mode_code] }}
{{ deviceInfo.dock.link_osd?.sdr?.up_quality }}
{{ deviceInfo.dock.link_osd?.sdr?.down_quality }}
{{ deviceInfo.device && deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device?.battery.capacity_percent + ' %' : str }}
total: {{ deviceInfo.device?.storage?.total }}
used: {{ deviceInfo.device?.storage?.used }}
Fixed
GPS
{{ deviceInfo.device ? deviceInfo.device.position_state.gps_number : str }}
{{ deviceInfo.device ? deviceInfo.device.position_state.rtk_number : str }}
{{ deviceInfo.device ? EGear[deviceInfo.device?.gear] : str }}
ASL
{{ !deviceInfo.device || deviceInfo.device.height === str ? str : deviceInfo.device?.height.toFixed(2) + ' m'}}
ALT
{{ !deviceInfo.device || deviceInfo.device.elevation === str ? str : deviceInfo.device?.elevation.toFixed(2) + ' m' }}
H
{{ !deviceInfo.device || deviceInfo.device.home_distance === str ? str : deviceInfo.device?.home_distance.toFixed(2) + ' m' }}
H.S
{{ !deviceInfo.device || deviceInfo.device?.horizontal_speed === str ? str : deviceInfo.device?.horizontal_speed.toFixed(2) + ' m/s'}}
V.S
{{ !deviceInfo.device || deviceInfo.device.vertical_speed === str ? str : deviceInfo.device?.vertical_speed.toFixed(2) + ' m/s'}}
W.S
{{ !deviceInfo.device || deviceInfo.device.wind_speed === str ? str : (deviceInfo.device?.wind_speed / 10).toFixed(2) + ' m/s'}}
{{ 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 }}
================================================
FILE: src/components/LayersTree.vue
================================================
{{ resource.name }}
================================================
FILE: src/components/MediaPanel.vue
================================================
================================================
FILE: src/components/common/sidebar.vue
================================================
================================================
FILE: src/components/common/topbar.vue
================================================
================================================
FILE: src/components/devices/DeviceFirmwareStatus.vue
================================================
{{ getText(firmware.firmware_status) }}
================================================
FILE: src/components/devices/device-hms/DeviceHmsDrawer.vue
================================================
{{ record.create_time }}
{{ record.update_time ?? 'It is happening...' }}
{{ text }}
{{ EDeviceTypeName[text] }}
================================================
FILE: src/components/devices/device-log/DeviceLogDetailModal.vue
================================================
下载机场日志
{{getLogTime(record)}}
{{getLogSize(record.size)}}
下载飞行器日志
{{getLogTime(record)}}
{{getLogSize(record.size)}}
>
================================================
FILE: src/components/devices/device-log/DeviceLogUploadModal.vue
================================================
{{getLogTime(record)}}
{{getLogSize(record.size)}}
{{getLogTime(record)}}
{{getLogSize(record.size)}}
================================================
FILE: src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue
================================================
{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key]}}
{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key]}}
{{ getDeviceInfo(record).parents[0].sn }}
{{ getDeviceInfo(record).hosts[0].sn }}
{{ getDeviceLogUploadStatus(record).text }}
================================================
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
================================================
{{ device.firmware_version }}
{{ getFirmwareTag(device.firmware_status).text }}
{{ `${device.firmware_progress}`}}
================================================
FILE: src/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue
================================================
升级固件版本: {{ deviceUpgradeInfo?.product_version }}
================================================
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 = 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
================================================
================================================
FILE: src/components/flight-area/FlightAreaDevicePanel.vue
================================================
================================================
FILE: src/components/flight-area/FlightAreaIcon.vue
================================================
{{ FlightAreaTypeTitleMap[type][isCircle ? EGeometryType.CIRCLE : EGeometryType.POLYGON] }}
================================================
FILE: src/components/flight-area/FlightAreaItem.vue
================================================
Update at {{ formatDateTime(flightArea.update_time).toLocaleString() }}
================================================
FILE: src/components/flight-area/FlightAreaPanel.vue
================================================
================================================
FILE: src/components/flight-area/FlightAreaSyncPanel.vue
================================================
Sync Across Devices
Syncing to {{ syncDevicesCount }} devices
================================================
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) => 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 = (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 = (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) => {
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
================================================
{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}
{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value }}
{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}:
Edit
{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}
{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value }}
{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}:
m
Edit
{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}
{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value }}
{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}:
m
Edit
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value }}
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}:
Edit
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value }}
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}:
Edit
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value }}
{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}:
Edit
================================================
FILE: src/components/g-map/DeviceSettingPopover.vue
================================================
{{ cancelText || '取消'}}
{{ okText || '确定' }}
================================================
FILE: src/components/g-map/DockControlPanel.vue
================================================
{{ cmdItem.label }}
{{ cmdItem.status }}
{{ cmdItem.operateText }}
================================================
FILE: src/components/g-map/DroneControlInfoPanel.vue
================================================
================================================
FILE: src/components/g-map/DroneControlPanel.vue
================================================
onFlyToConfirm(true)"
@cancel="($event) =>onFlyToConfirm(false)"
>
onTakeoffToPointConfirm(true)"
@cancel="($event) =>onTakeoffToPointConfirm(false)"
>
Safe Takeoff Altitude(m):
Return-to-Home Altitude(m):
Commander Mode Lost Action:
Commander Flight Height(m):
onGimbalResetConfirm(true)"
@cancel="($event) =>onGimbalResetConfirm(false)"
>
onZoomFactorConfirm(true)"
@cancel="($event) =>onZoomFactorConfirm(false)"
>
onCameraAimConfirm(true)"
@cancel="($event) =>onCameraAimConfirm(false)"
>
================================================
FILE: src/components/g-map/DroneControlPopover.vue
================================================
{{ cancelText || 'cancel'}}
{{ okText || 'ok' }}
================================================
FILE: src/components/g-map/use-connect-mqtt.ts
================================================
import {
ref,
watch,
computed,
onUnmounted,
} from 'vue'
import { useMyStore } from '/@/store'
import { postDrc } from '/@/api/drc'
import {
UranusMqtt,
} from '/@/mqtt'
type StatusOptions = {
status: 'close';
event?: CloseEvent;
} | {
status: 'open';
retryCount: number;
} | {
status: 'pending';
}
export function useConnectMqtt () {
const store = useMyStore()
const dockOsdVisible = computed(() => {
return store.state.osdVisible && store.state.osdVisible.visible && store.state.osdVisible.is_dock
})
const mqttState = ref(null)
// 监听已打开的设备小窗 窗口数量
watch(() => dockOsdVisible.value, async (val) => {
// 1.打开小窗
// 2.设备拥有飞行控制权
// 3.请求建立mqtt连接的认证信息
if (val) {
if (mqttState.value) return
const result = await postDrc({})
if (result?.code === 0) {
const { address, client_id, username, password, expire_time } = result.data
// @TODO: 校验 expire_time
mqttState.value = new UranusMqtt(address, {
clientId: client_id,
username,
password,
})
mqttState.value?.initMqtt()
mqttState.value?.on('onStatus', (statusOptions: StatusOptions) => {
// @TODO: 异常case
})
store.commit('SET_MQTT_STATE', mqttState.value)
store.commit('SET_CLIENT_ID', client_id)
}
// @TODO: 认证失败case
return
}
// 关闭所有小窗后
// 1.销毁mqtt连接重置mqtt状态
if (mqttState?.value) {
mqttState.value?.destroyed()
mqttState.value = null
store.commit('SET_MQTT_STATE', null)
store.commit('SET_CLIENT_ID', '')
}
}, { immediate: true })
onUnmounted(() => {
mqttState.value?.destroyed()
})
}
================================================
FILE: src/components/g-map/use-device-setting.ts
================================================
import { message } from 'ant-design-vue'
import { putDeviceProps, PutDevicePropsBody } from '/@/api/device-setting'
import { DeviceSettingKeyEnum, DeviceSettingFormModel, ObstacleAvoidanceStatusEnum, NightLightsStateEnum, DistanceLimitStatusEnum } from '/@/types/device-setting'
export function useDeviceSetting () {
// 生成参数
function genDevicePropsBySettingKey (key: DeviceSettingKeyEnum, fromModel: DeviceSettingFormModel) {
const body = {} as PutDevicePropsBody
if (key === DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET) {
body.night_lights_state = fromModel.nightLightsState ? NightLightsStateEnum.OPEN : NightLightsStateEnum.CLOSE
} else if (key === DeviceSettingKeyEnum.HEIGHT_LIMIT_SET) {
body.height_limit = fromModel.heightLimit
} else if (key === DeviceSettingKeyEnum.DISTANCE_LIMIT_SET) {
body.distance_limit_status = {}
if (fromModel.distanceLimitStatus.state) {
body.distance_limit_status.state = DistanceLimitStatusEnum.SET
body.distance_limit_status.distance_limit = fromModel.distanceLimitStatus.distanceLimit
} else {
body.distance_limit_status.state = DistanceLimitStatusEnum.UNSET
}
} else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON) {
body.obstacle_avoidance = {
horizon: fromModel.obstacleAvoidanceHorizon ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE
}
} else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE) {
body.obstacle_avoidance = {
upside: fromModel.obstacleAvoidanceUpside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE
}
} else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE) {
body.obstacle_avoidance = {
downside: fromModel.obstacleAvoidanceDownside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE
}
}
return body
}
// 设置设备属性
async function setDeviceProps (sn: string, body: PutDevicePropsBody) {
try {
const { code, message: msg } = await putDeviceProps(sn, body)
if (code === 0) {
// message.success('指令发送成功')
return true
}
throw (msg)
} catch (e) {
message.error('设备属性设置失败')
return false
}
}
return {
genDevicePropsBySettingKey,
setDeviceProps
}
}
================================================
FILE: src/components/g-map/use-dock-control.ts
================================================
import { message } from 'ant-design-vue'
import { ref } from 'vue'
import { postSendCmd } from '/@/api/device-cmd'
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
export function useDockControl () {
const dockControlPanelVisible = ref(false)
function setDockControlPanelVisible (visible: boolean) {
dockControlPanelVisible.value = visible
}
// 远程调试开关
async function dockDebugOnOff (sn: string, on: boolean) {
const result = await sendDockControlCmd({
sn: sn,
cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose
}, false)
return result
}
// 发送指令
async function sendDockControlCmd (params: {
sn: string,
cmd: DeviceCmd
action?: DeviceCmdItemAction
}, tip = true) {
try {
let body = undefined as any
if (params.action !== undefined) {
body = {
action: params.action
}
}
const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }, body)
if (code === 0) {
tip && message.success('指令发送成功')
return true
}
throw (msg)
} catch (e) {
tip && message.error('指令发送失败')
return false
}
}
// 控制面板关闭
async function onCloseControlPanel (sn: string, debugging: boolean) {
if (debugging) {
await dockDebugOnOff(sn, false)
}
setDockControlPanelVisible(false)
}
return {
dockControlPanelVisible,
setDockControlPanelVisible,
sendDockControlCmd,
dockDebugOnOff,
onCloseControlPanel,
}
}
================================================
FILE: src/components/g-map/use-drone-control-mqtt-event.ts
================================================
import { ref, onMounted, onBeforeUnmount } from 'vue'
import EventBus from '/@/event-bus/'
import {
DRC_METHOD,
DRCHsiInfo,
DRCOsdInfo,
DRCDelayTimeInfo,
DrcResponseInfo,
} from '/@/types/drc'
export function useDroneControlMqttEvent (sn: string) {
const drcInfo = ref('')
const hsiInfo = ref('')
const osdInfo = ref('')
const delayInfo = ref('')
const errorInfo = ref('')
function handleHsiInfo (data: DRCHsiInfo) {
hsiInfo.value = `method: ${DRC_METHOD.HSI_INFO_PUSH}\r\n ${JSON.stringify(data)}\r\n `
}
function handleOsdInfo (data: DRCOsdInfo) {
osdInfo.value = `method: ${DRC_METHOD.OSD_INFO_PUSH}\r\n ${JSON.stringify(data)}\r\n `
}
function handleDelayTimeInfo (data: DRCDelayTimeInfo) {
delayInfo.value = `method: ${DRC_METHOD.DELAY_TIME_INFO_PUSH}\r\n ${JSON.stringify(data)}\r\n `
}
function handleDroneControlErrorInfo (data: DrcResponseInfo) {
if (!data.result) {
return
}
errorInfo.value = `Drc error code: ${data.result}, seq: ${data.output?.seq}`
}
function handleDroneControlMqttEvent (payload: any) {
if (!payload || !payload.method) {
return
}
switch (payload.method) {
case DRC_METHOD.HSI_INFO_PUSH: {
handleHsiInfo(payload.data)
break
}
case DRC_METHOD.OSD_INFO_PUSH: {
handleOsdInfo(payload.data)
break
}
case DRC_METHOD.DELAY_TIME_INFO_PUSH: {
handleDelayTimeInfo(payload.data)
break
}
case DRC_METHOD.DRONE_EMERGENCY_STOP:
case DRC_METHOD.DRONE_CONTROL: {
handleDroneControlErrorInfo(payload.data)
break
}
}
drcInfo.value = hsiInfo.value + osdInfo.value + delayInfo.value
}
onMounted(() => {
EventBus.on('droneControlMqttInfo', handleDroneControlMqttEvent)
})
onBeforeUnmount(() => {
EventBus.off('droneControlMqttInfo', handleDroneControlMqttEvent)
})
return {
drcInfo: drcInfo,
errorInfo: errorInfo
}
}
================================================
FILE: src/components/g-map/use-drone-control-ws-event.ts
================================================
import { message, notification } from 'ant-design-vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import EventBus from '/@/event-bus/'
import { EBizCode } from '/@/types'
import { ControlSource } from '/@/types/device'
import { ControlSourceChangeType, ControlSourceChangeInfo, FlyToPointMessage, TakeoffToPointMessage, DrcModeExitNotifyMessage, DrcStatusNotifyMessage } from '/@/types/drone-control'
export interface UseDroneControlWsEventParams {
}
export function useDroneControlWsEvent (sn: string, payloadSn: string, funcs?: UseDroneControlWsEventParams) {
const droneControlSource = ref(ControlSource.A)
const payloadControlSource = ref(ControlSource.B)
function onControlSourceChange (data: ControlSourceChangeInfo) {
if (data.type === ControlSourceChangeType.Flight && data.sn === sn) {
droneControlSource.value = data.control_source
message.info(`Flight control is changed to ${droneControlSource.value}`)
return
}
if (data.type === ControlSourceChangeType.Payload && data.sn === payloadSn) {
payloadControlSource.value = data.control_source
message.info(`Payload control is changed to ${payloadControlSource.value}.`)
}
}
function handleProgress (key: string, message: string, error: number) {
if (error !== 0) {
notification.error({
key: key,
message: key + 'Error code:' + error,
description: message,
duration: null
})
} else {
notification.info({
key: key,
message: key,
description: message,
duration: 30
})
}
}
function handleDroneControlWsEvent (payload: any) {
if (!payload) {
return
}
switch (payload.biz_code) {
case EBizCode.ControlSourceChange: {
onControlSourceChange(payload.data)
break
}
case EBizCode.FlyToPointProgress: {
const { sn: deviceSn, result, message: msg } = payload.data as FlyToPointMessage
if (deviceSn !== sn) return
handleProgress(EBizCode.FlyToPointProgress, `device(sn: ${deviceSn}) ${msg}`, result)
break
}
case EBizCode.TakeoffToPointProgress: {
const { sn: deviceSn, result, message: msg } = payload.data as TakeoffToPointMessage
if (deviceSn !== sn) return
handleProgress(EBizCode.TakeoffToPointProgress, `device(sn: ${deviceSn}) ${msg}`, result)
break
}
case EBizCode.JoystickInvalidNotify: {
const { sn: deviceSn, result, message: msg } = payload.data as DrcModeExitNotifyMessage
if (deviceSn !== sn) return
handleProgress(EBizCode.JoystickInvalidNotify, `device(sn: ${deviceSn}) ${msg}`, result)
break
}
case EBizCode.DrcStatusNotify: {
const { sn: deviceSn, result, message: msg } = payload.data as DrcStatusNotifyMessage
// handleProgress(EBizCode.DrcStatusNotify, `device(sn: ${deviceSn}) ${msg}`, result)
break
}
}
// eslint-disable-next-line no-unused-expressions
// console.log('payload.biz_code', payload.data)
}
onMounted(() => {
EventBus.on('droneControlWs', handleDroneControlWsEvent)
})
onBeforeUnmount(() => {
EventBus.off('droneControlWs', handleDroneControlWsEvent)
})
return {
droneControlSource: droneControlSource,
payloadControlSource: payloadControlSource
}
}
================================================
FILE: src/components/g-map/use-drone-control.ts
================================================
import { ref } from 'vue'
import { postFlyToPoint, PostFlyToPointBody, deleteFlyToPoint, postTakeoffToPoint, PostTakeoffToPointBody } from '/@/api/drone-control/drone'
import { message } from 'ant-design-vue'
export function useDroneControl () {
const droneControlPanelVisible = ref(false)
function setDroneControlPanelVisible (visible: boolean) {
droneControlPanelVisible.value = visible
}
async function flyToPoint (sn: string, body: PostFlyToPointBody) {
const { code } = await postFlyToPoint(sn, body)
if (code === 0) {
message.success('Fly to')
}
}
async function stopFlyToPoint (sn: string) {
const { code } = await deleteFlyToPoint(sn)
if (code === 0) {
message.success('Stop fly to')
}
}
async function takeoffToPoint (sn: string, body: PostTakeoffToPointBody) {
const { code } = await postTakeoffToPoint(sn, body)
if (code === 0) {
message.success('Take off successfully')
}
}
return {
droneControlPanelVisible,
setDroneControlPanelVisible,
flyToPoint,
stopFlyToPoint,
takeoffToPoint
}
}
================================================
FILE: src/components/g-map/use-manual-control.ts
================================================
import {
ref,
onUnmounted,
watch,
Ref,
} from 'vue'
import { message } from 'ant-design-vue'
import {
DRC_METHOD,
DroneControlProtocol,
} from '/@/types/drc'
import {
useMqtt,
DeviceTopicInfo
} from './use-mqtt'
let myInterval: any
export enum KeyCode {
KEY_W = 'KeyW',
KEY_A = 'KeyA',
KEY_S = 'KeyS',
KEY_D = 'KeyD',
KEY_Q = 'KeyQ',
KEY_E = 'KeyE',
ARROW_UP = 'ArrowUp',
ARROW_DOWN = 'ArrowDown',
}
export function useManualControl (deviceTopicInfo: DeviceTopicInfo, isCurrentFlightController: Ref) {
const activeCodeKey = ref(null) as Ref
const mqttHooks = useMqtt(deviceTopicInfo)
let seq = 0
function handlePublish (params: DroneControlProtocol) {
const body = {
method: DRC_METHOD.DRONE_CONTROL,
data: params,
}
handleClearInterval()
myInterval = setInterval(() => {
body.data.seq = seq++
seq++
window.console.log('keyCode>>>>', activeCodeKey.value, body)
mqttHooks?.publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 0 })
}, 50)
}
function handleKeyup (keyCode: KeyCode) {
if (!deviceTopicInfo.pubTopic) {
message.error('请确保已经建立DRC链路')
return
}
const SPEED = 5 // check
const HEIGHT = 5 // check
const W_SPEED = 20 // 机头角速度
seq = 0
switch (keyCode) {
case 'KeyA':
if (activeCodeKey.value === keyCode) return
handlePublish({ y: -SPEED })
activeCodeKey.value = keyCode
break
case 'KeyW':
if (activeCodeKey.value === keyCode) return
handlePublish({ x: SPEED })
activeCodeKey.value = keyCode
break
case 'KeyS':
if (activeCodeKey.value === keyCode) return
handlePublish({ x: -SPEED })
activeCodeKey.value = keyCode
break
case 'KeyD':
if (activeCodeKey.value === keyCode) return
handlePublish({ y: SPEED })
activeCodeKey.value = keyCode
break
case 'ArrowUp':
if (activeCodeKey.value === keyCode) return
handlePublish({ h: HEIGHT })
activeCodeKey.value = keyCode
break
case 'ArrowDown':
if (activeCodeKey.value === keyCode) return
handlePublish({ h: -HEIGHT })
activeCodeKey.value = keyCode
break
case 'KeyQ':
if (activeCodeKey.value === keyCode) return
handlePublish({ w: -W_SPEED })
activeCodeKey.value = keyCode
break
case 'KeyE':
if (activeCodeKey.value === keyCode) return
handlePublish({ w: W_SPEED })
activeCodeKey.value = keyCode
break
default:
break
}
}
function handleClearInterval () {
clearInterval(myInterval)
myInterval = undefined
}
function resetControlState () {
activeCodeKey.value = null
seq = 0
handleClearInterval()
}
function onKeyup () {
resetControlState()
}
function onKeydown (e: KeyboardEvent) {
handleKeyup(e.code as KeyCode)
}
function startKeyboardManualControl () {
window.addEventListener('keydown', onKeydown)
window.addEventListener('keyup', onKeyup)
}
function closeKeyboardManualControl () {
resetControlState()
window.removeEventListener('keydown', onKeydown)
window.removeEventListener('keyup', onKeyup)
}
watch(() => isCurrentFlightController.value, (val) => {
if (val && deviceTopicInfo.pubTopic) {
startKeyboardManualControl()
} else {
closeKeyboardManualControl()
}
}, { immediate: true })
onUnmounted(() => {
closeKeyboardManualControl()
})
function handleEmergencyStop () {
if (!deviceTopicInfo.pubTopic) {
message.error('请确保已经建立DRC链路')
return
}
const body = {
method: DRC_METHOD.DRONE_EMERGENCY_STOP,
data: {}
}
resetControlState()
window.console.log('handleEmergencyStop>>>>', deviceTopicInfo.pubTopic, body)
mqttHooks?.publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 1 })
}
return {
activeCodeKey,
handleKeyup,
handleEmergencyStop,
resetControlState,
}
}
================================================
FILE: src/components/g-map/use-mqtt.ts
================================================
import {
ref,
reactive,
computed,
watch,
onUnmounted,
} from 'vue'
import {
IClientPublishOptions,
IPublishPacket,
} from '/@/mqtt'
import { useMyStore } from '/@/store'
import {
DRC_METHOD,
} from '/@/types/drc'
import EventBus from '/@/event-bus'
export interface DeviceTopicInfo{
sn: string
pubTopic: string
subTopic: string
}
type MessageMqtt = (topic: string, payload: Buffer, packet: IPublishPacket) => void | Promise
export function useMqtt (deviceTopicInfo: DeviceTopicInfo) {
let cacheSubscribeArr: {
topic: string;
callback?: MessageMqtt;
}[] = []
const store = useMyStore()
const mqttState = computed(() => {
return store.state.mqttState
})
function publishMqtt (topic: string, body: object, ots?: IClientPublishOptions) {
// const buffer = Buffer.from(JSON.stringify(body))
mqttState.value?.publishMqtt(topic, JSON.stringify(body), ots)
}
function subscribeMqtt (topic: string, handleMessageMqtt?: MessageMqtt) {
mqttState.value?.subscribeMqtt(topic)
const handler = handleMessageMqtt || onMessageMqtt
mqttState.value?.on('onMessageMqtt', handler)
cacheSubscribeArr.push({
topic,
callback: handler,
})
}
function onMessageMqtt (message: any) {
if (cacheSubscribeArr.findIndex(item => item.topic === message?.topic) !== -1) {
const payloadStr = new TextDecoder('utf-8').decode(message?.payload)
const payloadObj = JSON.parse(payloadStr)
switch (payloadObj?.method) {
case DRC_METHOD.HEART_BEAT:
break
case DRC_METHOD.DELAY_TIME_INFO_PUSH:
case DRC_METHOD.HSI_INFO_PUSH:
case DRC_METHOD.OSD_INFO_PUSH:
case DRC_METHOD.DRONE_CONTROL:
case DRC_METHOD.DRONE_EMERGENCY_STOP:
EventBus.emit('droneControlMqttInfo', payloadObj)
break
default:
break
}
}
}
function unsubscribeDrc () {
// 销毁已订阅事件
cacheSubscribeArr.forEach(item => {
mqttState.value?.off('onMessageMqtt', item.callback)
mqttState.value?.unsubscribeMqtt(item.topic)
})
cacheSubscribeArr = []
}
// 心跳
const heartBeatSeq = ref(0)
const state = reactive({
heartState: new Map(),
})
// 监听云控控制权
watch(() => deviceTopicInfo, (val, oldVal) => {
if (val.subTopic !== '') {
// 1.订阅topic
subscribeMqtt(deviceTopicInfo.subTopic)
// 2.发心跳
publishDrcPing(deviceTopicInfo.sn)
} else {
clearInterval(state.heartState.get(deviceTopicInfo.sn)?.pingInterval)
state.heartState.delete(deviceTopicInfo.sn)
heartBeatSeq.value = 0
}
}, { immediate: true, deep: true })
function publishDrcPing (sn: string) {
const body = {
method: DRC_METHOD.HEART_BEAT,
data: {
ts: new Date().getTime(),
seq: heartBeatSeq.value,
},
}
const pingInterval = setInterval(() => {
if (!mqttState.value) return
heartBeatSeq.value += 1
body.data.ts = new Date().getTime()
body.data.seq = heartBeatSeq.value
publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 0 })
}, 1000)
state.heartState.set(sn, {
pingInterval,
})
}
onUnmounted(() => {
unsubscribeDrc()
heartBeatSeq.value = 0
})
return {
mqttState,
publishMqtt,
subscribeMqtt,
}
}
================================================
FILE: src/components/g-map/use-payload-control.ts
================================================
import { message } from 'ant-design-vue'
import {
postPayloadAuth,
postPayloadCommands,
PayloadCommandsEnum,
PostCameraModeBody,
PostCameraFocalLengthBody,
PostGimbalResetBody,
PostCameraAimBody,
} from '/@/api/drone-control/payload'
import { ControlSource } from '/@/types/device'
export function usePayloadControl () {
function checkPayloadAuth (controlSource?: ControlSource) {
if (controlSource !== ControlSource.A) {
message.error('Get Payload Control first')
return false
}
return true
}
async function authPayload (sn: string, payloadIndx: string) {
const { code } = await postPayloadAuth(sn, {
payload_index: payloadIndx
})
if (code === 0) {
message.success('Get Payload Control successfully')
return true
}
return false
}
async function resetGimbal (sn: string, data: PostGimbalResetBody) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.GimbalReset,
data: data
})
if (code === 0) {
message.success('Gimbal Reset successfully')
}
}
async function switchCameraMode (sn: string, data: PostCameraModeBody) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.CameraModeSwitch,
data: data
})
if (code === 0) {
message.success('Camera Mode Switch successfully')
}
}
async function takeCameraPhoto (sn: string, payloadIndx: string) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.CameraPhotoTake,
data: {
payload_index: payloadIndx
}
})
if (code === 0) {
message.success('Take Photo successfully')
}
}
async function startCameraRecording (sn: string, payloadIndx: string) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.CameraRecordingStart,
data: {
payload_index: payloadIndx
}
})
if (code === 0) {
message.success('Start Recording successfully')
}
}
async function stopCameraRecording (sn: string, payloadIndx: string) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.CameraRecordingStop,
data: {
payload_index: payloadIndx
}
})
if (code === 0) {
message.success('Stop Recording successfully')
}
}
async function changeCameraFocalLength (sn: string, data: PostCameraFocalLengthBody) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.CameraFocalLengthSet,
data: data,
})
if (code === 0) {
message.success('Zoom successfully')
}
}
async function cameraAim (sn: string, data: PostCameraAimBody) {
const { code } = await postPayloadCommands(sn, {
cmd: PayloadCommandsEnum.CameraAim,
data: data,
})
if (code === 0) {
message.success('Zoom Aim successfully')
}
}
return {
checkPayloadAuth,
authPayload,
resetGimbal,
switchCameraMode,
takeCameraPhoto,
startCameraRecording,
stopCameraRecording,
changeCameraFocalLength,
cameraAim,
}
}
================================================
FILE: src/components/livestream-agora.vue
================================================
Live streaming source selection
Lens:
{{lens}}
{{ item.label }}
{{ item.label }}
{{ item.label }}
Note: Obtain The Following Parameters From https://console.agora.io
Switch Lens
Play
Stop
Update Clarity
Refresh Live Capacity
================================================
FILE: src/components/livestream-others.vue
================================================
Live streaming source selection
Lens:
{{lens}}
{{ item.label }}
{{ item.label }}
{{ item.label }}
{{ item.label }}
Please use VLC media player to play the RTSP livestream !!!
RTSP Parameter:{{ rtspData }}
Switch Lens
Play
Stop
Update Clarity
Refresh Live Capacity
================================================
FILE: src/components/svgIcon.vue
================================================
================================================
FILE: src/components/task/CreatePlan.vue
================================================
Select Route
{{ wayline.name }}
{{ wayline.user_name }}
{{ DEVICE_NAME[wayline.drone_model_key] }}
{{ DEVICE_NAME[payload] }}
Update at {{ new Date(wayline.update_time).toLocaleString() }}
Select Device
{{ dock.children?.nickname ?? 'No drone' }}
$refs.select_execute_time.onFieldChange()"
/>
-
================================================
FILE: src/components/task/TaskPanel.vue
================================================
{{ formatTaskTime(record.begin_time) }}
{{ formatTaskTime(record.end_time) }}
{{ formatTaskTime(record.execute_time) }}
{{ formatTaskTime(record.completed_time) }}
{{ formatTaskStatus(record).text }}
{{ getCodeMessage(record.code) }}
{{ formatTaskType(record) }}
{{ formatLostAction(record) }}
{{ formatMediaTaskStatus(record).text }}
{{ formatMediaTaskStatus(record).number }}
Upload now
================================================
FILE: src/components/task/use-format-task.ts
================================================
import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'
import { Task } from '/@/api/wayline'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap, MediaStatusMap, MediaStatusColorMap, MediaStatus } from '/@/types/task'
import { isNil } from 'lodash'
export function useFormatTask () {
function formatTaskType (task: Task) {
return TaskTypeMap[task.task_type] || DEFAULT_PLACEHOLDER
}
function formatTaskTime (time: string) {
return time || DEFAULT_PLACEHOLDER
}
function formatLostAction (task: Task) {
return OutOfControlActionMap[task.out_of_control_action] || DEFAULT_PLACEHOLDER
}
function formatTaskStatus (task: Task) {
const statusObj = {
text: '',
color: ''
}
const { status } = task
statusObj.text = TaskStatusMap[status]
statusObj.color = TaskStatusColor[status]
return statusObj
}
function formatMediaTaskStatus (task: Task) {
const statusObj = {
text: '',
color: '',
number: '',
status: MediaStatus.Empty,
}
const { media_count, uploaded_count, uploading } = task
if (isNil(media_count) || isNaN(media_count)) {
return statusObj
}
const expectedFileCount = media_count || 0
const uploadedFileCount = uploaded_count || 0
if (media_count === 0) {
statusObj.text = MediaStatusMap[MediaStatus.Empty]
statusObj.color = MediaStatusColorMap[MediaStatus.Empty]
} else if (media_count === uploaded_count) {
statusObj.text = MediaStatusMap[MediaStatus.Success]
statusObj.color = MediaStatusColorMap[MediaStatus.Success]
statusObj.number = `(${uploadedFileCount}/${expectedFileCount})`
statusObj.status = MediaStatus.Success
} else {
if (uploading) {
statusObj.text = MediaStatusMap[MediaStatus.Uploading]
statusObj.color = MediaStatusColorMap[MediaStatus.Uploading]
statusObj.status = MediaStatus.Uploading
} else {
statusObj.text = MediaStatusMap[MediaStatus.ToUpload]
statusObj.color = MediaStatusColorMap[MediaStatus.ToUpload]
statusObj.status = MediaStatus.ToUpload
}
statusObj.number = `(${uploadedFileCount}/${expectedFileCount})`
}
return statusObj
}
return {
formatTaskType,
formatTaskTime,
formatLostAction,
formatTaskStatus,
formatMediaTaskStatus,
}
}
================================================
FILE: src/components/task/use-task-ws-event.ts
================================================
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { TaskProgressInfo, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'
import { EBizCode } from '/@/types'
export interface UseTaskWsEventParams {
onTaskProgressWs: (data: TaskProgressInfo) => void,
onTaskMediaProgressWs: (data: MediaStatusProgressInfo) => void
onoTaskMediaHighestPriorityWS: (data: TaskMediaHighestPriorityProgressInfo) => void
}
export function useTaskWsEvent (funcs: UseTaskWsEventParams): void {
function handleTaskWsEvent (payload: any) {
if (!payload) {
return
}
switch (payload.biz_code) {
case EBizCode.FlightTaskProgress: {
funcs?.onTaskProgressWs(payload.data)
break
}
case EBizCode.FlightTaskMediaProgress: {
funcs?.onTaskMediaProgressWs(payload.data)
break
}
case EBizCode.FlightTaskMediaHighestPriority: {
funcs?.onoTaskMediaHighestPriorityWS(payload.data)
break
}
}
// eslint-disable-next-line no-unused-expressions
// console.log('payload', payload.data)
}
onMounted(() => {
EventBus.on('flightTaskWs', handleTaskWsEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightTaskWs', handleTaskWsEvent)
})
}
================================================
FILE: src/components/workspace/DividerLine.vue
================================================
================================================
FILE: src/components/workspace/Title.vue
================================================
================================================
FILE: src/constants/index.ts
================================================
import { CURRENT_CONFIG } from '/@/api/http/config'
export const AMapConfig = {
key: CURRENT_CONFIG.amapKey,
version: '2.0',
plugins: [
'AMap.Scale',
'AMap.ToolBar',
'AMap.ControlBar',
'AMap.ElasticMarker',
'AMap.MapType',
'AMap.Geocoder',
'AMap.CircleEditor',
'AMap.PolygonEditor',
'AMap.PolylineEditor',
'AMap.PolyEditor',
'AMap.RangingTool',
'AMap.Weather',
'AMap.MouseTool',
'AMap.MoveAnimation'
]
}
================================================
FILE: src/constants/map.ts
================================================
export enum MapElementColor {
Blue = '#2D8CF0',
Green = '#19BE6B',
Yellow = '#FFBB00',
Red = '#E23C39',
Orange = '#B620E0',
Default = '#212121'
}
export const MapElementDefaultColor = MapElementColor.Default
export enum MapDoodleColor {
PinColor = '#2D8CF0',
PolylineColor = '#2D8CF0',
PolygonColor = '#2D8CF0'
}
export enum MapElementEnum {
PIN = 0,
LINE = 1,
POLY = 2
}
export type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' | 'circle'
================================================
FILE: src/constants/mock-layers.ts
================================================
export const mapLayers = {
code: 0,
message: 'success',
data: {
list: [{
id: 'private_layer',
name: 'Private Layer',
order: 0,
is_distributed: false,
type: 1,
is_lock: false,
create_time: 1634268707424,
elements: [{
id: 'b2370d29-be65-42b0-9224-4d816e86dc64',
name: 'xuejia n. 1',
order: 0,
status: 1,
display: 1,
resource: {
content: {
type: 'Feature',
properties: {
color: '#2D8CF0'
},
geometry: {
type: 'Polygon',
coordinates: [
[
[114.156671, 38.468249],
[114.139517, 37.372177],
[115.52899, 37.712212]
]
]
}
},
type: 4,
user_name: 'xuejia n.',
user_id: '1402914943455727616'
},
update_time: 1636966336566,
create_time: 1636966325700,
elevation_load_status: 0,
icon: 'area'
}, {
id: '768e9fcd-121f-47a6-96b9-f1aee27d32f0',
name: 'xuejia n. 1',
order: 0,
status: 1,
display: 1,
resource: {
content: {
type: 'Feature',
properties: {
color: '#2D8CF0'
},
geometry: {
type: 'LineString',
coordinates: [
[116.263962, 40.234929],
[116.503006, 40.237026],
[116.335465, 40.155206],
[116.541458, 40.12371]
]
}
},
type: 4,
user_name: 'xuejia n.',
user_id: '1402914943455727616'
},
update_time: 1636966322636,
create_time: 1636966316803,
elevation_load_status: 0,
icon: 'line'
}, {
id: '4e741a76-3600-4af5-ace8-d805e7cd31fa',
name: 'xuejia n. 2',
order: 0,
status: 1,
display: 1,
resource: {
content: {
type: 'Feature',
properties: {
color: '#2D8CF0',
clampToGround: true
},
geometry: {
type: 'Point',
coordinates: [116.098223, 39.976538, 104]
}
},
type: 6,
user_name: 'xuejia n.',
user_id: '1402914943455727616'
},
update_time: 1636966305229,
create_time: 1636966305229,
elevation_load_status: 0,
icon: 'pin'
}, {
id: 'efff2b5d-de22-4d48-8d92-4f53170668f6',
name: 'xuejia n. 1',
order: 0,
status: 1,
display: 1,
resource: {
content: {
type: 'Feature',
properties: {
color: '#19BE6B',
clampToGround: true
},
geometry: {
type: 'Point',
coordinates: [113.35367028239645, 23.755194000519843, 22]
}
},
type: 6,
user_name: 'xuejia n.',
user_id: '1402914943455727616'
},
update_time: 1636966304432,
create_time: 1636966299455,
elevation_load_status: 0,
icon: 'pin'
}]
}, {
id: 'share_layer',
name: 'Share Layer',
order: 0,
is_distributed: true,
type: 2,
is_lock: false,
create_time: 1634268707414,
elements: []
}]
}
}
================================================
FILE: src/directives/drag-window.ts
================================================
import { nextTick, App } from 'vue'
export default function useDragWindowDirective (app: App): void {
app.directive('drag-window', async (el) => {
await nextTick()
const modal = el
const header = el.getElementsByClassName('drag-title')[0]
let left = 0
let top = 0
header.style.cursor = 'move'
top = top || modal.offsetTop
header.onpointerdown = (e: { clientX: number; clientY: number; pointerId: number }) => {
const startX = e.clientX
const startY = e.clientY
header.left = header.offsetLeft
header.top = header.offsetTop
header.setPointerCapture(e.pointerId)
el.onpointermove = (event: { clientX: number; clientY: number }) => {
const endX = event.clientX
const endY = event.clientY
modal.left = header.left + (endX - startX) + left
modal.top = header.top + (endY - startY) + top
modal.style.left = modal.left + 'px'
modal.style.top = modal.top + 'px'
}
el.onpointerup = () => {
left = modal.left || 0
top = modal.top || 0
el.onpointermove = null
el.onpointerup = null
header.releasePointerCapture(e.pointerId)
}
}
})
}
================================================
FILE: src/directives/index.ts
================================================
import { App } from 'vue'
import useDragWindowDirective from './drag-window'
export function useDirectives (app: App): void {
useDragWindowDirective(app)
}
================================================
FILE: src/env.d.ts
================================================
// Environment variable definition
// https://cn.vitejs.dev/guide/env-and-mode.html#env-files
interface ImportMetaEnv {
VITE_APP_ENVIRONMENT: 'DEV' | 'STAG' | 'UAT' | 'PROD',
// api gateway
VITE_APP_APIGATEWAY_BACKEND_HOST: string
// More environment variables...
}
================================================
FILE: src/event-bus/index.ts
================================================
import mitt, { Emitter } from 'mitt'
type Events = {
deviceUpgrade: any; // 设备升级
deviceLogUploadProgress: any // 设备日志上传
flightTaskWs: any // 机场任务消息
droneControlWs: any // 飞行指令信息
droneControlMqttInfo: any // drc 链路通知
flightAreasDroneLocationWs: any
flightAreasSyncProgressWs: any
flightAreasUpdateWs: any
};
const emitter: Emitter = mitt()
export default emitter
================================================
FILE: src/hooks/use-connect-websocket.ts
================================================
import { onMounted, onUnmounted } from 'vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
import ConnectWebSocket, { MessageHandler } from '/@/websocket'
import { getWebsocketUrl } from '/@/websocket/util/config'
/**
* 接收一个message函数
* @param messageHandler
*/
export function useConnectWebSocket (messageHandler: MessageHandler) {
const webSocket = new ConnectWebSocket(getWebsocketUrl())
onMounted(() => {
webSocket?.registerMessageHandler(messageHandler)
webSocket?.initSocket()
})
onUnmounted(() => {
webSocket?.close()
})
}
================================================
FILE: src/hooks/use-g-map-cover.ts
================================================
import { EFlightAreaType } from '../types/flight-area'
import pin19be6b from '/@/assets/icons/pin-19be6b.svg'
import pin212121 from '/@/assets/icons/pin-212121.svg'
import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'
import pinb620e0 from '/@/assets/icons/pin-b620e0.svg'
import pine23c39 from '/@/assets/icons/pin-e23c39.svg'
import pineffbb00 from '/@/assets/icons/pin-ffbb00.svg'
import { getRoot } from '/@/root'
import rootStore from '/@/store'
import { GeojsonCoordinate } from '/@/types/map'
export function useGMapCover () {
const root = getRoot()
const AMap = root.$aMap
const normalColor = '#2D8CF0'
const store = rootStore
const coverMap = store.state.coverMap
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
const disableColor = '#b3b3b3'
function AddCoverToMap (cover :any) {
root.$map.add(cover)
coverMap[cover.getExtData().id] = [cover]
}
function getPinIcon (color?:string) {
// console.log('color', color)
const colorObj: {
[key: number| string]: any
} = {
'2d8cf0': pin2d8cf0,
'19be6b': pin19be6b,
212121: pin212121,
b620e0: pinb620e0,
e23c39: pine23c39,
ffbb00: pineffbb00,
}
const iconName = (color?.replaceAll('#', '') || '').toLocaleLowerCase()
return new AMap.Icon({
// size: new AMap.Size(40, 50),
image: colorObj[iconName],
// imageOffset: new AMap.Pixel(0, -60),
// imageSize: new AMap.Size(40, 50)
})
}
function init2DPin (name: string, coordinates:GeojsonCoordinate, color?:string, data?:{}) {
const pin = new AMap.Marker({
position: new AMap.LngLat(coordinates[0], coordinates[1]),
title: name,
icon: getPinIcon(color),
// strokeColor: color || normalColor,
// fillColor: color || normalColor,
extData: data
})
// console.log('coordinates pin', pin)
AddCoverToMap(pin)
}
function AddOverlayGroup (overlayGroup) {
root.$map.add(overlayGroup)
const id = overlayGroup.getExtData().id
coverMap[id] = [...(coverMap[id] || []), overlayGroup]
}
function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) {
const path = [] as GeojsonCoordinate[]
coordinates.forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
const polyline = new AMap.Polyline({
path: path,
strokeColor: color || normalColor,
strokeOpacity: 1,
strokeWeight: 2,
strokeStyle: 'solid',
extData: data
// draggable: true,
})
AddOverlayGroup(polyline)
}
function initPolygon (name: string, coordinates:GeojsonCoordinate[][], color?:string, data?:{}) {
const path = [] as GeojsonCoordinate[]
coordinates[0].forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
// console.log('Polygon', path)
const Polygon = new AMap.Polygon({
path: path,
strokeOpacity: 1,
strokeWeight: 2,
fillColor: color || normalColor,
fillOpacity: 0.4,
// draggable: true,
strokeColor: color || normalColor,
extData: data
})
AddOverlayGroup(Polygon)
}
function removeCoverFromMap (id:string) {
coverMap[id].forEach(cover => root.$map.remove(cover))
coverMap[id] = []
}
function getElementFromMap (id:string): any[] {
return coverMap[id]
}
function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
const element = elements[0]
const icon = getPinIcon(color)
element.setPosition(new AMap.LngLat(coordinates[0], coordinates[1]))
element.setIcon(icon)
element.setTitle(name)
} else {
// console.log('into init PIN')
init2DPin(name, coordinates, color, {
id: id,
name: name
})
}
}
function updatePolylineElement (id:string, name: string, coordinates:GeojsonCoordinate[], color?:string) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
const element = elements[0]
const options = element.getOptions()
options.strokeColor = color || normalColor
element.setOptions(options)
} else {
initPolyline(name, coordinates, color, {
id: id,
name: name
})
}
}
function updatePolygonElement (id:string, name: string, coordinates:GeojsonCoordinate[][], color?:string) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
const element = elements[0]
const options = element.getOptions()
options.fillColor = color || normalColor
options.strokeColor = color || normalColor
element.setOptions(options)
} else {
initPolygon(name, coordinates, color, {
id: id,
name: name
})
}
}
function initTextInfo (content: string, coordinates: GeojsonCoordinate, id: string) {
const info = new AMap.Text({
text: content,
position: new AMap.LngLat(coordinates[0], coordinates[1]),
extData: { id: id, type: 'text' },
anchor: 'top-center',
style: {
background: 'none',
borderStyle: 'none',
fontSize: '16px',
},
})
AddOverlayGroup(info)
}
function initFlightAreaCircle (name: string, radius: number, position: GeojsonCoordinate, data: { id: string, type: EFlightAreaType, enable: boolean }) {
const circle = new AMap.Circle({
strokeColor: data.enable ? flightAreaColorMap[data.type] : disableColor,
strokeOpacity: 1,
strokeWeight: 6,
extData: data,
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === data.type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[data.type],
fillOpacity: EFlightAreaType.NFZ === data.type && data.enable ? 0.3 : 0,
radius: radius,
center: new AMap.LngLat(position[0], position[1]),
})
AddOverlayGroup(circle)
initTextInfo(name, position, data.id)
}
function updateFlightAreaCircle (id: string, name: string, radius: number, position: GeojsonCoordinate, enable: boolean, type: EFlightAreaType) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
let textIndex = elements.findIndex(ele => ele.getExtData()?.type === 'text')
if (textIndex === -1) {
textIndex = 1
initTextInfo(name, position, id)
} else {
const text = elements[textIndex]
text.setText(name)
text.setPosition(position)
}
const element = elements[textIndex ^ 1]
const options = element.getOptions()
options.fillOpacity = EFlightAreaType.NFZ === type && enable ? 0.3 : 0
options.strokeColor = enable ? flightAreaColorMap[type] : disableColor
options.radius = radius
options.center = new AMap.LngLat(position[0], position[1])
element.setOptions(options)
} else {
initFlightAreaCircle(name, radius, position, { id, type, enable })
}
}
function calcPolygonPosition (coordinate: GeojsonCoordinate[]): GeojsonCoordinate {
const index = coordinate.length - 1
return [(coordinate[0][0] + coordinate[index][0]) / 2.0, (coordinate[0][1] + coordinate[index][1]) / 2]
}
function initFlightAreaPolygon (name: string, coordinates: GeojsonCoordinate[], data: { id: string, type: EFlightAreaType, enable: boolean }) {
const path = [] as GeojsonCoordinate[]
coordinates.forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
const polygon = new AMap.Polygon({
path: path,
strokeColor: data.enable ? flightAreaColorMap[data.type] : disableColor,
strokeOpacity: 1,
strokeWeight: 4,
draggable: true,
extData: data,
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === data.type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[data.type],
fillOpacity: EFlightAreaType.NFZ === data.type && data.enable ? 0.3 : 0,
})
AddOverlayGroup(polygon)
initTextInfo(name, calcPolygonPosition(coordinates), data.id)
}
function updateFlightAreaPolygon (id: string, name: string, coordinates: GeojsonCoordinate[], enable: boolean, type: EFlightAreaType) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
let textIndex = elements.findIndex(ele => ele.getExtData()?.type === 'text')
if (textIndex === -1) {
textIndex = 1
initTextInfo(name, calcPolygonPosition(coordinates), id)
} else {
const text = elements[textIndex]
text.setText(name)
text.setPosition(calcPolygonPosition(coordinates))
}
const element = elements[textIndex ^ 1]
const options = element.getOptions()
const path = [] as GeojsonCoordinate[]
coordinates.forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
options.path = path
options.fillOpacity = EFlightAreaType.NFZ === type && enable ? 0.3 : 0
options.strokeColor = enable ? flightAreaColorMap[type] : disableColor
element.setOptions(options)
} else {
initFlightAreaPolygon(name, coordinates, { id, type, enable })
}
}
return {
init2DPin,
initPolyline,
initPolygon,
removeCoverFromMap,
getElementFromMap,
updatePinElement,
updatePolylineElement,
updatePolygonElement,
initFlightAreaCircle,
initFlightAreaPolygon,
updateFlightAreaPolygon,
updateFlightAreaCircle,
calcPolygonPosition,
}
}
================================================
FILE: src/hooks/use-g-map-tsa.ts
================================================
import store from '/@/store'
import { getRoot } from '/@/root'
import { ELocalStorageKey, EDeviceTypeName } from '/@/types'
import { getDeviceBySn } from '/@/api/manage'
import { message } from 'ant-design-vue'
import dockIcon from '/@/assets/icons/dock.png'
import rcIcon from '/@/assets/icons/rc.png'
import droneIcon from '/@/assets/icons/drone.png'
export function deviceTsaUpdate () {
const root = getRoot()
let AMap = root.$aMap
const icons = new Map([
[EDeviceTypeName.Aircraft, droneIcon],
[EDeviceTypeName.Gateway, rcIcon],
[EDeviceTypeName.Dock, dockIcon]
])
const markers = store.state.markerInfo.coverMap
const paths = store.state.markerInfo.pathMap
let trackLine = null as any
function getTrackLineInstance () {
if (!trackLine) {
trackLine = new AMap.Polyline({
map: root.$map,
strokeColor: '#939393' // 线颜色
})
}
return trackLine
}
function initIcon (type: number) {
return new AMap.Icon({
image: icons.get(type),
imageSize: new AMap.Size(40, 40),
size: new AMap.Size(40, 40)
})
}
function initMarker (type: number, name: string, sn: string, lng?: number, lat?: number) {
if (markers[sn]) {
return
}
if (root.$aMap === undefined) {
return
}
AMap = root.$aMap
markers[sn] = new AMap.Marker({
position: new AMap.LngLat(lng || 113.943225499, lat || 22.577673716),
icon: initIcon(type),
title: name,
anchor: 'top-center',
offset: [0, -20],
})
root.$map.add(markers[sn])
// markers[sn].on('moving', function (e: any) {
// let path = paths[sn]
// if (!path) {
// paths[sn] = e.passedPath
// return
// }
// path.push(e.passedPath[0])
// path.push(e.passedPath[1])
// getTrackLineInstance().setPath(path)
// })
}
function removeMarker (sn: string) {
if (!markers[sn]) {
return
}
root.$map.remove(markers[sn])
getTrackLineInstance().setPath([])
delete markers[sn]
delete paths[sn]
}
function addMarker (sn: string, lng?: number, lat?: number) {
getDeviceBySn(localStorage.getItem(ELocalStorageKey.WorkspaceId)!, sn)
.then(data => {
if (data.code !== 0) {
message.error(data.message)
return
}
initMarker(data.data.domain, data.data.nickname, sn, lng, lat)
})
}
function moveTo (sn: string, lng: number, lat: number) {
let marker = markers[sn]
if (!marker) {
addMarker(sn, lng, lat)
marker = markers[sn]
return
}
marker.moveTo([lng, lat], {
duration: 1800,
autoRotation: true
})
}
return {
marker: markers,
initMarker,
removeMarker,
moveTo
}
}
================================================
FILE: src/hooks/use-g-map.ts
================================================
import AMapLoader from '@amap/amap-jsapi-loader'
import { App, reactive } from 'vue'
import { AMapConfig } from '/@/constants/index'
export function useGMapManage () {
const state = reactive({
aMap: null, // Map类
map: null, // 地图对象
mouseTool: null,
})
async function initMap (container: string, app:App) {
AMapLoader.load({
...AMapConfig
}).then((AMap) => {
state.aMap = AMap
state.map = new AMap.Map(container, {
center: [113.943225499, 22.577673716],
zoom: 20
})
state.mouseTool = new AMap.MouseTool(state.map)
// 挂在到全局
app.config.globalProperties.$aMap = state.aMap
app.config.globalProperties.$map = state.map
app.config.globalProperties.$mouseTool = state.mouseTool
}).catch(e => {
console.log(e)
})
}
function globalPropertiesConfig (app:App) {
initMap('g-container', app)
}
return {
globalPropertiesConfig,
}
}
================================================
FILE: src/hooks/use-map-tool.ts
================================================
import { GeojsonCoordinate } from '../utils/genjson'
import { getRoot } from '/@/root'
export function useMapTool () {
const root = getRoot()
const map = root.$map
const AMap = root.$aMap
function panTo (coordinate: GeojsonCoordinate) {
map.panTo(coordinate, 100)
map.setZoom(18, false, 100)
}
return {
panTo,
}
}
================================================
FILE: src/hooks/use-mouse-tool.ts
================================================
import { reactive } from 'vue'
import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'
import { MapDoodleType } from '/@/constants/map'
import { getRoot } from '/@/root'
import { MapDoodleEnum } from '/@/types/map-enum'
import { EFlightAreaType } from '../types/flight-area'
import { message } from 'ant-design-vue'
export function useMouseTool () {
const root = getRoot()
const state = reactive({
pinNum: 0,
polylineNum: 0,
PolygonNum: 0,
currentType: '',
})
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
function drawPin (type:MapDoodleType, getDrawCallback:Function) {
root?.$mouseTool.marker({
title: type + state.pinNum,
icon: pin2d8cf0,
})
state.pinNum++
root?.$mouseTool.on('draw', getDrawCallback)
}
function drawPolyline (type:MapDoodleType, getDrawCallback:Function) {
root?.$mouseTool.polyline({
strokeColor: '#2d8cf0',
strokeOpacity: 1,
strokeWeight: 2,
strokeStyle: 'solid',
title: type + state.polylineNum++
})
root?.$mouseTool.on('draw', getDrawCallback)
}
function drawPolygon (type:MapDoodleType, getDrawCallback:Function) {
root?.$mouseTool.polygon({
strokeColor: '#2d8cf0',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791fc',
fillOpacity: 0.4,
title: type + state.PolygonNum++
})
root?.$mouseTool.on('draw', getDrawCallback)
}
function drawOff (type:MapDoodleType) {
root?.$mouseTool.close()
root?.$mouseTool.off('draw')
}
function drawFlightAreaPolygon (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
root?.$mouseTool.polygon({
strokeColor: flightAreaColorMap[type],
strokeOpacity: 1,
strokeWeight: 4,
extData: {
type: type,
mapType: 'polygon',
},
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[type],
fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,
})
root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
}
function drawFlightAreaCircle (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
root?.$mouseTool.circle({
strokeColor: flightAreaColorMap[type],
strokeOpacity: 1,
strokeWeight: 6,
extData: {
type: type,
mapType: 'circle',
},
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[type],
fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,
})
root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
}
function mouseTool (type: MapDoodleType, getDrawCallback: Function, flightAreaType?: EFlightAreaType) {
state.currentType = type
if (flightAreaType) {
switch (type) {
case MapDoodleEnum.POLYGON:
drawFlightAreaPolygon(flightAreaType, getDrawCallback)
return
case MapDoodleEnum.CIRCLE:
drawFlightAreaCircle(flightAreaType, getDrawCallback)
return
default:
message.error(`Invalid type: ${flightAreaType}`)
return
}
}
switch (type) {
case MapDoodleEnum.PIN:
drawPin(type, getDrawCallback)
break
case MapDoodleEnum.POLYLINE:
drawPolyline(type, getDrawCallback)
break
case MapDoodleEnum.POLYGON:
drawPolygon(type, getDrawCallback)
break
case MapDoodleEnum.Close:
drawOff(type)
break
}
}
return {
mouseTool
}
}
================================================
FILE: src/main.ts
================================================
import App from './App.vue'
import router from './router'
import { antComponents } from './antd'
import { CommonComponents } from './use-common-components'
import 'virtual:svg-icons-register'
import store, { storeKey } from './store'
import { createInstance } from '/@/root'
import { useDirectives } from './directives'
import '/@/styles/index.scss'
const app = createInstance(App)
app.use(store, storeKey)
app.use(router)
app.use(CommonComponents)
app.use(antComponents)
app.use(useDirectives)
app.mount('#demo-app')
================================================
FILE: src/mqtt/config.ts
================================================
import {
IClientOptions,
} from 'mqtt'
export const OPTIONS: IClientOptions = {
clean: true, // true: 清除会话, false: 保留会话
connectTimeout: 10000, // mqtt 超时时间
resubscribe: true, // 断开重连后,再次订阅原订阅
reconnectPeriod: 10000, // 重连间隔时间: 5s
keepalive: 1, // 心跳间隔时间:1s
}
================================================
FILE: src/mqtt/index.ts
================================================
import EventEmitter from 'eventemitter3'
import {
OPTIONS,
} from './config'
import {
connect,
MqttClient,
IClientPublishOptions,
IPublishPacket,
Packet,
ISubscriptionGrant,
IClientOptions,
} from 'mqtt/dist/mqtt.min'
export class UranusMqtt extends EventEmitter {
_url: string
_options?: IClientOptions
_client: MqttClient | null
_hasInit: boolean
constructor (url?: string, options?: IClientOptions) {
super()
this._url = url || ''
this._options = options
this._client = null
this._hasInit = false
}
initMqtt = () => {
// 仅初始化一次
if (this._hasInit) return
// 建立连接
this._client = connect(this._url, {
...OPTIONS,
...this._options,
})
this._hasInit = true
if (this._client) {
this._client.on('reconnect', this._onReconnect)
// 消息监听
this._client.on('message', this._onMessage)
// 连接关闭
this._client.on('close', this._onClose)
// 连接异常
this._client.on('error', this._onError)
}
}
// 发布
publishMqtt = (topic: string, body: string | Buffer, opts?: IClientPublishOptions) => {
if (!this._client?.connected) {
this.initMqtt()
}
this._client?.publish(topic, body, opts || {}, (error?: Error, packet?: Packet) => {
if (error) {
window.console.error('mqtt publish error,', error, packet)
}
})
}
// 订阅
subscribeMqtt = (topic: string) => {
if (!this._client?.connected) {
this.initMqtt()
}
window.console.log('subscribeMqtt>>>>>', topic)
this._client?.subscribe(topic, (error: Error, granted: ISubscriptionGrant[]) => {
window.console.log('mqtt subscribe,', error, granted)
})
}
// 取消订阅
unsubscribeMqtt = (topic: string) => {
window.console.log('mqtt unsubscribeMqtt,', topic)
this._client?.unsubscribe(topic)
}
// 关闭 mqtt 客户端
destroyed = () => {
window.console.log('mqtt destroyed')
this._client?.end()
}
_onReconnect = () => {
if (this._client) { window.console.error('mqtt reconnect,') }
}
_onMessage = (topic: string, payload: Buffer, packet: IPublishPacket) => {
this.emit('onMessageMqtt', { topic, payload, packet })
}
_onClose = () => {
// 连接异常关闭会自动重连
window.console.error('mqtt close,')
this.emit('onStatus', {
status: 'close',
})
}
_onError = (error: Error) => {
// 连接错误会自动重连
window.console.error('mqtt error,', error)
this.emit('onStatus', {
status: 'error',
data: error,
})
}
}
export {
IClientOptions,
IPublishPacket,
IClientPublishOptions,
}
================================================
FILE: src/pages/page-pilot/pilot-bind.vue
================================================
{{ drone.data.model }}
Bound
Bind
================================================
FILE: src/pages/page-pilot/pilot-home.vue
================================================
{{ workspaceName }}
{{ thingState }}
{{m.name}}:
{{ m.state.value }}
install
uninstall
Exit
Cancel
Exit
Data will not be synchronized between DJI Pilot and this server after exiting.
Serial Number
Remote Control Sn
{{ device.data.gateway_sn }}
Aircraft Sn
{{ device.data.sn }}
Settings
Device Binding
Aircraft bound
Aircraft not bound
Media File Upload
Livestream Manually
Open 3rd Party APP
================================================
FILE: src/pages/page-pilot/pilot-index.vue
================================================
Pilot Cloud API Demo
Login
================================================
FILE: src/pages/page-pilot/pilot-liveshare.vue
================================================
Before starting manually, please select the publish mode and livestream type
Select Video Publish Mode:
{{ item.label }}
Select Livestream Type:
{{ item.label }}
Param:
{{ rtmpParam }}
{{ rtspParam }}
{{ gb28181Param }}
{{ liveState }}
Frame Rate:{{ liveStreamStatus.fps }} fps
Video Bit Rate:{{ liveStreamStatus.videoBitRate }} kbps
Audio Bit Rate:{{ liveStreamStatus.audioBitRate }} kbps
Packet Loss Rate:{{ liveStreamStatus.dropRate }} %
RTT:{{ liveStreamStatus.rtt }} ms
Jitter:{{ liveStreamStatus.jitter }}
================================================
FILE: src/pages/page-pilot/pilot-media.vue
================================================
When enabled, photos and videos will be automatically uploaded to this server
Original Photo
Preview Photo
Path for uploading media resources in dual-controller mode
Mine
Another
================================================
FILE: src/pages/page-web/home.vue
================================================
================================================
FILE: src/pages/page-web/index.vue
================================================
================================================
FILE: src/pages/page-web/projects/Firmwares.vue
================================================
Click to Upload
{{ k }}
Import Firmware File
{{ text }}
{{ bytesToSize(record.file_size) }}
{{ text }}
================================================
FILE: src/pages/page-web/projects/devices.vue
================================================
Aircraft
Dock
{{ text }}
{{ record.firmware_version }}
Online
Offline
delete editableData[record.device_sn]" style="color: #e70102;">
{ deleteTip = true, deleteSn = record.device_sn }"/>
Delete device from workspace?
Delete devices
================================================
FILE: src/pages/page-web/projects/dock.vue
================================================
================================================
FILE: src/pages/page-web/projects/flight-area.vue
================================================
================================================
FILE: src/pages/page-web/projects/layer.vue
================================================
================================================
FILE: src/pages/page-web/projects/livestream.vue
================================================
================================================
FILE: src/pages/page-web/projects/media.vue
================================================
================================================
FILE: src/pages/page-web/projects/members.vue
================================================
delete editableData[record.user_id]" class="ml15" style="color: #e70102;">
================================================
FILE: src/pages/page-web/projects/task.vue
================================================
================================================
FILE: src/pages/page-web/projects/tsa.vue
================================================
My Username
{{ username }}
================================================
FILE: src/pages/page-web/projects/wayline.vue
================================================
================================================
FILE: src/pages/page-web/projects/workspace.vue
================================================
================================================
FILE: src/plugins/svgBuilder.ts
================================================
import { readFileSync, readdirSync } from 'fs'
let idPerfix = ''
const svgTitle = /', '')
arr.push(svg)
}
}
return arr
}
export const svgBuilder = (path: any, perfix = 'icon') => {
if (path === '') return
idPerfix = perfix
const res = svgFind(path)
console.log(res)
return {
name: 'svg-transform',
transformIndexHtml (dom: String) {
return dom.replace(
'',
``
)
}
}
}
================================================
FILE: src/root.ts
================================================
import { createApp, ComponentCustomProperties, App as VueApp } from 'vue'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$aMap: any // Map类
$map: any // 地图对象
$mouseTool: any
}
}
let root: ComponentCustomProperties
let app = null as any
export function createInstance (App: any): VueApp {
app = createApp(App)
root = app.config.globalProperties as ComponentCustomProperties
return app
}
export function getRoot (): ComponentCustomProperties {
return root
}
export function getApp (): VueApp {
return app
}
================================================
FILE: src/router/index.ts
================================================
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { ERouterName } from '/@/types/index'
import CreatePlan from '/@/components/task/CreatePlan.vue'
import WaylinePanel from '/@/pages/page-web/projects/wayline.vue'
import DockPanel from '/@/pages/page-web/projects/dock.vue'
import LiveAgora from '/@/components/livestream-agora.vue'
import LiveOthers from '/@/components/livestream-others.vue'
const routes: Array = [
{
path: '/',
redirect: '/' + ERouterName.PROJECT
},
// 首页
{
path: '/' + ERouterName.PROJECT,
name: ERouterName.PROJECT,
component: () => import('/@/pages/page-web/index.vue')
},
// members, devices
{
path: '/' + ERouterName.HOME,
name: ERouterName.HOME,
component: () => import('/@/pages/page-web/home.vue'),
children: [
{
path: '/' + ERouterName.MEMBERS,
name: ERouterName.MEMBERS,
component: () => import('/@/pages/page-web/projects/members.vue')
},
{
path: '/' + ERouterName.DEVICES,
name: ERouterName.DEVICES,
component: () => import('/@/pages/page-web/projects/devices.vue')
},
{
path: '/' + ERouterName.FIRMWARES,
name: ERouterName.FIRMWARES,
component: () => import('../pages/page-web/projects/Firmwares.vue')
}
]
},
// workspace
{
path: '/' + ERouterName.WORKSPACE,
name: ERouterName.WORKSPACE,
component: () => import('/@/pages/page-web/projects/workspace.vue'),
redirect: '/' + ERouterName.TSA,
children: [
{
path: '/' + ERouterName.TSA,
component: () => import('/@/pages/page-web/projects/tsa.vue')
},
{
path: '/' + ERouterName.LIVESTREAM,
name: ERouterName.LIVESTREAM,
component: () => import('/@/pages/page-web/projects/livestream.vue'),
children: [
{
path: ERouterName.LIVING,
name: ERouterName.LIVING,
components: {
LiveAgora,
LiveOthers
}
}
]
},
{
path: '/' + ERouterName.LAYER,
name: ERouterName.LAYER,
component: () => import('/@/pages/page-web/projects/layer.vue')
},
{
path: '/' + ERouterName.MEDIA,
name: ERouterName.MEDIA,
component: () => import('/@/pages/page-web/projects/media.vue')
},
{
path: '/' + ERouterName.WAYLINE,
name: ERouterName.WAYLINE,
component: () => import('/@/pages/page-web/projects/wayline.vue')
},
{
path: '/' + ERouterName.TASK,
name: ERouterName.TASK,
component: () => import('/@/pages/page-web/projects/task.vue'),
children: [
{
path: ERouterName.CREATE_PLAN,
name: ERouterName.CREATE_PLAN,
component: CreatePlan,
children: [
{
path: ERouterName.SELECT_PLAN,
name: ERouterName.SELECT_PLAN,
components: {
WaylinePanel,
DockPanel
}
}
]
}
]
},
{
path: '/' + ERouterName.FLIGHT_AREA,
name: ERouterName.FLIGHT_AREA,
component: () => import('/@/pages/page-web/projects/flight-area.vue')
},
]
},
// pilot
{
path: '/' + ERouterName.PILOT,
name: ERouterName.PILOT,
component: () => import('/@/pages/page-pilot/pilot-index.vue'),
},
{
path: '/' + ERouterName.PILOT_HOME,
component: () => import('/@/pages/page-pilot/pilot-home.vue')
},
{
path: '/' + ERouterName.PILOT_MEDIA,
component: () => import('/@/pages/page-pilot/pilot-media.vue')
},
{
path: '/' + ERouterName.PILOT_LIVESHARE,
component: () => import('/@/pages/page-pilot/pilot-liveshare.vue')
},
{
path: '/' + ERouterName.PILOT_BIND,
component: () => import('/@/pages/page-pilot/pilot-bind.vue')
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
================================================
FILE: src/shims-mqtt.d.ts
================================================
declare module 'mqtt/dist/mqtt.min' {
import MQTT from 'mqtt'
export = MQTT
}
================================================
FILE: src/shims-vue.d.ts
================================================
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
================================================
FILE: src/store/index.ts
================================================
import { InjectionKey } from 'vue'
import { ActionTree, createStore, GetterTree, MutationTree, Store, StoreOptions, useStore } from 'vuex'
import { EDeviceTypeName } from '../types'
import { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device'
import { getLayers } from '/@/api/layer'
import { LayerType } from '/@/types/mapLayer'
import { WaylineFile } from '/@/types/wayline'
import { DevicesCmdExecuteInfo } from '/@/types/device-cmd'
import { FlightAreaStatus } from '../api/flight-area'
const initStateFunc = () => ({
Layers: [
{
name: 'default',
id: '',
is_distributed: true,
elements: [],
is_check: false,
is_select: false,
type: 1
},
{
name: 'share',
id: '',
is_distributed: true,
elements: [],
is_check: false,
is_select: false,
type: 2
}
],
layerBaseInfo: {} as {
[key:string]:string
},
drawVisible: false,
livestreamOthersVisible: false,
livestreamAgoraVisible: false,
coverMap: {} as {
[key: string]: any[]
},
wsEvent: {
mapElementCreat: {},
mapElementUpdate: {},
mapElementDelete: {}
},
deviceStatusEvent: {
deviceOnline: {} as DeviceStatus,
deviceOffline: {}
},
markerInfo: {
coverMap: {} as {
[sn: string]: any
},
pathMap: {} as {
[sn: string]: any[]
}
},
deviceState: {
// remote controller, dock
gatewayInfo: {} as {
[sn: string]: GatewayOsd
},
// drone
deviceInfo: {} as {
[sn: string]: DeviceOsd
},
dockInfo: {} as {
[sn: string]: DockOsd
},
currentSn: '',
currentType: -1
},
osdVisible: { // osd 显示设备相关信息
sn: '',
callsign: '',
model: '',
visible: false,
gateway_sn: '',
is_dock: false,
payloads: null
} as OSDVisible,
waylineInfo: {
} as WaylineFile,
dockInfo: {
} as Device,
hmsInfo: {} as {
[sn: string]: DeviceHms[]
},
// 机场指令执行状态信息
devicesCmdExecuteInfo: {
} as DevicesCmdExecuteInfo,
mqttState: null as any, // mqtt 实例
clientId: '', // mqtt 连接 唯一客户端id
})
export type RootStateType = ReturnType
const getters: GetterTree = {
}
const mutations: MutationTree = {
SET_LAYER_INFO (state, info) {
state.Layers = info
},
SET_DEVICE_INFO (state, info) {
state.deviceState.deviceInfo[info.sn] = info.host
state.deviceState.currentSn = info.sn
state.deviceState.currentType = EDeviceTypeName.Aircraft
},
SET_GATEWAY_INFO (state, info) {
state.deviceState.gatewayInfo[info.sn] = info.host
state.deviceState.currentSn = info.sn
state.deviceState.currentType = EDeviceTypeName.Gateway
},
SET_DOCK_INFO (state, info) {
if (Object.keys(info.host).length === 0) {
return
}
if (!state.deviceState.dockInfo[info.sn]) {
state.deviceState.dockInfo[info.sn] = { } as DockOsd
}
state.deviceState.currentSn = info.sn
state.deviceState.currentType = EDeviceTypeName.Dock
const dock = state.deviceState.dockInfo[info.sn]
if (info.host.mode_code !== undefined) {
dock.basic_osd = info.host
return
}
if (info.host.wireless_link) {
dock.link_osd = info.host
return
}
if (info.host.job_number !== undefined) {
dock.work_osd = info.host
}
},
SET_DRAW_VISIBLE_INFO (state, bool) {
state.drawVisible = bool
},
SET_LIVESTREAM_OTHERS_VISIBLE (state, bool) {
state.livestreamOthersVisible = bool
},
SET_LIVESTREAM_AGORA_VISIBLE (state, bool) {
state.livestreamAgoraVisible = bool
},
SET_MAP_ELEMENT_CREATE (state, info) {
state.wsEvent.mapElementCreat = info
},
SET_MAP_ELEMENT_UPDATE (state, info) {
state.wsEvent.mapElementUpdate = info
},
SET_MAP_ELEMENT_DELETE (state, info) {
state.wsEvent.mapElementDelete = info
},
SET_DEVICE_ONLINE (state, info) {
state.deviceStatusEvent.deviceOnline = info
},
SET_DEVICE_OFFLINE (state, info) {
state.deviceStatusEvent.deviceOffline = info
delete state.deviceState.gatewayInfo[info.sn]
delete state.deviceState.deviceInfo[info.sn]
delete state.deviceState.dockInfo[info.sn]
delete state.hmsInfo[info.sn]
// delete state.markerInfo.coverMap[info.sn]
// delete state.markerInfo.pathMap[info.sn]
},
SET_OSD_VISIBLE_INFO (state, info) {
state.osdVisible = info
},
SET_SELECT_WAYLINE_INFO (state, info) {
state.waylineInfo = info
},
SET_SELECT_DOCK_INFO (state, info) {
state.dockInfo = info
},
SET_DEVICE_HMS_INFO (state, info) {
const hmsList: Array = state.hmsInfo[info.sn]
state.hmsInfo[info.sn] = info.host.concat(hmsList ?? [])
},
SET_DEVICES_CMD_EXECUTE_INFO (state, info) { // 保存设备指令ws消息推送
if (!info.sn) {
return
}
if (state.devicesCmdExecuteInfo[info.sn]) {
const index = state.devicesCmdExecuteInfo[info.sn].findIndex(cmdExecuteInfo => cmdExecuteInfo.biz_code === info.biz_code)
if (index >= 0) {
// 丢弃前面的消息
if (state.devicesCmdExecuteInfo[info.sn][index].timestamp > info.timestamp) {
return
}
state.devicesCmdExecuteInfo[info.sn][index] = info
} else {
state.devicesCmdExecuteInfo[info.sn].push(info)
}
} else {
state.devicesCmdExecuteInfo[info.sn] = [info]
}
},
SET_MQTT_STATE (state, mqttState) {
state.mqttState = mqttState
},
SET_CLIENT_ID (state, clientId) {
state.clientId = clientId
},
}
const actions: ActionTree = {
async getAllElement ({ commit }) {
const result = await getLayers({
groupId: '',
isDistributed: true
})
commit('SET_LAYER_INFO', result.data?.list)
console.log(result)
},
updateElement ({ state }, content: {type: 'is_check' | 'is_select', id: string, bool:boolean}) {
const key = content.id.replaceAll('resource__', '')
const type = content.type
const layers = state.Layers
const layer = layers.find(item => item.id === key)
if (layer) {
layer[type] = content.bool
}
},
setLayerInfo ({ state }, layers) {
// const layers = state.Layers
const obj:{
[key:string]:string
} = {}
layers.forEach(layer => {
if (layer.type === LayerType.Default) {
obj.default = layer.id
} else {
if (layer.type === LayerType.Share) {
obj.share = layer.id
}
}
})
state.layerBaseInfo = obj
console.log('state.layerBaseInfo', state.layerBaseInfo)
},
getLayerInfo ({ state }, id:string) {
return state.layerBaseInfo[id]
}
}
const storeOptions: StoreOptions = {
state: initStateFunc,
getters,
mutations,
actions
}
const rootStore = createStore(storeOptions)
export default rootStore
export const storeKey: InjectionKey> = Symbol('')
type AllStateStoreTypes = RootStateType & {
// moduleName: moduleType
}
export function useMyStore () {
return useStore(storeKey)
}
================================================
FILE: src/styles/common.scss
================================================
html, body, #app, #my-app {
height: 100%;
overflow: hidden;
}
body {
background-color: #f7f9fa;
-webkit-font-smoothing: antialiased;
// Prevent font enlargement in horizontal screen
text-size-adjust: 100%;
font-family: sans-serif, Roboto, sans-serif-medium, Arial;
font-feature-settings: normal;
color: $main-text-color;
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
::-webkit-scrollbar {
width: 8px;
height: 8px;
background: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
border: none;
background: rgb(89, 89, 89);
}
}
================================================
FILE: src/styles/flex.style.scss
================================================
.flex-display {
display: flex;
}
.flex-column {
@extend .flex-display;
flex-direction: column;
}
.flex-row {
@extend .flex-display;
flex-direction: row;
}
.flex-align-start {
align-items: flex-start;
}
.flex-align-end {
align-items: flex-end;
}
.flex-align-baseline {
align-items: baseline;
}
.flex-align-stretch {
align-items: stretch;
}
.flex-align-center {
align-items: center;
}
.flex-justify-start {
justify-content: flex-start;
}
.flex-justify-end {
justify-content: flex-end;
}
.flex-justify-center {
justify-content: center;
}
.flex-justify-between {
justify-content: space-between;
}
.flex-justify-around {
justify-content: space-around;
}
.flex-1 {
flex: 1;
}
.flex-shrink-0 {
flex-shrink: 0;
}
.flex-shrink-1 {
flex-shrink: 1;
}
//width
.width-100vw {
width: 100vw;
}
.width-100 {
width: 100%;
}
//height
.height-100vh {
height: 100vh;
}
.height-100 {
height: 100%;
}
//margin
m-5 {
margin: -5px !important;
}
.mt-5 {
margin-top: -5px !important;
}
.mt100 {
margin-top: 100px !important;
}
.mt110 {
margin-top: 110px !important;
}
.mb-5 {
margin-bottom: -5px !important;
}
.ml-5 {
margin-left: -5px !important;
}
.mr-5 {
margin-right: -5px !important;
}
.m0 {
margin: 0px !important;
}
.mt0 {
margin-top: 0px !important;
}
.mb0 {
margin-bottom: 0px !important;
}
.ml0 {
margin-left: 0px !important;
}
.mr0 {
margin-right: 0px !important;
}
.m5 {
margin: 5px !important;
}
.mt5 {
margin-top: 5px !important;
}
.mb5 {
margin-bottom: 5px !important;
}
.ml5 {
margin-left: 5px !important;
}
.mr5 {
margin-right: 5px !important;
}
.m10 {
margin: 10px !important;
}
.mt10 {
margin-top: 10px !important;
}
.mb10 {
margin-bottom: 10px !important;
}
.ml10 {
margin-left: 10px !important;
}
.mr10 {
margin-right: 10px !important;
}
.m15 {
margin: 15px !important;
}
.mt15 {
margin-top: 15px !important;
}
.mb15 {
margin-bottom: 15px !important;
}
.ml15 {
margin-left: 15px !important;
}
.mr15 {
margin-right: 15px !important;
}
.m20 {
margin: 20px !important;
}
.mt20 {
margin-top: 20px !important;
}
.mb20 {
margin-bottom: 20px !important;
}
.ml20 {
margin-left: 20px !important;
}
.mr20 {
margin-right: 20px !important;
}
.m25 {
margin: 25px !important;
}
.mt25 {
margin-top: 25px !important;
}
.mb25 {
margin-bottom: 25px !important;
}
.ml25 {
margin-left: 25px !important;
}
.mr25 {
margin-right: 25px !important;
}
.m30 {
margin: 30px !important;
}
.mt30 {
margin-top: 30px !important;
}
.mb30 {
margin-bottom: 30px !important;
}
.ml30 {
margin-left: 30px !important;
}
.ml40 {
margin-left: 40px !important;
}
.mr30 {
margin-right: 30px !important;
}
.m50 {
margin: 50px !important;
}
.mt50 {
margin-top: 50px !important;
}
.mb50 {
margin-bottom: 50px !important;
}
.ml50 {
margin-left: 50px !important;
}
.mr50 {
margin-right: 50px !important;
}
// padding值
.p0 {
padding: 0 !important;
}
.pt0 {
padding-top: 0 !important;
}
.pr0 {
padding-right: 0 !important;
}
.pb0 {
padding-bottom: 0 !important;
}
.pl0 {
padding-left: 0 !important;
}
.p5 {
padding: 5px;
}
.pt5 {
padding-top: 5px;
}
.pr5 {
padding-right: 5px;
}
.pb5 {
padding-bottom: 5px;
}
.pl5 {
padding-left: 5px;
}
.p10 {
padding: 10px;
}
.pt10 {
padding-top: 10px;
}
.pr10 {
padding-right: 10px;
}
.pb10 {
padding-bottom: 10px;
}
.pl10 {
padding-left: 10px;
}
.p15 {
padding: 15px;
}
.pt15 {
padding-top: 15px;
}
.pr15 {
padding-right: 15px;
}
.pb15 {
padding-bottom: 15px;
}
.pl15 {
padding-left: 15px;
}
.p20 {
padding: 20px;
box-sizing: border-box;
}
.pt20 {
padding-top: 20px;
}
.pr20 {
padding-right: 20px;
}
.pb20 {
padding-bottom: 20px;
}
.pl20 {
padding-left: 20px;
}
.p30 {
padding: 30px;
box-sizing: border-box;
}
.pt30 {
padding-top: 30px;
}
.pr30 {
padding-right: 30px;
}
.pb30 {
padding-bottom: 30px;
}
.pl30 {
padding-left: 30px;
}
.pb50 {
padding-bottom: 50px;
}
.pl50 {
padding-left: 50px;
}
.pl120 {
padding-left: 120px;
}
.pl150 {
padding-left: 150px;
}
.pt50 {
padding-top: 50px;
}
================================================
FILE: src/styles/fonts.scss
================================================
$font-family-sans-serif: 'Open Sans', BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', SimSun, sans-serif;
$line-heights: (
12: 20px,
14: 22px,
16: 24px,
18: 26px
);
// 用法: @include text(12)
@mixin text($size) {
font-size: #{$size}px;
line-height: map-get($line-heights, $size);
}
// 常规体
@mixin text-regular {
font-weight: 400;
}
// 中粗体
@mixin text-semibold {
font-weight: 600;
}
@mixin ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fz10 {
font-size: 10px;
}
.fz12 {
font-size: 12px;
}
.fz14 {
font-size: 14px;
}
.fz16 {
font-size: 16px;
}
.fz18 {
font-size: 18px;
}
.fz20 {
font-size: 20px;
}
.fz22 {
font-size: 22px;
}
.fz24 {
font-size: 24px;
}
.fz26 {
font-size: 26px;
}
.fz28 {
font-size: 28px;
}
.fz30 {
font-size: 30px;
}
.fz32 {
font-size: 32px;
}
.fz35 {
font-size: 35px;
}
================================================
FILE: src/styles/index.scss
================================================
@import './common.scss';
@import 'flex.style.scss';
@import 'fonts.scss';
================================================
FILE: src/styles/reset.scss
================================================
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin : 0;
padding : 0;
border : 0;
font : inherit;
font-size : 100%;
vertical-align: baseline;
}
html {
line-height : 1;
// -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
ol,
ul {
list-style: none;
}
table {
border-collapse: collapse;
border-spacing : 0;
}
caption,
th,
td {
font-weight : normal;
vertical-align: middle;
}
q,
blockquote {
quotes: none;
}
q::before,
q::after,
blockquote::before,
blockquote::after {
content: '';
content: none;
}
a img {
border: none;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section,
summary {
display: block;
}
* {
box-sizing: content-box;
}
a {
background : transparent;
text-decoration: none;
}
button,
input[type='number'],
input[type='text'],
input[type='password'],
input[type='email'],
input[type='search'],
select,
textarea {
font-family : inherit;
margin : 0;
-webkit-appearance: none;
}
================================================
FILE: src/styles/variables.scss
================================================
$main-text-color: #000;
$header-height: 52px;
// Auxiliary color
$info: #1fa3f6;
$success: #28d445;
$danger: #e70102;
$alarm: #ffcc00;
$warning: #ffcc00;
$error: #e70102;
// 品牌色
$primary: #2d8cf0;
$primary-click: #2b85e4;
$primary-hover: #5cadff;
$primary-hover-dropdown: rgba($primary-hover, 0.2);
$primary-disabled: #274d75;
// 辅助色拓展
$danger-hover: #ff4d4e;
$danger-active: #d40001;
$menu-primary: #464c5b; // tab菜单主题色
// 图标颜色
$ic-white-normal: #fff;
$ic-black-normal: #4e4e4e;
$ic-hover: #a7a7a7;
$ic-disabled: #5f5f5f;
$ic-selected: #1088f2;
// 中性色-黑
$dark-bg-light: #868688; // 背景色浅色
$dark-btn-hover: #5d5f61; // 按钮 hover 色
$dark-border: #4f4f4f; // 边框色
$dark-btn-disabled: #3c3c3c; // 按钮主色禁用
$dark-border-secondary: #393939; // 第二边框色
$dark-basic-primary: #232323; // 第一基础底色
$dark-basic-secondary: #282828; // 第二基础底色
$dark-highlight: #232323; // 高亮色
$dark-disable: #444444; // 置灰底色
$dark-project-disabled: #292929;
// 中性色-白
$light-bg-primary: #fff; // 1 背景
$light-bg-secondary: #f7f9fa; // 2 背景
$light-divider: #e8eaec; // 3 分割线
$light-border: #dcdee2; // 4 边框
$light-disabled: #c5c8ce; // 5 失效
$light-auxiliary: #808695; // 6 辅助图标
$light-main-text: #515a6e; // 7 正文 ?用处
$light-title: #17233d; // 8 标题 ?用处
$light-bg-menu: #3b3e40; // 9 菜单栏背景色 ?用处
$light-border-secondary: #e8e8e8; // 第二边框色
// 字体
// 白色
$text-white-basic: #fff; // 基础色
$text-white-main: rgba($text-white-basic, 1); // 正文
$text-white-secondary: rgba($text-white-basic, 0.45); // 次级
$text-white-disabled: rgba($text-white-basic, 0.25); // 置灰
// 黑色
$text-black-basic: #000000; // 基础色
$text-black-emphasize: rgba($text-black-basic, 0.85); // 强调
$text-black-main: rgba($text-black-basic, 0.65); // 正文
$text-black-secondary: rgba($text-black-basic, 0.45); // 次要
$text-black-disabled: rgba($text-black-basic, 0.25); // 置灰
$text-link: $primary;
$text-danger: $danger;
// 标签
$tag-green: #19be6b;
// 滚动条等颜色
$scroll-bar: #c5c8ce;
$scroll-bar-dark: #5f5f5f;
// 选择框
$select-disabled: #d8d8d8;
================================================
FILE: src/types/airport-tsa.ts
================================================
// 机场存储容量:总容量(单位:KB)、已使用(单位:KB)
export interface AirportStorage {
total: number, // 单位:KB
used: number
}
// 舱盖状态
export enum CoverStateEnum {
Close = 0, // 关闭
Open = 1, // 打开
HalfOpen = 2, // 半打开
Failed = 3 // 失败
}
// 推杆状态
export enum PutterStateEnum {
Close = 0, // 关闭
Open = 1, // 打开
HalfOpen = 2, // 半打开
Failed = 3 // 失败
}
// 充电状态
export enum ChargeStateEnum {
NotCharge = 0, // 空闲
Charge = 1, // 正在充电
}
export interface DroneChargeState {
state: ChargeStateEnum,
capacity_percent: string,
}
// 补光灯状态
export enum SupplementLightStateEnum {
Close = 0, // 关闭
Open = 1, // 打开
}
// 机场声光报警状态
export enum AlarmModeEnum {
CLOSE = 0, // 关闭
OPEN = 1, // 开启
}
// 电池保养
export enum BatteryStoreModeEnum {
BATTERY_PLAN_STORE = 1, // 电池计划存储策略
BATTERY_EMERGENCY_STORE = 2, // 电池应急存储策略
}
// 飞行器电池保养
export enum DroneBatteryStateEnum {
NoMaintenanceRequired = 0, // 0-无需保养
MaintenanceRequired = 1, // 1-待保养
MaintenanceInProgress = 2, // 2-正在保养
}
export enum DroneBatteryModeEnum {
CLOSE = 0, // 关闭
OPEN = 1, // 开启
}
// 4g链路连接状态
export enum FourGLinkStateEnum {
CLOSE = 0, // 断开
OPEN = 1, // 连接
}
// Sdr链路连接状态
export enum SdrLinkStateEnum {
CLOSE = 0, // 断开
OPEN = 1, // 连接
}
// 机场的图传链路模式
export enum LinkWorkModeEnum {
SDR = 0, // sdr模式
FourG_FUSION_MODE = 1, // 4G融合模式
}
================================================
FILE: src/types/device-cmd.ts
================================================
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum, LinkWorkModeEnum } from '/@/types/airport-tsa'
// 机场指令集
export enum DeviceCmd {
// 简单指令
DebugModeOpen = 'debug_mode_open', // 调试模式开启
DebugModeClose = 'debug_mode_close', // 调试模式关闭
SupplementLightOpen = 'supplement_light_open', // 打开补光灯
SupplementLightClose = 'supplement_light_close', // 关闭补光灯
ReturnHome = 'return_home', // 一键返航
ReturnHomeCancel = 'return_home_cancel', // 取消返航
// 复杂指令
DeviceReboot = 'device_reboot', // 机场重启
DroneOpen = 'drone_open', // 飞行器开机
DroneClose = 'drone_close', // 飞行器关机
// DeviceCheck = 'device_check', // 一键排障(一键起飞自检)
DeviceFormat = 'device_format', // 机场数据格式化
DroneFormat = 'drone_format', // 飞行器数据格式化
CoverOpen = 'cover_open', // 打开舱盖
CoverClose = 'cover_close', // 关闭舱盖
PutterOpen = 'putter_open', // 推杆展开
PutterClose = 'putter_close', // 推杆闭合
ChargeOpen = 'charge_open', // 打开充电
ChargeClose = 'charge_close', // 关闭充电
AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警
BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养
DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养
SdrWorkModeSwitch = 'sdr_workmode_switch', // 增强图传
}
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum | LinkWorkModeEnum
export interface DeviceCmdItem{
label: string, // 标题
status: string, // 当前状态
operateText: string, // 按钮文字
cmdKey: DeviceCmd, // 请求指令
oppositeCmdKey?: DeviceCmd, // 相反状态指令
action?: DeviceCmdItemAction, // 参数
func: string, // 处理函数
loading: boolean // 按钮loading
disabled?: boolean // 按钮disabled
}
export const noDebugCmdList: DeviceCmdItem[] = [
{
label: 'Return Home',
status: '--',
operateText: 'Return Home',
cmdKey: DeviceCmd.ReturnHome,
func: 'returnHome',
loading: false,
},
{
label: 'Return Home Cancel',
status: '--',
operateText: 'Return Home Cancel',
cmdKey: DeviceCmd.ReturnHomeCancel,
func: 'returnHomeCancel',
loading: false,
}
]
// 机场指令
export const cmdList: DeviceCmdItem[] = [
{
// iconName: ,
label: '机场系统',
status: '工作中',
operateText: '重启',
cmdKey: DeviceCmd.DeviceReboot,
func: 'deviceReboot',
loading: false,
// btnAnimationIconName: '',
// operateTips: '',
// statusColor: '',
},
{
label: '飞行器',
status: '关机',
operateText: '开机',
cmdKey: DeviceCmd.DroneOpen,
oppositeCmdKey: DeviceCmd.DroneClose,
func: 'droneStatus',
loading: false,
},
{
label: '舱盖',
status: '关',
operateText: '开启',
cmdKey: DeviceCmd.CoverOpen,
oppositeCmdKey: DeviceCmd.CoverClose,
func: 'coverStatus',
loading: false,
},
{
label: '推杆',
status: '闭合',
operateText: '展开',
cmdKey: DeviceCmd.PutterOpen,
oppositeCmdKey: DeviceCmd.PutterClose,
func: 'putterStatus',
loading: false,
},
{
label: '充电状态',
status: '未充电',
operateText: '充电',
cmdKey: DeviceCmd.ChargeOpen,
oppositeCmdKey: DeviceCmd.ChargeClose,
func: 'chargeStatus',
loading: false,
},
{
label: '机场存储',
status: '--',
operateText: '格式化',
cmdKey: DeviceCmd.DeviceFormat,
func: 'deviceFormat',
loading: false,
},
{
label: '飞行器存储',
status: '--',
operateText: '格式化',
cmdKey: DeviceCmd.DroneFormat,
func: 'droneFormat',
loading: false,
},
{
label: '补光灯',
status: '关',
operateText: '打开',
cmdKey: DeviceCmd.SupplementLightOpen,
oppositeCmdKey: DeviceCmd.SupplementLightClose,
func: 'supplementLightStatus',
loading: false,
},
{
label: '机场声光报警',
status: '关',
operateText: '打开',
cmdKey: DeviceCmd.AlarmStateSwitch,
action: AlarmModeEnum.OPEN,
func: 'alarmState',
loading: false,
},
{
label: '机场电池存储模式',
status: '计划',
operateText: '应急',
cmdKey: DeviceCmd.BatteryStoreModeSwitch,
action: BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE,
func: 'batteryStoreMode',
loading: false,
},
{
label: '飞机电池保养',
status: '--',
operateText: '保养',
cmdKey: DeviceCmd.DroneBatteryModeSwitch,
action: DroneBatteryModeEnum.OPEN,
func: 'droneBatteryMode',
loading: false,
disabled: true,
},
{
label: '4g 增强',
status: '--',
operateText: '开启',
cmdKey: DeviceCmd.SdrWorkModeSwitch,
action: LinkWorkModeEnum.FourG_FUSION_MODE,
func: 'sdrWorkMode',
loading: false,
},
]
export enum DeviceCmdStatusText {
DeviceRebootNormalText = '工作中',
DeviceRebootInProgressText = '重启中...',
DeviceRebootFailedText = '重启失败',
DroneStatusOpenNormalText = '开',
DroneStatusOpenInProgressText = '开机中...',
DroneStatusOpenFailedText = '关',
DroneStatusOpenBtnText = '关机',
DroneStatusCloseNormalText = '关',
DroneStatusCloseInProgressText = '关机中...',
DroneStatusCloseFailedText = '开',
DroneStatusCloseBtnText = '开机',
DeviceCoverOpenNormalText = '开',
DeviceCoverOpenInProgressText = '开启中...',
DeviceCoverOpenFailedText = '关',
DeviceCoverOpenBtnText = '关闭',
DeviceCoverCloseNormalText = '关',
DeviceCoverCloseInProgressText = '关闭中...',
DeviceCoverCloseFailedText = '开',
DeviceCoverCloseBtnText = '开启',
DevicePutterOpenNormalText = '展开',
DevicePutterOpenBtnText = '闭合',
DevicePutterOpenInProgressText = '推杆展开中',
DevicePutterOpenFailedText = '闭合',
DevicePutterCloseNormalText = '闭合',
DevicePutterCloseInProgressText = '推杆闭合中',
DevicePutterCloseFailedText = '展开',
DevicePutterCloseBtnText = '展开',
DeviceChargeOpenNormalText = '充电',
DeviceChargeOpenInProgressText = '充电中...',
DeviceChargeOpenFailedText = '未充电',
DeviceChargeOpenBtnText = '断电',
DeviceChargeCloseNormalText = '断电',
DeviceChargeCloseInProgressText = '断电中...',
DeviceChargeCloseFailedText = '充电',
DeviceChargeCloseBtnText = '充电',
DeviceFormatInProgressText = '格式化...',
DeviceFormatFailedText = '格式化失败',
DroneFormatInProgressText = '格式化...',
DroneFormatFailedText = '格式化失败',
DeviceSupplementLightOpenNormalText = '开',
DeviceSupplementLightOpenInProgressText = '开启中...',
DeviceSupplementLightOpenFailedText = '关',
DeviceSupplementLightOpenBtnText = '关闭',
DeviceSupplementLightCloseNormalText = '关',
DeviceSupplementLightCloseText = '关闭中...',
DeviceSupplementLightCloseFailedText = '开',
DeviceSupplementLightCloseBtnText = '打开',
AlarmStateOpenNormalText = '开',
AlarmStateOpenText = '开启中...',
AlarmStateOpenFailedText = '关',
AlarmStateOpenBtnText = '关闭',
AlarmStateCloseNormalText = '关',
AlarmStateCloseText = '关闭中...',
AlarmStateCloseFailedText = '开',
AlarmStateCloseBtnText = '打开',
BatteryStoreModePlanNormalText = '计划',
BatteryStoreModePlanText = '切换中...',
BatteryStoreModePlanFailedText = '应急',
BatteryStoreModePlanBtnText = '应急',
BatteryStoreModeEmergencyNormalText = '应急',
BatteryStoreModeEmergencyText = '切换中...',
BatteryStoreModeEmergencyFailedText = '计划',
BatteryStoreModeEmergencyBtnText = '计划',
DroneBatteryModeMaintenanceInProgressText = '保养中',
DroneBatteryModeMaintenanceNotNeedText = '无需保养',
DroneBatteryModeMaintenanceNeedText = '需保养',
DroneBatteryModeOpenBtnText = '保养',
DroneBatteryModeCloseBtnText = '关闭保养',
SdrWorkModeFourGOpenNormalText = '开',
SdrWorkModeFourGOpenText = '开启中...',
SdrWorkModeFourGOpenFailedText = '--',
SdrWorkModeFourGOpenBtnText = '关闭',
SdrWorkModeFourGCloseNormalText = '--',
SdrWorkModeFourGCloseText = '关闭中...',
SdrWorkModeFourGCloseFailedText = '开',
SdrWorkModeFourCloseBtnText = '开启',
}
// cmd ws 消息状态
export enum DeviceCmdExecuteStatus {
Sent = 'sent', // 已下发
InProgress = 'in_progress', // 执行中
OK = 'ok', // 执行成功
Failed = 'failed', // 失败
Canceled = 'canceled', // 取消
Timeout = 'timeout' // 超时
}
export interface DeviceCmdExecuteInfo {
biz_code: string,
timestamp: number,
sn: string,
bid: string,
output:{
status: DeviceCmdExecuteStatus,
progress?: {
percent: number,
step_key: string,
step_result: number
},
ext?: {
rate?: number
}
}
result: number,
}
// 所有机场的指令执行状态
export interface DevicesCmdExecuteInfo {
[key: string]: DeviceCmdExecuteInfo[], // sn --- DeviceCmdExecuteInfo
}
================================================
FILE: src/types/device-firmware.ts
================================================
export interface Firmware {
firmware_id: string
file_name: string
product_version: string
file_size: number
device_name: string[]
username: string
release_note: string
released_time: string
firmware_status: boolean
}
export enum FirmwareStatusEnum {
NONE = 'All',
FALSE = 'Disabled',
TRUE = 'Available'
}
export interface FirmwareQueryParam {
product_version: string
device_name: string
firmware_status: FirmwareStatusEnum
}
export interface FirmwareUploadParam {
device_name: string[]
release_note: string
status: boolean
}
export enum DeviceNameEnum {
DJI_DOCK = 'DJI Dock',
DJI_DOCK2 = 'DJI Dock2',
MATRICE_30 = 'Matrice 30',
MATRICE_30T = 'Matrice 30T',
M3D = 'M3D',
M3TD = 'M3TD',
}
================================================
FILE: src/types/device-log.ts
================================================
import { DOMAIN } from '/@/types/device'
import { commonColor } from '/@/utils/color'
// 日志上传状态
export enum DeviceLogUploadStatusEnum {
Uploading = 1, // 上传中
Done = 2, // 完成
Canceled = 3, // 取消
Failed = 4, // 失败
}
export const DeviceLogUploadStatusMap = {
[DeviceLogUploadStatusEnum.Uploading]: '上传中',
[DeviceLogUploadStatusEnum.Done]: '上传成功',
[DeviceLogUploadStatusEnum.Canceled]: '取消上传',
[DeviceLogUploadStatusEnum.Failed]: '上传失败',
}
export const DeviceLogUploadStatusColor = {
[DeviceLogUploadStatusEnum.Uploading]: commonColor.BLUE,
[DeviceLogUploadStatusEnum.Done]: commonColor.NORMAL,
[DeviceLogUploadStatusEnum.Canceled]: commonColor.WARN,
[DeviceLogUploadStatusEnum.Failed]: commonColor.FAIL,
}
// 设备日志上传 ws 消息状态
export enum DeviceLogUploadStatus {
FilePull = 'file_pull', // 拉取日志 可以作为 正在处理中
FileZip = 'file_zip', // 拉取日志,日志压缩可以作为 正在处理中
FileUploading = 'file_uploading', // 正在上传
Canceled = 'canceled', // 取消
Timeout = 'timeout', // 超时
Failed = 'failed', // 失败
OK = 'ok', // 上传成功
// Paused = 'paused' // 暂停
}
export interface DeviceLogUploadInfo {
sn: string,
bid: string,
output:{
logs_id: string
status: DeviceLogUploadStatus,
files: {
device_sn: string,
device_model_domain: DOMAIN,
progress: number,
result: number,
upload_rate: number,
status: DeviceLogUploadStatus
}[]
}
result: number,
}
// ws status => log status
export const DeviceLogUploadWsStatusMap = {
[DeviceLogUploadStatus.FilePull]: DeviceLogUploadStatusEnum.Uploading,
[DeviceLogUploadStatus.FileZip]: DeviceLogUploadStatusEnum.Uploading,
[DeviceLogUploadStatus.FileUploading]: DeviceLogUploadStatusEnum.Uploading,
[DeviceLogUploadStatus.OK]: DeviceLogUploadStatusEnum.Done,
[DeviceLogUploadStatus.Failed]: DeviceLogUploadStatusEnum.Failed,
[DeviceLogUploadStatus.Canceled]: DeviceLogUploadStatusEnum.Canceled,
[DeviceLogUploadStatus.Timeout]: DeviceLogUploadStatusEnum.Failed,
}
================================================
FILE: src/types/device-setting.ts
================================================
// 夜航灯开关
export enum NightLightsStateEnum {
CLOSE = 0, // 0-关闭
OPEN = 1, // 1-打开
}
// 限远开关
export enum DistanceLimitStatusEnum {
UNSET = 0, // 0-未设置
SET = 1, // 1-已设置
}
export interface DistanceLimitStatus {
state?: DistanceLimitStatusEnum;
distance_limit?: number; // 限远
}
// 避障
export enum ObstacleAvoidanceStatusEnum {
CLOSE = 0, // 0-关闭
OPEN = 1, // 1-开启
}
export interface ObstacleAvoidance {
horizon?: ObstacleAvoidanceStatusEnum;// 水平避障开关
upside?: ObstacleAvoidanceStatusEnum;// 上行方向避障开关
downside?: ObstacleAvoidanceStatusEnum;// 下行方向避障开关
}
// 设备管理设置key
export enum DeviceSettingKeyEnum {
NIGHT_LIGHTS_MODE_SET = 'night_lights_state', // 夜航灯开关
HEIGHT_LIMIT_SET = 'height_limit', // 限高设置
DISTANCE_LIMIT_SET = 'distance_limit_status', // 限远开关
OBSTACLE_AVOIDANCE_HORIZON = 'obstacle_avoidance_horizon', // 水平避障状态
OBSTACLE_AVOIDANCE_UPSIDE = 'obstacle_avoidance_upside', // 上视避障状态
OBSTACLE_AVOIDANCE_DOWNSIDE = 'obstacle_avoidance_downside', // 下视避障状态
}
export type DeviceSettingType = Record
export const initDeviceSetting = {
[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET]:
{
label: '飞行器夜航灯',
value: '',
trueValue: NightLightsStateEnum.CLOSE,
editable: false,
popConfirm: {
visible: false,
loading: false,
// content: '为保证飞行器的作业安全,建议打开夜航灯',
label: '飞行器夜航灯',
},
settingKey: DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET,
},
[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET]:
{
label: '限高',
value: '',
trueValue: 120,
editable: false,
popConfirm: {
visible: false,
loading: false,
// content: '限高:20 - 1500m',
// info: '修改限高会影响当前机场的所有作业任务,建议确认作业情况后再进行修改',
label: '限高',
},
settingKey: DeviceSettingKeyEnum.HEIGHT_LIMIT_SET,
},
[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET]:
{
label: '限远',
value: '',
trueValue: DistanceLimitStatusEnum.UNSET,
// info: '限远(15 - 8000m)是约束飞行器相对机场的最大作业距离',
editable: false,
popConfirm: {
visible: false,
loading: false,
// content: '限远 (15- 8000m) 是约束飞行器相对机场的最大作业距离',
// info: '修改限远会影响当前机场的所有作业任务,建议确认作业情况后再进行修改',
label: '限远',
},
settingKey: DeviceSettingKeyEnum.DISTANCE_LIMIT_SET,
},
[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON]:
{
label: '水平避障',
value: '',
trueValue: ObstacleAvoidanceStatusEnum.CLOSE,
// info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置',
editable: false,
popConfirm: {
visible: false,
loading: false,
// content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启',
label: '水平避障',
},
settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON,
},
[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE]:
{
label: '上视避障',
value: '',
trueValue: ObstacleAvoidanceStatusEnum.CLOSE,
// info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置',
editable: false,
popConfirm: {
visible: false,
loading: false,
// content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启',
label: '上视避障',
},
settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE,
},
[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE]:
{
label: '下视避障',
value: '',
trueValue: ObstacleAvoidanceStatusEnum.CLOSE,
// info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置',
editable: false,
popConfirm: {
visible: false,
loading: false,
// content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启',
label: '下视避障',
},
settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE,
},
} as DeviceSettingType
export const initDeviceSettingFormModel = {
nightLightsState: false, // 夜航灯开关
heightLimit: 20, // 限高设置
distanceLimitStatus: { state: false, distanceLimit: 15 }, // 限远开关
obstacleAvoidanceHorizon: false, // 飞行器避障-水平开关设置
obstacleAvoidanceUpside: false, // 飞行器避障-上视开关设置
obstacleAvoidanceDownside: false, // 飞行器避障-下视开关设置
}
export type DeviceSettingFormModel = typeof initDeviceSettingFormModel
================================================
FILE: src/types/device.ts
================================================
import { commonColor } from '/@/utils/color'
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum, FourGLinkStateEnum, SdrLinkStateEnum, LinkWorkModeEnum } from './airport-tsa'
import { CameraMode } from '/@/types/live-stream'
export interface DeviceValue {
key: string; // 'domain-type-subtype'
domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种
type: number; // 设备类型枚举
sub_type: number; // 设备类型枚举 负载一般表示镜头
}
// domain
export enum DOMAIN {
DRONE = '0', // 飞行器
PAYLOAD = '1', // 负载
RC = '2', // 遥控
DOCK = '3', // 机场
}
// DJI飞机类型
export enum DRONE_TYPE {
M30 = 67,
M300 = 60,
Mavic3EnterpriseAdvanced= 77,
M350 = 89,
M3D = 91,
}
// DJI负载类型枚举值
export enum PAYLOAD_TYPE {
FPV = 39,
H20 = 42,
H20T = 43,
H20N = 61,
EP600 = 50,
EP800 = 90742,
M30D = 52,
M30T = 53,
XT2 = 26,
XTS = 41,
Z30 = 20,
DockTopCamera = 165,
M3E = 66,
M3T = 67,
M3D = 80,
M3TD = 81,
// UNKNOWN = 65535
}
// RC type
export enum RC_TYPE {
RC = 56,
RCPlus = 119,
RC144 = 144,
}
// DOCK type
export enum DOCK_TYPE {
Dock = 1,
Dock2 = 2,
}
// 设备sub_type 从0升序
export enum DEVICE_SUB_TYPE {
ZERO,
ONE,
TWO,
THREE,
UNKNOWN = 65535,
}
export const DEVICE_MODEL_KEY = {
M30: `${DOMAIN.DRONE}-${DRONE_TYPE.M30}-${DEVICE_SUB_TYPE.ZERO}`,
M30T: `${DOMAIN.DRONE}-${DRONE_TYPE.M30}-${DEVICE_SUB_TYPE.ONE}`,
M3E: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ZERO}`,
M3T: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ONE}`,
M300: `${DOMAIN.DRONE}-${DRONE_TYPE.M300}-${DEVICE_SUB_TYPE.ZERO}`,
M350: `${DOMAIN.DRONE}-${DRONE_TYPE.M350}-${DEVICE_SUB_TYPE.ZERO}`,
M3D: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,
M3TD: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ONE}`,
FPV: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.FPV}-${DEVICE_SUB_TYPE.ZERO}`,
H20: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20}-${DEVICE_SUB_TYPE.ZERO}`,
H20T: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20T}-${DEVICE_SUB_TYPE.ZERO}`,
H20N: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20N}-${DEVICE_SUB_TYPE.ZERO}`,
EP600: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.EP600}-${DEVICE_SUB_TYPE.UNKNOWN}`,
EP800: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.EP800}-${DEVICE_SUB_TYPE.ZERO}`,
M30Camera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M30D}-${DEVICE_SUB_TYPE.ZERO}`,
M30TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M30T}-${DEVICE_SUB_TYPE.ZERO}`,
M3ECamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3E}-${DEVICE_SUB_TYPE.ZERO}`,
M3TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3T}-${DEVICE_SUB_TYPE.ZERO}`,
M3DCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,
M3TDCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3TD}-${DEVICE_SUB_TYPE.ZERO}`,
// M3MCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3M}-${DEVICE_SUB_TYPE.ZERO}`,
XT2: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XT2}-${DEVICE_SUB_TYPE.ZERO}`,
XTS: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XTS}-${DEVICE_SUB_TYPE.ZERO}`,
Z30: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.Z30}-${DEVICE_SUB_TYPE.ZERO}`,
DockTopCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.DockTopCamera}-${DEVICE_SUB_TYPE.ZERO}`,
RC: `${DOMAIN.RC}-${RC_TYPE.RC}-${DEVICE_SUB_TYPE.ZERO}`,
RCPlus: `${DOMAIN.RC}-${RC_TYPE.RCPlus}-${DEVICE_SUB_TYPE.ZERO}`,
Dock: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock}-${DEVICE_SUB_TYPE.ZERO}`,
Dock2: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock2}-${DEVICE_SUB_TYPE.ZERO}`,
}
export const DEVICE_NAME = {
// drone
[DEVICE_MODEL_KEY.M30]: 'M30',
[DEVICE_MODEL_KEY.M30T]: 'M30T',
[DEVICE_MODEL_KEY.M3E]: 'Mavic 3E',
[DEVICE_MODEL_KEY.M3T]: 'Mavic 3T',
// [DEVICE_MODEL_KEY.M3M]: 'Mavic 3M',
[DEVICE_MODEL_KEY.M300]: 'M300 RTK',
[DEVICE_MODEL_KEY.M350]: 'M350 RTK',
[DEVICE_MODEL_KEY.M3D]: 'M3D',
[DEVICE_MODEL_KEY.M3TD]: 'M3TD',
// payload
[DEVICE_MODEL_KEY.FPV]: 'FPV',
[DEVICE_MODEL_KEY.H20]: 'H20',
[DEVICE_MODEL_KEY.H20T]: 'H20T',
[DEVICE_MODEL_KEY.H20N]: 'H20N',
[DEVICE_MODEL_KEY.EP600]: 'P1',
[DEVICE_MODEL_KEY.EP800]: 'L1',
[DEVICE_MODEL_KEY.M30Camera]: 'M30 Camera',
[DEVICE_MODEL_KEY.M30TCamera]: 'M30T Camera',
[DEVICE_MODEL_KEY.M3ECamera]: 'Mavic 3E',
[DEVICE_MODEL_KEY.M3TCamera]: 'Mavic 3T',
// [DEVICE_MODEL_KEY.M3MCamera]: 'Mavic 3M',
[DEVICE_MODEL_KEY.XT2]: 'XT2',
[DEVICE_MODEL_KEY.XTS]: 'XTS',
[DEVICE_MODEL_KEY.Z30]: 'Z30',
[DEVICE_MODEL_KEY.DockTopCamera]: 'Dock Camera',
[DEVICE_MODEL_KEY.M3DCamera]: 'M3D Camera',
[DEVICE_MODEL_KEY.M3TDCamera]: 'M3TD Camera',
// rc
[DEVICE_MODEL_KEY.RC]: 'RC',
[DEVICE_MODEL_KEY.RCPlus]: 'RC Plus',
// dock
[DEVICE_MODEL_KEY.Dock]: 'Dock',
[DEVICE_MODEL_KEY.Dock2]: 'Dock2',
}
// 控制权
export enum ControlSource {
A = 'A',
B = 'B'
}
export interface PayloadInfo {
index: number,
model: string,
control_source?: ControlSource,
payload_sn?: string,
payload_index?: string,
payload_name?: string,
}
// 设备信息
export interface OnlineDevice {
model: string,
callsign: string,
sn: string,
mode: number,
gateway: {
model: string,
callsign: string,
sn: string,
domain: string,
},
payload: PayloadInfo[]
}
// 固件升级类型
export enum DeviceFirmwareTypeEnum {
ToUpgraded = 3, // 普通升级
ConsistencyUpgrade =2, // 一致性升级
}
// 固件升级状态
export enum DeviceFirmwareStatusEnum {
None = 1, // 无需升级
ToUpgraded = 2, // 待升级
ConsistencyUpgrade = 3, // 一致性升级
DuringUpgrade = 4, // 升级中
}
export const DeviceFirmwareStatus = {
[DeviceFirmwareStatusEnum.None]: '',
[DeviceFirmwareStatusEnum.ToUpgraded]: '待升级',
[DeviceFirmwareStatusEnum.ConsistencyUpgrade]: '一致性升级',
[DeviceFirmwareStatusEnum.DuringUpgrade]: '升级中',
}
export const DeviceFirmwareStatusColor = {
[DeviceFirmwareStatusEnum.None]: commonColor.WHITE,
[DeviceFirmwareStatusEnum.ToUpgraded]: commonColor.BLUE,
[DeviceFirmwareStatusEnum.ConsistencyUpgrade]: commonColor.WARN,
[DeviceFirmwareStatusEnum.DuringUpgrade]: commonColor.NORMAL,
}
export interface Device {
device_name: string,
device_sn: string,
nickname: string,
firmware_version: string,
firmware_status: DeviceFirmwareStatusEnum,
status: string,
workspace_name: string,
bound_time: string,
login_time: string,
children?: Device[],
domain: number,
type: number,
firmware_progress?: string, // 升级进度
}
export interface DeviceStatus {
sn: string,
online_status: boolean,
device_callsign: string,
user_id: string,
user_callsign: string
bound_status: boolean,
model: string,
gateway_sn: string,
domain: number
}
export interface OSDVisible {
sn: string,
model: string,
callsign: string,
visible: boolean,
is_dock: boolean,
gateway_sn: string,
gateway_callsign: string,
payloads: null | PayloadInfo [],
}
export interface GatewayOsd {
capacity_percent: string,
transmission_signal_quality: string,
longitude: number,
latitude: number,
}
export interface OsdCameraLiveview {
bottom: number,
left: number,
right: number,
top: number,
}
export interface DeviceOsdCamera {
camera_mode: CameraMode,
payload_index: string,
photo_state: number,
record_time: number,
recording_state: number,
remain_photo_num: number,
remain_record_duration: number,
liveview_world_region: OsdCameraLiveview
}
export interface DeviceOsd {
longitude: number,
latitude: number,
gear: number,
mode_code: number,
height: string,
home_distance: string,
horizontal_speed: string,
vertical_speed: string,
wind_speed: string,
wind_direction: string,
elevation: string,
position_state: {
gps_number: string,
is_fixed: number,
rtk_number: string
},
battery: {
capacity_percent: string,
landing_power: string,
remain_flight_time: number,
return_home_power: string,
},
night_lights_state?: NightLightsStateEnum;// 夜航灯开关
height_limit?: number;// 限高设置
distance_limit_status?: DistanceLimitStatus;// 限远开关
obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
cameras?: DeviceOsdCamera[]
}
export enum NetworkStateTypeEnum {
FOUR_G = 1,
ETHERNET = 2,
}
export enum NetworkStateQualityEnum {
NO_SIGNAL = 0,
BAD = 1,
POOR = 2,
FAIR = 3,
GOOD = 4,
EXCELLENT = 5,
}
export enum RainfallEnum {
NONE = 0,
LIGHT_RAIN = 1,
MODERATE_RAIN = 2,
HEAVY_RAIN = 3,
}
export enum DroneInDockEnum {
OUTSIDE, INSIDE
}
export interface DockBasicOsd {
network_state?: {
type: NetworkStateTypeEnum,
quality: number,
rate: number,
},
drone_charge_state?: {
state: number,
capacity_percent: number,
},
drone_in_dock: boolean,
rainfall: RainfallEnum,
wind_speed: number,
environment_temperature: number,
temperature: number,
humidity: number,
latitude: number,
longitude: number,
height: number,
alternate_land_point?: {
latitude: number,
longitude: number,
height: number,
safe_land_height: number,
is_configured: number
}
first_power_on: number,
positionState?: {
gps_number: number,
is_fixed: number,
rtk_number: number,
is_calibration: number,
quality: number,
},
storage?: {
total: number,
used: number,
},
mode_code: number,
cover_state: number,
supplement_light_state: number,
emergency_stop_state: number,
air_conditioner?: {
air_conditioner_state: number,
switch_time: number,
}
battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式
alarm_state?: AlarmModeEnum; // 机场声光报警状态
putter_state: number,
sub_device?: {
device_sn?: string,
device_model_key?: string,
device_online_status: number,
device_paired: number,
},
// live_capacity?: LiveCapacity; // 直播能力
// live_status?: Array; // 直播状态
}
export enum DrcStateEnum {
DISCONNECT = 0,
CONNECTING = 1,
CONNECTED = 2
}
export interface DockLinkOsd {
drc_state: DrcStateEnum,
flighttask_prepare_capacity: number,
flighttask_step_code: number,
media_file_detail?: {
remain_upload: number
},
sdr?: {
up_quality: string,
down_quality: string,
frequency_band: number,
},
wireless_link?:{ // 图传链路<会包括4G和sdr信息
dongle_number: number, // dongle 数量
['4g_link_state']: FourGLinkStateEnum, // 4g_link_state
sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态
link_workmode: LinkWorkModeEnum, // 图传链路模式
sdr_quality: number, // sdr信号质量 0-5
['4g_quality']: number, // 4G信号质量 0-5
['4g_freq_band']: number,
['4g_gnd_quality']: number,
['4g_uav_quality']: number,
sdr_freq_band: number,
}
}
export interface MaintainStatus {
state: number,
last_maintain_type: number,
last_maintain_time: number,
last_maintain_work_sorties: number,
}
export interface DockWorkOsd {
job_number: number,
acc_time: number,
activation_time: number,
maintain_status?: {
maintain_status_array: MaintainStatus[]
}
electric_supply_voltage: number,
working_voltage: string,
working_current: string,
backup_battery?: {
voltage: number,
temperature: number,
switch: number,
}
drone_battery_maintenance_info?: { // 飞行器电池保养信息
maintenance_state: DroneBatteryStateEnum, // 保养状态
maintenance_time_left: number, // 电池保养剩余时间(小时)
}
}
export interface DockOsd {
basic_osd: DockBasicOsd,
link_osd: DockLinkOsd,
work_osd: DockWorkOsd
}
export enum EModeCode {
Standby,
Preparing,
Ready,
Manual,
Automatic,
Waypoint,
Panoramic,
Active_Track,
ADS_B,
Return_To_Home,
Landing,
Forced_Landing,
Three_Blades_Landing,
Upgrading,
Disconnected,
}
export enum EGear {
A,
P,
NAV,
FPV,
FARM,
S,
F,
M,
G,
T
}
export enum EDockModeCode {
Disconnected = -1,
Idle,
Debugging,
Remote_Debugging,
Upgrading,
Working,
}
export interface DeviceHms {
hms_id: string,
tid: string,
bid: string,
sn: string,
level: number,
module: number,
key: string,
message_en: string,
message_zh: string,
create_time: string,
update_time: string,
domain: number
}
// TODO: 设备拓扑管理优化
// 设备osd信息
export interface DeviceInfoType {
gateway: GatewayOsd, // 遥控器
dock: DockOsd, // 机场
device: DeviceOsd, // 飞机
}
================================================
FILE: src/types/drc.ts
================================================
export enum DRC_METHOD {
HEART_BEAT = 'heart_beat',
DRONE_CONTROL = 'drone_control', // 飞行控制-虚拟摇杆
DRONE_EMERGENCY_STOP = 'drone_emergency_stop', // 急停
OSD_INFO_PUSH = 'osd_info_push', // 高频osd信息上报
HSI_INFO_PUSH = 'hsi_info_push', // 避障信息上报
DELAY_TIME_INFO_PUSH = 'delay_info_push', // 图传链路延时信息上报
}
// 手动控制
export interface DroneControlProtocol {
x?: number; // 水平方向速度,正值为A指令 负值为D指令 单位:m/s
y?: number; // 前进后退方向速度,正值为W指令 负值为S指令 单位:m/s
h?: number;// 上下高度值,正值为上升指令 负值为下降指令 单位:m
w?: number; // 机头角速度,正值为顺时针,负值为逆时针 单位:degree/s (web端暂无此设计)
seq?: number; // 从0计时
}
// 低延时osd
export interface DRCOsdInfo {
attitude_head: number;// 飞机姿态head角,单位:度
latitude: number;// 飞机经纬度
longitude: number;
altitude: number;
speed_x: number;
speed_y: number;
speed_z: number;
gimbal_pitch: number;// 云台pitch角
gimbal_roll: number;// 云台roll角
gimbal_yaw: number;// 云台yaw角
}
// 态势感知-HSI
export interface DRCHsiInfo {
up_distance: number;// 上方的障碍物距离,单位:mm
down_distance: number;// 下方的障碍物距离,单位:mm
around_distances: number[]; // 水平方向观察点,分布在[0,360)区间,表示障碍物与飞机距离,单位为mm。 0对应机头方向正前方,顺时针分布,例如0度为机头正前方,90度为飞机正右方
up_enable: boolean; // 上视避障开关状态,true:已开启 false:已关闭
up_work: boolean; // 上视避障工作状态,true:正常工作 false:异常或离线
down_enable: boolean; // 下视避障开关状态,true:已开启 false:已关闭
down_work: boolean; // 下视避障工作状态,true:正常工作 false:异常或离线
left_enable: boolean; // 左视避障开关状态,true:已开启 false:已关闭
left_work: boolean; // 左视避障工作状态,true:正常工作 false:异常或离线
right_enable: boolean; // 右视避障开关状态,true:已开启 false:已关闭
right_work: boolean; // 右视避障工作状态,true:正常工作 false:异常或离线
front_enable: boolean; // 前视避障开关状态,true:已开启 false:已关闭
front_work: boolean; // 前视避障工作状态,true:正常工作 false:异常或离线
back_enable: boolean; // 后视避障开关状态,true:已开启 false:已关闭
back_work: boolean; // 后视避障工作状态,true:正常工作 false:异常或离线
vertical_enable: boolean; // 垂直方向综合开关状态,当本协议中上、下视开关状态均为true时,输出true:已开启,否则输出false:已关闭
vertical_work: boolean; // 垂直方向避障工作状态,当本协议中上、下视工作均为true时,输出true:正常工作,否则输出false:异常或离线
horizontal_enable: boolean; // 水平方向综合开关状态,当本协议中前、后、左、右、开关状态均为true时,输出true:已开启,否则输出false:已关闭
horizontal_work: boolean; // 水平方向避障工作综合状态,当本协议中前、后、左、右视工作均为true时,输出true:正常工作,否则输出false:异常或离线
}
export interface LiveViewDelayItem {
video_id: string;
liveview_delay_time: number;
}
// 链路时延信息
export interface DRCDelayTimeInfo {
sdr_cmd_delay: number; // sdr链路命令延时,单位:ms
liveview_delay_list: LiveViewDelayItem[];
}
export interface DrcResponseInfo {
result: number;
output: {
seq: number
}
}
================================================
FILE: src/types/drone-control.ts
================================================
import { ControlSource } from './device'
import { ECommanderModeLostAction, ERthMode, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight } from '/@/api/drone-control/drone'
export enum ControlSourceChangeType {
Flight = 1,
Payload = 2,
}
// 控制权变化消息
export interface ControlSourceChangeInfo {
sn: string,
type: ControlSourceChangeType,
control_source: ControlSource
}
// 飞向目标点结果
export interface FlyToPointMessage {
sn: string,
result: number,
message: string,
}
// 一键起飞结果
export interface TakeoffToPointMessage {
sn: string,
result: number,
message: string,
}
// 设备端退出drc模式
export interface DrcModeExitNotifyMessage {
sn: string,
result: number,
message: string,
}
// 飞行控制模式状态
export interface DrcStatusNotifyMessage {
sn: string,
result: number,
message: string,
}
export const WaylineLostControlActionInCommandFlightOptions = [
{ label: 'Continue', value: WaylineLostControlActionInCommandFlight.CONTINUE },
{ label: 'Execute Lost Action', value: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION }
]
export const LostControlActionInCommandFLightOptions = [
{ label: 'Return Home', value: LostControlActionInCommandFLight.RETURN_HOME },
{ label: 'Hover', value: LostControlActionInCommandFLight.HOVER },
{ label: 'Landing', value: LostControlActionInCommandFLight.Land }
]
export const RthModeInCommandFlightOptions = [
{ label: 'Smart Height', value: ERthMode.SMART },
{ label: 'Setting Height', value: ERthMode.SETTING }
]
export const CommanderModeLostActionInCommandFlightOptions = [
{ label: 'Continue', value: ECommanderModeLostAction.CONTINUE },
{ label: 'Execute Lost Action', value: ECommanderModeLostAction.EXEC_LOST_ACTION }
]
export const CommanderFlightModeInCommandFlightOptions = [
{ label: 'Smart Height', value: ERthMode.SMART },
{ label: 'Setting Height', value: ERthMode.SETTING }
]
// 云台重置模式
export enum GimbalResetMode {
Recenter = 0,
Down = 1,
RecenterGimbalPan = 2,
PitchDown = 3,
}
export const GimbalResetModeOptions = [
{ label: 'Gimbal Recenter', value: GimbalResetMode.Recenter },
{ label: 'Gimbal down', value: GimbalResetMode.Down },
{ label: 'Recenter Gimbal Pan', value: GimbalResetMode.RecenterGimbalPan },
{ label: 'Gimbal Pitch Down', value: GimbalResetMode.PitchDown }
]
================================================
FILE: src/types/enums.ts
================================================
export enum ERouterName {
ELEMENT = 'element',
PROJECT = 'project',
HOME = 'home',
TSA = 'tsa',
LAYER = 'layer',
MEDIA = 'media',
WAYLINE = 'wayline',
LIVESTREAM = 'livestream',
LIVING = 'living',
WORKSPACE = 'workspace',
MEMBERS = 'members',
DEVICES = 'devices',
TASK = 'task',
CREATE_PLAN = 'create-plan',
SELECT_PLAN = 'select-plan',
FIRMWARES = 'firmwares',
FLIGHT_AREA = 'flight-area',
PILOT = 'pilot-login',
PILOT_HOME = 'pilot-home',
PILOT_MEDIA = 'pilot-media',
PILOT_LIVESHARE = 'pilot-liveshare',
PILOT_BIND = 'pilot-bind'
}
export enum EStorageKey {
LANG_CODE = 'DJI_CREATE_VITE_H5_APP:lang_code',
TEST_TOOLS_POSITION_STORAGE_KEY = 'DJI_CREATE_VITE_H5_APP:test_tools_position',
SESSION_ID = 'DJI_CREATE_VITE_H5_APP:sess'
}
export enum EStatusValue {
CONNECTED = 'Connected',
DISCONNECT = 'Disconnect',
LIVING = 'Living'
}
export enum ELiveStatusValue {
DISCONNECT,
CONNECTED,
LIVING
}
export enum EComponentName {
Thing = 'thing',
Liveshare = 'liveshare',
Api = 'api',
Ws = 'ws',
Map = 'map',
Tsa = 'tsa',
Media = 'media',
Mission = 'mission'
}
export enum ELocalStorageKey {
Username = 'username',
WorkspaceId = 'workspace_id',
Token = 'x-auth-token',
PlatformName = 'platform_name',
WorkspaceName = 'workspace_name',
WorkspaceDesc = 'workspace_desc',
Flag = 'flag',
UserId = 'user_id',
Device = 'device',
GatewayOnline = 'gateway_online',
}
export enum EPhotoType {
Original = 0,
Preview = 1,
Unknown = -1
}
export enum EDownloadOwner {
Mine = 0,
Others = 1,
Unknown = -1
}
export enum EUserType {
Web = 1,
Pilot = 2,
}
export enum EBizCode {
GatewayOsd = 'gateway_osd',
DeviceOsd = 'device_osd',
DockOsd = 'dock_osd',
MapElementCreate = 'map_element_create',
MapElementUpdate = 'map_element_update',
MapElementDelete = 'map_element_delete',
DeviceOnline = 'device_online',
DeviceOffline = 'device_offline',
DeviceHms = 'device_hms',
// 机场任务
FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度
FlightTaskMediaProgress = 'file_upload_callback', // 机场任务媒体上传进度
FlightTaskMediaHighestPriority = 'highest_priority_upload_flighttask_media', // 机场任务媒体优先级上报
// 设备指令
DeviceReboot = 'device_reboot', // 机场重启
DroneOpen = 'drone_open', // 飞行器开机
DroneClose = 'drone_close', // 飞行器关机
DeviceFormat = 'device_format', // 机场数据格式化
DroneFormat = 'drone_format', // 飞行器数据格式化
CoverOpen = 'cover_open', // 打开舱盖
CoverClose = 'cover_close', // 关闭舱盖
PutterOpen = 'putter_open', // 推杆展开
PutterClose = 'putter_close', // 推杆闭合
ChargeOpen = 'charge_open', // 打开充电
ChargeClose = 'charge_close', // 关闭充电
// 设备升级
DeviceUpgrade = 'ota_progress', // 设备升级
// 设备日志
DeviceLogUploadProgress = 'fileupload_progress', // 设备日志上传
// 飞行指令消息
ControlSourceChange = 'control_source_change', // 控制权更新
FlyToPointProgress = 'fly_to_point_progress', // 飞向目标点
TakeoffToPointProgress = 'takeoff_to_point_progress', // 一键起飞
JoystickInvalidNotify = 'joystick_invalid_notify', // 设备端退出drc模式
DrcStatusNotify = 'drc_status_notify', // 飞行控制模式状态
// custom flight area
FlightAreasSyncProgress = 'flight_areas_sync_progress',
FlightAreasDroneLocation = 'flight_areas_drone_location',
FlightAreasUpdate = 'flight_areas_update',
}
export enum EDeviceTypeName {
Aircraft = 0,
Gateway = 2,
Dock = 3,
}
export enum EHmsLevel {
NOTICE,
CAUTION,
WARN,
}
================================================
FILE: src/types/flight-area.ts
================================================
import { GeojsonCoordinate, GeojsonPolygon } from '../utils/genjson'
export enum EFlightAreaType {
NFZ = 'nfz',
DFENCE = 'dfence',
}
export enum EGeometryType {
CIRCLE = 'Circle',
POLYGON = 'Polygon',
}
export enum EFlightAreaUpdate {
ADD = 'add',
UPDATE = 'update',
DELETE = 'delete',
}
export enum ESyncStatus {
WAIT_SYNC = 'wait_sync',
SWITCH_FAIL = 'switch_fail',
SYNCHRONIZING = 'synchronizing',
SYNCHRONIZED = 'synchronized',
FAIL = 'fail',
}
export interface GeojsonCircle {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: EGeometryType.CIRCLE
coordinates: GeojsonCoordinate
radius: number
}
}
export interface DroneLocation {
area_distance: number,
area_id: string,
is_in_area: boolean,
}
export interface FlightAreasDroneLocation {
drone_locations: DroneLocation[]
}
export type FlightAreaContent = GeojsonCircle | GeojsonPolygon
export interface FlightAreaUpdate {
operation: EFlightAreaUpdate,
area_id: string,
name: string,
type: EFlightAreaType,
content: FlightAreaContent,
status: boolean,
username: string,
create_time: number,
update_time: number,
}
export interface FlightAreaSyncProgress {
sn: string,
result: number,
status: ESyncStatus,
message: string,
}
export const FlightAreaTypeTitleMap = {
[EFlightAreaType.NFZ]: {
[EGeometryType.CIRCLE]: 'Circular GEO Zone',
[EGeometryType.POLYGON]: 'Polygonal GEO Zone',
},
[EFlightAreaType.DFENCE]: {
[EGeometryType.CIRCLE]: 'Circular Task Area',
[EGeometryType.POLYGON]: 'Polygonal Task Area',
},
}
================================================
FILE: src/types/index.ts
================================================
export * from './enums'
================================================
FILE: src/types/live-stream.ts
================================================
export interface LiveStreamStatus {
audioBitRate: number,
dropRate: number,
fps: number,
jitter: number,
quality: number,
rtt: number,
status: number,
type: number,
videoBitRate: number
}
export interface GB28181Param {
serverIp: string,
serverPort: string,
serverId: string,
agentId: string,
password: string,
agentPort: string,
agentChannel: string
}
export interface RTSPParam {
userName: string,
password: string,
port: string
}
export interface LiveConfigParam {
params: number,
type: any
}
export enum EVideoPublishType {
VideoOnDemand = 'video-on-demand',
VideoByManual = 'video-by-manual',
VideoDemandAuxManual = 'video-demand-aux-manual'
}
export enum ELiveTypeValue {
Unknown,
Agora,
RTMP,
RTSP,
GB28181
}
export enum ELiveTypeName {
Unknown = 'Unknown',
Agora = 'Agora',
RTMP = 'RTMP',
RTSP = 'RTSP',
GB28181 = 'GB28181'
}
export enum CameraMode {
Photo = 0, // 拍照
Video = 1, // 录像
}
// 镜头类型
export enum VideoType {
NORMAL = 'normal',
WIDE = 'wide',
ZOOM = 'zoom',
IR = 'ir'
}
// 镜头类型
export enum CameraType {
WIDE = 'wide',
ZOOM = 'zoom',
IR = 'ir'
}
export const CameraTypeOptions = [
{ label: CameraType.WIDE, value: CameraType.WIDE },
{ label: CameraType.ZOOM, value: CameraType.ZOOM },
{ label: CameraType.IR, value: CameraType.IR },
]
export const ZoomCameraTypeOptions = [
{ label: CameraType.ZOOM, value: CameraType.ZOOM },
{ label: CameraType.IR, value: CameraType.IR },
]
export interface VideoListItem {
video_index: string;
video_type: VideoType;
switchable_video_types?: Array;
}
export interface CameraListItem {
available_video_number: number;
camera_index: string;
camera_name: string;
coexist_video_number_max: number;
video_list: VideoListItem[];
// 自定义
switchCamera?: boolean;
content?: string;
// 该camera由哪个控上报的
camera_carrier_sns?: string[];
}
export interface DeviceListItem {
sn: string;
available_video_number: number;
coexist_video_number_max: number;
camera_list: CameraListItem[];
}
// export interface LiveCapacity {
// available_video_number: number;
// coexist_video_number_max: number;
// device_list: DeviceListItem[];
// }
// export interface LiveStatus {
// live_time: number; // 直播时间 该路码流已推流时间 unit: s
// live_trendline: number; // 直播带宽的使用状态 代表直播性能趋势,0-4表示overuse,其中,数值越小,表示overuse程度越大,5表示normal状态,6~10表示underuse,其中,数值越大,表示有更多比例的带宽未能充分利用
// video_id: string; // 直播码流标识符 某路在推视频码流的标识符,格式为 #{uav_sn}/#{camera_id}/#{video_index}
// video_quality: number; // 直播码流的质量 0: 自动, 1: 流畅, 2: 高清, 3: 超清
// error_status?: number; // 设备端当前状态,是错误码,需要匹配到文案上
// }
================================================
FILE: src/types/map-enum.ts
================================================
export enum MapDoodleEnum {
PIN = 'pin',
POLYLINE = 'polyline',
POLYGON = 'polygon',
Close = 'off',
CIRCLE = 'circle',
}
================================================
FILE: src/types/map.d.ts
================================================
export interface MapGeographicPosition {
longitude: number;
latitude: number;
height?: number;
}
export enum LayerType {
Normal,
Default,
Share
}
export interface pinAMapPosition {
KL: number
className: string
kT: number
lng: number
lat: number
}
export enum ResourceStatus {
NotShow,
Show
}
export type GeojsonCoordinate = [number, number, number?]
export interface GeojsonLine {
type: 'Feature'
properties: {
color: string
directConnected?: boolean
}
geometry: {
type: 'LineString'
coordinates: GeojsonCoordinate[]
}
}
export interface GeojsonPolygon {
type: 'Feature'
properties: {
color: string
}
geometry: {
type: 'Polygon'
coordinates: GeojsonCoordinate[][]
}
}
export interface GeojsonPoint {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: 'Point'
coordinates: GeojsonCoordinate
}
}
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint
interface ResourceObjectBasic {
user_name: string
user_id?: string
type:0| 1 | 2
content: unknown
}
export interface PinResource extends ResourceObjectBasic {
type: 0
content: GeojsonFeature
}
export type ResourceObject = PinResource
export enum LayerElevationLoadStatus {
Unload,
Load
}
export interface LayerResource {
id: string
name: string
order: number
status: ResourceStatus
resource: ResourceObject | null
display: number
create_time: number
elevation_load_status?: LayerElevationLoadStatus //
}
export interface Layer {
id: string
name: string
order: number
create_time: number
type: LayerType
is_distributed: boolean
is_lock: boolean
elements: null | LayerResource[],
is_check?: boolean
is_select?: boolean
}
================================================
FILE: src/types/mapLayer.ts
================================================
import { MapElementEnum } from '/@/constants/map'
export interface mapLayerStyle {
background: string
}
export interface mapLayerChildren {
key: string
style: mapLayerStyle
title: string
obj: any
}
export interface mapLayerChildrenObj {
className: string
key: string
name: string
type: string
}
// 拖拽事件
export interface DropEvent {
node: {
eventKey: string
pos: string
$parent: any
}
dragNode: {
eventKey: string
}
dropPosition: number
dropToGap: boolean
}
export interface mapLayer {
key?: string
title: string
id: string
name: string
style: mapLayerStyle
elements: any
}
export interface elementGroupsReq{
groupId: string
isDistributed: boolean
}
export interface PostElementsBody {
id: string
name: string
resource: {
type: MapElementEnum,
user_name?: string,
content: {
type:string,
properties:{
color:string,
clampToGround:boolean
},
geometry:{
type:string,
coordinates:unknown
}
},
}
}
export interface Color {
id: number
color: string
selected: boolean,
name: string
}
export enum GeoType {
LineString = 'LineString',
Polygon = 'Polygon',
Point = 'Point'
}
export enum ResourceStatus {
NotShow,
Show
}
export enum LayerElevationLoadStatus {
Unload,
Load
}
export interface PutElementsBody {
name?: string
status?: ResourceStatus
content?: unknown
display?: number
elevation_load_status?: LayerElevationLoadStatus
}
export enum LayerType {
Normal,
Default,
Share,
Reconstruction
}
================================================
FILE: src/types/task.ts
================================================
import { commonColor } from '/@/utils/color'
// 任务类型
export enum TaskType {
Immediate = 0, // 立即执行
Timed = 1, // 单次定时任务
Condition = 2,
}
export const TaskTypeMap = {
[TaskType.Immediate]: 'Immediate',
[TaskType.Timed]: 'Timed',
[TaskType.Condition]: 'Continuous',
}
export const TaskTypeOptions = [
{ value: TaskType.Immediate, label: TaskTypeMap[TaskType.Immediate] },
{ value: TaskType.Timed, label: TaskTypeMap[TaskType.Timed] },
{ value: TaskType.Condition, label: TaskTypeMap[TaskType.Condition] },
]
// 失控动作
export enum OutOfControlAction {
ReturnToHome = 0,
Hover = 1,
Land = 2,
}
export const OutOfControlActionMap = {
[OutOfControlAction.ReturnToHome]: 'Return to Home',
[OutOfControlAction.Hover]: 'Hover',
[OutOfControlAction.Land]: 'Land',
}
export const OutOfControlActionOptions = [
{ value: OutOfControlAction.ReturnToHome, label: OutOfControlActionMap[OutOfControlAction.ReturnToHome] },
{ value: OutOfControlAction.Hover, label: OutOfControlActionMap[OutOfControlAction.Hover] },
{ value: OutOfControlAction.Land, label: OutOfControlActionMap[OutOfControlAction.Land] },
]
// 任务状态
export enum TaskStatus {
Wait = 1, // 待执行
Carrying = 2, // 执行中
Success = 3, // 完成
CanCel = 4, // 取消
Fail = 5, // 失败
Paused = 6, // 暂停
}
export const TaskStatusMap = {
[TaskStatus.Wait]: 'To be performed',
[TaskStatus.Carrying]: 'In progress',
[TaskStatus.Success]: 'Task completed',
[TaskStatus.CanCel]: 'Task canceled',
[TaskStatus.Fail]: 'Task failed',
[TaskStatus.Paused]: 'Paused',
}
export const TaskStatusColor = {
[TaskStatus.Wait]: commonColor.BLUE,
[TaskStatus.Carrying]: commonColor.BLUE,
[TaskStatus.Success]: commonColor.NORMAL,
[TaskStatus.CanCel]: commonColor.FAIL,
[TaskStatus.Fail]: commonColor.FAIL,
[TaskStatus.Paused]: commonColor.BLUE,
}
// 任务执行 ws 消息状态
export enum TaskProgressStatus {
Sent = 'sent', // 已下发
inProgress = 'in_progress', // 执行中
Paused = 'paused', // 暂停
Rejected = 'rejected', // 拒绝
Canceled = 'canceled', // 取消或终止
Timeout = 'timeout', // 超时
Failed = 'failed', // 失败
OK = 'ok', // 上传成功
}
// 任务进度消息
export interface TaskProgressInfo {
bid: string,
output:{
ext: {
current_waypoint_index: number,
media_count: number // 媒体文件
},
progress:{
current_step: number,
percent: number
},
status: TaskProgressStatus
},
result: number,
}
// ws status => log status
export const TaskProgressWsStatusMap = {
[TaskProgressStatus.Sent]: TaskStatus.Carrying,
[TaskProgressStatus.inProgress]: TaskStatus.Carrying,
[TaskProgressStatus.Rejected]: TaskStatus.Fail,
[TaskProgressStatus.OK]: TaskStatus.Success,
[TaskProgressStatus.Failed]: TaskStatus.Fail,
[TaskProgressStatus.Canceled]: TaskStatus.CanCel,
[TaskProgressStatus.Timeout]: TaskStatus.Fail,
[TaskProgressStatus.Paused]: TaskStatus.Paused,
}
// 根据媒体文件上传进度信息,前端自己判断出的状态
export enum MediaStatus { // 媒体上传进度
ToUpload = 1, // 待上传
Uploading = 2, // 上传中
Empty = 3, // 无媒体文件
Success = 4, // 上传成功
}
export const MediaStatusMap = {
[MediaStatus.ToUpload]: 'Waiting to upload',
[MediaStatus.Uploading]: 'Uploading…',
[MediaStatus.Success]: 'Uploaded',
[MediaStatus.Empty]: 'No media files',
}
export const MediaStatusColorMap = {
[MediaStatus.ToUpload]: commonColor.BLUE,
[MediaStatus.Uploading]: commonColor.BLUE,
[MediaStatus.Success]: commonColor.NORMAL,
[MediaStatus.Empty]: commonColor.WARN,
}
// 媒体上传进度消息
export interface MediaStatusProgressInfo {
job_id: string,
media_count: number
uploaded_count: number,
}
// 媒体上传优先级消息
export interface TaskMediaHighestPriorityProgressInfo {
pre_job_id: string,
job_id: string,
}
================================================
FILE: src/types/wayline.ts
================================================
// 航线类型
export enum WaylineType {
NormalWaypointWayline = 0, // 普通航点航线
AccurateReshootingWayline = 1 // 精准复拍航线
}
export interface WaylineFile {
id: string,
name: string,
drone_model_key: any,
payload_model_keys: string[],
template_types: WaylineType[],
update_time: number,
user_name: string,
}
================================================
FILE: src/use-common-components.ts
================================================
import { App, DefineComponent } from 'vue'
const components: Record> = {
}
export const CommonComponents = {
install (app: App): void {
Object.keys(components).forEach(name => {
app.component(name, components[name])
})
}
}
================================================
FILE: src/utils/bytes.ts
================================================
import { DEFAULT_PLACEHOLDER, SIZES as byteSizes, BYTE_SIZES } from './constants'
/**
* 转换字节数为单位B,KB,GB...
* 保留一位小数
* @param bytes 字节数
* @param holder 0字节占位符,默认 --
* @returns
*/
export function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1, unit = false): string {
if (isNaN(bytes) || bytes === 0) {
return holder
}
// 兼容负数
let prefix = ''
if (bytes < 0) {
bytes = 0 - bytes
prefix = '-'
}
const k = 1024
const sizes = unit ? BYTE_SIZES : byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return prefix + (bytes / Math.pow(k, i)).toFixed(fix) + '' + sizes[i]
}
// 获取转化后数据及单位
export function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1): {
value: string,
size: string
index: number
} {
if (isNaN(bytes) || bytes === 0) {
return {
value: holder,
size: '',
index: -1,
}
}
// 兼容负数
let prefix = ''
if (bytes < 0) {
bytes = 0 - bytes
prefix = '-'
}
const k = 1024
const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return {
value: prefix + (bytes / Math.pow(k, i)).toFixed(fix),
size: sizes[i],
index: i,
}
}
/**
* 根据最小单位返回文件大小
* @param bytes
* @param minUnit
* @param fix
* @returns
*/
export function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1): string {
const holder = `0${minUnit}`
const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
const k = 1024
const findIndex = sizes.findIndex(item => item === minUnit)
const { value, size, index } = getBytesObject(bytes, holder, fix)
// 0
if (index === -1) {
return holder
}
// 转换后单位小于传入的最小单位
if (index < findIndex) {
const sizeToMinUint = parseFloat(value) / (Math.pow(k, findIndex - index))
return sizeToMinUint.toFixed(fix) + minUnit
}
// 其他
return value + size
}
// console.log('size', bytesToSizeWithMinUnit(0))
// console.log('size', bytesToSizeWithMinUnit(1023))
// console.log('size', bytesToSizeWithMinUnit(1024))
// console.log('size', bytesToSizeWithMinUnit(1000 * 1024, 'MB', 2))
// console.log('size', bytesToSizeWithMinUnit(1024 * 1024, 'MB', 2))
================================================
FILE: src/utils/color.ts
================================================
export const commonColor = {
WARN: '#FF9900', // 黄色
FAIL: '#E02020', // 红色
WHITE: '#FFFFFF', // 白色
NORMAL: '#19BE6B', // 绿色
BLUE: '#2B85E4', // 蓝色
PINK: '#F7C0BA', // 粉
}
================================================
FILE: src/utils/common.ts
================================================
/**
* 下载文件
* @param data
* @param fileName
*/
export function downloadFile (data: Blob, fileName: string) {
const lable = document.createElement('a')
lable.href = window.URL.createObjectURL(data)
lable.download = fileName
lable.click()
URL.revokeObjectURL(lable.href)
}
================================================
FILE: src/utils/constants.ts
================================================
export const DEFAULT_PLACEHOLDER = '--' // 默认占位符
// 全局日期格式
export const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'
export const DATE_FORMAT_MINUTE = 'YYYY-MM-DD HH:mm'
export const DATE_FORMAT_DAY = 'YYYY-MM-DD'
export const TIME_FORMAT = 'HH:mm:ss'
export const TIME_FORMAT_MINUTE = 'HH:mm'
export const DATE_FORMAT_MM = 'MM-DD HH:mm'
export const SIZES = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
export const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
export const PAGE_SIZE_OPTIONS = ['20', '50', '100']
export const PAGE_SIZE = 50
================================================
FILE: src/utils/data-process.ts
================================================
export function formatPhoneNum (phoneNum: string | number) {
const str = String(phoneNum)
return str.substring(0, 3) + '****' + str.slice(-4)
}
================================================
FILE: src/utils/device-cmd.ts
================================================
import { DroneBatteryModeEnum, DroneBatteryStateEnum, LinkWorkModeEnum } from './../types/airport-tsa'
import { DeviceInfoType } from '/@/types/device'
import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum, AlarmModeEnum, BatteryStoreModeEnum } from '/@/types/airport-tsa'
import { getBytesObject } from './bytes'
import { DEFAULT_PLACEHOLDER } from './constants'
/**
* 根据osd 更新信息
* @param cmdList
* @param deviceInfo
* @returns
*/
export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo: DeviceInfoType) {
const { device, dock, gateway } = deviceInfo || {}
if (!cmdList || cmdList.length < 1) {
return
}
cmdList.forEach(cmdItem => {
if (cmdItem.loading) {
return
}
if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启
// console.log('DeviceReboot')
} else if (cmdItem.cmdKey === DeviceCmd.DroneOpen || cmdItem.cmdKey === DeviceCmd.DroneClose) { // 飞行器开关机
getDroneState(cmdItem, device)
} else if (cmdItem.cmdKey === DeviceCmd.CoverOpen || cmdItem.cmdKey === DeviceCmd.CoverClose) { // 舱盖开关
getCoverState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.PutterOpen || cmdItem.cmdKey === DeviceCmd.PutterClose) { // 推杆闭合展开
getPutterState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen || cmdItem.cmdKey === DeviceCmd.ChargeClose) { // 充电状态
getChargeState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储
deviceFormat(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储
droneFormat(cmdItem, device)
} else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关
getSupplementLightState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 声光报警
getAlarmState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养
getBatteryStoreMode(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
getDroneBatteryMode(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传开关
getSdrWorkNode(cmdItem, dock)
}
})
}
// 飞行器开关机
function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) {
if (!droneProperties) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText
if (cmdItem.cmdKey !== DeviceCmd.DroneOpen) {
exchangeDeviceCmd(cmdItem)
}
} else {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText
if (cmdItem.cmdKey !== DeviceCmd.DroneClose) {
exchangeDeviceCmd(cmdItem)
}
}
}
// 舱盖开关
function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {
const coverState = airportProperties?.basic_osd?.cover_state as CoverStateEnum
if (coverState === CoverStateEnum.Close || coverState === CoverStateEnum.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText
if (cmdItem.cmdKey !== DeviceCmd.CoverOpen) {
exchangeDeviceCmd(cmdItem)
}
} else if (coverState === CoverStateEnum.Open || coverState === CoverStateEnum.HalfOpen) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText
if (cmdItem.cmdKey !== DeviceCmd.CoverClose) {
exchangeDeviceCmd(cmdItem)
}
}
}
// 推杆状态
function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {
const putterState = airportProperties?.basic_osd?.putter_state as PutterStateEnum
if (putterState === PutterStateEnum.Close || putterState === PutterStateEnum.Failed) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText
if (cmdItem.cmdKey !== DeviceCmd.PutterOpen) {
exchangeDeviceCmd(cmdItem)
}
} else if (putterState === PutterStateEnum.Open || putterState === PutterStateEnum.HalfOpen) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText
if (cmdItem.cmdKey !== DeviceCmd.PutterClose) {
exchangeDeviceCmd(cmdItem)
}
}
}
// 充电状态
function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {
const chargeState = airportProperties?.basic_osd?.drone_charge_state
const state = chargeState?.state as ChargeStateEnum
if (!state) return
if (state === ChargeStateEnum.Charge) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText
if (cmdItem.cmdKey !== DeviceCmd.ChargeClose) {
exchangeDeviceCmd(cmdItem)
}
} else if (state === ChargeStateEnum.NotCharge) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText
if (cmdItem.cmdKey !== DeviceCmd.ChargeOpen) {
exchangeDeviceCmd(cmdItem)
}
}
}
// 机场存储格式化
function deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) {
const airportStorage = airportProperties?.basic_osd?.storage
const value = getAirportStorage(airportStorage)
cmdItem.status = value
}
// 机场存储格式化
function droneFormat (cmdItem: DeviceCmdItem, droneProperties: any) {
const droneStorage = droneProperties?.storage
const value = getAirportStorage(droneStorage)
cmdItem.status = value
}
// 获取机场存储容量
// {
// "total": 10000, // 单位:KB
// "used": 500
// }
export function getAirportStorage (storage: AirportStorage) {
if (!storage) {
return DEFAULT_PLACEHOLDER
}
const total = storage.total
const used = storage.used
const byteObj = getBytesObject(total * 1024)
const _total = byteObj.value
const _used = getBytes(used * 1024, byteObj.index)
return `${_used}/${_total} ${byteObj.size}`
}
function getBytes (bytes: number, index: number, fixed = 1) {
return (bytes / Math.pow(1024, index)).toFixed(fixed)
}
// 补光灯状态
function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any) {
const supplementLightState = airportProperties?.basic_osd?.supplement_light_state
if (supplementLightState === SupplementLightStateEnum.Close) {
cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText
cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText
if (cmdItem.cmdKey !== DeviceCmd.SupplementLightOpen) {
exchangeDeviceCmd(cmdItem)
}
} else if (supplementLightState === SupplementLightStateEnum.Open) {
cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText
cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText
if (cmdItem.cmdKey !== DeviceCmd.SupplementLightClose) {
exchangeDeviceCmd(cmdItem)
}
}
}
// 声光报警
function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {
const alarmState = airportProperties?.basic_osd?.alarm_state
if (alarmState === AlarmModeEnum.CLOSE) {
cmdItem.operateText = DeviceCmdStatusText.AlarmStateCloseBtnText
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseNormalText
cmdItem.action = AlarmModeEnum.OPEN
} else if (alarmState === AlarmModeEnum.OPEN) {
cmdItem.operateText = DeviceCmdStatusText.AlarmStateOpenBtnText
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenNormalText
cmdItem.action = AlarmModeEnum.CLOSE
}
}
// 机场电池模式
function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) {
const batteryStoreMode = airportProperties?.basic_osd?.battery_store_mode
if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModePlanBtnText
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanNormalText
cmdItem.action = BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE
} else if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModeEmergencyBtnText
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyNormalText
cmdItem.action = BatteryStoreModeEnum.BATTERY_PLAN_STORE
}
}
// 飞行器电池保养
function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) {
const maintenanceState = airportProperties?.work_osd?.drone_battery_maintenance_info?.maintenance_state
if (maintenanceState === DroneBatteryStateEnum.MaintenanceInProgress) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeCloseBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.action = DroneBatteryModeEnum.CLOSE
cmdItem.disabled = false
} else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
cmdItem.action = DroneBatteryModeEnum.OPEN
cmdItem.disabled = true
} else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.action = DroneBatteryModeEnum.OPEN
cmdItem.disabled = false
}
}
// 增强图传开关
function getSdrWorkNode (cmdItem: DeviceCmdItem, airportProperties: any) {
const linkWorkMode = airportProperties?.link_osd?.wireless_link?.link_workmode
if (linkWorkMode === LinkWorkModeEnum.SDR) {
cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourCloseBtnText
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText
cmdItem.action = LinkWorkModeEnum.FourG_FUSION_MODE
} else if (linkWorkMode === LinkWorkModeEnum.FourG_FUSION_MODE) {
cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourGOpenBtnText
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText
cmdItem.action = LinkWorkModeEnum.SDR
}
}
/**
* 交换指令
* @param cmd
*/
function exchangeDeviceCmd (cmdItem: DeviceCmdItem) {
if (cmdItem.oppositeCmdKey) {
const oppositeCmdKey = cmdItem.oppositeCmdKey
cmdItem.oppositeCmdKey = cmdItem.cmdKey
cmdItem.cmdKey = oppositeCmdKey
}
}
// /**
// * 更新简单指令发送情况更新信息
// * @param cmd
// */
// export function updateDeviceSingleCmdInfo (cmdItem: DeviceCmdItem) {
// // 补光灯
// if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen) {
// cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText
// cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText
// exchangeDeviceCmd(cmdItem)
// } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightClose) {
// cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText
// cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText
// exchangeDeviceCmd(cmdItem)
// }
// }
/**
* 根据指令执行消息更新信息
* @param cmd
* @param deviceCmdExecuteInfo
* @returns
*/
export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], deviceCmdExecuteInfos?: DeviceCmdExecuteInfo[]) {
if (!deviceCmdExecuteInfos || !cmdList) {
return
}
cmdList.forEach(cmdItem => {
// 获取当前设备相应指令信息
const deviceCmdExecuteInfo = deviceCmdExecuteInfos.find(cmdExecuteInfo => cmdExecuteInfo.biz_code === cmdItem.cmdKey)
if (deviceCmdExecuteInfo) {
if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootNormalText
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.DroneOpen) { // 飞行器开关机
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.DroneClose) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.CoverOpen) { // 舱盖开关
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.CoverClose) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.PutterOpen) { // 推杆闭合展开
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.PutterClose) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen) { // 充电状态
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.ChargeClose) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText
exchangeDeviceCmd(cmdItem)
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 机场声光报警
if (cmdItem.action === AlarmModeEnum.CLOSE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.action === AlarmModeEnum.OPEN) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
}
} else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养
if (cmdItem.action === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.action === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
}
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
if (cmdItem.action === DroneBatteryModeEnum.OPEN) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
cmdItem.loading = false
}
} else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.loading = false
}
}
} else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传
if (cmdItem.action === LinkWorkModeEnum.SDR) { // 关闭
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText
cmdItem.loading = false
}
} else if (cmdItem.action === LinkWorkModeEnum.FourG_FUSION_MODE) { // 开启
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText
cmdItem.loading = false
}
}
}
}
})
}
/**
* 判断是否执行失败
* @param status
* @returns
*/
function isExecuteFailed (status: DeviceCmdExecuteStatus) {
return [DeviceCmdExecuteStatus.Canceled, DeviceCmdExecuteStatus.Failed, DeviceCmdExecuteStatus.Timeout].includes(status)
}
================================================
FILE: src/utils/device-setting.ts
================================================
import { DeviceInfoType } from '/@/types/device'
import { DeviceSettingType, DeviceSettingKeyEnum, DistanceLimitStatusEnum, ObstacleAvoidanceStatusEnum, DeviceSettingFormModel, NightLightsStateEnum } from '/@/types/device-setting'
import { DEFAULT_PLACEHOLDER } from './constants'
import { isNil } from 'lodash'
const Unit_M = ' m'
/**
* 根据osd 更新信息
* @param deviceSetting
* @param deviceInfo
* @returns
*/
export function updateDeviceSettingInfoByOsd (deviceSetting: DeviceSettingType, deviceInfo: DeviceInfoType) {
const { device, dock, gateway } = deviceInfo || {}
if (!deviceSetting) {
return
}
// 夜航灯
let nightLightsState = '' as any
if (isNil(device?.night_lights_state)) {
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = false
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER
nightLightsState = DEFAULT_PLACEHOLDER
} else {
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = true
nightLightsState = device?.night_lights_state
if (nightLightsState === NightLightsStateEnum.CLOSE) {
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '关闭'
} else if (nightLightsState === NightLightsStateEnum.OPEN) {
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '开启'
} else {
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER
}
}
deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].trueValue = nightLightsState
// 限高
let heightLimit = device?.height_limit as any
if (isNil(heightLimit) || heightLimit === 0) {
heightLimit = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = false
} else {
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = true
}
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].trueValue = heightLimit
deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value = heightLimit + Unit_M
// 限远
let distanceLimitStatus = '' as any
if (isNil(device?.distance_limit_status?.state)) {
distanceLimitStatus = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = false
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER
} else {
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = true
distanceLimitStatus = device?.distance_limit_status?.state
if (distanceLimitStatus === DistanceLimitStatusEnum.UNSET) {
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = '关闭'
} else if (distanceLimitStatus === DistanceLimitStatusEnum.SET) {
const distanceLimit = device?.distance_limit_status?.distance_limit
if (distanceLimit) {
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = distanceLimit + Unit_M
} else {
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER
}
} else {
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER
}
}
deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].trueValue = distanceLimitStatus
// 避障
if (isNil(device?.obstacle_avoidance)) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER
} else {
const { horizon, upside, downside } = device.obstacle_avoidance || {}
if (isNil(horizon)) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER
} else {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false
if (horizon === ObstacleAvoidanceStatusEnum.CLOSE) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '关闭'
} else if (horizon === ObstacleAvoidanceStatusEnum.OPEN) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '开启'
} else {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER
}
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = horizon
}
if (isNil(upside)) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER
} else {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false
if (upside === ObstacleAvoidanceStatusEnum.CLOSE) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '关闭'
} else if (upside === ObstacleAvoidanceStatusEnum.OPEN) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '开启'
} else {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER
}
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = upside
}
if (isNil(downside)) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER
} else {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false
if (downside === ObstacleAvoidanceStatusEnum.CLOSE) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '关闭'
} else if (downside === ObstacleAvoidanceStatusEnum.OPEN) {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '开启'
} else {
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER
}
deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = downside
}
}
return deviceSetting
}
// 更新formModel
export function updateDeviceSettingFormModelByOsd (deviceSettingFormModelFromOsd: DeviceSettingFormModel, deviceInfo: DeviceInfoType) {
const { device, dock, gateway } = deviceInfo || {}
if (!deviceSettingFormModelFromOsd) {
return
}
// 夜航灯
const nightLightsState = device?.night_lights_state as any
if (!isNil(nightLightsState) && nightLightsState === NightLightsStateEnum.OPEN) {
deviceSettingFormModelFromOsd.nightLightsState = true
} else {
deviceSettingFormModelFromOsd.nightLightsState = false
}
// 限高
const heightLimit = device?.height_limit as any
if (isNil(heightLimit) || heightLimit === 0) {
deviceSettingFormModelFromOsd.heightLimit = 20
} else {
deviceSettingFormModelFromOsd.heightLimit = heightLimit
}
// 限远
const distanceLimitStatus = device?.distance_limit_status?.state as any
if (!isNil(distanceLimitStatus) && distanceLimitStatus === DistanceLimitStatusEnum.SET) {
deviceSettingFormModelFromOsd.distanceLimitStatus.state = true
deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = device?.distance_limit_status?.distance_limit || 15
} else {
deviceSettingFormModelFromOsd.distanceLimitStatus.state = false
deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = 15
}
// 避障
if (isNil(device?.obstacle_avoidance)) {
deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false
deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false
deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false
} else {
const { horizon, upside, downside } = device.obstacle_avoidance || {}
if (!isNil(horizon) && horizon === ObstacleAvoidanceStatusEnum.OPEN) {
deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = true
} else {
deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false
}
if (!isNil(upside) && upside === ObstacleAvoidanceStatusEnum.OPEN) {
deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = true
} else {
deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false
}
if (!isNil(downside) && downside === ObstacleAvoidanceStatusEnum.OPEN) {
deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = true
} else {
deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false
}
}
return deviceSettingFormModelFromOsd
}
================================================
FILE: src/utils/download.ts
================================================
/**
* 加载图片
* @param url
* @returns
*/
export function urlToImage (url: string) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = url
image.onload = () => { resolve(image) }
image.onerror = () => { reject(new Error('image load error')) }
})
}
export interface CompressImageData {
blob: Blob | null;
imageData: ImageData;
}
export function compressImage (imgToCompress: HTMLImageElement, targetWidth: number, targetHeight: number): Promise | undefined {
// resizing the image
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
if (context) {
const iWidth = imgToCompress.width
const iHeight = imgToCompress.height
const iRatio = iWidth / iHeight // 图像宽高比
const tRatio = targetWidth / targetHeight // 目标宽高比
let dw = targetWidth
let dh = targetHeight
let dx = 0
let dy = 0
if (iRatio > tRatio) {
// 如果图像宽高比比目标宽高比要大,说明图像比目标尺寸更宽,这时候我们应该按照高度缩放比来进行缩放宽度
dw = (targetHeight / iHeight) * iWidth
// 宽度溢出,应该放在中间
dx = -(dw - targetWidth) / 2
} else {
// 否则说明图像比目标尺寸更高,按照宽度缩放比来缩放高度
dh = (targetWidth / iWidth) * iHeight
// 高度溢出,应该放在中间
dy = -(dh - targetHeight) / 2
}
canvas.width = targetWidth
canvas.height = targetHeight
context.drawImage(
imgToCompress,
dx,
dy,
dw,
dh,
)
return new Promise((resolve) => {
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
canvas.toBlob(blob => resolve({
blob,
imageData,
}))
})
}
}
/**
* 根据资源url下载文件
* @param url
* @param fileName
*/
export function download (url: string, fileName = ''): void {
const aLink = document.createElement('a')
aLink.style.display = 'none'
aLink.download = fileName
aLink.href = url
document.body.appendChild(aLink)
// 避免新开页面,闪烁
// aLink.target = '_blank'
aLink.click()
document.body.removeChild(aLink)
// aLink.remove()
}
================================================
FILE: src/utils/error-code/index.ts
================================================
export interface ErrorCode {
code: number;
msg: string;
}
/**
* 根据错误码翻译错误信息
* @param code
* @param errorMsg
* @returns
*/
export function getErrorMessage (code: number, errorMsg?: string): string {
const errorInfo = ERROR_CODE.find((item: ErrorCode) => item.code === code)
return errorInfo ? errorInfo.msg : errorMsg || 'Server error'
}
// 暂时只添加航线错误
export const ERROR_CODE = [
{
code: 314001,
msg: 'The issued route task url is empty',
},
{
code: 314002,
msg: 'The issued route task md5 is empty',
},
{
code: 314003,
msg: 'MissionID is invalid',
},
{
code: 314004,
msg: 'Failed to send flight route task from cloud',
},
{
code: 314005,
msg: 'Route md5 check failed',
},
{
code: 314006,
msg: 'Timeout waiting for aircraft to upload route (waiting for gs_state)',
},
{
code: 314007,
msg: 'Failed to upload route to aircraft',
},
{
code: 314008,
msg: 'Timeout waiting for the aircraft to enter the route executable state',
},
{
code: 314009,
msg: 'Failed to open route mission',
},
{
code: 314010,
msg: 'Route execution failed',
},
{
code: 316001,
msg: 'Failed to set alternate point',
},
{
code: 316002,
msg: 'Alternate safety transfer altitude equipment failed',
},
{
code: 316003,
msg: 'Failed to set takeoff altitude. Remarks: The default safe takeoff height of the aircraft set by the current DJI Dock is: 1.8',
},
{
code: 316004,
msg: 'Failed to set runaway behavior',
},
{
code: 316005,
msg: 'Aircraft RTK convergence failed',
},
{
code: 316013,
msg: 'DJI Dock Moved',
},
{
code: 316015,
msg: 'The aircraft RTK convergence position is too far from the DJI Dock',
},
{
code: 316007,
msg: 'Set parameter timeout while waiting for aircraft to be ready',
},
{
code: 316008,
msg: 'Failed to gain control of aircraft',
},
{
code: 316009,
msg: 'Aircraft power is low',
},
{
code: 316010,
msg: 'After power on, the aircraft is not connected for more than 2 minutes (flight control OSD reception timeout)',
},
{
code: 316011,
msg: 'Landing Position Offset',
},
{
code: 317001,
msg: 'Failed to get the number of media files',
},
{
code: 319001,
msg: 'The task center is not currently idle',
},
{
code: 319002,
msg: 'dronenest communication timeout',
},
{
code: 319999,
msg: 'Unknown error, e.g. restart after crash',
},
{
code: 321000,
msg: 'Route execution failed, unknown error',
},
{
code: 321257,
msg: 'The route has already started and cannot be started again',
},
{
code: 321258,
msg: 'The route cannot be interrupted in this state',
},
{
code: 321259,
msg: 'The route has not started and cannot end the route',
},
{
code: 321513,
msg: 'Reach the height limit',
},
{
code: 321514,
msg: 'Reach the limit',
},
{
code: 321515,
msg: 'Crossing the restricted flight zone',
},
{
code: 321516,
msg: 'Low limit',
},
{
code: 321517,
msg: 'Obstacle Avoidance',
},
{
code: 321769,
msg: 'Weak GPS signal',
},
{
code: 321770,
msg: 'The current gear state cannot be executed, B control seizes the control, and the gear is switched',
},
{
code: 321771,
msg: 'The home point is not refreshed',
},
{
code: 321772,
msg: 'The current battery is too low to start the task',
},
{
code: 321773,
msg: 'Low battery return',
},
{
code: 321776,
msg: 'RTK not ready',
},
{
code: 321778,
msg: 'The aircraft is idling on the ground and is not allowed to start the route, thinking that the user is not ready.',
},
{
code: 322282,
msg: 'User interrupt (B control takeover)',
},
{
code: 514100,
msg: 'Command not supported',
},
{
code: 514101,
msg: 'Failed to close putter',
},
{
code: 514102,
msg: 'Failed to release putter',
},
{
code: 514103,
msg: 'Aircraft battery is low',
},
{
code: 514104,
msg: 'Failed to start charging',
},
{
code: 514105,
msg: 'Failed to stop charging',
},
{
code: 514106,
msg: 'Failed to restart the aircraft',
},
{
code: 514107,
msg: 'Failed to open hatch',
},
{
code: 514108,
msg: 'Failed to close hatch',
},
{
code: 514109,
msg: 'Failed to open the plane',
},
{
code: 514110,
msg: 'Failed to close the plane',
},
{
code: 514111,
msg: 'The aircraft failed to turn on the slow-rotating propeller in the cabin',
},
{
code: 514112,
msg: 'The aircraft failed to stop the slow-rotating propeller in the cabin',
},
{
code: 514113,
msg: 'Failed to establish wired connection with aircraft',
},
{
code: 514114,
msg: 'Get aircraft power status, command timed out, or return code is not 0',
},
{
code: 514116,
msg: 'The DJI Dock is busy and other control orders are being executed at the DJI Dock',
},
{
code: 514117,
msg: 'Check hatch status failed',
},
{
code: 514118,
msg: 'Check putter status failed',
},
{
code: 514120,
msg: 'DJI Dock and aircraft SDR connection failed',
},
{
code: 514121,
msg: 'Emergency stop state',
},
{
code: 514122,
msg: 'Failed to get the charging status of the aircraft (Failed to get the charging status, the flight mission can be executed, affecting charging and remote troubleshooting)',
},
{
code: 514123,
msg: 'Unable to power on due to low battery',
},
{
code: 514124,
msg: 'Failed to get battery information',
},
{
code: 514125,
msg: 'The battery is fully charged and cannot be charged',
},
{
code: 514145,
msg: 'Can not work while debugging on site',
},
{
code: 514146,
msg: 'Unable to work in remote debugging',
},
{
code: 514147,
msg: 'Unable to work in upgrade state',
},
{
code: 514148,
msg: 'Unable to execute new tasks in job state',
},
{
code: 514150,
msg: 'DJI Dock is automatically restarting',
},
]
================================================
FILE: src/utils/genjson.ts
================================================
import {
MapGeographicPosition,
} from '/@/types/map'
export type GeojsonCoordinate = [number, number, number?]
export interface GeojsonLine {
type: 'Feature'
properties: {
color: string
directConnected?: boolean
}
geometry: {
type: 'LineString'
coordinates: GeojsonCoordinate[]
}
}
export interface GeojsonPolygon {
type: 'Feature'
properties: {
color: string
}
geometry: {
type: 'Polygon'
coordinates: GeojsonCoordinate[][]
}
}
export interface GeojsonPoint {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: 'Point'
coordinates: GeojsonCoordinate
}
}
export interface GeojsonCircle {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: 'Circle'
coordinates: GeojsonCoordinate
radius: number
}
}
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint | GeojsonCircle
export function geographic2Coordinate (position: MapGeographicPosition): GeojsonCoordinate {
const coordinates: GeojsonCoordinate = [position.longitude, position.latitude]
if (position.height !== undefined) coordinates.push(position.height)
return coordinates
}
export function generateLine (coordinates: MapGeographicPosition[], properties: GeojsonLine['properties']): GeojsonFeature {
return {
type: 'Feature',
properties,
geometry: {
type: 'LineString',
coordinates: coordinates.map(geographic2Coordinate),
},
}
}
export function generatePolygon (coordinates: MapGeographicPosition[], properties: GeojsonPolygon['properties']): GeojsonFeature {
return {
type: 'Feature',
properties,
geometry: {
type: 'Polygon',
coordinates: [coordinates.map(geographic2Coordinate)],
},
}
}
export function generatePoint (position: MapGeographicPosition, properties: GeojsonPoint['properties']): GeojsonFeature {
return {
type: 'Feature',
properties,
geometry: {
type: 'Point',
coordinates: geographic2Coordinate(position),
},
}
}
export function generateCircle (position: MapGeographicPosition, properties: GeojsonCircle['properties'], radius: number): GeojsonFeature {
return {
type: 'Feature',
properties,
geometry: {
type: 'Circle',
coordinates: geographic2Coordinate(position),
radius: radius,
},
}
}
================================================
FILE: src/utils/layer-tree.ts
================================================
const layerTreeTypes = ['layer', 'resource'] as const
type LayerTreeType = (typeof layerTreeTypes)[number]
const Spliter = '__'
export function getLayerTreeKey (type: LayerTreeType, id: number | string) {
return `${type}${Spliter}${id}`
}
export function isLayerTreeKey (key: string, type?: LayerTreeType) {
if (type) {
return key.startsWith(`${type}${Spliter}`)
} else {
return layerTreeTypes.some(t => key.startsWith(`${t}${Spliter}`))
}
}
export function getIdFromLayerTreeKey (key: string) {
return key.split(Spliter)[1]
}
================================================
FILE: src/utils/logger.ts
================================================
/**
* Used for log printing in a non-production environment
* @param args
*/
export function consoleLog (...args: Parameters) {
if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {
window.console.log.apply(null, args) // eslint-disable-line no-console
}
}
export function consoleWarn (...args: Parameters) {
if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {
console.warn.apply(null, args) // eslint-disable-line no-console
}
}
export function consoleError (...args: Parameters) {
if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {
console.error.apply(null, args) // eslint-disable-line no-console
}
}
export function testEnvLog (...args: Parameters) {
if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {
console.log.apply(null, args) // eslint-disable-line no-console
}
}
================================================
FILE: src/utils/map-layer-utils.ts
================================================
import { pinAMapPosition, MapGeographicPosition, Layer, LayerType, LayerElevationLoadStatus } from '/@/types/map'
import { generatePoint, generateLine, generatePolygon, generateCircle } from '/@/utils/genjson'
import { MapDoodleColor, MapElementEnum } from '/@/constants/map'
function getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographicPosition {
return { height: 0, latitude: pinAMapPosition.lat, longitude: pinAMapPosition.lng }
}
export function generatePointContent (pinAMapPosition: pinAMapPosition) {
const position = getPinPosition(pinAMapPosition)
return {
type: MapElementEnum.PIN,
content: generatePoint(position, {
color: MapDoodleColor.PinColor,
clampToGround: true,
})
}
}
function getLieOrPolyPosition (mapPosition: pinAMapPosition[]):MapGeographicPosition[] {
const position = [] as MapGeographicPosition[]
mapPosition.forEach(item => {
position.push({ height: 0, latitude: item.lat, longitude: item.lng })
})
return position
}
export function generateLineContent (mapPosition: pinAMapPosition[]) {
const position = getLieOrPolyPosition(mapPosition)
return {
type: MapElementEnum.LINE,
content: generateLine(position, {
color: MapDoodleColor.PolylineColor,
directConnected: false,
})
}
}
export function generatePolyContent (mapPosition: pinAMapPosition[]) {
const position = getLieOrPolyPosition(mapPosition)
return {
type: MapElementEnum.POLY,
content: generatePolygon(position, {
color: MapDoodleColor.PolygonColor,
})
}
}
export function generateCircleContent (pinAMapPosition: pinAMapPosition, radius: number) {
const position = getPinPosition(pinAMapPosition)
return generateCircle(position, { color: MapDoodleColor.PolygonColor }, radius)
}
================================================
FILE: src/utils/storage.ts
================================================
import { EStorageKey } from '/@/types/enums'
import { consoleWarn } from './logger'
function getStorageData (key: EStorageKey, parse?: boolean): string | null
function getStorageData (key: EStorageKey, parse?: boolean): T | null
function getStorageData (key: EStorageKey, parse?: boolean): any {
const value = window.localStorage.getItem(key)
if (parse && value) {
try {
const result = JSON.parse(value)
return result
} catch (e) {
consoleWarn('appStorage.get failed, err:', e)
return null
}
} else {
return value
}
}
function clearStorageData (key: EStorageKey | EStorageKey[]) {
let keyList: EStorageKey[] = []
if (Array.isArray(key)) {
keyList = key
} else {
keyList = [key]
}
keyList.forEach(item => {
window.localStorage.removeItem(item)
})
}
const appStorage = {
save (key: EStorageKey, value: string) {
window.localStorage.setItem(key, value)
},
get: getStorageData,
clear: clearStorageData,
}
export default appStorage
================================================
FILE: src/utils/time.ts
================================================
import {
DATE_FORMAT,
DEFAULT_PLACEHOLDER
} from '/@/utils/constants'
import moment, { Moment } from 'moment'
// 时间字符串 或者 Unix 时间戳(毫秒数)
export function formatDateTime (time: string | number, format = DATE_FORMAT) {
return time ? moment(time).format(format) : DEFAULT_PLACEHOLDER
}
// Unix 时间戳 (秒)
export function formatUnixTime (time: number, format = DATE_FORMAT): string {
return time ? moment.unix(time).format(format) : DEFAULT_PLACEHOLDER
}
================================================
FILE: src/utils/uuid.ts
================================================
export function uuidv4 () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
================================================
FILE: src/vendors/coordtransform.js
================================================
/**
* Conversion between the coordinates of the National Bureau of Survey and Measurement (Mars coordinates, GCJ02) and the WGS84 coordinate system
*/
const x_PI = 3.14159265358979324 * 3000.0 / 180.0;
const PI = 3.1415926535897932384626;
const a = 6378245.0;
const ee = 0.00669342162296594323;
/**
* WGS84 to Mars coordinate system GCj02
* @param lng
* @param lat
* @returns {*[]}
*/
export function wgs84togcj02(lng, lat) {
if (out_of_china(lng, lat)) {
return [lng, lat]
}
else {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = lat / 180.0 * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
var mglat = lat + dlat;
var mglng = lng + dlng;
return [mglng, mglat]
}
}
/**
* GCJ02 transform WGS84
* @param lng
* @param lat
* @returns {*[]}
*/
export function gcj02towgs84(lng, lat) {
var lat = +lat;
var lng = +lng;
if (out_of_china(lng, lat)) {
return [lng, lat]
} else {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = lat / 180.0 * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
var mglat = lat + dlat;
var mglng = lng + dlng;
return [lng * 2 - mglng, lat * 2 - mglat]
}
}
function transformlat(lng, lat) {
var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret
}
export function transformlng(lng, lat) {
var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
return ret
}
/**
* Judge whether you are in the country or not if you are not in the country
* @param lng
* @param lat
* @returns {boolean}
*/
function out_of_china(lng, lat) {
return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false);
}
================================================
FILE: src/vendors/srs.sdk.js
================================================
//
// Copyright (c) 2013-2021 Winlin
//
// SPDX-License-Identifier: MIT
//
'use strict';
function SrsError(name, message) {
this.name = name;
this.message = message;
this.stack = (new Error()).stack;
}
SrsError.prototype = Object.create(Error.prototype);
SrsError.prototype.constructor = SrsError;
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher.
function SrsRtcPublisherAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: { ideal: 320, max: 576 }
}
};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// or autostart the publish:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.publish = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", { direction: "sendonly" });
self.pc.addTransceiver("video", { direction: "sendonly" });
//self.pc.addTransceiver("video", {direction: "sendonly"});
//self.pc.addTransceiver("audio", {direction: "sendonly"});
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({ track: track });
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: session.sdp })
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/publish/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-await-promise based SRS RTC Player.
function SrsRtcPlayerAsync() {
var self = {};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// webrtc://r.ossrs.net:80/live/livestream
// or autostart the play:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.play = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", { direction: "recvonly" });
self.pc.addTransceiver("video", { direction: "recvonly" });
//self.pc.addTransceiver("video", {direction: "recvonly"});
//self.pc.addTransceiver("audio", {direction: "recvonly"});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: session.sdp })
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the player.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got remote track.
// Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
self.ontrack = function (event) {
// https://webrtc.org/getting-started/remote-streams
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/play/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
self.stream = new MediaStream();
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function (event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher by WHIP.
function SrsRtcWhipWhepAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: { ideal: 320, max: 576 }
}
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to publish with, for example:
// http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
self.publish = async function (url) {
if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
self.pc.addTransceiver("audio", { direction: "sendonly" });
self.pc.addTransceiver("video", { direction: "sendonly" });
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({ track: track });
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer })
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to play with, for example:
// http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
self.play = async function (url) {
if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
self.pc.addTransceiver("video", { direction: "recvonly" });
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer })
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
// Internal APIs.
self.__internal = {
parseId: (url, offer, answer) => {
let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n'));
const a = document.createElement("a");
a.href = url;
return {
sessionid: sessionid, // Should be ice-ufrag of answer:offer.
simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
};
},
};
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function (event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
function SrsRtcFormatSenders(senders, kind) {
var codecs = [];
senders.forEach(function (sender) {
var params = sender.getParameters();
params && params.codecs && params.codecs.forEach(function (c) {
if (kind && sender.track.kind !== kind) {
return;
}
if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
return;
}
var s = '';
s += c.mimeType.replace('audio/', '').replace('video/', '');
s += ', ' + c.clockRate + 'HZ';
if (sender.track.kind === "audio") {
s += ', channels: ' + c.channels;
}
s += ', pt: ' + c.payloadType;
codecs.push(s);
});
});
return codecs.join(", ");
}
export default {
SrsError,
SrsRtcPublisherAsync,
SrsRtcPlayerAsync,
SrsRtcWhipWhepAsync,
SrsRtcFormatSenders,
}
================================================
FILE: src/vite-env.d.ts
================================================
///
================================================
FILE: src/websocket/index.ts
================================================
import { message } from 'ant-design-vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
import { EBizCode } from '../types'
interface WebSocketOptions {
data: any
cache?: boolean | string
destroyCache?: string
}
export interface MessageHandler {
(data : {[key: string]: any}): void
}
export interface CommonHostWs {
sn: string
host: T
}
/**
* ConnectWebSocket 类
* TODO: 优化messageHandler: EventEmitter。暂时传入回调函数
*/
class ConnectWebSocket {
_url: string
_socket: ReconnectingWebSocket | null
_hasInit: boolean
_messageHandler: MessageHandler | null
constructor (url: string) {
this._url = url
this._socket = null
this._hasInit = false
this._messageHandler = null
}
initSocket () {
if (this._hasInit) {
return
}
if (!this._url) {
return
}
// 会自动重连,无需处理重连逻辑
this._socket = new ReconnectingWebSocket(this._url, [], {
maxReconnectionDelay: 20000, // 断开后最大的重连时间: 20s,每多一次重连,会增加 1.3 倍,5 * 1.3 * 1.3 * 1.3...
minReconnectionDelay: 5000, // 断开后最短的重连时间: 5s
maxRetries: 5
})
this._hasInit = true
this._socket.addEventListener('open', this._onOpen.bind(this))
this._socket.addEventListener('close', this._onClose.bind(this))
this._socket.addEventListener('error', this._onError.bind(this))
this._socket.addEventListener('message', this._onMessage.bind(this))
}
_onOpen () {
console.log('连接成功')
}
_onClose () {
console.log('连接已断开')
}
_onError () {
console.log('连接 error')
}
registerMessageHandler (messageHandler: MessageHandler) {
this._messageHandler = messageHandler
}
_onMessage (msg: MessageEvent) {
const data = JSON.parse(msg.data)
this._messageHandler && this._messageHandler(data)
// console.log('接受消息', message)
}
sendMessage = (message: WebSocketOptions): void => {
this._socket?.send(JSON.stringify(message.data))
}
close () {
this._socket?.close()
}
}
export default ConnectWebSocket
================================================
FILE: src/websocket/util/config.ts
================================================
import { ELocalStorageKey } from '/@/types/enums'
import { CURRENT_CONFIG } from '/@/api/http/config'
export function getWebsocketUrl () {
const token: string = localStorage.getItem(ELocalStorageKey.Token) || '' as string
const url = CURRENT_CONFIG.websocketURL + '?x-auth-token=' + encodeURI(token)
return url
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"baseUrl": ".",
"types": ["vite/client"],
"lib": [
"esnext",
"dom"
],
"paths": {
"/@/*": [
"src/*"
],
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"src/vendors/coordtransform.js"
]
}
================================================
FILE: vite.config.ts
================================================
import vue from '@vitejs/plugin-vue'
// config alias
import path from 'path'
import { ConfigEnv, defineConfig, UserConfigExport } from 'vite'
import ViteComponents, { AntDesignVueResolver } from 'vite-plugin-components'
// Introduce eslint plugin
import eslintPlugin from 'vite-plugin-eslint'
import OptimizationPersist from 'vite-plugin-optimize-persist'
import PkgConfig from 'vite-plugin-package-config'
import viteSvgIcons from 'vite-plugin-svg-icons'
import { viteVConsole } from 'vite-plugin-vconsole'
// https://vitejs.dev/config/
export default ({ command, mode }: ConfigEnv): UserConfigExport => defineConfig({
plugins: [
vue(),
eslintPlugin({
fix: true
}),
ViteComponents({
customComponentResolvers: [AntDesignVueResolver()],
}),
viteSvgIcons({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
}),
viteVConsole({
entry: path.resolve(__dirname, './src/main.ts'), // 入口文件
localEnabled: command === 'serve', // serve开发环境下
// enabled: command !== 'serve' || mode === 'test', // 打包环境下/发布测试包,
config: { // vconsole 配置项
maxLogNumber: 1000,
theme: 'light'
}
}),
PkgConfig(),
OptimizationPersist()
// [svgBuilder('./src/assets/icons/')] // All svg under src/icons/svg/ have been imported here, no need to import separately
],
server: {
open: true,
host: '0.0.0.0',
port: 8080
},
envDir: './env',
resolve: {
alias: [{
// https://github.com/vitejs/vite/issues/279#issuecomment-635646269
find: '/@',
replacement: path.resolve(__dirname, './src'),
}
]
},
css: {
preprocessorOptions: {
scss: {
// example : additionalData: `@import "./src/design/styles/variables";`
// dont need include file extend .scss
additionalData: '@import "./src/styles/variables";'
},
}
},
base: '/',
build: {
target: ['es2015'], // 最低支持 es2015
sourcemap: true
}
})