From c740da02b9431be5c9567551cd6ef93398e309a9 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 1 Aug 2025 14:58:39 +0800 Subject: [PATCH 1/7] =?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?=E4=B8=BB=E8=A1=A8=E5=8D=95=E4=BC=98=E5=8C=96=E5=8E=BB=E9=99=A4?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/rule/scene/scene.types.ts | 4 - .../iot/rule/scene/form/RuleSceneForm.vue | 290 +++++++++++------- src/views/iot/rule/scene/utils/validation.ts | 188 ------------ 3 files changed, 174 insertions(+), 308 deletions(-) delete mode 100644 src/views/iot/rule/scene/utils/validation.ts diff --git a/src/api/iot/rule/scene/scene.types.ts b/src/api/iot/rule/scene/scene.types.ts index b1e899305..a9ba63b9c 100644 --- a/src/api/iot/rule/scene/scene.types.ts +++ b/src/api/iot/rule/scene/scene.types.ts @@ -256,10 +256,6 @@ export { IotAlertConfigReceiveTypeEnum, DeviceStateEnum, CommonStatusEnum, - TriggerType, - ActionType, - MessageType, - OperatorType, ValidationRule, FormValidationRules } diff --git a/src/views/iot/rule/scene/form/RuleSceneForm.vue b/src/views/iot/rule/scene/form/RuleSceneForm.vue index 3b00fdb7a..6c4a96859 100644 --- a/src/views/iot/rule/scene/form/RuleSceneForm.vue +++ b/src/views/iot/rule/scene/form/RuleSceneForm.vue @@ -1,5 +1,5 @@ @@ -41,38 +40,36 @@ import BasicInfoSection from './sections/BasicInfoSection.vue' import TriggerSection from './sections/TriggerSection.vue' import ActionSection from './sections/ActionSection.vue' import { - RuleSceneFormData, + CommonStatusEnum, IotRuleScene, IotRuleSceneActionTypeEnum, IotRuleSceneTriggerTypeEnum, - CommonStatusEnum + RuleSceneFormData } from '@/api/iot/rule/scene/scene.types' -import { getBaseValidationRules } from '../utils/validation' import { ElMessage } from 'element-plus' import { generateUUID } from '@/utils' /** IoT 场景联动规则表单 - 主表单组件 */ defineOptions({ name: 'RuleSceneForm' }) -// TODO @puhui999:是不是融合到 props -interface Props { +/** 组件属性定义 */ +const props = defineProps<{ + /** 抽屉显示状态 */ modelValue: boolean + /** 编辑的规则数据(新增时为空) */ ruleScene?: IotRuleScene -} +}>() -// TODO @puhui999:Emits 是不是融合到 emit -interface Emits { - (e: 'update:modelValue', value: boolean): void - (e: 'success'): void -} - -const props = defineProps() -const emit = defineEmits() +/** 组件事件定义 */ +const emit = defineEmits<{ + /** 更新抽屉显示状态 */ + 'update:modelValue': [value: boolean] + /** 操作成功事件 */ + success: [] +}>() const drawerVisible = useVModel(props, 'modelValue', emit) // 是否可见 -// TODO @puhui999:使用 /** 注释风格哈 */ - /** 创建默认的表单数据 */ const createDefaultFormData = (): RuleSceneFormData => { return { @@ -94,11 +91,50 @@ const createDefaultFormData = (): RuleSceneFormData => { } } -// TODO @puhui999:使用 convertFormToVO;下面也是类似哈; /** * 将表单数据转换为 API 请求格式 */ -const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => { +const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => { + // 构建触发器条件 + const buildTriggerConditions = () => { + const conditions: any[] = [] + + // 处理主条件 + if (formData.trigger.mainCondition) { + const mainCondition = formData.trigger.mainCondition + conditions.push({ + type: mainCondition.type === 2 ? 'property' : 'event', + identifier: mainCondition.identifier || '', + parameters: [ + { + operator: mainCondition.operator, + value: mainCondition.param + } + ] + }) + } + + // 处理条件组 + if (formData.trigger.conditionGroup?.subGroups) { + formData.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 + } + return { id: formData.id, name: formData.name, @@ -114,7 +150,7 @@ const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => { ? [`device_${formData.trigger.deviceId}`] : undefined, cronExpression: formData.trigger.cronExpression, - conditions: [] // TODO: 实现新的条件转换逻辑 + conditions: buildTriggerConditions() } ], actions: @@ -127,8 +163,12 @@ const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => { ? { productKey: action.productId ? `product_${action.productId}` : '', deviceNames: action.deviceId ? [`device_${action.deviceId}`] : [], - type: 'property', - identifier: 'set', + type: + action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET + ? 'property' + : 'service', + identifier: + action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ? 'set' : 'invoke', params: action.params || {} } : undefined @@ -139,15 +179,55 @@ const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => { /** * 将 API 响应数据转换为表单格式 */ -const transformApiToForm = (apiData: IotRuleScene): RuleSceneFormData => { +const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => { const firstTrigger = apiData.triggers?.[0] + + // 解析触发器条件 + 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 conditionData = firstTrigger + ? parseConditions(firstTrigger) + : { + mainCondition: undefined, + conditionGroup: undefined + } + return { ...apiData, - status: Number(apiData.status), // 确保状态为数字类型 + status: Number(apiData.status), trigger: firstTrigger ? { - ...firstTrigger, - type: Number(firstTrigger.type) + type: Number(firstTrigger.type), + productId: undefined, // 需要从 productKey 解析 + deviceId: undefined, // 需要从 deviceNames 解析 + identifier: undefined, + operator: undefined, + value: undefined, + cronExpression: firstTrigger.cronExpression, + ...conditionData } : { type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, @@ -164,6 +244,9 @@ const transformApiToForm = (apiData: IotRuleScene): RuleSceneFormData => { apiData.actions?.map((action) => ({ ...action, type: Number(action.type), + productId: undefined, // 需要从 deviceControl.productKey 解析 + deviceId: undefined, // 需要从 deviceControl.deviceNames 解析 + params: action.deviceControl?.params || {}, // 为每个执行器添加唯一标识符,解决组件索引重用问题 key: generateUUID() })) || [] @@ -173,7 +256,33 @@ const transformApiToForm = (apiData: IotRuleScene): RuleSceneFormData => { // 表单数据和状态 const formRef = ref() const formData = ref(createDefaultFormData()) -const formRules = getBaseValidationRules() +const formRules = reactive({ + name: [ + { required: true, message: '场景名称不能为空', trigger: 'blur' }, + { type: 'string', min: 1, max: 50, message: '场景名称长度应在1-50个字符之间', trigger: 'blur' } + ], + status: [ + { required: true, message: '场景状态不能为空', trigger: 'change' }, + { + type: 'enum', + enum: [CommonStatusEnum.ENABLE, CommonStatusEnum.DISABLE], + message: '状态值必须为启用或禁用', + trigger: 'change' + } + ], + description: [ + { type: 'string', max: 200, message: '场景描述不能超过200个字符', trigger: 'blur' } + ], + triggers: [ + { required: true, message: '触发器数组不能为空', trigger: 'change' }, + { type: 'array', min: 1, message: '至少需要一个触发器', trigger: 'change' } + ], + actions: [ + { required: true, message: '执行器数组不能为空', trigger: 'change' }, + { type: 'array', min: 1, message: '至少需要一个执行器', trigger: 'change' } + ] +}) + const submitLoading = ref(false) // 验证状态 @@ -212,11 +321,18 @@ const handleSubmit = async () => { submitLoading.value = true try { // 转换数据格式 - const apiData = transformFormToApi(formData.value) - - // 这里应该调用API保存数据 - // TODO @puhui999:貌似还没接入 - console.log('提交数据:', apiData) + const apiData = convertFormToVO(formData.value) + + // 调用API保存数据 + if (isEdit.value) { + // 更新场景联动规则 + // await RuleSceneApi.updateRuleScene(apiData) + console.log('更新数据:', apiData) + } else { + // 创建场景联动规则 + // await RuleSceneApi.createRuleScene(apiData) + console.log('创建数据:', apiData) + } // 模拟API调用 await new Promise((resolve) => setTimeout(resolve, 1000)) @@ -224,6 +340,9 @@ const handleSubmit = async () => { ElMessage.success(isEdit.value ? '更新成功' : '创建成功') drawerVisible.value = false emit('success') + } catch (error) { + console.error('保存失败:', error) + ElMessage.error(isEdit.value ? '更新失败' : '创建失败') } finally { submitLoading.value = false } @@ -233,10 +352,10 @@ const handleClose = () => { drawerVisible.value = false } -// 初始化表单数据 +/** 初始化表单数据 */ const initFormData = () => { if (props.ruleScene) { - formData.value = transformApiToForm(props.ruleScene) + formData.value = convertVOToForm(props.ruleScene) } else { formData.value = createDefaultFormData() } @@ -262,64 +381,3 @@ watch( } ) - - - diff --git a/src/views/iot/rule/scene/utils/validation.ts b/src/views/iot/rule/scene/utils/validation.ts deleted file mode 100644 index d5430aa87..000000000 --- a/src/views/iot/rule/scene/utils/validation.ts +++ /dev/null @@ -1,188 +0,0 @@ -// TODO @puhui999:貌似很多地方,都用不到啦?这个文件 -/** - * IoT 场景联动表单验证工具函数 - */ -import { FormValidationRules, TriggerConfig, ActionConfig } from '@/api/iot/rule/scene/scene.types' -import { - IotRuleSceneTriggerTypeEnum, - IotRuleSceneActionTypeEnum, - CommonStatusEnum -} from '@/api/iot/rule/scene/scene.types' - -/** 基础表单验证规则 */ -export const getBaseValidationRules = (): FormValidationRules => ({ - name: [ - { required: true, message: '场景名称不能为空', trigger: 'blur' }, - { type: 'string', min: 1, max: 50, message: '场景名称长度应在1-50个字符之间', trigger: 'blur' } - ], - status: [ - { required: true, message: '场景状态不能为空', trigger: 'change' }, - { - type: 'enum', - enum: [CommonStatusEnum.ENABLE, CommonStatusEnum.DISABLE], - message: '状态值必须为启用或禁用', - trigger: 'change' - } - ], - description: [ - { type: 'string', max: 200, message: '场景描述不能超过200个字符', trigger: 'blur' } - ], - triggers: [ - { required: true, message: '触发器数组不能为空', trigger: 'change' }, - { type: 'array', min: 1, message: '至少需要一个触发器', trigger: 'change' } - ], - actions: [ - { required: true, message: '执行器数组不能为空', trigger: 'change' }, - { type: 'array', min: 1, message: '至少需要一个执行器', trigger: 'change' } - ] -}) - -/** 验证CRON表达式格式 */ -// TODO @puhui999:这个可以拿到 cron 组件里哇? -export function validateCronExpression(cron: string): boolean { - if (!cron || cron.trim().length === 0) return false - // 基础的 CRON 表达式正则验证(支持 6 位和 7 位格式) - const cronRegex = - /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))( (\*|([1-9][0-9]{3})|\*\/([1-9][0-9]{3})))?$/ - return cronRegex.test(cron.trim()) -} - -/** 验证设备名称数组 */ -export function validateDeviceNames(deviceNames: string[]): boolean { - return ( - Array.isArray(deviceNames) && - deviceNames.length > 0 && - deviceNames.every((name) => name && name.trim().length > 0) - ) -} - -/** 验证比较值格式 */ -export function validateCompareValue(operator: string, value: string): boolean { - if (!value || value.trim().length === 0) return false - const trimmedValue = value.trim() - // TODO @puhui999:这里要用下枚举哇? - switch (operator) { - case 'between': - case 'not between': - const betweenValues = trimmedValue.split(',') - return ( - betweenValues.length === 2 && - betweenValues.every((v) => v.trim().length > 0) && - !isNaN(Number(betweenValues[0].trim())) && - !isNaN(Number(betweenValues[1].trim())) - ) - case 'in': - case 'not in': - const inValues = trimmedValue.split(',') - return inValues.length > 0 && inValues.every((v) => v.trim().length > 0) - case '>': - case '>=': - case '<': - case '<=': - return !isNaN(Number(trimmedValue)) - case '=': - case '!=': - case 'like': - case 'not null': - // TODO @puhui999:这里要不加个 default 抛出异常? - default: - return true - } -} - -// TODO @puhui999:貌似没用到? -/** 验证触发器配置 */ -export function validateTriggerConfig(trigger: TriggerConfig): { - valid: boolean - message?: string -} { - if (!trigger.type) { - return { valid: false, message: '触发类型不能为空' } - } - // 定时触发验证 - if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) { - if (!trigger.cronExpression) { - return { valid: false, message: 'CRON表达式不能为空' } - } - if (!validateCronExpression(trigger.cronExpression)) { - return { valid: false, message: 'CRON表达式格式不正确' } - } - return { valid: true } - } - // 设备触发验证 - if (!trigger.productKey) { - return { valid: false, message: '产品标识不能为空' } - } - if (!trigger.deviceNames || !validateDeviceNames(trigger.deviceNames)) { - return { valid: false, message: '设备名称不能为空' } - } - // 设备状态变更无需额外条件验证 - if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) { - return { valid: true } - } - // 其他设备触发类型需要验证条件 - if (!trigger.conditions || trigger.conditions.length === 0) { - return { valid: false, message: '触发条件不能为空' } - } - // 验证每个条件的参数 - for (const condition of trigger.conditions) { - if (!condition.parameters || condition.parameters.length === 0) { - return { valid: false, message: '触发条件参数不能为空' } - } - for (const param of condition.parameters) { - if (!param.operator) { - return { valid: false, message: '操作符不能为空' } - } - if (!validateCompareValue(param.operator, param.value)) { - return { valid: false, message: `操作符 "${param.operator}" 对应的比较值格式不正确` } - } - } - } - return { valid: true } -} - -// TODO @puhui999:貌似没用到? -/** 验证执行器配置 */ -export function validateActionConfig(action: ActionConfig): { valid: boolean; message?: string } { - if (!action.type) { - return { valid: false, message: '执行类型不能为空' } - } - // 告警触发/恢复验证 - if ( - action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER || - action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER - ) { - if (!action.alertConfigId) { - return { valid: false, message: '告警配置ID不能为空' } - } - return { valid: true } - } - // 设备控制验证 - if ( - action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET || - action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE - ) { - if (!action.deviceControl) { - return { valid: false, message: '设备控制配置不能为空' } - } - const { deviceControl } = action - if (!deviceControl.productKey) { - return { valid: false, message: '产品标识不能为空' } - } - if (!deviceControl.deviceNames || !validateDeviceNames(deviceControl.deviceNames)) { - return { valid: false, message: '设备名称不能为空' } - } - if (!deviceControl.type) { - return { valid: false, message: '消息类型不能为空' } - } - if (!deviceControl.identifier) { - return { valid: false, message: '消息标识符不能为空' } - } - if (!deviceControl.params || Object.keys(deviceControl.params).length === 0) { - return { valid: false, message: '参数不能为空' } - } - return { valid: true } - } - - return { valid: false, message: '未知的执行类型' } -} -- Gitee From a554bc53098d4494f3a0fa4f834c10169840d222 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 1 Aug 2025 16:14:11 +0800 Subject: [PATCH 2/7] =?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?=E8=A7=A6=E5=8F=91=E5=99=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/iot/rule/scene/scene.types.ts | 51 +--- .../iot/rule/scene/form/RuleSceneForm.vue | 109 +++---- .../form/configs/ConditionGroupConfig.vue | 8 +- .../form/configs/DeviceTriggerConfig.vue | 23 +- .../form/configs/MainConditionConfig.vue | 26 +- .../form/configs/MainConditionInnerConfig.vue | 9 +- .../scene/form/sections/BasicInfoSection.vue | 23 +- .../scene/form/sections/TriggerSection.vue | 277 +++++++++++------- .../scene/form/selectors/OperatorSelector.vue | 28 +- .../scene/form/selectors/PropertySelector.vue | 268 +++++++++++++++-- src/views/iot/utils/constants.ts | 46 +++ 11 files changed, 552 insertions(+), 316 deletions(-) diff --git a/src/api/iot/rule/scene/scene.types.ts b/src/api/iot/rule/scene/scene.types.ts index a9ba63b9c..29fc5f533 100644 --- a/src/api/iot/rule/scene/scene.types.ts +++ b/src/api/iot/rule/scene/scene.types.ts @@ -2,15 +2,7 @@ * IoT 场景联动接口定义 */ -// TODO @puhui999:枚举挪到 views/iot/utils/constants.ts 里 -// 枚举定义 -const IotRuleSceneTriggerTypeEnum = { - DEVICE_STATE_UPDATE: 1, // 设备上下线变更 - DEVICE_PROPERTY_POST: 2, // 物模型属性上报 - DEVICE_EVENT_POST: 3, // 设备事件上报 - DEVICE_SERVICE_INVOKE: 4, // 设备服务调用 - TIMER: 100 // 定时触发 -} as const +// 枚举定义已迁移到 constants.ts,这里不再重复导出 const IotRuleSceneActionTypeEnum = { DEVICE_PROPERTY_SET: 1, // 设备属性设置, @@ -25,11 +17,7 @@ const IotDeviceMessageTypeEnum = { EVENT: 'event' // 事件 } as const -// TODO @puhui999:这个貌似可以不要? -const IotDeviceMessageIdentifierEnum = { - PROPERTY_SET: 'set', // 属性设置 - SERVICE_INVOKE: '${identifier}' // 服务调用 -} as const +// 已删除不需要的 IotDeviceMessageIdentifierEnum const IotRuleSceneTriggerConditionParameterOperatorEnum = { EQUALS: { name: '等于', value: '=' }, // 等于 @@ -64,29 +52,10 @@ const IotRuleSceneTriggerTimeOperatorEnum = { TODAY: { name: '在今日之间', value: 'today' } // 在今日之间 } as const -// TODO @puhui999:下面 IotAlertConfigReceiveTypeEnum、DeviceStateEnum 没用到,貌似可以删除下? -const IotAlertConfigReceiveTypeEnum = { - SMS: 1, // 短信 - MAIL: 2, // 邮箱 - NOTIFY: 3 // 通知 -} as const - -// 设备状态枚举 -const DeviceStateEnum = { - INACTIVE: 0, // 未激活 - ONLINE: 1, // 在线 - OFFLINE: 2 // 离线 -} as const - -// TODO @puhui999:这个全局已经有啦 -// 通用状态枚举 -const CommonStatusEnum = { - ENABLE: 0, // 开启 - DISABLE: 1 // 关闭 -} as const +// 已删除未使用的枚举:IotAlertConfigReceiveTypeEnum、DeviceStateEnum +// CommonStatusEnum 已在全局定义,这里不再重复定义 -// 基础接口 -// TODO @puhui999:这个貌似可以不要? +// 基础接口(如果项目中有全局的 BaseDO,可以使用全局的) interface TenantBaseDO { createTime?: Date // 创建时间 updateTime?: Date // 更新时间 @@ -144,7 +113,7 @@ interface RuleSceneFormData { name: string description?: string status: number - trigger: TriggerFormData + triggers: TriggerFormData[] // 支持多个触发器 actions: ActionFormData[] } @@ -209,8 +178,7 @@ interface IotRuleScene extends TenantBaseDO { } // 工具类型 - 从枚举中提取类型 -export type TriggerType = - (typeof IotRuleSceneTriggerTypeEnum)[keyof typeof IotRuleSceneTriggerTypeEnum] +// TriggerType 现在从 constants.ts 中的枚举提取 export type ActionType = (typeof IotRuleSceneActionTypeEnum)[keyof typeof IotRuleSceneActionTypeEnum] export type MessageType = (typeof IotDeviceMessageTypeEnum)[keyof typeof IotDeviceMessageTypeEnum] @@ -246,16 +214,11 @@ export { ConditionGroupContainerFormData, SubConditionGroupFormData, ConditionFormData, - IotRuleSceneTriggerTypeEnum, IotRuleSceneActionTypeEnum, IotDeviceMessageTypeEnum, - IotDeviceMessageIdentifierEnum, IotRuleSceneTriggerConditionParameterOperatorEnum, IotRuleSceneTriggerConditionTypeEnum, IotRuleSceneTriggerTimeOperatorEnum, - IotAlertConfigReceiveTypeEnum, - DeviceStateEnum, - CommonStatusEnum, ValidationRule, FormValidationRules } diff --git a/src/views/iot/rule/scene/form/RuleSceneForm.vue b/src/views/iot/rule/scene/form/RuleSceneForm.vue index 6c4a96859..57437196d 100644 --- a/src/views/iot/rule/scene/form/RuleSceneForm.vue +++ b/src/views/iot/rule/scene/form/RuleSceneForm.vue @@ -9,12 +9,12 @@ :close-on-press-escape="false" @close="handleClose" > - + - + @@ -40,15 +40,21 @@ import BasicInfoSection from './sections/BasicInfoSection.vue' import TriggerSection from './sections/TriggerSection.vue' import ActionSection from './sections/ActionSection.vue' import { - CommonStatusEnum, IotRuleScene, IotRuleSceneActionTypeEnum, - IotRuleSceneTriggerTypeEnum, - RuleSceneFormData + RuleSceneFormData, + TriggerFormData } from '@/api/iot/rule/scene/scene.types' +import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants' import { ElMessage } from 'element-plus' import { generateUUID } from '@/utils' +// 导入全局的 CommonStatusEnum +const CommonStatusEnum = { + ENABLE: 0, // 开启 + DISABLE: 1 // 关闭 +} as const + /** IoT 场景联动规则表单 - 主表单组件 */ defineOptions({ name: 'RuleSceneForm' }) @@ -76,17 +82,19 @@ const createDefaultFormData = (): RuleSceneFormData => { name: '', description: '', status: CommonStatusEnum.ENABLE, // 默认启用状态 - trigger: { - type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, - productId: undefined, - deviceId: undefined, - identifier: undefined, - operator: undefined, - value: undefined, - cronExpression: undefined, - mainCondition: undefined, - conditionGroup: undefined - }, + triggers: [ + { + type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, + productId: undefined, + deviceId: undefined, + identifier: undefined, + operator: undefined, + value: undefined, + cronExpression: undefined, + mainCondition: undefined, + conditionGroup: undefined + } + ], actions: [] } } @@ -95,13 +103,13 @@ const createDefaultFormData = (): RuleSceneFormData => { * 将表单数据转换为 API 请求格式 */ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => { - // 构建触发器条件 - const buildTriggerConditions = () => { + // 构建单个触发器的条件 + const buildTriggerConditions = (trigger: TriggerFormData) => { const conditions: any[] = [] // 处理主条件 - if (formData.trigger.mainCondition) { - const mainCondition = formData.trigger.mainCondition + if (trigger.mainCondition) { + const mainCondition = trigger.mainCondition conditions.push({ type: mainCondition.type === 2 ? 'property' : 'event', identifier: mainCondition.identifier || '', @@ -115,8 +123,8 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => { } // 处理条件组 - if (formData.trigger.conditionGroup?.subGroups) { - formData.trigger.conditionGroup.subGroups.forEach((subGroup) => { + if (trigger.conditionGroup?.subGroups) { + trigger.conditionGroup.subGroups.forEach((subGroup) => { subGroup.conditions.forEach((condition) => { conditions.push({ type: condition.type === 2 ? 'property' : 'event', @@ -140,19 +148,13 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => { name: formData.name, description: formData.description, status: Number(formData.status), - triggers: [ - { - type: formData.trigger.type, - productKey: formData.trigger.productId - ? `product_${formData.trigger.productId}` - : undefined, - deviceNames: formData.trigger.deviceId - ? [`device_${formData.trigger.deviceId}`] - : undefined, - cronExpression: formData.trigger.cronExpression, - conditions: buildTriggerConditions() - } - ], + triggers: formData.triggers.map((trigger) => ({ + type: trigger.type, + productKey: trigger.productId ? `product_${trigger.productId}` : undefined, + deviceNames: trigger.deviceId ? [`device_${trigger.deviceId}`] : undefined, + cronExpression: trigger.cronExpression, + conditions: buildTriggerConditions(trigger) + })), actions: formData.actions?.map((action) => ({ type: action.type, @@ -180,9 +182,7 @@ const convertFormToVO = (formData: RuleSceneFormData): IotRuleScene => { * 将 API 响应数据转换为表单格式 */ const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => { - const firstTrigger = apiData.triggers?.[0] - - // 解析触发器条件 + // 解析单个触发器的条件 const parseConditions = (trigger: any) => { if (!trigger?.conditions?.length) { return { @@ -208,28 +208,23 @@ const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => { } } - const conditionData = firstTrigger - ? parseConditions(firstTrigger) - : { - mainCondition: undefined, - conditionGroup: undefined - } - - return { - ...apiData, - status: Number(apiData.status), - trigger: firstTrigger - ? { - type: Number(firstTrigger.type), + // 转换所有触发器 + 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: firstTrigger.cronExpression, + cronExpression: trigger.cronExpression, ...conditionData } - : { + }) + : [ + { type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, productId: undefined, deviceId: undefined, @@ -239,7 +234,13 @@ const convertVOToForm = (apiData: IotRuleScene): RuleSceneFormData => { cronExpression: undefined, mainCondition: undefined, conditionGroup: undefined - }, + } + ] + + return { + ...apiData, + status: Number(apiData.status), + triggers, actions: apiData.actions?.map((action) => ({ ...action, diff --git a/src/views/iot/rule/scene/form/configs/ConditionGroupConfig.vue b/src/views/iot/rule/scene/form/configs/ConditionGroupConfig.vue index dff6fe598..6aad617fa 100644 --- a/src/views/iot/rule/scene/form/configs/ConditionGroupConfig.vue +++ b/src/views/iot/rule/scene/form/configs/ConditionGroupConfig.vue @@ -114,11 +114,8 @@ diff --git a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index c054ae737..23c492a91 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -1,11 +1,5 @@ @@ -117,17 +89,14 @@ import { ConditionFormData } from '@/api/iot/rule/scene/scene.types' /** 设备状态条件配置组件 */ defineOptions({ name: 'DeviceStatusConditionConfig' }) -interface Props { +const props = defineProps<{ modelValue: ConditionFormData -} +}>() -interface Emits { +const emit = defineEmits<{ (e: 'update:modelValue', value: ConditionFormData): void (e: 'validate', result: { valid: boolean; message: string }): void -} - -const props = defineProps() -const emit = defineEmits() +}>() const condition = useVModel(props, 'modelValue', emit) @@ -169,22 +138,6 @@ const statusOperatorOptions = [ const validationMessage = ref('') const isValid = ref(true) -// 计算属性 -const conditionPreview = computed(() => { - if (!condition.value.param || !condition.value.operator) { - return '' - } - - const statusLabel = - deviceStatusOptions.find((opt) => opt.value === condition.value.param)?.label || - condition.value.param - const operatorLabel = - statusOperatorOptions.find((opt) => opt.value === condition.value.operator)?.label || - condition.value.operator - - return `设备状态 ${operatorLabel} ${statusLabel}` -}) - // 事件处理 const updateConditionField = (field: keyof ConditionFormData, value: any) => { condition.value[field] = value diff --git a/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue index 5e41a5f00..e16d558ec 100644 --- a/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue @@ -68,14 +68,13 @@ import { IotRuleSceneTriggerTypeEnum as TriggerTypeEnum } from '@/views/iot/util /** 设备触发配置组件 */ defineOptions({ name: 'DeviceTriggerConfig' }) -// Props 和 Emits 定义 const props = defineProps<{ modelValue: TriggerFormData }>() const emit = defineEmits<{ - 'update:modelValue': [value: TriggerFormData] - validate: [result: { valid: boolean; message: string }] + (e: 'update:modelValue', value: TriggerFormData): void + (e: 'validate', value: { valid: boolean; message: string }): void }>() const trigger = useVModel(props, 'modelValue', emit) @@ -126,7 +125,6 @@ const addConditionGroup = () => { } // 事件处理 - const handleConditionGroupValidate = () => { updateValidationResult() } diff --git a/src/views/iot/rule/scene/form/configs/MainConditionConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionConfig.vue index b652bae0d..a3342bd73 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionConfig.vue @@ -51,6 +51,7 @@ defineProps<{ modelValue?: ConditionFormData triggerType: number }>() + const emit = defineEmits<{ (e: 'update:modelValue', value?: ConditionFormData): void (e: 'validate', result: { valid: boolean; message: string }): void diff --git a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue index 709cf7356..9fec9b792 100644 --- a/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -104,19 +104,15 @@ import { useVModel } from '@vueuse/core' /** 主条件内部配置组件 */ defineOptions({ name: 'MainConditionInnerConfig' }) -interface Props { +const props = defineProps<{ modelValue: ConditionFormData triggerType: number -} +}>() -interface Emits { +const emit = defineEmits<{ (e: 'update:modelValue', value: ConditionFormData): void - (e: 'validate', result: { valid: boolean; message: string }): void -} - -const props = defineProps() -const emit = defineEmits() +}>() // 响应式数据 const condition = useVModel(props, 'modelValue', emit) diff --git a/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue index 3cd8167e7..62ed91d80 100644 --- a/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue +++ b/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue @@ -98,20 +98,16 @@ import { /** 子条件组配置组件 */ defineOptions({ name: 'SubConditionGroupConfig' }) -interface Props { +const props = defineProps<{ modelValue: SubConditionGroupFormData triggerType: number maxConditions?: number -} +}>() -interface Emits { +const emit = defineEmits<{ (e: 'update:modelValue', value: SubConditionGroupFormData): void - (e: 'validate', result: { valid: boolean; message: string }): void -} - -const props = defineProps() -const emit = defineEmits() +}>() const subGroup = useVModel(props, 'modelValue', emit) diff --git a/src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue b/src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue index 4e2f10248..087939fd0 100644 --- a/src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue @@ -25,18 +25,13 @@ import { Crontab } from '@/components/Crontab' /** 定时触发配置组件 */ defineOptions({ name: 'TimerTriggerConfig' }) -// TODO @puhui999:下面的 Props、Emits 可以合并到变量那; -interface Props { +const props = defineProps<{ modelValue?: string -} - -interface Emits { +}>() +const emit = defineEmits<{ (e: 'update:modelValue', value: string): void (e: 'validate', result: { valid: boolean; message: string }): void -} - -const props = defineProps() -const emit = defineEmits() +}>() const localValue = useVModel(props, 'modelValue', emit, { defaultValue: '0 0 12 * * ?' diff --git a/src/views/iot/rule/scene/form/selectors/ConditionTypeSelector.vue b/src/views/iot/rule/scene/form/selectors/ConditionTypeSelector.vue index c955ceffa..60a11f184 100644 --- a/src/views/iot/rule/scene/form/selectors/ConditionTypeSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/ConditionTypeSelector.vue @@ -29,17 +29,14 @@ import { IotRuleSceneTriggerConditionTypeEnum } from '@/api/iot/rule/scene/scene /** 条件类型选择器组件 */ defineOptions({ name: 'ConditionTypeSelector' }) -interface Props { +defineProps<{ modelValue?: number -} +}>() -interface Emits { +const emit = defineEmits<{ (e: 'update:modelValue', value: number): void (e: 'change', value: number): void -} - -const props = defineProps() -const emit = defineEmits() +}>() // 条件类型选项 const conditionTypeOptions = [ diff --git a/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue b/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue index 8afdf623d..28e7e689a 100644 --- a/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue @@ -18,9 +18,9 @@ >
-
{{ - device.deviceName - }}
+
{{ device.deviceName }} +
{{ device.deviceKey }}
@@ -42,18 +42,15 @@ import { DeviceApi } from '@/api/iot/device/device' /** 设备选择器组件 */ defineOptions({ name: 'DeviceSelector' }) -interface Props { +const props = defineProps<{ modelValue?: number productId?: number -} +}>() -interface Emits { +const emit = defineEmits<{ (e: 'update:modelValue', value?: number): void (e: 'change', value?: number): void -} - -const props = defineProps() -const emit = defineEmits() +}>() // 状态 const deviceLoading = ref(false) diff --git a/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue b/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue index 838302f1f..513fea4b9 100644 --- a/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue @@ -39,19 +39,15 @@ import { useVModel } from '@vueuse/core' /** 操作符选择器组件 */ defineOptions({ name: 'OperatorSelector' }) -interface Props { +const props = defineProps<{ modelValue?: string propertyType?: string -} +}>() -interface Emits { +const emit = defineEmits<{ (e: 'update:modelValue', value: string): void - (e: 'change', value: string): void -} - -const props = defineProps() -const emit = defineEmits() +}>() const localValue = useVModel(props, 'modelValue', emit) diff --git a/src/views/iot/rule/scene/form/selectors/ProductDeviceSelector.vue b/src/views/iot/rule/scene/form/selectors/ProductDeviceSelector.vue index bae5b7076..3132e98d8 100644 --- a/src/views/iot/rule/scene/form/selectors/ProductDeviceSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/ProductDeviceSelector.vue @@ -22,13 +22,14 @@ >
-
{{ product.name }}
-
{{ product.productKey }}
+
{{ product.name }} +
+
{{ product.productKey }} +
- - - {{ product.status === 0 ? '正常' : '禁用' }} - +
@@ -70,8 +71,12 @@ >
-
{{ device.deviceName }}
-
{{ device.nickname || '无备注' }}
+
{{ device.deviceName }} +
+
{{ device.nickname || '无备注' }} +
{{ getDeviceStatusText(device.state) }} @@ -84,7 +89,10 @@ -
+
已选择设备 @@ -92,14 +100,22 @@
产品: - {{ selectedProduct?.name }} + {{ + selectedProduct?.name + }} {{ selectedProduct?.productKey }}
设备: - 全部设备 - {{ selectedDevice?.deviceName }} - 全部 + 全部设备 + {{ + selectedDevice?.deviceName + }} + 全部 {{ getDeviceStatusText(selectedDevice?.state) }} @@ -113,6 +129,7 @@ import { useVModel } from '@vueuse/core' import { ProductApi } from '@/api/iot/product/product' import { DeviceApi } from '@/api/iot/device/device' +import { DICT_TYPE } from '@/utils/dict' /** 产品设备选择器组件 */ defineOptions({ name: 'ProductDeviceSelector' }) @@ -124,7 +141,9 @@ interface Props { interface Emits { (e: 'update:productId', value?: number): void + (e: 'update:deviceId', value?: number): void + (e: 'change', value: { productId?: number; deviceId?: number }): void } diff --git a/src/views/iot/rule/scene/form/selectors/ProductSelector.vue b/src/views/iot/rule/scene/form/selectors/ProductSelector.vue index 70d95964c..56f8c648b 100644 --- a/src/views/iot/rule/scene/form/selectors/ProductSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/ProductSelector.vue @@ -17,16 +17,14 @@ >
-
{{ - product.name - }}
-
{{ - product.productKey - }}
+
{{ product.name }} +
+
{{ product.productKey }} +
- - {{ product.status === 0 ? '正常' : '禁用' }} - +
@@ -34,21 +32,19 @@ diff --git a/src/views/iot/rule/scene/form/configs/ConditionConfig.vue b/src/views/iot/rule/scene/form/configs/ConditionConfig.vue index 79c580b7c..95bf1ab50 100644 --- a/src/views/iot/rule/scene/form/configs/ConditionConfig.vue +++ b/src/views/iot/rule/scene/form/configs/ConditionConfig.vue @@ -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) } diff --git a/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue b/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue index 8ad0b1144..caa2ec9b2 100644 --- a/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue +++ b/src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue @@ -14,16 +14,14 @@ 附加条件组
与主条件为且关系 - - {{ modelValue.subGroups?.length || 0 }}个子条件组 - + {{ modelValue?.length || 0 }}个子条件组
添加子条件组 @@ -36,11 +34,11 @@
-
+
@@ -88,7 +86,7 @@
@@ -125,21 +123,17 @@ - - diff --git a/src/views/iot/rule/scene/form/selectors/ActionTypeSelector.vue b/src/views/iot/rule/scene/form/selectors/ActionTypeSelector.vue index da86b7eda..77a422e69 100644 --- a/src/views/iot/rule/scene/form/selectors/ActionTypeSelector.vue +++ b/src/views/iot/rule/scene/form/selectors/ActionTypeSelector.vue @@ -17,10 +17,17 @@ >
- +
-
{{ option.label }}
-
{{ option.description }}
+
{{ + option.label + }}
+
{{ + option.description + }}
@@ -35,7 +42,7 @@