26 files modified
1 files renamed
11 files added
2 files deleted
| | |
| | | "ant-design-vue/es/empty/style/css", |
| | | "ant-design-vue/es/form/style/css", |
| | | "ant-design-vue/es/image/style/css", |
| | | "ant-design-vue/es/input-number/style/css", |
| | | "ant-design-vue/es/input/style/css", |
| | | "ant-design-vue/es/layout/style/css", |
| | | "ant-design-vue/es/menu/style/css", |
| | |
| | | "ant-design-vue/es/tag/style/css", |
| | | "ant-design-vue/es/tooltip/style/css", |
| | | "ant-design-vue/es/tree/style/css", |
| | | "ant-design-vue/es/upload/style/css", |
| | | "axios", |
| | | "lodash", |
| | | "mitt", |
| | | "moment", |
| | | "reconnecting-websocket", |
| | |
| | | <template> |
| | | <div id="demo-app" class="demo-app"> |
| | | <div class="demo-app"> |
| | | <router-view /> |
| | | <!-- <div class="map-wrapper"> |
| | | <GMap/> |
| | |
| | | .demo-app { |
| | | width: 100%; |
| | | height: 100%; |
| | | |
| | | .map-wrapper { |
| | | height: 100%; |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | <style lang="scss"> |
| | | #demo-app { |
| | | width: 100%; |
| | | height: 100% |
| | | } |
| | | </style> |
| | |
| | | import request, { IWorkspaceResponse } from '/@/api/http/request' |
| | | import { DeviceCmd } from '/@/types/device-cmd' |
| | | import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd' |
| | | |
| | | const CMD_API_PREFIX = '/control/api/v1' |
| | | |
| | |
| | | device_cmd: DeviceCmd // 指令 |
| | | } |
| | | |
| | | export interface PostSendCmdBody { |
| | | action: DeviceCmdItemAction |
| | | } |
| | | /** |
| | | * 发送机场控制指令 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | // /control/api/v1/devices/{dock_sn}/jobs/{service_identifier} |
| | | export async function postSendCmd (params: SendCmdParams): Promise<IWorkspaceResponse<{}>> { |
| | | const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`) |
| | | export async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> { |
| | | const postBody = body || {} |
| | | const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, { |
| | | ...postBody |
| | | }) |
| | | return resp.data |
| | | } |
| New file |
| | |
| | | import request, { IWorkspaceResponse } from '/@/api/http/request' |
| | | import { ELocalStorageKey } from '/@/types' |
| | | import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from '/@/types/device-setting' |
| | | |
| | | const MNG_API_PREFIX = '/manage/api/v1' |
| | | const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
| | | |
| | | export interface PutDevicePropsBody { |
| | | night_lights_state?: NightLightsStateEnum;// 夜航灯开关 |
| | | height_limit?: number;// 限高设置 |
| | | distance_limit_status?: DistanceLimitStatus;// 限远开关 |
| | | obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置 |
| | | } |
| | | |
| | | /** |
| | | * 设置设备属性 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | // /manage/api/v1/devices/{{workspace_id}}/devices/{{device_sn}}/property |
| | | export async function putDeviceProps (deviceSn: string, body: PutDevicePropsBody): Promise<IWorkspaceResponse<{}>> { |
| | | const resp = await request.put(`${MNG_API_PREFIX}/devices/${workspaceId}/devices/${deviceSn}/property`, body) |
| | | return resp.data |
| | | } |
| | |
| | | }) |
| | | const result = await request.get(url) |
| | | return result.data |
| | | } |
| | | |
| | | export const changeLivestreamLens = async function (body: {}): Promise<IWorkspaceResponse<any>> { |
| | | const url = `${HTTP_PREFIX}/live/streams/switch` |
| | | const result = await request.post(url, body) |
| | | return result.data |
| | | } |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import request, { IPage, IWorkspaceResponse } from '/@/api/http/request' |
| | | const HTTP_PREFIX = '/wayline/api/v1' |
| | | import request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request' |
| | | import { TaskType, TaskStatus, OutOfControlAction } from '/@/types/task' |
| | | import { WaylineType } from '/@/types/wayline' |
| | | |
| | | export interface CreatePlan { |
| | | name: string, |
| | | file_id: string, |
| | | dock_sn: string, |
| | | immediate: boolean, |
| | | type: string, |
| | | } |
| | | const HTTP_PREFIX = '/wayline/api/v1' |
| | | |
| | | // Get Wayline Files |
| | | export const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> { |
| | |
| | | if (result.data.type === 'application/json') { |
| | | const reader = new FileReader() |
| | | reader.onload = function (e) { |
| | | let text = reader.result as string |
| | | const text = reader.result as string |
| | | const result = JSON.parse(text) |
| | | message.error(result.message) |
| | | } |
| | | reader.readAsText(result.data, 'utf-8') |
| | | return |
| | | } else { |
| | | return result.data |
| | | } |
| | |
| | | return result.data |
| | | } |
| | | |
| | | export interface CreatePlan { |
| | | name: string, |
| | | file_id: string, |
| | | dock_sn: string, |
| | | task_type: TaskType, // 任务类型 |
| | | wayline_type: WaylineType, // 航线类型 |
| | | execute_time?: number // 执行时间(毫秒) |
| | | rth_altitude: number // 相对机场返航高度 20 - 500 |
| | | out_of_control_action: OutOfControlAction // 失控动作 |
| | | } |
| | | |
| | | // Create Wayline Job |
| | | export const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> { |
| | | const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks` |
| | |
| | | return result.data |
| | | } |
| | | |
| | | export interface Task { |
| | | job_id: string, |
| | | job_name: string, |
| | | task_type: TaskType, // 任务类型 |
| | | file_id: string, // 航线文件id |
| | | file_name: string, // 航线名称 |
| | | wayline_type: WaylineType, // 航线类型 |
| | | dock_sn: string, |
| | | dock_name: string, |
| | | workspace_id: string, |
| | | username: string, |
| | | execute_time: string, |
| | | end_time: string, |
| | | status: TaskStatus, // 任务状态 |
| | | progress: number, // 执行进度 |
| | | code: number, // 错误码 |
| | | rth_altitude: number // 相对机场返航高度 20 - 500 |
| | | out_of_control_action: OutOfControlAction // 失控动作 |
| | | } |
| | | |
| | | // Get Wayline Jobs |
| | | export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IWorkspaceResponse<any>> { |
| | | export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IListWorkspaceResponse<Task>> { |
| | | const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}` |
| | | const result = await request.get(url) |
| | | return result.data |
| | | } |
| | | |
| | | // Execute Wayline Job |
| | | export const executeWaylineJobs = async function (workspaceId: string, plan_id: string): Promise<IWorkspaceResponse<any>> { |
| | | const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${plan_id}` |
| | | const result = await request.post(url) |
| | | export interface DeleteTaskParams { |
| | | job_id: string |
| | | } |
| | | |
| | | // 取消机场任务 |
| | | export async function deleteTask (workspaceId: string, params: DeleteTaskParams): Promise<IWorkspaceResponse<{}>> { |
| | | const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs` |
| | | const result = await request.delete(url, { |
| | | params: params |
| | | }) |
| | | return result.data |
| | | } |
| | | |
| | | // Upload Wayline file |
| | | export const importKmzFile = async function (workspaceId: string, file: {}): Promise<IWorkspaceResponse<any>> { |
| | | const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/file/upload` |
| | | const result = await request.post(url, file, { |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data', |
| | | } |
| | | }) |
| | | return result.data |
| | | } |
| | |
| | | <a-row> |
| | | <a-col span="12"> |
| | | <a-tooltip title="Network State"> |
| | | <span :style="deviceInfo.dock.network_state.quality === 2 ? 'color: #00ee8b' : |
| | | deviceInfo.dock.network_state.quality === 1 ? 'color: yellow' : 'color: red'"> |
| | | <span v-if="deviceInfo.dock.network_state.type === 1"><SignalFilled /></span> |
| | | <span :style="deviceInfo.dock.network_state?.quality === 2 ? 'color: #00ee8b' : |
| | | deviceInfo.dock.network_state?.quality === 1 ? 'color: yellow' : 'color: red'"> |
| | | <span v-if="deviceInfo.dock.network_state?.type === 1"><SignalFilled /></span> |
| | | <span v-else><GlobalOutlined /></span> |
| | | </span> |
| | | <span class="ml10" >{{ deviceInfo.dock.network_state.rate }} KB/S</span> |
| | | <span class="ml10" >{{ deviceInfo.dock.network_state?.rate }} KB/S</span> |
| | | </a-tooltip> |
| | | </a-col> |
| | | <a-col span="6"> |
| | |
| | | <a-col span="6"> |
| | | <a-tooltip> |
| | | <template #title> |
| | | <p>total: {{ deviceInfo.dock.storage.total }}</p> |
| | | <p>used: {{ deviceInfo.dock.storage.used }}</p> |
| | | <p>total: {{ deviceInfo.dock.storage?.total }}</p> |
| | | <p>used: {{ deviceInfo.dock.storage?.used }}</p> |
| | | </template> |
| | | <span><FolderOpenOutlined /></span> |
| | | <span class="ml10" v-if="deviceInfo.dock.storage.total > 0"> |
| | | <a-progress type="circle" :width="20" :percent="deviceInfo.dock.storage.used * 100/ deviceInfo.dock.storage.total" |
| | | :strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.storage.used * 100 / deviceInfo.dock.storage.total > 80 ? 'red' : '#00ee8b' "/> |
| | | <span class="ml10" v-if="deviceInfo.dock.storage?.total > 0"> |
| | | <a-progress type="circle" :width="20" :percent="deviceInfo.dock.storage?.used * 100/ deviceInfo.dock.storage?.total" |
| | | :strokeWidth="20" :showInfo="false" :strokeColor="deviceInfo.dock.storage?.used * 100 / deviceInfo.dock.storage?.total > 80 ? 'red' : '#00ee8b' "/> |
| | | </span> |
| | | </a-tooltip> |
| | | </a-col> |
| | |
| | | </a-row> |
| | | <a-row class="p5"> |
| | | <a-col span="24"> |
| | | <a-button type="primary" :disabled="controlPanelVisible" size="small" @click="dockDebugOnOff(osdVisible.gateway_sn, true)"> |
| | | 远程调试 |
| | | <a-button type="primary" :disabled="controlPanelVisible" size="small" @click="setControlPanelVisible(true)"> |
| | | 设备操作 |
| | | </a-button> |
| | | </a-col> |
| | | </a-row> |
| | |
| | | } |
| | | } |
| | | if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) { |
| | | deviceTsaUpdateHook.value.initMarker(EDeviceTypeName.Dock, EDeviceTypeName.Dock, data.currentSn, data.dockInfo[data.currentSn].longitude, data.dockInfo[data.currentSn].latitude) |
| | | if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') { |
| | | deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn] |
| | | deviceInfo.device = data.deviceInfo[deviceInfo.dock.sub_device?.device_sn] |
| | |
| | | EDockModeCode, |
| | | controlPanelVisible, |
| | | dockDebugOnOff, |
| | | setControlPanelVisible, |
| | | } |
| | | } |
| | | }) |
| | |
| | | } |
| | | .osd-panel { |
| | | position: absolute; |
| | | left: 350px; |
| | | left: 10px; |
| | | top: 10px; |
| | | width: 480px; |
| | | background: black; |
| | |
| | | <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 type="primary" class="download-btn" :disabled="!airportTableLogState.logList?.file_id || !airportTableLogState.logList?.object_key" size="small" @click="onDownloadLog(airportTableLogState.logList.file_id)"> |
| | | 下载机场日志 |
| | | </a-button> |
| | | <a-table :columns="airportLogColumns" |
| | |
| | | </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 type="primary" class="download-btn" :disabled="!droneTableLogState.logList?.file_id || !droneTableLogState.logList?.object_key" size="small" @click="onDownloadLog(droneTableLogState.logList.file_id)"> |
| | | 下载飞行器日志 |
| | | </a-button> |
| | | <a-table :columns="droneLogColumns" |
| New file |
| | |
| | | <template> |
| | | <div class="device-setting-wrapper"> |
| | | <div class="device-setting-header">设备属性设置</div> |
| | | <div class="device-setting-box"> |
| | | <!-- 飞行器夜航灯 --> |
| | | <div class="control-setting-item"> |
| | | <div class="control-setting-item-left"> |
| | | <div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}</div> |
| | | <div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value }}</div> |
| | | </div> |
| | | <div class="control-setting-item-right"> |
| | | <DeviceSettingPopover |
| | | :visible="deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].popConfirm.visible" |
| | | :loading="deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].popConfirm.loading" |
| | | @confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)" |
| | | @cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)" |
| | | > |
| | | <template #formContent> |
| | | <div class="form-content"> |
| | | <span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}:</span> |
| | | <a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.nightLightsState" /> |
| | | </div> |
| | | </template> |
| | | <a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)">Edit</a> |
| | | </DeviceSettingPopover> |
| | | </div> |
| | | </div> |
| | | <!-- 限高 --> |
| | | <div class="control-setting-item"> |
| | | <div class="control-setting-item-left"> |
| | | <div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}</div> |
| | | <div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value }}</div> |
| | | </div> |
| | | <div class="control-setting-item-right"> |
| | | <DeviceSettingPopover |
| | | :visible="deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].popConfirm.visible" |
| | | :loading="deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].popConfirm.loading" |
| | | @confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)" |
| | | @cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)" |
| | | > |
| | | <template #formContent> |
| | | <div class="form-content"> |
| | | <span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}:</span> |
| | | <a-input-number v-model:value="deviceSettingFormModel.heightLimit" :min="20" :max="1500" /> |
| | | m |
| | | </div> |
| | | </template> |
| | | <a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)">Edit</a> |
| | | </DeviceSettingPopover> |
| | | </div> |
| | | </div> |
| | | <!-- 限远 --> |
| | | <div class="control-setting-item"> |
| | | <div class="control-setting-item-left"> |
| | | <div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}</div> |
| | | <div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value }}</div> |
| | | </div> |
| | | <div class="control-setting-item-right"> |
| | | <DeviceSettingPopover |
| | | :visible="deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].popConfirm.visible" |
| | | :loading="deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].popConfirm.loading" |
| | | @confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)" |
| | | @cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)" |
| | | > |
| | | <template #formContent> |
| | | <div class="form-content"> |
| | | <span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}:</span> |
| | | <a-switch style="margin-right: 10px;" checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.distanceLimitStatus.state" /> |
| | | <a-input-number v-model:value="deviceSettingFormModel.distanceLimitStatus.distanceLimit" :min="15" :max="8000" /> |
| | | m |
| | | </div> |
| | | </template> |
| | | <a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)">Edit</a> |
| | | </DeviceSettingPopover> |
| | | </div> |
| | | </div> |
| | | <!-- 水平避障 --> |
| | | <div class="control-setting-item"> |
| | | <div class="control-setting-item-left"> |
| | | <div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}</div> |
| | | <div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value }}</div> |
| | | </div> |
| | | <div class="control-setting-item-right"> |
| | | <DeviceSettingPopover |
| | | :visible="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].popConfirm.visible" |
| | | :loading="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].popConfirm.loading" |
| | | @confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)" |
| | | @cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)" |
| | | > |
| | | <template #formContent> |
| | | <div class="form-content"> |
| | | <span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}:</span> |
| | | <a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.obstacleAvoidanceHorizon" /> |
| | | </div> |
| | | </template> |
| | | <a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)">Edit</a> |
| | | </DeviceSettingPopover> |
| | | </div> |
| | | </div> |
| | | <!-- 上视避障 --> |
| | | <div class="control-setting-item"> |
| | | <div class="control-setting-item-left"> |
| | | <div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}</div> |
| | | <div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value }}</div> |
| | | </div> |
| | | <div class="control-setting-item-right"> |
| | | <DeviceSettingPopover |
| | | :visible="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].popConfirm.visible" |
| | | :loading="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].popConfirm.loading" |
| | | @confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)" |
| | | @cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)" |
| | | > |
| | | <template #formContent> |
| | | <div class="form-content"> |
| | | <span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}:</span> |
| | | <a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.obstacleAvoidanceUpside" /> |
| | | </div> |
| | | </template> |
| | | <a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)">Edit</a> |
| | | </DeviceSettingPopover> |
| | | </div> |
| | | </div> |
| | | <!-- 下视避障 --> |
| | | <div class="control-setting-item"> |
| | | <div class="control-setting-item-left"> |
| | | <div class="item-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}</div> |
| | | <div class="item-status">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value }}</div> |
| | | </div> |
| | | <div class="control-setting-item-right"> |
| | | <DeviceSettingPopover |
| | | :visible="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].popConfirm.visible" |
| | | :loading="deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].popConfirm.loading" |
| | | @confirm="onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)" |
| | | @cancel="onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)" |
| | | > |
| | | <template #formContent> |
| | | <div class="form-content"> |
| | | <span class="form-label">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}:</span> |
| | | <a-switch checked-children="开" un-checked-children="关" v-model:checked="deviceSettingFormModel.obstacleAvoidanceDownside" /> |
| | | </div> |
| | | </template> |
| | | <a @click="onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)">Edit</a> |
| | | </DeviceSettingPopover> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { defineProps, ref, watch } from 'vue' |
| | | import { DeviceInfoType } from '/@/types/device' |
| | | import { useMyStore } from '/@/store' |
| | | import { cloneDeep } from 'lodash' |
| | | import { initDeviceSetting, initDeviceSettingFormModel, DeviceSettingKeyEnum } from '/@/types/device-setting' |
| | | import { updateDeviceSettingInfoByOsd, updateDeviceSettingFormModelByOsd } from '/@/utils/device-setting' |
| | | import { useDeviceSetting } from './useDeviceSetting' |
| | | import DeviceSettingPopover from './DeviceSettingPopover.vue' |
| | | |
| | | const props = defineProps<{ |
| | | sn: string, |
| | | deviceInfo: DeviceInfoType, |
| | | }>() |
| | | |
| | | const store = useMyStore() |
| | | const deviceSetting = ref(cloneDeep(initDeviceSetting)) |
| | | const deviceSettingFormModelFromOsd = ref(cloneDeep(initDeviceSettingFormModel)) |
| | | const deviceSettingFormModel = ref(cloneDeep(initDeviceSettingFormModel)) // 真实使用的formModel |
| | | |
| | | // 根据设备osd信息更新信息 |
| | | watch(() => props.deviceInfo, (value) => { |
| | | updateDeviceSettingInfoByOsd(deviceSetting.value, value) |
| | | updateDeviceSettingFormModelByOsd(deviceSettingFormModelFromOsd.value, value) |
| | | // console.log('deviceInfo', value) |
| | | }, { |
| | | immediate: true, |
| | | deep: true |
| | | }) |
| | | |
| | | function onShowPopConfirm (settingKey: DeviceSettingKeyEnum) { |
| | | deviceSetting.value[settingKey].popConfirm.visible = true |
| | | deviceSettingFormModel.value = cloneDeep(deviceSettingFormModelFromOsd.value) |
| | | } |
| | | |
| | | function onCancel (settingKey: DeviceSettingKeyEnum) { |
| | | deviceSetting.value[settingKey].popConfirm.visible = false |
| | | } |
| | | |
| | | async function onConfirm (settingKey: DeviceSettingKeyEnum) { |
| | | deviceSetting.value[settingKey].popConfirm.loading = true |
| | | const body = genDevicePropsBySettingKey(settingKey, deviceSettingFormModel.value) |
| | | await setDeviceProps(props.sn, body) |
| | | deviceSetting.value[settingKey].popConfirm.loading = false |
| | | deviceSetting.value[settingKey].popConfirm.visible = false |
| | | } |
| | | |
| | | // 更新设备属性 |
| | | const { |
| | | genDevicePropsBySettingKey, |
| | | setDeviceProps, |
| | | } = useDeviceSetting() |
| | | |
| | | </script> |
| | | |
| | | <style lang='scss' scoped> |
| | | .device-setting-wrapper{ |
| | | border-bottom: 1px solid #515151; |
| | | |
| | | .device-setting-header{ |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | padding: 10px 10px 0px; |
| | | } |
| | | |
| | | .device-setting-box{ |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: space-between; |
| | | padding: 4px 10px; |
| | | |
| | | .control-setting-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-setting-item-left{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .item-label{ |
| | | font-weight: 700; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <a-popover :visible="state.sVisible" |
| | | trigger="click" |
| | | v-bind="$attrs" |
| | | :overlay-class-name="overlayClassName" |
| | | placement="bottom" |
| | | @visibleChange=";" |
| | | v-on="$attrs"> |
| | | <template #content> |
| | | <div class="title-content"> |
| | | </div> |
| | | <slot name="formContent" /> |
| | | <div class="uranus-popconfirm-btns"> |
| | | <a-button size="sm" |
| | | @click="onCancel"> |
| | | {{ cancelText || '取消'}} |
| | | </a-button> |
| | | <a-button size="sm" |
| | | :loading="loading" |
| | | type="primary" |
| | | class="confirm-btn" |
| | | @click="onConfirm"> |
| | | {{ okText || '确定' }} |
| | | </a-button> |
| | | </div> |
| | | </template> |
| | | <template v-if="$slots.default"> |
| | | <slot></slot> |
| | | </template> |
| | | </a-popover> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { defineProps, defineEmits, reactive, watch, computed } from 'vue' |
| | | |
| | | const props = defineProps<{ |
| | | visible?: boolean, |
| | | loading?: Boolean, |
| | | disabled?: Boolean, |
| | | title?: String, |
| | | okText?: String, |
| | | cancelText?: String, |
| | | width?: Number, |
| | | }>() |
| | | |
| | | const emit = defineEmits(['cancel', 'confirm']) |
| | | |
| | | const state = reactive({ |
| | | sVisible: false, |
| | | loading: false, |
| | | }) |
| | | |
| | | watch(() => props.visible, (val) => { |
| | | state.sVisible = val || false |
| | | }) |
| | | |
| | | const loading = computed(() => { |
| | | return props.loading |
| | | }) |
| | | const okLabel = computed(() => { |
| | | return props.loading ? '' : '确定' |
| | | }) |
| | | |
| | | const overlayClassName = computed(() => { |
| | | const classList = ['device-setting-popconfirm'] |
| | | return classList.join(' ') |
| | | }) |
| | | |
| | | function onConfirm (e: Event) { |
| | | if (props.disabled) { |
| | | return |
| | | } |
| | | emit('confirm', e) |
| | | } |
| | | |
| | | function onCancel (e: Event) { |
| | | state.sVisible = false |
| | | emit('cancel', e) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .device-setting-popconfirm { |
| | | min-width: 300px; |
| | | |
| | | .uranus-popconfirm-btns{ |
| | | display: flex; |
| | | padding: 10px 0px; |
| | | justify-content: flex-end; |
| | | |
| | | .confirm-btn{ |
| | | margin-left: 10px; |
| | | } |
| | | } |
| | | |
| | | .form-content{ |
| | | display: inline-flex; |
| | | align-items: center; |
| | | |
| | | .form-label{ |
| | | padding-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <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>设备操作 {{ props.sn}}</span> |
| | | <span @click="closeControlPanel"> |
| | | <CloseOutlined /> |
| | | </span> |
| | | </div> |
| | | <!-- setting --> |
| | | <DeviceSettingBox :sn="props.sn" :deviceInfo="props.deviceInfo"></DeviceSettingBox> |
| | | <!-- 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 class="control-cmd-header"> |
| | | 远程调试 |
| | | <a-switch class="debug-btn" checked-children="开" un-checked-children="关" v-model:checked="debugStatus" @change="onDeviceStatusChange"/> |
| | | </div> |
| | | <div class="control-cmd-box"> |
| | | <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 :disabled="!debugStatus || cmdItem.disabled" :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)"> |
| | | {{ cmdItem.operateText }} |
| | | </a-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd' |
| | | import { useMyStore } from '/@/store' |
| | | import { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd' |
| | | import DeviceSettingBox from './DeviceSettingBox.vue' |
| | | |
| | | const props = defineProps<{ |
| | | sn: string, |
| | |
| | | } |
| | | |
| | | // dock 控制指令 |
| | | const debugStatus = ref(false) |
| | | |
| | | async function onDeviceStatusChange (status: boolean) { |
| | | let result = false |
| | | if (status) { |
| | | result = await dockDebugOnOff(props.sn, true) |
| | | } else { |
| | | result = await dockDebugOnOff(props.sn, false) |
| | | } |
| | | if (!result) { |
| | | if (status) { |
| | | debugStatus.value = false |
| | | } else { |
| | | debugStatus.value = true |
| | | } |
| | | } |
| | | } |
| | | |
| | | const { |
| | | sendDockControlCmd, |
| | | dockDebugOnOff |
| | | } = useDockControl() |
| | | |
| | | async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) { |
| | | const success = await sendDockControlCmd({ |
| | | sn: props.sn, |
| | | cmd: cmdItem.cmdKey |
| | | cmd: cmdItem.cmdKey, |
| | | action: cmdItem.action |
| | | }, true) |
| | | if (success) { |
| | | // updateDeviceSingleCmdInfo(cmdList.value[index]) |
| | |
| | | } |
| | | |
| | | .control-cmd-wrapper{ |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: space-between; |
| | | padding: 4px 10px; |
| | | .control-cmd-item{ |
| | | width: 220px; |
| | | height: 58px; |
| | | .control-cmd-header{ |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | padding: 10px 10px 0px; |
| | | |
| | | .debug-btn{ |
| | | margin-left: 10px; |
| | | border:1px solid #585858; |
| | | } |
| | | } |
| | | |
| | | .control-cmd-box{ |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | justify-content: space-between; |
| | | border: 1px solid #666; |
| | | margin: 4px 0; |
| | | padding: 0 8px; |
| | | |
| | | .control-cmd-item-left{ |
| | | padding: 4px 10px; |
| | | .control-cmd-item{ |
| | | width: 220px; |
| | | height: 58px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | border: 1px solid #666; |
| | | margin: 4px 0; |
| | | padding: 0 8px; |
| | | |
| | | .item-label{ |
| | | font-weight: 700; |
| | | .control-cmd-item-left{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .item-label{ |
| | | font-weight: 700; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import { putDeviceProps, PutDevicePropsBody } from '/@/api/device-setting' |
| | | import { DeviceSettingKeyEnum, DeviceSettingFormModel, ObstacleAvoidanceStatusEnum, NightLightsStateEnum, DistanceLimitStatusEnum } from '/@/types/device-setting' |
| | | |
| | | export function useDeviceSetting () { |
| | | // 生成参数 |
| | | function genDevicePropsBySettingKey (key: DeviceSettingKeyEnum, fromModel: DeviceSettingFormModel) { |
| | | const body = {} as PutDevicePropsBody |
| | | if (key === DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET) { |
| | | body.night_lights_state = fromModel.nightLightsState ? NightLightsStateEnum.OPEN : NightLightsStateEnum.CLOSE |
| | | } else if (key === DeviceSettingKeyEnum.HEIGHT_LIMIT_SET) { |
| | | body.height_limit = fromModel.heightLimit |
| | | } else if (key === DeviceSettingKeyEnum.DISTANCE_LIMIT_SET) { |
| | | body.distance_limit_status = {} |
| | | if (fromModel.distanceLimitStatus.state) { |
| | | body.distance_limit_status.state = DistanceLimitStatusEnum.SET |
| | | body.distance_limit_status.distance_limit = fromModel.distanceLimitStatus.distanceLimit |
| | | } else { |
| | | body.distance_limit_status.state = DistanceLimitStatusEnum.UNSET |
| | | } |
| | | } else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON) { |
| | | body.obstacle_avoidance = { |
| | | horizon: fromModel.obstacleAvoidanceHorizon ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE |
| | | } |
| | | } else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE) { |
| | | body.obstacle_avoidance = { |
| | | upside: fromModel.obstacleAvoidanceUpside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE |
| | | } |
| | | } else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE) { |
| | | body.obstacle_avoidance = { |
| | | downside: fromModel.obstacleAvoidanceDownside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE |
| | | } |
| | | } |
| | | return body |
| | | } |
| | | |
| | | // 设置设备属性 |
| | | async function setDeviceProps (sn: string, body: PutDevicePropsBody) { |
| | | try { |
| | | const { code, message: msg } = await putDeviceProps(sn, body) |
| | | if (code === 0) { |
| | | // message.success('指令发送成功') |
| | | return true |
| | | } |
| | | throw (msg) |
| | | } catch (e) { |
| | | message.error('设备属性设置失败') |
| | | return false |
| | | } |
| | | } |
| | | |
| | | return { |
| | | genDevicePropsBySettingKey, |
| | | setDeviceProps |
| | | } |
| | | } |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import { ref } from 'vue' |
| | | import { postSendCmd } from '/@/api/device-cmd' |
| | | import { DeviceCmd } from '/@/types/device-cmd' |
| | | import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd' |
| | | |
| | | export function useDockControl () { |
| | | const controlPanelVisible = ref(false) |
| | |
| | | |
| | | // 远程调试开关 |
| | | async function dockDebugOnOff (sn: string, on: boolean) { |
| | | const success = await sendDockControlCmd({ |
| | | const result = await sendDockControlCmd({ |
| | | sn: sn, |
| | | cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose |
| | | }, false) |
| | | if (success) { |
| | | if (result) { |
| | | setControlPanelVisible(on) |
| | | } |
| | | return result |
| | | } |
| | | |
| | | // 发送指令 |
| | | async function sendDockControlCmd (params: { |
| | | sn: string, |
| | | cmd: DeviceCmd |
| | | action?: DeviceCmdItemAction |
| | | }, tip = true) { |
| | | try { |
| | | const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }) |
| | | let body = undefined as any |
| | | if (params.action !== undefined) { |
| | | body = { |
| | | action: params.action |
| | | } |
| | | } |
| | | const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }, body) |
| | | if (code === 0) { |
| | | tip && message.success('指令发送成功') |
| | | return true |
| | |
| | | <div id="player" style="width: 720px; height: 420px; border: 1px solid"></div> |
| | | <p class="fz24">Live streaming source selection</p> |
| | | <div class="flex-row flex-justify-center flex-align-center mt10"> |
| | | <template v-if="livePara.liveState && dronePara.isDockLive"> |
| | | <span class="mr10">Lens:</span> |
| | | <a-radio-group v-model:value="dronePara.lensSelected" button-style="solid"> |
| | | <a-radio-button v-for="lens in dronePara.lensList" :key="lens" :value="lens">{{lens}}</a-radio-button> |
| | | </a-radio-group> |
| | | </template> |
| | | <template v-else> |
| | | <a-select |
| | | style="width:150px" |
| | | placeholder="Select Drone" |
| | | @select="onDroneSelect" |
| | | v-model:value="dronePara.droneSelected" |
| | | > |
| | | <a-select-option |
| | | v-for="item in dronePara.droneList" |
| | | :key="item.value" |
| | | :value="item.value" |
| | | @click="onDroneSelect(item)" |
| | | >{{ item.label }}</a-select-option |
| | | > |
| | | </a-select> |
| | |
| | | class="ml10" |
| | | style="width:150px" |
| | | placeholder="Select Camera" |
| | | @select="onCameraSelect" |
| | | v-model:value="dronePara.cameraSelected" |
| | | > |
| | | <a-select-option |
| | | v-for="item in dronePara.cameraList" |
| | | :key="item.value" |
| | | :value="item.value" |
| | | @click="onCameraSelect(item)" |
| | | >{{ item.label }}</a-select-option |
| | | > |
| | | </a-select> |
| | |
| | | @select="onVideoSelect" |
| | | > |
| | | <a-select-option |
| | | class="ml10" |
| | | v-for="item in dronePara.videoList" |
| | | :key="item.value" |
| | | :value="item.value" |
| | | >{{ item.label }}</a-select-option |
| | | > |
| | | </a-select> --> |
| | | </template> |
| | | <a-select |
| | | class="ml10" |
| | | style="width:150px" |
| | |
| | | Note: Obtain The Following Parameters From https://console.agora.io |
| | | </p> |
| | | <div class="flex-row flex-justify-center flex-align-center"> |
| | | <span class="mr10">AppId:</span> |
| | | <a-input v-model:value="agoraPara.appid" placeholder="APP ID"></a-input> |
| | | <span class="ml10">Token:</span> |
| | | <a-input |
| | | class="ml10" |
| | | v-model:value="agoraPara.token" |
| | | placeholder="Token" |
| | | @change="encodeToken" |
| | | ></a-input> |
| | | <span class="ml10">Channel:</span> |
| | | <a-input |
| | | class="ml10" |
| | | v-model:value="agoraPara.channel" |
| | |
| | | ></a-input> |
| | | </div> |
| | | <div class="mt20 flex-row flex-justify-center flex-align-center"> |
| | | <a-button type="primary" large @click="onStart">Play</a-button> |
| | | <a-button v-if="livePara.liveState && dronePara.isDockLive" type="primary" large @click="onSwitch">Switch Lens</a-button> |
| | | <a-button v-else type="primary" large @click="onStart">Play</a-button> |
| | | <a-button class="ml20" type="primary" large @click="onStop" |
| | | >Stop</a-button |
| | | > |
| | | <a-button class="ml20" type="primary" large @click="onUpdateQuality" |
| | | >Update Clarity</a-button |
| | | > |
| | | <a-button class="ml20" type="primary" large @click="onRefresh" |
| | | <a-button v-if="!livePara.liveState || !dronePara.isDockLive" class="ml20" type="primary" large @click="onRefresh" |
| | | >Refresh Live Capacity</a-button |
| | | > |
| | | </div> |
| | |
| | | import AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng' |
| | | import { message } from 'ant-design-vue' |
| | | import { onMounted, reactive } from 'vue' |
| | | import { uuidv4 } from '../utils/uuid' |
| | | import { CURRENT_CONFIG as config } from '/@/api/http/config' |
| | | import { getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage' |
| | | import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage' |
| | | import { getRoot } from '/@/root' |
| | | |
| | | const root = getRoot() |
| | |
| | | } |
| | | ] |
| | | |
| | | interface SelectOption { |
| | | value: any, |
| | | label: string, |
| | | more?: any |
| | | } |
| | | |
| | | let agoraClient = {} as IAgoraRTCClient |
| | | const agoraPara = reactive({ |
| | | appid: config.agoraAPPID, |
| | |
| | | }) |
| | | const dronePara = reactive({ |
| | | livestreamSource: [], |
| | | droneList: [] as any[], |
| | | cameraList: [] as any[], |
| | | videoList: [] as any[], |
| | | droneSelected: '', |
| | | cameraSelected: '', |
| | | videoSelected: '', |
| | | claritySelected: 0 |
| | | droneList: [] as SelectOption[], |
| | | cameraList: [] as SelectOption[], |
| | | videoList: [] as SelectOption[], |
| | | droneSelected: undefined as string | undefined, |
| | | cameraSelected: undefined as string | undefined, |
| | | videoSelected: undefined as string | undefined, |
| | | claritySelected: 0, |
| | | lensList: [] as string[], |
| | | lensSelected: undefined as string | undefined, |
| | | isDockLive: false |
| | | }) |
| | | const livePara = reactive({ |
| | | url: '', |
| | |
| | | videoId: '', |
| | | liveState: false |
| | | }) |
| | | const nonSwitchable = 'normal' |
| | | |
| | | const onRefresh = async () => { |
| | | dronePara.droneList = [] |
| | | dronePara.cameraList = [] |
| | | dronePara.videoList = [] |
| | | dronePara.droneSelected = '' |
| | | dronePara.cameraSelected = '' |
| | | dronePara.videoSelected = '' |
| | | dronePara.droneSelected = undefined |
| | | dronePara.cameraSelected = undefined |
| | | dronePara.videoSelected = undefined |
| | | await getLiveCapacity({}) |
| | | .then(res => { |
| | | if (res.code === 0) { |
| | |
| | | |
| | | if (dronePara.livestreamSource) { |
| | | dronePara.livestreamSource.forEach((ele: any) => { |
| | | dronePara.droneList.push({ label: ele.name + '-' + ele.sn, value: ele.sn }) |
| | | dronePara.droneList.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list }) |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | message.error(error) |
| | | console.error(error) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | onRefresh() |
| | | agoraPara.token = encodeURIComponent(agoraPara.token) |
| | | agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' }) |
| | | // Subscribe when a remote user publishes a stream |
| | | agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => { |
| | |
| | | const remoteVideoTrack = user.videoTrack! |
| | | // Dynamically create a container in the form of a DIV element for playing the remote video track. |
| | | const remotePlayerContainer: any = document.getElementById('player') |
| | | // remotePlayerContainer.id = agoraPara.uid |
| | | remotePlayerContainer.id = user.uid.toString() |
| | | remoteVideoTrack.play(remotePlayerContainer) |
| | | } |
| | | }) |
| | | agoraClient.on('user-unpublished', async (user: any) => { |
| | | console.log('unpublish live:', user) |
| | | message.info('unpublish live') |
| | | }) |
| | | agoraClient.on('exception', async (e: any) => { |
| | | console.log(e) |
| | | message.error(e.msg) |
| | | }) |
| | | }) |
| | | |
| | |
| | | const handleJoinChannel = (uid: any) => { |
| | | agoraPara.uid = uid |
| | | } |
| | | |
| | | const encodeToken = (e: any) => { |
| | | agoraPara.token = encodeURIComponent(agoraPara.token) |
| | | } |
| | | const onStart = async () => { |
| | | const that = this |
| | | console.log( |
| | |
| | | if ( |
| | | dronePara.droneSelected == null || |
| | | dronePara.cameraSelected == null || |
| | | dronePara.videoSelected == null || |
| | | dronePara.claritySelected == null |
| | | ) { |
| | | message.warn('waring: not select live para!!!') |
| | | return |
| | | } |
| | | agoraClient.setClientRole('audience', { level: 1 }) |
| | | agoraClient.setClientRole('audience', { level: 2 }) |
| | | if (agoraClient.connectionState === 'DISCONNECTED') { |
| | | agoraClient |
| | | .join(agoraPara.appid, agoraPara.channel, agoraPara.token) |
| | |
| | | livePara.videoId = |
| | | dronePara.droneSelected + |
| | | '/' + |
| | | dronePara.cameraSelected + |
| | | '/' + |
| | | dronePara.videoSelected |
| | | dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0') |
| | | console.log(agoraPara) |
| | | agoraPara.token = encodeURIComponent(agoraPara.token) |
| | | |
| | | livePara.url = |
| | | 'channel=' + |
| | |
| | | video_quality: dronePara.claritySelected |
| | | }) |
| | | .then(res => { |
| | | if (res.code !== 0) { |
| | | return |
| | | } |
| | | livePara.liveState = true |
| | | }) |
| | | .catch(err => { |
| | |
| | | livePara.videoId = |
| | | dronePara.droneSelected + |
| | | '/' + |
| | | dronePara.cameraSelected + |
| | | '/' + |
| | | dronePara.videoSelected |
| | | dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0') |
| | | |
| | | stopLivestream({ |
| | | video_id: livePara.videoId |
| | | }).then(res => { |
| | |
| | | message.success(res.message) |
| | | } |
| | | livePara.liveState = false |
| | | dronePara.lensSelected = '' |
| | | console.log('stop play livestream') |
| | | }) |
| | | } |
| | | const onDroneSelect = (val: any) => { |
| | | dronePara.droneSelected = val |
| | | if (dronePara.droneSelected) { |
| | | const droneTemp = dronePara.livestreamSource |
| | | dronePara.cameraList = [] |
| | | const onDroneSelect = (val: SelectOption) => { |
| | | dronePara.cameraList = [] |
| | | dronePara.videoList = [] |
| | | dronePara.lensList = [] |
| | | |
| | | droneTemp.forEach((ele: any) => { |
| | | const drone = ele |
| | | if (drone.cameras_list && drone.sn === dronePara.droneSelected) { |
| | | const cameraListTemp = drone.cameras_list |
| | | cameraListTemp.forEach((ele: any) => { |
| | | dronePara.cameraList.push({ label: ele.name, value: ele.index }) |
| | | }) |
| | | } |
| | | }) |
| | | dronePara.cameraSelected = undefined |
| | | dronePara.videoSelected = undefined |
| | | dronePara.lensSelected = undefined |
| | | dronePara.droneSelected = val.value |
| | | if (!val.more) { |
| | | return |
| | | } |
| | | val.more.forEach((ele: any) => { |
| | | dronePara.cameraList.push({ label: ele.name, value: ele.index, more: ele.videos_list }) |
| | | }) |
| | | } |
| | | const onCameraSelect = (val: any) => { |
| | | dronePara.cameraSelected = val |
| | | const onCameraSelect = (val: SelectOption) => { |
| | | dronePara.cameraSelected = val.value |
| | | dronePara.videoSelected = undefined |
| | | dronePara.lensSelected = undefined |
| | | dronePara.videoList = [] |
| | | dronePara.lensList = [] |
| | | if (!val.more) { |
| | | return |
| | | } |
| | | |
| | | if (dronePara.cameraSelected) { |
| | | const droneTemp = dronePara.livestreamSource |
| | | droneTemp.forEach((ele: any) => { |
| | | const drone = ele |
| | | if (drone.sn === dronePara.droneSelected) { |
| | | const cameraListTemp = drone.cameras_list |
| | | cameraListTemp.forEach((ele: any) => { |
| | | const camera = ele |
| | | if (camera.index === dronePara.cameraSelected) { |
| | | const videoListTemp = camera.videos_list |
| | | dronePara.videoList = [] |
| | | videoListTemp.forEach((ele: any) => { |
| | | dronePara.videoList.push({ label: ele.type, value: ele.index }) |
| | | }) |
| | | dronePara.videoSelected = dronePara.videoList[0]?.value |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | val.more.forEach((ele: any) => { |
| | | dronePara.videoList.push({ label: ele.type, value: ele.index, more: ele.switch_video_types }) |
| | | }) |
| | | if (dronePara.videoList.length === 0) { |
| | | return |
| | | } |
| | | const firstVideo: SelectOption = dronePara.videoList[0] |
| | | dronePara.videoSelected = firstVideo.value |
| | | dronePara.lensList = firstVideo.more |
| | | dronePara.lensSelected = firstVideo.label |
| | | dronePara.isDockLive = dronePara.lensList.length > 0 |
| | | } |
| | | const onVideoSelect = (val: any) => { |
| | | dronePara.videoSelected = val |
| | | const onVideoSelect = (val: SelectOption) => { |
| | | dronePara.videoSelected = val.value |
| | | dronePara.lensList = val.more |
| | | dronePara.lensSelected = val.label |
| | | } |
| | | const onClaritySelect = (val: any) => { |
| | | dronePara.claritySelected = val |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const onSwitch = () => { |
| | | if (dronePara.lensSelected === undefined || dronePara.lensSelected === nonSwitchable) { |
| | | message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8) |
| | | return |
| | | } |
| | | changeLivestreamLens({ |
| | | video_id: livePara.videoId, |
| | | video_type: dronePara.lensSelected |
| | | }).then(res => { |
| | | if (res.code === 0) { |
| | | message.success('Switching live camera successfully.') |
| | | } |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | class="mt20" |
| | | ></video> |
| | | <p class="fz24">Live streaming source selection</p> |
| | | |
| | | <div class="flex-row flex-justify-center flex-align-center mt10"> |
| | | <template v-if="liveState && isDockLive"> |
| | | <span class="mr10">Lens:</span> |
| | | <a-radio-group v-model:value="lensSelected" button-style="solid"> |
| | | <a-radio-button v-for="lens in lensList" :key="lens" :value="lens">{{lens}}</a-radio-button> |
| | | </a-radio-group> |
| | | </template> |
| | | <template v-else> |
| | | <a-select |
| | | style="width: 150px" |
| | | placeholder="Select Live Type" |
| | | @select="onLiveTypeSelect" |
| | | v-model:value="livetypeSelected" |
| | | > |
| | | <a-select-option |
| | | v-for="item in liveTypeList" |
| | |
| | | class="ml10" |
| | | style="width:150px" |
| | | placeholder="Select Drone" |
| | | @select="onDroneSelect" |
| | | v-model:value="droneSelected" |
| | | > |
| | | <a-select-option |
| | | v-for="item in droneList" |
| | | :key="item.value" |
| | | :value="item.value" |
| | | @click="onDroneSelect(item)" |
| | | >{{ item.label }}</a-select-option |
| | | > |
| | | </a-select> |
| | |
| | | class="ml10" |
| | | style="width:150px" |
| | | placeholder="Select Camera" |
| | | @select="onCameraSelect" |
| | | v-model:value="cameraSelected" |
| | | > |
| | | <a-select-option |
| | | v-for="item in cameraList" |
| | | :key="item.value" |
| | | :value="item.value" |
| | | @click="onCameraSelect(item)" |
| | | >{{ item.label }}</a-select-option |
| | | > |
| | | </a-select> |
| | |
| | | class="ml10" |
| | | style="width:150px" |
| | | placeholder="Select Lens" |
| | | @select="onVideoSelect" |
| | | v-model:value="videoSelected" |
| | | > |
| | | <a-select-option |
| | | class="ml10" |
| | | v-for="item in videoList" |
| | | :key="item.value" |
| | | :value="item.value" |
| | | @click="onVideoSelect(item)" |
| | | >{{ item.label }}</a-select-option |
| | | > |
| | | </a-select> --> |
| | | </template> |
| | | <a-select |
| | | class="ml10" |
| | | style="width:150px" |
| | | placeholder="Select Clarity" |
| | | @select="onClaritySelect" |
| | | v-model:value="claritySelected" |
| | | > |
| | | <a-select-option |
| | | v-for="item in clarityList" |
| | |
| | | </p> |
| | | </div> |
| | | <div class="mt10 flex-row flex-justify-center flex-align-center"> |
| | | <a-button type="primary" large @click="onStart">Play</a-button> |
| | | <a-button v-if="liveState && isDockLive" type="primary" large @click="onSwitch">Switch Lens</a-button> |
| | | <a-button v-else type="primary" large @click="onStart">Play</a-button> |
| | | <a-button class="ml20" type="primary" large @click="onStop" |
| | | >Stop</a-button |
| | | > |
| | | <a-button class="ml20" type="primary" large @click="onUpdateQuality" |
| | | >Update Clarity</a-button |
| | | > |
| | | <a-button class="ml20" type="primary" large @click="onRefresh" |
| | | <a-button v-if="!liveState || !isDockLive" class="ml20" type="primary" large @click="onRefresh" |
| | | >Refresh Live Capacity</a-button |
| | | > |
| | | </div> |
| | |
| | | import { message } from 'ant-design-vue' |
| | | import { onMounted, reactive, ref } from 'vue' |
| | | import { CURRENT_CONFIG as config } from '/@/api/http/config' |
| | | import { getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage' |
| | | import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage' |
| | | import { getRoot } from '/@/root' |
| | | import jswebrtc from '/@/vendors/jswebrtc.min.js' |
| | | const root = getRoot() |
| | | |
| | | const liveTypeList = [ |
| | | interface SelectOption { |
| | | value: any, |
| | | label: string, |
| | | more?: any |
| | | } |
| | | |
| | | const liveTypeList: SelectOption[] = [ |
| | | { |
| | | value: 1, |
| | | label: 'RTMP' |
| | |
| | | label: 'GB28181' |
| | | } |
| | | ] |
| | | const clarityList = [ |
| | | const clarityList: SelectOption[] = [ |
| | | { |
| | | value: 0, |
| | | label: 'Adaptive' |
| | |
| | | const videoList = ref() |
| | | const droneSelected = ref() |
| | | const cameraSelected = ref() |
| | | const videoSeleted = ref() |
| | | const claritySeleted = ref() |
| | | const videoSelected = ref() |
| | | const claritySelected = ref() |
| | | const videoId = ref() |
| | | const liveState = ref<boolean>(false) |
| | | const livetypeSelected = ref() |
| | | const rtspData = ref() |
| | | const lensList = ref<string[]>([]) |
| | | const lensSelected = ref<String>() |
| | | const isDockLive = ref(false) |
| | | const nonSwitchable = 'normal' |
| | | |
| | | const onRefresh = async () => { |
| | | droneList.value = [] |
| | |
| | | videoList.value = [] |
| | | droneSelected.value = null |
| | | cameraSelected.value = null |
| | | videoSeleted.value = null |
| | | videoSelected.value = null |
| | | await getLiveCapacity({}) |
| | | .then(res => { |
| | | console.log(res) |
| | |
| | | console.log('live_capacity:', resData) |
| | | livestreamSource.value = resData |
| | | |
| | | const temp: Array<{}> = [] |
| | | const temp: Array<SelectOption> = [] |
| | | if (livestreamSource.value) { |
| | | livestreamSource.value.forEach((ele: any) => { |
| | | temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn }) |
| | | temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list }) |
| | | }) |
| | | droneList.value = temp |
| | | } |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | message.error(error) |
| | | console.error(error) |
| | | }) |
| | | } |
| | |
| | | livetypeSelected.value, |
| | | droneSelected.value, |
| | | cameraSelected.value, |
| | | videoSeleted.value, |
| | | claritySeleted.value |
| | | videoSelected.value, |
| | | claritySelected.value |
| | | ) |
| | | const timestamp = new Date().getTime().toString() |
| | | if ( |
| | | livetypeSelected.value == null || |
| | | droneSelected.value == null || |
| | | cameraSelected.value == null || |
| | | videoSeleted.value == null || |
| | | claritySeleted.value == null |
| | | claritySelected.value == null |
| | | ) { |
| | | message.warn('waring: not select live para!!!') |
| | | return |
| | | } |
| | | videoId.value = |
| | | droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value |
| | | droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0') |
| | | |
| | | let liveURL = '' |
| | | switch (livetypeSelected.value) { |
| | | case 1: { |
| | |
| | | url: liveURL, |
| | | video_id: videoId.value, |
| | | url_type: livetypeSelected.value, |
| | | video_quality: claritySeleted.value |
| | | video_quality: claritySelected.value |
| | | }) |
| | | .then(res => { |
| | | if (res.code !== 0) { |
| | | return |
| | | } |
| | | if (livetypeSelected.value === 3) { |
| | | const url = res.data.url |
| | | const videoElement = videowebrtc.value |
| | |
| | | console.log('start play livestream') |
| | | } |
| | | }) |
| | | liveState.value = true |
| | | } |
| | | }) |
| | | } else if (livetypeSelected.value === 2) { |
| | |
| | | autoplay: true, |
| | | onPlay: (obj: any) => { |
| | | console.log('start play livestream') |
| | | liveState.value = true |
| | | } |
| | | }) |
| | | } |
| | | liveState.value = true |
| | | }) |
| | | .catch(err => { |
| | | console.error(err) |
| | |
| | | } |
| | | const onStop = () => { |
| | | videoId.value = |
| | | droneSelected.value + '/' + cameraSelected.value + '/' + videoSeleted.value |
| | | droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0') |
| | | |
| | | stopLivestream({ |
| | | video_id: videoId.value |
| | | }).then(res => { |
| | | if (res.code === 0) { |
| | | message.info(res.message) |
| | | message.success(res.message) |
| | | liveState.value = false |
| | | lensSelected.value = undefined |
| | | console.log('stop play livestream') |
| | | } |
| | | }) |
| | |
| | | } |
| | | setLivestreamQuality({ |
| | | video_id: videoId.value, |
| | | video_quality: claritySeleted.value |
| | | video_quality: claritySelected.value |
| | | }).then(res => { |
| | | if (res.code === 0) { |
| | | message.success('Set the clarity to ' + clarityList[claritySeleted.value].label) |
| | | message.success('Set the clarity to ' + clarityList[claritySelected.value].label) |
| | | } |
| | | }) |
| | | } |
| | |
| | | const onLiveTypeSelect = (val: any) => { |
| | | livetypeSelected.value = val |
| | | } |
| | | const onDroneSelect = (val: any) => { |
| | | droneSelected.value = val |
| | | const temp: Array<{}> = [] |
| | | const onDroneSelect = (val: SelectOption) => { |
| | | droneSelected.value = val.value |
| | | const temp: Array<SelectOption> = [] |
| | | cameraList.value = [] |
| | | if (droneSelected.value) { |
| | | const droneTemp = livestreamSource.value |
| | | droneTemp.forEach((ele: any) => { |
| | | const drone = ele |
| | | if (drone.cameras_list && drone.sn === droneSelected.value) { |
| | | const cameraListTemp = drone.cameras_list |
| | | console.info(ele) |
| | | cameraListTemp.forEach((ele: any) => { |
| | | temp.push({ label: ele.name, value: ele.index }) |
| | | }) |
| | | cameraList.value = temp |
| | | } |
| | | }) |
| | | cameraSelected.value = undefined |
| | | videoSelected.value = undefined |
| | | videoList.value = [] |
| | | lensList.value = [] |
| | | if (!val.more) { |
| | | return |
| | | } |
| | | val.more.forEach((ele: any) => { |
| | | temp.push({ label: ele.name, value: ele.index, more: ele.videos_list }) |
| | | }) |
| | | cameraList.value = temp |
| | | } |
| | | const onCameraSelect = (val: any) => { |
| | | cameraSelected.value = val |
| | | const result: Array<{}> = [] |
| | | if (cameraSelected.value) { |
| | | const droneTemp = livestreamSource.value |
| | | droneTemp.forEach((ele: any) => { |
| | | const drone = ele |
| | | if (drone.sn === droneSelected.value) { |
| | | const cameraListTemp = drone.cameras_list |
| | | cameraListTemp.forEach((ele: any) => { |
| | | const camera = ele |
| | | if (camera.index === cameraSelected.value) { |
| | | const videoListTemp = camera.videos_list |
| | | videoListTemp.forEach((ele: any) => { |
| | | result.push({ label: ele.type, value: ele.index }) |
| | | }) |
| | | videoList.value = result |
| | | videoSeleted.value = videoList.value[0]?.value |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | const onCameraSelect = (val: SelectOption) => { |
| | | cameraSelected.value = val.value |
| | | const result: Array<SelectOption> = [] |
| | | videoSelected.value = undefined |
| | | videoList.value = [] |
| | | lensList.value = [] |
| | | if (!val.more) { |
| | | return |
| | | } |
| | | |
| | | val.more.forEach((ele: any) => { |
| | | result.push({ label: ele.type, value: ele.index, more: ele.switch_video_types }) |
| | | }) |
| | | videoList.value = result |
| | | if (videoList.value.length === 0) { |
| | | return |
| | | } |
| | | const firstVideo: SelectOption = videoList.value[0] |
| | | videoSelected.value = firstVideo.value |
| | | lensList.value = firstVideo.more |
| | | lensSelected.value = firstVideo.label |
| | | isDockLive.value = lensList.value.length > 0 |
| | | } |
| | | const onVideoSelect = (val: any) => { |
| | | videoSeleted.value = val |
| | | const onVideoSelect = (val: SelectOption) => { |
| | | videoSelected.value = val.value |
| | | lensList.value = val.more |
| | | lensSelected.value = val.label |
| | | } |
| | | const onClaritySelect = (val: any) => { |
| | | claritySeleted.value = val |
| | | claritySelected.value = val |
| | | } |
| | | const onSwitch = () => { |
| | | if (lensSelected.value === undefined || lensSelected.value === nonSwitchable) { |
| | | message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8) |
| | | return |
| | | } |
| | | changeLivestreamLens({ |
| | | video_id: videoId.value, |
| | | video_type: lensSelected.value |
| | | }).then(res => { |
| | | if (res.code === 0) { |
| | | message.success('Switching live camera successfully.') |
| | | } |
| | | }) |
| | | } |
| | | </script> |
| | | |
| File was renamed from src/pages/page-web/projects/create-plan.vue |
| | |
| | | <template> |
| | | <div class="plan"> |
| | | <div class="create-plan-wrapper"> |
| | | <div class="header"> |
| | | Create Plan |
| | | </div> |
| | |
| | | <a-form-item label="Plan Name" name="name" :labelCol="{span: 24}"> |
| | | <a-input style="background: black;" placeholder="Please enter plan name" v-model:value="planBody.name"/> |
| | | </a-form-item> |
| | | <!-- 航线 --> |
| | | <a-form-item label="Flight Route" :wrapperCol="{offset: 7}" name="file_id"> |
| | | <router-link |
| | | :to="{name: 'select-plan'}" |
| | |
| | | </div> |
| | | </div> |
| | | </a-form-item> |
| | | <!-- 设备 --> |
| | | <a-form-item label="Device" :wrapperCol="{offset: 10}" v-model:value="planBody.dock_sn" name="dock_sn"> |
| | | <router-link |
| | | :to="{name: 'select-plan'}" |
| | |
| | | </div> |
| | | </div> |
| | | </a-form-item> |
| | | <a-form-item label="Immediate"> |
| | | <a-switch v-model:checked="planBody.immediate"> |
| | | <template #checkedChildren><CheckOutlined /></template> |
| | | <template #unCheckedChildren><CloseOutlined /></template> |
| | | </a-switch> |
| | | <!-- 任务类型 --> |
| | | <a-form-item label="Plan Timer" class="plan-timer-form-item"> |
| | | <div style="white-space: nowrap;"> |
| | | <a-radio-group v-model:value="planBody.task_type" button-style="solid"> |
| | | <a-radio-button :value="TaskType.Immediate">Immediate</a-radio-button> |
| | | <a-radio-button :value="TaskType.Single">Timed&One-Time</a-radio-button> |
| | | </a-radio-group> |
| | | </div> |
| | | </a-form-item> |
| | | <a-form-item style="position: absolute; bottom: 0px; margin-bottom: 0; margin-left: -10px; width: 280px;"> |
| | | <!-- 执行时间 --> |
| | | <a-form-item label="Start Time" v-if="planBody.task_type === TaskType.Single" name="select_execute_time"> |
| | | <a-date-picker |
| | | v-model:value="planBody.select_execute_time" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | show-time |
| | | placeholder="Select Time" |
| | | style="width: 280px;" |
| | | /> |
| | | </a-form-item> |
| | | <!-- RTH Altitude Relative to Dock --> |
| | | <a-form-item label="RTH Altitude Relative to Dock (m)" :labelCol="{span: 24}" name="rth_altitude"> |
| | | <a-input v-model:value="planBody.rth_altitude" style="background: black !important;"> |
| | | </a-input> |
| | | </a-form-item> |
| | | <!-- Lost Action --> |
| | | <a-form-item label="Lost Action" :labelCol="{span: 24}" name="out_of_control_action"> |
| | | <div style="white-space: nowrap;"> |
| | | <a-radio-group v-model:value="planBody.out_of_control_action" button-style="solid"> |
| | | <a-radio-button v-for="action in OutOfControlActionOptions" :value="action.value" :key="action.value"> |
| | | {{ action.label }} |
| | | </a-radio-button> |
| | | </a-radio-group> |
| | | </div> |
| | | </a-form-item> |
| | | <a-form-item style="width: 280px;"> |
| | | <div class="footer"> |
| | | <a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">Cancel |
| | | </a-button> |
| | |
| | | |
| | | <script lang="ts" setup> |
| | | import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue' |
| | | import { CheckOutlined, CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue' |
| | | import { message } from 'ant-design-vue' |
| | | import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue' |
| | | import { ELocalStorageKey, ERouterName } from '/@/types' |
| | | import { useMyStore } from '/@/store' |
| | | import { WaylineFile } from '/@/types/wayline' |
| | | import { WaylineType, WaylineFile } from '/@/types/wayline' |
| | | import { Device, EDeviceType } from '/@/types/device' |
| | | import { createPlan, CreatePlan } from '/@/api/wayline' |
| | | import { getRoot } from '/@/root' |
| | | import { TaskType, OutOfControlActionOptions, OutOfControlAction } from '/@/types/task' |
| | | import moment, { Moment } from 'moment' |
| | | import { RuleObject } from 'ant-design-vue/es/form/interface' |
| | | |
| | | const root = getRoot() |
| | | const store = useMyStore() |
| | |
| | | const disabled = ref(false) |
| | | |
| | | const routeName = ref('') |
| | | const planBody: UnwrapRef<CreatePlan> = reactive({ |
| | | const planBody = reactive({ |
| | | name: '', |
| | | file_id: computed(() => store.state.waylineInfo.id), |
| | | dock_sn: computed(() => store.state.dockInfo.device_sn), |
| | | immediate: false, |
| | | type: 'wayline' |
| | | task_type: TaskType.Immediate, |
| | | select_execute_time: undefined as Moment| undefined, |
| | | rth_altitude: '', |
| | | out_of_control_action: OutOfControlAction.ReturnToHome, |
| | | }) |
| | | |
| | | const drawerVisible = ref(false) |
| | | const valueRef = ref() |
| | | const rules = { |
| | |
| | | { max: 20, message: 'Length should be 1 to 20', trigger: 'blur' } |
| | | ], |
| | | file_id: [{ required: true, message: 'Select Route' }], |
| | | dock_sn: [{ required: true, message: 'Select Device' }] |
| | | dock_sn: [{ required: true, message: 'Select Device' }], |
| | | select_execute_time: [{ required: true, message: 'Select start time' }], |
| | | rth_altitude: [ |
| | | { |
| | | validator: async (rule: RuleObject, value: string) => { |
| | | if (!/^[0-9]{1,}$/.test(value)) { |
| | | throw new Error('RTH Altitude Relative Require number') |
| | | } |
| | | }, |
| | | } |
| | | ], |
| | | out_of_control_action: [{ required: true, message: 'Select Lost Action' }], |
| | | } |
| | | |
| | | function onSubmit () { |
| | | valueRef.value.validate().then(() => { |
| | | disabled.value = true |
| | | createPlan(workspaceId, planBody) |
| | | const createPlanBody = { ...planBody } as unknown as CreatePlan |
| | | if (planBody.select_execute_time) { |
| | | createPlanBody.execute_time = moment(planBody.select_execute_time).valueOf() |
| | | } |
| | | createPlanBody.rth_altitude = Number(createPlanBody.rth_altitude) |
| | | if (wayline.value && wayline.value.template_types && wayline.value.template_types.length > 0) { |
| | | createPlanBody.wayline_type = wayline.value.template_types[0] |
| | | } |
| | | // console.log('planBody', createPlanBody) |
| | | createPlan(workspaceId, createPlanBody) |
| | | .then(res => { |
| | | message.success('Saved Successfully') |
| | | setTimeout(() => { |
| | | disabled.value = false |
| | | }, 1500) |
| | | }).finally(() => { |
| | | closePlan() |
| | | }) |
| | | }).catch((e: any) => { |
| | | console.log('validate err', e) |
| | | }) |
| | | } |
| | | |
| | |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | |
| | | .plan { |
| | | .create-plan-wrapper { |
| | | background-color: #232323; |
| | | color: white; |
| | | color: fff; |
| | | padding-bottom: 0; |
| | | height: 100vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .header { |
| | | height: 53px; |
| | | height: 52px; |
| | | border-bottom: 1px solid #4f4f4f; |
| | | font-weight: 700; |
| | | font-size: 16px; |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .content { |
| | | height: 100%; |
| | | height: calc(100% - 54px); |
| | | overflow-y: auto; |
| | | |
| | | form { |
| | | margin: 10px; |
| | | } |
| | | |
| | | form label, input { |
| | | color: white; |
| | | background-color: #232323; |
| | | color: #fff; |
| | | } |
| | | |
| | | .ant-input-suffix { |
| | | color: #fff; |
| | | } |
| | | |
| | | .plan-timer-form-item { |
| | | // flex-direction: column; |
| | | |
| | | .ant-radio-button-wrapper{ |
| | | background-color: #232323; |
| | | color: #fff; |
| | | |
| | | &.ant-radio-button-wrapper-checked{ |
| | | background-color: #1890ff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-top: 1px solid #4f4f4f; |
| | | min-height: 65px; |
| | | margin-bottom: 0; |
| | | padding-bottom: 0; |
| | | padding:10px 0; |
| | | |
| | | button { |
| | | width: 45%; |
| | | color: white; |
| | | color: #fff ; |
| | | border: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .wayline-panel { |
| | | background: #3c3c3c; |
| | | margin-left: auto; |
| | |
| | | margin: 0px 10px 0 10px; |
| | | } |
| | | } |
| | | |
| | | .panel { |
| | | background: #3c3c3c; |
| | | margin-left: auto; |
| New file |
| | |
| | | <template> |
| | | <div class="header">Task Plan Library</div> |
| | | <div class="plan-panel-wrapper"> |
| | | <a-table class="plan-table" :columns="columns" :data-source="plansData.data" row-key="job_id" |
| | | :pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData"> |
| | | <!-- 执行时间 --> |
| | | <template #duration="{ record }"> |
| | | <div> |
| | | <div>{{ formatTaskTime(record.execute_time) }}</div> |
| | | <div>{{ formatTaskTime(record.end_time) }}</div> |
| | | </div> |
| | | </template> |
| | | <!-- 任务类型 --> |
| | | <template #taskType="{ record }"> |
| | | <div>{{ formatTaskType(record) }}</div> |
| | | </template> |
| | | <!-- 失控动作 --> |
| | | <template #lostAction="{ record }"> |
| | | <div>{{ formatLostAction(record) }}</div> |
| | | </template> |
| | | <!-- 状态 --> |
| | | <template #status="{ record }"> |
| | | <div> |
| | | <div class="flex-display flex-align-center"> |
| | | <span class="circle-icon" :style="{backgroundColor: formatTaskStatus(record).color}"></span> |
| | | {{ formatTaskStatus(record).text }} |
| | | <a-tooltip v-if="!!record.code" placement="bottom" arrow-point-at-center > |
| | | <template #title> |
| | | <div>{{ getCodeMessage(record.code) }}</div> |
| | | </template> |
| | | <exclamation-circle-outlined class="ml5" :style="{color: commonColor.WARN, fontSize: '16px' }"/> |
| | | </a-tooltip> |
| | | </div> |
| | | <div v-if="record.status === TaskStatus.Carrying"> |
| | | <a-progress :percent="record.progress || 0" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <!-- 操作 --> |
| | | <template #action="{ record }"> |
| | | <span class="action-area"> |
| | | <a-popconfirm |
| | | v-if="record.status === TaskStatus.Wait" |
| | | title="Are you sure you want to delete flight task?" |
| | | ok-text="Yes" |
| | | cancel-text="No" |
| | | @confirm="onDeleteTask(record.job_id)" |
| | | > |
| | | <a-button type="primary" size="small">Delete</a-button> |
| | | </a-popconfirm> |
| | | </span> |
| | | </template> |
| | | </a-table> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { reactive, ref } from '@vue/reactivity' |
| | | import { message } from 'ant-design-vue' |
| | | import { TableState } from 'ant-design-vue/lib/table/interface' |
| | | import { onMounted } from 'vue' |
| | | import { IPage } from '/@/api/http/type' |
| | | import { deleteTask, getWaylineJobs, Task } from '/@/api/wayline' |
| | | import { useMyStore } from '/@/store' |
| | | import { ELocalStorageKey } from '/@/types/enums' |
| | | import { useFormatTask } from './use-format-task' |
| | | import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap } from '/@/types/task' |
| | | import { useTaskProgressEvent } from './use-task-progress-event' |
| | | import { getErrorMessage } from '/@/utils/error-code/index' |
| | | import { commonColor } from '/@/utils/color' |
| | | import { ExclamationCircleOutlined } from '@ant-design/icons-vue' |
| | | |
| | | const store = useMyStore() |
| | | const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)! |
| | | |
| | | const body: IPage = { |
| | | page: 1, |
| | | total: 0, |
| | | page_size: 50 |
| | | } |
| | | const paginationProp = reactive({ |
| | | pageSizeOptions: ['20', '50', '100'], |
| | | showQuickJumper: true, |
| | | showSizeChanger: true, |
| | | pageSize: 50, |
| | | current: 1, |
| | | total: 0 |
| | | }) |
| | | |
| | | const columns = [ |
| | | { |
| | | title: 'Planned/Actual Time', |
| | | dataIndex: 'duration', |
| | | width: 180, |
| | | slots: { customRender: 'duration' }, |
| | | }, |
| | | { |
| | | title: 'Plan Name', |
| | | dataIndex: 'job_name', |
| | | width: 150, |
| | | ellipsis: true |
| | | }, |
| | | { |
| | | title: 'Type', |
| | | dataIndex: 'taskType', |
| | | width: 120, |
| | | slots: { customRender: 'taskType' }, |
| | | }, |
| | | { |
| | | title: 'Flight Route Name', |
| | | dataIndex: 'file_name', |
| | | width: 150, |
| | | ellipsis: true |
| | | }, |
| | | { |
| | | title: 'Dock Name', |
| | | dataIndex: 'dock_name', |
| | | width: 120, |
| | | ellipsis: true |
| | | }, |
| | | { |
| | | title: 'RTH Altitude Relative to Dock (m)', |
| | | dataIndex: 'rth_altitude', |
| | | width: 120, |
| | | ellipsis: true |
| | | }, |
| | | { |
| | | title: 'Lost Action', |
| | | dataIndex: 'out_of_control_action', |
| | | width: 120, |
| | | slots: { customRender: 'lostAction' }, |
| | | }, |
| | | { |
| | | title: 'Creator', |
| | | dataIndex: 'username', |
| | | width: 120, |
| | | }, |
| | | { |
| | | title: 'Status', |
| | | key: 'status', |
| | | width: 200, |
| | | slots: { customRender: 'status' } |
| | | }, |
| | | { |
| | | title: 'Action', |
| | | width: 120, |
| | | slots: { customRender: 'action' } |
| | | } |
| | | ] |
| | | type Pagination = TableState['pagination'] |
| | | |
| | | const plansData = reactive({ |
| | | data: [] as Task[] |
| | | }) |
| | | |
| | | const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus } = useFormatTask() |
| | | |
| | | // 设备任务执行进度更新 |
| | | function onTaskProgressWs (data: TaskProgressInfo) { |
| | | const { bid, output } = data |
| | | if (output) { |
| | | const { status, progress } = output || {} |
| | | const taskItem = plansData.data.find(task => task.job_id === bid) |
| | | if (!taskItem) return |
| | | if (status) { |
| | | taskItem.status = TaskProgressWsStatusMap[status] |
| | | // 执行中,更新进度 |
| | | if (status === TaskProgressStatus.Sent || status === TaskProgressStatus.inProgress) { |
| | | taskItem.progress = progress?.percent || 0 |
| | | } else if ([TaskProgressStatus.Rejected, TaskProgressStatus.Canceled, TaskProgressStatus.Timeout, TaskProgressStatus.Failed, TaskProgressStatus.OK].includes(status)) { |
| | | getPlans() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function getCodeMessage (code: number) { |
| | | return getErrorMessage(code) + `(code: ${code})` |
| | | } |
| | | |
| | | useTaskProgressEvent(onTaskProgressWs) |
| | | |
| | | onMounted(() => { |
| | | getPlans() |
| | | }) |
| | | |
| | | function getPlans () { |
| | | getWaylineJobs(workspaceId, body).then(res => { |
| | | if (res.code !== 0) { |
| | | return |
| | | } |
| | | plansData.data = res.data.list |
| | | paginationProp.total = res.data.pagination.total |
| | | paginationProp.current = res.data.pagination.page |
| | | }) |
| | | } |
| | | |
| | | function refreshData (page: Pagination) { |
| | | body.page = page?.current! |
| | | body.page_size = page?.pageSize! |
| | | getPlans() |
| | | } |
| | | |
| | | // 删除任务 |
| | | async function onDeleteTask (jobId: string) { |
| | | const { code } = await deleteTask(workspaceId, { |
| | | job_id: jobId |
| | | }) |
| | | if (code === 0) { |
| | | message.success('Deleted successfully') |
| | | getPlans() |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .plan-panel-wrapper { |
| | | width: 100%; |
| | | padding: 16px; |
| | | .plan-table { |
| | | background: #fff; |
| | | margin-top: 10px; |
| | | } |
| | | .action-area { |
| | | color: $primary; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .circle-icon { |
| | | display: inline-block; |
| | | width: 12px; |
| | | height: 12px; |
| | | margin-right: 3px; |
| | | border-radius: 50%; |
| | | vertical-align: middle; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | .header { |
| | | width: 100%; |
| | | height: 60px; |
| | | background: #fff; |
| | | padding: 16px; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | text-align: start; |
| | | color: #000; |
| | | } |
| | | </style> |
| New file |
| | |
| | | import { DEFAULT_PLACEHOLDER } from '/@/utils/constants' |
| | | import { Task } from '/@/api/wayline' |
| | | import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap } from '/@/types/task' |
| | | |
| | | export function useFormatTask () { |
| | | function formatTaskType (task: Task) { |
| | | return TaskTypeMap[task.task_type] || DEFAULT_PLACEHOLDER |
| | | } |
| | | |
| | | function formatTaskTime (time: string) { |
| | | return time || DEFAULT_PLACEHOLDER |
| | | } |
| | | |
| | | function formatLostAction (task: Task) { |
| | | return OutOfControlActionMap[task.out_of_control_action] || DEFAULT_PLACEHOLDER |
| | | } |
| | | |
| | | function formatTaskStatus (task: Task) { |
| | | const statusObj = { |
| | | text: '', |
| | | color: '' |
| | | } |
| | | const { status } = task |
| | | statusObj.text = TaskStatusMap[status] |
| | | statusObj.color = TaskStatusColor[status] |
| | | return statusObj |
| | | } |
| | | |
| | | return { |
| | | formatTaskType, |
| | | formatTaskTime, |
| | | formatLostAction, |
| | | formatTaskStatus, |
| | | } |
| | | } |
| New file |
| | |
| | | import EventBus from '/@/event-bus/' |
| | | import { onMounted, onBeforeUnmount } from 'vue' |
| | | import { TaskProgressInfo } from '/@/types/task' |
| | | |
| | | export function useTaskProgressEvent (onTaskProgressWs: (data: TaskProgressInfo) => void): void { |
| | | function handleTaskProgress (payload: any) { |
| | | onTaskProgressWs(payload.data) |
| | | // eslint-disable-next-line no-unused-expressions |
| | | // console.log('payload', payload.data) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | EventBus.on('deviceTaskProgress', handleTaskProgress) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | EventBus.off('deviceTaskProgress', handleTaskProgress) |
| | | }) |
| | | } |
| | |
| | | import mitt, { Emitter } from 'mitt' |
| | | |
| | | type Events = { |
| | | deviceUpgrade: any; |
| | | deviceLogUploadProgress: any |
| | | deviceUpgrade: any; // 设备升级 |
| | | deviceLogUploadProgress: any // 设备日志上传 |
| | | deviceTaskProgress: any // 设备任务进度 |
| | | }; |
| | | |
| | | const emitter: Emitter<Events> = mitt<Events>() |
| | |
| | | import { ELocalStorageKey } from '/@/types' |
| | | import { getDeviceBySn } from '/@/api/manage' |
| | | import { message } from 'ant-design-vue' |
| | | import dockIcon from '/@/assets/icons/dock.png' |
| | | import rcIcon from '/@/assets/icons/rc.png' |
| | | import droneIcon from '/@/assets/icons/drone.png' |
| | | |
| | | export function deviceTsaUpdate () { |
| | | const root = getRoot() |
| | | const AMap = root.$aMap |
| | | |
| | | const icons: { |
| | | [key: string]: string |
| | | } = { |
| | | 'sub-device': '/@/assets/icons/drone.png', |
| | | 'gateway': '/@/assets/icons/rc.png', |
| | | 'dock': '/@/assets/icons/dock.png' |
| | | } |
| | | const icons = new Map([ |
| | | ['sub-device', droneIcon], |
| | | ['gateway', rcIcon], |
| | | ['dock', dockIcon] |
| | | ]) |
| | | const markers = store.state.markerInfo.coverMap |
| | | const paths = store.state.markerInfo.pathMap |
| | | |
| | |
| | | |
| | | function initIcon (type: string) { |
| | | return new AMap.Icon({ |
| | | image: icons[type], |
| | | imageSize: new AMap.Size(40, 40) |
| | | image: icons.get(type), |
| | | imageSize: new AMap.Size(40, 40), |
| | | size: new AMap.Size(40, 40) |
| | | }) |
| | | } |
| | | |
| | | function initMarker (type: string, name: string, sn: string, lng?: number, lat?: number) { |
| | | if (AMap === undefined) { |
| | | location.reload() |
| | | return |
| | | } |
| | | if (markers[sn]) { |
| | | return |
| | | } |
| | |
| | | offset: [0, -20], |
| | | }) |
| | | root.$map.add(markers[sn]) |
| | | |
| | | // markers[sn].on('moving', function (e: any) { |
| | | // let path = paths[sn] |
| | | // if (!path) { |
| | |
| | | apiPilot.onBackClickReg() |
| | | apiPilot.onStopPlatform() |
| | | |
| | | window.connectCallback = arg => { |
| | | connectCallback(arg) |
| | | } |
| | | window.wsConnectCallback = arg => { |
| | | wsConnectCallback(arg) |
| | | } |
| | | device.data.gateway_sn = apiPilot.getRemoteControllerSN() |
| | | if (device.data.gateway_sn === EStatusValue.DISCONNECT.toString()) { |
| | | message.warn('Data is not available, please restart the remote control.') |
| | |
| | | bindParam.user_id = res.data.user_id |
| | | bindParam.workspace_id = res.data.workspace_id |
| | | }) |
| | | window.connectCallback = arg => { |
| | | connectCallback(arg) |
| | | } |
| | | window.wsConnectCallback = arg => { |
| | | wsConnectCallback(arg) |
| | | } |
| | | }) |
| | | |
| | | const connectCallback = async (arg: any) => { |
| | |
| | | </div> |
| | | <div v-if="docksData.data.length !== 0"> |
| | | <div v-for="dock in docksData.data" :key="dock.device_sn"> |
| | | <div v-if="dock.children" class="panel" style="padding-top: 5px;" @click="selectDock(dock)"> |
| | | <div v-if="dock?.children" class="panel" style="padding-top: 5px;" @click="selectDock(dock)"> |
| | | <div class="title"> |
| | | <a-tooltip :title="dock.nickname"> |
| | | <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div> |
| | |
| | | <a-col :span="1"></a-col> |
| | | <a-col :span="20">Task Plan Library</a-col> |
| | | <a-col :span="2"> |
| | | <span v-if="!createPlanTip"> |
| | | <router-link :to="{ name: 'create-plan'}"> |
| | | <PlusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = true"/> |
| | | <span v-if="taskRoute"> |
| | | <router-link :to="{ name: ERouterName.CREATE_PLAN}"> |
| | | <PlusOutlined class="route-icon"/> |
| | | </router-link> |
| | | </span> |
| | | <span v-else> |
| | | <router-link :to="{ name: 'task'}"> |
| | | <MinusOutlined style="color: white; font-size: 16px;" @click="() => createPlanTip = false"/> |
| | | <router-link :to="{ name: ERouterName.TASK}"> |
| | | <MinusOutlined class="route-icon"/> |
| | | </router-link> |
| | | </span> |
| | | </a-col> |
| | | <a-col :span="1"></a-col> |
| | | </a-row> |
| | | </div> |
| | | <div v-if="createPlanTip"> |
| | | <router-view /> |
| | | <div v-if="!taskRoute"> |
| | | <router-view/> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { PlusOutlined, MinusOutlined } from '@ant-design/icons-vue' |
| | | import { onMounted, onUnmounted, ref } from 'vue' |
| | | import { computed, ref } from 'vue' |
| | | import { useRoute } from 'vue-router' |
| | | import { ERouterName } from '/@/types/enums' |
| | | |
| | | const createPlanTip = ref(false) |
| | | const route = useRoute() |
| | | |
| | | const taskRoute = computed(() => { |
| | | return route.name === ERouterName.TASK |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | |
| | | .route-icon { |
| | | color: #fff; |
| | | font-size: 16px; |
| | | } |
| | | </style> |
| | |
| | | <div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;"> |
| | | <a-row> |
| | | <a-col :span="1"></a-col> |
| | | <a-col :span="22">Flight Route Library</a-col> |
| | | <a-col :span="1"></a-col> |
| | | <a-col :span="15">Flight Route Library</a-col> |
| | | <a-col :span="8" v-if="importVisible" class="flex-row flex-justify-end flex-align-center"> |
| | | <a-upload |
| | | name="file" |
| | | :multiple="false" |
| | | :before-upload="beforeUpload" |
| | | :show-upload-list="false" |
| | | :customRequest="uploadFile" |
| | | > |
| | | <a-button type="text" style="color: white;"> |
| | | <SelectOutlined /> |
| | | </a-button> |
| | | </a-upload> |
| | | </a-col> |
| | | </a-row> |
| | | </div> |
| | | <div class="height-100"> |
| | | <a-spin :spinning="loading" :delay="1000" tip="downloading" size="large"> |
| | | <a-spin :spinning="loading" :delay="300" tip="downloading" size="large"> |
| | | <div class="scrollbar uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll"> |
| | | <div v-for="wayline in waylinesData.data" :key="wayline.id"> |
| | | <div class="wayline-panel" style="padding-top: 5px;" @click="selectRoute(wayline)"> |
| | |
| | | import { reactive } from '@vue/reactivity' |
| | | import { message } from 'ant-design-vue' |
| | | import { onMounted, onUpdated, ref } from 'vue' |
| | | import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles } from '/@/api/wayline' |
| | | import { ELocalStorageKey } from '/@/types' |
| | | import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue' |
| | | import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline' |
| | | import { ELocalStorageKey, ERouterName } from '/@/types' |
| | | import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue' |
| | | import { EDeviceType } from '/@/types/device' |
| | | import { useMyStore } from '/@/store' |
| | | import { WaylineFile } from '/@/types/wayline' |
| | | import { downloadFile } from '/@/utils/common' |
| | | import { IPage } from '/@/api/http/type' |
| | | import { CURRENT_CONFIG } from '/@/api/http/config' |
| | | import { load } from '@amap/amap-jsapi-loader' |
| | | import { getRoot } from '/@/root' |
| | | |
| | | const loading = ref(false) |
| | | const store = useMyStore() |
| | |
| | | data: [] as WaylineFile[] |
| | | }) |
| | | |
| | | const root = getRoot() |
| | | const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)! |
| | | const deleteTip = ref(false) |
| | | const deleteWaylineId = ref<string>('') |
| | | const canRefresh = ref(true) |
| | | const importVisible = ref<boolean>(root.$router.currentRoute.value.name === ERouterName.WAYLINE) |
| | | |
| | | onMounted(() => { |
| | | getWaylines() |
| | | setTimeout(() => { |
| | | const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement |
| | | const parent = element?.parentNode as HTMLDivElement |
| | | console.info(element, parent) |
| | | // console.info(element.scrollHeight, parent.clientHeight) |
| | | }, 1000) |
| | | }) |
| | | |
| | | onUpdated(() => { |
| | | const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement |
| | | const parent = element?.parentNode as HTMLDivElement |
| | | setTimeout(() => { |
| | | console.info(element, parent) |
| | | if (element?.scrollHeight < parent?.clientHeight && pagination.total > waylinesData.data.length) { |
| | | if (canRefresh.value) { |
| | | pagination.page++ |
| | |
| | | if (res.code !== 0) { |
| | | return |
| | | } |
| | | waylinesData.data = [] |
| | | res.data.list.forEach((wayline: WaylineFile) => waylinesData.data.push(wayline)) |
| | | pagination.total = res.data.pagination.total |
| | | pagination.page = res.data.pagination.page |
| | |
| | | deleteWaylineId.value = '' |
| | | deleteTip.value = false |
| | | pagination.total-- |
| | | waylinesData.data = [] |
| | | setTimeout(getWaylines, 500) |
| | | getWaylines() |
| | | }) |
| | | } |
| | | |
| | |
| | | |
| | | function onScroll (e: any) { |
| | | const element = e.srcElement |
| | | console.info(element) |
| | | if (element.scrollTop + element.clientHeight === element.scrollHeight && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) { |
| | | pagination.page++ |
| | | getWaylines() |
| | | } |
| | | } |
| | | |
| | | interface FileItem { |
| | | uid: string; |
| | | name?: string; |
| | | status?: string; |
| | | response?: string; |
| | | url?: string; |
| | | } |
| | | |
| | | interface FileInfo { |
| | | file: FileItem; |
| | | fileList: FileItem[]; |
| | | } |
| | | const fileList = ref<FileItem[]>([]) |
| | | |
| | | function beforeUpload (file: FileItem) { |
| | | fileList.value = [file] |
| | | loading.value = true |
| | | return true |
| | | } |
| | | const uploadFile = async () => { |
| | | console.info(loading.value) |
| | | fileList.value.forEach(async (file: FileItem) => { |
| | | const fileData = new FormData() |
| | | fileData.append('file', file, file.name) |
| | | await importKmzFile(workspaceId, fileData).then((res) => { |
| | | if (res.code === 0) { |
| | | message.success(`${file.name} file uploaded successfully`) |
| | | canRefresh.value = true |
| | | getWaylines() |
| | | } |
| | | }).finally(() => { |
| | | loading.value = false |
| | | fileList.value = [] |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | <div class="media-wrapper" v-if="root.$route.name === ERouterName.MEDIA"> |
| | | <MediaPanel /> |
| | | </div> |
| | | <div class="media-wrapper" v-if="root.$route.name === ERouterName.TASK"> |
| | | <div class="task-wrapper" v-if="root.$route.name === ERouterName.TASK"> |
| | | <TaskPanel /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | |
| | | import Sidebar from '/@/components/common/sidebar.vue' |
| | | import MediaPanel from '/@/components/MediaPanel.vue' |
| | | import TaskPanel from '/@/components/TaskPanel.vue' |
| | | import TaskPanel from '/@/components/task/TaskPanel.vue' |
| | | import GMap from '/@/components/GMap.vue' |
| | | import { EBizCode, ERouterName } from '/@/types' |
| | | import { getRoot } from '/@/root' |
| | | import { useMyStore } from '/@/store' |
| | | import { useConnectWebSocket } from '/@/hooks/use-connect-websocket' |
| | | import EventBus from '/@/event-bus' |
| | | |
| | | const root = getRoot() |
| | | const store = useMyStore() |
| | |
| | | break |
| | | } |
| | | case EBizCode.FlightTaskProgress: { |
| | | store.commit('SET_FLIGHT_TASK_PROGRESS', payload.data) |
| | | EventBus.emit('deviceTaskProgress', payload) |
| | | break |
| | | } |
| | | case EBizCode.DeviceHms: { |
| | |
| | | |
| | | .project-app-wrapper { |
| | | display: flex; |
| | | position: absolute; |
| | | transition: width 0.2s ease; |
| | | height: 100%; |
| | | width: 100%; |
| | | |
| | | .left { |
| | | width: 400px; |
| | | display: flex; |
| | | width: 335px; |
| | | flex: 0 0 335px; |
| | | background-color: #232323; |
| | | float: left; |
| | | |
| | | .main-content { |
| | | flex: 1; |
| | | color: $text-white-basic; |
| | | } |
| | | } |
| | | |
| | | .right { |
| | | width: 100%; |
| | | height: 100%; |
| | | .map-wrapper { |
| | | flex-grow: 1; |
| | | position: relative; |
| | | |
| | | .map-wrapper{ |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | .main-content { |
| | | flex: 1; |
| | | color: $text-white-basic; |
| | | } |
| | | .media-wrapper { |
| | | position: absolute; |
| | | top: 0; |
| | | bottom: 0; |
| | | z-index: 100; |
| | | background: #f6f8fa; |
| | | } |
| | | .wayline-wrapper { |
| | | position: absolute; |
| | | top: 0; |
| | | bottom: 0; |
| | | z-index: 100; |
| | | background: #f6f8fa; |
| | | padding: 16px; |
| | | |
| | | .media-wrapper, |
| | | .task-wrapper { |
| | | position: absolute; |
| | | top: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 100; |
| | | background: #f6f8fa; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' |
| | | import { ERouterName } from '/@/types/index' |
| | | import CreatePlan from '../pages/page-web/projects/create-plan.vue' |
| | | import CreatePlan from '/@/components/task/CreatePlan.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 { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device' |
| | | import { getLayers } from '/@/api/layer' |
| | | import { LayerType } from '/@/types/mapLayer' |
| | | import { ETaskStatus, TaskInfo, WaylineFile } from '/@/types/wayline' |
| | | import { WaylineFile } from '/@/types/wayline' |
| | | import { DevicesCmdExecuteInfo } from '/@/types/device-cmd' |
| | | |
| | | const initStateFunc = () => ({ |
| | |
| | | dockInfo: { |
| | | |
| | | } as Device, |
| | | taskProgressInfo: { |
| | | |
| | | } as { |
| | | [bid: string]: TaskInfo |
| | | }, |
| | | hmsInfo: {} as { |
| | | [sn: string]: DeviceHms[] |
| | | }, |
| | |
| | | state.deviceState.currentType = EDeviceTypeName.Gateway |
| | | }, |
| | | SET_DOCK_INFO (state, info) { |
| | | if (Object.keys(info.host).length === 0) { |
| | | return |
| | | } |
| | | if (!state.deviceState.dockInfo[info.sn]) { |
| | | state.deviceState.dockInfo[info.sn] = info.host |
| | | return |
| | | } |
| | | state.deviceState.currentSn = info.sn |
| | | state.deviceState.currentType = EDeviceTypeName.Dock |
| | | const dock = state.deviceState.dockInfo[info.sn] |
| | | if (info.host.sdr && state.deviceState.dockInfo[info.sn]) { |
| | | if (info.host.sdr) { |
| | | dock.sdr = info.host.sdr |
| | | dock.media_file_detail = info.host.media_file_detail |
| | | return |
| | | } |
| | | const sdr = dock?.sdr |
| | | const mediaFileDetail = dock?.media_file_detail |
| | | if (info.host.job_number !== undefined) { |
| | | if (info.host.drone_battery_maintenance_info) { |
| | | dock.drone_battery_maintenance_info = info.host.drone_battery_maintenance_info |
| | | } |
| | | return |
| | | } |
| | | const sdr = dock.sdr |
| | | const mediaFileDetail = dock.media_file_detail |
| | | state.deviceState.dockInfo[info.sn] = info.host |
| | | state.deviceState.dockInfo[info.sn].sdr = sdr |
| | | state.deviceState.dockInfo[info.sn].media_file_detail = mediaFileDetail |
| | |
| | | }, |
| | | SET_SELECT_DOCK_INFO (state, info) { |
| | | state.dockInfo = info |
| | | }, |
| | | SET_FLIGHT_TASK_PROGRESS (state, info) { |
| | | const taskInfo: TaskInfo = info.output |
| | | |
| | | if (taskInfo.status === ETaskStatus.OK || taskInfo.status === ETaskStatus.FAILED) { |
| | | taskInfo.status = taskInfo.status.concat('(Code:').concat(info.result).concat(')') |
| | | setTimeout(() => { |
| | | delete state.taskProgressInfo[info.bid] |
| | | }, 60000) |
| | | } |
| | | state.taskProgressInfo[info.bid] = info.output |
| | | }, |
| | | SET_DEVICE_HMS_INFO (state, info) { |
| | | const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn] |
| | |
| | | Close = 0, // 关闭 |
| | | Open = 1, // 打开 |
| | | } |
| | | |
| | | // 机场声光报警状态 |
| | | export enum AlarmModeEnum { |
| | | CLOSE = 0, // 关闭 |
| | | OPEN = 1, // 开启 |
| | | } |
| | | |
| | | // 电池保养 |
| | | export enum BatteryStoreModeEnum { |
| | | BATTERY_PLAN_STORE = 1, // 电池计划存储策略 |
| | | BATTERY_EMERGENCY_STORE = 2, // 电池应急存储策略 |
| | | } |
| | | |
| | | // 飞行器电池保养 |
| | | export enum DroneBatteryStateEnum { |
| | | NoMaintenanceRequired = 0, // 0-无需保养 |
| | | MaintenanceRequired = 1, // 1-待保养 |
| | | MaintenanceInProgress = 2, // 2-正在保养 |
| | | } |
| | | |
| | | export enum DroneBatteryModeEnum { |
| | | CLOSE = 0, // 关闭 |
| | | OPEN = 1, // 开启 |
| | | } |
| | |
| | | import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum } from '/@/types/airport-tsa'; |
| | | // 机场指令集 |
| | | export enum DeviceCmd { |
| | | // 简单指令 |
| | |
| | | PutterClose = 'putter_close', // 推杆闭合 |
| | | ChargeOpen = 'charge_open', // 打开充电 |
| | | ChargeClose = 'charge_close', // 关闭充电 |
| | | AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警 |
| | | BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养 |
| | | DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养 |
| | | |
| | | } |
| | | |
| | | export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum |
| | | |
| | | export interface DeviceCmdItem{ |
| | | label: string, // 标题 |
| | |
| | | operateText: string, // 按钮文字 |
| | | cmdKey: DeviceCmd, // 请求指令 |
| | | oppositeCmdKey?: DeviceCmd, // 相反状态指令 |
| | | action?: DeviceCmdItemAction, // 参数 |
| | | func: string, // 处理函数 |
| | | loading: boolean // 按钮loading |
| | | disabled?: boolean // 按钮disabled |
| | | } |
| | | |
| | | // 机场指令 |
| | |
| | | func: 'supplementLightStatus', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '机场声光报警', |
| | | status: '关', |
| | | operateText: '打开', |
| | | cmdKey: DeviceCmd.AlarmStateSwitch, |
| | | action: AlarmModeEnum.OPEN, |
| | | func: 'alarmState', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '机场电池存储模式', |
| | | status: '计划', |
| | | operateText: '应急', |
| | | cmdKey: DeviceCmd.BatteryStoreModeSwitch, |
| | | action: BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE, |
| | | func: 'batteryStoreMode', |
| | | loading: false, |
| | | }, |
| | | { |
| | | label: '飞机电池保养', |
| | | status: '--', |
| | | operateText: '保养', |
| | | cmdKey: DeviceCmd.DroneBatteryModeSwitch, |
| | | action: DroneBatteryModeEnum.OPEN, |
| | | func: 'droneBatteryMode', |
| | | loading: false, |
| | | disabled: true, |
| | | }, |
| | | ] |
| | | |
| | | export enum DeviceCmdStatusText { |
| | |
| | | DeviceSupplementLightCloseText = '关闭中...', |
| | | DeviceSupplementLightCloseFailedText = '开', |
| | | DeviceSupplementLightCloseBtnText = '打开', |
| | | |
| | | AlarmStateOpenNormalText = '开', |
| | | AlarmStateOpenText = '开启中...', |
| | | AlarmStateOpenFailedText = '关', |
| | | AlarmStateOpenBtnText = '关闭', |
| | | |
| | | AlarmStateCloseNormalText = '关', |
| | | AlarmStateCloseText = '关闭中...', |
| | | AlarmStateCloseFailedText = '开', |
| | | AlarmStateCloseBtnText = '打开', |
| | | |
| | | BatteryStoreModePlanNormalText = '计划', |
| | | BatteryStoreModePlanText = '切换中...', |
| | | BatteryStoreModePlanFailedText = '应急', |
| | | BatteryStoreModePlanBtnText = '应急', |
| | | |
| | | BatteryStoreModeEmergencyNormalText = '应急', |
| | | BatteryStoreModeEmergencyText = '切换中...', |
| | | BatteryStoreModeEmergencyFailedText = '计划', |
| | | BatteryStoreModeEmergencyBtnText = '计划', |
| | | |
| | | DroneBatteryModeMaintenanceInProgressText = '保养中', |
| | | DroneBatteryModeMaintenanceNotNeedText = '无需保养', |
| | | DroneBatteryModeMaintenanceNeedText = '需保养', |
| | | DroneBatteryModeOpenBtnText = '保养', |
| | | DroneBatteryModeCloseBtnText = '关闭保养', |
| | | } |
| | | |
| | | // cmd ws 消息状态 |
| New file |
| | |
| | | // 夜航灯开关 |
| | | export enum NightLightsStateEnum { |
| | | CLOSE = 0, // 0-关闭 |
| | | OPEN = 1, // 1-打开 |
| | | } |
| | | |
| | | // 限远开关 |
| | | export enum DistanceLimitStatusEnum { |
| | | UNSET = 0, // 0-未设置 |
| | | SET = 1, // 1-已设置 |
| | | } |
| | | |
| | | export interface DistanceLimitStatus { |
| | | state?: DistanceLimitStatusEnum; |
| | | distance_limit?: number; // 限远 |
| | | } |
| | | |
| | | // 避障 |
| | | export enum ObstacleAvoidanceStatusEnum { |
| | | CLOSE = 0, // 0-关闭 |
| | | OPEN = 1, // 1-开启 |
| | | } |
| | | |
| | | export interface ObstacleAvoidance { |
| | | horizon?: ObstacleAvoidanceStatusEnum;// 水平避障开关 |
| | | upside?: ObstacleAvoidanceStatusEnum;// 上行方向避障开关 |
| | | downside?: ObstacleAvoidanceStatusEnum;// 下行方向避障开关 |
| | | } |
| | | |
| | | // 设备管理设置key |
| | | export enum DeviceSettingKeyEnum { |
| | | NIGHT_LIGHTS_MODE_SET = 'night_lights_state', // 夜航灯开关 |
| | | HEIGHT_LIMIT_SET = 'height_limit', // 限高设置 |
| | | DISTANCE_LIMIT_SET = 'distance_limit_status', // 限远开关 |
| | | OBSTACLE_AVOIDANCE_HORIZON = 'obstacle_avoidance_horizon', // 水平避障状态 |
| | | OBSTACLE_AVOIDANCE_UPSIDE = 'obstacle_avoidance_upside', // 上视避障状态 |
| | | OBSTACLE_AVOIDANCE_DOWNSIDE = 'obstacle_avoidance_downside', // 下视避障状态 |
| | | } |
| | | |
| | | export type DeviceSettingType = Record<DeviceSettingKeyEnum, any> |
| | | |
| | | export const initDeviceSetting = { |
| | | [DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET]: |
| | | { |
| | | label: '飞行器夜航灯', |
| | | value: '', |
| | | trueValue: NightLightsStateEnum.CLOSE, |
| | | editable: false, |
| | | popConfirm: { |
| | | visible: false, |
| | | loading: false, |
| | | // content: '为保证飞行器的作业安全,建议打开夜航灯', |
| | | label: '飞行器夜航灯', |
| | | }, |
| | | settingKey: DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET, |
| | | }, |
| | | [DeviceSettingKeyEnum.HEIGHT_LIMIT_SET]: |
| | | { |
| | | label: '限高', |
| | | value: '', |
| | | trueValue: 120, |
| | | editable: false, |
| | | popConfirm: { |
| | | visible: false, |
| | | loading: false, |
| | | // content: '限高:20 - 1500m', |
| | | // info: '修改限高会影响当前机场的所有作业任务,建议确认作业情况后再进行修改', |
| | | label: '限高', |
| | | }, |
| | | settingKey: DeviceSettingKeyEnum.HEIGHT_LIMIT_SET, |
| | | }, |
| | | [DeviceSettingKeyEnum.DISTANCE_LIMIT_SET]: |
| | | { |
| | | label: '限远', |
| | | value: '', |
| | | trueValue: DistanceLimitStatusEnum.UNSET, |
| | | // info: '限远(15 - 8000m)是约束飞行器相对机场的最大作业距离', |
| | | editable: false, |
| | | popConfirm: { |
| | | visible: false, |
| | | loading: false, |
| | | // content: '限远 (15- 8000m) 是约束飞行器相对机场的最大作业距离', |
| | | // info: '修改限远会影响当前机场的所有作业任务,建议确认作业情况后再进行修改', |
| | | label: '限远', |
| | | |
| | | }, |
| | | settingKey: DeviceSettingKeyEnum.DISTANCE_LIMIT_SET, |
| | | }, |
| | | [DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON]: |
| | | { |
| | | label: '水平避障', |
| | | value: '', |
| | | trueValue: ObstacleAvoidanceStatusEnum.CLOSE, |
| | | // info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置', |
| | | editable: false, |
| | | popConfirm: { |
| | | visible: false, |
| | | loading: false, |
| | | // content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启', |
| | | label: '水平避障', |
| | | |
| | | }, |
| | | settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON, |
| | | }, |
| | | [DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE]: |
| | | { |
| | | label: '上视避障', |
| | | value: '', |
| | | trueValue: ObstacleAvoidanceStatusEnum.CLOSE, |
| | | // info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置', |
| | | editable: false, |
| | | popConfirm: { |
| | | visible: false, |
| | | loading: false, |
| | | // content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启', |
| | | label: '上视避障', |
| | | |
| | | }, |
| | | settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE, |
| | | }, |
| | | [DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE]: |
| | | { |
| | | label: '下视避障', |
| | | value: '', |
| | | trueValue: ObstacleAvoidanceStatusEnum.CLOSE, |
| | | // info: '飞行器的避障工作状态显示,可以快速开启/关闭飞行器避障,如需进一步设置请在设备运维页面设置', |
| | | editable: false, |
| | | popConfirm: { |
| | | visible: false, |
| | | loading: false, |
| | | // content: '飞行器避障是保障飞行作业安全的基础功能,建议保持飞行器避障开启', |
| | | label: '下视避障', |
| | | |
| | | }, |
| | | settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE, |
| | | }, |
| | | } as DeviceSettingType |
| | | |
| | | export const initDeviceSettingFormModel = { |
| | | nightLightsState: false, // 夜航灯开关 |
| | | heightLimit: 20, // 限高设置 |
| | | distanceLimitStatus: { state: false, distanceLimit: 15 }, // 限远开关 |
| | | obstacleAvoidanceHorizon: false, // 飞行器避障-水平开关设置 |
| | | obstacleAvoidanceUpside: false, // 飞行器避障-上视开关设置 |
| | | obstacleAvoidanceDownside: false, // 飞行器避障-下视开关设置 |
| | | } |
| | | |
| | | export type DeviceSettingFormModel = typeof initDeviceSettingFormModel |
| | |
| | | import { commonColor } from '/@/utils/color' |
| | | |
| | | import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting' |
| | | import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum } from './airport-tsa' |
| | | export interface DeviceValue { |
| | | key: string; // 'domain-type-subtype' |
| | | domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种 |
| | |
| | | landing_power: string, |
| | | remain_flight_time: number, |
| | | return_home_power: string, |
| | | } |
| | | }, |
| | | night_lights_state?: NightLightsStateEnum;// 夜航灯开关 |
| | | height_limit?: number;// 限高设置 |
| | | distance_limit_status?: DistanceLimitStatus;// 限远开关 |
| | | obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置 |
| | | } |
| | | |
| | | export interface DockOsd { |
| | |
| | | device_online_status: number, |
| | | device_paired: number, |
| | | }, |
| | | alarm_state?: AlarmModeEnum; // 机场声光报警状态 |
| | | battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式 |
| | | drone_battery_maintenance_info?: { // 飞行器电池保养信息 |
| | | maintenance_state: DroneBatteryStateEnum, // 保养状态 |
| | | maintenance_time_left: number, // 电池保养剩余时间(小时) |
| | | } |
| | | } |
| | | |
| | | export enum EModeCode { |
| | |
| | | H20N = '1-61-0' as any, |
| | | DJI_Dock_Camera = '1-165-0' as any, |
| | | L1 = '1-90742-0' as any, |
| | | M3E = '0-77-0' as any, |
| | | M3D = '0-77-1' as any, |
| | | M3E_Camera = '1-66-0' as any, |
| | | M3T_Camera = '1-67-0' as any, |
| | | } |
| | | |
| | | export enum EDockModeCode { |
| | |
| | | MapElementDelete = 'map_element_delete', |
| | | DeviceOnline = 'device_online', |
| | | DeviceOffline = 'device_offline', |
| | | FlightTaskProgress = 'flighttask_progress', |
| | | FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度 |
| | | DeviceHms = 'device_hms', |
| | | |
| | | // 设备指令 |
| New file |
| | |
| | | import { commonColor } from '/@/utils/color' |
| | | |
| | | // 任务类型 |
| | | export enum TaskType { |
| | | Immediate = 0, // 立即执行 |
| | | Single = 1, // 单次定时任务 |
| | | } |
| | | |
| | | export const TaskTypeMap = { |
| | | [TaskType.Immediate]: 'Immediate', |
| | | [TaskType.Single]: 'Timed & One-Time', |
| | | } |
| | | |
| | | // 失控动作 |
| | | export enum OutOfControlAction { |
| | | ReturnToHome = 0, |
| | | Hover = 1, |
| | | Land = 2, |
| | | } |
| | | |
| | | export const OutOfControlActionMap = { |
| | | [OutOfControlAction.ReturnToHome]: 'Return to Home', |
| | | [OutOfControlAction.Hover]: 'Hover', |
| | | [OutOfControlAction.Land]: 'Land', |
| | | } |
| | | |
| | | export const OutOfControlActionOptions = [ |
| | | { value: OutOfControlAction.ReturnToHome, label: OutOfControlActionMap[OutOfControlAction.ReturnToHome] }, |
| | | { value: OutOfControlAction.Hover, label: OutOfControlActionMap[OutOfControlAction.Hover] }, |
| | | { value: OutOfControlAction.Land, label: OutOfControlActionMap[OutOfControlAction.Land] }, |
| | | ] |
| | | |
| | | // 任务状态 |
| | | export enum TaskStatus { |
| | | Wait = 1, // 待执行 |
| | | Carrying = 2, // 执行中 |
| | | Success = 3, // 完成 |
| | | CanCel = 4, // 取消 |
| | | Fail = 5, // 失败 |
| | | } |
| | | |
| | | export const TaskStatusMap = { |
| | | [TaskStatus.Wait]: 'To be performed', |
| | | [TaskStatus.Carrying]: 'In progress', |
| | | [TaskStatus.Success]: 'Task completed', |
| | | [TaskStatus.CanCel]: 'Task canceled', |
| | | [TaskStatus.Fail]: 'Task failed', |
| | | } |
| | | |
| | | export const TaskStatusColor = { |
| | | [TaskStatus.Wait]: commonColor.BLUE, |
| | | [TaskStatus.Carrying]: commonColor.BLUE, |
| | | [TaskStatus.Success]: commonColor.NORMAL, |
| | | [TaskStatus.CanCel]: commonColor.FAIL, |
| | | [TaskStatus.Fail]: commonColor.FAIL, |
| | | } |
| | | |
| | | // 任务执行 ws 消息状态 |
| | | export enum TaskProgressStatus { |
| | | Sent = 'sent', // 已下发 |
| | | inProgress = 'in_progress', // 执行中 |
| | | Paused = 'paused', // 暂停 |
| | | Rejected = 'rejected', // 拒绝 |
| | | Canceled = 'canceled', // 取消或终止 |
| | | Timeout = 'timeout', // 超时 |
| | | Failed = 'failed', // 失败 |
| | | OK = 'ok', // 上传成功 |
| | | } |
| | | |
| | | // 任务进度消息 |
| | | export interface TaskProgressInfo { |
| | | bid: string, |
| | | output:{ |
| | | ext: { |
| | | current_waypoint_index: number, |
| | | media_count: number // 媒体文件 |
| | | }, |
| | | progress:{ |
| | | current_step: number, |
| | | percent: number |
| | | }, |
| | | status: TaskProgressStatus |
| | | }, |
| | | result: number, |
| | | } |
| | | |
| | | // ws status => log status |
| | | export const TaskProgressWsStatusMap = { |
| | | [TaskProgressStatus.Sent]: TaskStatus.Carrying, |
| | | [TaskProgressStatus.inProgress]: TaskStatus.Carrying, |
| | | [TaskProgressStatus.Rejected]: TaskStatus.Fail, |
| | | [TaskProgressStatus.OK]: TaskStatus.Success, |
| | | [TaskProgressStatus.Failed]: TaskStatus.Fail, |
| | | [TaskProgressStatus.Canceled]: TaskStatus.CanCel, |
| | | [TaskProgressStatus.Timeout]: TaskStatus.Fail, |
| | | [TaskProgressStatus.Paused]: TaskStatus.Wait, |
| | | } |
| | |
| | | // 航线类型 |
| | | export enum WaylineType { |
| | | NormalWaypointWayline = 0, // 普通航点航线 |
| | | AccurateReshootingWayline = 1 // 精准复拍航线 |
| | | } |
| | | |
| | | export interface WaylineFile { |
| | | id: string, |
| | | name: string, |
| | | drone_model_key: any, |
| | | payload_model_keys: string[], |
| | | template_types: number[], |
| | | template_types: WaylineType[], |
| | | update_time: number, |
| | | user_name: string, |
| | | } |
| | | |
| | | export interface TaskExt { |
| | | current_waypoint_index: number, |
| | | media_count: number, |
| | | } |
| | | |
| | | export interface TaskProgress { |
| | | current_step: number, |
| | | percent: number, |
| | | } |
| | | |
| | | export interface TaskInfo { |
| | | status: string, |
| | | progress: TaskProgress, |
| | | ext: TaskExt, |
| | | } |
| | | |
| | | export enum ETaskStatus { |
| | | OK = 'ok', |
| | | FAILED = 'failed' |
| | | } |
| | |
| | | import { DroneBatteryModeEnum, DroneBatteryStateEnum } from './../types/airport-tsa'; |
| | | import { DeviceInfoType } from '/@/types/device' |
| | | import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
| | | import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum } from '/@/types/airport-tsa' |
| | | import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum, AlarmModeEnum, BatteryStoreModeEnum } from '/@/types/airport-tsa' |
| | | import { getBytesObject } from './bytes' |
| | | import { DEFAULT_PLACEHOLDER } from './constants' |
| | | |
| | |
| | | droneFormat(cmdItem, device) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关 |
| | | getSupplementLightState(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 声光报警 |
| | | getAlarmState(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养 |
| | | getBatteryStoreMode(cmdItem, dock) |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养 |
| | | getDroneBatteryMode(cmdItem, dock) |
| | | } |
| | | }) |
| | | } |
| | |
| | | if (cmdItem.cmdKey !== DeviceCmd.SupplementLightClose) { |
| | | exchangeDeviceCmd(cmdItem) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 声光报警 |
| | | function getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const alarmState = airportProperties?.alarm_state |
| | | if (alarmState === AlarmModeEnum.CLOSE) { |
| | | cmdItem.operateText = DeviceCmdStatusText.AlarmStateCloseBtnText |
| | | cmdItem.status = DeviceCmdStatusText.AlarmStateCloseNormalText |
| | | cmdItem.action = AlarmModeEnum.OPEN |
| | | } else if (alarmState === AlarmModeEnum.OPEN) { |
| | | cmdItem.operateText = DeviceCmdStatusText.AlarmStateOpenBtnText |
| | | cmdItem.status = DeviceCmdStatusText.AlarmStateOpenNormalText |
| | | cmdItem.action = AlarmModeEnum.CLOSE |
| | | } |
| | | } |
| | | |
| | | // 机场电池模式 |
| | | function getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const batteryStoreMode = airportProperties?.battery_store_mode |
| | | if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_PLAN_STORE) { |
| | | cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModePlanBtnText |
| | | cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanNormalText |
| | | cmdItem.action = BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE |
| | | } else if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) { |
| | | cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModeEmergencyBtnText |
| | | cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyNormalText |
| | | cmdItem.action = BatteryStoreModeEnum.BATTERY_PLAN_STORE |
| | | } |
| | | } |
| | | |
| | | // 飞行器电池保养 |
| | | function getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) { |
| | | const maintenanceState = airportProperties?.drone_battery_maintenance_info?.maintenance_state |
| | | if (maintenanceState === DroneBatteryStateEnum.MaintenanceInProgress) { |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeCloseBtnText |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText |
| | | cmdItem.action = DroneBatteryModeEnum.CLOSE |
| | | cmdItem.disabled = false |
| | | } else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) { |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText |
| | | cmdItem.action = DroneBatteryModeEnum.OPEN |
| | | cmdItem.disabled = true |
| | | } else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) { |
| | | cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText |
| | | cmdItem.action = DroneBatteryModeEnum.OPEN |
| | | cmdItem.disabled = false |
| | | } |
| | | } |
| | | |
| | |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 机场声光报警 |
| | | if (cmdItem.action === AlarmModeEnum.CLOSE) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.action === AlarmModeEnum.OPEN) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养 |
| | | if (cmdItem.action === BatteryStoreModeEnum.BATTERY_PLAN_STORE) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.action === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.loading = false |
| | | } |
| | | } |
| | | } else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养 |
| | | if (cmdItem.action === DroneBatteryModeEnum.OPEN) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText |
| | | // cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText |
| | | // cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText |
| | | // cmdItem.loading = false |
| | | } |
| | | } else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) { |
| | | if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText |
| | | cmdItem.loading = true |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText |
| | | cmdItem.loading = false |
| | | } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
| | | cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText |
| | | cmdItem.loading = false |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }) |
| New file |
| | |
| | | import { DeviceInfoType } from '/@/types/device' |
| | | import { DeviceSettingType, DeviceSettingKeyEnum, DistanceLimitStatusEnum, ObstacleAvoidanceStatusEnum, DeviceSettingFormModel, NightLightsStateEnum } from '/@/types/device-setting' |
| | | import { DEFAULT_PLACEHOLDER } from './constants' |
| | | import { isNil } from 'lodash' |
| | | |
| | | const Unit_M = ' m' |
| | | |
| | | /** |
| | | * 根据osd 更新信息 |
| | | * @param deviceSetting |
| | | * @param deviceInfo |
| | | * @returns |
| | | */ |
| | | export function updateDeviceSettingInfoByOsd (deviceSetting: DeviceSettingType, deviceInfo: DeviceInfoType) { |
| | | const { device, dock, gateway } = deviceInfo || {} |
| | | if (!deviceSetting) { |
| | | return |
| | | } |
| | | // 夜航灯 |
| | | let nightLightsState = '' as any |
| | | if (isNil(device?.night_lights_state)) { |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER |
| | | nightLightsState = DEFAULT_PLACEHOLDER |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = true |
| | | nightLightsState = device?.night_lights_state |
| | | if (nightLightsState === NightLightsStateEnum.CLOSE) { |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '关闭' |
| | | } else if (nightLightsState === NightLightsStateEnum.OPEN) { |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '开启' |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER |
| | | } |
| | | } |
| | | deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].trueValue = nightLightsState |
| | | |
| | | // 限高 |
| | | let heightLimit = device?.height_limit as any |
| | | if (isNil(heightLimit) || heightLimit === 0) { |
| | | heightLimit = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = false |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = true |
| | | } |
| | | deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].trueValue = heightLimit |
| | | deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value = heightLimit + Unit_M |
| | | |
| | | // 限远 |
| | | let distanceLimitStatus = '' as any |
| | | if (isNil(device?.distance_limit_status?.state)) { |
| | | distanceLimitStatus = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = true |
| | | distanceLimitStatus = device?.distance_limit_status?.state |
| | | if (distanceLimitStatus === DistanceLimitStatusEnum.UNSET) { |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = '关闭' |
| | | } else if (distanceLimitStatus === DistanceLimitStatusEnum.SET) { |
| | | const distanceLimit = device?.distance_limit_status?.distance_limit |
| | | if (distanceLimit) { |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = distanceLimit + Unit_M |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER |
| | | } |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER |
| | | } |
| | | } |
| | | deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].trueValue = distanceLimitStatus |
| | | |
| | | // 避障 |
| | | if (isNil(device?.obstacle_avoidance)) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER |
| | | } else { |
| | | const { horizon, upside, downside } = device.obstacle_avoidance || {} |
| | | if (isNil(horizon)) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false |
| | | if (horizon === ObstacleAvoidanceStatusEnum.CLOSE) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '关闭' |
| | | } else if (horizon === ObstacleAvoidanceStatusEnum.OPEN) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '开启' |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER |
| | | } |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = horizon |
| | | } |
| | | |
| | | if (isNil(upside)) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false |
| | | if (upside === ObstacleAvoidanceStatusEnum.CLOSE) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '关闭' |
| | | } else if (upside === ObstacleAvoidanceStatusEnum.OPEN) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '开启' |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER |
| | | } |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = upside |
| | | } |
| | | |
| | | if (isNil(downside)) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false |
| | | if (downside === ObstacleAvoidanceStatusEnum.CLOSE) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '关闭' |
| | | } else if (downside === ObstacleAvoidanceStatusEnum.OPEN) { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '开启' |
| | | } else { |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER |
| | | } |
| | | deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = downside |
| | | } |
| | | } |
| | | return deviceSetting |
| | | } |
| | | |
| | | // 更新formModel |
| | | export function updateDeviceSettingFormModelByOsd (deviceSettingFormModelFromOsd: DeviceSettingFormModel, deviceInfo: DeviceInfoType) { |
| | | const { device, dock, gateway } = deviceInfo || {} |
| | | if (!deviceSettingFormModelFromOsd) { |
| | | return |
| | | } |
| | | // 夜航灯 |
| | | const nightLightsState = device?.night_lights_state as any |
| | | if (!isNil(nightLightsState) && nightLightsState === NightLightsStateEnum.OPEN) { |
| | | deviceSettingFormModelFromOsd.nightLightsState = true |
| | | } else { |
| | | deviceSettingFormModelFromOsd.nightLightsState = false |
| | | } |
| | | |
| | | // 限高 |
| | | const heightLimit = device?.height_limit as any |
| | | if (isNil(heightLimit) || heightLimit === 0) { |
| | | deviceSettingFormModelFromOsd.heightLimit = 20 |
| | | } else { |
| | | deviceSettingFormModelFromOsd.heightLimit = heightLimit |
| | | } |
| | | |
| | | // 限远 |
| | | const distanceLimitStatus = device?.distance_limit_status?.state as any |
| | | if (!isNil(distanceLimitStatus) && distanceLimitStatus === DistanceLimitStatusEnum.SET) { |
| | | deviceSettingFormModelFromOsd.distanceLimitStatus.state = true |
| | | deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = device?.distance_limit_status?.distance_limit || 15 |
| | | } else { |
| | | deviceSettingFormModelFromOsd.distanceLimitStatus.state = false |
| | | deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = 15 |
| | | } |
| | | |
| | | // 避障 |
| | | if (isNil(device?.obstacle_avoidance)) { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false |
| | | } else { |
| | | const { horizon, upside, downside } = device.obstacle_avoidance || {} |
| | | if (!isNil(horizon) && horizon === ObstacleAvoidanceStatusEnum.OPEN) { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = true |
| | | } else { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false |
| | | } |
| | | if (!isNil(upside) && upside === ObstacleAvoidanceStatusEnum.OPEN) { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = true |
| | | } else { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false |
| | | } |
| | | if (!isNil(downside) && downside === ObstacleAvoidanceStatusEnum.OPEN) { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = true |
| | | } else { |
| | | deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false |
| | | } |
| | | } |
| | | return deviceSettingFormModelFromOsd |
| | | } |
| New file |
| | |
| | | export interface ErrorCode { |
| | | code: number; |
| | | msg: string; |
| | | } |
| | | |
| | | /** |
| | | * 根据错误码翻译错误信息 |
| | | * @param code |
| | | * @param errorMsg |
| | | * @returns |
| | | */ |
| | | export function getErrorMessage (code: number, errorMsg?: string): string { |
| | | const errorInfo = ERROR_CODE.find((item: ErrorCode) => item.code === code) |
| | | return errorInfo ? errorInfo.msg : errorMsg || 'Server error' |
| | | } |
| | | |
| | | // 暂时只添加航线错误 |
| | | export const ERROR_CODE = [ |
| | | { |
| | | code: 314001, |
| | | msg: 'The issued route task url is empty', |
| | | }, |
| | | { |
| | | code: 314002, |
| | | msg: 'The issued route task md5 is empty', |
| | | }, |
| | | { |
| | | code: 314003, |
| | | msg: 'MissionID is invalid', |
| | | }, |
| | | { |
| | | code: 314004, |
| | | msg: 'Failed to send flight route task from cloud', |
| | | }, |
| | | { |
| | | code: 314005, |
| | | msg: 'Route md5 check failed', |
| | | }, |
| | | { |
| | | code: 314006, |
| | | msg: 'Timeout waiting for aircraft to upload route (waiting for gs_state)', |
| | | }, |
| | | { |
| | | code: 314007, |
| | | msg: 'Failed to upload route to aircraft', |
| | | }, |
| | | { |
| | | code: 314008, |
| | | msg: 'Timeout waiting for the aircraft to enter the route executable state', |
| | | }, |
| | | { |
| | | code: 314009, |
| | | msg: 'Failed to open route mission', |
| | | }, |
| | | { |
| | | code: 314010, |
| | | msg: 'Route execution failed', |
| | | }, |
| | | { |
| | | code: 316001, |
| | | msg: 'Failed to set alternate point', |
| | | }, |
| | | { |
| | | code: 316002, |
| | | msg: 'Alternate safety transfer altitude equipment failed', |
| | | }, |
| | | { |
| | | code: 316003, |
| | | msg: 'Failed to set takeoff altitude. Remarks: The default safe takeoff height of the aircraft set by the current DJI Dock is: 1.8', |
| | | }, |
| | | { |
| | | code: 316004, |
| | | msg: 'Failed to set runaway behavior', |
| | | }, |
| | | { |
| | | code: 316005, |
| | | msg: 'Aircraft RTK convergence failed', |
| | | }, |
| | | { |
| | | code: 316013, |
| | | msg: 'DJI Dock Moved', |
| | | }, |
| | | { |
| | | code: 316015, |
| | | msg: 'The aircraft RTK convergence position is too far from the DJI Dock', |
| | | }, |
| | | { |
| | | code: 316007, |
| | | msg: 'Set parameter timeout while waiting for aircraft to be ready', |
| | | }, |
| | | { |
| | | code: 316008, |
| | | msg: 'Failed to gain control of aircraft', |
| | | }, |
| | | { |
| | | code: 316009, |
| | | msg: 'Aircraft power is low', |
| | | }, |
| | | { |
| | | code: 316010, |
| | | msg: 'After power on, the aircraft is not connected for more than 2 minutes (flight control OSD reception timeout)', |
| | | }, |
| | | { |
| | | code: 316011, |
| | | msg: 'Landing Position Offset', |
| | | }, |
| | | |
| | | { |
| | | code: 317001, |
| | | msg: 'Failed to get the number of media files', |
| | | }, |
| | | |
| | | { |
| | | code: 319001, |
| | | msg: 'The task center is not currently idle', |
| | | }, |
| | | { |
| | | code: 319002, |
| | | msg: 'dronenest communication timeout', |
| | | }, |
| | | { |
| | | code: 319999, |
| | | msg: 'Unknown error, e.g. restart after crash', |
| | | }, |
| | | { |
| | | code: 321000, |
| | | msg: 'Route execution failed, unknown error', |
| | | }, |
| | | { |
| | | code: 321257, |
| | | msg: 'The route has already started and cannot be started again', |
| | | }, |
| | | { |
| | | code: 321258, |
| | | msg: 'The route cannot be interrupted in this state', |
| | | }, |
| | | { |
| | | code: 321259, |
| | | msg: 'The route has not started and cannot end the route', |
| | | }, |
| | | { |
| | | code: 321513, |
| | | msg: 'Reach the height limit', |
| | | }, |
| | | { |
| | | code: 321514, |
| | | msg: 'Reach the limit', |
| | | }, |
| | | { |
| | | code: 321515, |
| | | msg: 'Crossing the restricted flight zone', |
| | | }, |
| | | { |
| | | code: 321516, |
| | | msg: 'Low limit', |
| | | }, |
| | | |
| | | { |
| | | code: 321517, |
| | | msg: 'Obstacle Avoidance', |
| | | }, |
| | | { |
| | | code: 321769, |
| | | msg: 'Weak GPS signal', |
| | | }, |
| | | { |
| | | code: 321770, |
| | | msg: 'The current gear state cannot be executed, B control seizes the control, and the gear is switched', |
| | | }, |
| | | { |
| | | code: 321771, |
| | | msg: 'The home point is not refreshed', |
| | | }, |
| | | { |
| | | code: 321772, |
| | | msg: 'The current battery is too low to start the task', |
| | | }, |
| | | { |
| | | code: 321773, |
| | | msg: 'Low battery return', |
| | | }, |
| | | { |
| | | code: 321776, |
| | | msg: 'RTK not ready', |
| | | }, |
| | | { |
| | | code: 321778, |
| | | msg: 'The aircraft is idling on the ground and is not allowed to start the route, thinking that the user is not ready.', |
| | | }, |
| | | { |
| | | code: 322282, |
| | | msg: 'User interrupt (B control takeover)', |
| | | }, |
| | | { |
| | | code: 514100, |
| | | msg: 'Command not supported', |
| | | }, |
| | | { |
| | | code: 514101, |
| | | msg: 'Failed to close putter', |
| | | }, |
| | | { |
| | | code: 514102, |
| | | msg: 'Failed to release putter', |
| | | }, |
| | | { |
| | | code: 514103, |
| | | msg: 'Aircraft battery is low', |
| | | }, |
| | | { |
| | | code: 514104, |
| | | msg: 'Failed to start charging', |
| | | }, |
| | | { |
| | | code: 514105, |
| | | msg: 'Failed to stop charging', |
| | | }, |
| | | { |
| | | code: 514106, |
| | | msg: 'Failed to restart the aircraft', |
| | | }, |
| | | { |
| | | code: 514107, |
| | | msg: 'Failed to open hatch', |
| | | }, |
| | | { |
| | | code: 514108, |
| | | msg: 'Failed to close hatch', |
| | | }, |
| | | { |
| | | code: 514109, |
| | | msg: 'Failed to open the plane', |
| | | }, |
| | | { |
| | | code: 514110, |
| | | msg: 'Failed to close the plane', |
| | | }, |
| | | { |
| | | code: 514111, |
| | | msg: 'The aircraft failed to turn on the slow-rotating propeller in the cabin', |
| | | }, |
| | | { |
| | | code: 514112, |
| | | msg: 'The aircraft failed to stop the slow-rotating propeller in the cabin', |
| | | }, |
| | | { |
| | | code: 514113, |
| | | msg: 'Failed to establish wired connection with aircraft', |
| | | }, |
| | | { |
| | | code: 514114, |
| | | msg: 'Get aircraft power status, command timed out, or return code is not 0', |
| | | }, |
| | | { |
| | | code: 514116, |
| | | msg: 'The DJI Dock is busy and other control orders are being executed at the DJI Dock', |
| | | }, |
| | | { |
| | | code: 514117, |
| | | msg: 'Check hatch status failed', |
| | | }, |
| | | { |
| | | code: 514118, |
| | | msg: 'Check putter status failed', |
| | | }, |
| | | { |
| | | code: 514120, |
| | | msg: 'DJI Dock and aircraft SDR connection failed', |
| | | }, |
| | | { |
| | | code: 514121, |
| | | msg: 'Emergency stop state', |
| | | }, |
| | | { |
| | | code: 514122, |
| | | msg: 'Failed to get the charging status of the aircraft (Failed to get the charging status, the flight mission can be executed, affecting charging and remote troubleshooting)', |
| | | }, |
| | | { |
| | | code: 514123, |
| | | msg: 'Unable to power on due to low battery', |
| | | }, |
| | | { |
| | | code: 514124, |
| | | msg: 'Failed to get battery information', |
| | | }, |
| | | { |
| | | code: 514125, |
| | | msg: 'The battery is fully charged and cannot be charged', |
| | | }, |
| | | { |
| | | code: 514145, |
| | | msg: 'Can not work while debugging on site', |
| | | }, |
| | | { |
| | | code: 514146, |
| | | msg: 'Unable to work in remote debugging', |
| | | }, |
| | | { |
| | | code: 514147, |
| | | msg: 'Unable to work in upgrade state', |
| | | }, |
| | | { |
| | | code: 514148, |
| | | msg: 'Unable to execute new tasks in job state', |
| | | }, |
| | | { |
| | | code: 514150, |
| | | msg: 'DJI Dock is automatically restarting', |
| | | }, |
| | | ] |