V1.1.0 for dock
@@ -6,15 +6,15 @@ The launch of the Cloud API mainly solves the problem of developers reinventing
|
||||
|
||||
## Docker
|
||||
|
||||
If you don't want to install the development environment, you can try deploying with docker. [Click the link to download.](https://terra-sz-hc1pro-cloudapi.oss-cn-shenzhen.aliyuncs.com/c0af9fe0d7eb4f35a8fe5b695e4d0b96/docker/cloud_api_sample_docker_1.0.0.zip)
|
||||
If you don't want to install the development environment, you can try deploying with docker. [Click the link to download.](https://terra-sz-hc1pro-cloudapi.oss-cn-shenzhen.aliyuncs.com/c0af9fe0d7eb4f35a8fe5b695e4d0b96/docker/cloud_api_sample_docker.zip)
|
||||
|
||||
## Usage
|
||||
|
||||
For more documentation, please visit the [DJI Developer Documentation](https://developer.dji.com/cn/document/209883f1-f2ad-406e-b99c-be7498df7f10).
|
||||
For more documentation, please visit the [DJI Developer Documentation](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
|
||||
|
||||
## Latest Release
|
||||
|
||||
Cloud API 1.0.0 was released on 21 March 2022. For more information, please visit the [Release Note](https://developer.dji.com/cn/document/87026f9b-e906-4809-9aba-870f569061b5).
|
||||
Cloud API 1.1.0 was released on 22 July 2022. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@ant-design/icons-vue": "^6.0.1",
|
||||
"@vitejs/plugin-legacy": "^1.6.2",
|
||||
"agora-rtc-sdk-ng": "latest",
|
||||
"agora-rtc-sdk-ng": "^4.12.1",
|
||||
"ant-design-vue": "^2.2.8",
|
||||
"axios": "^0.21.1",
|
||||
"query-string": "^7.0.1",
|
||||
@@ -1226,9 +1226,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/agora-rtc-sdk-ng": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmmirror.com/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.9.1.tgz",
|
||||
"integrity": "sha512-Jogn3TQC7VdA7uZjGYmaAs0XzgYBgGs6nGA67/dQVjqC7kiwAfkQsAuvbevE/qxrVJmLfqtDTNxP40IFvnTlgQ=="
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmmirror.com/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.12.1.tgz",
|
||||
"integrity": "sha512-kmc+ZyKDdnY/BN3iAwBs+MSgTX8Zkc6THFSIAXN9WebjZ/F+N/JXItoNEcgQe3MdTABUli6w3pZ+iObnDqVkBw==",
|
||||
"dependencies": {
|
||||
"agora-rte-extension": "^1.0.22"
|
||||
}
|
||||
},
|
||||
"node_modules/agora-rte-extension": {
|
||||
"version": "1.0.23",
|
||||
"resolved": "https://registry.npmmirror.com/agora-rte-extension/-/agora-rte-extension-1.0.23.tgz",
|
||||
"integrity": "sha512-X2cGBg+L5ZJIFU91qvMASvRsBfg1HXTktVG3YROw9wxHsILSI7jgF9R9XraLc3fNX/UjovaYAlUW+hiJe0v6Xw=="
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
@@ -8774,9 +8782,17 @@
|
||||
"requires": {}
|
||||
},
|
||||
"agora-rtc-sdk-ng": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmmirror.com/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.9.1.tgz",
|
||||
"integrity": "sha512-Jogn3TQC7VdA7uZjGYmaAs0XzgYBgGs6nGA67/dQVjqC7kiwAfkQsAuvbevE/qxrVJmLfqtDTNxP40IFvnTlgQ=="
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmmirror.com/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.12.1.tgz",
|
||||
"integrity": "sha512-kmc+ZyKDdnY/BN3iAwBs+MSgTX8Zkc6THFSIAXN9WebjZ/F+N/JXItoNEcgQe3MdTABUli6w3pZ+iObnDqVkBw==",
|
||||
"requires": {
|
||||
"agora-rte-extension": "^1.0.22"
|
||||
}
|
||||
},
|
||||
"agora-rte-extension": {
|
||||
"version": "1.0.23",
|
||||
"resolved": "https://registry.npmmirror.com/agora-rte-extension/-/agora-rte-extension-1.0.23.tgz",
|
||||
"integrity": "sha512-X2cGBg+L5ZJIFU91qvMASvRsBfg1HXTktVG3YROw9wxHsILSI7jgF9R9XraLc3fNX/UjovaYAlUW+hiJe0v6Xw=="
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@ant-design/icons-vue": "^6.0.1",
|
||||
"@vitejs/plugin-legacy": "^1.6.2",
|
||||
"agora-rtc-sdk-ng": "latest",
|
||||
"agora-rtc-sdk-ng": "^4.12.1",
|
||||
"ant-design-vue": "^2.2.8",
|
||||
"axios": "^0.21.1",
|
||||
"query-string": "^7.0.1",
|
||||
@@ -61,21 +61,39 @@
|
||||
"agora-rtc-sdk-ng",
|
||||
"ant-design-vue",
|
||||
"ant-design-vue/es",
|
||||
"ant-design-vue/es/avatar/style/css",
|
||||
"ant-design-vue/es/breadcrumb/style/css",
|
||||
"ant-design-vue/es/button/style/css",
|
||||
"ant-design-vue/es/checkbox/style/css",
|
||||
"ant-design-vue/es/col/style/css",
|
||||
"ant-design-vue/es/collapse/style/css",
|
||||
"ant-design-vue/es/date-picker/style/css",
|
||||
"ant-design-vue/es/divider/style/css",
|
||||
"ant-design-vue/es/drawer/style/css",
|
||||
"ant-design-vue/es/dropdown/style/css",
|
||||
"ant-design-vue/es/empty/style/css",
|
||||
"ant-design-vue/es/form/style/css",
|
||||
"ant-design-vue/es/image/style/css",
|
||||
"ant-design-vue/es/input/style/css",
|
||||
"ant-design-vue/es/layout/style/css",
|
||||
"ant-design-vue/es/menu/style/css",
|
||||
"ant-design-vue/es/message/style/css",
|
||||
"ant-design-vue/es/modal/style/css",
|
||||
"ant-design-vue/es/pagination/style/css",
|
||||
"ant-design-vue/es/popconfirm/style/css",
|
||||
"ant-design-vue/es/popover/style/css",
|
||||
"ant-design-vue/es/progress/style/css",
|
||||
"ant-design-vue/es/radio/style/css",
|
||||
"ant-design-vue/es/row/style/css",
|
||||
"ant-design-vue/es/select/style/css",
|
||||
"ant-design-vue/es/space/style/css",
|
||||
"ant-design-vue/es/spin/style/css",
|
||||
"ant-design-vue/es/switch/style/css",
|
||||
"ant-design-vue/es/table/style/css",
|
||||
"ant-design-vue/es/tooltip/style/css",
|
||||
"ant-design-vue/es/tree/style/css",
|
||||
"axios",
|
||||
"moment",
|
||||
"reconnecting-websocket",
|
||||
"vconsole",
|
||||
"vue",
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
export const CURRENT_CONFIG = {
|
||||
|
||||
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'
|
||||
|
||||
rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'
|
||||
gb28181Para:
|
||||
'serverIP=Please enter the server ip.&serverPort=Please enter the server port.&serverID=Please enter the server id.' +
|
||||
'&agentID=Please enter the agent id.&agentPassword=Please enter the agent password' +
|
||||
'&localPort=Please enter the local port.&channel=Please enter the channel.',
|
||||
rtspPara: 'userName=Please enter the username.&password=Please enter the password&port=Please enter the port.',
|
||||
amapKey: 'Please enter the amap key.',
|
||||
agoraAPPID: 'Please enter the agora app id.',
|
||||
agoraToken: 'Please enter the agora token.',
|
||||
agoraChannel: 'Please enter the agora channel.',
|
||||
|
||||
// license
|
||||
appId: 'Please enter the app id.', // You need to go to the development website to apply.
|
||||
appKey: 'Please enter the app key.', // You need to go to the development website to apply.
|
||||
appLicense: 'Please enter the app license.' // You need to go to the development website to apply.
|
||||
|
||||
// http
|
||||
baseURL: 'Please enter the backend access address prefix.', // This url must end with "/". Example: 'http://192.168.1.1:6789/'
|
||||
websocketURL: 'Please enter the WebSocket access address.', // Example: 'ws://192.168.1.1:6789/api/v1/ws'
|
||||
|
||||
// livestreaming
|
||||
// RTMP Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.
|
||||
rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'
|
||||
// GB28181 Note:If you don't know what these parameters mean, you can go to Pilot2 and select the GB28181 page in the cloud platform. Where the parameters same as these parameters.
|
||||
gbServerIp: 'Please enter the server ip.',
|
||||
gbServerPort: 'Please enter the server port.',
|
||||
gbServerId: 'Please enter the server id.',
|
||||
gbAgentId: 'Please enter the agent id',
|
||||
gbPassword: 'Please enter the agent password',
|
||||
gbAgentPort: 'Please enter the local port.',
|
||||
gbAgentChannel: 'Please enter the channel.',
|
||||
// RTSP
|
||||
rtspUserName: 'Please enter the username.',
|
||||
rtspPassword: 'Please enter the password.',
|
||||
rtspPort: '8554',
|
||||
// Agora
|
||||
agoraAPPID: 'Please enter the agora app id.',
|
||||
agoraToken: 'Please enter the agora temporary token.',
|
||||
agoraChannel: 'Please enter the agora channel.',
|
||||
|
||||
// map
|
||||
// You can apply on the AMap website.
|
||||
amapKey: 'Please enter the amap key.',
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import axios from 'axios'
|
||||
import { uuidv4 } from '/@/utils/uuid'
|
||||
import { CURRENT_CONFIG } from './config'
|
||||
import { message } from 'ant-design-vue'
|
||||
import router from '/@/router'
|
||||
import { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums'
|
||||
export * from './type'
|
||||
|
||||
const REQUEST_ID = 'X-Request-Id'
|
||||
function getAuthToken () {
|
||||
return localStorage.getItem('x-auth-token')
|
||||
return localStorage.getItem(ELocalStorageKey.Token)
|
||||
}
|
||||
|
||||
const instance = axios.create({
|
||||
@@ -17,7 +21,7 @@ const instance = axios.create({
|
||||
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
config.headers['X-Auth-Token'] = getAuthToken()
|
||||
config.headers[ELocalStorageKey.Token] = getAuthToken()
|
||||
// config.headers[REQUEST_ID] = uuidv4()
|
||||
config.baseURL = CURRENT_CONFIG.baseURL
|
||||
return config
|
||||
@@ -28,10 +32,15 @@ instance.interceptors.request.use(
|
||||
)
|
||||
|
||||
instance.interceptors.response.use(
|
||||
response => response,
|
||||
response => {
|
||||
console.info('URL: ' + response.config.baseURL + response.config.url, '\nData: ', response.data, '\nResponse:', response)
|
||||
if (response.data.code && response.data.code !== 0) {
|
||||
message.error(response.data.message)
|
||||
}
|
||||
return response
|
||||
},
|
||||
err => {
|
||||
const requestId = err?.config?.headers && err?.config?.headers[REQUEST_ID]
|
||||
console.info('')
|
||||
if (requestId) {
|
||||
console.info(REQUEST_ID, ':', requestId)
|
||||
}
|
||||
@@ -46,15 +55,26 @@ instance.interceptors.response.use(
|
||||
}
|
||||
// @See: https://github.com/axios/axios/issues/383
|
||||
if (!err.response || !err.response.status) {
|
||||
console.log('The network is abnormal, please check the network and try again')
|
||||
} else if (err.response?.status !== 200) {
|
||||
console.log(`ERROR_CODE: ${err.response?.status}`)
|
||||
message.error('The network is abnormal, please check the backend service and try again')
|
||||
return
|
||||
}
|
||||
if (err.response?.status === 403) {
|
||||
// window.location.href = '/'
|
||||
if (err.response?.status !== 200) {
|
||||
message.error(`ERROR_CODE: ${err.response?.status}`)
|
||||
}
|
||||
// if (err.response?.status === 403) {
|
||||
// // window.location.href = '/'
|
||||
// }
|
||||
if (err.response?.status === 401) {
|
||||
console.log(err.response)
|
||||
console.error(err.response)
|
||||
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag))
|
||||
switch (flag) {
|
||||
case EUserType.Web:
|
||||
router.push(ERouterName.PROJECT)
|
||||
break
|
||||
case EUserType.Pilot:
|
||||
router.push(ERouterName.PILOT)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(err)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ELocalStorageKey } from '../types/enums'
|
||||
import request, { IWorkspaceResponse } from '/@/api/http/request'
|
||||
import { mapLayers } from '/@/constants/mock-layers'
|
||||
import { elementGroupsReq, PostElementsBody, PutElementsBody } from '/@/types/mapLayer'
|
||||
const PREFIX = '/map/api/v1'
|
||||
const workspace_id = localStorage.getItem('workspace-id')
|
||||
const workspace_id = localStorage.getItem(ELocalStorageKey.WorkspaceId)
|
||||
type UnknownResponse = Promise<IWorkspaceResponse<unknown>>
|
||||
// get elements group
|
||||
// export const getLayers = async (reqParams: elementGroupsReq): UnknownResponse => {
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import request, { IWorkspaceResponse } from '/@/api/http/request'
|
||||
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request'
|
||||
const HTTP_PREFIX = '/manage/api/v1'
|
||||
|
||||
// login
|
||||
interface loginBody {
|
||||
export interface LoginBody {
|
||||
username: string,
|
||||
password: string
|
||||
password: string,
|
||||
flag: number,
|
||||
}
|
||||
export const login = async function (body: loginBody): Promise<IWorkspaceResponse<any>> {
|
||||
export interface BindBody {
|
||||
device_sn: string,
|
||||
user_id: string,
|
||||
workspace_id: string,
|
||||
domain?: string
|
||||
}
|
||||
export interface HmsQueryBody {
|
||||
sns: string[],
|
||||
children_sn: string,
|
||||
device_sn: string,
|
||||
language: string,
|
||||
level: number | string,
|
||||
begin_time: number,
|
||||
end_time: number,
|
||||
message: string,
|
||||
domain: string,
|
||||
}
|
||||
|
||||
export const login = async function (body: LoginBody): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/login`
|
||||
const result = await request.post(url, body)
|
||||
return result.data
|
||||
@@ -20,23 +39,23 @@ export const refreshToken = async function (body: {}): Promise<IWorkspaceRespons
|
||||
}
|
||||
|
||||
// Get Platform Info
|
||||
export const getPlatformInfo = async function (body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
export const getPlatformInfo = async function (): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/current`
|
||||
const result = await request.get(url, body)
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Get User Info
|
||||
export const getUserInfo = async function (body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
export const getUserInfo = async function (): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/users/current`
|
||||
const result = await request.get(url, body)
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Get Device Topo
|
||||
export const getDeviceTopo = async function (body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/devices`
|
||||
const result = await request.get(url, body)
|
||||
export const getDeviceTopo = async function (workspace_id: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
@@ -60,3 +79,75 @@ export const stopLivestream = async function (body: {}): Promise<IWorkspaceRespo
|
||||
const result = await request.post(url, body)
|
||||
return result.data
|
||||
}
|
||||
// Update Quality
|
||||
export const setLivestreamQuality = async function (body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/live/streams/update`
|
||||
const result = await request.post(url, body)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const getAllUsersInfo = async function (wid: string, body: IPage): Promise<CommonListResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/users/${wid}/users?&page=${body.page}&page_size=${body.page_size}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const updateUserInfo = async function (wid: string, user_id: string, body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/users/${wid}/users/${user_id}`
|
||||
const result = await request.put(url, body)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const bindDevice = async function (body: BindBody): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${body.device_sn}/binding`
|
||||
const result = await request.post(url, body)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const unbindDevice = async function (device_sn: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${device_sn}/unbinding`
|
||||
const result = await request.delete(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const getDeviceBySn = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const updateDevice = async function (body: {}, workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`
|
||||
const result = await request.put(url, body)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const getUnreadDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const updateDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`
|
||||
const result = await request.put(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: string, pagination: IPage): Promise<IListWorkspaceResponse<any>> {
|
||||
let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&pageSize=${pagination.page_size}` +
|
||||
`&level=${body.level ?? ''}&beginTime=${body.begin_time ?? ''}&endTime=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}`
|
||||
body.sns.forEach((sn: string) => {
|
||||
if (sn !== '') {
|
||||
url = url.concat(`&deviceSn=${sn}`)
|
||||
}
|
||||
})
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
import request from '/@/api/http/request'
|
||||
import request, { IPage, IWorkspaceResponse } from '/@/api/http/request'
|
||||
const HTTP_PREFIX = '/media/api/v1'
|
||||
|
||||
// Get Media Files
|
||||
export const getMediaFiles = async function (wid: string, body: {}): Promise<any> {
|
||||
const url = `${HTTP_PREFIX}/files/${wid}/files`
|
||||
const result = await request.get(url, body)
|
||||
export const getMediaFiles = async function (wid: string, pagination: IPage): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/files/${wid}/files?page=${pagination.page}&page_size=${pagination.page_size}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
// Download Media File
|
||||
export const downloadMediaFile = async function (workspaceId: string, fingerprint: string): Promise<any> {
|
||||
const url = `${HTTP_PREFIX}/files/${workspaceId}/file/${fingerprint}/url`
|
||||
const result = await request.get(url, { responseType: 'blob' })
|
||||
if (result.data.code) {
|
||||
return result.data
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,95 +1,151 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { EComponentName, EPhotoType, ERouterName } from '../types'
|
||||
import { CURRENT_CONFIG } from './http/config'
|
||||
import { EVideoPublishType, LiveStreamStatus } from '../types/live-stream'
|
||||
import { getRoot } from '/@/root'
|
||||
|
||||
const root = getRoot()
|
||||
const components = new Map()
|
||||
|
||||
export const components = new Map()
|
||||
declare let window:any
|
||||
|
||||
interface JsResponse{
|
||||
code:number,
|
||||
message:string,
|
||||
data:{}
|
||||
data:any
|
||||
}
|
||||
|
||||
export interface ThingParam {
|
||||
host: string,
|
||||
username: string,
|
||||
password: string,
|
||||
connectCallback: string
|
||||
}
|
||||
|
||||
export interface LiveshareParam {
|
||||
videoPublishType: string, // video-on-demand、video-by-manual、video-demand-aux-manual
|
||||
statusCallback: string
|
||||
}
|
||||
|
||||
export interface MapParam {
|
||||
userName: string,
|
||||
elementPreName: string
|
||||
}
|
||||
|
||||
export interface WsParam {
|
||||
host: string,
|
||||
token: string,
|
||||
connectCallback: string
|
||||
}
|
||||
|
||||
export interface ApiParam {
|
||||
host: string,
|
||||
token: string
|
||||
}
|
||||
|
||||
export interface MediaParam {
|
||||
autoUploadPhoto: boolean, // 是否自动上传图片, 非必需
|
||||
autoUploadPhotoType: number, // 自动上传的照片类型,0:原图, 1:缩略图, 非必需
|
||||
autoUploadVideo: boolean // 是否自动上传视频, 非必需
|
||||
}
|
||||
|
||||
function returnBool (response: string): boolean {
|
||||
const res: JsResponse = JSON.parse(response)
|
||||
const isError = errorHint(res)
|
||||
if (JSON.stringify(res.data) !== '{}') {
|
||||
return isError && res.data
|
||||
}
|
||||
return isError
|
||||
}
|
||||
|
||||
function returnString (response: string): string {
|
||||
const res: JsResponse = JSON.parse(response)
|
||||
return errorHint(res) ? res.data : ''
|
||||
}
|
||||
|
||||
function returnNumber (response: string): number {
|
||||
const res: JsResponse = JSON.parse(response)
|
||||
return errorHint(res) ? res.data : -1
|
||||
}
|
||||
|
||||
function errorHint (response: JsResponse): boolean {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.message)
|
||||
console.error(response.message)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export default {
|
||||
init () {
|
||||
components.set('thing', {
|
||||
init (): Map<EComponentName, any> {
|
||||
const thingParam: ThingParam = {
|
||||
host: '',
|
||||
connectCallback: '',
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
components.set('liveshare', {
|
||||
videoPublishType: 'video-demand-aux-manual', // video-on-demand、video-by-manual、video-demand-aux-manual
|
||||
statusCallback: ''
|
||||
})
|
||||
components.set('map', {
|
||||
}
|
||||
components.set(EComponentName.Thing, thingParam)
|
||||
const liveshareParam: LiveshareParam = {
|
||||
videoPublishType: EVideoPublishType.VideoDemandAuxManual,
|
||||
statusCallback: 'liveStatusCallback'
|
||||
}
|
||||
components.set(EComponentName.Liveshare, liveshareParam)
|
||||
const mapParam: MapParam = {
|
||||
userName: '',
|
||||
elementPreName: ''
|
||||
})
|
||||
components.set('ws', {
|
||||
host: '',
|
||||
elementPreName: 'PILOT'
|
||||
}
|
||||
components.set(EComponentName.Map, mapParam)
|
||||
const wsParam: WsParam = {
|
||||
host: CURRENT_CONFIG.websocketURL,
|
||||
token: '',
|
||||
connectCallback: ''
|
||||
})
|
||||
components.set('api', {
|
||||
connectCallback: 'wsConnectCallback'
|
||||
}
|
||||
components.set(EComponentName.Ws, wsParam)
|
||||
const apiParam: ApiParam = {
|
||||
host: '',
|
||||
token: ''
|
||||
})
|
||||
components.set('tsa', {
|
||||
})
|
||||
components.set('media', {
|
||||
autoUploadPhoto: true, // 是否自动上传图片, 非必需
|
||||
autoUploadPhotoType: 1, // 自动上传的照片类型,0:原图, 1:缩略图, 非必需
|
||||
autoUploadVideo: true // 是否自动上传视频, 非必需
|
||||
})
|
||||
components.set('mission', {
|
||||
})
|
||||
}
|
||||
components.set(EComponentName.Api, apiParam)
|
||||
components.set(EComponentName.Tsa, {})
|
||||
const mediaParam: MediaParam = {
|
||||
autoUploadPhoto: true,
|
||||
autoUploadPhotoType: EPhotoType.Preview,
|
||||
autoUploadVideo: true
|
||||
}
|
||||
components.set(EComponentName.Media, mediaParam)
|
||||
components.set(EComponentName.Mission, {})
|
||||
|
||||
return components
|
||||
},
|
||||
getComponentParam (key:string) {
|
||||
|
||||
getComponentParam (key:EComponentName): any {
|
||||
return components.get(key)
|
||||
},
|
||||
setComponentParam (key:string, value:any) {
|
||||
setComponentParam (key:EComponentName, value:any) {
|
||||
components.set(key, value)
|
||||
},
|
||||
loadComponent (name:string, param:any):string {
|
||||
return window.djiBridge.platformLoadComponent(name, JSON.stringify(param))
|
||||
return returnString(window.djiBridge.platformLoadComponent(name, JSON.stringify(param)))
|
||||
},
|
||||
unloadComponent (name:string) :string {
|
||||
return window.djiBridge.platformUnloadComponent(name)
|
||||
return returnString(window.djiBridge.platformUnloadComponent(name))
|
||||
},
|
||||
isComponentLoaded (module:string):string {
|
||||
return window.djiBridge.platformIsComponentLoaded(module)
|
||||
isComponentLoaded (module:string): boolean {
|
||||
return returnBool(window.djiBridge.platformIsComponentLoaded(module))
|
||||
},
|
||||
setWorkspaceId (uuid:string):string {
|
||||
return window.djiBridge.platformSetWorkspaceId(uuid)
|
||||
return returnString(window.djiBridge.platformSetWorkspaceId(uuid))
|
||||
},
|
||||
setPlatformMessage (platformName:string, title:string, desc:string):string {
|
||||
return window.djiBridge.platformSetInformation(platformName, title, desc)
|
||||
setPlatformMessage (platformName:string, title:string, desc:string): boolean {
|
||||
return returnBool(window.djiBridge.platformSetInformation(platformName, title, desc))
|
||||
},
|
||||
getRemoteControllerSN () :string {
|
||||
return window.djiBridge.platformGetRemoteControllerSN()
|
||||
return returnString(window.djiBridge.platformGetRemoteControllerSN())
|
||||
},
|
||||
getAircraftSN ():string {
|
||||
return window.djiBridge.platformGetAircraftSN()
|
||||
return returnString(window.djiBridge.platformGetAircraftSN())
|
||||
},
|
||||
stopwebview ():string {
|
||||
return window.djiBridge.platformStopSelf()
|
||||
},
|
||||
getToken () :string {
|
||||
const res:string = this.isComponentLoaded('api')
|
||||
const resObj = JSON.parse(res)
|
||||
console.log('api load status:', resObj)
|
||||
if (resObj.data === true) {
|
||||
const tokenRes = JSON.parse(window.djiBridge.apiGetToken())
|
||||
return tokenRes.data
|
||||
} else {
|
||||
console.warn('warning: not api component loaded!!')
|
||||
return ''
|
||||
}
|
||||
},
|
||||
setToken (token:string):string {
|
||||
return window.djiBridge.apiSetToken(token)
|
||||
return returnString(window.djiBridge.platformStopSelf())
|
||||
},
|
||||
setLogEncryptKey (key:string):string {
|
||||
return window.djiBridge.platformSetLogEncryptKey(key)
|
||||
@@ -98,14 +154,42 @@ export default {
|
||||
return window.djiBridge.platformClearLogEncryptKey()
|
||||
},
|
||||
getLogPath ():string {
|
||||
return window.djiBridge.platformGetLogPath()
|
||||
return returnString(window.djiBridge.platformGetLogPath())
|
||||
},
|
||||
platformVerifyLicense (appId:string, appKey:string, appLicense:string):string {
|
||||
return window.djiBridge.platformVerifyLicense(appId, appKey, appLicense)
|
||||
platformVerifyLicense (appId:string, appKey:string, appLicense:string): boolean {
|
||||
return returnBool(window.djiBridge.platformVerifyLicense(appId, appKey, appLicense))
|
||||
},
|
||||
isPlatformVerifySuccess ():string {
|
||||
return window.djiBridge.platformIsVerified()
|
||||
isPlatformVerifySuccess (): boolean {
|
||||
return returnBool(window.djiBridge.platformIsVerified())
|
||||
},
|
||||
isAppInstalled (pkgName: string): boolean {
|
||||
return returnBool(window.djiBridge.platformIsAppInstalled(pkgName))
|
||||
},
|
||||
getVersion (): string {
|
||||
return window.djiBridge.platformGetVersion()
|
||||
},
|
||||
|
||||
// thing
|
||||
thingGetConnectState (): boolean {
|
||||
return returnBool(window.djiBridge.thingGetConnectState())
|
||||
},
|
||||
|
||||
thingGetConfigs (): ThingParam {
|
||||
const thingParam = JSON.parse(window.djiBridge.thingGetConfigs())
|
||||
return thingParam.code === 0 ? JSON.parse(thingParam.data) : {}
|
||||
},
|
||||
|
||||
// api
|
||||
getToken () : string {
|
||||
return returnString(window.djiBridge.apiGetToken())
|
||||
},
|
||||
setToken (token:string):string {
|
||||
return returnString(window.djiBridge.apiSetToken(token))
|
||||
},
|
||||
getHost (): string {
|
||||
return returnString(window.djiBridge.apiGetHost())
|
||||
},
|
||||
|
||||
// liveshare
|
||||
/**
|
||||
*
|
||||
@@ -114,8 +198,8 @@ export default {
|
||||
* video-by-manual:手动点播,配置好直播类型参数之后,在图传页面可修改直播参数,停止直播
|
||||
* video-demand-aux-manual: 混合模式,支持服务器点播,以及图传页面修改直播参数,停止直播
|
||||
*/
|
||||
setVideoPublishType (type:string):string {
|
||||
return window.djiBridge.liveshareSetVideoPublishType(type)
|
||||
setVideoPublishType (type:string): boolean {
|
||||
return returnBool(window.djiBridge.liveshareSetVideoPublishType(type))
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -123,8 +207,8 @@ export default {
|
||||
* @returns
|
||||
* type: liveshare type, 0:unknown, 1:agora, 2:rtmp, 3:rtsp, 4:gb28181
|
||||
*/
|
||||
getLiveshareConfig () {
|
||||
return window.djiBridge.liveshareGetConfig()
|
||||
getLiveshareConfig (): string {
|
||||
return returnString(window.djiBridge.liveshareGetConfig())
|
||||
},
|
||||
|
||||
setLiveshareConfig (type:number, params:string):string {
|
||||
@@ -134,50 +218,66 @@ export default {
|
||||
setLiveshareStatusCallback (callbackFunc:string) :string {
|
||||
return window.djiBridge.liveshareSetStatusCallback(callbackFunc)
|
||||
},
|
||||
getLiveshareStatus () {
|
||||
return window.djiBridge.liveshareGetStatus()
|
||||
getLiveshareStatus (): LiveStreamStatus {
|
||||
return JSON.parse(JSON.parse(window.djiBridge.liveshareGetStatus()).data)
|
||||
},
|
||||
startLiveshare ():string {
|
||||
return window.djiBridge.liveshareStartLive()
|
||||
startLiveshare (): boolean {
|
||||
return returnBool(window.djiBridge.liveshareStartLive())
|
||||
},
|
||||
stopLiveshare ():string {
|
||||
return window.djiBridge.liveshareStopLive()
|
||||
stopLiveshare (): boolean {
|
||||
return returnBool(window.djiBridge.liveshareStopLive())
|
||||
},
|
||||
// WebSocket
|
||||
wsGetConnectState (): boolean {
|
||||
return returnBool(window.djiBridge.wsGetConnectState())
|
||||
},
|
||||
wsConnect (host: string, token: string, callback: string): string {
|
||||
return window.djiBridge.wsConnect(host, token, callback)
|
||||
},
|
||||
wsDisconnect (): string {
|
||||
return window.djiBridge.wsConnect()
|
||||
},
|
||||
wsSend (message: string): string {
|
||||
return window.djiBridge.wsSend(message)
|
||||
},
|
||||
// media
|
||||
setAutoUploadPhoto (auto:boolean):string {
|
||||
return window.djiBridge.mediaSetAutoUploadPhoto(auto)
|
||||
},
|
||||
getAutoUploadPhoto () {
|
||||
return window.djiBridge.mediaGetAutoUploadPhoto()
|
||||
getAutoUploadPhoto (): boolean {
|
||||
return returnBool(window.djiBridge.mediaGetAutoUploadPhoto())
|
||||
},
|
||||
setUploadPhotoType (type:number):string {
|
||||
return window.djiBridge.mediaSetUploadPhotoType(type)
|
||||
},
|
||||
getUploadPhotoType () {
|
||||
return window.djiBridge.mediaGetUploadPhotoType()
|
||||
getUploadPhotoType (): number {
|
||||
return returnNumber(window.djiBridge.mediaGetUploadPhotoType())
|
||||
},
|
||||
setAutoUploadVideo (auto:boolean):string {
|
||||
return window.djiBridge.mediaSetAutoUploadVideo(auto)
|
||||
},
|
||||
getAutoUploadVideo () {
|
||||
return window.djiBridge.mediaGetAutoUploadVideo()
|
||||
getAutoUploadVideo (): boolean {
|
||||
return returnBool(window.djiBridge.mediaGetAutoUploadVideo())
|
||||
},
|
||||
setDownloadOwner (rcIndex:number):string {
|
||||
return window.djiBridge.mediaSetDownloadOwner(rcIndex)
|
||||
},
|
||||
getDownloadOwner () {
|
||||
return window.djiBridge.mediaGetDownloadOwner()
|
||||
getDownloadOwner (): number {
|
||||
return returnNumber(window.djiBridge.mediaGetDownloadOwner())
|
||||
},
|
||||
onBackClickReg () {
|
||||
window.djiBridge.onBackClick = () => {
|
||||
if (root.$router.currentRoute.value.path === '/pilot-home') {
|
||||
console.log(root.$router.currentRoute.value.path)
|
||||
if (root.$router.currentRoute.value.path === '/' + ERouterName.PILOT_HOME) {
|
||||
return false
|
||||
} else {
|
||||
console.log(root.$router.currentRoute.value.path)
|
||||
history.go(-1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
onStopPlatform () {
|
||||
window.djiBridge.onStopPlatform = () => {
|
||||
localStorage.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
import request from '/@/api/http/request'
|
||||
import request, { IPage, IWorkspaceResponse } from '/@/api/http/request'
|
||||
const HTTP_PREFIX = '/wayline/api/v1'
|
||||
|
||||
export interface CreatePlan {
|
||||
name: string,
|
||||
file_id: string,
|
||||
dock_sn: string,
|
||||
immediate: boolean,
|
||||
type: string,
|
||||
}
|
||||
|
||||
// Get Wayline Files
|
||||
export const getWaylineFiles = async function (wid: string, body: {}): Promise<any> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${wid}/waylines?` + 'order_by=' + body.order_by + '&page=' + body.page + '&page_size=' + body.page_size
|
||||
export const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${wid}/waylines?order_by=${body.order_by}&page=${body.page}&page_size=${body.page_size}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Download Wayline File
|
||||
export const downloadWaylineFile = async function (workspaceId: string, waylineId: string): Promise<any> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}/url`
|
||||
const result = await request.get(url, { responseType: 'blob' })
|
||||
if (result.data.code) {
|
||||
return result.data
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Delete Wayline File
|
||||
export const deleteWaylineFile = async function (workspaceId: string, waylineId: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}`
|
||||
const result = await request.delete(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Create Wayline Job
|
||||
export const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks`
|
||||
const result = await request.post(url, plan)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Get Wayline Jobs
|
||||
export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}`
|
||||
const result = await request.get(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
// Execute Wayline Job
|
||||
export const executeWaylineJobs = async function (workspaceId: string, plan_id: string): Promise<IWorkspaceResponse<any>> {
|
||||
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${plan_id}`
|
||||
const result = await request.post(url)
|
||||
return result.data
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
import { ELocalStorageKey } from '../types/enums'
|
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config'
|
||||
|
||||
let socket = {}
|
||||
let socket: ReconnectingWebSocket
|
||||
|
||||
export default {
|
||||
init (getMsgFunc) {
|
||||
const token = localStorage.getItem('x-auth-token')
|
||||
const wspath =
|
||||
config.websocketURL + '?x-auth-token=' + escape(token)
|
||||
socket = new ReconnectingWebSocket(wspath)
|
||||
init (getMsgFunc: any) {
|
||||
const token: string = localStorage.getItem(ELocalStorageKey.Token)!
|
||||
const wspath = config.websocketURL + '?x-auth-token=' + encodeURI(token)
|
||||
socket = new ReconnectingWebSocket(wspath, '', { maxRetries: 5 })
|
||||
socket.onopen = this.onOpen
|
||||
socket.onerror = this.onError
|
||||
socket.onmessage = getMsgFunc
|
||||
@@ -18,13 +18,16 @@ export default {
|
||||
onOpen () {
|
||||
console.log('ws opened')
|
||||
},
|
||||
onError (err) {
|
||||
onError (err: any) {
|
||||
console.error(err)
|
||||
},
|
||||
onClose () {
|
||||
console.log('ws closed')
|
||||
},
|
||||
sendMsg (data) {
|
||||
this.socket.send(data)
|
||||
sendMsg (data: any) {
|
||||
socket.send(data)
|
||||
},
|
||||
close () {
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
Depois Largura: | Altura: | Tamanho: 14 KiB |
|
Depois Largura: | Altura: | Tamanho: 34 KiB |
|
Depois Largura: | Altura: | Tamanho: 8.7 KiB |
|
Depois Largura: | Altura: | Tamanho: 6.4 KiB |
|
Depois Largura: | Altura: | Tamanho: 38 KiB |
|
Depois Largura: | Altura: | Tamanho: 50 KiB |
|
Depois Largura: | Altura: | Tamanho: 8.8 KiB |
@@ -5,19 +5,378 @@
|
||||
class="g-action-panle"
|
||||
:style="{ right: drawVisible ? '316px' : '16px' }"
|
||||
>
|
||||
<div class="g-action-item" @click="draw('pin', true)">
|
||||
<a-button type="primary">PIN</a-button>
|
||||
<div :class="state.currentType === 'pin' ? 'g-action-item selection' : 'g-action-item'" @click="draw('pin', true)">
|
||||
<a><a-image :src="pin" :preview="false" /></a>
|
||||
</div>
|
||||
<div class="g-action-item" @click="draw('polyline', true)">
|
||||
<a-button type="primary">Line</a-button>
|
||||
<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="g-action-item" @click="draw('polygon', true)">
|
||||
<a-button type="primary">Poly</a-button>
|
||||
<div :class="state.currentType === 'polygon' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polygon', true)">
|
||||
<a><BorderOutlined class="fz18" /></a>
|
||||
</div>
|
||||
<div v-if="mouseMode" class="g-action-item" @click="draw('off', false)">
|
||||
<a-button type="primary" danger>X</a-button>
|
||||
<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%;">
|
||||
<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;">
|
||||
<a-tooltip :title="osdVisible.model">
|
||||
<div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
|
||||
<span><a-image :src="M30" :preview="false"/></span>
|
||||
<span>{{ osdVisible.model }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="osd">
|
||||
<a-row>
|
||||
<a-col span="16" :style="deviceInfo.device.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">{{ EModeCode[deviceInfo.device.mode_code] }}</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Signal strength">
|
||||
<span>HD</span>
|
||||
<span class="ml10">{{ deviceInfo.gateway?.transmission_signal_quality }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="RC Battery Level">
|
||||
<span><ThunderboltOutlined class="fz14"/></span>
|
||||
<span class="ml10">{{ deviceInfo.gateway && deviceInfo.gateway.capacity_percent !== str ? deviceInfo.gateway.capacity_percent + ' %' : deviceInfo.gateway.capacity_percent }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Drone Battery Level">
|
||||
<span><ThunderboltOutlined class="fz14"/></span>
|
||||
<span class="ml10">{{ deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device.battery.capacity_percent + ' %' : deviceInfo.device.battery.capacity_percent }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-tooltip title="RTK Fixed">
|
||||
<a-col span="6" class="flex-row flex-align-center flex-justify-start">
|
||||
<span>Fixed</span>
|
||||
<span class="ml10 circle" :style="deviceInfo.device.position_state.is_fixed === 1 ? 'backgroud: rgb(25,190,107);' : ' background: red;'"></span>
|
||||
</a-col>
|
||||
</a-tooltip>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="GPS">
|
||||
<span>GPS</span>
|
||||
<span class="ml10">{{ deviceInfo.device.position_state.gps_number }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="RTK">
|
||||
<span><TrademarkOutlined class="fz14"/></span>
|
||||
<span class="ml10">{{ deviceInfo.device.position_state.rtk_number }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Flight Mode">
|
||||
<span><ControlOutlined class="fz16" /></span>
|
||||
<span class="ml10">{{ EGear[deviceInfo.device.gear] }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Altitude above sea level">
|
||||
<span>ASL</span>
|
||||
<span class="ml10">{{ deviceInfo.device.height === str ? str : deviceInfo.device.height.toFixed(2) + ' m'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Altitude above takeoff level">
|
||||
<span>ALT</span>
|
||||
<span class="ml10">{{ deviceInfo.device.elevation === str ? str : deviceInfo.device.elevation.toFixed(2) + ' m' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Distance to Home Point">
|
||||
<span>H</span>
|
||||
<span class="ml10">{{ deviceInfo.device.home_distance === str ? str : deviceInfo.device.home_distance.toFixed(2) + ' m' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Horizontal Speed">
|
||||
<span>H.S</span>
|
||||
<span class="ml10">{{ deviceInfo.device.horizontal_speed === str ? str : deviceInfo.device.horizontal_speed.toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Vertical Speed">
|
||||
<span>V.S</span>
|
||||
<span class="ml10">{{ deviceInfo.device.vertical_speed === str ? str : deviceInfo.device.vertical_speed.toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Wind Speed">
|
||||
<span>W.S</span>
|
||||
<span class="ml10">{{ deviceInfo.device.wind_speed === str ? str : (deviceInfo.device.wind_speed / 10).toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="battery-slide" v-if="deviceInfo.device.battery.remain_flight_time !== 0">
|
||||
<div style="background: #535759;" class="width-100"></div>
|
||||
<div class="capacity-percent" :style="{ width: deviceInfo.device.battery.capacity_percent + '%'}"></div>
|
||||
<div class="return-home" :style="{ width: deviceInfo.device.battery.return_home_power + '%'}"></div>
|
||||
<div class="landing" :style="{ width: deviceInfo.device.battery.landing_power + '%'}"></div>
|
||||
<div class="white-point" :style="{ left: deviceInfo.device.battery.landing_power + '%'}"></div>
|
||||
<div class="battery" :style="{ left: deviceInfo.device.battery.capacity_percent + '%' }">
|
||||
{{ Math.floor(deviceInfo.device.battery.remain_flight_time / 60) }}:
|
||||
{{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="osdVisible.visible && osdVisible.is_dock" class="osd-panel fz12" style="height: 280px;">
|
||||
<div class="fz16 pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 10%;">
|
||||
<span>{{ osdVisible.gateway_callsign }}</span>
|
||||
<span><a style="color: white;" @click="() => osdVisible.visible = false"><CloseOutlined /></a></span>
|
||||
</div>
|
||||
<div style="height: 45%; border-bottom: 1px solid #515151;">
|
||||
<div class="flex-column flex-align-center flex-justify-center" style="float: left; width: 60px; height: 100%; background: #2d2d2d;">
|
||||
<a-tooltip :title="osdVisible.model">
|
||||
<div class="flex-column flex-align-center flex-justify-center" style="width: 90%;">
|
||||
<span><RobotFilled style="font-size: 48px;"/></span>
|
||||
<span class="mt10">Dock</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="osd">
|
||||
<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-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>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="12">
|
||||
<a-tooltip title="Last login">
|
||||
<span><FieldTimeOutlined /></span>
|
||||
<span class="ml10">{{ new Date(deviceInfo.dock.first_power_on).toLocaleString() }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="12">
|
||||
<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 v-else><GlobalOutlined /></span>
|
||||
</span>
|
||||
<span class="ml10" >{{ deviceInfo.dock.network_state.rate }} KB/S</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Media File Remain Upload">
|
||||
<span><CloudUploadOutlined class="fz14"/></span>
|
||||
<span class="ml10">{{ deviceInfo.dock.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>
|
||||
</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>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Wind Speed">
|
||||
<span>W.S</span>
|
||||
<span class="ml10">{{ deviceInfo.dock.wind_speed === str ? str : (deviceInfo.dock.wind_speed / 10).toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Rainfall">
|
||||
<span>🌧</span>
|
||||
<span class="ml10">{{ deviceInfo.dock.rainfall === str ? str : deviceInfo.dock.rainfall + ' mm/h' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Environment Temperature">
|
||||
<span>°C</span>
|
||||
<span class="ml10">{{ deviceInfo.dock.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>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Dock Humidity">
|
||||
<span>💦</span>
|
||||
<span class="ml10">{{ deviceInfo.dock.humidity === str ? str : deviceInfo.dock.humidity }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Working Voltage">
|
||||
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 16px; text-align: center; float: left;">V</span>
|
||||
<span class="ml10">{{ deviceInfo.dock.working_voltage === str ? str : deviceInfo.dock.working_voltage + ' mV' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Working Current">
|
||||
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center; float: left;" >A</span>
|
||||
<span class="ml10">{{ deviceInfo.dock.working_current === str ? str : deviceInfo.dock.working_current + ' mA' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 45%;">
|
||||
<div class="flex-column flex-align-center flex-justify-center" style="float: left; width: 60px; height: 100%; background: #2d2d2d;">
|
||||
<a-tooltip :title="osdVisible.model">
|
||||
<div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
|
||||
<span><a-image :src="M30" :preview="false"/></span>
|
||||
<span>M30</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="osd">
|
||||
<a-row>
|
||||
<a-col span="16" :style="!deviceInfo.device || deviceInfo.device?.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'">
|
||||
{{ !deviceInfo.device ? EModeCode[EModeCode.Disconnected] : EModeCode[deviceInfo.device?.mode_code] }}</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Upward Quality">
|
||||
<span><SignalFilled /><ArrowUpOutlined style="font-size: 9px; vertical-align: top;" /></span>
|
||||
<span class="ml10">{{ deviceInfo.dock.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>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Drone Battery Level">
|
||||
<span><ThunderboltOutlined class="fz14"/></span>
|
||||
<span class="ml10">{{ deviceInfo.device && deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device?.battery.capacity_percent + ' %' : str }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-tooltip title="RTK Fixed">
|
||||
<a-col span="6" class="flex-row flex-align-center flex-justify-start">
|
||||
<span>Fixed</span>
|
||||
<span class="ml10 circle" :style="deviceInfo.device?.position_state.is_fixed === 1 ? 'backgroud: rgb(25,190,107);' : ' background: red;'"></span>
|
||||
</a-col>
|
||||
</a-tooltip>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="GPS">
|
||||
<span>GPS</span>
|
||||
<span class="ml10">{{ deviceInfo.device ? deviceInfo.device.position_state.gps_number : str }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="RTK">
|
||||
<span><TrademarkOutlined class="fz14"/></span>
|
||||
<span class="ml10">{{ deviceInfo.device ? deviceInfo.device.position_state.rtk_number : str }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Flight Mode">
|
||||
<span><ControlOutlined class="fz16" /></span>
|
||||
<span class="ml10">{{ deviceInfo.device ? EGear[deviceInfo.device?.gear] : str }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Altitude above sea level">
|
||||
<span>ASL</span>
|
||||
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.height === str ? str : deviceInfo.device?.height.toFixed(2) + ' m'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Altitude above takeoff level">
|
||||
<span>ALT</span>
|
||||
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.elevation === str ? str : deviceInfo.device?.elevation.toFixed(2) + ' m' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Distance to Home Point">
|
||||
<span style="border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center; display: block; float: left;" >H</span>
|
||||
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.home_distance === str ? str : deviceInfo.device?.home_distance.toFixed(2) + ' m' }}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Horizontal Speed">
|
||||
<span>H.S</span>
|
||||
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device?.horizontal_speed === str ? str : deviceInfo.device?.horizontal_speed.toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Vertical Speed">
|
||||
<span>V.S</span>
|
||||
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.vertical_speed === str ? str : deviceInfo.device?.vertical_speed.toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-tooltip title="Wind Speed">
|
||||
<span>W.S</span>
|
||||
<span class="ml10">{{ !deviceInfo.device || deviceInfo.device.wind_speed === str ? str : (deviceInfo.device?.wind_speed / 10).toFixed(2) + ' m/s'}}</span>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="battery-slide" v-if="deviceInfo.device && deviceInfo.device.battery.remain_flight_time !== 0" style="border: 1px solid red">
|
||||
<div style="background: #535759;" class="width-100"></div>
|
||||
<div class="capacity-percent" :style="{ width: deviceInfo.device.battery.capacity_percent + '%'}"></div>
|
||||
<div class="return-home" :style="{ width: deviceInfo.device.battery.return_home_power + '%'}"></div>
|
||||
<div class="landing" :style="{ width: deviceInfo.device.battery.landing_power + '%'}"></div>
|
||||
<div class="white-point" :style="{ left: deviceInfo.device.battery.landing_power + '%'}"></div>
|
||||
<div class="battery" :style="{ left: deviceInfo.device.battery.capacity_percent + '%' }">
|
||||
{{ Math.floor(deviceInfo.device.battery.remain_flight_time / 60) }}:
|
||||
{{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -40,13 +399,43 @@ import { MapDoodleEnum } from '/@/types/map-enum'
|
||||
import { PostElementsBody } from '/@/types/mapLayer'
|
||||
import { uuidv4 } from '/@/utils/uuid'
|
||||
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'
|
||||
import { deviceTsaUpdate } from '/@/hooks/use-g-map-tsa'
|
||||
import { DeviceOsd, DeviceStatus, DockOsd, EGear, EModeCode, GatewayOsd, EDockModeCode } 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
|
||||
} from '@ant-design/icons-vue'
|
||||
import { EDeviceTypeName } from '../types'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BorderOutlined,
|
||||
LineOutlined,
|
||||
CloseOutlined,
|
||||
ControlOutlined,
|
||||
TrademarkOutlined,
|
||||
ThunderboltOutlined,
|
||||
SignalFilled,
|
||||
GlobalOutlined,
|
||||
HistoryOutlined,
|
||||
CloudUploadOutlined,
|
||||
FieldTimeOutlined,
|
||||
CloudOutlined,
|
||||
CloudFilled,
|
||||
FolderOpenOutlined,
|
||||
RobotFilled,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined
|
||||
},
|
||||
name: 'GMap',
|
||||
props: {},
|
||||
setup () {
|
||||
const useMouseToolHook = useMouseTool()
|
||||
const useGMapManageHook = useGMapManage()
|
||||
const deviceTsaUpdateHook = ref()
|
||||
|
||||
const mouseMode = ref(false)
|
||||
const store = useMyStore()
|
||||
@@ -54,6 +443,86 @@ export default defineComponent({
|
||||
currentType: '',
|
||||
coverIndex: 0
|
||||
})
|
||||
const str: string = '--'
|
||||
const deviceInfo = reactive({
|
||||
gateway: {
|
||||
capacity_percent: str,
|
||||
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: {
|
||||
gear: -1,
|
||||
mode_code: EModeCode.Disconnected,
|
||||
height: str,
|
||||
home_distance: str,
|
||||
horizontal_speed: str,
|
||||
vertical_speed: str,
|
||||
wind_speed: str,
|
||||
wind_direction: str,
|
||||
elevation: str,
|
||||
position_state: {
|
||||
gps_number: str,
|
||||
is_fixed: 0,
|
||||
rtk_number: str
|
||||
},
|
||||
battery: {
|
||||
capacity_percent: str,
|
||||
landing_power: str,
|
||||
remain_flight_time: 0,
|
||||
return_home_power: str,
|
||||
},
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
} as DeviceOsd
|
||||
})
|
||||
const shareId = computed(() => {
|
||||
return store.state.layerBaseInfo.share
|
||||
})
|
||||
@@ -63,6 +532,57 @@ export default defineComponent({
|
||||
const drawVisible = computed(() => {
|
||||
return store.state.drawVisible
|
||||
})
|
||||
const osdVisible = computed(() => {
|
||||
return store.state.osdVisible
|
||||
})
|
||||
|
||||
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)
|
||||
store.state.deviceStatusEvent.deviceOnline = {} as DeviceStatus
|
||||
}
|
||||
if (Object.keys(data.deviceOffline).length !== 0) {
|
||||
deviceTsaUpdateHook.value.removeMarker(data.deviceOffline.sn)
|
||||
if ((data.deviceOffline.sn === osdVisible.value.sn) || (osdVisible.value.is_dock && data.deviceOffline.sn === osdVisible.value.gateway_sn)) {
|
||||
osdVisible.value.visible = false
|
||||
store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
|
||||
}
|
||||
store.state.deviceStatusEvent.deviceOffline = {}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
if (osdVisible.value.visible && osdVisible.value.sn !== '') {
|
||||
deviceInfo.device = data.deviceInfo[osdVisible.value.sn]
|
||||
}
|
||||
}
|
||||
if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) {
|
||||
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]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
watch(
|
||||
() => store.state.wsEvent,
|
||||
newData => {
|
||||
@@ -144,7 +664,9 @@ export default defineComponent({
|
||||
async function postPinPositionResource (obj) {
|
||||
const req = getPinPositionResource(obj)
|
||||
setLayers(req)
|
||||
updateCoordinates('gcj02-wgs84', req)
|
||||
const coordinates = req.resource.content.geometry.coordinates
|
||||
updateCoordinates('gcj02-wgs84', req);
|
||||
(req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2])
|
||||
const result = await postElementsReq(shareId.value, req)
|
||||
obj.setExtData({ id: req.id, name: req.name })
|
||||
store.state.coverList.push(obj)
|
||||
@@ -281,7 +803,16 @@ export default defineComponent({
|
||||
return {
|
||||
draw,
|
||||
mouseMode,
|
||||
drawVisible
|
||||
drawVisible,
|
||||
osdVisible,
|
||||
pin,
|
||||
state,
|
||||
M30,
|
||||
deviceInfo,
|
||||
EGear,
|
||||
EModeCode,
|
||||
str,
|
||||
EDockModeCode,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -296,16 +827,77 @@ export default defineComponent({
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
.g-action-item {
|
||||
padding-top: 8px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: white;
|
||||
color: $primary;
|
||||
border-radius: 2px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.g-action-item:hover {
|
||||
border: 1px solid $primary;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.amap-logo {
|
||||
opacity: 0;
|
||||
}
|
||||
.amap-copyright {
|
||||
opacity: 0;
|
||||
.selection {
|
||||
border: 1px solid $primary;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.osd-panel {
|
||||
position: absolute;
|
||||
left: 350px;
|
||||
top: 10px;
|
||||
width: 480px;
|
||||
height: 160px;
|
||||
background: black;
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.osd > div {
|
||||
padding-top: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.battery-slide {
|
||||
.capacity-percent {
|
||||
background: #00ee8b;
|
||||
}
|
||||
.return-home {
|
||||
background: #ff9f0a;
|
||||
}
|
||||
.landing {
|
||||
background: #f5222d;
|
||||
}
|
||||
.white-point {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
bottom: -0.5px;
|
||||
}
|
||||
.battery {
|
||||
background: #141414;
|
||||
color: #00ee8b;
|
||||
margin-top: -10px;
|
||||
height: 20px;
|
||||
width: auto;
|
||||
border-left: 1px solid #00ee8b;
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
.battery-slide > div {
|
||||
position: absolute;
|
||||
min-height: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,92 +1,168 @@
|
||||
<template>
|
||||
<div class="media-panel-wrapper">
|
||||
<div class="header">Media</div>
|
||||
<a-button type="primary" style="margin-top:20px" @click="onRefresh"
|
||||
>Refresh</a-button
|
||||
>
|
||||
<a-table class="media-table" :columns="columns" :data-source="data">
|
||||
<template #name="{ text, record }">
|
||||
<a :href="record.preview_url">{{ text }}</a>
|
||||
</template>
|
||||
<template #action>
|
||||
<span class="action-area">
|
||||
action
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<div class="header">Media Files</div>
|
||||
<a-spin :spinning="loading" :delay="1000" tip="downloading" size="large">
|
||||
<div class="media-panel-wrapper">
|
||||
<a-table class="media-table" :columns="columns" :data-source="mediaData.data" row-key="fingerprint"
|
||||
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
|
||||
<template v-for="col in ['name', 'path']" #[col]="{ text }" :key="col">
|
||||
<a-tooltip :title="text">
|
||||
<a v-if="col === 'name'">{{ text }}</a>
|
||||
<span v-else>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #original="{ text }">
|
||||
{{ text }}
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-tooltip title="download">
|
||||
<a class="fz18" @click="downloadMedia(record)"><DownloadOutlined /></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from '@vue/reactivity'
|
||||
import { getMediaFiles } from '/@/api/media'
|
||||
import { TableState } from 'ant-design-vue/lib/table/interface'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { IPage } from '../api/http/type'
|
||||
import { ELocalStorageKey } from '../types/enums'
|
||||
import { downloadFile } from '../utils/common'
|
||||
import { downloadMediaFile, getMediaFiles } from '/@/api/media'
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue'
|
||||
import { Pagination } from 'ant-design-vue'
|
||||
import { load } from '@amap/amap-jsapi-loader'
|
||||
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
const loading = ref(false)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'FileName',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
title: 'File Name',
|
||||
dataIndex: 'file_name',
|
||||
ellipsis: true,
|
||||
slots: { customRender: 'name' }
|
||||
},
|
||||
{
|
||||
title: 'FileSize',
|
||||
dataIndex: 'size',
|
||||
key: 'size'
|
||||
title: 'File Path',
|
||||
dataIndex: 'file_path',
|
||||
ellipsis: true,
|
||||
slots: { customRender: 'path' }
|
||||
},
|
||||
// {
|
||||
// title: 'FileSize',
|
||||
// dataIndex: 'size',
|
||||
// },
|
||||
{
|
||||
title: 'Drone',
|
||||
dataIndex: 'drone'
|
||||
},
|
||||
{
|
||||
title: 'PayloadType',
|
||||
dataIndex: 'payload_type',
|
||||
key: 'payload_type',
|
||||
ellipsis: true
|
||||
title: 'Payload Type',
|
||||
dataIndex: 'payload'
|
||||
},
|
||||
{
|
||||
title: 'Original',
|
||||
dataIndex: 'is_original',
|
||||
slots: { customRender: 'original' }
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
dataIndex: 'create_time'
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
key: 'action',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
const data = ref([
|
||||
{
|
||||
key: '1',
|
||||
name: 'name1',
|
||||
size: 32,
|
||||
payload_type: 'PM320_DUAL',
|
||||
preview_url: ''
|
||||
}
|
||||
])
|
||||
const onRefresh = async () => {
|
||||
const wid = localStorage.getItem('workspace-id')
|
||||
data.value = []
|
||||
const index = 1
|
||||
const res = await getMediaFiles(wid, {})
|
||||
res.data.forEach(ele => {
|
||||
data.value.push({
|
||||
key: index.toString(),
|
||||
name: ele.file_name
|
||||
})
|
||||
})
|
||||
console.log(res)
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
const paginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
type Pagination = TableState['pagination']
|
||||
|
||||
interface MediaFile {
|
||||
fingerprint: string,
|
||||
drone: string,
|
||||
payload: string,
|
||||
is_original: string,
|
||||
file_name: string,
|
||||
file_path: string,
|
||||
create_time: string,
|
||||
}
|
||||
|
||||
const mediaData = reactive({
|
||||
data: [] as MediaFile[]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getFiles()
|
||||
})
|
||||
|
||||
function getFiles () {
|
||||
getMediaFiles(workspaceId, body).then(res => {
|
||||
mediaData.data = res.data.list
|
||||
paginationProp.total = res.data.pagination.total
|
||||
paginationProp.current = res.data.pagination.page
|
||||
console.info(mediaData.data[0])
|
||||
})
|
||||
}
|
||||
|
||||
function refreshData (page: Pagination) {
|
||||
body.page = page?.current!
|
||||
body.page_size = page?.pageSize!
|
||||
getFiles()
|
||||
}
|
||||
|
||||
function downloadMedia (media: MediaFile) {
|
||||
loading.value = true
|
||||
downloadMediaFile(workspaceId, media.fingerprint).then(res => {
|
||||
if (res.code && res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const suffix = media.file_name.substring(media.file_name.lastIndexOf('.'))
|
||||
const data = new Blob([res.data], { type: suffix === '.mp4' ? 'video/mp4' : 'image/jpeg' })
|
||||
downloadFile(data, media.file_name)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.media-panel-wrapper {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
.media-table {
|
||||
background: #fff;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 16px 24px;
|
||||
font-size: 20px;
|
||||
text-align: start;
|
||||
color: #000;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.action-area {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-align: start;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="header">Task Plan Library</div>
|
||||
<div class="plan-panel-wrapper">
|
||||
<!-- <router-link :to=" '/' + ERouterName.CREATE_PLAN">
|
||||
<a-button type="primary">Create Plan</a-button>
|
||||
</router-link> -->
|
||||
<a-table class="plan-table" :columns="columns" :data-source="plansData.data" row-key="job_id"
|
||||
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
|
||||
<template #status="{ record }">
|
||||
<span v-if="taskProgressMap[record.bid]">
|
||||
<a-progress type="line" :percent="taskProgressMap[record.bid]?.progress?.percent"
|
||||
:status="taskProgressMap[record.bid]?.status === ETaskStatus.FAILED ? 'exception' : taskProgressMap[record.bid]?.status === ETaskStatus.OK ? 'success' : 'normal'">
|
||||
<template #format="percent">
|
||||
<a-tooltip :title="taskProgressMap[record.bid]?.status">
|
||||
<div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden; position: absolute; left: 5px; top: -12px;">
|
||||
{{ percent }}% {{ taskProgressMap[record.bid]?.status }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-progress>
|
||||
</span>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<span class="action-area">
|
||||
<a-popconfirm
|
||||
title="Are you sure execute this task?"
|
||||
ok-text="Yes"
|
||||
cancel-text="No"
|
||||
@confirm="executePlan(record.job_id)"
|
||||
>
|
||||
<a-button type="primary" size="small">Execute</a-button>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from '@vue/reactivity'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { TableState } from 'ant-design-vue/lib/table/interface'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { IPage } from '../api/http/type'
|
||||
import { executeWaylineJobs, getWaylineJobs } from '../api/wayline'
|
||||
import { getRoot } from '../root'
|
||||
import { useMyStore } from '../store'
|
||||
import { ELocalStorageKey, ERouterName } from '../types/enums'
|
||||
import router from '/@/router'
|
||||
import { ETaskStatus } from '/@/types/wayline'
|
||||
|
||||
const store = useMyStore()
|
||||
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
const root = getRoot()
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
const paginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Plan Name',
|
||||
dataIndex: 'job_name'
|
||||
},
|
||||
{
|
||||
title: 'Flight Route Name',
|
||||
dataIndex: 'file_name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Dock Name',
|
||||
dataIndex: 'dock_name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Creator',
|
||||
dataIndex: 'username',
|
||||
},
|
||||
{
|
||||
title: 'Updated',
|
||||
dataIndex: 'update_time'
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
key: 'status',
|
||||
width: 200,
|
||||
slots: { customRender: 'status' }
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Action',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
type Pagination = TableState['pagination']
|
||||
|
||||
interface TaskPlan {
|
||||
bid: string,
|
||||
job_id: string,
|
||||
job_name: string,
|
||||
file_name: string,
|
||||
dock_name: string,
|
||||
username: string,
|
||||
create_time: string,
|
||||
}
|
||||
|
||||
const plansData = reactive({
|
||||
data: [] as TaskPlan[]
|
||||
})
|
||||
|
||||
function createPlan () {
|
||||
root.$router.push('/' + ERouterName.CREATE_PLAN)
|
||||
}
|
||||
|
||||
const taskProgressMap = computed(() => store.state.taskProgressInfo)
|
||||
|
||||
onMounted(() => {
|
||||
getPlans()
|
||||
})
|
||||
|
||||
function getPlans () {
|
||||
getWaylineJobs(workspaceId, body).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
plansData.data = res.data.list
|
||||
paginationProp.total = res.data.pagination.total
|
||||
paginationProp.current = res.data.pagination.page
|
||||
})
|
||||
}
|
||||
|
||||
function refreshData (page: Pagination) {
|
||||
body.page = page?.current!
|
||||
body.page_size = page?.pageSize!
|
||||
getPlans()
|
||||
}
|
||||
|
||||
function executePlan (jobId: string) {
|
||||
executeWaylineJobs(workspaceId, jobId).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Executed Successfully')
|
||||
getPlans()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plan-panel-wrapper {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
.plan-table {
|
||||
background: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.action-area {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-align: start;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div class="mt20 flex-column flex-justify-start flex-align-center">
|
||||
<div id="player" style="width: 720px; height: 420px; border: 1px solid"></div>
|
||||
<p class="fz24">Live streaming source selection</p>
|
||||
<div class="flex-row flex-justify-center flex-align-center mt10">
|
||||
<a-select
|
||||
style="width:150px"
|
||||
placeholder="Select Drone"
|
||||
@select="onDroneSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in dronePara.droneList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Camera"
|
||||
@select="onCameraSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in dronePara.cameraList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<!-- <a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Lens"
|
||||
@select="onVideoSelect"
|
||||
>
|
||||
<a-select-option
|
||||
class="ml10"
|
||||
v-for="item in dronePara.videoList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select> -->
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Clarity"
|
||||
@select="onClaritySelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in clarityList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</div>
|
||||
<p class="fz16 mt10">
|
||||
Note: Obtain The Following Parameters From https://console.agora.io
|
||||
</p>
|
||||
<div class="flex-row flex-justify-center flex-align-center">
|
||||
<a-input v-model:value="agoraPara.appid" placeholder="APP ID"></a-input>
|
||||
<a-input
|
||||
class="ml10"
|
||||
v-model:value="agoraPara.token"
|
||||
placeholder="Token"
|
||||
></a-input>
|
||||
<a-input
|
||||
class="ml10"
|
||||
v-model:value="agoraPara.channel"
|
||||
placeholder="Channel"
|
||||
></a-input>
|
||||
</div>
|
||||
<div class="mt20 flex-row flex-justify-center flex-align-center">
|
||||
<a-button type="primary" large @click="onStart">Play</a-button>
|
||||
<a-button class="ml20" type="primary" large @click="onStop"
|
||||
>Stop</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onUpdateQuality"
|
||||
>Update Clarity</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onRefresh"
|
||||
>Refresh Live Capacity</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config'
|
||||
import { getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
|
||||
import { getRoot } from '/@/root'
|
||||
|
||||
const root = getRoot()
|
||||
|
||||
const clarityList = [
|
||||
{
|
||||
value: 0,
|
||||
label: 'Adaptive'
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: 'Smooth'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Standard'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'HD'
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: 'Super Clear'
|
||||
}
|
||||
]
|
||||
|
||||
let agoraClient = {} as IAgoraRTCClient
|
||||
const agoraPara = reactive({
|
||||
appid: config.agoraAPPID,
|
||||
token: config.agoraToken,
|
||||
channel: config.agoraChannel,
|
||||
uid: 123456,
|
||||
stream: {}
|
||||
})
|
||||
const dronePara = reactive({
|
||||
livestreamSource: [],
|
||||
droneList: [] as any[],
|
||||
cameraList: [] as any[],
|
||||
videoList: [] as any[],
|
||||
droneSelected: '',
|
||||
cameraSelected: '',
|
||||
videoSelected: '',
|
||||
claritySelected: 0
|
||||
})
|
||||
const livePara = reactive({
|
||||
url: '',
|
||||
webrtc: {} as any,
|
||||
videoId: '',
|
||||
liveState: false
|
||||
})
|
||||
|
||||
const onRefresh = async () => {
|
||||
dronePara.droneList = []
|
||||
dronePara.cameraList = []
|
||||
dronePara.videoList = []
|
||||
dronePara.droneSelected = ''
|
||||
dronePara.cameraSelected = ''
|
||||
dronePara.videoSelected = ''
|
||||
await getLiveCapacity({})
|
||||
.then(res => {
|
||||
if (res.code === 0) {
|
||||
if (res.data === null) {
|
||||
console.warn('warning: get live capacity is null!!!')
|
||||
return
|
||||
}
|
||||
dronePara.livestreamSource = res.data
|
||||
dronePara.droneList = []
|
||||
|
||||
console.log('live_capacity:', dronePara.livestreamSource)
|
||||
|
||||
if (dronePara.livestreamSource) {
|
||||
dronePara.livestreamSource.forEach((ele: any) => {
|
||||
dronePara.droneList.push({ label: ele.name + '-' + ele.sn, value: ele.sn })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onRefresh()
|
||||
agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' })
|
||||
// Subscribe when a remote user publishes a stream
|
||||
agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => {
|
||||
message.info('user[' + user.uid + '] join')
|
||||
})
|
||||
agoraClient.on('user-published', async (user: IAgoraRTCRemoteUser, mediaType: 'audio' | 'video') => {
|
||||
await agoraClient.subscribe(user, mediaType)
|
||||
if (mediaType === 'video') {
|
||||
console.log('subscribe success')
|
||||
// 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 = agoraPara.uid
|
||||
remoteVideoTrack.play(remotePlayerContainer)
|
||||
}
|
||||
})
|
||||
agoraClient.on('user-unpublished', async (user: any) => {
|
||||
console.log('unpublish live:', user)
|
||||
message.info('unpublish live')
|
||||
})
|
||||
})
|
||||
|
||||
const handleError = (err: any) => {
|
||||
console.error(err)
|
||||
}
|
||||
const handleJoinChannel = (uid: any) => {
|
||||
agoraPara.uid = uid
|
||||
}
|
||||
|
||||
const onStart = async () => {
|
||||
const that = this
|
||||
console.log(
|
||||
'drone parameter:',
|
||||
dronePara.droneSelected,
|
||||
dronePara.cameraSelected,
|
||||
dronePara.videoSelected,
|
||||
dronePara.claritySelected
|
||||
)
|
||||
const timestamp = new Date().getTime().toString()
|
||||
const liveTimestamp = timestamp
|
||||
if (
|
||||
dronePara.droneSelected == null ||
|
||||
dronePara.cameraSelected == null ||
|
||||
dronePara.videoSelected == null ||
|
||||
dronePara.claritySelected == null
|
||||
) {
|
||||
message.warn('waring: not select live para!!!')
|
||||
return
|
||||
}
|
||||
agoraClient.setClientRole('audience', { level: 1 })
|
||||
if (agoraClient.connectionState === 'DISCONNECTED') {
|
||||
agoraClient
|
||||
.join(agoraPara.appid, agoraPara.channel, agoraPara.token)
|
||||
}
|
||||
livePara.videoId =
|
||||
dronePara.droneSelected +
|
||||
'/' +
|
||||
dronePara.cameraSelected +
|
||||
'/' +
|
||||
dronePara.videoSelected
|
||||
console.log(agoraPara)
|
||||
agoraPara.token = encodeURIComponent(agoraPara.token)
|
||||
|
||||
livePara.url =
|
||||
'channel=' +
|
||||
agoraPara.channel +
|
||||
'&sn=' +
|
||||
dronePara.droneSelected +
|
||||
'&token=' +
|
||||
agoraPara.token +
|
||||
'&uid=' +
|
||||
agoraPara.uid
|
||||
|
||||
startLivestream({
|
||||
url: livePara.url,
|
||||
video_id: livePara.videoId,
|
||||
url_type: 0,
|
||||
video_quality: dronePara.claritySelected
|
||||
})
|
||||
.then(res => {
|
||||
livePara.liveState = true
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
const onStop = async () => {
|
||||
livePara.videoId =
|
||||
dronePara.droneSelected +
|
||||
'/' +
|
||||
dronePara.cameraSelected +
|
||||
'/' +
|
||||
dronePara.videoSelected
|
||||
stopLivestream({
|
||||
video_id: livePara.videoId
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success(res.message)
|
||||
}
|
||||
livePara.liveState = false
|
||||
console.log('stop play livestream')
|
||||
})
|
||||
}
|
||||
const onDroneSelect = (val: any) => {
|
||||
dronePara.droneSelected = val
|
||||
if (dronePara.droneSelected) {
|
||||
const droneTemp = dronePara.livestreamSource
|
||||
dronePara.cameraList = []
|
||||
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.cameras_list && drone.sn === dronePara.droneSelected) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
dronePara.cameraList.push({ label: ele.name, value: ele.index })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const onCameraSelect = (val: any) => {
|
||||
dronePara.cameraSelected = val
|
||||
|
||||
if (dronePara.cameraSelected) {
|
||||
const droneTemp = dronePara.livestreamSource
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.sn === dronePara.droneSelected) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
const camera = ele
|
||||
if (camera.index === dronePara.cameraSelected) {
|
||||
const videoListTemp = camera.videos_list
|
||||
dronePara.videoList = []
|
||||
videoListTemp.forEach((ele: any) => {
|
||||
dronePara.videoList.push({ label: ele.type, value: ele.index })
|
||||
})
|
||||
dronePara.videoSelected = dronePara.videoList[0]?.value
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const onVideoSelect = (val: any) => {
|
||||
dronePara.videoSelected = val
|
||||
}
|
||||
const onClaritySelect = (val: any) => {
|
||||
dronePara.claritySelected = val
|
||||
}
|
||||
const onUpdateQuality = () => {
|
||||
if (!livePara.liveState) {
|
||||
message.info('Please turn on the livestream first.')
|
||||
return
|
||||
}
|
||||
setLivestreamQuality({
|
||||
video_id: livePara.videoId,
|
||||
video_quality: dronePara.claritySelected
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Set the clarity to ' + clarityList[dronePara.claritySelected].label)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/@/styles/index.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div class="flex-column flex-justify-start flex-align-center mt20">
|
||||
<video
|
||||
:style="{ width: '720px', height: '480px' }"
|
||||
id="video-webrtc"
|
||||
ref="videowebrtc"
|
||||
controls
|
||||
class="mt20"
|
||||
></video>
|
||||
<p class="fz24">Live streaming source selection</p>
|
||||
<div class="flex-row flex-justify-center flex-align-center mt10">
|
||||
<a-select
|
||||
style="width: 150px"
|
||||
placeholder="Select Live Type"
|
||||
@select="onLiveTypeSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in liveTypeList"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Drone"
|
||||
@select="onDroneSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in droneList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Camera"
|
||||
@select="onCameraSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in cameraList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<!-- <a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Lens"
|
||||
@select="onVideoSelect"
|
||||
>
|
||||
<a-select-option
|
||||
class="ml10"
|
||||
v-for="item in videoList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select> -->
|
||||
<a-select
|
||||
class="ml10"
|
||||
style="width:150px"
|
||||
placeholder="Select Clarity"
|
||||
@select="onClaritySelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in clarityList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="mt20">
|
||||
<p class="fz10" v-if="livetypeSelected == 2">
|
||||
Please use VLC media player to play the RTSP livestream !!!
|
||||
</p>
|
||||
<p class="fz10" v-if="livetypeSelected == 2">
|
||||
RTSP Parameter:{{ rtspData }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt10 flex-row flex-justify-center flex-align-center">
|
||||
<a-button type="primary" large @click="onStart">Play</a-button>
|
||||
<a-button class="ml20" type="primary" large @click="onStop"
|
||||
>Stop</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onUpdateQuality"
|
||||
>Update Clarity</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" large @click="onRefresh"
|
||||
>Refresh Live Capacity</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config'
|
||||
import { getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
|
||||
import { getRoot } from '/@/root'
|
||||
import jswebrtc from '/@/vendors/jswebrtc.min.js'
|
||||
const root = getRoot()
|
||||
|
||||
const liveTypeList = [
|
||||
{
|
||||
value: 1,
|
||||
label: 'RTMP'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'RTSP'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'GB28181'
|
||||
}
|
||||
]
|
||||
const clarityList = [
|
||||
{
|
||||
value: 0,
|
||||
label: 'Adaptive'
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: 'Smooth'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Standard'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'HD'
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: 'Super Clear'
|
||||
}
|
||||
]
|
||||
|
||||
const videowebrtc = ref(null)
|
||||
const livestreamSource = ref()
|
||||
const droneList = ref()
|
||||
const cameraList = ref()
|
||||
const videoList = ref()
|
||||
const droneSelected = ref()
|
||||
const cameraSelected = ref()
|
||||
const videoSeleted = ref()
|
||||
const claritySeleted = ref()
|
||||
const videoId = ref()
|
||||
const liveState = ref<boolean>(false)
|
||||
const livetypeSelected = ref()
|
||||
const rtspData = ref()
|
||||
|
||||
const onRefresh = async () => {
|
||||
droneList.value = []
|
||||
cameraList.value = []
|
||||
videoList.value = []
|
||||
droneSelected.value = null
|
||||
cameraSelected.value = null
|
||||
videoSeleted.value = null
|
||||
await getLiveCapacity({})
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 0) {
|
||||
if (res.data === null) {
|
||||
console.warn('warning: get live capacity is null!!!')
|
||||
return
|
||||
}
|
||||
const resData: Array<[]> = res.data
|
||||
console.log('live_capacity:', resData)
|
||||
livestreamSource.value = resData
|
||||
|
||||
const temp: Array<{}> = []
|
||||
if (livestreamSource.value) {
|
||||
livestreamSource.value.forEach((ele: any) => {
|
||||
temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn })
|
||||
})
|
||||
droneList.value = temp
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onRefresh()
|
||||
})
|
||||
const onStart = async () => {
|
||||
console.log(
|
||||
'Param:',
|
||||
livetypeSelected.value,
|
||||
droneSelected.value,
|
||||
cameraSelected.value,
|
||||
videoSeleted.value,
|
||||
claritySeleted.value
|
||||
)
|
||||
const timestamp = new Date().getTime().toString()
|
||||
if (
|
||||
livetypeSelected.value == null ||
|
||||
droneSelected.value == null ||
|
||||
cameraSelected.value == null ||
|
||||
videoSeleted.value == null ||
|
||||
claritySeleted.value == null
|
||||
) {
|
||||
message.warn('waring: not select live para!!!')
|
||||
return
|
||||
}
|
||||
videoId.value =
|
||||
droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value
|
||||
let liveURL = ''
|
||||
switch (livetypeSelected.value) {
|
||||
case 1: {
|
||||
// RTMP
|
||||
liveURL = config.rtmpURL + timestamp
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
// RTSP
|
||||
liveURL = `userName=${config.rtspUserName}&password=${config.rtspPassword}&port=${config.rtspPort}`
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
liveURL = `serverIP=${config.gbServerIp}&serverPort=${config.gbServerPort}&serverID=${config.gbServerId}&agentID=${config.gbAgentId}&agentPassword=${config.gbPassword}&localPort=${config.gbAgentPort}&channel=${config.gbAgentChannel}`
|
||||
break
|
||||
}
|
||||
default:
|
||||
console.warn('warning: live type is not correct!!!')
|
||||
break
|
||||
}
|
||||
await startLivestream({
|
||||
url: liveURL,
|
||||
video_id: videoId.value,
|
||||
url_type: livetypeSelected.value,
|
||||
video_quality: claritySeleted.value
|
||||
})
|
||||
.then(res => {
|
||||
if (livetypeSelected.value === 3) {
|
||||
const url = res.data.url
|
||||
const videoElement = videowebrtc.value
|
||||
// gb28181,it will fail if not wait.
|
||||
message.loading({
|
||||
content: 'Loding...',
|
||||
duration: 4,
|
||||
onClose () {
|
||||
const player = new jswebrtc.Player(url, {
|
||||
video: videoElement,
|
||||
autoplay: true,
|
||||
onPlay: (obj: any) => {
|
||||
console.log('start play livestream')
|
||||
}
|
||||
})
|
||||
liveState.value = true
|
||||
}
|
||||
})
|
||||
} else if (livetypeSelected.value === 2) {
|
||||
console.log(res)
|
||||
rtspData.value =
|
||||
'url:' +
|
||||
res.data.url +
|
||||
'&username:' +
|
||||
res.data.username +
|
||||
'&password:' +
|
||||
res.data.password
|
||||
} else if (livetypeSelected.value === 1) {
|
||||
const url = res.data.url
|
||||
const videoElement = videowebrtc.value
|
||||
console.log('start live:', url)
|
||||
console.log(videoElement)
|
||||
const player = new jswebrtc.Player(url, {
|
||||
video: videoElement,
|
||||
autoplay: true,
|
||||
onPlay: (obj: any) => {
|
||||
console.log('start play livestream')
|
||||
liveState.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
const onStop = () => {
|
||||
videoId.value =
|
||||
droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value
|
||||
stopLivestream({
|
||||
video_id: videoId.value
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.info(res.message)
|
||||
liveState.value = false
|
||||
console.log('stop play livestream')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onUpdateQuality = () => {
|
||||
if (!liveState.value) {
|
||||
message.info('Please turn on the livestream first.')
|
||||
return
|
||||
}
|
||||
setLivestreamQuality({
|
||||
video_id: videoId.value,
|
||||
video_quality: claritySeleted.value
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Set the clarity to ' + clarityList[claritySeleted.value].label)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onLiveTypeSelect = (val: any) => {
|
||||
livetypeSelected.value = val
|
||||
}
|
||||
const onDroneSelect = (val: any) => {
|
||||
droneSelected.value = val
|
||||
const temp: Array<{}> = []
|
||||
cameraList.value = []
|
||||
if (droneSelected.value) {
|
||||
const droneTemp = livestreamSource.value
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.cameras_list && drone.sn === droneSelected.value) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
console.info(ele)
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
temp.push({ label: ele.name, value: ele.index })
|
||||
})
|
||||
cameraList.value = temp
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const onCameraSelect = (val: any) => {
|
||||
cameraSelected.value = val
|
||||
const result: Array<{}> = []
|
||||
if (cameraSelected.value) {
|
||||
const droneTemp = livestreamSource.value
|
||||
droneTemp.forEach((ele: any) => {
|
||||
const drone = ele
|
||||
if (drone.sn === droneSelected.value) {
|
||||
const cameraListTemp = drone.cameras_list
|
||||
cameraListTemp.forEach((ele: any) => {
|
||||
const camera = ele
|
||||
if (camera.index === cameraSelected.value) {
|
||||
const videoListTemp = camera.videos_list
|
||||
videoListTemp.forEach((ele: any) => {
|
||||
result.push({ label: ele.type, value: ele.index })
|
||||
})
|
||||
videoList.value = result
|
||||
videoSeleted.value = videoList.value[0]?.value
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const onVideoSelect = (val: any) => {
|
||||
videoSeleted.value = val
|
||||
}
|
||||
const onClaritySelect = (val: any) => {
|
||||
claritySeleted.value = val
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/@/styles/index.scss';
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="panel-wrapper">
|
||||
<div class="panel-wrapper" draggable="true">
|
||||
<div class="header">Wayline Library</div>
|
||||
<a-button type="primary" style="margin-top:20px" @click="onRefresh"
|
||||
>Refresh</a-button
|
||||
@@ -20,6 +20,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from '@vue/reactivity'
|
||||
import { onMounted } from 'vue'
|
||||
import { ELocalStorageKey } from '../types/enums'
|
||||
import { getWaylineFiles } from '/@/api/wayline'
|
||||
const columns = [
|
||||
{
|
||||
@@ -74,7 +75,7 @@ onMounted(() => {
|
||||
onRefresh()
|
||||
})
|
||||
const onRefresh = async () => {
|
||||
const wid: string = localStorage.getItem('workspace-id')
|
||||
const wid: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)
|
||||
data.value = []
|
||||
const index = 1
|
||||
const res = await getWaylineFiles(wid, {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { CURRENT_CONFIG } from '/@/api/http/config'
|
||||
|
||||
export const AMapConfig = {
|
||||
key: '26d54da6733de88435c68d1a2e88b682',
|
||||
key: CURRENT_CONFIG.amapKey,
|
||||
version: '2.0',
|
||||
plugins: [
|
||||
'AMap.Scale',
|
||||
@@ -14,6 +16,7 @@ export const AMapConfig = {
|
||||
'AMap.PolyEditor',
|
||||
'AMap.RangingTool',
|
||||
'AMap.Weather',
|
||||
'AMap.MouseTool'
|
||||
'AMap.MouseTool',
|
||||
'AMap.MoveAnimation'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import { GeojsonCoordinate } from '/@/types/map'
|
||||
export function useGMapCover () {
|
||||
const root = getRoot()
|
||||
const AMap = root.$aMapObj
|
||||
|
||||
const normalColor = '#2D8CF0'
|
||||
const store = rootStore
|
||||
const coverList = store.state.coverList
|
||||
|
||||
function AddCoverToMap (cover :any) {
|
||||
root.$aMap.add(cover)
|
||||
coverList.push(cover)
|
||||
@@ -27,10 +27,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()
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import store from '/@/store'
|
||||
import { getRoot } from '/@/root'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { getDeviceBySn } from '/@/api/manage'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
export function deviceTsaUpdate () {
|
||||
const root = getRoot()
|
||||
const AMap = root.$aMapObj
|
||||
|
||||
const map = root.$aMap
|
||||
const icons: {
|
||||
[key: string]: string
|
||||
} = {
|
||||
'sub-device' : '/@/assets/icons/drone.png',
|
||||
'gateway': '/@/assets/icons/rc.png',
|
||||
'dock': '/@/assets/icons/dock.png'
|
||||
}
|
||||
const markers = store.state.markerInfo.coverMap
|
||||
const paths = store.state.markerInfo.pathMap
|
||||
|
||||
const passedPolyline = new AMap.Polyline({
|
||||
map: map,
|
||||
strokeColor: '#939393' // 线颜色
|
||||
})
|
||||
|
||||
function initIcon (type: string) {
|
||||
return new AMap.Icon({
|
||||
image: icons[type],
|
||||
imageSize: new AMap.Size(40, 40)
|
||||
})
|
||||
}
|
||||
|
||||
function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) {
|
||||
if (markers[sn]) {
|
||||
return
|
||||
}
|
||||
markers[sn] = new AMap.Marker({
|
||||
position: new AMap.LngLat(lng ? lng : 113.935913, lat ? lat : 22.525335),
|
||||
icon: initIcon(type),
|
||||
title: name,
|
||||
anchor: 'top-center',
|
||||
offset: [0, -20],
|
||||
})
|
||||
root.$aMap.add(markers[sn])
|
||||
|
||||
// markers[sn].on('moving', function (e: any) {
|
||||
// let path = paths[sn]
|
||||
// if (!path) {
|
||||
// paths[sn] = e.passedPath
|
||||
// return
|
||||
// }
|
||||
// path.push(e.passedPath[0])
|
||||
// path.push(e.passedPath[1])
|
||||
// passedPolyline.setPath(path)
|
||||
// })
|
||||
}
|
||||
function removeMarker (sn: string) {
|
||||
if (!markers[sn]) {
|
||||
return
|
||||
}
|
||||
root.$aMap.remove(markers[sn])
|
||||
passedPolyline.setPath([])
|
||||
delete markers[sn]
|
||||
delete paths[sn]
|
||||
}
|
||||
function addMarker(sn: string, lng?: number, lat?: number) {
|
||||
getDeviceBySn(localStorage.getItem(ELocalStorageKey.WorkspaceId)!, sn)
|
||||
.then(data => {
|
||||
if (data.code !== 0) {
|
||||
message.error(data.message)
|
||||
return
|
||||
}
|
||||
initMarker(data.data.domain, data.data.nickname, sn, lng, lat)
|
||||
})
|
||||
}
|
||||
function moveTo(sn: string, lng: number, lat: number) {
|
||||
let marker = markers[sn]
|
||||
if (!marker) {
|
||||
addMarker(sn, lng, lat)
|
||||
marker = markers[sn]
|
||||
return
|
||||
}
|
||||
marker.moveTo([lng, lat], {
|
||||
duration: 1800,
|
||||
autoRotation: true
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
marker: markers,
|
||||
initMarker,
|
||||
removeMarker,
|
||||
moveTo
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<a-layout class="flex-display" style="height: 100vh; background-color: white;">
|
||||
<div class="height100 width100 flex-column flex-justify-start flex-align-start">
|
||||
<a-row class="pt20 pl20" style="height: 45px; width: 100vw" align="middle">
|
||||
<a-col :span="1">
|
||||
<span style="color: #1fa3f6" class="fz26"><SendOutlined rotate="90" /></span>
|
||||
</a-col>
|
||||
<a-col :span="20">
|
||||
<span class="fz20 pl5">{{ drone.data.model }}</span>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<span class="fz16" v-if="drone.data.bound_status" style="color: #737373">Bound</span>
|
||||
<a-button type="primary" @click="onBindDevice" v-else>Bind</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { SendOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { BindBody, bindDevice } from '/@/api/manage'
|
||||
import apiPilot from '/@/api/pilot-bridge'
|
||||
import { getRoot } from '/@/root'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { DeviceStatus } from '/@/types/device'
|
||||
|
||||
const root = getRoot()
|
||||
interface DeviceStatusData {
|
||||
data: DeviceStatus
|
||||
}
|
||||
const drone = reactive<DeviceStatusData>({
|
||||
data: JSON.parse(localStorage.getItem(ELocalStorageKey.Device)!)
|
||||
})
|
||||
|
||||
function onBindDevice () {
|
||||
const bindParam: BindBody = {
|
||||
device_sn: drone.data.sn,
|
||||
user_id: localStorage.getItem(ELocalStorageKey.UserId)!,
|
||||
workspace_id: localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
}
|
||||
bindDevice(bindParam).then(bindRes => {
|
||||
if (bindRes.code !== 0) {
|
||||
message.error('bind failed:' + bindRes.message)
|
||||
console.error(bindRes.message)
|
||||
return
|
||||
}
|
||||
drone.data.bound_status = true
|
||||
localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(drone.data))
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,132 +1,501 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="left flex-column flex-justify-start flex-align-center">
|
||||
<p class="fz26 mb0 mt10" style="color: #727272">
|
||||
{{ platformName }}
|
||||
</p>
|
||||
<p class="fz16 ml10 mb0 mt10" style="color: #2d8cf0">
|
||||
status:{{ connect }}
|
||||
</p>
|
||||
<p class="fz32 mb0 mt50" style="color: #000000">{{ workspaceName }}</p>
|
||||
<a-button
|
||||
class="fz20 mt20 flex-column flex-justify-center flex-align-center"
|
||||
style="width: 30vw; height: 12vh;"
|
||||
type="default"
|
||||
@click="onOpen3rdApp"
|
||||
>Open 3rd Party APP</a-button
|
||||
>
|
||||
<a-button
|
||||
class="fz20"
|
||||
style="width: 15vw; height: 12vh; position: fixed; bottom: 7vh"
|
||||
type="primary"
|
||||
@click="onExit"
|
||||
>Quit</a-button
|
||||
>
|
||||
</div>
|
||||
<div class="right flex-column flex-justify-start flex-align-center">
|
||||
<p class="fz24 mb0 mt10 ">Setting</p>
|
||||
<a-button class="mt10 fz16" style="width:90%" @click="onMediaSetting"
|
||||
>Media File Upload Setting</a-button
|
||||
>
|
||||
<a-button class="mt10 fz16" style="width:90%" @click="onLiveshareSetting"
|
||||
>Manual Live Share Setting</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<a-layout class="page">
|
||||
<a-layout-sider class="left" width="40%" style="border-radius: 4px;">
|
||||
<div style="width:90%; height: 90%; margin: 4vh">
|
||||
<a-layout style="height: 20%; margin-top: 3vh; background-color: white; ">
|
||||
<a-layout-sider width="25%" theme="light" align="center">
|
||||
<a-avatar :size="60" :src="cloudapi">
|
||||
</a-avatar>
|
||||
</a-layout-sider>
|
||||
<a-layout-content style="margin-left: 1vw;" @click="showStatus">
|
||||
<div style="height: 50%;">
|
||||
<span style="font-size: 16px; font-weight: bolder">{{ workspaceName }}</span>
|
||||
<RightOutlined style="float: right; margin-top: 5px; color: #8894a0" />
|
||||
</div>
|
||||
<div style="height: 50%;">
|
||||
<CloudSyncOutlined v-if="state === EStatusValue.CONNECTED" style="color: #75c5f6" />
|
||||
<SyncOutlined spin v-else/>
|
||||
<span style="color: #737373; margin-left: 3px;">{{ state }}</span>
|
||||
</div>
|
||||
<a-drawer placement="right" v-model:visible="drawerVisible" width="340px">
|
||||
<div class="mb10 flex-row flex-justify-center flex-align-center">
|
||||
<p class="fz14" style="font-weight: 100;">Module State</p>
|
||||
</div>
|
||||
<div class= "width-100 mb10 flex-align-start" v-for="m in modules" :key="m.name" style="height: 30px;">
|
||||
|
||||
<div class="ml5" style="float: left; color: #000000;">{{m.name}}:</div>
|
||||
<div class="ml10" style="float: right; margin-bottom: 8px;">
|
||||
<span :key="m.state" :class="m.state.value === EStatusValue.CONNECTED ? 'green' : 'red'">{{ m.state.value }} </span>
|
||||
<a-button-group >
|
||||
<a-button class="ml5" type="primary" size="small" @click.stop="moduleInstall(m)">install</a-button>
|
||||
<a-button class="ml5 mr5" type="danger" size="small" @click.stop="moduleUninstall(m)">uninstall</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
</div>
|
||||
</a-drawer>
|
||||
</a-layout-content>
|
||||
|
||||
</a-layout>
|
||||
<a-divider style="height: 2px; background-color: #f5f5f5; margin-top: 3vh;" />
|
||||
|
||||
<a-button id="exitBtn" class="fz18" @click="confirmAgain"
|
||||
style="width: 10vw; height: 10vh; position: fixed; bottom: 13vh; left: 15vw; background-color: #e6e6e6; color: red; border: 0;"
|
||||
type="primary">Exit
|
||||
</a-button>
|
||||
<a-modal v-model:visible="exitVisible" width="300px" :closable="false">
|
||||
<template #footer>
|
||||
<a-button type="text" style="width: 48%; float: left;" @click="onBack">Cancel</a-button>
|
||||
<a-button type="text" style="width: 48%;" @click="onExit">Exit</a-button>
|
||||
</template>
|
||||
<p>Data will not be synchronized between DJI Pilot and this server after exiting.</p>
|
||||
</a-modal>
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<a-layout-content class="right flex-column">
|
||||
<div class="mb5">
|
||||
<span class="ml5" style="color: #939393;">Serial Number</span>
|
||||
</div>
|
||||
<div class="fz16" style="background-color: white; border-radius: 4px;">
|
||||
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle">
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="9">
|
||||
Remote Control Sn
|
||||
</a-col>
|
||||
<a-col :span="13" class="flex-align-end flex-column">
|
||||
<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-col :span="1"></a-col>
|
||||
<a-col :span="9">Aircraft Sn</a-col>
|
||||
<a-col :span="13" class="flex-align-end flex-column" >
|
||||
<span style="color: #737373">{{ device.data.sn }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="mt5 mb5">
|
||||
<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-col :span="1"></a-col>
|
||||
<a-col :span="11">
|
||||
Device Binding
|
||||
</a-col>
|
||||
<a-col :span="10" style="text-align: right">
|
||||
<span v-if="device.data.bound_status" style="color: #737373">Aircraft bound</span>
|
||||
<span v-else style="color: #737373">Aircraft not bound</span>
|
||||
</a-col>
|
||||
<a-col :span="2" class="flex-align-center flex-column" >
|
||||
<RightOutlined style="color: #8894a0; font-size: 20px;" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onMediaSetting">
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="21">
|
||||
Media File Upload
|
||||
</a-col>
|
||||
<a-col :span="2" class="flex-align-center flex-column" >
|
||||
<RightOutlined style="color: #8894a0; font-size: 20px;" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onLiveshareSetting">
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="21">Livestream Manually</a-col>
|
||||
<a-col :span="2" class="flex-align-center flex-column">
|
||||
<RightOutlined style="color: #8894a0; font-size: 20px;" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onOpen3rdApp">
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="21">Open 3rd Party APP</a-col>
|
||||
<a-col :span="2" class="flex-align-center flex-column">
|
||||
<RightOutlined style="color: #8894a0; font-size: 20px;" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { message, Popconfirm } from 'ant-design-vue'
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { CURRENT_CONFIG } from '/@/api/http/config'
|
||||
import { getPlatformInfo, getUserInfo } from '/@/api/manage'
|
||||
import apiPilot from '/@/api/pilot-bridge'
|
||||
import { BindBody, bindDevice, getDeviceBySn, getPlatformInfo, getUserInfo } from '/@/api/manage'
|
||||
import apiPilot, { ApiParam, MapParam, ThingParam, WsParam } from '/@/api/pilot-bridge'
|
||||
import { getRoot } from '/@/root'
|
||||
import { EBizCode, EComponentName, EDownloadOwner, ELocalStorageKey, ERouterName, EStatusValue } from '/@/types'
|
||||
import cloudapi from '/@/assets/icons/cloudapi.png'
|
||||
import { RightOutlined, CloudOutlined, CloudSyncOutlined, SyncOutlined } from '@ant-design/icons-vue'
|
||||
import { useMyStore } from '/@/store'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
import websocket from '/@/api/websocket'
|
||||
import { DeviceStatus } from '/@/types/device'
|
||||
|
||||
const root = getRoot()
|
||||
const connect = ref('Disconnect')
|
||||
const platformName = ref('Unknown')
|
||||
const workspaceName = ref('Unknown')
|
||||
const workspaceDesc = ref('Unknown')
|
||||
const wsId = ref()
|
||||
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)
|
||||
const wsState = ref(EStatusValue.DISCONNECT)
|
||||
const mapState = ref(EStatusValue.DISCONNECT)
|
||||
const tsaState = ref(EStatusValue.DISCONNECT)
|
||||
const mediaState = ref(EStatusValue.DISCONNECT)
|
||||
const waylineState = ref(EStatusValue.DISCONNECT)
|
||||
const workspaceName = ref<string>(localStorage.getItem(ELocalStorageKey.WorkspaceName)!)
|
||||
const username = ref(localStorage.getItem(ELocalStorageKey.Username)!)
|
||||
const wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)
|
||||
const components = apiPilot.init()
|
||||
const exitVisible = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
let minitor = -1
|
||||
|
||||
interface DeviceInfoData {
|
||||
data: DeviceStatus
|
||||
}
|
||||
const device = reactive<DeviceInfoData>({
|
||||
data: {
|
||||
sn: EStatusValue.DISCONNECT,
|
||||
online_status: false,
|
||||
device_callsign: '',
|
||||
user_id: '',
|
||||
user_callsign: '',
|
||||
bound_status: false,
|
||||
model: '',
|
||||
gateway_sn: EStatusValue.DISCONNECT,
|
||||
domain: ''
|
||||
}
|
||||
})
|
||||
const bindParam: BindBody = {
|
||||
device_sn: '',
|
||||
user_id: '',
|
||||
workspace_id: wsId.value
|
||||
}
|
||||
|
||||
const modules = [{
|
||||
name: 'Cloud',
|
||||
state: thingState,
|
||||
module: EComponentName.Thing
|
||||
}, {
|
||||
name: 'Api',
|
||||
state: apiState,
|
||||
module: EComponentName.Api
|
||||
}, {
|
||||
name: 'Live',
|
||||
state: liveState,
|
||||
module: EComponentName.Liveshare
|
||||
}, {
|
||||
name: 'Ws',
|
||||
state: wsState,
|
||||
module: EComponentName.Ws
|
||||
}, {
|
||||
name: 'Map',
|
||||
state: mapState,
|
||||
module: EComponentName.Map
|
||||
}, {
|
||||
name: 'Tsa',
|
||||
state: tsaState,
|
||||
module: EComponentName.Tsa
|
||||
}, {
|
||||
name: 'Media',
|
||||
state: mediaState,
|
||||
module: EComponentName.Media
|
||||
}, {
|
||||
name: 'Wayline',
|
||||
state: waylineState,
|
||||
module: EComponentName.Mission
|
||||
}]
|
||||
|
||||
const store = useMyStore()
|
||||
|
||||
const wsGetMsg = async (res: any) => {
|
||||
const payload = JSON.parse(res.data)
|
||||
switch (payload.biz_code) {
|
||||
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) {
|
||||
device.data = payload.data
|
||||
localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))
|
||||
}
|
||||
break
|
||||
}
|
||||
case EBizCode.DeviceOffline: {
|
||||
console.info('offline: ', payload)
|
||||
if (payload.data.sn === device.data.sn) {
|
||||
device.data.online_status = payload.data.online_status
|
||||
localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
let bindNum: number
|
||||
let socket: ReconnectingWebSocket
|
||||
|
||||
onMounted(() => {
|
||||
apiPilot.init()
|
||||
const token = apiPilot.getToken()
|
||||
if (token) {
|
||||
getPlatformInfo({}).then(res => {
|
||||
console.log(res)
|
||||
platformName.value = res.data.platform_name
|
||||
workspaceName.value = res.data.workspace_name
|
||||
workspaceDesc.value = res.data.workspace_desc
|
||||
wsId.value = res.data.workspace_id
|
||||
apiPilot.setPlatformMessage(
|
||||
platformName.value,
|
||||
workspaceName.value,
|
||||
workspaceDesc.value
|
||||
)
|
||||
apiPilot.setWorkspaceId(wsId.value)
|
||||
})
|
||||
apiPilot.onBackClickReg()
|
||||
apiPilot.onStopPlatform()
|
||||
|
||||
device.data.gateway_sn = apiPilot.getRemoteControllerSN()
|
||||
if (device.data.gateway_sn === EStatusValue.DISCONNECT.toString()) {
|
||||
message.warn('Data is not available, please restart the remote control.')
|
||||
return
|
||||
}
|
||||
if (JSON.parse(apiPilot.isComponentLoaded('thing')).data === false || token) {
|
||||
getUserInfo({}).then(res => {
|
||||
const param = {
|
||||
host: res.data.mqtt_addr,
|
||||
username: res.data.mqtt_username,
|
||||
password: res.data.mqtt_password,
|
||||
connectCallback: 'connectCallback'
|
||||
}
|
||||
apiPilot.setComponentParam('thing', param)
|
||||
apiPilot.loadComponent('thing', apiPilot.getComponentParam('thing'))
|
||||
})
|
||||
} else {
|
||||
const connectState = JSON.parse(window.djiBridge.thingGetConnectState())
|
||||
if (connectState.code === 0 && connectState.data) {
|
||||
connect.value = 'Connected'
|
||||
} else {
|
||||
connect.value = 'Disconnect'
|
||||
const oldDevice = localStorage.getItem(ELocalStorageKey.Device)
|
||||
if (oldDevice) {
|
||||
device.data = JSON.parse(oldDevice)
|
||||
}
|
||||
device.data.sn = apiPilot.getAircraftSN()
|
||||
getDeviceInfo()
|
||||
|
||||
socket = websocket.init(wsGetMsg)
|
||||
|
||||
const isLoaded = apiPilot.isComponentLoaded(EComponentName.Thing)
|
||||
if (isLoaded) {
|
||||
username.value = '' + localStorage.getItem(ELocalStorageKey.Username)
|
||||
workspaceName.value = '' + localStorage.getItem(ELocalStorageKey.WorkspaceName)
|
||||
refreshStatus()
|
||||
apiPilot.setPlatformMessage(
|
||||
'' + localStorage.getItem(ELocalStorageKey.PlatformName),
|
||||
workspaceName.value,
|
||||
'' + localStorage.getItem(ELocalStorageKey.WorkspaceDesc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setWorkspaceInfo()
|
||||
|
||||
getUserInfo().then(res => {
|
||||
username.value = res.data.username
|
||||
localStorage.setItem(ELocalStorageKey.Username, username.value)
|
||||
// thing
|
||||
const param: ThingParam = {
|
||||
host: res.data.mqtt_addr,
|
||||
username: username.value,
|
||||
password: res.data.mqtt_password,
|
||||
connectCallback: 'connectCallback'
|
||||
}
|
||||
}
|
||||
apiPilot.loadComponent('liveshare', apiPilot.getComponentParam('liveshare'))
|
||||
console.log('ws token:', token)
|
||||
apiPilot.setComponentParam('ws', {
|
||||
host: CURRENT_CONFIG.websocketURL,
|
||||
token: token
|
||||
components.set(EComponentName.Thing, param)
|
||||
apiPilot.loadComponent(EComponentName.Thing, components.get(EComponentName.Thing))
|
||||
|
||||
bindParam.device_sn = device.data.gateway_sn
|
||||
bindParam.user_id = res.data.user_id
|
||||
bindParam.workspace_id = res.data.workspace_id
|
||||
})
|
||||
apiPilot.loadComponent('ws', apiPilot.getComponentParam('ws'))
|
||||
apiPilot.setComponentParam('map', {
|
||||
userName: 'pilot1',
|
||||
elementPreName: 'PILOT'
|
||||
})
|
||||
apiPilot.loadComponent('map', apiPilot.getComponentParam('map'))
|
||||
apiPilot.loadComponent('tsa', apiPilot.getComponentParam('tsa'))
|
||||
apiPilot.loadComponent('media', apiPilot.getComponentParam('media'))
|
||||
apiPilot.loadComponent('mission', {})
|
||||
window.connectCallback = arg => {
|
||||
connectCallback(arg)
|
||||
}
|
||||
apiPilot.onBackClickReg()
|
||||
window.wsConnectCallback = arg => {
|
||||
wsConnectCallback(arg)
|
||||
}
|
||||
})
|
||||
const connectCallback = (arg: any) => {
|
||||
console.info('into callback', arg)
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.close()
|
||||
})
|
||||
|
||||
const connectCallback = async (arg: any) => {
|
||||
if (arg) {
|
||||
connect.value = 'Connected'
|
||||
window.djiBridge.mediaSetDownloadOwner(0)
|
||||
window.djiBridge.mediaSetUploadPhotoType(1)
|
||||
thingState.value = EStatusValue.CONNECTED
|
||||
// liveshare
|
||||
apiPilot.loadComponent(EComponentName.Liveshare, components.get(EComponentName.Liveshare))
|
||||
|
||||
// ws
|
||||
const wsParam: WsParam = components.get(EComponentName.Ws)
|
||||
wsParam.token = apiPilot.getToken()
|
||||
apiPilot.loadComponent(EComponentName.Ws, components.get(EComponentName.Ws))
|
||||
|
||||
// map
|
||||
const mapParam: MapParam = components.get(EComponentName.Map)
|
||||
mapParam.userName = username.value
|
||||
apiPilot.loadComponent(EComponentName.Map, components.get(EComponentName.Map))
|
||||
|
||||
// tsa
|
||||
apiPilot.loadComponent(EComponentName.Tsa, components.get(EComponentName.Tsa))
|
||||
|
||||
// media
|
||||
apiPilot.loadComponent(EComponentName.Media, components.get(EComponentName.Media))
|
||||
apiPilot.setDownloadOwner(EDownloadOwner.Mine.valueOf())
|
||||
|
||||
// mission
|
||||
apiPilot.loadComponent(EComponentName.Mission, {})
|
||||
|
||||
bindNum = setInterval(() => {
|
||||
bindDevice(bindParam).then(bindRes => {
|
||||
if (bindRes.code !== 0) {
|
||||
message.error(bindRes.message)
|
||||
console.error(bindRes.message)
|
||||
} else {
|
||||
clearInterval(bindNum)
|
||||
}
|
||||
})
|
||||
}, 2000)
|
||||
setTimeout(getDeviceInfo, 3000)
|
||||
} else {
|
||||
connect.value = 'Disconnect'
|
||||
thingState.value = EStatusValue.DISCONNECT
|
||||
}
|
||||
refreshStatus()
|
||||
}
|
||||
const wsConnectCallback = async (arg: any) => {
|
||||
if (arg) {
|
||||
wsState.value = EStatusValue.CONNECTED
|
||||
} else {
|
||||
wsState.value = EStatusValue.DISCONNECT
|
||||
}
|
||||
}
|
||||
const onExit = async (e: any) => {
|
||||
|
||||
const confirmAgain = () => {
|
||||
exitVisible.value = true
|
||||
}
|
||||
|
||||
const onBack = () => {
|
||||
exitVisible.value = false
|
||||
}
|
||||
|
||||
const onExit = () => {
|
||||
localStorage.clear()
|
||||
apiPilot.stopwebview()
|
||||
}
|
||||
|
||||
const bindingDevice = async () => {
|
||||
root.$router.push(ERouterName.PILOT_BIND)
|
||||
}
|
||||
|
||||
const onMediaSetting = async (e: any) => {
|
||||
root.$router.push('/pilot-media')
|
||||
root.$router.push(ERouterName.PILOT_MEDIA)
|
||||
}
|
||||
const onLiveshareSetting = async (e: any) => {
|
||||
root.$router.push('/pilot-liveshare')
|
||||
root.$router.push(ERouterName.PILOT_LIVESHARE)
|
||||
}
|
||||
const onOpen3rdApp = () => {
|
||||
window.open('mydjischeme://www.dji.com')
|
||||
const packageName = 'com.dji.sample'
|
||||
const isInstalled = apiPilot.isAppInstalled(packageName)
|
||||
if (isInstalled) {
|
||||
window.open('https://www.dji.com')
|
||||
} else {
|
||||
message.error(packageName + ' is not installed.')
|
||||
}
|
||||
}
|
||||
|
||||
const showStatus = async () => {
|
||||
minitor = setInterval(() => {
|
||||
refreshStatus()
|
||||
if (!drawerVisible.value) {
|
||||
clearInterval(minitor)
|
||||
}
|
||||
}, 2000)
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
function setWorkspaceInfo () {
|
||||
if (localStorage.getItem(ELocalStorageKey.WorkspaceName)) {
|
||||
apiPilot.setPlatformMessage(
|
||||
'' + localStorage.getItem(ELocalStorageKey.PlatformName),
|
||||
workspaceName.value,
|
||||
'' + localStorage.getItem(ELocalStorageKey.WorkspaceDesc)
|
||||
)
|
||||
apiPilot.setWorkspaceId(wsId.value)
|
||||
|
||||
return
|
||||
}
|
||||
getPlatformInfo().then(res => {
|
||||
console.log(res)
|
||||
workspaceName.value = res.data.workspace_name
|
||||
wsId.value = res.data.workspace_id
|
||||
localStorage.setItem(ELocalStorageKey.PlatformName, res.data.platform_name)
|
||||
localStorage.setItem(ELocalStorageKey.WorkspaceName, workspaceName.value)
|
||||
localStorage.setItem(ELocalStorageKey.WorkspaceDesc, res.data.workspace_desc)
|
||||
apiPilot.setPlatformMessage(
|
||||
res.data.platform_name,
|
||||
workspaceName.value,
|
||||
res.data.workspace_desc
|
||||
)
|
||||
apiPilot.setWorkspaceId(wsId.value)
|
||||
})
|
||||
}
|
||||
|
||||
function refreshStatus () {
|
||||
thingState.value = apiPilot.thingGetConnectState() ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
|
||||
apiState.value = apiPilot.isComponentLoaded(EComponentName.Api) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
|
||||
liveState.value = apiPilot.isComponentLoaded(EComponentName.Liveshare) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
|
||||
wsState.value = apiPilot.isComponentLoaded(EComponentName.Ws) && apiPilot.wsGetConnectState()
|
||||
? EStatusValue.CONNECTED
|
||||
: EStatusValue.DISCONNECT
|
||||
mapState.value = apiPilot.isComponentLoaded(EComponentName.Map) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
|
||||
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) {
|
||||
let param
|
||||
switch (m.module) {
|
||||
case EComponentName.Thing:
|
||||
param = apiPilot.thingGetConfigs()
|
||||
break
|
||||
case EComponentName.Api: {
|
||||
const apiParam: ApiParam = {
|
||||
host: apiPilot.getHost(),
|
||||
token: apiPilot.getToken()
|
||||
}
|
||||
param = apiParam
|
||||
break
|
||||
}
|
||||
case EComponentName.Map: {
|
||||
const mapParam: MapParam = components.get(EComponentName.Map)
|
||||
mapParam.userName = '' + localStorage.getItem(ELocalStorageKey.Username)
|
||||
param = mapParam
|
||||
break
|
||||
}
|
||||
case EComponentName.Ws: {
|
||||
const wsParam: WsParam = components.get(EComponentName.Ws)
|
||||
wsParam.token = '' + localStorage.getItem(ELocalStorageKey.Token)
|
||||
param = wsParam
|
||||
break
|
||||
}
|
||||
default:
|
||||
param = components.get(m.module)
|
||||
}
|
||||
|
||||
components.set(m.module, param)
|
||||
console.info(components.get(m.module))
|
||||
apiPilot.loadComponent(m.module, components.get(m.module))
|
||||
refreshStatus()
|
||||
}
|
||||
|
||||
function moduleUninstall (m: any) {
|
||||
message.info('uninstall ' + m.module)
|
||||
apiPilot.unloadComponent(m.module)
|
||||
refreshStatus()
|
||||
}
|
||||
|
||||
function getDeviceInfo () {
|
||||
if (device.data.sn === EStatusValue.DISCONNECT) {
|
||||
return
|
||||
}
|
||||
getDeviceBySn(bindParam.workspace_id, device.data.sn).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
device.data.online_status = res.data.status
|
||||
device.data.bound_status = res.data.bound_status
|
||||
device.data.device_callsign = res.data.nickname
|
||||
device.data.model = res.data.device_name
|
||||
localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -139,12 +508,32 @@ const onOpen3rdApp = () => {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.left {
|
||||
width: 50%;
|
||||
border-right: red solid 2px;
|
||||
height: 90%;
|
||||
background-color: white;
|
||||
margin-top: 6vh;
|
||||
margin-left: 2vh;
|
||||
}
|
||||
.right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 90%;
|
||||
margin-top: 6vh;
|
||||
margin-left: 5vh;
|
||||
margin-right: 5vh;
|
||||
}
|
||||
}
|
||||
.green {
|
||||
color: green
|
||||
}
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
#exitBtn:hover :active {
|
||||
background-color: rgb(77, 75, 75);
|
||||
width: 10vw;
|
||||
height: 10vh;
|
||||
position: fixed;
|
||||
bottom: 13vh;
|
||||
left: 15vw;
|
||||
line-height: 10vh;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="login flex-column flex-justify-center flex-align-center m0 b0">
|
||||
<a-image
|
||||
style="width: 17vw; height: 10vw; margin-bottom: 50px"
|
||||
src="http://lofrev.net/wp-content/photos/2016/09/dji_logo_png.png"
|
||||
:src="djiLogo"
|
||||
/>
|
||||
<p class="logo fz35 pb50">Pilot Cloud API Demo</p>
|
||||
<a-form
|
||||
@@ -11,7 +11,7 @@
|
||||
class="flex-row flex-justify-center flex-align-center"
|
||||
>
|
||||
<a-form-item>
|
||||
<a-input v-model:value="formState.user" placeholder="Username">
|
||||
<a-input v-model:value="formState.username" placeholder="Username">
|
||||
<template #prefix
|
||||
><UserOutlined style="color: rgba(0, 0, 0, 0.25)"
|
||||
/></template>
|
||||
@@ -44,93 +44,97 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive, UnwrapRef } from 'vue'
|
||||
import { onMounted, reactive, ref, UnwrapRef } from 'vue'
|
||||
import { CURRENT_CONFIG } from '/@/api/http/config'
|
||||
import { login, refreshToken } from '/@/api/manage'
|
||||
import { login, LoginBody, refreshToken } from '/@/api/manage'
|
||||
import apiPilot from '/@/api/pilot-bridge'
|
||||
import { getRoot } from '/@/root'
|
||||
import router from '/@/router'
|
||||
import { EComponentName, ELocalStorageKey, ERouterName, EUserType } from '/@/types'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import djiLogo from '/@/assets/icons/dji_logo.png'
|
||||
|
||||
interface FormState {
|
||||
user: string
|
||||
password: string
|
||||
}
|
||||
const root = getRoot()
|
||||
|
||||
const formState: UnwrapRef<FormState> = reactive({
|
||||
user: 'pilot',
|
||||
password: 'pilot123'
|
||||
const formState: UnwrapRef<LoginBody> = reactive({
|
||||
username: 'pilot',
|
||||
password: 'pilot123',
|
||||
flag: EUserType.Pilot,
|
||||
})
|
||||
let isVerified:any
|
||||
const isVerified = ref<boolean>(false)
|
||||
onMounted(async () => {
|
||||
const verifyLicense = JSON.parse(apiPilot.platformVerifyLicense(CURRENT_CONFIG.appId,
|
||||
CURRENT_CONFIG.appKey, CURRENT_CONFIG.appLicense))
|
||||
const platformVerify = JSON.parse(apiPilot.isPlatformVerifySuccess())
|
||||
isVerified = platformVerify.data
|
||||
if (platformVerify.data === true) {
|
||||
message.success('The license verification is successful.')
|
||||
} else {
|
||||
message.error('Filed to verify the license. message is ' + verifyLicense.data)
|
||||
verifyLicense()
|
||||
if (!isVerified.value) {
|
||||
return
|
||||
}
|
||||
const token = apiPilot.getToken()
|
||||
console.log('api token:', token)
|
||||
|
||||
apiPilot.setPlatformMessage('Cloud Api Platform', '', '')
|
||||
if (token && token !== undefined) {
|
||||
|
||||
const token = localStorage.getItem(ELocalStorageKey.Token)
|
||||
if (token) {
|
||||
await refreshToken({})
|
||||
.then(res => {
|
||||
apiPilot.setComponentParam('api', {
|
||||
apiPilot.setComponentParam(EComponentName.Api, {
|
||||
host: CURRENT_CONFIG.baseURL,
|
||||
token: res.data.access_token
|
||||
})
|
||||
const jsres = JSON.parse(
|
||||
apiPilot.loadComponent('api', apiPilot.getComponentParam('api'))
|
||||
)
|
||||
console.log('load api module status:', jsres)
|
||||
const jsres = apiPilot.loadComponent(EComponentName.Api, apiPilot.getComponentParam(EComponentName.Api))
|
||||
if (!jsres) {
|
||||
message.error('Failed to load api module.')
|
||||
return
|
||||
}
|
||||
apiPilot.setToken(res.data.access_token)
|
||||
localStorage.setItem('x-auth-token', res.data.access_token)
|
||||
message.success('Login Success')
|
||||
root.$router.push('/pilot-home')
|
||||
localStorage.setItem(ELocalStorageKey.Token, res.data.access_token)
|
||||
root.$router.push(ERouterName.PILOT_HOME)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
message.error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
const onSubmit = async (e: any) => {
|
||||
await login({
|
||||
username: formState.user,
|
||||
password: formState.password
|
||||
})
|
||||
await login(formState)
|
||||
.then(res => {
|
||||
if (!isVerified) {
|
||||
if (!isVerified.value) {
|
||||
message.error('Please verify the license firstly.')
|
||||
return
|
||||
}
|
||||
console.log('login res:', res)
|
||||
if (res.code === 0) {
|
||||
apiPilot.setComponentParam('api', {
|
||||
apiPilot.setComponentParam(EComponentName.Api, {
|
||||
host: CURRENT_CONFIG.baseURL,
|
||||
token: res.data.access_token
|
||||
})
|
||||
const jsres = apiPilot.loadComponent(
|
||||
'api',
|
||||
apiPilot.getComponentParam('api')
|
||||
EComponentName.Api,
|
||||
apiPilot.getComponentParam(EComponentName.Api)
|
||||
)
|
||||
console.log('load api module res:', jsres)
|
||||
apiPilot.setToken(res.data.access_token)
|
||||
localStorage.setItem('x-auth-token', res.data.access_token)
|
||||
localStorage.setItem('workspace-id', res.data.workspace_id)
|
||||
localStorage.setItem('username', res.data.username)
|
||||
localStorage.setItem(ELocalStorageKey.Token, res.data.access_token)
|
||||
localStorage.setItem(ELocalStorageKey.WorkspaceId, res.data.workspace_id)
|
||||
localStorage.setItem(ELocalStorageKey.UserId, res.data.user_id)
|
||||
localStorage.setItem(ELocalStorageKey.Username, res.data.username)
|
||||
localStorage.setItem(ELocalStorageKey.Flag, EUserType.Pilot.toString())
|
||||
message.success('Login Success')
|
||||
root.$router.push('/pilot-home')
|
||||
root.$router.push(ERouterName.PILOT_HOME)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
message.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
function verifyLicense () {
|
||||
isVerified.value = apiPilot.platformVerifyLicense(CURRENT_CONFIG.appId, CURRENT_CONFIG.appKey, CURRENT_CONFIG.appLicense) &&
|
||||
apiPilot.isPlatformVerifySuccess()
|
||||
if (isVerified.value) {
|
||||
message.success('The license verification is successful.')
|
||||
} else {
|
||||
message.error('Filed to verify the license. Please check license whether the license is correct, or apply again.')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
class="width-100vw height-100vh flex-column flex-justify-start flex-align-start"
|
||||
>
|
||||
<div class="width100 flex-column flex-justify-start flex-align-start" style="background-color: white;">
|
||||
|
||||
<p class="fz16 ml10 mt15 mb10 color-text-title color-font-bold" style="color: #939393">
|
||||
Before starting manually, please select the publish mode and livestream type
|
||||
</p>
|
||||
<div
|
||||
class="mt20 flex-row flex-align-center flex-justify-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
class="mt15 flex-row flex-align-center flex-justify-between"
|
||||
style="width: 100%;">
|
||||
<p class="ml10 mb0 fz16" style="color: black">
|
||||
Select Video Publish Mode:
|
||||
</p>
|
||||
<a-select
|
||||
style="width: 200px"
|
||||
style="width: 200px; margin-right: 20px;"
|
||||
placeholder="Select Mode"
|
||||
@select="onPublishModeSelect"
|
||||
>
|
||||
@@ -24,16 +25,18 @@
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<a-divider dashed class="mt10 mb0"></a-divider>
|
||||
|
||||
<div class="ml10 mr10" style="width: 96%; margin-top: -10px;">
|
||||
<a-divider />
|
||||
</div>
|
||||
<div
|
||||
class="flex-row flex-align-center flex-justify-between mt10"
|
||||
style="width: 100%"
|
||||
class="flex-row flex-align-center flex-justify-between"
|
||||
style="width: 100%; margin-top: -10px;"
|
||||
>
|
||||
<p class="ml10 mb0 fz16" style="color: black">Select Live Share Type:</p>
|
||||
<p class="ml10 mb0 fz16">Select Livestream Type:</p>
|
||||
<a-select
|
||||
style="width: 200px"
|
||||
style="width: 200px; margin-right: 20px;"
|
||||
placeholder="Select Live Type"
|
||||
:value="liveStreamStatus.type"
|
||||
@select="onLiveTypeSelect"
|
||||
>
|
||||
<a-select-option
|
||||
@@ -45,141 +48,265 @@
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<a-divider dashed class="mt10 mb0"></a-divider>
|
||||
<div class="ml10 mr10" style="width: 96%; margin-top: -10px;">
|
||||
<a-divider />
|
||||
</div>
|
||||
<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-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>
|
||||
<span v-else></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-row flex-align-center flex-justify-center mt20"
|
||||
style="width: 100%"
|
||||
>
|
||||
<p>Live Share State: {{ liveState }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex-row flex-align-center flex-justify-center mt20"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-button type="primary" @click="onPlay">Play</a-button>
|
||||
<a-button class="ml20" type="primary" @click="onGetConfig"
|
||||
>Get Config</a-button
|
||||
>
|
||||
<a-button class="ml20" type="primary" @click="onGetStatus"
|
||||
>Get Status</a-button
|
||||
>
|
||||
<div class="ml10 mr10" style="width: 96%; margin-top: -10px;">
|
||||
<a-divider />
|
||||
</div>
|
||||
<div class="mb20 flex-row flex-align-center flex-justify-center"
|
||||
style="width: 100%; ">
|
||||
<a-button class="flex-column fz20 flex-align-center flex-justify-center" style="width: 100px;" type="ghost" @click="onPlay">Play</a-button>
|
||||
<a-button class="flex-column fz20 flex-align-center flex-justify-center ml40" style="width: 100px;" type="ghost" @click="onStop">Stop</a-button>
|
||||
</div>
|
||||
<a-button v-if="playVisiable" class="flex-column flex-align-center" shape="circle" @click="showLivingStatus"
|
||||
style="position: fixed; top: 13vh; left: 5vw; opacity: 0.8; background-color: rgb(0,0,0,0)">
|
||||
<template #icon><CaretRightFilled style="font-size: 26px; color: " /></template>
|
||||
</a-button>
|
||||
|
||||
<a-drawer placement="right" v-model:visible="drawerVisible" width="280px" :mask="false" @close="closeDrawer">
|
||||
<div class="fz16 width-100">
|
||||
<div class="mt20" style=" margin-bottom: -10px;">
|
||||
<span class="fz20 flex-row flex-align-center flex-justify-center">
|
||||
<font :color="liveState === EStatusValue.LIVING ? 'green' : liveState === EStatusValue.CONNECTED ? 'blue' : 'red'">{{ liveState }}</font></span>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div style=" margin-top: -10px; margin-bottom: -15px;">
|
||||
<span>Frame Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.fps }}<span v-if="liveStreamStatus.fps != -1"> fps</span></span><br/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div style=" margin-top: -10px; margin-bottom: -10px;">
|
||||
<span>Video Bit Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.videoBitRate }}<span v-if="liveStreamStatus.videoBitRate != -1"> kbps</span></span><br/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div style=" margin-top: -10px; margin-bottom: -10px;">
|
||||
<span>Audio Bit Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.audioBitRate }}<span v-if="liveStreamStatus.audioBitRate != -1"> kbps</span></span><br/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div style=" margin-top: -10px; margin-bottom: -10px;">
|
||||
<span>Packet Loss Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.dropRate }}<span v-if="liveStreamStatus.dropRate != -1"> %</span></span><br/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div style=" margin-top: -10px; margin-bottom: -10px;">
|
||||
<span>RTT:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.rtt }}<span v-if="liveStreamStatus.rtt != -1"> ms</span></span><br/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div style=" margin-top: -10px;">
|
||||
<span >Jitter:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.jitter }}</span><br/>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config'
|
||||
import { onMounted, reactive, ref, UnwrapRef } from 'vue'
|
||||
import { CURRENT_CONFIG as config, CURRENT_CONFIG } from '/@/api/http/config'
|
||||
import { ELiveTypeName, ELiveTypeValue, GB28181Param, LiveConfigParam, LiveStreamStatus, RTSPParam, EVideoPublishType } from '/@/api/live-stream'
|
||||
import apiPilot from '/@/api/pilot-bridge'
|
||||
import { getRoot } from '/@/root'
|
||||
import { ELiveStatusValue, EStatusValue } from '/@/types'
|
||||
import { CaretRightFilled } from '@ant-design/icons-vue'
|
||||
|
||||
const root = getRoot()
|
||||
|
||||
const publishModeList = [
|
||||
{
|
||||
value: 'video-on-demand',
|
||||
label: 'video-on-demand'
|
||||
value: EVideoPublishType.VideoOnDemand,
|
||||
label: EVideoPublishType.VideoOnDemand
|
||||
},
|
||||
{
|
||||
value: 'video-by-manual',
|
||||
label: 'video-by-manual'
|
||||
value: EVideoPublishType.VideoByManual,
|
||||
label: EVideoPublishType.VideoByManual
|
||||
},
|
||||
{
|
||||
value: 'video-demand-aux-manual',
|
||||
label: 'video-demand-aux-manual'
|
||||
value: EVideoPublishType.VideoDemandAuxManual,
|
||||
label: EVideoPublishType.VideoDemandAuxManual
|
||||
}
|
||||
]
|
||||
const liveTypeList = [
|
||||
{
|
||||
value: 1,
|
||||
label: 'AGORA'
|
||||
value: ELiveTypeValue.Agora,
|
||||
label: ELiveTypeName.Agora
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'RTMP'
|
||||
value: ELiveTypeValue.RTMP,
|
||||
label: ELiveTypeName.RTMP
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'RTSP'
|
||||
value: ELiveTypeValue.RTSP,
|
||||
label: ELiveTypeName.RTSP
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: 'GB28181'
|
||||
value: ELiveTypeValue.GB28181,
|
||||
label: ELiveTypeName.GB28181
|
||||
}
|
||||
]
|
||||
const agoraParam = {
|
||||
uid: config.agoraAPPID,
|
||||
uid: '2892130292',
|
||||
token: config.agoraToken,
|
||||
channelId: config.agoraChannel
|
||||
}
|
||||
const rtmpParam = {
|
||||
url: config.rtmpURL + '12345'
|
||||
url: config.rtmpURL + new Date().getTime()
|
||||
}
|
||||
const liveState = ref<string>('STOP')
|
||||
const livetypeSelected = ref<number>(1)
|
||||
const publishModeSelected = ref<string>('video-demand-aux-manual')
|
||||
const rtspParam: RTSPParam = {
|
||||
userName: CURRENT_CONFIG.rtspUserName,
|
||||
password: CURRENT_CONFIG.rtspPassword,
|
||||
port: CURRENT_CONFIG.rtspPort
|
||||
}
|
||||
const gb28181Param: GB28181Param = {
|
||||
serverIp: CURRENT_CONFIG.gbServerIp,
|
||||
serverPort: CURRENT_CONFIG.gbServerPort,
|
||||
serverId: CURRENT_CONFIG.gbServerId,
|
||||
agentId: CURRENT_CONFIG.gbAgentId,
|
||||
password: CURRENT_CONFIG.gbPassword,
|
||||
agentPort: CURRENT_CONFIG.gbAgentPort,
|
||||
agentChannel: CURRENT_CONFIG.gbAgentChannel
|
||||
}
|
||||
|
||||
const playVisiable = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
const liveState = ref(EStatusValue.DISCONNECT)
|
||||
const liveTypeSelected = ref<string>()
|
||||
const publishModeSelected = ref<string>()
|
||||
const liveStreamStatus: LiveStreamStatus = reactive({
|
||||
audioBitRate: -1,
|
||||
dropRate: -1,
|
||||
fps: -1,
|
||||
jitter: -1,
|
||||
quality: -1,
|
||||
rtt: -1,
|
||||
status: -1,
|
||||
type: -1,
|
||||
videoBitRate: -1
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const status: any = apiPilot.getLiveshareStatus()
|
||||
console.log(status)
|
||||
// liveState.value =
|
||||
// status.status === 0
|
||||
// ? 'Cannot connect to server'
|
||||
// : status.status === 1
|
||||
// ? 'Connect to server'
|
||||
// : 'Playing'
|
||||
const config: LiveConfigParam = JSON.parse(apiPilot.getLiveshareConfig())
|
||||
liveStreamStatus.type = config.type
|
||||
refreshLiveType()
|
||||
|
||||
// console.log(liveState.value)
|
||||
window.liveStatusCallback = arg => {
|
||||
liveStatusCallback(arg)
|
||||
}
|
||||
})
|
||||
const onLiveTypeSelect = (val: any) => {
|
||||
livetypeSelected.value = val
|
||||
message.info('set livetype:' + livetypeSelected.value, 5)
|
||||
|
||||
const liveStatusCallback = async (arg: LiveStreamStatus) => {
|
||||
liveStreamStatus.fps = arg.fps
|
||||
liveStreamStatus.audioBitRate = arg.audioBitRate
|
||||
liveStreamStatus.dropRate = arg.dropRate
|
||||
liveStreamStatus.jitter = arg.jitter
|
||||
liveStreamStatus.rtt = arg.rtt
|
||||
liveStreamStatus.videoBitRate = arg.videoBitRate
|
||||
liveStreamStatus.quality = arg.quality
|
||||
liveStreamStatus.type = arg.type
|
||||
liveStreamStatus.status = arg.status
|
||||
|
||||
switch (liveStreamStatus.status) {
|
||||
case ELiveStatusValue.LIVING:
|
||||
liveState.value = EStatusValue.LIVING
|
||||
break
|
||||
case ELiveStatusValue.CONNECTED:
|
||||
liveState.value = EStatusValue.CONNECTED
|
||||
break
|
||||
default:
|
||||
liveState.value = EStatusValue.DISCONNECT
|
||||
}
|
||||
}
|
||||
function refreshLiveType () {
|
||||
switch (liveStreamStatus.type) {
|
||||
case ELiveTypeValue.Agora:
|
||||
liveTypeSelected.value = ELiveTypeName.Agora
|
||||
break
|
||||
case ELiveTypeValue.RTMP:
|
||||
liveTypeSelected.value = ELiveTypeName.RTMP
|
||||
break
|
||||
case ELiveTypeValue.RTSP:
|
||||
liveTypeSelected.value = ELiveTypeName.RTSP
|
||||
break
|
||||
case ELiveTypeValue.GB28181:
|
||||
liveTypeSelected.value = ELiveTypeName.GB28181
|
||||
break
|
||||
default:
|
||||
liveTypeSelected.value = ELiveTypeName.Unknown
|
||||
}
|
||||
}
|
||||
const onLiveTypeSelect = (val: number) => {
|
||||
liveStreamStatus.type = val
|
||||
refreshLiveType()
|
||||
}
|
||||
const onPublishModeSelect = (val: string) => {
|
||||
publishModeSelected.value = val
|
||||
message.info(
|
||||
'set publish mode res:' +
|
||||
apiPilot.setVideoPublishType(publishModeSelected.value),
|
||||
5
|
||||
)
|
||||
apiPilot.setVideoPublishType(publishModeSelected.value)
|
||||
}
|
||||
const onPlay = () => {
|
||||
switch (livetypeSelected.value) {
|
||||
if (!publishModeSelected.value) {
|
||||
message.warn('Please select publish mode!')
|
||||
return
|
||||
}
|
||||
if (liveTypeSelected.value === ELiveTypeName.Unknown) {
|
||||
message.warn('Please select livestream type!')
|
||||
return
|
||||
}
|
||||
switch (liveStreamStatus.type) {
|
||||
case 1: {
|
||||
message.info('agoraParam:' + JSON.stringify(agoraParam))
|
||||
apiPilot.setLiveshareConfig(1, JSON.stringify(agoraParam))
|
||||
apiPilot.startLiveshare()
|
||||
apiPilot.setLiveshareConfig(ELiveTypeValue.Agora, JSON.stringify(agoraParam))
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
message.info('rtmpParam:' + JSON.stringify(rtmpParam))
|
||||
apiPilot.setLiveshareConfig(2, JSON.stringify(rtmpParam))
|
||||
message.info(apiPilot.startLiveshare())
|
||||
apiPilot.setLiveshareConfig(ELiveTypeValue.RTMP, JSON.stringify(rtmpParam))
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
message.info('rtspParam:' + config.rtspPara)
|
||||
apiPilot.setLiveshareConfig(3, config.rtspPara)
|
||||
apiPilot.startLiveshare()
|
||||
apiPilot.setLiveshareConfig(ELiveTypeValue.RTSP, JSON.stringify(rtspParam))
|
||||
break
|
||||
}
|
||||
case 4: {
|
||||
message.info('gb28181Param:' + config.gb28181Para)
|
||||
apiPilot.setLiveshareConfig(4, config.gb28181Para)
|
||||
apiPilot.startLiveshare()
|
||||
apiPilot.setLiveshareConfig(ELiveTypeValue.GB28181, JSON.stringify(gb28181Param))
|
||||
break
|
||||
}
|
||||
}
|
||||
const status = apiPilot.startLiveshare()
|
||||
if (status) {
|
||||
playVisiable.value = true
|
||||
drawerVisible.value = true
|
||||
message.success('success')
|
||||
}
|
||||
}
|
||||
const onGetStatus = () => {
|
||||
const status = apiPilot.getLiveshareStatus()
|
||||
message.info(status, 5)
|
||||
|
||||
const showLivingStatus = () => {
|
||||
drawerVisible.value = !drawerVisible.value
|
||||
}
|
||||
const onGetConfig = () => {
|
||||
const status = apiPilot.getLiveshareConfig()
|
||||
message.info(status, 5)
|
||||
|
||||
const onStop = () => {
|
||||
const status = apiPilot.stopLiveshare()
|
||||
if (status) {
|
||||
message.success('success')
|
||||
playVisiable.value = false
|
||||
drawerVisible.value = false
|
||||
setTimeout(() => {
|
||||
let key: (keyof LiveStreamStatus)
|
||||
for (key in liveStreamStatus) {
|
||||
if (key === 'type') {
|
||||
continue
|
||||
}
|
||||
liveStreamStatus[key] = -1
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="width-100vw height-100vh flex-column flex-justify-start flex-align-start"
|
||||
>
|
||||
<p class="fz16 ml10 mt10 mb10 color-text-title color-font-bold">
|
||||
If Enabled, Pilot will upload photos or videos to the server
|
||||
automatically.
|
||||
<a-layout>
|
||||
<div class="width100 flex-column flex-justify-start flex-align-start" style="background-color: white;">
|
||||
|
||||
<p class="fz16 ml10 mt15 mb10 color-text-title color-font-bold" style="color: #939393">
|
||||
When enabled, photos and videos will be automatically uploaded to this server
|
||||
</p>
|
||||
<div
|
||||
class="flex-row flex-align-center flex-justify-between"
|
||||
style="width: 100%"
|
||||
class="flex-row flex-align-center mt20"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<p class="ml10 mb0 fz16" style="color: black">Auto Upload Photos</p>
|
||||
<p class="ml10 mb0 fz16" style="margin-right: 73vw;">Auto Photo Upload</p>
|
||||
<a-switch
|
||||
class="mt0 mb0"
|
||||
v-model:checked="enablePhotoUpload"
|
||||
@change="onPhotoUpload"
|
||||
></a-switch>
|
||||
@@ -23,34 +21,36 @@
|
||||
>
|
||||
<a-radio-group
|
||||
class="mt10 ml20"
|
||||
v-if="enablePhotoUpload == true"
|
||||
v-if="enablePhotoUpload === true"
|
||||
v-model:value="photoType"
|
||||
defaultChecked="0"
|
||||
@change="onPhototype"
|
||||
>
|
||||
<a-radio :value="0">Original Photo</a-radio>
|
||||
<a-radio class="ml20" :value="1">Preview Photo</a-radio>
|
||||
<a-radio :value="EPhotoType.Original">Original Photo</a-radio>
|
||||
<a-radio class="ml20" :value="EPhotoType.Preview">Preview Photo</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-divider dashed class="mt10 mb0"></a-divider>
|
||||
|
||||
<div class="ml10 mr10" style="width: 96%; margin-top: -10px;">
|
||||
<a-divider />
|
||||
</div>
|
||||
<div
|
||||
class="flex-row flex-align-center flex-justify-between mt10"
|
||||
style="width: 100%"
|
||||
class="flex-row flex-align-center"
|
||||
style="width: 100%; margin-top: -10px;"
|
||||
>
|
||||
<p class="ml10 mb0 fz16" style="color: black">Auto Upload Video</p>
|
||||
<p class="ml10 mb0 fz16" style="margin-right: 73vw;">Auto Video Upload</p>
|
||||
<a-switch
|
||||
@change="onVideoUpload"
|
||||
v-model:checked="enableVideoUpload"
|
||||
></a-switch>
|
||||
</div>
|
||||
<a-divider dashed class="mt10 mb0"></a-divider>
|
||||
|
||||
<div class="ml10 mr10" style="width: 96%; margin-top: -10px;">
|
||||
<a-divider />
|
||||
</div>
|
||||
<div
|
||||
class="flex-row flex-align-center flex-justify-between mt20"
|
||||
style="width: 100%"
|
||||
class="flex-row flex-align-center flex-justify-between mb15"
|
||||
style="width: 100%; margin-top: -10px;"
|
||||
>
|
||||
<p class="ml10 mb0 fz16 color-font-bold" style="color: black">
|
||||
<p class="ml10 mb0 fz16 color-font-bold">
|
||||
Path for uploading media resources in dual-controller mode
|
||||
</p>
|
||||
<a-radio-group
|
||||
@@ -59,11 +59,12 @@
|
||||
button-style="solid"
|
||||
@change="onUploadPath"
|
||||
>
|
||||
<a-radio-button :value="0">Mine</a-radio-button>
|
||||
<a-radio-button :value="1">Another</a-radio-button>
|
||||
<a-radio-button :value="EDownloadOwner.Mine">Mine</a-radio-button>
|
||||
<a-radio-button :value="EDownloadOwner.Others">Another</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -71,58 +72,31 @@ import { message } from 'ant-design-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import apiPilot from '/@/api/pilot-bridge'
|
||||
import { getRoot } from '/@/root'
|
||||
import { EComponentName, EPhotoType, EDownloadOwner } from '/@/types'
|
||||
|
||||
const root = getRoot()
|
||||
|
||||
const enablePhotoUpload = ref<boolean>(true)
|
||||
const enableVideoUpload = ref<boolean>(false)
|
||||
const photoType = ref<number>(1)
|
||||
const uploadPath = ref<number>(0)
|
||||
const enablePhotoUpload = ref<boolean>(apiPilot.getAutoUploadPhoto())
|
||||
const enableVideoUpload = ref<boolean>(apiPilot.getAutoUploadVideo())
|
||||
const photoType = ref<number>(apiPilot.getUploadPhotoType())
|
||||
const uploadPath = ref<number>(apiPilot.getDownloadOwner())
|
||||
|
||||
onMounted(() => {
|
||||
message.info('After setting, please use the physical button of the remote control to return, otherwise the setting is invalid.')
|
||||
|
||||
enablePhotoUpload.value =
|
||||
apiPilot.getAutoUploadPhoto() === undefined
|
||||
? true
|
||||
: apiPilot.getAutoUploadPhoto()
|
||||
enableVideoUpload.value =
|
||||
apiPilot.getAutoUploadVideo() === undefined
|
||||
? false
|
||||
: apiPilot.getAutoUploadVideo()
|
||||
photoType.value =
|
||||
apiPilot.getUploadPhotoType() === undefined
|
||||
? 1
|
||||
: apiPilot.getUploadPhotoType()
|
||||
uploadPath.value =
|
||||
apiPilot.getDownloadOwner() === undefined ? 0 : apiPilot.getDownloadOwner()
|
||||
|
||||
console.log(
|
||||
enablePhotoUpload.value,
|
||||
enableVideoUpload.value,
|
||||
photoType.value,
|
||||
uploadPath.value
|
||||
)
|
||||
apiPilot.setComponentParam('media', {
|
||||
autoUploadPhoto: enablePhotoUpload.value,
|
||||
autoUploadPhotoType: photoType.value,
|
||||
autoUploadVideo: enableVideoUpload.value
|
||||
})
|
||||
})
|
||||
const onPhotoUpload = () => {
|
||||
apiPilot.setAutoUploadPhoto(enablePhotoUpload.value)
|
||||
}
|
||||
const onVideoUpload = () => {
|
||||
console.log(enableVideoUpload.value)
|
||||
apiPilot.setAutoUploadVideo(enableVideoUpload.value)
|
||||
}
|
||||
const onPhototype = () => {
|
||||
console.log(photoType.value)
|
||||
apiPilot.setUploadPhotoType(photoType.value)
|
||||
}
|
||||
const onUploadPath = (e: any) => {
|
||||
apiPilot.setDownloadOwner(uploadPath.value)
|
||||
}
|
||||
onMounted(() => {
|
||||
console.error(apiPilot.getUploadPhotoType())
|
||||
console.error(apiPilot.getAutoUploadVideo())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<a-layout class="width-100 flex-display" style="height: 100vh">
|
||||
<a-layout-header class="header">
|
||||
<Topbar />
|
||||
</a-layout-header>
|
||||
<a-layout-content>
|
||||
<router-view />
|
||||
</a-layout-content>
|
||||
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Topbar from './topbar.vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, reactive, ref, UnwrapRef, watch } from 'vue'
|
||||
import { getPlatformInfo, getUserInfo } from '/@/api/manage'
|
||||
import websocket from '/@/api/websocket'
|
||||
import { useGMapCover } from '/@/hooks/use-g-map-cover'
|
||||
import { getRoot } from '/@/root'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { ELocalStorageKey, ERouterName } from '/@/types'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
|
||||
interface FormState {
|
||||
user: string
|
||||
password: string
|
||||
}
|
||||
|
||||
const root = getRoot()
|
||||
const showLogin = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
const token = localStorage.getItem(ELocalStorageKey.Token)
|
||||
if (!token) {
|
||||
root.$router.push(ERouterName.PROJECT)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/@/styles/index.scss';
|
||||
|
||||
.fontBold {
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: black;
|
||||
color: white;
|
||||
height: 60px;
|
||||
font-size: 15px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="showLogin"
|
||||
class="login flex-column flex-justify-center flex-align-center m0 b0"
|
||||
>
|
||||
class="login flex-column flex-justify-center flex-align-center m0 b0">
|
||||
<a-image
|
||||
style="width: 17vw; height: 10vw; margin-bottom: 50px"
|
||||
src="http://lofrev.net/wp-content/photos/2016/09/dji_logo_png.png"
|
||||
:src="djiLogo"
|
||||
/>
|
||||
<p class="logo fz35 pb50">Cloud API Demo</p>
|
||||
<p class="fz35 pb50" style="color: #2d8cf0">Cloud API Demo</p>
|
||||
<a-form
|
||||
layout="inline"
|
||||
:model="formState"
|
||||
class="flex-row flex-justify-center flex-align-center"
|
||||
>
|
||||
<a-form-item>
|
||||
<a-input v-model:value="formState.user" placeholder="Username">
|
||||
<a-input v-model:value="formState.username" placeholder="Username">
|
||||
<template #prefix
|
||||
><UserOutlined style="color: rgba(0, 0, 0, 0.25)"
|
||||
/></template>
|
||||
@@ -44,115 +42,41 @@
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-else class="project-app-wrapper">
|
||||
<div class="left">
|
||||
<Sidebar />
|
||||
<div class="main-content uranus-scrollbar dark">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="map-wrapper">
|
||||
<GMap />
|
||||
</div>
|
||||
<div class="media-wrapper" v-if="getMediaRoute()">
|
||||
<MediaPanel />
|
||||
</div>
|
||||
<div class="wayline-wrapper" v-if="getWaylineRoute()">
|
||||
<WaylinePanel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import djiLogo from '/@/assets/icons/dji_logo.png'
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { reactive, ref, UnwrapRef } from 'vue'
|
||||
import Sidebar from './sidebar.vue'
|
||||
import { login } from '/@/api/manage'
|
||||
import websocket from '/@/api/websocket'
|
||||
import GMap from '/@/components/GMap.vue'
|
||||
import MediaPanel from '/@/components/MediaPanel.vue'
|
||||
import WaylinePanel from '/@/components/wayline-panel.vue'
|
||||
import { useGMapCover } from '/@/hooks/use-g-map-cover'
|
||||
import { login, LoginBody } from '/@/api/manage'
|
||||
import { getRoot } from '/@/root'
|
||||
import { useMyStore } from '/@/store'
|
||||
|
||||
interface FormState {
|
||||
user: string
|
||||
password: string
|
||||
}
|
||||
import { ELocalStorageKey, ERouterName, EUserType } from '/@/types'
|
||||
import router from '/@/router'
|
||||
|
||||
const root = getRoot()
|
||||
const showLogin = ref(true)
|
||||
const store = useMyStore()
|
||||
const formState: UnwrapRef<FormState> = reactive({
|
||||
user: 'adminPC',
|
||||
password: 'adminPC'
|
||||
const formState: UnwrapRef<LoginBody> = reactive({
|
||||
username: 'adminPC',
|
||||
password: 'adminPC',
|
||||
flag: EUserType.Web,
|
||||
})
|
||||
let socket = {} as any
|
||||
const gMapCoverHook = useGMapCover()
|
||||
|
||||
const onSubmit = async (e: any) => {
|
||||
const result = await login({
|
||||
username: formState.user,
|
||||
password: formState.password
|
||||
})
|
||||
const result = await login(formState)
|
||||
if (result.code === 0) {
|
||||
console.log(result)
|
||||
localStorage.setItem('x-auth-token', result.data.access_token)
|
||||
localStorage.setItem('workspace-id', result.data.workspace_id)
|
||||
localStorage.setItem('username', result.data.username)
|
||||
showLogin.value = false
|
||||
message.info('login success')
|
||||
socket = websocket.init(wsGetMsg)
|
||||
localStorage.setItem(ELocalStorageKey.Token, result.data.access_token)
|
||||
localStorage.setItem(ELocalStorageKey.WorkspaceId, result.data.workspace_id)
|
||||
localStorage.setItem(ELocalStorageKey.Username, result.data.username)
|
||||
localStorage.setItem(ELocalStorageKey.UserId, result.data.user_id)
|
||||
localStorage.setItem(ELocalStorageKey.Flag, EUserType.Web.toString())
|
||||
root.$router.push(ERouterName.MEMBERS)
|
||||
} else {
|
||||
message.error(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
// function wsInfo (data) {
|
||||
// store.commit('SET_DEVICE_INFO', data)
|
||||
// }
|
||||
// function getDeviceInfo () {
|
||||
// const info = store.state.DeviceInfo
|
||||
// console.log(info)
|
||||
const wsGetMsg = async (res: any) => {
|
||||
const payload = JSON.parse(res.data)
|
||||
// console.log(payload)
|
||||
switch (payload.biz_code) {
|
||||
case 'gateway_osd': {
|
||||
store.commit('SET_GATEWAY_INFO', payload.data)
|
||||
break
|
||||
}
|
||||
case 'device_osd': {
|
||||
store.commit('SET_DEVICE_INFO', payload.data)
|
||||
break
|
||||
}
|
||||
case 'map_element_create': {
|
||||
store.commit('SET_MAP_ELEMENT_CREATE', payload.data)
|
||||
break
|
||||
}
|
||||
case 'map_element_update': {
|
||||
store.commit('SET_MAP_ELEMENT_UPDATE', payload.data)
|
||||
break
|
||||
}
|
||||
case 'map_element_delete': {
|
||||
store.commit('SET_MAP_ELEMENT_DELETE', payload.data)
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function getMediaRoute () {
|
||||
return root.$route.name === 'media'
|
||||
}
|
||||
|
||||
function getWaylineRoute () {
|
||||
return root.$route.name === 'wayline'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -161,48 +85,4 @@ function getWaylineRoute () {
|
||||
background-color: $dark-highlight;
|
||||
height: 100vh;
|
||||
}
|
||||
.logo {
|
||||
color: $primary;
|
||||
}
|
||||
.project-app-wrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
transition: width 0.2s ease;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.left {
|
||||
width: 450px;
|
||||
display: flex;
|
||||
background-color: #232323;
|
||||
float: left;
|
||||
}
|
||||
.right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.map-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
color: $text-white-basic;
|
||||
}
|
||||
.media-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
padding: 16px;
|
||||
}
|
||||
.wayline-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="plan">
|
||||
<div class="header">
|
||||
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-input style="background: black;" placeholder="Please enter plan name" v-model:value="planBody.name"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Flight Route" :wrapperCol="{offset: 7}" name="file_id">
|
||||
<router-link
|
||||
:to="{name: 'select-plan'}"
|
||||
@click="selectRoute"
|
||||
>
|
||||
Select Route
|
||||
</router-link>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="planBody.file_id" style="margin-top: -15px;">
|
||||
<div class="wayline-panel" style="padding-top: 5px;">
|
||||
<div class="title">
|
||||
<a-tooltip :title="wayline.name">
|
||||
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ wayline.name }}</div>
|
||||
</a-tooltip>
|
||||
<div class="ml10"><UserOutlined /></div>
|
||||
<a-tooltip :title="wayline.user_name">
|
||||
<div class="ml5 pr10" style="width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ wayline.user_name }}</div>
|
||||
</a-tooltip>
|
||||
</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="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)] }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
|
||||
<span class="mr10">Update at {{ new Date(wayline.update_time).toLocaleString() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="Device" :wrapperCol="{offset: 10}" v-model:value="planBody.dock_sn" name="dock_sn">
|
||||
<router-link
|
||||
:to="{name: 'select-plan'}"
|
||||
@click="selectDevice"
|
||||
>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="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>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="Immediate">
|
||||
<a-switch v-model:checked="planBody.immediate">
|
||||
<template #checkedChildren><CheckOutlined /></template>
|
||||
<template #unCheckedChildren><CloseOutlined /></template>
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item style="position: absolute; bottom: 0px; margin-bottom: 0; margin-left: -10px; width: 280px;">
|
||||
<div class="footer">
|
||||
<a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="onSubmit" :disabled="disabled">OK
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</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>
|
||||
<router-view :name="routeName"/>
|
||||
</div>
|
||||
<div style="position: absolute; top: 15px; right: 10px;">
|
||||
<a style="color: white;" @click="closePanel"><CloseOutlined /></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'
|
||||
import { CheckOutlined, CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ELocalStorageKey, ERouterName } from '/@/types'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { WaylineFile } from '/@/types/wayline'
|
||||
import { Device, EDeviceType } from '/@/types/device'
|
||||
import { createPlan, CreatePlan } from '/@/api/wayline'
|
||||
import { getRoot } from '/@/root'
|
||||
|
||||
const root = getRoot()
|
||||
const store = useMyStore()
|
||||
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
const wayline = computed<WaylineFile>(() => {
|
||||
return store.state.waylineInfo
|
||||
})
|
||||
|
||||
const dock = computed<Device>(() => {
|
||||
return store.state.dockInfo
|
||||
})
|
||||
|
||||
const disabled = ref(false)
|
||||
|
||||
const routeName = ref('')
|
||||
const planBody: UnwrapRef<CreatePlan> = reactive({
|
||||
name: '',
|
||||
file_id: computed(() => store.state.waylineInfo.id),
|
||||
dock_sn: computed(() => store.state.dockInfo.device_sn),
|
||||
immediate: false,
|
||||
type: 'wayline'
|
||||
})
|
||||
const drawerVisible = ref(false)
|
||||
const valueRef = ref()
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: 'Please enter plan name.' },
|
||||
{ max: 20, message: 'Length should be 1 to 20', trigger: 'blur' }
|
||||
],
|
||||
file_id: [{ required: true, message: 'Select Route' }],
|
||||
dock_sn: [{ required: true, message: 'Select Device' }]
|
||||
}
|
||||
|
||||
function onSubmit () {
|
||||
valueRef.value.validate().then(() => {
|
||||
disabled.value = true
|
||||
createPlan(workspaceId, planBody)
|
||||
.then(res => {
|
||||
message.success('Saved Successfully')
|
||||
setTimeout(() => {
|
||||
disabled.value = false
|
||||
}, 1500)
|
||||
}).finally(() => {
|
||||
closePlan()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function closePlan () {
|
||||
root.$router.push('/' + ERouterName.TASK)
|
||||
}
|
||||
|
||||
function closePanel () {
|
||||
drawerVisible.value = false
|
||||
routeName.value = ''
|
||||
}
|
||||
|
||||
function selectRoute () {
|
||||
drawerVisible.value = true
|
||||
routeName.value = 'WaylinePanel'
|
||||
}
|
||||
|
||||
function selectDevice () {
|
||||
drawerVisible.value = true
|
||||
routeName.value = 'DockPanel'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.plan {
|
||||
background-color: #232323;
|
||||
color: white;
|
||||
padding-bottom: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.header {
|
||||
height: 53px;
|
||||
border-bottom: 1px solid #4f4f4f;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.content {
|
||||
height: 100%;
|
||||
form {
|
||||
margin: 10px;
|
||||
}
|
||||
form label, input {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #4f4f4f;
|
||||
min-height: 65px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
button {
|
||||
width: 45%;
|
||||
color: white;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wayline-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;
|
||||
color: white;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
font-weight: bold;
|
||||
margin: 0px 10px 0 10px;
|
||||
}
|
||||
}
|
||||
.panel {
|
||||
background: #3c3c3c;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 10px;
|
||||
height: 70px;
|
||||
width: 95%;
|
||||
font-size: 13px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
.title {
|
||||
display: flex;
|
||||
color: white;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
font-weight: bold;
|
||||
margin: 0px 10px 0 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,518 @@
|
||||
|
||||
<template>
|
||||
<a-menu v-model:selectedKeys="current" mode="horizontal" @select="select">
|
||||
<a-menu-item :key="EDeviceTypeName.Aircraft" class="ml20">
|
||||
Aircraft
|
||||
</a-menu-item>
|
||||
<a-menu-item :key="EDeviceTypeName.Dock">
|
||||
Dock
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<div class="table flex-display flex-column">
|
||||
<a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData" row-key="device_sn" :expandedRowKeys="expandRows"
|
||||
:row-selection="rowSelection" :rowClassName="rowClassName" :scroll="{ x: '100%', y: 600 }"
|
||||
:expandIcon="expandIcon" :loading="loading">
|
||||
<template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col">
|
||||
<div>
|
||||
<a-input
|
||||
v-if="editableData[record.device_sn]"
|
||||
v-model:value="editableData[record.device_sn][col]"
|
||||
style="margin: -5px 0"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ text }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col">
|
||||
<a-tooltip :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #status="{ text }">
|
||||
<span v-if="text" class="flex-row flex-align-center">
|
||||
<span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" />
|
||||
<span>Online</span>
|
||||
</span>
|
||||
<span class="flex-row flex-align-center" v-else>
|
||||
<span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" />
|
||||
<span>Offline</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="editable-row-operations">
|
||||
<span v-if="editableData[record.device_sn]">
|
||||
<a-tooltip title="Confirm changes">
|
||||
<span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="Modification canceled">
|
||||
<span @click="() => delete editableData[record.device_sn]" class="ml15" style="color: #e70102;"><CloseOutlined /></span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span v-else class="flex-align-center flex-row" style="color: #2d8cf0">
|
||||
<a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="Hms Info">
|
||||
<FileSearchOutlined @click="showHms(record)"/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="Edit">
|
||||
<EditOutlined @click="edit(record)" class="ml10" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="Delete">
|
||||
<DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }" class="ml15" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
<a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }" @ok="unbind">
|
||||
<p class="pt10 pl20" style="height: 50px;">Delete device from workspace?</p>
|
||||
<template #title>
|
||||
<div class="flex-row flex-justify-center">
|
||||
<span>Delete devices</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<a-drawer
|
||||
title="Hms Info"
|
||||
placement="right"
|
||||
v-model:visible="hmsVisible"
|
||||
:width="800">
|
||||
<div class="flex-row flex-align-center">
|
||||
<div style="width: 240px;">
|
||||
<a-range-picker
|
||||
v-model:value="time"
|
||||
format="YYYY-MM-DD"
|
||||
:placeholder="['Start Time', 'End Time']"
|
||||
@change="onTimeChange"/>
|
||||
</div>
|
||||
<div class="ml5">
|
||||
<a-select
|
||||
style="width: 150px"
|
||||
v-model:value="param.level"
|
||||
@select="onLevelSelect">
|
||||
<a-select-option
|
||||
v-for="item in levels"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="ml5">
|
||||
<a-select
|
||||
v-model:value="param.domain"
|
||||
:disabled="!param.children_sn || !param.device_sn"
|
||||
style="width: 150px"
|
||||
@select="onDeviceTypeSelect">
|
||||
<a-select-option
|
||||
v-for="item in deviceTypes"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="ml5">
|
||||
<a-input-search
|
||||
v-model:value="param.message"
|
||||
placeholder="input search message"
|
||||
style="width: 200px"
|
||||
@search="getHms"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a-table :columns="hmsColumns" :scroll="{ x: '100%', y: 600 }" :data-source="hmsData.data" :pagination="hmsPaginationProp" @change="refreshHmsData" row-key="hms_id"
|
||||
:rowClassName="rowClassName" :loading="loading">
|
||||
<template #time="{ record }">
|
||||
<div>{{ record.create_time }}</div>
|
||||
<div :style="record.update_time ? '' : record.level === EHmsLevel.CAUTION ? 'color: orange;' :
|
||||
record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'">{{ record.update_time ?? 'It is happening...' }}</div>
|
||||
</template>
|
||||
<template #level="{ text }">
|
||||
<div class="flex-row flex-align-center">
|
||||
<div :class="text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'" style="width: 10px; height: 10px; border-radius: 50%;"></div>
|
||||
<div style="margin-left: 3px;">{{ EHmsLevel[text] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col">
|
||||
<a-tooltip :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
|
||||
import { h, onMounted, reactive, ref, UnwrapRef } from 'vue'
|
||||
import { IPage } from '/@/api/http/type'
|
||||
import { BindBody, bindDevice, getBindingDevices, getDeviceHms, HmsQueryBody, unbindDevice, updateDevice } from '/@/api/manage'
|
||||
import { EDeviceTypeName, EHmsLevel, ELocalStorageKey } from '/@/types'
|
||||
import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined } from '@ant-design/icons-vue'
|
||||
import { Device, DeviceHms } from '/@/types/device'
|
||||
import moment, { Moment } from 'moment'
|
||||
|
||||
interface DeviceData {
|
||||
device: Device[]
|
||||
}
|
||||
const loading = ref(true)
|
||||
const deleteTip = ref<boolean>(false)
|
||||
const deleteSn = ref<string>()
|
||||
const hmsVisible = ref<boolean>(false)
|
||||
const columns: ColumnProps[] = [
|
||||
{ title: 'Model', dataIndex: 'device_name', width: '10%', className: 'titleStyle' },
|
||||
{ title: 'SN', dataIndex: 'device_sn', width: '10%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } },
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'nickname',
|
||||
width: '15%',
|
||||
sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
|
||||
className: 'titleStyle',
|
||||
ellipsis: true,
|
||||
slots: { customRender: 'nickname' }
|
||||
},
|
||||
{ title: 'Firmware Version', dataIndex: 'firmware_version', width: '10%', className: 'titleStyle' },
|
||||
{ title: 'Status', dataIndex: 'status', width: '100px', className: 'titleStyle', slots: { customRender: 'status' } },
|
||||
{
|
||||
title: 'Workspace',
|
||||
dataIndex: 'workspace_name',
|
||||
width: '10%',
|
||||
className: 'titleStyle',
|
||||
ellipsis: true,
|
||||
slots: { customRender: 'workspace' },
|
||||
customRender: ({ text, record, index }) => {
|
||||
const obj = {
|
||||
children: text,
|
||||
props: {} as any,
|
||||
}
|
||||
if (current.value.indexOf(EDeviceTypeName.Aircraft) !== -1 || (!record.child_device_sn && record.domain === EDeviceTypeName.Dock)) {
|
||||
return obj
|
||||
}
|
||||
|
||||
obj.props.rowSpan = record.domain === EDeviceTypeName.Dock ? 2 : 0
|
||||
return obj
|
||||
}
|
||||
},
|
||||
{ title: 'Joined', dataIndex: 'bound_time', width: '15%', sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' },
|
||||
{ title: 'Last Online', dataIndex: 'login_time', width: '15%', sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' },
|
||||
{
|
||||
title: 'Actions',
|
||||
dataIndex: 'actions',
|
||||
className: 'titleStyle',
|
||||
slots: { customRender: 'action' }
|
||||
},
|
||||
]
|
||||
const expandIcon = (props: any) => {
|
||||
if (judgeCurrentType(EDeviceTypeName.Dock) && !props.expanded) {
|
||||
return h('div',
|
||||
{
|
||||
style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;',
|
||||
class: 'mt-5 ml0',
|
||||
})
|
||||
}
|
||||
}
|
||||
const rowClassName = (record: any, index: number) => {
|
||||
const className = []
|
||||
if ((index & 1) === 0) {
|
||||
className.push('table-striped')
|
||||
}
|
||||
if (record.domain !== EDeviceTypeName.Dock) {
|
||||
className.push('child-row')
|
||||
}
|
||||
return className.toString().replaceAll(',', ' ')
|
||||
}
|
||||
|
||||
const expandRows = ref<string[]>([])
|
||||
const data = reactive<DeviceData>({
|
||||
device: []
|
||||
})
|
||||
|
||||
const paginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const rowSelection = {
|
||||
onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {
|
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
|
||||
},
|
||||
onSelect: (record: any, selected: boolean, selectedRows: []) => {
|
||||
console.log(record, selected, selectedRows)
|
||||
},
|
||||
onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {
|
||||
console.log(selected, selectedRows, changeRows)
|
||||
},
|
||||
getCheckboxProps: (record: any) => ({
|
||||
disabled: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock,
|
||||
style: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock ? 'display: none' : ''
|
||||
}),
|
||||
}
|
||||
type Pagination = TableState['pagination']
|
||||
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
const editableData: UnwrapRef<Record<string, Device>> = reactive({})
|
||||
|
||||
const current = ref([EDeviceTypeName.Aircraft])
|
||||
|
||||
onMounted(() => {
|
||||
getDevices(workspaceId, body, current.value[0])
|
||||
})
|
||||
|
||||
function judgeCurrentType (type: EDeviceTypeName): boolean {
|
||||
return current.value.indexOf(type) !== -1
|
||||
}
|
||||
|
||||
function getDevices (workspaceId: string, body: IPage, domain: string) {
|
||||
loading.value = true
|
||||
getBindingDevices(workspaceId, body, domain).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const resData: Device[] = res.data.list
|
||||
expandRows.value = []
|
||||
resData.forEach((val: any) => {
|
||||
if (val.children) {
|
||||
val.children = [val.children]
|
||||
}
|
||||
if (judgeCurrentType(EDeviceTypeName.Dock)) {
|
||||
expandRows.value.push(val.device_sn)
|
||||
}
|
||||
})
|
||||
data.device = resData
|
||||
paginationProp.total = res.data.pagination.total
|
||||
paginationProp.current = res.data.pagination.page
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function refreshData (page: Pagination) {
|
||||
body.page = page?.current!
|
||||
body.page_size = page?.pageSize!
|
||||
getDevices(workspaceId, body, current.value[0])
|
||||
}
|
||||
|
||||
function edit (record: Device) {
|
||||
editableData[record.device_sn] = record
|
||||
}
|
||||
|
||||
function save (record: Device) {
|
||||
delete editableData[record.device_sn]
|
||||
updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn)
|
||||
}
|
||||
|
||||
function showDeleteTip (sn: any) {
|
||||
deleteTip.value = true
|
||||
}
|
||||
|
||||
function unbind () {
|
||||
deleteTip.value = false
|
||||
unbindDevice(deleteSn.value?.toString()!).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
getDevices(workspaceId, body, current.value[0])
|
||||
})
|
||||
}
|
||||
|
||||
function select (item: any) {
|
||||
getDevices(workspaceId, body, item.key)
|
||||
}
|
||||
|
||||
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: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
|
||||
]
|
||||
|
||||
interface DeviceHmsData {
|
||||
data: DeviceHms[]
|
||||
}
|
||||
const hmsData = reactive<DeviceHmsData>({
|
||||
data: []
|
||||
})
|
||||
|
||||
const hmsPaginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const hmsPage: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
|
||||
function showHms (dock: Device) {
|
||||
hmsVisible.value = true
|
||||
if (dock.domain === EDeviceTypeName.Dock) {
|
||||
param.domain = ''
|
||||
getDeviceHmsBySn(dock.device_sn, dock.children?.[0].device_sn ?? '')
|
||||
}
|
||||
if (dock.domain === EDeviceTypeName.Aircraft) {
|
||||
param.domain = EDeviceTypeName.Aircraft
|
||||
getDeviceHmsBySn('', dock.device_sn)
|
||||
}
|
||||
}
|
||||
|
||||
function refreshHmsData (page: Pagination) {
|
||||
hmsPage.page = page?.current!
|
||||
hmsPage.page_size = page?.pageSize!
|
||||
getHms()
|
||||
}
|
||||
|
||||
const param = reactive<HmsQueryBody>({
|
||||
sns: [],
|
||||
device_sn: '',
|
||||
children_sn: '',
|
||||
language: 'en',
|
||||
begin_time: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0),
|
||||
end_time: new Date().setHours(23, 59, 59, 999),
|
||||
domain: '',
|
||||
level: '',
|
||||
message: ''
|
||||
})
|
||||
|
||||
const levels = [
|
||||
{
|
||||
label: 'All',
|
||||
value: ''
|
||||
}, {
|
||||
label: EHmsLevel[0],
|
||||
value: EHmsLevel.NOTICE
|
||||
}, {
|
||||
label: EHmsLevel[1],
|
||||
value: EHmsLevel.CAUTION
|
||||
}, {
|
||||
label: EHmsLevel[2],
|
||||
value: EHmsLevel.WARN
|
||||
}
|
||||
]
|
||||
|
||||
const deviceTypes = [
|
||||
{
|
||||
label: 'All',
|
||||
value: ''
|
||||
}, {
|
||||
label: EDeviceTypeName.Aircraft,
|
||||
value: EDeviceTypeName.Aircraft
|
||||
}, {
|
||||
label: EDeviceTypeName.Dock,
|
||||
value: EDeviceTypeName.Dock
|
||||
}
|
||||
]
|
||||
|
||||
const time = ref([moment(param.begin_time), moment(param.end_time)])
|
||||
|
||||
function getHms () {
|
||||
getDeviceHms(param, workspaceId, hmsPage)
|
||||
.then(res => {
|
||||
hmsPaginationProp.total = res.data.pagination.total
|
||||
hmsPaginationProp.current = res.data.pagination.page
|
||||
hmsData.data = res.data.list
|
||||
hmsData.data.forEach(hms => {
|
||||
hms.domain = hms.sn === param.children_sn ? EDeviceTypeName.Aircraft : EDeviceTypeName.Dock
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getDeviceHmsBySn (sn: string, childSn: string) {
|
||||
param.device_sn = sn
|
||||
param.children_sn = childSn
|
||||
param.sns = [param.device_sn, param.children_sn]
|
||||
getHms()
|
||||
}
|
||||
|
||||
function onTimeChange (newTime: [Moment, Moment]) {
|
||||
param.begin_time = newTime[0].valueOf()
|
||||
param.end_time = newTime[1].valueOf()
|
||||
getHms()
|
||||
}
|
||||
|
||||
function onDeviceTypeSelect (val: string) {
|
||||
param.sns = [param.device_sn, param.children_sn]
|
||||
if (val === EDeviceTypeName.Dock) {
|
||||
param.sns = [param.device_sn, '']
|
||||
}
|
||||
if (val === EDeviceTypeName.Aircraft) {
|
||||
param.sns = ['', param.children_sn]
|
||||
}
|
||||
getHms()
|
||||
}
|
||||
|
||||
function onLevelSelect (val: number) {
|
||||
param.level = val
|
||||
getHms()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
.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;
|
||||
}
|
||||
.child-row {
|
||||
height: 70px;
|
||||
}
|
||||
.notice {
|
||||
background: $success;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.caution {
|
||||
background: orange;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.warn {
|
||||
background: red;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
|
||||
<a-row>
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="22">Devices</a-col>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from '@vue/reactivity'
|
||||
import { message } from 'ant-design-vue'
|
||||
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 { useMyStore } from '/@/store'
|
||||
import { getBindingDevices } from '/@/api/manage'
|
||||
import { IPage } from '/@/api/http/type'
|
||||
|
||||
const store = useMyStore()
|
||||
|
||||
const docksData = reactive({
|
||||
data: [] as Device[]
|
||||
})
|
||||
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
onMounted(() => {
|
||||
getDocks()
|
||||
})
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 100
|
||||
}
|
||||
function getDocks () {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
function selectDock (dock: Device) {
|
||||
store.commit('SET_SELECT_DOCK_INFO', dock)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.panel {
|
||||
background: #3c3c3c;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 10px;
|
||||
height: 70px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div class="project-layer-wrapper">
|
||||
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
|
||||
<a-row>
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="22">Annotations</a-col>
|
||||
<a-col :span="1"></a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<LayersTree
|
||||
:layer-data="mapLayers"
|
||||
class="project-layer-content"
|
||||
@@ -389,9 +396,6 @@ function updateCoordinates (transformType: string, element: LayerResource) {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/@/styles/index.scss';
|
||||
.project-layer-wrapper {
|
||||
padding-top: 16px;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.drawer-element-wrapper {
|
||||
|
||||
@@ -1,65 +1,56 @@
|
||||
<template>
|
||||
<div class="flex-column flex-justify-start flex-align-center">
|
||||
<a-button
|
||||
class="mt10 "
|
||||
style="width:90%"
|
||||
type="primary"
|
||||
@click="onAgoraLiveStream"
|
||||
>Agora Live</a-button
|
||||
<router-link
|
||||
style="width: 90%; margin: auto;"
|
||||
v-for="item in options"
|
||||
:key="item.key"
|
||||
:to="item.path"
|
||||
:class="{
|
||||
'menu-item': true,
|
||||
}"
|
||||
>
|
||||
<a-button
|
||||
class="mt10"
|
||||
style="width:90%"
|
||||
style="width:100%;"
|
||||
type="primary"
|
||||
@click="onOthersLive"
|
||||
>RTMP/GB28181 Live</a-button
|
||||
@click="selectLivestream(item.routeName)"
|
||||
>{{ item.label }}</a-button
|
||||
>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-if="enableAgoraLive">
|
||||
<a-modal
|
||||
style="top:0"
|
||||
v-model:visible="enableAgoraLive"
|
||||
title="Agora Live"
|
||||
width="100%"
|
||||
:maskClosable="false"
|
||||
wrapClassName="full-modal"
|
||||
:footer="null"
|
||||
>
|
||||
<LiveAgora />
|
||||
</a-modal>
|
||||
</div>
|
||||
<div v-if="enableOthersLive">
|
||||
<a-modal
|
||||
style="top:0"
|
||||
v-model:visible="enableOthersLive"
|
||||
title="RTMP/GB28181/RTSP Live"
|
||||
width="100%"
|
||||
:maskClosable="false"
|
||||
wrapClassName="full-modal"
|
||||
:footer="null"
|
||||
>
|
||||
<LiveOthers />
|
||||
</a-modal>
|
||||
<div class="live" v-if="showLive">
|
||||
<a style="position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;" @click="() => root.$router.push('/' + ERouterName.LIVESTREAM)"><CloseOutlined /></a>
|
||||
<router-view :name="routeName" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import LiveAgora from './livestream-agora.vue'
|
||||
import LiveOthers from './livestream-others.vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
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 enableAgoraLive = ref(false)
|
||||
const enableOthersLive = ref(false)
|
||||
const onAgoraLiveStream = () => {
|
||||
console.log('agora')
|
||||
enableAgoraLive.value = true
|
||||
}
|
||||
const onOthersLive = () => {
|
||||
console.log('liveview')
|
||||
enableOthersLive.value = true
|
||||
const options = [
|
||||
{ key: 0, label: 'Agora Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveAgora' },
|
||||
{ key: 1, label: 'RTMP/GB28181 Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveOthers' }
|
||||
]
|
||||
|
||||
const selectLivestream = (route: string) => {
|
||||
routeName.value = route
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
watch(() => root.$route.name, data => {
|
||||
showLive.value = data === ERouterName.LIVING
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -79,4 +70,17 @@ const onOthersLive = () => {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.live {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: 50%;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
width: 800px;
|
||||
height: 700px;
|
||||
background: #232323;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div class="project-media-wrapper">
|
||||
Media
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
|
||||
<template>
|
||||
<div class="table flex-display flex-column">
|
||||
<a-table :columns="columns" :data-source="data.member" :pagination="paginationProp" @change="refreshData" row-key="user_id"
|
||||
:row-selection="rowSelection" :rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)" :scroll="{ x: '100%', y: 600 }">
|
||||
<template v-for="col in ['mqtt_username', 'mqtt_password']" #[col]="{ text, record }" :key="col">
|
||||
<div>
|
||||
<a-input
|
||||
v-if="editableData[record.user_id]"
|
||||
v-model:value="editableData[record.user_id][col]"
|
||||
style="margin: -5px 0"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ text }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<div class="editable-row-operations">
|
||||
<span v-if="editableData[record.user_id]">
|
||||
<a-tooltip title="Confirm changes">
|
||||
<span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="Modification canceled">
|
||||
<span @click="() => delete editableData[record.user_id]" class="ml15" style="color: #e70102;"><CloseOutlined /></span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span v-else class="fz18 flex-align-center flex-row" style="color: #2d8cf0">
|
||||
<EditOutlined @click="edit(record)" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { message, 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 { getAllUsersInfo, updateUserInfo } from '/@/api/manage'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export interface Member {
|
||||
user_id: string
|
||||
username: string
|
||||
user_type: string
|
||||
workspace_name: string
|
||||
create_time: string
|
||||
mqtt_username: string
|
||||
mqtt_password: string
|
||||
}
|
||||
|
||||
interface MemberData {
|
||||
member: Member[]
|
||||
}
|
||||
const columns = [
|
||||
{ title: 'Account', dataIndex: 'username', width: 250, sorter: (a: Member, b: Member) => a.username.localeCompare(b.username), className: 'titleStyle' },
|
||||
{ title: 'User Type', dataIndex: 'user_type', width: 250, className: 'titleStyle' },
|
||||
{ title: 'Workspace Name', dataIndex: 'workspace_name', width: 250, className: 'titleStyle' },
|
||||
{ title: 'Mqtt Username', dataIndex: 'mqtt_username', width: 250, className: 'titleStyle', slots: { customRender: 'mqtt_username' } },
|
||||
{ title: 'Mqtt Password', dataIndex: 'mqtt_password', width: 250, className: 'titleStyle', slots: { customRender: 'mqtt_password' } },
|
||||
{ title: 'Joined', dataIndex: 'create_time', width: 250, sorter: (a: Member, b: Member) => a.create_time.localeCompare(b.create_time), className: 'titleStyle' },
|
||||
{ title: 'Action', dataIndex: 'action', className: 'titleStyle', slots: { customRender: 'action' } },
|
||||
]
|
||||
|
||||
const data = reactive<MemberData>({
|
||||
member: []
|
||||
})
|
||||
|
||||
const editableData: UnwrapRef<Record<string, Member>> = reactive({})
|
||||
|
||||
const paginationProp = reactive({
|
||||
pageSizeOptions: ['20', '50', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: 50,
|
||||
current: 1,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const rowSelection = {
|
||||
onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {
|
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
|
||||
},
|
||||
onSelect: (record: any, selected: boolean, selectedRows: []) => {
|
||||
console.log(record, selected, selectedRows)
|
||||
},
|
||||
onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {
|
||||
console.log(selected, selectedRows, changeRows)
|
||||
},
|
||||
}
|
||||
type Pagination = TableState['pagination']
|
||||
|
||||
const body: IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 50
|
||||
}
|
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
|
||||
onMounted(() => {
|
||||
getAllUsers(workspaceId, body)
|
||||
})
|
||||
|
||||
function refreshData (page: Pagination) {
|
||||
body.page = page?.current!
|
||||
body.page_size = page?.pageSize!
|
||||
getAllUsers(workspaceId, body)
|
||||
}
|
||||
|
||||
function getAllUsers (workspaceId: string, page: IPage) {
|
||||
getAllUsersInfo(workspaceId, page).then(res => {
|
||||
const userList: Member[] = res.data.list
|
||||
data.member = userList
|
||||
paginationProp.total = res.data.pagination.total
|
||||
paginationProp.current = res.data.pagination.page
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function edit (record: Member) {
|
||||
editableData[record.user_id] = record
|
||||
}
|
||||
|
||||
function save (record: Member) {
|
||||
delete editableData[record.user_id]
|
||||
updateUserInfo(workspaceId, record.user_id, record).then(res => {
|
||||
if (res.code !== 0) {
|
||||
message.error(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</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>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
|
||||
<a-row>
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="20">Task Plan Library</a-col>
|
||||
<a-col :span="2">
|
||||
<span v-if="!createPlanTip">
|
||||
<router-link :to="{ name: 'create-plan'}">
|
||||
<PlusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = true"/>
|
||||
</router-link>
|
||||
</span>
|
||||
<span v-else>
|
||||
<router-link :to="{ name: 'task'}">
|
||||
<MinusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = false"/>
|
||||
</router-link>
|
||||
</span>
|
||||
</a-col>
|
||||
<a-col :span="1"></a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div v-if="createPlanTip">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined, MinusOutlined } from '@ant-design/icons-vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const createPlanTip = ref(false)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,11 +1,463 @@
|
||||
<template>
|
||||
<div class="project-tsa-wrapper">
|
||||
TSA
|
||||
<div class="project-tsa-wrapper ">
|
||||
<div>
|
||||
<a-row>
|
||||
<a-col :span="1"></a-col>
|
||||
<a-col :span="11">My Username</a-col>
|
||||
<a-col :span="11" align="right" style="font-weight: 700">{{ username }}</a-col>
|
||||
<a-col :span="1"></a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div>
|
||||
<a-collapse :bordered="false" expandIconPosition="right" accordion style="background: #232323;">
|
||||
<a-collapse-panel :key="EDeviceTypeName.Dock" header="Dock" style="border-bottom: 1px solid #4f4f4f;">
|
||||
<div v-if="onlineDocks.data.length === 0" style="height: 150px; color: white;">
|
||||
<a-empty :image="noData" :image-style="{ height: '60px' }" />
|
||||
</div>
|
||||
<div v-else class="fz12" style="color: white;">
|
||||
<div v-for="dock in onlineDocks.data" :key="dock.sn" style="background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;">
|
||||
<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>
|
||||
</div>
|
||||
<div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
|
||||
<div>
|
||||
<span class="ml5 mr5"><RobotOutlined /></span>
|
||||
<span class="font-bold" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
|
||||
{{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].mode_code] : EDockModeCode[EDockModeCode.Disconnected] }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
|
||||
<div v-if="hmsInfo[dock.gateway.sn]" class="flex-align-center flex-row">
|
||||
<div :class="hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :
|
||||
hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'" style="width: 18px; height: 16px; text-align: center;">
|
||||
<span :style="hmsInfo[dock.gateway.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'">{{ hmsInfo[dock.gateway.sn].length }}</span>
|
||||
<span class="fz10">{{ hmsInfo[dock.gateway.sn].length > 99 ? '+' : ''}}</span>
|
||||
</div>
|
||||
<a-popover trigger="click" placement="bottom" color="black" v-model:visible="hmsVisible[dock.gateway.sn]"
|
||||
@visibleChange="readHms(hmsVisible[dock.gateway.sn], dock.gateway.sn)"
|
||||
:overlayStyle="{width: '200px', height: '300px'}">
|
||||
<div :class="hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution' :
|
||||
hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'" style="margin-left: 3px; width: 62px; height: 16px;">
|
||||
<span class="word-loop">{{ hmsInfo[dock.gateway.sn][0].message_en }}</span>
|
||||
</div>
|
||||
<template #content>
|
||||
<a-collapse style="background: black; height: 300px; overflow-y: auto;" :bordered="false" expand-icon-position="right" :accordion="true">
|
||||
<a-collapse-panel v-for="hms in hmsInfo[dock.gateway.sn]" :key="hms.hms_id" :showArrow="false"
|
||||
style=" margin: 0 auto 3px auto; border: 0; width: 140px; border-radius: 3px"
|
||||
:class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
|
||||
>
|
||||
<template #header="{ isActive }">
|
||||
<div class="flex-row flex-align-center" style="width: 130px;">
|
||||
<div style="width: 110px;">
|
||||
<span class="word-loop">{{ hms.message_en }}</span>
|
||||
</div>
|
||||
<div style="width: 20px; height: 15px; font-size: 10px; z-index: 2 " class="flex-row flex-align-center flex-justify-center"
|
||||
:class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
|
||||
>
|
||||
<DoubleRightOutlined :rotate="isActive ? 90 : 0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-tooltip :title="hms.create_time">
|
||||
<div style="color: white;" class="text-hidden">{{ hms.create_time }}</div>
|
||||
</a-tooltip>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div v-else class="width-100" style="height: 90%; background: rgba(0, 0, 0, 0.35)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
|
||||
<div>
|
||||
<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;'">
|
||||
{{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
|
||||
<div v-if="hmsInfo[dock.sn]" class="flex-align-center flex-row">
|
||||
<div :class="hmsInfo[dock.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :
|
||||
hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'" style="width: 18px; height: 16px; text-align: center;">
|
||||
<span :style="hmsInfo[dock.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'">{{ hmsInfo[dock.sn].length }}</span>
|
||||
<span class="fz10">{{ hmsInfo[dock.sn].length > 99 ? '+' : ''}}</span>
|
||||
</div>
|
||||
<a-popover trigger="click" placement="bottom" color="black" v-model:visible="hmsVisible[dock.sn]" @visibleChange="readHms(hmsVisible[dock.sn], dock.sn)"
|
||||
:overlayStyle="{width: '200px', height: '300px'}">
|
||||
<div :class="hmsInfo[dock.sn][0].level === EHmsLevel.CAUTION ? 'caution' :
|
||||
hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'" style="margin-left: 3px; width: 62px; height: 16px;">
|
||||
<span class="word-loop">{{ hmsInfo[dock.sn][0].message_en }}</span>
|
||||
</div>
|
||||
<template #content>
|
||||
<a-collapse style="background: black; height: 300px; overflow-y: auto;" :bordered="false" expand-icon-position="right" :accordion="true">
|
||||
<a-collapse-panel v-for="hms in hmsInfo[dock.sn]" :key="hms.hms_id" :showArrow="false"
|
||||
style=" margin: 0 auto 3px auto; border: 0; width: 140px; border-radius: 3px"
|
||||
:class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
|
||||
>
|
||||
<template #header="{ isActive }">
|
||||
<div class="flex-row flex-align-center" style="width: 130px;">
|
||||
<div style="width: 110px;">
|
||||
<span class="word-loop">{{ hms.message_en }}</span>
|
||||
</div>
|
||||
<div style="width: 20px; height: 15px; font-size: 10px; z-index: 2 " class="flex-row flex-align-center flex-justify-center"
|
||||
:class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
|
||||
>
|
||||
<DoubleRightOutlined :rotate="isActive ? 90 : 0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-tooltip :title="hms.create_time">
|
||||
<div style="color: white;" class="text-hidden">{{ hms.create_time }}</div>
|
||||
</a-tooltip>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div v-else class="width-100" style="height: 90%; background: rgba(0, 0, 0, 0.35)"></div>
|
||||
</div>
|
||||
</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)">
|
||||
<a v-if="osdVisible.gateway_sn === dock.gateway.sn && osdVisible.visible"><EyeOutlined /></a>
|
||||
<a v-else><EyeInvisibleOutlined /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-collapse :bordered="false" expandIconPosition="right" accordion style="background: #232323;">
|
||||
<a-collapse-panel :key="EDeviceTypeName.Aircraft" header="Online Devices" style="border-bottom: 1px solid #4f4f4f;">
|
||||
<div v-if="onlineDevices.data.length === 0" style="height: 150px; color: white;">
|
||||
<a-empty :image="noData" :image-style="{ height: '60px' }" />
|
||||
</div>
|
||||
<div v-else class="fz12" style="color: white;">
|
||||
<div v-for="device in onlineDevices.data" :key="device.sn" style="background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;">
|
||||
<div class="battery-slide" v-if="deviceInfo[device.sn]">
|
||||
<div style="background: #535759; width: 100%;"></div>
|
||||
<div class="capacity-percent" :style="{ width: deviceInfo[device.sn].battery.capacity_percent + '%'}"></div>
|
||||
<div class="return-home" :style="{ width: deviceInfo[device.sn].battery.return_home_power + '%'}"></div>
|
||||
<div class="landing" :style="{ width: deviceInfo[device.sn].battery.landing_power + '%'}"></div>
|
||||
<div class="battery" :style="{ left: deviceInfo[device.sn].battery.capacity_percent + '%' }"></div>
|
||||
</div>
|
||||
<div style="border-bottom: 1px solid #515151; border-radius: 2px; height: 50px; width: 100%;" class="flex-row flex-justify-between flex-align-center">
|
||||
<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>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="mt5" style="background: #595959;">
|
||||
<span class="ml5 mr5"><RocketOutlined /></span>
|
||||
<span class="font-bold" :style="deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
|
||||
{{ deviceInfo[device.sn] ? EModeCode[deviceInfo[device.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="float: right; background: #595959; height: 50px; width: 40px;" class="flex-row flex-justify-center flex-align-center">
|
||||
<div class="fz16" @click="switchVisible($event, device, false, deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected)">
|
||||
<a v-if="osdVisible.sn === device.sn && osdVisible.visible"><EyeOutlined /></a>
|
||||
<a v-else><EyeInvisibleOutlined /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row flex-justify-center flex-align-center" style="height: 40px;">
|
||||
<div style="height: 20px; background: #595959; width: 94%;" >
|
||||
<span class="mr5"><a-image style="margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;" :src="rc" /></span>
|
||||
<a-tooltip>
|
||||
<template #title>{{ device.gateway.callsign }} </template>
|
||||
<span>{{ device.gateway.callsign }}</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref, watch, WritableComputedRef } from 'vue'
|
||||
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 { 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 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 onlineDevices = reactive({
|
||||
data: [] as OnlineDevice[]
|
||||
})
|
||||
|
||||
const onlineDocks = reactive({
|
||||
data: [] as OnlineDevice[]
|
||||
})
|
||||
|
||||
const deviceInfo = computed(() => store.state.deviceState.deviceInfo)
|
||||
const dockInfo = computed(() => store.state.deviceState.dockInfo)
|
||||
const hmsInfo = computed({
|
||||
get: () => store.state.hmsInfo,
|
||||
set: (val) => {
|
||||
return val
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getOnlineTopo()
|
||||
setTimeout(() => {
|
||||
watch(() => store.state.deviceStatusEvent,
|
||||
data => {
|
||||
getOnlineTopo()
|
||||
if (data.deviceOnline.sn) {
|
||||
getUnreadHms(data.deviceOnline.sn)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
getOnlineDeviceHms()
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
function getOnlineTopo () {
|
||||
getDeviceTopo(workspaceId.value).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
onlineDevices.data = []
|
||||
onlineDocks.data = []
|
||||
res.data.forEach((val: any) => {
|
||||
const gateway = val.gateways_list.pop()
|
||||
const device: OnlineDevice = {
|
||||
model: val.device_name,
|
||||
callsign: val.nickname,
|
||||
sn: val.device_sn,
|
||||
mode: EModeCode.Disconnected,
|
||||
gateway: {
|
||||
model: gateway?.device_name,
|
||||
callsign: gateway?.nickname,
|
||||
sn: gateway?.device_sn,
|
||||
domain: gateway?.domain
|
||||
},
|
||||
payload: []
|
||||
}
|
||||
val.payloads_list.forEach((payload: any) => {
|
||||
device.payload.push({
|
||||
model: payload.payload_name
|
||||
})
|
||||
})
|
||||
if (gateway && 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) {
|
||||
onlineDevices.data.push(device)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick: boolean) {
|
||||
if (!isClick) {
|
||||
e.target.style.cursor = 'not-allowed'
|
||||
return
|
||||
}
|
||||
if (device.sn === osdVisible.value.sn) {
|
||||
osdVisible.value.visible = !osdVisible.value.visible
|
||||
} else {
|
||||
osdVisible.value.sn = device.sn
|
||||
osdVisible.value.callsign = device.callsign
|
||||
osdVisible.value.model = device.model
|
||||
osdVisible.value.visible = true
|
||||
osdVisible.value.gateway_sn = device.gateway.sn
|
||||
osdVisible.value.is_dock = isDock
|
||||
osdVisible.value.gateway_callsign = device.gateway.callsign
|
||||
}
|
||||
store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
|
||||
}
|
||||
|
||||
function getUnreadHms (sn: string) {
|
||||
getUnreadDeviceHms(workspaceId.value, sn).then(res => {
|
||||
if (res.data.length !== 0) {
|
||||
hmsInfo.value[sn] = res.data
|
||||
}
|
||||
})
|
||||
console.info(hmsInfo.value)
|
||||
}
|
||||
|
||||
function getOnlineDeviceHms () {
|
||||
const snList = Object.keys(dockInfo.value)
|
||||
if (snList.length === 0) {
|
||||
return
|
||||
}
|
||||
snList.forEach(sn => {
|
||||
getUnreadHms(sn)
|
||||
})
|
||||
const deviceSnList = Object.keys(deviceInfo.value)
|
||||
if (deviceSnList.length === 0) {
|
||||
return
|
||||
}
|
||||
deviceSnList.forEach(sn => {
|
||||
getUnreadHms(sn)
|
||||
})
|
||||
}
|
||||
|
||||
function readHms (visiable: boolean, sn: string) {
|
||||
if (!visiable) {
|
||||
updateDeviceHms(workspaceId.value, sn).then(res => {
|
||||
if (res.code === 0) {
|
||||
delete hmsInfo.value[sn]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.project-tsa-wrapper > :first-child {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #4f4f4f;
|
||||
}
|
||||
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
|
||||
color: white;
|
||||
border: 0;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.text-hidden {
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap;
|
||||
-o-text-overflow: ellipsis;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.battery-slide {
|
||||
width: 100%;
|
||||
.capacity-percent {
|
||||
background: #00ee8b;
|
||||
}
|
||||
.return-home {
|
||||
background: #ff9f0a;
|
||||
}
|
||||
.landing {
|
||||
background: #f5222d;
|
||||
}
|
||||
.battery {
|
||||
background: white;
|
||||
border-radius: 1px;
|
||||
width: 8px;
|
||||
height: 4px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
.battery-slide > div {
|
||||
position: relative;
|
||||
margin-top: -2px;
|
||||
min-height: 2px;
|
||||
border-radius: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.disable {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.notice-blink {
|
||||
background: $success;
|
||||
animation: blink 500ms infinite;
|
||||
}
|
||||
.caution-blink {
|
||||
background: orange;
|
||||
animation: blink 500ms infinite;
|
||||
}
|
||||
.warn-blink {
|
||||
background: red;
|
||||
animation: blink 500ms infinite;
|
||||
}
|
||||
.notice {
|
||||
background: $success;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.caution {
|
||||
background: orange;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.warn {
|
||||
background: red;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.word-loop {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
animation: 10s loop linear infinite normal;
|
||||
}
|
||||
@keyframes blink {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.35;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes loop {
|
||||
0% {
|
||||
transform: translateX(20px);
|
||||
-webkit-transform: translateX(20px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
-webkit-transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,205 @@
|
||||
<template>
|
||||
<div class="project-wayline-wrapper">
|
||||
wayline
|
||||
<div class="project-wayline-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>
|
||||
<a-col :span="22">Flight Route Library</a-col>
|
||||
<a-col :span="1"></a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="height-100">
|
||||
<div class="scrollbar uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll">
|
||||
<div v-for="wayline in waylinesData.data" :key="wayline.id">
|
||||
<div class="wayline-panel" style="padding-top: 5px;" @click="selectRoute(wayline)">
|
||||
<div class="title">
|
||||
<a-tooltip :title="wayline.name">
|
||||
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ wayline.name }}</div>
|
||||
</a-tooltip>
|
||||
<div class="ml10"><UserOutlined /></div>
|
||||
<a-tooltip :title="wayline.user_name">
|
||||
<div class="ml5 pr10" style="width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ wayline.user_name }}</div>
|
||||
</a-tooltip>
|
||||
<div class="fz20">
|
||||
<a-dropdown>
|
||||
<a style="color: white;">
|
||||
<EllipsisOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu theme="dark" class="more" style="background: #3c3c3c;">
|
||||
<a-menu-item @click="downloadWayline(wayline.id, wayline.name)">
|
||||
<span>Download</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="showWaylineTip(wayline.id)">
|
||||
<span>Delete</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</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="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)] }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
|
||||
<span class="mr10">Update at {{ new Date(wayline.update_time).toLocaleString() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
|
||||
</div>
|
||||
<a-modal v-model:visible="deleteTip" width="450px" :closable="false" :maskClosable="false" centered :okButtonProps="{ danger: true }" @ok="deleteWayline">
|
||||
<p class="pt10 pl20" style="height: 50px;">Wayline file is unrecoverable once deleted. Continue?</p>
|
||||
<template #title>
|
||||
<div class="flex-row flex-justify-center">
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from '@vue/reactivity'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onMounted, onUpdated, ref } from 'vue'
|
||||
import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles } from '/@/api/wayline'
|
||||
import { ELocalStorageKey } from '/@/types'
|
||||
import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { EDeviceType } from '/@/types/device'
|
||||
import { useMyStore } from '/@/store'
|
||||
import { WaylineFile } from '/@/types/wayline'
|
||||
import { downloadFile } from '/@/utils/common'
|
||||
import { IPage } from '/@/api/http/type'
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
const store = useMyStore()
|
||||
const pagination :IPage = {
|
||||
page: 1,
|
||||
total: 0,
|
||||
page_size: 10
|
||||
}
|
||||
|
||||
const waylinesData = reactive({
|
||||
data: [] as WaylineFile[]
|
||||
})
|
||||
|
||||
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
||||
const deleteTip = ref(false)
|
||||
const deleteWaylineId = ref<string>('')
|
||||
const canRefresh = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
getWaylines()
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
|
||||
const parent = element?.parentNode as HTMLDivElement
|
||||
setTimeout(() => {
|
||||
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'
|
||||
}
|
||||
}, 300)
|
||||
})
|
||||
|
||||
function getWaylines () {
|
||||
if (!canRefresh.value) {
|
||||
return
|
||||
}
|
||||
canRefresh.value = false
|
||||
getWaylineFiles(workspaceId, {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
order_by: 'update_time desc'
|
||||
}).then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
res.data.list.forEach((wayline: WaylineFile) => waylinesData.data.push(wayline))
|
||||
pagination.total = res.data.pagination.total
|
||||
pagination.page = res.data.pagination.page
|
||||
}).finally(() => {
|
||||
canRefresh.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function showWaylineTip (waylineId: string) {
|
||||
deleteWaylineId.value = waylineId
|
||||
deleteTip.value = true
|
||||
}
|
||||
|
||||
function deleteWayline () {
|
||||
deleteWaylineFile(workspaceId, deleteWaylineId.value).then(res => {
|
||||
if (res.code === 0) {
|
||||
message.success('Wayline file deleted')
|
||||
}
|
||||
deleteWaylineId.value = ''
|
||||
deleteTip.value = false
|
||||
pagination.total--
|
||||
waylinesData.data = []
|
||||
setTimeout(getWaylines, 500)
|
||||
})
|
||||
}
|
||||
|
||||
function downloadWayline (waylineId: string, fileName: string) {
|
||||
downloadWaylineFile(workspaceId, waylineId).then(res => {
|
||||
if (res.code && res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = new Blob([res.data], { type: 'application/zip' })
|
||||
downloadFile(data, fileName + '.kmz')
|
||||
})
|
||||
}
|
||||
|
||||
function selectRoute (wayline: WaylineFile) {
|
||||
store.commit('SET_SELECT_WAYLINE_INFO', wayline)
|
||||
}
|
||||
|
||||
function onScroll (e: any) {
|
||||
const element = e.srcElement
|
||||
if (element.scrollTop + element.clientHeight === element.scrollHeight && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
|
||||
pagination.page++
|
||||
getWaylines()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wayline-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;
|
||||
}
|
||||
}
|
||||
.uranus-scrollbar {
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #c5c8cc transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="project-app-wrapper">
|
||||
<div class="left">
|
||||
<Sidebar />
|
||||
<div class="main-content uranus-scrollbar dark">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="map-wrapper">
|
||||
<GMap />
|
||||
</div>
|
||||
<div class="media-wrapper" v-if="root.$route.name === ERouterName.MEDIA">
|
||||
<MediaPanel />
|
||||
</div>
|
||||
<div class="media-wrapper" v-if="root.$route.name === ERouterName.TASK">
|
||||
<TaskPanel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
import Sidebar from '../sidebar.vue'
|
||||
import MediaPanel from '@/components/MediaPanel.vue'
|
||||
import TaskPanel from '@/components/TaskPanel.vue'
|
||||
import GMap from '/@/components/GMap.vue'
|
||||
import { EBizCode, ERouterName } from '/@/types'
|
||||
import { getRoot } from '/@/root'
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
import { useMyStore } from '/@/store'
|
||||
import websocket from '/@/api/websocket'
|
||||
// import { enableAgoraLive, enableOthersLive } from '/@/pages/project-app/projects/livestream.vue'
|
||||
|
||||
const root = getRoot()
|
||||
|
||||
const wsGetMsg = async (res: any) => {
|
||||
const payload = JSON.parse(res.data)
|
||||
switch (payload.biz_code) {
|
||||
case EBizCode.GatewayOsd: {
|
||||
store.commit('SET_GATEWAY_INFO', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.DeviceOsd: {
|
||||
store.commit('SET_DEVICE_INFO', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.DockOsd: {
|
||||
store.commit('SET_DOCK_INFO', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.MapElementCreate: {
|
||||
store.commit('SET_MAP_ELEMENT_CREATE', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.MapElementUpdate: {
|
||||
store.commit('SET_MAP_ELEMENT_UPDATE', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.MapElementDelete: {
|
||||
store.commit('SET_MAP_ELEMENT_DELETE', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.DeviceOnline: {
|
||||
store.commit('SET_DEVICE_ONLINE', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.DeviceOffline: {
|
||||
store.commit('SET_DEVICE_OFFLINE', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.FlightTaskProgress: {
|
||||
store.commit('SET_FLIGHT_TASK_PROGRESS', payload.data)
|
||||
break
|
||||
}
|
||||
case EBizCode.DeviceHms: {
|
||||
store.commit('SET_DEVICE_HMS_INFO', payload.data)
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const store = useMyStore()
|
||||
|
||||
let socket: ReconnectingWebSocket
|
||||
|
||||
onMounted(() => {
|
||||
socket = websocket.init(wsGetMsg)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
socket.close()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '/@/styles/index.scss';
|
||||
|
||||
.project-app-wrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
transition: width 0.2s ease;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.left {
|
||||
width: 400px;
|
||||
display: flex;
|
||||
background-color: #232323;
|
||||
float: left;
|
||||
}
|
||||
.right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.map-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
color: $text-white-basic;
|
||||
}
|
||||
.media-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
}
|
||||
.wayline-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #f6f8fa;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="demo-project-sidebar-wrapper">
|
||||
<div class="demo-project-sidebar-wrapper flex-justify-between">
|
||||
<div>
|
||||
<router-link
|
||||
v-for="item in options"
|
||||
:key="item.key"
|
||||
@@ -7,19 +8,28 @@
|
||||
:class="{
|
||||
'menu-item': true,
|
||||
selected: selectedRoute(item),
|
||||
disabled: item.key > 6
|
||||
}"
|
||||
>
|
||||
<a-tooltip :title="item.label" placement="right">
|
||||
<span>{{ item.label }}</span>
|
||||
<Icon class="fz20" style="width: 50px;" :icon="item.icon"/>
|
||||
</a-tooltip>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="mb20 flex-display flex-column flex-align-center flex-justify-between">
|
||||
<a-tooltip title="Back to home" placement="right">
|
||||
<a @click="goHome"> <Icon icon="ImportOutlined" style="font-size: 22px; color: white"/></a>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { createVNode, defineComponent } from 'vue'
|
||||
import { getRoot } from '/@/root'
|
||||
import * as icons from '@ant-design/icons-vue'
|
||||
import { ERouterName } from '/@/types'
|
||||
import websocket from '/@/api/websocket'
|
||||
|
||||
interface IOptions {
|
||||
key: number
|
||||
label: string
|
||||
@@ -32,26 +42,38 @@ interface IOptions {
|
||||
icon: string
|
||||
}
|
||||
|
||||
const Icon = (props: {icon: string}) => {
|
||||
return createVNode((icons as any)[props.icon])
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
name: 'Sidebar',
|
||||
setup () {
|
||||
const root = getRoot()
|
||||
const options = [
|
||||
{ key: 0, label: 'livestream', path: '/livestream', icon: 'livestream' },
|
||||
{ key: 1, label: 'tsa', path: '/tsa', icon: 'tsa' },
|
||||
{ key: 2, label: 'layer', path: '/layer', icon: 'layer' },
|
||||
{ key: 3, label: 'media', path: '/media', icon: 'media' },
|
||||
{ key: 4, label: 'wayline', path: '/wayline', icon: 'wayline' }
|
||||
{ key: 0, label: 'Tsa', path: '/' + ERouterName.TSA, icon: 'TeamOutlined' },
|
||||
{ key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },
|
||||
{ key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
|
||||
{ key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
|
||||
{ key: 4, label: 'Fligth Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
|
||||
{ key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' }
|
||||
]
|
||||
|
||||
function selectedRoute (item: IOptions) {
|
||||
const path = typeof item.path === 'string' ? item.path : item.path.path
|
||||
return root.$route.path?.indexOf(path) === 0
|
||||
}
|
||||
|
||||
function goHome () {
|
||||
root.$router.push('/' + ERouterName.MEMBERS)
|
||||
websocket.close()
|
||||
}
|
||||
return {
|
||||
options,
|
||||
selectedRoute
|
||||
selectedRoute,
|
||||
goHome,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -62,7 +84,7 @@ export default defineComponent({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 80px;
|
||||
width: 50px;
|
||||
border-right: 1px solid #4f4f4f;
|
||||
color: $text-white-basic;
|
||||
// flex: 1;
|
||||
@@ -76,7 +98,7 @@ export default defineComponent({
|
||||
color: $text-white-basic;
|
||||
cursor: pointer;
|
||||
&.selected {
|
||||
background-color: $dark-highlight;
|
||||
background-color: #101010;
|
||||
color: $primary;
|
||||
}
|
||||
&.disabled {
|
||||
@@ -95,8 +117,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.ant-tooltip-open {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div class="width-100 flex-row flex-justify-between flex-align-center" style="height: 60px;">
|
||||
<div class="height-100">
|
||||
<a-avatar :size="40" shape="square" :src="cloudapi" />
|
||||
<span class="ml10 fontBold">{{ workspaceName }}</span>
|
||||
</div>
|
||||
|
||||
<a-space class="fz16 height-100" size="large">
|
||||
<router-link
|
||||
v-for="item in options"
|
||||
:key="item.key"
|
||||
:to="item.path"
|
||||
:class="{
|
||||
'menu-item': true,
|
||||
}">
|
||||
<span @click="selectedRoute(item.path)" :style="selected === item.path ? 'color: #2d8cf0;' : 'color: white'">{{ item.label }}</span>
|
||||
</router-link>
|
||||
</a-space>
|
||||
|
||||
<div class="height-100 fz16 flex-row flex-justify-between flex-align-center">
|
||||
<a-dropdown>
|
||||
<div class="height-100">
|
||||
<span class="fz20 mt20" style="border: 2px solid white; border-radius: 50%; display: inline-flex;"><UserOutlined /></span>
|
||||
<span class="ml10 mr10" style="float: right;">{{ username }}</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu theme="dark" class="flex-column flex-justify-between flex-align-center">
|
||||
<a-menu-item>
|
||||
<span class="mr10" style="font-size: 16px;"><ExportOutlined /></span>
|
||||
<span @click="logout">Log Out</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue'
|
||||
import { defineComponent, onMounted, ref } from 'vue'
|
||||
import { getRoot } from '/@/root'
|
||||
import { getPlatformInfo } from '/@/api/manage'
|
||||
import { ELocalStorageKey, ERouterName } from '/@/types'
|
||||
import { UserOutlined, ExportOutlined } from '@ant-design/icons-vue'
|
||||
import cloudapi from '/@/assets/icons/cloudapi.png'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
import websocket from '/@/api/websocket'
|
||||
|
||||
const root = getRoot()
|
||||
|
||||
interface IOptions {
|
||||
key: number
|
||||
label: string
|
||||
path:
|
||||
| string
|
||||
| {
|
||||
path: string
|
||||
query?: any
|
||||
}
|
||||
icon: string
|
||||
}
|
||||
const username = ref(localStorage.getItem(ELocalStorageKey.Username))
|
||||
const workspaceName = ref('')
|
||||
const options = [
|
||||
{ key: 0, label: ERouterName.WORKSPACE.charAt(0).toUpperCase() + ERouterName.WORKSPACE.substr(1), path: '/' + ERouterName.WORKSPACE },
|
||||
{ key: 1, label: ERouterName.MEMBERS.charAt(0).toUpperCase() + ERouterName.MEMBERS.substr(1), path: '/' + ERouterName.MEMBERS },
|
||||
{ key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES }
|
||||
]
|
||||
|
||||
const selected = ref<string>(root.$route.path)
|
||||
|
||||
onMounted(() => {
|
||||
getPlatformInfo().then(res => {
|
||||
workspaceName.value = res.data.workspace_name
|
||||
})
|
||||
})
|
||||
|
||||
function selectedRoute (path: string) {
|
||||
selected.value = path
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
localStorage.clear()
|
||||
root.$router.push(ERouterName.PROJECT)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/@/styles/index.scss';
|
||||
|
||||
.fontBold {
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,63 +1,127 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
import { ERouterName } from '/@/types/index'
|
||||
import CreatePlan from '../pages/project-app/projects/create-plan.vue'
|
||||
import WaylinePanel from '/@/pages/project-app/projects/wayline.vue'
|
||||
import DockPanel from '/@/pages/project-app/projects/dock.vue'
|
||||
import LiveAgora from '/@/components/livestream-agora.vue'
|
||||
import LiveOthers from '/@/components/livestream-others.vue'
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/' + ERouterName.Project,
|
||||
name: ERouterName.Project,
|
||||
// redirect: {
|
||||
// name: ERouterName.Project
|
||||
// },
|
||||
component: () => import('/@/pages/project-app/index.vue'),
|
||||
path: '/',
|
||||
redirect: '/' + ERouterName.PROJECT
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.PROJECT,
|
||||
name: ERouterName.PROJECT,
|
||||
component: () => import('/@/pages/project-app/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.HOME,
|
||||
name: ERouterName.HOME,
|
||||
component: () => import('/@/pages/project-app/home.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/' + ERouterName.Livestream,
|
||||
component: () => import('/@/pages/project-app/projects/livestream.vue')
|
||||
path: '/' + ERouterName.MEMBERS,
|
||||
name: ERouterName.MEMBERS,
|
||||
component: () => import('/@/pages/project-app/projects/members.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.Tsa,
|
||||
path: '/' + ERouterName.DEVICES,
|
||||
name: ERouterName.DEVICES,
|
||||
component: () => import('/@/pages/project-app/projects/devices.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.WORKSPACE,
|
||||
name: ERouterName.WORKSPACE,
|
||||
component: () => import('/@/pages/project-app/projects/workspace.vue'),
|
||||
redirect: '/' + ERouterName.TSA,
|
||||
children: [
|
||||
{
|
||||
path: '/' + ERouterName.LIVESTREAM,
|
||||
name: ERouterName.LIVESTREAM,
|
||||
component: () => import('/@/pages/project-app/projects/livestream.vue'),
|
||||
children: [
|
||||
{
|
||||
path: ERouterName.LIVING,
|
||||
name: ERouterName.LIVING,
|
||||
components: {
|
||||
LiveAgora,
|
||||
LiveOthers
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.TSA,
|
||||
component: () => import('/@/pages/project-app/projects/tsa.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.Layer,
|
||||
name: ERouterName.Layer,
|
||||
path: '/' + ERouterName.LAYER,
|
||||
name: ERouterName.LAYER,
|
||||
component: () => import('/@/pages/project-app/projects/layer.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.Media,
|
||||
name: ERouterName.Media,
|
||||
path: '/' + ERouterName.MEDIA,
|
||||
name: ERouterName.MEDIA,
|
||||
component: () => import('/@/pages/project-app/projects/media.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.Wayline,
|
||||
name: ERouterName.Wayline,
|
||||
path: '/' + ERouterName.WAYLINE,
|
||||
name: ERouterName.WAYLINE,
|
||||
component: () => import('/@/pages/project-app/projects/wayline.vue')
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.Pilot,
|
||||
name: ERouterName.Pilot,
|
||||
component: () => import('/@/pages/page-pilot/pilot-index.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/' + ERouterName.TASK,
|
||||
name: ERouterName.TASK,
|
||||
component: () => import('/@/pages/project-app/projects/task.vue'),
|
||||
children: [
|
||||
{
|
||||
path: ERouterName.CREATE_PLAN,
|
||||
name: ERouterName.CREATE_PLAN,
|
||||
component: CreatePlan,
|
||||
children: [
|
||||
{
|
||||
path: ERouterName.SELECT_PLAN,
|
||||
name: ERouterName.SELECT_PLAN,
|
||||
components: {
|
||||
WaylinePanel,
|
||||
DockPanel
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.PilotHome,
|
||||
path: '/' + ERouterName.PILOT,
|
||||
name: ERouterName.PILOT,
|
||||
component: () => import('/@/pages/page-pilot/pilot-index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.PILOT_HOME,
|
||||
component: () => import('/@/pages/page-pilot/pilot-home.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.PilotMedia,
|
||||
path: '/' + ERouterName.PILOT_MEDIA,
|
||||
component: () => import('/@/pages/page-pilot/pilot-media.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.PilotLiveshare,
|
||||
path: '/' + ERouterName.PILOT_LIVESHARE,
|
||||
component: () => import('/@/pages/page-pilot/pilot-liveshare.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.Element,
|
||||
name: ERouterName.Element,
|
||||
path: '/' + ERouterName.PILOT_BIND,
|
||||
component: () => import('/@/pages/page-pilot/pilot-bind.vue')
|
||||
},
|
||||
{
|
||||
path: '/' + ERouterName.ELEMENT,
|
||||
name: ERouterName.ELEMENT,
|
||||
component: () => import('/@/pages/elements/elements.vue')
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { InjectionKey } from 'vue'
|
||||
import { ActionTree, createStore, GetterTree, MutationTree, Store, StoreOptions, useStore } from 'vuex'
|
||||
import { EDeviceTypeName } from '../types'
|
||||
import { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device'
|
||||
import { getLayers } from '/@/api/layer'
|
||||
import { LayerType } from '/@/types/mapLayer'
|
||||
import { ETaskStatus, TaskInfo, WaylineFile } from '/@/types/wayline'
|
||||
|
||||
const initStateFunc = () => ({
|
||||
Layers: [
|
||||
{
|
||||
@@ -23,12 +27,6 @@ const initStateFunc = () => ({
|
||||
type: 2
|
||||
}
|
||||
],
|
||||
GatewayInfo: { // remote controller, dock
|
||||
|
||||
},
|
||||
DeviceInfo: { // drone
|
||||
|
||||
},
|
||||
layerBaseInfo: {} as {
|
||||
[key:string]:string
|
||||
},
|
||||
@@ -40,6 +38,55 @@ const initStateFunc = () => ({
|
||||
mapElementCreat: {},
|
||||
mapElementUpdate: {},
|
||||
mapElementDelete: {}
|
||||
},
|
||||
deviceStatusEvent: {
|
||||
deviceOnline: {} as DeviceStatus,
|
||||
deviceOffline: {}
|
||||
},
|
||||
markerInfo: {
|
||||
coverMap: {} as {
|
||||
[sn: string]: any
|
||||
},
|
||||
pathMap: {} as {
|
||||
[sn: string]: any[]
|
||||
}
|
||||
},
|
||||
deviceState: {
|
||||
// remote controller, dock
|
||||
gatewayInfo: {} as {
|
||||
[sn: string]: GatewayOsd
|
||||
},
|
||||
// drone
|
||||
deviceInfo: {} as {
|
||||
[sn: string]: DeviceOsd
|
||||
},
|
||||
dockInfo: {} as {
|
||||
[sn: string]: DockOsd
|
||||
},
|
||||
currentSn: '',
|
||||
currentType: ''
|
||||
},
|
||||
osdVisible: {
|
||||
sn: '',
|
||||
callsign: '',
|
||||
model: '',
|
||||
visible: false,
|
||||
gateway_sn: '',
|
||||
is_dock: false,
|
||||
} as OSDVisible,
|
||||
waylineInfo: {
|
||||
|
||||
} as WaylineFile,
|
||||
dockInfo: {
|
||||
|
||||
} as Device,
|
||||
taskProgressInfo: {
|
||||
|
||||
} as {
|
||||
[bid: string]: TaskInfo
|
||||
},
|
||||
hmsInfo: {} as {
|
||||
[sn: string]: DeviceHms[]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -52,12 +99,29 @@ const mutations: MutationTree<RootStateType> = {
|
||||
state.Layers = info
|
||||
},
|
||||
SET_DEVICE_INFO (state, info) {
|
||||
state.DeviceInfo = info
|
||||
// console.log(state.DeviceInfo)
|
||||
state.deviceState.deviceInfo[info.sn] = info.host
|
||||
state.deviceState.currentSn = info.sn
|
||||
state.deviceState.currentType = EDeviceTypeName.Aircraft
|
||||
},
|
||||
SET_GATEWAY_INFO (state, info) {
|
||||
state.GatewayInfo = info
|
||||
// console.log(state.GatewayInfo)
|
||||
state.deviceState.gatewayInfo[info.sn] = info.host
|
||||
state.deviceState.currentSn = info.sn
|
||||
state.deviceState.currentType = EDeviceTypeName.Gateway
|
||||
},
|
||||
SET_DOCK_INFO (state, info) {
|
||||
state.deviceState.currentSn = info.sn
|
||||
state.deviceState.currentType = EDeviceTypeName.Dock
|
||||
const dock = state.deviceState.dockInfo[info.sn]
|
||||
if (info.host.sdr && state.deviceState.dockInfo[info.sn]) {
|
||||
dock.sdr = info.host.sdr
|
||||
dock.media_file_detail = info.host.media_file_detail
|
||||
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
|
||||
},
|
||||
SET_DRAW_VISIBLE_INFO (state, bool) {
|
||||
state.drawVisible = bool
|
||||
@@ -71,6 +135,43 @@ const mutations: MutationTree<RootStateType> = {
|
||||
SET_MAP_ELEMENT_DELETE (state, info) {
|
||||
state.wsEvent.mapElementDelete = info
|
||||
},
|
||||
SET_DEVICE_ONLINE (state, info) {
|
||||
state.deviceStatusEvent.deviceOnline = info
|
||||
},
|
||||
SET_DEVICE_OFFLINE (state, info) {
|
||||
state.deviceStatusEvent.deviceOffline = info
|
||||
delete state.deviceState.gatewayInfo[info.sn]
|
||||
delete state.deviceState.deviceInfo[info.sn]
|
||||
delete state.deviceState.dockInfo[info.sn]
|
||||
delete state.hmsInfo[info.sn]
|
||||
|
||||
// delete state.markerInfo.coverMap[info.sn]
|
||||
// delete state.markerInfo.pathMap[info.sn]
|
||||
},
|
||||
SET_OSD_VISIBLE_INFO (state, info) {
|
||||
state.osdVisible = info
|
||||
},
|
||||
SET_SELECT_WAYLINE_INFO (state, info) {
|
||||
state.waylineInfo = info
|
||||
},
|
||||
SET_SELECT_DOCK_INFO (state, info) {
|
||||
state.dockInfo = info
|
||||
},
|
||||
SET_FLIGHT_TASK_PROGRESS (state, info) {
|
||||
const taskInfo: TaskInfo = info.output
|
||||
|
||||
if (taskInfo.status === ETaskStatus.OK || taskInfo.status === ETaskStatus.FAILED) {
|
||||
taskInfo.status = taskInfo.status.concat('(Code:').concat(info.result).concat(')')
|
||||
setTimeout(() => {
|
||||
delete state.taskProgressInfo[info.bid]
|
||||
}, 60000)
|
||||
}
|
||||
state.taskProgressInfo[info.bid] = info.output
|
||||
},
|
||||
SET_DEVICE_HMS_INFO (state, info) {
|
||||
const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn]
|
||||
state.hmsInfo[info.sn] = info.host.concat(hmsList ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<RootStateType, RootStateType> = {
|
||||
|
||||
@@ -10,10 +10,22 @@ body {
|
||||
// Prevent font enlargement in horizontal screen
|
||||
text-size-adjust: 100%;
|
||||
|
||||
font-family: Roboto, sans-serif-medium, Arial, sans-serif;
|
||||
font-family: sans-serif, Roboto, sans-serif-medium, Arial;
|
||||
font-feature-settings: normal;
|
||||
color: $main-text-color;
|
||||
font-size: 14px;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
background: rgb(89, 89, 89);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ $line-heights: (
|
||||
}
|
||||
|
||||
.fz10 {
|
||||
font-size: 10;
|
||||
font-size: 10px;
|
||||
}
|
||||
.fz12 {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import { EDeviceTypeName } from ".";
|
||||
|
||||
export interface Device {
|
||||
device_name: string,
|
||||
device_sn: string,
|
||||
nickname: string,
|
||||
firmware_version: string,
|
||||
status: string,
|
||||
workspace_name: string,
|
||||
bound_time: string,
|
||||
login_time: string,
|
||||
children?: Device[]
|
||||
domain: string
|
||||
}
|
||||
|
||||
export interface DeviceStatus {
|
||||
sn: string,
|
||||
online_status: boolean,
|
||||
device_callsign: string,
|
||||
user_id: string,
|
||||
user_callsign: string
|
||||
bound_status: boolean,
|
||||
model: string,
|
||||
gateway_sn: string,
|
||||
domain: string
|
||||
}
|
||||
|
||||
export interface OSDVisible {
|
||||
sn: string,
|
||||
model: string,
|
||||
callsign: string,
|
||||
visible: boolean,
|
||||
is_dock: boolean,
|
||||
gateway_sn: string,
|
||||
gateway_callsign: string,
|
||||
}
|
||||
|
||||
export interface GatewayOsd {
|
||||
capacity_percent: string,
|
||||
transmission_signal_quality: string,
|
||||
longitude: number,
|
||||
latitude: number,
|
||||
}
|
||||
export interface DeviceOsd {
|
||||
longitude: number,
|
||||
latitude: number,
|
||||
gear: number,
|
||||
mode_code: number,
|
||||
height: string,
|
||||
home_distance: string,
|
||||
horizontal_speed: string,
|
||||
vertical_speed: string,
|
||||
wind_speed: string,
|
||||
wind_direction: string,
|
||||
elevation: string,
|
||||
position_state: {
|
||||
gps_number: string,
|
||||
is_fixed: number,
|
||||
rtk_number: string
|
||||
},
|
||||
battery: {
|
||||
capacity_percent: string,
|
||||
landing_power: string,
|
||||
remain_flight_time: number,
|
||||
return_home_power: string,
|
||||
}
|
||||
}
|
||||
|
||||
export interface DockOsd {
|
||||
media_file_detail: {
|
||||
remain_upload: number
|
||||
},
|
||||
sdr: {
|
||||
up_quality: string,
|
||||
down_quality: string,
|
||||
frequency_band: number,
|
||||
},
|
||||
network_state: {
|
||||
type: number,
|
||||
quality: number,
|
||||
rate: number,
|
||||
},
|
||||
drone_in_dock: number,
|
||||
drone_charge_state: {
|
||||
state: number,
|
||||
capacity_percent: string,
|
||||
},
|
||||
rainfall: string,
|
||||
wind_speed: string,
|
||||
environment_temperature: string,
|
||||
environment_humidity: string
|
||||
temperature: string,
|
||||
humidity: string,
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
height: number,
|
||||
job_number: number,
|
||||
acc_time: number,
|
||||
first_power_on: number,
|
||||
positionState: {
|
||||
gps_number: string,
|
||||
is_fixed: number,
|
||||
rtk_number: string,
|
||||
is_calibration: number,
|
||||
quality: number,
|
||||
},
|
||||
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,
|
||||
putter_state: number,
|
||||
sub_device: {
|
||||
device_sn: string,
|
||||
device_model_key: string,
|
||||
device_online_status: number,
|
||||
device_paired: number,
|
||||
},
|
||||
}
|
||||
|
||||
export enum EModeCode {
|
||||
Standby,
|
||||
Preparing,
|
||||
Ready,
|
||||
Manual,
|
||||
Automatic,
|
||||
Waypoint,
|
||||
Panoramic,
|
||||
Active_Track,
|
||||
ADS_B,
|
||||
Return_To_Home,
|
||||
Landing,
|
||||
Forced_Landing,
|
||||
Three_Blades_Landing,
|
||||
Upgrading,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
export enum EGear {
|
||||
A,
|
||||
P,
|
||||
NAV,
|
||||
FPV,
|
||||
FARM,
|
||||
S,
|
||||
F,
|
||||
M,
|
||||
G,
|
||||
T
|
||||
}
|
||||
|
||||
export enum 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,
|
||||
}
|
||||
|
||||
export enum EDockModeCode {
|
||||
Disconnected = -1,
|
||||
Idle,
|
||||
Debugging,
|
||||
Remote_Debugging,
|
||||
Upgrading,
|
||||
Working,
|
||||
}
|
||||
|
||||
export interface DeviceHms {
|
||||
hms_id: string,
|
||||
tid: string,
|
||||
bid: string,
|
||||
sn: string,
|
||||
level: number,
|
||||
module: number,
|
||||
key: string,
|
||||
message_en: string,
|
||||
message_zh: string,
|
||||
create_time: string,
|
||||
update_time: string,
|
||||
domain: string
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
export enum ERouterName {
|
||||
Element = 'element',
|
||||
Project = 'project',
|
||||
Tsa = 'tsa',
|
||||
Layer = 'layer',
|
||||
Media = 'media',
|
||||
Wayline = 'wayline',
|
||||
Livestream = 'livestream',
|
||||
Pilot = 'pilot-login',
|
||||
PilotHome = 'pilot-home',
|
||||
PilotMedia = 'pilot-media',
|
||||
PilotLiveshare = 'pilot-liveshare'
|
||||
ELEMENT = 'element',
|
||||
PROJECT = 'project',
|
||||
HOME = 'home',
|
||||
TSA = 'tsa',
|
||||
LAYER = 'layer',
|
||||
MEDIA = 'media',
|
||||
WAYLINE = 'wayline',
|
||||
LIVESTREAM = 'livestream',
|
||||
LIVING = 'living',
|
||||
WORKSPACE = 'workspace',
|
||||
MEMBERS = 'members',
|
||||
DEVICES = 'devices',
|
||||
TASK = 'task',
|
||||
CREATE_PLAN = 'create-plan',
|
||||
SELECT_PLAN = 'select-plan',
|
||||
|
||||
PILOT = 'pilot-login',
|
||||
PILOT_HOME = 'pilot-home',
|
||||
PILOT_MEDIA = 'pilot-media',
|
||||
PILOT_LIVESHARE = 'pilot-liveshare',
|
||||
PILOT_BIND = 'pilot-bind'
|
||||
}
|
||||
|
||||
export enum EStorageKey {
|
||||
@@ -17,3 +27,81 @@ export enum EStorageKey {
|
||||
TEST_TOOLS_POSITION_STORAGE_KEY = 'DJI_CREATE_VITE_H5_APP:test_tools_position',
|
||||
SESSION_ID = 'DJI_CREATE_VITE_H5_APP:sess'
|
||||
}
|
||||
|
||||
export enum EStatusValue {
|
||||
CONNECTED = 'Connected',
|
||||
DISCONNECT = 'Disconnect',
|
||||
LIVING = 'Living'
|
||||
}
|
||||
|
||||
export enum ELiveStatusValue {
|
||||
DISCONNECT,
|
||||
CONNECTED,
|
||||
LIVING
|
||||
}
|
||||
|
||||
export enum EComponentName {
|
||||
Thing = 'thing',
|
||||
Liveshare = 'liveshare',
|
||||
Api = 'api',
|
||||
Ws = 'ws',
|
||||
Map = 'map',
|
||||
Tsa = 'tsa',
|
||||
Media = 'media',
|
||||
Mission = 'mission'
|
||||
}
|
||||
|
||||
export enum ELocalStorageKey {
|
||||
Username = 'username',
|
||||
WorkspaceId = 'workspace_id',
|
||||
Token = 'x-auth-token',
|
||||
PlatformName = 'platform_name',
|
||||
WorkspaceName = 'workspace_name',
|
||||
WorkspaceDesc = 'workspace_desc',
|
||||
Flag = 'flag',
|
||||
UserId = 'user_id',
|
||||
Device = 'device',
|
||||
GatewayOnline = 'gateway_online',
|
||||
}
|
||||
|
||||
export enum EPhotoType {
|
||||
Original = 0,
|
||||
Preview = 1,
|
||||
Unknown = -1
|
||||
}
|
||||
|
||||
export enum EDownloadOwner {
|
||||
Mine = 0,
|
||||
Others = 1,
|
||||
Unknown = -1
|
||||
}
|
||||
|
||||
export enum EUserType {
|
||||
Web = 1,
|
||||
Pilot = 2,
|
||||
}
|
||||
|
||||
export enum EBizCode {
|
||||
GatewayOsd = 'gateway_osd',
|
||||
DeviceOsd = 'device_osd',
|
||||
DockOsd = 'dock_osd',
|
||||
MapElementCreate = 'map_element_create',
|
||||
MapElementUpdate = 'map_element_update',
|
||||
MapElementDelete = 'map_element_delete',
|
||||
DeviceOnline = 'device_online',
|
||||
DeviceOffline = 'device_offline',
|
||||
FlightTaskProgress = 'flighttask_progress',
|
||||
DeviceHms = 'device_hms',
|
||||
}
|
||||
|
||||
export enum EDeviceTypeName {
|
||||
Aircraft = 'sub-device',
|
||||
Gateway = 'gateway',
|
||||
Dock = 'dock',
|
||||
}
|
||||
|
||||
export enum EHmsLevel {
|
||||
NOTICE,
|
||||
CAUTION,
|
||||
WARN,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
export interface LiveStreamStatus {
|
||||
audioBitRate: number,
|
||||
dropRate: number,
|
||||
fps: number,
|
||||
jitter: number,
|
||||
quality: number,
|
||||
rtt: number,
|
||||
status: number,
|
||||
type: number,
|
||||
videoBitRate: number
|
||||
}
|
||||
|
||||
export interface GB28181Param {
|
||||
serverIp: string,
|
||||
serverPort: string,
|
||||
serverId: string,
|
||||
agentId: string,
|
||||
password: string,
|
||||
agentPort: string,
|
||||
agentChannel: string
|
||||
}
|
||||
|
||||
export interface RTSPParam {
|
||||
userName: string,
|
||||
password: string,
|
||||
port: string
|
||||
}
|
||||
|
||||
export interface LiveConfigParam {
|
||||
params: number,
|
||||
type: any
|
||||
}
|
||||
|
||||
export enum EVideoPublishType {
|
||||
VideoOnDemand = 'video-on-demand',
|
||||
VideoByManual = 'video-by-manual',
|
||||
VideoDemandAuxManual = 'video-demand-aux-manual'
|
||||
}
|
||||
|
||||
export enum ELiveTypeValue {
|
||||
Unknown,
|
||||
Agora,
|
||||
RTMP,
|
||||
RTSP,
|
||||
GB28181
|
||||
}
|
||||
|
||||
export enum ELiveTypeName {
|
||||
Unknown = 'Unknown',
|
||||
Agora = 'Agora',
|
||||
RTMP = 'RTMP',
|
||||
RTSP = 'RTSP',
|
||||
GB28181 = 'GB28181'
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export interface WaylineFile {
|
||||
id: string,
|
||||
name: string,
|
||||
drone_model_key: any,
|
||||
payload_model_keys: string[],
|
||||
template_types: number[],
|
||||
update_time: number,
|
||||
user_name: string,
|
||||
}
|
||||
|
||||
export interface TaskExt {
|
||||
current_waypoint_index: number,
|
||||
media_count: number,
|
||||
}
|
||||
|
||||
export interface TaskProgress {
|
||||
current_step: number,
|
||||
percent: number,
|
||||
}
|
||||
|
||||
export interface TaskInfo {
|
||||
status: string,
|
||||
progress: TaskProgress,
|
||||
ext: TaskExt,
|
||||
}
|
||||
|
||||
export enum ETaskStatus {
|
||||
OK = 'ok',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
export function downloadFile (data: Blob, fileName: string) {
|
||||
const lable = document.createElement('a')
|
||||
lable.href = window.URL.createObjectURL(data)
|
||||
lable.download = fileName
|
||||
lable.click()
|
||||
URL.revokeObjectURL(lable.href)
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
, "src/vendors/coordtransform.js" ]
|
||||
"src/**/*.vue",
|
||||
"src/vendors/coordtransform.js"
|
||||
]
|
||||
}
|
||||
@@ -28,7 +28,7 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => defineConfig(
|
||||
}),
|
||||
viteVConsole({
|
||||
entry: path.resolve(__dirname, './src/main.ts'), // 入口文件
|
||||
// localEnabled: command === 'serve', // serve开发环境下
|
||||
localEnabled: command === 'serve', // serve开发环境下
|
||||
// enabled: command !== 'serve' || mode === 'test', // 打包环境下/发布测试包,
|
||||
config: { // vconsole 配置项
|
||||
maxLogNumber: 1000,
|
||||
|
||||
@@ -613,10 +613,17 @@
|
||||
"resolved" "https://registry.npmmirror.com/acorn/download/acorn-7.4.1.tgz"
|
||||
"version" "7.4.1"
|
||||
|
||||
"agora-rtc-sdk-ng@latest":
|
||||
"integrity" "sha512-Jogn3TQC7VdA7uZjGYmaAs0XzgYBgGs6nGA67/dQVjqC7kiwAfkQsAuvbevE/qxrVJmLfqtDTNxP40IFvnTlgQ=="
|
||||
"resolved" "https://registry.npmmirror.com/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.9.1.tgz"
|
||||
"version" "4.9.1"
|
||||
"agora-rtc-sdk-ng@^4.12.1":
|
||||
"integrity" "sha512-kmc+ZyKDdnY/BN3iAwBs+MSgTX8Zkc6THFSIAXN9WebjZ/F+N/JXItoNEcgQe3MdTABUli6w3pZ+iObnDqVkBw=="
|
||||
"resolved" "https://registry.npmmirror.com/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.12.1.tgz"
|
||||
"version" "4.12.1"
|
||||
dependencies:
|
||||
"agora-rte-extension" "^1.0.22"
|
||||
|
||||
"agora-rte-extension@^1.0.22":
|
||||
"integrity" "sha512-X2cGBg+L5ZJIFU91qvMASvRsBfg1HXTktVG3YROw9wxHsILSI7jgF9R9XraLc3fNX/UjovaYAlUW+hiJe0v6Xw=="
|
||||
"resolved" "https://registry.npmmirror.com/agora-rte-extension/-/agora-rte-extension-1.0.23.tgz"
|
||||
"version" "1.0.23"
|
||||
|
||||
"ajv@^6.10.0", "ajv@^6.12.4":
|
||||
"integrity" "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="
|
||||
|
||||