sean.zhou
2022-12-12 83d4c07a67e7588e3938237cfad9eaa6be5ea6c5
initial v1.3.1
18 files modified
4 files added
1 files deleted
885 ■■■■■ changed files
src/api/manage.ts 22 ●●●●● patch | view | raw | blame | history
src/api/media.ts 3 ●●●● patch | view | raw | blame | history
src/api/wayline.ts 10 ●●●●● patch | view | raw | blame | history
src/components/common/topbar.vue 3 ●●●● patch | view | raw | blame | history
src/components/devices/DeviceFirmwareStatus.vue 61 ●●●●● patch | view | raw | blame | history
src/components/task/TaskPanel.vue 115 ●●●● patch | view | raw | blame | history
src/components/task/use-format-task.ts 40 ●●●●● patch | view | raw | blame | history
src/components/task/use-task-progress-event.ts 19 ●●●●● patch | view | raw | blame | history
src/components/task/use-task-ws-event.ts 43 ●●●●● patch | view | raw | blame | history
src/event-bus/index.ts 2 ●●● patch | view | raw | blame | history
src/pages/page-web/projects/Firmwares.vue 321 ●●●●● patch | view | raw | blame | history
src/pages/page-web/projects/wayline.vue 1 ●●●● patch | view | raw | blame | history
src/pages/page-web/projects/workspace.vue 6 ●●●●● patch | view | raw | blame | history
src/router/index.ts 5 ●●●●● patch | view | raw | blame | history
src/store/index.ts 5 ●●●● patch | view | raw | blame | history
src/types/airport-tsa.ts 18 ●●●●● patch | view | raw | blame | history
src/types/device-cmd.ts 25 ●●●● patch | view | raw | blame | history
src/types/device-firmware.ts 34 ●●●●● patch | view | raw | blame | history
src/types/device.ts 10 ●●●●● patch | view | raw | blame | history
src/types/enums.ts 7 ●●●● patch | view | raw | blame | history
src/types/task.ts 35 ●●●●● patch | view | raw | blame | history
src/utils/device-cmd.ts 95 ●●●● patch | view | raw | blame | history
tsconfig.json 5 ●●●●● patch | view | raw | blame | history
src/api/manage.ts
@@ -1,3 +1,4 @@
import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request'
import { Device } from '/@/types/device'
@@ -150,7 +151,7 @@
}
export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: string, pagination: IPage): Promise<IListWorkspaceResponse<any>> {
  let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +
  let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +
    `&level=${body.level ?? ''}&begin_time=${body.begin_time ?? ''}&end_time=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}`
  body.sns.forEach((sn: string) => {
    if (sn !== '') {
@@ -165,4 +166,23 @@
  const url = `${HTTP_PREFIX}/live/streams/switch`
  const result = await request.post(url, body)
  return result.data
}
export const getFirmwares = async function (workspace_id: string, page: IPage, body: FirmwareQueryParam): Promise<IListWorkspaceResponse<Firmware>> {
  const url = `${HTTP_PREFIX}/workspaces/${workspace_id}/firmwares?page=${page.page}&page_size=${page.page_size}` +
    `&device_name=${body.device_name}&product_version=${body.product_version}&status=${body.firmware_status ?? ''}`
  const result = await request.get(url)
  return result.data
}
export const importFirmareFile = async function (workspaceId: string, param: FormData): Promise<IWorkspaceResponse<any>> {
  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/file/upload`
  const result = await request.post(url, param)
  return result.data
}
export const changeFirmareStatus = async function (workspaceId: string, firmwareId: string, param: {status: boolean}): Promise<IWorkspaceResponse<any>> {
  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/${firmwareId}`
  const result = await request.put(url, param)
  return result.data
}
src/api/media.ts
@@ -15,12 +15,11 @@
  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
  }
src/api/wayline.ts
@@ -72,6 +72,9 @@
  code: number, // 错误码
  rth_altitude: number // 相对机场返航高度 20 - 500
  out_of_control_action: OutOfControlAction // 失控动作
  media_count: number // 媒体数量
  uploading:boolean // 是否正在上传媒体
  uploaded_count: number // 已上传媒体数量
}
// Get Wayline Jobs
@@ -104,3 +107,10 @@
  })
  return result.data
}
// 媒体立即上传
export const uploadMediaFileNow = async function (workspaceId: string, jobId: string): Promise<IWorkspaceResponse<{}>> {
  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${jobId}/media-highest`
  const result = await request.post(url)
  return result.data
}
src/components/common/topbar.vue
@@ -63,7 +63,8 @@
const options = [
  { key: 0, label: ERouterName.WORKSPACE.charAt(0).toUpperCase() + ERouterName.WORKSPACE.substr(1), path: '/' + ERouterName.WORKSPACE },
  { key: 1, label: ERouterName.MEMBERS.charAt(0).toUpperCase() + ERouterName.MEMBERS.substr(1), path: '/' + ERouterName.MEMBERS },
  { key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES }
  { key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES },
  { key: 3, label: ERouterName.FIRMWARES.charAt(0).toUpperCase() + ERouterName.FIRMWARES.substr(1), path: '/' + ERouterName.FIRMWARES },
]
const selected = ref<string>(root.$route.path)
src/components/devices/DeviceFirmwareStatus.vue
New file
@@ -0,0 +1,61 @@
<template>
<div>
  <span class="status-tag pointer">
    <a-popconfirm
      :title="getTitle()"
      ok-text="Yes"
      cancel-text="No"
      placement="left"
      @confirm="onFirmwareStatusClick(firmware)"
    >
      <a-tag :color="firmware.firmware_status ? commonColor.NORMAL : commonColor.FAIL"
          :class="firmware.firmware_status ? 'border-corner ' : 'status-disable border-corner'">
        {{ getText(firmware.firmware_status) }}
      </a-tag>
    </a-popconfirm>
  </span>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, watch, computed } from 'vue'
import { changeFirmareStatus } from '/@/api/manage'
import { ELocalStorageKey } from '/@/types'
import { Firmware, FirmwareStatusEnum } from '/@/types/device-firmware'
import { commonColor } from '/@/utils/color'
const props = defineProps<{
  firmware: Firmware
}>()
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
function getTitle () {
  return `Are you sure to set this firmware to ${getText(!props.firmware.firmware_status)}?`
}
function getText (status: boolean) {
  return status ? FirmwareStatusEnum.TRUE : FirmwareStatusEnum.FALSE
}
function onFirmwareStatusClick (record: Firmware) {
  changeFirmareStatus(workspaceId, record.firmware_id, { status: !record.firmware_status }).then((res) => {
    if (res.code === 0) {
      record.firmware_status = !record.firmware_status
    }
  })
}
</script>
<style lang="scss" scoped>
  .status-disable{
    opacity: 0.4;
  }
  .border-corner {
    border-radius: 3px;
  }
  .pointer {
    cursor: pointer;
  }
</style>
src/components/task/TaskPanel.vue
@@ -10,14 +10,6 @@
          <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>
@@ -33,6 +25,32 @@
          </div>
          <div v-if="record.status === TaskStatus.Carrying">
            <a-progress :percent="record.progress || 0" />
          </div>
        </div>
      </template>
      <!-- 任务类型 -->
      <template #taskType="{ record }">
        <div>{{ formatTaskType(record) }}</div>
      </template>
      <!-- 失控动作 -->
      <template #lostAction="{ record }">
        <div>{{ formatLostAction(record) }}</div>
      </template>
     <!-- 媒体上传状态 -->
      <template #media_upload="{ record }">
        <div>
          <div class="flex-display flex-align-center">
            <span class="circle-icon" :style="{backgroundColor: formatMediaTaskStatus(record).color}"></span>
            {{ formatMediaTaskStatus(record).text }}
          </div>
          <div class="pl15">
            {{ formatMediaTaskStatus(record).number }}
            <a-tooltip v-if="formatMediaTaskStatus(record).status === MediaStatus.ToUpload" placement="bottom" arrow-point-at-center >
              <template #title>
              <div>Upload now</div>
              </template>
              <UploadOutlined class="ml5" :style="{color: commonColor.BLUE, fontSize: '16px' }"  @click="onUploadMediaFileNow(record.job_id)"/>
            </a-tooltip>
          </div>
        </div>
      </template>
@@ -60,15 +78,15 @@
import { TableState } from 'ant-design-vue/lib/table/interface'
import { onMounted } from 'vue'
import { IPage } from '/@/api/http/type'
import { deleteTask, getWaylineJobs, Task } from '/@/api/wayline'
import { deleteTask, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'
import { useMyStore } from '/@/store'
import { ELocalStorageKey } from '/@/types/enums'
import { useFormatTask } from './use-format-task'
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap } from '/@/types/task'
import { useTaskProgressEvent } from './use-task-progress-event'
import { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap, MediaStatus, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'
import { useTaskWsEvent } from './use-task-ws-event'
import { getErrorMessage } from '/@/utils/error-code/index'
import { commonColor } from '/@/utils/color'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { ExclamationCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
const store = useMyStore()
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
@@ -91,38 +109,41 @@
  {
    title: 'Planned/Actual Time',
    dataIndex: 'duration',
    width: 180,
    width: 160,
    slots: { customRender: 'duration' },
  },
  {
    title: 'Status',
    key: 'status',
    width: 150,
    slots: { customRender: 'status' }
  },
  {
    title: 'Plan Name',
    dataIndex: 'job_name',
    width: 150,
    ellipsis: true
    width: 100,
  },
  {
    title: 'Type',
    dataIndex: 'taskType',
    width: 120,
    width: 100,
    slots: { customRender: 'taskType' },
  },
  {
    title: 'Flight Route Name',
    dataIndex: 'file_name',
    width: 150,
    ellipsis: true
    width: 100,
  },
  {
    title: 'Dock Name',
    dataIndex: 'dock_name',
    width: 120,
    width: 100,
    ellipsis: true
  },
  {
    title: 'RTH Altitude Relative to Dock (m)',
    dataIndex: 'rth_altitude',
    width: 120,
    ellipsis: true
  },
  {
    title: 'Lost Action',
@@ -136,10 +157,10 @@
    width: 120,
  },
  {
    title: 'Status',
    key: 'status',
    width: 200,
    slots: { customRender: 'status' }
    title: 'Media File Upload',
    key: 'media_upload',
    width: 160,
    slots: { customRender: 'media_upload' }
  },
  {
    title: 'Action',
@@ -153,7 +174,7 @@
  data: [] as Task[]
})
const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus } = useFormatTask()
const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus, formatMediaTaskStatus } = useFormatTask()
// 设备任务执行进度更新
function onTaskProgressWs (data: TaskProgressInfo) {
@@ -174,11 +195,44 @@
  }
}
// 媒体上传进度更新
function onTaskMediaProgressWs (data: MediaStatusProgressInfo) {
  const { media_count: mediaCount, uploaded_count: uploadedCount, job_id: jobId } = data
  if (isNaN(mediaCount) || isNaN(uploadedCount) || !jobId) {
    return
  }
  const taskItem = plansData.data.find(task => task.job_id === jobId)
  if (!taskItem) return
  if (mediaCount === uploadedCount) {
    taskItem.uploading = false
  } else {
    taskItem.uploading = true
  }
  taskItem.media_count = mediaCount
  taskItem.uploaded_count = uploadedCount
}
function onoTaskMediaHighestPriorityWS (data: TaskMediaHighestPriorityProgressInfo) {
  const { pre_job_id: preJobId, job_id: jobId } = data
  const preTaskItem = plansData.data.find(task => task.job_id === preJobId)
  const taskItem = plansData.data.find(task => task.job_id === jobId)
  if (preTaskItem) {
    preTaskItem.uploading = false
  }
  if (taskItem) {
    taskItem.uploading = true
  }
}
function getCodeMessage (code: number) {
  return getErrorMessage(code) + `(code: ${code})`
}
useTaskProgressEvent(onTaskProgressWs)
useTaskWsEvent({
  onTaskProgressWs,
  onTaskMediaProgressWs,
  onoTaskMediaHighestPriorityWS,
})
onMounted(() => {
  getPlans()
@@ -211,6 +265,15 @@
    getPlans()
  }
}
// 立即上传媒体
async function onUploadMediaFileNow (jobId: string) {
  const { code } = await uploadMediaFileNow(workspaceId, jobId)
  if (code === 0) {
    message.success('Upload Media File successfully')
    getPlans()
  }
}
</script>
<style lang="scss" scoped>
src/components/task/use-format-task.ts
@@ -1,6 +1,7 @@
import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'
import { Task } from '/@/api/wayline'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap } from '/@/types/task'
import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap, MediaStatusMap, MediaStatusColorMap, MediaStatus } from '/@/types/task'
import { isNil } from 'lodash'
export function useFormatTask () {
  function formatTaskType (task: Task) {
@@ -26,10 +27,47 @@
    return statusObj
  }
  function formatMediaTaskStatus (task: Task) {
    const statusObj = {
      text: '',
      color: '',
      number: '',
      status: MediaStatus.Empty,
    }
    const { media_count, uploaded_count, uploading } = task
    if (isNil(media_count) || isNaN(media_count)) {
      return statusObj
    }
    const expectedFileCount = media_count || 0
    const uploadedFileCount = uploaded_count || 0
    if (media_count === 0) {
      statusObj.text = MediaStatusMap[MediaStatus.Empty]
      statusObj.color = MediaStatusColorMap[MediaStatus.Empty]
    } else if (media_count === uploaded_count) {
      statusObj.text = MediaStatusMap[MediaStatus.Success]
      statusObj.color = MediaStatusColorMap[MediaStatus.Success]
      statusObj.number = `(${uploadedFileCount}/${expectedFileCount})`
      statusObj.status = MediaStatus.Success
    } else {
      if (uploading) {
        statusObj.text = MediaStatusMap[MediaStatus.Uploading]
        statusObj.color = MediaStatusColorMap[MediaStatus.Uploading]
        statusObj.status = MediaStatus.Uploading
      } else {
        statusObj.text = MediaStatusMap[MediaStatus.ToUpload]
        statusObj.color = MediaStatusColorMap[MediaStatus.ToUpload]
        statusObj.status = MediaStatus.ToUpload
      }
      statusObj.number = `(${uploadedFileCount}/${expectedFileCount})`
    }
    return statusObj
  }
  return {
    formatTaskType,
    formatTaskTime,
    formatLostAction,
    formatTaskStatus,
    formatMediaTaskStatus,
  }
}
src/components/task/use-task-progress-event.ts
File was deleted
src/components/task/use-task-ws-event.ts
New file
@@ -0,0 +1,43 @@
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { TaskProgressInfo, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'
import { EBizCode } from '/@/types'
export interface UseTaskWsEventParams {
  onTaskProgressWs: (data: TaskProgressInfo) => void,
  onTaskMediaProgressWs: (data: MediaStatusProgressInfo) => void
  onoTaskMediaHighestPriorityWS: (data: TaskMediaHighestPriorityProgressInfo) => void
}
export function useTaskWsEvent (funcs: UseTaskWsEventParams): void {
  function handleTaskWsEvent (payload: any) {
    if (!payload) {
      return
    }
    switch (payload.biz_code) {
      case EBizCode.FlightTaskProgress: {
        funcs?.onTaskProgressWs(payload.data)
        break
      }
      case EBizCode.FlightTaskMediaProgress: {
        funcs?.onTaskMediaProgressWs(payload.data)
        break
      }
      case EBizCode.FlightTaskMediaHighestPriority: {
        funcs?.onoTaskMediaHighestPriorityWS(payload.data)
        break
      }
    }
    // eslint-disable-next-line no-unused-expressions
    // console.log('payload', payload.data)
  }
  onMounted(() => {
    EventBus.on('flightTaskWs', handleTaskWsEvent)
  })
  onBeforeUnmount(() => {
    EventBus.off('flightTaskWs', handleTaskWsEvent)
  })
}
src/event-bus/index.ts
@@ -3,7 +3,7 @@
type Events = {
  deviceUpgrade: any; // 设备升级
  deviceLogUploadProgress: any // 设备日志上传
  deviceTaskProgress: any // 设备任务进度
  flightTaskWs: any // 机场任务消息
};
const emitter: Emitter<Events> = mitt<Events>()
src/pages/page-web/projects/Firmwares.vue
New file
@@ -0,0 +1,321 @@
<template>
  <div class="ml20 mt20 mr20 flex-row flex-align-center flex-justify-between">
    <div class="flex-row">
      <a-button type="primary" @click="sVisible = true">
        Click to Upload
      </a-button>
      <a-modal :visible="sVisible"
         title="Import Firmware File"
         :closable="false"
         @cancel="onCancel"
         @ok="uploadFile"
         centered>
         <a-form :rules="rules" ref="formRef" :model="uploadParam" :label-col="{ span: 6 }">
          <a-form-item name="status" label="Avaliable" required>
            <a-switch v-model:checked="uploadParam.status" />
          </a-form-item>
          <a-form-item name="device_name" label="Device Name" required>
            <a-select
              style="width: 150px"
              v-model:value="uploadParam.device_name">
              <a-select-option
                v-for="item in deviceNameList"
                :key="item.label"
                :value="item.value"
              >
                {{ item.label }}
              </a-select-option>
            </a-select>
          </a-form-item>
          <a-form-item name="release_note" label="Release Note" required>
            <a-textarea v-model:value="uploadParam.release_note" showCount :maxlength="300" />
          </a-form-item>
          <a-form-item label="File" required>
            <a-upload
              :multiple="false"
              :before-upload="beforeUpload"
              :show-upload-list="true"
              :file-list="fileList"
              :remove="removeFile"
             >
              <a-button type="primary">
                <UploadOutlined />
                Import Firmware File
              </a-button>
            </a-upload>
          </a-form-item>
         </a-form>
      </a-modal>
    </div>
    <div class="flex-row">
      <div class="ml5">
        <a-select
          style="width: 150px"
          v-model:value="param.firmware_status"
          @select="getAllFirmwares(pageParam)">
          <a-select-option
            v-for="(key, value) in FirmwareStatusEnum"
            :key="key"
            :value="value"
          >
            {{ key }}
          </a-select-option>
        </a-select>
      </div>
      <div class="ml5">
        <a-select
          style="width: 150px"
          v-model:value="param.device_name"
          @select="getAllFirmwares(pageParam)">
          <a-select-option
            v-for="item in deviceNameList"
            :key="item.label"
            :value="item.value"
          >
            {{ item.label }}
          </a-select-option>
        </a-select>
      </div>
      <div class="ml5">
        <a-input-search
          :enter-button="true"
          v-model:value="param.product_version"
          placeholder="input search verison"
          style="width: 250px"
          @search="getAllFirmwares(pageParam)"/>
      </div>
    </div>
  </div>
  <div class="table flex-display flex-column">
    <a-table :columns="columns" :data-source="data.firmware" :pagination="paginationProp" @change="refreshData" row-key="firmware_id"
     :rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)" :scroll="{ x: '100%', y: 600 }">
      <template v-for="col in ['mqtt_username', 'mqtt_password']" #[col]="{ text, record }" :key="col">
        <div>
          <a-input
            v-if="editableData[record.user_id]"
            v-model:value="editableData[record.user_id][col]"
            style="margin: -5px 0"
          />
          <template v-else>
            {{ text }}
          </template>
        </div>
      </template>
      <template #file_size="{ record }">
        <div>{{ bytesToSize(record.file_size) }}</div>
      </template>
      <template #firmware_status="{ record }">
        <DeviceFirmwareStatus :firmware="record" />
      </template>
      <template v-for="col in ['file_name', 'release_note']" #[col]="{ text }" :key="col">
        <a-tooltip :title="text">
            <span>{{ text }}</span>
        </a-tooltip>
      </template>
    </a-table>
  </div>
</template>
<script lang="ts" setup>
import { message, notification, PaginationProps } from 'ant-design-vue'
import { TableState } from 'ant-design-vue/lib/table/interface'
import { onMounted, reactive, Ref, ref, UnwrapRef } from 'vue'
import { IPage } from '/@/api/http/type'
import { getFirmwares, importFirmareFile } from '/@/api/manage'
import DeviceFirmwareStatus from '/@/components/devices/DeviceFirmwareStatus.vue'
import { ELocalStorageKey } from '/@/types'
import { UploadOutlined } from '@ant-design/icons-vue'
import { Firmware, FirmwareQueryParam, FirmwareStatusEnum, DeviceNameEnum, FirmwareUploadParam } from '/@/types/device-firmware'
import { commonColor } from '/@/utils/color'
import { bytesToSize } from '/@/utils/bytes'
import moment from 'moment'
interface FirmwareData {
  firmware: Firmware[]
}
const columns = [
  { title: 'Model', dataIndex: 'device_name', width: 120, className: 'titleStyle' },
  { title: 'File Name', dataIndex: 'file_name', width: 220, ellipsis: true, className: 'titleStyle', slots: { customRender: 'file_name' } },
  { title: 'Firmware Version', dataIndex: 'product_version', width: 180, className: 'titleStyle' },
  { title: 'File Size', dataIndex: 'file_size', width: 150, className: 'titleStyle', slots: { customRender: 'file_size' } },
  { title: 'Creator', dataIndex: 'username', width: 100, className: 'titleStyle' },
  { title: 'Release Date', dataIndex: 'released_time', width: 160, sorter: (a: Firmware, b: Firmware) => a.released_time.localeCompare(b.released_time), className: 'titleStyle' },
  { title: 'Release Note', dataIndex: 'release_note', width: 300, ellipsis: true, className: 'titleStyle', slots: { customRender: 'release_note' } },
  { title: 'Status', dataIndex: 'firmware_status', width: 100, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
]
const data = reactive<FirmwareData>({
  firmware: []
})
const paginationProp = reactive({
  pageSizeOptions: ['20', '50', '100'],
  showQuickJumper: true,
  showSizeChanger: true,
  pageSize: 50,
  current: 1,
  total: 0
})
const deviceNameList = ref<any[]>([{ label: 'All', value: '' }])
type Pagination = TableState['pagination']
const pageParam: IPage = {
  page: 1,
  total: 0,
  page_size: 50
}
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const param = reactive<FirmwareQueryParam>({
  product_version: '',
  device_name: '',
  firmware_status: FirmwareStatusEnum.NONE
})
onMounted(() => {
  getAllFirmwares(pageParam)
  for (const key in DeviceNameEnum) {
    const value = DeviceNameEnum[key]
    deviceNameList.value.push({ label: value, value: value })
  }
})
function refreshData (page: Pagination) {
  pageParam.page = page?.current!
  pageParam.page_size = page?.pageSize!
  getAllFirmwares(pageParam)
}
function getAllFirmwares (page: IPage) {
  getFirmwares(workspaceId, page, param).then(res => {
    const firmwareList: Firmware[] = res.data.list
    data.firmware = firmwareList
    paginationProp.total = res.data.pagination.total
    paginationProp.current = res.data.pagination.page
  })
}
const sVisible = ref(false)
const uploadParam = reactive<FirmwareUploadParam>({
  device_name: '',
  release_note: '',
  status: true
})
const rules = {
  status: [{ required: true }],
  release_note: [{ required: true, message: 'Please input release note.' }],
  device_name: [{ required: true, message: 'Please select which model this firmware belongs to. Can not be [All].' }]
}
interface FileItem {
  uid: string;
  name?: string;
  status?: string;
  response?: string;
  url?: string;
}
interface FileInfo {
  file: FileItem;
  fileList: FileItem[];
}
const fileList = ref<FileItem[]>([])
function beforeUpload (file: FileItem) {
  if (!file.name || !file.name?.endsWith('.zip')) {
    message.error('Format error. Please select zip file.')
    return false
  }
  fileList.value = [file]
  return false
}
const formRef = ref()
function removeFile (file: FileItem) {
  fileList.value = []
}
function onCancel () {
  formRef.value.resetFields()
  fileList.value = []
  sVisible.value = false
}
const uploadFile = async () => {
  if (fileList.value.length === 0) {
    message.error('Please select at least one file.')
  }
  const uploading: string = 'uploading'
  formRef.value.validate().then(async () => {
    const file: FileItem = fileList.value[0]
    const fileData = new FormData()
    fileData.append('file', file as any, file.name)
    Object.keys(uploadParam).forEach((key) => {
      fileData.append(key, uploadParam[key as keyof FirmwareUploadParam].toString())
    })
    notification.open({
      key: uploading,
      message: `Uploading  ${moment().format()}`,
      description: `[${file.name}] is uploading... `,
      duration: null
    })
    importFirmareFile(workspaceId, fileData).then((res) => {
      if (res.code === 0) {
        notification.success({
          message: `Uploaded  ${moment().format()}`,
          description: `[${file.name}] file uploaded successfully.`,
          duration: null
        })
        getAllFirmwares(pageParam)
      } else {
        notification.error({
          message: `Failed to upload [${file.name}]. Check and try again.`,
          description: `Error message: ${res.message} ${moment().format()}`,
          style: { color: commonColor.FAIL },
          duration: null,
        })
      }
    }).finally(() => {
      notification.close(uploading)
    })
    fileList.value = []
    formRef.value.resetFields()
    sVisible.value = false
  })
}
</script>
<style>
.table {
    background-color: white;
    margin: 20px;
    padding: 20px;
    height: 88vh;
}
.table-striped {
  background-color: #f7f9fa;
}
.ant-table {
  border-top: 1px solid rgb(0,0,0,0.06);
  border-bottom: 1px solid rgb(0,0,0,0.06);
}
.ant-table-tbody tr td {
  border: 0;
}
.ant-table td {
  white-space: nowrap;
}
.ant-table-thead tr th {
  background: white !important;
  border: 0;
}
th.ant-table-selection-column {
  background-color: white !important;
}
.ant-table-header {
  background-color: white !important;
}
</style>
src/pages/page-web/projects/wayline.vue
@@ -226,7 +226,6 @@
  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)
src/pages/page-web/projects/workspace.vue
@@ -71,8 +71,10 @@
      store.commit('SET_DEVICE_OFFLINE', payload.data)
      break
    }
    case EBizCode.FlightTaskProgress: {
      EventBus.emit('deviceTaskProgress', payload)
    case EBizCode.FlightTaskProgress:
    case EBizCode.FlightTaskMediaProgress:
    case EBizCode.FlightTaskMediaHighestPriority: {
      EventBus.emit('flightTaskWs', payload)
      break
    }
    case EBizCode.DeviceHms: {
src/router/index.ts
@@ -32,6 +32,11 @@
        path: '/' + ERouterName.DEVICES,
        name: ERouterName.DEVICES,
        component: () => import('/@/pages/page-web/projects/devices.vue')
      },
      {
        path: '/' + ERouterName.FIRMWARES,
        name: ERouterName.FIRMWARES,
        component: () => import('../pages/page-web/projects/Firmwares.vue')
      }
    ]
  },
src/store/index.ts
@@ -121,9 +121,10 @@
    if (info.host.sdr) {
      dock.sdr = info.host.sdr
      dock.media_file_detail = info.host.media_file_detail
      dock.wireless_link = info.host.wireless_link
      return
    }
    if (info.host.job_number !== undefined) {
    if (info.host.job_number) {
      if (info.host.drone_battery_maintenance_info) {
        dock.drone_battery_maintenance_info = info.host.drone_battery_maintenance_info
      }
@@ -131,9 +132,11 @@
    }
    const sdr = dock.sdr
    const mediaFileDetail = dock.media_file_detail
    const wireless = dock.wireless_link
    state.deviceState.dockInfo[info.sn] = info.host
    state.deviceState.dockInfo[info.sn].sdr = sdr
    state.deviceState.dockInfo[info.sn].media_file_detail = mediaFileDetail
    state.deviceState.dockInfo[info.sn].wireless_link = wireless
  },
  SET_DRAW_VISIBLE_INFO (state, bool) {
    state.drawVisible = bool
src/types/airport-tsa.ts
@@ -60,3 +60,21 @@
  CLOSE = 0, // 关闭
  OPEN = 1, // 开启
}
// 4g链路连接状态
export enum FourGLinkStateEnum {
  CLOSE = 0, // 断开
  OPEN = 1, // 连接
}
//  Sdr链路连接状态
export enum SdrLinkStateEnum {
  CLOSE = 0, // 断开
  OPEN = 1, // 连接
}
// 机场的图传链路模式
export enum LinkWorkModeEnum {
  SDR = 0, // sdr模式
  FourG_FUSION_MODE = 1, // 4G融合模式
}
src/types/device-cmd.ts
@@ -1,4 +1,4 @@
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum } from '/@/types/airport-tsa';
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum, LinkWorkModeEnum } from '/@/types/airport-tsa'
// 机场指令集
export enum DeviceCmd {
  // 简单指令
@@ -23,10 +23,10 @@
  AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警
  BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养
  DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养
  SdrWorkModeSwitch = 'sdr_workmode_switch', // 增强图传
}
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum
export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum | LinkWorkModeEnum
export interface DeviceCmdItem{
  label: string, // 标题
@@ -151,6 +151,15 @@
    loading: false,
    disabled: true,
  },
  {
    label: '4g 增强',
    status: '--',
    operateText: '开启',
    cmdKey: DeviceCmd.SdrWorkModeSwitch,
    action: LinkWorkModeEnum.FourG_FUSION_MODE,
    func: 'sdrWorkMode',
    loading: false,
  },
]
export enum DeviceCmdStatusText {
@@ -239,6 +248,16 @@
  DroneBatteryModeMaintenanceNeedText = '需保养',
  DroneBatteryModeOpenBtnText = '保养',
  DroneBatteryModeCloseBtnText = '关闭保养',
  SdrWorkModeFourGOpenNormalText = '开',
  SdrWorkModeFourGOpenText = '开启中...',
  SdrWorkModeFourGOpenFailedText = '--',
  SdrWorkModeFourGOpenBtnText = '关闭',
  SdrWorkModeFourGCloseNormalText = '--',
  SdrWorkModeFourGCloseText = '关闭中...',
  SdrWorkModeFourGCloseFailedText = '开',
  SdrWorkModeFourCloseBtnText = '开启',
}
// cmd ws 消息状态
src/types/device-firmware.ts
New file
@@ -0,0 +1,34 @@
export interface Firmware {
  firmware_id: string
  file_name: string
  product_version: string
  file_size: number
  device_name: string
  username: string
  release_note: string
  released_time: string
  firmware_status: boolean
}
export interface FirmwareQueryParam {
  product_version: string
  device_name: string
  firmware_status: FirmwareStatusEnum
}
export interface FirmwareUploadParam {
  device_name: string
  release_note: string
  status: boolean
}
export enum FirmwareStatusEnum {
  NONE = 'All',
  FALSE = 'Disabled',
  TRUE = 'Available'
}
export enum DeviceNameEnum {
  DJI_DOCK = 'DJI Dock',
  MATRICE_30 = 'Matrice 30'
}
src/types/device.ts
@@ -1,6 +1,6 @@
import { commonColor } from '/@/utils/color'
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum } from './airport-tsa'
import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum, FourGLinkStateEnum, SdrLinkStateEnum, LinkWorkModeEnum } from './airport-tsa'
export interface DeviceValue {
  key: string; // 'domain-type-subtype'
  domain: string; // 表示一个领域,作为一个命名空间,暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种
@@ -305,6 +305,14 @@
    maintenance_state: DroneBatteryStateEnum, // 保养状态
    maintenance_time_left: number, // 电池保养剩余时间(小时)
  }
  wireless_link?:{
    dongle_number: number, // dongle 数量
    ['4g_link_state']: FourGLinkStateEnum, // 4g_link_state
    sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态
    link_workmode: LinkWorkModeEnum, // 图传链路模式
    sdr_quality: number, // sdr信号质量 0-5
    ['4g_quality']: number, // 4G信号质量 0-5
  }
}
export enum EModeCode {
src/types/enums.ts
@@ -14,6 +14,7 @@
    TASK = 'task',
    CREATE_PLAN = 'create-plan',
    SELECT_PLAN = 'select-plan',
    FIRMWARES = 'firmwares',
    PILOT = 'pilot-login',
    PILOT_HOME = 'pilot-home',
@@ -90,9 +91,13 @@
    MapElementDelete = 'map_element_delete',
    DeviceOnline = 'device_online',
    DeviceOffline = 'device_offline',
    FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度
    DeviceHms = 'device_hms',
    // 机场任务
    FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度
    FlightTaskMediaProgress = 'file_upload_callback', // 机场任务媒体上传进度
    FlightTaskMediaHighestPriority = 'highest_priority_upload_flighttask_media', // 机场任务媒体优先级上报
    // 设备指令
    DeviceReboot = 'device_reboot', // 机场重启
    DroneOpen = 'drone_open', // 飞行器开机
src/types/task.ts
@@ -95,3 +95,38 @@
  [TaskProgressStatus.Timeout]: TaskStatus.Fail,
  [TaskProgressStatus.Paused]: TaskStatus.Wait,
}
// 根据媒体文件上传进度信息,前端自己判断出的状态
export enum MediaStatus { // 媒体上传进度
  ToUpload = 1, // 待上传
  Uploading = 2, // 上传中
  Empty = 3, // 无媒体文件
  Success = 4, // 上传成功
}
export const MediaStatusMap = {
  [MediaStatus.ToUpload]: 'Waiting to upload',
  [MediaStatus.Uploading]: 'Uploading…',
  [MediaStatus.Success]: 'Uploaded',
  [MediaStatus.Empty]: 'No media files',
}
export const MediaStatusColorMap = {
  [MediaStatus.ToUpload]: commonColor.BLUE,
  [MediaStatus.Uploading]: commonColor.BLUE,
  [MediaStatus.Success]: commonColor.NORMAL,
  [MediaStatus.Empty]: commonColor.WARN,
}
// 媒体上传进度消息
export interface MediaStatusProgressInfo {
  job_id: string,
  media_count: number
  uploaded_count: number,
}
// 媒体上传优先级消息
export interface TaskMediaHighestPriorityProgressInfo {
  pre_job_id: string,
  job_id: string,
}
src/utils/device-cmd.ts
@@ -1,4 +1,4 @@
import { DroneBatteryModeEnum, DroneBatteryStateEnum } from './../types/airport-tsa';
import { DroneBatteryModeEnum, DroneBatteryStateEnum, LinkWorkModeEnum } from './../types/airport-tsa'
import { DeviceInfoType } from '/@/types/device'
import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum, AlarmModeEnum, BatteryStoreModeEnum } from '/@/types/airport-tsa'
@@ -42,6 +42,8 @@
      getBatteryStoreMode(cmdItem, dock)
    } else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养
      getDroneBatteryMode(cmdItem, dock)
    } else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传开关
      getSdrWorkNode(cmdItem, dock)
    }
  })
}
@@ -211,14 +213,28 @@
    cmdItem.disabled = false
  } else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) {
    cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
    cmdItem.action = DroneBatteryModeEnum.OPEN
    cmdItem.disabled = true
  } else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) {
    cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText
    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
    cmdItem.action = DroneBatteryModeEnum.OPEN
    cmdItem.disabled = false
  }
}
// 增强图传开关
function getSdrWorkNode (cmdItem: DeviceCmdItem, airportProperties: any) {
  const linkWorkMode = airportProperties?.wireless_link?.link_workmode
  if (linkWorkMode === LinkWorkModeEnum.SDR) {
    cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourCloseBtnText
    cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText
    cmdItem.action = LinkWorkModeEnum.FourG_FUSION_MODE
  } else if (linkWorkMode === LinkWorkModeEnum.FourG_FUSION_MODE) {
    cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourGOpenBtnText
    cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText
    cmdItem.action = LinkWorkModeEnum.SDR
  }
}
@@ -269,7 +285,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -280,7 +296,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -293,7 +309,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -306,7 +322,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -319,7 +335,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -332,7 +348,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -345,7 +361,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -358,7 +374,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -371,7 +387,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -384,7 +400,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -394,7 +410,7 @@
        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
          cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText
          cmdItem.loading = true
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
          cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText
          cmdItem.loading = false
        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -405,7 +421,7 @@
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText
            cmdItem.loading = true
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -415,7 +431,7 @@
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText
            cmdItem.loading = true
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -427,7 +443,7 @@
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText
            cmdItem.loading = true
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -437,7 +453,7 @@
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText
            cmdItem.loading = true
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -448,19 +464,19 @@
        if (cmdItem.action === DroneBatteryModeEnum.OPEN) {
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
            // cmdItem.loading = true
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
            cmdItem.loading = true
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText
            // cmdItem.loading = false
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText
            // cmdItem.loading = false
            cmdItem.loading = false
          }
        } else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) {
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
            cmdItem.loading = true
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) {
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
@@ -468,7 +484,40 @@
            cmdItem.loading = false
          }
        }
      } else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传
        if (cmdItem.action === LinkWorkModeEnum.SDR) { // 关闭
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseText
            cmdItem.loading = true
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseFailedText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText
            cmdItem.loading = false
          }
        } else if (cmdItem.action === LinkWorkModeEnum.FourG_FUSION_MODE) { // 开启
          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {
            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenText
            cmdItem.loading = true
          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {
            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenFailedText
            cmdItem.loading = false
          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {
            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText
            cmdItem.loading = false
          }
        }
      }
    }
  })
}
/**
 * 判断是否执行失败
 * @param status
 * @returns
 */
function isExecuteFailed (status: DeviceCmdExecuteStatus) {
  return [DeviceCmdExecuteStatus.Canceled, DeviceCmdExecuteStatus.Failed, DeviceCmdExecuteStatus.Timeout].includes(status)
}
tsconfig.json
@@ -24,7 +24,6 @@
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "src/vendors/coordtransform.js"
    ]
    "src/**/*.vue"
, "src/vendors/coordtransform.js"  ]
}