From 83d4c07a67e7588e3938237cfad9eaa6be5ea6c5 Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Mon, 12 Dec 2022 19:00:47 +0800
Subject: [PATCH] initial v1.3.1

---
 src/types/task.ts                               |   35 ++
 src/components/task/TaskPanel.vue               |  115 +++++-
 src/api/media.ts                                |    3 
 src/components/task/use-format-task.ts          |   40 ++
 src/event-bus/index.ts                          |    2 
 src/pages/page-web/projects/wayline.vue         |    1 
 src/types/device.ts                             |   10 
 src/components/task/use-task-ws-event.ts        |   43 ++
 src/pages/page-web/projects/Firmwares.vue       |  321 +++++++++++++++++++++
 src/store/index.ts                              |    5 
 /dev/null                                       |   19 -
 src/types/enums.ts                              |    7 
 src/api/wayline.ts                              |   10 
 tsconfig.json                                   |    5 
 src/types/device-firmware.ts                    |   34 ++
 src/api/manage.ts                               |   22 +
 src/pages/page-web/projects/workspace.vue       |    6 
 src/components/devices/DeviceFirmwareStatus.vue |   61 ++++
 src/types/device-cmd.ts                         |   25 +
 src/components/common/topbar.vue                |    3 
 src/router/index.ts                             |    5 
 src/utils/device-cmd.ts                         |   95 ++++-
 src/types/airport-tsa.ts                        |   18 +
 23 files changed, 799 insertions(+), 86 deletions(-)

diff --git a/src/api/manage.ts b/src/api/manage.ts
index fbdcb48..29fbc23 100644
--- a/src/api/manage.ts
+++ b/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
 }
\ No newline at end of file
diff --git a/src/api/media.ts b/src/api/media.ts
index 9a03ef5..634bd13 100644
--- a/src/api/media.ts
+++ b/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
   }
diff --git a/src/api/wayline.ts b/src/api/wayline.ts
index dfbeef3..ec579dc 100644
--- a/src/api/wayline.ts
+++ b/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
+}
diff --git a/src/components/common/topbar.vue b/src/components/common/topbar.vue
index 02423e4..4f0bff3 100644
--- a/src/components/common/topbar.vue
+++ b/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)
diff --git a/src/components/devices/DeviceFirmwareStatus.vue b/src/components/devices/DeviceFirmwareStatus.vue
new file mode 100644
index 0000000..7102b35
--- /dev/null
+++ b/src/components/devices/DeviceFirmwareStatus.vue
@@ -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>
diff --git a/src/components/task/TaskPanel.vue b/src/components/task/TaskPanel.vue
index 339c189..5b70155 100644
--- a/src/components/task/TaskPanel.vue
+++ b/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>
diff --git a/src/components/task/use-format-task.ts b/src/components/task/use-format-task.ts
index 167a581..486b85c 100644
--- a/src/components/task/use-format-task.ts
+++ b/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,
   }
 }
diff --git a/src/components/task/use-task-progress-event.ts b/src/components/task/use-task-progress-event.ts
deleted file mode 100644
index 54d4551..0000000
--- a/src/components/task/use-task-progress-event.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-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)
-  })
-}
diff --git a/src/components/task/use-task-ws-event.ts b/src/components/task/use-task-ws-event.ts
new file mode 100644
index 0000000..941ecbd
--- /dev/null
+++ b/src/components/task/use-task-ws-event.ts
@@ -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)
+  })
+}
diff --git a/src/event-bus/index.ts b/src/event-bus/index.ts
index d4d24b3..72feae5 100644
--- a/src/event-bus/index.ts
+++ b/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>()
diff --git a/src/pages/page-web/projects/Firmwares.vue b/src/pages/page-web/projects/Firmwares.vue
new file mode 100644
index 0000000..1b16783
--- /dev/null
+++ b/src/pages/page-web/projects/Firmwares.vue
@@ -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>
diff --git a/src/pages/page-web/projects/wayline.vue b/src/pages/page-web/projects/wayline.vue
index da7db6b..4a6dda9 100644
--- a/src/pages/page-web/projects/wayline.vue
+++ b/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)
diff --git a/src/pages/page-web/projects/workspace.vue b/src/pages/page-web/projects/workspace.vue
index 4d07a56..4d965f0 100644
--- a/src/pages/page-web/projects/workspace.vue
+++ b/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: {
diff --git a/src/router/index.ts b/src/router/index.ts
index 9e5918d..4b38451 100644
--- a/src/router/index.ts
+++ b/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')
       }
     ]
   },
diff --git a/src/store/index.ts b/src/store/index.ts
index 00a49fa..95d170b 100644
--- a/src/store/index.ts
+++ b/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
diff --git a/src/types/airport-tsa.ts b/src/types/airport-tsa.ts
index 9de449e..e752b64 100644
--- a/src/types/airport-tsa.ts
+++ b/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融合模式
+}
diff --git a/src/types/device-cmd.ts b/src/types/device-cmd.ts
index 621fd25..d8804cd 100644
--- a/src/types/device-cmd.ts
+++ b/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 消息状态
diff --git a/src/types/device-firmware.ts b/src/types/device-firmware.ts
new file mode 100644
index 0000000..bff2e1a
--- /dev/null
+++ b/src/types/device-firmware.ts
@@ -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'
+}
\ No newline at end of file
diff --git a/src/types/device.ts b/src/types/device.ts
index 733ef3f..0516cb8 100644
--- a/src/types/device.ts
+++ b/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 {
diff --git a/src/types/enums.ts b/src/types/enums.ts
index e0e1995..49b5962 100644
--- a/src/types/enums.ts
+++ b/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', // 飞行器开机
diff --git a/src/types/task.ts b/src/types/task.ts
index 86d91d3..cd5d97a 100644
--- a/src/types/task.ts
+++ b/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,
+}
diff --git a/src/utils/device-cmd.ts b/src/utils/device-cmd.ts
index 3ffe75f..8613e05 100644
--- a/src/utils/device-cmd.ts
+++ b/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)
+}
diff --git a/tsconfig.json b/tsconfig.json
index 8aefcd9..10e74cf 100644
--- a/tsconfig.json
+++ b/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"  ]
 }
\ No newline at end of file

--
Gitblit v1.9.3