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) + const apiData = convertFormToVO(formData.value) - // 这里应该调用API保存数据 - // TODO @puhui999:貌似还没接入 - console.log('提交数据:', apiData) + // 调用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: '未知的执行类型' } -}