feat:【IoT 物联网】完整实现“数据流转”功能(后台管理)

This commit is contained in:
YunaiV
2025-06-25 21:43:16 +08:00
parent b1ae56281d
commit b5bd1a8fb4
7 changed files with 58 additions and 62 deletions

View File

@ -1,5 +1,5 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="860">
<Dialog :title="dialogTitle" v-model="dialogVisible" width="870">
<el-form
ref="formRef"
:model="formData"
@ -78,7 +78,7 @@ const formData = ref({
const formRules = reactive({
name: [{ required: true, message: '规则名称不能为空', trigger: 'blur' }],
status: [{ required: true, message: '规则状态不能为空', trigger: 'blur' }],
// sourceConfigs: [{ required: true, message: '数据源配置数组不能为空', trigger: 'blur' }],
sourceConfigs: [{ required: true, message: '数据源配置数组不能为空', trigger: 'blur' }],
sinkIds: [{ required: true, message: '数据目的编号数组不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
@ -115,16 +115,16 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 校验数据源配置
await sourceConfigRef.value?.validate()
formData.value.sourceConfigs = sourceConfigRef.value?.getData() || []
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = { ...formData.value } as unknown as DataRule
data.sourceConfigs = sourceConfigRef.value?.getData() || []
if (formType.value === 'create') {
await DataRuleApi.createDataRule(data)
message.success(t('common.createSuccess'))
@ -141,7 +141,7 @@ const submitForm = async () => {
}
/** 重置表单 */
const resetForm = () => {
const resetForm = async () => {
formData.value = {
id: undefined,
name: undefined,
@ -152,8 +152,7 @@ const resetForm = () => {
}
formRef.value?.resetFields()
// 重置数据源配置
nextTick(() => {
sourceConfigRef.value?.setData([])
})
await nextTick()
sourceConfigRef.value?.setData([])
}
</script>

View File

@ -15,6 +15,7 @@
placeholder="请选择产品"
@change="handleProductChange(row, $index)"
clearable
filterable
style="width: 100%"
>
<el-option
@ -33,10 +34,11 @@
<el-select
v-model="row.deviceId"
placeholder="请选择设备"
@change="handleDeviceChange(row, $index)"
clearable
filterable
style="width: 100%"
>
<el-option label="全部设备" :value="0" />
<el-option
v-for="device in getFilteredDevices(row.productId)"
:key="device.id"
@ -47,14 +49,15 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column label="消息方法" min-width="180">
<el-table-column label="消息" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.method`" :rules="formRules.method" class="mb-0px!">
<el-select
v-model="row.method"
placeholder="请选择消息方法"
placeholder="请选择消息"
@change="handleMethodChange(row, $index)"
clearable
filterable
style="width: 100%"
>
<el-option
@ -67,7 +70,7 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column label="标识符" min-width="150">
<el-table-column label="标识符" min-width="200">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.identifier`" class="mb-0px!">
<el-select
@ -75,6 +78,7 @@
v-model="row.identifier"
placeholder="请选择标识符"
clearable
filterable
style="width: 100%"
v-loading="row.identifierLoading"
>
@ -85,7 +89,6 @@
:value="item.value"
/>
</el-select>
<span v-else>-</span>
</el-form-item>
</template>
</el-table-column>
@ -105,12 +108,12 @@
import { ProductApi } from '@/api/iot/product/product'
import { DeviceApi } from '@/api/iot/device/device'
import { ThingModelApi } from '@/api/iot/thingmodel'
import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants'
import { IotDeviceMessageMethodEnum, IotThingModelTypeEnum } from '@/views/iot/utils/constants'
const formData = ref<any[]>([])
const productList = ref<any[]>([]) // 产品列表
const deviceList = ref<any[]>([]) // 设备列表
const thingModelMap = ref<Map<number, any[]>>(new Map()) // 缓存物模型数据key 为 productId
const thingModelCache = ref<Map<number, any[]>>(new Map()) // 缓存物模型数据key 为 productId
const formRules = reactive({
productId: [{ required: true, message: '产品不能为空', trigger: 'change' }],
@ -124,7 +127,7 @@ const upstreamMethods = computed(() => {
return Object.values(IotDeviceMessageMethodEnum).filter((item) => item.upstream)
})
/** 根据产品ID过滤设备 */
/** 根据产品 ID 过滤设备 */
const getFilteredDevices = (productId: number) => {
if (!productId) return []
return deviceList.value.filter((device: any) => device.productId === productId)
@ -132,27 +135,24 @@ const getFilteredDevices = (productId: number) => {
/** 判断是否需要显示标识符选择器 */
const shouldShowIdentifierSelect = (row: any) => {
return (
row.method === IotDeviceMessageMethodEnum.EVENT_POST.method ||
row.method === IotDeviceMessageMethodEnum.PROPERTY_POST.method
)
return [
IotDeviceMessageMethodEnum.EVENT_POST.method,
IotDeviceMessageMethodEnum.PROPERTY_POST.method
].includes(row.method)
}
/** 获取物模型选项 */
const getThingModelOptions = (row: any) => {
if (!row.productId || !shouldShowIdentifierSelect(row)) return []
const thingModels: any[] = thingModelMap.value.get(row.productId) || []
let filteredModels: any[] = []
if (row.method === IotDeviceMessageMethodEnum.EVENT_POST.method) {
// 事件类型type = 3
filteredModels = thingModels.filter((item: any) => item.type === 3)
} else if (row.method === IotDeviceMessageMethodEnum.PROPERTY_POST.method) {
// 属性类型type = 1
filteredModels = thingModels.filter((item: any) => item.type === 1)
if (!row.productId || !shouldShowIdentifierSelect(row)) {
return []
}
const thingModels: any[] = thingModelCache.value.get(row.productId) || []
let filteredModels: any[] = []
if (row.method === IotDeviceMessageMethodEnum.EVENT_POST.method) {
filteredModels = thingModels.filter((item: any) => item.type === IotThingModelTypeEnum.EVENT)
} else if (row.method === IotDeviceMessageMethodEnum.PROPERTY_POST.method) {
filteredModels = thingModels.filter((item: any) => item.type === IotThingModelTypeEnum.PROPERTY)
}
return filteredModels.map((item: any) => ({
label: `${item.name} (${item.identifier})`,
value: item.identifier
@ -179,39 +179,30 @@ const loadDeviceList = async () => {
/** 加载物模型数据 */
const loadThingModel = async (productId: number) => {
if (thingModelMap.value.has(productId)) {
return // 已缓存,无需重复加载
// 已缓存,无需重复加载
if (thingModelCache.value.has(productId)) {
return
}
try {
const thingModels = await ThingModelApi.getThingModelList({ productId })
thingModelMap.value.set(productId, thingModels)
thingModelCache.value.set(productId, thingModels)
} catch (error) {
console.error('加载物模型失败:', error)
thingModelMap.value.set(productId, [])
}
}
/** 产品变化时处理 */
const handleProductChange = async (row: any, _index: number) => {
// 清空其他字段
row.deviceId = undefined
row.deviceId = 0
row.method = undefined
row.identifier = undefined
// 根据产品ID过滤设备列表不需要额外处理计算属性会自动过滤
}
/** 设备变化时处理 */
const handleDeviceChange = (row: any, _index: number) => {
// 设备变化时可以做一些额外处理
row.identifierLoading = false
}
/** 消息方法变化时处理 */
const handleMethodChange = async (row: any, _index: number) => {
// 清空标识符
row.identifier = undefined
// 如果需要加载物模型数据
if (shouldShowIdentifierSelect(row) && row.productId) {
row.identifierLoading = true
@ -254,7 +245,6 @@ const setData = (data: any[]) => {
...item,
identifierLoading: false
}))
// 为已有数据预加载物模型
data?.forEach(async (item) => {
if (item.productId && shouldShowIdentifierSelect(item)) {

View File

@ -76,8 +76,12 @@
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="数据源配置数组" align="center" prop="sourceConfigs" />
<el-table-column label="数据目的编号数组" align="center" prop="sinkIds" />
<el-table-column label="数据源" align="center" prop="sourceConfigs">
<template #default="scope"> {{ scope.row.sourceConfigs?.length || 0 }} </template>
</el-table-column>
<el-table-column label="数据目的" align="center" prop="sinkIds">
<template #default="scope"> {{ scope.row.sinkIds?.length || 0 }} </template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
@ -142,7 +146,6 @@ const queryParams = reactive({
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {

View File

@ -198,7 +198,7 @@ const handleDelete = async (id: number) => {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await DataSinkApi.deleteDataBridge(id)
await DataSinkApi.deleteDataSink(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()

View File

@ -4,7 +4,7 @@
<span class="mr-10px w-80px">数据流转目的</span>
<el-select v-model="dataBridgeId" class="!w-240px" clearable placeholder="选择数据流转目的">
<el-option
v-for="bridge in dataBridgeList"
v-for="bridge in dataSinkList"
:key="bridge.id"
:label="bridge.name"
:value="bridge.id"
@ -25,14 +25,10 @@ const props = defineProps<{ modelValue: any }>()
const emits = defineEmits(['update:modelValue'])
const dataBridgeId = useVModel(props, 'modelValue', emits)
const dataBridgeList = ref<any[]>([]) // 数据流转目的列表
const dataSinkList = ref<any[]>([]) // 数据流转目的列表
/** 获取数据流转目的列表 */
const getDataBridgeList = async () => {
dataBridgeList.value = await DataSinkApi.getDataSinkSimpleList()
}
onMounted(() => {
getDataBridgeList()
onMounted(async () => {
// 获取数据流转目的列表
dataSinkList.value = await DataSinkApi.getDataSinkSimpleList()
})
</script>

View File

@ -55,6 +55,7 @@ export const getDataTypeOptionsLabel = (value: string) => {
return dataType && `${dataType.value}(${dataType.label})`
}
// TODO @puhui999使用 ThingModelTypeEnum 替换
// IOT 产品物模型类型枚举类
export const ThingModelType = {
PROPERTY: 1, // 属性

View File

@ -52,3 +52,10 @@ export const IotDeviceMessageMethodEnum = {
upstream: false
}
}
// IOT 产品物模型类型枚举类
export const IotThingModelTypeEnum = {
PROPERTY: 1, // 属性
SERVICE: 2, // 服务
EVENT: 3 // 事件
}