perf:【IoT 物联网】场景联动优化数据结构对齐后端

This commit is contained in:
puhui999
2025-08-01 23:15:38 +08:00
parent 858f1cdb0b
commit d06835ae7f
8 changed files with 104 additions and 192 deletions

View File

@ -39,14 +39,7 @@ import { useVModel } from '@vueuse/core'
import BasicInfoSection from './sections/BasicInfoSection.vue'
import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.vue'
import {
IotRuleScene,
IotRuleSceneDO,
IotRuleSceneActionTypeEnum,
RuleSceneFormData,
TriggerFormData,
TriggerConditionFormData
} from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
import { ElMessage } from 'element-plus'
import { generateUUID } from '@/utils'
@ -64,16 +57,12 @@ defineOptions({ name: 'RuleSceneForm' })
const props = defineProps<{
/** 抽屉显示状态 */
modelValue: boolean
/** 编辑的规则数据(新增时为空) */
ruleScene?: IotRuleScene
}>()
/** 组件事件定义 */
const emit = defineEmits<{
/** 更新抽屉显示状态 */
'update:modelValue': [value: boolean]
/** 操作成功事件 */
success: []
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}>()
const drawerVisible = useVModel(props, 'modelValue', emit) // 是否可见
@ -120,13 +109,14 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleSceneDO => {
cronExpression: trigger.cronExpression,
conditionGroups: trigger.conditionGroups || []
})),
actions: formData.actions?.map((action) => ({
type: action.type,
productId: action.productId,
deviceId: action.deviceId,
params: action.params,
alertConfigId: action.alertConfigId
})) || []
actions:
formData.actions?.map((action) => ({
type: action.type,
productId: action.productId,
deviceId: action.deviceId,
params: action.params,
alertConfigId: action.alertConfigId
})) || []
}
}
@ -216,7 +206,7 @@ const triggerValidation = ref({ valid: true, message: '' })
const actionValidation = ref({ valid: true, message: '' })
// 计算属性
const isEdit = computed(() => !!props.ruleScene?.id)
const isEdit = ref(false)
const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则'))
// 事件处理
@ -284,30 +274,28 @@ const handleClose = () => {
/** 初始化表单数据 */
const initFormData = () => {
if (props.ruleScene) {
formData.value = convertVOToForm(props.ruleScene)
} else {
formData.value = createDefaultFormData()
}
// TODO @puhui999: 编辑的情况后面实现
formData.value = createDefaultFormData()
}
// 监听抽屉显示
watch(drawerVisible, (visible) => {
if (visible) {
initFormData()
nextTick(() => {
formRef.value?.clearValidate()
})
// TODO @puhui999: 重置表单的情况
// nextTick(() => {
// formRef.value?.clearValidate()
// })
}
})
// 监听 props 变化
watch(
() => props.ruleScene,
() => {
if (drawerVisible.value) {
initFormData()
}
}
)
// watch(
// () => props.ruleScene,
// () => {
// if (drawerVisible.value) {
// initFormData()
// }
// }
// )
</script>

View File

@ -122,7 +122,7 @@ import PropertySelector from '../selectors/PropertySelector.vue'
import OperatorSelector from '../selectors/OperatorSelector.vue'
import ValueInput from '../inputs/ValueInput.vue'
import {
ConditionFormData,
TriggerConditionFormData,
IotRuleSceneTriggerConditionTypeEnum
} from '@/api/iot/rule/scene/scene.types'
@ -130,12 +130,12 @@ import {
defineOptions({ name: 'ConditionConfig' })
const props = defineProps<{
modelValue: ConditionFormData
modelValue: TriggerConditionFormData
triggerType: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: ConditionFormData): void
(e: 'update:modelValue', value: TriggerConditionFormData): void
(e: 'validate', result: { valid: boolean; message: string }): void
}>()
@ -152,12 +152,12 @@ const isValid = ref(true)
const valueValidation = ref({ valid: true, message: '' })
// 事件处理
const updateConditionField = (field: keyof ConditionFormData, value: any) => {
const updateConditionField = (field: keyof TriggerConditionFormData, value: any) => {
;(condition.value as any)[field] = value
emit('update:modelValue', condition.value)
}
const updateCondition = (newCondition: ConditionFormData) => {
const updateCondition = (newCondition: TriggerConditionFormData) => {
condition.value = newCondition
emit('update:modelValue', condition.value)
}

View File

@ -14,16 +14,14 @@
<span>附加条件组</span>
</div>
<el-tag size="small" type="success">与主条件为且关系</el-tag>
<el-tag size="small" type="info">
{{ modelValue.subGroups?.length || 0 }}个子条件组
</el-tag>
<el-tag size="small" type="info"> {{ modelValue?.length || 0 }}个子条件组 </el-tag>
</div>
<div class="flex items-center gap-8px">
<el-button
type="primary"
size="small"
@click="addSubGroup"
:disabled="(modelValue.subGroups?.length || 0) >= maxSubGroups"
:disabled="(modelValue?.length || 0) >= maxSubGroups"
>
<Icon icon="ep:plus" />
添加子条件组
@ -36,11 +34,11 @@
</div>
<!-- 子条件组列表 -->
<div v-if="modelValue.subGroups && modelValue.subGroups.length > 0" class="space-y-16px">
<div v-if="modelValue && modelValue.length > 0" class="space-y-16px">
<!-- 逻辑关系说明 -->
<div class="relative">
<div
v-for="(subGroup, subGroupIndex) in modelValue.subGroups"
v-for="(subGroup, subGroupIndex) in modelValue"
:key="`sub-group-${subGroupIndex}`"
class="relative"
>
@ -88,7 +86,7 @@
<!-- 子条件组间的""连接符 -->
<div
v-if="subGroupIndex < modelValue.subGroups!.length - 1"
v-if="subGroupIndex < modelValue!.length - 1"
class="flex items-center justify-center py-12px"
>
<div class="flex items-center gap-8px">
@ -125,21 +123,17 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import SubConditionGroupConfig from './SubConditionGroupConfig.vue'
import {
ConditionGroupContainerFormData,
SubConditionGroupFormData
} from '@/api/iot/rule/scene/scene.types'
/** 条件组容器配置组件 */
defineOptions({ name: 'ConditionGroupContainerConfig' })
const props = defineProps<{
modelValue: ConditionGroupContainerFormData
modelValue: any
triggerType: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: ConditionGroupContainerFormData): void
(e: 'update:modelValue', value: any): void
(e: 'validate', result: { valid: boolean; message: string }): void
(e: 'remove'): void
}>()
@ -155,24 +149,20 @@ const subGroupValidations = ref<{ [key: number]: { valid: boolean; message: stri
// 事件处理
const addSubGroup = () => {
if (!container.value.subGroups) {
container.value.subGroups = []
if (!container.value) {
container.value = []
}
if (container.value.subGroups.length >= maxSubGroups) {
if (container.value.length >= maxSubGroups) {
return
}
const newSubGroup: SubConditionGroupFormData = {
conditions: []
}
container.value.subGroups.push(newSubGroup)
container.value.push([])
}
const removeSubGroup = (index: number) => {
if (container.value.subGroups) {
container.value.subGroups.splice(index, 1)
if (container.value) {
container.value.splice(index, 1)
delete subGroupValidations.value[index]
// 重新索引验证结果
@ -191,9 +181,9 @@ const removeSubGroup = (index: number) => {
}
}
const updateSubGroup = (index: number, subGroup: SubConditionGroupFormData) => {
if (container.value.subGroups) {
container.value.subGroups[index] = subGroup
const updateSubGroup = (index: number, subGroup: any) => {
if (container.value) {
container.value[index] = subGroup
}
}
@ -207,7 +197,7 @@ const handleSubGroupValidate = (index: number, result: { valid: boolean; message
}
const updateValidationResult = () => {
if (!container.value.subGroups || container.value.subGroups.length === 0) {
if (!container.value || container.value.length === 0) {
emit('validate', { valid: true, message: '条件组容器为空,验证通过' })
return
}
@ -225,7 +215,7 @@ const updateValidationResult = () => {
// 监听变化
watch(
() => container.value.subGroups,
() => container.value,
() => {
updateValidationResult()
},

View File

@ -84,17 +84,17 @@
import { useVModel } from '@vueuse/core'
import ProductSelector from '../selectors/ProductSelector.vue'
import DeviceSelector from '../selectors/DeviceSelector.vue'
import { ConditionFormData } from '@/api/iot/rule/scene/scene.types'
import { TriggerConditionFormData } from '@/api/iot/rule/scene/scene.types'
/** 设备状态条件配置组件 */
defineOptions({ name: 'DeviceStatusConditionConfig' })
const props = defineProps<{
modelValue: ConditionFormData
modelValue: TriggerConditionFormData
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: ConditionFormData): void
(e: 'update:modelValue', value: TriggerConditionFormData): void
(e: 'validate', result: { valid: boolean; message: string }): void
}>()
@ -139,18 +139,18 @@ const validationMessage = ref('')
const isValid = ref(true)
// 事件处理
const updateConditionField = (field: keyof ConditionFormData, value: any) => {
const updateConditionField = (field: any, value: any) => {
condition.value[field] = value
updateValidationResult()
}
const handleProductChange = (productId: number) => {
const handleProductChange = (_: number) => {
// 产品变化时清空设备
condition.value.deviceId = undefined
updateValidationResult()
}
const handleDeviceChange = (deviceId: number) => {
const handleDeviceChange = (_: number) => {
// 设备变化时可以进行其他处理
updateValidationResult()
}

View File

@ -11,48 +11,14 @@
</div>
<!-- 条件组配置 -->
<div v-if="trigger.mainCondition" class="space-y-16px">
<div v-if="!trigger.conditionGroup" class="flex items-center justify-between">
<div class="flex items-center gap-8px">
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">附加条件组</span>
<el-tag size="small" type="success">与主条件为且关系</el-tag>
<el-tag size="small" type="info">
{{ trigger.conditionGroup?.subGroups?.length || 0 }} 个子条件组
</el-tag>
</div>
<el-button
type="primary"
size="small"
@click="addConditionGroup"
v-if="!trigger.conditionGroup"
>
<Icon icon="ep:plus" />
添加条件组
</el-button>
</div>
<div class="space-y-16px">
<!-- 条件组配置 -->
<ConditionGroupContainerConfig
v-if="trigger.conditionGroup"
v-model="trigger.conditionGroup"
v-model="trigger.conditionGroups"
:trigger-type="trigger.type"
@validate="handleConditionGroupValidate"
@remove="removeConditionGroup"
/>
<!-- 空状态 -->
<div v-else class="py-40px text-center">
<el-empty description="暂无触发条件">
<template #description>
<div class="space-y-8px">
<p class="text-[var(--el-text-color-secondary)]">暂无触发条件</p>
<p class="text-12px text-[var(--el-text-color-placeholder)]">
请使用上方的"添加条件组"按钮来设置触发规则
</p>
</div>
</template>
</el-empty>
</div>
</div>
</div>
</template>
@ -70,6 +36,7 @@ defineOptions({ name: 'DeviceTriggerConfig' })
const props = defineProps<{
modelValue: TriggerFormData
index: number
}>()
const emit = defineEmits<{
@ -89,16 +56,17 @@ const isValid = ref(true)
// 初始化主条件
const initMainCondition = () => {
if (!trigger.value.mainCondition) {
trigger.value.mainCondition = {
type: trigger.value.type, // 使用触发事件类型作为条件类型
productId: undefined,
deviceId: undefined,
identifier: '',
operator: '=',
param: ''
}
}
// TODO @puhui999: 等到编辑回显时联调
// if (!trigger.value.mainCondition) {
// trigger.value = {
// type: trigger.value.type, // 使用触发事件类型作为条件类型
// productId: undefined,
// deviceId: undefined,
// identifier: '',
// operator: '=',
// param: ''
// }
// }
}
// 监听触发器类型变化,自动初始化主条件
@ -110,18 +78,16 @@ watch(
{ immediate: true }
)
// 新的事件处理函数
const handleMainConditionValidate = (result: { valid: boolean; message: string }) => {
mainConditionValidation.value = result
updateValidationResult()
}
const addConditionGroup = () => {
if (!trigger.value.conditionGroup) {
trigger.value.conditionGroup = {
subGroups: []
}
if (!trigger.value.conditionGroups) {
trigger.value.conditionGroups = []
}
trigger.value.conditionGroups.push([])
}
// 事件处理
@ -130,7 +96,7 @@ const handleConditionGroupValidate = () => {
}
const removeConditionGroup = () => {
trigger.value.conditionGroup = undefined
trigger.value.conditionGroups = undefined
}
const updateValidationResult = () => {
@ -151,7 +117,7 @@ const updateValidationResult = () => {
}
// 主条件验证
if (!trigger.value.mainCondition) {
if (!trigger.value.value) {
isValid.value = false
validationMessage.value = '请配置主条件'
emit('validate', { valid: false, message: validationMessage.value })

View File

@ -1,10 +1,7 @@
<template>
<div class="p-16px">
<!-- 空状态 -->
<div
v-if="!subGroup.conditions || subGroup.conditions.length === 0"
class="text-center py-24px"
>
<div v-if="!subGroup || subGroup.length === 0" class="text-center py-24px">
<div class="flex flex-col items-center gap-12px">
<Icon icon="ep:plus" class="text-32px text-[var(--el-text-color-placeholder)]" />
<div class="text-[var(--el-text-color-secondary)]">
@ -21,7 +18,7 @@
<!-- 条件列表 -->
<div v-else class="space-y-16px">
<div
v-for="(condition, conditionIndex) in subGroup.conditions"
v-for="(condition, conditionIndex) in subGroup"
:key="`condition-${conditionIndex}`"
class="relative"
>
@ -47,7 +44,7 @@
size="small"
text
@click="removeCondition(conditionIndex)"
v-if="subGroup.conditions!.length > 1"
v-if="subGroup!.length > 1"
class="hover:bg-red-50"
>
<Icon icon="ep:delete" />
@ -67,11 +64,7 @@
<!-- 添加条件按钮 -->
<div
v-if="
subGroup.conditions &&
subGroup.conditions.length > 0 &&
subGroup.conditions.length < maxConditions
"
v-if="subGroup && subGroup.length > 0 && subGroup.length < maxConditions"
class="text-center py-16px"
>
<el-button type="primary" plain @click="addCondition">
@ -90,22 +83,21 @@
import { useVModel } from '@vueuse/core'
import ConditionConfig from './ConditionConfig.vue'
import {
ConditionFormData,
IotRuleSceneTriggerConditionTypeEnum,
SubConditionGroupFormData
TriggerConditionFormData
} from '@/api/iot/rule/scene/scene.types'
/** 子条件组配置组件 */
defineOptions({ name: 'SubConditionGroupConfig' })
const props = defineProps<{
modelValue: SubConditionGroupFormData
modelValue: TriggerConditionFormData[]
triggerType: number
maxConditions?: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: SubConditionGroupFormData): void
(e: 'update:modelValue', value: TriggerConditionFormData[]): void
(e: 'validate', result: { valid: boolean; message: string }): void
}>()
@ -119,15 +111,13 @@ const conditionValidations = ref<{ [key: number]: { valid: boolean; message: str
// 事件处理
const addCondition = () => {
if (!subGroup.value.conditions) {
subGroup.value.conditions = []
if (!subGroup.value) {
subGroup.value = []
}
if (subGroup.value.conditions.length >= maxConditions.value) {
if (subGroup.value.length >= maxConditions.value) {
return
}
const newCondition: ConditionFormData = {
const newCondition: TriggerConditionFormData = {
type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // 默认为设备属性
productId: undefined,
deviceId: undefined,
@ -135,13 +125,12 @@ const addCondition = () => {
operator: '=',
param: ''
}
subGroup.value.conditions.push(newCondition)
subGroup.value.push(newCondition)
}
const removeCondition = (index: number) => {
if (subGroup.value.conditions) {
subGroup.value.conditions.splice(index, 1)
if (subGroup.value) {
subGroup.value.splice(index, 1)
delete conditionValidations.value[index]
// 重新索引验证结果
@ -160,9 +149,9 @@ const removeCondition = (index: number) => {
}
}
const updateCondition = (index: number, condition: ConditionFormData) => {
if (subGroup.value.conditions) {
subGroup.value.conditions[index] = condition
const updateCondition = (index: number, condition: TriggerConditionFormData) => {
if (subGroup.value) {
subGroup.value[index] = condition
}
}
@ -172,7 +161,7 @@ const handleConditionValidate = (index: number, result: { valid: boolean; messag
}
const updateValidationResult = () => {
if (!subGroup.value.conditions || subGroup.value.conditions.length === 0) {
if (!subGroup.value || subGroup.value.length === 0) {
emit('validate', { valid: false, message: '子条件组至少需要一个条件' })
return
}
@ -190,7 +179,7 @@ const updateValidationResult = () => {
// 监听变化
watch(
() => subGroup.value.conditions,
() => subGroup.value,
() => {
updateValidationResult()
},

View File

@ -67,6 +67,7 @@
<DeviceTriggerConfig
v-if="isDeviceTrigger(triggerItem.type)"
:model-value="triggerItem"
:index="index"
@update:model-value="(value) => updateTriggerDeviceConfig(index, value)"
/>
@ -111,13 +112,12 @@ import {
/** 触发器配置组件 */
defineOptions({ name: 'TriggerSection' })
// Props 和 Emits 定义
const props = defineProps<{
triggers: TriggerFormData[]
}>()
const emit = defineEmits<{
'update:triggers': [value: TriggerFormData[]]
(e: 'update:triggers', value: TriggerFormData[]): void
}>()
const triggers = useVModel(props, 'triggers', emit)
@ -172,36 +172,15 @@ const updateTriggerCronConfig = (index: number, cronExpression?: string) => {
triggers.value[index].cronExpression = cronExpression
}
const onTriggerTypeChange = (index: number, type: number) => {
const onTriggerTypeChange = (index: number, _: number) => {
const triggerItem = triggers.value[index]
// 清理不相关的配置
if (type === TriggerTypeEnum.TIMER) {
triggerItem.productId = undefined
triggerItem.deviceId = undefined
triggerItem.identifier = undefined
triggerItem.operator = undefined
triggerItem.value = undefined
triggerItem.mainCondition = undefined
triggerItem.conditionGroup = undefined
if (!triggerItem.cronExpression) {
triggerItem.cronExpression = '0 0 12 * * ?'
}
} else {
triggerItem.cronExpression = undefined
if (type === TriggerTypeEnum.DEVICE_STATE_UPDATE) {
triggerItem.mainCondition = undefined
triggerItem.conditionGroup = undefined
} else {
// 设备属性、事件、服务触发需要条件配置
if (!triggerItem.mainCondition) {
triggerItem.mainCondition = undefined // 等待用户配置
}
if (!triggerItem.conditionGroup) {
triggerItem.conditionGroup = undefined // 可选的条件组
}
}
}
triggerItem.productId = undefined
triggerItem.deviceId = undefined
triggerItem.identifier = undefined
triggerItem.operator = undefined
triggerItem.value = undefined
triggerItem.cronExpression = undefined
triggerItem.conditionGroups = []
}
// 初始化:确保至少有一个触发器

View File

@ -270,7 +270,7 @@
</div>
<!-- 表单对话框 -->
<RuleSceneForm v-model="formVisible" :rule-scene="currentRule" @success="getList" />
<RuleSceneForm v-model="formVisible" @success="getList" />
</ContentWrap>
</template>