【功能新增】IoT: 规则场景监听器相关组件
This commit is contained in:
@ -2,36 +2,36 @@
|
||||
* 场景规则触发器配置
|
||||
*/
|
||||
export interface IotRuleSceneTriggerConfig {
|
||||
/**
|
||||
/**
|
||||
* 触发类型
|
||||
* - 1: 设备触发
|
||||
* - 2: 定时触发
|
||||
*/
|
||||
type: number;
|
||||
type: number
|
||||
/** 产品标识 */
|
||||
productKey?: string;
|
||||
productKey?: string
|
||||
/** 设备名称数组 */
|
||||
deviceNames?: string[];
|
||||
deviceNames?: string[]
|
||||
/** 触发条件数组。条件之间是"或"的关系 */
|
||||
conditions?: IotRuleSceneTriggerCondition[];
|
||||
conditions?: IotRuleSceneTriggerCondition[]
|
||||
/** CRON 表达式。当 type = 2 时必填 */
|
||||
cronExpression?: string;
|
||||
cronExpression?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发条件
|
||||
*/
|
||||
export interface IotRuleSceneTriggerCondition {
|
||||
/**
|
||||
/**
|
||||
* 消息类型
|
||||
* - property: 属性上报
|
||||
* - event: 事件上报
|
||||
*/
|
||||
type: string;
|
||||
type: string
|
||||
/** 消息标识符 */
|
||||
identifier: string;
|
||||
identifier: string
|
||||
/** 参数数组。参数之间是"或"的关系 */
|
||||
parameters: IotRuleSceneTriggerConditionParameter[];
|
||||
parameters: IotRuleSceneTriggerConditionParameter[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,39 +39,32 @@ export interface IotRuleSceneTriggerCondition {
|
||||
*/
|
||||
export interface IotRuleSceneTriggerConditionParameter {
|
||||
/** 标识符(属性、事件、服务) */
|
||||
identifier: string;
|
||||
identifier: string
|
||||
/**
|
||||
* 操作符
|
||||
* - eq: 等于
|
||||
* - gt: 大于
|
||||
* - gte: 大于等于
|
||||
* - lt: 小于
|
||||
* - lte: 小于等于
|
||||
* - between: 范围
|
||||
* - in: 在列表中
|
||||
*/
|
||||
operator: string;
|
||||
/**
|
||||
operator: string
|
||||
/**
|
||||
* 比较值
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"
|
||||
*/
|
||||
value: string;
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行器配置
|
||||
*/
|
||||
export interface IotRuleSceneActionConfig {
|
||||
/**
|
||||
/**
|
||||
* 执行类型
|
||||
* - 1: 设备控制
|
||||
* - 2: 数据桥接
|
||||
*/
|
||||
type: number;
|
||||
type: number
|
||||
/** 设备控制配置。当 type = 1 时必填 */
|
||||
deviceControl?: IotRuleSceneActionDeviceControl;
|
||||
deviceControl?: IotRuleSceneActionDeviceControl
|
||||
/** 数据桥接编号。当 type = 2 时必填 */
|
||||
dataBridgeId?: number;
|
||||
dataBridgeId?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,23 +72,23 @@ export interface IotRuleSceneActionConfig {
|
||||
*/
|
||||
export interface IotRuleSceneActionDeviceControl {
|
||||
/** 产品标识 */
|
||||
productKey: string;
|
||||
productKey: string
|
||||
/** 设备名称数组 */
|
||||
deviceNames: string[];
|
||||
/**
|
||||
deviceNames: string[]
|
||||
/**
|
||||
* 消息类型
|
||||
* - property: 属性
|
||||
* - service: 服务
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
type: string
|
||||
/**
|
||||
* 消息标识符
|
||||
* - property_set: 属性设置
|
||||
* - service_invoke: 服务调用
|
||||
*/
|
||||
identifier: string;
|
||||
identifier: string
|
||||
/** 具体数据 */
|
||||
data: Record<string, any>;
|
||||
data: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,17 +96,17 @@ export interface IotRuleSceneActionDeviceControl {
|
||||
*/
|
||||
export interface IotRuleSceneSaveReqVO {
|
||||
/** 场景规则编号 */
|
||||
id?: number;
|
||||
id?: number
|
||||
/** 场景规则名称 */
|
||||
name: string;
|
||||
name: string
|
||||
/** 场景规则状态(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 {
|
||||
/** 场景规则编号 */
|
||||
id: number;
|
||||
id: number
|
||||
/** 场景规则名称 */
|
||||
name: string;
|
||||
name: string
|
||||
/** 场景规则状态(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 {
|
||||
/** 触发次数 */
|
||||
triggerCount: number;
|
||||
triggerCount: number
|
||||
/** 最后触发时间 */
|
||||
lastTriggerTime?: Date;
|
||||
lastTriggerTime?: Date
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,15 +144,15 @@ export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
|
||||
*/
|
||||
export interface IotRuleScenePageReqVO {
|
||||
/** 场景规则名称 */
|
||||
name?: string;
|
||||
name?: string
|
||||
/** 场景规则状态(0=禁用 1=启用) */
|
||||
status?: number;
|
||||
status?: number
|
||||
/** 创建时间 */
|
||||
createTime?: [Date, Date];
|
||||
createTime?: [Date, Date]
|
||||
/** 页码 */
|
||||
pageNo?: number;
|
||||
pageNo?: number
|
||||
/** 每页条数 */
|
||||
pageSize?: number;
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,4 +219,4 @@ export enum IotRuleSceneTriggerConditionParameterOperatorEnum {
|
||||
BETWEEN = 'between',
|
||||
/** 在列表中 */
|
||||
IN = 'in'
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,5 +245,7 @@ export enum DICT_TYPE {
|
||||
IOT_PLUGIN_STATUS = 'iot_plugin_status', // IOT 插件状态
|
||||
IOT_PLUGIN_TYPE = 'iot_plugin_type', // IOT 插件类型
|
||||
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>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="70%">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
@ -7,29 +7,41 @@
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="场景名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入场景名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="场景描述" prop="description">
|
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入场景描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="场景状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="触发器数组" prop="triggers">
|
||||
<el-input v-model="formData.triggers" placeholder="请输入触发器数组" />
|
||||
</el-form-item>
|
||||
<el-form-item label="执行器数组" prop="actions">
|
||||
<el-input v-model="formData.actions" placeholder="请输入执行器数组" />
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场景名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入场景名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场景状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="场景描述" prop="description">
|
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入场景描述" />
|
||||
</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>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
@ -40,6 +52,7 @@
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
|
||||
import DeviceListener from './components/DeviceListener.vue'
|
||||
|
||||
/** IoT 规则场景(场景联动) 表单 */
|
||||
defineOptions({ name: 'RuleSceneForm' })
|
||||
|
||||
241
src/views/iot/rule/scene/components/ConditionSelector.vue
Normal file
241
src/views/iot/rule/scene/components/ConditionSelector.vue
Normal file
@ -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>
|
||||
87
src/views/iot/rule/scene/components/DeviceListener.vue
Normal file
87
src/views/iot/rule/scene/components/DeviceListener.vue
Normal file
@ -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>
|
||||
379
src/views/iot/rule/scene/components/TriggerConditions.vue
Normal file
379
src/views/iot/rule/scene/components/TriggerConditions.vue
Normal file
@ -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>
|
||||
Reference in New Issue
Block a user