diff --git a/src/api/iot/thingmodel/index.ts b/src/api/iot/thingmodel/index.ts index f00dfe242..544227f2e 100644 --- a/src/api/iot/thingmodel/index.ts +++ b/src/api/iot/thingmodel/index.ts @@ -1,4 +1,5 @@ import request from '@/config/axios' +import { isEmpty } from '@/utils/is' /** * IoT 产品物模型 @@ -46,6 +47,26 @@ export interface ThingModelService { [key: string]: any } +/** dataSpecs 数值型数据结构 */ +export interface DataSpecsNumberDataVO { + dataType: 'int' | 'float' | 'double' // 数据类型,取值为 INT、FLOAT 或 DOUBLE + max: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型 + min: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型 + step: string // 步长,必须与 dataType 设置一致,且为 STRING 类型 + precise?: string // 精度,当 dataType 为 FLOAT 或 DOUBLE 时可选 + defaultValue?: string // 默认值,可选 + unit: string // 单位的符号 + unitName: string // 单位的名称 +} + +/** dataSpecs 枚举型数据结构 */ +export interface DataSpecsEnumOrBoolDataVO { + dataType: 'enum' | 'bool' + defaultValue?: string // 默认值,可选 + name: string // 枚举项的名称 + value: number | undefined // 枚举值 +} + // IoT 产品物模型 API export const ThingModelApi = { // 查询产品物模型分页 @@ -85,3 +106,103 @@ export const ThingModelApi = { return await request.delete({ url: `/iot/thing-model/delete?id=` + id }) } } + +/** 公共校验规则 */ +export const ThingModelFormRules = { + name: [ + { required: true, message: '功能名称不能为空', trigger: 'blur' }, + { + pattern: /^[\u4e00-\u9fa5a-zA-Z0-9][\u4e00-\u9fa5a-zA-Z0-9\-_/\.]{0,29}$/, + message: + '支持中文、大小写字母、日文、数字、短划线、下划线、斜杠和小数点,必须以中文、英文或数字开头,不超过 30 个字符', + trigger: 'blur' + } + ], + type: [{ required: true, message: '功能类型不能为空', trigger: 'blur' }], + identifier: [ + { required: true, message: '标识符不能为空', trigger: 'blur' }, + { + pattern: /^[a-zA-Z0-9_]{1,50}$/, + message: '支持大小写字母、数字和下划线,不超过 50 个字符', + trigger: 'blur' + }, + { + validator: (_: any, value: string, callback: any) => { + const reservedKeywords = ['set', 'get', 'post', 'property', 'event', 'time', 'value'] + if (reservedKeywords.includes(value)) { + callback( + new Error( + 'set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义' + ) + ) + } else if (/^\d+$/.test(value)) { + callback(new Error('标识符不能是纯数字')) + } else { + callback() + } + }, + trigger: 'blur' + } + ], + 'property.dataSpecs.childDataType': [{ required: true, message: '元素类型不能为空' }], + 'property.dataSpecs.size': [ + { required: true, message: '元素个数不能为空' }, + { + validator: (_: any, value: any, callback: any) => { + if (isEmpty(value)) { + callback(new Error('元素个数不能为空')) + return + } + if (isNaN(Number(value))) { + callback(new Error('元素个数必须是数字')) + return + } + callback() + }, + trigger: 'blur' + } + ], + 'property.dataSpecs.length': [ + { required: true, message: '请输入文本字节长度', trigger: 'blur' }, + { + validator: (_: any, value: any, callback: any) => { + if (isEmpty(value)) { + callback(new Error('文本长度不能为空')) + return + } + if (isNaN(Number(value))) { + callback(new Error('文本长度必须是数字')) + return + } + callback() + }, + trigger: 'blur' + } + ], + 'property.accessMode': [{ required: true, message: '请选择读写类型', trigger: 'change' }] +} + +/** 校验布尔值名称 */ +export const validateBoolName = (_: any, value: string, callback: any) => { + if (isEmpty(value)) { + callback(new Error('布尔值名称不能为空')) + return + } + // 检查开头字符 + if (!/^[\u4e00-\u9fa5a-zA-Z0-9]/.test(value)) { + callback(new Error('布尔值名称必须以中文、英文字母或数字开头')) + return + } + // 检查整体格式 + if (!/^[\u4e00-\u9fa5a-zA-Z0-9][a-zA-Z0-9\u4e00-\u9fa5_-]*$/.test(value)) { + callback(new Error('布尔值名称只能包含中文、英文字母、数字、下划线和短划线')) + return + } + // 检查长度(一个中文算一个字符) + if (value.length > 20) { + callback(new Error('布尔值名称长度不能超过 20 个字符')) + return + } + + callback() +} diff --git a/src/views/iot/device/device/detail/DeviceDetailsSimulator.vue b/src/views/iot/device/device/detail/DeviceDetailsSimulator.vue index 7758d1238..8d6a369b3 100644 --- a/src/views/iot/device/device/detail/DeviceDetailsSimulator.vue +++ b/src/views/iot/device/device/detail/DeviceDetailsSimulator.vue @@ -24,7 +24,7 @@ @@ -145,9 +145,8 @@ import { ProductVO } from '@/api/iot/product/product' import { SimulatorData, ThingModelApi } from '@/api/iot/thingmodel' import { DeviceApi, DeviceStateEnum, DeviceVO } from '@/api/iot/device/device' import DeviceDetailsMessage from './DeviceDetailsMessage.vue' -import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config' import { DataDefinition } from '@/views/iot/thingmodel/components' -import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants' +import { getDataTypeOptionsLabel, IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants' const props = defineProps<{ product: ProductVO @@ -166,8 +165,6 @@ const queryParams = reactive({ productId: -1 }) const list = ref([]) // 物模型列表的数据 TODO @super:thingModelList -// TODO @super:dataTypeOptionsLabel 是不是不用定义,直接用 getDataTypeOptionsLabel 在 template 中使用即可? -const dataTypeOptionsLabel = computed(() => (value: string) => getDataTypeOptionsLabel(value)) // 解析数据类型 /** 查询物模型列表 */ // TODO @super:getThingModelList 更精准 diff --git a/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue b/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue index 3555c7b36..04f9a9f53 100644 --- a/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue +++ b/src/views/iot/device/device/detail/DeviceDetailsThingModelEvent.vue @@ -95,8 +95,11 @@ import { DeviceApi } from '@/api/iot/device/device' import { ThingModelData } from '@/api/iot/thingmodel' import { formatDate, defaultShortcuts } from '@/utils/formatTime' -import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants' -import { ThingModelType, getEventTypeByValue } from '@/views/iot/thingmodel/config' +import { + getEventTypeLabel, + IotDeviceMessageMethodEnum, + IoTThingModelTypeEnum +} from '@/views/iot/utils/constants' const props = defineProps<{ deviceId: number @@ -118,7 +121,9 @@ const queryFormRef = ref() // 搜索的表单 /** 事件类型的物模型数据 */ const eventThingModels = computed(() => { - return props.thingModelList.filter((item: ThingModelData) => item.type === ThingModelType.EVENT) + return props.thingModelList.filter( + (item: ThingModelData) => item.type === IoTThingModelTypeEnum.EVENT + ) }) /** 查询列表 */ @@ -164,7 +169,7 @@ const getEventType = (identifier: string | undefined) => { (item: ThingModelData) => item.identifier === identifier ) if (!event?.event?.type) return '-' - return getEventTypeByValue(event.event.type) || '-' + return getEventTypeLabel(event.event.type) || '-' } /** 解析参数 */ diff --git a/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue b/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue index 916d2dc15..fd8456162 100644 --- a/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue +++ b/src/views/iot/device/device/detail/DeviceDetailsThingModelService.vue @@ -110,8 +110,11 @@ import { DeviceApi } from '@/api/iot/device/device' import { ThingModelData } from '@/api/iot/thingmodel' import { formatDate, defaultShortcuts } from '@/utils/formatTime' -import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants' -import { ThingModelType, getCallTypeByValue } from '@/views/iot/thingmodel/config' +import { + getThingModelServiceCallTypeLabel, + IotDeviceMessageMethodEnum, + IoTThingModelTypeEnum +} from '@/views/iot/utils/constants' const props = defineProps<{ deviceId: number @@ -133,7 +136,9 @@ const queryFormRef = ref() // 搜索的表单 /** 服务类型的物模型数据 */ const serviceThingModels = computed(() => { - return props.thingModelList.filter((item: ThingModelData) => item.type === ThingModelType.SERVICE) + return props.thingModelList.filter( + (item: ThingModelData) => item.type === IoTThingModelTypeEnum.SERVICE + ) }) /** 查询列表 */ @@ -179,7 +184,7 @@ const getCallType = (identifier: string | undefined) => { (item: ThingModelData) => item.identifier === identifier ) if (!service?.service?.callType) return '-' - return getCallTypeByValue(service.service.callType) || '-' + return getThingModelServiceCallTypeLabel(service.service.callType) || '-' } /** 解析参数 */ diff --git a/src/views/iot/rule/scene/components/ThingModelParamInput.vue b/src/views/iot/rule/scene/components/ThingModelParamInput.vue index b6e35cbe9..9172fcf70 100644 --- a/src/views/iot/rule/scene/components/ThingModelParamInput.vue +++ b/src/views/iot/rule/scene/components/ThingModelParamInput.vue @@ -77,8 +77,8 @@ diff --git a/src/views/iot/thingmodel/ThingModelForm.vue b/src/views/iot/thingmodel/ThingModelForm.vue index f52179bf6..088391c76 100644 --- a/src/views/iot/thingmodel/ThingModelForm.vue +++ b/src/views/iot/thingmodel/ThingModelForm.vue @@ -27,16 +27,19 @@ - + ({ - type: ThingModelType.PROPERTY, - dataType: DataSpecsDataType.INT, + type: IoTThingModelTypeEnum.PROPERTY, + dataType: IoTDataSpecsDataTypeEnum.INT, property: { - dataType: DataSpecsDataType.INT, + dataType: IoTDataSpecsDataTypeEnum.INT, dataSpecs: { - dataType: DataSpecsDataType.INT + dataType: IoTDataSpecsDataTypeEnum.INT } }, service: {}, @@ -106,11 +112,11 @@ const open = async (type: string, id?: number) => { formData.value = await ThingModelApi.getThingModel(id) // 情况一:属性初始化 if (isEmpty(formData.value.property)) { - formData.value.dataType = DataSpecsDataType.INT + formData.value.dataType = IoTDataSpecsDataTypeEnum.INT formData.value.property = { - dataType: DataSpecsDataType.INT, + dataType: IoTDataSpecsDataTypeEnum.INT, dataSpecs: { - dataType: DataSpecsDataType.INT + dataType: IoTDataSpecsDataTypeEnum.INT } } } @@ -158,7 +164,7 @@ const submitForm = async () => { const fillExtraAttributes = (data: any) => { // 处理不同类型的情况 // 属性 - if (data.type === ThingModelType.PROPERTY) { + if (data.type === IoTThingModelTypeEnum.PROPERTY) { removeDataSpecs(data.property) data.dataType = data.property.dataType data.property.identifier = data.identifier @@ -167,7 +173,7 @@ const fillExtraAttributes = (data: any) => { delete data.event } // 服务 - if (data.type === ThingModelType.SERVICE) { + if (data.type === IoTThingModelTypeEnum.SERVICE) { removeDataSpecs(data.service) data.dataType = data.service.dataType data.service.identifier = data.identifier @@ -176,7 +182,7 @@ const fillExtraAttributes = (data: any) => { delete data.event } // 事件 - if (data.type === ThingModelType.EVENT) { + if (data.type === IoTThingModelTypeEnum.EVENT) { removeDataSpecs(data.event) data.dataType = data.event.dataType data.event.identifier = data.identifier @@ -198,12 +204,12 @@ const removeDataSpecs = (val: any) => { /** 重置表单 */ const resetForm = () => { formData.value = { - type: ThingModelType.PROPERTY, - dataType: DataSpecsDataType.INT, + type: IoTThingModelTypeEnum.PROPERTY, + dataType: IoTDataSpecsDataTypeEnum.INT, property: { - dataType: DataSpecsDataType.INT, + dataType: IoTDataSpecsDataTypeEnum.INT, dataSpecs: { - dataType: DataSpecsDataType.INT + dataType: IoTDataSpecsDataTypeEnum.INT } }, service: {}, diff --git a/src/views/iot/thingmodel/ThingModelInputOutputParam.vue b/src/views/iot/thingmodel/ThingModelInputOutputParam.vue index 2bf4bb932..2dd6740cb 100644 --- a/src/views/iot/thingmodel/ThingModelInputOutputParam.vue +++ b/src/views/iot/thingmodel/ThingModelInputOutputParam.vue @@ -43,8 +43,9 @@ diff --git a/src/views/iot/thingmodel/components/DataDefinition.vue b/src/views/iot/thingmodel/components/DataDefinition.vue index 2b94a5783..22e6e3dd9 100644 --- a/src/views/iot/thingmodel/components/DataDefinition.vue +++ b/src/views/iot/thingmodel/components/DataDefinition.vue @@ -1,56 +1,68 @@