perf:【IoT 物联网】场景联动触发条件属性选择器组件优化
							parent
							
								
									ec94c33d85
								
							
						
					
					
						commit
						f31d034c79
					
				|  | @ -0,0 +1,468 @@ | |||
| # IotThingModelTSLRespVO 数据结构文档 | ||||
| 
 | ||||
| ## 概述 | ||||
| 
 | ||||
| `IotThingModelTSLRespVO` 是IoT产品物模型TSL(Thing Specification Language)的响应数据结构,用于返回完整的产品物模型定义,包括属性、事件和服务的详细信息。TSL是阿里云IoT平台定义的一套物模型描述规范。 | ||||
| 
 | ||||
| ## 主体数据结构 | ||||
| 
 | ||||
| ### IotThingModelTSLRespVO | ||||
| 
 | ||||
| ```typescript | ||||
| interface IotThingModelTSLRespVO { | ||||
|   productId: number;                    // 产品编号(必填) | ||||
|   productKey: string;                   // 产品标识(必填) | ||||
|   properties: ThingModelProperty[];     // 属性列表(必填) | ||||
|   events: ThingModelEvent[];           // 事件列表(必填) | ||||
|   services: ThingModelService[];       // 服务列表(必填) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| **字段说明:** | ||||
| - `productId`: 产品编号,唯一标识一个IoT产品 | ||||
| - `productKey`: 产品标识符,用于设备连接和识别 | ||||
| - `properties`: 设备属性列表,描述设备的状态信息 | ||||
| - `events`: 设备事件列表,描述设备主动上报的事件 | ||||
| - `services`: 设备服务列表,描述可以调用的设备功能 | ||||
| 
 | ||||
| ## 属性数据结构 (ThingModelProperty) | ||||
| 
 | ||||
| ### 基本结构 | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelProperty { | ||||
|   identifier: string;                   // 属性标识符(必填) | ||||
|   name: string;                        // 属性名称(必填) | ||||
|   accessMode: string;                  // 访问模式(必填) | ||||
|   required?: boolean;                  // 是否必选 | ||||
|   dataType: string;                    // 数据类型(必填) | ||||
|   dataSpecs?: ThingModelDataSpecs;     // 数据规范(非列表型) | ||||
|   dataSpecsList?: ThingModelDataSpecs[]; // 数据规范(列表型) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 字段详细说明 | ||||
| 
 | ||||
| #### identifier(属性标识符) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **格式**: 正则表达式 `^[a-zA-Z][a-zA-Z0-9_]{0,31}$` | ||||
| - **说明**: 只能由字母、数字和下划线组成,必须以字母开头,长度不超过32个字符 | ||||
| - **示例**: `"temperature"`, `"humidity"`, `"power_status"` | ||||
| 
 | ||||
| #### name(属性名称) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **说明**: 属性的显示名称,用于界面展示 | ||||
| - **示例**: `"温度"`, `"湿度"`, `"电源状态"` | ||||
| 
 | ||||
| #### accessMode(访问模式) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **枚举值**: | ||||
|   - `"r"`: 只读,设备只能上报,平台不能下发 | ||||
|   - `"rw"`: 读写,设备可以上报,平台也可以下发 | ||||
| - **示例**: `"r"`, `"rw"` | ||||
| 
 | ||||
| #### dataType(数据类型) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **枚举值**: | ||||
|   - `"int"`: 整数型 | ||||
|   - `"float"`: 单精度浮点型 | ||||
|   - `"double"`: 双精度浮点型 | ||||
|   - `"enum"`: 枚举型 | ||||
|   - `"bool"`: 布尔型 | ||||
|   - `"text"`: 文本型 | ||||
|   - `"date"`: 时间型 | ||||
|   - `"struct"`: 结构体型 | ||||
|   - `"array"`: 数组型 | ||||
| 
 | ||||
| ## 事件数据结构 (ThingModelEvent) | ||||
| 
 | ||||
| ### 基本结构 | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelEvent { | ||||
|   identifier: string;                   // 事件标识符(必填) | ||||
|   name: string;                        // 事件名称(必填) | ||||
|   required?: boolean;                  // 是否必选 | ||||
|   type: string;                        // 事件类型(必填) | ||||
|   outputParams?: ThingModelParam[];    // 输出参数 | ||||
|   method?: string;                     // 执行方法 | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 字段详细说明 | ||||
| 
 | ||||
| #### type(事件类型) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **枚举值**: | ||||
|   - `"info"`: 信息事件 | ||||
|   - `"alert"`: 告警事件 | ||||
|   - `"error"`: 故障事件 | ||||
| 
 | ||||
| #### outputParams(输出参数) | ||||
| - **类型**: `ThingModelParam[]` | ||||
| - **必填**: 否 | ||||
| - **说明**: 事件触发时返回的参数信息 | ||||
| 
 | ||||
| ## 服务数据结构 (ThingModelService) | ||||
| 
 | ||||
| ### 基本结构 | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelService { | ||||
|   identifier: string;                   // 服务标识符(必填) | ||||
|   name: string;                        // 服务名称(必填) | ||||
|   required?: boolean;                  // 是否必选 | ||||
|   callType: string;                    // 调用类型(必填) | ||||
|   inputParams?: ThingModelParam[];     // 输入参数 | ||||
|   outputParams?: ThingModelParam[];    // 输出参数 | ||||
|   method?: string;                     // 执行方法 | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 字段详细说明 | ||||
| 
 | ||||
| #### callType(调用类型) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **枚举值**: | ||||
|   - `"async"`: 异步调用 | ||||
|   - `"sync"`: 同步调用 | ||||
| 
 | ||||
| ## 参数数据结构 (ThingModelParam) | ||||
| 
 | ||||
| ### 基本结构 | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelParam { | ||||
|   identifier: string;                   // 参数标识符(必填) | ||||
|   name: string;                        // 参数名称(必填) | ||||
|   direction: string;                   // 参数方向(必填) | ||||
|   paraOrder?: number;                  // 参数序号 | ||||
|   dataType: string;                    // 数据类型(必填) | ||||
|   dataSpecs?: ThingModelDataSpecs;     // 数据规范(非列表型) | ||||
|   dataSpecsList?: ThingModelDataSpecs[]; // 数据规范(列表型) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 字段详细说明 | ||||
| 
 | ||||
| #### direction(参数方向) | ||||
| - **类型**: `string` | ||||
| - **必填**: 是 | ||||
| - **枚举值**: | ||||
|   - `"input"`: 输入参数 | ||||
|   - `"output"`: 输出参数 | ||||
| 
 | ||||
| ## 数据规范结构 (ThingModelDataSpecs) | ||||
| 
 | ||||
| 数据规范是一个抽象基类,根据不同的数据类型有不同的具体实现: | ||||
| 
 | ||||
| ### 1. 数值型数据规范 (ThingModelNumericDataSpec) | ||||
| 
 | ||||
| 适用于 `int`、`float`、`double` 类型: | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelNumericDataSpec { | ||||
|   dataType: "int" | "float" | "double"; | ||||
|   max: string;                         // 最大值(必填) | ||||
|   min: string;                         // 最小值(必填) | ||||
|   step: string;                        // 步长(必填) | ||||
|   precise?: string;                    // 精度(float/double可选) | ||||
|   defaultValue?: string;               // 默认值 | ||||
|   unit?: string;                       // 单位符号 | ||||
|   unitName?: string;                   // 单位名称 | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 2. 布尔/枚举型数据规范 (ThingModelBoolOrEnumDataSpecs) | ||||
| 
 | ||||
| 适用于 `bool`、`enum` 类型: | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelBoolOrEnumDataSpecs { | ||||
|   dataType: "bool" | "enum"; | ||||
|   name: string;                        // 枚举项名称(必填) | ||||
|   value: number;                       // 枚举值(必填) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 3. 文本/时间型数据规范 (ThingModelDateOrTextDataSpecs) | ||||
| 
 | ||||
| 适用于 `text`、`date` 类型: | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelDateOrTextDataSpecs { | ||||
|   dataType: "text" | "date"; | ||||
|   length?: number;                     // 数据长度(text类型需要,最大2048) | ||||
|   defaultValue?: string;               // 默认值 | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 4. 数组型数据规范 (ThingModelArrayDataSpecs) | ||||
| 
 | ||||
| 适用于 `array` 类型: | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelArrayDataSpecs { | ||||
|   dataType: "array"; | ||||
|   size: number;                        // 数组元素个数(必填) | ||||
|   childDataType: string;               // 数组元素数据类型(必填) | ||||
|   dataSpecsList?: ThingModelDataSpecs[]; // 子元素数据规范(struct类型时) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| **childDataType 枚举值**: | ||||
| - `"struct"`: 结构体 | ||||
| - `"int"`: 整数 | ||||
| - `"float"`: 单精度浮点 | ||||
| - `"double"`: 双精度浮点 | ||||
| - `"text"`: 文本 | ||||
| 
 | ||||
| ### 5. 结构体型数据规范 (ThingModelStructDataSpecs) | ||||
| 
 | ||||
| 适用于 `struct` 类型: | ||||
| 
 | ||||
| ```typescript | ||||
| interface ThingModelStructDataSpecs { | ||||
|   dataType: "struct"; | ||||
|   identifier: string;                  // 属性标识符(必填) | ||||
|   name: string;                        // 属性名称(必填) | ||||
|   accessMode: string;                  // 操作类型(必填) | ||||
|   required?: boolean;                  // 是否必选 | ||||
|   childDataType: string;               // 子数据类型(必填) | ||||
|   dataSpecs?: ThingModelDataSpecs;     // 数据规范(非列表型) | ||||
|   dataSpecsList?: ThingModelDataSpecs[]; // 数据规范(列表型) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| **childDataType 枚举值**: | ||||
| - `"int"`: 整数 | ||||
| - `"float"`: 单精度浮点 | ||||
| - `"double"`: 双精度浮点 | ||||
| - `"text"`: 文本 | ||||
| - `"date"`: 时间 | ||||
| - `"enum"`: 枚举 | ||||
| - `"bool"`: 布尔 | ||||
| 
 | ||||
| ## 数据类型映射关系 | ||||
| 
 | ||||
| ### dataSpecs vs dataSpecsList | ||||
| 
 | ||||
| - **dataSpecs**: 用于非列表型数据类型(`int`、`float`、`double`、`text`、`date`、`array`) | ||||
| - **dataSpecsList**: 用于列表型数据类型(`enum`、`bool`、`struct`) | ||||
| 
 | ||||
| ### JSON多态序列化 | ||||
| 
 | ||||
| 数据规范使用Jackson的`@JsonTypeInfo`和`@JsonSubTypes`注解实现多态序列化: | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
|   "dataType": "int", | ||||
|   "max": "100", | ||||
|   "min": "0", | ||||
|   "step": "1", | ||||
|   "unit": "°C", | ||||
|   "unitName": "摄氏度" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## 完整示例 | ||||
| 
 | ||||
| ### 温度传感器物模型示例 | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
|   "productId": 1024, | ||||
|   "productKey": "temperature_sensor", | ||||
|   "properties": [ | ||||
|     { | ||||
|       "identifier": "temperature", | ||||
|       "name": "温度", | ||||
|       "accessMode": "r", | ||||
|       "required": true, | ||||
|       "dataType": "float", | ||||
|       "dataSpecs": { | ||||
|         "dataType": "float", | ||||
|         "max": "100.0", | ||||
|         "min": "-40.0", | ||||
|         "step": "0.1", | ||||
|         "precise": "1", | ||||
|         "unit": "°C", | ||||
|         "unitName": "摄氏度" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identifier": "power_switch", | ||||
|       "name": "电源开关", | ||||
|       "accessMode": "rw", | ||||
|       "required": false, | ||||
|       "dataType": "bool", | ||||
|       "dataSpecsList": [ | ||||
|         { | ||||
|           "dataType": "bool", | ||||
|           "name": "关闭", | ||||
|           "value": 0 | ||||
|         }, | ||||
|         { | ||||
|           "dataType": "bool", | ||||
|           "name": "开启", | ||||
|           "value": 1 | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "events": [ | ||||
|     { | ||||
|       "identifier": "high_temperature_alert", | ||||
|       "name": "高温告警", | ||||
|       "required": false, | ||||
|       "type": "alert", | ||||
|       "outputParams": [ | ||||
|         { | ||||
|           "identifier": "current_temp", | ||||
|           "name": "当前温度", | ||||
|           "direction": "output", | ||||
|           "dataType": "float", | ||||
|           "dataSpecs": { | ||||
|             "dataType": "float", | ||||
|             "max": "100.0", | ||||
|             "min": "-40.0", | ||||
|             "step": "0.1" | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "services": [ | ||||
|     { | ||||
|       "identifier": "reset_device", | ||||
|       "name": "重置设备", | ||||
|       "required": false, | ||||
|       "callType": "async", | ||||
|       "inputParams": [ | ||||
|         { | ||||
|           "identifier": "reset_type", | ||||
|           "name": "重置类型", | ||||
|           "direction": "input", | ||||
|           "dataType": "enum", | ||||
|           "dataSpecsList": [ | ||||
|             { | ||||
|               "dataType": "enum", | ||||
|               "name": "软重置", | ||||
|               "value": 1 | ||||
|             }, | ||||
|             { | ||||
|               "dataType": "enum", | ||||
|               "name": "硬重置", | ||||
|               "value": 2 | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       ], | ||||
|       "outputParams": [ | ||||
|         { | ||||
|           "identifier": "result", | ||||
|           "name": "执行结果", | ||||
|           "direction": "output", | ||||
|           "dataType": "bool", | ||||
|           "dataSpecsList": [ | ||||
|             { | ||||
|               "dataType": "bool", | ||||
|               "name": "失败", | ||||
|               "value": 0 | ||||
|             }, | ||||
|             { | ||||
|               "dataType": "bool", | ||||
|               "name": "成功", | ||||
|               "value": 1 | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## 前端使用建议 | ||||
| 
 | ||||
| ### 1. TypeScript类型定义 | ||||
| 
 | ||||
| 建议在前端项目中定义完整的TypeScript接口,确保类型安全: | ||||
| 
 | ||||
| ```typescript | ||||
| // 定义完整的类型接口 | ||||
| export interface IotThingModelTSLRespVO { | ||||
|   productId: number; | ||||
|   productKey: string; | ||||
|   properties: ThingModelProperty[]; | ||||
|   events: ThingModelEvent[]; | ||||
|   services: ThingModelService[]; | ||||
| } | ||||
| 
 | ||||
| // 使用联合类型处理数据规范的多态性 | ||||
| export type ThingModelDataSpecs =  | ||||
|   | ThingModelNumericDataSpec | ||||
|   | ThingModelBoolOrEnumDataSpecs | ||||
|   | ThingModelDateOrTextDataSpecs | ||||
|   | ThingModelArrayDataSpecs | ||||
|   | ThingModelStructDataSpecs; | ||||
| ``` | ||||
| 
 | ||||
| ### 2. 数据验证 | ||||
| 
 | ||||
| ```typescript | ||||
| // 验证数据类型和数据规范的一致性 | ||||
| function validateDataSpecs(dataType: string, dataSpecs: any): boolean { | ||||
|   switch (dataType) { | ||||
|     case 'int': | ||||
|     case 'float': | ||||
|     case 'double': | ||||
|       return dataSpecs.dataType === dataType &&  | ||||
|              dataSpecs.max !== undefined &&  | ||||
|              dataSpecs.min !== undefined; | ||||
|     case 'bool': | ||||
|     case 'enum': | ||||
|       return Array.isArray(dataSpecs) &&  | ||||
|              dataSpecs.every(spec => spec.name && spec.value !== undefined); | ||||
|     // ... 其他类型验证 | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 3. 数据转换工具 | ||||
| 
 | ||||
| ```typescript | ||||
| // 将后端数据转换为前端展示格式 | ||||
| function formatPropertyValue(property: ThingModelProperty, value: any): string { | ||||
|   if (property.dataType === 'enum' || property.dataType === 'bool') { | ||||
|     const spec = property.dataSpecsList?.find(s => s.value === value); | ||||
|     return spec?.name || String(value); | ||||
|   } | ||||
|    | ||||
|   if (property.dataType === 'float' || property.dataType === 'double') { | ||||
|     const unit = property.dataSpecs?.unit || ''; | ||||
|     return `${value}${unit}`; | ||||
|   } | ||||
|    | ||||
|   return String(value); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## 注意事项 | ||||
| 
 | ||||
| 1. **数据规范选择**: 根据`dataType`选择使用`dataSpecs`还是`dataSpecsList` | ||||
| 2. **标识符唯一性**: 在同一产品下,所有功能的`identifier`必须唯一 | ||||
| 3. **数据类型一致性**: 参数的`dataType`必须与其`dataSpecs`的`dataType`保持一致 | ||||
| 4. **枚举值处理**: 布尔型和枚举型数据使用`dataSpecsList`数组存储可选值 | ||||
| 5. **嵌套结构**: 结构体和数组类型可能包含嵌套的数据规范定义 | ||||
| 6. **版本兼容**: 物模型结构可能随版本演进,前端需要做好兼容性处理 | ||||
| 
 | ||||
| 这个数据结构为IoT设备的完整功能描述提供了标准化的格式,支持复杂的数据类型和嵌套结构,能够满足各种IoT设备的建模需求。 | ||||
|  | @ -34,8 +34,20 @@ | |||
|         </el-form-item> | ||||
|       </el-col> | ||||
| 
 | ||||
|       <!-- 设备选择 --> | ||||
|       <!-- 设备选择模式 --> | ||||
|       <el-col :span="12"> | ||||
|         <el-form-item label="设备选择模式" required> | ||||
|           <el-radio-group v-model="deviceSelectionMode" @change="handleDeviceSelectionModeChange"> | ||||
|             <el-radio value="specific">选择设备</el-radio> | ||||
|             <el-radio value="all">全部设备</el-radio> | ||||
|           </el-radio-group> | ||||
|         </el-form-item> | ||||
|       </el-col> | ||||
|     </el-row> | ||||
| 
 | ||||
|     <!-- 具体设备选择 --> | ||||
|     <el-row v-if="deviceSelectionMode === 'specific'" :gutter="16"> | ||||
|       <el-col :span="24"> | ||||
|         <el-form-item label="选择设备" required> | ||||
|           <el-select | ||||
|             v-model="localDeviceId" | ||||
|  | @ -72,7 +84,7 @@ | |||
|     </el-row> | ||||
| 
 | ||||
|     <!-- 选择结果展示 --> | ||||
|     <div v-if="localProductId && localDeviceId" class="selection-result"> | ||||
|     <div v-if="localProductId && (localDeviceId !== undefined)" class="selection-result"> | ||||
|       <div class="result-header"> | ||||
|         <Icon icon="ep:check" class="result-icon" /> | ||||
|         <span class="result-title">已选择设备</span> | ||||
|  | @ -85,8 +97,17 @@ | |||
|         </div> | ||||
|         <div class="result-item"> | ||||
|           <span class="result-label">设备:</span> | ||||
|           <span class="result-value">{{ selectedDevice?.deviceName }}</span> | ||||
|           <span v-if="deviceSelectionMode === 'all'" class="result-value">全部设备</span> | ||||
|           <span v-else class="result-value">{{ selectedDevice?.deviceName }}</span> | ||||
|           <el-tag | ||||
|             v-if="deviceSelectionMode === 'all'" | ||||
|             size="small" | ||||
|             type="warning" | ||||
|           > | ||||
|             全部 | ||||
|           </el-tag> | ||||
|           <el-tag | ||||
|             v-else | ||||
|             size="small" | ||||
|             :type="getDeviceStatusTag(selectedDevice?.state)" | ||||
|           > | ||||
|  | @ -123,6 +144,9 @@ const emit = defineEmits<Emits>() | |||
| const localProductId = useVModel(props, 'productId', emit) | ||||
| const localDeviceId = useVModel(props, 'deviceId', emit) | ||||
| 
 | ||||
| // 设备选择模式 | ||||
| const deviceSelectionMode = ref<'specific' | 'all'>('specific') | ||||
| 
 | ||||
| // 数据状态 | ||||
| const productLoading = ref(false) | ||||
| const deviceLoading = ref(false) | ||||
|  | @ -175,6 +199,20 @@ const handleDeviceChange = (deviceId?: number) => { | |||
|   emitChange() | ||||
| } | ||||
| 
 | ||||
| const handleDeviceSelectionModeChange = (mode: 'specific' | 'all') => { | ||||
|   deviceSelectionMode.value = mode | ||||
| 
 | ||||
|   if (mode === 'all') { | ||||
|     // 全部设备时,设备ID设为0 | ||||
|     localDeviceId.value = 0 | ||||
|   } else { | ||||
|     // 选择设备时,清空设备ID | ||||
|     localDeviceId.value = undefined | ||||
|   } | ||||
| 
 | ||||
|   emitChange() | ||||
| } | ||||
| 
 | ||||
| const emitChange = () => { | ||||
|   emit('change', { | ||||
|     productId: localProductId.value, | ||||
|  | @ -223,6 +261,13 @@ const getDeviceList = async (productId: number) => { | |||
| onMounted(async () => { | ||||
|   await getProductList() | ||||
| 
 | ||||
|   // 根据初始设备ID设置选择模式 | ||||
|   if (localDeviceId.value === 0) { | ||||
|     deviceSelectionMode.value = 'all' | ||||
|   } else if (localDeviceId.value) { | ||||
|     deviceSelectionMode.value = 'specific' | ||||
|   } | ||||
| 
 | ||||
|   if (localProductId.value) { | ||||
|     await getDeviceList(localProductId.value) | ||||
|   } | ||||
|  |  | |||
|  | @ -27,8 +27,8 @@ | |||
|               <div class="option-identifier">{{ property.identifier }}</div> | ||||
|             </div> | ||||
|             <div class="option-meta"> | ||||
|               <el-tag :type="getPropertyTypeTag(property.type)" size="small"> | ||||
|                 {{ getPropertyTypeName(property.type) }} | ||||
|               <el-tag :type="getPropertyTypeTag(property.dataType)" size="small"> | ||||
|                 {{ getPropertyTypeName(property.dataType) }} | ||||
|               </el-tag> | ||||
|             </div> | ||||
|           </div> | ||||
|  | @ -41,8 +41,8 @@ | |||
|       <div class="details-header"> | ||||
|         <Icon icon="ep:info-filled" class="details-icon" /> | ||||
|         <span class="details-title">{{ selectedProperty.name }}</span> | ||||
|         <el-tag :type="getPropertyTypeTag(selectedProperty.type)" size="small"> | ||||
|           {{ getPropertyTypeName(selectedProperty.type) }} | ||||
|         <el-tag :type="getPropertyTypeTag(selectedProperty.dataType)" size="small"> | ||||
|           {{ getPropertyTypeName(selectedProperty.dataType) }} | ||||
|         </el-tag> | ||||
|       </div> | ||||
|       <div class="details-content"> | ||||
|  | @ -70,6 +70,9 @@ | |||
| <script setup lang="ts"> | ||||
| import { useVModel } from '@vueuse/core' | ||||
| import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types' | ||||
| import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel' | ||||
| import { IoTThingModelTypeEnum } from '@/views/iot/utils/constants' | ||||
| import type { IotThingModelTSLRespVO, PropertySelectorItem } from './types' | ||||
| 
 | ||||
| /** 属性选择器组件 */ | ||||
| defineOptions({ name: 'PropertySelector' }) | ||||
|  | @ -93,7 +96,8 @@ const localValue = useVModel(props, 'modelValue', emit) | |||
| 
 | ||||
| // 状态 | ||||
| const loading = ref(false) | ||||
| const propertyList = ref<any[]>([]) | ||||
| const propertyList = ref<PropertySelectorItem[]>([]) | ||||
| const thingModelTSL = ref<IotThingModelTSLRespVO | null>(null) | ||||
| 
 | ||||
| // 计算属性 | ||||
| const propertyGroups = computed(() => { | ||||
|  | @ -102,21 +106,21 @@ const propertyGroups = computed(() => { | |||
|   if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST) { | ||||
|     groups.push({ | ||||
|       label: '设备属性', | ||||
|       options: propertyList.value.filter(p => p.category === 'property') | ||||
|       options: propertyList.value.filter(p => p.type === IoTThingModelTypeEnum.PROPERTY) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) { | ||||
|     groups.push({ | ||||
|       label: '设备事件', | ||||
|       options: propertyList.value.filter(p => p.category === 'event') | ||||
|       options: propertyList.value.filter(p => p.type === IoTThingModelTypeEnum.EVENT) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) { | ||||
|     groups.push({ | ||||
|       label: '设备服务', | ||||
|       options: propertyList.value.filter(p => p.category === 'service') | ||||
|       options: propertyList.value.filter(p => p.type === IoTThingModelTypeEnum.SERVICE) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|  | @ -128,34 +132,34 @@ const selectedProperty = computed(() => { | |||
| }) | ||||
| 
 | ||||
| // 工具函数 | ||||
| const getPropertyTypeName = (type: string) => { | ||||
| const getPropertyTypeName = (dataType: string) => { | ||||
|   const typeMap = { | ||||
|     'int': '整数', | ||||
|     'float': '浮点数', | ||||
|     'double': '双精度', | ||||
|     'string': '字符串', | ||||
|     'text': '字符串', | ||||
|     'bool': '布尔值', | ||||
|     'enum': '枚举', | ||||
|     'date': '日期', | ||||
|     'struct': '结构体', | ||||
|     'array': '数组' | ||||
|   } | ||||
|   return typeMap[type] || type | ||||
|   return typeMap[dataType] || dataType | ||||
| } | ||||
| 
 | ||||
| const getPropertyTypeTag = (type: string) => { | ||||
| const getPropertyTypeTag = (dataType: string) => { | ||||
|   const tagMap = { | ||||
|     'int': 'primary', | ||||
|     'float': 'success', | ||||
|     'double': 'success', | ||||
|     'string': 'info', | ||||
|     'text': 'info', | ||||
|     'bool': 'warning', | ||||
|     'enum': 'danger', | ||||
|     'date': 'primary', | ||||
|     'struct': 'info', | ||||
|     'array': 'warning' | ||||
|   } | ||||
|   return tagMap[type] || 'info' | ||||
|   return tagMap[dataType] || 'info' | ||||
| } | ||||
| 
 | ||||
| // 事件处理 | ||||
|  | @ -163,84 +167,162 @@ const handleChange = (value: string) => { | |||
|   const property = propertyList.value.find(p => p.identifier === value) | ||||
|   if (property) { | ||||
|     emit('change', { | ||||
|       type: property.type, | ||||
|       type: property.dataType, | ||||
|       config: property | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // API 调用 | ||||
| const getPropertyList = async () => { | ||||
| // 获取物模型TSL数据 | ||||
| const getThingModelTSL = async () => { | ||||
|   if (!props.productId) { | ||||
|     thingModelTSL.value = null | ||||
|     propertyList.value = [] | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   loading.value = true | ||||
|   try { | ||||
|     // 这里应该调用真实的API获取物模型数据 | ||||
|     // 暂时使用模拟数据 | ||||
|     propertyList.value = [ | ||||
|       // 属性 | ||||
|       { | ||||
|         identifier: 'temperature', | ||||
|         name: '温度', | ||||
|         type: 'float', | ||||
|         category: 'property', | ||||
|         description: '环境温度', | ||||
|         unit: '°C', | ||||
|         range: '-40~80' | ||||
|       }, | ||||
|       { | ||||
|         identifier: 'humidity', | ||||
|         name: '湿度', | ||||
|         type: 'float', | ||||
|         category: 'property', | ||||
|         description: '环境湿度', | ||||
|         unit: '%', | ||||
|         range: '0~100' | ||||
|       }, | ||||
|       { | ||||
|         identifier: 'power', | ||||
|         name: '电源状态', | ||||
|         type: 'bool', | ||||
|         category: 'property', | ||||
|         description: '设备电源开关状态' | ||||
|       }, | ||||
|       // 事件 | ||||
|       { | ||||
|         identifier: 'alarm', | ||||
|         name: '告警事件', | ||||
|         type: 'struct', | ||||
|         category: 'event', | ||||
|         description: '设备告警事件' | ||||
|       }, | ||||
|       { | ||||
|         identifier: 'fault', | ||||
|         name: '故障事件', | ||||
|         type: 'struct', | ||||
|         category: 'event', | ||||
|         description: '设备故障事件' | ||||
|       }, | ||||
|       // 服务 | ||||
|       { | ||||
|         identifier: 'restart', | ||||
|         name: '重启服务', | ||||
|         type: 'struct', | ||||
|         category: 'service', | ||||
|         description: '设备重启服务' | ||||
|       } | ||||
|     ] | ||||
|     thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(props.productId) | ||||
|     parseThingModelData() | ||||
|   } catch (error) { | ||||
|     console.error('获取物模型失败:', error) | ||||
|     console.error('获取物模型TSL失败:', error) | ||||
|     // 如果TSL获取失败,尝试获取物模型列表 | ||||
|     await getThingModelList() | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 获取物模型列表(备用方案) | ||||
| const getThingModelList = async () => { | ||||
|   if (!props.productId) { | ||||
|     propertyList.value = [] | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     const data = await ThingModelApi.getThingModelList({ productId: props.productId }) | ||||
|     propertyList.value = data || [] | ||||
|   } catch (error) { | ||||
|     console.error('获取物模型列表失败:', error) | ||||
|     propertyList.value = [] | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 解析物模型TSL数据 | ||||
| const parseThingModelData = () => { | ||||
|   const tsl = thingModelTSL.value | ||||
|   const properties: PropertySelectorItem[] = [] | ||||
| 
 | ||||
|   if (tsl) { | ||||
|     // 解析属性 | ||||
|     if (tsl.properties && Array.isArray(tsl.properties)) { | ||||
|       tsl.properties.forEach((prop) => { | ||||
|         properties.push({ | ||||
|           identifier: prop.identifier, | ||||
|           name: prop.name, | ||||
|           description: prop.description, | ||||
|           dataType: prop.dataType, | ||||
|           type: IoTThingModelTypeEnum.PROPERTY, | ||||
|           accessMode: prop.accessMode, | ||||
|           required: prop.required, | ||||
|           unit: getPropertyUnit(prop), | ||||
|           range: getPropertyRange(prop), | ||||
|           property: prop | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     // 解析事件 | ||||
|     if (tsl.events && Array.isArray(tsl.events)) { | ||||
|       tsl.events.forEach((event) => { | ||||
|         properties.push({ | ||||
|           identifier: event.identifier, | ||||
|           name: event.name, | ||||
|           description: event.description, | ||||
|           dataType: 'struct', | ||||
|           type: IoTThingModelTypeEnum.EVENT, | ||||
|           eventType: event.type, | ||||
|           required: event.required, | ||||
|           outputParams: event.outputParams, | ||||
|           event: event | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     // 解析服务 | ||||
|     if (tsl.services && Array.isArray(tsl.services)) { | ||||
|       tsl.services.forEach((service) => { | ||||
|         properties.push({ | ||||
|           identifier: service.identifier, | ||||
|           name: service.name, | ||||
|           description: service.description, | ||||
|           dataType: 'struct', | ||||
|           type: IoTThingModelTypeEnum.SERVICE, | ||||
|           callType: service.callType, | ||||
|           required: service.required, | ||||
|           inputParams: service.inputParams, | ||||
|           outputParams: service.outputParams, | ||||
|           service: service | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   propertyList.value = properties | ||||
| } | ||||
| 
 | ||||
| // 获取属性单位 | ||||
| const getPropertyUnit = (property: any) => { | ||||
|   if (!property) return undefined | ||||
| 
 | ||||
|   // 数值型数据的单位 | ||||
|   if (property.dataSpecs && property.dataSpecs.unit) { | ||||
|     return property.dataSpecs.unit | ||||
|   } | ||||
| 
 | ||||
|   return undefined | ||||
| } | ||||
| 
 | ||||
| // 获取属性范围描述 | ||||
| const getPropertyRange = (property: any) => { | ||||
|   if (!property) return undefined | ||||
| 
 | ||||
|   // 数值型数据的范围 | ||||
|   if (property.dataSpecs) { | ||||
|     const specs = property.dataSpecs | ||||
|     if (specs.min !== undefined && specs.max !== undefined) { | ||||
|       return `${specs.min}~${specs.max}` | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 枚举型和布尔型数据的选项 | ||||
|   if (property.dataSpecsList && Array.isArray(property.dataSpecsList)) { | ||||
|     return property.dataSpecsList.map((item: any) => `${item.name}(${item.value})`).join(', ') | ||||
|   } | ||||
| 
 | ||||
|   return undefined | ||||
| } | ||||
| 
 | ||||
| // 获取数据范围描述(保留兼容性) | ||||
| const getDataRange = (dataSpecs: any) => { | ||||
|   if (!dataSpecs) return undefined | ||||
| 
 | ||||
|   if (dataSpecs.min !== undefined && dataSpecs.max !== undefined) { | ||||
|     return `${dataSpecs.min}~${dataSpecs.max}` | ||||
|   } | ||||
| 
 | ||||
|   if (dataSpecs.dataSpecsList && Array.isArray(dataSpecs.dataSpecsList)) { | ||||
|     return dataSpecs.dataSpecsList.map((item: any) => `${item.name}(${item.value})`).join(', ') | ||||
|   } | ||||
| 
 | ||||
|   return undefined | ||||
| } | ||||
| 
 | ||||
| // 监听产品变化 | ||||
| watch(() => props.productId, () => { | ||||
|   getPropertyList() | ||||
|   getThingModelTSL() | ||||
| }, { immediate: true }) | ||||
| 
 | ||||
| // 监听触发类型变化 | ||||
|  |  | |||
|  | @ -0,0 +1,168 @@ | |||
| // IoT物模型TSL数据类型定义
 | ||||
| 
 | ||||
| /** 物模型TSL响应数据结构 */ | ||||
| export interface IotThingModelTSLRespVO { | ||||
|   productId: number | ||||
|   productKey: string | ||||
|   properties: ThingModelProperty[] | ||||
|   events: ThingModelEvent[] | ||||
|   services: ThingModelService[] | ||||
| } | ||||
| 
 | ||||
| /** 物模型属性 */ | ||||
| export interface ThingModelProperty { | ||||
|   identifier: string | ||||
|   name: string | ||||
|   accessMode: string | ||||
|   required?: boolean | ||||
|   dataType: string | ||||
|   description?: string | ||||
|   dataSpecs?: ThingModelDataSpecs | ||||
|   dataSpecsList?: ThingModelDataSpecs[] | ||||
| } | ||||
| 
 | ||||
| /** 物模型事件 */ | ||||
| export interface ThingModelEvent { | ||||
|   identifier: string | ||||
|   name: string | ||||
|   required?: boolean | ||||
|   type: string | ||||
|   description?: string | ||||
|   outputParams?: ThingModelParam[] | ||||
|   method?: string | ||||
| } | ||||
| 
 | ||||
| /** 物模型服务 */ | ||||
| export interface ThingModelService { | ||||
|   identifier: string | ||||
|   name: string | ||||
|   required?: boolean | ||||
|   callType: string | ||||
|   description?: string | ||||
|   inputParams?: ThingModelParam[] | ||||
|   outputParams?: ThingModelParam[] | ||||
|   method?: string | ||||
| } | ||||
| 
 | ||||
| /** 物模型参数 */ | ||||
| export interface ThingModelParam { | ||||
|   identifier: string | ||||
|   name: string | ||||
|   direction: string | ||||
|   paraOrder?: number | ||||
|   dataType: string | ||||
|   dataSpecs?: ThingModelDataSpecs | ||||
|   dataSpecsList?: ThingModelDataSpecs[] | ||||
| } | ||||
| 
 | ||||
| /** 数值型数据规范 */ | ||||
| export interface ThingModelNumericDataSpec { | ||||
|   dataType: 'int' | 'float' | 'double' | ||||
|   max: string | ||||
|   min: string | ||||
|   step: string | ||||
|   precise?: string | ||||
|   defaultValue?: string | ||||
|   unit?: string | ||||
|   unitName?: string | ||||
| } | ||||
| 
 | ||||
| /** 布尔/枚举型数据规范 */ | ||||
| export interface ThingModelBoolOrEnumDataSpecs { | ||||
|   dataType: 'bool' | 'enum' | ||||
|   name: string | ||||
|   value: number | ||||
| } | ||||
| 
 | ||||
| /** 文本/时间型数据规范 */ | ||||
| export interface ThingModelDateOrTextDataSpecs { | ||||
|   dataType: 'text' | 'date' | ||||
|   length?: number | ||||
|   defaultValue?: string | ||||
| } | ||||
| 
 | ||||
| /** 数组型数据规范 */ | ||||
| export interface ThingModelArrayDataSpecs { | ||||
|   dataType: 'array' | ||||
|   size: number | ||||
|   childDataType: string | ||||
|   dataSpecsList?: ThingModelDataSpecs[] | ||||
| } | ||||
| 
 | ||||
| /** 结构体型数据规范 */ | ||||
| export interface ThingModelStructDataSpecs { | ||||
|   dataType: 'struct' | ||||
|   identifier: string | ||||
|   name: string | ||||
|   accessMode: string | ||||
|   required?: boolean | ||||
|   childDataType: string | ||||
|   dataSpecs?: ThingModelDataSpecs | ||||
|   dataSpecsList?: ThingModelDataSpecs[] | ||||
| } | ||||
| 
 | ||||
| /** 数据规范联合类型 */ | ||||
| export type ThingModelDataSpecs = | ||||
|   | ThingModelNumericDataSpec | ||||
|   | ThingModelBoolOrEnumDataSpecs | ||||
|   | ThingModelDateOrTextDataSpecs | ||||
|   | ThingModelArrayDataSpecs | ||||
|   | ThingModelStructDataSpecs | ||||
| 
 | ||||
| /** 属性选择器内部使用的统一数据结构 */ | ||||
| export interface PropertySelectorItem { | ||||
|   identifier: string | ||||
|   name: string | ||||
|   description?: string | ||||
|   dataType: string | ||||
|   type: number // IoTThingModelTypeEnum
 | ||||
|   accessMode?: string | ||||
|   required?: boolean | ||||
|   unit?: string | ||||
|   range?: string | ||||
|   eventType?: string | ||||
|   callType?: string | ||||
|   inputParams?: ThingModelParam[] | ||||
|   outputParams?: ThingModelParam[] | ||||
|   property?: ThingModelProperty | ||||
|   event?: ThingModelEvent | ||||
|   service?: ThingModelService | ||||
| } | ||||
| 
 | ||||
| /** 数据类型枚举 */ | ||||
| export enum DataTypeEnum { | ||||
|   INT = 'int', | ||||
|   FLOAT = 'float', | ||||
|   DOUBLE = 'double', | ||||
|   ENUM = 'enum', | ||||
|   BOOL = 'bool', | ||||
|   TEXT = 'text', | ||||
|   DATE = 'date', | ||||
|   STRUCT = 'struct', | ||||
|   ARRAY = 'array' | ||||
| } | ||||
| 
 | ||||
| /** 访问模式枚举 */ | ||||
| export enum AccessModeEnum { | ||||
|   READ = 'r', | ||||
|   READ_write = 'rw' | ||||
| } | ||||
| 
 | ||||
| /** 事件类型枚举 */ | ||||
| export enum EventTypeEnum { | ||||
|   INFO = 'info', | ||||
|   ALERT = 'alert', | ||||
|   ERROR = 'error' | ||||
| } | ||||
| 
 | ||||
| /** 调用类型枚举 */ | ||||
| export enum CallTypeEnum { | ||||
|   ASYNC = 'async', | ||||
|   SYNC = 'sync' | ||||
| } | ||||
| 
 | ||||
| /** 参数方向枚举 */ | ||||
| export enum ParamDirectionEnum { | ||||
|   INPUT = 'input', | ||||
|   OUTPUT = 'output' | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	 puhui999
						puhui999