feat(iot):【网关设备:80%】动态注册的初步实现(已测试)

This commit is contained in:
YunaiV
2026-01-25 16:58:05 +08:00
parent 69d8224305
commit 2a0ce1fb66
7 changed files with 112 additions and 28 deletions

View File

@@ -21,7 +21,6 @@ export interface DeviceVO {
mqttClientId: string // MQTT 客户端 ID
mqttUsername: string // MQTT 用户名
mqttPassword: string // MQTT 密码
authType: string // 认证类型
latitude?: number // 设备位置的纬度
longitude?: number // 设备位置的经度
areaId: number // 地区编码
@@ -161,12 +160,12 @@ export const DeviceApi = {
},
// 绑定子设备到网关
bindDeviceGateway: async (data: { ids: number[]; gatewayId: number }) => {
bindDeviceGateway: async (data: { subIds: number[]; gatewayId: number }) => {
return await request.put({ url: `/iot/device/bind-gateway`, data })
},
// 解绑子设备与网关
unbindDeviceGateway: async (data: { ids: number[] }) => {
unbindDeviceGateway: async (data: { subIds: number[]; gatewayId: number }) => {
return await request.put({ url: `/iot/device/unbind-gateway`, data })
},

View File

@@ -5,6 +5,8 @@ export interface ProductVO {
id: number // 产品编号
name: string // 产品名称
productKey: string // 产品标识
productSecret?: string // 产品密钥
registerEnabled?: boolean // 动态注册
protocolId: number // 协议编号
categoryId: number // 产品所属品类标识符
categoryName?: string // 产品所属品类名称

View File

@@ -30,20 +30,6 @@
:disabled="formType === 'update'"
/>
</el-form-item>
<el-form-item
v-if="formData.deviceType === DeviceTypeEnum.GATEWAY_SUB"
label="网关设备"
prop="gatewayId"
>
<el-select v-model="formData.gatewayId" placeholder="子设备可选择父设备" clearable>
<el-option
v-for="gateway in gatewayDevices"
:key="gateway.id"
:label="gateway.nickname || gateway.deviceName"
:value="gateway.id"
/>
</el-select>
</el-form-item>
<el-collapse>
<el-collapse-item title="更多配置">
@@ -114,7 +100,6 @@ const formData = ref({
deviceName: undefined,
nickname: undefined,
picUrl: undefined,
gatewayId: undefined,
deviceType: undefined as number | undefined,
serialNumber: undefined,
longitude: undefined as number | string | undefined,
@@ -222,7 +207,6 @@ const formRules = reactive({
})
const formRef = ref() // 表单 Ref
const products = ref<ProductVO[]>([]) // 产品列表
const gatewayDevices = ref<DeviceVO[]>([]) // 网关设备列表
const deviceGroups = ref<any[]>([])
/** 打开弹窗 */
@@ -242,8 +226,6 @@ const open = async (type: string, id?: number) => {
}
}
// 加载网关设备列表
gatewayDevices.value = await DeviceApi.getSimpleDeviceList(DeviceTypeEnum.GATEWAY)
// 加载产品列表
products.value = await ProductApi.getSimpleProductList()
// 加载设备分组列表
@@ -283,7 +265,6 @@ const resetForm = () => {
deviceName: undefined,
nickname: undefined,
picUrl: undefined,
gatewayId: undefined,
deviceType: undefined,
serialNumber: undefined,
longitude: undefined,

View File

@@ -225,7 +225,7 @@ const handleBindSubmit = async () => {
bindFormLoading.value = true
try {
await DeviceApi.bindDeviceGateway({
ids: bindSelectedIds.value,
subIds: bindSelectedIds.value,
gatewayId: props.gatewayId
})
message.success('绑定成功')
@@ -240,7 +240,7 @@ const handleBindSubmit = async () => {
const handleUnbind = async (id: number) => {
try {
await message.confirm('确定要解绑该子设备吗?')
await DeviceApi.unbindDeviceGateway({ ids: [id] })
await DeviceApi.unbindDeviceGateway({ subIds: [id], gatewayId: props.gatewayId })
message.success('解绑成功')
await getSubDeviceList()
} catch {}
@@ -250,7 +250,7 @@ const handleUnbind = async (id: number) => {
const handleUnbindBatch = async () => {
try {
await message.confirm(`确定要解绑选中的 ${selectedIds.value.length} 个子设备吗?`)
await DeviceApi.unbindDeviceGateway({ ids: selectedIds.value })
await DeviceApi.unbindDeviceGateway({ subIds: selectedIds.value, gatewayId: props.gatewayId })
message.success('批量解绑成功')
selectedIds.value = []
await getSubDeviceList()

View File

@@ -75,6 +75,20 @@
</el-form-item>
<el-collapse>
<el-collapse-item title="更多配置">
<el-form-item label="动态注册" prop="registerEnabled">
<template #label>
<el-tooltip
content="设备动态注册无需一一烧录设备证书DeviceSecret每台设备烧录相同的产品证书即 ProductKey 和 ProductSecret ,云端鉴权通过后下发设备证书,您可以根据需要开启或关闭动态注册,保障安全性。"
placement="top"
>
<span>
动态注册
<Icon icon="ep:question-filled" class="ml-2px" />
</span>
</el-tooltip>
</template>
<el-switch v-model="formData.registerEnabled" />
</el-form-item>
<el-form-item label="产品图标" prop="icon">
<UploadImg v-model="formData.icon" :height="'80px'" :width="'80px'" />
</el-form-item>
@@ -120,7 +134,8 @@ const formData = ref({
description: undefined,
deviceType: undefined,
netType: undefined,
codecType: CodecTypeEnum.ALINK
codecType: CodecTypeEnum.ALINK,
registerEnabled: false
})
const formRules = reactive({
productKey: [{ required: true, message: 'ProductKey 不能为空', trigger: 'blur' }],
@@ -194,7 +209,8 @@ const resetForm = () => {
description: undefined,
deviceType: undefined,
netType: undefined,
codecType: CodecTypeEnum.ALINK
codecType: CodecTypeEnum.ALINK,
registerEnabled: false
}
formRef.value?.resetFields()
}

View File

@@ -21,6 +21,28 @@
>
<dict-tag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
</el-descriptions-item>
<el-descriptions-item label="动态注册">
<el-tag :type="product.registerEnabled ? 'success' : 'info'">
{{ product.registerEnabled ? '已开启' : '已关闭' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="产品密钥">
<div class="flex items-center">
<span>{{ secretVisible ? product.productSecret : '******' }}</span>
<el-button link type="primary" class="ml-2" @click="secretVisible = !secretVisible">
<Icon :icon="secretVisible ? 'ep:hide' : 'ep:view'" />
</el-button>
<el-button
v-if="secretVisible && product.productSecret"
link
type="primary"
class="ml-1"
@click="copySecret"
>
<Icon icon="ep:document-copy" />
</el-button>
</div>
</el-descriptions-item>
<el-descriptions-item label="产品描述">{{ product.description }}</el-descriptions-item>
</el-descriptions>
</ContentWrap>
@@ -29,6 +51,19 @@
import { DICT_TYPE } from '@/utils/dict'
import { DeviceTypeEnum, ProductVO } from '@/api/iot/product/product'
import { formatDate } from '@/utils/formatTime'
import { useClipboard } from '@vueuse/core'
const { product } = defineProps<{ product: ProductVO }>()
const message = useMessage()
const secretVisible = ref(false)
const { copy } = useClipboard()
/** 复制产品密钥 */
const copySecret = async () => {
if (product.productSecret) {
await copy(product.productSecret)
message.success('复制成功')
}
}
</script>

View File

@@ -24,7 +24,41 @@ export const IotDeviceMessageMethodEnum = {
// ========== 设备状态 ==========
STATE_UPDATE: {
method: 'thing.state.update',
name: '设备状态更',
name: '设备状态更',
upstream: true
},
// ========== 拓扑管理 ==========
TOPO_ADD: {
method: 'thing.topo.add',
name: '添加拓扑关系',
upstream: true
},
TOPO_DELETE: {
method: 'thing.topo.delete',
name: '删除拓扑关系',
upstream: true
},
TOPO_GET: {
method: 'thing.topo.get',
name: '获取拓扑关系',
upstream: true
},
TOPO_CHANGE: {
method: 'thing.topo.change',
name: '拓扑关系变更通知',
upstream: false
},
// ========== 设备注册 ==========
DEVICE_REGISTER: {
method: 'thing.auth.register',
name: '设备动态注册',
upstream: true
},
SUB_DEVICE_REGISTER: {
method: 'thing.auth.register.sub',
name: '子设备动态注册',
upstream: true
},
@@ -39,6 +73,11 @@ export const IotDeviceMessageMethodEnum = {
name: '属性设置',
upstream: false
},
PROPERTY_PACK_POST: {
method: 'thing.event.property.pack.post',
name: '批量上报(属性 + 事件 + 子设备)',
upstream: true
},
// ========== 设备事件 ==========
EVENT_POST: {
@@ -59,6 +98,18 @@ export const IotDeviceMessageMethodEnum = {
method: 'thing.config.push',
name: '配置推送',
upstream: false
},
// ========== OTA 固件 ==========
OTA_UPGRADE: {
method: 'thing.ota.upgrade',
name: 'OTA 固件信息推送',
upstream: false
},
OTA_PROGRESS: {
method: 'thing.ota.progress',
name: 'OTA 升级进度上报',
upstream: true
}
}