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

This commit is contained in:
puhui999
2025-08-01 18:16:31 +08:00
parent a5d458b96d
commit 858f1cdb0b
5 changed files with 129 additions and 178 deletions

View File

@ -107,7 +107,7 @@ interface ActionConfig {
alertConfigId?: number // 告警配置ID告警恢复时必填
}
// 表单数据接口
// 表单数据接口 - 直接对应后端 DO 结构
interface RuleSceneFormData {
id?: number
name: string
@ -117,57 +117,38 @@ interface RuleSceneFormData {
actions: ActionFormData[]
}
// 触发器表单数据 - 直接对应 TriggerDO
interface TriggerFormData {
type: number
productId?: number
deviceId?: number
identifier?: string
operator?: string
value?: string
cronExpression?: string
// 新的条件结构
mainCondition?: ConditionFormData // 主条件(必须满足)
conditionGroup?: ConditionGroupContainerFormData // 条件组容器(可选,与主条件为且关系)
type: number // 触发类型
productId?: number // 产品编号
deviceId?: number // 设备编号
identifier?: string // 物模型标识符
operator?: string // 操作符
value?: string // 参数值
cronExpression?: string // CRON 表达式
conditionGroups?: TriggerConditionFormData[][] // 条件组(二维数组)
}
interface ActionFormData {
type: number
productId?: number
deviceId?: number
params?: Record<string, any>
alertConfigId?: number
}
// 条件组容器(包含多个子条件组,子条件组间为或关系)
interface ConditionGroupContainerFormData {
subGroups: SubConditionGroupFormData[] // 子条件组数组,子条件组间为或关系
}
// 子条件组(内部条件为且关系)
interface SubConditionGroupFormData {
conditions: ConditionFormData[] // 条件数组,条件间为且关系
}
// 保留原有接口用于兼容性
interface ConditionGroupFormData {
conditions: ConditionFormData[]
// 注意:条件组内部的条件固定为"且"关系,条件组之间固定为"或"关系
// logicOperator 字段保留用于兼容性但在UI中固定为 'AND'
logicOperator: 'AND' | 'OR'
}
interface ConditionFormData {
// 触发条件表单数据 - 直接对应 TriggerConditionDO
interface TriggerConditionFormData {
type: number // 条件类型1-设备状态2-设备属性3-当前时间
productId?: number // 产品ID设备状态和设备属性时必填
deviceId?: number // 设备ID设备状态和设备属性时必填
identifier?: string // 标识符(设备属性时必填)
productId?: number // 产品编号
deviceId?: number // 设备编号
identifier?: string // 标识符
operator: string // 操作符
param: string // 参数值
timeValue?: string // 时间值(当前时间条件时使用)
timeValue2?: string // 第二个时间值(时间范围条件时使用)
}
// 主接口
// 执行器表单数据 - 直接对应 ActionDO
interface ActionFormData {
type: number // 执行类型
productId?: number // 产品编号
deviceId?: number // 设备编号
params?: Record<string, any> // 请求参数
alertConfigId?: number // 告警配置编号
}
// 主接口 - 原有的 API 接口格式(保持兼容性)
interface IotRuleScene extends TenantBaseDO {
id?: number // 场景编号(新增时为空)
name: string // 场景名称(必填)
@ -177,6 +158,47 @@ interface IotRuleScene extends TenantBaseDO {
actions: ActionConfig[] // 执行器数组(必填,至少一个)
}
// 后端 DO 接口 - 匹配后端数据结构
interface IotRuleSceneDO {
id?: number // 场景编号
name: string // 场景名称
description?: string // 场景描述
status: number // 场景状态0-开启1-关闭
triggers: TriggerDO[] // 触发器数组
actions: ActionDO[] // 执行器数组
}
// 触发器 DO 结构
interface TriggerDO {
type: number // 触发类型
productId?: number // 产品编号
deviceId?: number // 设备编号
identifier?: string // 物模型标识符
operator?: string // 操作符
value?: string // 参数值
cronExpression?: string // CRON 表达式
conditionGroups?: TriggerConditionDO[][] // 条件组(二维数组)
}
// 触发条件 DO 结构
interface TriggerConditionDO {
type: number // 条件类型
productId?: number // 产品编号
deviceId?: number // 设备编号
identifier?: string // 标识符
operator: string // 操作符
param: string // 参数
}
// 执行器 DO 结构
interface ActionDO {
type: number // 执行类型
productId?: number // 产品编号
deviceId?: number // 设备编号
params?: Record<string, any> // 请求参数
alertConfigId?: number // 告警配置编号
}
// 工具类型 - 从枚举中提取类型
// TriggerType 现在从 constants.ts 中的枚举提取
export type ActionType =
@ -202,6 +224,10 @@ interface FormValidationRules {
export {
IotRuleScene,
IotRuleSceneDO,
TriggerDO,
TriggerConditionDO,
ActionDO,
TriggerConfig,
TriggerCondition,
TriggerConditionParameter,
@ -209,11 +235,8 @@ export {
ActionDeviceControl,
RuleSceneFormData,
TriggerFormData,
TriggerConditionFormData,
ActionFormData,
ConditionGroupFormData,
ConditionGroupContainerFormData,
SubConditionGroupFormData,
ConditionFormData,
IotRuleSceneActionTypeEnum,
IotDeviceMessageTypeEnum,
IotRuleSceneTriggerConditionParameterOperatorEnum,

View File

@ -41,9 +41,11 @@ import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.vue'
import {
IotRuleScene,
IotRuleSceneDO,
IotRuleSceneActionTypeEnum,
RuleSceneFormData,
TriggerFormData
TriggerFormData,
TriggerConditionFormData
} from '@/api/iot/rule/scene/scene.types'
import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
import { ElMessage } from 'element-plus'
@ -91,8 +93,7 @@ const createDefaultFormData = (): RuleSceneFormData => {
operator: undefined,
value: undefined,
cronExpression: undefined,
mainCondition: undefined,
conditionGroup: undefined
conditionGroups: [] // 空的条件组数组
}
],
actions: []
@ -100,49 +101,10 @@ const createDefaultFormData = (): RuleSceneFormData => {
}
/**
* 将表单数据转换为 API 请求格式
* 将表单数据转换为后端 DO 格式
* 由于数据结构已对齐,转换变得非常简单
*/
const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
// 构建单个触发器的条件
const buildTriggerConditions = (trigger: TriggerFormData) => {
const conditions: any[] = []
// 处理主条件
if (trigger.mainCondition) {
const mainCondition = trigger.mainCondition
conditions.push({
type: mainCondition.type === 2 ? 'property' : 'event',
identifier: mainCondition.identifier || '',
parameters: [
{
operator: mainCondition.operator,
value: mainCondition.param
}
]
})
}
// 处理条件组
if (trigger.conditionGroup?.subGroups) {
trigger.conditionGroup.subGroups.forEach((subGroup) => {
subGroup.conditions.forEach((condition) => {
conditions.push({
type: condition.type === 2 ? 'property' : 'event',
identifier: condition.identifier || '',
parameters: [
{
operator: condition.operator,
value: condition.param
}
]
})
})
})
}
return conditions
}
const convertFormToVO = (formData: RuleSceneFormData): IotRuleSceneDO => {
return {
id: formData.id,
name: formData.name,
@ -150,79 +112,41 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => {
status: Number(formData.status),
triggers: formData.triggers.map((trigger) => ({
type: trigger.type,
productKey: trigger.productId ? `product_${trigger.productId}` : undefined,
deviceNames: trigger.deviceId ? [`device_${trigger.deviceId}`] : undefined,
productId: trigger.productId,
deviceId: trigger.deviceId,
identifier: trigger.identifier,
operator: trigger.operator,
value: trigger.value,
cronExpression: trigger.cronExpression,
conditions: buildTriggerConditions(trigger)
conditionGroups: trigger.conditionGroups || []
})),
actions:
formData.actions?.map((action) => ({
type: action.type,
alertConfigId: action.alertConfigId,
deviceControl:
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
? {
productKey: action.productId ? `product_${action.productId}` : '',
deviceNames: action.deviceId ? [`device_${action.deviceId}`] : [],
type:
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET
? 'property'
: 'service',
identifier:
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ? 'set' : 'invoke',
params: action.params || {}
}
: undefined
})) || []
} as IotRuleScene
actions: formData.actions?.map((action) => ({
type: action.type,
productId: action.productId,
deviceId: action.deviceId,
params: action.params,
alertConfigId: action.alertConfigId
})) || []
}
}
/**
* 将 API 响应数据转换为表单格式
* 将后端 DO 数据转换为表单格式
* 由于数据结构已对齐,转换变得非常简单
*/
const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => {
// 解析单个触发器的条件
const parseConditions = (trigger: any) => {
if (!trigger?.conditions?.length) {
return {
mainCondition: undefined,
conditionGroup: undefined
}
}
// 简化处理:将第一个条件作为主条件
const firstCondition = trigger.conditions[0]
const mainCondition = {
type: firstCondition.type === 'property' ? 2 : 3,
productId: undefined, // 需要从 productKey 解析
deviceId: undefined, // 需要从 deviceNames 解析
identifier: firstCondition.identifier,
operator: firstCondition.parameters?.[0]?.operator || '=',
param: firstCondition.parameters?.[0]?.value || ''
}
return {
mainCondition,
conditionGroup: undefined // 暂时简化处理
}
}
const convertVOToForm = (apiData: IotRuleSceneDO): RuleSceneFormData => {
// 转换所有触发器
const triggers = apiData.triggers?.length
? apiData.triggers.map((trigger) => {
const conditionData = parseConditions(trigger)
return {
type: Number(trigger.type),
productId: undefined, // 需要从 productKey 解析
deviceId: undefined, // 需要从 deviceNames 解析
identifier: undefined,
operator: undefined,
value: undefined,
cronExpression: trigger.cronExpression,
...conditionData
}
})
? apiData.triggers.map((trigger: any) => ({
type: Number(trigger.type),
productId: trigger.productId,
deviceId: trigger.deviceId,
identifier: trigger.identifier,
operator: trigger.operator,
value: trigger.value,
cronExpression: trigger.cronExpression,
conditionGroups: trigger.conditionGroups || []
}))
: [
{
type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
@ -232,22 +156,23 @@ const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => {
operator: undefined,
value: undefined,
cronExpression: undefined,
mainCondition: undefined,
conditionGroup: undefined
conditionGroups: []
}
]
return {
...apiData,
id: apiData.id,
name: apiData.name,
description: apiData.description,
status: Number(apiData.status),
triggers,
actions:
apiData.actions?.map((action) => ({
...action,
apiData.actions?.map((action: any) => ({
type: Number(action.type),
productId: undefined, // 需要从 deviceControl.productKey 解析
deviceId: undefined, // 需要从 deviceControl.deviceNames 解析
params: action.deviceControl?.params || {},
productId: action.productId,
deviceId: action.deviceId,
params: action.params || {},
alertConfigId: action.alertConfigId,
// 为每个执行器添加唯一标识符,解决组件索引重用问题
key: generateUUID()
})) || []
@ -321,9 +246,13 @@ const handleSubmit = async () => {
// 提交请求
submitLoading.value = true
try {
console.log(formData.value)
// 转换数据格式
const apiData = convertFormToVO(formData.value)
if (true) {
console.log('转换后', apiData)
return
}
// 调用API保存数据
if (isEdit.value) {
// 更新场景联动规则

View File

@ -1,10 +1,10 @@
<!-- 设备触发配置组件 - 优化版本 -->
<!-- 设备触发配置组件 -->
<template>
<div class="flex flex-col gap-16px">
<!-- 主条件配置 - 默认直接展示 -->
<div class="space-y-16px">
<MainConditionConfig
v-model="trigger.mainCondition"
v-model="trigger"
:trigger-type="trigger.type"
@validate="handleMainConditionValidate"
/>

View File

@ -40,37 +40,37 @@
<script setup lang="ts">
import MainConditionInnerConfig from './MainConditionInnerConfig.vue'
import {
ConditionFormData,
IotRuleSceneTriggerConditionTypeEnum
IotRuleSceneTriggerConditionTypeEnum,
TriggerFormData
} from '@/api/iot/rule/scene/scene.types'
/** 主条件配置组件 */
defineOptions({ name: 'MainConditionConfig' })
defineProps<{
modelValue?: ConditionFormData
modelValue?: TriggerFormData
triggerType: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value?: ConditionFormData): void
(e: 'update:modelValue', value?: TriggerFormData): void
(e: 'validate', result: { valid: boolean; message: string }): void
}>()
// 事件处理
const addMainCondition = () => {
const newCondition: ConditionFormData = {
const newCondition: TriggerFormData = {
type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // 默认为设备属性
productId: undefined,
deviceId: undefined,
identifier: '',
operator: '=',
param: ''
value: ''
}
emit('update:modelValue', newCondition)
}
const updateCondition = (condition: ConditionFormData) => {
const updateCondition = (condition: TriggerFormData) => {
emit('update:modelValue', condition)
}

View File

@ -148,8 +148,7 @@ const addTrigger = () => {
operator: undefined,
value: undefined,
cronExpression: undefined,
mainCondition: undefined,
conditionGroup: undefined
conditionGroups: [] // 空的条件组数组
}
triggers.value.push(newTrigger)
}