【新增】 IOT 产品管理,Topic 类列表

pull/542/head
安浩浩 2024-09-23 23:34:21 +08:00
parent 93a0789e34
commit 6aaf611291
7 changed files with 275 additions and 100 deletions

View File

@ -67,8 +67,8 @@ export const DeviceApi = {
return await request.delete({ url: `/iot/device/delete?id=` + id }) return await request.delete({ url: `/iot/device/delete?id=` + id })
}, },
// 导出设备 Excel // 获取设备数量
exportDevice: async (params) => { getDeviceCount: async (productId: number) => {
return await request.download({ url: `/iot/device/export-excel`, params }) return await request.get({ url: `/iot/device/count?productId=` + productId })
} }
} }

View File

@ -14,6 +14,7 @@ export interface ProductVO {
netType: number // 联网方式, 0: Wi-Fi, 1: Cellular, 2: Ethernet, 3: 其他 netType: number // 联网方式, 0: Wi-Fi, 1: Cellular, 2: Ethernet, 3: 其他
protocolType: number // 接入网关协议, 0: modbus, 1: opc-ua, 2: customize, 3: ble, 4: zigbee protocolType: number // 接入网关协议, 0: modbus, 1: opc-ua, 2: customize, 3: ble, 4: zigbee
dataFormat: number // 数据格式, 0: 透传模式, 1: Alink JSON dataFormat: number // 数据格式, 0: 透传模式, 1: Alink JSON
deviceCount: number // 设备数量
} }
// iot 产品 API // iot 产品 API

View File

@ -58,49 +58,10 @@ const emit = defineEmits(['refresh'])
* *
* @param text 需要复制的文本 * @param text 需要复制的文本
*/ */
const copyToClipboard = async (text: string) => { const copyToClipboard = (text: string) => {
if (!navigator.clipboard) { navigator.clipboard.writeText(text).then(() => {
// Clipboard API使退 message.success('复制成功')
const textarea = document.createElement('textarea') })
textarea.value = text
//
textarea.style.position = 'fixed'
textarea.style.top = '0'
textarea.style.left = '0'
textarea.style.width = '2em'
textarea.style.height = '2em'
textarea.style.padding = '0'
textarea.style.border = 'none'
textarea.style.outline = 'none'
textarea.style.boxShadow = 'none'
textarea.style.background = 'transparent'
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
try {
const successful = document.execCommand('copy')
if (successful) {
message.success('复制成功!')
} else {
message.error('复制失败,请手动复制')
}
} catch (err) {
console.error('Fallback: Oops, unable to copy', err)
message.error('复制失败,请手动复制')
}
document.body.removeChild(textarea)
return
}
try {
await navigator.clipboard.writeText(text)
message.success('复制成功!')
} catch (err) {
console.error('Async: Could not copy text: ', err)
message.error('复制失败,请手动复制')
}
} }
/** /**

View File

@ -103,49 +103,10 @@ const emit = defineEmits(['refresh'])
const activeNames = ref(['basicInfo']) const activeNames = ref(['basicInfo'])
// //
const copyToClipboard = async (text: string) => { const copyToClipboard = (text: string) => {
if (!navigator.clipboard) { navigator.clipboard.writeText(text).then(() => {
// Clipboard API使退 message.success('复制成功')
const textarea = document.createElement('textarea') })
textarea.value = text
//
textarea.style.position = 'fixed'
textarea.style.top = '0'
textarea.style.left = '0'
textarea.style.width = '2em'
textarea.style.height = '2em'
textarea.style.padding = '0'
textarea.style.border = 'none'
textarea.style.outline = 'none'
textarea.style.boxShadow = 'none'
textarea.style.background = 'transparent'
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
try {
const successful = document.execCommand('copy')
if (successful) {
message.success('复制成功!')
} else {
message.error('复制失败,请手动复制')
}
} catch (err) {
console.error('Fallback: Oops, unable to copy', err)
message.error('复制失败,请手动复制')
}
document.body.removeChild(textarea)
return
}
try {
await navigator.clipboard.writeText(text)
message.success('复制成功!')
} catch (err) {
console.error('Async: Could not copy text: ', err)
message.error('复制失败,请手动复制')
}
} }
// MQTT // MQTT

View File

@ -45,8 +45,8 @@
</el-descriptions> </el-descriptions>
<el-descriptions :column="5" direction="horizontal"> <el-descriptions :column="5" direction="horizontal">
<el-descriptions-item label="设备数"> <el-descriptions-item label="设备数">
0 {{ product.deviceCount }}
<el-button @click="goToManagement(product.productKey)"></el-button> <el-button @click="goToManagement(product.id)"></el-button>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</ContentWrap> </ContentWrap>
@ -63,8 +63,11 @@ const copyToClipboard = (text: string) => {
message.success('复制成功') message.success('复制成功')
}) })
} }
const goToManagement = (productKey: string) => {
message.warning('暂未开放') //
const { currentRoute, push } = useRouter()
const goToManagement = (productId: string) => {
push({ name: 'IoTDevice', query: { productId } })
} }
// //
@ -93,6 +96,9 @@ const confirmUnpublish = async (id: number) => {
} }
} }
// Props
const { product } = defineProps<{ product: ProductVO }>() const { product } = defineProps<{ product: ProductVO }>()
// Emits
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
</script> </script>

View File

@ -0,0 +1,229 @@
<template>
<ContentWrap>
<el-tabs>
<el-tab-pane label="基础通信 Topic">
<Table :columns="columns1" :data="data1" :span-method="objectSpanMethod" />
</el-tab-pane>
<el-tab-pane label="物模型通信 Topic">
<Table :columns="columns2" :data="data2" :span-method="objectSpanMethod" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import { ProductVO } from '@/api/iot/product'
const { product } = defineProps<{ product: ProductVO }>()
//
const columns1 = reactive([
{
label: '功能',
field: 'function',
width: 150
},
{
label: 'Topic 类',
field: 'topicClass',
width: 500
},
{
label: '操作权限',
field: 'operationPermission',
width: 100
},
{
label: '描述',
field: 'description'
}
])
const columns2 = reactive([
{
label: '功能',
field: 'function',
width: 150
},
{
label: 'Topic 类',
field: 'topicClass',
width: 500
},
{
label: '操作权限',
field: 'operationPermission',
width: 150
},
{
label: '描述',
field: 'description'
}
])
//
const data1 = reactive([
{
function: 'OTA 升级',
topicClass: '/ota/device/inform/' + product.productKey + '/${deviceName}',
operationPermission: '发布',
description: '设备上报固件升级信息'
},
{
function: 'OTA 升级',
topicClass: '/ota/device/upgrade/' + product.productKey + '/${deviceName}',
operationPermission: '订阅',
description: '固件升级信息下行'
},
{
function: 'OTA 升级',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/ota/firmware/get',
operationPermission: '发布',
description: '设备上报固件升级进度'
},
{
function: 'OTA 升级',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/ota/firmware/get',
operationPermission: '发布',
description: '设备主动拉取固件升级信息'
},
{
function: '设备标签',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/deviceinfo/update',
operationPermission: '发布',
description: '设备上报标签数据'
},
{
function: '设备标签',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/deviceinfo/update_reply',
operationPermission: '订阅',
description: '云端响应标签上报'
},
{
function: '设备标签',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/deviceinfo/delete',
operationPermission: '订阅',
description: '设备删除标签信息'
},
{
function: '设备标签',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/deviceinfo/delete_reply',
operationPermission: '订阅',
description: '云端响应标签删除'
},
{
function: '时钟同步',
topicClass: '/ext/ntp/' + product.productKey + '/${deviceName}/request',
operationPermission: '发布',
description: 'NTP 时钟同步请求'
},
{
function: '时钟同步',
topicClass: '/ext/ntp/' + product.productKey + '/${deviceName}/response',
operationPermission: '订阅',
description: 'NTP 时钟同步响应'
},
{
function: '设备影子',
topicClass: '/shadow/update/' + product.productKey + '/${deviceName}',
operationPermission: '发布',
description: '设备影子发布'
},
{
function: '设备影子',
topicClass: '/shadow/get/' + product.productKey + '/${deviceName}',
operationPermission: '订阅',
description: '设备接收影子变更'
},
{
function: '配置更新',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/config/push',
operationPermission: '订阅',
description: '云端主动下推配置信息'
},
{
function: '配置更新',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/config/get',
operationPermission: '发布',
description: '设备端查询配置信息'
},
{
function: '配置更新',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/config/get_reply',
operationPermission: '订阅',
description: '云端响应配置信息'
},
{
function: '广播',
topicClass: '/broadcast/' + product.productKey + '/${identifier}',
operationPermission: '订阅',
description: '广播 Topicidentifier 为用户自定义字符串'
}
])
const data2 = reactive([
{
function: '属性上报',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/event/property/post',
operationPermission: '发布',
description: '设备属性上报'
},
{
function: '属性上报',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/event/property/post_reply',
operationPermission: '订阅',
description: '云端响应属性上报'
},
{
function: '属性设置',
topicClass: '/sys/' + product.productKey + '/${deviceName}/thing/service/property/set',
operationPermission: '订阅',
description: '设备属性设置'
},
{
function: '事件上报',
topicClass:
'/sys/' + product.productKey + '/${deviceName}/thing/event/${tsl.event.identifier}/post',
operationPermission: '发布',
description: '设备事件上报'
},
{
function: '事件上报',
topicClass:
'/sys/' +
product.productKey +
'/${deviceName}/thing/event/${tsl.event.identifier}/post_reply',
operationPermission: '订阅',
description: '云端响应事件上报'
},
{
function: '服务调用',
topicClass:
'/sys/' + product.productKey + '/${deviceName}/thing/service/${tsl.service.identifier}',
operationPermission: '订阅',
description: '设备服务调用'
},
{
function: '服务调用',
topicClass:
'/sys/' + product.productKey + '/${deviceName}/thing/service/${tsl.service.identifier}_reply',
operationPermission: '发布',
description: '设备端响应服务调用'
}
])
//
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }: SpanMethodProps) => {
if (columnIndex === 0 || columnIndex === 2) {
if (rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
</script>

View File

@ -1,11 +1,13 @@
<template> <template>
<ProductDetailsHeader :loading="loading" :product="product" @refresh="getProductData(id)" /> <ProductDetailsHeader :loading="loading" :product="product" @refresh="() => getProductData(id)" />
<el-col> <el-col>
<el-tabs> <el-tabs>
<el-tab-pane label="产品信息"> <el-tab-pane label="产品信息">
<ProductDetailsInfo :product="product" /> <ProductDetailsInfo :product="product" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Topic 类列表" /> <el-tab-pane label="Topic 类列表">
<ProductTopic :product="product" />
</el-tab-pane>
<el-tab-pane label="物模型"> <el-tab-pane label="物模型">
<!-- <ProductDetailsModel :product="product" />--> <!-- <ProductDetailsModel :product="product" />-->
</el-tab-pane> </el-tab-pane>
@ -15,10 +17,10 @@
</el-col> </el-col>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useTagsViewStore } from '@/store/modules/tagsView'
import { ProductApi, ProductVO } from '@/api/iot/product' import { ProductApi, ProductVO } from '@/api/iot/product'
import ProductDetailsHeader from '@/views/iot/product/detail/ProductDetailsHeader.vue' import ProductDetailsHeader from '@/views/iot/product/detail/ProductDetailsHeader.vue'
import ProductDetailsInfo from '@/views/iot/product/detail/ProductDetailsInfo.vue' import ProductDetailsInfo from '@/views/iot/product/detail/ProductDetailsInfo.vue'
import { DeviceApi } from '@/api/iot/device'
defineOptions({ name: 'IoTProductDetail' }) defineOptions({ name: 'IoTProductDetail' })
@ -33,17 +35,25 @@ const getProductData = async (id: number) => {
loading.value = true loading.value = true
try { try {
product.value = await ProductApi.getProduct(id) product.value = await ProductApi.getProduct(id)
console.log(product.value) console.log('Product data:', product.value)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
/** 获取物模型 */ //
const getDeviceCount = async (productId: string) => {
try {
const count = await DeviceApi.getDeviceCount(productId)
console.log('Device count response:', count)
return count
} catch (error) {
console.error('Error fetching device count:', error)
return 0
}
}
/** 初始化 */ /** 初始化 */
const { delView } = useTagsViewStore() //
const { currentRoute } = useRouter() //
onMounted(async () => { onMounted(async () => {
if (!id) { if (!id) {
message.warning('参数错误,产品不能为空!') message.warning('参数错误,产品不能为空!')
@ -51,5 +61,12 @@ onMounted(async () => {
return return
} }
await getProductData(id) await getProductData(id)
//
if (product.value.id) {
product.value.deviceCount = await getDeviceCount(product.value.id)
console.log('Device count:', product.value.deviceCount)
} else {
console.error('Product ID is undefined')
}
}) })
</script> </script>