OtaTaskDetail.vue 10.3 KB
<template>
  <Dialog v-model="dialogVisible" title="升级任务详情" width="1200px" append-to-body>
    <!-- 任务信息 -->
    <ContentWrap title="任务信息" class="mb-20px">
      <el-descriptions :column="3" v-loading="taskLoading" border>
        <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="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">
              {{ 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="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">
              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.PENDING.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-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">
              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.UPGRADING.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-green-500">
              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.SUCCESS.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-red-500">
              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.FAILURE.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-gray-400">
              {{ taskStatistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] || 0 }}
            </div>
            <div class="text-14px text-gray-600">升级取消</div>
          </div>
        </el-col>
      </el-row>
    </ContentWrap>

    <!-- 设备管理 -->
    <ContentWrap title="升级设备记录">
      <!-- Tab 切换 -->
      <el-tabs v-model="activeTab" @tab-click="handleTabClick" class="mb-15px">
        <el-tab-pane v-for="tab in statusTabs" :key="tab.key" :label="tab.label" :name="tab.key" />
      </el-tabs>
      <!-- Tab 内容 -->
      <div v-for="tab in statusTabs" :key="tab.key" v-show="activeTab === tab.key">
        <!-- 设备列表 -->
        <el-table
          v-loading="recordLoading"
          :data="recordList"
          :stripe="true"
          :show-overflow-tooltip="true"
        >
          <el-table-column label="设备名称" align="center" prop="deviceName" />
          <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_TASK_RECORD_STATUS" :value="scope.row.status" />
            </template>
          </el-table-column>
          <el-table-column label="升级进度" align="center" prop="progress" width="120">
            <template #default="scope"> {{ scope.row.progress }}% </template>
          </el-table-column>
          <el-table-column label="状态描述" align="center" prop="description" />
          <el-table-column label="更新时间" align="center" prop="updateTime" width="180">
            <template #default="scope">
              {{ formatDate(scope.row.updateTime) }}
            </template>
          </el-table-column>
          <el-table-column label="操作" align="center" width="80">
            <template #default="scope">
              <el-button
                v-if="
                  [
                    IoTOtaTaskRecordStatusEnum.PENDING.value,
                    IoTOtaTaskRecordStatusEnum.PUSHED.value,
                    IoTOtaTaskRecordStatusEnum.UPGRADING.value
                  ].includes(scope.row.status)
                "
                link
                type="danger"
                @click="handleCancelUpgrade(scope.row)"
                v-hasPermi="['iot:ota-task-record:cancel']"
              >
                取消
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 分页 -->
        <Pagination
          :total="recordTotal"
          v-model:page="queryParams.pageNo"
          v-model:limit="queryParams.pageSize"
          @pagination="getRecordList"
        />
      </div>
    </ContentWrap>
  </Dialog>
</template>

<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { TabsPaneContext } from 'element-plus'
import { Dialog } from '@/components/Dialog'
import { ContentWrap } from '@/components/ContentWrap'
import Pagination from '@/components/Pagination/index.vue'
import { IoTOtaTaskApi, OtaTask } from '@/api/iot/ota/task'
import { IoTOtaTaskRecordApi, OtaTaskRecord } from '@/api/iot/ota/task/record'
import { DICT_TYPE } from '@/utils/dict'
import { IoTOtaTaskRecordStatusEnum } from '@/views/iot/utils/constants'
import { formatDate } from '@/utils/formatTime'

/** OTA 任务详情组件 */
defineOptions({ name: 'OtaTaskDetail' })

const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示

const taskId = ref<number>() // 任务编号
const taskLoading = ref(false) // 任务加载状态
const task = ref<OtaTask>({} as OtaTask) // 任务信息

const taskStatisticsLoading = ref(false) // 任务统计加载状态
const taskStatistics = ref<Record<string, number>>({}) // 任务统计数据

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
}) // 查询参数
const activeTab = ref('') // 当前激活的标签页

/** 状态标签配置 */
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
  }
  taskLoading.value = true
  try {
    task.value = await IoTOtaTaskApi.getOtaTask(taskId.value)
  } finally {
    taskLoading.value = false
  }
}

/** 获取统计数据 */
const getStatistics = async () => {
  if (!taskId.value) {
    return
  }
  taskStatisticsLoading.value = true
  try {
    taskStatistics.value = await IoTOtaTaskRecordApi.getOtaTaskRecordStatusStatistics(
      undefined,
      taskId.value
    )
  } finally {
    taskStatisticsLoading.value = false
  }
}

/** 获取升级记录列表 */
const getRecordList = async () => {
  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
  } finally {
    recordLoading.value = false
  }
}

/** 切换标签 */
const handleTabClick = (tab: TabsPaneContext) => {
  const tabKey = tab.paneName as string
  activeTab.value = tabKey
  queryParams.pageNo = 1
  queryParams.status = activeTab.value === '' ? undefined : parseInt(tabKey)
  getRecordList()
}

/** 取消升级 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const handleCancelUpgrade = async (record: OtaTaskRecord) => {
  try {
    await message.confirm('确认要取消该设备的升级任务吗?')
    await IoTOtaTaskRecordApi.cancelOtaTaskRecord(record.id!)
    message.success('取消成功')
    // 刷新数据
    await getRecordList()
    await getStatistics()
    await getTaskInfo()
    // 通知父组件刷新数据
    emit('success')
  } catch (error) {
    console.error('取消升级失败', error)
  }
}

/** 打开弹窗 */
const open = (id: number) => {
  taskId.value = id
  dialogVisible.value = true
  // 重置数据
  activeTab.value = ''
  queryParams.pageNo = 1
  queryParams.status = undefined

  // 加载数据
  getTaskInfo()
  getStatistics()
  getRecordList()
}

/** 暴露方法 */
defineExpose({ open })
</script>