Przeglądaj źródła

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

YunaiV 9 miesięcy temu
rodzic
commit
b5bd1a8fb4

+ 8 - 9
src/views/iot/rule/data/rule/DataRuleForm.vue

@@ -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>

+ 27 - 37
src/views/iot/rule/data/rule/components/SourceConfigForm.vue

@@ -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) || []
+  if (!row.productId || !shouldShowIdentifierSelect(row)) {
+    return []
+  }
+  const thingModels: any[] = thingModelCache.value.get(row.productId) || []
   let filteredModels: any[] = []
-
   if (row.method === IotDeviceMessageMethodEnum.EVENT_POST.method) {
-    // 事件类型,type = 3
-    filteredModels = thingModels.filter((item: any) => item.type === 3)
+    filteredModels = thingModels.filter((item: any) => item.type === IotThingModelTypeEnum.EVENT)
   } else if (row.method === IotDeviceMessageMethodEnum.PROPERTY_POST.method) {
-    // 属性类型,type = 1
-    filteredModels = thingModels.filter((item: any) => item.type === 1)
+    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)) {

+ 6 - 3
src/views/iot/rule/data/rule/index.vue

@@ -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 () => {

+ 1 - 1
src/views/iot/rule/data/sink/index.vue

@@ -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()

+ 5 - 9
src/views/iot/rule/scene/components/action/DataBridgeAction.vue

@@ -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>

+ 1 - 0
src/views/iot/thingmodel/config.ts

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

+ 7 - 0
src/views/iot/utils/constants.ts

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