23 files modified
13 files renamed
30 files added
4 files deleted
| | |
| | | "agora-rtc-sdk-ng": "^4.12.1", |
| | | "ant-design-vue": "^2.2.8", |
| | | "axios": "^0.21.1", |
| | | "mitt": "^3.0.0", |
| | | "query-string": "^7.0.1", |
| | | "reconnecting-websocket": "^4.4.0", |
| | | "vconsole": "^3.8.1", |
| | |
| | | "resolved": "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz", |
| | | "integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mitt": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz", |
| | | "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==" |
| | | }, |
| | | "node_modules/mixin-deep": { |
| | | "version": "1.3.2", |
| | |
| | | "resolved": "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz", |
| | | "integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=" |
| | | }, |
| | | "mitt": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz", |
| | | "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==" |
| | | }, |
| | | "mixin-deep": { |
| | | "version": "1.3.2", |
| | | "resolved": "https://registry.npm.taobao.org/mixin-deep/download/mixin-deep-1.3.2.tgz", |
| | |
| | | "agora-rtc-sdk-ng": "^4.12.1", |
| | | "ant-design-vue": "^2.2.8", |
| | | "axios": "^0.21.1", |
| | | "mitt": "^3.0.0", |
| | | "query-string": "^7.0.1", |
| | | "reconnecting-websocket": "^4.4.0", |
| | | "vconsole": "^3.8.1", |
| | |
| | | "ant-design-vue/es/spin/style/css", |
| | | "ant-design-vue/es/switch/style/css", |
| | | "ant-design-vue/es/table/style/css", |
| | | "ant-design-vue/es/tag/style/css", |
| | | "ant-design-vue/es/tooltip/style/css", |
| | | "ant-design-vue/es/tree/style/css", |
| | | "axios", |
| | | "mitt", |
| | | "moment", |
| | | "reconnecting-websocket", |
| | | "vconsole", |
| New file |
| | |
| | | import request, { IWorkspaceResponse } from '/@/api/http/request' |
| | | import { DeviceCmd } from '/@/types/device-cmd' |
| | | |
| | | const CMD_API_PREFIX = '/control/api/v1' |
| | | |
| | | export interface SendCmdParams { |
| | | dock_sn: string, // 机场cn |
| | | device_cmd: DeviceCmd // 指令 |
| | | } |
| | | |
| | | /** |
| | | * 发送机场控制指令 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | // /control/api/v1/devices/{dock_sn}/jobs/{service_identifier} |
| | | export async function postSendCmd (params: SendCmdParams): Promise<IWorkspaceResponse<{}>> { |
| | | const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`) |
| | | return resp.data |
| | | } |
| New file |
| | |
| | | import request, { IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request' |
| | | import { DeviceValue, DOMAIN } from '/@/types/device' |
| | | import { DeviceLogUploadStatusEnum } from '/@/types/device-log' |
| | | import { ELocalStorageKey } from '/@/types' |
| | | import { CURRENT_CONFIG } from '/@/api/http/config' |
| | | |
| | | const MNG_API_PREFIX = '/manage/api/v1' |
| | | |
| | | const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
| | | |
| | | export interface GetDeviceUploadLogListParams { |
| | | device_sn: string, |
| | | page: number, |
| | | page_size: number, |
| | | begin_time?: number, // 开始时间 |
| | | end_time?: number, // 结束时间 |
| | | status?: DeviceLogUploadStatusEnum, // 日志上传状态 |
| | | logs_information?: string // 搜索内容 |
| | | } |
| | | |
| | | export interface BriefDeviceInfo { |
| | | sn: string, |
| | | device_model: DeviceValue, |
| | | device_callsign: string |
| | | } |
| | | |
| | | export interface DeviceLogProgressInfo{ |
| | | device_sn: string, |
| | | device_model_domain: DOMAIN, |
| | | progress: number, // 进度 |
| | | result: number, // 上传结果 |
| | | upload_rate: number, // 上传速率 |
| | | status: DeviceLogUploadStatusEnum // 上传状态 |
| | | } |
| | | |
| | | export interface DeviceLogItem { |
| | | boot_index: number, // 日志id |
| | | start_time: number, // 日志开始时间 |
| | | end_time: number, // 日志结束时间 |
| | | size: number // 日志大小 |
| | | } |
| | | |
| | | export interface DeviceLogFileInfo { |
| | | device_sn: string, |
| | | module: DOMAIN, |
| | | result: number, |
| | | object_key: string, |
| | | file_id: string, |
| | | list: DeviceLogItem[] |
| | | } |
| | | |
| | | export interface DeviceLogFileListInfo { |
| | | files: DeviceLogFileInfo[] |
| | | } |
| | | |
| | | export interface GetDeviceUploadLogListRsp { |
| | | logs_id: string, // 记录id |
| | | happen_time: string, // 发生时间 |
| | | user_name: string, // 用户 |
| | | logs_information: string, // 异常描述 |
| | | create_time: string, // 上传时间 |
| | | status:DeviceLogUploadStatusEnum, // 日志上传状态 |
| | | device_topo:{ // 设备topo |
| | | hosts: BriefDeviceInfo[], |
| | | parents: BriefDeviceInfo[] |
| | | }, |
| | | logs_progress: DeviceLogProgressInfo[], // 日志上传进度 |
| | | device_logs: DeviceLogFileListInfo // 设备日志 |
| | | } |
| | | |
| | | /** |
| | | * 获取设备上传日志列表信息 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export async function getDeviceUploadLogList (params: GetDeviceUploadLogListParams): Promise<IListWorkspaceResponse<GetDeviceUploadLogListRsp>> { |
| | | const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs-uploaded`, { |
| | | params: params |
| | | }) |
| | | return resp.data |
| | | } |
| | | |
| | | export interface GetDeviceLogListParams{ |
| | | device_sn: string, |
| | | domain: DOMAIN[] |
| | | } |
| | | |
| | | /** |
| | | * 获取设备日志列表信息 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export async function getDeviceLogList (params: GetDeviceLogListParams): Promise<IWorkspaceResponse<DeviceLogFileListInfo>> { |
| | | const domain = params.domain ? params.domain : [] |
| | | const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs`, { |
| | | params: { |
| | | domain_list: domain.join(',') |
| | | } |
| | | }) |
| | | return resp.data |
| | | } |
| | | |
| | | export interface UploadDeviceLogBody { |
| | | device_sn: string |
| | | happen_time: string // 发生时间 |
| | | logs_information: string // 异常描述 |
| | | files:{ |
| | | list: DeviceLogItem[], |
| | | device_sn: string, |
| | | module: DOMAIN |
| | | }[] |
| | | } |
| | | |
| | | /** |
| | | * 上传设备日志 |
| | | * @param body |
| | | * @returns |
| | | */ |
| | | export async function postDeviceUpgrade (body: UploadDeviceLogBody): Promise<IWorkspaceResponse<{}>> { |
| | | const resp = await request.post(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs`, body) |
| | | return resp.data |
| | | } |
| | | |
| | | export type DeviceLogUploadAction = 'cancel' |
| | | |
| | | export interface CancelDeviceLogUploadBody { |
| | | device_sn: string |
| | | status: DeviceLogUploadAction |
| | | module_list: DOMAIN[] |
| | | } |
| | | |
| | | // 取消上传 |
| | | export async function cancelDeviceLogUpload (body: CancelDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> { |
| | | const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs` |
| | | const result = await request.delete(url, { |
| | | data: body |
| | | }) |
| | | return result.data |
| | | } |
| | | |
| | | export interface DeleteDeviceLogUploadBody { |
| | | device_sn: string |
| | | logs_id: string |
| | | } |
| | | |
| | | // 取消上传 |
| | | export async function deleteDeviceLogUpload (body: DeleteDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> { |
| | | const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs/${body.logs_id}` |
| | | const result = await request.delete(url, { |
| | | data: body |
| | | }) |
| | | return result.data |
| | | } |
| | | |
| | | export interface GetUploadDeviceLogUrlParams{ |
| | | logs_id: string, |
| | | file_id: string, |
| | | } |
| | | |
| | | // export interface GetUploadDeviceLogRsp{ |
| | | // url: string |
| | | // } |
| | | |
| | | /** |
| | | * 获取设备上传日志url |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export async function getUploadDeviceLogUrl (params: GetUploadDeviceLogUrlParams): Promise<IWorkspaceResponse<string>> { |
| | | const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/logs/${params.logs_id}/url/${params.file_id}`) |
| | | return resp.data |
| | | } |
| New file |
| | |
| | | import request, { IWorkspaceResponse } from '/@/api/http/request' |
| | | import { DeviceFirmwareTypeEnum } from '/@/types/device' |
| | | |
| | | const MNG_API_PREFIX = '/manage/api/v1' |
| | | |
| | | export interface GetDeviceUpgradeInfoParams { |
| | | device_name: string |
| | | } |
| | | |
| | | export interface GetDeviceUpgradeInfoRsp { |
| | | device_name: string |
| | | product_version: string |
| | | release_note: string |
| | | released_time: string |
| | | } |
| | | |
| | | /** |
| | | * 获取设备升级信息 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export async function getDeviceUpgradeInfo (params: GetDeviceUpgradeInfoParams): Promise<IWorkspaceResponse<GetDeviceUpgradeInfoRsp[]>> { |
| | | const resp = await request.get(`${MNG_API_PREFIX}/workspaces/firmware-release-notes/latest`, { |
| | | params: params |
| | | }) |
| | | return resp.data |
| | | } |
| | | |
| | | export interface UpgradeDeviceInfo { |
| | | device_name: string, |
| | | sn: string, |
| | | product_version: string, |
| | | firmware_upgrade_type: DeviceFirmwareTypeEnum // 1-普通升级,2-一致性升级 |
| | | } |
| | | |
| | | export type DeviceUpgradeBody = UpgradeDeviceInfo[] |
| | | |
| | | /** |
| | | * 设备升级 |
| | | * @param workspace_id |
| | | * @param body |
| | | * @returns |
| | | */ |
| | | export async function postDeviceUpgrade (workspace_id: string, body: DeviceUpgradeBody): Promise<IWorkspaceResponse<{}>> { |
| | | const resp = await request.post(`${MNG_API_PREFIX}/devices/${workspace_id}/devices/ota`, body) |
| | | return resp.data |
| | | } |
| | |
| | | } |
| | | // Workspace |
| | | export interface IWorkspaceResponse<T> { |
| | | [x: string]: number; |
| | | code: number; |
| | | data: T; |
| | | message: string; |
| | | } |
| | | |
| | | export type IStatus = 'WAITING' | 'DOING' | 'SUCCESS' | 'FAILED'; |
| | | |
| | | export interface CommonListResponse<T> extends IResult { |
| | |
| | | import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request' |
| | | import { Device } from '/@/types/device' |
| | | |
| | | const HTTP_PREFIX = '/manage/api/v1' |
| | | |
| | | // login |
| | |
| | | return result.data |
| | | } |
| | | |
| | | export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: string): Promise<IWorkspaceResponse<any>> { |
| | | /** |
| | | * 获取绑定设备信息 |
| | | * @param workspace_id |
| | | * @param body |
| | | * @param domain |
| | | * @returns |
| | | */ |
| | | export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: string): Promise<IListWorkspaceResponse<Device>> { |
| | | const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}` |
| | | const result = await request.get(url) |
| | | return result.data |
| | |
| | | } |
| | | |
| | | 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}` |
| | | let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` + |
| | | `&level=${body.level ?? ''}&begin_time=${body.begin_time ?? ''}&end_time=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}` |
| | | body.sns.forEach((sn: string) => { |
| | | if (sn !== '') { |
| | | url = url.concat(`&deviceSn=${sn}`) |
| | | url = url.concat(`&device_sn=${sn}`) |
| | | } |
| | | }) |
| | | const result = await request.get(url) |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import request, { IPage, IWorkspaceResponse } from '/@/api/http/request' |
| | | const HTTP_PREFIX = '/media/api/v1' |
| | | |
| | |
| | | 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` |
| | | export const downloadMediaFile = async function (workspaceId: string, fileId: string): Promise<any> { |
| | | const url = `${HTTP_PREFIX}/files/${workspaceId}/file/${fileId}/url` |
| | | const result = await request.get(url, { responseType: 'blob' }) |
| | | if (result.data.code) { |
| | | if (result.data.type === 'application/json') { |
| | | const reader = new FileReader() |
| | | reader.onload = function (e) { |
| | | let text = reader.result as string |
| | | const result = JSON.parse(text) |
| | | message.error(result.message) |
| | | } |
| | | reader.readAsText(result.data, 'utf-8') |
| | | return |
| | | } else { |
| | | return result.data |
| | | } |
| | | return result |
| | | } |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import request, { IPage, IWorkspaceResponse } from '/@/api/http/request' |
| | | const HTTP_PREFIX = '/wayline/api/v1' |
| | | |
| | |
| | | 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) { |
| | | if (result.data.type === 'application/json') { |
| | | const reader = new FileReader() |
| | | reader.onload = function (e) { |
| | | let text = reader.result as string |
| | | const result = JSON.parse(text) |
| | | message.error(result.message) |
| | | } |
| | | reader.readAsText(result.data, 'utf-8') |
| | | return |
| | | } else { |
| | | return result.data |
| | | } |
| | | return result |
| | | } |
| | | |
| | | // Delete Wayline File |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-if="osdVisible.visible && osdVisible.is_dock" class="osd-panel fz12" style="height: 280px;"> |
| | | <!-- 机场OSD --> |
| | | <div v-if="osdVisible.visible && osdVisible.is_dock" class="osd-panel fz12"> |
| | | <div class="fz16 pl5 pr5 flex-align-center flex-row flex-justify-between" style="border-bottom: 1px solid #515151; height: 10%;"> |
| | | <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;"> |
| | | <!-- 机场 --> |
| | | <div class ="flex-display" style="border-bottom: 1px solid #515151;"> |
| | | <div class="flex-column flex-align-stretch flex-justify-center" style="width: 60px; background: #2d2d2d;"> |
| | | <a-tooltip :title="osdVisible.model"> |
| | | <div class="flex-column flex-align-center flex-justify-center" style="width: 90%;"> |
| | | <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"> |
| | | <div class="osd flex-1" style="flex: 1"> |
| | | <a-row> |
| | | <a-col span="16" :style="deviceInfo.dock.mode_code === EDockModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'"> |
| | | {{ EDockModeCode[deviceInfo.dock.mode_code] }}</a-col> |
| | |
| | | </a-tooltip> |
| | | </a-col> |
| | | </a-row> |
| | | <a-row class="p5"> |
| | | <a-col span="24"> |
| | | <a-button type="primary" :disabled="controlPanelVisible" size="small" @click="dockDebugOnOff(osdVisible.gateway_sn, true)"> |
| | | 远程调试 |
| | | </a-button> |
| | | </a-col> |
| | | </a-row> |
| | | <DockControlPanel v-if="controlPanelVisible" :sn="osdVisible.gateway_sn" :deviceInfo="deviceInfo" @close-control-panel="dockDebugOnOff"> |
| | | </DockControlPanel> |
| | | </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;"> |
| | | <!-- 飞机--> |
| | | <div class ="flex-display"> |
| | | <div class="flex-column flex-align-stretch flex-justify-center" style="width: 60px; background: #2d2d2d;"> |
| | | <a-tooltip :title="osdVisible.model"> |
| | | <div style="width: 90%;" class="flex-column flex-align-center flex-justify-center"> |
| | | <span><a-image :src="M30" :preview="false"/></span> |
| | |
| | | </div> |
| | | </a-tooltip> |
| | | </div> |
| | | <div class="osd"> |
| | | <div class="osd flex-1"> |
| | | <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> |
| | |
| | | FieldTimeOutlined, CloudOutlined, CloudFilled, FolderOpenOutlined, RobotFilled, ArrowUpOutlined |
| | | } from '@ant-design/icons-vue' |
| | | import { EDeviceTypeName } from '../types' |
| | | import DockControlPanel from './g-map/DockControlPanel.vue' |
| | | import { useDockControl } from './g-map/useDockControl' |
| | | |
| | | export default defineComponent({ |
| | | components: { |
| | |
| | | FolderOpenOutlined, |
| | | RobotFilled, |
| | | ArrowUpOutlined, |
| | | ArrowDownOutlined |
| | | ArrowDownOutlined, |
| | | DockControlPanel |
| | | }, |
| | | name: 'GMap', |
| | | props: {}, |
| | |
| | | useMouseToolHook.mouseTool(type, getDrawCallback) |
| | | mouseMode.value = bool |
| | | } |
| | | |
| | | // dock 控制指令 |
| | | const { |
| | | controlPanelVisible, |
| | | setControlPanelVisible, |
| | | sendDockControlCmd, |
| | | dockDebugOnOff, |
| | | } = useDockControl() |
| | | |
| | | onMounted(() => { |
| | | const app = getApp() |
| | | useGMapManageHook.globalPropertiesConfig(app) |
| | |
| | | EModeCode, |
| | | str, |
| | | EDockModeCode, |
| | | controlPanelVisible, |
| | | dockDebugOnOff, |
| | | } |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | |
| | | .g-map-wrapper { |
| | | height: 100%; |
| | | width: 100%; |
| | | |
| | | .g-action-panle { |
| | | position: absolute; |
| | | top: 16px; |
| | |
| | | border: 1px solid $primary; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | // antd button 光晕 |
| | | &:deep(.ant-btn){ |
| | | &::after { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | .osd-panel { |
| | | position: absolute; |
| | | left: 350px; |
| | | top: 10px; |
| | | width: 480px; |
| | | height: 160px; |
| | | background: black; |
| | | color: white; |
| | | border-radius: 2px; |
| | |
| | | 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 { message, Pagination } from 'ant-design-vue' |
| | | import { load } from '@amap/amap-jsapi-loader' |
| | | |
| | | const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)! |
| | |
| | | file_name: string, |
| | | file_path: string, |
| | | create_time: string, |
| | | file_id: string, |
| | | } |
| | | |
| | | const mediaData = reactive({ |
| | |
| | | |
| | | function downloadMedia (media: MediaFile) { |
| | | loading.value = true |
| | | downloadMediaFile(workspaceId, media.fingerprint).then(res => { |
| | | if (res.code && res.code !== 0) { |
| | | downloadMediaFile(workspaceId, media.file_id).then(res => { |
| | | if (!res) { |
| | | return |
| | | } |
| | | const suffix = media.file_name.substring(media.file_name.lastIndexOf('.')) |
| | | const data = new Blob([res.data], { type: suffix === '.mp4' ? 'video/mp4' : 'image/jpeg' }) |
| | | const data = new Blob([res]) |
| | | downloadFile(data, media.file_name) |
| | | }).finally(() => { |
| | | loading.value = false |
| | |
| | | <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'"> |
| | | :status="taskProgressMap[record.bid]?.status.indexOf(ETaskStatus.FAILED) != -1 ? 'exception' : taskProgressMap[record.bid]?.status.indexOf(ETaskStatus.OK) != -1 ? '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;"> |
| File was renamed from src/pages/project-app/sidebar.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 |
| | |
| | | { 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: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' }, |
| | | { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' } |
| | | ] |
| | | |
| | |
| | | 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, |
| File was renamed from src/pages/project-app/topbar.vue |
| | |
| | | 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() |
| | | |
| New file |
| | |
| | | <template> |
| | | <a-drawer |
| | | title="Hms Info" |
| | | placement="right" |
| | | v-model:visible="sVisible" |
| | | @update:visible="onVisibleChange" |
| | | :destroyOnClose="true" |
| | | :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> |
| | | </template> |
| | | |
| | | <!-- 暂时只抽取该组件 --> |
| | | <script lang="ts" setup> |
| | | import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue' |
| | | import { getDeviceHms, HmsQueryBody } from '/@/api/manage' |
| | | import moment, { Moment } from 'moment' |
| | | import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
| | | import { Device, DeviceHms } from '/@/types/device' |
| | | import { IPage } from '/@/api/http/type' |
| | | import { EDeviceTypeName, EHmsLevel, ELocalStorageKey } from '/@/types' |
| | | |
| | | const props = defineProps<{ |
| | | visible: boolean, |
| | | device: null | Device, |
| | | }>() |
| | | const emit = defineEmits(['update:visible', 'ok', 'cancel']) |
| | | |
| | | const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
| | | // 健康状态 |
| | | const sVisible = ref(false) |
| | | |
| | | watchEffect(() => { |
| | | sVisible.value = props.visible |
| | | // 显示弹框时,获取设备hms信息 |
| | | if (props.visible) { |
| | | showHms() |
| | | } |
| | | }) |
| | | |
| | | function onVisibleChange (sVisible: boolean) { |
| | | setVisible(sVisible) |
| | | } |
| | | |
| | | function setVisible (v: boolean, e?: Event) { |
| | | sVisible.value = v |
| | | emit('update:visible', v, e) |
| | | } |
| | | |
| | | const loading = ref(false) |
| | | |
| | | const hmsColumns: ColumnProps[] = [ |
| | | { title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } }, |
| | | { title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } }, |
| | | { title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle' }, |
| | | { title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', slots: { customRender: 'code' } }, |
| | | { title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } }, |
| | | ] |
| | | |
| | | interface DeviceHmsData { |
| | | data: DeviceHms[] |
| | | } |
| | | |
| | | const hmsData = reactive<DeviceHmsData>({ |
| | | data: [] |
| | | }) |
| | | |
| | | type Pagination = TableState['pagination'] |
| | | |
| | | const hmsPaginationProp = reactive({ |
| | | pageSizeOptions: ['20', '50', '100'], |
| | | showQuickJumper: true, |
| | | showSizeChanger: true, |
| | | pageSize: 50, |
| | | current: 1, |
| | | total: 0 |
| | | }) |
| | | |
| | | // 获取分页信息 |
| | | function getPaginationBody () { |
| | | return { |
| | | page: hmsPaginationProp.current, |
| | | page_size: hmsPaginationProp.pageSize |
| | | } as IPage |
| | | } |
| | | |
| | | function showHms () { |
| | | const dock = props.device |
| | | if (!dock) return |
| | | if (dock.domain === EDeviceTypeName.Dock) { |
| | | 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) { |
| | | hmsPaginationProp.current = page?.current! |
| | | hmsPaginationProp.pageSize = 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 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 time = ref([moment(param.begin_time), moment(param.end_time)]) |
| | | |
| | | function getHms () { |
| | | loading.value = true |
| | | getDeviceHms(param, workspaceId, getPaginationBody()) |
| | | .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 |
| | | }) |
| | | loading.value = false |
| | | }).catch(_err => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | |
| | | 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> |
| New file |
| | |
| | | <template> |
| | | <a-modal |
| | | title="日志上传详情" |
| | | v-model:visible="sVisible" |
| | | width="900px" |
| | | :footer="null" |
| | | @update:visible="onVisibleChange"> |
| | | <div class="device-log-detail-wrap"> |
| | | <div class="device-log-list"> |
| | | <div class="log-list-item"> |
| | | <a-button type="primary" class="download-btn" :disabled="!airportTableLogState.logList?.file_id" size="small" @click="onDownloadLog(airportTableLogState.logList.file_id)"> |
| | | 下载机场日志 |
| | | </a-button> |
| | | <a-table :columns="airportLogColumns" |
| | | :scroll="{ x: '100%', y: 600 }" |
| | | :data-source="airportTableLogState.logList?.list" |
| | | rowKey="boot_index" |
| | | :pagination = "false" |
| | | > |
| | | <template #log_time="{record}"> |
| | | <div>{{getLogTime(record)}}</div> |
| | | </template> |
| | | <template #size="{record}"> |
| | | <div>{{getLogSize(record.size)}}</div> |
| | | </template> |
| | | </a-table> |
| | | </div> |
| | | <div class="log-list-item"> |
| | | <a-button type="primary" class="download-btn" :disabled="!droneTableLogState.logList?.file_id" size="small" @click="onDownloadLog(droneTableLogState.logList.file_id)"> |
| | | 下载飞行器日志 |
| | | </a-button> |
| | | <a-table :columns="droneLogColumns" |
| | | :scroll="{ x: '100%', y: 600 }" |
| | | :data-source="droneTableLogState.logList?.list" |
| | | rowKey="boot_index" |
| | | :pagination = "false" |
| | | > |
| | | <template #log_time="{record}"> |
| | | <div>{{getLogTime(record)}}</div> |
| | | </template> |
| | | <template #size="{record}"> |
| | | <div>{{getLogSize(record.size)}}</div> |
| | | </template> |
| | | </a-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </a-modal> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue' |
| | | import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
| | | import { IPage } from '/@/api/http/type' |
| | | import { DOMAIN } from '/@/types/device' |
| | | import { DeviceLogFileInfo, GetDeviceUploadLogListRsp, getUploadDeviceLogUrl } from '/@/api/device-log' |
| | | import { useDeviceLogUploadDetail } from './use-device-log-upload-detail' |
| | | import { download } from '/@/utils/download' |
| | | |
| | | const props = defineProps<{ |
| | | visible: boolean, |
| | | deviceLog: null | GetDeviceUploadLogListRsp, |
| | | }>() |
| | | const emit = defineEmits(['update:visible']) |
| | | |
| | | const sVisible = ref(false) |
| | | |
| | | watchEffect(() => { |
| | | sVisible.value = props.visible |
| | | if (props.visible) { |
| | | classifyDeviceLog() |
| | | } |
| | | }) |
| | | |
| | | function onVisibleChange (sVisible: boolean) { |
| | | setVisible(sVisible) |
| | | } |
| | | |
| | | function setVisible (v: boolean, e?: Event) { |
| | | sVisible.value = v |
| | | emit('update:visible', v, e) |
| | | } |
| | | |
| | | // 表格 |
| | | const airportLogColumns: ColumnProps[] = [ |
| | | { title: '机场日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } }, |
| | | { title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } }, |
| | | ] |
| | | |
| | | const droneLogColumns: ColumnProps[] = [ |
| | | { title: '飞行器日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } }, |
| | | { title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } }, |
| | | ] |
| | | |
| | | const airportTableLogState = reactive({ |
| | | logList: {} as DeviceLogFileInfo, |
| | | }) |
| | | |
| | | const droneTableLogState = reactive({ |
| | | logList: {} as DeviceLogFileInfo, |
| | | }) |
| | | |
| | | function classifyDeviceLog () { |
| | | if (!props.deviceLog) return |
| | | const { device_logs } = props.deviceLog |
| | | const { files } = device_logs || {} |
| | | if (files && files.length > 0) { |
| | | files.forEach(file => { |
| | | if (file.module === DOMAIN.DOCK) { |
| | | airportTableLogState.logList = file |
| | | } else if (file.module === DOMAIN.DRONE) { |
| | | droneTableLogState.logList = file |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const { getLogTime, getLogSize } = useDeviceLogUploadDetail() |
| | | |
| | | async function onDownloadLog (fileId: string) { |
| | | const { data } = await getUploadDeviceLogUrl({ |
| | | file_id: fileId, |
| | | logs_id: props.deviceLog?.logs_id || '' |
| | | }) |
| | | if (data) { |
| | | download(data) |
| | | // download('https:/github.com/dji-sdk/Mobile-SDK-Android-V5/archive/refs/heads/dev-sdk-main.zip') |
| | | } |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .device-log-detail-wrap{ |
| | | |
| | | .device-log-list{ |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 8px 0; |
| | | .log-list-item{ |
| | | width: 420px; |
| | | |
| | | .download-btn{ |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | > |
| New file |
| | |
| | | <template> |
| | | <a-modal |
| | | title="设备日志上传" |
| | | v-model:visible="sVisible" |
| | | width="900px" |
| | | :footer="null" |
| | | @update:visible="onVisibleChange"> |
| | | <div class="device-log-upload-wrap"> |
| | | <div class="page-action-row"> |
| | | <a-button type="primary" :disabled="deviceLogUploadBtnDisabled" @click="uploadDeviceLog">上传日志</a-button> |
| | | </div> |
| | | <div class="device-log-list"> |
| | | <div class="log-list-item"> |
| | | <a-table :columns="airportLogColumns" |
| | | :scroll="{ x: '100%', y: 600 }" |
| | | :data-source="airportTableLogState.logList?.list" |
| | | :loading="airportTableLogState.tableLoading" |
| | | :row-selection="airportTableLogState.rowSelection" |
| | | rowKey="boot_index" |
| | | :pagination = "false"> |
| | | <template #log_time="{record}"> |
| | | <div>{{getLogTime(record)}}</div> |
| | | </template> |
| | | <template #size="{record}"> |
| | | <div>{{getLogSize(record.size)}}</div> |
| | | </template> |
| | | </a-table> |
| | | </div> |
| | | <div class="log-list-item"> |
| | | <a-table :columns="droneLogColumns" |
| | | :scroll="{ x: '100%', y: 600 }" |
| | | :data-source="droneTableLogState.logList?.list" |
| | | :loading="droneTableLogState.tableLoading" |
| | | :row-selection="droneTableLogState.rowSelection" |
| | | rowKey="boot_index" |
| | | :pagination = "false"> |
| | | <template #log_time="{record}"> |
| | | <div>{{getLogTime(record)}}</div> |
| | | </template> |
| | | <template #size="{record}"> |
| | | <div>{{getLogSize(record.size)}}</div> |
| | | </template> |
| | | </a-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </a-modal> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { watchEffect, reactive, ref, computed, defineProps, defineEmits } from 'vue' |
| | | import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
| | | import { IPage } from '/@/api/http/type' |
| | | import { Device, DOMAIN } from '/@/types/device' |
| | | import { getDeviceLogList, postDeviceUpgrade, DeviceLogFileInfo, UploadDeviceLogBody, DeviceLogItem } from '/@/api/device-log' |
| | | import { message } from 'ant-design-vue' |
| | | import { useDeviceLogUploadDetail } from './use-device-log-upload-detail' |
| | | |
| | | const props = defineProps<{ |
| | | visible: boolean, |
| | | device: null | Device, |
| | | }>() |
| | | const emit = defineEmits(['update:visible', 'upload-log-ok']) |
| | | |
| | | const sVisible = ref(false) |
| | | |
| | | watchEffect(() => { |
| | | sVisible.value = props.visible |
| | | // 显示弹框时,获取设备日志信息 |
| | | if (props.visible) { |
| | | getDeviceLogInfo() |
| | | } |
| | | }) |
| | | |
| | | function onVisibleChange (sVisible: boolean) { |
| | | setVisible(sVisible) |
| | | if (!sVisible) { |
| | | resetTableLogState() |
| | | } |
| | | } |
| | | |
| | | function setVisible (v: boolean, e?: Event) { |
| | | sVisible.value = v |
| | | emit('update:visible', v, e) |
| | | } |
| | | |
| | | // 表格 |
| | | const airportLogColumns: ColumnProps[] = [ |
| | | { title: '机场日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } }, |
| | | { title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } }, |
| | | ] |
| | | |
| | | const droneLogColumns: ColumnProps[] = [ |
| | | { title: '飞行器日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } }, |
| | | { title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } }, |
| | | ] |
| | | |
| | | const airportTableLogState = reactive({ |
| | | logList: {} as DeviceLogFileInfo, |
| | | tableLoading: false, |
| | | selectRow: [], |
| | | rowSelection: { |
| | | columnWidth: 15, |
| | | selectedRowKeys: [] as number[], |
| | | onChange: (selectedRowKeys:number[], selectedRows: []) => { |
| | | airportTableLogState.rowSelection.selectedRowKeys = selectedRowKeys |
| | | airportTableLogState.selectRow = selectedRows |
| | | console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows) |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | function resetTableLogState () { |
| | | airportTableLogState.logList = {} as DeviceLogFileInfo |
| | | airportTableLogState.selectRow = [] |
| | | airportTableLogState.tableLoading = false |
| | | } |
| | | |
| | | const droneTableLogState = reactive({ |
| | | logList: {} as DeviceLogFileInfo, |
| | | tableLoading: false, |
| | | selectRow: [], |
| | | rowSelection: { |
| | | columnWidth: 15, |
| | | selectedRowKeys: [] as number[], |
| | | onChange: (selectedRowKeys: number[], selectedRows: []) => { |
| | | droneTableLogState.rowSelection.selectedRowKeys = selectedRowKeys |
| | | droneTableLogState.selectRow = selectedRows |
| | | console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows) |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | const deviceLogUploadBtnDisabled = computed(() => { |
| | | return (airportTableLogState.rowSelection.selectedRowKeys && airportTableLogState.rowSelection.selectedRowKeys.length <= 0) && |
| | | (droneTableLogState.rowSelection.selectedRowKeys && droneTableLogState.rowSelection.selectedRowKeys.length <= 0) |
| | | }) |
| | | |
| | | // 获取设备内日志 |
| | | async function getDeviceLogInfo () { |
| | | airportTableLogState.tableLoading = true |
| | | droneTableLogState.tableLoading = true |
| | | try { |
| | | const { code, data } = await getDeviceLogList({ |
| | | device_sn: props.device?.device_sn || '', |
| | | domain: [DOMAIN.DOCK, DOMAIN.DRONE] |
| | | }) |
| | | if (code === 0) { |
| | | const { files } = data |
| | | if (files && files.length > 0) { |
| | | files.forEach(file => { |
| | | if (file.module === DOMAIN.DOCK) { |
| | | airportTableLogState.logList = file |
| | | } else if (file.module === DOMAIN.DRONE) { |
| | | droneTableLogState.logList = file |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } catch (err) { |
| | | } |
| | | airportTableLogState.tableLoading = false |
| | | droneTableLogState.tableLoading = false |
| | | } |
| | | |
| | | // 日志上传 |
| | | async function uploadDeviceLog () { |
| | | const body = { |
| | | device_sn: props.device?.device_sn || '', |
| | | files: [] as any |
| | | } as UploadDeviceLogBody |
| | | if (airportTableLogState.selectRow && airportTableLogState.selectRow.length > 0) { |
| | | body.files.push({ |
| | | list: airportTableLogState.selectRow, |
| | | device_sn: airportTableLogState.logList.device_sn, |
| | | module: airportTableLogState.logList.module |
| | | }) |
| | | } |
| | | if (droneTableLogState.selectRow && droneTableLogState.selectRow.length > 0) { |
| | | body.files.push({ |
| | | list: droneTableLogState.selectRow, |
| | | device_sn: droneTableLogState.logList.device_sn, |
| | | module: droneTableLogState.logList.module |
| | | }) |
| | | } |
| | | const { code } = await postDeviceUpgrade(body) |
| | | if (code === 0) { |
| | | message.success('日志上传任务执行成功') |
| | | emit('upload-log-ok') |
| | | setVisible(false) |
| | | } |
| | | } |
| | | |
| | | const { getLogTime, getLogSize } = useDeviceLogUploadDetail() |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .device-log-upload-wrap{ |
| | | |
| | | .device-log-list{ |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 8px 0; |
| | | .log-list-item{ |
| | | width: 420px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <a-drawer |
| | | title="设备日志上传记录" |
| | | placement="right" |
| | | v-model:visible="sVisible" |
| | | @update:visible="onVisibleChange" |
| | | :width="800"> |
| | | <!-- 设备日志上传记录 --> |
| | | <div class="device-log-upload-record-wrap"> |
| | | <div class="page-action-row"> |
| | | <a-button type="primary" @click="onUploadDeviceLog">上传日志</a-button> |
| | | </div> |
| | | <div class="device-log-upload-list"> |
| | | <a-table :columns="deviceLogUploadListColumns" |
| | | :scroll="{ x: '100%', y: 600 }" |
| | | :data-source="deviceUploadLogState.uploadLogList" |
| | | :loading="deviceUploadLogState.loading" |
| | | :pagination="deviceUploadLogState.paginationProp" |
| | | @change="onDeviceUploadLogTableChange" |
| | | rowKey="logs_id"> |
| | | <!-- 设备类型 --> |
| | | <template #device_type="{ record }"> |
| | | <div> |
| | | <div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.key]}}</div> |
| | | <div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.key]}}</div> |
| | | </div> |
| | | </template> |
| | | <!-- 设备sn --> |
| | | <template #device_sn="{ record }"> |
| | | <div> |
| | | <div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ getDeviceInfo(record).parents[0].sn }}</div> |
| | | <div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ getDeviceInfo(record).hosts[0].sn }}</div> |
| | | </div> |
| | | </template> |
| | | <!-- 上传状态 --> |
| | | <template #status="{ record }"> |
| | | <div> |
| | | <div> |
| | | <span class="circle-icon" :style="{backgroundColor: getDeviceLogUploadStatus(record).color}"></span> |
| | | {{ getDeviceLogUploadStatus(record).text }} |
| | | </div> |
| | | <div v-if="record.status === DeviceLogUploadStatusEnum.Uploading"> |
| | | <a-progress :percent="getLogProgress(record)" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <!-- 操作 --> |
| | | <template #action="{ record }"> |
| | | <div class="row-action"> |
| | | <a-tooltip title="查看详情"> |
| | | <FileTextOutlined @click="showDeviceLogDetail(record)"/> |
| | | </a-tooltip> |
| | | <span v-if="record.status === DeviceLogUploadStatusEnum.Uploading"> |
| | | <a-tooltip title="取消"> |
| | | <StopOutlined @click="onCancelUploadDeviceLog(record)"/> |
| | | </a-tooltip> |
| | | </span> |
| | | <span v-else> |
| | | <a-tooltip title="删除"> |
| | | <DeleteOutlined @click="onDeleteUploadDeviceLog(record)"/> |
| | | </a-tooltip> |
| | | </span> |
| | | </div> |
| | | </template> |
| | | </a-table> |
| | | </div> |
| | | </div> |
| | | </a-drawer> |
| | | <!-- 设备日志上传弹框 --> |
| | | <DeviceLogUploadModal |
| | | v-model:visible="deviceLogUploadModalVisible" |
| | | :device="props.device" |
| | | @upload-log-ok="onUploadLogOk" |
| | | ></DeviceLogUploadModal> |
| | | |
| | | <!-- 设备日志上传详情弹框 --> |
| | | <DeviceLogDetailModal |
| | | v-model:visible="deviceLogDetailModalVisible" |
| | | :deviceLog="currentDeviceLog" |
| | | ></DeviceLogDetailModal> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue' |
| | | import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
| | | import { IPage } from '/@/api/http/type' |
| | | import { Device, DOMAIN, DEVICE_NAME } from '/@/types/device' |
| | | import DeviceLogUploadModal from './DeviceLogUploadModal.vue' |
| | | import DeviceLogDetailModal from './DeviceLogDetailModal.vue' |
| | | import { getDeviceUploadLogList, GetDeviceUploadLogListRsp, cancelDeviceLogUpload, deleteDeviceLogUpload } from '/@/api/device-log' |
| | | import { StopOutlined, DeleteOutlined, FileTextOutlined } from '@ant-design/icons-vue' |
| | | import { DeviceLogUploadStatusEnum, DeviceLogUploadStatusMap, DeviceLogUploadStatusColor, DeviceLogUploadInfo, DeviceLogUploadWsStatusMap, DeviceLogProgressInfo } from '/@/types/device-log' |
| | | import { useDeviceLogUploadProgressEvent } from './use-device-log-upload-progress-event' |
| | | import { Modal } from 'ant-design-vue' |
| | | |
| | | const props = defineProps<{ |
| | | visible: boolean, |
| | | device: null | Device, |
| | | }>() |
| | | const emit = defineEmits(['update:visible']) |
| | | |
| | | const sVisible = ref(false) |
| | | |
| | | watchEffect(() => { |
| | | sVisible.value = props.visible |
| | | // 显示弹框时,获取设备日志上传记录信息 |
| | | if (props.visible) { |
| | | getDeviceUploadLogInfo() |
| | | } |
| | | }) |
| | | |
| | | function onVisibleChange (sVisible: boolean) { |
| | | setVisible(sVisible) |
| | | } |
| | | |
| | | function setVisible (v: boolean, e?: Event) { |
| | | sVisible.value = v |
| | | emit('update:visible', v, e) |
| | | } |
| | | |
| | | // 日志列表 |
| | | const deviceLogUploadListColumns: ColumnProps[] = [ |
| | | { title: '上传时间', dataIndex: 'create_time', width: 100 }, |
| | | { title: '设备型号', dataIndex: 'device_type', width: 80, slots: { customRender: 'device_type' } }, |
| | | { title: '设备SN', dataIndex: 'device_sn', width: 120, slots: { customRender: 'device_sn' } }, |
| | | { title: '上传状态', dataIndex: 'status', width: 120, slots: { customRender: 'status' } }, |
| | | { title: '操作', dataIndex: 'actions', width: 80, slots: { customRender: 'action' } }, |
| | | ] |
| | | |
| | | const deviceUploadLogState = reactive({ |
| | | uploadLogList: [] as GetDeviceUploadLogListRsp[], |
| | | loading: false, |
| | | paginationProp: { |
| | | pageSizeOptions: ['20', '50', '100'], |
| | | showQuickJumper: true, |
| | | showSizeChanger: true, |
| | | pageSize: 50, |
| | | current: 1, |
| | | total: 0 |
| | | } |
| | | }) |
| | | |
| | | // 获取上传的设备日志 |
| | | async function getDeviceUploadLogInfo () { |
| | | deviceUploadLogState.loading = true |
| | | try { |
| | | const { code, data } = await getDeviceUploadLogList({ |
| | | device_sn: props.device?.device_sn || '', |
| | | page: deviceUploadLogState.paginationProp.current, |
| | | page_size: deviceUploadLogState.paginationProp.pageSize |
| | | }) |
| | | if (code === 0) { |
| | | deviceUploadLogState.uploadLogList = data.list |
| | | deviceUploadLogState.paginationProp.total = data.pagination.total |
| | | deviceUploadLogState.paginationProp.current = data.pagination.page |
| | | deviceUploadLogState.paginationProp.pageSize = data.pagination.page_size |
| | | } |
| | | deviceUploadLogState.loading = false |
| | | } catch (error) { |
| | | deviceUploadLogState.loading = false |
| | | } |
| | | } |
| | | type Pagination = TableState['pagination'] |
| | | |
| | | // 获取设备信息 |
| | | function getDeviceInfo (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | const { device_topo: deviceTopo } = deviceLogItem |
| | | return deviceTopo |
| | | } |
| | | |
| | | // 获取上传状态 |
| | | function getDeviceLogUploadStatus (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | const statusObj = { |
| | | color: '', |
| | | text: '' |
| | | } |
| | | const { status } = deviceLogItem |
| | | statusObj.color = DeviceLogUploadStatusColor[status] |
| | | statusObj.text = DeviceLogUploadStatusMap[status] |
| | | return statusObj |
| | | } |
| | | |
| | | // 获取上传进度 |
| | | function getLogProgress (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | let percent = 0 |
| | | const { logs_progress } = deviceLogItem |
| | | if (logs_progress && logs_progress.length > 0) { |
| | | logs_progress.forEach(log => { |
| | | percent += (log.progress || 0) |
| | | }) |
| | | percent = percent / logs_progress.length |
| | | } |
| | | return Math.floor(percent) |
| | | } |
| | | |
| | | // 设备日志上传进度更新 |
| | | function onDeviceLogUploadWs (data: DeviceLogUploadInfo) { |
| | | const { sn, output } = data |
| | | if (output) { |
| | | const { files, status, logs_id: logId } = output || {} |
| | | const deviceLogItem = deviceUploadLogState.uploadLogList.find(log => log.logs_id === logId) |
| | | if (!deviceLogItem) return |
| | | if (status) { |
| | | deviceLogItem.status = DeviceLogUploadWsStatusMap[status] |
| | | } |
| | | if (files && files.length > 0) { |
| | | const logsProgress = [] as DeviceLogProgressInfo[] |
| | | files.forEach(file => { |
| | | logsProgress.push({ |
| | | ...file, |
| | | status: DeviceLogUploadWsStatusMap[file.status] |
| | | }) |
| | | }) |
| | | deviceLogItem.logs_progress = logsProgress |
| | | } |
| | | } |
| | | } |
| | | |
| | | useDeviceLogUploadProgressEvent(onDeviceLogUploadWs) |
| | | |
| | | // 搜索 |
| | | async function onDeviceUploadLogTableChange (page: Pagination) { |
| | | deviceUploadLogState.paginationProp.current = page?.current || 1 |
| | | deviceUploadLogState.paginationProp.pageSize = page?.pageSize || 20 |
| | | await getDeviceUploadLogInfo() |
| | | } |
| | | |
| | | // 查看上传设备日志详情 |
| | | const deviceLogDetailModalVisible = ref(false) |
| | | const currentDeviceLog = ref({} as GetDeviceUploadLogListRsp) |
| | | |
| | | function showDeviceLogDetail (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | if (!deviceLogItem) return |
| | | currentDeviceLog.value = deviceLogItem |
| | | deviceLogDetailModalVisible.value = true |
| | | } |
| | | |
| | | // 取消上传设备日志 |
| | | async function onCancelUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | Modal.confirm({ |
| | | title: '取消日志上传', |
| | | content: '您确认取消设备日志上传吗?', |
| | | okType: 'danger', |
| | | onOk () { |
| | | cancelDeviceLogUploadOk() |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | async function cancelDeviceLogUploadOk () { |
| | | const { code } = await cancelDeviceLogUpload({ |
| | | device_sn: props.device?.device_sn || '', |
| | | module_list: [DOMAIN.DOCK, DOMAIN.DRONE], |
| | | status: 'cancel' |
| | | }) |
| | | if (code === 0) { |
| | | await getDeviceUploadLogInfo() |
| | | } |
| | | } |
| | | |
| | | // 删除上传的设备日志 |
| | | function onDeleteUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | Modal.confirm({ |
| | | title: '删除上传日志', |
| | | content: '您确认删除该条已上传设备日志吗?', |
| | | okType: 'danger', |
| | | onOk () { |
| | | deleteUploadDeviceLogOk(deviceLogItem) |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | async function deleteUploadDeviceLogOk (deviceLogItem: GetDeviceUploadLogListRsp) { |
| | | const { code } = await deleteDeviceLogUpload({ |
| | | device_sn: props.device?.device_sn || '', |
| | | logs_id: deviceLogItem.logs_id |
| | | }) |
| | | if (code === 0) { |
| | | await getDeviceUploadLogInfo() |
| | | } |
| | | } |
| | | |
| | | // 上传日志 |
| | | const deviceLogUploadModalVisible = ref(false) |
| | | |
| | | function onUploadDeviceLog () { |
| | | deviceLogUploadModalVisible.value = true |
| | | } |
| | | |
| | | function onUploadLogOk () { |
| | | // 刷新列表 |
| | | getDeviceUploadLogInfo() |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .device-log-upload-record-wrap{ |
| | | .page-action-row{ |
| | | display: flex; |
| | | justify-content: space-between; |
| | | width: 100%; |
| | | } |
| | | |
| | | .device-log-upload-list{ |
| | | padding: 20px 0 10px; |
| | | } |
| | | |
| | | .circle-icon { |
| | | display: inline-block; |
| | | width: 12px; |
| | | height: 12px; |
| | | margin-right: 3px; |
| | | border-radius: 50%; |
| | | vertical-align: middle; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .row-action{ |
| | | color: #2d8cf0; |
| | | |
| | | & > span{ |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | import { DeviceLogItem } from '/@/api/device-log' |
| | | import { bytesToSize } from '/@/utils/bytes' |
| | | import { formatUnixTime } from '/@/utils/time' |
| | | import { |
| | | DATE_FORMAT_MINUTE |
| | | } from '/@/utils/constants' |
| | | |
| | | export function useDeviceLogUploadDetail () { |
| | | function getLogTime (deviceLog: DeviceLogItem): string { |
| | | const startTime = formatUnixTime(deviceLog.start_time, DATE_FORMAT_MINUTE) |
| | | const endTime = formatUnixTime(deviceLog.end_time, DATE_FORMAT_MINUTE) |
| | | return `${startTime} — ${endTime}` |
| | | } |
| | | |
| | | function getLogSize (size: number) { |
| | | return bytesToSize(size) |
| | | } |
| | | |
| | | return { |
| | | getLogTime, |
| | | getLogSize |
| | | } |
| | | } |
| New file |
| | |
| | | import EventBus from '/@/event-bus/' |
| | | import { onMounted, onBeforeUnmount } from 'vue' |
| | | import { DeviceLogUploadInfo } from '/@/types/device-log' |
| | | |
| | | export function useDeviceLogUploadProgressEvent (onDeviceLogUploadWs: (data: DeviceLogUploadInfo) => void): void { |
| | | function handleDeviceLogUploadProgress (payload: any) { |
| | | onDeviceLogUploadWs(payload.data) |
| | | // eslint-disable-next-line no-unused-expressions |
| | | // console.log('payload', payload.data) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | EventBus.on('deviceLogUploadProgress', handleDeviceLogUploadProgress) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | EventBus.off('deviceLogUploadProgress', handleDeviceLogUploadProgress) |
| | | }) |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="firmware_upgrade_wrap"> |
| | | <!-- 版本 --> |
| | | <span class="version"> {{ device.firmware_version }}</span> |
| | | <!-- tag --> |
| | | <span v-if="getTagStatus(device)" |
| | | class="status-tag pointer"> |
| | | <a-tag class="pointer" |
| | | :color="getFirmwareTag(device.firmware_status).color" |
| | | @click="deviceUpgrade(device)"> |
| | | {{ getFirmwareTag(device.firmware_status).text }} |
| | | </a-tag> |
| | | </span> |
| | | <!-- 进度 --> |
| | | <span v-if="device.firmware_status === DeviceFirmwareStatusEnum.DuringUpgrade"> |
| | | {{ `${device.firmware_progress}%`}} |
| | | </span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { defineProps, defineEmits, ref, watch, computed } from 'vue' |
| | | import { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareStatusColor } from '/@/types/device' |
| | | |
| | | const props = defineProps<{ |
| | | device: Device, |
| | | }>() |
| | | |
| | | const emit = defineEmits(['device-upgrade']) |
| | | const needUpgrade = computed(() => { |
| | | return props.device.firmware_status === DeviceFirmwareStatusEnum.ConsistencyUpgrade || |
| | | props.device.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded |
| | | }) |
| | | |
| | | function getTagStatus (record: Device) { |
| | | return record.firmware_status && record.firmware_status !== DeviceFirmwareStatusEnum.None |
| | | } |
| | | |
| | | function getFirmwareTag (status: DeviceFirmwareStatusEnum) { |
| | | return { |
| | | text: DeviceFirmwareStatus[status] || '', |
| | | color: DeviceFirmwareStatusColor[status] || '' |
| | | } |
| | | } |
| | | |
| | | function deviceUpgrade (record: Device) { |
| | | if (!needUpgrade.value) return |
| | | emit('device-upgrade', record) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .firmware_upgrade_wrap{ |
| | | |
| | | .status-tag{ |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .pointer { |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <a-modal :visible="sVisible" |
| | | :title="title" |
| | | :closable="false" |
| | | centered |
| | | @update:visible="onVisibleChange" |
| | | @cancel="onCancel" |
| | | @ok="onConfirm"> |
| | | <div> |
| | | 升级固件版本: {{ deviceUpgradeInfo?.product_version }} |
| | | </div> |
| | | </a-modal> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { defineProps, defineEmits, ref, Ref, watchEffect } from 'vue' |
| | | import { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareTypeEnum } from '/@/types/device' |
| | | import { getDeviceUpgradeInfo, GetDeviceUpgradeInfoRsp, DeviceUpgradeBody } from '/@/api/device-upgrade' |
| | | |
| | | const props = defineProps<{ |
| | | visible: boolean, |
| | | title: string, |
| | | device: null | Device, |
| | | }>() |
| | | |
| | | const emit = defineEmits(['update:visible', 'ok', 'cancel']) |
| | | |
| | | const deviceUpgradeInfo:Ref<GetDeviceUpgradeInfoRsp> = ref({} as GetDeviceUpgradeInfoRsp) |
| | | const sVisible = ref(false) |
| | | |
| | | watchEffect(() => { |
| | | sVisible.value = props.visible |
| | | // 显示弹框时,获取设备升级信息 |
| | | if (props.visible) { |
| | | initDeviceUpgradeInfo() |
| | | } |
| | | }) |
| | | |
| | | function onVisibleChange (sVisible: boolean) { |
| | | setVisible(sVisible) |
| | | } |
| | | |
| | | function setVisible (v: boolean, e?: Event) { |
| | | sVisible.value = v |
| | | emit('update:visible', v, e) |
| | | } |
| | | |
| | | // 获取设备升级信息 |
| | | async function initDeviceUpgradeInfo () { |
| | | if (!props.device?.device_name) { |
| | | return |
| | | } |
| | | const { code, data } = await getDeviceUpgradeInfo({ device_name: props.device?.device_name }) |
| | | if (code === 0) { |
| | | deviceUpgradeInfo.value = data && data[0] |
| | | } |
| | | } |
| | | |
| | | // 提交 |
| | | function checkConfirm () { |
| | | if (!deviceUpgradeInfo.value.product_version) { |
| | | return false |
| | | } |
| | | if (!props.device) { |
| | | return false |
| | | } |
| | | if (props.device.firmware_status !== DeviceFirmwareStatusEnum.ToUpgraded && props.device.firmware_status !== DeviceFirmwareStatusEnum.ConsistencyUpgrade) { |
| | | return false |
| | | } |
| | | return true |
| | | } |
| | | |
| | | function onConfirm (e: Event) { |
| | | if (!checkConfirm()) { |
| | | return |
| | | } |
| | | setVisible(false, e) |
| | | emit('ok', [{ |
| | | device_name: props.device?.device_name, |
| | | sn: props.device?.device_sn, |
| | | product_version: deviceUpgradeInfo.value.product_version, |
| | | firmware_upgrade_type: props.device?.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded ? DeviceFirmwareTypeEnum.ToUpgraded : DeviceFirmwareTypeEnum.ConsistencyUpgrade // 1-普通升级,2-一致性升级 |
| | | }] as DeviceUpgradeBody, e) |
| | | } |
| | | |
| | | function onCancel (e: Event) { |
| | | setVisible(false, e) |
| | | emit('cancel', e) |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | </style> |
| New file |
| | |
| | | import EventBus from '/@/event-bus/' |
| | | import { onMounted, onBeforeUnmount } from 'vue' |
| | | import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
| | | |
| | | export function useDeviceUpgradeEvent (onDeviceUpgradeWs: (payload: DeviceCmdExecuteInfo) => void): void { |
| | | function handleDeviceUpgrade (payload: any) { |
| | | onDeviceUpgradeWs(payload.data) |
| | | // eslint-disable-next-line no-unused-expressions |
| | | // console.log('payload', payload.data) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | EventBus.on('deviceUpgrade', handleDeviceUpgrade) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | EventBus.off('deviceUpgrade', handleDeviceUpgrade) |
| | | }) |
| | | } |
| New file |
| | |
| | | import { Ref, ref } from 'vue' |
| | | import { Device } from '/@/types/device' |
| | | import { postDeviceUpgrade, DeviceUpgradeBody } from '/@/api/device-upgrade' |
| | | |
| | | export function useDeviceFirmwareUpgrade (workspaceId: string) { |
| | | const deviceFirmwareUpgradeModalVisible = ref(false) |
| | | const selectedDevice: Ref<null | Device> = ref(null) |
| | | |
| | | function setDeviceFirmwareUpgradeModalVisible (visible: boolean) { |
| | | deviceFirmwareUpgradeModalVisible.value = visible |
| | | } |
| | | |
| | | function setSelectedDevice (device: null | Device) { |
| | | selectedDevice.value = device |
| | | } |
| | | |
| | | // 点击设备升级 |
| | | function onDeviceUpgrade (record: Device) { |
| | | if (!record) { |
| | | return |
| | | } |
| | | setSelectedDevice(record) |
| | | setDeviceFirmwareUpgradeModalVisible(true) |
| | | } |
| | | |
| | | // 确认设备升级 |
| | | async function onUpgradeDeviceOk (deviceUpgradeBody: DeviceUpgradeBody) { |
| | | const { code } = await postDeviceUpgrade(workspaceId, deviceUpgradeBody) |
| | | if (code === 0) { |
| | | // setDeviceFirmwareUpgradeModalVisible(false) |
| | | } |
| | | } |
| | | |
| | | return { |
| | | deviceFirmwareUpgradeModalVisible, |
| | | setDeviceFirmwareUpgradeModalVisible, |
| | | selectedDevice, |
| | | setSelectedDevice, |
| | | onDeviceUpgrade, |
| | | onUpgradeDeviceOk, |
| | | } |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="dock-control-panel"> |
| | | <!-- title --> |
| | | <div class="dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between"> |
| | | <span>远程调试 {{ props.sn}}</span> |
| | | <span @click="closeControlPanel"> |
| | | <CloseOutlined /> |
| | | </span> |
| | | </div> |
| | | <!-- cmd --> |
| | | <div class="control-cmd-wrapper"> |
| | | <div v-for="(cmdItem, index) in cmdList" :key="cmdItem.cmdKey" class="control-cmd-item"> |
| | | <div class="control-cmd-item-left"> |
| | | <div class="item-label">{{ cmdItem.label }}</div> |
| | | <div class="item-status">{{ cmdItem.status }}</div> |
| | | </div> |
| | | <div class="control-cmd-item-right"> |
| | | <a-button :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)"> |
| | | {{ cmdItem.operateText }} |
| | | </a-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { defineProps, defineEmits, ref, watch } from 'vue' |
| | | import { |
| | | CloseOutlined |
| | | } from '@ant-design/icons-vue' |
| | | import { useDockControl } from './useDockControl' |
| | | import { DeviceInfoType } from '/@/types/device' |
| | | import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd' |
| | | import { useMyStore } from '/@/store' |
| | | import { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd' |
| | | |
| | | const props = defineProps<{ |
| | | sn: string, |
| | | deviceInfo: DeviceInfoType, |
| | | }>() |
| | | |
| | | const store = useMyStore() |
| | | const initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem)) |
| | | const cmdList = ref(initCmdList) |
| | | |
| | | // 根据机场指令执行状态更新信息 |
| | | watch(() => store.state.devicesCmdExecuteInfo, (devicesCmdExecuteInfo) => { |
| | | if (props.sn && devicesCmdExecuteInfo[props.sn]) { |
| | | updateDeviceCmdInfoByExecuteInfo(cmdList.value, devicesCmdExecuteInfo[props.sn]) |
| | | } |
| | | }, { |
| | | immediate: true, |
| | | deep: true, |
| | | }) |
| | | |
| | | // 根据设备osd信息更新信息 |
| | | watch(() => props.deviceInfo, (value) => { |
| | | updateDeviceCmdInfoByOsd(cmdList.value, value) |
| | | // console.log('deviceInfo', value) |
| | | }, { |
| | | immediate: true, |
| | | deep: true |
| | | }) |
| | | |
| | | const emit = defineEmits(['close-control-panel']) |
| | | |
| | | function closeControlPanel () { |
| | | emit('close-control-panel', props.sn, false) |
| | | } |
| | | |
| | | // dock 控制指令 |
| | | const { |
| | | sendDockControlCmd, |
| | | } = useDockControl() |
| | | |
| | | async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) { |
| | | const success = await sendDockControlCmd({ |
| | | sn: props.sn, |
| | | cmd: cmdItem.cmdKey |
| | | }, true) |
| | | if (success) { |
| | | // updateDeviceSingleCmdInfo(cmdList.value[index]) |
| | | } |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang='scss' scoped> |
| | | .dock-control-panel{ |
| | | position: absolute; |
| | | left: calc(100% + 10px); |
| | | top: 0px; |
| | | width: 480px; |
| | | padding: 0 !important; |
| | | background: #000; |
| | | color: #fff; |
| | | border-radius: 2px; |
| | | |
| | | .dock-control-panel-header{ |
| | | border-bottom: 1px solid #515151; |
| | | } |
| | | |
| | | .control-cmd-wrapper{ |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: space-between; |
| | | padding: 4px 10px; |
| | | .control-cmd-item{ |
| | | width: 220px; |
| | | height: 58px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | border: 1px solid #666; |
| | | margin: 4px 0; |
| | | padding: 0 8px; |
| | | |
| | | .control-cmd-item-left{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .item-label{ |
| | | font-weight: 700; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import { ref } from 'vue' |
| | | import { postSendCmd } from '/@/api/device-cmd' |
| | | import { DeviceCmd } from '/@/types/device-cmd' |
| | | |
| | | export function useDockControl () { |
| | | const controlPanelVisible = ref(false) |
| | | |
| | | function setControlPanelVisible (visible: boolean) { |
| | | controlPanelVisible.value = visible |
| | | } |
| | | |
| | | // 远程调试开关 |
| | | async function dockDebugOnOff (sn: string, on: boolean) { |
| | | const success = await sendDockControlCmd({ |
| | | sn: sn, |
| | | cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose |
| | | }, false) |
| | | if (success) { |
| | | setControlPanelVisible(on) |
| | | } |
| | | } |
| | | |
| | | // 发送指令 |
| | | async function sendDockControlCmd (params: { |
| | | sn: string, |
| | | cmd: DeviceCmd |
| | | }, tip = true) { |
| | | try { |
| | | const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }) |
| | | if (code === 0) { |
| | | tip && message.success('指令发送成功') |
| | | return true |
| | | } |
| | | throw (msg) |
| | | } catch (e) { |
| | | tip && message.error('指令发送失败') |
| | | return false |
| | | } |
| | | } |
| | | |
| | | return { |
| | | controlPanelVisible, |
| | | setControlPanelVisible, |
| | | sendDockControlCmd, |
| | | dockDebugOnOff, |
| | | } |
| | | } |
| New file |
| | |
| | | import mitt, { Emitter } from 'mitt' |
| | | |
| | | type Events = { |
| | | deviceUpgrade: any; |
| | | deviceLogUploadProgress: any |
| | | }; |
| | | |
| | | const emitter: Emitter<Events> = mitt<Events>() |
| | | |
| | | export default emitter |
| New file |
| | |
| | | import { onMounted, onUnmounted } from 'vue' |
| | | import ReconnectingWebSocket from 'reconnecting-websocket' |
| | | import ConnectWebSocket, { MessageHandler } from '/@/websocket' |
| | | import { getWebsocketUrl } from '/@/websocket/util/config' |
| | | |
| | | /** |
| | | * 接收一个message函数 |
| | | * @param messageHandler |
| | | */ |
| | | export function useConnectWebSocket (messageHandler: MessageHandler) { |
| | | const webSocket = new ConnectWebSocket(getWebsocketUrl()) |
| | | |
| | | onMounted(() => { |
| | | webSocket?.registerMessageHandler(messageHandler) |
| | | webSocket?.initSocket() |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | webSocket?.close() |
| | | }) |
| | | } |
| | |
| | | |
| | | export function useGMapCover () { |
| | | const root = getRoot() |
| | | const AMap = root.$aMapObj |
| | | const AMap = root.$aMap |
| | | |
| | | const normalColor = '#2D8CF0' |
| | | const store = rootStore |
| | | const coverList = store.state.coverList |
| | | |
| | | function AddCoverToMap (cover :any) { |
| | | root.$aMap.add(cover) |
| | | root.$map.add(cover) |
| | | coverList.push(cover) |
| | | // console.log('coverList:', store.state.coverList) |
| | | } |
| | | |
| | | function getPinIcon (color?:string) { |
| | | // console.log('color', color) |
| | | const colorObj: { |
| | |
| | | 'b620e0': pinb620e0, |
| | | 'e23c39': pine23c39, |
| | | 'ffbb00': pineffbb00, |
| | | |
| | | } |
| | | const iconName = (color?.replaceAll('#', '') || '').toLocaleLowerCase() |
| | | return new AMap.Icon({ |
| | |
| | | // imageSize: new AMap.Size(40, 50) |
| | | }) |
| | | } |
| | | |
| | | function init2DPin (name: string, coordinates:GeojsonCoordinate, color?:string, data?:{}) { |
| | | console.log(name, coordinates[0], coordinates[1], color, data) |
| | | const pin = new AMap.Marker({ |
| | |
| | | // console.log('coordinates pin', pin) |
| | | AddCoverToMap(pin) |
| | | } |
| | | |
| | | function AddOverlayGroup (overlayGroup) { |
| | | root.$aMap.add(overlayGroup) |
| | | root.$map.add(overlayGroup) |
| | | coverList.push(overlayGroup) |
| | | } |
| | | function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) { |
| | |
| | | }) |
| | | AddOverlayGroup(polyline) |
| | | } |
| | | |
| | | function initPolygon (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) { |
| | | const path = [] as GeojsonCoordinate[] |
| | | coordinates.forEach(coordinate => { |
| | |
| | | }) |
| | | AddOverlayGroup(Polygon) |
| | | } |
| | | |
| | | function removeCoverFromMap (id:string) { |
| | | for (let i = 0; i < coverList.length; i++) { |
| | | const ele = coverList[i] |
| | |
| | | const extdata = ele?.getExtData() |
| | | if (extdata?.id === id) { |
| | | console.log(extdata) |
| | | root.$aMap.remove(ele) |
| | | root.$map.remove(ele) |
| | | coverList.slice(i, 1) |
| | | break |
| | | } |
| | | } |
| | | } |
| | | |
| | | function getElementFromMap (id:string) { |
| | | // console.log('start', new Date().getTime()) |
| | | const ele = coverList.find(ele => ele?.getExtData().id === id) |
| | |
| | | // } |
| | | // }) |
| | | } |
| | | |
| | | function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) { |
| | | const element = getElementFromMap(id) as any |
| | | if (element) { |
| | |
| | | }) |
| | | } |
| | | } |
| | | |
| | | return { |
| | | init2DPin, |
| | | initPolyline, |
| | |
| | | |
| | | export function deviceTsaUpdate () { |
| | | const root = getRoot() |
| | | const AMap = root.$aMapObj |
| | | const AMap = root.$aMap |
| | | |
| | | const map = root.$aMap |
| | | const icons: { |
| | | [key: string]: string |
| | | } = { |
| | | 'sub-device' : '/@/assets/icons/drone.png', |
| | | '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' // 线颜色 |
| | | }) |
| | | |
| | | // Fix: 航迹初始化报错 |
| | | // TODO: 从时序上解决 |
| | | let trackLine = null as any |
| | | function getTrackLineInstance () { |
| | | if (!trackLine) { |
| | | trackLine = new AMap.Polyline({ |
| | | map: root.$map, |
| | | strokeColor: '#939393' // 线颜色 |
| | | }) |
| | | } |
| | | return trackLine |
| | | } |
| | | |
| | | function initIcon (type: string) { |
| | | return new AMap.Icon({ |
| | |
| | | return |
| | | } |
| | | markers[sn] = new AMap.Marker({ |
| | | position: new AMap.LngLat(lng ? lng : 113.935913, lat ? lat : 22.525335), |
| | | position: new AMap.LngLat(lng || 113.935913, lat || 22.525335), |
| | | icon: initIcon(type), |
| | | title: name, |
| | | anchor: 'top-center', |
| | | offset: [0, -20], |
| | | }) |
| | | root.$aMap.add(markers[sn]) |
| | | root.$map.add(markers[sn]) |
| | | |
| | | // markers[sn].on('moving', function (e: any) { |
| | | // let path = paths[sn] |
| | |
| | | // } |
| | | // path.push(e.passedPath[0]) |
| | | // path.push(e.passedPath[1]) |
| | | // passedPolyline.setPath(path) |
| | | // getTrackLineInstance().setPath(path) |
| | | // }) |
| | | } |
| | | |
| | | function removeMarker (sn: string) { |
| | | if (!markers[sn]) { |
| | | return |
| | | } |
| | | root.$aMap.remove(markers[sn]) |
| | | passedPolyline.setPath([]) |
| | | root.$map.remove(markers[sn]) |
| | | getTrackLineInstance().setPath([]) |
| | | delete markers[sn] |
| | | delete paths[sn] |
| | | } |
| | | function addMarker(sn: string, lng?: number, lat?: number) { |
| | | |
| | | function addMarker (sn: string, lng?: number, lat?: number) { |
| | | getDeviceBySn(localStorage.getItem(ELocalStorageKey.WorkspaceId)!, sn) |
| | | .then(data => { |
| | | if (data.code !== 0) { |
| | |
| | | initMarker(data.data.domain, data.data.nickname, sn, lng, lat) |
| | | }) |
| | | } |
| | | function moveTo(sn: string, lng: number, lat: number) { |
| | | |
| | | function moveTo (sn: string, lng: number, lat: number) { |
| | | let marker = markers[sn] |
| | | if (!marker) { |
| | | addMarker(sn, lng, lat) |
| | |
| | | autoRotation: true |
| | | }) |
| | | } |
| | | |
| | | |
| | | return { |
| | | marker: markers, |
| | | initMarker, |
| | |
| | | |
| | | export function useGMapManage () { |
| | | const state = reactive({ |
| | | mapEntity: null, |
| | | mapObj: null, |
| | | aMap: null, // Map类 |
| | | map: null, // 地图对象 |
| | | mouseTool: null, |
| | | }) |
| | | |
| | | async function initMap (container: string, app:App) { |
| | | AMapLoader.load({ |
| | | ...AMapConfig |
| | | }).then((AMap) => { |
| | | state.mapObj = AMap |
| | | state.mapEntity = new AMap.Map(container, { |
| | | state.aMap = AMap |
| | | state.map = new AMap.Map(container, { |
| | | center: [113.935913, 22.525335], |
| | | zoom: 15 |
| | | }) |
| | | state.mouseTool = new AMap.MouseTool(state.mapEntity) |
| | | app.config.globalProperties.$aMap = state.mapEntity |
| | | app.config.globalProperties.$aMapObj = state.mapObj |
| | | state.mouseTool = new AMap.MouseTool(state.map) |
| | | |
| | | // 挂在到全局 |
| | | app.config.globalProperties.$aMap = state.aMap |
| | | app.config.globalProperties.$map = state.map |
| | | app.config.globalProperties.$mouseTool = state.mouseTool |
| | | }).catch(e => { |
| | | console.log(e) |
| | | }) |
| | | } |
| | | |
| | | function globalPropertiesConfig (app:App) { |
| | | initMap('g-container', app) |
| | | } |
| | | |
| | | return { |
| | | globalPropertiesConfig, |
| | | } |
| | |
| | | |
| | | export function useMouseTool () { |
| | | const root = getRoot() |
| | | const AMap = root.$aMapObj |
| | | |
| | | const state = reactive({ |
| | | pinNum: 0, |
| | | polylineNum: 0, |
| | | PolygonNum: 0, |
| | | currentType: '', |
| | | }) |
| | | |
| | | function drawPin (type:MapDoodleType, getDrawCallback:Function) { |
| | | root?.$mouseTool.marker({ |
| | | title: type + state.pinNum, |
| | |
| | | state.pinNum++ |
| | | root?.$mouseTool.on('draw', getDrawCallback) |
| | | } |
| | | |
| | | function drawPolyline (type:MapDoodleType, getDrawCallback:Function) { |
| | | root?.$mouseTool.polyline({ |
| | | strokeColor: '#2d8cf0', |
| | |
| | | }) |
| | | root?.$mouseTool.on('draw', getDrawCallback) |
| | | } |
| | | |
| | | function drawPolygon (type:MapDoodleType, getDrawCallback:Function) { |
| | | root?.$mouseTool.polygon({ |
| | | strokeColor: '#2d8cf0', |
| | |
| | | }) |
| | | root?.$mouseTool.on('draw', getDrawCallback) |
| | | } |
| | | |
| | | function drawOff (type:MapDoodleType) { |
| | | root?.$mouseTool.close() |
| | | root?.$mouseTool.off('draw') |
| | | } |
| | | |
| | | function mouseTool (type: MapDoodleType, getDrawCallback: Function) { |
| | | state.currentType = type |
| | | switch (type) { |
| | |
| | | break |
| | | } |
| | | } |
| | | |
| | | return { |
| | | mouseTool |
| | | } |
| | |
| | | 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' |
| | | import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' |
| | | |
| | | const root = getRoot() |
| | | const gatewayState = ref<boolean>(localStorage.getItem(ELocalStorageKey.GatewayOnline) === 'true') |
| | |
| | | |
| | | const store = useMyStore() |
| | | |
| | | const wsGetMsg = async (res: any) => { |
| | | const payload = JSON.parse(res.data) |
| | | const messageHandler = async (payload: any) => { |
| | | if (!payload) { |
| | | return |
| | | } |
| | | switch (payload.biz_code) { |
| | | case EBizCode.DeviceOnline: { |
| | | console.info('online: ', payload) |
| | |
| | | break |
| | | } |
| | | } |
| | | |
| | | // 监听ws 消息 |
| | | useConnectWebSocket(messageHandler) |
| | | |
| | | let bindNum: number |
| | | let socket: ReconnectingWebSocket |
| | | |
| | | onMounted(() => { |
| | | apiPilot.onBackClickReg() |
| | |
| | | } |
| | | device.data.sn = apiPilot.getAircraftSN() |
| | | getDeviceInfo() |
| | | |
| | | socket = websocket.init(wsGetMsg) |
| | | |
| | | const isLoaded = apiPilot.isComponentLoaded(EComponentName.Thing) |
| | | if (isLoaded) { |
| | |
| | | window.wsConnectCallback = arg => { |
| | | wsConnectCallback(arg) |
| | | } |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | socket.close() |
| | | }) |
| | | |
| | | const connectCallback = async (arg: any) => { |
| 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 '/@/components/common/topbar.vue' |
| | | import { onMounted, reactive, ref, UnwrapRef, watch } from 'vue' |
| | | import { getRoot } from '/@/root' |
| | | import { EBizCode, ELocalStorageKey, ERouterName } from '/@/types' |
| | | import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' |
| | | import EventBus from '/@/event-bus' |
| | | |
| | | interface FormState { |
| | | user: string |
| | | password: string |
| | | } |
| | | |
| | | const root = getRoot() |
| | | |
| | | const messageHandler = async (payload: any) => { |
| | | if (!payload) { |
| | | return |
| | | } |
| | | switch (payload.biz_code) { |
| | | case EBizCode.DeviceUpgrade: { |
| | | EventBus.emit('deviceUpgrade', payload) |
| | | break |
| | | } |
| | | case EBizCode.DeviceLogUploadProgress: { |
| | | EventBus.emit('deviceLogUploadProgress', payload) |
| | | break |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 监听ws 消息 |
| | | useConnectWebSocket(messageHandler) |
| | | |
| | | 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> |
| File was renamed from src/pages/project-app/index.vue |
| | |
| | | class="m0" |
| | | type="primary" |
| | | html-type="submit" |
| | | :disabled="formState.user === '' || formState.password === ''" |
| | | :disabled="loginBtnDisabled" |
| | | @click="onSubmit" |
| | | > |
| | | Login |
| | |
| | | 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 { reactive, computed, UnwrapRef } from 'vue' |
| | | import { login, LoginBody } from '/@/api/manage' |
| | | import { getRoot } from '/@/root' |
| | | import { ELocalStorageKey, ERouterName, EUserType } from '/@/types' |
| | | import router from '/@/router' |
| | | |
| | | const root = getRoot() |
| | | |
| | | const formState: UnwrapRef<LoginBody> = reactive({ |
| | | username: 'adminPC', |
| | | password: 'adminPC', |
| | | flag: EUserType.Web, |
| | | }) |
| | | |
| | | const loginBtnDisabled = computed(() => { |
| | | return !formState.username || !formState.password |
| | | }) |
| | | |
| | | const onSubmit = async (e: any) => { |
| | | const result = await login(formState) |
| | | if (result.code === 0) { |
| | | console.log(result) |
| | | localStorage.setItem(ELocalStorageKey.Token, result.data.access_token) |
| | | localStorage.setItem(ELocalStorageKey.WorkspaceId, result.data.workspace_id) |
| | | localStorage.setItem(ELocalStorageKey.Username, result.data.username) |
| 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="device-table-wrap 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 #firmware_version="{ record }"> |
| | | <span v-if="judgeCurrentType(EDeviceTypeName.Dock)"> |
| | | <DeviceFirmwareUpgrade :device="record" |
| | | class="table-flex-col" |
| | | @device-upgrade="onDeviceUpgrade" |
| | | /> |
| | | </span> |
| | | <span v-else> |
| | | {{ record.firmware_version }} |
| | | </span> |
| | | </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"> |
| | | <!-- 编辑态操作 --> |
| | | <div 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]" style="color: #e70102;"><CloseOutlined /></span> |
| | | </a-tooltip> |
| | | </div> |
| | | <!-- 非编辑态操作 --> |
| | | <div v-else class="flex-align-center flex-row" style="color: #2d8cf0"> |
| | | <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="设备日志"> |
| | | <CloudServerOutlined @click="showDeviceLogUploadRecord(record)"/> |
| | | </a-tooltip> |
| | | <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)"/> |
| | | </a-tooltip> |
| | | <a-tooltip title="Delete"> |
| | | <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }"/> |
| | | </a-tooltip> |
| | | </div> |
| | | </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> |
| | | |
| | | <!-- 设备升级 --> |
| | | <DeviceFirmwareUpgradeModal title="设备升级" |
| | | v-model:visible="deviceFirmwareUpgradeModalVisible" |
| | | :device="selectedDevice" |
| | | @ok="onUpgradeDeviceOk" |
| | | ></DeviceFirmwareUpgradeModal> |
| | | |
| | | <!-- 设备日志上传记录 --> |
| | | <DeviceLogUploadRecordDrawer |
| | | v-model:visible="deviceLogUploadRecordVisible" |
| | | :device="currentDevice" |
| | | ></DeviceLogUploadRecordDrawer> |
| | | |
| | | <!-- hms 信息 --> |
| | | <DeviceHmsDrawer |
| | | v-model:visible="hmsVisible" |
| | | :device="currentDevice"> |
| | | </DeviceHmsDrawer> |
| | | </div> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | 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, unbindDevice, updateDevice } from '/@/api/manage' |
| | | import { EDeviceTypeName, ELocalStorageKey } from '/@/types' |
| | | import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue' |
| | | import { Device, DeviceFirmwareStatusEnum } from '/@/types/device' |
| | | import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue' |
| | | import DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue' |
| | | import { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade' |
| | | import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-device-upgrade-event' |
| | | import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
| | | import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue' |
| | | import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue' |
| | | import { message } from 'ant-design-vue' |
| | | |
| | | interface DeviceData { |
| | | device: Device[] |
| | | } |
| | | |
| | | const loading = ref(true) |
| | | const deleteTip = ref<boolean>(false) |
| | | const deleteSn = ref<string>() |
| | | const columns: ColumnProps[] = [ |
| | | { title: 'Model', dataIndex: 'device_name', width: 100, className: 'titleStyle' }, |
| | | { title: 'SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } }, |
| | | { |
| | | title: 'Name', |
| | | dataIndex: 'nickname', |
| | | width: 100, |
| | | sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname), |
| | | className: 'titleStyle', |
| | | ellipsis: true, |
| | | slots: { customRender: 'nickname' } |
| | | }, |
| | | { title: 'Firmware Version', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } }, |
| | | { title: 'Status', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } }, |
| | | { |
| | | title: 'Workspace', |
| | | dataIndex: 'workspace_name', |
| | | width: 100, |
| | | className: 'titleStyle', |
| | | ellipsis: true, |
| | | slots: { customRender: 'workspace' }, |
| | | customRender: ({ text, record, index }) => { |
| | | const obj = { |
| | | children: text, |
| | | props: {} as any, |
| | | } |
| | | if (current.value.indexOf(EDeviceTypeName.Dock) !== -1) { |
| | | if (record.domain === EDeviceTypeName.Aircraft) { |
| | | obj.children = '' |
| | | } |
| | | } |
| | | return obj |
| | | } |
| | | }, |
| | | { title: 'Joined', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' }, |
| | | { title: 'Last Online', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' }, |
| | | { |
| | | title: 'Actions', |
| | | dataIndex: 'actions', |
| | | width: 100, |
| | | 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 |
| | | }) |
| | | |
| | | // 获取分页信息 |
| | | function getPaginationBody () { |
| | | return { |
| | | page: paginationProp.current, |
| | | page_size: paginationProp.pageSize |
| | | } as IPage |
| | | } |
| | | |
| | | 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 workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
| | | const editableData: UnwrapRef<Record<string, Device>> = reactive({}) |
| | | const current = ref([EDeviceTypeName.Aircraft]) |
| | | |
| | | function judgeCurrentType (type: EDeviceTypeName): boolean { |
| | | return current.value.indexOf(type) !== -1 |
| | | } |
| | | |
| | | // 设备升级 |
| | | const { |
| | | deviceFirmwareUpgradeModalVisible, |
| | | selectedDevice, |
| | | onDeviceUpgrade, |
| | | onUpgradeDeviceOk |
| | | } = useDeviceFirmwareUpgrade(workspaceId) |
| | | |
| | | function onDeviceUpgradeWs (payload: DeviceCmdExecuteInfo) { |
| | | updateDevicesByWs(data.device, payload) |
| | | } |
| | | |
| | | function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) { |
| | | if (!devices || devices.length <= 0) { |
| | | return |
| | | } |
| | | for (let i = 0; i < devices.length; i++) { |
| | | if (devices[i].device_sn === payload.sn) { |
| | | if (!payload.output) return |
| | | const { status, progress } = payload.output |
| | | if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中 |
| | | devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade |
| | | devices[i].firmware_progress = progress?.percent || 0 |
| | | } else { // 终态:成功,失败,超时 |
| | | if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) { |
| | | message.error(`设备(${payload.sn}) 升级失败`) |
| | | } |
| | | // 拉取列表 |
| | | getDevices(current.value[0], true) |
| | | } |
| | | return |
| | | } |
| | | if (devices[i].children) { |
| | | updateDevicesByWs(devices[i].children || [], payload) |
| | | } |
| | | } |
| | | } |
| | | |
| | | useDeviceUpgradeEvent(onDeviceUpgradeWs) |
| | | |
| | | // 获取设备列表信息 |
| | | function getDevices (domain: string, closeLoading?: boolean) { |
| | | if (!closeLoading) { |
| | | loading.value = true |
| | | } |
| | | getBindingDevices(workspaceId, getPaginationBody(), 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 |
| | | paginationProp.pageSize = res.data.pagination.page_size |
| | | loading.value = false |
| | | }) |
| | | } |
| | | |
| | | function refreshData (page: Pagination) { |
| | | paginationProp.current = page?.current! |
| | | paginationProp.pageSize = page?.pageSize! |
| | | getDevices(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(current.value[0]) |
| | | }) |
| | | } |
| | | |
| | | // 选择设备 |
| | | function select (item: any) { |
| | | getDevices(item.key) |
| | | } |
| | | |
| | | const currentDevice = ref({} as Device) |
| | | // 设备日志 |
| | | const deviceLogUploadRecordVisible = ref(false) |
| | | function showDeviceLogUploadRecord (dock: Device) { |
| | | deviceLogUploadRecordVisible.value = true |
| | | currentDevice.value = dock |
| | | } |
| | | |
| | | // 健康状态 |
| | | const hmsVisible = ref<boolean>(false) |
| | | |
| | | function showHms (dock: Device) { |
| | | hmsVisible.value = true |
| | | currentDevice.value = dock |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDevices(current.value[0]) |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .device-table-wrap{ |
| | | .editable-row-operations{ |
| | | div > span { |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | <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> |
| File was renamed from src/pages/project-app/projects/members.vue |
| | |
| | | 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' } }, |
| | | { title: 'Account', dataIndex: 'username', width: 150, sorter: (a: Member, b: Member) => a.username.localeCompare(b.username), className: 'titleStyle' }, |
| | | { title: 'User Type', dataIndex: 'user_type', width: 150, className: 'titleStyle' }, |
| | | { title: 'Workspace Name', dataIndex: 'workspace_name', width: 150, className: 'titleStyle' }, |
| | | { title: 'Mqtt Username', dataIndex: 'mqtt_username', width: 150, className: 'titleStyle', slots: { customRender: 'mqtt_username' } }, |
| | | { title: 'Mqtt Password', dataIndex: 'mqtt_password', width: 150, className: 'titleStyle', slots: { customRender: 'mqtt_password' } }, |
| | | { title: 'Joined', dataIndex: 'create_time', width: 150, sorter: (a: Member, b: Member) => a.create_time.localeCompare(b.create_time), className: 'titleStyle' }, |
| | | { title: 'Action', dataIndex: 'action', width: 100, className: 'titleStyle', slots: { customRender: 'action' } }, |
| | | ] |
| | | |
| | | const data = reactive<MemberData>({ |
| | |
| | | data.member = userList |
| | | paginationProp.total = res.data.pagination.total |
| | | paginationProp.current = res.data.pagination.page |
| | | |
| | | }) |
| | | } |
| | | |
| File was renamed from src/pages/project-app/projects/tsa.vue |
| | |
| | | </a-tooltip> |
| | | </div> |
| | | <div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;"> |
| | | <div> |
| | | <div class="flex-align-center flex-row"> |
| | | <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;'"> |
| | | <span class="font-bold text-hidden" style="max-width: 80px;" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'"> |
| | | {{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].mode_code] : EDockModeCode[EDockModeCode.Disconnected] }} |
| | | </span> |
| | | </div> |
| File was renamed from src/pages/project-app/projects/wayline.vue |
| | |
| | | </a-row> |
| | | </div> |
| | | <div class="height-100"> |
| | | <a-spin :spinning="loading" :delay="1000" tip="downloading" size="large"> |
| | | <div class="scrollbar uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll"> |
| | | <div v-for="wayline in waylinesData.data" :key="wayline.id"> |
| | | <div class="wayline-panel" style="padding-top: 5px;" @click="selectRoute(wayline)"> |
| | |
| | | </div> |
| | | </template> |
| | | </a-modal> |
| | | </a-spin> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | import { downloadFile } from '/@/utils/common' |
| | | import { IPage } from '/@/api/http/type' |
| | | |
| | | const loading = ref(false) |
| | | const store = useMyStore() |
| | | const pagination :IPage = { |
| | | page: 1, |
| | |
| | | } |
| | | |
| | | function downloadWayline (waylineId: string, fileName: string) { |
| | | loading.value = true |
| | | downloadWaylineFile(workspaceId, waylineId).then(res => { |
| | | if (res.code && res.code !== 0) { |
| | | if (!res) { |
| | | return |
| | | } |
| | | const data = new Blob([res.data], { type: 'application/zip' }) |
| | | const data = new Blob([res], { type: 'application/zip' }) |
| | | downloadFile(data, fileName + '.kmz') |
| | | }).finally(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | |
| File was renamed from src/pages/project-app/projects/workspace.vue |
| | |
| | | </template> |
| | | <script lang="ts" setup> |
| | | |
| | | import Sidebar from '../sidebar.vue' |
| | | import Sidebar from '/@/components/common/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' |
| | | import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' |
| | | |
| | | const root = getRoot() |
| | | const store = useMyStore() |
| | | |
| | | const wsGetMsg = async (res: any) => { |
| | | const payload = JSON.parse(res.data) |
| | | const messageHandler = async (payload: any) => { |
| | | if (!payload) { |
| | | return |
| | | } |
| | | |
| | | switch (payload.biz_code) { |
| | | case EBizCode.GatewayOsd: { |
| | | store.commit('SET_GATEWAY_INFO', payload.data) |
| | |
| | | store.commit('SET_DEVICE_HMS_INFO', payload.data) |
| | | break |
| | | } |
| | | case EBizCode.DeviceReboot: |
| | | case EBizCode.DroneOpen: |
| | | case EBizCode.DroneClose: |
| | | case EBizCode.CoverOpen: |
| | | case EBizCode.CoverClose: |
| | | case EBizCode.PutterOpen: |
| | | case EBizCode.PutterClose: |
| | | case EBizCode.ChargeOpen: |
| | | case EBizCode.ChargeClose: |
| | | case EBizCode.DeviceFormat: |
| | | case EBizCode.DroneFormat: |
| | | { |
| | | store.commit('SET_DEVICES_CMD_EXECUTE_INFO', { |
| | | biz_code: payload.biz_code, |
| | | timestamp: payload.timestamp, |
| | | ...payload.data, |
| | | }) |
| | | break |
| | | } |
| | | default: |
| | | break |
| | | } |
| | | } |
| | | |
| | | const store = useMyStore() |
| | | // 监听ws 消息 |
| | | useConnectWebSocket(messageHandler) |
| | | |
| | | let socket: ReconnectingWebSocket |
| | | |
| | | onMounted(() => { |
| | | socket = websocket.init(wsGetMsg) |
| | | }) |
| | | onUnmounted(() => { |
| | | socket.close() |
| | | }) |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | @import '/@/styles/index.scss'; |
| | |
| | | import { createApp, ComponentCustomProperties, App as VueApp } from 'vue' |
| | | declare module '@vue/runtime-core' { |
| | | interface ComponentCustomProperties { |
| | | $aMap: any |
| | | $aMapObj: any |
| | | $aMap: any // Map类 |
| | | $map: any // 地图对象 |
| | | $mouseTool: any |
| | | } |
| | | } |
| | |
| | | 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 CreatePlan from '../pages/page-web/projects/create-plan.vue' |
| | | import WaylinePanel from '/@/pages/page-web/projects/wayline.vue' |
| | | import DockPanel from '/@/pages/page-web/projects/dock.vue' |
| | | import LiveAgora from '/@/components/livestream-agora.vue' |
| | | import LiveOthers from '/@/components/livestream-others.vue' |
| | | |
| | |
| | | path: '/', |
| | | redirect: '/' + ERouterName.PROJECT |
| | | }, |
| | | // 首页 |
| | | { |
| | | path: '/' + ERouterName.PROJECT, |
| | | name: ERouterName.PROJECT, |
| | | component: () => import('/@/pages/project-app/index.vue') |
| | | component: () => import('/@/pages/page-web/index.vue') |
| | | }, |
| | | // members, devices |
| | | { |
| | | path: '/' + ERouterName.HOME, |
| | | name: ERouterName.HOME, |
| | | component: () => import('/@/pages/project-app/home.vue'), |
| | | component: () => import('/@/pages/page-web/home.vue'), |
| | | children: [ |
| | | { |
| | | path: '/' + ERouterName.MEMBERS, |
| | | name: ERouterName.MEMBERS, |
| | | component: () => import('/@/pages/project-app/projects/members.vue') |
| | | component: () => import('/@/pages/page-web/projects/members.vue') |
| | | }, |
| | | { |
| | | path: '/' + ERouterName.DEVICES, |
| | | name: ERouterName.DEVICES, |
| | | component: () => import('/@/pages/project-app/projects/devices.vue') |
| | | component: () => import('/@/pages/page-web/projects/devices.vue') |
| | | } |
| | | ] |
| | | }, |
| | | // workspace |
| | | { |
| | | path: '/' + ERouterName.WORKSPACE, |
| | | name: ERouterName.WORKSPACE, |
| | | component: () => import('/@/pages/project-app/projects/workspace.vue'), |
| | | component: () => import('/@/pages/page-web/projects/workspace.vue'), |
| | | redirect: '/' + ERouterName.TSA, |
| | | children: [ |
| | | { |
| | | path: '/' + ERouterName.LIVESTREAM, |
| | | name: ERouterName.LIVESTREAM, |
| | | component: () => import('/@/pages/project-app/projects/livestream.vue'), |
| | | component: () => import('/@/pages/page-web/projects/livestream.vue'), |
| | | children: [ |
| | | { |
| | | path: ERouterName.LIVING, |
| | |
| | | }, |
| | | { |
| | | path: '/' + ERouterName.TSA, |
| | | component: () => import('/@/pages/project-app/projects/tsa.vue') |
| | | component: () => import('/@/pages/page-web/projects/tsa.vue') |
| | | }, |
| | | { |
| | | path: '/' + ERouterName.LAYER, |
| | | name: ERouterName.LAYER, |
| | | component: () => import('/@/pages/project-app/projects/layer.vue') |
| | | component: () => import('/@/pages/page-web/projects/layer.vue') |
| | | }, |
| | | { |
| | | path: '/' + ERouterName.MEDIA, |
| | | name: ERouterName.MEDIA, |
| | | component: () => import('/@/pages/project-app/projects/media.vue') |
| | | component: () => import('/@/pages/page-web/projects/media.vue') |
| | | }, |
| | | { |
| | | path: '/' + ERouterName.WAYLINE, |
| | | name: ERouterName.WAYLINE, |
| | | component: () => import('/@/pages/project-app/projects/wayline.vue') |
| | | component: () => import('/@/pages/page-web/projects/wayline.vue') |
| | | }, |
| | | { |
| | | path: '/' + ERouterName.TASK, |
| | | name: ERouterName.TASK, |
| | | component: () => import('/@/pages/project-app/projects/task.vue'), |
| | | component: () => import('/@/pages/page-web/projects/task.vue'), |
| | | children: [ |
| | | { |
| | | path: ERouterName.CREATE_PLAN, |
| | |
| | | } |
| | | ] |
| | | }, |
| | | // pilot |
| | | { |
| | | path: '/' + ERouterName.PILOT, |
| | | name: ERouterName.PILOT, |
| | |
| | | { |
| | | 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 { getLayers } from '/@/api/layer' |
| | | import { LayerType } from '/@/types/mapLayer' |
| | | import { ETaskStatus, TaskInfo, WaylineFile } from '/@/types/wayline' |
| | | import { DevicesCmdExecuteInfo } from '/@/types/device-cmd' |
| | | |
| | | const initStateFunc = () => ({ |
| | | Layers: [ |
| | |
| | | }, |
| | | hmsInfo: {} as { |
| | | [sn: string]: DeviceHms[] |
| | | } |
| | | }, |
| | | // 机场指令执行状态信息 |
| | | devicesCmdExecuteInfo: { |
| | | } as DevicesCmdExecuteInfo |
| | | }) |
| | | |
| | | export type RootStateType = ReturnType<typeof initStateFunc> |
| | |
| | | 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_DEVICE_HMS_INFO (state, info) { |
| | | const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn] |
| | | state.hmsInfo[info.sn] = info.host.concat(hmsList ?? []) |
| | | }, |
| | | SET_DEVICES_CMD_EXECUTE_INFO (state, info) { // 保存设备指令ws消息推送 |
| | | if (!info.sn) { |
| | | return |
| | | } |
| | | if (state.devicesCmdExecuteInfo[info.sn]) { |
| | | const index = state.devicesCmdExecuteInfo[info.sn].findIndex(cmdExecuteInfo => cmdExecuteInfo.biz_code === info.biz_code) |
| | | if (index >= 0) { |
| | | // 丢弃前面的消息 |
| | | if (state.devicesCmdExecuteInfo[info.sn][index].timestamp > info.timestamp) { |
| | | return |
| | | } |
| | | state.devicesCmdExecuteInfo[info.sn][index] = info |
| | | } else { |
| | | state.devicesCmdExecuteInfo[info.sn].push(info) |
| | | } |
| | | } else { |
| | | state.devicesCmdExecuteInfo[info.sn] = [info] |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | justify-content: space-around; |
| | | } |
| | | |
| | | .flex-1 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .flex-shrink-0 { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .flex-shrink-1 { |
| | | flex-shrink: 1; |
| | | } |
| | | |
| | | //width |
| | | .width-100vw { |
| | | width: 100vw; |
| New file |
| | |
| | | // 机场存储容量:总容量(单位:KB)、已使用(单位:KB) |
| | | export interface AirportStorage { |
| | | total: number, // 单位:KB |
| | | used: number |
| | | } |
| | | |
| | | // 舱盖状态 |
| | | export enum CoverStateEnum { |
| | | Close = 0, // 关闭 |
| | | Open = 1, // 打开 |
| | | HalfOpen = 2, // 半打开 |
| | | Failed = 3 // 失败 |
| | | } |
| | | |
| | | // 推杆状态 |
| | | export enum PutterStateEnum { |
| | | Close = 0, // 关闭 |
| | | Open = 1, // 打开 |
| | | HalfOpen = 2, // 半打开 |
| | | Failed = 3 // 失败 |
| | | } |
| | | |
| | | // 充电状态 |
| | | export enum ChargeStateEnum { |
| | | NotCharge = 0, // 空闲 |
| | | Charge = 1, // 正在充电 |
| | | } |
| | | |
| | | export interface DroneChargeState { |
| | | state: ChargeStateEnum, |
| | | capacity_percent: string, |
| | | } |
| | | |
| | | // 补光灯状态 |
| | | export enum SupplementLightStateEnum { |
| | | Close = 0, // 关闭 |
| | | Open = 1, // 打开 |
| | | } |
| New file |
| | |
| | | // 机场指令集 |
| | | export enum DeviceCmd { |
| | | // 简单指令 |
| | | DebugModeOpen = 'debug_mode_open', // 调试模式开启 |
| | | DebugModeClose = 'debug_mode_close', // 调试模式关闭 |
| | | SupplementLightOpen = 'supplement_light_open', // 打开补光灯 |
| | | SupplementLightClose = 'supplement_light_close', // 关闭补光灯 |
| | | ReturnHome = 'return_home', // 一键返航 |
| | | // 复杂指令 |
| | | DeviceReboot = 'device_reboot', // 机场重启 |
| | | DroneOpen = 'drone_open', // 飞行器开机 |
| | | DroneClose = 'drone_close', // 飞行器关机 |
| | | // DeviceCheck = 'device_check', // 一键排障(一键起飞自检) |
| | | DeviceFormat = 'device_format', // 机场数据格式化 |
| | | DroneFormat = 'drone_format', // 飞行器数据格式化 |
| | | CoverOpen = 'cover_open', // 打开舱盖 |
| | | CoverClose = 'cover_close', // 关闭舱盖 |
| | | PutterOpen = 'putter_open', // 推杆展开 |
| | | PutterClose = 'putter_close', // 推杆闭合 |
| | | ChargeOpen = 'charge_open', // 打开充电 |
| | | ChargeClose = 'charge_close', // 关闭充电 |
| | | } |
| | | |
| | | export interface DeviceCmdItem{ |
| | | label: string, // 标题 |
| | | status: string, // 当前状态 |
| | | operateText: string, // 按钮文字 |
| | | cmdKey: DeviceCmd, // 请求指令 |
| | | oppositeCmdKey?: DeviceCmd, // 相反状态指令 |
| | | func: string, // 处理函数 |
| | | loading: boolean // 按钮loading |
| | | } |
| | | |
| | | // 机场指令 |
| | | export const cmdList: DeviceCmdItem[] = [ |
| | | { |
| | | // iconName: , |
| | | label: '机场系统', |
| | | status: '工作中', |
| | | operateText: '重启', |
| | | cmdKey: DeviceCmd.DeviceReboot, |
| | | func: 'deviceReboot', |
| | | loading: false, |
| | | // btnAnimationIconName: '', |
| | | // operateTips: '', |
| | | // statusColor: '', |
| | | }, |
| | | { |
| | | label: '飞行器', |
| | | status: '关机', |
| | | operateText: '开机', |
| | | cmdKey: DeviceCmd.DroneOpen, |
| | | oppositeCmdKey: DeviceCmd.DroneClose, |
| | | func: 'droneStatus', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '舱盖', |
| | | status: '关', |
| | | operateText: '开启', |
| | | cmdKey: DeviceCmd.CoverOpen, |
| | | oppositeCmdKey: DeviceCmd.CoverClose, |
| | | func: 'coverStatus', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '推杆', |
| | | status: '闭合', |
| | | operateText: '展开', |
| | | cmdKey: DeviceCmd.PutterOpen, |
| | | oppositeCmdKey: DeviceCmd.PutterClose, |
| | | func: 'putterStatus', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '充电状态', |
| | | status: '未充电', |
| | | operateText: '充电', |
| | | cmdKey: DeviceCmd.ChargeOpen, |
| | | oppositeCmdKey: DeviceCmd.ChargeClose, |
| | | func: 'chargeStatus', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '一键返航', |
| | | status: '--', |
| | | operateText: '返航', |
| | | cmdKey: DeviceCmd.ReturnHome, |
| | | func: 'returnHome', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '机场存储', |
| | | status: '--', |
| | | operateText: '格式化', |
| | | cmdKey: DeviceCmd.DeviceFormat, |
| | | func: 'deviceFormat', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '飞行器存储', |
| | | status: '--', |
| | | operateText: '格式化', |
| | | cmdKey: DeviceCmd.DroneFormat, |
| | | func: 'droneFormat', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '补光灯', |
| | | status: '关', |
| | | operateText: '打开', |
| | | cmdKey: DeviceCmd.SupplementLightOpen, |
| | | oppositeCmdKey: DeviceCmd.SupplementLightClose, |
| | | func: 'supplementLightStatus', |
| | | loading: false, |
| | | }, |
| | | ] |
| | | |
| | | export enum DeviceCmdStatusText { |
| | | DeviceRebootNormalText = '工作中', |
| | | DeviceRebootInProgressText = '重启中...', |
| | | DeviceRebootFailedText = '重启失败', |
| | | |
| | | DroneStatusOpenNormalText = '开', |
| | | DroneStatusOpenInProgressText = '开机中...', |
| | | DroneStatusOpenFailedText = '关', |
| | | DroneStatusOpenBtnText = '关机', |
| | | |
| | | DroneStatusCloseNormalText = '关', |
| | | DroneStatusCloseInProgressText = '关机中...', |
| | | DroneStatusCloseFailedText = '开', |
| | | DroneStatusCloseBtnText = '开机', |
| | | |
| | | DeviceCoverOpenNormalText = '开', |
| | | DeviceCoverOpenInProgressText = '开启中...', |
| | | DeviceCoverOpenFailedText = '关', |
| | | DeviceCoverOpenBtnText = '关闭', |
| | | |
| | | DeviceCoverCloseNormalText = '关', |
| | | DeviceCoverCloseInProgressText = '关闭中...', |
| | | DeviceCoverCloseFailedText = '开', |
| | | DeviceCoverCloseBtnText = '开启', |
| | | |
| | | DevicePutterOpenNormalText = '展开', |
| | | DevicePutterOpenBtnText = '闭合', |
| | | DevicePutterOpenInProgressText = '推杆展开中', |
| | | DevicePutterOpenFailedText = '闭合', |
| | | |
| | | DevicePutterCloseNormalText = '闭合', |
| | | DevicePutterCloseInProgressText = '推杆闭合中', |
| | | DevicePutterCloseFailedText = '展开', |
| | | DevicePutterCloseBtnText = '展开', |
| | | |
| | | DeviceChargeOpenNormalText = '充电', |
| | | DeviceChargeOpenInProgressText = '充电中...', |
| | | DeviceChargeOpenFailedText = '未充电', |
| | | DeviceChargeOpenBtnText = '断电', |
| | | |
| | | DeviceChargeCloseNormalText = '断电', |
| | | DeviceChargeCloseInProgressText = '断电中...', |
| | | DeviceChargeCloseFailedText = '充电', |
| | | DeviceChargeCloseBtnText = '充电', |
| | | |
| | | DeviceFormatInProgressText = '格式化...', |
| | | DeviceFormatFailedText = '格式化失败', |
| | | |
| | | DroneFormatInProgressText = '格式化...', |
| | | DroneFormatFailedText = '格式化失败', |
| | | |
| | | DeviceSupplementLightOpenNormalText = '开', |
| | | DeviceSupplementLightOpenInProgressText = '开启中...', |
| | | DeviceSupplementLightOpenFailedText = '关', |
| | | DeviceSupplementLightOpenBtnText = '关闭', |
| | | |
| | | DeviceSupplementLightCloseNormalText = '关', |
| | | DeviceSupplementLightCloseText = '关闭中...', |
| | | DeviceSupplementLightCloseFailedText = '开', |
| | | DeviceSupplementLightCloseBtnText = '打开', |
| | | } |
| | | |
| | | // cmd ws 消息状态 |
| | | export enum DeviceCmdExecuteStatus { |
| | | Sent = 'sent', // 已下发 |
| | | InProgress = 'in_progress', // 执行中 |
| | | OK = 'ok', // 执行成功 |
| | | Failed = 'failed', // 失败 |
| | | Canceled = 'canceled', // 取消 |
| | | Timeout = 'timeout' // 超时 |
| | | } |
| | | |
| | | export interface DeviceCmdExecuteInfo { |
| | | biz_code: string, |
| | | timestamp: number, |
| | | sn: string, |
| | | bid: string, |
| | | output:{ |
| | | status: DeviceCmdExecuteStatus, |
| | | progress?: { |
| | | percent: number, |
| | | step_key: string, |
| | | step_result: number |
| | | } |
| | | } |
| | | result: number, |
| | | } |
| | | |
| | | // 所有机场的指令执行状态 |
| | | export interface DevicesCmdExecuteInfo { |
| | | [key: string]: DeviceCmdExecuteInfo[], // sn --- DeviceCmdExecuteInfo |
| | | } |
| New file |
| | |
| | | import { DOMAIN } from '/@/types/device' |
| | | import { commonColor } from '/@/utils/color' |
| | | |
| | | // 日志上传状态 |
| | | export enum DeviceLogUploadStatusEnum { |
| | | Uploading = 1, // 上传中 |
| | | Done = 2, // 完成 |
| | | Canceled = 3, // 取消 |
| | | Failed = 4, // 失败 |
| | | } |
| | | |
| | | export const DeviceLogUploadStatusMap = { |
| | | [DeviceLogUploadStatusEnum.Uploading]: '上传中', |
| | | [DeviceLogUploadStatusEnum.Done]: '上传成功', |
| | | [DeviceLogUploadStatusEnum.Canceled]: '取消上传', |
| | | [DeviceLogUploadStatusEnum.Failed]: '上传失败', |
| | | } |
| | | |
| | | export const DeviceLogUploadStatusColor = { |
| | | [DeviceLogUploadStatusEnum.Uploading]: commonColor.BLUE, |
| | | [DeviceLogUploadStatusEnum.Done]: commonColor.NORMAL, |
| | | [DeviceLogUploadStatusEnum.Canceled]: commonColor.WARN, |
| | | [DeviceLogUploadStatusEnum.Failed]: commonColor.FAIL, |
| | | } |
| | | |
| | | // 设备日志上传 ws 消息状态 |
| | | export enum DeviceLogUploadStatus { |
| | | FilePull = 'file_pull', // 拉取日志 可以作为 正在处理中 |
| | | FileZip = 'file_zip', // 拉取日志,日志压缩可以作为 正在处理中 |
| | | FileUploading = 'file_uploading', // 正在上传 |
| | | Canceled = 'canceled', // 取消 |
| | | Timeout = 'timeout', // 超时 |
| | | Failed = 'failed', // 失败 |
| | | OK = 'ok', // 上传成功 |
| | | // Paused = 'paused' // 暂停 |
| | | } |
| | | |
| | | export interface DeviceLogUploadInfo { |
| | | sn: string, |
| | | bid: string, |
| | | output:{ |
| | | logs_id: string |
| | | status: DeviceLogUploadStatus, |
| | | files: { |
| | | device_sn: string, |
| | | device_model_domain: DOMAIN, |
| | | progress: number, |
| | | result: number, |
| | | upload_rate: number, |
| | | status: DeviceLogUploadStatus |
| | | }[] |
| | | } |
| | | result: number, |
| | | } |
| | | |
| | | // ws status => log status |
| | | export const DeviceLogUploadWsStatusMap = { |
| | | [DeviceLogUploadStatus.FilePull]: DeviceLogUploadStatusEnum.Uploading, |
| | | [DeviceLogUploadStatus.FileZip]: DeviceLogUploadStatusEnum.Uploading, |
| | | [DeviceLogUploadStatus.FileUploading]: DeviceLogUploadStatusEnum.Uploading, |
| | | [DeviceLogUploadStatus.OK]: DeviceLogUploadStatusEnum.Done, |
| | | [DeviceLogUploadStatus.Failed]: DeviceLogUploadStatusEnum.Failed, |
| | | [DeviceLogUploadStatus.Canceled]: DeviceLogUploadStatusEnum.Canceled, |
| | | [DeviceLogUploadStatus.Timeout]: DeviceLogUploadStatusEnum.Failed, |
| | | } |
| | |
| | | import { EDeviceTypeName } from "."; |
| | | import { commonColor } from '/@/utils/color' |
| | | |
| | | export interface DeviceValue { |
| | | key: string; // 'domain-type-subtype' |
| | | domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种 |
| | | type: number; // 设备类型枚举 |
| | | sub_type: number; // 设备类型枚举 负载一般表示镜头 |
| | | } |
| | | |
| | | // domain |
| | | export enum DOMAIN { |
| | | DRONE = '0', // 飞行器 |
| | | PAYLOAD = '1', // 负载 |
| | | RC = '2', // 遥控 |
| | | DOCK = '3', // 机场 |
| | | } |
| | | |
| | | // DJI飞机类型 |
| | | export enum DRONE_TYPE { |
| | | M30 = 67, |
| | | M300 = 60, |
| | | Phantom4 = 11, |
| | | Phantom4Pro = 18, |
| | | Phantom4RTK = 59, |
| | | Phantom4Advanced = 27, |
| | | Mavic3EnterpriseAdvanced= 77, |
| | | } |
| | | |
| | | // DJI负载类型枚举值 |
| | | export enum PAYLOAD_TYPE { |
| | | FPV = 39, |
| | | H20 = 42, |
| | | H20T = 43, |
| | | H20N = 61, |
| | | EP600 = 50, |
| | | EP800 = 90742, |
| | | M30D = 52, |
| | | M30T = 53, |
| | | XT2 = 26, |
| | | XTS = 41, |
| | | Z30 = 20, |
| | | DockTopCamera = 165, |
| | | |
| | | M3E = 66, |
| | | M3T = 67, |
| | | // UNKNOWN = 65535 |
| | | } |
| | | |
| | | // RC type |
| | | export enum RC_TYPE { |
| | | RC = 56, |
| | | RCPlus = 119, |
| | | RC144 = 144, |
| | | } |
| | | |
| | | // DOCK type |
| | | export enum DOCK_TYPE { |
| | | Dock = 1, |
| | | } |
| | | |
| | | // 设备sub_type 从0升序 |
| | | export enum DEVICE_SUB_TYPE { |
| | | ZERO, |
| | | ONE, |
| | | TWO, |
| | | THREE, |
| | | UNKNOWN = 65535, |
| | | } |
| | | |
| | | export const DEVICE_MODEL_KEY = { |
| | | M30: `${DOMAIN.DRONE}-${DRONE_TYPE.M30}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | M30T: `${DOMAIN.DRONE}-${DRONE_TYPE.M30}-${DEVICE_SUB_TYPE.ONE}`, |
| | | |
| | | M3E: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | M3T: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ONE}`, |
| | | |
| | | M300: `${DOMAIN.DRONE}-${DRONE_TYPE.M300}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | Phantom4: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | Phantom4Pro: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4Pro}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | Phantom4RTK: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4RTK}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | Phantom4Advanced: `${DOMAIN.DRONE}-${DRONE_TYPE.Phantom4Advanced}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | |
| | | FPV: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.FPV}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | H20: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | H20T: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20T}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | H20N: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20N}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | EP600: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.EP600}-${DEVICE_SUB_TYPE.UNKNOWN}`, |
| | | EP800: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.EP800}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | M30Camera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M30D}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | M30TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M30T}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | |
| | | M3ECamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3E}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | M3TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3T}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | // M3MCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3M}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | |
| | | XT2: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XT2}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | XTS: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XTS}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | Z30: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.Z30}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | DockTopCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.DockTopCamera}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | |
| | | RC: `${DOMAIN.RC}-${RC_TYPE.RC}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | RCPlus: `${DOMAIN.RC}-${RC_TYPE.RCPlus}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | |
| | | Dock: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock}-${DEVICE_SUB_TYPE.ZERO}`, |
| | | } |
| | | |
| | | export const DEVICE_NAME = { |
| | | // drone |
| | | [DEVICE_MODEL_KEY.M30]: 'M30', |
| | | [DEVICE_MODEL_KEY.M30T]: 'M30T', |
| | | [DEVICE_MODEL_KEY.M3E]: 'Mavic 3E', |
| | | [DEVICE_MODEL_KEY.M3T]: 'Mavic 3T', |
| | | // [DEVICE_MODEL_KEY.M3M]: 'Mavic 3M', |
| | | [DEVICE_MODEL_KEY.M300]: 'M300 RTK', |
| | | [DEVICE_MODEL_KEY.Phantom4]: 'Phantom 4', |
| | | [DEVICE_MODEL_KEY.Phantom4Pro]: 'Phantom 4 Pro', |
| | | [DEVICE_MODEL_KEY.Phantom4RTK]: 'Phantom 4 RTK', |
| | | [DEVICE_MODEL_KEY.Phantom4Advanced]: 'Phantom 4 Advanced', |
| | | |
| | | // payload |
| | | [DEVICE_MODEL_KEY.FPV]: 'FPV', |
| | | [DEVICE_MODEL_KEY.H20]: 'H20', |
| | | [DEVICE_MODEL_KEY.H20T]: 'H20T', |
| | | [DEVICE_MODEL_KEY.H20N]: 'H20N', |
| | | [DEVICE_MODEL_KEY.EP600]: 'P1', |
| | | [DEVICE_MODEL_KEY.EP800]: 'L1', |
| | | [DEVICE_MODEL_KEY.M30Camera]: 'M30 Camera', |
| | | [DEVICE_MODEL_KEY.M30TCamera]: 'M30T Camera', |
| | | [DEVICE_MODEL_KEY.M3ECamera]: 'Mavic 3E', |
| | | [DEVICE_MODEL_KEY.M3TCamera]: 'Mavic 3T', |
| | | // [DEVICE_MODEL_KEY.M3MCamera]: 'Mavic 3M', |
| | | [DEVICE_MODEL_KEY.XT2]: 'XT2', |
| | | [DEVICE_MODEL_KEY.XTS]: 'XTS', |
| | | [DEVICE_MODEL_KEY.Z30]: 'Z30', |
| | | [DEVICE_MODEL_KEY.DockTopCamera]: 'Dock Camera', |
| | | |
| | | // rc |
| | | [DEVICE_MODEL_KEY.RC]: 'RC', |
| | | [DEVICE_MODEL_KEY.RCPlus]: 'RC Plus', |
| | | |
| | | // dock |
| | | [DEVICE_MODEL_KEY.Dock]: 'Dock', |
| | | } |
| | | |
| | | // 固件升级类型 |
| | | export enum DeviceFirmwareTypeEnum { |
| | | ToUpgraded = 3, // 普通升级 |
| | | ConsistencyUpgrade =2, // 一致性升级 |
| | | } |
| | | |
| | | // 固件升级状态 |
| | | export enum DeviceFirmwareStatusEnum { |
| | | None = 1, // 无需升级 |
| | | ToUpgraded = 2, // 待升级 |
| | | ConsistencyUpgrade = 3, // 一致性升级 |
| | | DuringUpgrade = 4, // 升级中 |
| | | } |
| | | |
| | | export const DeviceFirmwareStatus = { |
| | | [DeviceFirmwareStatusEnum.None]: '', |
| | | [DeviceFirmwareStatusEnum.ToUpgraded]: '待升级', |
| | | [DeviceFirmwareStatusEnum.ConsistencyUpgrade]: '一致性升级', |
| | | [DeviceFirmwareStatusEnum.DuringUpgrade]: '升级中', |
| | | } |
| | | |
| | | export const DeviceFirmwareStatusColor = { |
| | | [DeviceFirmwareStatusEnum.None]: commonColor.WHITE, |
| | | [DeviceFirmwareStatusEnum.ToUpgraded]: commonColor.BLUE, |
| | | [DeviceFirmwareStatusEnum.ConsistencyUpgrade]: commonColor.WARN, |
| | | [DeviceFirmwareStatusEnum.DuringUpgrade]: commonColor.NORMAL, |
| | | } |
| | | |
| | | export interface Device { |
| | | device_name: string, |
| | | device_sn: string, |
| | | nickname: string, |
| | | firmware_version: string, |
| | | firmware_status: DeviceFirmwareStatusEnum, |
| | | status: string, |
| | | workspace_name: string, |
| | | bound_time: string, |
| | | login_time: string, |
| | | children?: Device[] |
| | | domain: string |
| | | children?: Device[], |
| | | domain: string, |
| | | firmware_progress?: number, // 升级进度 |
| | | } |
| | | |
| | | export interface DeviceStatus { |
| | |
| | | network_state: { |
| | | type: number, |
| | | quality: number, |
| | | rate: number, |
| | | rate: number, |
| | | }, |
| | | drone_in_dock: number, |
| | | drone_charge_state: { |
| | |
| | | create_time: string, |
| | | update_time: string, |
| | | domain: string |
| | | } |
| | | } |
| | | |
| | | // TODO: 设备拓扑管理优化 |
| | | // 设备信息 |
| | | export interface DeviceInfoType { |
| | | gateway: GatewayOsd, // 遥控器 |
| | | dock: DockOsd, // 机场 |
| | | device: DeviceOsd, // 飞机 |
| | | } |
| | |
| | | DeviceOffline = 'device_offline', |
| | | FlightTaskProgress = 'flighttask_progress', |
| | | DeviceHms = 'device_hms', |
| | | |
| | | // 设备指令 |
| | | DeviceReboot = 'device_reboot', // 机场重启 |
| | | DroneOpen = 'drone_open', // 飞行器开机 |
| | | DroneClose = 'drone_close', // 飞行器关机 |
| | | DeviceFormat = 'device_format', // 机场数据格式化 |
| | | DroneFormat = 'drone_format', // 飞行器数据格式化 |
| | | CoverOpen = 'cover_open', // 打开舱盖 |
| | | CoverClose = 'cover_close', // 关闭舱盖 |
| | | PutterOpen = 'putter_open', // 推杆展开 |
| | | PutterClose = 'putter_close', // 推杆闭合 |
| | | ChargeOpen = 'charge_open', // 打开充电 |
| | | ChargeClose = 'charge_close', // 关闭充电 |
| | | |
| | | // 设备升级 |
| | | DeviceUpgrade = 'ota_progress', // 设备升级 |
| | | |
| | | // 设备日志 |
| | | DeviceLogUploadProgress = 'fileupload_progress' // 设备日志上传上传 |
| | | } |
| | | |
| | | export enum EDeviceTypeName { |
| | |
| | | NOTICE, |
| | | CAUTION, |
| | | WARN, |
| | | } |
| | | } |
| New file |
| | |
| | | import { DEFAULT_PLACEHOLDER, SIZES as byteSizes, BYTE_SIZES } from './constants' |
| | | |
| | | /** |
| | | * 转换字节数为单位B,KB,GB... |
| | | * 保留一位小数 |
| | | * @param bytes 字节数 |
| | | * @param holder 0字节占位符,默认 -- |
| | | * @returns |
| | | */ |
| | | export function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1, unit = false): string { |
| | | if (isNaN(bytes) || bytes === 0) { |
| | | return holder |
| | | } |
| | | // 兼容负数 |
| | | let prefix = '' |
| | | if (bytes < 0) { |
| | | bytes = 0 - bytes |
| | | prefix = '-' |
| | | } |
| | | const k = 1024 |
| | | const sizes = unit ? BYTE_SIZES : byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] |
| | | const i = Math.floor(Math.log(bytes) / Math.log(k)) |
| | | return prefix + (bytes / Math.pow(k, i)).toFixed(fix) + '' + sizes[i] |
| | | } |
| | | |
| | | // 获取转化后数据及单位 |
| | | export function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1): { |
| | | value: string, |
| | | size: string |
| | | index: number |
| | | } { |
| | | if (isNaN(bytes) || bytes === 0) { |
| | | return { |
| | | value: holder, |
| | | size: '', |
| | | index: -1, |
| | | } |
| | | } |
| | | // 兼容负数 |
| | | let prefix = '' |
| | | if (bytes < 0) { |
| | | bytes = 0 - bytes |
| | | prefix = '-' |
| | | } |
| | | const k = 1024 |
| | | const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] |
| | | const i = Math.floor(Math.log(bytes) / Math.log(k)) |
| | | |
| | | return { |
| | | value: prefix + (bytes / Math.pow(k, i)).toFixed(fix), |
| | | size: sizes[i], |
| | | index: i, |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据最小单位返回文件大小 |
| | | * @param bytes |
| | | * @param minUnit |
| | | * @param fix |
| | | * @returns |
| | | */ |
| | | export function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1): string { |
| | | const holder = `0${minUnit}` |
| | | const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] |
| | | const k = 1024 |
| | | const findIndex = sizes.findIndex(item => item === minUnit) |
| | | |
| | | const { value, size, index } = getBytesObject(bytes, holder, fix) |
| | | // 0 |
| | | if (index === -1) { |
| | | return holder |
| | | } |
| | | // 转换后单位小于传入的最小单位 |
| | | if (index < findIndex) { |
| | | const sizeToMinUint = parseFloat(value) / (Math.pow(k, findIndex - index)) |
| | | return sizeToMinUint.toFixed(fix) + minUnit |
| | | } |
| | | // 其他 |
| | | return value + size |
| | | } |
| | | // console.log('size', bytesToSizeWithMinUnit(0)) |
| | | // console.log('size', bytesToSizeWithMinUnit(1023)) |
| | | // console.log('size', bytesToSizeWithMinUnit(1024)) |
| | | // console.log('size', bytesToSizeWithMinUnit(1000 * 1024, 'MB', 2)) |
| | | // console.log('size', bytesToSizeWithMinUnit(1024 * 1024, 'MB', 2)) |
| New file |
| | |
| | | export const commonColor = { |
| | | WARN: '#FF9900', // 黄色 |
| | | FAIL: '#E02020', // 红色 |
| | | WHITE: '#FFFFFF', // 白色 |
| | | NORMAL: '#19BE6B', // 绿色 |
| | | BLUE: '#2B85E4', // 蓝色 |
| | | PINK: '#F7C0BA', // 粉 |
| | | } |
| | |
| | | |
| | | /** |
| | | * 下载文件 |
| | | * @param data |
| | | * @param fileName |
| | | */ |
| | | export function downloadFile (data: Blob, fileName: string) { |
| | | const lable = document.createElement('a') |
| | | lable.href = window.URL.createObjectURL(data) |
| New file |
| | |
| | | |
| | | export const DEFAULT_PLACEHOLDER = '--' // 默认占位符 |
| | | |
| | | // 全局日期格式 |
| | | export const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss' |
| | | export const DATE_FORMAT_MINUTE = 'YYYY-MM-DD HH:mm' |
| | | export const DATE_FORMAT_DAY = 'YYYY-MM-DD' |
| | | export const TIME_FORMAT = 'HH:mm:ss' |
| | | export const TIME_FORMAT_MINUTE = 'HH:mm' |
| | | export const DATE_FORMAT_MM = 'MM-DD HH:mm' |
| | | |
| | | export const SIZES = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] |
| | | export const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] |
| | | export const PAGE_SIZE_OPTIONS = ['20', '50', '100'] |
| | | export const PAGE_SIZE = 50 |
| New file |
| | |
| | | import { DeviceInfoType } from '/@/types/device' |
| | | import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
| | | import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum } from '/@/types/airport-tsa' |
| | | import { getBytesObject } from './bytes' |
| | | import { DEFAULT_PLACEHOLDER } from './constants' |
| | | |
| | | /** |
| | | * 根据osd 更新信息 |
| | | * @param cmdList |
| | | * @param deviceInfo |
| | | * @returns |
| | | */ |
| | | export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo: DeviceInfoType) { |
| | | const { device, dock, gateway } = deviceInfo || {} |
| | | if (!cmdList || cmdList.length < 1) { |
| | | return |
| | | } |
| | | cmdList.forEach(cmdItem => { |
| | | if (cmdItem.loading) { |
| | | return |
| | | } |
| | | if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启 |
| | | // console.log('DeviceReboot') |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneOpen || cmdItem.cmdKey === DeviceCmd.DroneClose) { // 飞行器开关机 |
| | | getDroneState(cmdItem, device) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.CoverOpen || cmdItem.cmdKey === DeviceCmd.CoverClose) { // 舱盖开关 |
| | | getCoverState(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.PutterOpen || cmdItem.cmdKey === DeviceCmd.PutterClose) { // 推杆闭合展开 |
| | | getPutterState(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen || cmdItem.cmdKey === DeviceCmd.ChargeClose) { // 充电状态 |
| | | getChargeState(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储 |
| | | deviceFormat(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储 |
| | | droneFormat(cmdItem, device) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关 |
| | | getSupplementLightState(cmdItem, dock) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 飞行器开关机 |
| | | function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) { |
| | | if (!droneProperties) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.DroneOpen) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } else { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.DroneClose) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 舱盖开关 |
| | | function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const coverState = airportProperties?.cover_state as CoverStateEnum |
| | | |
| | | if (coverState === CoverStateEnum.Close || coverState === CoverStateEnum.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.CoverOpen) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } else if (coverState === CoverStateEnum.Open || coverState === CoverStateEnum.HalfOpen) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.CoverClose) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 推杆状态 |
| | | function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const putterState = airportProperties?.putter_state as PutterStateEnum |
| | | if (putterState === PutterStateEnum.Close || putterState === PutterStateEnum.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.PutterOpen) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } else if (putterState === PutterStateEnum.Open || putterState === PutterStateEnum.HalfOpen) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.PutterClose) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 充电状态 |
| | | function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const chargeState = airportProperties?.drone_charge_state |
| | | const state = chargeState?.state as ChargeStateEnum |
| | | if (!state) return |
| | | if (state === ChargeStateEnum.Charge) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.ChargeClose) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } else if (state === ChargeStateEnum.NotCharge) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText |
| | | if (cmdItem.cmdKey !== DeviceCmd.ChargeOpen) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 机场存储格式化 |
| | | function deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const airportStorage = airportProperties?.storage |
| | | const value = getAirportStorage(airportStorage) |
| | | cmdItem.status = value |
| | | } |
| | | |
| | | // 机场存储格式化 |
| | | function droneFormat (cmdItem: DeviceCmdItem, droneProperties: any) { |
| | | const droneStorage = droneProperties?.storage |
| | | const value = getAirportStorage(droneStorage) |
| | | cmdItem.status = value |
| | | } |
| | | |
| | | // 获取机场存储容量 |
| | | // { |
| | | // "total": 10000, // 单位:KB |
| | | // "used": 500 |
| | | // } |
| | | export function getAirportStorage (storage: AirportStorage) { |
| | | if (!storage) { |
| | | return DEFAULT_PLACEHOLDER |
| | | } |
| | | const total = storage.total |
| | | const used = storage.used |
| | | const byteObj = getBytesObject(total * 1024) |
| | | const _total = byteObj.value |
| | | const _used = getBytes(used * 1024, byteObj.index) |
| | | return `${_used}/${_total} ${byteObj.size}` |
| | | } |
| | | |
| | | function getBytes (bytes: number, index: number, fixed = 1) { |
| | | return (bytes / Math.pow(1024, index)).toFixed(fixed) |
| | | } |
| | | |
| | | // 补光灯状态 |
| | | function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const supplementLightState = airportProperties?.supplement_light_state |
| | | if (supplementLightState === SupplementLightStateEnum.Close) { |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText |
| | | cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText |
| | | if (cmdItem.cmdKey !== DeviceCmd.SupplementLightOpen) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } else if (supplementLightState === SupplementLightStateEnum.Open) { |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText |
| | | cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText |
| | | if (cmdItem.cmdKey !== DeviceCmd.SupplementLightClose) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 交换指令 |
| | | * @param cmd |
| | | */ |
| | | function exchangeDeviceCmd (cmdItem: DeviceCmdItem) { |
| | | if (cmdItem.oppositeCmdKey) { |
| | | const oppositeCmdKey = cmdItem.oppositeCmdKey |
| | | cmdItem.oppositeCmdKey = cmdItem.cmdKey |
| | | cmdItem.cmdKey = oppositeCmdKey |
| | | } |
| | | } |
| | | |
| | | // /** |
| | | // * 更新简单指令发送情况更新信息 |
| | | // * @param cmd |
| | | // */ |
| | | // export function updateDeviceSingleCmdInfo (cmdItem: DeviceCmdItem) { |
| | | // // 补光灯 |
| | | // if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen) { |
| | | // cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText |
| | | // cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText |
| | | // exchangeDeviceCmd(cmdItem) |
| | | // } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { |
| | | // cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText |
| | | // cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText |
| | | // exchangeDeviceCmd(cmdItem) |
| | | // } |
| | | // } |
| | | |
| | | /** |
| | | * 根据指令执行消息更新信息 |
| | | * @param cmd |
| | | * @param deviceCmdExecuteInfo |
| | | * @returns |
| | | */ |
| | | export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], deviceCmdExecuteInfos?: DeviceCmdExecuteInfo[]) { |
| | | if (!deviceCmdExecuteInfos || !cmdList) { |
| | | return |
| | | } |
| | | cmdList.forEach(cmdItem => { |
| | | // 获取当前设备相应指令信息 |
| | | const deviceCmdExecuteInfo = deviceCmdExecuteInfos.find(cmdExecuteInfo => cmdExecuteInfo.biz_code === cmdItem.cmdKey) |
| | | if (deviceCmdExecuteInfo) { |
| | | if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceRebootNormalText |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneOpen) { // 飞行器开关机 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneClose) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.CoverOpen) { // 舱盖开关 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.CoverClose) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.PutterOpen) { // 推杆闭合展开 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.PutterClose) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen) { // 充电状态 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.ChargeClose) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText |
| | | cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText |
| | | exchangeDeviceCmd(cmdItem) |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储 |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | } |
| New file |
| | |
| | | /** |
| | | * 加载图片 |
| | | * @param url |
| | | * @returns |
| | | */ |
| | | export function urlToImage (url: string) { |
| | | return new Promise<HTMLImageElement>((resolve, reject) => { |
| | | const image = new Image() |
| | | image.src = url |
| | | image.onload = () => { resolve(image) } |
| | | image.onerror = () => { reject(new Error('image load error')) } |
| | | }) |
| | | } |
| | | |
| | | export interface CompressImageData { |
| | | blob: Blob | null; |
| | | imageData: ImageData; |
| | | } |
| | | export function compressImage (imgToCompress: HTMLImageElement, targetWidth: number, targetHeight: number): Promise<CompressImageData> | undefined { |
| | | // resizing the image |
| | | const canvas = document.createElement('canvas') |
| | | const context = canvas.getContext('2d') |
| | | if (context) { |
| | | const iWidth = imgToCompress.width |
| | | const iHeight = imgToCompress.height |
| | | const iRatio = iWidth / iHeight // 图像宽高比 |
| | | const tRatio = targetWidth / targetHeight // 目标宽高比 |
| | | let dw = targetWidth |
| | | let dh = targetHeight |
| | | let dx = 0 |
| | | let dy = 0 |
| | | if (iRatio > tRatio) { |
| | | // 如果图像宽高比比目标宽高比要大,说明图像比目标尺寸更宽,这时候我们应该按照高度缩放比来进行缩放宽度 |
| | | dw = (targetHeight / iHeight) * iWidth |
| | | // 宽度溢出,应该放在中间 |
| | | dx = -(dw - targetWidth) / 2 |
| | | } else { |
| | | // 否则说明图像比目标尺寸更高,按照宽度缩放比来缩放高度 |
| | | dh = (targetWidth / iWidth) * iHeight |
| | | // 高度溢出,应该放在中间 |
| | | dy = -(dh - targetHeight) / 2 |
| | | } |
| | | |
| | | canvas.width = targetWidth |
| | | canvas.height = targetHeight |
| | | |
| | | context.drawImage( |
| | | imgToCompress, |
| | | dx, |
| | | dy, |
| | | dw, |
| | | dh, |
| | | ) |
| | | |
| | | return new Promise<CompressImageData>((resolve) => { |
| | | const imageData = context.getImageData(0, 0, canvas.width, canvas.height) |
| | | |
| | | canvas.toBlob(blob => resolve({ |
| | | blob, |
| | | imageData, |
| | | })) |
| | | }) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据资源url下载文件 |
| | | * @param url |
| | | * @param fileName |
| | | */ |
| | | export function download (url: string, fileName = ''): void { |
| | | const aLink = document.createElement('a') |
| | | aLink.style.display = 'none' |
| | | aLink.download = fileName |
| | | aLink.href = url |
| | | document.body.appendChild(aLink) |
| | | // 避免新开页面,闪烁 |
| | | // aLink.target = '_blank' |
| | | aLink.click() |
| | | document.body.removeChild(aLink) |
| | | // aLink.remove() |
| | | } |
| New file |
| | |
| | | import { |
| | | DATE_FORMAT, |
| | | DEFAULT_PLACEHOLDER |
| | | } from '/@/utils/constants' |
| | | import moment, { Moment } from 'moment' |
| | | |
| | | // 时间字符串 或者 Unix 时间戳(毫秒数) |
| | | export function formatDateTime (time: string | number, format = DATE_FORMAT) { |
| | | return time ? moment(time, format) : DEFAULT_PLACEHOLDER |
| | | } |
| | | |
| | | // Unix 时间戳 (秒) |
| | | export function formatUnixTime (time: number, format = DATE_FORMAT): string { |
| | | return time ? moment.unix(time).format(format) : DEFAULT_PLACEHOLDER |
| | | } |
| New file |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import ReconnectingWebSocket from 'reconnecting-websocket' |
| | | |
| | | interface WebSocketOptions { |
| | | data: any |
| | | cache?: boolean | string |
| | | destroyCache?: string |
| | | } |
| | | |
| | | export interface MessageHandler { |
| | | (data : {[key: string]: any}): void |
| | | } |
| | | |
| | | /** |
| | | * ConnectWebSocket 类 |
| | | * TODO: 优化messageHandler: EventEmitter。暂时传入回调函数 |
| | | */ |
| | | class ConnectWebSocket { |
| | | _url: string |
| | | _socket: ReconnectingWebSocket | null |
| | | _hasInit: boolean |
| | | _messageHandler: MessageHandler | null |
| | | |
| | | constructor (url: string) { |
| | | this._url = url |
| | | this._socket = null |
| | | this._hasInit = false |
| | | this._messageHandler = null |
| | | } |
| | | |
| | | initSocket () { |
| | | if (this._hasInit) { |
| | | return |
| | | } |
| | | if (!this._url) { |
| | | return |
| | | } |
| | | |
| | | // 会自动重连,无需处理重连逻辑 |
| | | this._socket = new ReconnectingWebSocket(this._url, [], { |
| | | maxReconnectionDelay: 20000, // 断开后最大的重连时间: 20s,每多一次重连,会增加 1.3 倍,5 * 1.3 * 1.3 * 1.3... |
| | | minReconnectionDelay: 5000, // 断开后最短的重连时间: 5s |
| | | maxRetries: 5 |
| | | }) |
| | | |
| | | this._hasInit = true |
| | | |
| | | this._socket.addEventListener('open', this._onOpen.bind(this)) |
| | | this._socket.addEventListener('close', this._onClose.bind(this)) |
| | | this._socket.addEventListener('error', this._onError.bind(this)) |
| | | this._socket.addEventListener('message', this._onMessage.bind(this)) |
| | | } |
| | | |
| | | _onOpen () { |
| | | console.log('连接成功') |
| | | } |
| | | |
| | | _onClose () { |
| | | console.log('连接已断开') |
| | | } |
| | | |
| | | _onError () { |
| | | console.log('连接 error') |
| | | } |
| | | |
| | | registerMessageHandler (messageHandler: MessageHandler) { |
| | | this._messageHandler = messageHandler |
| | | } |
| | | |
| | | _onMessage (msg: MessageEvent) { |
| | | const data = JSON.parse(msg.data) |
| | | this._messageHandler && this._messageHandler(data) |
| | | // console.log('接受消息', message) |
| | | } |
| | | |
| | | sendMessage = (message: WebSocketOptions): void => { |
| | | this._socket?.send(JSON.stringify(message.data)) |
| | | } |
| | | |
| | | close () { |
| | | this._socket?.close() |
| | | } |
| | | } |
| | | |
| | | export default ConnectWebSocket |
| New file |
| | |
| | | import { ELocalStorageKey } from '/@/types/enums' |
| | | import { CURRENT_CONFIG } from '/@/api/http/config' |
| | | |
| | | export function getWebsocketUrl () { |
| | | const token: string = localStorage.getItem(ELocalStorageKey.Token) || '' as string |
| | | const url = CURRENT_CONFIG.websocketURL + '?x-auth-token=' + encodeURI(token) |
| | | return url |
| | | } |
| | |
| | | "src/**/*.d.ts", |
| | | "src/**/*.tsx", |
| | | "src/**/*.vue", |
| | | "src/vendors/coordtransform.js" |
| | | ] |
| | | "src/vendors/coordtransform.js" |
| | | ] |
| | | } |