feat:【IoT 物联网】完善设备消息的查询

This commit is contained in:
YunaiV
2025-06-14 11:10:47 +08:00
parent a33eda3b1c
commit f17404615e
8 changed files with 214 additions and 238 deletions

View File

@ -157,8 +157,8 @@ export const DeviceApi = {
},
// 查询设备日志分页
getDeviceLogPage: async (params: any) => {
return await request.get({ url: `/iot/device/log/page`, params })
getDeviceMessagePage: async (params: any) => {
return await request.get({ url: `/iot/device/message/page`, params })
},
// 获取设备 MQTT 连接参数

View File

@ -1,51 +0,0 @@
import request from '@/config/axios'
// IoT 插件配置 VO
export interface PluginConfigVO {
id: number // 主键ID
pluginKey: string // 插件标识
name: string // 插件名称
description: string // 描述
deployType: number // 部署方式
fileName: string // 插件包文件名
version: string // 插件版本
type: number // 插件类型
protocol: string // 设备插件协议类型
status: number // 状态
configSchema: string // 插件配置项描述信息
config: string // 插件配置信息
script: string // 插件脚本
}
// IoT 插件配置 API
export const PluginConfigApi = {
// 查询插件配置分页
getPluginConfigPage: async (params: any) => {
return await request.get({ url: `/iot/plugin-config/page`, params })
},
// 查询插件配置详情
getPluginConfig: async (id: number) => {
return await request.get({ url: `/iot/plugin-config/get?id=` + id })
},
// 新增插件配置
createPluginConfig: async (data: PluginConfigVO) => {
return await request.post({ url: `/iot/plugin-config/create`, data })
},
// 修改插件配置
updatePluginConfig: async (data: PluginConfigVO) => {
return await request.put({ url: `/iot/plugin-config/update`, data })
},
// 删除插件配置
deletePluginConfig: async (id: number) => {
return await request.delete({ url: `/iot/plugin-config/delete?id=` + id })
},
// 修改插件状态
updatePluginStatus: async (data: any) => {
return await request.put({ url: `/iot/plugin-config/update-status`, data })
}
}

View File

@ -733,17 +733,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
activeMenu: '/iot/device/device'
},
component: () => import('@/views/iot/device/device/detail/index.vue')
},
{
path: 'plugin/detail/:id',
name: 'IoTPluginDetail',
meta: {
title: '插件详情',
noCache: true,
hidden: true,
activeMenu: '/iot/plugin'
},
component: () => import('@/views/iot/plugin/detail/index.vue')
}
]
}

View File

@ -1,166 +0,0 @@
<!-- 设备日志 -->
<template>
<ContentWrap>
<!-- 搜索区域 -->
<el-form :model="queryParams" inline>
<el-form-item>
<el-select v-model="queryParams.type" placeholder="所有" class="!w-160px">
<el-option label="所有" value="" />
<!-- TODO @super搞成枚举 -->
<el-option label="状态" value="state" />
<el-option label="事件" value="event" />
<el-option label="属性" value="property" />
<el-option label="服务" value="service" />
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="queryParams.identifier" placeholder="日志识符" class="!w-200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-switch
size="large"
width="80"
v-model="autoRefresh"
class="ml-20px"
inline-prompt
active-text="定时刷新"
inactive-text="定时刷新"
style="--el-switch-on-color: #13ce66"
/>
</el-form-item>
</el-form>
<!-- 日志列表 -->
<el-table v-loading="loading" :data="list" :stripe="true" class="whitespace-nowrap">
<el-table-column label="时间" align="center" prop="ts" width="180">
<template #default="scope">
{{ formatDate(scope.row.ts) }}
</template>
</el-table-column>
<el-table-column label="类型" align="center" prop="type" width="120" />
<!-- TODO @super标识符需要翻译 -->
<el-table-column label="标识符" align="center" prop="identifier" width="120" />
<el-table-column label="内容" align="center" prop="content" :show-overflow-tooltip="true" />
</el-table>
<!-- 分页 -->
<div class="mt-10px flex justify-end">
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getLogList"
/>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { DeviceApi } from '@/api/iot/device/device'
import { formatDate } from '@/utils/formatTime'
const props = defineProps<{
deviceKey: string
}>()
// 查询参数
const queryParams = reactive({
deviceKey: props.deviceKey,
type: '',
identifier: '',
pageNo: 1,
pageSize: 10
})
// 列表数据
const loading = ref(false)
const total = ref(0)
const list = ref([])
const autoRefresh = ref(false)
let timer: any = null // TODO @superautoRefreshEnableautoRefreshTimer对应上
// 类型映射 TODO @super需要删除么
const typeMap = {
lifetime: '生命周期',
state: '设备状态',
property: '属性',
event: '事件',
service: '服务'
}
/** 查询日志列表 */
const getLogList = async () => {
if (!props.deviceKey) return
loading.value = true
try {
const data = await DeviceApi.getDeviceLogPage(queryParams)
total.value = data.total
list.value = data.list
} finally {
loading.value = false
}
}
/** 获取日志名称 */
const getLogName = (log: any) => {
const { type, identifier } = log
let name = '未知'
if (type === 'property') {
if (identifier === 'set_reply') name = '设置回复'
else if (identifier === 'report') name = '上报'
else if (identifier === 'set') name = '设置'
} else if (type === 'state') {
name = identifier === 'online' ? '上线' : '下线'
} else if (type === 'lifetime') {
name = identifier === 'register' ? '注册' : name
}
return `${name}(${identifier})`
}
/** 搜索操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getLogList()
}
/** 监听自动刷新 */
watch(autoRefresh, (newValue) => {
if (newValue) {
timer = setInterval(() => {
getLogList()
}, 5000)
} else {
clearInterval(timer)
timer = null
}
})
/** 监听设备标识变化 */
watch(
() => props.deviceKey,
(newValue) => {
if (newValue) {
handleQuery()
}
}
)
/** 组件卸载时清除定时器 */
onBeforeUnmount(() => {
if (timer) {
clearInterval(timer)
}
})
/** 初始化 */
onMounted(() => {
if (props.deviceKey) {
getLogList()
}
})
</script>

View File

@ -0,0 +1,169 @@
<!-- 设备消息列表 -->
<template>
<ContentWrap>
<!-- 搜索区域 -->
<el-form :model="queryParams" inline>
<el-form-item>
<el-select v-model="queryParams.method" placeholder="所有方法" class="!w-160px" clearable>
<el-option v-for="item in methodOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="queryParams.upstream" placeholder="上行/下行" class="!w-160px" clearable>
<el-option label="上行" value="true" />
<el-option label="下行" value="false" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-switch
size="large"
width="80"
v-model="autoRefresh"
class="ml-20px"
inline-prompt
active-text="定时刷新"
inactive-text="定时刷新"
style="--el-switch-on-color: #13ce66"
/>
</el-form-item>
</el-form>
<!-- 消息列表 -->
<el-table v-loading="loading" :data="list" :stripe="true" class="whitespace-nowrap">
<el-table-column label="请求编号" align="center" prop="requestId" width="300" />
<el-table-column label="时间" align="center" prop="ts" width="180">
<template #default="scope">
{{ formatDate(scope.row.ts) }}
</template>
</el-table-column>
<el-table-column label="上行/下行" align="center" prop="upstream" width="140">
<template #default="scope">
<el-tag :type="scope.row.upstream ? 'primary' : 'success'">
{{ scope.row.upstream ? '上行' : '下行' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="是否回复" align="center" prop="reply" width="140">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.reply" />
</template>
</el-table-column>
<el-table-column label="请求方法" align="center" prop="method" width="140">
<template #default="scope">
{{ methodOptions.find(item => item.value === scope.row.method)?.label }}
</template>
</el-table-column>
<el-table-column label="请求/响应数据" align="center" prop="params" :show-overflow-tooltip="true">
<template #default="scope">
<span v-if="scope.row.reply">
{{ `{"code":${scope.row.code},"msg":"${scope.row.msg}","data":${scope.row.data}\}` }}
</span>
<span v-else>{{ scope.row.params }}</span>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="mt-10px flex justify-end">
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getMessageList"
/>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { DeviceApi } from '@/api/iot/device/device'
import { formatDate } from '@/utils/formatTime'
import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants'
const props = defineProps<{
deviceId: number
}>()
// 查询参数
const queryParams = reactive({
deviceId: props.deviceId,
method: undefined,
upstream: undefined,
pageNo: 1,
pageSize: 10
})
// 列表数据
const loading = ref(false)
const total = ref(0)
const list = ref([])
const autoRefresh = ref(false)
let autoRefreshTimer: any = null // TODO @superautoRefreshEnableautoRefreshTimer对应上
// 消息方法选项
const methodOptions = computed(() => {
return Object.values(IotDeviceMessageMethodEnum).map(item => ({
label: item.name,
value: item.method
}))
})
/** 查询消息列表 */
const getMessageList = async () => {
if (!props.deviceId) return
loading.value = true
try {
const data = await DeviceApi.getDeviceMessagePage(queryParams)
total.value = data.total
list.value = data.list
} finally {
loading.value = false
}
}
/** 搜索操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getMessageList()
}
/** 监听自动刷新 */
watch(autoRefresh, (newValue) => {
if (newValue) {
autoRefreshTimer = setInterval(() => {
getMessageList()
}, 5000)
} else {
clearInterval(autoRefreshTimer)
autoRefreshTimer = null
}
})
/** 监听设备标识变化 */
watch(
() => props.deviceId,
(newValue) => {
if (newValue) {
handleQuery()
}
}
)
/** 组件卸载时清除定时器 */
onBeforeUnmount(() => {
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer)
}
})
/** 初始化 */
onMounted(() => {
if (props.deviceId) {
getMessageList()
}
})
</script>

View File

@ -134,7 +134,7 @@
<el-col :span="12">
<el-tabs type="border-card">
<el-tab-pane label="设备日志">
<DeviceDetailsLog :device-key="device.deviceKey" />
<DeviceDetailsMessage :device-id="device.id" />
</el-tab-pane>
</el-tabs>
</el-col>
@ -146,7 +146,7 @@
import { ProductVO } from '@/api/iot/product/product'
import { SimulatorData, ThingModelApi } from '@/api/iot/thingmodel'
import { DeviceApi, DeviceStateEnum, DeviceVO } from '@/api/iot/device/device'
import DeviceDetailsLog from './DeviceDetailsLog.vue'
import DeviceDetailsMessage from './DeviceDetailsMessage.vue'
import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config'
import { DataDefinition } from '@/views/iot/thingmodel/components'

View File

@ -10,14 +10,12 @@
<el-tab-pane label="设备信息" name="info">
<DeviceDetailsInfo v-if="activeTab === 'info'" :product="product" :device="device" />
</el-tab-pane>
<el-tab-pane label="Topic 列表" />
<el-tab-pane label="物模型数据" name="model">
<DeviceDetailsModel v-if="activeTab === 'model'" :product="product" :device="device" />
</el-tab-pane>
<el-tab-pane label="子设备管理" v-if="product.deviceType === DeviceTypeEnum.GATEWAY" />
<el-tab-pane label="设备影子" />
<el-tab-pane label="设备日志" name="log">
<DeviceDetailsLog v-if="activeTab === 'log'" :device-key="device.deviceKey" />
<el-tab-pane label="设备消息" name="log">
<DeviceDetailsMessage v-if="activeTab === 'log'" :device-id="device.id" />
</el-tab-pane>
<el-tab-pane label="模拟设备" name="simulator">
<DeviceDetailsSimulator
@ -43,7 +41,7 @@ import { DeviceTypeEnum, ProductApi, ProductVO } from '@/api/iot/product/product
import DeviceDetailsHeader from './DeviceDetailsHeader.vue'
import DeviceDetailsInfo from './DeviceDetailsInfo.vue'
import DeviceDetailsModel from './DeviceDetailsModel.vue'
import DeviceDetailsLog from './DeviceDetailsLog.vue'
import DeviceDetailsMessage from './DeviceDetailsMessage.vue'
import DeviceDetailsSimulator from './DeviceDetailsSimulator.vue'
import DeviceDetailConfig from './DeviceDetailConfig.vue'
@ -75,7 +73,8 @@ const getProductData = async (id: number) => {
/** 初始化 */
const { delView } = useTagsViewStore() // 视图操作
const { currentRoute } = useRouter() // 路由
const router = useRouter() // 路由
const { currentRoute } = router
onMounted(async () => {
if (!id) {
message.warning('参数错误,产品不能为空!')

View File

@ -2,3 +2,39 @@
export const IOT_PROVIDE_KEY = {
PRODUCT: 'IOT_PRODUCT'
}
/**
* IoT 设备消息的方法枚举
*/
export const IotDeviceMessageMethodEnum = {
// ========== 设备状态 ==========
STATE_ONLINE: {
method: 'thing.state.online',
name: '设备上线',
upstream: true
},
STATE_OFFLINE: {
method: 'thing.state.offline',
name: '设备下线',
upstream: true
},
// ========== 设备属性 ==========
PROPERTY_POST: {
method: 'thing.property.post',
name: '属性上报',
upstream: true
},
PROPERTY_SET: {
method: 'thing.property.set',
name: '属性设置',
upstream: false
},
// ========== 设备事件 ==========
EVENT_POST: {
method: 'thing.event.post',
name: '事件上报',
upstream: true
}
}