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"
|
||||
|
@ -58,8 +70,8 @@
|
|||
<div class="option-name">{{ device.deviceName }}</div>
|
||||
<div class="option-nickname">{{ device.nickname || '无备注' }}</div>
|
||||
</div>
|
||||
<el-tag
|
||||
size="small"
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="getDeviceStatusTag(device.state)"
|
||||
>
|
||||
{{ getDeviceStatusText(device.state) }}
|
||||
|
@ -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,9 +97,18 @@
|
|||
</div>
|
||||
<div class="result-item">
|
||||
<span class="result-label">设备:</span>
|
||||
<span class="result-value">{{ selectedDevice?.deviceName }}</span>
|
||||
<el-tag
|
||||
size="small"
|
||||
<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)"
|
||||
>
|
||||
{{ getDeviceStatusText(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)
|
||||
|
@ -162,11 +186,11 @@ const handleProductChange = async (productId?: number) => {
|
|||
localProductId.value = productId
|
||||
localDeviceId.value = undefined
|
||||
deviceList.value = []
|
||||
|
||||
|
||||
if (productId) {
|
||||
await getDeviceList(productId)
|
||||
}
|
||||
|
||||
|
||||
emitChange()
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -222,7 +260,14 @@ 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,33 +96,34 @@ 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(() => {
|
||||
const groups: { label: string; options: any[] }[] = []
|
||||
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return groups.filter(group => group.options.length > 0)
|
||||
})
|
||||
|
||||
|
@ -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