Compare commits

..

3 Commits

Author SHA1 Message Date
sean.zhou b3adb8adb0 update mqtt_username 2 years ago
sean.zhou 1f522544d6 initial v1.3.0-beta2 2 years ago
sean.zhou 1810c61f5a v1.3.0-beta1 2 years ago
  1. 1
      .gitignore
  2. 2
      .npmrc
  3. 3
      README.md
  4. 1525
      package-lock.json
  5. 6
      package.json
  6. 5185
      pnpm-lock.yaml
  7. 10
      src/App.vue
  8. 9
      src/api/device-cmd/index.ts
  9. 58
      src/api/drc.ts
  10. 74
      src/api/drone-control/drone.ts
  11. 93
      src/api/drone-control/payload.ts
  12. 81
      src/api/flight-area/index.ts
  13. 15
      src/api/http/config.ts
  14. 24
      src/api/manage.ts
  15. 3
      src/api/media.ts
  16. 45
      src/api/wayline.ts
  17. 383
      src/components/GMap.vue
  18. 3
      src/components/common/sidebar.vue
  19. 3
      src/components/common/topbar.vue
  20. 61
      src/components/devices/DeviceFirmwareStatus.vue
  21. 26
      src/components/devices/device-hms/DeviceHmsDrawer.vue
  22. 4
      src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue
  23. 2
      src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue
  24. 59
      src/components/flight-area/FlightAreaActionIcon.vue
  25. 197
      src/components/flight-area/FlightAreaDevicePanel.vue
  26. 33
      src/components/flight-area/FlightAreaIcon.vue
  27. 89
      src/components/flight-area/FlightAreaItem.vue
  28. 43
      src/components/flight-area/FlightAreaPanel.vue
  29. 66
      src/components/flight-area/FlightAreaSyncPanel.vue
  30. 18
      src/components/flight-area/use-flight-area-drone-location-event.ts
  31. 17
      src/components/flight-area/use-flight-area-sync-progress-event.ts
  32. 30
      src/components/flight-area/use-flight-area-update.ts
  33. 155
      src/components/flight-area/use-flight-area.ts
  34. 4
      src/components/g-map/DeviceSettingBox.vue
  35. 20
      src/components/g-map/DockControlPanel.vue
  36. 34
      src/components/g-map/DroneControlInfoPanel.vue
  37. 837
      src/components/g-map/DroneControlPanel.vue
  38. 115
      src/components/g-map/DroneControlPopover.vue
  39. 71
      src/components/g-map/use-connect-mqtt.ts
  40. 76
      src/components/g-map/use-drone-control-mqtt-event.ts
  41. 95
      src/components/g-map/use-drone-control-ws-event.ts
  42. 40
      src/components/g-map/use-drone-control.ts
  43. 165
      src/components/g-map/use-manual-control.ts
  44. 134
      src/components/g-map/use-mqtt.ts
  45. 120
      src/components/g-map/use-payload-control.ts
  46. 0
      src/components/g-map/useDeviceSetting.ts
  47. 33
      src/components/g-map/useDockControl.ts
  48. 36
      src/components/livestream-agora.vue
  49. 40
      src/components/livestream-others.vue
  50. 240
      src/components/task/CreatePlan.vue
  51. 184
      src/components/task/TaskPanel.vue
  52. 45
      src/components/task/use-format-task.ts
  53. 19
      src/components/task/use-task-progress-event.ts
  54. 43
      src/components/task/use-task-ws-event.ts
  55. 15
      src/components/workspace/DividerLine.vue
  56. 21
      src/components/workspace/Title.vue
  57. 6
      src/constants/map.ts
  58. 38
      src/directives/drag-window.ts
  59. 6
      src/directives/index.ts
  60. 7
      src/event-bus/index.ts
  61. 209
      src/hooks/use-g-map-cover.ts
  62. 24
      src/hooks/use-g-map-tsa.ts
  63. 4
      src/hooks/use-g-map.ts
  64. 16
      src/hooks/use-map-tool.ts
  65. 58
      src/hooks/use-mouse-tool.ts
  66. 4
      src/main.ts
  67. 12
      src/mqtt/config.ts
  68. 117
      src/mqtt/index.ts
  69. 40
      src/pages/page-pilot/pilot-home.vue
  70. 28
      src/pages/page-pilot/pilot-liveshare.vue
  71. 325
      src/pages/page-web/projects/Firmwares.vue
  72. 17
      src/pages/page-web/projects/devices.vue
  73. 66
      src/pages/page-web/projects/dock.vue
  74. 98
      src/pages/page-web/projects/flight-area.vue
  75. 50
      src/pages/page-web/projects/layer.vue
  76. 19
      src/pages/page-web/projects/livestream.vue
  77. 97
      src/pages/page-web/projects/tsa.vue
  78. 70
      src/pages/page-web/projects/wayline.vue
  79. 66
      src/pages/page-web/projects/workspace.vue
  80. 20
      src/router/index.ts
  81. 4
      src/shims-mqtt.d.ts
  82. 52
      src/store/index.ts
  83. 42
      src/types/airport-tsa.ts
  84. 112
      src/types/device-cmd.ts
  85. 38
      src/types/device-firmware.ts
  86. 272
      src/types/device.ts
  87. 72
      src/types/drc.ts
  88. 83
      src/types/drone-control.ts
  89. 28
      src/types/enums.ts
  90. 80
      src/types/flight-area.ts
  91. 71
      src/types/live-stream.ts
  92. 3
      src/types/map-enum.ts
  93. 72
      src/types/task.ts
  94. 207
      src/utils/device-cmd.ts
  95. 27
      src/utils/genjson.ts
  96. 7
      src/utils/map-layer-utils.ts
  97. 2
      src/utils/time.ts
  98. 2
      src/vendors/jswebrtc.min.js
  99. 687
      src/vendors/srs.sdk.js
  100. 6
      src/websocket/index.ts
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -23,3 +23,4 @@ yarn-error.log* @@ -23,3 +23,4 @@ yarn-error.log*
/coverage
/backup
node_modules
./src/api/http/config.ts

2
.npmrc

@ -1 +1 @@ @@ -1 +1 @@
registry=https://registry.npmmirror.com/
registry=https://registry.npm.taobao.org/

3
README.md

@ -14,8 +14,9 @@ For more documentation, please visit the [DJI Developer Documentation](https://d @@ -14,8 +14,9 @@ For more documentation, please visit the [DJI Developer Documentation](https://d
## 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/).
Cloud API 1.1.0 was released on 22 July 2022. 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.

1525
package-lock.json generated

File diff suppressed because it is too large Load Diff

6
package.json

@ -15,9 +15,7 @@ @@ -15,9 +15,7 @@
"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",
@ -95,17 +93,13 @@ @@ -95,17 +93,13 @@
"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",

5185
pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

10
src/App.vue

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div class="demo-app">
<div id="demo-app" class="demo-app">
<router-view />
<!-- <div class="map-wrapper">
<GMap/>
@ -26,17 +26,9 @@ export default defineComponent({ @@ -26,17 +26,9 @@ export default defineComponent({
.demo-app {
width: 100%;
height: 100%;
.map-wrapper {
height: 100%;
width: 100%;
}
}
</style>
<style lang="scss">
#demo-app {
width: 100%;
height: 100%
}
</style>

9
src/api/device-cmd/index.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
import { DeviceCmd } from '/@/types/device-cmd'
const CMD_API_PREFIX = '/control/api/v1'
@ -8,16 +8,13 @@ export interface SendCmdParams { @@ -8,16 +8,13 @@ export interface SendCmdParams {
device_cmd: DeviceCmd // 指令
}
export interface PostSendCmdBody {
action: DeviceCmdItemAction
}
/**
*
* @param params
* @returns
*/
// /control/api/v1/devices/{dock_sn}/jobs/{service_identifier}
export async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> {
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, body)
export async function postSendCmd (params: SendCmdParams): Promise<IWorkspaceResponse<{}>> {
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`)
return resp.data
}

58
src/api/drc.ts

@ -1,58 +0,0 @@ @@ -1,58 +0,0 @@
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { ELocalStorageKey } from '/@/types'
// DRC 链路
const DRC_API_PREFIX = '/control/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export interface PostDrcBody {
client_id?: string // token过期时,用于续期则必填
expire_sec?: number // 过期时间,单位秒,默认3600
}
export interface DrcParams {
address: string
username: string
password: string
client_id: string
expire_time: number // 过期时间
enable_tls: boolean // 是否开启tls
}
// 获取 mqtt 连接认证
export async function postDrc (body: PostDrcBody): Promise<IWorkspaceResponse<DrcParams>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/connect`, body)
return resp.data
}
export interface DrcEnterBody {
client_id: string
dock_sn: string
expire_sec?: number // 过期时间,单位秒,默认3600
device_info?: {
osd_frequency?: number
hsi_frequency?: number
}
}
export interface DrcEnterResp {
sub: string[] // 需要订阅接收的topic
pub: string[] // 推送的topic地址
}
// 进入飞行控制 (建立drc连接&获取云控控制权)
export async function postDrcEnter (body: DrcEnterBody): Promise<IWorkspaceResponse<DrcEnterResp>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body)
return resp.data
}
export interface DrcExitBody {
client_id: string
dock_sn: string
}
// 退出飞行控制 (退出drc连接&退出云控控制权)
export async function postDrcExit (body: DrcExitBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/exit`, body)
return resp.data
}

74
src/api/drone-control/drone.ts

@ -1,74 +0,0 @@ @@ -1,74 +0,0 @@
import request, { IWorkspaceResponse } from '/@/api/http/request'
// import { ELocalStorageKey } from '/@/types'
const API_PREFIX = '/control/api/v1'
// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
// 获取飞行控制权
export async function postFlightAuth (sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/flight`)
return resp.data
}
export enum WaylineLostControlActionInCommandFlight {
CONTINUE = 0,
EXEC_LOST_ACTION = 1
}
export enum LostControlActionInCommandFLight {
HOVER = 0, // 悬停
Land = 1, // 着陆
RETURN_HOME = 2, // 返航
}
export enum ERthMode {
SMART = 0,
SETTING = 1
}
export enum ECommanderModeLostAction {
CONTINUE = 0,
EXEC_LOST_ACTION = 1
}
export enum ECommanderFlightMode {
SMART = 0,
SETTING = 1
}
export interface PointBody {
latitude: number;
longitude: number;
height: number;
}
export interface PostFlyToPointBody {
max_speed: number,
points: PointBody[]
}
// 飞向目标点
export async function postFlyToPoint (sn: string, body: PostFlyToPointBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`, body)
return resp.data
}
// 停止飞向目标点
export async function deleteFlyToPoint (sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.delete(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`)
return resp.data
}
export interface PostTakeoffToPointBody{
target_height: number;
target_latitude: number;
target_longitude: number;
security_takeoff_height: number; // 安全起飞高
max_speed: number; // flyto过程中能达到的最大速度, 单位m/s 跟飞机档位有关
rc_lost_action: LostControlActionInCommandFLight; // 失控行为
rth_altitude: number; // 返航高度
exit_wayline_when_rc_lost: WaylineLostControlActionInCommandFlight;
rth_mode: ERthMode;
commander_mode_lost_action: ECommanderModeLostAction;
commander_flight_mode: ECommanderFlightMode;
commander_flight_height: number;
}
// 一键起飞
export async function postTakeoffToPoint (sn: string, body: PostTakeoffToPointBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/takeoff-to-point`, body)
return resp.data
}

93
src/api/drone-control/payload.ts

@ -1,93 +0,0 @@ @@ -1,93 +0,0 @@
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { CameraType, CameraMode } from '/@/types/live-stream'
import { GimbalResetMode } from '/@/types/drone-control'
// import { ELocalStorageKey } from '/@/types'
const API_PREFIX = '/control/api/v1'
// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
export interface PostPayloadAuthBody {
payload_index: string
}
// 获取负载控制权
export async function postPayloadAuth (sn: string, body: PostPayloadAuthBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)
return resp.data
}
// TODO: 画面拖动控制
export enum PayloadCommandsEnum {
CameraModeSwitch = 'camera_mode_switch',
CameraPhotoTake = 'camera_photo_take',
CameraRecordingStart = 'camera_recording_start',
CameraRecordingStop = 'camera_recording_stop',
CameraFocalLengthSet = 'camera_focal_length_set',
GimbalReset = 'gimbal_reset',
CameraAim = 'camera_aim'
}
export interface PostCameraModeBody {
payload_index: string
camera_mode: CameraMode
}
export interface PostCameraPhotoBody {
payload_index: string
}
export interface PostCameraRecordingBody {
payload_index: string
}
export interface DeleteCameraRecordingParams {
payload_index: string
}
export interface PostCameraFocalLengthBody {
payload_index: string,
camera_type: CameraType,
zoom_factor: number
}
export interface PostGimbalResetBody{
payload_index: string,
reset_mode: GimbalResetMode,
}
export interface PostCameraAimBody{
payload_index: string,
camera_type: CameraType,
locked: boolean,
x: number,
y: number,
}
export type PostPayloadCommandsBody = {
cmd: PayloadCommandsEnum.CameraModeSwitch,
data: PostCameraModeBody
} | {
cmd: PayloadCommandsEnum.CameraPhotoTake,
data: PostCameraPhotoBody
} | {
cmd: PayloadCommandsEnum.CameraRecordingStart,
data: PostCameraRecordingBody
} | {
cmd: PayloadCommandsEnum.CameraRecordingStop,
data: DeleteCameraRecordingParams
} | {
cmd: PayloadCommandsEnum.CameraFocalLengthSet,
data: PostCameraFocalLengthBody
} | {
cmd: PayloadCommandsEnum.GimbalReset,
data: PostGimbalResetBody
} | {
cmd: PayloadCommandsEnum.CameraAim,
data: PostCameraAimBody
}
// 发送负载名称
export async function postPayloadCommands (sn: string, body: PostPayloadCommandsBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body)
return resp.data
}

81
src/api/flight-area/index.ts

@ -1,81 +0,0 @@ @@ -1,81 +0,0 @@
import request from '../http/request'
import { IWorkspaceResponse } from '../http/type'
import { EFlightAreaType, ESyncStatus, FlightAreaContent } from './../../types/flight-area'
import { ELocalStorageKey } from '/@/types/enums'
import { GeojsonCoordinate } from '/@/utils/genjson'
export interface GetFlightArea {
area_id: string,
name: string,
type: EFlightAreaType,
content: FlightAreaContent,
status: boolean,
username: string,
create_time: number,
update_time: number,
}
export interface PostFlightAreaBody {
id: string,
name: string,
type: EFlightAreaType,
content: {
properties: {
color: string,
clampToGround: boolean,
},
geometry: {
type: string,
coordinates: GeojsonCoordinate | GeojsonCoordinate[][],
radius?: number,
}
}
}
export interface FlightAreaStatus {
sync_code: number,
sync_status: ESyncStatus,
sync_msg: string,
}
export interface GetDeviceStatus {
device_sn: string,
nickname?: string,
device_name?: string,
online?: boolean,
flight_area_status: FlightAreaStatus,
}
const MAP_API_PREFIX = '/map/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export async function getFlightAreaList (): Promise<IWorkspaceResponse<GetFlightArea[]>> {
const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-areas`)
return resp.data
}
export async function changeFlightAreaStatus (area_id: string, status: boolean): Promise<IWorkspaceResponse<any>> {
const resp = await request.put(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`, { status })
return resp.data
}
export async function saveFlightArea (body: PostFlightAreaBody): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area`, body)
return resp.data
}
export async function deleteFlightArea (area_id: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.delete(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`)
return resp.data
}
export async function syncFlightArea (device_sn: string[]): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/sync`, { device_sn })
return resp.data
}
export async function getDeviceStatus (): Promise<IWorkspaceResponse<GetDeviceStatus[]>> {
const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/device-status`)
return resp.data
}

15
src/api/http/config.ts

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
export const CURRENT_CONFIG = {
// license
appId: '156484', // You need to go to the development website to apply.
appKey: 'fdfca95ec7e04aa41c743b837f5b6c5', // You need to go to the development website to apply.
appLicense:
'rpyFPcmu47YOtazIxOYXZDJEmmboLVt3urGN2HbP7yocd2oNxvI2Ty3luE+KL0iM5rCPb0R+o8xR3IebOhSzDdK0DU21iujkuRCt70FdMTZdm8uRXC6NMJXfmej6BUFZtAhBhT+87jEnPGjhmfd51rMgOmovSsw/IndV6as3dbQ=', // You need to go to the development website to apply.
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: 'http://47.113.196.86:30001', // This url must end with "/". Example: 'http://192.168.1.1:6789/'
websocketURL: 'http://47.113.196.86:30001/api/v1/ws', // Example: 'ws://192.168.1.1:6789/api/v1/ws'
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.
@ -31,5 +31,6 @@ export const CURRENT_CONFIG = { @@ -31,5 +31,6 @@ export const CURRENT_CONFIG = {
// map
// You can apply on the AMap website.
amapKey: '8b0131c34d408f6663aae7a779e62cd1',
amapKey: 'Please enter the amap key.',
}

24
src/api/manage.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request'
import { Device } from '/@/types/device'
@ -25,7 +24,7 @@ export interface HmsQueryBody { @@ -25,7 +24,7 @@ export interface HmsQueryBody {
begin_time: number,
end_time: number,
message: string,
domain: number,
domain: string,
}
export const login = async function (body: LoginBody): Promise<IWorkspaceResponse<any>> {
@ -126,7 +125,7 @@ export const getDeviceBySn = async function (workspace_id: string, device_sn: st @@ -126,7 +125,7 @@ export const getDeviceBySn = async function (workspace_id: string, device_sn: st
* @param domain
* @returns
*/
export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: number): Promise<IListWorkspaceResponse<Device>> {
export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: string): Promise<IListWorkspaceResponse<Device>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}`
const result = await request.get(url)
return result.data
@ -167,22 +166,3 @@ export const changeLivestreamLens = async function (body: {}): Promise<IWorkspac @@ -167,22 +166,3 @@ export const changeLivestreamLens = async function (body: {}): Promise<IWorkspac
const result = await request.post(url, body)
return result.data
}
export const getFirmwares = async function (workspace_id: string, page: IPage, body: FirmwareQueryParam): Promise<IListWorkspaceResponse<Firmware>> {
const url = `${HTTP_PREFIX}/workspaces/${workspace_id}/firmwares?page=${page.page}&page_size=${page.page_size}` +
`&device_name=${body.device_name}&product_version=${body.product_version}&status=${body.firmware_status ?? ''}`
const result = await request.get(url)
return result.data
}
export const importFirmareFile = async function (workspaceId: string, param: FormData): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/file/upload`
const result = await request.post(url, param)
return result.data
}
export const changeFirmareStatus = async function (workspaceId: string, firmwareId: string, param: {status: boolean}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/${firmwareId}`
const result = await request.put(url, param)
return result.data
}

3
src/api/media.ts

@ -15,11 +15,12 @@ export const downloadMediaFile = async function (workspaceId: string, fileId: st @@ -15,11 +15,12 @@ export const downloadMediaFile = async function (workspaceId: string, fileId: st
if (result.data.type === 'application/json') {
const reader = new FileReader()
reader.onload = function (e) {
const text = reader.result as string
let text = reader.result as string
const result = JSON.parse(text)
message.error(result.message)
}
reader.readAsText(result.data, 'utf-8')
return
} else {
return result.data
}

45
src/api/wayline.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { message } from 'ant-design-vue'
import request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'
import { TaskType, TaskStatus, OutOfControlAction } from '/@/types/task'
import { TaskType, TaskStatus } from '/@/types/task'
import { WaylineType } from '/@/types/wayline'
const HTTP_PREFIX = '/wayline/api/v1'
@ -42,12 +42,7 @@ export interface CreatePlan { @@ -42,12 +42,7 @@ export interface CreatePlan {
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.
execute_time?: number // 执行时间(毫秒)
}
// Create Wayline Job
@ -68,18 +63,11 @@ export interface Task { @@ -68,18 +63,11 @@ export interface Task {
dock_name: string,
workspace_id: string,
username: string,
begin_time: string,
end_time: string,
execute_time: string,
completed_time: string,
end_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
@ -93,7 +81,7 @@ export interface DeleteTaskParams { @@ -93,7 +81,7 @@ export interface DeleteTaskParams {
job_id: string
}
// 删除机场任务
// 取消机场任务
export async function deleteTask (workspaceId: string, params: DeleteTaskParams): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs`
const result = await request.delete(url, {
@ -102,24 +90,6 @@ export async function deleteTask (workspaceId: string, params: DeleteTaskParams) @@ -102,24 +90,6 @@ export async function deleteTask (workspaceId: string, params: DeleteTaskParams)
return result.data
}
export enum UpdateTaskStatus {
Suspend = 0, // 暂停
Resume = 1, // 恢复
}
export interface UpdateTaskStatusBody {
job_id: string
status: UpdateTaskStatus
}
// 更新机场任务状态
export async function updateTaskStatus (workspaceId: string, body: UpdateTaskStatusBody): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${body.job_id}`
const result = await request.put(url, {
status: body.status
})
return result.data
}
// Upload Wayline file
export const importKmzFile = async function (workspaceId: string, file: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/file/upload`
@ -130,10 +100,3 @@ export const importKmzFile = async function (workspaceId: string, file: {}): Pro @@ -130,10 +100,3 @@ export const importKmzFile = async function (workspaceId: string, file: {}): Pro
})
return result.data
}
// 媒体立即上传
export const uploadMediaFileNow = async function (workspaceId: string, jobId: string): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${jobId}/media-highest`
const result = await request.post(url)
return result.data
}

383
src/components/GMap.vue

@ -1,10 +1,8 @@ @@ -1,10 +1,8 @@
<template>
<div class="g-map-wrapper">
<!-- 地图区域 -->
<div id="g-container" :style="{ width: '100%', height: '100%' }" />
<!-- 绘制面板 -->
<div
class="g-action-panel"
class="g-action-panle"
:style="{ right: drawVisible ? '316px' : '16px' }"
>
<div :class="state.currentType === 'pin' ? 'g-action-item selection' : 'g-action-item'" @click="draw('pin', true)">
@ -13,22 +11,20 @@ @@ -13,22 +11,20 @@
<div :class="state.currentType === 'polyline' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polyline', true)">
<a><LineOutlined :rotate="135" class="fz20"/></a>
</div>
<div :class="state.currentType === 'polygon' && !state.isFlightArea ? 'g-action-item selection' : 'g-action-item'" @click="draw('polygon', true)">
<div :class="state.currentType === 'polygon' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polygon', true)">
<a><BorderOutlined class="fz18" /></a>
</div>
<FlightAreaActionIcon class="g-action-item mt10" :class="{'selection': mouseMode && state.isFlightArea}" @select-action="selectFlightAreaAction" @click="selectFlightAreaAction"/>
<div v-if="mouseMode" class="g-action-item" @click="draw('off', false)">
<a style="color: red;"><CloseOutlined /></a>
</div>
</div>
<!-- 飞机OSD -->
<div v-if="osdVisible.visible && !osdVisible.is_dock" v-drag-window class="osd-panel fz12">
<div class="drag-title pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 18%;">
<div v-if="osdVisible.visible && !osdVisible.is_dock" class="osd-panel fz12">
<div class="pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 18%;">
<span>{{ osdVisible.callsign }}</span>
<span><a class="fz16" style="color: white;" @click="() => osdVisible.visible = false"><CloseOutlined /></a></span>
</div>
<div style="height: 82%;">
<div class="flex-column flex-align-center flex-justify-center" style="margin-top: -5px; padding-top: 25px; float: left; width: 60px; background: #2d2d2d;">
<div class="flex-column flex-align-center flex-justify-center" style="float: left; width: 60px; height: 100%; background: #2d2d2d;">
<a-tooltip :title="osdVisible.model">
<div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
<span><a-image :src="M30" :preview="false"/></span>
@ -50,7 +46,7 @@ @@ -50,7 +46,7 @@
<a-col span="6">
<a-tooltip title="RC Battery Level">
<span><ThunderboltOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.gateway && deviceInfo.gateway.capacity_percent !== str ? deviceInfo.gateway?.capacity_percent + ' %' : deviceInfo.gateway?.capacity_percent }}</span>
<span class="ml10">{{ deviceInfo.gateway && deviceInfo.gateway.capacity_percent !== str ? deviceInfo.gateway.capacity_percent + ' %' : deviceInfo.gateway.capacity_percent }}</span>
</a-tooltip>
</a-col>
@ -142,11 +138,11 @@ @@ -142,11 +138,11 @@
</div>
</div>
<!-- 机场OSD -->
<div v-if="osdVisible.visible && osdVisible.is_dock" v-drag-window class="osd-panel fz12">
<div class="drag-title fz16 pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 10%;">
<div v-if="osdVisible.visible && osdVisible.is_dock" class="osd-panel fz12">
<div class="fz16 pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 10%;">
<span>{{ osdVisible.gateway_callsign }}</span>
<span><a style="color: white;" @click="() => osdVisible.visible = false"><CloseOutlined /></a></span>
</div>
<span><a style="color: white; position: absolute; top: 5px; right: 5px;" @click="() => osdVisible.visible = false"><CloseOutlined /></a></span>
<!-- 机场 -->
<div class ="flex-display" style="border-bottom: 1px solid #515151;">
<div class="flex-column flex-align-stretch flex-justify-center" style="width: 60px; background: #2d2d2d;">
@ -159,62 +155,57 @@ @@ -159,62 +155,57 @@
</div>
<div class="osd flex-1" style="flex: 1">
<a-row>
<a-col span="16" :style="deviceInfo.dock.basic_osd?.mode_code === EDockModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">
{{ EDockModeCode[deviceInfo.dock.basic_osd?.mode_code] }}</a-col>
<a-col span="16" :style="deviceInfo.dock.mode_code === EDockModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">
{{ EDockModeCode[deviceInfo.dock.mode_code] }}</a-col>
</a-row>
<a-row>
<a-col span="12">
<a-tooltip title="Accumulated Running Time">
<span><HistoryOutlined /></span>
<span class="ml10">
<span v-if="deviceInfo.dock.work_osd?.acc_time >= 2592000"> {{ Math.floor(deviceInfo.dock.work_osd?.acc_time / 2592000) }}m </span>
<span v-if="(deviceInfo.dock.work_osd?.acc_time % 2592000) >= 86400"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000) / 86400) }}d </span>
<span v-if="(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) >= 3600"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) / 3600) }}h </span>
<span v-if="(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) >= 60"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) / 60) }}min </span>
<span>{{ Math.floor(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600 % 60) }} s</span>
<span v-if="deviceInfo.dock.acc_time >= 2592000"> {{ Math.floor(deviceInfo.dock.acc_time / 2592000) }}m </span>
<span v-if="(deviceInfo.dock.acc_time % 2592000) >= 86400"> {{ Math.floor((deviceInfo.dock.acc_time % 2592000) / 86400) }}d </span>
<span v-if="(deviceInfo.dock.acc_time % 2592000 % 86400) >= 3600"> {{ Math.floor((deviceInfo.dock.acc_time % 2592000 % 86400) / 3600) }}h </span>
<span v-if="(deviceInfo.dock.acc_time % 2592000 % 86400 % 3600) >= 60"> {{ Math.floor((deviceInfo.dock.acc_time % 2592000 % 86400 % 3600) / 60) }}min </span>
<span>{{ Math.floor(deviceInfo.dock.acc_time % 2592000 % 86400 % 3600 % 60) }} s</span>
</span>
</a-tooltip>
</a-col>
<a-col span="12">
<a-tooltip title="Activation time">
<a-tooltip title="Last login">
<span><FieldTimeOutlined /></span>
<span class="ml10">{{ new Date((deviceInfo.dock.work_osd?.activation_time ?? 0) * 1000).toLocaleString() }}
<span class="ml10">{{ new Date(deviceInfo.dock.first_power_on).toLocaleString() }}
</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-col span="12">
<a-tooltip title="Network State">
<span :style="qualityStyle">
<span v-if="deviceInfo.dock.basic_osd?.network_state?.type === NetworkStateTypeEnum.FOUR_G"><SignalFilled /></span>
<span :style="deviceInfo.dock.network_state?.quality === 2 ? 'color: #00ee8b' :
deviceInfo.dock.network_state?.quality === 1 ? 'color: yellow' : 'color: red'">
<span v-if="deviceInfo.dock.network_state?.type === 1"><SignalFilled /></span>
<span v-else><GlobalOutlined /></span>
</span>
<span class="ml10" >{{ deviceInfo.dock.basic_osd?.network_state?.rate }} kb/s</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="The total number of times the dock has performed missions.">
<span><CarryOutOutlined /></span>
<span class="ml10" >{{ deviceInfo.dock.work_osd?.job_number }} </span>
<span class="ml10" >{{ deviceInfo.dock.network_state?.rate }} KB/S</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Media File Remain Upload">
<span><CloudUploadOutlined class="fz14"/></span>
<span class="ml10">{{ deviceInfo.dock.link_osd?.media_file_detail?.remain_upload }}</span>
<span class="ml10">{{ deviceInfo.dock.media_file_detail?.remain_upload }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip>
<template #title>
<p>total: {{ deviceInfo.dock.basic_osd?.storage?.total }}</p>
<p>used: {{ deviceInfo.dock.basic_osd?.storage?.used }}</p>
<p>total: {{ deviceInfo.dock.storage?.total }}</p>
<p>used: {{ deviceInfo.dock.storage?.used }}</p>
</template>
<span><FolderOpenOutlined /></span>
<span class="ml10" v-if="deviceInfo.dock.basic_osd?.storage?.total > 0">
<a-progress type="circle" :width="20" :percent="deviceInfo.dock.basic_osd?.storage?.used * 100/ deviceInfo.dock.basic_osd?.storage?.total"
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.basic_osd?.storage?.used * 100 / deviceInfo.dock.basic_osd?.storage?.total > 80 ? 'red' : '#00ee8b' "/>
<span class="ml10" v-if="deviceInfo.dock.storage?.total > 0">
<a-progress type="circle" :width="20" :percent="deviceInfo.dock.storage?.used * 100/ deviceInfo.dock.storage?.total"
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.storage?.used * 100 / deviceInfo.dock.storage?.total > 80 ? 'red' : '#00ee8b' "/>
</span>
</a-tooltip>
</a-col>
@ -223,63 +214,62 @@ @@ -223,63 +214,62 @@
<a-col span="6">
<a-tooltip title="Wind Speed">
<span>W.S</span>
<span class="ml10">{{ (deviceInfo.dock.basic_osd?.wind_speed ?? str) + ' m/s'}}</span>
<span class="ml10">{{ deviceInfo.dock.wind_speed === str ? str : (deviceInfo.dock.wind_speed / 10).toFixed(2) + ' m/s'}}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Rainfall">
<span>🌧</span>
<span class="ml10">{{ RainfallEnum[deviceInfo.dock.basic_osd?.rainfall] }}</span>
<span class="ml10">{{ deviceInfo.dock.rainfall === str ? str : deviceInfo.dock.rainfall + ' mm/h' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Environment Temperature">
<span>°C</span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.environment_temperature }}</span>
<span class="ml10">{{ deviceInfo.dock.environment_temperature }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Dock Temperature">
<span>°C</span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.temperature }}</span>
<a-tooltip title="Environment Humidity">
<span>💦</span>
<span class="ml10">{{ deviceInfo.dock.environment_humidity === str ? str : deviceInfo.dock.environment_humidity }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col span="6">
<a-tooltip title="Dock Temperature">
<span>°C</span>
<span class="ml10">{{ deviceInfo.dock.temperature }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Dock Humidity">
<span>💦</span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.humidity }}</span>
<span class="ml10">{{ deviceInfo.dock.humidity === str ? str : deviceInfo.dock.humidity }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Working Voltage">
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 16px; text-align: center; float: left;">V</span>
<span class="ml10">{{ (deviceInfo.dock.work_osd?.working_voltage ?? str) + ' mV' }}</span>
<span class="ml10">{{ deviceInfo.dock.working_voltage === str ? str : deviceInfo.dock.working_voltage + ' mV' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Working Current">
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center; float: left;" >A</span>
<span class="ml10">{{ (deviceInfo.dock.work_osd?.working_current ?? str) + ' mA' }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Drone in dock">
<span><RocketOutlined /></span>
<span class="ml10">{{ deviceInfo.dock.basic_osd?.drone_in_dock }}</span>
<span class="ml10">{{ deviceInfo.dock.working_current === str ? str : deviceInfo.dock.working_current + ' mA' }}</span>
</a-tooltip>
</a-col>
</a-row>
<a-row class="p5">
<a-col span="24">
<a-button type="primary" :disabled="dockControlPanelVisible" size="small" @click="setDockControlPanelVisible(true)">
Actions
<a-button type="primary" :disabled="controlPanelVisible" size="small" @click="setControlPanelVisible(true)">
设备操作
</a-button>
</a-col>
</a-row>
<!-- 机场控制面板 -->
<DockControlPanel v-if="dockControlPanelVisible" :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" @close-control-panel="onCloseControlPanel">
<DockControlPanel v-if="controlPanelVisible" :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" @close-control-panel="dockDebugOnOff">
</DockControlPanel>
</div>
</div>
@ -302,13 +292,13 @@ @@ -302,13 +292,13 @@
<a-col span="6">
<a-tooltip title="Upward Quality">
<span><SignalFilled /><ArrowUpOutlined style="font-size: 9px; vertical-align: top;" /></span>
<span class="ml10">{{ deviceInfo.dock.link_osd?.sdr?.up_quality }}</span>
<span class="ml10">{{ deviceInfo.dock.sdr?.up_quality }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip title="Downward Quality">
<span><SignalFilled /><ArrowDownOutlined style="font-size: 9px; vertical-align: top;" /></span>
<span class="ml10">{{ deviceInfo.dock.link_osd?.sdr?.down_quality }}</span>
<span class="ml10">{{ deviceInfo.dock.sdr?.down_quality }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
@ -317,19 +307,6 @@ @@ -317,19 +307,6 @@
<span class="ml10">{{ deviceInfo.device && deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device?.battery.capacity_percent + ' %' : str }}</span>
</a-tooltip>
</a-col>
<a-col span="6">
<a-tooltip>
<template #title>
<p>total: {{ deviceInfo.device?.storage?.total }}</p>
<p>used: {{ deviceInfo.device?.storage?.used }}</p>
</template>
<span><FolderOpenOutlined /></span>
<span class="ml10" v-if="deviceInfo.device?.storage?.total > 0">
<a-progress type="circle" :width="20" :percent="deviceInfo.device?.storage?.used * 100/ deviceInfo.device?.storage?.total"
:strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.device?.storage?.used * 100 / deviceInfo.device?.storage?.total > 80 ? 'red' : '#00ee8b' "/>
</span>
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-tooltip title="RTK Fixed">
@ -410,19 +387,7 @@ @@ -410,19 +387,7 @@
{{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}
</div>
</div>
<!-- 飞行指令 -->
<DroneControlPanel :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" :payloads="osdVisible.payloads"></DroneControlPanel>
</div>
<!-- liveview -->
<div class="liveview" v-if="livestreamOthersVisible" v-drag-window >
<div style="height: 40px; width: 100%" class="drag-title"></div>
<a style="position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;" @click="closeLivestreamOthers"><CloseOutlined /></a>
<LivestreamOthers />
</div>
<div class="liveview" v-if="livestreamAgoraVisible" v-drag-window >
<div style="height: 40px; width: 100%" class="drag-title"></div>
<a style="position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;" @click="closeLivestreamAgora"><CloseOutlined /></a>
<LivestreamAgora />
</div>
</div>
</template>
@ -439,7 +404,7 @@ import { MapDoodleType, MapElementEnum } from '/@/constants/map' @@ -439,7 +404,7 @@ import { MapDoodleType, MapElementEnum } from '/@/constants/map'
import { useGMapManage } from '/@/hooks/use-g-map'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import { useMouseTool } from '/@/hooks/use-mouse-tool'
import { getApp, getRoot } from '/@/root'
import { getApp } from '/@/root'
import { useMyStore } from '/@/store'
import { GeojsonCoordinate } from '/@/types/map'
import { MapDoodleEnum } from '/@/types/map-enum'
@ -447,28 +412,17 @@ import { PostElementsBody } from '/@/types/mapLayer' @@ -447,28 +412,17 @@ import { PostElementsBody } from '/@/types/mapLayer'
import { uuidv4 } from '/@/utils/uuid'
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'
import { deviceTsaUpdate } from '/@/hooks/use-g-map-tsa'
import {
DeviceOsd, DeviceStatus, DockOsd, EGear, EModeCode, GatewayOsd, EDockModeCode,
NetworkStateQualityEnum, NetworkStateTypeEnum, RainfallEnum, DroneInDockEnum
} from '/@/types/device'
import { DeviceOsd, DeviceStatus, DockOsd, EGear, EModeCode, GatewayOsd, EDockModeCode } from '/@/types/device'
import pin from '/@/assets/icons/pin-2d8cf0.svg'
import M30 from '/@/assets/icons/m30.png'
import {
BorderOutlined, LineOutlined, CloseOutlined, ControlOutlined, TrademarkOutlined, ArrowDownOutlined,
ThunderboltOutlined, SignalFilled, GlobalOutlined, HistoryOutlined, CloudUploadOutlined, RocketOutlined,
FieldTimeOutlined, CloudOutlined, CloudFilled, FolderOpenOutlined, RobotFilled, ArrowUpOutlined, CarryOutOutlined
ThunderboltOutlined, SignalFilled, GlobalOutlined, HistoryOutlined, CloudUploadOutlined,
FieldTimeOutlined, CloudOutlined, CloudFilled, FolderOpenOutlined, RobotFilled, ArrowUpOutlined
} from '@ant-design/icons-vue'
import { EDeviceTypeName } from '../types'
import DockControlPanel from './g-map/DockControlPanel.vue'
import { useDockControl } from './g-map/use-dock-control'
import DroneControlPanel from './g-map/DroneControlPanel.vue'
import { useConnectMqtt } from './g-map/use-connect-mqtt'
import LivestreamOthers from './livestream-others.vue'
import LivestreamAgora from './livestream-agora.vue'
import FlightAreaActionIcon from './flight-area/FlightAreaActionIcon.vue'
import { EFlightAreaType } from '../types/flight-area'
import { useFlightArea } from './flight-area/use-flight-area'
import { useFlightAreaDroneLocationEvent } from './flight-area/use-flight-area-drone-location-event'
import { useDockControl } from './g-map/useDockControl'
export default defineComponent({
components: {
@ -489,28 +443,20 @@ export default defineComponent({ @@ -489,28 +443,20 @@ export default defineComponent({
RobotFilled,
ArrowUpOutlined,
ArrowDownOutlined,
DockControlPanel,
DroneControlPanel,
CarryOutOutlined,
RocketOutlined,
LivestreamOthers,
LivestreamAgora,
FlightAreaActionIcon,
DockControlPanel
},
name: 'GMap',
props: {},
setup () {
const useMouseToolHook = useMouseTool()
const useGMapManageHook = useGMapManage()
const deviceTsaUpdateHook = deviceTsaUpdate()
const root = getRoot()
const deviceTsaUpdateHook = ref()
const mouseMode = ref(false)
const store = useMyStore()
const state = reactive({
currentType: '',
coverIndex: 0,
isFlightArea: false,
coverIndex: 0
})
const str: string = '--'
const deviceInfo = reactive({
@ -519,6 +465,52 @@ export default defineComponent({ @@ -519,6 +465,52 @@ export default defineComponent({
transmission_signal_quality: str,
} as GatewayOsd,
dock: {
media_file_detail: {
remain_upload: 0
},
sdr: {
up_quality: str,
down_quality: str,
frequency_band: -1,
},
network_state: {
type: 0,
quality: 0,
rate: 0,
},
drone_in_dock: 0,
drone_charge_state: {
state: 0,
capacity_percent: str,
},
rainfall: str,
wind_speed: str,
environment_temperature: str,
environment_humidity: str,
temperature: str,
humidity: str,
job_number: 0,
acc_time: 0,
first_power_on: 0,
positionState: {
gps_number: str,
is_fixed: 0,
rtk_number: str,
is_calibration: 0,
quality: 0,
},
storage: {
total: 0,
used: 0,
},
electric_supply_voltage: 0,
working_voltage: str,
working_current: str,
backup_battery_voltage: 0,
mode_code: -1,
cover_state: -1,
supplement_light_state: -1,
putter_state: -1,
} as DockOsd,
device: {
@ -555,33 +547,19 @@ export default defineComponent({ @@ -555,33 +547,19 @@ export default defineComponent({
const drawVisible = computed(() => {
return store.state.drawVisible
})
const livestreamOthersVisible = computed(() => {
return store.state.livestreamOthersVisible
})
const livestreamAgoraVisible = computed(() => {
return store.state.livestreamAgoraVisible
})
const osdVisible = computed(() => {
return store.state.osdVisible
})
const qualityStyle = computed(() => {
if (deviceInfo.dock.basic_osd?.network_state?.type === NetworkStateTypeEnum.ETHERNET ||
(deviceInfo.dock.basic_osd?.network_state?.quality || 0) > NetworkStateQualityEnum.FAIR) {
return 'color: #00ee8b'
}
if ((deviceInfo.dock.basic_osd?.network_state?.quality || 0) === NetworkStateQualityEnum.FAIR) {
return 'color: yellow'
}
return 'color: red'
})
watch(() => store.state.deviceStatusEvent,
data => {
deviceTsaUpdateHook.value = deviceTsaUpdate()
if (Object.keys(data.deviceOnline).length !== 0) {
deviceTsaUpdateHook.initMarker(data.deviceOnline.domain, data.deviceOnline.device_callsign, data.deviceOnline.sn)
deviceTsaUpdateHook.value.initMarker(data.deviceOnline.domain, data.deviceOnline.device_callsign, data.deviceOnline.sn)
store.state.deviceStatusEvent.deviceOnline = {} as DeviceStatus
}
if (Object.keys(data.deviceOffline).length !== 0) {
deviceTsaUpdateHook.removeMarker(data.deviceOffline.sn)
deviceTsaUpdateHook.value.removeMarker(data.deviceOffline.sn)
if ((data.deviceOffline.sn === osdVisible.value.sn) || (osdVisible.value.is_dock && data.deviceOffline.sn === osdVisible.value.gateway_sn)) {
osdVisible.value.visible = false
store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
@ -595,26 +573,26 @@ export default defineComponent({ @@ -595,26 +573,26 @@ export default defineComponent({
)
watch(() => store.state.deviceState, data => {
if (!deviceTsaUpdateHook.value) {
deviceTsaUpdateHook.value = deviceTsaUpdate()
}
if (data.currentType === EDeviceTypeName.Gateway && data.gatewayInfo[data.currentSn]) {
const coordinate = wgs84togcj02(data.gatewayInfo[data.currentSn].longitude, data.gatewayInfo[data.currentSn].latitude)
deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])
deviceTsaUpdateHook.value.moveTo(data.currentSn, data.gatewayInfo[data.currentSn].longitude, data.gatewayInfo[data.currentSn].latitude)
if (osdVisible.value.visible && osdVisible.value.gateway_sn !== '') {
deviceInfo.gateway = data.gatewayInfo[osdVisible.value.gateway_sn]
}
}
if (data.currentType === EDeviceTypeName.Aircraft && data.deviceInfo[data.currentSn]) {
const coordinate = wgs84togcj02(data.deviceInfo[data.currentSn].longitude, data.deviceInfo[data.currentSn].latitude)
deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])
deviceTsaUpdateHook.value.moveTo(data.currentSn, data.deviceInfo[data.currentSn].longitude, data.deviceInfo[data.currentSn].latitude)
if (osdVisible.value.visible && osdVisible.value.sn !== '') {
deviceInfo.device = data.deviceInfo[osdVisible.value.sn]
}
}
if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) {
const coordinate = wgs84togcj02(data.dockInfo[data.currentSn].basic_osd?.longitude, data.dockInfo[data.currentSn].basic_osd?.latitude)
deviceTsaUpdateHook.initMarker(EDeviceTypeName.Dock, EDeviceTypeName[EDeviceTypeName.Dock], data.currentSn, coordinate[0], coordinate[1])
deviceTsaUpdateHook.value.initMarker(EDeviceTypeName.Dock, EDeviceTypeName.Dock, data.currentSn, data.dockInfo[data.currentSn].longitude, data.dockInfo[data.currentSn].latitude)
if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn]
deviceInfo.device = data.deviceInfo[deviceInfo.dock.basic_osd.sub_device?.device_sn ?? osdVisible.value.sn]
deviceInfo.device = data.deviceInfo[deviceInfo.dock.sub_device?.device_sn]
}
}
}, {
@ -646,27 +624,15 @@ export default defineComponent({ @@ -646,27 +624,15 @@ export default defineComponent({
})
updateCoordinates('wgs84-gcj02', ele)
const data = { id: ele.id, name: ele.name }
if (MapElementEnum.PIN === ele.resource?.type) {
useGMapCoverHook.init2DPin(
ele.name,
ele.resource.content.geometry.coordinates,
ele.resource.content.properties.color,
data
)
} else if (MapElementEnum.LINE === ele.resource?.type) {
useGMapCoverHook.initPolyline(
ele.name,
ele.resource.content.geometry.coordinates,
ele.resource.content.properties.color,
data)
} else if (MapElementEnum.POLY === ele.resource?.type) {
useGMapCoverHook.initPolygon(
ele.name,
ele.resource.content.geometry.coordinates,
ele.resource.content.properties.color,
data)
{
id: ele.id,
name: ele.name
}
)
}
store.state.wsEvent.mapElementCreat = {}
@ -687,40 +653,25 @@ export default defineComponent({ @@ -687,40 +653,25 @@ export default defineComponent({
}
)
function draw (type: MapDoodleType, bool: boolean, flightAreaType?: EFlightAreaType) {
function draw (type: MapDoodleType, bool: boolean) {
state.currentType = type
useMouseToolHook.mouseTool(type, getDrawCallback)
mouseMode.value = bool
state.isFlightArea = !!flightAreaType
useMouseToolHook.mouseTool(type, getDrawCallback, flightAreaType)
}
// dock
// dock
const {
dockControlPanelVisible,
setDockControlPanelVisible,
onCloseControlPanel,
controlPanelVisible,
setControlPanelVisible,
sendDockControlCmd,
dockDebugOnOff,
} = useDockControl()
// drc
useConnectMqtt()
onMounted(() => {
const app = getApp()
useGMapManageHook.globalPropertiesConfig(app)
})
const { getDrawFlightAreaCallback, onFlightAreaDroneLocationWs } = useFlightArea()
useFlightAreaDroneLocationEvent(onFlightAreaDroneLocationWs)
function selectFlightAreaAction ({ type, isCircle }: { type: EFlightAreaType, isCircle: boolean }) {
draw(isCircle ? MapDoodleEnum.CIRCLE : MapDoodleEnum.POLYGON, true, type)
}
function getDrawCallback ({ obj }: { obj : any }) {
if (state.isFlightArea) {
getDrawFlightAreaCallback(obj)
return
}
function getDrawCallback ({ obj }) {
switch (state.currentType) {
case MapDoodleEnum.PIN:
postPinPositionResource(obj)
@ -743,7 +694,8 @@ export default defineComponent({ @@ -743,7 +694,8 @@ export default defineComponent({
(req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2])
const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name })
store.state.coverMap[req.id] = [obj]
store.state.coverList.push(obj)
// console.log(store.state.coverList)
}
async function postPolylineResource (obj) {
const req = getPolylineResource(obj)
@ -751,7 +703,8 @@ export default defineComponent({ @@ -751,7 +703,8 @@ export default defineComponent({
updateCoordinates('gcj02-wgs84', req)
const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name })
store.state.coverMap[req.id] = [obj]
store.state.coverList.push(obj)
// console.log(store.state.coverList)
}
async function postPolygonResource (obj) {
const req = getPoygonResource(obj)
@ -759,7 +712,8 @@ export default defineComponent({ @@ -759,7 +712,8 @@ export default defineComponent({
updateCoordinates('gcj02-wgs84', req)
const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name })
store.state.coverMap[req.id] = [obj]
store.state.coverList.push(obj)
// console.log(store.state.coverList)
}
function getPinPositionResource (obj) {
@ -809,12 +763,6 @@ export default defineComponent({ @@ -809,12 +763,6 @@ export default defineComponent({
console.log('layers', layers)
store.commit('SET_LAYER_INFO', layers)
}
function closeLivestreamOthers () {
store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', false)
}
function closeLivestreamAgora () {
store.commit('SET_LIVESTREAM_AGORA_VISIBLE', false)
}
function updateCoordinates (transformType: string, element: any) {
const geoType = element.resource?.content.geometry.type
const type = element.resource?.type as number
@ -835,38 +783,39 @@ export default defineComponent({ @@ -835,38 +783,39 @@ export default defineComponent({
) as GeojsonCoordinate
element.resource.content.geometry.coordinates = transResult
}
} else if (MapElementEnum.LINE === type) {
} else if (MapElementEnum.LINE === type && geoType === 'LineString') {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinates.forEach(coordinate => {
coordinate = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinates.forEach(coordinate => {
coordinate = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
}
element.resource.content.geometry.coordinates = coordinates
} else if (MapElementEnum.POLY === type) {
} else if (MapElementEnum.LINE === type && geoType === 'Polygon') {
const coordinates = element.resource?.content.geometry
.coordinates[0] as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinates.forEach(coordinate => {
coordinate = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinates.forEach(coordinate => {
coordinate = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
@ -880,8 +829,6 @@ export default defineComponent({ @@ -880,8 +829,6 @@ export default defineComponent({
draw,
mouseMode,
drawVisible,
livestreamOthersVisible,
livestreamAgoraVisible,
osdVisible,
pin,
state,
@ -891,17 +838,9 @@ export default defineComponent({ @@ -891,17 +838,9 @@ export default defineComponent({
EModeCode,
str,
EDockModeCode,
dockControlPanelVisible,
setDockControlPanelVisible,
onCloseControlPanel,
NetworkStateTypeEnum,
NetworkStateQualityEnum,
RainfallEnum,
DroneInDockEnum,
closeLivestreamOthers,
closeLivestreamAgora,
qualityStyle,
selectFlightAreaAction,
controlPanelVisible,
dockDebugOnOff,
setControlPanelVisible,
}
}
})
@ -913,7 +852,7 @@ export default defineComponent({ @@ -913,7 +852,7 @@ export default defineComponent({
height: 100%;
width: 100%;
.g-action-panel {
.g-action-panle {
position: absolute;
top: 16px;
right: 16px;
@ -946,17 +885,16 @@ export default defineComponent({ @@ -946,17 +885,16 @@ export default defineComponent({
}
.osd-panel {
position: absolute;
margin-left: 10px;
left: 0;
left: 350px;
top: 10px;
width: 480px;
background: #000;
color: #fff;
background: black;
color: white;
border-radius: 2px;
opacity: 0.8;
opacity: 0.7;
}
.osd > div:not(.dock-control-panel) {
margin-top: 5px;
.osd > div {
padding-top: 5px;
padding-left: 5px;
}
@ -998,17 +936,4 @@ export default defineComponent({ @@ -998,17 +936,4 @@ export default defineComponent({
min-height: 2px;
border-radius: 2px;
}
.liveview {
position: absolute;
color: #fff;
z-index: 1;
left: 0;
margin-left: 10px;
top: 10px;
text-align: center;
width: 800px;
height: 720px;
background: #232323;
}
</style>

3
src/components/common/sidebar.vue

@ -58,8 +58,7 @@ export default defineComponent({ @@ -58,8 +58,7 @@ export default defineComponent({
{ key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
{ key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
{ key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
{ key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
{ key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
{ key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' }
]
function selectedRoute (item: IOptions) {

3
src/components/common/topbar.vue

@ -63,8 +63,7 @@ const workspaceName = ref('') @@ -63,8 +63,7 @@ const workspaceName = ref('')
const options = [
{ key: 0, label: ERouterName.WORKSPACE.charAt(0).toUpperCase() + ERouterName.WORKSPACE.substr(1), path: '/' + ERouterName.WORKSPACE },
{ key: 1, label: ERouterName.MEMBERS.charAt(0).toUpperCase() + ERouterName.MEMBERS.substr(1), path: '/' + ERouterName.MEMBERS },
{ key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES },
{ key: 3, label: ERouterName.FIRMWARES.charAt(0).toUpperCase() + ERouterName.FIRMWARES.substr(1), path: '/' + ERouterName.FIRMWARES },
{ key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES }
]
const selected = ref<string>(root.$route.path)

61
src/components/devices/DeviceFirmwareStatus.vue

@ -1,61 +0,0 @@ @@ -1,61 +0,0 @@
<template>
<div>
<span class="status-tag pointer">
<a-popconfirm
:title="getTitle()"
ok-text="Yes"
cancel-text="No"
placement="left"
@confirm="onFirmwareStatusClick(firmware)"
>
<a-tag :color="firmware.firmware_status ? commonColor.NORMAL : commonColor.FAIL"
:class="firmware.firmware_status ? 'border-corner ' : 'status-disable border-corner'">
{{ getText(firmware.firmware_status) }}
</a-tag>
</a-popconfirm>
</span>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, watch, computed } from 'vue'
import { changeFirmareStatus } from '/@/api/manage'
import { ELocalStorageKey } from '/@/types'
import { Firmware, FirmwareStatusEnum } from '/@/types/device-firmware'
import { commonColor } from '/@/utils/color'
const props = defineProps<{
firmware: Firmware
}>()
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
function getTitle () {
return `Are you sure to set this firmware to ${getText(!props.firmware.firmware_status)}?`
}
function getText (status: boolean) {
return status ? FirmwareStatusEnum.TRUE : FirmwareStatusEnum.FALSE
}
function onFirmwareStatusClick (record: Firmware) {
changeFirmareStatus(workspaceId, record.firmware_id, { status: !record.firmware_status }).then((res) => {
if (res.code === 0) {
record.firmware_status = !record.firmware_status
}
})
}
</script>
<style lang="scss" scoped>
.status-disable{
opacity: 0.4;
}
.border-corner {
border-radius: 3px;
}
.pointer {
cursor: pointer;
}
</style>

26
src/components/devices/device-hms/DeviceHmsDrawer.vue

@ -67,12 +67,7 @@ @@ -67,12 +67,7 @@
</template>
<template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<div >{{ text }}</div>
</a-tooltip>
</template>
<template #domain="{text}">
<a-tooltip :title="EDeviceTypeName[text]">
<div >{{ EDeviceTypeName[text] }}</div>
<span>{{ text }}</span>
</a-tooltip>
</template>
</a-table>
@ -82,7 +77,7 @@ @@ -82,7 +77,7 @@
<!-- 暂时只抽取该组件 -->
<script lang="ts" setup>
import { watchEffect, reactive, ref, defineProps, defineEmits, watch } from 'vue'
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'
import { getDeviceHms, HmsQueryBody } from '/@/api/manage'
import moment, { Moment } from 'moment'
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
@ -100,7 +95,7 @@ const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) | @@ -100,7 +95,7 @@ const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) |
//
const sVisible = ref(false)
watch(props, () => {
watchEffect(() => {
sVisible.value = props.visible
// hms
if (props.visible) {
@ -122,10 +117,9 @@ const loading = ref(false) @@ -122,10 +117,9 @@ const loading = ref(false)
const hmsColumns: ColumnProps[] = [
{ title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } },
{ title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } },
{ title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },
{ title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle' },
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', slots: { customRender: 'code' } },
{ title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
{ title: 'Hms Message', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
]
interface DeviceHmsData {
@ -180,7 +174,7 @@ const param = reactive<HmsQueryBody>({ @@ -180,7 +174,7 @@ const param = reactive<HmsQueryBody>({
language: 'en',
begin_time: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0),
end_time: new Date().setHours(23, 59, 59, 999),
domain: -1,
domain: '',
level: '',
message: ''
})
@ -204,12 +198,12 @@ const levels = [ @@ -204,12 +198,12 @@ const levels = [
const deviceTypes = [
{
label: 'All',
value: -1
value: ''
}, {
label: EDeviceTypeName[EDeviceTypeName.Aircraft],
label: EDeviceTypeName.Aircraft,
value: EDeviceTypeName.Aircraft
}, {
label: EDeviceTypeName[EDeviceTypeName.Dock],
label: EDeviceTypeName.Dock,
value: EDeviceTypeName.Dock
}
]
@ -256,7 +250,7 @@ function onTimeChange (newTime: [Moment, Moment]) { @@ -256,7 +250,7 @@ function onTimeChange (newTime: [Moment, Moment]) {
getHms()
}
function onDeviceTypeSelect (val: number) {
function onDeviceTypeSelect (val: string) {
param.sns = [param.device_sn, param.children_sn]
if (val === EDeviceTypeName.Dock) {
param.sns = [param.device_sn, '']

4
src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue

@ -21,8 +21,8 @@ @@ -21,8 +21,8 @@
<!-- 设备类型 -->
<template #device_type="{ record }">
<div>
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key]}}</div>
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key]}}</div>
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.key]}}</div>
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.key]}}</div>
</div>
</template>
<!-- 设备sn -->

2
src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
</span>
<!-- 进度 -->
<span v-if="device.firmware_status === DeviceFirmwareStatusEnum.DuringUpgrade">
{{ `${device.firmware_progress}`}}
{{ `${device.firmware_progress}%`}}
</span>
</div>
</template>

59
src/components/flight-area/FlightAreaActionIcon.vue

@ -1,59 +0,0 @@ @@ -1,59 +0,0 @@
<template>
<div @click="selectCurrent">
<a-dropdown class="height-100 width-100 icon-panel">
<FlightAreaIcon :type="actionMap[selectedKey].type" :is-circle="actionMap[selectedKey].isCircle" :hide-title="true"/>
<template #overlay>
<a-menu @click="selectAction" mode="vertical-right" :selectedKeys="[selectedKey]">
<a-menu-item v-for="(v, k) in actionMap" :key="k">
<FlightAreaIcon :type="v.type" :is-circle="v.isCircle"/>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</template>
<script lang="ts" setup>
import { ref, defineEmits } from 'vue'
import { EFlightAreaType } from '../../types/flight-area'
import FlightAreaIcon from './FlightAreaIcon.vue'
const emit = defineEmits(['select-action', 'click'])
const actionMap: Record<string, { type: EFlightAreaType, isCircle: boolean}> = {
1: {
type: EFlightAreaType.DFENCE,
isCircle: true,
},
2: {
type: EFlightAreaType.DFENCE,
isCircle: false,
},
3: {
type: EFlightAreaType.NFZ,
isCircle: true,
},
4: {
type: EFlightAreaType.NFZ,
isCircle: false,
},
}
const selectedKey = ref<string>('1')
const selectAction = (item: any) => {
selectedKey.value = item.key
emit('select-action', actionMap[item.key])
}
const selectCurrent = () => {
emit('click', actionMap[selectedKey.value])
}
</script>
<style lang="scss">
.icon-panel {
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>

197
src/components/flight-area/FlightAreaDevicePanel.vue

@ -1,197 +0,0 @@ @@ -1,197 +0,0 @@
<template>
<div class="flight-area-device-panel">
<Title title="Choose Synchronous Devices">
<div style="position: absolute; right: 10px;">
<a style="color: white;" @click="closePanel"><CloseOutlined /></a>
</div>
</Title>
<div class="scrollbar">
<div id="data" v-if="data.length !== 0">
<div v-for="dock in data" :key="dock.device_sn">
<div class="pt5 panel flex-row" @click="selectDock(dock)" :style="{opacity: selectedDocksMap[dock.device_sn] ? 1 : 0.5 }">
<div style="width: 88%">
<div class="title">
<RobotFilled class="fz20"/>
<a-tooltip :title="dock.nickname">
<div class="pr10 ml5" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
</a-tooltip>
</div>
<div class="ml10 mr10 pr5 pl5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
<div>
Custom Flight Area
</div>
<div>
<div v-if="!dock.status">
<a-tooltip title="Dock offline">
<ApiOutlined />
</a-tooltip>
</div>
<div v-else-if="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZED">
<a-tooltip title="Data synced">
<CheckCircleTwoTone twoToneColor="#28d445"/>
</a-tooltip>
</div>
<div v-else-if="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING
|| deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC">
<a-tooltip title="To be synced">
<SyncOutlined spin />
</a-tooltip>
</div>
<div v-else>
<a-tooltip :title="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_msg || 'No synchronization'">
<ExclamationCircleTwoTone twoToneColor="#e70102" />
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="box" v-if="selectedDocksMap[dock.device_sn]">
<CheckOutlined />
</div>
</div>
</div>
<DividerLine style="position: absolute; bottom: 68px;" />
<div class="flex-row flex-justify-between footer">
<a-button class="mr10" @click="closePanel">Cancel
</a-button>
<a-button type="primary" :disabled="confirmDisabled" @click="syncDeviceFlightArea">Sync
</a-button>
</div>
</div>
<div v-else>
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { CloseOutlined, RobotFilled, CheckOutlined, ApiOutlined, CheckCircleTwoTone, SyncOutlined, ExclamationCircleTwoTone } from '@ant-design/icons-vue'
import Title from '/@/components/workspace/Title.vue'
import { defineEmits, onMounted, ref, defineProps, computed } from 'vue'
import { getBindingDevices } from '/@/api/manage'
import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
import { IPage } from '/@/api/http/type'
import { Device } from '/@/types/device'
import DividerLine from '../workspace/DividerLine.vue'
import { message } from 'ant-design-vue'
import { GetDeviceStatus, syncFlightArea } from '/@/api/flight-area'
import { ESyncStatus } from '/@/types/flight-area'
const props = defineProps<{
data: GetDeviceStatus[]
}>()
const emit = defineEmits(['closePanel'])
const closePanel = () => {
emit('closePanel', false)
}
const confirmDisabled = ref(false)
const deviceStatusMap = computed(() => props.data.reduce((obj: Record<string, GetDeviceStatus>, val: GetDeviceStatus) => {
obj[val.device_sn] = val
return obj
}, {} as Record<string, GetDeviceStatus>))
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
const body: IPage = {
page: 1,
total: 0,
page_size: 10,
}
const data = ref<Device[]>([])
const selectedDocksMap = ref<Record<string, boolean>>({})
const getDocks = async () => {
await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
if (res.code !== 0) {
return
}
data.value.push(...res.data.list)
body.page = res.data.pagination.page
body.page_size = res.data.pagination.page_size
body.total = res.data.pagination.total
})
}
const selectDock = (dock: Device) => {
if (!dock.status) {
message.info(`Dock(${dock.nickname}) is offline.`)
return
}
if (deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING ||
deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC) {
message.info('The dock is synchronizing.')
return
}
selectedDocksMap.value[dock.device_sn] = !selectedDocksMap.value[dock.device_sn]
}
onMounted(() => {
getDocks()
const key = setInterval(() => {
if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page) {
clearInterval(key)
return
}
body.page++
getDocks()
}, 1000)
})
const syncDeviceFlightArea = () => {
const keys = Object.keys(selectedDocksMap.value)
if (keys.length === 0) {
message.warn('Please select the docks that need to be synchronized.')
return
}
confirmDisabled.value = true
Object.keys(selectedDocksMap.value).forEach(k => {
const device = deviceStatusMap.value[k]
if (device) {
device.flight_area_status = { sync_code: 0, sync_status: ESyncStatus.WAIT_SYNC, sync_msg: '' }
}
})
syncFlightArea(keys).then(res => {
if (res.code === 0) {
message.success('The devices are synchronizing...')
selectedDocksMap.value = {}
}
}).finally(() => setTimeout(() => {
confirmDisabled.value = false
}, 3000))
}
</script>
<style lang="scss" scoped>
.flight-area-device-panel {
position: absolute;
left: 285px;
width: 280px;
height: 100vh;
float: right;
top: 0;
z-index: 1000;
color: white;
background: #282828;
.footer {
position: absolute;
width: 100%;
bottom: 10px;
padding: 10px;
button {
width: 45%;
border: 0;
}
}
.scrollbar {
overflow-y: auto;
height: calc(100vh - 150px);
}
.box {
font-size: 22px;
line-height: 60px;
}
}
</style>

33
src/components/flight-area/FlightAreaIcon.vue

@ -1,33 +0,0 @@ @@ -1,33 +0,0 @@
<template>
<div class="flex-row flex-align-center">
<div class="shape" :class="type" :style="isCircle ? 'border-radius: 50%;' : ''"></div>
<div class="ml5" v-if="!hideTitle">{{ FlightAreaTypeTitleMap[type][isCircle ? EGeometryType.CIRCLE : EGeometryType.POLYGON] }}</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import { EFlightAreaType, EGeometryType, FlightAreaTypeTitleMap } from '../../types/flight-area'
const props = defineProps<{
type: EFlightAreaType,
isCircle: boolean,
hideTitle?: boolean
}>()
</script>
<style lang="scss">
.nfz {
border-color: red;
}
.dfence {
border-color: $tag-green;
}
.shape {
width: 16px;
height: 16px;
border-width: 3px;
border-style: solid;
}
</style>

89
src/components/flight-area/FlightAreaItem.vue

@ -1,89 +0,0 @@ @@ -1,89 +0,0 @@
<template>
<div class="panel" style="padding-top: 5px;" :class="{disable: !flightArea.status}">
<div class="title">
<a-tooltip :title="flightArea.name">
<div class="pr10" style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ flightArea.name }}</div>
</a-tooltip>
</div>
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
<span class="mr10">Update at {{ formatDateTime(flightArea.update_time).toLocaleString() }}</span>
</div>
<div class="flex-row flex-justify-between flex-align-center ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<FlightAreaIcon :type="flightArea.type" :isCircle="EGeometryType.CIRCLE === flightArea.content.geometry.type"/>
<div class="mr10 operate">
<a-popconfirm v-if="flightArea.status" title="Is it determined to disable the current area?" okText="Disable" @confirm="changeAreaStatus(false)">
<stop-outlined />
</a-popconfirm>
<a-popconfirm v-else @confirm="changeAreaStatus(true)" title="Is it determined to enable the current area?" okText="Enable" >
<check-circle-outlined />
</a-popconfirm>
<EnvironmentFilled class="ml10" @click="clickLocation"/>
<a-popconfirm title="Is it determined to delete the current area?" okText="Delete" okType="danger" @confirm="deleteArea">
<delete-outlined class="ml10" />
</a-popconfirm>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, reactive, defineEmits, computed } from 'vue'
import { GetFlightArea, changeFlightAreaStatus } from '../../api/flight-area'
import FlightAreaIcon from './FlightAreaIcon.vue'
import { formatDateTime } from '../../utils/time'
import { EGeometryType } from '../../types/flight-area'
import { StopOutlined, CheckCircleOutlined, DeleteOutlined, EnvironmentFilled } from '@ant-design/icons-vue'
const props = defineProps<{
data: GetFlightArea
}>()
const emit = defineEmits(['delete', 'update', 'location'])
const flightArea = computed(() => props.data)
const changeAreaStatus = (status: boolean) => {
changeFlightAreaStatus(props.data.area_id, status).then(res => {
if (res.code === 0) {
flightArea.value.status = status
emit('update', flightArea)
}
})
}
const deleteArea = () => {
emit('delete', flightArea.value.area_id)
}
const clickLocation = () => {
emit('location', flightArea.value.area_id)
}
</script>
<style lang="scss" scoped>
.panel {
background: #3c3c3c;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
height: 90px;
width: 95%;
font-size: 13px;
border-radius: 2px;
cursor: pointer;
.title {
display: flex;
flex-direction: row;
align-items: center;
height: 30px;
font-weight: bold;
margin: 0px 10px 0 10px;
}
.operate > *{
font-size: 16px;
}
}
.disable {
opacity: 50%;
}
</style>

43
src/components/flight-area/FlightAreaPanel.vue

@ -1,43 +0,0 @@ @@ -1,43 +0,0 @@
<template>
<div class="flight-area-panel">
<div v-if="data.length === 0">
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
<div v-else v-for="area in flightAreaList" :key="area.area_id">
<FlightAreaItem :data="area" @delete="deleteArea" @update="updateArea" @location="clickLocation(area)"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, computed } from 'vue'
import FlightAreaItem from './FlightAreaItem.vue'
import { GetFlightArea } from '/@/api/flight-area'
const emit = defineEmits(['deleteArea', 'updateArea', 'locationArea'])
const props = defineProps<{
data: GetFlightArea[]
}>()
const flightAreaList = computed(() => props.data)
const deleteArea = (areaId: string) => {
emit('deleteArea', areaId)
}
const updateArea = (area: GetFlightArea) => {
emit('updateArea', area)
}
const clickLocation = (area: GetFlightArea) => {
emit('locationArea', area)
}
</script>
<style lang="scss" scoped>
.flight-area-panel {
overflow-y: auto;
height: calc(100vh - 150px);
}
</style>

66
src/components/flight-area/FlightAreaSyncPanel.vue

@ -1,66 +0,0 @@ @@ -1,66 +0,0 @@
<template>
<div class="flight-area-sync-panel p10 flex-row flex-align-center" >
<RobotFilled class="fz30" twoToneColor="red" fill="#00ff00"/>
<div class="ml20 mr10 flex-column" @click="switchPanel">
<div class="fz18">Sync Across Devices</div>
<div v-if="syncDevicesCount > 0"><a-spin /> Syncing to {{ syncDevicesCount }} devices</div>
</div>
<RightOutlined class="fz18" @click="switchPanel"/>
<FlightAreaDevicePanel v-if="visible" @close-panel="closePanel" :data="syncDevices"/>
</div>
</template>
<script lang="ts" setup>
import { RobotFilled, RightOutlined } from '@ant-design/icons-vue'
import FlightAreaDevicePanel from '/@/components/flight-area/FlightAreaDevicePanel.vue'
import { computed, onMounted, ref, watch } from 'vue'
import { GetDeviceStatus, getDeviceStatus } from '/@/api/flight-area'
import { ESyncStatus, FlightAreaSyncProgress } from '/@/types/flight-area'
import { useFlightAreaSyncProgressEvent } from './use-flight-area-sync-progress-event'
const visible = ref(false)
const syncDevices = ref<GetDeviceStatus[]>([])
const syncDevicesCount = computed(() => syncDevices.value.filter(device =>
device.flight_area_status.sync_status === ESyncStatus.SYNCHRONIZING || device.flight_area_status.sync_status === ESyncStatus.WAIT_SYNC).length)
const getAllDeviceStatus = () => {
getDeviceStatus().then(res => {
if (res.code === 0) {
syncDevices.value = res.data
}
})
}
onMounted(() => {
getAllDeviceStatus()
})
const switchPanel = () => {
visible.value = !visible.value
}
const closePanel = (val: boolean) => {
visible.value = val
}
const handleSyncProgress = (data: FlightAreaSyncProgress) => {
let has = false
const status = { sync_code: data.result, sync_status: data.status, sync_msg: data.message }
syncDevices.value.forEach(device => {
if (data.sn === device.device_sn) {
device.flight_area_status = status
has = true
}
})
if (!has) {
syncDevices.value.push({ device_sn: data.sn, flight_area_status: status })
}
}
useFlightAreaSyncProgressEvent(handleSyncProgress)
</script>
<style lang="scss" scoped>
.flight-area-sync-panel {
height: 70px;
cursor: pointer;
}
</style>

18
src/components/flight-area/use-flight-area-drone-location-event.ts

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
import { FlightAreasDroneLocation } from '/@/types/flight-area'
import { CommonHostWs } from '/@/websocket'
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
export function useFlightAreaDroneLocationEvent (onFlightAreaDroneLocationWs: (data: CommonHostWs<FlightAreasDroneLocation>) => void): void {
function handleDroneLocationEvent (data: any) {
onFlightAreaDroneLocationWs(data.data)
}
onMounted(() => {
EventBus.on('flightAreasDroneLocationWs', handleDroneLocationEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasDroneLocationWs', handleDroneLocationEvent)
})
}

17
src/components/flight-area/use-flight-area-sync-progress-event.ts

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

30
src/components/flight-area/use-flight-area-update.ts

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

155
src/components/flight-area/use-flight-area.ts

@ -1,155 +0,0 @@ @@ -1,155 +0,0 @@
import { message, notification } from 'ant-design-vue'
import { MapDoodleEnum } from '/@/types/map-enum'
import { getRoot } from '/@/root'
import { PostFlightAreaBody, saveFlightArea } from '/@/api/flight-area'
import { generateCircleContent, generatePolyContent } from '/@/utils/map-layer-utils'
import { GeojsonCoordinate } from '/@/utils/genjson'
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform.js'
import { uuidv4 } from '/@/utils/uuid'
import { CommonHostWs } from '/@/websocket'
import { FlightAreasDroneLocation } from '/@/types/flight-area'
import rootStore from '/@/store'
import { h } from 'vue'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import moment from 'moment'
import { DATE_FORMAT } from '/@/utils/constants'
export function useFlightArea () {
const root = getRoot()
const store = rootStore
const coverMap = store.state.coverMap
let useGMapCoverHook = useGMapCover()
const MIN_RADIUS = 10
function checkCircle (obj: any): boolean {
if (obj.getRadius() < MIN_RADIUS) {
message.error(`The radius must be greater than ${MIN_RADIUS}m.`)
root.$map.remove(obj)
return false
}
return true
}
function checkPolygon (obj: any): boolean {
const path: any[][] = obj.getPath()
if (path.length < 3) {
message.error('The path of the polygon cannot be crossed.')
root.$map.remove(obj)
return false
}
// root.$aMap.GeometryUtil.doesLineLineIntersect()
return true
}
function setExtData (obj: any) {
let ext = obj.getExtData()
const id = uuidv4()
const name = `${ext.type}-${moment().format(DATE_FORMAT)}`
ext = Object.assign({}, ext, { id, name })
obj.setExtData(ext)
return ext
}
function createFlightArea (obj: any) {
const ext = obj.getExtData()
const data = {
id: ext.id,
type: ext.type,
name: ext.name,
}
let coordinates: GeojsonCoordinate | GeojsonCoordinate[][]
let content
switch (ext.mapType) {
case 'circle':
content = generateCircleContent(obj.getCenter(), obj.getRadius())
coordinates = getWgs84(content.geometry.coordinates as GeojsonCoordinate)
break
case 'polygon':
content = generatePolyContent(obj.getPath()).content
coordinates = [getWgs84(content.geometry.coordinates[0] as GeojsonCoordinate[])]
break
default:
message.error(`Invalid type: ${obj.mapType}`)
root.$map.remove(obj)
return
}
content.geometry.coordinates = coordinates
saveFlightArea(Object.assign({}, data, { content }) as PostFlightAreaBody).then(res => {
if (res.code !== 0) {
useGMapCoverHook.removeCoverFromMap(ext.id)
}
}).finally(() => root.$map.remove(obj))
}
function getDrawFlightAreaCallback (obj: any) {
useGMapCoverHook = useGMapCover()
const ext = setExtData(obj)
switch (ext.mapType) {
case MapDoodleEnum.CIRCLE:
if (!checkCircle(obj)) {
return
}
break
case MapDoodleEnum.POLYGON:
if (!checkPolygon(obj)) {
return
}
break
default:
break
}
createFlightArea(obj)
}
const getWgs84 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {
if (coordinate[0] instanceof Array) {
return (coordinate as GeojsonCoordinate[]).map(c => gcj02towgs84(c[0], c[1])) as T
}
return gcj02towgs84(coordinate[0], coordinate[1])
}
const getGcj02 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {
if (coordinate[0] instanceof Array) {
return (coordinate as GeojsonCoordinate[]).map(c => wgs84togcj02(c[0], c[1])) as T
}
return wgs84togcj02(coordinate[0], coordinate[1])
}
const onFlightAreaDroneLocationWs = (data: CommonHostWs<FlightAreasDroneLocation>) => {
const nearArea = data.host.drone_locations.filter(val => !val.is_in_area)
const inArea = data.host.drone_locations.filter(val => val.is_in_area)
notification.warning({
key: `flight-area-${data.sn}`,
message: `Drone(${data.sn}) flight area information`,
description: h('div',
[
h('div', [
h('span', { class: 'fz18' }, 'In the flight area: '),
h('ul', [
...inArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))
])
]),
h('div', [
h('span', { class: 'fz18' }, 'Near the flight area: '),
h('ul', [
...nearArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))
])
])
]),
duration: null,
style: {
width: '420px',
marginTop: '-8px',
marginLeft: '-28px',
}
})
}
return {
getDrawFlightAreaCallback,
getGcj02,
getWgs84,
onFlightAreaDroneLocationWs,
}
}

4
src/components/g-map/DeviceSettingBox.vue

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<template>
<div class="device-setting-wrapper">
<div class="device-setting-header">Device Property Set</div>
<div class="device-setting-header">设备属性设置</div>
<div class="device-setting-box">
<!-- 飞行器夜航灯 -->
<div class="control-setting-item">
@ -154,7 +154,7 @@ import { useMyStore } from '/@/store' @@ -154,7 +154,7 @@ import { useMyStore } from '/@/store'
import { cloneDeep } from 'lodash'
import { initDeviceSetting, initDeviceSettingFormModel, DeviceSettingKeyEnum } from '/@/types/device-setting'
import { updateDeviceSettingInfoByOsd, updateDeviceSettingFormModelByOsd } from '/@/utils/device-setting'
import { useDeviceSetting } from './use-device-setting'
import { useDeviceSetting } from './useDeviceSetting'
import DeviceSettingPopover from './DeviceSettingPopover.vue'
const props = defineProps<{

20
src/components/g-map/DockControlPanel.vue

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
<div class="dock-control-panel">
<!-- title -->
<div class="dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between">
<span>Device Control<span class="fz12 pl15">{{ props.sn}}</span></span>
<span>设备操作 {{ props.sn}}</span>
<span @click="closeControlPanel">
<CloseOutlined />
</span>
@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
<!-- cmd -->
<div class="control-cmd-wrapper">
<div class="control-cmd-header">
Device Remote Debug
远程调试
<a-switch class="debug-btn" checked-children="" un-checked-children="" v-model:checked="debugStatus" @change="onDeviceStatusChange"/>
</div>
<div class="control-cmd-box">
@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
<div class="item-status">{{ cmdItem.status }}</div>
</div>
<div class="control-cmd-item-right">
<a-button :disabled="!debugStatus || cmdItem.disabled" :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)">
<a-button :disabled="!debugStatus" :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)">
{{ cmdItem.operateText }}
</a-button>
</div>
@ -38,8 +38,8 @@ import { defineProps, defineEmits, ref, watch } from 'vue' @@ -38,8 +38,8 @@ import { defineProps, defineEmits, ref, watch } from 'vue'
import {
CloseOutlined
} from '@ant-design/icons-vue'
import { useDockControl } from './use-dock-control'
import { DeviceInfoType, EDockModeCode } from '/@/types/device'
import { useDockControl } from './useDockControl'
import { DeviceInfoType } from '/@/types/device'
import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd'
import { useMyStore } from '/@/store'
import { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd'
@ -73,14 +73,15 @@ watch(() => props.deviceInfo, (value) => { @@ -73,14 +73,15 @@ watch(() => props.deviceInfo, (value) => {
deep: true
})
// dock
const debugStatus = ref(props.deviceInfo.dock?.basic_osd?.mode_code === EDockModeCode.Remote_Debugging)
const emit = defineEmits(['close-control-panel'])
function closeControlPanel () {
emit('close-control-panel', props.sn, debugStatus.value)
emit('close-control-panel', props.sn, false)
}
// dock
const debugStatus = ref(false)
async function onDeviceStatusChange (status: boolean) {
let result = false
if (status) {
@ -105,8 +106,7 @@ const { @@ -105,8 +106,7 @@ const {
async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {
const success = await sendDockControlCmd({
sn: props.sn,
cmd: cmdItem.cmdKey,
action: cmdItem.action
cmd: cmdItem.cmdKey
}, true)
if (success) {
// updateDeviceSingleCmdInfo(cmdList.value[index])

34
src/components/g-map/DroneControlInfoPanel.vue

@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
<template>
<div class="drone-control-info-wrap">
<a-textarea v-model:value="info" placeholder="drc info" :rows="5" disabled/>
</div>
</template>
<script lang="ts" setup>
import { ref, defineProps, watch } from 'vue'
const props = defineProps<{
message?: string,
}>()
const info = ref('')
watch(() => props.message, message => {
info.value = message || ''
}, {
immediate: true
})
// const emit = defineEmits(['cancel', 'confirm'])
</script>
<style lang="scss" scoped>
.drone-control-info-wrap {
&::v-deep{
textarea.ant-input {
background-color: #000;
color: #fff;
white-space: pre-wrap;
}
}
}
</style>

837
src/components/g-map/DroneControlPanel.vue

@ -1,837 +0,0 @@ @@ -1,837 +0,0 @@
<template>
<div class="drone-control-wrapper">
<div class="drone-control-header">Drone Flight Control</div>
<div class="drone-control-box">
<div class="box">
<div class="row">
<div class="drone-control"><Button :ghost="!flightController" size="small" @click="onClickFightControl">{{ flightController ? 'Exit Remote Control' : 'Enter Remote Control'}}</Button></div>
</div>
<div class="row">
<div class="drone-control-direction">
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_Q)" @onmouseup="onMouseUp">
<template #icon><UndoOutlined /></template><span class="word">Q</span>
</Button>
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_W)" @onmouseup="onMouseUp">
<template #icon><UpOutlined/></template><span class="word">W</span>
</Button>
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_E)" @onmouseup="onMouseUp">
<template #icon><RedoOutlined /></template><span class="word">E</span>
</Button>
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.ARROW_UP)" @onmouseup="onMouseUp">
<template #icon><ArrowUpOutlined /></template>
</Button>
<br />
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_A)" @onmouseup="onMouseUp">
<template #icon><LeftOutlined/></template><span class="word">A</span>
</Button>
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_S)" @onmouseup="onMouseUp">
<template #icon><DownOutlined/></template><span class="word">S</span>
</Button>
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.KEY_D)" @onmouseup="onMouseUp">
<template #icon><RightOutlined/></template><span class="word">D</span>
</Button>
<Button size="small" ghost @mousedown="onMouseDown(KeyCode.ARROW_DOWN)" @onmouseup="onMouseUp">
<template #icon><ArrowDownOutlined /></template>
</Button>
</div>
<Button type="primary" size="small" danger ghost @click="handleEmergencyStop" >
<template #icon><PauseCircleOutlined/></template><span>Break</span>
</Button>
</div>
<div class="row">
<DroneControlPopover
:visible="flyToPointPopoverData.visible"
:loading="flyToPointPopoverData.loading"
@confirm="($event) => onFlyToConfirm(true)"
@cancel="($event) =>onFlyToConfirm(false)"
>
<template #formContent>
<div class="form-content">
<div>
<span class="form-label">latitude:</span>
<a-input-number v-model:value="flyToPointPopoverData.latitude"/>
</div>
<div>
<span class="form-label">longitude:</span>
<a-input-number v-model:value="flyToPointPopoverData.longitude"/>
</div>
<div>
<span class="form-label">height(m):</span>
<a-input-number v-model:value="flyToPointPopoverData.height"/>
</div>
</div>
</template>
<Button size="small" ghost @click="onShowFlyToPopover" >
<span>Fly to</span>
</Button>
</DroneControlPopover>
<Button size="small" ghost @click="onStopFlyToPoint" >
<span>Stop Fly to</span>
</Button>
<DroneControlPopover
:visible="takeoffToPointPopoverData.visible"
:loading="takeoffToPointPopoverData.loading"
@confirm="($event) => onTakeoffToPointConfirm(true)"
@cancel="($event) =>onTakeoffToPointConfirm(false)"
>
<template #formContent>
<div class="form-content">
<div>
<span class="form-label">latitude:</span>
<a-input-number v-model:value="takeoffToPointPopoverData.latitude"/>
</div>
<div>
<span class="form-label">longitude:</span>
<a-input-number v-model:value="takeoffToPointPopoverData.longitude"/>
</div>
<div>
<span class="form-label">height(m):</span>
<a-input-number v-model:value="takeoffToPointPopoverData.height"/>
</div>
<div>
<span class="form-label">Safe Takeoff Altitude(m):</span>
<a-input-number v-model:value="takeoffToPointPopoverData.securityTakeoffHeight"/>
</div>
<div>
<span class="form-label">Return-to-Home Altitude(m):</span>
<a-input-number v-model:value="takeoffToPointPopoverData.rthAltitude"/>
</div>
<div>
<span class="form-label">Lost Action:</span>
<a-select
v-model:value="takeoffToPointPopoverData.rcLostAction"
style="width: 120px"
:options="LostControlActionInCommandFLightOptions"
></a-select>
</div>
<div>
<span class="form-label">Wayline Lost Action:</span>
<a-select
v-model:value="takeoffToPointPopoverData.exitWaylineWhenRcLost"
style="width: 120px"
:options="WaylineLostControlActionInCommandFlightOptions"
></a-select>
</div>
<div>
<span class="form-label">Return-to-Home Mode:</span>
<a-select
v-model:value="takeoffToPointPopoverData.rthMode"
style="width: 120px"
:options="RthModeInCommandFlightOptions"
></a-select>
</div>
<div>
<span class="form-label">Commander Mode Lost Action:</span>
<a-select
v-model:value="takeoffToPointPopoverData.commanderModeLostAction"
style="width: 120px"
:options="CommanderModeLostActionInCommandFlightOptions"
></a-select>
</div>
<div>
<span class="form-label">Commander Flight Mode:</span>
<a-select
v-model:value="takeoffToPointPopoverData.commanderFlightMode"
style="width: 120px"
:options="CommanderFlightModeInCommandFlightOptions"
></a-select>
</div>
<div>
<span class="form-label">Commander Flight Height(m):</span>
<a-input-number v-model:value="takeoffToPointPopoverData.commanderFlightHeight"/>
</div>
</div>
</template>
<Button size="small" ghost @click="onShowTakeoffToPointPopover" >
<span>Take off</span>
</Button>
<div v-for="(cmdItem) in cmdList" :key="cmdItem.cmdKey" class="control-cmd-item">
<Button :loading="cmdItem.loading" size="small" ghost @click="sendControlCmd(cmdItem, 0)">
{{ cmdItem.operateText }}
</Button>
</div>
<div>
<Button size="small" ghost @click="openLivestreamAgora" >
<span>Agora Live</span>
</Button>
<Button size="small" ghost @click="openLivestreamOthers" >
<span>RTMP/GB28181 Live</span>
</Button>
</div>
</DroneControlPopover>
</div>
</div>
<div class="box">
<div class="row">
<Select v-model:value="payloadSelectInfo.value" style="width: 110px; marginRight: 5px" :options="payloadSelectInfo.options" @change="handlePayloadChange"/>
<div class="drone-control">
<Button type="primary" size="small" @click="onAuthPayload">Payload Control</Button>
</div>
</div>
<div class="row">
<DroneControlPopover
:visible="gimbalResetPopoverData.visible"
:loading="gimbalResetPopoverData.loading"
@confirm="($event) => onGimbalResetConfirm(true)"
@cancel="($event) =>onGimbalResetConfirm(false)"
>
<template #formContent>
<div class="form-content">
<div>
<span class="form-label">reset mode:</span>
<a-select
v-model:value="gimbalResetPopoverData.resetMode"
style="width: 180px"
:options="GimbalResetModeOptions"
></a-select>
</div>
</div>
</template>
<Button size="small" ghost @click="onShowGimbalResetPopover">
<span>Gimbal Reset</span>
</Button>
</DroneControlPopover>
<Button size="small" ghost @click="onSwitchCameraMode">
<span>Camera Mode Switch</span>
</Button>
</div>
<div class="row">
<Button size="small" ghost @click="onStartCameraRecording">
<span>Start Recording</span>
</Button>
<Button size="small" ghost @click="onStopCameraRecording">
<span>Stop Recording</span>
</Button>
</div>
<div class="row">
<Button size="small" ghost @click="onTakeCameraPhoto">
<span>Take Photo</span>
</Button>
<DroneControlPopover
:visible="zoomFactorPopoverData.visible"
:loading="zoomFactorPopoverData.loading"
@confirm="($event) => onZoomFactorConfirm(true)"
@cancel="($event) =>onZoomFactorConfirm(false)"
>
<template #formContent>
<div class="form-content">
<div>
<span class="form-label">camera type:</span>
<a-select
v-model:value="zoomFactorPopoverData.cameraType"
style="width: 120px"
:options="ZoomCameraTypeOptions"
></a-select>
</div>
<div>
<span class="form-label">zoom factor:</span>
<a-input-number v-model:value="zoomFactorPopoverData.zoomFactor" :min="2" :max="200" />
</div>
</div>
</template>
<Button size="small" ghost @click="($event) => onShowZoomFactorPopover()">
<span class="word" @click=";">Zoom</span>
</Button>
</DroneControlPopover>
<DroneControlPopover
:visible="cameraAimPopoverData.visible"
:loading="cameraAimPopoverData.loading"
@confirm="($event) => onCameraAimConfirm(true)"
@cancel="($event) =>onCameraAimConfirm(false)"
>
<template #formContent>
<div class="form-content">
<div>
<span class="form-label">camera type:</span>
<a-select
v-model:value="cameraAimPopoverData.cameraType"
style="width: 120px"
:options="CameraTypeOptions"
></a-select>
</div>
<div>
<span class="form-label">locked:</span>
<a-switch v-model:checked="cameraAimPopoverData.locked"/>
</div>
<div>
<span class="form-label">x:</span>
<a-input-number v-model:value="cameraAimPopoverData.x" :min="0" :max="1"/>
</div>
<div>
<span class="form-label">y:</span>
<a-input-number v-model:value="cameraAimPopoverData.y" :min="0" :max="1"/>
</div>
</div>
</template>
<Button size="small" ghost @click="($event) => onShowCameraAimPopover()">
<span class="word" @click=";">AIM</span>
</Button>
</DroneControlPopover>
</div>
</div>
</div>
<!-- 信息提示 -->
<DroneControlInfoPanel :message="drcInfo"></DroneControlInfoPanel>
</div>
</template>
<script setup lang="ts">
import { defineProps, reactive, ref, watch, computed, onMounted, watchEffect } from 'vue'
import { Select, message, Button } from 'ant-design-vue'
import { PayloadInfo, DeviceInfoType, ControlSource, DeviceOsdCamera, DrcStateEnum } from '/@/types/device'
import { useMyStore } from '/@/store'
import { postDrcEnter, postDrcExit } from '/@/api/drc'
import { useMqtt, DeviceTopicInfo } from './use-mqtt'
import { DownOutlined, UpOutlined, LeftOutlined, RightOutlined, PauseCircleOutlined, UndoOutlined, RedoOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons-vue'
import { useManualControl, KeyCode } from './use-manual-control'
import { usePayloadControl } from './use-payload-control'
import { CameraMode, CameraType, CameraTypeOptions, ZoomCameraTypeOptions, CameraListItem } from '/@/types/live-stream'
import { useDroneControlWsEvent } from './use-drone-control-ws-event'
import { useDroneControlMqttEvent } from './use-drone-control-mqtt-event'
import {
postFlightAuth, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight, ERthMode,
ECommanderModeLostAction, ECommanderFlightMode
} from '/@/api/drone-control/drone'
import { useDroneControl } from './use-drone-control'
import {
GimbalResetMode, GimbalResetModeOptions, LostControlActionInCommandFLightOptions, WaylineLostControlActionInCommandFlightOptions,
RthModeInCommandFlightOptions, CommanderModeLostActionInCommandFlightOptions, CommanderFlightModeInCommandFlightOptions
} from '/@/types/drone-control'
import DroneControlPopover from './DroneControlPopover.vue'
import DroneControlInfoPanel from './DroneControlInfoPanel.vue'
import { noDebugCmdList as baseCmdList, DeviceCmdItem, DeviceCmd } from '/@/types/device-cmd'
import { useDockControl } from './use-dock-control'
const props = defineProps<{
sn: string,
deviceInfo: DeviceInfoType,
payloads: null | PayloadInfo[]
}>()
const store = useMyStore()
const clientId = computed(() => {
return store.state.clientId
})
const initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem))
const cmdList = ref(initCmdList)
const {
sendDockControlCmd
} = useDockControl()
async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {
cmdItem.loading = true
const result = await sendDockControlCmd({
sn: props.sn,
cmd: cmdItem.cmdKey,
action: cmdItem.action
}, false)
if (result) {
message.success('Return home successful')
if (flightController.value) {
exitFlightCOntrol()
}
} else {
message.error('Failed to return home')
}
cmdItem.loading = false
}
const { flyToPoint, stopFlyToPoint, takeoffToPoint } = useDroneControl()
const MAX_SPEED = 14
const flyToPointPopoverData = reactive({
visible: false,
loading: false,
latitude: null as null | number,
longitude: null as null | number,
height: null as null | number,
maxSpeed: MAX_SPEED,
})
function onShowFlyToPopover () {
flyToPointPopoverData.visible = !flyToPointPopoverData.visible
flyToPointPopoverData.loading = false
flyToPointPopoverData.latitude = null
flyToPointPopoverData.longitude = null
flyToPointPopoverData.height = null
}
async function onFlyToConfirm (confirm: boolean) {
if (confirm) {
if (!flyToPointPopoverData.height || !flyToPointPopoverData.latitude || !flyToPointPopoverData.longitude) {
message.error('Input error')
return
}
try {
await flyToPoint(props.sn, {
max_speed: flyToPointPopoverData.maxSpeed,
points: [
{
latitude: flyToPointPopoverData.latitude,
longitude: flyToPointPopoverData.longitude,
height: flyToPointPopoverData.height
}
]
})
} catch (error) {
}
}
flyToPointPopoverData.visible = false
}
async function onStopFlyToPoint () {
await stopFlyToPoint(props.sn)
}
const takeoffToPointPopoverData = reactive({
visible: false,
loading: false,
latitude: null as null | number,
longitude: null as null | number,
height: null as null | number,
securityTakeoffHeight: null as null | number,
maxSpeed: MAX_SPEED,
rthAltitude: null as null | number,
rcLostAction: LostControlActionInCommandFLight.RETURN_HOME,
exitWaylineWhenRcLost: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION,
rthMode: ERthMode.SETTING,
commanderModeLostAction: ECommanderModeLostAction.CONTINUE,
commanderFlightMode: ECommanderFlightMode.SETTING,
commanderFlightHeight: null as null | number,
})
function onShowTakeoffToPointPopover () {
takeoffToPointPopoverData.visible = !takeoffToPointPopoverData.visible
takeoffToPointPopoverData.loading = false
takeoffToPointPopoverData.latitude = null
takeoffToPointPopoverData.longitude = null
takeoffToPointPopoverData.securityTakeoffHeight = null
takeoffToPointPopoverData.rthAltitude = null
takeoffToPointPopoverData.rcLostAction = LostControlActionInCommandFLight.RETURN_HOME
takeoffToPointPopoverData.exitWaylineWhenRcLost = WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION
takeoffToPointPopoverData.rthMode = ERthMode.SETTING
takeoffToPointPopoverData.commanderModeLostAction = ECommanderModeLostAction.CONTINUE
takeoffToPointPopoverData.commanderFlightMode = ECommanderFlightMode.SETTING
takeoffToPointPopoverData.commanderFlightHeight = null
}
async function onTakeoffToPointConfirm (confirm: boolean) {
if (confirm) {
if (!takeoffToPointPopoverData.height ||
!takeoffToPointPopoverData.latitude ||
!takeoffToPointPopoverData.longitude ||
!takeoffToPointPopoverData.securityTakeoffHeight ||
!takeoffToPointPopoverData.rthAltitude ||
!takeoffToPointPopoverData.commanderFlightHeight) {
message.error('Input error')
return
}
try {
await takeoffToPoint(props.sn, {
target_latitude: takeoffToPointPopoverData.latitude,
target_longitude: takeoffToPointPopoverData.longitude,
target_height: takeoffToPointPopoverData.height,
security_takeoff_height: takeoffToPointPopoverData.securityTakeoffHeight,
rth_altitude: takeoffToPointPopoverData.rthAltitude,
max_speed: takeoffToPointPopoverData.maxSpeed,
rc_lost_action: takeoffToPointPopoverData.rcLostAction,
exit_wayline_when_rc_lost: takeoffToPointPopoverData.exitWaylineWhenRcLost,
rth_mode: takeoffToPointPopoverData.rthMode,
commander_mode_lost_action: takeoffToPointPopoverData.commanderModeLostAction,
commander_flight_mode: takeoffToPointPopoverData.commanderFlightMode,
commander_flight_height: takeoffToPointPopoverData.commanderFlightHeight,
})
} catch (error) {
}
}
takeoffToPointPopoverData.visible = false
}
const deviceTopicInfo: DeviceTopicInfo = reactive({
sn: props.sn,
pubTopic: '',
subTopic: ''
})
useMqtt(deviceTopicInfo)
//
// const drcState = computed(() => {
// return store.state.deviceState?.dockInfo[props.sn]?.link_osd?.drc_state === DrcStateEnum.CONNECTED
// })
const flightController = ref(false)
async function onClickFightControl () {
if (flightController.value) {
exitFlightCOntrol()
return
}
enterFlightControl()
}
//
async function enterFlightControl () {
try {
const { code, data } = await postDrcEnter({
client_id: clientId.value,
dock_sn: props.sn,
})
if (code === 0) {
flightController.value = true
if (data.sub && data.sub.length > 0) {
deviceTopicInfo.subTopic = data.sub[0]
}
if (data.pub && data.pub.length > 0) {
deviceTopicInfo.pubTopic = data.pub[0]
}
//
if (droneControlSource.value !== ControlSource.A) {
await postFlightAuth(props.sn)
}
message.success('Get flight control successfully')
}
} catch (error: any) {
}
}
// 退
async function exitFlightCOntrol () {
try {
const { code } = await postDrcExit({
client_id: clientId.value,
dock_sn: props.sn,
})
if (code === 0) {
flightController.value = false
deviceTopicInfo.subTopic = ''
deviceTopicInfo.pubTopic = ''
message.success('Exit flight control')
}
} catch (error: any) {
}
}
// drc mqtt message
const { drcInfo, errorInfo } = useDroneControlMqttEvent(props.sn)
const {
handleKeyup,
handleEmergencyStop,
resetControlState,
} = useManualControl(deviceTopicInfo, flightController)
function onMouseDown (type: KeyCode) {
handleKeyup(type)
}
function onMouseUp () {
resetControlState()
}
//
const payloadSelectInfo = {
value: null as any,
controlSource: undefined as undefined | ControlSource,
options: [] as any,
payloadIndex: '' as string,
camera: undefined as undefined | DeviceOsdCamera // osd
}
const handlePayloadChange = (value: string) => {
const payload = props.payloads?.find(item => item.payload_sn === value)
if (payload) {
payloadSelectInfo.payloadIndex = payload.payload_index || ''
payloadSelectInfo.controlSource = payload.control_source
payloadSelectInfo.camera = undefined
}
}
// function getCurrentCamera (cameraList: CameraListItem[], cameraIndex?: string):CameraListItem | null {
// let camera = null
// cameraList.forEach(item => {
// if (item.camera_index === cameraIndex) {
// camera = item
// }
// })
// return camera
// }
// const currentCamera = computed(() => {
// return getCurrentCamera(props.deviceInfo.dock.basic_osd.live_capacity?.device_list[0]?.camera_list as CameraListItem[], camera_index)
// })
//
watch(() => props.payloads, (payloads) => {
if (payloads && payloads.length > 0) {
payloadSelectInfo.value = payloads[0].payload_sn
payloadSelectInfo.controlSource = payloads[0].control_source || ControlSource.B
payloadSelectInfo.payloadIndex = payloads[0].payload_index || ''
payloadSelectInfo.options = payloads.map(item => ({ label: item.payload_name, value: item.payload_sn }))
payloadSelectInfo.camera = undefined
} else {
payloadSelectInfo.value = null
payloadSelectInfo.controlSource = undefined
payloadSelectInfo.options = []
payloadSelectInfo.payloadIndex = ''
payloadSelectInfo.camera = undefined
}
}, {
immediate: true,
deep: true
})
watch(() => props.deviceInfo.device, (droneOsd) => {
if (droneOsd && droneOsd.cameras) {
payloadSelectInfo.camera = droneOsd.cameras.find(item => item.payload_index === payloadSelectInfo.payloadIndex)
} else {
payloadSelectInfo.camera = undefined
}
}, {
immediate: true,
deep: true
})
// ws
const { droneControlSource, payloadControlSource } = useDroneControlWsEvent(props.sn, payloadSelectInfo.value)
watch(() => payloadControlSource, (controlSource) => {
payloadSelectInfo.controlSource = controlSource.value
}, {
immediate: true,
deep: true
})
const {
checkPayloadAuth,
authPayload,
resetGimbal,
switchCameraMode,
takeCameraPhoto,
startCameraRecording,
stopCameraRecording,
changeCameraFocalLength,
cameraAim,
} = usePayloadControl()
async function onAuthPayload () {
const result = await authPayload(props.sn, payloadSelectInfo.payloadIndex)
if (result) {
payloadControlSource.value = ControlSource.A
}
}
const gimbalResetPopoverData = reactive({
visible: false,
loading: false,
resetMode: null as null | GimbalResetMode,
})
function onShowGimbalResetPopover () {
gimbalResetPopoverData.visible = !gimbalResetPopoverData.visible
gimbalResetPopoverData.loading = false
gimbalResetPopoverData.resetMode = null
}
async function onGimbalResetConfirm (confirm: boolean) {
if (confirm) {
if (gimbalResetPopoverData.resetMode === null) {
message.error('Please select reset mode')
return
}
gimbalResetPopoverData.loading = true
try {
await resetGimbal(props.sn, {
payload_index: payloadSelectInfo.payloadIndex,
reset_mode: gimbalResetPopoverData.resetMode
})
} catch (err) {
}
}
gimbalResetPopoverData.visible = false
}
async function onSwitchCameraMode () {
if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
return
}
const currentCameraMode = payloadSelectInfo.camera?.camera_mode
await switchCameraMode(props.sn, {
payload_index: payloadSelectInfo.payloadIndex,
camera_mode: currentCameraMode === CameraMode.Photo ? CameraMode.Video : CameraMode.Photo
})
}
async function onTakeCameraPhoto () {
if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
return
}
await takeCameraPhoto(props.sn, payloadSelectInfo.payloadIndex)
}
async function onStartCameraRecording () {
if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
return
}
await startCameraRecording(props.sn, payloadSelectInfo.payloadIndex)
}
async function onStopCameraRecording () {
if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {
return
}
await stopCameraRecording(props.sn, payloadSelectInfo.payloadIndex)
}
const zoomFactorPopoverData = reactive({
visible: false,
loading: false,
cameraType: null as null | CameraType,
zoomFactor: null as null | number,
})
function onShowZoomFactorPopover () {
zoomFactorPopoverData.visible = !zoomFactorPopoverData.visible
zoomFactorPopoverData.loading = false
zoomFactorPopoverData.cameraType = null
zoomFactorPopoverData.zoomFactor = null
}
async function onZoomFactorConfirm (confirm: boolean) {
if (confirm) {
if (!zoomFactorPopoverData.zoomFactor || zoomFactorPopoverData.cameraType === null) {
message.error('Please input Zoom Factor')
return
}
zoomFactorPopoverData.loading = true
try {
await changeCameraFocalLength(props.sn, {
payload_index: payloadSelectInfo.payloadIndex,
camera_type: zoomFactorPopoverData.cameraType,
zoom_factor: zoomFactorPopoverData.zoomFactor
})
} catch (err) {
}
}
zoomFactorPopoverData.visible = false
}
const cameraAimPopoverData = reactive({
visible: false,
loading: false,
cameraType: null as null | CameraType,
locked: false,
x: null as null | number,
y: null as null | number,
})
function onShowCameraAimPopover () {
cameraAimPopoverData.visible = !cameraAimPopoverData.visible
cameraAimPopoverData.loading = false
cameraAimPopoverData.cameraType = null
cameraAimPopoverData.locked = false
cameraAimPopoverData.x = null
cameraAimPopoverData.y = null
}
function openLivestreamOthers () {
store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)
}
function openLivestreamAgora () {
store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)
}
async function onCameraAimConfirm (confirm: boolean) {
if (confirm) {
if (cameraAimPopoverData.cameraType === null || cameraAimPopoverData.x === null || cameraAimPopoverData.y === null) {
message.error('Input error')
return
}
try {
await cameraAim(props.sn, {
payload_index: payloadSelectInfo.payloadIndex,
camera_type: cameraAimPopoverData.cameraType,
locked: cameraAimPopoverData.locked,
x: cameraAimPopoverData.x,
y: cameraAimPopoverData.y,
})
} catch (error) {
}
}
cameraAimPopoverData.visible = false
}
watch(() => errorInfo, (errorInfo) => {
if (errorInfo.value) {
message.error(errorInfo.value)
console.error(errorInfo.value)
errorInfo.value = ''
}
}, {
immediate: true,
deep: true
})
</script>
<style lang='scss' scoped>
.drone-control-wrapper{
// border-bottom: 1px solid #515151;
.drone-control-header{
font-size: 14px;
font-weight: 600;
padding: 10px 10px 0px;
}
.drone-control-box {
display: flex;
flex-wrap: 1;
.box {
width: 50%;
padding: 5px;
border: 0.5px solid rgba(255,255,255,0.3);
.row {
display: flex;
flex-wrap: wrap;
padding: 2px;
+ .row{
margin-bottom: 6px;
}
&::v-deep{
.ant-btn{
font-size: 12px;
padding: 0px 4px;
margin-right: 5px;
}
}
}
.drone-control{
&::v-deep{
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{
padding: 0 2px;
}
}
}
.drone-control-direction{
margin-right: 10px;
.ant-btn {
// padding: 0px 1px;
margin-right: 0;
}
.word{
width: 12px;
margin-left: 2px;
font-size: 12px;
color: #aaa;
}
}
}
}
}
</style>

115
src/components/g-map/DroneControlPopover.vue

@ -1,115 +0,0 @@ @@ -1,115 +0,0 @@
<template>
<a-popover :visible="state.sVisible"
trigger="click"
v-bind="$attrs"
:overlay-class-name="overlayClassName"
placement="bottom"
@visibleChange=";"
v-on="$attrs">
<template #content>
<div class="title-content">
</div>
<slot name="formContent" />
<div class="uranus-popconfirm-btns">
<a-button size="sm"
@click="onCancel">
{{ cancelText || 'cancel'}}
</a-button>
<a-button size="sm"
:loading="loading"
type="primary"
class="confirm-btn"
@click="onConfirm">
{{ okText || 'ok' }}
</a-button>
</div>
</template>
<template v-if="$slots.default">
<slot></slot>
</template>
</a-popover>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, reactive, watch, computed } from 'vue'
const props = defineProps<{
visible?: boolean,
loading?: Boolean,
disabled?: Boolean,
title?: String,
okText?: String,
cancelText?: String,
width?: Number,
}>()
const emit = defineEmits(['cancel', 'confirm'])
const state = reactive({
sVisible: false,
loading: false,
})
watch(() => props.visible, (val) => {
state.sVisible = val || false
})
const loading = computed(() => {
return props.loading
})
const okLabel = computed(() => {
return props.loading ? '' : '确定'
})
const overlayClassName = computed(() => {
const classList = ['drone-control-popconfirm']
return classList.join(' ')
})
function onConfirm (e: Event) {
if (props.disabled) {
return
}
emit('confirm', e)
}
function onCancel (e: Event) {
state.sVisible = false
emit('cancel', e)
}
</script>
<style lang="scss">
.drone-control-popconfirm {
min-width: 300px;
.uranus-popconfirm-btns{
display: flex;
padding: 10px 0px;
justify-content: flex-end;
.confirm-btn{
margin-left: 10px;
}
}
.form-content{
display: flex;
flex-direction: column;
> div {
display: flex;
margin-bottom: 5px;
.form-label {
flex: 1 0 60px;
margin-right: 10px;
}
> div {
}
}
}
}
</style>

71
src/components/g-map/use-connect-mqtt.ts

@ -1,71 +0,0 @@ @@ -1,71 +0,0 @@
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<UranusMqtt | null>(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()
})
}

76
src/components/g-map/use-drone-control-mqtt-event.ts

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

95
src/components/g-map/use-drone-control-ws-event.ts

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

40
src/components/g-map/use-drone-control.ts

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

165
src/components/g-map/use-manual-control.ts

@ -1,165 +0,0 @@ @@ -1,165 +0,0 @@
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<boolean>) {
const activeCodeKey = ref(null) as Ref<KeyCode | null>
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,
}
}

134
src/components/g-map/use-mqtt.ts

@ -1,134 +0,0 @@ @@ -1,134 +0,0 @@
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<void>
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<string, {
pingInterval: any;
}>(),
})
// 监听云控控制权
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,
}
}

120
src/components/g-map/use-payload-control.ts

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

0
src/components/g-map/use-device-setting.ts → src/components/g-map/useDeviceSetting.ts

33
src/components/g-map/use-dock-control.ts → src/components/g-map/useDockControl.ts

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
import { message } from 'ant-design-vue'
import { ref } from 'vue'
import { postSendCmd } from '/@/api/device-cmd'
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
import { DeviceCmd } from '/@/types/device-cmd'
export function useDockControl () {
const dockControlPanelVisible = ref(false)
const controlPanelVisible = ref(false)
function setDockControlPanelVisible (visible: boolean) {
dockControlPanelVisible.value = visible
function setControlPanelVisible (visible: boolean) {
controlPanelVisible.value = visible
}
// 远程调试开关
@ -16,6 +16,9 @@ export function useDockControl () { @@ -16,6 +16,9 @@ export function useDockControl () {
sn: sn,
cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose
}, false)
if (result) {
setControlPanelVisible(on)
}
return result
}
@ -23,16 +26,9 @@ export function useDockControl () { @@ -23,16 +26,9 @@ export function useDockControl () {
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)
const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd })
if (code === 0) {
tip && message.success('指令发送成功')
return true
@ -44,19 +40,10 @@ export function useDockControl () { @@ -44,19 +40,10 @@ export function useDockControl () {
}
}
// 控制面板关闭
async function onCloseControlPanel (sn: string, debugging: boolean) {
if (debugging) {
await dockDebugOnOff(sn, false)
}
setDockControlPanelVisible(false)
}
return {
dockControlPanelVisible,
setDockControlPanelVisible,
controlPanelVisible,
setControlPanelVisible,
sendDockControlCmd,
dockDebugOnOff,
onCloseControlPanel,
}
}

36
src/components/livestream-agora.vue

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div class="flex-column flex-justify-start flex-align-center">
<div class="mt20 flex-column flex-justify-start flex-align-center">
<div id="player" style="width: 720px; height: 420px; border: 1px solid"></div>
<p class="fz24">Live streaming source selection</p>
<div class="flex-row flex-justify-center flex-align-center mt10">
@ -76,6 +76,7 @@ @@ -76,6 +76,7 @@
class="ml10"
v-model:value="agoraPara.token"
placeholder="Token"
@change="encodeToken"
></a-input>
<span class="ml10">Channel:</span>
<a-input
@ -168,6 +169,7 @@ const livePara = reactive({ @@ -168,6 +169,7 @@ const livePara = reactive({
liveState: false
})
const nonSwitchable = 'normal'
const onRefresh = async () => {
dronePara.droneList = []
dronePara.cameraList = []
@ -202,11 +204,8 @@ const onRefresh = async () => { @@ -202,11 +204,8 @@ const onRefresh = async () => {
onMounted(() => {
onRefresh()
agoraPara.token = encodeURIComponent(agoraPara.token)
agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' })
agoraClient.setClientRole('audience', { level: 2 })
if (agoraClient.connectionState === 'DISCONNECTED') {
agoraClient.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
}
// Subscribe when a remote user publishes a stream
agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => {
message.info('user[' + user.uid + '] join')
@ -218,7 +217,9 @@ onMounted(() => { @@ -218,7 +217,9 @@ onMounted(() => {
// Get `RemoteVideoTrack` in the `user` object.
const remoteVideoTrack = user.videoTrack!
// Dynamically create a container in the form of a DIV element for playing the remote video track.
remoteVideoTrack.play(document.getElementById('player') as HTMLElement)
const remotePlayerContainer: any = document.getElementById('player')
remotePlayerContainer.id = user.uid.toString()
remoteVideoTrack.play(remotePlayerContainer)
}
})
agoraClient.on('user-unpublished', async (user: any) => {
@ -226,16 +227,20 @@ onMounted(() => { @@ -226,16 +227,20 @@ onMounted(() => {
message.info('unpublish live')
})
agoraClient.on('exception', async (e: any) => {
console.log(e)
console.error(e)
message.error(e.msg)
})
})
const handleError = (err: any) => {
console.error(err)
}
const handleJoinChannel = (uid: any) => {
agoraPara.uid = uid
}
const encodeToken = (e: any) => {
agoraPara.token = encodeURIComponent(agoraPara.token)
}
const onStart = async () => {
const that = this
console.log(
@ -257,7 +262,8 @@ const onStart = async () => { @@ -257,7 +262,8 @@ const onStart = async () => {
}
agoraClient.setClientRole('audience', { level: 2 })
if (agoraClient.connectionState === 'DISCONNECTED') {
await agoraClient.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
agoraClient
.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
}
livePara.videoId =
dronePara.droneSelected +
@ -271,7 +277,7 @@ const onStart = async () => { @@ -271,7 +277,7 @@ const onStart = async () => {
'&sn=' +
dronePara.droneSelected +
'&token=' +
encodeURIComponent(agoraPara.token) +
agoraPara.token +
'&uid=' +
agoraPara.uid
@ -292,14 +298,6 @@ const onStart = async () => { @@ -292,14 +298,6 @@ const onStart = async () => {
})
}
const onStop = async () => {
if (
dronePara.droneSelected == null ||
dronePara.cameraSelected == null ||
dronePara.claritySelected == null
) {
message.warn('waring: not select live para!!!')
return
}
livePara.videoId =
dronePara.droneSelected +
'/' +
@ -310,10 +308,10 @@ const onStop = async () => { @@ -310,10 +308,10 @@ const onStop = async () => {
}).then(res => {
if (res.code === 0) {
message.success(res.message)
}
livePara.liveState = false
dronePara.lensSelected = ''
console.log('stop play livestream')
}
})
}
const onDroneSelect = (val: SelectOption) => {
@ -352,7 +350,7 @@ const onCameraSelect = (val: SelectOption) => { @@ -352,7 +350,7 @@ const onCameraSelect = (val: SelectOption) => {
dronePara.videoSelected = firstVideo.value
dronePara.lensList = firstVideo.more
dronePara.lensSelected = firstVideo.label
dronePara.isDockLive = dronePara.lensList?.length > 0
dronePara.isDockLive = dronePara.lensList.length > 0
}
const onVideoSelect = (val: SelectOption) => {
dronePara.videoSelected = val.value

40
src/components/livestream-others.vue

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
<template>
<div class="flex-column flex-justify-start flex-align-center">
<div class="flex-column flex-justify-start flex-align-center mt20">
<video
:style="{ width: '720px', height: '480px' }"
id="video-webrtc"
ref="videowebrtc"
controls
autoplay
class="mt20"
></video>
<p class="fz24">Live streaming source selection</p>
@ -121,8 +120,6 @@ import { CURRENT_CONFIG as config } from '/@/api/http/config' @@ -121,8 +120,6 @@ import { CURRENT_CONFIG as config } from '/@/api/http/config'
import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
import { getRoot } from '/@/root'
import jswebrtc from '/@/vendors/jswebrtc.min.js'
import srs from '/@/vendors/srs.sdk.js'
const root = getRoot()
interface SelectOption {
@ -143,10 +140,6 @@ const liveTypeList: SelectOption[] = [ @@ -143,10 +140,6 @@ const liveTypeList: SelectOption[] = [
{
value: 3,
label: 'GB28181'
},
{
value: 4,
label: 'WEBRTC'
}
]
const clarityList: SelectOption[] = [
@ -189,7 +182,6 @@ const lensList = ref<string[]>([]) @@ -189,7 +182,6 @@ const lensList = ref<string[]>([])
const lensSelected = ref<String>()
const isDockLive = ref(false)
const nonSwitchable = 'normal'
let webrtc: any = null
const onRefresh = async () => {
droneList.value = []
@ -266,9 +258,6 @@ const onStart = async () => { @@ -266,9 +258,6 @@ const onStart = async () => {
liveURL = `serverIP=${config.gbServerIp}&serverPort=${config.gbServerPort}&serverID=${config.gbServerId}&agentID=${config.gbAgentId}&agentPassword=${config.gbPassword}&localPort=${config.gbAgentPort}&channel=${config.gbAgentChannel}`
break
}
case 4: {
break
}
default:
console.warn('warning: live type is not correct!!!')
break
@ -302,7 +291,13 @@ const onStart = async () => { @@ -302,7 +291,13 @@ const onStart = async () => {
})
} else if (livetypeSelected.value === 2) {
console.log(res)
rtspData.value = 'url:' + res.data.url
rtspData.value =
'url:' +
res.data.url +
'&username:' +
res.data.username +
'&password:' +
res.data.password
} else if (livetypeSelected.value === 1) {
const url = res.data.url
const videoElement = videowebrtc.value
@ -315,10 +310,6 @@ const onStart = async () => { @@ -315,10 +310,6 @@ const onStart = async () => {
console.log('start play livestream')
}
})
} else if (livetypeSelected.value === 4) {
const videoElement = videowebrtc.value as unknown as HTMLMediaElement
videoElement.muted = true
playWebrtc(videoElement, res.data.url)
}
liveState.value = true
})
@ -397,7 +388,7 @@ const onCameraSelect = (val: SelectOption) => { @@ -397,7 +388,7 @@ const onCameraSelect = (val: SelectOption) => {
videoSelected.value = firstVideo.value
lensList.value = firstVideo.more
lensSelected.value = firstVideo.label
isDockLive.value = lensList.value?.length > 0
isDockLive.value = lensList.value.length > 0
}
const onVideoSelect = (val: SelectOption) => {
videoSelected.value = val.value
@ -421,19 +412,6 @@ const onSwitch = () => { @@ -421,19 +412,6 @@ const onSwitch = () => {
}
})
}
const playWebrtc = (videoElement: HTMLMediaElement, url: string) => {
if (webrtc) {
webrtc.close()
}
webrtc = new srs.SrsRtcWhipWhepAsync()
videoElement.srcObject = webrtc.stream
webrtc.play(url).then(function (session: any) {
console.info(session)
}).catch(function (reason: any) {
webrtc.close()
console.error(reason)
})
}
</script>
<style lang="scss" scoped>

240
src/components/task/CreatePlan.vue

@ -4,8 +4,8 @@ @@ -4,8 +4,8 @@
Create Plan
</div>
<div class="content">
<a-form ref="valueRef" layout="horizontal" :hideRequiredMark="true" :rules="rules" :model="planBody" labelAlign="left">
<a-form-item label="Plan Name" name="name" :labelCol="{span: 23}">
<a-form ref="valueRef" layout="horizontal" :hideRequiredMark="true" :rules="rules" :model="planBody">
<a-form-item label="Plan Name" name="name" :labelCol="{span: 24}">
<a-input style="background: black;" placeholder="Please enter plan name" v-model:value="planBody.name"/>
</a-form-item>
<!-- 航线 -->
@ -30,10 +30,10 @@ @@ -30,10 +30,10 @@
</div>
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<span><RocketOutlined /></span>
<span class="ml5">{{ DEVICE_NAME[wayline.drone_model_key] }}</span>
<span class="ml5">{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(wayline.drone_model_key)] }}</span>
<span class="ml10"><CameraFilled style="border-top: 1px solid; padding-top: -3px;" /></span>
<span class="ml5" v-for="payload in wayline.payload_model_keys" :key="payload.id">
{{ DEVICE_NAME[payload] }}
{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(payload)] }}
</span>
</div>
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
@ -49,7 +49,7 @@ @@ -49,7 +49,7 @@
>Select Device</router-link>
</a-form-item>
<a-form-item v-if="planBody.dock_sn" style="margin-top: -15px;">
<div class="panel" style="padding-top: 5px;">
<div class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
<div class="title">
<a-tooltip :title="dock.nickname">
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
@ -57,7 +57,7 @@ @@ -57,7 +57,7 @@
</div>
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<span><RocketOutlined /></span>
<span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span>
<span class="ml5">{{ dock.children?.nickname }}</span>
</div>
</div>
</a-form-item>
@ -65,77 +65,21 @@ @@ -65,77 +65,21 @@
<a-form-item label="Plan Timer" class="plan-timer-form-item">
<div style="white-space: nowrap;">
<a-radio-group v-model:value="planBody.task_type" button-style="solid">
<a-radio-button v-for="type in TaskTypeOptions" :value="type.value" :key="type.value">{{ type.label }}</a-radio-button>
<a-radio-button :value="TaskType.Immediate">Immediate</a-radio-button>
<a-radio-button :value="TaskType.Single">Timed&One-Time</a-radio-button>
</a-radio-group>
</div>
</a-form-item>
<!-- execute date -->
<a-form-item label="Date" v-if="planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition" name="select_execute_date" :labelCol="{span: 23}">
<a-range-picker
v-model:value="planBody.select_execute_date"
:disabledDate="(current: Moment) => current < moment().subtract(1, 'days')"
format="YYYY-MM-DD"
:placeholder="['Start Time', 'End Time']"
style="width: 100%;"
/>
</a-form-item>
<!-- execute time -->
<a-form-item label="Time" v-if="planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition"
name="select_execute_time" ref="select_execute_time" :labelCol="{span: 23}" :autoLink="false">
<div class="mb10 flex-row flex-align-center flex-justify-around" v-for="n in planBody.select_time_number" :key="n">
<a-time-picker
v-model:value="planBody.select_time[n - 1][0]"
format="HH:mm:ss"
show-time
placeholder="Start Time"
:style="planBody.task_type === TaskType.Condition ? 'width: 40%' : 'width: 82%'"
@change="() => $refs.select_execute_time.onFieldChange()"
/>
<template v-if="planBody.task_type === TaskType.Condition">
<div><span style="color: white;">-</span></div>
<a-time-picker
v-model:value="planBody.select_time[n - 1][1]"
format="HH:mm:ss"
<!-- 执行时间 -->
<a-form-item label="Start Time" v-if="planBody.task_type === TaskType.Single" name="select_execute_time">
<a-date-picker v-model:value="planBody.select_execute_time"
format="YYYY-MM-DD HH:mm:ss"
show-time
placeholder="End Time"
style="width: 40%;"
placeholder="Select Time"
/>
</template>
<div class="ml5" style="font-size:18px">
<PlusCircleOutlined class="mr5" style="color: #1890ff" @click="addTime"/>
<MinusCircleOutlined :style="planBody.select_time_number === 1 ? 'color: gray' : 'color: red;'" @click="removeTime"/>
</div>
</div>
</a-form-item>
<template v-if="planBody.task_type === TaskType.Condition">
<!-- battery capacity -->
<a-form-item label="Start task when battery level reaches" :labelCol="{span: 23}" name="min_battery_capacity">
<a-input-number class="width-100" v-model:value="planBody.min_battery_capacity" :min="50" :max="100"
:formatter="(value: number) => `${value}%`" :parser="(value: string) => value.replace('%', '')">
</a-input-number>
</a-form-item>
<!-- storage capacity -->
<a-form-item label="Start task when storage level reaches (MB)" :labelCol="{span: 23}" name="storage_capacity">
<a-input-number v-model:value="planBody.min_storage_capacity" class="width-100">
</a-input-number>
</a-form-item>
</template>
<!-- RTH Altitude Relative to Dock -->
<a-form-item label="RTH Altitude Relative to Dock (m)" :labelCol="{span: 23}" name="rth_altitude">
<a-input-number v-model:value="planBody.rth_altitude" :min="20" :max="1500" class="width-100" required>
</a-input-number>
</a-form-item>
<!-- Lost Action -->
<a-form-item label="Lost Action" :labelCol="{span: 23}" name="out_of_control_action">
<div style="white-space: nowrap;">
<a-radio-group v-model:value="planBody.out_of_control_action" button-style="solid">
<a-radio-button v-for="action in OutOfControlActionOptions" :value="action.value" :key="action.value">
{{ action.label }}
</a-radio-button>
</a-radio-group>
</div>
</a-form-item>
<a-form-item class="width-100" style="margin-bottom: 40px;">
<!-- 操作 -->
<a-form-item style="position: absolute; bottom: 0px; margin-bottom: 0; margin-left: -10px; width: 280px;">
<div class="footer">
<a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel
</a-button>
@ -146,7 +90,7 @@ @@ -146,7 +90,7 @@
</a-form>
</div>
</div>
<div v-if="drawerVisible" style="position: absolute; left: 335px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;">
<div v-if="drawerVisible" style="position: absolute; left: 330px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;">
<div>
<router-view :name="routeName"/>
</div>
@ -158,16 +102,16 @@ @@ -158,16 +102,16 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'
import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons-vue'
import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
import { ELocalStorageKey, ERouterName } from '/@/types'
import { useMyStore } from '/@/store'
import { WaylineType, WaylineFile } from '/@/types/wayline'
import { Device, DEVICE_NAME } from '/@/types/device'
import { Device, EDeviceType } from '/@/types/device'
import { createPlan, CreatePlan } from '/@/api/wayline'
import { getRoot } from '/@/root'
import { TaskType, OutOfControlActionOptions, OutOfControlAction, TaskTypeOptions } from '/@/types/task'
import { TaskType } from '/@/types/task'
import moment, { Moment } from 'moment'
import { RuleObject } from 'ant-design-vue/es/form/interface'
import { message } from 'ant-design-vue'
const root = getRoot()
const store = useMyStore()
@ -187,16 +131,10 @@ const disabled = ref(false) @@ -187,16 +131,10 @@ const disabled = ref(false)
const routeName = ref('')
const planBody = reactive({
name: '',
file_id: computed(() => store.state?.waylineInfo.id),
dock_sn: computed(() => store.state?.dockInfo.device_sn),
file_id: computed(() => store.state.waylineInfo.id),
dock_sn: computed(() => store.state.dockInfo.device_sn),
task_type: TaskType.Immediate,
select_execute_date: [moment(), moment()] as Moment[],
select_time_number: 1,
select_time: [[]] as Moment[][],
rth_altitude: '',
out_of_control_action: OutOfControlAction.ReturnToHome,
min_battery_capacity: 90 as number,
min_storage_capacity: undefined as number | undefined,
select_execute_time: undefined as Moment| undefined
})
const drawerVisible = ref(false)
@ -204,100 +142,29 @@ const valueRef = ref() @@ -204,100 +142,29 @@ const valueRef = ref()
const rules = {
name: [
{ required: true, message: 'Please enter plan name.' },
{ max: 20, message: 'Length should be 1 to 20' }
{ max: 20, message: 'Length should be 1 to 20', trigger: 'blur' }
],
file_id: [{ required: true, message: 'Select Route' }],
dock_sn: [{ required: true, message: 'Select Device' }],
select_execute_time: [{
validator: async (rule: RuleObject, value: Moment[]) => {
validEndTime()
validStartTime()
if (planBody.select_time.length < planBody.select_time_number) {
throw new Error('Select time')
}
validOverlapped()
}
}],
select_execute_date: [{ required: true, message: 'Select date' }],
rth_altitude: [
{
validator: async (rule: RuleObject, value: string) => {
if (!/^[0-9]{1,}$/.test(value)) {
throw new Error('RTH Altitude Require number')
}
},
}
],
min_battery_capacity: [
{
validator: async (rule: RuleObject, value: any) => {
if (TaskType.Condition === planBody.task_type && !value) {
throw new Error('Please enter battery capacity')
}
},
}
],
out_of_control_action: [{ required: true, message: 'Select Lost Action' }],
}
function validStartTime (): Error | void {
for (let i = 0; i < planBody.select_time.length; i++) {
if (!planBody.select_time[i][0]) {
throw new Error('Select start time')
}
}
}
function validEndTime (): Error | void {
if (TaskType.Condition !== planBody.task_type) return
for (let i = 0; i < planBody.select_time.length; i++) {
if (!planBody.select_time[i][1]) {
throw new Error('Select end time')
}
if (planBody.select_time[i][0] && planBody.select_time[i][1].isSameOrBefore(planBody.select_time[i][0])) {
throw new Error('End time should be later than start time')
}
}
}
function validOverlapped (): Error | void {
if (TaskType.Condition !== planBody.task_type) return
const arr = planBody.select_time.slice()
arr.sort((a, b) => a[0].unix() - b[0].unix())
arr.forEach((v, i, arr) => {
if (i > 0 && v[0] < arr[i - 1][1]) {
throw new Error('Overlapping time periods.')
}
})
select_execute_time: [{ required: true, message: 'Select start time' }]
}
function onSubmit () {
console.info(dock, '12131231')
valueRef.value.validate().then(() => {
disabled.value = true
const createPlanBody = { ...planBody } as unknown as CreatePlan
if (planBody.select_execute_date.length === 2) {
createPlanBody.task_days = []
for (let i = planBody.select_execute_date[0]; i.isSameOrBefore(planBody.select_execute_date[1]); i.add(1, 'days')) {
createPlanBody.task_days.push(i.unix())
}
if (planBody.select_execute_time) {
createPlanBody.execute_time = moment(planBody.select_execute_time).valueOf()
}
createPlanBody.task_periods = []
if (TaskType.Immediate !== planBody.task_type) {
for (let i = 0; i < planBody.select_time.length; i++) {
const result = []
result.push(planBody.select_time[i][0].unix())
if (TaskType.Condition === planBody.task_type) {
result.push(planBody.select_time[i][1].unix())
}
createPlanBody.task_periods.push(result)
}
}
createPlanBody.rth_altitude = Number(createPlanBody.rth_altitude)
if (wayline.value && wayline.value.template_types && wayline.value.template_types.length > 0) {
createPlanBody.wayline_type = wayline.value.template_types[0]
}
// console.log('planBody', createPlanBody)
createPlan(workspaceId, createPlanBody)
.then(res => {
setTimeout(() => {
disabled.value = false
}, 1500)
}).finally(() => {
closePlan()
})
@ -324,32 +191,19 @@ function selectDevice () { @@ -324,32 +191,19 @@ function selectDevice () {
drawerVisible.value = true
routeName.value = 'DockPanel'
}
function addTime () {
valueRef.value.validateFields(['select_execute_time']).then(() => {
planBody.select_time_number++
planBody.select_time.push([])
})
}
function removeTime () {
if (planBody.select_time_number === 1) return
planBody.select_time_number--
planBody.select_time.splice(planBody.select_time_number)
}
</script>
<style lang="scss">
.create-plan-wrapper {
background-color: #232323;
color: fff;
color: white;
padding-bottom: 0;
height: 100vh;
display: flex;
flex-direction: column;
width: 285px;
.header {
height: 52px;
height: 53px;
border-bottom: 1px solid #4f4f4f;
font-weight: 700;
font-size: 16px;
@ -358,35 +212,23 @@ function removeTime () { @@ -358,35 +212,23 @@ function removeTime () {
align-items: center;
}
::-webkit-scrollbar {
display: none;
}
.content {
height: calc(100% - 54px);
overflow-y: auto;
height: 100%;
form {
margin: 10px;
}
form label, input, .ant-input, .ant-calendar-range-picker-separator,
.ant-input:hover, .ant-time-picker .anticon, .ant-calendar-picker .anticon {
form label, input {
background-color: #232323;
color: #fff;
}
.ant-input-suffix {
color: #fff;
}
.plan-timer-form-item {
// flex-direction: column;
.ant-radio-button-wrapper{
background-color: #232323;
color: #fff;
width: 33%;
text-align: center;
&.ant-radio-button-wrapper-checked{
background-color: #1890ff;
}
@ -396,11 +238,15 @@ function removeTime () { @@ -396,11 +238,15 @@ function removeTime () {
.footer {
display: flex;
padding:10px 0;
align-items: center;
justify-content: center;
border-top: 1px solid #4f4f4f;
min-height: 65px;
margin-bottom: 0;
padding-bottom: 0;
button {
width: 45%;
color: #fff ;
color: white;
border: 0;
}
}

184
src/components/task/TaskPanel.vue

@ -5,17 +5,15 @@ @@ -5,17 +5,15 @@
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
<!-- 执行时间 -->
<template #duration="{ record }">
<div class="flex-row" style="white-space: pre-wrap">
<div>
<div>{{ formatTaskTime(record.begin_time) }}</div>
<div>{{ formatTaskTime(record.end_time) }}</div>
</div>
<div class="ml10">
<div>{{ formatTaskTime(record.execute_time) }}</div>
<div>{{ formatTaskTime(record.completed_time) }}</div>
</div>
<div>{{ formatTaskTime(record.end_time) }}</div>
</div>
</template>
<!-- 任务类型 -->
<template #taskType="{ record }">
<div>{{ formatTaskType(record) }}</div>
</template>
<!-- 状态 -->
<template #status="{ record }">
<div>
@ -34,35 +32,9 @@ @@ -34,35 +32,9 @@
</div>
</div>
</template>
<!-- 任务类型 -->
<template #taskType="{ record }">
<div>{{ formatTaskType(record) }}</div>
</template>
<!-- 失控动作 -->
<template #lostAction="{ record }">
<div>{{ formatLostAction(record) }}</div>
</template>
<!-- 媒体上传状态 -->
<template #media_upload="{ record }">
<div>
<div class="flex-display flex-align-center">
<span class="circle-icon" :style="{backgroundColor: formatMediaTaskStatus(record).color}"></span>
{{ formatMediaTaskStatus(record).text }}
</div>
<div class="pl15">
{{ formatMediaTaskStatus(record).number }}
<a-tooltip v-if="formatMediaTaskStatus(record).status === MediaStatus.ToUpload" placement="bottom" arrow-point-at-center >
<template #title>
<div>Upload now</div>
</template>
<UploadOutlined class="ml5" :style="{color: commonColor.BLUE, fontSize: '16px' }" @click="onUploadMediaFileNow(record.job_id)"/>
</a-tooltip>
</div>
</div>
</template>
<!-- 操作 -->
<template #action="{ record }">
<div class="action-area">
<span class="action-area">
<a-popconfirm
v-if="record.status === TaskStatus.Wait"
title="Are you sure you want to delete flight task?"
@ -72,25 +44,7 @@ @@ -72,25 +44,7 @@
>
<a-button type="primary" size="small">Delete</a-button>
</a-popconfirm>
<a-popconfirm
v-if="record.status === TaskStatus.Carrying"
title="Are you sure you want to suspend?"
ok-text="Yes"
cancel-text="No"
@confirm="onSuspendTask(record.job_id)"
>
<a-button type="primary" size="small">Suspend</a-button>
</a-popconfirm>
<a-popconfirm
v-if="record.status === TaskStatus.Paused"
title="Are you sure you want to resume?"
ok-text="Yes"
cancel-text="No"
@confirm="onResumeTask(record.job_id)"
>
<a-button type="primary" size="small">Resume</a-button>
</a-popconfirm>
</div>
</span>
</template>
</a-table>
</div>
@ -102,15 +56,15 @@ import { message } from 'ant-design-vue' @@ -102,15 +56,15 @@ import { message } from 'ant-design-vue'
import { TableState } from 'ant-design-vue/lib/table/interface'
import { onMounted } from 'vue'
import { IPage } from '/@/api/http/type'
import { deleteTask, updateTaskStatus, UpdateTaskStatus, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'
import { deleteTask, getWaylineJobs, Task } from '/@/api/wayline'
import { useMyStore } from '/@/store'
import { ELocalStorageKey } from '/@/types/enums'
import { useFormatTask } from './use-format-task'
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap, MediaStatus, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'
import { useTaskWsEvent } from './use-task-ws-event'
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap } from '/@/types/task'
import { useTaskProgressEvent } from './use-task-progress-event'
import { getErrorMessage } from '/@/utils/error-code/index'
import { commonColor } from '/@/utils/color'
import { ExclamationCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
const store = useMyStore()
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
@ -136,55 +90,40 @@ const columns = [ @@ -136,55 +90,40 @@ const columns = [
width: 200,
slots: { customRender: 'duration' },
},
{
title: 'Status',
key: 'status',
width: 150,
slots: { customRender: 'status' }
},
{
title: 'Plan Name',
dataIndex: 'job_name',
width: 100,
width: 150,
ellipsis: true
},
{
title: 'Type',
dataIndex: 'taskType',
width: 100,
width: 150,
slots: { customRender: 'taskType' },
},
{
title: 'Flight Route Name',
dataIndex: 'file_name',
width: 100,
width: 150,
ellipsis: true
},
{
title: 'Dock Name',
dataIndex: 'dock_name',
width: 100,
width: 150,
ellipsis: true
},
{
title: 'RTH Altitude Relative to Dock (m)',
dataIndex: 'rth_altitude',
width: 120,
},
{
title: 'Lost Action',
dataIndex: 'out_of_control_action',
width: 120,
slots: { customRender: 'lostAction' },
},
{
title: 'Creator',
dataIndex: 'username',
width: 120,
width: 150,
},
{
title: 'Media File Upload',
key: 'media_upload',
width: 160,
slots: { customRender: 'media_upload' }
title: 'Status',
key: 'status',
width: 200,
slots: { customRender: 'status' }
},
{
title: 'Action',
@ -198,7 +137,7 @@ const plansData = reactive({ @@ -198,7 +137,7 @@ const plansData = reactive({
data: [] as Task[]
})
const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus, formatMediaTaskStatus } = useFormatTask()
const { formatTaskType, formatTaskTime, formatTaskStatus } = useFormatTask()
//
function onTaskProgressWs (data: TaskProgressInfo) {
@ -219,44 +158,11 @@ function onTaskProgressWs (data: TaskProgressInfo) { @@ -219,44 +158,11 @@ function onTaskProgressWs (data: TaskProgressInfo) {
}
}
//
function onTaskMediaProgressWs (data: MediaStatusProgressInfo) {
const { media_count: mediaCount, uploaded_count: uploadedCount, job_id: jobId } = data
if (isNaN(mediaCount) || isNaN(uploadedCount) || !jobId) {
return
}
const taskItem = plansData.data.find(task => task.job_id === jobId)
if (!taskItem) return
if (mediaCount === uploadedCount) {
taskItem.uploading = false
} else {
taskItem.uploading = true
}
taskItem.media_count = mediaCount
taskItem.uploaded_count = uploadedCount
}
function onoTaskMediaHighestPriorityWS (data: TaskMediaHighestPriorityProgressInfo) {
const { pre_job_id: preJobId, job_id: jobId } = data
const preTaskItem = plansData.data.find(task => task.job_id === preJobId)
const taskItem = plansData.data.find(task => task.job_id === jobId)
if (preTaskItem) {
preTaskItem.uploading = false
}
if (taskItem) {
taskItem.uploading = true
}
}
function getCodeMessage (code: number) {
return getErrorMessage(code) + `(code: ${code}`
}
useTaskWsEvent({
onTaskProgressWs,
onTaskMediaProgressWs,
onoTaskMediaHighestPriorityWS,
})
useTaskProgressEvent(onTaskProgressWs)
onMounted(() => {
getPlans()
@ -289,39 +195,6 @@ async function onDeleteTask (jobId: string) { @@ -289,39 +195,6 @@ async function onDeleteTask (jobId: string) {
getPlans()
}
}
//
async function onSuspendTask (jobId: string) {
const { code } = await updateTaskStatus(workspaceId, {
job_id: jobId,
status: UpdateTaskStatus.Suspend
})
if (code === 0) {
message.success('Suspended successfully')
getPlans()
}
}
//
async function onResumeTask (jobId: string) {
const { code } = await updateTaskStatus(workspaceId, {
job_id: jobId,
status: UpdateTaskStatus.Resume
})
if (code === 0) {
message.success('Resumed successfully')
getPlans()
}
}
//
async function onUploadMediaFileNow (jobId: string) {
const { code } = await uploadMediaFileNow(workspaceId, jobId)
if (code === 0) {
message.success('Upload Media File successfully')
getPlans()
}
}
</script>
<style lang="scss" scoped>
@ -333,13 +206,8 @@ async function onUploadMediaFileNow (jobId: string) { @@ -333,13 +206,8 @@ async function onUploadMediaFileNow (jobId: string) {
margin-top: 10px;
}
.action-area {
&::v-deep {
.ant-btn {
margin-right: 10px;
margin-bottom: 10px;
}
}
color: $primary;
cursor: pointer;
}
.circle-icon {

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

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
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'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap } from '/@/types/task'
export function useFormatTask () {
function formatTaskType (task: Task) {
@ -12,10 +11,6 @@ export function useFormatTask () { @@ -12,10 +11,6 @@ export function useFormatTask () {
return time || DEFAULT_PLACEHOLDER
}
function formatLostAction (task: Task) {
return OutOfControlActionMap[task.out_of_control_action] || DEFAULT_PLACEHOLDER
}
function formatTaskStatus (task: Task) {
const statusObj = {
text: '',
@ -27,47 +22,9 @@ export function useFormatTask () { @@ -27,47 +22,9 @@ export function useFormatTask () {
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,
}
}

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

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { TaskProgressInfo } from '/@/types/task'
export function useTaskProgressEvent (onTaskProgressWs: (data: TaskProgressInfo) => void): void {
function handleTaskProgress (payload: any) {
onTaskProgressWs(payload.data)
// eslint-disable-next-line no-unused-expressions
// console.log('payload', payload.data)
}
onMounted(() => {
EventBus.on('deviceTaskProgress', handleTaskProgress)
})
onBeforeUnmount(() => {
EventBus.off('deviceTaskProgress', handleTaskProgress)
})
}

43
src/components/task/use-task-ws-event.ts

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

15
src/components/workspace/DividerLine.vue

@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
<template>
<Divider class="divider" />
</template>
<script lang="ts" setup>
import { Divider } from 'ant-design-vue'
</script>
<style lang="scss" scoped>
.divider {
margin: 10px 0;
height: 1px;
background-color: #4f4f4f;
}
</style>

21
src/components/workspace/Title.vue

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
<template>
<div style="height: 40px; line-height: 50px; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
<a-col :span="(23 - (extSpan || 0))">{{ title }}</a-col>
<a-col :span="extSpan"><slot /></a-col>
</a-row>
</div>
<DividerLine />
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import DividerLine from '/@/components/workspace/DividerLine.vue'
const props = defineProps < {
extSpan?: number,
title: string,
} >()
</script>

6
src/constants/map.ts

@ -11,8 +11,8 @@ export const MapElementDefaultColor = MapElementColor.Default @@ -11,8 +11,8 @@ export const MapElementDefaultColor = MapElementColor.Default
export enum MapDoodleColor {
PinColor = '#2D8CF0',
PolylineColor = '#2D8CF0',
PolygonColor = '#2D8CF0'
PolylineColor = '#3366FF',
PolygonColor = '#FF33FF'
}
export enum MapElementEnum {
@ -20,4 +20,4 @@ export enum MapElementEnum { @@ -20,4 +20,4 @@ export enum MapElementEnum {
LINE = 1,
POLY = 2
}
export type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' | 'circle'
export type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off'

38
src/directives/drag-window.ts

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

6
src/directives/index.ts

@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
import { App } from 'vue'
import useDragWindowDirective from './drag-window'
export function useDirectives (app: App): void {
useDragWindowDirective(app)
}

7
src/event-bus/index.ts

@ -3,12 +3,7 @@ import mitt, { Emitter } from 'mitt' @@ -3,12 +3,7 @@ import mitt, { Emitter } from 'mitt'
type Events = {
deviceUpgrade: any; // 设备升级
deviceLogUploadProgress: any // 设备日志上传
flightTaskWs: any // 机场任务消息
droneControlWs: any // 飞行指令信息
droneControlMqttInfo: any // drc 链路通知
flightAreasDroneLocationWs: any
flightAreasSyncProgressWs: any
flightAreasUpdateWs: any
deviceTaskProgress: any // 设备任务进度
};
const emitter: Emitter<Events> = mitt<Events>()

209
src/hooks/use-g-map-cover.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
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'
@ -15,16 +14,12 @@ export function useGMapCover () { @@ -15,16 +14,12 @@ export function useGMapCover () {
const normalColor = '#2D8CF0'
const store = rootStore
const coverMap = store.state.coverMap
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
const disableColor = '#b3b3b3'
const coverList = store.state.coverList
function AddCoverToMap (cover :any) {
root.$map.add(cover)
coverMap[cover.getExtData().id] = [cover]
coverList.push(cover)
// console.log('coverList:', store.state.coverList)
}
function getPinIcon (color?:string) {
@ -34,10 +29,10 @@ export function useGMapCover () { @@ -34,10 +29,10 @@ export function useGMapCover () {
} = {
'2d8cf0': pin2d8cf0,
'19be6b': pin19be6b,
212121: pin212121,
b620e0: pinb620e0,
e23c39: pine23c39,
ffbb00: pineffbb00,
'212121': pin212121,
'b620e0': pinb620e0,
'e23c39': pine23c39,
'ffbb00': pineffbb00,
}
const iconName = (color?.replaceAll('#', '') || '').toLocaleLowerCase()
return new AMap.Icon({
@ -49,6 +44,7 @@ export function useGMapCover () { @@ -49,6 +44,7 @@ export function useGMapCover () {
}
function init2DPin (name: string, coordinates:GeojsonCoordinate, color?:string, data?:{}) {
console.log(name, coordinates[0], coordinates[1], color, data)
const pin = new AMap.Marker({
position: new AMap.LngLat(coordinates[0], coordinates[1]),
title: name,
@ -63,8 +59,7 @@ export function useGMapCover () { @@ -63,8 +59,7 @@ export function useGMapCover () {
function AddOverlayGroup (overlayGroup) {
root.$map.add(overlayGroup)
const id = overlayGroup.getExtData().id
coverMap[id] = [...(coverMap[id] || []), overlayGroup]
coverList.push(overlayGroup)
}
function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) {
const path = [] as GeojsonCoordinate[]
@ -83,9 +78,9 @@ export function useGMapCover () { @@ -83,9 +78,9 @@ export function useGMapCover () {
AddOverlayGroup(polyline)
}
function initPolygon (name: string, coordinates:GeojsonCoordinate[][], color?:string, data?:{}) {
function initPolygon (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) {
const path = [] as GeojsonCoordinate[]
coordinates[0].forEach(coordinate => {
coordinates.forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
// console.log('Polygon', path)
@ -103,18 +98,36 @@ export function useGMapCover () { @@ -103,18 +98,36 @@ export function useGMapCover () {
}
function removeCoverFromMap (id:string) {
coverMap[id].forEach(cover => root.$map.remove(cover))
coverMap[id] = []
for (let i = 0; i < coverList.length; i++) {
const ele = coverList[i]
// console.log(ele)
const extdata = ele?.getExtData()
if (extdata?.id === id) {
console.log(extdata)
root.$map.remove(ele)
coverList.slice(i, 1)
break
}
}
}
function getElementFromMap (id:string): any[] {
return coverMap[id]
function getElementFromMap (id:string) {
// console.log('start', new Date().getTime())
const ele = coverList.find(ele => ele?.getExtData().id === id)
// console.log('end', new Date().getTime())
return ele
// coverList.forEach((ele:any) => {
// const extdata = ele?.getExtData()
// // console.log(extdata)
// if (extdata?.id === id) {
// return ele
// }
// })
}
function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
const element = elements[0]
const element = getElementFromMap(id) as any
if (element) {
const icon = getPinIcon(color)
element.setPosition(new AMap.LngLat(coordinates[0], coordinates[1]))
element.setIcon(icon)
@ -128,160 +141,12 @@ export function useGMapCover () { @@ -128,160 +141,12 @@ export function useGMapCover () {
}
}
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,
updatePinElement
}
}

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

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import store from '/@/store'
import { getRoot } from '/@/root'
import { ELocalStorageKey, EDeviceTypeName } from '/@/types'
import { ELocalStorageKey } from '/@/types'
import { getDeviceBySn } from '/@/api/manage'
import { message } from 'ant-design-vue'
import dockIcon from '/@/assets/icons/dock.png'
@ -9,16 +9,18 @@ import droneIcon from '/@/assets/icons/drone.png' @@ -9,16 +9,18 @@ import droneIcon from '/@/assets/icons/drone.png'
export function deviceTsaUpdate () {
const root = getRoot()
let AMap = root.$aMap
const AMap = root.$aMap
const icons = new Map([
[EDeviceTypeName.Aircraft, droneIcon],
[EDeviceTypeName.Gateway, rcIcon],
[EDeviceTypeName.Dock, dockIcon]
['sub-device', droneIcon],
['gateway', rcIcon],
['dock', dockIcon]
])
const markers = store.state.markerInfo.coverMap
const paths = store.state.markerInfo.pathMap
// Fix: 航迹初始化报错
// TODO: 从时序上解决
let trackLine = null as any
function getTrackLineInstance () {
if (!trackLine) {
@ -30,7 +32,7 @@ export function deviceTsaUpdate () { @@ -30,7 +32,7 @@ export function deviceTsaUpdate () {
return trackLine
}
function initIcon (type: number) {
function initIcon (type: string) {
return new AMap.Icon({
image: icons.get(type),
imageSize: new AMap.Size(40, 40),
@ -38,16 +40,16 @@ export function deviceTsaUpdate () { @@ -38,16 +40,16 @@ export function deviceTsaUpdate () {
})
}
function initMarker (type: number, name: string, sn: string, lng?: number, lat?: number) {
if (markers[sn]) {
function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) {
if (AMap === undefined) {
location.reload()
return
}
if (root.$aMap === undefined) {
if (markers[sn]) {
return
}
AMap = root.$aMap
markers[sn] = new AMap.Marker({
position: new AMap.LngLat(lng || 113.943225499, lat || 22.577673716),
position: new AMap.LngLat(lng || 113.935913, lat || 22.525335),
icon: initIcon(type),
title: name,
anchor: 'top-center',

4
src/hooks/use-g-map.ts

@ -15,8 +15,8 @@ export function useGMapManage () { @@ -15,8 +15,8 @@ export function useGMapManage () {
}).then((AMap) => {
state.aMap = AMap
state.map = new AMap.Map(container, {
center: [113.943225499, 22.577673716],
zoom: 20
center: [113.935913, 22.525335],
zoom: 15
})
state.mouseTool = new AMap.MouseTool(state.map)

16
src/hooks/use-map-tool.ts

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

58
src/hooks/use-mouse-tool.ts

@ -3,8 +3,6 @@ import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg' @@ -3,8 +3,6 @@ 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()
@ -15,10 +13,7 @@ export function useMouseTool () { @@ -15,10 +13,7 @@ export function useMouseTool () {
PolygonNum: 0,
currentType: '',
})
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
function drawPin (type:MapDoodleType, getDrawCallback:Function) {
root?.$mouseTool.marker({
title: type + state.pinNum,
@ -34,6 +29,7 @@ export function useMouseTool () { @@ -34,6 +29,7 @@ export function useMouseTool () {
strokeOpacity: 1,
strokeWeight: 2,
strokeStyle: 'solid',
draggable: true,
title: type + state.polylineNum++
})
root?.$mouseTool.on('draw', getDrawCallback)
@ -46,6 +42,7 @@ export function useMouseTool () { @@ -46,6 +42,7 @@ export function useMouseTool () {
strokeWeight: 2,
fillColor: '#1791fc',
fillOpacity: 0.4,
draggable: true,
title: type + state.PolygonNum++
})
root?.$mouseTool.on('draw', getDrawCallback)
@ -56,55 +53,8 @@ export function useMouseTool () { @@ -56,55 +53,8 @@ export function useMouseTool () {
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) {
function mouseTool (type: MapDoodleType, getDrawCallback: Function) {
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)

4
src/main.ts

@ -5,14 +5,10 @@ import { CommonComponents } from './use-common-components' @@ -5,14 +5,10 @@ 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')

12
src/mqtt/config.ts

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
import {
IClientOptions,
} from 'mqtt'
export const OPTIONS: IClientOptions = {
clean: true, // true: 清除会话, false: 保留会话
connectTimeout: 10000, // mqtt 超时时间
resubscribe: true, // 断开重连后,再次订阅原订阅
reconnectPeriod: 10000, // 重连间隔时间: 5s
keepalive: 1, // 心跳间隔时间:1s
}

117
src/mqtt/index.ts

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

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

@ -13,9 +13,9 @@ @@ -13,9 +13,9 @@
<RightOutlined style="float: right; margin-top: 5px; color: #8894a0" />
</div>
<div style="height: 50%;">
<CloudSyncOutlined v-if="thingState === EStatusValue.CONNECTED" style="color: #75c5f6" />
<CloudSyncOutlined v-if="state === EStatusValue.CONNECTED" style="color: #75c5f6" />
<SyncOutlined spin v-else/>
<span style="color: #737373; margin-left: 3px;">{{ thingState }}</span>
<span style="color: #737373; margin-left: 3px;">{{ state }}</span>
</div>
<a-drawer placement="right" v-model:visible="drawerVisible" width="340px">
<div class="mb10 flex-row flex-justify-center flex-align-center">
@ -67,7 +67,7 @@ @@ -67,7 +67,7 @@
<span style="color: #737373">{{ device.data.gateway_sn }}</span>
</a-col>
</a-row>
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" v-if="device.data.online_status && device.data.sn">
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" v-if="device.data.online_status">
<a-col :span="1"></a-col>
<a-col :span="9">Aircraft Sn</a-col>
<a-col :span="13" class="flex-align-end flex-column" >
@ -79,7 +79,7 @@ @@ -79,7 +79,7 @@
<span class="ml5" style="color: #939393;">Settings</span>
</div>
<div class="fz16" style="background-color: white; border-radius: 4px;">
<a-row v-if="device.data.online_status && device.data.sn" style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="bindingDevice">
<a-row v-if="device.data.online_status" style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="bindingDevice">
<a-col :span="1"></a-col>
<a-col :span="11">
Device Binding
@ -135,6 +135,7 @@ import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' @@ -135,6 +135,7 @@ import { useConnectWebSocket } from '/@/hooks/use-connect-websocket'
const root = getRoot()
const gatewayState = ref<boolean>(localStorage.getItem(ELocalStorageKey.GatewayOnline) === 'true')
const state = ref(EStatusValue.DISCONNECT)
const thingState = ref(EStatusValue.DISCONNECT)
const apiState = ref(EStatusValue.DISCONNECT)
const liveState = ref(EStatusValue.DISCONNECT)
@ -149,14 +150,14 @@ const wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!) @@ -149,14 +150,14 @@ const wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)
const components = apiPilot.init()
const exitVisible = ref(false)
const drawerVisible = ref(false)
let minitor: any
let minitor = -1
interface DeviceInfoData {
data: DeviceStatus
}
const device = reactive<DeviceInfoData>({
data: {
sn: '',
sn: EStatusValue.DISCONNECT,
online_status: false,
device_callsign: '',
user_id: '',
@ -164,7 +165,7 @@ const device = reactive<DeviceInfoData>({ @@ -164,7 +165,7 @@ const device = reactive<DeviceInfoData>({
bound_status: false,
model: '',
gateway_sn: EStatusValue.DISCONNECT,
domain: -1
domain: ''
}
})
const bindParam: BindBody = {
@ -217,7 +218,9 @@ const messageHandler = async (payload: any) => { @@ -217,7 +218,9 @@ const messageHandler = async (payload: any) => {
case EBizCode.DeviceOnline: {
console.info('online: ', payload)
if (payload.data.sn === device.data.gateway_sn) {
gatewayState.value = true
localStorage.setItem(ELocalStorageKey.GatewayOnline, gatewayState.value.toString())
state.value = gatewayState.value && thingState.value === EStatusValue.CONNECTED ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
break
}
if (payload.data.gateway_sn === device.data.gateway_sn) {
@ -243,20 +246,16 @@ const messageHandler = async (payload: any) => { @@ -243,20 +246,16 @@ const messageHandler = async (payload: any) => {
// ws
useConnectWebSocket(messageHandler)
let bindNum: any
let bindNum: number
onMounted(() => {
apiPilot.onBackClickReg()
apiPilot.onStopPlatform()
const oldDevice = localStorage.getItem(ELocalStorageKey.Device)
if (oldDevice) {
device.data = JSON.parse(oldDevice)
}
window.connectCallback = (arg: any) => {
window.connectCallback = arg => {
connectCallback(arg)
}
window.wsConnectCallback = (arg: any) => {
window.wsConnectCallback = arg => {
wsConnectCallback(arg)
}
device.data.gateway_sn = apiPilot.getRemoteControllerSN()
@ -264,7 +263,10 @@ onMounted(() => { @@ -264,7 +263,10 @@ onMounted(() => {
message.warn('Data is not available, please restart the remote control.')
return
}
const oldDevice = localStorage.getItem(ELocalStorageKey.Device)
if (oldDevice) {
device.data = JSON.parse(oldDevice)
}
device.data.sn = apiPilot.getAircraftSN()
getDeviceInfo()
@ -329,11 +331,6 @@ const connectCallback = async (arg: any) => { @@ -329,11 +331,6 @@ const connectCallback = async (arg: any) => {
apiPilot.loadComponent(EComponentName.Mission, {})
bindNum = setInterval(() => {
if (!bindParam.device_sn) {
device.data.gateway_sn = apiPilot.getRemoteControllerSN()
bindParam.device_sn = device.data.gateway_sn
return
}
bindDevice(bindParam).then(bindRes => {
if (bindRes.code !== 0) {
message.error(bindRes.message)
@ -438,6 +435,7 @@ function refreshStatus () { @@ -438,6 +435,7 @@ function refreshStatus () {
tsaState.value = apiPilot.isComponentLoaded(EComponentName.Tsa) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
mediaState.value = apiPilot.isComponentLoaded(EComponentName.Media) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
waylineState.value = apiPilot.isComponentLoaded(EComponentName.Mission) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
state.value = thingState.value === EStatusValue.CONNECTED && gatewayState.value ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
}
function moduleInstall (m: any) {
@ -483,7 +481,7 @@ function moduleUninstall (m: any) { @@ -483,7 +481,7 @@ function moduleUninstall (m: any) {
}
function getDeviceInfo () {
if (!device.data.sn || device.data.sn === EStatusValue.DISCONNECT) {
if (device.data.sn === EStatusValue.DISCONNECT) {
return
}
getDeviceBySn(bindParam.workspace_id, device.data.sn).then(res => {

28
src/pages/page-pilot/pilot-liveshare.vue

@ -54,26 +54,7 @@ @@ -54,26 +54,7 @@
<div class="width-100" style="margin-top: -10px;">
<div class="ml10" style="width: 97%;">
<span class="fz16">Param: </span>
<span v-if="liveStreamStatus.type === ELiveTypeValue.Agora" style="word-break: break-all; color: #75c5f6;">
<div class="flex-col flex-justify-center flex-align-center">
<div>
<span class="ml10">Token:</span>
<a-input
class="ml10"
v-model:value="agoraParam.token"
placeholder="Token"
></a-input>
</div>
<div>
<span class="ml10">Channel:</span>
<a-input
class="ml10"
v-model:value="agoraParam.channelId"
placeholder="Channel"
></a-input>
</div>
</div>
</span>
<span v-if="liveStreamStatus.type === ELiveTypeValue.Agora" style="word-break: break-all; color: #75c5f6;">{{ agoraParam }}</span>
<span v-else-if="liveStreamStatus.type === ELiveTypeValue.RTMP" style="word-break: break-all; color: #75c5f6;">{{ rtmpParam }}</span>
<span v-else-if="liveStreamStatus.type === ELiveTypeValue.RTSP" style="word-break: break-all; color: #75c5f6;">{{ rtspParam }}</span>
<span v-else-if="liveStreamStatus.type === ELiveTypeValue.GB28181" style="word-break: break-all; color: #75c5f6;">{{ gb28181Param }}</span>
@ -173,11 +154,11 @@ const liveTypeList = [ @@ -173,11 +154,11 @@ const liveTypeList = [
label: ELiveTypeName.GB28181
}
]
const agoraParam = reactive({
const agoraParam = {
uid: '2892130292',
token: config.agoraToken,
channelId: config.agoraChannel
})
}
const rtmpParam = {
url: config.rtmpURL + new Date().getTime()
}
@ -193,7 +174,7 @@ const gb28181Param: GB28181Param = { @@ -193,7 +174,7 @@ const gb28181Param: GB28181Param = {
agentId: CURRENT_CONFIG.gbAgentId,
password: CURRENT_CONFIG.gbPassword,
agentPort: CURRENT_CONFIG.gbAgentPort,
agentChannel: CURRENT_CONFIG.gbAgentChannel,
agentChannel: CURRENT_CONFIG.gbAgentChannel
}
const playVisiable = ref(false)
@ -272,7 +253,6 @@ const onPublishModeSelect = (val: string) => { @@ -272,7 +253,6 @@ const onPublishModeSelect = (val: string) => {
apiPilot.setVideoPublishType(publishModeSelected.value)
}
const onPlay = () => {
console.info(JSON.stringify(agoraParam))
if (!publishModeSelected.value) {
message.warn('Please select publish mode!')
return

325
src/pages/page-web/projects/Firmwares.vue

@ -1,325 +0,0 @@ @@ -1,325 +0,0 @@
<template>
<div class="ml20 mt20 mr20 flex-row flex-align-center flex-justify-between">
<div class="flex-row">
<a-button type="primary" @click="sVisible = true">
Click to Upload
</a-button>
<a-modal :visible="sVisible"
title="Import Firmware File"
:closable="false"
@cancel="onCancel"
@ok="uploadFile"
centered>
<a-form :rules="rules" ref="formRef" :model="uploadParam" :label-col="{ span: 6 }">
<a-form-item name="status" label="Avaliable" required>
<a-switch v-model:checked="uploadParam.status" />
</a-form-item>
<a-form-item name="device_name" label="Device Name" required>
<a-select
style="width: 220px"
mode="multiple"
placeholder="can choose multiple"
v-model:value="uploadParam.device_name">
<a-select-option
v-for="k in DeviceNameEnum"
:key="k"
:value="k"
>
{{ k }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="release_note" label="Release Note" required>
<a-textarea v-model:value="uploadParam.release_note" showCount :maxlength="300" />
</a-form-item>
<a-form-item label="File" required>
<a-upload
:multiple="false"
:before-upload="beforeUpload"
:show-upload-list="true"
:file-list="fileList"
:remove="removeFile"
>
<a-button type="primary">
<UploadOutlined />
Import Firmware File
</a-button>
</a-upload>
</a-form-item>
</a-form>
</a-modal>
</div>
<div class="flex-row">
<div class="ml5">
<a-select
style="width: 150px"
v-model:value="param.firmware_status"
@select="getAllFirmwares(pageParam)">
<a-select-option
v-for="(key, value) in FirmwareStatusEnum"
:key="key"
:value="value"
>
{{ key }}
</a-select-option>
</a-select>
</div>
<div class="ml5">
<a-select
style="width: 150px"
v-model:value="param.device_name"
@select="getAllFirmwares(pageParam)">
<a-select-option
v-for="item in deviceNameList"
:key="item.label"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</div>
<div class="ml5">
<a-input-search
:enter-button="true"
v-model:value="param.product_version"
placeholder="input search verison"
style="width: 250px"
@search="getAllFirmwares(pageParam)"/>
</div>
</div>
</div>
<div class="table flex-display flex-column">
<a-table :columns="columns" :data-source="data.firmware" :pagination="paginationProp" @change="refreshData" row-key="firmware_id"
:rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)" :scroll="{ x: '100%', y: 600 }">
<template #device_name="{ record }">
<div v-for="text in record.device_name" :key="text">
{{ text }}
</div>
</template>
<template #file_size="{ record }">
<div>{{ bytesToSize(record.file_size) }}</div>
</template>
<template #firmware_status="{ record }">
<DeviceFirmwareStatus :firmware="record" />
</template>
<template v-for="col in ['file_name', 'release_note']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
</a-table>
</div>
</template>
<script lang="ts" setup>
import { message, notification, PaginationProps } from 'ant-design-vue'
import { TableState } from 'ant-design-vue/lib/table/interface'
import { onMounted, reactive, Ref, ref, UnwrapRef } from 'vue'
import { IPage } from '/@/api/http/type'
import { getFirmwares, importFirmareFile } from '/@/api/manage'
import DeviceFirmwareStatus from '/@/components/devices/DeviceFirmwareStatus.vue'
import { ELocalStorageKey } from '/@/types'
import { UploadOutlined } from '@ant-design/icons-vue'
import { Firmware, FirmwareQueryParam, FirmwareStatusEnum, DeviceNameEnum, FirmwareUploadParam } from '/@/types/device-firmware'
import { commonColor } from '/@/utils/color'
import { bytesToSize } from '/@/utils/bytes'
import moment from 'moment'
interface FirmwareData {
firmware: Firmware[]
}
const columns = [
{ title: 'Model', dataIndex: 'device_name', width: 120, ellipsis: true, className: 'titleStyle', slots: { customRender: 'device_name' } },
{ title: 'File Name', dataIndex: 'file_name', width: 220, ellipsis: true, className: 'titleStyle', slots: { customRender: 'file_name' } },
{ title: 'Firmware Version', dataIndex: 'product_version', width: 180, className: 'titleStyle' },
{ title: 'File Size', dataIndex: 'file_size', width: 150, className: 'titleStyle', slots: { customRender: 'file_size' } },
{ title: 'Creator', dataIndex: 'username', width: 100, className: 'titleStyle' },
{ title: 'Release Date', dataIndex: 'released_time', width: 160, sorter: (a: Firmware, b: Firmware) => a.released_time.localeCompare(b.released_time), className: 'titleStyle' },
{ title: 'Release Note', dataIndex: 'release_note', width: 300, ellipsis: true, className: 'titleStyle', slots: { customRender: 'release_note' } },
{ title: 'Status', dataIndex: 'firmware_status', width: 100, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
]
const data = reactive<FirmwareData>({
firmware: []
})
const paginationProp = reactive({
pageSizeOptions: ['20', '50', '100'],
showQuickJumper: true,
showSizeChanger: true,
pageSize: 50,
current: 1,
total: 0
})
const deviceNameList = ref<any[]>([{ label: 'All', value: '' }])
type Pagination = TableState['pagination']
const pageParam: IPage = {
page: 1,
total: 0,
page_size: 50
}
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const param = reactive<FirmwareQueryParam>({
product_version: '',
device_name: '',
firmware_status: FirmwareStatusEnum.NONE
})
onMounted(() => {
getAllFirmwares(pageParam)
for (const key in DeviceNameEnum) {
const value = DeviceNameEnum[key]
deviceNameList.value.push({ label: value, value: value })
}
})
function refreshData (page: Pagination) {
pageParam.page = page?.current!
pageParam.page_size = page?.pageSize!
getAllFirmwares(pageParam)
}
function getAllFirmwares (page: IPage) {
getFirmwares(workspaceId, page, param).then(res => {
const firmwareList: Firmware[] = res.data.list
data.firmware = firmwareList
paginationProp.total = res.data.pagination.total
paginationProp.current = res.data.pagination.page
})
}
const sVisible = ref(false)
const uploadParam = reactive<FirmwareUploadParam>({
device_name: [],
release_note: '',
status: true
})
const rules = {
status: [{ required: true }],
release_note: [{ required: true, message: 'Please input release note.' }],
device_name: [{ required: true, message: 'Please select which models this firmware belongs to.' }]
}
interface FileItem {
uid: string;
name?: string;
status?: string;
response?: string;
url?: string;
}
interface FileInfo {
file: FileItem;
fileList: FileItem[];
}
const fileList = ref<FileItem[]>([])
function beforeUpload (file: FileItem) {
if (!file.name || !file.name?.endsWith('.zip')) {
message.error('Format error. Please select zip file.')
return false
}
fileList.value = [file]
return false
}
const formRef = ref()
function removeFile (file: FileItem) {
fileList.value = []
}
function onCancel () {
formRef.value.resetFields()
fileList.value = []
sVisible.value = false
}
const uploadFile = async () => {
if (fileList.value.length === 0) {
message.error('Please select at least one file.')
}
let uploading: string
formRef.value.validate().then(async () => {
const file: FileItem = fileList.value[0]
const fileData = new FormData()
fileData.append('file', file as any, file.name)
Object.keys(uploadParam).forEach((key) => {
const val = uploadParam[key as keyof FirmwareUploadParam]
if (val instanceof Array) {
val.forEach((value) => {
fileData.append(key, value)
})
} else {
fileData.append(key, val.toString())
}
})
const timestamp = new Date().getTime()
uploading = (file.name ?? 'uploding') + timestamp
notification.open({
key: uploading,
message: `Uploading ${moment().format()}`,
description: `[${file.name}] is uploading... `,
duration: null
})
importFirmareFile(workspaceId, fileData).then((res) => {
if (res.code === 0) {
notification.success({
message: `Uploaded ${moment().format()}`,
description: `[${file.name}] file uploaded successfully. Duration: ${moment.duration(new Date().getTime() - timestamp).asSeconds()}`,
duration: null
})
getAllFirmwares(pageParam)
} else {
notification.error({
message: `Failed to upload [${file.name}]. Check and try again.`,
description: `Error message: ${res.message} ${moment().format()}`,
style: { color: commonColor.FAIL },
duration: null,
})
}
}).finally(() => {
notification.close(uploading)
})
fileList.value = []
formRef.value.resetFields()
sVisible.value = false
})
}
</script>
<style>
.table {
background-color: white;
margin: 20px;
padding: 20px;
height: 88vh;
}
.table-striped {
background-color: #f7f9fa;
}
.ant-table {
border-top: 1px solid rgb(0,0,0,0.06);
border-bottom: 1px solid rgb(0,0,0,0.06);
}
.ant-table-tbody tr td {
border: 0;
}
.ant-table td {
white-space: nowrap;
}
.ant-table-thead tr th {
background: white !important;
border: 0;
}
th.ant-table-selection-column {
background-color: white !important;
}
.ant-table-header {
background-color: white !important;
}
</style>

17
src/pages/page-web/projects/devices.vue

@ -20,9 +20,7 @@ @@ -20,9 +20,7 @@
style="margin: -5px 0"
/>
<template v-else>
<a-tooltip :title="text">
{{ text }}
</a-tooltip>
</template>
</div>
</template>
@ -129,7 +127,7 @@ import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use- @@ -129,7 +127,7 @@ import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-
import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'
import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'
import { message, notification } from 'ant-design-vue'
import { message } from 'ant-design-vue'
interface DeviceData {
device: Device[]
@ -270,18 +268,13 @@ function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) { @@ -270,18 +268,13 @@ function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) {
for (let i = 0; i < devices.length; i++) {
if (devices[i].device_sn === payload.sn) {
if (!payload.output) return
const { status, progress, ext } = payload.output
const { status, progress } = payload.output
if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { //
const rate = ext?.rate ? (ext.rate / 1024).toFixed(2) + 'kb/s' : ''
devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade
devices[i].firmware_progress = (progress?.percent || 0) + '% ' + rate
devices[i].firmware_progress = progress?.percent || 0
} else { //
if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) {
notification.error({
message: `(${payload.sn}) Upgrade failed`,
description: `Error Code: ${payload.result}`,
duration: null
})
message.error(`设备(${payload.sn}) 升级失败`)
}
//
getDevices(current.value[0], true)
@ -297,7 +290,7 @@ function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) { @@ -297,7 +290,7 @@ function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) {
useDeviceUpgradeEvent(onDeviceUpgradeWs)
//
function getDevices (domain: number, closeLoading?: boolean) {
function getDevices (domain: string, closeLoading?: boolean) {
if (!closeLoading) {
loading.value = true
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div class="height-100">
<div>
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
@ -7,10 +7,9 @@ @@ -7,10 +7,9 @@
<a-col :span="1"></a-col>
</a-row>
</div>
<div class="scrollbar height-100" :style="{ height: scorllHeight + 'px'}">
<div id="data" class=" uranus-scrollbar" v-if="docksData.data.length !== 0" @scroll="onScroll">
<div v-if="docksData.data.length !== 0">
<div v-for="dock in docksData.data" :key="dock.device_sn">
<div class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
<div v-if="dock?.children" class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
<div class="title">
<a-tooltip :title="dock.nickname">
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
@ -18,7 +17,7 @@ @@ -18,7 +17,7 @@
</div>
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<span><RocketOutlined /></span>
<span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span>
<span class="ml5">{{ dock.children?.nickname }}</span>
</div>
</div>
</div>
@ -27,7 +26,6 @@ @@ -27,7 +26,6 @@
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
@ -37,7 +35,7 @@ import { onMounted, ref } from 'vue' @@ -37,7 +35,7 @@ import { onMounted, ref } from 'vue'
import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles } from '/@/api/wayline'
import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
import { Device } from '/@/types/device'
import { Device, EDeviceType } from '/@/types/device'
import { useMyStore } from '/@/store'
import { getBindingDevices } from '/@/api/manage'
import { IPage } from '/@/api/http/type'
@ -49,54 +47,28 @@ const docksData = reactive({ @@ -49,54 +47,28 @@ const docksData = reactive({
})
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const scorllHeight = ref()
const canRefresh = ref(true)
onMounted(() => {
const parent = document.getElementsByClassName('scrollbar').item(0)?.parentNode as HTMLDivElement
scorllHeight.value = document.body.clientHeight - parent.firstElementChild!.clientHeight
getDocks()
const key = setInterval(() => {
const data = document.getElementById('data')?.lastElementChild as HTMLDivElement
if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page || scorllHeight.value + 50 <= data?.clientHeight + data?.offsetTop) {
clearInterval(key)
return
}
body.page++
getDocks()
}, 1000)
})
const body: IPage = {
page: 1,
total: -1,
page_size: 10,
total: 0,
page_size: 100
}
async function getDocks () {
if (!canRefresh.value) {
return
}
canRefresh.value = false
await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
function getDocks () {
getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
if (res.code !== 0) {
return
}
docksData.data.push(...res.data.list)
body.page = res.data.pagination.page
body.page_size = res.data.pagination.page_size
body.total = res.data.pagination.total
}).finally(() => {
canRefresh.value = true
})
}
function onScroll (e: any) {
const element = e.srcElement
if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(body.total / body.page_size) > body.page && canRefresh.value) {
body.page++
getDocks()
docksData.data = []
res.data.list.forEach((dock: any) => {
if (dock.child_device_sn) {
docksData.data.push(dock)
}
})
console.info(docksData.data)
})
}
function selectDock (dock: Device) {
@ -125,10 +97,4 @@ function selectDock (dock: Device) { @@ -125,10 +97,4 @@ function selectDock (dock: Device) {
margin: 0px 10px 0 10px;
}
}
.uranus-scrollbar {
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #c5c8cc transparent;
height: 100%;
}
</style>

98
src/pages/page-web/projects/flight-area.vue

@ -1,98 +0,0 @@ @@ -1,98 +0,0 @@
<template>
<div class="project-flight-area-wrapper height-100">
<a-spin :spinning="loading" :delay="300" tip="loading" size="large" class="height-100">
<Title title="Custom Flight Area" />
<FlightAreaPanel :data="flightAreaList" @location-area="clickArea" @delete-area="deleteAreaById"/>
<DividerLine />
<FlightAreaSyncPanel />
</a-spin>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import Title from '/@/components/workspace/Title.vue'
import DividerLine from '/@/components/workspace/DividerLine.vue'
import FlightAreaPanel from '/@/components/flight-area/FlightAreaPanel.vue'
import FlightAreaSyncPanel from '/@/components/flight-area/FlightAreaSyncPanel.vue'
import { GetFlightArea, deleteFlightArea, getFlightAreaList } from '/@/api/flight-area'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import { useMapTool } from '/@/hooks/use-map-tool'
import { EFlightAreaType, EGeometryType, FlightAreaUpdate } from '/@/types/flight-area'
import { useFlightArea } from '/@/components/flight-area/use-flight-area'
import { useFlightAreaUpdateEvent } from '/@/components/flight-area/use-flight-area-update'
const loading = ref(false)
const flightAreaList = ref<GetFlightArea[]>([])
let useGMapCoverHook = useGMapCover()
let useMapToolHook = useMapTool()
onMounted(() => {
getDataList()
})
const { getGcj02 } = useFlightArea()
const initMapFlightArea = () => {
useMapToolHook = useMapTool()
useGMapCoverHook = useGMapCover()
flightAreaList.value.forEach(area => {
updateMapFlightArea(area)
})
}
const updateMapFlightArea = (area: GetFlightArea) => {
switch (area.content.geometry.type) {
case EGeometryType.CIRCLE:
useGMapCoverHook.updateFlightAreaCircle(area.area_id, area.name, area.content.geometry.radius, getGcj02(area.content.geometry.coordinates), area.status, area.type)
break
case 'Polygon':
useGMapCoverHook.updateFlightAreaPolygon(area.area_id, area.name, getGcj02(area.content.geometry.coordinates[0]), area.status, area.type)
break
}
}
const getDataList = () => {
loading.value = true
getFlightAreaList().then(res => {
flightAreaList.value = res.data
setTimeout(initMapFlightArea, 2000)
}).finally(() => {
loading.value = false
})
}
const deleteAreaById = (areaId: string) => {
deleteFlightArea(areaId)
}
const deleteArea = (area: FlightAreaUpdate) => {
flightAreaList.value = flightAreaList.value.filter(data => data.area_id !== area.area_id)
useGMapCoverHook.removeCoverFromMap(area.area_id)
}
const updateArea = (area: FlightAreaUpdate) => {
flightAreaList.value = flightAreaList.value.map(data => data.area_id === area.area_id ? area : data)
updateMapFlightArea(area as GetFlightArea)
}
const addArea = (area: FlightAreaUpdate) => {
flightAreaList.value.push(area as GetFlightArea)
updateMapFlightArea(area as GetFlightArea)
}
useFlightAreaUpdateEvent(addArea, deleteArea, updateArea)
const clickArea = (area: GetFlightArea) => {
console.info(area)
let coordinate
switch (area.content.geometry.type) {
case EGeometryType.CIRCLE:
coordinate = getGcj02(area.content.geometry.coordinates)
break
case 'Polygon':
coordinate = useGMapCoverHook.calcPolygonPosition(getGcj02(area.content.geometry.coordinates[0]))
break
}
useMapToolHook.panTo(coordinate)
}
</script>

50
src/pages/page-web/projects/layer.vue

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div class="project-layer-wrapper height-100">
<div class="project-layer-wrapper">
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
@ -7,7 +7,6 @@ @@ -7,7 +7,6 @@
<a-col :span="1"></a-col>
</a-row>
</div>
<div class="scrollbar" :style="{ height: scorllHeight + 'px'}">
<LayersTree
:layer-data="mapLayers"
class="project-layer-content"
@ -16,7 +15,6 @@ @@ -16,7 +15,6 @@
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
/>
</div>
<a-drawer
title="Map Element"
placement="right"
@ -130,7 +128,6 @@ const colors = ref<Color[]>([ @@ -130,7 +128,6 @@ const colors = ref<Color[]>([
{ id: 5, name: 'RED', color: '#E23C39', selected: false },
{ id: 6, name: 'NAME_DEFAULT', color: '#212121', selected: false }
])
const scorllHeight = ref()
async function getAllElement () {
getElementGroups('init')
@ -171,6 +168,7 @@ function updateMapElement ( @@ -171,6 +168,7 @@ function updateMapElement (
const geoType = element.resource?.content.geometry.type
const id = element.id
const type = element.resource?.type as number
if (MapElementEnum.PIN === type) {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate
@ -178,11 +176,17 @@ function updateMapElement ( @@ -178,11 +176,17 @@ function updateMapElement (
} else if (MapElementEnum.LINE === type && geoType === 'LineString') {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[]
useGMapCoverHook.updatePolylineElement(id, name, coordinates, color)
useGMapCoverHook.initPolyline(name, coordinates, color, {
id: id,
name: name
})
} else if (MapElementEnum.POLY === type && geoType === 'Polygon') {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[][]
useGMapCoverHook.updatePolygonElement(id, name, coordinates, color)
.coordinates[0] as GeojsonCoordinate[]
useGMapCoverHook.initPolygon(name, coordinates, color, {
id: id,
name: name
})
}
}
function checkLayer (keys: string[]) {
@ -228,12 +232,10 @@ function setBaseInfo () { @@ -228,12 +232,10 @@ function setBaseInfo () {
layerState.layerName = layer.name
layerState.layerId = layer.id
layerState.color = layer.resource?.content.properties.color
let coordinate: GeojsonCoordinate
switch (geoType) {
case GeoType.Point:
coordinate = gcj02towgs84(layer.resource?.content.geometry.coordinates[0], layer.resource?.content.geometry.coordinates[1]) as GeojsonCoordinate
layerState.longitude = coordinate[0]
layerState.latitude = coordinate[1]
layerState.longitude = layer.resource?.content.geometry.coordinates[0]
layerState.latitude = layer.resource?.content.geometry.coordinates[1]
break
case GeoType.LineString:
break
@ -243,9 +245,6 @@ function setBaseInfo () { @@ -243,9 +245,6 @@ function setBaseInfo () {
}
}
onMounted(() => {
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
const parent = element?.parentNode as HTMLDivElement
scorllHeight.value = parent?.clientHeight - parent.firstElementChild!.clientHeight
getAllElement()
})
function closeDrawer () {
@ -351,39 +350,39 @@ function updateCoordinates (transformType: string, element: LayerResource) { @@ -351,39 +350,39 @@ function updateCoordinates (transformType: string, element: LayerResource) {
) as GeojsonCoordinate
element.resource.content.geometry.coordinates = transResult
}
} else if (MapElementEnum.LINE === type) {
} else if (MapElementEnum.LINE === type && geoType === 'LineString') {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinates.forEach(coordinate => {
coordinate = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinates.forEach(coordinate => {
coordinate = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
}
element.resource.content.geometry.coordinates = coordinates
} else if (MapElementEnum.POLY === type) {
} else if (MapElementEnum.LINE === type && geoType === 'Polygon') {
const coordinates = element.resource?.content.geometry
.coordinates[0] as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinates.forEach(coordinate => {
coordinate = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinates.forEach(coordinate => {
coordinate = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
@ -440,7 +439,4 @@ function updateCoordinates (transformType: string, element: LayerResource) { @@ -440,7 +439,4 @@ function updateCoordinates (transformType: string, element: LayerResource) {
margin-bottom: 10px;
}
}
.scrollbar {
overflow: auto;
}
</style>

19
src/pages/page-web/projects/livestream.vue

@ -18,8 +18,7 @@ @@ -18,8 +18,7 @@
>
</router-link>
</div>
<div class="live" v-if="showLive" v-drag-window>
<div style="height: 40px; width: 100%" class="drag-title"></div>
<div class="live" v-if="showLive">
<a style="position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;" @click="() => root.$router.push('/' + ERouterName.LIVESTREAM)"><CloseOutlined /></a>
<router-view :name="routeName" />
</div>
@ -32,8 +31,8 @@ import { CloseOutlined } from '@ant-design/icons-vue' @@ -32,8 +31,8 @@ import { CloseOutlined } from '@ant-design/icons-vue'
import { getRoot } from '/@/root'
import { ERouterName } from '/@/types'
const root = getRoot()
const routeName = ref<string>('LiveOthers')
const showLive = ref<boolean>(root.$route.name === ERouterName.LIVING)
const routeName = ref<string>()
const showLive = ref<boolean>(false)
const options = [
{ key: 0, label: 'Agora Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveAgora' },
@ -41,7 +40,6 @@ const options = [ @@ -41,7 +40,6 @@ const options = [
]
const selectLivestream = (route: string) => {
showLive.value = root.$route.name === ERouterName.LIVING
routeName.value = route
}
@ -75,13 +73,14 @@ onMounted(() => { @@ -75,13 +73,14 @@ onMounted(() => {
.live {
position: absolute;
z-index: 1;
left: 0;
top: 10px;
margin-left: 345px;
right: 50%;
left: 50%;
top: 50%;
margin: auto;
transform: translate(-50%, -50%);
text-align: center;
width: 800px;
height: 720px;
height: 700px;
background: #232323;
}
</style>

97
src/pages/page-web/projects/tsa.vue

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
<a-col :span="1"></a-col>
</a-row>
</div>
<div class="scrollbar" :style="{ height: scorllHeight + 'px'}">
<div>
<a-collapse :bordered="false" expandIconPosition="right" accordion style="background: #232323;">
<a-collapse-panel :key="EDeviceTypeName.Dock" header="Dock" style="border-bottom: 1px solid #4f4f4f;">
<div v-if="onlineDocks.data.length === 0" style="height: 150px; color: white;">
@ -19,16 +19,16 @@ @@ -19,16 +19,16 @@
<div style="border-radius: 2px; height: 100%; width: 100%;" class="flex-row flex-justify-between flex-align-center">
<div style="float: left; padding: 0px 5px 8px 8px; width: 88%">
<div style="width: 80%; height: 30px; line-height: 30px; font-size: 16px;">
<a-tooltip :title="`${dock.gateway.callsign} - ${dock.callsign ?? 'No Drone'}`">
<div class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }} - {{ dock.callsign ?? 'No Drone' }}</div>
<a-tooltip :title="dock.gateway.callsign">
<span class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }}</span>
</a-tooltip>
</div>
<div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
<div class="flex-align-center flex-row">
<span class="ml5 mr5"><RobotOutlined /></span>
<div class="font-bold text-hidden" style="max-width: 80px;" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
{{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].basic_osd?.mode_code] : EDockModeCode[EDockModeCode.Disconnected] }}
</div>
<span class="font-bold text-hidden" style="max-width: 80px;" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
{{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].mode_code] : EDockModeCode[EDockModeCode.Disconnected] }}
</span>
</div>
<div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
<div v-if="hmsInfo[dock.gateway.sn]" class="flex-align-center flex-row">
@ -75,11 +75,11 @@ @@ -75,11 +75,11 @@
</div>
</div>
<div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
<div class="flex-row">
<div>
<span class="ml5 mr5"><RocketOutlined /></span>
<div class="font-bold text-hidden" style="max-width: 80px" :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
<span class="font-bold" :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
{{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
</div>
</span>
</div>
<div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
<div v-if="hmsInfo[dock.sn]" class="flex-align-center flex-row">
@ -126,7 +126,7 @@ @@ -126,7 +126,7 @@
</div>
</div>
<div style="float: right; background: #595959; height: 100%; width: 40px;" class="flex-row flex-justify-center flex-align-center">
<div class="fz16" @click="switchVisible($event, dock, true, dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected)">
<div class="fz16" @click="switchVisible($event, dock, true, dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].mode_code !== EDockModeCode.Disconnected)">
<a v-if="osdVisible.gateway_sn === dock.gateway.sn && osdVisible.visible"><EyeOutlined /></a>
<a v-else><EyeInvisibleOutlined /></a>
</div>
@ -154,8 +154,8 @@ @@ -154,8 +154,8 @@
<div style="float: left; padding: 5px 5px 8px 8px; width: 88%">
<div style="width: 100%; height: 100%;">
<a-tooltip>
<template #title>{{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone'}}</template>
<span class="text-hidden" style="max-width: 200px; display: block; height: 20px;">{{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone'}}</span>
<template #title>{{ device.model }} - {{ device.callsign }}</template>
<span class="text-hidden" style="max-width: 200px; display: block; height: 20px;">{{ device.model }} - {{ device.callsign }}</span>
</a-tooltip>
</div>
<div class="mt5" style="background: #595959;">
@ -173,11 +173,11 @@ @@ -173,11 +173,11 @@
</div>
</div>
<div class="flex-row flex-justify-center flex-align-center" style="height: 40px;">
<div class="flex-row" style="height: 20px; background: #595959; width: 94%;" >
<div style="height: 20px; background: #595959; width: 94%;" >
<span class="mr5"><a-image style="margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;" :src="rc" /></span>
<a-tooltip>
<template #title>{{ device.gateway.model }} - {{ device.gateway.callsign }} </template>
<div class="text-hidden" style="max-width: 200px;">{{ device.gateway.model }} - {{ device.gateway.callsign }}</div>
<template #title>{{ device.gateway.callsign }} </template>
<span>{{ device.gateway.callsign }}</span>
</a-tooltip>
</div>
</div>
@ -194,18 +194,34 @@ import { computed, onMounted, reactive, ref, watch, WritableComputedRef } from ' @@ -194,18 +194,34 @@ import { computed, onMounted, reactive, ref, watch, WritableComputedRef } from '
import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
import noData from '/@/assets/icons/no-data.png'
import rc from '/@/assets/icons/rc.png'
import { OnlineDevice, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'
import { DeviceStatus, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'
import { useMyStore } from '/@/store'
import { getDeviceTopo, getUnreadDeviceHms, updateDeviceHms } from '/@/api/manage'
import { message } from 'ant-design-vue'
import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
import { EHmsLevel } from '/@/types/enums'
const store = useMyStore()
const username = ref(localStorage.getItem(ELocalStorageKey.Username))
const workspaceId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)
const osdVisible = computed(() => store.state.osdVisible)
const osdVisible = ref({} as OSDVisible)
const hmsVisible = new Map<string, boolean>()
const scorllHeight = ref()
interface OnlineDevice {
model: string,
callsign: string,
sn: string,
mode: number,
gateway: {
model: string,
callsign: string,
sn: string,
domain: string,
},
payload: {
model: string
}[]
}
const onlineDevices = reactive({
data: [] as OnlineDevice[]
@ -240,9 +256,6 @@ onMounted(() => { @@ -240,9 +256,6 @@ onMounted(() => {
)
getOnlineDeviceHms()
}, 3000)
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
const parent = element?.parentNode as HTMLDivElement
scorllHeight.value = parent?.clientHeight - parent?.firstElementChild?.clientHeight
})
function getOnlineTopo () {
@ -252,12 +265,12 @@ function getOnlineTopo () { @@ -252,12 +265,12 @@ function getOnlineTopo () {
}
onlineDevices.data = []
onlineDocks.data = []
res.data.forEach((gateway: any) => {
const child = gateway.children
res.data.forEach((val: any) => {
const gateway = val.gateways_list.pop()
const device: OnlineDevice = {
model: child?.device_name,
callsign: child?.nickname,
sn: child?.device_sn,
model: val.device_name,
callsign: val.nickname,
sn: val.device_sn,
mode: EModeCode.Disconnected,
gateway: {
model: gateway?.device_name,
@ -267,22 +280,17 @@ function getOnlineTopo () { @@ -267,22 +280,17 @@ function getOnlineTopo () {
},
payload: []
}
child?.payloads_list.forEach((payload: any) => {
val.payloads_list.forEach((payload: any) => {
device.payload.push({
index: payload.index,
model: payload.model,
payload_name: payload.payload_name,
payload_sn: payload.payload_sn,
control_source: payload.control_source,
payload_index: payload.payload_index
model: payload.payload_name
})
})
if (EDeviceTypeName.Dock === gateway.domain) {
if (gateway && EDeviceTypeName.Dock === gateway.domain) {
hmsVisible.set(device.sn, false)
hmsVisible.set(device.gateway.sn, false)
onlineDocks.data.push(device)
}
if (gateway.status && EDeviceTypeName.Gateway === gateway.domain) {
if (val.status && EDeviceTypeName.Gateway === gateway.domain) {
onlineDevices.data.push(device)
}
})
@ -304,7 +312,6 @@ function switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick: @@ -304,7 +312,6 @@ function switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick:
osdVisible.value.gateway_sn = device.gateway.sn
osdVisible.value.is_dock = isDock
osdVisible.value.gateway_callsign = device.gateway.callsign
osdVisible.value.payloads = device.payload
}
store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
}
@ -344,15 +351,6 @@ function readHms (visiable: boolean, sn: string) { @@ -344,15 +351,6 @@ function readHms (visiable: boolean, sn: string) {
})
}
}
function openLivestreamOthers () {
store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)
}
function openLivestreamAgora () {
store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)
}
</script>
<style lang="scss">
@ -362,15 +360,6 @@ function openLivestreamAgora () { @@ -362,15 +360,6 @@ function openLivestreamAgora () {
align-items: center;
border-bottom: 1px solid #4f4f4f;
}
.project-tsa-wrapper {
height: 100%;
.scrollbar {
overflow: auto;
}
::-webkit-scrollbar {
display: none;
}
}
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
color: white;
border: 0;

70
src/pages/page-web/projects/wayline.vue

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
<template>
<div class="project-wayline-wrapper height-100">
<a-spin :spinning="loading" :delay="300" tip="downloading" size="large">
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
<a-col :span="15">Flight Route Library</a-col>
<a-col :span="8" v-if="importVisible" class="flex-row flex-justify-end flex-align-center">
<a-col :span="8" class="flex-row flex-justify-end flex-align-center">
<a-upload
name="file"
:multiple="false"
@ -13,15 +12,16 @@ @@ -13,15 +12,16 @@
:show-upload-list="false"
:customRequest="uploadFile"
>
<a-button type="text" style="color: white;">
<a-button type="text" style="color: white;" @click="() => importVisible = !importVisible">
<SelectOutlined />
</a-button>
</a-upload>
</a-col>
</a-row>
</div>
<div :style="{ height : height + 'px'}" class="scrollbar">
<div id="data" class="height-100 uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll">
<div class="height-100">
<a-spin :spinning="loading" :delay="300" tip="downloading" size="large">
<div class="scrollbar uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll">
<div v-for="wayline in waylinesData.data" :key="wayline.id">
<div class="wayline-panel" style="padding-top: 5px;" @click="selectRoute(wayline)">
<div class="title">
@ -52,10 +52,10 @@ @@ -52,10 +52,10 @@
</div>
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<span><RocketOutlined /></span>
<span class="ml5">{{ DEVICE_NAME[wayline.drone_model_key] }}</span>
<span class="ml5">{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(wayline.drone_model_key)] }}</span>
<span class="ml10"><CameraFilled style="border-top: 1px solid; padding-top: -3px;" /></span>
<span class="ml5" v-for="payload in wayline.payload_model_keys" :key="payload.id">
{{ DEVICE_NAME[payload] }}
{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(payload)] }}
</span>
</div>
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
@ -75,9 +75,9 @@ @@ -75,9 +75,9 @@
</div>
</template>
</a-modal>
</div>
</a-spin>
</div>
</div>
</template>
<script lang="ts" setup>
@ -85,22 +85,21 @@ import { reactive } from '@vue/reactivity' @@ -85,22 +85,21 @@ import { reactive } from '@vue/reactivity'
import { message } from 'ant-design-vue'
import { onMounted, onUpdated, ref } from 'vue'
import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline'
import { ELocalStorageKey, ERouterName } from '/@/types'
import { ELocalStorageKey } from '/@/types'
import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue'
import { DEVICE_NAME } from '/@/types/device'
import { EDeviceType } from '/@/types/device'
import { useMyStore } from '/@/store'
import { WaylineFile } from '/@/types/wayline'
import { downloadFile } from '/@/utils/common'
import { IPage } from '/@/api/http/type'
import { CURRENT_CONFIG } from '/@/api/http/config'
import { load } from '@amap/amap-jsapi-loader'
import { getRoot } from '/@/root'
const loading = ref(false)
const store = useMyStore()
const pagination :IPage = {
page: 1,
total: -1,
total: 0,
page_size: 10
}
@ -108,28 +107,37 @@ const waylinesData = reactive({ @@ -108,28 +107,37 @@ const waylinesData = reactive({
data: [] as WaylineFile[]
})
const root = getRoot()
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const deleteTip = ref(false)
const deleteWaylineId = ref<string>('')
const canRefresh = ref(true)
const importVisible = ref<boolean>(root.$router.currentRoute.value.name === ERouterName.WAYLINE)
const height = ref()
const importVisible = ref(false)
const urlUpload = `${CURRENT_CONFIG.baseURL}wayline/api/v1/workspaces/${workspaceId}/waylines/file/upload`
onMounted(() => {
const parent = document.getElementsByClassName('scrollbar').item(0)?.parentNode as HTMLDivElement
height.value = document.body.clientHeight - parent.firstElementChild!.clientHeight
getWaylines()
setTimeout(() => {
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
const parent = element?.parentNode as HTMLDivElement
console.info(element, parent)
// console.info(element.scrollHeight, parent.clientHeight)
}, 1000)
})
const key = setInterval(() => {
const data = document.getElementById('data')?.lastElementChild as HTMLDivElement
if (pagination.total === 0 || Math.ceil(pagination.total / pagination.page_size) <= pagination.page || height.value <= data?.clientHeight + data?.offsetTop) {
clearInterval(key)
return
}
onUpdated(() => {
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
const parent = element?.parentNode as HTMLDivElement
setTimeout(() => {
console.info(element, parent)
if (element?.scrollHeight < parent?.clientHeight && pagination.total > waylinesData.data.length) {
if (canRefresh.value) {
pagination.page++
getWaylines()
}, 1000)
}
} else if (element && element.className.indexOf('height-100') === -1) {
element.className = element.className + ' height-100'
}
}, 300)
})
function getWaylines () {
@ -145,7 +153,8 @@ function getWaylines () { @@ -145,7 +153,8 @@ function getWaylines () {
if (res.code !== 0) {
return
}
waylinesData.data = [...waylinesData.data, ...res.data.list]
waylinesData.data = []
res.data.list.forEach((wayline: WaylineFile) => waylinesData.data.push(wayline))
pagination.total = res.data.pagination.total
pagination.page = res.data.pagination.page
}).finally(() => {
@ -165,9 +174,7 @@ function deleteWayline () { @@ -165,9 +174,7 @@ function deleteWayline () {
}
deleteWaylineId.value = ''
deleteTip.value = false
pagination.total = 0
pagination.page = 1
waylinesData.data = []
pagination.total--
getWaylines()
})
}
@ -191,7 +198,8 @@ function selectRoute (wayline: WaylineFile) { @@ -191,7 +198,8 @@ function selectRoute (wayline: WaylineFile) {
function onScroll (e: any) {
const element = e.srcElement
if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
console.info(element)
if (element.scrollTop + element.clientHeight === element.scrollHeight && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
pagination.page++
getWaylines()
}
@ -217,6 +225,7 @@ function beforeUpload (file: FileItem) { @@ -217,6 +225,7 @@ function beforeUpload (file: FileItem) {
return true
}
const uploadFile = async () => {
console.info(loading.value)
fileList.value.forEach(async (file: FileItem) => {
const fileData = new FormData()
fileData.append('file', file, file.name)
@ -224,9 +233,6 @@ const uploadFile = async () => { @@ -224,9 +233,6 @@ const uploadFile = async () => {
if (res.code === 0) {
message.success(`${file.name} file uploaded successfully`)
canRefresh.value = true
pagination.total = 0
pagination.page = 1
waylinesData.data = []
getWaylines()
}
}).finally(() => {

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

@ -13,13 +13,14 @@ @@ -13,13 +13,14 @@
<div class="media-wrapper" v-if="root.$route.name === ERouterName.MEDIA">
<MediaPanel />
</div>
<div class="task-wrapper" v-if="root.$route.name === ERouterName.TASK">
<div class="media-wrapper" v-if="root.$route.name === ERouterName.TASK">
<TaskPanel />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Sidebar from '/@/components/common/sidebar.vue'
import MediaPanel from '/@/components/MediaPanel.vue'
import TaskPanel from '/@/components/task/TaskPanel.vue'
@ -71,10 +72,8 @@ const messageHandler = async (payload: any) => { @@ -71,10 +72,8 @@ const messageHandler = async (payload: any) => {
store.commit('SET_DEVICE_OFFLINE', payload.data)
break
}
case EBizCode.FlightTaskProgress:
case EBizCode.FlightTaskMediaProgress:
case EBizCode.FlightTaskMediaHighestPriority: {
EventBus.emit('flightTaskWs', payload)
case EBizCode.FlightTaskProgress: {
EventBus.emit('deviceTaskProgress', payload)
break
}
case EBizCode.DeviceHms: {
@ -100,27 +99,6 @@ const messageHandler = async (payload: any) => { @@ -100,27 +99,6 @@ const messageHandler = async (payload: any) => {
})
break
}
case EBizCode.ControlSourceChange:
case EBizCode.FlyToPointProgress:
case EBizCode.TakeoffToPointProgress:
case EBizCode.JoystickInvalidNotify:
case EBizCode.DrcStatusNotify:
{
EventBus.emit('droneControlWs', payload)
break
}
case EBizCode.FlightAreasSyncProgress: {
EventBus.emit('flightAreasSyncProgressWs', payload.data)
break
}
case EBizCode.FlightAreasDroneLocation: {
EventBus.emit('flightAreasDroneLocationWs', payload)
break
}
case EBizCode.FlightAreasUpdate: {
EventBus.emit('flightAreasUpdateWs', payload.data)
break
}
default:
break
}
@ -135,42 +113,42 @@ useConnectWebSocket(messageHandler) @@ -135,42 +113,42 @@ useConnectWebSocket(messageHandler)
.project-app-wrapper {
display: flex;
position: absolute;
transition: width 0.2s ease;
height: 100%;
width: 100%;
.left {
width: 400px;
display: flex;
width: 335px;
flex: 0 0 335px;
background-color: #232323;
.main-content {
flex: 1;
color: $text-white-basic;
width: 285px;
float: left;
}
}
.right {
flex-grow: 1;
position: relative;
width: 100%;
height: 100%;
.map-wrapper {
width: 100%;
height: 100%;
}
.media-wrapper,
.task-wrapper {
}
.main-content {
flex: 1;
color: $text-white-basic;
}
.media-wrapper {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
background: #f6f8fa;
}
.wayline-wrapper {
position: absolute;
top: 0;
bottom: 0;
z-index: 100;
background: #f6f8fa;
padding: 16px;
}
}
</style>

20
src/router/index.ts

@ -32,11 +32,6 @@ const routes: Array<RouteRecordRaw> = [ @@ -32,11 +32,6 @@ const routes: Array<RouteRecordRaw> = [
path: '/' + ERouterName.DEVICES,
name: ERouterName.DEVICES,
component: () => import('/@/pages/page-web/projects/devices.vue')
},
{
path: '/' + ERouterName.FIRMWARES,
name: ERouterName.FIRMWARES,
component: () => import('../pages/page-web/projects/Firmwares.vue')
}
]
},
@ -47,10 +42,6 @@ const routes: Array<RouteRecordRaw> = [ @@ -47,10 +42,6 @@ const routes: Array<RouteRecordRaw> = [
component: () => import('/@/pages/page-web/projects/workspace.vue'),
redirect: '/' + ERouterName.TSA,
children: [
{
path: '/' + ERouterName.TSA,
component: () => import('/@/pages/page-web/projects/tsa.vue')
},
{
path: '/' + ERouterName.LIVESTREAM,
name: ERouterName.LIVESTREAM,
@ -66,6 +57,10 @@ const routes: Array<RouteRecordRaw> = [ @@ -66,6 +57,10 @@ const routes: Array<RouteRecordRaw> = [
}
]
},
{
path: '/' + ERouterName.TSA,
component: () => import('/@/pages/page-web/projects/tsa.vue')
},
{
path: '/' + ERouterName.LAYER,
name: ERouterName.LAYER,
@ -103,12 +98,7 @@ const routes: Array<RouteRecordRaw> = [ @@ -103,12 +98,7 @@ const routes: Array<RouteRecordRaw> = [
}
]
},
{
path: '/' + ERouterName.FLIGHT_AREA,
name: ERouterName.FLIGHT_AREA,
component: () => import('/@/pages/page-web/projects/flight-area.vue')
},
}
]
},
// pilot

4
src/shims-mqtt.d.ts vendored

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
declare module 'mqtt/dist/mqtt.min' {
import MQTT from 'mqtt'
export = MQTT
}

52
src/store/index.ts

@ -6,7 +6,6 @@ import { getLayers } from '/@/api/layer' @@ -6,7 +6,6 @@ import { getLayers } from '/@/api/layer'
import { LayerType } from '/@/types/mapLayer'
import { WaylineFile } from '/@/types/wayline'
import { DevicesCmdExecuteInfo } from '/@/types/device-cmd'
import { FlightAreaStatus } from '../api/flight-area'
const initStateFunc = () => ({
Layers: [
@ -33,11 +32,9 @@ const initStateFunc = () => ({ @@ -33,11 +32,9 @@ const initStateFunc = () => ({
[key:string]:string
},
drawVisible: false,
livestreamOthersVisible: false,
livestreamAgoraVisible: false,
coverMap: {} as {
[key: string]: any[]
},
coverList: [
] as any,
wsEvent: {
mapElementCreat: {},
mapElementUpdate: {},
@ -68,16 +65,15 @@ const initStateFunc = () => ({ @@ -68,16 +65,15 @@ const initStateFunc = () => ({
[sn: string]: DockOsd
},
currentSn: '',
currentType: -1
currentType: ''
},
osdVisible: { // osd 显示设备相关信息
osdVisible: {
sn: '',
callsign: '',
model: '',
visible: false,
gateway_sn: '',
is_dock: false,
payloads: null
} as OSDVisible,
waylineInfo: {
@ -90,9 +86,7 @@ const initStateFunc = () => ({ @@ -90,9 +86,7 @@ const initStateFunc = () => ({
},
// 机场指令执行状态信息
devicesCmdExecuteInfo: {
} as DevicesCmdExecuteInfo,
mqttState: null as any, // mqtt 实例
clientId: '', // mqtt 连接 唯一客户端id
} as DevicesCmdExecuteInfo
})
export type RootStateType = ReturnType<typeof initStateFunc>
@ -118,32 +112,26 @@ const mutations: MutationTree<RootStateType> = { @@ -118,32 +112,26 @@ const mutations: MutationTree<RootStateType> = {
return
}
if (!state.deviceState.dockInfo[info.sn]) {
state.deviceState.dockInfo[info.sn] = { } as DockOsd
state.deviceState.dockInfo[info.sn] = info.host
return
}
state.deviceState.currentSn = info.sn
state.deviceState.currentType = EDeviceTypeName.Dock
const dock = state.deviceState.dockInfo[info.sn]
if (info.host.mode_code !== undefined) {
dock.basic_osd = info.host
if (info.host.sdr) {
dock.sdr = info.host.sdr
dock.media_file_detail = info.host.media_file_detail
return
}
if (info.host.wireless_link) {
dock.link_osd = info.host
return
}
if (info.host.job_number !== undefined) {
dock.work_osd = info.host
}
const sdr = dock.sdr
const mediaFileDetail = dock.media_file_detail
state.deviceState.dockInfo[info.sn] = info.host
state.deviceState.dockInfo[info.sn].sdr = sdr
state.deviceState.dockInfo[info.sn].media_file_detail = mediaFileDetail
},
SET_DRAW_VISIBLE_INFO (state, bool) {
state.drawVisible = bool
},
SET_LIVESTREAM_OTHERS_VISIBLE (state, bool) {
state.livestreamOthersVisible = bool
},
SET_LIVESTREAM_AGORA_VISIBLE (state, bool) {
state.livestreamAgoraVisible = bool
},
SET_MAP_ELEMENT_CREATE (state, info) {
state.wsEvent.mapElementCreat = info
},
@ -196,13 +184,7 @@ const mutations: MutationTree<RootStateType> = { @@ -196,13 +184,7 @@ const mutations: MutationTree<RootStateType> = {
} else {
state.devicesCmdExecuteInfo[info.sn] = [info]
}
},
SET_MQTT_STATE (state, mqttState) {
state.mqttState = mqttState
},
SET_CLIENT_ID (state, clientId) {
state.clientId = clientId
},
}
}
const actions: ActionTree<RootStateType, RootStateType> = {

42
src/types/airport-tsa.ts

@ -36,45 +36,3 @@ export enum SupplementLightStateEnum { @@ -36,45 +36,3 @@ export enum SupplementLightStateEnum {
Close = 0, // 关闭
Open = 1, // 打开
}
// 机场声光报警状态
export enum AlarmModeEnum {
CLOSE = 0, // 关闭
OPEN = 1, // 开启
}
// 电池保养
export enum BatteryStoreModeEnum {
BATTERY_PLAN_STORE = 1, // 电池计划存储策略
BATTERY_EMERGENCY_STORE = 2, // 电池应急存储策略
}
// 飞行器电池保养
export enum DroneBatteryStateEnum {
NoMaintenanceRequired = 0, // 0-无需保养
MaintenanceRequired = 1, // 1-待保养
MaintenanceInProgress = 2, // 2-正在保养
}
export enum DroneBatteryModeEnum {
CLOSE = 0, // 关闭
OPEN = 1, // 开启
}
// 4g链路连接状态
export enum FourGLinkStateEnum {
CLOSE = 0, // 断开
OPEN = 1, // 连接
}
// Sdr链路连接状态
export enum SdrLinkStateEnum {
CLOSE = 0, // 断开
OPEN = 1, // 连接
}
// 机场的图传链路模式
export enum LinkWorkModeEnum {
SDR = 0, // sdr模式
FourG_FUSION_MODE = 1, // 4G融合模式
}

112
src/types/device-cmd.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum, LinkWorkModeEnum } from '/@/types/airport-tsa'
// 机场指令集
export enum DeviceCmd {
// 简单指令
@ -7,7 +6,6 @@ export enum DeviceCmd { @@ -7,7 +6,6 @@ export enum DeviceCmd {
SupplementLightOpen = 'supplement_light_open', // 打开补光灯
SupplementLightClose = 'supplement_light_close', // 关闭补光灯
ReturnHome = 'return_home', // 一键返航
ReturnHomeCancel = 'return_home_cancel', // 取消返航
// 复杂指令
DeviceReboot = 'device_reboot', // 机场重启
DroneOpen = 'drone_open', // 飞行器开机
@ -21,43 +19,17 @@ export enum DeviceCmd { @@ -21,43 +19,17 @@ export enum DeviceCmd {
PutterClose = 'putter_close', // 推杆闭合
ChargeOpen = 'charge_open', // 打开充电
ChargeClose = 'charge_close', // 关闭充电
AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警
BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养
DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养
SdrWorkModeSwitch = 'sdr_workmode_switch', // 增强图传
}
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum | LinkWorkModeEnum
export interface DeviceCmdItem{
label: string, // 标题
status: string, // 当前状态
operateText: string, // 按钮文字
cmdKey: DeviceCmd, // 请求指令
oppositeCmdKey?: DeviceCmd, // 相反状态指令
action?: DeviceCmdItemAction, // 参数
func: string, // 处理函数
loading: boolean // 按钮loading
disabled?: boolean // 按钮disabled
}
export const noDebugCmdList: DeviceCmdItem[] = [
{
label: 'Return Home',
status: '--',
operateText: 'Return Home',
cmdKey: DeviceCmd.ReturnHome,
func: 'returnHome',
loading: false,
},
{
label: 'Return Home Cancel',
status: '--',
operateText: 'Return Home Cancel',
cmdKey: DeviceCmd.ReturnHomeCancel,
func: 'returnHomeCancel',
loading: false,
}
]
// 机场指令
export const cmdList: DeviceCmdItem[] = [
@ -109,6 +81,14 @@ export const cmdList: DeviceCmdItem[] = [ @@ -109,6 +81,14 @@ export const cmdList: DeviceCmdItem[] = [
func: 'chargeStatus',
loading: false,
},
{
label: '一键返航',
status: '--',
operateText: '返航',
cmdKey: DeviceCmd.ReturnHome,
func: 'returnHome',
loading: false,
},
{
label: '机场存储',
status: '--',
@ -134,43 +114,6 @@ export const cmdList: DeviceCmdItem[] = [ @@ -134,43 +114,6 @@ export const cmdList: DeviceCmdItem[] = [
func: 'supplementLightStatus',
loading: false,
},
{
label: '机场声光报警',
status: '关',
operateText: '打开',
cmdKey: DeviceCmd.AlarmStateSwitch,
action: AlarmModeEnum.OPEN,
func: 'alarmState',
loading: false,
},
{
label: '机场电池存储模式',
status: '计划',
operateText: '应急',
cmdKey: DeviceCmd.BatteryStoreModeSwitch,
action: BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE,
func: 'batteryStoreMode',
loading: false,
},
{
label: '飞机电池保养',
status: '--',
operateText: '保养',
cmdKey: DeviceCmd.DroneBatteryModeSwitch,
action: DroneBatteryModeEnum.OPEN,
func: 'droneBatteryMode',
loading: false,
disabled: true,
},
{
label: '4g 增强',
status: '--',
operateText: '开启',
cmdKey: DeviceCmd.SdrWorkModeSwitch,
action: LinkWorkModeEnum.FourG_FUSION_MODE,
func: 'sdrWorkMode',
loading: false,
},
]
export enum DeviceCmdStatusText {
@ -233,42 +176,6 @@ export enum DeviceCmdStatusText { @@ -233,42 +176,6 @@ export enum DeviceCmdStatusText {
DeviceSupplementLightCloseText = '关闭中...',
DeviceSupplementLightCloseFailedText = '开',
DeviceSupplementLightCloseBtnText = '打开',
AlarmStateOpenNormalText = '开',
AlarmStateOpenText = '开启中...',
AlarmStateOpenFailedText = '关',
AlarmStateOpenBtnText = '关闭',
AlarmStateCloseNormalText = '关',
AlarmStateCloseText = '关闭中...',
AlarmStateCloseFailedText = '开',
AlarmStateCloseBtnText = '打开',
BatteryStoreModePlanNormalText = '计划',
BatteryStoreModePlanText = '切换中...',
BatteryStoreModePlanFailedText = '应急',
BatteryStoreModePlanBtnText = '应急',
BatteryStoreModeEmergencyNormalText = '应急',
BatteryStoreModeEmergencyText = '切换中...',
BatteryStoreModeEmergencyFailedText = '计划',
BatteryStoreModeEmergencyBtnText = '计划',
DroneBatteryModeMaintenanceInProgressText = '保养中',
DroneBatteryModeMaintenanceNotNeedText = '无需保养',
DroneBatteryModeMaintenanceNeedText = '需保养',
DroneBatteryModeOpenBtnText = '保养',
DroneBatteryModeCloseBtnText = '关闭保养',
SdrWorkModeFourGOpenNormalText = '开',
SdrWorkModeFourGOpenText = '开启中...',
SdrWorkModeFourGOpenFailedText = '--',
SdrWorkModeFourGOpenBtnText = '关闭',
SdrWorkModeFourGCloseNormalText = '--',
SdrWorkModeFourGCloseText = '关闭中...',
SdrWorkModeFourGCloseFailedText = '开',
SdrWorkModeFourCloseBtnText = '开启',
}
// cmd ws 消息状态
@ -292,9 +199,6 @@ export interface DeviceCmdExecuteInfo { @@ -292,9 +199,6 @@ export interface DeviceCmdExecuteInfo {
percent: number,
step_key: string,
step_result: number
},
ext?: {
rate?: number
}
}
result: number,

38
src/types/device-firmware.ts

@ -1,38 +0,0 @@ @@ -1,38 +0,0 @@
export interface Firmware {
firmware_id: string
file_name: string
product_version: string
file_size: number
device_name: string[]
username: string
release_note: string
released_time: string
firmware_status: boolean
}
export enum FirmwareStatusEnum {
NONE = 'All',
FALSE = 'Disabled',
TRUE = 'Available'
}
export interface FirmwareQueryParam {
product_version: string
device_name: string
firmware_status: FirmwareStatusEnum
}
export interface FirmwareUploadParam {
device_name: string[]
release_note: string
status: boolean
}
export enum DeviceNameEnum {
DJI_DOCK = 'DJI Dock',
DJI_DOCK2 = 'DJI Dock2',
MATRICE_30 = 'Matrice 30',
MATRICE_30T = 'Matrice 30T',
M3D = 'M3D',
M3TD = 'M3TD',
}

272
src/types/device.ts

@ -1,8 +1,5 @@ @@ -1,8 +1,5 @@
import { commonColor } from '/@/utils/color'
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum, FourGLinkStateEnum, SdrLinkStateEnum, LinkWorkModeEnum } from './airport-tsa'
import { CameraMode } from '/@/types/live-stream'
export interface DeviceValue {
key: string; // 'domain-type-subtype'
domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种
@ -22,9 +19,11 @@ export enum DOMAIN { @@ -22,9 +19,11 @@ export enum DOMAIN {
export enum DRONE_TYPE {
M30 = 67,
M300 = 60,
Phantom4 = 11,
Phantom4Pro = 18,
Phantom4RTK = 59,
Phantom4Advanced = 27,
Mavic3EnterpriseAdvanced= 77,
M350 = 89,
M3D = 91,
}
// DJI负载类型枚举值
@ -44,8 +43,6 @@ export enum PAYLOAD_TYPE { @@ -44,8 +43,6 @@ export enum PAYLOAD_TYPE {
M3E = 66,
M3T = 67,
M3D = 80,
M3TD = 81,
// UNKNOWN = 65535
}
@ -59,7 +56,6 @@ export enum RC_TYPE { @@ -59,7 +56,6 @@ export enum RC_TYPE {
// DOCK type
export enum DOCK_TYPE {
Dock = 1,
Dock2 = 2,
}
// 设备sub_type 从0升序
@ -79,10 +75,10 @@ export const DEVICE_MODEL_KEY = { @@ -79,10 +75,10 @@ export const DEVICE_MODEL_KEY = {
M3T: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ONE}`,
M300: `${DOMAIN.DRONE}-${DRONE_TYPE.M300}-${DEVICE_SUB_TYPE.ZERO}`,
M350: `${DOMAIN.DRONE}-${DRONE_TYPE.M350}-${DEVICE_SUB_TYPE.ZERO}`,
M3D: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,
M3TD: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ONE}`,
Phantom4: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4}-${DEVICE_SUB_TYPE.ZERO}`,
Phantom4Pro: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4Pro}-${DEVICE_SUB_TYPE.ZERO}`,
Phantom4RTK: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4RTK}-${DEVICE_SUB_TYPE.ZERO}`,
Phantom4Advanced: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4Advanced}-${DEVICE_SUB_TYPE.ZERO}`,
FPV: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.FPV}-${DEVICE_SUB_TYPE.ZERO}`,
H20: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20}-${DEVICE_SUB_TYPE.ZERO}`,
@ -95,8 +91,6 @@ export const DEVICE_MODEL_KEY = { @@ -95,8 +91,6 @@ export const DEVICE_MODEL_KEY = {
M3ECamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3E}-${DEVICE_SUB_TYPE.ZERO}`,
M3TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3T}-${DEVICE_SUB_TYPE.ZERO}`,
M3DCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,
M3TDCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3TD}-${DEVICE_SUB_TYPE.ZERO}`,
// M3MCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3M}-${DEVICE_SUB_TYPE.ZERO}`,
XT2: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XT2}-${DEVICE_SUB_TYPE.ZERO}`,
@ -108,7 +102,6 @@ export const DEVICE_MODEL_KEY = { @@ -108,7 +102,6 @@ export const DEVICE_MODEL_KEY = {
RCPlus: `${DOMAIN.RC}-${RC_TYPE.RCPlus}-${DEVICE_SUB_TYPE.ZERO}`,
Dock: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock}-${DEVICE_SUB_TYPE.ZERO}`,
Dock2: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock2}-${DEVICE_SUB_TYPE.ZERO}`,
}
export const DEVICE_NAME = {
@ -119,9 +112,10 @@ export const DEVICE_NAME = { @@ -119,9 +112,10 @@ export const DEVICE_NAME = {
[DEVICE_MODEL_KEY.M3T]: 'Mavic 3T',
// [DEVICE_MODEL_KEY.M3M]: 'Mavic 3M',
[DEVICE_MODEL_KEY.M300]: 'M300 RTK',
[DEVICE_MODEL_KEY.M350]: 'M350 RTK',
[DEVICE_MODEL_KEY.M3D]: 'M3D',
[DEVICE_MODEL_KEY.M3TD]: 'M3TD',
[DEVICE_MODEL_KEY.Phantom4]: 'Phantom 4',
[DEVICE_MODEL_KEY.Phantom4Pro]: 'Phantom 4 Pro',
[DEVICE_MODEL_KEY.Phantom4RTK]: 'Phantom 4 RTK',
[DEVICE_MODEL_KEY.Phantom4Advanced]: 'Phantom 4 Advanced',
// payload
[DEVICE_MODEL_KEY.FPV]: 'FPV',
@ -139,8 +133,6 @@ export const DEVICE_NAME = { @@ -139,8 +133,6 @@ export const DEVICE_NAME = {
[DEVICE_MODEL_KEY.XTS]: 'XTS',
[DEVICE_MODEL_KEY.Z30]: 'Z30',
[DEVICE_MODEL_KEY.DockTopCamera]: 'Dock Camera',
[DEVICE_MODEL_KEY.M3DCamera]: 'M3D Camera',
[DEVICE_MODEL_KEY.M3TDCamera]: 'M3TD Camera',
// rc
[DEVICE_MODEL_KEY.RC]: 'RC',
@ -148,37 +140,6 @@ export const DEVICE_NAME = { @@ -148,37 +140,6 @@ export const DEVICE_NAME = {
// dock
[DEVICE_MODEL_KEY.Dock]: 'Dock',
[DEVICE_MODEL_KEY.Dock2]: 'Dock2',
}
// 控制权
export enum ControlSource {
A = 'A',
B = 'B'
}
export interface PayloadInfo {
index: number,
model: string,
control_source?: ControlSource,
payload_sn?: string,
payload_index?: string,
payload_name?: string,
}
// 设备信息
export interface OnlineDevice {
model: string,
callsign: string,
sn: string,
mode: number,
gateway: {
model: string,
callsign: string,
sn: string,
domain: string,
},
payload: PayloadInfo[]
}
// 固件升级类型
@ -220,9 +181,8 @@ export interface Device { @@ -220,9 +181,8 @@ export interface Device {
bound_time: string,
login_time: string,
children?: Device[],
domain: number,
type: number,
firmware_progress?: string, // 升级进度
domain: string,
firmware_progress?: number, // 升级进度
}
export interface DeviceStatus {
@ -234,7 +194,7 @@ export interface DeviceStatus { @@ -234,7 +194,7 @@ export interface DeviceStatus {
bound_status: boolean,
model: string,
gateway_sn: string,
domain: number
domain: string
}
export interface OSDVisible {
@ -245,7 +205,6 @@ export interface OSDVisible { @@ -245,7 +205,6 @@ export interface OSDVisible {
is_dock: boolean,
gateway_sn: string,
gateway_callsign: string,
payloads: null | PayloadInfo [],
}
export interface GatewayOsd {
@ -254,24 +213,6 @@ export interface GatewayOsd { @@ -254,24 +213,6 @@ export interface GatewayOsd {
longitude: number,
latitude: number,
}
export interface OsdCameraLiveview {
bottom: number,
left: number,
right: number,
top: number,
}
export interface DeviceOsdCamera {
camera_mode: CameraMode,
payload_index: string,
photo_state: number,
record_time: number,
recording_state: number,
remain_photo_num: number,
remain_record_duration: number,
liveview_world_region: OsdCameraLiveview
}
export interface DeviceOsd {
longitude: number,
latitude: number,
@ -299,155 +240,64 @@ export interface DeviceOsd { @@ -299,155 +240,64 @@ export interface DeviceOsd {
height_limit?: number;// 限高设置
distance_limit_status?: DistanceLimitStatus;// 限远开关
obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
cameras?: DeviceOsdCamera[]
}
export enum NetworkStateTypeEnum {
FOUR_G = 1,
ETHERNET = 2,
}
export enum NetworkStateQualityEnum {
NO_SIGNAL = 0,
BAD = 1,
POOR = 2,
FAIR = 3,
GOOD = 4,
EXCELLENT = 5,
}
export enum RainfallEnum {
NONE = 0,
LIGHT_RAIN = 1,
MODERATE_RAIN = 2,
HEAVY_RAIN = 3,
}
export enum DroneInDockEnum {
OUTSIDE, INSIDE
}
export interface DockBasicOsd {
network_state?: {
type: NetworkStateTypeEnum,
export interface DockOsd {
media_file_detail: {
remain_upload: number
},
sdr: {
up_quality: string,
down_quality: string,
frequency_band: number,
},
network_state: {
type: number,
quality: number,
rate: number,
},
drone_charge_state?: {
drone_in_dock: number,
drone_charge_state: {
state: number,
capacity_percent: number,
capacity_percent: string,
},
drone_in_dock: boolean,
rainfall: RainfallEnum,
wind_speed: number,
environment_temperature: number,
temperature: number,
humidity: number,
latitude: number,
longitude: number,
height: number,
alternate_land_point?: {
rainfall: string,
wind_speed: string,
environment_temperature: string,
environment_humidity: string
temperature: string,
humidity: string,
latitude: number,
longitude: number,
height: number,
safe_land_height: number,
is_configured: number
}
job_number: number,
acc_time: number,
first_power_on: number,
positionState?: {
gps_number: number,
positionState: {
gps_number: string,
is_fixed: number,
rtk_number: number,
rtk_number: string,
is_calibration: number,
quality: number,
},
storage?: {
storage: {
total: number,
used: number,
},
electric_supply_voltage: number,
working_voltage: string,
working_current: string,
backup_battery_voltage: number,
mode_code: number,
cover_state: number,
supplement_light_state: number,
emergency_stop_state: number,
air_conditioner?: {
air_conditioner_state: number,
switch_time: number,
}
battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式
alarm_state?: AlarmModeEnum; // 机场声光报警状态
putter_state: number,
sub_device?: {
device_sn?: string,
device_model_key?: string,
sub_device: {
device_sn: string,
device_model_key: string,
device_online_status: number,
device_paired: number,
},
// live_capacity?: LiveCapacity; // 直播能力
// live_status?: Array<LiveStatus>; // 直播状态
}
export enum DrcStateEnum {
DISCONNECT = 0,
CONNECTING = 1,
CONNECTED = 2
}
export interface DockLinkOsd {
drc_state: DrcStateEnum,
flighttask_prepare_capacity: number,
flighttask_step_code: number,
media_file_detail?: {
remain_upload: number
},
sdr?: {
up_quality: string,
down_quality: string,
frequency_band: number,
},
wireless_link?:{ // 图传链路<会包括4G和sdr信息
dongle_number: number, // dongle 数量
['4g_link_state']: FourGLinkStateEnum, // 4g_link_state
sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态
link_workmode: LinkWorkModeEnum, // 图传链路模式
sdr_quality: number, // sdr信号质量 0-5
['4g_quality']: number, // 4G信号质量 0-5
['4g_freq_band']: number,
['4g_gnd_quality']: number,
['4g_uav_quality']: number,
sdr_freq_band: number,
}
}
export interface MaintainStatus {
state: number,
last_maintain_type: number,
last_maintain_time: number,
last_maintain_work_sorties: number,
}
export interface DockWorkOsd {
job_number: number,
acc_time: number,
activation_time: number,
maintain_status?: {
maintain_status_array: MaintainStatus[]
}
electric_supply_voltage: number,
working_voltage: string,
working_current: string,
backup_battery?: {
voltage: number,
temperature: number,
switch: number,
}
drone_battery_maintenance_info?: { // 飞行器电池保养信息
maintenance_state: DroneBatteryStateEnum, // 保养状态
maintenance_time_left: number, // 电池保养剩余时间(小时)
}
}
export interface DockOsd {
basic_osd: DockBasicOsd,
link_osd: DockLinkOsd,
work_osd: DockWorkOsd
}
export enum EModeCode {
@ -481,6 +331,28 @@ export enum EGear { @@ -481,6 +331,28 @@ export enum EGear {
T
}
export enum EDeviceType {
M30 = '0-67-0' as any,
M30T = '0-67-1' as any,
M300 = '0-60-0' as any,
Z30 = '1-20-0' as any,
XT2 = '1-26-0' as any,
FPV = '1-39-0' as any,
XTS = '1-41-0' as any,
H20 = '1-42-0' as any,
H20T = '1-43-0' as any,
P1 = '1-50-65535' as any,
M30_Camera = '1-52-0' as any,
M30T_Camera = '1-53-0' as any,
H20N = '1-61-0' as any,
DJI_Dock_Camera = '1-165-0' as any,
L1 = '1-90742-0' as any,
M3E = '0-77-0' as any,
M3D = '0-77-1' as any,
M3E_Camera = '1-66-0' as any,
M3T_Camera = '1-67-0' as any,
}
export enum EDockModeCode {
Disconnected = -1,
Idle,
@ -502,11 +374,11 @@ export interface DeviceHms { @@ -502,11 +374,11 @@ export interface DeviceHms {
message_zh: string,
create_time: string,
update_time: string,
domain: number
domain: string
}
// TODO: 设备拓扑管理优化
// 设备osd信息
// 设备信息
export interface DeviceInfoType {
gateway: GatewayOsd, // 遥控器
dock: DockOsd, // 机场

72
src/types/drc.ts

@ -1,72 +0,0 @@ @@ -1,72 +0,0 @@
export enum DRC_METHOD {
HEART_BEAT = 'heart_beat',
DRONE_CONTROL = 'drone_control', // 飞行控制-虚拟摇杆
DRONE_EMERGENCY_STOP = 'drone_emergency_stop', // 急停
OSD_INFO_PUSH = 'osd_info_push', // 高频osd信息上报
HSI_INFO_PUSH = 'hsi_info_push', // 避障信息上报
DELAY_TIME_INFO_PUSH = 'delay_info_push', // 图传链路延时信息上报
}
// 手动控制
export interface DroneControlProtocol {
x?: number; // 水平方向速度,正值为A指令 负值为D指令 单位:m/s
y?: number; // 前进后退方向速度,正值为W指令 负值为S指令 单位:m/s
h?: number;// 上下高度值,正值为上升指令 负值为下降指令 单位:m
w?: number; // 机头角速度,正值为顺时针,负值为逆时针 单位:degree/s (web端暂无此设计)
seq?: number; // 从0计时
}
// 低延时osd
export interface DRCOsdInfo {
attitude_head: number;// 飞机姿态head角,单位:度
latitude: number;// 飞机经纬度
longitude: number;
altitude: number;
speed_x: number;
speed_y: number;
speed_z: number;
gimbal_pitch: number;// 云台pitch角
gimbal_roll: number;// 云台roll角
gimbal_yaw: number;// 云台yaw角
}
// 态势感知-HSI
export interface DRCHsiInfo {
up_distance: number;// 上方的障碍物距离,单位:mm
down_distance: number;// 下方的障碍物距离,单位:mm
around_distances: number[]; // 水平方向观察点,分布在[0,360)区间,表示障碍物与飞机距离,单位为mm。 0对应机头方向正前方,顺时针分布,例如0度为机头正前方,90度为飞机正右方
up_enable: boolean; // 上视避障开关状态,true:已开启 false:已关闭
up_work: boolean; // 上视避障工作状态,true:正常工作 false:异常或离线
down_enable: boolean; // 下视避障开关状态,true:已开启 false:已关闭
down_work: boolean; // 下视避障工作状态,true:正常工作 false:异常或离线
left_enable: boolean; // 左视避障开关状态,true:已开启 false:已关闭
left_work: boolean; // 左视避障工作状态,true:正常工作 false:异常或离线
right_enable: boolean; // 右视避障开关状态,true:已开启 false:已关闭
right_work: boolean; // 右视避障工作状态,true:正常工作 false:异常或离线
front_enable: boolean; // 前视避障开关状态,true:已开启 false:已关闭
front_work: boolean; // 前视避障工作状态,true:正常工作 false:异常或离线
back_enable: boolean; // 后视避障开关状态,true:已开启 false:已关闭
back_work: boolean; // 后视避障工作状态,true:正常工作 false:异常或离线
vertical_enable: boolean; // 垂直方向综合开关状态,当本协议中上、下视开关状态均为true时,输出true:已开启,否则输出false:已关闭
vertical_work: boolean; // 垂直方向避障工作状态,当本协议中上、下视工作均为true时,输出true:正常工作,否则输出false:异常或离线
horizontal_enable: boolean; // 水平方向综合开关状态,当本协议中前、后、左、右、开关状态均为true时,输出true:已开启,否则输出false:已关闭
horizontal_work: boolean; // 水平方向避障工作综合状态,当本协议中前、后、左、右视工作均为true时,输出true:正常工作,否则输出false:异常或离线
}
export interface LiveViewDelayItem {
video_id: string;
liveview_delay_time: number;
}
// 链路时延信息
export interface DRCDelayTimeInfo {
sdr_cmd_delay: number; // sdr链路命令延时,单位:ms
liveview_delay_list: LiveViewDelayItem[];
}
export interface DrcResponseInfo {
result: number;
output: {
seq: number
}
}

83
src/types/drone-control.ts

@ -1,83 +0,0 @@ @@ -1,83 +0,0 @@
import { ControlSource } from './device'
import { ECommanderModeLostAction, ERthMode, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight } from '/@/api/drone-control/drone'
export enum ControlSourceChangeType {
Flight = 1,
Payload = 2,
}
// 控制权变化消息
export interface ControlSourceChangeInfo {
sn: string,
type: ControlSourceChangeType,
control_source: ControlSource
}
// 飞向目标点结果
export interface FlyToPointMessage {
sn: string,
result: number,
message: string,
}
// 一键起飞结果
export interface TakeoffToPointMessage {
sn: string,
result: number,
message: string,
}
// 设备端退出drc模式
export interface DrcModeExitNotifyMessage {
sn: string,
result: number,
message: string,
}
// 飞行控制模式状态
export interface DrcStatusNotifyMessage {
sn: string,
result: number,
message: string,
}
export const WaylineLostControlActionInCommandFlightOptions = [
{ label: 'Continue', value: WaylineLostControlActionInCommandFlight.CONTINUE },
{ label: 'Execute Lost Action', value: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION }
]
export const LostControlActionInCommandFLightOptions = [
{ label: 'Return Home', value: LostControlActionInCommandFLight.RETURN_HOME },
{ label: 'Hover', value: LostControlActionInCommandFLight.HOVER },
{ label: 'Landing', value: LostControlActionInCommandFLight.Land }
]
export const RthModeInCommandFlightOptions = [
{ label: 'Smart Height', value: ERthMode.SMART },
{ label: 'Setting Height', value: ERthMode.SETTING }
]
export const CommanderModeLostActionInCommandFlightOptions = [
{ label: 'Continue', value: ECommanderModeLostAction.CONTINUE },
{ label: 'Execute Lost Action', value: ECommanderModeLostAction.EXEC_LOST_ACTION }
]
export const CommanderFlightModeInCommandFlightOptions = [
{ label: 'Smart Height', value: ERthMode.SMART },
{ label: 'Setting Height', value: ERthMode.SETTING }
]
// 云台重置模式
export enum GimbalResetMode {
Recenter = 0,
Down = 1,
RecenterGimbalPan = 2,
PitchDown = 3,
}
export const GimbalResetModeOptions = [
{ label: 'Gimbal Recenter', value: GimbalResetMode.Recenter },
{ label: 'Gimbal down', value: GimbalResetMode.Down },
{ label: 'Recenter Gimbal Pan', value: GimbalResetMode.RecenterGimbalPan },
{ label: 'Gimbal Pitch Down', value: GimbalResetMode.PitchDown }
]

28
src/types/enums.ts

@ -14,8 +14,6 @@ export enum ERouterName { @@ -14,8 +14,6 @@ export enum ERouterName {
TASK = 'task',
CREATE_PLAN = 'create-plan',
SELECT_PLAN = 'select-plan',
FIRMWARES = 'firmwares',
FLIGHT_AREA = 'flight-area',
PILOT = 'pilot-login',
PILOT_HOME = 'pilot-home',
@ -92,12 +90,8 @@ export enum EBizCode { @@ -92,12 +90,8 @@ export enum EBizCode {
MapElementDelete = 'map_element_delete',
DeviceOnline = 'device_online',
DeviceOffline = 'device_offline',
DeviceHms = 'device_hms',
// 机场任务
FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度
FlightTaskMediaProgress = 'file_upload_callback', // 机场任务媒体上传进度
FlightTaskMediaHighestPriority = 'highest_priority_upload_flighttask_media', // 机场任务媒体优先级上报
DeviceHms = 'device_hms',
// 设备指令
DeviceReboot = 'device_reboot', // 机场重启
@ -116,25 +110,13 @@ export enum EBizCode { @@ -116,25 +110,13 @@ export enum EBizCode {
DeviceUpgrade = 'ota_progress', // 设备升级
// 设备日志
DeviceLogUploadProgress = 'fileupload_progress', // 设备日志上传
// 飞行指令消息
ControlSourceChange = 'control_source_change', // 控制权更新
FlyToPointProgress = 'fly_to_point_progress', // 飞向目标点
TakeoffToPointProgress = 'takeoff_to_point_progress', // 一键起飞
JoystickInvalidNotify = 'joystick_invalid_notify', // 设备端退出drc模式
DrcStatusNotify = 'drc_status_notify', // 飞行控制模式状态
// custom flight area
FlightAreasSyncProgress = 'flight_areas_sync_progress',
FlightAreasDroneLocation = 'flight_areas_drone_location',
FlightAreasUpdate = 'flight_areas_update',
DeviceLogUploadProgress = 'fileupload_progress' // 设备日志上传上传
}
export enum EDeviceTypeName {
Aircraft = 0,
Gateway = 2,
Dock = 3,
Aircraft = 'sub-device',
Gateway = 'gateway',
Dock = 'dock',
}
export enum EHmsLevel {

80
src/types/flight-area.ts

@ -1,80 +0,0 @@ @@ -1,80 +0,0 @@
import { GeojsonCoordinate, GeojsonPolygon } from '../utils/genjson'
export enum EFlightAreaType {
NFZ = 'nfz',
DFENCE = 'dfence',
}
export enum EGeometryType {
CIRCLE = 'Circle',
POLYGON = 'Polygon',
}
export enum EFlightAreaUpdate {
ADD = 'add',
UPDATE = 'update',
DELETE = 'delete',
}
export enum ESyncStatus {
WAIT_SYNC = 'wait_sync',
SWITCH_FAIL = 'switch_fail',
SYNCHRONIZING = 'synchronizing',
SYNCHRONIZED = 'synchronized',
FAIL = 'fail',
}
export interface GeojsonCircle {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: EGeometryType.CIRCLE
coordinates: GeojsonCoordinate
radius: number
}
}
export interface DroneLocation {
area_distance: number,
area_id: string,
is_in_area: boolean,
}
export interface FlightAreasDroneLocation {
drone_locations: DroneLocation[]
}
export type FlightAreaContent = GeojsonCircle | GeojsonPolygon
export interface FlightAreaUpdate {
operation: EFlightAreaUpdate,
area_id: string,
name: string,
type: EFlightAreaType,
content: FlightAreaContent,
status: boolean,
username: string,
create_time: number,
update_time: number,
}
export interface FlightAreaSyncProgress {
sn: string,
result: number,
status: ESyncStatus,
message: string,
}
export const FlightAreaTypeTitleMap = {
[EFlightAreaType.NFZ]: {
[EGeometryType.CIRCLE]: 'Circular GEO Zone',
[EGeometryType.POLYGON]: 'Polygonal GEO Zone',
},
[EFlightAreaType.DFENCE]: {
[EGeometryType.CIRCLE]: 'Circular Task Area',
[EGeometryType.POLYGON]: 'Polygonal Task Area',
},
}

71
src/types/live-stream.ts

@ -53,74 +53,3 @@ export enum ELiveTypeName { @@ -53,74 +53,3 @@ export enum ELiveTypeName {
RTSP = 'RTSP',
GB28181 = 'GB28181'
}
export enum CameraMode {
Photo = 0, // 拍照
Video = 1, // 录像
}
// 镜头类型
export enum VideoType {
NORMAL = 'normal',
WIDE = 'wide',
ZOOM = 'zoom',
IR = 'ir'
}
// 镜头类型
export enum CameraType {
WIDE = 'wide',
ZOOM = 'zoom',
IR = 'ir'
}
export const CameraTypeOptions = [
{ label: CameraType.WIDE, value: CameraType.WIDE },
{ label: CameraType.ZOOM, value: CameraType.ZOOM },
{ label: CameraType.IR, value: CameraType.IR },
]
export const ZoomCameraTypeOptions = [
{ label: CameraType.ZOOM, value: CameraType.ZOOM },
{ label: CameraType.IR, value: CameraType.IR },
]
export interface VideoListItem {
video_index: string;
video_type: VideoType;
switchable_video_types?: Array<VideoType>;
}
export interface CameraListItem {
available_video_number: number;
camera_index: string;
camera_name: string;
coexist_video_number_max: number;
video_list: VideoListItem[];
// 自定义
switchCamera?: boolean;
content?: string;
// 该camera由哪个控上报的
camera_carrier_sns?: string[];
}
export interface DeviceListItem {
sn: string;
available_video_number: number;
coexist_video_number_max: number;
camera_list: CameraListItem[];
}
// export interface LiveCapacity {
// available_video_number: number;
// coexist_video_number_max: number;
// device_list: DeviceListItem[];
// }
// export interface LiveStatus {
// live_time: number; // 直播时间 该路码流已推流时间 unit: s
// live_trendline: number; // 直播带宽的使用状态 代表直播性能趋势,0-4表示overuse,其中,数值越小,表示overuse程度越大,5表示normal状态,6~10表示underuse,其中,数值越大,表示有更多比例的带宽未能充分利用
// video_id: string; // 直播码流标识符 某路在推视频码流的标识符,格式为 #{uav_sn}/#{camera_id}/#{video_index}
// video_quality: number; // 直播码流的质量 0: 自动, 1: 流畅, 2: 高清, 3: 超清
// error_status?: number; // 设备端当前状态,是错误码,需要匹配到文案上
// }

3
src/types/map-enum.ts

@ -2,6 +2,5 @@ export enum MapDoodleEnum { @@ -2,6 +2,5 @@ export enum MapDoodleEnum {
PIN = 'pin',
POLYLINE = 'polyline',
POLYGON = 'polygon',
Close = 'off',
CIRCLE = 'circle',
Close = 'off'
}

72
src/types/task.ts

@ -3,41 +3,14 @@ import { commonColor } from '/@/utils/color' @@ -3,41 +3,14 @@ import { commonColor } from '/@/utils/color'
// 任务类型
export enum TaskType {
Immediate = 0, // 立即执行
Timed = 1, // 单次定时任务
Condition = 2,
Single = 1, // 单次定时任务
}
export const TaskTypeMap = {
[TaskType.Immediate]: 'Immediate',
[TaskType.Timed]: 'Timed',
[TaskType.Condition]: 'Continuous',
[TaskType.Single]: 'Timed & One-Time',
}
export const TaskTypeOptions = [
{ value: TaskType.Immediate, label: TaskTypeMap[TaskType.Immediate] },
{ value: TaskType.Timed, label: TaskTypeMap[TaskType.Timed] },
{ value: TaskType.Condition, label: TaskTypeMap[TaskType.Condition] },
]
// 失控动作
export enum OutOfControlAction {
ReturnToHome = 0,
Hover = 1,
Land = 2,
}
export const OutOfControlActionMap = {
[OutOfControlAction.ReturnToHome]: 'Return to Home',
[OutOfControlAction.Hover]: 'Hover',
[OutOfControlAction.Land]: 'Land',
}
export const OutOfControlActionOptions = [
{ value: OutOfControlAction.ReturnToHome, label: OutOfControlActionMap[OutOfControlAction.ReturnToHome] },
{ value: OutOfControlAction.Hover, label: OutOfControlActionMap[OutOfControlAction.Hover] },
{ value: OutOfControlAction.Land, label: OutOfControlActionMap[OutOfControlAction.Land] },
]
// 任务状态
export enum TaskStatus {
Wait = 1, // 待执行
@ -45,7 +18,6 @@ export enum TaskStatus { @@ -45,7 +18,6 @@ export enum TaskStatus {
Success = 3, // 完成
CanCel = 4, // 取消
Fail = 5, // 失败
Paused = 6, // 暂停
}
export const TaskStatusMap = {
@ -54,8 +26,6 @@ export const TaskStatusMap = { @@ -54,8 +26,6 @@ export const TaskStatusMap = {
[TaskStatus.Success]: 'Task completed',
[TaskStatus.CanCel]: 'Task canceled',
[TaskStatus.Fail]: 'Task failed',
[TaskStatus.Paused]: 'Paused',
}
export const TaskStatusColor = {
@ -64,7 +34,6 @@ export const TaskStatusColor = { @@ -64,7 +34,6 @@ export const TaskStatusColor = {
[TaskStatus.Success]: commonColor.NORMAL,
[TaskStatus.CanCel]: commonColor.FAIL,
[TaskStatus.Fail]: commonColor.FAIL,
[TaskStatus.Paused]: commonColor.BLUE,
}
// 任务执行 ws 消息状态
@ -105,40 +74,5 @@ export const TaskProgressWsStatusMap = { @@ -105,40 +74,5 @@ export const TaskProgressWsStatusMap = {
[TaskProgressStatus.Failed]: TaskStatus.Fail,
[TaskProgressStatus.Canceled]: TaskStatus.CanCel,
[TaskProgressStatus.Timeout]: TaskStatus.Fail,
[TaskProgressStatus.Paused]: TaskStatus.Paused,
}
// 根据媒体文件上传进度信息,前端自己判断出的状态
export enum MediaStatus { // 媒体上传进度
ToUpload = 1, // 待上传
Uploading = 2, // 上传中
Empty = 3, // 无媒体文件
Success = 4, // 上传成功
}
export const MediaStatusMap = {
[MediaStatus.ToUpload]: 'Waiting to upload',
[MediaStatus.Uploading]: 'Uploading…',
[MediaStatus.Success]: 'Uploaded',
[MediaStatus.Empty]: 'No media files',
}
export const MediaStatusColorMap = {
[MediaStatus.ToUpload]: commonColor.BLUE,
[MediaStatus.Uploading]: commonColor.BLUE,
[MediaStatus.Success]: commonColor.NORMAL,
[MediaStatus.Empty]: commonColor.WARN,
}
// 媒体上传进度消息
export interface MediaStatusProgressInfo {
job_id: string,
media_count: number
uploaded_count: number,
}
// 媒体上传优先级消息
export interface TaskMediaHighestPriorityProgressInfo {
pre_job_id: string,
job_id: string,
[TaskProgressStatus.Paused]: TaskStatus.Wait,
}

207
src/utils/device-cmd.ts

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
import { DroneBatteryModeEnum, DroneBatteryStateEnum, LinkWorkModeEnum } from './../types/airport-tsa'
import { DeviceInfoType } from '/@/types/device'
import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum, AlarmModeEnum, BatteryStoreModeEnum } from '/@/types/airport-tsa'
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum } from '/@/types/airport-tsa'
import { getBytesObject } from './bytes'
import { DEFAULT_PLACEHOLDER } from './constants'
@ -36,14 +35,6 @@ export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo: @@ -36,14 +35,6 @@ export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo:
droneFormat(cmdItem, device)
} else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关
getSupplementLightState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 声光报警
getAlarmState(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养
getBatteryStoreMode(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
getDroneBatteryMode(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传开关
getSdrWorkNode(cmdItem, dock)
}
})
}
@ -67,7 +58,7 @@ function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) { @@ -67,7 +58,7 @@ function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) {
// 舱盖开关
function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {
const coverState = airportProperties?.basic_osd?.cover_state as CoverStateEnum
const coverState = airportProperties?.cover_state as CoverStateEnum
if (coverState === CoverStateEnum.Close || coverState === CoverStateEnum.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText
@ -86,7 +77,7 @@ function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -86,7 +77,7 @@ function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 推杆状态
function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {
const putterState = airportProperties?.basic_osd?.putter_state as PutterStateEnum
const putterState = airportProperties?.putter_state as PutterStateEnum
if (putterState === PutterStateEnum.Close || putterState === PutterStateEnum.Failed) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText
@ -104,7 +95,7 @@ function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -104,7 +95,7 @@ function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 充电状态
function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {
const chargeState = airportProperties?.basic_osd?.drone_charge_state
const chargeState = airportProperties?.drone_charge_state
const state = chargeState?.state as ChargeStateEnum
if (!state) return
if (state === ChargeStateEnum.Charge) {
@ -124,7 +115,7 @@ function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -124,7 +115,7 @@ function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 机场存储格式化
function deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) {
const airportStorage = airportProperties?.basic_osd?.storage
const airportStorage = airportProperties?.storage
const value = getAirportStorage(airportStorage)
cmdItem.status = value
}
@ -159,7 +150,7 @@ function getBytes (bytes: number, index: number, fixed = 1) { @@ -159,7 +150,7 @@ function getBytes (bytes: number, index: number, fixed = 1) {
// 补光灯状态
function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any) {
const supplementLightState = airportProperties?.basic_osd?.supplement_light_state
const supplementLightState = airportProperties?.supplement_light_state
if (supplementLightState === SupplementLightStateEnum.Close) {
cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText
cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText
@ -175,69 +166,6 @@ function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any @@ -175,69 +166,6 @@ function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any
}
}
// 声光报警
function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {
const alarmState = airportProperties?.basic_osd?.alarm_state
if (alarmState === AlarmModeEnum.CLOSE) {
cmdItem.operateText = DeviceCmdStatusText.AlarmStateCloseBtnText
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseNormalText
cmdItem.action = AlarmModeEnum.OPEN
} else if (alarmState === AlarmModeEnum.OPEN) {
cmdItem.operateText = DeviceCmdStatusText.AlarmStateOpenBtnText
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenNormalText
cmdItem.action = AlarmModeEnum.CLOSE
}
}
// 机场电池模式
function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) {
const batteryStoreMode = airportProperties?.basic_osd?.battery_store_mode
if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModePlanBtnText
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanNormalText
cmdItem.action = BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE
} else if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModeEmergencyBtnText
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyNormalText
cmdItem.action = BatteryStoreModeEnum.BATTERY_PLAN_STORE
}
}
// 飞行器电池保养
function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) {
const maintenanceState = airportProperties?.work_osd?.drone_battery_maintenance_info?.maintenance_state
if (maintenanceState === DroneBatteryStateEnum.MaintenanceInProgress) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeCloseBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.action = DroneBatteryModeEnum.CLOSE
cmdItem.disabled = false
} else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
cmdItem.action = DroneBatteryModeEnum.OPEN
cmdItem.disabled = true
} else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.action = DroneBatteryModeEnum.OPEN
cmdItem.disabled = false
}
}
// 增强图传开关
function getSdrWorkNode (cmdItem: DeviceCmdItem, airportProperties: any) {
const linkWorkMode = airportProperties?.link_osd?.wireless_link?.link_workmode
if (linkWorkMode === LinkWorkModeEnum.SDR) {
cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourCloseBtnText
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText
cmdItem.action = LinkWorkModeEnum.FourG_FUSION_MODE
} else if (linkWorkMode === LinkWorkModeEnum.FourG_FUSION_MODE) {
cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourGOpenBtnText
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText
cmdItem.action = LinkWorkModeEnum.SDR
}
}
/**
*
* @param cmd
@ -285,7 +213,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -285,7 +213,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -296,7 +224,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -296,7 +224,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -309,7 +237,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -309,7 +237,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -322,7 +250,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -322,7 +250,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -335,7 +263,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -335,7 +263,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -348,7 +276,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -348,7 +276,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -361,7 +289,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -361,7 +289,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -374,7 +302,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -374,7 +302,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -387,7 +315,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -387,7 +315,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -400,7 +328,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -400,7 +328,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -410,114 +338,13 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -410,114 +338,13 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 机场声光报警
if (cmdItem.action === AlarmModeEnum.CLOSE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.action === AlarmModeEnum.OPEN) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
}
} else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养
if (cmdItem.action === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
} else if (cmdItem.action === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.loading = false
}
}
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
if (cmdItem.action === DroneBatteryModeEnum.OPEN) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
cmdItem.loading = false
}
} else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.loading = false
}
}
} else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传
if (cmdItem.action === LinkWorkModeEnum.SDR) { // 关闭
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText
cmdItem.loading = false
}
} else if (cmdItem.action === LinkWorkModeEnum.FourG_FUSION_MODE) { // 开启
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenText
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText
cmdItem.loading = false
}
}
}
}
})
}
/**
*
* @param status
* @returns
*/
function isExecuteFailed (status: DeviceCmdExecuteStatus) {
return [DeviceCmdExecuteStatus.Canceled, DeviceCmdExecuteStatus.Failed, DeviceCmdExecuteStatus.Timeout].includes(status)
}

27
src/utils/genjson.ts

@ -39,20 +39,7 @@ export interface GeojsonPoint { @@ -39,20 +39,7 @@ export interface GeojsonPoint {
}
}
export interface GeojsonCircle {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: 'Circle'
coordinates: GeojsonCoordinate
radius: number
}
}
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint | GeojsonCircle
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint
export function geographic2Coordinate (position: MapGeographicPosition): GeojsonCoordinate {
const coordinates: GeojsonCoordinate = [position.longitude, position.latitude]
@ -92,15 +79,3 @@ export function generatePoint (position: MapGeographicPosition, properties: Geoj @@ -92,15 +79,3 @@ export function generatePoint (position: MapGeographicPosition, properties: Geoj
},
}
}
export function generateCircle (position: MapGeographicPosition, properties: GeojsonCircle['properties'], radius: number): GeojsonFeature {
return {
type: 'Feature',
properties,
geometry: {
type: 'Circle',
coordinates: geographic2Coordinate(position),
radius: radius,
},
}
}

7
src/utils/map-layer-utils.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { pinAMapPosition, MapGeographicPosition, Layer, LayerType, LayerElevationLoadStatus } from '/@/types/map'
import { generatePoint, generateLine, generatePolygon, generateCircle } from '/@/utils/genjson'
import { generatePoint, generateLine, generatePolygon } from '/@/utils/genjson'
import { MapDoodleColor, MapElementEnum } from '/@/constants/map'
function getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographicPosition {
return { height: 0, latitude: pinAMapPosition.lat, longitude: pinAMapPosition.lng }
@ -42,8 +42,3 @@ export function generatePolyContent (mapPosition: pinAMapPosition[]) { @@ -42,8 +42,3 @@ export function generatePolyContent (mapPosition: pinAMapPosition[]) {
})
}
}
export function generateCircleContent (pinAMapPosition: pinAMapPosition, radius: number) {
const position = getPinPosition(pinAMapPosition)
return generateCircle(position, { color: MapDoodleColor.PolygonColor }, radius)
}

2
src/utils/time.ts

@ -6,7 +6,7 @@ import moment, { Moment } from 'moment' @@ -6,7 +6,7 @@ import moment, { Moment } from 'moment'
// 时间字符串 或者 Unix 时间戳(毫秒数)
export function formatDateTime (time: string | number, format = DATE_FORMAT) {
return time ? moment(time).format(format) : DEFAULT_PLACEHOLDER
return time ? moment(time, format) : DEFAULT_PLACEHOLDER
}
// Unix 时间戳 (秒)

2
src/vendors/jswebrtc.min.js vendored

File diff suppressed because one or more lines are too long

687
src/vendors/srs.sdk.js vendored

@ -1,687 +0,0 @@ @@ -1,687 +0,0 @@
//
// Copyright (c) 2013-2021 Winlin
//
// SPDX-License-Identifier: MIT
//
'use strict';
function SrsError(name, message) {
this.name = name;
this.message = message;
this.stack = (new Error()).stack;
}
SrsError.prototype = Object.create(Error.prototype);
SrsError.prototype.constructor = SrsError;
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher.
function SrsRtcPublisherAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: { ideal: 320, max: 576 }
}
};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// or autostart the publish:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.publish = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", { direction: "sendonly" });
self.pc.addTransceiver("video", { direction: "sendonly" });
//self.pc.addTransceiver("video", {direction: "sendonly"});
//self.pc.addTransceiver("audio", {direction: "sendonly"});
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({ track: track });
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: session.sdp })
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/publish/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-await-promise based SRS RTC Player.
function SrsRtcPlayerAsync() {
var self = {};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// webrtc://r.ossrs.net:80/live/livestream
// or autostart the play:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.play = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", { direction: "recvonly" });
self.pc.addTransceiver("video", { direction: "recvonly" });
//self.pc.addTransceiver("video", {direction: "recvonly"});
//self.pc.addTransceiver("audio", {direction: "recvonly"});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: session.sdp })
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the player.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got remote track.
// Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
self.ontrack = function (event) {
// https://webrtc.org/getting-started/remote-streams
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/play/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
self.stream = new MediaStream();
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function (event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher by WHIP.
function SrsRtcWhipWhepAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: { ideal: 320, max: 576 }
}
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to publish with, for example:
// http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
self.publish = async function (url) {
if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
self.pc.addTransceiver("audio", { direction: "sendonly" });
self.pc.addTransceiver("video", { direction: "sendonly" });
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({ track: track });
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer })
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to play with, for example:
// http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
self.play = async function (url) {
if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
self.pc.addTransceiver("video", { direction: "recvonly" });
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer })
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
// Internal APIs.
self.__internal = {
parseId: (url, offer, answer) => {
let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n'));
const a = document.createElement("a");
a.href = url;
return {
sessionid: sessionid, // Should be ice-ufrag of answer:offer.
simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
};
},
};
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function (event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
function SrsRtcFormatSenders(senders, kind) {
var codecs = [];
senders.forEach(function (sender) {
var params = sender.getParameters();
params && params.codecs && params.codecs.forEach(function (c) {
if (kind && sender.track.kind !== kind) {
return;
}
if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
return;
}
var s = '';
s += c.mimeType.replace('audio/', '').replace('video/', '');
s += ', ' + c.clockRate + 'HZ';
if (sender.track.kind === "audio") {
s += ', channels: ' + c.channels;
}
s += ', pt: ' + c.payloadType;
codecs.push(s);
});
});
return codecs.join(", ");
}
export default {
SrsError,
SrsRtcPublisherAsync,
SrsRtcPlayerAsync,
SrsRtcWhipWhepAsync,
SrsRtcFormatSenders,
}

6
src/websocket/index.ts

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import { message } from 'ant-design-vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
import { EBizCode } from '../types'
interface WebSocketOptions {
data: any
@ -12,11 +11,6 @@ export interface MessageHandler { @@ -12,11 +11,6 @@ export interface MessageHandler {
(data : {[key: string]: any}): void
}
export interface CommonHostWs<T> {
sn: string
host: T
}
/**
* ConnectWebSocket
* TODO: 优化messageHandler: EventEmitter

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save