diff --git a/src/api/iot/device/device/index.ts b/src/api/iot/device/device/index.ts index 2311a9a64..3b7bf2bac 100644 --- a/src/api/iot/device/device/index.ts +++ b/src/api/iot/device/device/index.ts @@ -71,6 +71,29 @@ export interface IotDeviceMessageSendReqVO { params?: any // 请求参数 } +// IoT 设备消息拉取 Request VO +export interface IotDeviceMessagePullReqVO { + deviceId: number // 设备 ID + limit?: number // 拉取数量限制(默认 10,最大 100) +} + +// IoT 设备消息拉取 Response VO +export interface IotDeviceMessagePullRespVO { + id: number // 离线消息记录 ID + messageId: string // 消息 ID + method: string // 消息方法 + message: any // 完整的消息对象 + retryCount: number // 重试次数 + createTime: Date // 创建时间 + expireTime: Date // 过期时间 +} + +// IoT 设备消息确认 Request VO +export interface IotDeviceMessageAckReqVO { + messageId: number // 消息 ID(离线消息表的主键 ID) + deviceId: number // 设备 ID +} + // 设备 API export const DeviceApi = { // 查询设备分页 @@ -161,5 +184,32 @@ export const DeviceApi = { // 发送设备消息 sendDeviceMessage: async (params: IotDeviceMessageSendReqVO) => { return await request.post({ url: `/iot/device/message/send`, data: params }) + }, + + // 拉取待处理消息(Pull 模式) + pullDeviceMessages: async (params: IotDeviceMessagePullReqVO) => { + return await request.get({ url: `/iot/device/message/pull`, params }) + }, + + // 确认消息已处理(Pull 模式) + confirmDeviceMessage: async (data: IotDeviceMessageAckReqVO) => { + return await request.post({ url: `/iot/device/message/ack`, data }) + }, + + // 获取设备离线消息列表(管理后台) + getPullMessageList: async (deviceId: number, status?: number) => { + return await request.get({ + url: `/iot/device/message/pull-list`, + params: { deviceId, status } + }) + }, + + // 删除离线消息(管理后台) + deletePullMessage: async (messageId: number, deviceId: number) => { + return await request.delete({ + url: `/iot/device/message/pull-delete`, + params: { messageId, deviceId } + }) } } + diff --git a/src/api/iot/ota/config/index.ts b/src/api/iot/ota/config/index.ts new file mode 100644 index 000000000..9b9342b29 --- /dev/null +++ b/src/api/iot/ota/config/index.ts @@ -0,0 +1,15 @@ +import request from '@/config/axios' + +export interface OtaConfig { + allowOfflinePush: boolean // 是否允许离线推送 +} + +// 获取 OTA 配置 +export const getOtaConfig = async () => { + return await request.get({ url: `/iot/ota/config/get` }) +} + +// 更新 OTA 配置 +export const updateOtaConfig = async (data: OtaConfig) => { + return await request.put({ url: `/iot/ota/config/update`, data }) +} diff --git a/src/api/iot/ota/statistics/index.ts b/src/api/iot/ota/statistics/index.ts new file mode 100644 index 000000000..35659a89d --- /dev/null +++ b/src/api/iot/ota/statistics/index.ts @@ -0,0 +1,34 @@ +import request from '@/config/axios' + +export interface OtaTaskStatistics { + // PUSH 模式统计 + pushOnlineTotalCount: number // PUSH 在线推送总数 + pushOnlineSuccessCount: number // PUSH 在线推送成功数 + + // PULL 模式统计 + pullOfflineCount: number // PULL 离线消息总数 + pullDeliveredCount: number // PULL 已送达数量(设备已拉取) + pullTodayCount: number // 今日新增 PULL 消息数 + + // 设备统计 + totalDeviceCount: number // 总设备数 + onlineDeviceCount: number // 在线设备数 + offlineDeviceCount: number // 离线设备数 + + // 任务完成率 + totalTaskCount: number // 总任务数 + completedTaskCount: number // 已完成任务数(成功+失败) +} + +// 获取 OTA 任务统计信息 +export const getOtaTaskStatistics = async (params?: { + startTime?: Date + endTime?: Date +}) => { + return await request.get({ url: `/iot/ota/task/statistics`, params }) +} + +// 导出 API +export const OtaTaskStatisticsApi = { + getOtaTaskStatistics +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts index eb2d9930b..0ef47d339 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -246,5 +246,6 @@ 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_TASK_RECORD_STATUS = 'iot_ota_task_record_status' // IoT OTA 记录状态 + IOT_OTA_TASK_RECORD_STATUS = 'iot_ota_task_record_status', // IoT OTA 记录状态 + IOT_STATUS_OPTIONS = 'iot_status_options' //消息状态 } diff --git a/src/views/iot/device/device/DeviceForm.vue b/src/views/iot/device/device/DeviceForm.vue index dfed0c63e..0492649f6 100644 --- a/src/views/iot/device/device/DeviceForm.vue +++ b/src/views/iot/device/device/DeviceForm.vue @@ -23,10 +23,10 @@ /> - + diff --git a/src/views/iot/device/device/components/DeviceTableSelect.vue b/src/views/iot/device/device/components/DeviceTableSelect.vue index 73c252da5..906381921 100644 --- a/src/views/iot/device/device/components/DeviceTableSelect.vue +++ b/src/views/iot/device/device/components/DeviceTableSelect.vue @@ -25,10 +25,10 @@ /> - + + + + + + + + + + + 查询 + 重置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ currentMessage?.messageId }} + + + {{ currentMessage?.method }} + + + + + + {{ currentMessage?.retryCount }} + + + {{ dateFormatter(currentMessage, '', '', currentMessage?.createTime) }} + + + {{ dateFormatter(currentMessage, '', '', currentMessage?.expireTime) }} + + + + + + + + + + + + diff --git a/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue b/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue index 04f9a9f53..5178ed7e8 100644 --- a/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue +++ b/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue @@ -131,6 +131,7 @@ const getList = async () => { if (!props.deviceId) return loading.value = true try { + queryParams.deviceId = props.deviceId const data = await DeviceApi.getDeviceMessagePairPage(queryParams) list.value = data.list total.value = data.length @@ -139,6 +140,17 @@ const getList = async () => { } } +/** 监听 deviceId 变化 */ +watch( + () => props.deviceId, + (val) => { + if (val) { + queryParams.deviceId = val + getList() + } + } +) + /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 diff --git a/src/views/iot/device/device/detail/DeviceDetailsThingModelProperty.vue b/src/views/iot/device/device/detail/DeviceDetailsThingModelProperty.vue index 438b529d6..e63b8d112 100644 --- a/src/views/iot/device/device/detail/DeviceDetailsThingModelProperty.vue +++ b/src/views/iot/device/device/detail/DeviceDetailsThingModelProperty.vue @@ -171,6 +171,9 @@ const queryFormRef = ref() // 搜索的表单 /** 查询列表 */ const getList = async () => { + if (!props.deviceId) { + return + } loading.value = true try { const params = { @@ -185,6 +188,16 @@ const getList = async () => { } } +/** 监听 deviceId 变化 */ +watch( + () => props.deviceId, + (val) => { + if (val) { + getList() + } + } +) + /** 前端筛选数据 */ const handleFilter = () => { if (!queryParams.keyword.trim()) { diff --git a/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue b/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue index fd8456162..9d5937951 100644 --- a/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue +++ b/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue @@ -146,6 +146,7 @@ const getList = async () => { if (!props.deviceId) return loading.value = true try { + queryParams.deviceId = props.deviceId const data = await DeviceApi.getDeviceMessagePairPage(queryParams) list.value = data.list total.value = data.length @@ -154,6 +155,17 @@ const getList = async () => { } } +/** 监听 deviceId 变化 */ +watch( + () => props.deviceId, + (val) => { + if (val) { + queryParams.deviceId = val + getList() + } + } +) + /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 diff --git a/src/views/iot/device/device/detail/index.vue b/src/views/iot/device/device/detail/index.vue index 3ec756b5c..33f62757f 100644 --- a/src/views/iot/device/device/detail/index.vue +++ b/src/views/iot/device/device/detail/index.vue @@ -21,6 +21,9 @@ + + + - + { if (productId) { queryParams.productId = Number(productId) } - + getList() // 获取产品列表 diff --git a/src/views/iot/home/components/OtaMonitorCard.vue b/src/views/iot/home/components/OtaMonitorCard.vue new file mode 100644 index 000000000..48924a1ce --- /dev/null +++ b/src/views/iot/home/components/OtaMonitorCard.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/src/views/iot/home/index.vue b/src/views/iot/home/index.vue index 3d60acefc..4e46a97db 100644 --- a/src/views/iot/home/index.vue +++ b/src/views/iot/home/index.vue @@ -53,14 +53,21 @@ - + + + + + + + + - + + + diff --git a/src/views/iot/thingmodel/ThingModelProperty.vue b/src/views/iot/thingmodel/ThingModelProperty.vue index 8ff965559..1a33262a4 100644 --- a/src/views/iot/thingmodel/ThingModelProperty.vue +++ b/src/views/iot/thingmodel/ThingModelProperty.vue @@ -107,7 +107,7 @@ {{ mode.label }} -
{{ getCollectionModeDesc() }}
+
-
采集频率范围:10-86400秒,默认300秒(5分钟)
@@ -134,7 +133,6 @@ @change="handleFunctionCodeChange" class="w-full" /> -
常用0x03-读保持寄存器,0x04-读输入寄存器,0x01-读线圈,0x02-读离散输入
@@ -157,7 +155,19 @@ @change="handleRegisterCountChange" class="w-full" /> -
读取的寄存器或线圈的数量,默认为 1
+
+ + + + + {{ order.label }} + + + @@ -192,6 +202,7 @@ import { IoTDataSpecsDataTypeEnum, IoTThingModelAccessModeEnum, IoTModbusCollectionModeEnum, + IoTModbusByteOrderEnum, IOT_PROVIDE_KEY } from '@/views/iot/utils/constants' import { ProductVO } from '@/api/iot/product/product' @@ -288,6 +299,10 @@ watch( if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.pollingInterval) { property.value.modbusConfig.pollingInterval = 300 } + // 向下兼容:如果没有 byteOrder,默认设置为大端模式 + if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.byteOrder) { + property.value.modbusConfig.byteOrder = IoTModbusByteOrderEnum.BIG_ENDIAN.value + } }, { immediate: true } ) @@ -345,16 +360,7 @@ const handleCollectionModeChange = (mode: number) => { } } -/** 获取采集模式描述 */ -const getCollectionModeDesc = () => { - const mode = property.value.modbusConfig?.collectionMode - if (mode === IoTModbusCollectionModeEnum.POLLING.value) { - return IoTModbusCollectionModeEnum.POLLING.desc - } else if (mode === IoTModbusCollectionModeEnum.ACTIVE_REPORT.value) { - return IoTModbusCollectionModeEnum.ACTIVE_REPORT.desc - } - return '' -} + diff --git a/src/views/iot/thingmodel/ThingModelService.vue b/src/views/iot/thingmodel/ThingModelService.vue index ffb9574b2..475b89635 100644 --- a/src/views/iot/thingmodel/ThingModelService.vue +++ b/src/views/iot/thingmodel/ThingModelService.vue @@ -27,7 +27,7 @@ :direction="IoTThingModelParamDirectionEnum.OUTPUT" /> - + Modbus 配置(可选) @@ -45,12 +45,9 @@ v-model="service.modbusConfig.functionCode" :min="1" :max="127" - placeholder="如:5(写单个线圈)" + placeholder="如:5" clearable /> -
- 常用:05=写单线圈、06=写单寄存器、15=写多线圈、16=写多寄存器 -
-
- 功能码05:65280(0xFF00)=闭合,0(0x0000)=断开 -
+
+ + + + + {{ order.label }} + + + @@ -82,7 +86,8 @@ import { ThingModelService } from '@/api/iot/thingmodel' import { isEmpty } from '@/utils/is' import { IoTThingModelParamDirectionEnum, - IoTThingModelServiceCallTypeEnum + IoTThingModelServiceCallTypeEnum, + IoTModbusByteOrderEnum } from '@/views/iot/utils/constants' /** IoT 物模型服务 */ @@ -100,10 +105,16 @@ if (!service.value.modbusConfig) { writeValue: undefined, registerCount: undefined, fullFrame: '', - crcHex: '' + crcHex: '', + byteOrder: IoTModbusByteOrderEnum.BIG_ENDIAN.value // 默认大端模式 } +} else if (!service.value.modbusConfig.byteOrder) { + // 向下兼容:如果没有 byteOrder,默认设置为大端模式 + service.value.modbusConfig.byteOrder = IoTModbusByteOrderEnum.BIG_ENDIAN.value } + + /** 默认选中,ASYNC 异步 */ watch( () => service.value.callType, diff --git a/src/views/iot/thingmodel/index.vue b/src/views/iot/thingmodel/index.vue index 3502bfada..96a1882a8 100644 --- a/src/views/iot/thingmodel/index.vue +++ b/src/views/iot/thingmodel/index.vue @@ -63,7 +63,7 @@ - + @@ -97,12 +106,15 @@ + +