Kaynağa Gözat

【功能完善】IOT: ThingModel 服务和事件

puhui999 7 ay önce
ebeveyn
işleme
5391720b8b

+ 0 - 53
src/api/iot/thingmodel/index.ts

@@ -38,59 +38,6 @@ export interface ThingModelService {
   [key: string]: any
 }
 
-// IOT 产品物模型类型枚举类
-export const ThingModelType = {
-  PROPERTY: 1, // 属性
-  SERVICE: 2, // 服务
-  EVENT: 3 // 事件
-} as const
-
-// IOT 产品物模型访问模式枚举类
-export const ThingModelAccessMode = {
-  READ_WRITE: {
-    label: '读写',
-    value: 'rw'
-  },
-  READ_ONLY: {
-    label: '只读',
-    value: 'r'
-  }
-} as const
-
-// IOT 产品物模型服务调用方式枚举
-export const ThingModelServiceCallType = {
-  ASYNC: {
-    label: '异步调用',
-    value: 'async'
-  },
-  SYNC: {
-    label: '同步调用',
-    value: 'sync'
-  }
-} as const
-
-// IOT 产品物模型事件类型枚举
-export const ThingModelServiceEventType = {
-  INFO: {
-    label: '信息',
-    value: 'info'
-  },
-  ALERT: {
-    label: '告警',
-    value: 'alert'
-  },
-  ERROR: {
-    label: '故障',
-    value: 'error'
-  }
-} as const
-
-// IOT 产品物模型参数是输入参数还是输出参数
-export const ThingModelParamDirection = {
-  INPUT: 'input', // 输入参数
-  OUTPUT: 'output' // 输出参数
-} as const
-
 // IoT 产品物模型 API
 export const ThingModelApi = {
   // 查询产品物模型分页

+ 16 - 8
src/views/iot/thingmodel/ThingModelEvent.vue

@@ -2,25 +2,33 @@
   <el-form-item
     :rules="[{ required: true, message: '请选择事件类型', trigger: 'change' }]"
     label="事件类型"
-    prop="thingModelEvent.type"
+    prop="event.type"
   >
     <el-radio-group v-model="thingModelEvent.type">
-      <el-radio :value="ThingModelServiceEventType.INFO.value">
-        {{ ThingModelServiceEventType.INFO.label }}
+      <el-radio :value="ThingModelEventType.INFO.value">
+        {{ ThingModelEventType.INFO.label }}
       </el-radio>
-      <el-radio :value="ThingModelServiceEventType.ALERT.value">
-        {{ ThingModelServiceEventType.ALERT.label }}
+      <el-radio :value="ThingModelEventType.ALERT.value">
+        {{ ThingModelEventType.ALERT.label }}
       </el-radio>
-      <el-radio :value="ThingModelServiceEventType.ERROR.value">
-        {{ ThingModelServiceEventType.ERROR.label }}
+      <el-radio :value="ThingModelEventType.ERROR.value">
+        {{ ThingModelEventType.ERROR.label }}
       </el-radio>
     </el-radio-group>
   </el-form-item>
+  <el-form-item label="输出参数">
+    <ThingModelInputOutputParam
+      v-model="thingModelEvent.outputParams"
+      :direction="ThingModelParamDirection.OUTPUT"
+    />
+  </el-form-item>
 </template>
 
 <script lang="ts" setup>
+import ThingModelInputOutputParam from './ThingModelInputOutputParam.vue'
 import { useVModel } from '@vueuse/core'
-import { ThingModelEvent, ThingModelServiceEventType } from '@/api/iot/thingmodel'
+import { ThingModelEvent } from '@/api/iot/thingmodel'
+import { ThingModelParamDirection, ThingModelEventType } from './config'
 
 /** IoT 物模型事件 */
 defineOptions({ name: 'ThingModelEvent' })

+ 14 - 4
src/views/iot/thingmodel/ThingModelForm.vue

@@ -36,6 +36,15 @@
       />
       <!-- 事件配置 -->
       <ThingModelEvent v-if="formData.type === ThingModelType.EVENT" v-model="formData.event" />
+      <el-form-item label="描述" prop="description">
+        <el-input
+          v-model="formData.description"
+          :maxlength="200"
+          :rows="3"
+          placeholder="请输入属性描述"
+          type="textarea"
+        />
+      </el-form-item>
     </el-form>
 
     <template #footer>
@@ -50,9 +59,9 @@ import { ProductVO } from '@/api/iot/product/product'
 import ThingModelProperty from './ThingModelProperty.vue'
 import ThingModelService from './ThingModelService.vue'
 import ThingModelEvent from './ThingModelEvent.vue'
-import { ThingModelApi, ThingModelData, ThingModelType } from '@/api/iot/thingmodel'
+import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
 import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
-import { DataSpecsDataType, ThingModelFormRules } from './config'
+import { DataSpecsDataType, ThingModelFormRules, ThingModelType } from './config'
 import { cloneDeep } from 'lodash-es'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { isEmpty } from '@/utils/is'
@@ -111,7 +120,6 @@ const submitForm = async () => {
     // 信息补全
     data.productId = product!.value.id
     data.productKey = product!.value.productKey
-    data.description = data.property.description
     data.dataType = data.property.dataType
     data.property.identifier = data.identifier
     data.property.name = data.name
@@ -164,7 +172,9 @@ const resetForm = () => {
       dataSpecs: {
         dataType: DataSpecsDataType.INT
       }
-    }
+    },
+    service: {},
+    event: {}
   }
   formRef.value?.resetFields()
 }

+ 153 - 0
src/views/iot/thingmodel/ThingModelInputOutputParam.vue

@@ -0,0 +1,153 @@
+<template>
+  <div
+    v-for="(item, index) in thingModelParams"
+    :key="index"
+    class="w-1/1 param-item flex justify-between px-10px mb-10px"
+  >
+    <span>参数名称:{{ item.name }}</span>
+    <div class="btn">
+      <el-button link type="primary" @click="openParamForm(item)">编辑</el-button>
+      <el-divider direction="vertical" />
+      <el-button link type="danger" @click="deleteParamItem(index)">删除</el-button>
+    </div>
+  </div>
+  <el-button link type="primary" @click="openParamForm(null)">+新增参数</el-button>
+
+  <!-- param 表单 -->
+  <Dialog v-model="dialogVisible" :title="dialogTitle" append-to-body>
+    <el-form
+      ref="paramFormRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="ThingModelFormRules"
+      label-width="100px"
+    >
+      <el-form-item label="参数名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入功能名称" />
+      </el-form-item>
+      <el-form-item label="标识符" prop="identifier">
+        <el-input v-model="formData.identifier" placeholder="请输入标识符" />
+      </el-form-item>
+      <!-- 属性配置 -->
+      <ThingModelProperty v-model="formData.property" is-params />
+    </el-form>
+
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { useVModel } from '@vueuse/core'
+import ThingModelProperty from './ThingModelProperty.vue'
+import { DataSpecsDataType, ThingModelFormRules } from './config'
+import { isEmpty } from '@/utils/is'
+
+/** 输入输出参数配置组件 */
+defineOptions({ name: 'ThingModelInputOutputParam' })
+
+const props = defineProps<{ modelValue: any; direction: string }>()
+const emits = defineEmits(['update:modelValue'])
+const thingModelParams = useVModel(props, 'modelValue', emits) as Ref<any[]>
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('新增参数') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const paramFormRef = ref() // 表单 ref
+const formData = ref<any>({
+  dataType: DataSpecsDataType.INT,
+  property: {
+    dataType: DataSpecsDataType.INT,
+    dataSpecs: {
+      dataType: DataSpecsDataType.INT
+    }
+  }
+})
+
+/** 打开 param 表单 */
+const openParamForm = (val: any) => {
+  dialogVisible.value = true
+  resetForm()
+  if (isEmpty(val)) {
+    return
+  }
+  // 编辑时回显数据
+  formData.value = {
+    identifier: val.identifier,
+    name: val.name,
+    description: val.description,
+    property: {
+      dataType: val.dataType,
+      dataSpecs: val.dataSpecs,
+      dataSpecsList: val.dataSpecsList
+    }
+  }
+}
+/** 删除 param 项 */
+const deleteParamItem = (index: number) => {
+  thingModelParams.value.splice(index, 1)
+}
+
+/** 添加参数 */
+const submitForm = async () => {
+  // 初始化参数列表
+  if (isEmpty(thingModelParams.value)) {
+    thingModelParams.value = []
+  }
+  // 校验参数
+  await paramFormRef.value.validate()
+  try {
+    const data = unref(formData)
+    // 构建数据对象
+    const item = {
+      identifier: data.identifier,
+      name: data.name,
+      description: data.description,
+      dataType: data.property.dataType,
+      paraOrder: 0, // TODO @puhui999: 先写死默认看看后续
+      direction: props.direction,
+      dataSpecs:
+        !!data.property.dataSpecs && Object.keys(data.property.dataSpecs).length > 1
+          ? data.property.dataSpecs
+          : undefined,
+      dataSpecsList: isEmpty(data.property.dataSpecsList) ? undefined : data.property.dataSpecsList
+    }
+
+    // 查找是否已有相同 identifier 的项
+    const existingIndex = thingModelParams.value.findIndex(
+      (spec) => spec.identifier === data.identifier
+    )
+    if (existingIndex > -1) {
+      // 更新已有项
+      thingModelParams.value[existingIndex] = item
+    } else {
+      // 添加新项
+      thingModelParams.value.push(item)
+    }
+  } finally {
+    // 隐藏对话框
+    dialogVisible.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    dataType: DataSpecsDataType.INT,
+    property: {
+      dataType: DataSpecsDataType.INT,
+      dataSpecs: {
+        dataType: DataSpecsDataType.INT
+      }
+    }
+  }
+  paramFormRef.value?.resetFields()
+}
+</script>
+
+<style lang="scss" scoped>
+.param-item {
+  background-color: #e4f2fd;
+}
+</style>

+ 10 - 14
src/views/iot/thingmodel/ThingModelProperty.vue

@@ -75,7 +75,7 @@
     v-if="property.dataType === DataSpecsDataType.STRUCT"
     v-model="property.dataSpecsList"
   />
-  <el-form-item v-if="!isStructDataSpecs" label="读写类型" prop="property.accessMode">
+  <el-form-item v-if="!isStructDataSpecs && !isParams" label="读写类型" prop="property.accessMode">
     <el-radio-group v-model="property.accessMode">
       <el-radio :label="ThingModelAccessMode.READ_WRITE.value">
         {{ ThingModelAccessMode.READ_WRITE.label }}
@@ -85,32 +85,28 @@
       </el-radio>
     </el-radio-group>
   </el-form-item>
-  <el-form-item label="属性描述" prop="description">
-    <el-input
-      v-model="property.description"
-      :maxlength="200"
-      :rows="3"
-      placeholder="请输入属性描述"
-      type="textarea"
-    />
-  </el-form-item>
 </template>
 
 <script lang="ts" setup>
 import { useVModel } from '@vueuse/core'
-import { DataSpecsDataType, dataTypeOptions, validateBoolName } from './config'
+import {
+  DataSpecsDataType,
+  dataTypeOptions,
+  ThingModelAccessMode,
+  validateBoolName
+} from './config'
 import {
   ThingModelArrayDataSpecs,
   ThingModelEnumDataSpecs,
   ThingModelNumberDataSpecs,
   ThingModelStructDataSpecs
 } from './dataSpecs'
-import { ThingModelAccessMode, ThingModelProperty } from '@/api/iot/thingmodel'
+import { ThingModelProperty } from '@/api/iot/thingmodel'
 
-/** IoT 物模型数据 */
+/** IoT 物模型属性 */
 defineOptions({ name: 'ThingModelProperty' })
 
-const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean }>()
+const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean; isParams?: boolean }>()
 const emits = defineEmits(['update:modelValue'])
 const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
 const getDataTypeOptions = computed(() => {

+ 15 - 1
src/views/iot/thingmodel/ThingModelService.vue

@@ -13,11 +13,25 @@
       </el-radio>
     </el-radio-group>
   </el-form-item>
+  <el-form-item label="输入参数">
+    <ThingModelInputOutputParam
+      v-model="service.inputParams"
+      :direction="ThingModelParamDirection.INPUT"
+    />
+  </el-form-item>
+  <el-form-item label="输出参数">
+    <ThingModelInputOutputParam
+      v-model="service.outputParams"
+      :direction="ThingModelParamDirection.OUTPUT"
+    />
+  </el-form-item>
 </template>
 
 <script lang="ts" setup>
+import ThingModelInputOutputParam from './ThingModelInputOutputParam.vue'
 import { useVModel } from '@vueuse/core'
-import { ThingModelService, ThingModelServiceCallType } from '@/api/iot/thingmodel'
+import { ThingModelService } from '@/api/iot/thingmodel'
+import { ThingModelParamDirection, ThingModelServiceCallType } from './config'
 
 /** IoT 物模型服务 */
 defineOptions({ name: 'ThingModelService' })

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

@@ -1,4 +1,4 @@
-import {isEmpty} from '@/utils/is'
+import { isEmpty } from '@/utils/is'
 
 /** dataSpecs 数值型数据结构 */
 export interface DataSpecsNumberDataVO {
@@ -48,9 +48,69 @@ export const dataTypeOptions = [
 
 /** 获得物体模型数据类型配置项名称 */
 export const getDataTypeOptionsLabel = (value: string) => {
+  if (isEmpty(value)) {
+    return value
+  }
   return dataTypeOptions.find((option) => option.value === value)?.label
 }
 
+// IOT 产品物模型类型枚举类
+export const ThingModelType = {
+  PROPERTY: 1, // 属性
+  SERVICE: 2, // 服务
+  EVENT: 3 // 事件
+} as const
+
+// IOT 产品物模型访问模式枚举类
+export const ThingModelAccessMode = {
+  READ_WRITE: {
+    label: '读写',
+    value: 'rw'
+  },
+  READ_ONLY: {
+    label: '只读',
+    value: 'r'
+  }
+} as const
+
+// IOT 产品物模型服务调用方式枚举
+export const ThingModelServiceCallType = {
+  ASYNC: {
+    label: '异步调用',
+    value: 'async'
+  },
+  SYNC: {
+    label: '同步调用',
+    value: 'sync'
+  }
+} as const
+export const getCallTypeByValue = (value: string): string | undefined =>
+  Object.values(ThingModelServiceCallType).find((type) => type.value === value)?.label
+
+// IOT 产品物模型事件类型枚举
+export const ThingModelEventType = {
+  INFO: {
+    label: '信息',
+    value: 'info'
+  },
+  ALERT: {
+    label: '告警',
+    value: 'alert'
+  },
+  ERROR: {
+    label: '故障',
+    value: 'error'
+  }
+} as const
+export const getEventTypeByValue = (value: string): string | undefined =>
+  Object.values(ThingModelEventType).find((type) => type.value === value)?.label
+
+// IOT 产品物模型参数是输入参数还是输出参数
+export const ThingModelParamDirection = {
+  INPUT: 'input', // 输入参数
+  OUTPUT: 'output' // 输出参数
+} as const
+
 /** 公共校验规则 */
 export const ThingModelFormRules = {
   name: [

+ 62 - 5
src/views/iot/thingmodel/index.vue

@@ -56,13 +56,63 @@
         <el-table-column align="center" label="标识符" prop="identifier" />
         <el-table-column align="center" label="数据类型" prop="identifier">
           <template #default="{ row }">
-            {{ dataTypeOptionsLabel(row.property.dataType) ?? '-' }}
+            {{ dataTypeOptionsLabel(row.property?.dataType) ?? '-' }}
           </template>
         </el-table-column>
-        <el-table-column align="center" label="数据定义" prop="identifier">
+        <el-table-column align="left" label="数据定义" prop="identifier">
           <template #default="{ row }">
-            <!-- TODO puhui999: 数据定义展示待完善 -->
-            {{ row.property.dataSpecs ?? row.property.dataSpecsList }}
+            <!-- 属性 -->
+            <template v-if="row.type === ThingModelType.PROPERTY">
+              <!-- 非列表型:数值 -->
+              <div
+                v-if="
+                  [
+                    DataSpecsDataType.INT,
+                    DataSpecsDataType.DOUBLE,
+                    DataSpecsDataType.FLOAT
+                  ].includes(row.property.dataType)
+                "
+              >
+                取值范围:{{ `${row.property.dataSpecs.min}~${row.property.dataSpecs.max}` }}
+              </div>
+              <!-- 非列表型:文本 -->
+              <div v-if="DataSpecsDataType.TEXT === row.property.dataType">
+                数据长度:{{ row.property.dataSpecs.length }}
+              </div>
+              <!-- 列表型: 数组、结构、时间(特殊) -->
+              <div
+                v-if="
+                  [
+                    DataSpecsDataType.ARRAY,
+                    DataSpecsDataType.STRUCT,
+                    DataSpecsDataType.DATE
+                  ].includes(row.property.dataType)
+                "
+              >
+                -
+              </div>
+              <!-- 列表型: 布尔值、枚举 -->
+              <div
+                v-if="
+                  [DataSpecsDataType.BOOL, DataSpecsDataType.ENUM].includes(row.property.dataType)
+                "
+              >
+                <div>
+                  {{ DataSpecsDataType.BOOL === row.property.dataType ? '布尔值' : '枚举值' }}:
+                </div>
+                <div v-for="item in row.property.dataSpecsList" :key="item.value">
+                  {{ `${item.name}-${item.value}` }}
+                </div>
+              </div>
+            </template>
+            <!-- 服务 -->
+            <div v-if="row.type === ThingModelType.SERVICE">
+              调用方式:{{ getCallTypeByValue(row.service.callType) }}
+            </div>
+            <!-- 事件 -->
+            <div v-if="row.type === ThingModelType.EVENT">
+              事件类型:{{ getEventTypeByValue(row.event.type) }}
+            </div>
           </template>
         </el-table-column>
         <el-table-column align="center" label="操作">
@@ -104,7 +154,14 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import ThingModelForm from './ThingModelForm.vue'
 import { ProductVO } from '@/api/iot/product/product'
 import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
-import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config'
+import {
+  DataSpecsDataType,
+  getCallTypeByValue,
+  getDataTypeOptionsLabel,
+  getEventTypeByValue,
+  ThingModelType
+} from './config'
+import { ThingModelNumberDataSpecs } from '@/views/iot/thingmodel/dataSpecs'
 
 defineOptions({ name: 'IoTProductThingModel' })