【功能完善】IoT: 场景联动

This commit is contained in:
puhui999 2025-03-29 20:27:38 +08:00
parent 4eb7188ecf
commit f46540759f
7 changed files with 250 additions and 238 deletions

View File

@ -58,7 +58,8 @@ interface TenantBaseDO {
// 触发条件参数 // 触发条件参数
interface TriggerConditionParameter { interface TriggerConditionParameter {
identifier: string // 标识符(属性、事件、服务) identifier0: string // 标识符(事件、服务)
identifier: string // 标识符(属性)
operator: string // 操作符 operator: string // 操作符
value: string // 比较值 value: string // 比较值
} }

View File

@ -117,7 +117,7 @@
<template #default="scope"> <template #default="scope">
<el-radio <el-radio
v-model="selectedId" v-model="selectedId"
:label="scope.row.id" :value="scope.row.id"
@change="() => handleRadioChange(scope.row)" @change="() => handleRadioChange(scope.row)"
> >
&nbsp; &nbsp;

View File

@ -57,7 +57,7 @@
<template #default="scope"> <template #default="scope">
<el-radio <el-radio
v-model="selectedId" v-model="selectedId"
:label="scope.row.id" :value="scope.row.id"
@change="() => handleRadioChange(scope.row)" @change="() => handleRadioChange(scope.row)"
> >
&nbsp; &nbsp;

View File

@ -19,7 +19,7 @@
<el-radio <el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :key="dict.value"
:label="dict.value" :value="dict.value"
> >
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>

View File

@ -1,81 +1,83 @@
<template> <template>
<div class="m-10px"> <div>
<!-- 产品设备回显区域 --> <div class="m-10px">
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px"> <!-- 产品设备回显区域 -->
<div class="flex items-center mr-60px"> <div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
<span class="mr-10px">执行动作</span> <div class="flex items-center mr-60px">
<el-select <span class="mr-10px">执行动作</span>
v-model="actionConfig.type" <el-select
class="!w-240px" v-model="actionConfig.type"
clearable class="!w-240px"
placeholder="请选择执行类型" clearable
placeholder="请选择执行类型"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_ACTION_TYPE_ENUM)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</div>
<div
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL"
class="flex items-center mr-60px"
> >
<el-option <span class="mr-10px">产品</span>
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_ACTION_TYPE_ENUM)" <el-button type="primary" @click="handleSelectProduct" size="small" plain>
:key="dict.value" {{ product ? product.name : '选择产品' }}
:label="dict.label" </el-button>
:value="dict.value" </div>
/> <div
</el-select> v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL"
class="flex items-center mr-60px"
>
<span class="mr-10px">设备</span>
<el-button type="primary" @click="handleSelectDevice" size="small" plain>
{{ isEmpty(deviceList) ? '选择设备' : deviceList.map((d) => d.deviceName).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>
<div
<!-- 设备控制执行器 -->
<DeviceControlAction
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL" v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL"
class="flex items-center mr-60px" :model-value="actionConfig.deviceControl"
> :product-id="product?.id"
<span class="mr-10px">产品</span> :product-key="product?.productKey"
<el-button type="primary" @click="handleSelectProduct" size="small" plain> @update:model-value="(val) => (actionConfig.deviceControl = val)"
{{ product ? product.name : '选择产品' }} />
</el-button>
</div> <!-- 告警执行器 -->
<div <AlertAction
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL" v-else-if="actionConfig.type === IotRuleSceneActionTypeEnum.ALERT"
class="flex items-center mr-60px" :model-value="actionConfig.alert"
> @update:model-value="(val) => (actionConfig.alert = val)"
<span class="mr-10px">设备</span> />
<el-button type="primary" @click="handleSelectDevice" size="small" plain>
{{ isEmpty(deviceList) ? '选择设备' : deviceList.map((d) => d.deviceName).join(',') }} <!-- 数据桥接执行器 -->
</el-button> <DataBridgeAction
</div> v-else-if="actionConfig.type === IotRuleSceneActionTypeEnum.DATA_BRIDGE"
<!-- 删除执行器 --> :model-value="actionConfig.dataBridgeId"
<div class="absolute top-auto right-16px bottom-auto"> @update:model-value="(val) => (actionConfig.dataBridgeId = val)"
<el-tooltip content="删除执行器" placement="top"> />
<slot></slot>
</el-tooltip>
</div>
</div> </div>
<!-- 设备控制执行器 --> <!-- 产品设备的选择 -->
<DeviceControlAction <ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
v-if="actionConfig.type === IotRuleSceneActionTypeEnum.DEVICE_CONTROL" <DeviceTableSelect
:model-value="actionConfig.deviceControl" ref="deviceTableSelectRef"
multiple
:product-id="product?.id" :product-id="product?.id"
:product-key="product?.productKey" @success="handleDeviceSelect"
@update:model-value="(val) => (actionConfig.deviceControl = val)"
/>
<!-- 告警执行器 -->
<AlertAction
v-else-if="actionConfig.type === IotRuleSceneActionTypeEnum.ALERT"
:model-value="actionConfig.alert"
@update:model-value="(val) => (actionConfig.alert = val)"
/>
<!-- 数据桥接执行器 -->
<DataBridgeAction
v-else-if="actionConfig.type === IotRuleSceneActionTypeEnum.DATA_BRIDGE"
:model-value="actionConfig.dataBridgeId"
@update:model-value="(val) => (actionConfig.dataBridgeId = val)"
/> />
</div> </div>
<!-- 产品设备的选择 -->
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
<DeviceTableSelect
ref="deviceTableSelectRef"
multiple
:product-id="product?.id"
@success="handleDeviceSelect"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,140 +1,143 @@
<template> <template>
<div class="m-10px"> <div>
<div class="relative bg-[#eff3f7] h-50px flex items-center px-10px"> <div class="m-10px">
<div class="flex items-center mr-60px"> <div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
<span class="mr-10px">触发条件</span> <div class="flex items-center mr-60px">
<el-select <span class="mr-10px">触发条件</span>
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
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
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"
>
<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>
<!-- 设备触发器条件 -->
<template v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE">
<div
class="bg-[#dbe5f6] flex p-10px"
v-for="(condition, index) in triggerConfig.conditions"
:key="index"
>
<div class="flex flex-col items-center justify-center mr-10px h-a">
<el-select <el-select
v-model="condition.type" v-model="triggerConfig.type"
@change="condition.parameters = []" class="!w-240px"
class="!w-160px"
clearable clearable
placeholder="" placeholder="请选择触发条件"
> >
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" /> <el-option
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" /> v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_TRIGGER_TYPE_ENUM)"
<el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" /> :key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</div> </div>
<div class=""> <div
<DeviceListenerCondition v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
v-for="(parameter, index2) in condition.parameters" class="flex items-center mr-60px"
:key="index2" >
:model-value="parameter" <span class="mr-10px">产品</span>
:thingModels="thingModels(condition)" <el-button type="primary" @click="productTableSelectRef?.open()" size="small" plain>
@update:model-value="(val) => (condition.parameters[index2] = val)" {{ product ? product.name : '选择产品' }}
class="mb-10px last:mb-0" </el-button>
> </div>
<el-tooltip content="删除参数" placement="top"> <div
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
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>
<!-- 设备触发器条件 -->
<template v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE">
<div
class="bg-[#dbe5f6] flex p-10px"
v-for="(condition, index) in triggerConfig.conditions"
:key="index"
>
<div class="flex flex-col items-center justify-center mr-10px h-a">
<el-select
v-model="condition.type"
@change="condition.parameters = []"
class="!w-160px"
clearable
placeholder=""
>
<el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
<el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
<el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" />
</el-select>
</div>
<div class="w-70%">
<DeviceListenerCondition
v-for="(parameter, index2) in condition.parameters"
:key="index2"
:model-value="parameter"
:condition-type="condition.type"
:thingModels="thingModels(condition)"
@update:model-value="(val) => (condition.parameters[index2] = val)"
class="mb-10px last:mb-0"
>
<el-tooltip content="删除参数" placement="top">
<el-button
type="danger"
circle
size="small"
@click="removeConditionParameter(condition.parameters, index2)"
>
<Icon icon="ep:delete" />
</el-button>
</el-tooltip>
</DeviceListenerCondition>
</div>
<!-- 添加参数 -->
<div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
<el-tooltip content="添加参数" placement="top">
<el-button <el-button
type="danger" type="primary"
circle circle
size="small" size="small"
@click="removeConditionParameter(condition.parameters, index2)" @click="addConditionParameter(condition.parameters)"
> >
<Icon icon="ep:plus" />
</el-button>
</el-tooltip>
</div>
<!-- 删除条件 -->
<div
class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
>
<el-tooltip content="删除条件" placement="top">
<el-button type="danger" size="small" @click="removeCondition(index)">
<Icon icon="ep:delete" /> <Icon icon="ep:delete" />
</el-button> </el-button>
</el-tooltip> </el-tooltip>
</DeviceListenerCondition> </div>
</div>
<!-- 添加参数 -->
<div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
<el-tooltip content="添加参数" placement="top">
<el-button
type="primary"
circle
size="small"
@click="addConditionParameter(condition.parameters)"
>
<Icon icon="ep:plus" />
</el-button>
</el-tooltip>
</div>
<!-- 删除条件 -->
<div
class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
>
<el-tooltip content="删除条件" placement="top">
<el-button type="danger" size="small" @click="removeCondition(index)">
<Icon icon="ep:delete" />
</el-button>
</el-tooltip>
</div> </div>
</template>
<!-- 定时触发 -->
<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>
</template> <!-- 设备触发才可以设置多个触发条件 -->
<!-- 定时触发 --> <el-text
<div v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.TIMER" class="ml-10px!"
class="bg-[#dbe5f6] flex items-center justify-between p-10px" type="primary"
> @click="addCondition"
<span class="w-120px">CRON 表达式</span> >
<crontab v-model="triggerConfig.cronExpression" /> 添加触发条件
</el-text>
</div> </div>
<!-- 设备触发才可以设置多个触发条件 -->
<el-text
v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
class="ml-10px!"
type="primary"
@click="addCondition"
>
添加触发条件
</el-text>
</div>
<!-- 产品设备的选择 --> <!-- 产品设备的选择 -->
<ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" /> <ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
<DeviceTableSelect <DeviceTableSelect
ref="deviceTableSelectRef" ref="deviceTableSelectRef"
multiple multiple
:product-id="product?.id" :product-id="product?.id"
@success="handleDeviceSelect" @success="handleDeviceSelect"
/> />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -272,12 +275,11 @@ const thingModels = computed(() => (condition: TriggerCondition) => {
} }
switch (condition.type) { switch (condition.type) {
case IotDeviceMessageTypeEnum.PROPERTY: case IotDeviceMessageTypeEnum.PROPERTY:
return thingModelTSL.value.properties return thingModelTSL.value?.properties || []
// TODO puhui999:
case IotDeviceMessageTypeEnum.SERVICE: case IotDeviceMessageTypeEnum.SERVICE:
return thingModelTSL.value.services return thingModelTSL.value?.services || []
case IotDeviceMessageTypeEnum.EVENT: case IotDeviceMessageTypeEnum.EVENT:
return thingModelTSL.value.events return thingModelTSL.value?.events || []
} }
return [] return []
}) })

View File

@ -1,10 +1,14 @@
<template> <template>
<div class="device-listener-condition"> <div class="flex items-center w-1/1">
<!-- 选择服务 -->
<el-select <el-select
v-model="conditionParameter.identifier" v-if="
class="!w-240px mr-10px" [IotDeviceMessageTypeEnum.SERVICE, IotDeviceMessageTypeEnum.EVENT].includes(conditionType)
"
v-model="conditionParameter.identifier0"
class="!w-150px mr-10px"
clearable clearable
placeholder="请选择物模型" placeholder="请选择服务"
> >
<el-option <el-option
v-for="thingModel in thingModels" v-for="thingModel in thingModels"
@ -13,23 +17,33 @@
:value="thingModel.identifier" :value="thingModel.identifier"
/> />
</el-select> </el-select>
<el-select
v-model="conditionParameter.identifier"
class="!w-150px mr-10px"
clearable
placeholder="请选择物模型"
>
<el-option
v-for="thingModel in getThingModels"
:key="thingModel.identifier"
:label="thingModel.name"
:value="thingModel.identifier"
/>
</el-select>
<ConditionSelector <ConditionSelector
v-model="conditionParameter.operator" v-model="conditionParameter.operator"
:data-type="getDataType" :data-type="model?.dataType"
class="!w-180px mr-10px" class="!w-150px mr-10px"
/> />
<!-- TODO puhui999: 输入值范围校验 --> <ThingModelParamInput
<el-input
v-if=" v-if="
conditionParameter.operator !== conditionParameter.operator !==
IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value
" "
class="!w-200px mr-10px"
v-model="conditionParameter.value" v-model="conditionParameter.value"
class="!w-240px mr-10px" :thing-model="model"
placeholder="请输入值" />
>
<template v-if="getUnitName" #append> {{ getUnitName }} </template>
</el-input>
<!-- 按钮插槽 --> <!-- 按钮插槽 -->
<slot></slot> <slot></slot>
</div> </div>
@ -38,43 +52,36 @@
<script setup lang="ts"> <script setup lang="ts">
import ConditionSelector from './ConditionSelector.vue' import ConditionSelector from './ConditionSelector.vue'
import { import {
IotDeviceMessageTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum, IotRuleSceneTriggerConditionParameterOperatorEnum,
TriggerConditionParameter TriggerConditionParameter
} from '@/api/iot/rule/scene/scene.types' } from '@/api/iot/rule/scene/scene.types'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import ThingModelParamInput from '@/views/iot/rule/scene/components/ThingModelParamInput.vue'
/** 设备触发条件 */
defineOptions({ name: 'DeviceListenerCondition' }) defineOptions({ name: 'DeviceListenerCondition' })
const props = defineProps<{ modelValue: any; thingModels: any }>() const props = defineProps<{ modelValue: any; conditionType: any; thingModels: any }>()
const emits = defineEmits(['update:modelValue']) const emits = defineEmits(['update:modelValue'])
const conditionParameter = useVModel(props, 'modelValue', emits) as Ref<TriggerConditionParameter> const conditionParameter = useVModel(props, 'modelValue', emits) as Ref<TriggerConditionParameter>
/** 获得物模型属性类型 */ /** 属性就是 thingModels服务和事件取对应的 outputParams */
const getDataType = computed(() => { const getThingModels = computed(() => {
const model = props.thingModels?.find( switch (props.conditionType) {
(item: any) => item.identifier === conditionParameter.value.identifier case IotDeviceMessageTypeEnum.PROPERTY:
) return props.thingModels || []
// case IotDeviceMessageTypeEnum.SERVICE:
if (model?.dataSpecs) { case IotDeviceMessageTypeEnum.EVENT:
return model.dataSpecs.dataType return (
props.thingModels.find(
(item: any) => item.identifier === conditionParameter.value.identifier0
)?.outputParams || []
)
} }
return ''
}) })
/** 获得属性单位 */
const getUnitName = computed(() => {
const model = props.thingModels?.find(
(item: any) => item.identifier === conditionParameter.value.identifier
)
//
if (model?.dataSpecs) {
return model.dataSpecs.unitName
}
// TODO puhui999:
//
// if (model?.outputParams) {
// return model.dataSpecs.unitName
// }
return ''
})
</script>
<style scoped lang="scss"></style> /** 获得物模型属性、类型 */
const model = computed(() =>
getThingModels.value.find((item: any) => item.identifier === conditionParameter.value.identifier)
)
</script>