From 32ce19fcd856c22403ad87650031b9840b151283 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 2 Aug 2025 11:22:10 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix=EF=BC=9A=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=B7=BB=E5=8A=A0=E6=9D=A1=E4=BB=B6=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configs/ConditionGroupContainerConfig.vue | 10 +++++++-- .../form/configs/DeviceTriggerConfig.vue | 7 ------ .../form/configs/SubConditionGroupConfig.vue | 22 +++++++++++++++---- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue b/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue index caa2ec9b2..1ece2c40c 100644 --- a/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue @@ -153,11 +153,17 @@ const addSubGroup = () => { container.value = [] } - if (container.value.length >= maxSubGroups) { + // 检查是否达到最大子组数量限制 + if (container.value?.length >= maxSubGroups) { return } - container.value.push([]) + // 使用 nextTick 确保响应式更新完成后再添加新的子组 + nextTick(() => { + if (container.value) { + container.value.push([]) + } + }) } const removeSubGroup = (index: number) => { diff --git a/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue index 991a87fdb..b5e37d5d0 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue @@ -83,13 +83,6 @@ const handleMainConditionValidate = (result: { valid: boolean; message: string } updateValidationResult() } -const addConditionGroup = () => { - if (!trigger.value.conditionGroups) { - trigger.value.conditionGroups = [] - } - trigger.value.conditionGroups.push([]) -} - // 事件处理 const handleConditionGroupValidate = () => { updateValidationResult() diff --git a/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue index f432b4d46..d89e4c4de 100644 --- a/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue +++ b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue @@ -80,10 +80,14 @@ diff --git a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index fa8d3202d..301fc7b50 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -1,5 +1,23 @@ @@ -66,21 +56,17 @@ const props = defineProps<{ const emit = defineEmits<{ (e: 'update:modelValue', value: ActionFormData): void - (e: 'validate', result: { valid: boolean; message: string }): void }>() const action = useVModel(props, 'modelValue', emit) // 状态 const paramsJson = ref('') -const validationMessage = ref('') -const isValid = ref(true) // 事件处理 const handleDeviceChange = ({ productId, deviceId }: { productId?: number; deviceId?: number }) => { action.value.productId = productId action.value.deviceId = deviceId - updateValidationResult() } const handleParamsChange = () => { @@ -90,34 +76,9 @@ const handleParamsChange = () => { } else { action.value.params = {} } - updateValidationResult() } catch (error) { - isValid.value = false - validationMessage.value = 'JSON格式错误' - emit('validate', { valid: false, message: validationMessage.value }) - } -} - -const updateValidationResult = () => { - // 基础验证 - if (!action.value.productId || !action.value.deviceId) { - isValid.value = false - validationMessage.value = '请选择产品和设备' - emit('validate', { valid: false, message: validationMessage.value }) - return - } - - if (!action.value.params || Object.keys(action.value.params).length === 0) { - isValid.value = false - validationMessage.value = '请配置控制参数' - emit('validate', { valid: false, message: validationMessage.value }) - return + console.error('JSON格式错误:', error) } - - // 验证通过 - isValid.value = true - validationMessage.value = '设备控制配置验证通过' - emit('validate', { valid: true, message: validationMessage.value }) } // 初始化 @@ -125,7 +86,6 @@ onMounted(() => { if (action.value.params) { paramsJson.value = JSON.stringify(action.value.params, null, 2) } - updateValidationResult() }) // 监听参数变化 diff --git a/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue index 69ab5052c..350a6cee3 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue @@ -6,7 +6,6 @@ @@ -17,7 +16,6 @@ @@ -48,14 +46,6 @@ const emit = defineEmits<{ const trigger = useVModel(props, 'modelValue', emit) -// 验证状态 -const mainConditionValidation = ref<{ valid: boolean; message: string }>({ - valid: true, - message: '' -}) -const validationMessage = ref('') -const isValid = ref(true) - // 初始化主条件 const initMainCondition = () => { // TODO @puhui999: 等到编辑回显时联调 @@ -80,76 +70,12 @@ watch( { immediate: true } ) -const handleMainConditionValidate = (result: { valid: boolean; message: string }) => { - mainConditionValidation.value = result - updateValidationResult() -} - const handleTriggerTypeChange = (type: number) => { trigger.value.type = type emit('trigger-type-change', type) } -// 事件处理 -const handleConditionGroupValidate = () => { - updateValidationResult() -} - const removeConditionGroup = () => { trigger.value.conditionGroups = undefined } - -const updateValidationResult = () => { - // 主条件验证 - if (!mainConditionValidation.value.valid) { - isValid.value = false - validationMessage.value = mainConditionValidation.value.message - emit('validate', { valid: false, message: validationMessage.value }) - return - } - - // 设备状态变更不需要条件验证 - if (trigger.value.type === TriggerTypeEnum.DEVICE_STATE_UPDATE) { - isValid.value = true - validationMessage.value = '设备触发配置验证通过' - emit('validate', { valid: true, message: validationMessage.value }) - return - } - - // 主条件验证 - if (!trigger.value.value) { - isValid.value = false - validationMessage.value = '请配置主条件' - emit('validate', { valid: false, message: validationMessage.value }) - return - } - - // 主条件详细验证 - if (!mainConditionValidation.value.valid) { - isValid.value = false - validationMessage.value = `主条件配置错误: ${mainConditionValidation.value.message}` - emit('validate', { valid: false, message: validationMessage.value }) - return - } - - isValid.value = true - validationMessage.value = '设备触发配置验证通过' - emit('validate', { valid: isValid.value, message: validationMessage.value }) -} - -// 监听触发器类型变化 -watch( - () => trigger.value.type, - () => { - updateValidationResult() - } -) - -// 监听产品设备变化 -watch( - () => [trigger.value.productId, trigger.value.deviceId], - () => { - updateValidationResult() - } -) diff --git a/src/views/iot/rule/scene/form/sections/ActionSection.vue b/src/views/iot/rule/scene/form/sections/ActionSection.vue index 4535be7e1..7fac9a15a 100644 --- a/src/views/iot/rule/scene/form/sections/ActionSection.vue +++ b/src/views/iot/rule/scene/form/sections/ActionSection.vue @@ -76,7 +76,6 @@ v-if="isDeviceAction(action.type)" :model-value="action" @update:model-value="(value) => updateAction(index, value)" - @validate="(result) => handleActionValidate(index, result)" /> @@ -84,7 +83,6 @@ v-if="isAlertAction(action.type)" :model-value="action.alertConfigId" @update:model-value="(value) => updateActionAlertConfig(index, value)" - @validate="(result) => handleActionValidate(index, result)" /> @@ -100,16 +98,6 @@ 最多可添加 {{ maxActions }} 个执行器 - - -
- -
@@ -131,7 +119,6 @@ interface Props { interface Emits { (e: 'update:actions', value: ActionFormData[]): void - (e: 'validate', result: { valid: boolean; message: string }): void } const props = defineProps() @@ -155,11 +142,6 @@ const createDefaultActionData = (): ActionFormData => { // 配置常量 const maxActions = 5 -// 验证状态 -const actionValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({}) -const validationMessage = ref('') -const isValid = ref(true) - // 执行器类型映射 const actionTypeNames = { [ActionTypeEnum.DEVICE_PROPERTY_SET]: '属性设置', @@ -206,21 +188,6 @@ const addAction = () => { const removeAction = (index: number) => { actions.value.splice(index, 1) - delete actionValidations.value[index] - - // 重新索引验证结果 - const newValidations: { [key: number]: { valid: boolean; message: string } } = {} - Object.keys(actionValidations.value).forEach((key) => { - const numKey = parseInt(key) - if (numKey > index) { - newValidations[numKey - 1] = actionValidations.value[numKey] - } else if (numKey < index) { - newValidations[numKey] = actionValidations.value[numKey] - } - }) - actionValidations.value = newValidations - - updateValidationResult() } const updateActionType = (index: number, type: number) => { @@ -249,37 +216,4 @@ const onActionTypeChange = (action: ActionFormData, type: number) => { action.params = undefined } } - -const handleActionValidate = (index: number, result: { valid: boolean; message: string }) => { - actionValidations.value[index] = result - updateValidationResult() -} - -const updateValidationResult = () => { - const validations = Object.values(actionValidations.value) - const allValid = validations.every((v) => v.valid) - const hasValidations = validations.length > 0 - - if (!hasValidations) { - isValid.value = true - validationMessage.value = '' - } else if (allValid) { - isValid.value = true - validationMessage.value = '所有执行器配置验证通过' - } else { - isValid.value = false - const errorMessages = validations.filter((v) => !v.valid).map((v) => v.message) - validationMessage.value = `执行器配置错误: ${errorMessages.join('; ')}` - } - - emit('validate', { valid: isValid.value, message: validationMessage.value }) -} - -// 监听执行器数量变化 -watch( - () => actions.value.length, - () => { - updateValidationResult() - } -) -- Gitee From 38ad857c3386c335b8cd3f63b3ec56f7a8bf559c Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 4 Aug 2025 00:21:19 +0800 Subject: [PATCH 6/9] =?UTF-8?q?perf=EF=BC=9A=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=99=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/rule/scene/form/RuleSceneForm.vue | 99 ++- .../rule/scene/form/configs/AlertConfig.vue | 210 +++++-- .../form/configs/DeviceControlConfig.vue | 591 ++++++++++++++++-- .../scene/form/sections/ActionSection.vue | 49 +- .../form/selectors/ProductDeviceSelector.vue | 20 +- src/views/iot/utils/constants.ts | 72 ++- 6 files changed, 882 insertions(+), 159 deletions(-) diff --git a/src/views/iot/rule/scene/form/RuleSceneForm.vue b/src/views/iot/rule/scene/form/RuleSceneForm.vue index d348046cf..e2f155a80 100644 --- a/src/views/iot/rule/scene/form/RuleSceneForm.vue +++ b/src/views/iot/rule/scene/form/RuleSceneForm.vue @@ -36,11 +36,12 @@ import { useVModel } from '@vueuse/core' import BasicInfoSection from './sections/BasicInfoSection.vue' import TriggerSection from './sections/TriggerSection.vue' import ActionSection from './sections/ActionSection.vue' -import { IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types' +import { IotRuleScene, IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types' import { RuleSceneApi } from '@/api/iot/rule/scene' import { IotRuleSceneTriggerTypeEnum, IotRuleSceneActionTypeEnum, + IotDeviceMessageTypeEnum, isDeviceTrigger } from '@/views/iot/utils/constants' import { ElMessage } from 'element-plus' @@ -53,6 +54,57 @@ const CommonStatusEnum = { DISABLE: 1 // 关闭 } as const +// 工具函数:根据触发器类型获取消息类型 +const getMessageTypeByTriggerType = (triggerType: number): string => { + switch (triggerType) { + case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST: + return IotDeviceMessageTypeEnum.PROPERTY + case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST: + return IotDeviceMessageTypeEnum.EVENT + case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE: + return IotDeviceMessageTypeEnum.SERVICE + default: + return IotDeviceMessageTypeEnum.PROPERTY + } +} + +// 工具函数:根据执行器类型获取消息类型 +const getMessageTypeByActionType = (actionType: number): string => { + switch (actionType) { + case IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET: + return IotDeviceMessageTypeEnum.PROPERTY + case IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE: + return IotDeviceMessageTypeEnum.SERVICE + default: + return IotDeviceMessageTypeEnum.PROPERTY + } +} + +// 工具函数:根据执行器类型和参数获取标识符 +const getIdentifierByActionType = (actionType: number, params?: Record): string => { + if (!params) return '' + + switch (actionType) { + case IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET: + // 属性设置:取第一个属性名作为标识符 + return Object.keys(params)[0] || '' + case IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE: + // 服务调用:取 method 字段作为标识符 + return params.method || '' + default: + return '' + } +} + +// 工具函数:判断是否为设备执行器 +const isDeviceAction = (type: number): boolean => { + const deviceActionTypes = [ + IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, + IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE + ] as number[] + return deviceActionTypes.includes(type) +} + /** IoT 场景联动规则表单 - 主表单组件 */ defineOptions({ name: 'RuleSceneForm' }) @@ -95,31 +147,50 @@ const createDefaultFormData = (): RuleSceneFormData => { } /** - * 将表单数据转换为后端 DO 格式 - * 由于数据结构已对齐,转换变得非常简单 + * 将表单数据转换为后端 API 格式 + * 转换为 IotRuleScene 格式,与后端 API 接口对齐 */ -const convertFormToVO = (formData: RuleSceneFormData): IotRuleSceneDO => { +const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => { return { id: formData.id, name: formData.name, description: formData.description, status: Number(formData.status), triggers: formData.triggers.map((trigger) => ({ + key: generateUUID(), // 为每个触发器生成唯一标识 type: trigger.type, - productId: trigger.productId, - deviceId: trigger.deviceId, - identifier: trigger.identifier, - operator: trigger.operator, - value: trigger.value, - cronExpression: trigger.cronExpression, - conditionGroups: trigger.conditionGroups || [] + productKey: trigger.productId ? `product_${trigger.productId}` : undefined, // 转换为产品标识 + deviceNames: trigger.deviceId ? [`device_${trigger.deviceId}`] : undefined, // 转换为设备名称数组 + conditions: trigger.identifier + ? [ + { + type: getMessageTypeByTriggerType(trigger.type), + identifier: trigger.identifier, + parameters: [ + { + identifier: trigger.identifier, + operator: trigger.operator || '=', + value: trigger.value || '' + } + ] + } + ] + : undefined, + cronExpression: trigger.cronExpression })), actions: formData.actions?.map((action) => ({ + key: generateUUID(), // 为每个执行器生成唯一标识 type: action.type, - productId: action.productId, - deviceId: action.deviceId, - params: action.params, + deviceControl: isDeviceAction(action.type) + ? { + productKey: action.productId ? `product_${action.productId}` : '', + deviceNames: action.deviceId ? [`device_${action.deviceId}`] : [], + type: getMessageTypeByActionType(action.type), + identifier: getIdentifierByActionType(action.type, action.params), + params: action.params || {} + } + : undefined, alertConfigId: action.alertConfigId })) || [] } diff --git a/src/views/iot/rule/scene/form/configs/AlertConfig.vue b/src/views/iot/rule/scene/form/configs/AlertConfig.vue index 756f05697..bffe9d578 100644 --- a/src/views/iot/rule/scene/form/configs/AlertConfig.vue +++ b/src/views/iot/rule/scene/form/configs/AlertConfig.vue @@ -1,72 +1,152 @@