【功能新增】IoT: 规则场景监听器相关组件
parent
477b2439c5
commit
14ffb6483f
|
|
@ -7,15 +7,15 @@ export interface IotRuleSceneTriggerConfig {
|
||||||
* - 1: 设备触发
|
* - 1: 设备触发
|
||||||
* - 2: 定时触发
|
* - 2: 定时触发
|
||||||
*/
|
*/
|
||||||
type: number;
|
type: number
|
||||||
/** 产品标识 */
|
/** 产品标识 */
|
||||||
productKey?: string;
|
productKey?: string
|
||||||
/** 设备名称数组 */
|
/** 设备名称数组 */
|
||||||
deviceNames?: string[];
|
deviceNames?: string[]
|
||||||
/** 触发条件数组。条件之间是"或"的关系 */
|
/** 触发条件数组。条件之间是"或"的关系 */
|
||||||
conditions?: IotRuleSceneTriggerCondition[];
|
conditions?: IotRuleSceneTriggerCondition[]
|
||||||
/** CRON 表达式。当 type = 2 时必填 */
|
/** CRON 表达式。当 type = 2 时必填 */
|
||||||
cronExpression?: string;
|
cronExpression?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,11 +27,11 @@ export interface IotRuleSceneTriggerCondition {
|
||||||
* - property: 属性上报
|
* - property: 属性上报
|
||||||
* - event: 事件上报
|
* - event: 事件上报
|
||||||
*/
|
*/
|
||||||
type: string;
|
type: string
|
||||||
/** 消息标识符 */
|
/** 消息标识符 */
|
||||||
identifier: string;
|
identifier: string
|
||||||
/** 参数数组。参数之间是"或"的关系 */
|
/** 参数数组。参数之间是"或"的关系 */
|
||||||
parameters: IotRuleSceneTriggerConditionParameter[];
|
parameters: IotRuleSceneTriggerConditionParameter[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,23 +39,16 @@ export interface IotRuleSceneTriggerCondition {
|
||||||
*/
|
*/
|
||||||
export interface IotRuleSceneTriggerConditionParameter {
|
export interface IotRuleSceneTriggerConditionParameter {
|
||||||
/** 标识符(属性、事件、服务) */
|
/** 标识符(属性、事件、服务) */
|
||||||
identifier: string;
|
identifier: string
|
||||||
/**
|
/**
|
||||||
* 操作符
|
* 操作符
|
||||||
* - eq: 等于
|
|
||||||
* - gt: 大于
|
|
||||||
* - gte: 大于等于
|
|
||||||
* - lt: 小于
|
|
||||||
* - lte: 小于等于
|
|
||||||
* - between: 范围
|
|
||||||
* - in: 在列表中
|
|
||||||
*/
|
*/
|
||||||
operator: string;
|
operator: string
|
||||||
/**
|
/**
|
||||||
* 比较值
|
* 比较值
|
||||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"
|
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"
|
||||||
*/
|
*/
|
||||||
value: string;
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -67,11 +60,11 @@ export interface IotRuleSceneActionConfig {
|
||||||
* - 1: 设备控制
|
* - 1: 设备控制
|
||||||
* - 2: 数据桥接
|
* - 2: 数据桥接
|
||||||
*/
|
*/
|
||||||
type: number;
|
type: number
|
||||||
/** 设备控制配置。当 type = 1 时必填 */
|
/** 设备控制配置。当 type = 1 时必填 */
|
||||||
deviceControl?: IotRuleSceneActionDeviceControl;
|
deviceControl?: IotRuleSceneActionDeviceControl
|
||||||
/** 数据桥接编号。当 type = 2 时必填 */
|
/** 数据桥接编号。当 type = 2 时必填 */
|
||||||
dataBridgeId?: number;
|
dataBridgeId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,23 +72,23 @@ export interface IotRuleSceneActionConfig {
|
||||||
*/
|
*/
|
||||||
export interface IotRuleSceneActionDeviceControl {
|
export interface IotRuleSceneActionDeviceControl {
|
||||||
/** 产品标识 */
|
/** 产品标识 */
|
||||||
productKey: string;
|
productKey: string
|
||||||
/** 设备名称数组 */
|
/** 设备名称数组 */
|
||||||
deviceNames: string[];
|
deviceNames: string[]
|
||||||
/**
|
/**
|
||||||
* 消息类型
|
* 消息类型
|
||||||
* - property: 属性
|
* - property: 属性
|
||||||
* - service: 服务
|
* - service: 服务
|
||||||
*/
|
*/
|
||||||
type: string;
|
type: string
|
||||||
/**
|
/**
|
||||||
* 消息标识符
|
* 消息标识符
|
||||||
* - property_set: 属性设置
|
* - property_set: 属性设置
|
||||||
* - service_invoke: 服务调用
|
* - service_invoke: 服务调用
|
||||||
*/
|
*/
|
||||||
identifier: string;
|
identifier: string
|
||||||
/** 具体数据 */
|
/** 具体数据 */
|
||||||
data: Record<string, any>;
|
data: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,17 +96,17 @@ export interface IotRuleSceneActionDeviceControl {
|
||||||
*/
|
*/
|
||||||
export interface IotRuleSceneSaveReqVO {
|
export interface IotRuleSceneSaveReqVO {
|
||||||
/** 场景规则编号 */
|
/** 场景规则编号 */
|
||||||
id?: number;
|
id?: number
|
||||||
/** 场景规则名称 */
|
/** 场景规则名称 */
|
||||||
name: string;
|
name: string
|
||||||
/** 场景规则状态(0=禁用 1=启用) */
|
/** 场景规则状态(0=禁用 1=启用) */
|
||||||
status: number;
|
status: number
|
||||||
/** 触发器配置 */
|
/** 触发器配置 */
|
||||||
triggerConfig: IotRuleSceneTriggerConfig;
|
triggerConfig: IotRuleSceneTriggerConfig
|
||||||
/** 执行动作配置数组 */
|
/** 执行动作配置数组 */
|
||||||
actionConfigs: IotRuleSceneActionConfig[];
|
actionConfigs: IotRuleSceneActionConfig[]
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,19 +114,19 @@ export interface IotRuleSceneSaveReqVO {
|
||||||
*/
|
*/
|
||||||
export interface IotRuleSceneRespVO {
|
export interface IotRuleSceneRespVO {
|
||||||
/** 场景规则编号 */
|
/** 场景规则编号 */
|
||||||
id: number;
|
id: number
|
||||||
/** 场景规则名称 */
|
/** 场景规则名称 */
|
||||||
name: string;
|
name: string
|
||||||
/** 场景规则状态(0=禁用 1=启用) */
|
/** 场景规则状态(0=禁用 1=启用) */
|
||||||
status: number;
|
status: number
|
||||||
/** 触发器配置 */
|
/** 触发器配置 */
|
||||||
triggerConfig: IotRuleSceneTriggerConfig;
|
triggerConfig: IotRuleSceneTriggerConfig
|
||||||
/** 执行动作配置数组 */
|
/** 执行动作配置数组 */
|
||||||
actionConfigs: IotRuleSceneActionConfig[];
|
actionConfigs: IotRuleSceneActionConfig[]
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark?: string;
|
remark?: string
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
createTime: Date;
|
createTime: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -141,9 +134,9 @@ export interface IotRuleSceneRespVO {
|
||||||
*/
|
*/
|
||||||
export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
|
export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
|
||||||
/** 触发次数 */
|
/** 触发次数 */
|
||||||
triggerCount: number;
|
triggerCount: number
|
||||||
/** 最后触发时间 */
|
/** 最后触发时间 */
|
||||||
lastTriggerTime?: Date;
|
lastTriggerTime?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -151,15 +144,15 @@ export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
|
||||||
*/
|
*/
|
||||||
export interface IotRuleScenePageReqVO {
|
export interface IotRuleScenePageReqVO {
|
||||||
/** 场景规则名称 */
|
/** 场景规则名称 */
|
||||||
name?: string;
|
name?: string
|
||||||
/** 场景规则状态(0=禁用 1=启用) */
|
/** 场景规则状态(0=禁用 1=启用) */
|
||||||
status?: number;
|
status?: number
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
createTime?: [Date, Date];
|
createTime?: [Date, Date]
|
||||||
/** 页码 */
|
/** 页码 */
|
||||||
pageNo?: number;
|
pageNo?: number
|
||||||
/** 每页条数 */
|
/** 每页条数 */
|
||||||
pageSize?: number;
|
pageSize?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -245,5 +245,7 @@ export enum DICT_TYPE {
|
||||||
IOT_PLUGIN_STATUS = 'iot_plugin_status', // IOT 插件状态
|
IOT_PLUGIN_STATUS = 'iot_plugin_status', // IOT 插件状态
|
||||||
IOT_PLUGIN_TYPE = 'iot_plugin_type', // IOT 插件类型
|
IOT_PLUGIN_TYPE = 'iot_plugin_type', // IOT 插件类型
|
||||||
IOT_DATA_BRIDGE_DIRECTION_ENUM = 'iot_data_bridge_direction_enum', // 桥梁方向
|
IOT_DATA_BRIDGE_DIRECTION_ENUM = 'iot_data_bridge_direction_enum', // 桥梁方向
|
||||||
IOT_DATA_BRIDGE_TYPE_ENUM = 'iot_data_bridge_type_enum' // 桥梁类型
|
IOT_DATA_BRIDGE_TYPE_ENUM = 'iot_data_bridge_type_enum', // 桥梁类型
|
||||||
|
IOT_DEVICE_MESSAGE_TYPE_ENUM = 'iot_device_message_type_enum', // IoT 设备消息类型枚举
|
||||||
|
IOT_RULE_SCENE_TRIGGER_TYPE_ENUM = 'iot_rule_scene_trigger_type_enum' // IoT 场景流转的触发类型枚举
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="70%">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
|
|
@ -7,29 +7,41 @@
|
||||||
label-width="100px"
|
label-width="100px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="场景名称" prop="name">
|
<el-row>
|
||||||
<el-input v-model="formData.name" placeholder="请输入场景名称" />
|
<el-col :span="12">
|
||||||
</el-form-item>
|
<el-form-item label="场景名称" prop="name">
|
||||||
<el-form-item label="场景描述" prop="description">
|
<el-input v-model="formData.name" placeholder="请输入场景名称" />
|
||||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入场景描述" />
|
</el-form-item>
|
||||||
</el-form-item>
|
</el-col>
|
||||||
<el-form-item label="场景状态" prop="status">
|
<el-col :span="12">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-form-item label="场景状态" prop="status">
|
||||||
<el-radio
|
<el-radio-group v-model="formData.status">
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
<el-radio
|
||||||
:key="dict.value"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:label="dict.value"
|
:key="dict.value"
|
||||||
>
|
:label="dict.value"
|
||||||
{{ dict.label }}
|
>
|
||||||
</el-radio>
|
{{ dict.label }}
|
||||||
</el-radio-group>
|
</el-radio>
|
||||||
</el-form-item>
|
</el-radio-group>
|
||||||
<el-form-item label="触发器数组" prop="triggers">
|
</el-form-item>
|
||||||
<el-input v-model="formData.triggers" placeholder="请输入触发器数组" />
|
</el-col>
|
||||||
</el-form-item>
|
<el-col :span="24">
|
||||||
<el-form-item label="执行器数组" prop="actions">
|
<el-form-item label="场景描述" prop="description">
|
||||||
<el-input v-model="formData.actions" placeholder="请输入执行器数组" />
|
<el-input v-model="formData.description" type="textarea" placeholder="请输入场景描述" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-divider content-position="left">触发器配置</el-divider>
|
||||||
|
<device-listener v-model="formData.triggers" />
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-divider content-position="left">执行动作配置</el-divider>
|
||||||
|
<el-form-item label="执行器数组" prop="actionConfigs">
|
||||||
|
<el-input v-model="formData.actions" placeholder="请输入执行器数组" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
|
@ -40,6 +52,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
|
import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
|
||||||
|
import DeviceListener from './components/DeviceListener.vue'
|
||||||
|
|
||||||
/** IoT 规则场景(场景联动) 表单 */
|
/** IoT 规则场景(场景联动) 表单 */
|
||||||
defineOptions({ name: 'RuleSceneForm' })
|
defineOptions({ name: 'RuleSceneForm' })
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
<template>
|
||||||
|
<div class="condition-selector">
|
||||||
|
<el-select
|
||||||
|
v-model="selectedOperator"
|
||||||
|
:placeholder="placeholder || '请选择操作符'"
|
||||||
|
class="condition-select"
|
||||||
|
@change="handleOperatorChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in operatorOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
<div class="operator-option">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<span class="operator-symbol">{{ item.symbol }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<!-- 普通输入值 -->
|
||||||
|
<template v-if="!isRangeOperator && !isListOperator">
|
||||||
|
<el-input
|
||||||
|
v-model="inputValue"
|
||||||
|
:placeholder="valuePlaceholder || '请输入值'"
|
||||||
|
class="value-input"
|
||||||
|
@change="handleValueChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 范围值输入 -->
|
||||||
|
<template v-if="isRangeOperator">
|
||||||
|
<div class="range-input">
|
||||||
|
<el-input
|
||||||
|
v-model="rangeValue.min"
|
||||||
|
placeholder="最小值"
|
||||||
|
class="range-input-item"
|
||||||
|
@change="handleRangeValueChange"
|
||||||
|
/>
|
||||||
|
<span class="range-separator">至</span>
|
||||||
|
<el-input
|
||||||
|
v-model="rangeValue.max"
|
||||||
|
placeholder="最大值"
|
||||||
|
class="range-input-item"
|
||||||
|
@change="handleRangeValueChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 列表值输入 -->
|
||||||
|
<template v-if="isListOperator">
|
||||||
|
<el-select
|
||||||
|
v-model="listValue"
|
||||||
|
:placeholder="valuePlaceholder || '请选择值'"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
class="list-select"
|
||||||
|
@change="handleListValueChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in listOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, defineEmits, defineProps } from 'vue'
|
||||||
|
import { IotRuleSceneTriggerConditionParameterOperatorEnum } from '@/api/iot/rule/scene/scene.types'
|
||||||
|
|
||||||
|
interface ConditionValue {
|
||||||
|
operator: IotRuleSceneTriggerConditionParameterOperatorEnum
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件属性
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object as () => ConditionValue,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
valuePlaceholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
listOptions: {
|
||||||
|
type: Array as () => { label: string, value: string | number }[],
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// 操作符选项
|
||||||
|
const operatorOptions = [
|
||||||
|
{ label: '等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQ, symbol: '=' },
|
||||||
|
{ label: '不等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.NE, symbol: '≠' },
|
||||||
|
{ label: '大于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.GT, symbol: '>' },
|
||||||
|
{ label: '小于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.LT, symbol: '<' },
|
||||||
|
{ label: '大于等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.GTE, symbol: '≥' },
|
||||||
|
{ label: '小于等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.LTE, symbol: '≤' },
|
||||||
|
{ label: '在...之间', value: IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN, symbol: '↔' },
|
||||||
|
{ label: '不在...之间', value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN, symbol: '⟷' },
|
||||||
|
{ label: '在列表中', value: IotRuleSceneTriggerConditionParameterOperatorEnum.IN, symbol: '∈' },
|
||||||
|
{ label: '不在列表中', value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN, symbol: '∉' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 当前选中的操作符
|
||||||
|
const selectedOperator = ref(props.modelValue.operator || '')
|
||||||
|
|
||||||
|
// 输入值
|
||||||
|
const inputValue = ref(props.modelValue.value || '')
|
||||||
|
|
||||||
|
// 范围值
|
||||||
|
const rangeValue = ref({
|
||||||
|
min: '',
|
||||||
|
max: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 列表值
|
||||||
|
const listValue = ref([])
|
||||||
|
|
||||||
|
// 计算属性:是否为范围操作符
|
||||||
|
const isRangeOperator = computed(() => {
|
||||||
|
return ['between', 'notBetween'].includes(selectedOperator.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:是否为列表操作符
|
||||||
|
const isListOperator = computed(() => {
|
||||||
|
return ['in', 'notIn'].includes(selectedOperator.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理操作符变更
|
||||||
|
const handleOperatorChange = () => {
|
||||||
|
updateModelValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理值变更
|
||||||
|
const handleValueChange = () => {
|
||||||
|
updateModelValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理范围值变更
|
||||||
|
const handleRangeValueChange = () => {
|
||||||
|
updateModelValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理列表值变更
|
||||||
|
const handleListValueChange = () => {
|
||||||
|
updateModelValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新组件值
|
||||||
|
const updateModelValue = () => {
|
||||||
|
const value = isRangeOperator.value
|
||||||
|
? `${rangeValue.value.min},${rangeValue.value.max}`
|
||||||
|
: isListOperator.value
|
||||||
|
? listValue.value.join(',')
|
||||||
|
: inputValue.value
|
||||||
|
|
||||||
|
emit('update:modelValue', {
|
||||||
|
operator: selectedOperator.value,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 modelValue 变化
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
selectedOperator.value = newVal.operator || ''
|
||||||
|
|
||||||
|
if (isRangeOperator.value && newVal.value) {
|
||||||
|
const [min, max] = newVal.value.split(',')
|
||||||
|
rangeValue.value = { min, max }
|
||||||
|
} else if (isListOperator.value && newVal.value) {
|
||||||
|
listValue.value = newVal.value.split(',')
|
||||||
|
} else {
|
||||||
|
inputValue.value = newVal.value || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { deep: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.condition-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-select {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-option {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-symbol {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-input {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-input-item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-separator {
|
||||||
|
padding: 0 4px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-select {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<div class="device-listener m-10px">
|
||||||
|
<div class="device-listener-header h-50px flex items-center px-10px">
|
||||||
|
<div class="flex items-center mr-60px">
|
||||||
|
<span class="mr-10px">触发条件</span>
|
||||||
|
<el-select v-model="triggerType" class="!w-240px" clearable placeholder="请选择触发条件">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_TRIGGER_TYPE_ENUM)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mr-60px">
|
||||||
|
<span class="mr-10px">产品</span>
|
||||||
|
<el-button type="primary">选择产品</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mr-60px">
|
||||||
|
<span class="mr-10px">设备</span>
|
||||||
|
<el-button type="primary">选择设备</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-listener-condition flex p-10px">
|
||||||
|
<div class="flex flex-col items-center justify-center mr-10px h-a">
|
||||||
|
<el-select v-model="messageType" class="!w-160px" clearable placeholder="">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center flex-wrap">
|
||||||
|
<DeviceListenerCondition
|
||||||
|
v-for="i in 2"
|
||||||
|
:key="i"
|
||||||
|
v-model="conditionParameter"
|
||||||
|
class="mb-10px last:mb-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center mr-10px h-a">
|
||||||
|
<!-- 添加规则 -->
|
||||||
|
<el-button type="primary" circle :icon="Plus" size="small" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增条件按钮 -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<el-button type="primary"> 新增监听器 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
|
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import DeviceListenerCondition from './DeviceListenerCondition.vue'
|
||||||
|
|
||||||
|
/** 场景联动之监听器组件 */
|
||||||
|
defineOptions({ name: 'DeviceListener' })
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
modelValue: any[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// 添加响应式变量
|
||||||
|
const triggerType = ref()
|
||||||
|
const messageType = ref('property')
|
||||||
|
const conditionParameter = ref({})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.device-listener {
|
||||||
|
.device-listener-header {
|
||||||
|
background-color: #eff3f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-listener-condition {
|
||||||
|
background-color: #dbe5f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div class="device-listener-condition">
|
||||||
|
<el-select
|
||||||
|
v-model="conditionParameter.identifier"
|
||||||
|
class="!w-240px mr-10px"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择物模型"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-model="conditionParameter.operator"
|
||||||
|
class="!w-240px mr-10px"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择条件"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="conditionParameter.value" class="!w-240px mr-10px" placeholder="请输入值">
|
||||||
|
<template #append> 单位 </template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { IotRuleSceneTriggerConditionParameter } from '@/api/iot/rule/scene/scene.types'
|
||||||
|
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeviceListenerCondition' })
|
||||||
|
const props = defineProps<{ modelValue: any }>()
|
||||||
|
const emits = defineEmits(['update:modelValue'])
|
||||||
|
const conditionParameter = useVModel(
|
||||||
|
props,
|
||||||
|
'modelValue',
|
||||||
|
emits
|
||||||
|
) as Ref<IotRuleSceneTriggerConditionParameter>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,379 @@
|
||||||
|
<template>
|
||||||
|
<div class="trigger-conditions">
|
||||||
|
<div class="conditions-header mb-3">
|
||||||
|
<el-button type="primary" @click="addCondition" :disabled="!productKey">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 添加条件
|
||||||
|
</el-button>
|
||||||
|
<div class="conditions-tips" v-if="modelValue && modelValue.length > 0">
|
||||||
|
注:多个条件之间为"或"关系
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-empty v-if="!modelValue || modelValue.length === 0" description="暂无触发条件" />
|
||||||
|
|
||||||
|
<div class="conditions-list" v-else>
|
||||||
|
<div v-for="(condition, index) in modelValue" :key="index" class="condition-item mb-3">
|
||||||
|
<el-card class="box-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>条件 {{ index + 1 }}</span>
|
||||||
|
<el-button type="danger" link @click="removeCondition(index)"> 删除 </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="condition-content">
|
||||||
|
<el-form label-width="100px" :model="condition">
|
||||||
|
<el-form-item label="消息类型">
|
||||||
|
<el-select
|
||||||
|
v-model="condition.type"
|
||||||
|
placeholder="请选择消息类型"
|
||||||
|
@change="handleMessageTypeChange(index)"
|
||||||
|
>
|
||||||
|
<el-option label="属性上报" :value="IotDeviceMessageTypeEnum.PROPERTY" />
|
||||||
|
<el-option label="事件上报" :value="IotDeviceMessageTypeEnum.EVENT" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="消息标识符">
|
||||||
|
<el-select
|
||||||
|
v-model="condition.identifier"
|
||||||
|
placeholder="请选择消息标识符"
|
||||||
|
filterable
|
||||||
|
:loading="thingModelLoading"
|
||||||
|
@change="handleIdentifierChange(index)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in getThingModelOptions(condition.type)"
|
||||||
|
:key="item.identifier"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.identifier"
|
||||||
|
>
|
||||||
|
<div class="thing-model-option">
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
<span class="thing-model-identifier">{{ item.identifier }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="thing-model-desc" v-if="item.description">{{
|
||||||
|
item.description
|
||||||
|
}}</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div class="parameters-area mt-3 mb-2">
|
||||||
|
<div class="parameters-header">
|
||||||
|
<div>参数列表(多个参数之间为"或"关系)</div>
|
||||||
|
<el-button type="primary" link @click="addParameter(index)"> 添加参数 </el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-empty
|
||||||
|
v-if="!condition.parameters || condition.parameters.length === 0"
|
||||||
|
description="暂无参数"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="parameters-list mt-2" v-else>
|
||||||
|
<div
|
||||||
|
v-for="(param, pIndex) in condition.parameters"
|
||||||
|
:key="pIndex"
|
||||||
|
class="parameter-item mb-2"
|
||||||
|
>
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<div class="parameter-item-header">
|
||||||
|
<span>参数 {{ pIndex + 1 }}</span>
|
||||||
|
<el-button type="danger" link @click="removeParameter(index, pIndex)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form label-width="90px" :model="param" class="mt-2">
|
||||||
|
<el-form-item label="标识符">
|
||||||
|
<el-select
|
||||||
|
v-model="param.identifier"
|
||||||
|
placeholder="请选择参数标识符"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in getParameterOptions(condition)"
|
||||||
|
:key="item.identifier"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.identifier"
|
||||||
|
>
|
||||||
|
<div class="thing-model-option">
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
<span class="thing-model-identifier">{{ item.identifier }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="thing-model-desc" v-if="item.description">{{
|
||||||
|
item.description
|
||||||
|
}}</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="条件">
|
||||||
|
<condition-selector
|
||||||
|
v-model="param.condition"
|
||||||
|
:placeholder="'请选择条件'"
|
||||||
|
:value-placeholder="'请输入比较值'"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineEmits, defineProps, onMounted, ref, watch } from 'vue'
|
||||||
|
import {
|
||||||
|
IotDeviceMessageIdentifierEnum,
|
||||||
|
IotDeviceMessageTypeEnum,
|
||||||
|
IotRuleSceneTriggerCondition,
|
||||||
|
IotRuleSceneTriggerConditionParameter
|
||||||
|
} from '@/api/iot/rule/scene/scene.types'
|
||||||
|
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
||||||
|
import ConditionSelector from './ConditionSelector.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array as () => IotRuleSceneTriggerCondition[],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
productKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// 物模型数据
|
||||||
|
const thingModelList = ref<ThingModelData[]>([])
|
||||||
|
const thingModelLoading = ref(false)
|
||||||
|
|
||||||
|
// 加载物模型数据
|
||||||
|
const loadThingModelData = async () => {
|
||||||
|
if (!props.productKey) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
thingModelLoading.value = true
|
||||||
|
const result = await ThingModelApi.getThingModelListByProductId({
|
||||||
|
productKey: props.productKey
|
||||||
|
})
|
||||||
|
thingModelList.value = result || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取物模型数据失败', error)
|
||||||
|
} finally {
|
||||||
|
thingModelLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取物模型选项
|
||||||
|
const getThingModelOptions = (type: string) => {
|
||||||
|
if (!thingModelList.value) return []
|
||||||
|
|
||||||
|
return thingModelList.value.filter((item) => {
|
||||||
|
if (type === IotDeviceMessageTypeEnum.PROPERTY) {
|
||||||
|
return item.property
|
||||||
|
} else if (type === IotDeviceMessageTypeEnum.EVENT) {
|
||||||
|
return item.event
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数选项
|
||||||
|
const getParameterOptions = (condition: IotRuleSceneTriggerCondition) => {
|
||||||
|
if (!condition || !condition.identifier) return []
|
||||||
|
|
||||||
|
const model = thingModelList.value?.find((item) => item.identifier === condition.identifier)
|
||||||
|
if (!model) return []
|
||||||
|
|
||||||
|
if (condition.type === IotDeviceMessageTypeEnum.PROPERTY) {
|
||||||
|
return [model] // 属性本身就是参数
|
||||||
|
} else if (condition.type === IotDeviceMessageTypeEnum.EVENT) {
|
||||||
|
// TODO: 获取事件的输出参数列表
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加条件
|
||||||
|
const addCondition = () => {
|
||||||
|
const newCondition: IotRuleSceneTriggerCondition = {
|
||||||
|
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||||
|
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_REPORT,
|
||||||
|
parameters: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = [...(props.modelValue || []), newCondition]
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除条件
|
||||||
|
const removeCondition = (index: number) => {
|
||||||
|
const newValue = [...props.modelValue]
|
||||||
|
newValue.splice(index, 1)
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息类型变更
|
||||||
|
const handleMessageTypeChange = (index: number) => {
|
||||||
|
const newValue = [...props.modelValue]
|
||||||
|
// 更新标识符
|
||||||
|
if (newValue[index].type === IotDeviceMessageTypeEnum.PROPERTY) {
|
||||||
|
newValue[index].identifier = IotDeviceMessageIdentifierEnum.PROPERTY_REPORT
|
||||||
|
} else if (newValue[index].type === IotDeviceMessageTypeEnum.EVENT) {
|
||||||
|
newValue[index].identifier = IotDeviceMessageIdentifierEnum.EVENT_REPORT
|
||||||
|
}
|
||||||
|
// 清空参数
|
||||||
|
newValue[index].parameters = []
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标识符变更
|
||||||
|
const handleIdentifierChange = (index: number) => {
|
||||||
|
const newValue = [...props.modelValue]
|
||||||
|
// 清空参数
|
||||||
|
newValue[index].parameters = []
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加参数
|
||||||
|
const addParameter = (conditionIndex: number) => {
|
||||||
|
const newValue = [...props.modelValue]
|
||||||
|
if (!newValue[conditionIndex].parameters) {
|
||||||
|
newValue[conditionIndex].parameters = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const newParameter: IotRuleSceneTriggerConditionParameter = {
|
||||||
|
identifier: '',
|
||||||
|
condition: {
|
||||||
|
operator: 'eq',
|
||||||
|
value: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newValue[conditionIndex].parameters.push(newParameter)
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除参数
|
||||||
|
const removeParameter = (conditionIndex: number, paramIndex: number) => {
|
||||||
|
const newValue = [...props.modelValue]
|
||||||
|
newValue[conditionIndex].parameters.splice(paramIndex, 1)
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 productKey 变化
|
||||||
|
watch(
|
||||||
|
() => props.productKey,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
// 清空条件
|
||||||
|
if (props.modelValue?.length > 0) {
|
||||||
|
emit('update:modelValue', [])
|
||||||
|
}
|
||||||
|
// 清空物模型数据
|
||||||
|
thingModelList.value = []
|
||||||
|
} else {
|
||||||
|
// 加载物模型数据
|
||||||
|
loadThingModelData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.productKey) {
|
||||||
|
loadThingModelData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.trigger-conditions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conditions-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conditions-tips {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-item {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parameters-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parameter-item-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-tips {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thing-model-option {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thing-model-identifier {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thing-model-desc {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-2 {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-5px {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue