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 ================================================ ================================================ FILE: src/components/LayersTree.vue ================================================ ================================================ FILE: src/components/MediaPanel.vue ================================================ ================================================ FILE: src/components/common/sidebar.vue ================================================ ================================================ FILE: src/components/common/topbar.vue ================================================ ================================================ FILE: src/components/devices/DeviceFirmwareStatus.vue ================================================ ================================================ FILE: src/components/devices/device-hms/DeviceHmsDrawer.vue ================================================ ================================================ FILE: src/components/devices/device-log/DeviceLogDetailModal.vue ================================================ > ================================================ FILE: src/components/devices/device-log/DeviceLogUploadModal.vue ================================================ ================================================ FILE: src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: src/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: src/components/flight-area/FlightAreaItem.vue ================================================ ================================================ FILE: src/components/flight-area/FlightAreaPanel.vue ================================================ ================================================ FILE: src/components/flight-area/FlightAreaSyncPanel.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: src/components/g-map/DeviceSettingPopover.vue ================================================ ================================================ FILE: src/components/g-map/DockControlPanel.vue ================================================ ================================================ FILE: src/components/g-map/DroneControlInfoPanel.vue ================================================ ================================================ FILE: src/components/g-map/DroneControlPanel.vue ================================================ ================================================ FILE: src/components/g-map/DroneControlPopover.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: src/components/livestream-others.vue ================================================ ================================================ FILE: src/components/svgIcon.vue ================================================ ================================================ FILE: src/components/task/CreatePlan.vue ================================================ ================================================ FILE: src/components/task/TaskPanel.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: src/pages/page-pilot/pilot-home.vue ================================================ ================================================ FILE: src/pages/page-pilot/pilot-index.vue ================================================ ================================================ FILE: src/pages/page-pilot/pilot-liveshare.vue ================================================ ================================================ FILE: src/pages/page-pilot/pilot-media.vue ================================================ ================================================ FILE: src/pages/page-web/home.vue ================================================ ================================================ FILE: src/pages/page-web/index.vue ================================================ ================================================ FILE: src/pages/page-web/projects/Firmwares.vue ================================================ ================================================ FILE: src/pages/page-web/projects/devices.vue ================================================ ================================================ FILE: src/pages/page-web/projects/dock.vue ================================================ ================================================ FILE: src/pages/page-web/projects/flight-area.vue ================================================