feat:【IoT 物联网】场景联动触发器优化对齐后端
parent
677b0d61ca
commit
508de312b3
|
|
@ -4,13 +4,18 @@
|
|||
|
||||
// 枚举定义
|
||||
const IotRuleSceneTriggerTypeEnum = {
|
||||
DEVICE: 1, // 设备触发
|
||||
TIMER: 2 // 定时触发
|
||||
DEVICE_STATE_UPDATE: 1, // 设备上下线变更
|
||||
DEVICE_PROPERTY_POST: 2, // 物模型属性上报
|
||||
DEVICE_EVENT_POST: 3, // 设备事件上报
|
||||
DEVICE_SERVICE_INVOKE: 4, // 设备服务调用
|
||||
TIMER: 100 // 定时触发
|
||||
} as const
|
||||
|
||||
const IotRuleSceneActionTypeEnum = {
|
||||
DEVICE_CONTROL: 1, // 设备执行
|
||||
ALERT: 2 // 告警执行
|
||||
DEVICE_PROPERTY_SET: 1, // 设备属性设置,
|
||||
DEVICE_SERVICE_INVOKE: 2, // 设备服务调用
|
||||
ALERT_TRIGGER: 100, // 告警触发
|
||||
ALERT_RECOVER: 101 // 告警恢复
|
||||
} as const
|
||||
|
||||
const IotDeviceMessageTypeEnum = {
|
||||
|
|
|
|||
|
|
@ -33,17 +33,36 @@
|
|||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-divider content-position="left">触发器配置</el-divider>
|
||||
<device-listener
|
||||
v-for="(trigger, index) in formData.triggers"
|
||||
:key="trigger.key"
|
||||
:model-value="trigger"
|
||||
@update:model-value="(val) => (formData.triggers[index] = val)"
|
||||
class="mb-10px"
|
||||
>
|
||||
<el-button type="danger" round size="small" @click="removeTrigger(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</device-listener>
|
||||
<!-- 根据触发类型选择不同的监听器组件 -->
|
||||
<template v-for="(trigger, index) in formData.triggers" :key="trigger.key">
|
||||
<!-- 设备状态变更和定时触发使用简化的监听器 -->
|
||||
<device-state-listener
|
||||
v-if="
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE ||
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
|
||||
"
|
||||
:model-value="trigger"
|
||||
@update:model-value="(val) => (formData.triggers[index] = val)"
|
||||
class="mb-10px"
|
||||
>
|
||||
<el-button type="danger" round size="small" @click="removeTrigger(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</device-state-listener>
|
||||
|
||||
<!-- 其他设备触发类型使用完整的监听器 -->
|
||||
<device-listener
|
||||
v-else
|
||||
:model-value="trigger"
|
||||
@update:model-value="(val) => (formData.triggers[index] = val)"
|
||||
class="mb-10px"
|
||||
>
|
||||
<el-button type="danger" round size="small" @click="removeTrigger(index)">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</device-listener>
|
||||
</template>
|
||||
|
||||
<el-button class="ml-10px!" type="primary" size="small" @click="addTrigger">
|
||||
添加触发器
|
||||
</el-button>
|
||||
|
|
@ -77,6 +96,7 @@
|
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { RuleSceneApi } from '@/api/iot/rule/scene'
|
||||
import DeviceListener from './components/listener/DeviceListener.vue'
|
||||
import DeviceStateListener from './components/listener/DeviceStateListener.vue'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import {
|
||||
ActionConfig,
|
||||
|
|
@ -117,7 +137,7 @@ const formRef = ref() // 表单 Ref
|
|||
const addTrigger = () => {
|
||||
formData.value.triggers.push({
|
||||
key: generateUUID(), // 解决组件索引重用
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE,
|
||||
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, // 默认为物模型属性上报
|
||||
productKey: '',
|
||||
deviceNames: [],
|
||||
conditions: [
|
||||
|
|
@ -138,7 +158,7 @@ const removeTrigger = (index: number) => {
|
|||
const addAction = () => {
|
||||
formData.value.actions.push({
|
||||
key: generateUUID(), // 解决组件索引重用
|
||||
type: IotRuleSceneActionTypeEnum.DEVICE_CONTROL
|
||||
type: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET
|
||||
} as ActionConfig)
|
||||
}
|
||||
/** 移除执行器 */
|
||||
|
|
|
|||
|
|
@ -18,19 +18,13 @@
|
|||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<div v-if="isDeviceTrigger" class="flex items-center mr-60px">
|
||||
<span class="mr-10px">产品</span>
|
||||
<el-button type="primary" @click="productTableSelectRef?.open()" size="small" plain>
|
||||
{{ product ? product.name : '选择产品' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
class="flex items-center mr-60px"
|
||||
>
|
||||
<div v-if="isDeviceTrigger" class="flex items-center mr-60px">
|
||||
<span class="mr-10px">设备</span>
|
||||
<el-button type="primary" @click="openDeviceSelect" size="small" plain>
|
||||
{{ isEmpty(deviceList) ? '选择设备' : triggerConfig.deviceNames.join(',') }}
|
||||
|
|
@ -44,8 +38,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 设备触发器条件 -->
|
||||
<template v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE">
|
||||
<template v-if="isDeviceTrigger">
|
||||
<!-- 设备上下线变更 - 无需额外配置 -->
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE"
|
||||
class="bg-[#dbe5f6] flex items-center justify-center p-10px"
|
||||
>
|
||||
<span class="text-gray-600">设备上下线状态变更时触发,无需额外配置</span>
|
||||
</div>
|
||||
|
||||
<!-- 物模型属性上报、设备事件上报、设备服务调用 - 需要配置条件 -->
|
||||
<div
|
||||
v-else
|
||||
class="bg-[#dbe5f6] flex p-10px"
|
||||
v-for="(condition, index) in triggerConfig.conditions"
|
||||
:key="index"
|
||||
|
|
@ -57,10 +61,23 @@
|
|||
class="!w-160px"
|
||||
clearable
|
||||
placeholder=""
|
||||
:disabled="isConditionTypeFixed"
|
||||
>
|
||||
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
|
||||
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
|
||||
<el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" />
|
||||
<el-option
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST"
|
||||
label="属性"
|
||||
:value="IotDeviceMessageTypeEnum.PROPERTY"
|
||||
/>
|
||||
<el-option
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST"
|
||||
label="事件"
|
||||
:value="IotDeviceMessageTypeEnum.EVENT"
|
||||
/>
|
||||
<el-option
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE"
|
||||
label="服务"
|
||||
:value="IotDeviceMessageTypeEnum.SERVICE"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="w-70%">
|
||||
|
|
@ -118,9 +135,11 @@
|
|||
<span class="w-120px">CRON 表达式</span>
|
||||
<crontab v-model="triggerConfig.cronExpression" />
|
||||
</div>
|
||||
<!-- 设备触发才可以设置多个触发条件 -->
|
||||
<!-- 除了设备上下线变更,其他设备触发类型都可以设置多个触发条件 -->
|
||||
<el-text
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
|
||||
v-if="
|
||||
isDeviceTrigger && triggerConfig.type !== IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE
|
||||
"
|
||||
class="ml-10px!"
|
||||
type="primary"
|
||||
@click="addCondition"
|
||||
|
|
@ -158,6 +177,7 @@ import {
|
|||
TriggerConditionParameter,
|
||||
TriggerConfig
|
||||
} from '@/api/iot/rule/scene/scene.types'
|
||||
import { Crontab } from '@/components/Crontab'
|
||||
|
||||
/** 场景联动之监听器组件 */
|
||||
defineOptions({ name: 'DeviceListener' })
|
||||
|
|
@ -168,10 +188,40 @@ const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<TriggerConfig
|
|||
|
||||
const message = useMessage()
|
||||
|
||||
/** 计算属性:判断是否为设备触发类型 */
|
||||
const isDeviceTrigger = computed(() => {
|
||||
return [
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
|
||||
IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
].includes(triggerConfig.value.type as any)
|
||||
})
|
||||
|
||||
/** 计算属性:判断条件类型是否固定(根据触发类型自动确定) */
|
||||
const isConditionTypeFixed = computed(() => {
|
||||
return triggerConfig.value.type !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
})
|
||||
|
||||
/** 添加触发条件 */
|
||||
const addCondition = () => {
|
||||
// 根据触发类型设置默认的条件类型
|
||||
let defaultConditionType: string = IotDeviceMessageTypeEnum.PROPERTY
|
||||
|
||||
switch (triggerConfig.value.type) {
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
|
||||
defaultConditionType = IotDeviceMessageTypeEnum.PROPERTY
|
||||
break
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
|
||||
defaultConditionType = IotDeviceMessageTypeEnum.EVENT
|
||||
break
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
|
||||
defaultConditionType = IotDeviceMessageTypeEnum.SERVICE
|
||||
break
|
||||
}
|
||||
|
||||
triggerConfig.value.conditions?.push({
|
||||
type: IotDeviceMessageTypeEnum.PROPERTY,
|
||||
type: defaultConditionType,
|
||||
identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
|
||||
parameters: []
|
||||
})
|
||||
|
|
@ -290,12 +340,48 @@ const getThingModelTSL = async () => {
|
|||
thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(product.value.id)
|
||||
}
|
||||
|
||||
/** 监听触发类型变化,自动设置条件类型 */
|
||||
watch(
|
||||
() => triggerConfig.value.type,
|
||||
(newType) => {
|
||||
if (!newType || newType === IotRuleSceneTriggerTypeEnum.TIMER) {
|
||||
return
|
||||
}
|
||||
|
||||
// 设备上下线变更不需要条件配置
|
||||
if (newType === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
|
||||
triggerConfig.value.conditions = []
|
||||
return
|
||||
}
|
||||
|
||||
// 为其他设备触发类型设置默认条件
|
||||
if (triggerConfig.value.conditions && triggerConfig.value.conditions.length > 0) {
|
||||
triggerConfig.value.conditions.forEach((condition) => {
|
||||
switch (newType) {
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
|
||||
condition.type = IotDeviceMessageTypeEnum.PROPERTY
|
||||
break
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
|
||||
condition.type = IotDeviceMessageTypeEnum.EVENT
|
||||
break
|
||||
case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
|
||||
condition.type = IotDeviceMessageTypeEnum.SERVICE
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 初始化产品和设备回显
|
||||
if (triggerConfig.value) {
|
||||
// 初始化conditions数组,如果不存在
|
||||
if (!triggerConfig.value.conditions) {
|
||||
// 初始化conditions数组,如果不存在且不是设备上下线变更类型
|
||||
if (
|
||||
!triggerConfig.value.conditions &&
|
||||
triggerConfig.value.type !== IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE
|
||||
) {
|
||||
triggerConfig.value.conditions = []
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="m-10px">
|
||||
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">触发条件</span>
|
||||
<el-select
|
||||
v-model="triggerConfig.type"
|
||||
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" @click="productTableSelectRef?.open()" size="small" plain>
|
||||
{{ product ? product.name : '选择产品' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="flex items-center mr-60px">
|
||||
<span class="mr-10px">设备</span>
|
||||
<el-button type="primary" @click="openDeviceSelect" size="small" plain>
|
||||
{{ isEmpty(deviceList) ? '选择设备' : triggerConfig.deviceNames.join(',') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 删除触发器 -->
|
||||
<div class="absolute top-auto right-16px bottom-auto">
|
||||
<el-tooltip content="删除触发器" placement="top">
|
||||
<slot></slot>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备状态变更说明 -->
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE"
|
||||
class="bg-[#dbe5f6] flex items-center justify-center p-10px"
|
||||
>
|
||||
<el-icon class="mr-5px text-blue-500"><Icon icon="ep:info-filled" /></el-icon>
|
||||
<span class="text-gray-600">当选中的设备上线或下线时触发场景联动</span>
|
||||
</div>
|
||||
|
||||
<!-- 定时触发 -->
|
||||
<div
|
||||
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.TIMER"
|
||||
class="bg-[#dbe5f6] flex items-center justify-between p-10px"
|
||||
>
|
||||
<span class="w-120px">CRON 表达式</span>
|
||||
<crontab v-model="triggerConfig.cronExpression" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 产品、设备的选择 -->
|
||||
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
|
||||
<DeviceTableSelect
|
||||
ref="deviceTableSelectRef"
|
||||
multiple
|
||||
:product-id="product?.id"
|
||||
@success="handleDeviceSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import ProductTableSelect from '@/views/iot/product/product/components/ProductTableSelect.vue'
|
||||
import DeviceTableSelect from '@/views/iot/device/device/components/DeviceTableSelect.vue'
|
||||
import { ProductApi, ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
|
||||
import { IotRuleSceneTriggerTypeEnum, TriggerConfig } from '@/api/iot/rule/scene/scene.types'
|
||||
import { Crontab } from '@/components/Crontab'
|
||||
|
||||
/** 设备状态监听器组件 */
|
||||
defineOptions({ name: 'DeviceStateListener' })
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<TriggerConfig>
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
/** 产品和设备选择引用 */
|
||||
const productTableSelectRef = ref<InstanceType<typeof ProductTableSelect>>()
|
||||
const deviceTableSelectRef = ref<InstanceType<typeof DeviceTableSelect>>()
|
||||
const product = ref<ProductVO>()
|
||||
const deviceList = ref<DeviceVO[]>([])
|
||||
|
||||
/** 处理产品选择 */
|
||||
const handleProductSelect = (val: ProductVO) => {
|
||||
product.value = val
|
||||
triggerConfig.value.productKey = val.productKey
|
||||
deviceList.value = []
|
||||
}
|
||||
|
||||
/** 处理设备选择 */
|
||||
const handleDeviceSelect = (val: DeviceVO[]) => {
|
||||
deviceList.value = val
|
||||
triggerConfig.value.deviceNames = val.map((item) => item.deviceName)
|
||||
}
|
||||
|
||||
/** 打开设备选择器 */
|
||||
const openDeviceSelect = () => {
|
||||
if (!product.value) {
|
||||
message.warning('请先选择一个产品')
|
||||
return
|
||||
}
|
||||
deviceTableSelectRef.value?.open()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化产品回显信息
|
||||
*/
|
||||
const initProductInfo = async () => {
|
||||
if (!triggerConfig.value.productKey) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const productData = await ProductApi.getProductByKey(triggerConfig.value.productKey)
|
||||
if (productData) {
|
||||
product.value = productData
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取产品信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设备回显信息
|
||||
*/
|
||||
const initDeviceInfo = async () => {
|
||||
if (!triggerConfig.value.productKey || !triggerConfig.value.deviceNames?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const deviceData = await DeviceApi.getDevicesByProductKeyAndNames(
|
||||
triggerConfig.value.productKey,
|
||||
triggerConfig.value.deviceNames
|
||||
)
|
||||
|
||||
if (deviceData && deviceData.length > 0) {
|
||||
deviceList.value = deviceData
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
if (triggerConfig.value) {
|
||||
await initProductInfo()
|
||||
await initDeviceInfo()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Loading…
Reference in New Issue