Compare commits

...

15 Commits
v1.3.0 ... main

Author SHA1 Message Date
zhangxu 9d8e1c6eae xxx 7 days ago
jessie.huo@dji.com b26ef22ab6 feat(v1.10.0): v1.10.0 版本增加 10 months ago
jessie.huo@dji.com ae62a2c5ef 1. 增加webrtc 功能示例;2. 增加自定义飞行区功能示例 11 months ago
sean.zhou 42854e4db7 Merge branch 'v1.8.0' 1 year ago
sean.zhou 3ef74070df What's new? 1 year ago
sean.zhou 91f0f86730 Merge branch 'v1.7.0' 1 year ago
sean.zhou 07ff9ab4cc What's new? 1 year ago
sean.zhou 763e39bd71 Merge branch 'v1.5.0' 2 years ago
sean.zhou 91fbc1342f What's new? 2 years ago
sean.zhou a6d5406418 fix the debugging parameters 2 years ago
sean.zhou f3e92fa131 initial v1.4.0 2 years ago
sean.zhou b09ed762ed Fix the issue that the data is not displayed when the map is not loaded. 2 years ago
sean.zhou 9650dd9f1d Fixed some issues 2 years ago
sean.zhou 20e5f4cd98 update device-firmware.ts 2 years ago
sean.zhou 83d4c07a67 initial v1.3.1 2 years ago
  1. 2
      .npmrc
  2. 5
      README.md
  3. 1525
      package-lock.json
  4. 6
      package.json
  5. 5185
      pnpm-lock.yaml
  6. 5
      src/api/device-cmd/index.ts
  7. 58
      src/api/drc.ts
  8. 74
      src/api/drone-control/drone.ts
  9. 93
      src/api/drone-control/payload.ts
  10. 81
      src/api/flight-area/index.ts
  11. 21
      src/api/http/config.ts
  12. 28
      src/api/manage.ts
  13. 3
      src/api/media.ts
  14. 39
      src/api/wayline.ts
  15. 393
      src/components/GMap.vue
  16. 3
      src/components/common/sidebar.vue
  17. 3
      src/components/common/topbar.vue
  18. 61
      src/components/devices/DeviceFirmwareStatus.vue
  19. 26
      src/components/devices/device-hms/DeviceHmsDrawer.vue
  20. 4
      src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue
  21. 2
      src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue
  22. 59
      src/components/flight-area/FlightAreaActionIcon.vue
  23. 197
      src/components/flight-area/FlightAreaDevicePanel.vue
  24. 33
      src/components/flight-area/FlightAreaIcon.vue
  25. 89
      src/components/flight-area/FlightAreaItem.vue
  26. 43
      src/components/flight-area/FlightAreaPanel.vue
  27. 66
      src/components/flight-area/FlightAreaSyncPanel.vue
  28. 18
      src/components/flight-area/use-flight-area-drone-location-event.ts
  29. 17
      src/components/flight-area/use-flight-area-sync-progress-event.ts
  30. 30
      src/components/flight-area/use-flight-area-update.ts
  31. 155
      src/components/flight-area/use-flight-area.ts
  32. 4
      src/components/g-map/DeviceSettingBox.vue
  33. 17
      src/components/g-map/DockControlPanel.vue
  34. 34
      src/components/g-map/DroneControlInfoPanel.vue
  35. 837
      src/components/g-map/DroneControlPanel.vue
  36. 115
      src/components/g-map/DroneControlPopover.vue
  37. 71
      src/components/g-map/use-connect-mqtt.ts
  38. 0
      src/components/g-map/use-device-setting.ts
  39. 22
      src/components/g-map/use-dock-control.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. 38
      src/components/livestream-agora.vue
  47. 40
      src/components/livestream-others.vue
  48. 202
      src/components/task/CreatePlan.vue
  49. 182
      src/components/task/TaskPanel.vue
  50. 40
      src/components/task/use-format-task.ts
  51. 19
      src/components/task/use-task-progress-event.ts
  52. 43
      src/components/task/use-task-ws-event.ts
  53. 15
      src/components/workspace/DividerLine.vue
  54. 21
      src/components/workspace/Title.vue
  55. 6
      src/constants/map.ts
  56. 38
      src/directives/drag-window.ts
  57. 6
      src/directives/index.ts
  58. 7
      src/event-bus/index.ts
  59. 209
      src/hooks/use-g-map-cover.ts
  60. 24
      src/hooks/use-g-map-tsa.ts
  61. 4
      src/hooks/use-g-map.ts
  62. 16
      src/hooks/use-map-tool.ts
  63. 58
      src/hooks/use-mouse-tool.ts
  64. 4
      src/main.ts
  65. 12
      src/mqtt/config.ts
  66. 117
      src/mqtt/index.ts
  67. 40
      src/pages/page-pilot/pilot-home.vue
  68. 28
      src/pages/page-pilot/pilot-liveshare.vue
  69. 325
      src/pages/page-web/projects/Firmwares.vue
  70. 19
      src/pages/page-web/projects/devices.vue
  71. 88
      src/pages/page-web/projects/dock.vue
  72. 98
      src/pages/page-web/projects/flight-area.vue
  73. 50
      src/pages/page-web/projects/layer.vue
  74. 19
      src/pages/page-web/projects/livestream.vue
  75. 97
      src/pages/page-web/projects/tsa.vue
  76. 61
      src/pages/page-web/projects/wayline.vue
  77. 28
      src/pages/page-web/projects/workspace.vue
  78. 20
      src/router/index.ts
  79. 4
      src/shims-mqtt.d.ts
  80. 54
      src/store/index.ts
  81. 18
      src/types/airport-tsa.ts
  82. 55
      src/types/device-cmd.ts
  83. 38
      src/types/device-firmware.ts
  84. 271
      src/types/device.ts
  85. 72
      src/types/drc.ts
  86. 83
      src/types/drone-control.ts
  87. 28
      src/types/enums.ts
  88. 80
      src/types/flight-area.ts
  89. 71
      src/types/live-stream.ts
  90. 3
      src/types/map-enum.ts
  91. 53
      src/types/task.ts
  92. 111
      src/utils/device-cmd.ts
  93. 27
      src/utils/genjson.ts
  94. 7
      src/utils/map-layer-utils.ts
  95. 2
      src/utils/time.ts
  96. 2
      src/vendors/jswebrtc.min.js
  97. 687
      src/vendors/srs.sdk.js
  98. 6
      src/websocket/index.ts
  99. 4
      tsconfig.json
  100. 7170
      yarn.lock

2
.npmrc

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

5
README.md

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
## What is the DJI Cloud API?
The launch of the Cloud API mainly solves the problem of developers reinventing the wheel. For developers who do not need in-depth customization of APP, they can directly use DJI Pilot2 to communicate with the third cloud platform, and developers can focus on the development and implementation of cloud service interfaces.
The launch of the Cloud API mainly solves the problem of developers reinventing the wheel. For developers who do not need in-depth customization of APP, they can directly use DJI Pilot2 to communicate with the third cloud platform, and developers can focus on the development and implementation of cloud service interfaces.
## Docker
@ -14,9 +14,8 @@ For more documentation, please visit the [DJI Developer Documentation](https://d @@ -14,9 +14,8 @@ For more documentation, please visit the [DJI Developer Documentation](https://d
## Latest Release
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/).
Cloud API 1.10.0 was released on 7 April 2024. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
## License
Cloud API is MIT-licensed. Please refer to the LICENSE file for more information.

1525
package-lock.json generated

File diff suppressed because it is too large Load Diff

6
package.json

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

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

@ -18,9 +18,6 @@ export interface PostSendCmdBody { @@ -18,9 +18,6 @@ export interface PostSendCmdBody {
*/
// /control/api/v1/devices/{dock_sn}/jobs/{service_identifier}
export async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> {
const postBody = body || {}
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, {
...postBody
})
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, body)
return resp.data
}

58
src/api/drc.ts

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

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

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

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

21
src/api/http/config.ts

@ -1,17 +1,17 @@ @@ -1,17 +1,17 @@
export const CURRENT_CONFIG = {
// license
appId: 'Please enter the app id.', // You need to go to the development website to apply.
appKey: 'Please enter the app key.', // You need to go to the development website to apply.
appLicense: 'Please enter the app license.', // You need to go to the development website to apply.
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.
// http
baseURL: 'Please enter the backend access address prefix.', // This url must end with "/". Example: 'http://192.168.1.1:6789/'
websocketURL: 'Please enter the WebSocket access address.', // Example: 'ws://192.168.1.1:6789/api/v1/ws'
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'
// livestreaming
// RTMP Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.
rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'
rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'
// GB28181 Note:If you don't know what these parameters mean, you can go to Pilot2 and select the GB28181 page in the cloud platform. Where the parameters same as these parameters.
gbServerIp: 'Please enter the server ip.',
gbServerPort: 'Please enter the server port.',
@ -29,8 +29,7 @@ export const CURRENT_CONFIG = { @@ -29,8 +29,7 @@ export const CURRENT_CONFIG = {
agoraToken: 'Please enter the agora temporary token.',
agoraChannel: 'Please enter the agora channel.',
// map
// map
// You can apply on the AMap website.
amapKey: 'Please enter the amap key.',
amapKey: '8b0131c34d408f6663aae7a779e62cd1',
}

28
src/api/manage.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request'
import { Device } from '/@/types/device'
@ -24,7 +25,7 @@ export interface HmsQueryBody { @@ -24,7 +25,7 @@ export interface HmsQueryBody {
begin_time: number,
end_time: number,
message: string,
domain: string,
domain: number,
}
export const login = async function (body: LoginBody): Promise<IWorkspaceResponse<any>> {
@ -125,7 +126,7 @@ export const getDeviceBySn = async function (workspace_id: string, device_sn: st @@ -125,7 +126,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: string): Promise<IListWorkspaceResponse<Device>> {
export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: number): Promise<IListWorkspaceResponse<Device>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}`
const result = await request.get(url)
return result.data
@ -150,7 +151,7 @@ export const updateDeviceHms = async function (workspace_id: string, device_sn: @@ -150,7 +151,7 @@ export const updateDeviceHms = async function (workspace_id: string, device_sn:
}
export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: string, pagination: IPage): Promise<IListWorkspaceResponse<any>> {
let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +
let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +
`&level=${body.level ?? ''}&begin_time=${body.begin_time ?? ''}&end_time=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}`
body.sns.forEach((sn: string) => {
if (sn !== '') {
@ -165,4 +166,23 @@ export const changeLivestreamLens = async function (body: {}): Promise<IWorkspac @@ -165,4 +166,23 @@ export const changeLivestreamLens = async function (body: {}): Promise<IWorkspac
const url = `${HTTP_PREFIX}/live/streams/switch`
const result = await request.post(url, body)
return result.data
}
}
export const getFirmwares = async function (workspace_id: string, page: IPage, body: FirmwareQueryParam): Promise<IListWorkspaceResponse<Firmware>> {
const url = `${HTTP_PREFIX}/workspaces/${workspace_id}/firmwares?page=${page.page}&page_size=${page.page_size}` +
`&device_name=${body.device_name}&product_version=${body.product_version}&status=${body.firmware_status ?? ''}`
const result = await request.get(url)
return result.data
}
export const importFirmareFile = async function (workspaceId: string, param: FormData): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/file/upload`
const result = await request.post(url, param)
return result.data
}
export const changeFirmareStatus = async function (workspaceId: string, firmwareId: string, param: {status: boolean}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/${firmwareId}`
const result = await request.put(url, param)
return result.data
}

3
src/api/media.ts

@ -15,12 +15,11 @@ export const downloadMediaFile = async function (workspaceId: string, fileId: st @@ -15,12 +15,11 @@ export const downloadMediaFile = async function (workspaceId: string, fileId: st
if (result.data.type === 'application/json') {
const reader = new FileReader()
reader.onload = function (e) {
let text = reader.result as string
const 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
}

39
src/api/wayline.ts

@ -42,9 +42,12 @@ export interface CreatePlan { @@ -42,9 +42,12 @@ export interface CreatePlan {
dock_sn: string,
task_type: TaskType, // 任务类型
wayline_type: WaylineType, // 航线类型
execute_time?: number // 执行时间(毫秒)
task_days: number[] // 执行任务的日期(秒)
task_periods: number[][] // 执行任务的时间点(秒)
rth_altitude: number // 相对机场返航高度 20 - 500
out_of_control_action: OutOfControlAction // 失控动作
min_battery_capacity?: number, // The minimum battery capacity of aircraft.
min_storage_capacity?: number, // The minimum storage capacity of dock and aircraft.
}
// Create Wayline Job
@ -65,13 +68,18 @@ export interface Task { @@ -65,13 +68,18 @@ export interface Task {
dock_name: string,
workspace_id: string,
username: string,
execute_time: string,
begin_time: string,
end_time: string,
execute_time: string,
completed_time: string,
status: TaskStatus, // 任务状态
progress: number, // 执行进度
code: number, // 错误码
rth_altitude: number // 相对机场返航高度 20 - 500
out_of_control_action: OutOfControlAction // 失控动作
media_count: number // 媒体数量
uploading:boolean // 是否正在上传媒体
uploaded_count: number // 已上传媒体数量
}
// Get Wayline Jobs
@ -85,7 +93,7 @@ export interface DeleteTaskParams { @@ -85,7 +93,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, {
@ -94,6 +102,24 @@ export async function deleteTask (workspaceId: string, params: DeleteTaskParams) @@ -94,6 +102,24 @@ 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`
@ -104,3 +130,10 @@ export const importKmzFile = async function (workspaceId: string, file: {}): Pro @@ -104,3 +130,10 @@ 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
}

393
src/components/GMap.vue

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

3
src/components/common/topbar.vue

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

61
src/components/devices/DeviceFirmwareStatus.vue

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
<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,7 +67,12 @@ @@ -67,7 +67,12 @@
</template>
<template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<span>{{ text }}</span>
<div >{{ text }}</div>
</a-tooltip>
</template>
<template #domain="{text}">
<a-tooltip :title="EDeviceTypeName[text]">
<div >{{ EDeviceTypeName[text] }}</div>
</a-tooltip>
</template>
</a-table>
@ -77,7 +82,7 @@ @@ -77,7 +82,7 @@
<!-- 暂时只抽取该组件 -->
<script lang="ts" setup>
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'
import { watchEffect, reactive, ref, defineProps, defineEmits, watch } from 'vue'
import { getDeviceHms, HmsQueryBody } from '/@/api/manage'
import moment, { Moment } from 'moment'
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
@ -95,7 +100,7 @@ const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) | @@ -95,7 +100,7 @@ const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) |
//
const sVisible = ref(false)
watchEffect(() => {
watch(props, () => {
sVisible.value = props.visible
// hms
if (props.visible) {
@ -117,9 +122,10 @@ const loading = ref(false) @@ -117,9 +122,10 @@ 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' },
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', slots: { customRender: 'code' } },
{ title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },
{ title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
{ title: 'Hms Message', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
]
interface DeviceHmsData {
@ -174,7 +180,7 @@ const param = reactive<HmsQueryBody>({ @@ -174,7 +180,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: '',
domain: -1,
level: '',
message: ''
})
@ -198,12 +204,12 @@ const levels = [ @@ -198,12 +204,12 @@ const levels = [
const deviceTypes = [
{
label: 'All',
value: ''
value: -1
}, {
label: EDeviceTypeName.Aircraft,
label: EDeviceTypeName[EDeviceTypeName.Aircraft],
value: EDeviceTypeName.Aircraft
}, {
label: EDeviceTypeName.Dock,
label: EDeviceTypeName[EDeviceTypeName.Dock],
value: EDeviceTypeName.Dock
}
]
@ -250,7 +256,7 @@ function onTimeChange (newTime: [Moment, Moment]) { @@ -250,7 +256,7 @@ function onTimeChange (newTime: [Moment, Moment]) {
getHms()
}
function onDeviceTypeSelect (val: string) {
function onDeviceTypeSelect (val: number) {
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.key]}}</div>
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.key]}}</div>
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key]}}</div>
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key]}}</div>
</div>
</template>
<!-- 设备sn -->

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,155 @@ @@ -0,0 +1,155 @@
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">设备属性设置</div>
<div class="device-setting-header">Device Property Set</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 './useDeviceSetting'
import { useDeviceSetting } from './use-device-setting'
import DeviceSettingPopover from './DeviceSettingPopover.vue'
const props = defineProps<{

17
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>设备操作 {{ props.sn}}</span>
<span>Device Control<span class="fz12 pl15">{{ props.sn}}</span></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">
@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@ -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 './useDockControl'
import { DeviceInfoType } from '/@/types/device'
import { useDockControl } from './use-dock-control'
import { DeviceInfoType, EDockModeCode } from '/@/types/device'
import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd'
import { useMyStore } from '/@/store'
import { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd'
@ -73,15 +73,14 @@ watch(() => props.deviceInfo, (value) => { @@ -73,15 +73,14 @@ 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, false)
emit('close-control-panel', props.sn, debugStatus.value)
}
// dock
const debugStatus = ref(false)
async function onDeviceStatusChange (status: boolean) {
let result = false
if (status) {

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

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

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

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

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

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

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

@ -4,10 +4,10 @@ import { postSendCmd } from '/@/api/device-cmd' @@ -4,10 +4,10 @@ import { postSendCmd } from '/@/api/device-cmd'
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
export function useDockControl () {
const controlPanelVisible = ref(false)
const dockControlPanelVisible = ref(false)
function setControlPanelVisible (visible: boolean) {
controlPanelVisible.value = visible
function setDockControlPanelVisible (visible: boolean) {
dockControlPanelVisible.value = visible
}
// 远程调试开关
@ -16,9 +16,6 @@ export function useDockControl () { @@ -16,9 +16,6 @@ export function useDockControl () {
sn: sn,
cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose
}, false)
if (result) {
setControlPanelVisible(on)
}
return result
}
@ -47,10 +44,19 @@ export function useDockControl () { @@ -47,10 +44,19 @@ export function useDockControl () {
}
}
// 控制面板关闭
async function onCloseControlPanel (sn: string, debugging: boolean) {
if (debugging) {
await dockDebugOnOff(sn, false)
}
setDockControlPanelVisible(false)
}
return {
controlPanelVisible,
setControlPanelVisible,
dockControlPanelVisible,
setDockControlPanelVisible,
sendDockControlCmd,
dockDebugOnOff,
onCloseControlPanel,
}
}

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

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

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

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

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

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

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

38
src/components/livestream-agora.vue

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div class="mt20 flex-column flex-justify-start flex-align-center">
<div class="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,7 +76,6 @@ @@ -76,7 +76,6 @@
class="ml10"
v-model:value="agoraPara.token"
placeholder="Token"
@change="encodeToken"
></a-input>
<span class="ml10">Channel:</span>
<a-input
@ -169,7 +168,6 @@ const livePara = reactive({ @@ -169,7 +168,6 @@ const livePara = reactive({
liveState: false
})
const nonSwitchable = 'normal'
const onRefresh = async () => {
dronePara.droneList = []
dronePara.cameraList = []
@ -204,8 +202,11 @@ const onRefresh = async () => { @@ -204,8 +202,11 @@ 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')
@ -217,9 +218,7 @@ onMounted(() => { @@ -217,9 +218,7 @@ 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.
const remotePlayerContainer: any = document.getElementById('player')
remotePlayerContainer.id = user.uid.toString()
remoteVideoTrack.play(remotePlayerContainer)
remoteVideoTrack.play(document.getElementById('player') as HTMLElement)
}
})
agoraClient.on('user-unpublished', async (user: any) => {
@ -231,16 +230,12 @@ onMounted(() => { @@ -231,16 +230,12 @@ onMounted(() => {
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(
@ -262,8 +257,7 @@ const onStart = async () => { @@ -262,8 +257,7 @@ const onStart = async () => {
}
agoraClient.setClientRole('audience', { level: 2 })
if (agoraClient.connectionState === 'DISCONNECTED') {
agoraClient
.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
await agoraClient.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
}
livePara.videoId =
dronePara.droneSelected +
@ -277,7 +271,7 @@ const onStart = async () => { @@ -277,7 +271,7 @@ const onStart = async () => {
'&sn=' +
dronePara.droneSelected +
'&token=' +
agoraPara.token +
encodeURIComponent(agoraPara.token) +
'&uid=' +
agoraPara.uid
@ -298,6 +292,14 @@ const onStart = async () => { @@ -298,6 +292,14 @@ 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 +
'/' +
@ -308,10 +310,10 @@ const onStop = async () => { @@ -308,10 +310,10 @@ const onStop = async () => {
}).then(res => {
if (res.code === 0) {
message.success(res.message)
livePara.liveState = false
dronePara.lensSelected = ''
console.log('stop play livestream')
}
livePara.liveState = false
dronePara.lensSelected = ''
console.log('stop play livestream')
})
}
const onDroneSelect = (val: SelectOption) => {
@ -350,7 +352,7 @@ const onCameraSelect = (val: SelectOption) => { @@ -350,7 +352,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,10 +1,11 @@ @@ -1,10 +1,11 @@
<template>
<div class="flex-column flex-justify-start flex-align-center mt20">
<div class="flex-column flex-justify-start flex-align-center">
<video
:style="{ width: '720px', height: '480px' }"
id="video-webrtc"
ref="videowebrtc"
controls
autoplay
class="mt20"
></video>
<p class="fz24">Live streaming source selection</p>
@ -120,6 +121,8 @@ import { CURRENT_CONFIG as config } from '/@/api/http/config' @@ -120,6 +121,8 @@ 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 {
@ -140,6 +143,10 @@ const liveTypeList: SelectOption[] = [ @@ -140,6 +143,10 @@ const liveTypeList: SelectOption[] = [
{
value: 3,
label: 'GB28181'
},
{
value: 4,
label: 'WEBRTC'
}
]
const clarityList: SelectOption[] = [
@ -182,6 +189,7 @@ const lensList = ref<string[]>([]) @@ -182,6 +189,7 @@ const lensList = ref<string[]>([])
const lensSelected = ref<String>()
const isDockLive = ref(false)
const nonSwitchable = 'normal'
let webrtc: any = null
const onRefresh = async () => {
droneList.value = []
@ -258,6 +266,9 @@ const onStart = async () => { @@ -258,6 +266,9 @@ 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
@ -291,13 +302,7 @@ const onStart = async () => { @@ -291,13 +302,7 @@ const onStart = async () => {
})
} else if (livetypeSelected.value === 2) {
console.log(res)
rtspData.value =
'url:' +
res.data.url +
'&username:' +
res.data.username +
'&password:' +
res.data.password
rtspData.value = 'url:' + res.data.url
} else if (livetypeSelected.value === 1) {
const url = res.data.url
const videoElement = videowebrtc.value
@ -310,6 +315,10 @@ const onStart = async () => { @@ -310,6 +315,10 @@ 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
})
@ -388,7 +397,7 @@ const onCameraSelect = (val: SelectOption) => { @@ -388,7 +397,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
@ -412,6 +421,19 @@ const onSwitch = () => { @@ -412,6 +421,19 @@ 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>

202
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">
<a-form-item label="Plan Name" name="name" :labelCol="{span: 24}">
<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-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">{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(wayline.drone_model_key)] }}</span>
<span class="ml5">{{ DEVICE_NAME[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">
{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(payload)] }}
{{ DEVICE_NAME[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;" @click="selectDock(dock)">
<div class="panel" style="padding-top: 5px;">
<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 }}</span>
<span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span>
</div>
</div>
</a-form-item>
@ -65,28 +65,68 @@ @@ -65,28 +65,68 @@
<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 :value="TaskType.Immediate">Immediate</a-radio-button>
<a-radio-button :value="TaskType.Single">Timed&One-Time</a-radio-button>
<a-radio-button v-for="type in TaskTypeOptions" :value="type.value" :key="type.value">{{ type.label }}</a-radio-button>
</a-radio-group>
</div>
</a-form-item>
<!-- 执行时间 -->
<a-form-item label="Start Time" v-if="planBody.task_type === TaskType.Single" name="select_execute_time">
<a-date-picker
v-model:value="planBody.select_execute_time"
format="YYYY-MM-DD HH:mm:ss"
show-time
placeholder="Select Time"
style="width: 280px;"
<!-- 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"
show-time
placeholder="End Time"
style="width: 40%;"
/>
</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: 24}" name="rth_altitude">
<a-input v-model:value="planBody.rth_altitude" style="background: black !important;">
</a-input>
<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: 24}" name="out_of_control_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">
@ -95,7 +135,7 @@ @@ -95,7 +135,7 @@
</a-radio-group>
</div>
</a-form-item>
<a-form-item style="width: 280px;">
<a-form-item class="width-100" style="margin-bottom: 40px;">
<div class="footer">
<a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel
</a-button>
@ -106,7 +146,7 @@ @@ -106,7 +146,7 @@
</a-form>
</div>
</div>
<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 v-if="drawerVisible" style="position: absolute; left: 335px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;">
<div>
<router-view :name="routeName"/>
</div>
@ -118,14 +158,14 @@ @@ -118,14 +158,14 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'
import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons-vue'
import { ELocalStorageKey, ERouterName } from '/@/types'
import { useMyStore } from '/@/store'
import { WaylineType, WaylineFile } from '/@/types/wayline'
import { Device, EDeviceType } from '/@/types/device'
import { Device, DEVICE_NAME } from '/@/types/device'
import { createPlan, CreatePlan } from '/@/api/wayline'
import { getRoot } from '/@/root'
import { TaskType, OutOfControlActionOptions, OutOfControlAction } from '/@/types/task'
import { TaskType, OutOfControlActionOptions, OutOfControlAction, TaskTypeOptions } from '/@/types/task'
import moment, { Moment } from 'moment'
import { RuleObject } from 'ant-design-vue/es/form/interface'
@ -147,12 +187,16 @@ const disabled = ref(false) @@ -147,12 +187,16 @@ 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_time: undefined as Moment| undefined,
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,
})
const drawerVisible = ref(false)
@ -160,16 +204,35 @@ const valueRef = ref() @@ -160,16 +204,35 @@ const valueRef = ref()
const rules = {
name: [
{ required: true, message: 'Please enter plan name.' },
{ max: 20, message: 'Length should be 1 to 20', trigger: 'blur' }
{ max: 20, message: 'Length should be 1 to 20' }
],
file_id: [{ required: true, message: 'Select Route' }],
dock_sn: [{ required: true, message: 'Select Device' }],
select_execute_time: [{ required: true, message: 'Select start time' }],
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 Relative Require number')
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')
}
},
}
@ -177,23 +240,64 @@ const rules = { @@ -177,23 +240,64 @@ const rules = {
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.')
}
})
}
function onSubmit () {
console.info(dock, '12131231')
valueRef.value.validate().then(() => {
disabled.value = true
const createPlanBody = { ...planBody } as unknown as CreatePlan
if (planBody.select_execute_time) {
createPlanBody.execute_time = moment(planBody.select_execute_time).valueOf()
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())
}
}
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)
disabled.value = false
}).finally(() => {
closePlan()
})
@ -220,6 +324,18 @@ function selectDevice () { @@ -220,6 +324,18 @@ 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">
@ -230,6 +346,7 @@ function selectDevice () { @@ -230,6 +346,7 @@ function selectDevice () {
height: 100vh;
display: flex;
flex-direction: column;
width: 285px;
.header {
height: 52px;
@ -241,6 +358,10 @@ function selectDevice () { @@ -241,6 +358,10 @@ function selectDevice () {
align-items: center;
}
::-webkit-scrollbar {
display: none;
}
.content {
height: calc(100% - 54px);
overflow-y: auto;
@ -249,7 +370,8 @@ function selectDevice () { @@ -249,7 +370,8 @@ function selectDevice () {
margin: 10px;
}
form label, input {
form label, input, .ant-input, .ant-calendar-range-picker-separator,
.ant-input:hover, .ant-time-picker .anticon, .ant-calendar-picker .anticon {
background-color: #232323;
color: #fff;
}
@ -259,12 +381,12 @@ function selectDevice () { @@ -259,12 +381,12 @@ function selectDevice () {
}
.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;
}

182
src/components/task/TaskPanel.vue

@ -5,19 +5,17 @@ @@ -5,19 +5,17 @@
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
<!-- 执行时间 -->
<template #duration="{ record }">
<div>
<div>{{ formatTaskTime(record.execute_time) }}</div>
<div>{{ formatTaskTime(record.end_time) }}</div>
<div 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>
</template>
<!-- 任务类型 -->
<template #taskType="{ record }">
<div>{{ formatTaskType(record) }}</div>
</template>
<!-- 失控动作 -->
<template #lostAction="{ record }">
<div>{{ formatLostAction(record) }}</div>
</template>
<!-- 状态 -->
<template #status="{ record }">
<div>
@ -36,9 +34,35 @@ @@ -36,9 +34,35 @@
</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 }">
<span class="action-area">
<div class="action-area">
<a-popconfirm
v-if="record.status === TaskStatus.Wait"
title="Are you sure you want to delete flight task?"
@ -48,7 +72,25 @@ @@ -48,7 +72,25 @@
>
<a-button type="primary" size="small">Delete</a-button>
</a-popconfirm>
</span>
<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>
</template>
</a-table>
</div>
@ -60,15 +102,15 @@ import { message } from 'ant-design-vue' @@ -60,15 +102,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, getWaylineJobs, Task } from '/@/api/wayline'
import { deleteTask, updateTaskStatus, UpdateTaskStatus, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'
import { useMyStore } from '/@/store'
import { ELocalStorageKey } from '/@/types/enums'
import { useFormatTask } from './use-format-task'
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap } from '/@/types/task'
import { useTaskProgressEvent } from './use-task-progress-event'
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap, MediaStatus, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'
import { useTaskWsEvent } from './use-task-ws-event'
import { getErrorMessage } from '/@/utils/error-code/index'
import { commonColor } from '/@/utils/color'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { ExclamationCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
const store = useMyStore()
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
@ -91,38 +133,41 @@ const columns = [ @@ -91,38 +133,41 @@ const columns = [
{
title: 'Planned/Actual Time',
dataIndex: 'duration',
width: 180,
width: 200,
slots: { customRender: 'duration' },
},
{
title: 'Status',
key: 'status',
width: 150,
slots: { customRender: 'status' }
},
{
title: 'Plan Name',
dataIndex: 'job_name',
width: 150,
ellipsis: true
width: 100,
},
{
title: 'Type',
dataIndex: 'taskType',
width: 120,
width: 100,
slots: { customRender: 'taskType' },
},
{
title: 'Flight Route Name',
dataIndex: 'file_name',
width: 150,
ellipsis: true
width: 100,
},
{
title: 'Dock Name',
dataIndex: 'dock_name',
width: 120,
width: 100,
ellipsis: true
},
{
title: 'RTH Altitude Relative to Dock (m)',
dataIndex: 'rth_altitude',
width: 120,
ellipsis: true
},
{
title: 'Lost Action',
@ -136,10 +181,10 @@ const columns = [ @@ -136,10 +181,10 @@ const columns = [
width: 120,
},
{
title: 'Status',
key: 'status',
width: 200,
slots: { customRender: 'status' }
title: 'Media File Upload',
key: 'media_upload',
width: 160,
slots: { customRender: 'media_upload' }
},
{
title: 'Action',
@ -153,7 +198,7 @@ const plansData = reactive({ @@ -153,7 +198,7 @@ const plansData = reactive({
data: [] as Task[]
})
const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus } = useFormatTask()
const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus, formatMediaTaskStatus } = useFormatTask()
//
function onTaskProgressWs (data: TaskProgressInfo) {
@ -174,11 +219,44 @@ function onTaskProgressWs (data: TaskProgressInfo) { @@ -174,11 +219,44 @@ 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}`
}
useTaskProgressEvent(onTaskProgressWs)
useTaskWsEvent({
onTaskProgressWs,
onTaskMediaProgressWs,
onoTaskMediaHighestPriorityWS,
})
onMounted(() => {
getPlans()
@ -211,6 +289,39 @@ async function onDeleteTask (jobId: string) { @@ -211,6 +289,39 @@ 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>
@ -222,8 +333,13 @@ async function onDeleteTask (jobId: string) { @@ -222,8 +333,13 @@ async function onDeleteTask (jobId: string) {
margin-top: 10px;
}
.action-area {
color: $primary;
cursor: pointer;
&::v-deep {
.ant-btn {
margin-right: 10px;
margin-bottom: 10px;
}
}
}
.circle-icon {

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

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'
import { Task } from '/@/api/wayline'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap } from '/@/types/task'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap, MediaStatusMap, MediaStatusColorMap, MediaStatus } from '/@/types/task'
import { isNil } from 'lodash'
export function useFormatTask () {
function formatTaskType (task: Task) {
@ -26,10 +27,47 @@ export function useFormatTask () { @@ -26,10 +27,47 @@ 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

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

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

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

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<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 = '#3366FF',
PolygonColor = '#FF33FF'
PolylineColor = '#2D8CF0',
PolygonColor = '#2D8CF0'
}
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'
export type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' | 'circle'

38
src/directives/drag-window.ts

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

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

7
src/event-bus/index.ts

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

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

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
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'
@ -14,12 +15,16 @@ export function useGMapCover () { @@ -14,12 +15,16 @@ export function useGMapCover () {
const normalColor = '#2D8CF0'
const store = rootStore
const coverList = store.state.coverList
const coverMap = store.state.coverMap
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
const disableColor = '#b3b3b3'
function AddCoverToMap (cover :any) {
root.$map.add(cover)
coverList.push(cover)
// console.log('coverList:', store.state.coverList)
coverMap[cover.getExtData().id] = [cover]
}
function getPinIcon (color?:string) {
@ -29,10 +34,10 @@ export function useGMapCover () { @@ -29,10 +34,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({
@ -44,7 +49,6 @@ export function useGMapCover () { @@ -44,7 +49,6 @@ 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,
@ -59,7 +63,8 @@ export function useGMapCover () { @@ -59,7 +63,8 @@ export function useGMapCover () {
function AddOverlayGroup (overlayGroup) {
root.$map.add(overlayGroup)
coverList.push(overlayGroup)
const id = overlayGroup.getExtData().id
coverMap[id] = [...(coverMap[id] || []), overlayGroup]
}
function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) {
const path = [] as GeojsonCoordinate[]
@ -78,9 +83,9 @@ export function useGMapCover () { @@ -78,9 +83,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.forEach(coordinate => {
coordinates[0].forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
// console.log('Polygon', path)
@ -98,36 +103,18 @@ export function useGMapCover () { @@ -98,36 +103,18 @@ export function useGMapCover () {
}
function removeCoverFromMap (id:string) {
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
}
}
coverMap[id].forEach(cover => root.$map.remove(cover))
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 getElementFromMap (id:string): any[] {
return coverMap[id]
}
function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) {
const element = getElementFromMap(id) as any
if (element) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
const element = elements[0]
const icon = getPinIcon(color)
element.setPosition(new AMap.LngLat(coordinates[0], coordinates[1]))
element.setIcon(icon)
@ -141,12 +128,160 @@ export function useGMapCover () { @@ -141,12 +128,160 @@ 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
updatePinElement,
updatePolylineElement,
updatePolygonElement,
initFlightAreaCircle,
initFlightAreaPolygon,
updateFlightAreaPolygon,
updateFlightAreaCircle,
calcPolygonPosition,
}
}

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 } from '/@/types'
import { ELocalStorageKey, EDeviceTypeName } from '/@/types'
import { getDeviceBySn } from '/@/api/manage'
import { message } from 'ant-design-vue'
import dockIcon from '/@/assets/icons/dock.png'
@ -9,18 +9,16 @@ import droneIcon from '/@/assets/icons/drone.png' @@ -9,18 +9,16 @@ import droneIcon from '/@/assets/icons/drone.png'
export function deviceTsaUpdate () {
const root = getRoot()
const AMap = root.$aMap
let AMap = root.$aMap
const icons = new Map([
['sub-device', droneIcon],
['gateway', rcIcon],
['dock', dockIcon]
[EDeviceTypeName.Aircraft, droneIcon],
[EDeviceTypeName.Gateway, rcIcon],
[EDeviceTypeName.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) {
@ -32,7 +30,7 @@ export function deviceTsaUpdate () { @@ -32,7 +30,7 @@ export function deviceTsaUpdate () {
return trackLine
}
function initIcon (type: string) {
function initIcon (type: number) {
return new AMap.Icon({
image: icons.get(type),
imageSize: new AMap.Size(40, 40),
@ -40,16 +38,16 @@ export function deviceTsaUpdate () { @@ -40,16 +38,16 @@ export function deviceTsaUpdate () {
})
}
function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) {
if (AMap === undefined) {
location.reload()
function initMarker (type: number, name: string, sn: string, lng?: number, lat?: number) {
if (markers[sn]) {
return
}
if (markers[sn]) {
if (root.$aMap === undefined) {
return
}
AMap = root.$aMap
markers[sn] = new AMap.Marker({
position: new AMap.LngLat(lng || 113.935913, lat || 22.525335),
position: new AMap.LngLat(lng || 113.943225499, lat || 22.577673716),
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.935913, 22.525335],
zoom: 15
center: [113.943225499, 22.577673716],
zoom: 20
})
state.mouseTool = new AMap.MouseTool(state.map)

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

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
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,6 +3,8 @@ import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg' @@ -3,6 +3,8 @@ 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()
@ -13,7 +15,10 @@ export function useMouseTool () { @@ -13,7 +15,10 @@ 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,
@ -29,7 +34,6 @@ export function useMouseTool () { @@ -29,7 +34,6 @@ export function useMouseTool () {
strokeOpacity: 1,
strokeWeight: 2,
strokeStyle: 'solid',
draggable: true,
title: type + state.polylineNum++
})
root?.$mouseTool.on('draw', getDrawCallback)
@ -42,7 +46,6 @@ export function useMouseTool () { @@ -42,7 +46,6 @@ export function useMouseTool () {
strokeWeight: 2,
fillColor: '#1791fc',
fillOpacity: 0.4,
draggable: true,
title: type + state.PolygonNum++
})
root?.$mouseTool.on('draw', getDrawCallback)
@ -53,8 +56,55 @@ export function useMouseTool () { @@ -53,8 +56,55 @@ export function useMouseTool () {
root?.$mouseTool.off('draw')
}
function mouseTool (type: MapDoodleType, getDrawCallback: Function) {
function drawFlightAreaPolygon (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
root?.$mouseTool.polygon({
strokeColor: flightAreaColorMap[type],
strokeOpacity: 1,
strokeWeight: 4,
extData: {
type: type,
mapType: 'polygon',
},
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[type],
fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,
})
root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
}
function drawFlightAreaCircle (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
root?.$mouseTool.circle({
strokeColor: flightAreaColorMap[type],
strokeOpacity: 1,
strokeWeight: 6,
extData: {
type: type,
mapType: 'circle',
},
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[type],
fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,
})
root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
}
function mouseTool (type: MapDoodleType, getDrawCallback: Function, flightAreaType?: EFlightAreaType) {
state.currentType = type
if (flightAreaType) {
switch (type) {
case MapDoodleEnum.POLYGON:
drawFlightAreaPolygon(flightAreaType, getDrawCallback)
return
case MapDoodleEnum.CIRCLE:
drawFlightAreaCircle(flightAreaType, getDrawCallback)
return
default:
message.error(`Invalid type: ${flightAreaType}`)
return
}
}
switch (type) {
case MapDoodleEnum.PIN:
drawPin(type, getDrawCallback)

4
src/main.ts

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

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

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
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="state === EStatusValue.CONNECTED" style="color: #75c5f6" />
<CloudSyncOutlined v-if="thingState === EStatusValue.CONNECTED" style="color: #75c5f6" />
<SyncOutlined spin v-else/>
<span style="color: #737373; margin-left: 3px;">{{ state }}</span>
<span style="color: #737373; margin-left: 3px;">{{ thingState }}</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">
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" v-if="device.data.online_status && device.data.sn">
<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" style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="bindingDevice">
<a-row v-if="device.data.online_status && device.data.sn" 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,7 +135,6 @@ import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' @@ -135,7 +135,6 @@ 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)
@ -150,14 +149,14 @@ const wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!) @@ -150,14 +149,14 @@ const wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)
const components = apiPilot.init()
const exitVisible = ref(false)
const drawerVisible = ref(false)
let minitor = -1
let minitor: any
interface DeviceInfoData {
data: DeviceStatus
}
const device = reactive<DeviceInfoData>({
data: {
sn: EStatusValue.DISCONNECT,
sn: '',
online_status: false,
device_callsign: '',
user_id: '',
@ -165,7 +164,7 @@ const device = reactive<DeviceInfoData>({ @@ -165,7 +164,7 @@ const device = reactive<DeviceInfoData>({
bound_status: false,
model: '',
gateway_sn: EStatusValue.DISCONNECT,
domain: ''
domain: -1
}
})
const bindParam: BindBody = {
@ -218,9 +217,7 @@ const messageHandler = async (payload: any) => { @@ -218,9 +217,7 @@ 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) {
@ -246,16 +243,20 @@ const messageHandler = async (payload: any) => { @@ -246,16 +243,20 @@ const messageHandler = async (payload: any) => {
// ws
useConnectWebSocket(messageHandler)
let bindNum: number
let bindNum: any
onMounted(() => {
apiPilot.onBackClickReg()
apiPilot.onStopPlatform()
const oldDevice = localStorage.getItem(ELocalStorageKey.Device)
if (oldDevice) {
device.data = JSON.parse(oldDevice)
}
window.connectCallback = arg => {
window.connectCallback = (arg: any) => {
connectCallback(arg)
}
window.wsConnectCallback = arg => {
window.wsConnectCallback = (arg: any) => {
wsConnectCallback(arg)
}
device.data.gateway_sn = apiPilot.getRemoteControllerSN()
@ -263,10 +264,7 @@ onMounted(() => { @@ -263,10 +264,7 @@ 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()
@ -331,6 +329,11 @@ const connectCallback = async (arg: any) => { @@ -331,6 +329,11 @@ 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)
@ -435,7 +438,6 @@ function refreshStatus () { @@ -435,7 +438,6 @@ 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) {
@ -481,7 +483,7 @@ function moduleUninstall (m: any) { @@ -481,7 +483,7 @@ function moduleUninstall (m: any) {
}
function getDeviceInfo () {
if (device.data.sn === EStatusValue.DISCONNECT) {
if (!device.data.sn || device.data.sn === EStatusValue.DISCONNECT) {
return
}
getDeviceBySn(bindParam.workspace_id, device.data.sn).then(res => {

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

@ -54,7 +54,26 @@ @@ -54,7 +54,26 @@
<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;">{{ agoraParam }}</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-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>
@ -154,11 +173,11 @@ const liveTypeList = [ @@ -154,11 +173,11 @@ const liveTypeList = [
label: ELiveTypeName.GB28181
}
]
const agoraParam = {
const agoraParam = reactive({
uid: '2892130292',
token: config.agoraToken,
channelId: config.agoraChannel
}
})
const rtmpParam = {
url: config.rtmpURL + new Date().getTime()
}
@ -174,7 +193,7 @@ const gb28181Param: GB28181Param = { @@ -174,7 +193,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)
@ -253,6 +272,7 @@ const onPublishModeSelect = (val: string) => { @@ -253,6 +272,7 @@ 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

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

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

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

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div>
<div class="height-100">
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
@ -7,23 +7,25 @@ @@ -7,23 +7,25 @@
<a-col :span="1"></a-col>
</a-row>
</div>
<div v-if="docksData.data.length !== 0">
<div v-for="dock in docksData.data" :key="dock.device_sn">
<div v-if="dock?.children" class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
<div class="title">
<a-tooltip :title="dock.nickname">
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
</a-tooltip>
</div>
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<span><RocketOutlined /></span>
<span class="ml5">{{ dock.children?.nickname }}</span>
<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-for="dock in docksData.data" :key="dock.device_sn">
<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>
</a-tooltip>
</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>
</div>
</div>
</div>
</div>
</div>
<div v-else>
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
<div v-else>
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
</div>
</div>
</template>
@ -35,7 +37,7 @@ import { onMounted, ref } from 'vue' @@ -35,7 +37,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, EDeviceType } from '/@/types/device'
import { Device } from '/@/types/device'
import { useMyStore } from '/@/store'
import { getBindingDevices } from '/@/api/manage'
import { IPage } from '/@/api/http/type'
@ -47,30 +49,56 @@ const docksData = reactive({ @@ -47,30 +49,56 @@ 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: 0,
page_size: 100
total: -1,
page_size: 10,
}
function getDocks () {
getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
async function getDocks () {
if (!canRefresh.value) {
return
}
canRefresh.value = false
await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
if (res.code !== 0) {
return
}
docksData.data = []
res.data.list.forEach((dock: any) => {
if (dock.child_device_sn) {
docksData.data.push(dock)
}
})
console.info(docksData.data)
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()
}
}
function selectDock (dock: Device) {
store.commit('SET_SELECT_DOCK_INFO', dock)
}
@ -97,4 +125,10 @@ function selectDock (dock: Device) { @@ -97,4 +125,10 @@ 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

@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
<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">
<div class="project-layer-wrapper height-100">
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
<a-col :span="1"></a-col>
</a-row>
</div>
<div class="scrollbar" :style="{ height: scorllHeight + 'px'}">
<LayersTree
:layer-data="mapLayers"
class="project-layer-content"
@ -15,6 +16,7 @@ @@ -15,6 +16,7 @@
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
/>
</div>
<a-drawer
title="Map Element"
placement="right"
@ -128,6 +130,7 @@ const colors = ref<Color[]>([ @@ -128,6 +130,7 @@ 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')
@ -168,7 +171,6 @@ function updateMapElement ( @@ -168,7 +171,6 @@ 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
@ -176,17 +178,11 @@ function updateMapElement ( @@ -176,17 +178,11 @@ function updateMapElement (
} else if (MapElementEnum.LINE === type && geoType === 'LineString') {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[]
useGMapCoverHook.initPolyline(name, coordinates, color, {
id: id,
name: name
})
useGMapCoverHook.updatePolylineElement(id, name, coordinates, color)
} else if (MapElementEnum.POLY === type && geoType === 'Polygon') {
const coordinates = element.resource?.content.geometry
.coordinates[0] as GeojsonCoordinate[]
useGMapCoverHook.initPolygon(name, coordinates, color, {
id: id,
name: name
})
.coordinates as GeojsonCoordinate[][]
useGMapCoverHook.updatePolygonElement(id, name, coordinates, color)
}
}
function checkLayer (keys: string[]) {
@ -232,10 +228,12 @@ function setBaseInfo () { @@ -232,10 +228,12 @@ 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:
layerState.longitude = layer.resource?.content.geometry.coordinates[0]
layerState.latitude = layer.resource?.content.geometry.coordinates[1]
coordinate = gcj02towgs84(layer.resource?.content.geometry.coordinates[0], layer.resource?.content.geometry.coordinates[1]) as GeojsonCoordinate
layerState.longitude = coordinate[0]
layerState.latitude = coordinate[1]
break
case GeoType.LineString:
break
@ -245,6 +243,9 @@ function setBaseInfo () { @@ -245,6 +243,9 @@ 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 () {
@ -350,39 +351,39 @@ function updateCoordinates (transformType: string, element: LayerResource) { @@ -350,39 +351,39 @@ function updateCoordinates (transformType: string, element: LayerResource) {
) as GeojsonCoordinate
element.resource.content.geometry.coordinates = transResult
}
} else if (MapElementEnum.LINE === type && geoType === 'LineString') {
} else if (MapElementEnum.LINE === type) {
const coordinates = element.resource?.content.geometry
.coordinates as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach(coordinate => {
coordinate = wgs84togcj02(
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach(coordinate => {
coordinate = gcj02towgs84(
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
}
element.resource.content.geometry.coordinates = coordinates
} else if (MapElementEnum.LINE === type && geoType === 'Polygon') {
} else if (MapElementEnum.POLY === type) {
const coordinates = element.resource?.content.geometry
.coordinates[0] as GeojsonCoordinate[]
if (transformType === 'wgs84-gcj02') {
coordinates.forEach(coordinate => {
coordinate = wgs84togcj02(
coordinates.forEach((coordinate, i, arr) => {
arr[i] = wgs84togcj02(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
})
} else if (transformType === 'gcj02-wgs84') {
coordinates.forEach(coordinate => {
coordinate = gcj02towgs84(
coordinates.forEach((coordinate, i, arr) => {
arr[i] = gcj02towgs84(
coordinate[0],
coordinate[1]
) as GeojsonCoordinate
@ -439,4 +440,7 @@ function updateCoordinates (transformType: string, element: LayerResource) { @@ -439,4 +440,7 @@ function updateCoordinates (transformType: string, element: LayerResource) {
margin-bottom: 10px;
}
}
.scrollbar {
overflow: auto;
}
</style>

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

@ -18,7 +18,8 @@ @@ -18,7 +18,8 @@
>
</router-link>
</div>
<div class="live" v-if="showLive">
<div class="live" v-if="showLive" 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="() => root.$router.push('/' + ERouterName.LIVESTREAM)"><CloseOutlined /></a>
<router-view :name="routeName" />
</div>
@ -31,8 +32,8 @@ import { CloseOutlined } from '@ant-design/icons-vue' @@ -31,8 +32,8 @@ import { CloseOutlined } from '@ant-design/icons-vue'
import { getRoot } from '/@/root'
import { ERouterName } from '/@/types'
const root = getRoot()
const routeName = ref<string>()
const showLive = ref<boolean>(false)
const routeName = ref<string>('LiveOthers')
const showLive = ref<boolean>(root.$route.name === ERouterName.LIVING)
const options = [
{ key: 0, label: 'Agora Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveAgora' },
@ -40,6 +41,7 @@ const options = [ @@ -40,6 +41,7 @@ const options = [
]
const selectLivestream = (route: string) => {
showLive.value = root.$route.name === ERouterName.LIVING
routeName.value = route
}
@ -73,14 +75,13 @@ onMounted(() => { @@ -73,14 +75,13 @@ onMounted(() => {
.live {
position: absolute;
z-index: 1;
right: 50%;
left: 50%;
top: 50%;
margin: auto;
transform: translate(-50%, -50%);
left: 0;
top: 10px;
margin-left: 345px;
text-align: center;
width: 800px;
height: 700px;
height: 720px;
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>
<div class="scrollbar" :style="{ height: scorllHeight + 'px'}">
<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">
<span class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }}</span>
<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>
</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>
<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 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>
</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>
<div class="flex-row">
<span class="ml5 mr5"><RocketOutlined /></span>
<span class="font-bold" :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
<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;'">
{{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
</span>
</div>
</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].mode_code !== EDockModeCode.Disconnected)">
<div class="fz16" @click="switchVisible($event, dock, true, dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.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.callsign }}</template>
<span class="text-hidden" style="max-width: 200px; display: block; height: 20px;">{{ device.model }} - {{ device.callsign }}</span>
<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>
</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 style="height: 20px; background: #595959; width: 94%;" >
<div class="flex-row" 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.callsign }} </template>
<span>{{ device.gateway.callsign }}</span>
<template #title>{{ device.gateway.model }} - {{ device.gateway.callsign }} </template>
<div class="text-hidden" style="max-width: 200px;">{{ device.gateway.model }} - {{ device.gateway.callsign }}</div>
</a-tooltip>
</div>
</div>
@ -194,34 +194,18 @@ import { computed, onMounted, reactive, ref, watch, WritableComputedRef } from ' @@ -194,34 +194,18 @@ 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 { DeviceStatus, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'
import { OnlineDevice, 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 = ref({} as OSDVisible)
const osdVisible = computed(() => store.state.osdVisible)
const hmsVisible = new Map<string, boolean>()
interface OnlineDevice {
model: string,
callsign: string,
sn: string,
mode: number,
gateway: {
model: string,
callsign: string,
sn: string,
domain: string,
},
payload: {
model: string
}[]
}
const scorllHeight = ref()
const onlineDevices = reactive({
data: [] as OnlineDevice[]
@ -256,6 +240,9 @@ onMounted(() => { @@ -256,6 +240,9 @@ 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 () {
@ -265,12 +252,12 @@ function getOnlineTopo () { @@ -265,12 +252,12 @@ function getOnlineTopo () {
}
onlineDevices.data = []
onlineDocks.data = []
res.data.forEach((val: any) => {
const gateway = val.gateways_list.pop()
res.data.forEach((gateway: any) => {
const child = gateway.children
const device: OnlineDevice = {
model: val.device_name,
callsign: val.nickname,
sn: val.device_sn,
model: child?.device_name,
callsign: child?.nickname,
sn: child?.device_sn,
mode: EModeCode.Disconnected,
gateway: {
model: gateway?.device_name,
@ -280,17 +267,22 @@ function getOnlineTopo () { @@ -280,17 +267,22 @@ function getOnlineTopo () {
},
payload: []
}
val.payloads_list.forEach((payload: any) => {
child?.payloads_list.forEach((payload: any) => {
device.payload.push({
model: payload.payload_name
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
})
})
if (gateway && EDeviceTypeName.Dock === gateway.domain) {
if (EDeviceTypeName.Dock === gateway.domain) {
hmsVisible.set(device.sn, false)
hmsVisible.set(device.gateway.sn, false)
onlineDocks.data.push(device)
}
if (val.status && EDeviceTypeName.Gateway === gateway.domain) {
if (gateway.status && EDeviceTypeName.Gateway === gateway.domain) {
onlineDevices.data.push(device)
}
})
@ -312,6 +304,7 @@ function switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick: @@ -312,6 +304,7 @@ 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)
}
@ -351,6 +344,15 @@ function readHms (visiable: boolean, sn: string) { @@ -351,6 +344,15 @@ 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">
@ -360,6 +362,15 @@ function readHms (visiable: boolean, sn: string) { @@ -360,6 +362,15 @@ function readHms (visiable: boolean, sn: string) {
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;

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

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<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>
@ -19,9 +20,8 @@ @@ -19,9 +20,8 @@
</a-col>
</a-row>
</div>
<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 :style="{ height : height + 'px'}" class="scrollbar">
<div id="data" class="height-100 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">{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(wayline.drone_model_key)] }}</span>
<span class="ml5">{{ DEVICE_NAME[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">
{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(payload)] }}
{{ DEVICE_NAME[payload] }}
</span>
</div>
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
@ -75,8 +75,8 @@ @@ -75,8 +75,8 @@
</div>
</template>
</a-modal>
</a-spin>
</div>
</a-spin>
</div>
</template>
@ -87,7 +87,7 @@ import { onMounted, onUpdated, ref } from 'vue' @@ -87,7 +87,7 @@ import { onMounted, onUpdated, ref } from 'vue'
import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline'
import { ELocalStorageKey, ERouterName } from '/@/types'
import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue'
import { EDeviceType } from '/@/types/device'
import { DEVICE_NAME } from '/@/types/device'
import { useMyStore } from '/@/store'
import { WaylineFile } from '/@/types/wayline'
import { downloadFile } from '/@/utils/common'
@ -100,7 +100,7 @@ const loading = ref(false) @@ -100,7 +100,7 @@ const loading = ref(false)
const store = useMyStore()
const pagination :IPage = {
page: 1,
total: 0,
total: -1,
page_size: 10
}
@ -114,31 +114,22 @@ const deleteTip = ref(false) @@ -114,31 +114,22 @@ 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()
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)
})
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()
}
} else if (element && element.className.indexOf('height-100') === -1) {
element.className = element.className + ' height-100'
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
}
}, 300)
pagination.page++
getWaylines()
}, 1000)
})
function getWaylines () {
@ -154,8 +145,7 @@ function getWaylines () { @@ -154,8 +145,7 @@ function getWaylines () {
if (res.code !== 0) {
return
}
waylinesData.data = []
res.data.list.forEach((wayline: WaylineFile) => waylinesData.data.push(wayline))
waylinesData.data = [...waylinesData.data, ...res.data.list]
pagination.total = res.data.pagination.total
pagination.page = res.data.pagination.page
}).finally(() => {
@ -175,7 +165,9 @@ function deleteWayline () { @@ -175,7 +165,9 @@ function deleteWayline () {
}
deleteWaylineId.value = ''
deleteTip.value = false
pagination.total--
pagination.total = 0
pagination.page = 1
waylinesData.data = []
getWaylines()
})
}
@ -199,8 +191,7 @@ function selectRoute (wayline: WaylineFile) { @@ -199,8 +191,7 @@ function selectRoute (wayline: WaylineFile) {
function onScroll (e: any) {
const element = e.srcElement
console.info(element)
if (element.scrollTop + element.clientHeight === element.scrollHeight && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
pagination.page++
getWaylines()
}
@ -226,7 +217,6 @@ function beforeUpload (file: FileItem) { @@ -226,7 +217,6 @@ 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)
@ -234,6 +224,9 @@ const uploadFile = async () => { @@ -234,6 +224,9 @@ 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(() => {

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

@ -71,8 +71,10 @@ const messageHandler = async (payload: any) => { @@ -71,8 +71,10 @@ const messageHandler = async (payload: any) => {
store.commit('SET_DEVICE_OFFLINE', payload.data)
break
}
case EBizCode.FlightTaskProgress: {
EventBus.emit('deviceTaskProgress', payload)
case EBizCode.FlightTaskProgress:
case EBizCode.FlightTaskMediaProgress:
case EBizCode.FlightTaskMediaHighestPriority: {
EventBus.emit('flightTaskWs', payload)
break
}
case EBizCode.DeviceHms: {
@ -98,6 +100,27 @@ const messageHandler = async (payload: any) => { @@ -98,6 +100,27 @@ 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
}
@ -125,6 +148,7 @@ useConnectWebSocket(messageHandler) @@ -125,6 +148,7 @@ useConnectWebSocket(messageHandler)
.main-content {
flex: 1;
color: $text-white-basic;
width: 285px;
}
}

20
src/router/index.ts

@ -32,6 +32,11 @@ const routes: Array<RouteRecordRaw> = [ @@ -32,6 +32,11 @@ 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')
}
]
},
@ -42,6 +47,10 @@ const routes: Array<RouteRecordRaw> = [ @@ -42,6 +47,10 @@ 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,
@ -57,10 +66,6 @@ const routes: Array<RouteRecordRaw> = [ @@ -57,10 +66,6 @@ const routes: Array<RouteRecordRaw> = [
}
]
},
{
path: '/' + ERouterName.TSA,
component: () => import('/@/pages/page-web/projects/tsa.vue')
},
{
path: '/' + ERouterName.LAYER,
name: ERouterName.LAYER,
@ -98,7 +103,12 @@ const routes: Array<RouteRecordRaw> = [ @@ -98,7 +103,12 @@ 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

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

54
src/store/index.ts

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

18
src/types/airport-tsa.ts

@ -60,3 +60,21 @@ export enum DroneBatteryModeEnum { @@ -60,3 +60,21 @@ 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融合模式
}

55
src/types/device-cmd.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum } from '/@/types/airport-tsa';
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum, LinkWorkModeEnum } from '/@/types/airport-tsa'
// 机场指令集
export enum DeviceCmd {
// 简单指令
@ -7,6 +7,7 @@ export enum DeviceCmd { @@ -7,6 +7,7 @@ export enum DeviceCmd {
SupplementLightOpen = 'supplement_light_open', // 打开补光灯
SupplementLightClose = 'supplement_light_close', // 关闭补光灯
ReturnHome = 'return_home', // 一键返航
ReturnHomeCancel = 'return_home_cancel', // 取消返航
// 复杂指令
DeviceReboot = 'device_reboot', // 机场重启
DroneOpen = 'drone_open', // 飞行器开机
@ -23,10 +24,10 @@ export enum DeviceCmd { @@ -23,10 +24,10 @@ export enum DeviceCmd {
AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警
BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养
DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养
SdrWorkModeSwitch = 'sdr_workmode_switch', // 增强图传
}
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum | LinkWorkModeEnum
export interface DeviceCmdItem{
label: string, // 标题
@ -39,6 +40,24 @@ export interface DeviceCmdItem{ @@ -39,6 +40,24 @@ export interface DeviceCmdItem{
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[] = [
@ -90,14 +109,6 @@ export const cmdList: DeviceCmdItem[] = [ @@ -90,14 +109,6 @@ export const cmdList: DeviceCmdItem[] = [
func: 'chargeStatus',
loading: false,
},
{
label: '一键返航',
status: '--',
operateText: '返航',
cmdKey: DeviceCmd.ReturnHome,
func: 'returnHome',
loading: false,
},
{
label: '机场存储',
status: '--',
@ -151,6 +162,15 @@ export const cmdList: DeviceCmdItem[] = [ @@ -151,6 +162,15 @@ export const cmdList: DeviceCmdItem[] = [
loading: false,
disabled: true,
},
{
label: '4g 增强',
status: '--',
operateText: '开启',
cmdKey: DeviceCmd.SdrWorkModeSwitch,
action: LinkWorkModeEnum.FourG_FUSION_MODE,
func: 'sdrWorkMode',
loading: false,
},
]
export enum DeviceCmdStatusText {
@ -239,6 +259,16 @@ export enum DeviceCmdStatusText { @@ -239,6 +259,16 @@ export enum DeviceCmdStatusText {
DroneBatteryModeMaintenanceNeedText = '需保养',
DroneBatteryModeOpenBtnText = '保养',
DroneBatteryModeCloseBtnText = '关闭保养',
SdrWorkModeFourGOpenNormalText = '开',
SdrWorkModeFourGOpenText = '开启中...',
SdrWorkModeFourGOpenFailedText = '--',
SdrWorkModeFourGOpenBtnText = '关闭',
SdrWorkModeFourGCloseNormalText = '--',
SdrWorkModeFourGCloseText = '关闭中...',
SdrWorkModeFourGCloseFailedText = '开',
SdrWorkModeFourCloseBtnText = '开启',
}
// cmd ws 消息状态
@ -262,6 +292,9 @@ export interface DeviceCmdExecuteInfo { @@ -262,6 +292,9 @@ export interface DeviceCmdExecuteInfo {
percent: number,
step_key: string,
step_result: number
},
ext?: {
rate?: number
}
}
result: number,

38
src/types/device-firmware.ts

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

271
src/types/device.ts

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
import { commonColor } from '/@/utils/color'
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum } from './airport-tsa'
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种
@ -20,11 +22,9 @@ export enum DOMAIN { @@ -20,11 +22,9 @@ 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,6 +44,8 @@ export enum PAYLOAD_TYPE { @@ -44,6 +44,8 @@ export enum PAYLOAD_TYPE {
M3E = 66,
M3T = 67,
M3D = 80,
M3TD = 81,
// UNKNOWN = 65535
}
@ -57,6 +59,7 @@ export enum RC_TYPE { @@ -57,6 +59,7 @@ export enum RC_TYPE {
// DOCK type
export enum DOCK_TYPE {
Dock = 1,
Dock2 = 2,
}
// 设备sub_type 从0升序
@ -76,10 +79,10 @@ export const DEVICE_MODEL_KEY = { @@ -76,10 +79,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}`,
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}`,
M350: `${DOMAIN.DRONE}-${DRONE_TYPE.M350}-${DEVICE_SUB_TYPE.ZERO}`,
M3D: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,
M3TD: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ONE}`,
FPV: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.FPV}-${DEVICE_SUB_TYPE.ZERO}`,
H20: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20}-${DEVICE_SUB_TYPE.ZERO}`,
@ -92,6 +95,8 @@ export const DEVICE_MODEL_KEY = { @@ -92,6 +95,8 @@ 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}`,
@ -103,6 +108,7 @@ export const DEVICE_MODEL_KEY = { @@ -103,6 +108,7 @@ 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 = {
@ -113,10 +119,9 @@ export const DEVICE_NAME = { @@ -113,10 +119,9 @@ 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.Phantom4]: 'Phantom 4',
[DEVICE_MODEL_KEY.Phantom4Pro]: 'Phantom 4 Pro',
[DEVICE_MODEL_KEY.Phantom4RTK]: 'Phantom 4 RTK',
[DEVICE_MODEL_KEY.Phantom4Advanced]: 'Phantom 4 Advanced',
[DEVICE_MODEL_KEY.M350]: 'M350 RTK',
[DEVICE_MODEL_KEY.M3D]: 'M3D',
[DEVICE_MODEL_KEY.M3TD]: 'M3TD',
// payload
[DEVICE_MODEL_KEY.FPV]: 'FPV',
@ -134,6 +139,8 @@ export const DEVICE_NAME = { @@ -134,6 +139,8 @@ 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',
@ -141,6 +148,37 @@ export const DEVICE_NAME = { @@ -141,6 +148,37 @@ 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[]
}
// 固件升级类型
@ -182,8 +220,9 @@ export interface Device { @@ -182,8 +220,9 @@ export interface Device {
bound_time: string,
login_time: string,
children?: Device[],
domain: string,
firmware_progress?: number, // 升级进度
domain: number,
type: number,
firmware_progress?: string, // 升级进度
}
export interface DeviceStatus {
@ -195,7 +234,7 @@ export interface DeviceStatus { @@ -195,7 +234,7 @@ export interface DeviceStatus {
bound_status: boolean,
model: string,
gateway_sn: string,
domain: string
domain: number
}
export interface OSDVisible {
@ -206,6 +245,7 @@ export interface OSDVisible { @@ -206,6 +245,7 @@ export interface OSDVisible {
is_dock: boolean,
gateway_sn: string,
gateway_callsign: string,
payloads: null | PayloadInfo [],
}
export interface GatewayOsd {
@ -214,6 +254,24 @@ export interface GatewayOsd { @@ -214,6 +254,24 @@ 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,
@ -241,72 +299,157 @@ export interface DeviceOsd { @@ -241,72 +299,157 @@ export interface DeviceOsd {
height_limit?: number;// 限高设置
distance_limit_status?: DistanceLimitStatus;// 限远开关
obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
cameras?: DeviceOsdCamera[]
}
export interface DockOsd {
media_file_detail: {
remain_upload: number
},
sdr: {
up_quality: string,
down_quality: string,
frequency_band: number,
},
network_state: {
type: number,
export enum NetworkStateTypeEnum {
FOUR_G = 1,
ETHERNET = 2,
}
export enum NetworkStateQualityEnum {
NO_SIGNAL = 0,
BAD = 1,
POOR = 2,
FAIR = 3,
GOOD = 4,
EXCELLENT = 5,
}
export enum RainfallEnum {
NONE = 0,
LIGHT_RAIN = 1,
MODERATE_RAIN = 2,
HEAVY_RAIN = 3,
}
export enum DroneInDockEnum {
OUTSIDE, INSIDE
}
export interface DockBasicOsd {
network_state?: {
type: NetworkStateTypeEnum,
quality: number,
rate: number,
},
drone_in_dock: number,
drone_charge_state: {
drone_charge_state?: {
state: number,
capacity_percent: string,
capacity_percent: number,
},
rainfall: string,
wind_speed: string,
environment_temperature: string,
environment_humidity: string
temperature: string,
humidity: string,
drone_in_dock: boolean,
rainfall: RainfallEnum,
wind_speed: number,
environment_temperature: number,
temperature: number,
humidity: number,
latitude: number,
longitude: number,
height: number,
job_number: number,
acc_time: number,
alternate_land_point?: {
latitude: number,
longitude: number,
height: number,
safe_land_height: number,
is_configured: number
}
first_power_on: number,
positionState: {
gps_number: string,
positionState?: {
gps_number: number,
is_fixed: number,
rtk_number: string,
rtk_number: number,
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,
},
alarm_state?: AlarmModeEnum; // 机场声光报警状态
battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式
// 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 {
Standby,
Preparing,
@ -338,28 +481,6 @@ export enum EGear { @@ -338,28 +481,6 @@ 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,
@ -381,11 +502,11 @@ export interface DeviceHms { @@ -381,11 +502,11 @@ export interface DeviceHms {
message_zh: string,
create_time: string,
update_time: string,
domain: string
domain: number
}
// TODO: 设备拓扑管理优化
// 设备信息
// 设备osd信息
export interface DeviceInfoType {
gateway: GatewayOsd, // 遥控器
dock: DockOsd, // 机场

72
src/types/drc.ts

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

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

80
src/types/flight-area.ts

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
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,3 +53,74 @@ export enum ELiveTypeName { @@ -53,3 +53,74 @@ 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,5 +2,6 @@ export enum MapDoodleEnum { @@ -2,5 +2,6 @@ export enum MapDoodleEnum {
PIN = 'pin',
POLYLINE = 'polyline',
POLYGON = 'polygon',
Close = 'off'
Close = 'off',
CIRCLE = 'circle',
}

53
src/types/task.ts

@ -3,14 +3,22 @@ import { commonColor } from '/@/utils/color' @@ -3,14 +3,22 @@ import { commonColor } from '/@/utils/color'
// 任务类型
export enum TaskType {
Immediate = 0, // 立即执行
Single = 1, // 单次定时任务
Timed = 1, // 单次定时任务
Condition = 2,
}
export const TaskTypeMap = {
[TaskType.Immediate]: 'Immediate',
[TaskType.Single]: 'Timed & One-Time',
[TaskType.Timed]: 'Timed',
[TaskType.Condition]: 'Continuous',
}
export const TaskTypeOptions = [
{ value: TaskType.Immediate, label: TaskTypeMap[TaskType.Immediate] },
{ value: TaskType.Timed, label: TaskTypeMap[TaskType.Timed] },
{ value: TaskType.Condition, label: TaskTypeMap[TaskType.Condition] },
]
// 失控动作
export enum OutOfControlAction {
ReturnToHome = 0,
@ -37,6 +45,7 @@ export enum TaskStatus { @@ -37,6 +45,7 @@ export enum TaskStatus {
Success = 3, // 完成
CanCel = 4, // 取消
Fail = 5, // 失败
Paused = 6, // 暂停
}
export const TaskStatusMap = {
@ -45,6 +54,8 @@ export const TaskStatusMap = { @@ -45,6 +54,8 @@ export const TaskStatusMap = {
[TaskStatus.Success]: 'Task completed',
[TaskStatus.CanCel]: 'Task canceled',
[TaskStatus.Fail]: 'Task failed',
[TaskStatus.Paused]: 'Paused',
}
export const TaskStatusColor = {
@ -53,6 +64,7 @@ export const TaskStatusColor = { @@ -53,6 +64,7 @@ export const TaskStatusColor = {
[TaskStatus.Success]: commonColor.NORMAL,
[TaskStatus.CanCel]: commonColor.FAIL,
[TaskStatus.Fail]: commonColor.FAIL,
[TaskStatus.Paused]: commonColor.BLUE,
}
// 任务执行 ws 消息状态
@ -93,5 +105,40 @@ export const TaskProgressWsStatusMap = { @@ -93,5 +105,40 @@ export const TaskProgressWsStatusMap = {
[TaskProgressStatus.Failed]: TaskStatus.Fail,
[TaskProgressStatus.Canceled]: TaskStatus.CanCel,
[TaskProgressStatus.Timeout]: TaskStatus.Fail,
[TaskProgressStatus.Paused]: TaskStatus.Wait,
[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,
}

111
src/utils/device-cmd.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { DroneBatteryModeEnum, DroneBatteryStateEnum } from './../types/airport-tsa';
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'
@ -42,6 +42,8 @@ export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo: @@ -42,6 +42,8 @@ export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo:
getBatteryStoreMode(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
getDroneBatteryMode(cmdItem, dock)
} else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传开关
getSdrWorkNode(cmdItem, dock)
}
})
}
@ -65,7 +67,7 @@ function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) { @@ -65,7 +67,7 @@ function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) {
// 舱盖开关
function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {
const coverState = airportProperties?.cover_state as CoverStateEnum
const coverState = airportProperties?.basic_osd?.cover_state as CoverStateEnum
if (coverState === CoverStateEnum.Close || coverState === CoverStateEnum.Failed) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText
@ -84,7 +86,7 @@ function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -84,7 +86,7 @@ function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 推杆状态
function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {
const putterState = airportProperties?.putter_state as PutterStateEnum
const putterState = airportProperties?.basic_osd?.putter_state as PutterStateEnum
if (putterState === PutterStateEnum.Close || putterState === PutterStateEnum.Failed) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText
cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText
@ -102,7 +104,7 @@ function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -102,7 +104,7 @@ function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 充电状态
function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {
const chargeState = airportProperties?.drone_charge_state
const chargeState = airportProperties?.basic_osd?.drone_charge_state
const state = chargeState?.state as ChargeStateEnum
if (!state) return
if (state === ChargeStateEnum.Charge) {
@ -122,7 +124,7 @@ function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -122,7 +124,7 @@ function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 机场存储格式化
function deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) {
const airportStorage = airportProperties?.storage
const airportStorage = airportProperties?.basic_osd?.storage
const value = getAirportStorage(airportStorage)
cmdItem.status = value
}
@ -157,7 +159,7 @@ function getBytes (bytes: number, index: number, fixed = 1) { @@ -157,7 +159,7 @@ function getBytes (bytes: number, index: number, fixed = 1) {
// 补光灯状态
function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any) {
const supplementLightState = airportProperties?.supplement_light_state
const supplementLightState = airportProperties?.basic_osd?.supplement_light_state
if (supplementLightState === SupplementLightStateEnum.Close) {
cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText
cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText
@ -175,7 +177,7 @@ function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any @@ -175,7 +177,7 @@ function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any
// 声光报警
function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {
const alarmState = airportProperties?.alarm_state
const alarmState = airportProperties?.basic_osd?.alarm_state
if (alarmState === AlarmModeEnum.CLOSE) {
cmdItem.operateText = DeviceCmdStatusText.AlarmStateCloseBtnText
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseNormalText
@ -189,7 +191,7 @@ function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -189,7 +191,7 @@ function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {
// 机场电池模式
function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) {
const batteryStoreMode = airportProperties?.battery_store_mode
const batteryStoreMode = airportProperties?.basic_osd?.battery_store_mode
if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {
cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModePlanBtnText
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanNormalText
@ -203,7 +205,7 @@ function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -203,7 +205,7 @@ function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) {
// 飞行器电池保养
function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) {
const maintenanceState = airportProperties?.drone_battery_maintenance_info?.maintenance_state
const maintenanceState = airportProperties?.work_osd?.drone_battery_maintenance_info?.maintenance_state
if (maintenanceState === DroneBatteryStateEnum.MaintenanceInProgress) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeCloseBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
@ -211,17 +213,31 @@ function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) { @@ -211,17 +213,31 @@ function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) {
cmdItem.disabled = false
} else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
cmdItem.action = DroneBatteryModeEnum.OPEN
cmdItem.disabled = true
} else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) {
cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
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
@ -269,7 +285,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -269,7 +285,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -280,7 +296,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -280,7 +296,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -293,7 +309,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -293,7 +309,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -306,7 +322,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -306,7 +322,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -319,7 +335,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -319,7 +335,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -332,7 +348,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -332,7 +348,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -345,7 +361,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -345,7 +361,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -358,7 +374,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -358,7 +374,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -371,7 +387,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -371,7 +387,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -384,7 +400,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -384,7 +400,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -394,7 +410,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -394,7 +410,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -405,7 +421,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -405,7 +421,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -415,7 +431,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -415,7 +431,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -427,7 +443,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -427,7 +443,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -437,7 +453,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -437,7 +453,7 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -448,19 +464,19 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -448,19 +464,19 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
if (cmdItem.action === DroneBatteryModeEnum.OPEN) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
// cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
cmdItem.loading = true
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
// cmdItem.loading = false
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
// cmdItem.loading = false
cmdItem.loading = false
}
} else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) {
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = true
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
} else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
cmdItem.loading = false
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@ -468,7 +484,40 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi @@ -468,7 +484,40 @@ export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], devi
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,7 +39,20 @@ export interface GeojsonPoint { @@ -39,7 +39,20 @@ export interface GeojsonPoint {
}
}
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | 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 function geographic2Coordinate (position: MapGeographicPosition): GeojsonCoordinate {
const coordinates: GeojsonCoordinate = [position.longitude, position.latitude]
@ -79,3 +92,15 @@ export function generatePoint (position: MapGeographicPosition, properties: Geoj @@ -79,3 +92,15 @@ 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 } from '/@/utils/genjson'
import { generatePoint, generateLine, generatePolygon, generateCircle } from '/@/utils/genjson'
import { MapDoodleColor, MapElementEnum } from '/@/constants/map'
function getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographicPosition {
return { height: 0, latitude: pinAMapPosition.lat, longitude: pinAMapPosition.lng }
@ -42,3 +42,8 @@ export function generatePolyContent (mapPosition: pinAMapPosition[]) { @@ -42,3 +42,8 @@ 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) : DEFAULT_PLACEHOLDER
return time ? moment(time).format(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

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

4
tsconfig.json

@ -25,6 +25,6 @@ @@ -25,6 +25,6 @@
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"src/vendors/coordtransform.js"
]
"src/vendors/coordtransform.js"
]
}

7170
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save