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

This commit is contained in:
YunaiV
2025-07-02 23:30:09 +08:00
parent 667d6fc35c
commit a5cb8e510c
5 changed files with 113 additions and 178 deletions

View File

@@ -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

View File

@@ -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 })
}
}

View File

@@ -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 记录状态
}

View File

@@ -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 () => {

View File

@@ -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 message = useMessage() // 消息弹窗
// 弹窗相关
const dialogVisible = ref(false)
const taskId = ref<number>()
const dialogVisible = ref(false) // 弹窗的是否展示
// 任务信息
const taskLoading = ref(false)
const taskInfo = ref<OtaTask>({})
const taskId = ref<number>() // 任务编号
const taskLoading = ref(false) // 任务加载状态
const task = ref<OtaTask>({} as OtaTask) // 任务信息
// 统计加载状态
const statisticsLoading = ref(false)
const taskStatisticsLoading = ref(false) // 任务统计加载状态
const taskStatistics = ref<Record<string, number>>({}) // 任务统计数据
// 统计数据
const statisticsData = ref({
total: 0,
pending: 0,
pushed: 0,
upgrading: 0,
success: 0,
failure: 0,
stopped: 0
})
// 当前选中的标签
const activeTab = ref('')
// 记录列表相关
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(() => {
const tabs = [{ key: '', label: '全部设备' }]
Object.values(IoTOtaTaskRecordStatusEnum).forEach((status) => {
tabs.push({
key: status.value.toString(),
label: status.label
})
})
return tabs
})
// 状态标签配置
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 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>