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

pull/628/head
puhui999 2024-12-25 23:53:14 +08:00
parent d7c33b454f
commit 5391720b8b
8 changed files with 331 additions and 86 deletions

View File

@ -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 = {
// 查询产品物模型分页

View File

@ -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' })

View File

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

View File

@ -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) // 12
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>

View File

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

View File

@ -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' })

View File

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

View File

@ -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' })