Procházet zdrojové kódy

feat:【IoT 物联网】初始化 IoT 固件详情页 80%

YunaiV před 9 měsíci
rodič
revize
a5cb8e510c

+ 3 - 3
src/api/iot/ota/task/index.ts

@@ -3,15 +3,15 @@ import request from '@/config/axios'
 /** IoT OTA 任务信息 */
 export interface OtaTask {
   id?: number // 任务编号
-  name?: string // 任务名称
+  name: string // 任务名称
   description?: string // 任务描述
   firmwareId?: number // 固件编号
-  status?: number // 任务状态
+  status: number // 任务状态
   deviceScope?: number // 升级范围
   deviceIds?: number[] // 指定设备ID列表(当升级范围为指定设备时使用)
   deviceTotalCount?: number // 设备总共数量
   deviceSuccessCount?: number // 设备成功数量
-  createTime?: string // 创建时间
+  createTime?: Date // 创建时间
 }
 
 // IoT OTA 任务 API

+ 8 - 0
src/api/iot/ota/task/record/index.ts

@@ -7,11 +7,14 @@ export interface OtaTaskRecord {
   firmwareVersion?: string // 固件版本
   taskId?: number // 任务编号
   deviceId?: string // 设备编号
+  deviceName?: string // 设备名称
+  currentVersion?: string // 当前版本
   fromFirmwareId?: number // 来源的固件编号
   fromFirmwareVersion?: string // 来源的固件版本
   status?: number // 升级状态
   progress?: number // 升级进度,百分比
   description?: string // 升级进度描述
+  updateTime?: Date // 更新时间
 }
 
 // IoT OTA 任务记录 API
@@ -31,5 +34,10 @@ export const IoTOtaTaskRecordApi = {
   // 查询 OTA 任务记录详情
   getOtaTaskRecord: async (id: number) => {
     return await request.get({ url: `/iot/ota/task/record/get?id=` + id })
+  },
+
+  // 取消 OTA 任务记录
+  cancelOtaTaskRecord: async (id: number) => {
+    return await request.post({ url: `/iot/ota/task/record/cancel?id=` + id })
   }
 }

+ 1 - 1
src/utils/dict.ts

@@ -244,5 +244,5 @@ export enum DICT_TYPE {
   IOT_ALERT_RECEIVE_TYPE = 'iot_alert_receive_type', // IoT 告警接收类型
   IOT_OTA_TASK_DEVICE_SCOPE = 'iot_ota_task_device_scope', // IoT OTA任务设备范围
   IOT_OTA_TASK_STATUS = 'iot_ota_task_status', // IoT OTA 任务状态
-  IOT_OTA_RECORD_STATUS = 'iot_ota_record_status' // IoT OTA 记录状态
+  IOT_OTA_TASK_RECORD_STATUS = 'iot_ota_task_record_status' // IoT OTA 记录状态
 }

+ 6 - 8
src/views/iot/ota/firmware/detail/index.vue

@@ -104,16 +104,14 @@ import OtaTaskList from '../../task/OtaTaskList.vue'
 /** IoT OTA 固件详情 */
 defineOptions({ name: 'IoTOtaFirmwareDetail' })
 
-const route = useRoute()
-const firmwareId = ref(Number(route.params.id))
+const route = useRoute() // 路由
 
-// 固件信息
-const firmwareLoading = ref(false)
-const firmware = ref<IoTOtaFirmware>({} as IoTOtaFirmware)
+const firmwareId = ref(Number(route.params.id)) // 固件编号
+const firmwareLoading = ref(false) // 固件加载状态
+const firmware = ref<IoTOtaFirmware>({} as IoTOtaFirmware) // 固件信息
 
-// 统计信息
-const firmwareStatisticsLoading = ref(false)
-const firmwareStatistics = ref<Record<string, number>>({})
+const firmwareStatisticsLoading = ref(false) // 统计信息加载状态
+const firmwareStatistics = ref<Record<string, number>>({}) // 统计信息
 
 /** 获取固件信息 */
 const getFirmwareInfo = async () => {

+ 95 - 166
src/views/iot/ota/task/OtaTaskDetail.vue

@@ -3,71 +3,80 @@
     <!-- 任务信息 -->
     <ContentWrap title="任务信息" class="mb-20px">
       <el-descriptions :column="3" v-loading="taskLoading">
-        <el-descriptions-item label="任务ID">{{ taskInfo.id }}</el-descriptions-item>
-        <el-descriptions-item label="任务名称">{{ taskInfo.name }}</el-descriptions-item>
-        <el-descriptions-item label="任务类型">版本升级</el-descriptions-item>
-        <el-descriptions-item label="设备数量">{{
-          taskInfo.deviceTotalCount
-        }}</el-descriptions-item>
-        <el-descriptions-item label="预定时间">-</el-descriptions-item>
-        <el-descriptions-item label="添加时间">{{
-          formatTime(taskInfo.createTime)
-        }}</el-descriptions-item>
-        <el-descriptions-item label="任务描述" :span="3">{{
-          taskInfo.description || '-'
-        }}</el-descriptions-item>
+        <el-descriptions-item label="任务编号">{{ task.id }}</el-descriptions-item>
+        <el-descriptions-item label="任务名称">{{ task.name }}</el-descriptions-item>
+        <el-descriptions-item label="升级范围">
+          <dict-tag :type="DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE" :value="task.deviceScope" />
+        </el-descriptions-item>
+        <el-descriptions-item label="任务状态">
+          <dict-tag :type="DICT_TYPE.IOT_OTA_TASK_STATUS" :value="task.status" />
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间">
+          {{ task.createTime ? formatDate(task.createTime) : '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="任务描述" :span="3">
+          {{ task.description }}
+        </el-descriptions-item>
       </el-descriptions>
     </ContentWrap>
 
     <!-- 任务升级设备统计 -->
     <ContentWrap title="升级设备统计" class="mb-20px">
-      <el-row :gutter="20" class="py-20px" v-loading="statisticsLoading">
-        <el-col :span="4">
+      <el-row :gutter="20" class="py-20px" v-loading="taskStatisticsLoading">
+        <el-col :span="6">
           <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
             <div class="text-32px font-bold mb-8px text-blue-500">
-              {{ statisticsData.total }}
+              {{ Object.values(taskStatistics).reduce((sum, count) => sum + (count || 0), 0) || 0 }}
             </div>
             <div class="text-14px text-gray-600">升级设备总数</div>
           </div>
         </el-col>
-        <el-col :span="4">
+        <el-col :span="3">
           <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
             <div class="text-32px font-bold mb-8px text-gray-400">
-              {{ statisticsData.pending }}
+              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.PENDING.value] || 0 }}
             </div>
             <div class="text-14px text-gray-600">待推送</div>
           </div>
         </el-col>
-        <el-col :span="4">
+        <el-col :span="3">
+          <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
+            <div class="text-32px font-bold mb-8px text-blue-400">
+              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0 }}
+            </div>
+            <div class="text-14px text-gray-600">已推送</div>
+          </div>
+        </el-col>
+        <el-col :span="3">
           <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
             <div class="text-32px font-bold mb-8px text-yellow-500">
-              {{ statisticsData.upgrading }}
+              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.UPGRADING.value] || 0 }}
             </div>
             <div class="text-14px text-gray-600">正在升级</div>
           </div>
         </el-col>
-        <el-col :span="4">
+        <el-col :span="3">
           <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
             <div class="text-32px font-bold mb-8px text-green-500">
-              {{ statisticsData.success }}
+              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] || 0 }}
             </div>
             <div class="text-14px text-gray-600">升级成功</div>
           </div>
         </el-col>
-        <el-col :span="4">
+        <el-col :span="3">
           <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
             <div class="text-32px font-bold mb-8px text-red-500">
-              {{ statisticsData.failure }}
+              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] || 0 }}
             </div>
             <div class="text-14px text-gray-600">升级失败</div>
           </div>
         </el-col>
-        <el-col :span="4">
+        <el-col :span="3">
           <div class="text-center p-20px border border-solid border-gray-200 rounded bg-gray-50">
             <div class="text-32px font-bold mb-8px text-gray-400">
-              {{ statisticsData.stopped }}
+              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] || 0 }}
             </div>
-            <div class="text-14px text-gray-600">停止</div>
+            <div class="text-14px text-gray-600">升级取消</div>
           </div>
         </el-col>
       </el-row>
@@ -89,10 +98,10 @@
           :show-overflow-tooltip="true"
         >
           <el-table-column label="设备名称" align="center" prop="deviceName" />
-          <el-table-column label="当前版本" align="center" prop="currentVersion" />
+          <el-table-column label="当前版本" align="center" prop="fromFirmwareVersion" />
           <el-table-column label="升级状态" align="center" prop="status" width="120">
             <template #default="scope">
-              <dict-tag :type="DICT_TYPE.IOT_OTA_RECORD_STATUS" :value="scope.row.status" />
+              <dict-tag :type="DICT_TYPE.IOT_OTA_TASK_RECORD_STATUS" :value="scope.row.status" />
             </template>
           </el-table-column>
           <el-table-column label="升级进度" align="center" prop="progress" width="120">
@@ -100,7 +109,9 @@
           </el-table-column>
           <el-table-column label="状态描述" align="center" prop="description" />
           <el-table-column label="更新时间" align="center" prop="updateTime" width="180">
-            <template #default="scope"> {{ formatTime(scope.row.updateTime) }} </template>
+            <template #default="scope">
+              {{ formatDate(scope.row.updateTime) }}
+            </template>
           </el-table-column>
           <el-table-column label="操作" align="center" width="80">
             <template #default="scope">
@@ -116,14 +127,12 @@
           </el-table-column>
         </el-table>
         <!-- 分页 -->
-        <div class="flex justify-center mt-20px">
-          <Pagination
-            :total="recordTotal"
-            v-model:page="queryParams.pageNo"
-            v-model:limit="queryParams.pageSize"
-            @pagination="getRecordList"
-          />
-        </div>
+        <Pagination
+          :total="recordTotal"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getRecordList"
+        />
       </div>
     </ContentWrap>
   </Dialog>
@@ -144,90 +153,48 @@ import { formatDate } from '@/utils/formatTime'
 /** OTA 任务详情组件 */
 defineOptions({ name: 'OtaTaskDetail' })
 
-const message = useMessage()
-
-// 弹窗相关
-const dialogVisible = ref(false)
-const taskId = ref<number>()
+const message = useMessage() // 消息弹窗
 
-// 任务信息
-const taskLoading = ref(false)
-const taskInfo = ref<OtaTask>({})
+const dialogVisible = ref(false) // 弹窗的是否展示
 
-// 统计加载状态
-const statisticsLoading = ref(false)
+const taskId = ref<number>() // 任务编号
+const taskLoading = ref(false) // 任务加载状态
+const task = ref<OtaTask>({} as OtaTask) // 任务信息
 
-// 统计数据
-const statisticsData = ref({
-  total: 0,
-  pending: 0,
-  pushed: 0,
-  upgrading: 0,
-  success: 0,
-  failure: 0,
-  stopped: 0
-})
-
-// 当前选中的标签
-const activeTab = ref('')
+const taskStatisticsLoading = ref(false) // 任务统计加载状态
+const taskStatistics = ref<Record<string, number>>({}) // 任务统计数据
 
-// 记录列表相关
-const recordLoading = ref(false)
-const recordList = ref<OtaTaskRecord[]>([])
-const recordTotal = ref(0)
+const recordLoading = ref(false) // 记录列表加载状态
+const recordList = ref<OtaTaskRecord[]>([]) // 记录列表数据
+const recordTotal = ref(0) // 记录总数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   taskId: undefined as number | undefined,
-  status: undefined as number | undefined,
-  deviceNumber: ''
-})
+  status: undefined as number | undefined
+}) // 查询参数
+const activeTab = ref('') // 当前激活的标签页
 
-// 状态标签配置
-const statusTabs = computed(() => [
-  { key: '', label: '全部设备' },
-  {
-    key: IoTOtaTaskRecordStatusEnum.PENDING.value.toString(),
-    label: IoTOtaTaskRecordStatusEnum.PENDING.label
-  },
-  {
-    key: IoTOtaTaskRecordStatusEnum.PUSHED.value.toString(),
-    label: IoTOtaTaskRecordStatusEnum.PUSHED.label
-  },
-  {
-    key: IoTOtaTaskRecordStatusEnum.UPGRADING.value.toString(),
-    label: IoTOtaTaskRecordStatusEnum.UPGRADING.label
-  },
-  {
-    key: IoTOtaTaskRecordStatusEnum.SUCCESS.value.toString(),
-    label: IoTOtaTaskRecordStatusEnum.SUCCESS.label
-  },
-  {
-    key: IoTOtaTaskRecordStatusEnum.FAILURE.value.toString(),
-    label: IoTOtaTaskRecordStatusEnum.FAILURE.label
-  },
-  {
-    key: IoTOtaTaskRecordStatusEnum.CANCELED.value.toString(),
-    label: IoTOtaTaskRecordStatusEnum.CANCELED.label
-  }
-])
-
-/** 时间格式化 */
-const formatTime = (time: string | undefined) => {
-  if (!time) return '-'
-  return formatDate(new Date(time))
-}
+/** 状态标签配置 */
+const statusTabs = computed(() => {
+  const tabs = [{ key: '', label: '全部设备' }]
+  Object.values(IoTOtaTaskRecordStatusEnum).forEach((status) => {
+    tabs.push({
+      key: status.value.toString(),
+      label: status.label
+    })
+  })
+  return tabs
+})
 
 /** 获取任务详情 */
 const getTaskInfo = async () => {
-  if (!taskId.value) return
-
+  if (!taskId.value) {
+    return
+  }
   taskLoading.value = true
   try {
-    const data = await IoTOtaTaskApi.getOtaTask(taskId.value)
-    taskInfo.value = data
-  } catch (error) {
-    console.error('获取任务详情失败', error)
+    task.value = await IoTOtaTaskApi.getOtaTask(taskId.value)
   } finally {
     taskLoading.value = false
   }
@@ -235,61 +202,31 @@ const getTaskInfo = async () => {
 
 /** 获取统计数据 */
 const getStatistics = async () => {
-  if (!taskId.value) return
-
-  statisticsLoading.value = true
+  if (!taskId.value) {
+    return
+  }
+  taskStatisticsLoading.value = true
   try {
-    const data = await IoTOtaTaskRecordApi.getOtaTaskRecordStatusStatistics(undefined, taskId.value)
-    statisticsData.value = {
-      total: data.total || 0,
-      pending: data.pending || 0,
-      pushed: data.pushed || 0,
-      upgrading: data.upgrading || 0,
-      success: data.success || 0,
-      failure: data.failure || 0,
-      stopped: data.stopped || 0
-    }
-  } catch (error) {
-    console.error('获取统计数据失败', error)
-    // 模拟数据
-    statisticsData.value = {
-      total: 1,
-      pending: 0,
-      pushed: 0,
-      upgrading: 0,
-      success: 0,
-      failure: 1,
-      stopped: 0
-    }
+    taskStatistics.value = await IoTOtaTaskRecordApi.getOtaTaskRecordStatusStatistics(
+      undefined,
+      taskId.value
+    )
   } finally {
-    statisticsLoading.value = false
+    taskStatisticsLoading.value = false
   }
 }
 
-/** 获取记录列表 */
+/** 获取升级记录列表 */
 const getRecordList = async () => {
-  if (!taskId.value) return
-
+  if (!taskId.value) {
+    return
+  }
   recordLoading.value = true
   try {
     queryParams.taskId = taskId.value
     const data = await IoTOtaTaskRecordApi.getOtaTaskRecordPage(queryParams)
     recordList.value = data.list || []
     recordTotal.value = data.total || 0
-  } catch (error) {
-    console.error('获取记录列表失败', error)
-    // 模拟数据
-    recordList.value = [
-      {
-        id: 1,
-        taskId: taskId.value,
-        deviceId: '1',
-        status: IoTOtaTaskRecordStatusEnum.FAILURE.value,
-        progress: 0,
-        description: '升级失败'
-      } as OtaTaskRecord
-    ]
-    recordTotal.value = 1
   } finally {
     recordLoading.value = false
   }
@@ -300,14 +237,7 @@ const handleTabClick = (tab: TabsPaneContext) => {
   const tabKey = tab.paneName as string
   activeTab.value = tabKey
   queryParams.pageNo = 1
-
-  // 设置状态过滤:使用 IoTOtaTaskRecordStatusEnum 的值作为 tab key
-  if (tabKey === '') {
-    queryParams.status = undefined // 全部
-  } else {
-    queryParams.status = parseInt(tabKey) // 直接使用枚举值
-  }
-
+  queryParams.status = activeTab.value === '' ? undefined : parseInt(tabKey)
   getRecordList()
 }
 
@@ -315,9 +245,12 @@ const handleTabClick = (tab: TabsPaneContext) => {
 const handleCancelUpgrade = async (record: OtaTaskRecord) => {
   try {
     await message.confirm('确认要取消该设备的升级任务吗?')
-    // TODO: 调用取消升级接口
+    await IoTOtaTaskRecordApi.cancelOtaTaskRecord(record.id!)
     message.success('取消成功')
-    getRecordList()
+    // 刷新数据
+    await getRecordList()
+    await getStatistics()
+    // TODO @AI:需要 succes 不断刷新出去
   } catch (error) {
     console.error('取消升级失败', error)
   }
@@ -327,12 +260,10 @@ const handleCancelUpgrade = async (record: OtaTaskRecord) => {
 const open = (id: number) => {
   taskId.value = id
   dialogVisible.value = true
-
   // 重置数据
   activeTab.value = ''
   queryParams.pageNo = 1
   queryParams.status = undefined
-  queryParams.deviceNumber = ''
 
   // 加载数据
   getTaskInfo()
@@ -341,7 +272,5 @@ const open = (id: number) => {
 }
 
 /** 暴露方法 */
-defineExpose({
-  open
-})
+defineExpose({ open })
 </script>