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

This commit is contained in:
YunaiV
2026-01-25 18:50:26 +08:00
parent c55465a6c0
commit 1ce562601f
17 changed files with 507 additions and 173 deletions

View File

@@ -0,0 +1 @@
export { default as ProductSelect } from './select.vue';

View File

@@ -0,0 +1,57 @@
<script lang="ts" setup>
import type { IotProductApi } from '#/api/iot/product/product';
import { onMounted, ref } from 'vue';
import { Select } from 'ant-design-vue';
import { getSimpleProductList } from '#/api/iot/product/product';
/** 产品下拉选择器组件 */
defineOptions({ name: 'ProductSelect' });
const props = defineProps<{
deviceType?: number; // 设备类型过滤
modelValue?: number;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value?: number): void;
(e: 'change', value?: number): void;
}>();
const loading = ref(false);
const productList = ref<IotProductApi.Product[]>([]);
/** 处理选择变化 */
function handleChange(value?: number) {
emit('update:modelValue', value);
emit('change', value);
}
/** 获取产品列表 */
async function getProductList() {
try {
loading.value = true;
productList.value = (await getSimpleProductList(props.deviceType)) || [];
} finally {
loading.value = false;
}
}
onMounted(() => {
getProductList();
});
</script>
<template>
<Select
:value="modelValue"
:options="productList.map((p) => ({ label: p.name, value: p.id }))"
:loading="loading"
placeholder="请选择产品"
allow-clear
class="w-full"
@change="handleChange"
/>
</template>

View File

@@ -153,9 +153,20 @@ export function useBasicFormSchema(
];
}
/** 高级设置表单字段(图标、图片、产品描述) */
/** 高级设置表单字段(图标、图片、产品描述、动态注册 */
export function useAdvancedFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'registerEnabled',
label: '动态注册',
component: 'Switch',
componentProps: {
checkedChildren: '开',
unCheckedChildren: '关',
},
defaultValue: false,
help: '设备动态注册无需一一烧录设备证书DeviceSecret每台设备烧录相同的产品证书即 ProductKey 和 ProductSecret ,云端鉴权通过后下发设备证书,您可以根据需要开启或关闭动态注册,保障安全性。',
},
{
fieldName: 'icon',
label: '产品图标',

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup>
import type { IotProductApi } from '#/api/iot/product/product';
import { ref } from 'vue';
import { DeviceTypeEnum, DICT_TYPE } from '@vben/constants';
import { Card, Descriptions } from 'ant-design-vue';
import { Button, Card, Descriptions, message } from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
@@ -13,11 +15,28 @@ interface Props {
defineProps<Props>();
const showProductSecret = ref(false); // 是否显示产品密钥
/** 格式化日期 */
function formatDate(date?: Date | string) {
if (!date) return '-';
return new Date(date).toLocaleString('zh-CN');
}
/** 切换产品密钥显示状态 */
function toggleProductSecretVisible() {
showProductSecret.value = !showProductSecret.value;
}
/** 复制到剪贴板 */
async function copyToClipboard(text: string) {
try {
await navigator.clipboard.writeText(text);
message.success('复制成功');
} catch {
message.error('复制失败');
}
}
</script>
<template>
@@ -54,6 +73,23 @@ function formatDate(date?: Date | string) {
>
<DictTag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
</Descriptions.Item>
<Descriptions.Item v-if="product.productSecret" label="ProductSecret">
<span v-if="showProductSecret">{{ product.productSecret }}</span>
<span v-else>********</span>
<Button class="ml-2" size="small" @click="toggleProductSecretVisible">
{{ showProductSecret ? '隐藏' : '显示' }}
</Button>
<Button
class="ml-2"
size="small"
@click="copyToClipboard(product.productSecret || '')"
>
复制
</Button>
</Descriptions.Item>
<Descriptions.Item label="动态注册">
{{ product.registerEnabled ? '已开启' : '未开启' }}
</Descriptions.Item>
<Descriptions.Item :span="3" label="产品描述">
{{ product.description || '-' }}
</Descriptions.Item>

View File

@@ -68,6 +68,7 @@ async function getAdvancedFormValues() {
}
// 表单未挂载(折叠状态),从 formData 中获取
return {
registerEnabled: formData.value?.registerEnabled,
icon: formData.value?.icon,
picUrl: formData.value?.picUrl,
description: formData.value?.description,
@@ -120,6 +121,7 @@ const [Modal, modalApi] = useVbenModal({
await formApi.setValues(formData.value);
// 如果存在高级字段数据,自动展开 Collapse
if (
formData.value?.registerEnabled ||
formData.value?.icon ||
formData.value?.picUrl ||
formData.value?.description