35 files modified
24 files added
| | |
| | | |
| | | ## 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 |
| | | |
| | |
| | | "@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", |
| | |
| | | } |
| | | }, |
| | | "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", |
| | |
| | | "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", |
| | |
| | | "@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", |
| | |
| | | "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", |
| | |
| | | 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.', |
| | | |
| | | } |
| | |
| | | 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({ |
| | |
| | | |
| | | 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 |
| | |
| | | ) |
| | | |
| | | 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) |
| | | } |
| | |
| | | } |
| | | // @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) |
| | |
| | | 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 => { |
| | |
| | | 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 |
| | |
| | | } |
| | | |
| | | // 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 |
| | | } |
| | | |
| | |
| | | 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 |
| | | } |
| | |
| | | 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 |
| | | } |
| | |
| | | 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) |
| | |
| | | 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 |
| | | /** |
| | | * |
| | |
| | | * 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)) |
| | | }, |
| | | |
| | | /** |
| | |
| | | * @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 { |
| | |
| | | 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() |
| | | } |
| | | } |
| | | } |
| | |
| | | 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 |
| | | } |
| | |
| | | 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 |
| | |
| | | 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() |
| | | } |
| | | } |
| | |
| | | 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> |
| | |
| | | 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() |
| | | const state = reactive({ |
| | | 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 |
| | |
| | | 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 => { |
| | |
| | | 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) |
| | |
| | | return { |
| | | draw, |
| | | mouseMode, |
| | | drawVisible |
| | | drawVisible, |
| | | osdVisible, |
| | | pin, |
| | | state, |
| | | M30, |
| | | deviceInfo, |
| | | EGear, |
| | | EModeCode, |
| | | str, |
| | | EDockModeCode, |
| | | } |
| | | } |
| | | }) |
| | |
| | | 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; |
| | | } |
| | | } |
| | | .selection { |
| | | border: 1px solid $primary; |
| | | border-radius: 2px; |
| | | } |
| | | } |
| | | </style> |
| | | <style lang="scss"> |
| | | .amap-logo { |
| | | opacity: 0; |
| | | .osd-panel { |
| | | position: absolute; |
| | | left: 350px; |
| | | top: 10px; |
| | | width: 480px; |
| | | height: 160px; |
| | | background: black; |
| | | color: white; |
| | | border-radius: 2px; |
| | | opacity: 0.7; |
| | | } |
| | | .amap-copyright { |
| | | opacity: 0; |
| | | .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> |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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 |
| | |
| | | <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 = [ |
| | | { |
| | |
| | | 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, { |
| | |
| | | import { CURRENT_CONFIG } from '/@/api/http/config' |
| | | |
| | | export const AMapConfig = { |
| | | key: '26d54da6733de88435c68d1a2e88b682', |
| | | key: CURRENT_CONFIG.amapKey, |
| | | version: '2.0', |
| | | plugins: [ |
| | | 'AMap.Scale', |
| | |
| | | 'AMap.PolyEditor', |
| | | 'AMap.RangingTool', |
| | | 'AMap.Weather', |
| | | 'AMap.MouseTool' |
| | | 'AMap.MouseTool', |
| | | 'AMap.MoveAnimation' |
| | | ] |
| | | } |
| | |
| | | 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) |
| | |
| | | } = { |
| | | '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() |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | <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> |
| | |
| | | <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> |
| | | |
| | |
| | | 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> |
| | |
| | | <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 |
| | |
| | | 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> |
| | |
| | | </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> |
| | |
| | | <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" |
| | | > |
| | |
| | | </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 |
| | |
| | | </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> |
| | | |
| | |
| | | <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> |
| | |
| | | > |
| | | <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 |
| | |
| | | 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> |
| | |
| | | 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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | .login { |
| | | 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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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" |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | @import '/@/styles/index.scss'; |
| | | .project-layer-wrapper { |
| | | padding-top: 16px; |
| | | } |
| | | </style> |
| | | <style lang="scss"> |
| | | .drawer-element-wrapper { |
| | |
| | | <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 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 |
| | | } |
| | | const onOthersLive = () => { |
| | | console.log('liveview') |
| | | enableOthersLive.value = true |
| | | } |
| | | |
| | | onMounted(() => { |
| | | watch(() => root.$route.name, data => { |
| | | showLive.value = data === ERouterName.LIVING |
| | | }, |
| | | { |
| | | deep: true |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | |
| | | 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> |
| | |
| | | <template> |
| | | <div class="project-media-wrapper"> |
| | | Media |
| | | </div> |
| | | </template> |
| | | |
| New file |
| | |
| | | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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" |
| | |
| | | :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 |
| | |
| | | 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, |
| | | } |
| | | } |
| | | }) |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | width: 80px; |
| | | width: 50px; |
| | | border-right: 1px solid #4f4f4f; |
| | | color: $text-white-basic; |
| | | // flex: 1; |
| | |
| | | color: $text-white-basic; |
| | | cursor: pointer; |
| | | &.selected { |
| | | background-color: $dark-highlight; |
| | | background-color: #101010; |
| | | color: $primary; |
| | | } |
| | | &.disabled { |
| | |
| | | } |
| | | |
| | | } |
| | | </style> |
| | | <style> |
| | | .ant-tooltip-open { |
| | | border: 0; |
| | | } |
| New file |
| | |
| | | <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> |
| | |
| | | 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') |
| | | } |
| | | ] |
| | |
| | | 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: [ |
| | | { |
| | |
| | | type: 2 |
| | | } |
| | | ], |
| | | GatewayInfo: { // remote controller, dock |
| | | |
| | | }, |
| | | DeviceInfo: { // drone |
| | | |
| | | }, |
| | | layerBaseInfo: {} as { |
| | | [key:string]:string |
| | | }, |
| | |
| | | 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[] |
| | | } |
| | | }) |
| | | |
| | |
| | | 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 |
| | |
| | | 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> = { |
| | |
| | | // 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); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | .fz10 { |
| | | font-size: 10; |
| | | font-size: 10px; |
| | | } |
| | | .fz12 { |
| | | font-size: 12px; |
| New file |
| | |
| | | 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 |
| | | } |
| | |
| | | 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 { |
| | |
| | | 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, |
| | | } |
| New file |
| | |
| | | |
| | | 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' |
| | | } |
| New file |
| | |
| | | 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' |
| | | } |
| New file |
| | |
| | | |
| | | 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) |
| | | } |
| | |
| | | "src/**/*.ts", |
| | | "src/**/*.d.ts", |
| | | "src/**/*.tsx", |
| | | "src/**/*.vue" |
| | | , "src/vendors/coordtransform.js" ] |
| | | "src/**/*.vue", |
| | | "src/vendors/coordtransform.js" |
| | | ] |
| | | } |
| | |
| | | }), |
| | | 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, |
| | |
| | | "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==" |