Przeglądaj źródła

perf:【IoT 物联网】场景联动表单校验规则优化

puhui999 10 miesięcy temu
rodzic
commit
00e193d3e2

+ 110 - 35
src/views/iot/rule/scene/form/RuleSceneForm.vue

@@ -12,9 +12,9 @@
       <!-- 基础信息配置 -->
       <BasicInfoSection v-model="formData" :rules="formRules" />
       <!-- 触发器配置 -->
-      <TriggerSection v-model:triggers="formData.triggers" @validate="handleTriggerValidate" />
+      <TriggerSection v-model:triggers="formData.triggers" />
       <!-- 执行器配置 -->
-      <ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
+      <ActionSection v-model:actions="formData.actions" />
     </el-form>
     <template #footer>
       <div class="drawer-footer">
@@ -38,7 +38,11 @@ import TriggerSection from './sections/TriggerSection.vue'
 import ActionSection from './sections/ActionSection.vue'
 import { IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
 import { RuleSceneApi } from '@/api/iot/rule/scene'
-import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
+import {
+  IotRuleSceneTriggerTypeEnum,
+  IotRuleSceneActionTypeEnum,
+  isDeviceTrigger
+} from '@/views/iot/utils/constants'
 import { ElMessage } from 'element-plus'
 import { generateUUID } from '@/utils'
 
@@ -174,6 +178,107 @@ const convertVOToForm = (apiData: IotRuleSceneDO): RuleSceneFormData => {
 // 表单数据和状态
 const formRef = ref()
 const formData = ref<RuleSceneFormData>(createDefaultFormData())
+// 自定义校验器
+const validateTriggers = (rule: any, value: any, callback: any) => {
+  if (!value || !Array.isArray(value) || value.length === 0) {
+    callback(new Error('至少需要一个触发器'))
+    return
+  }
+
+  for (let i = 0; i < value.length; i++) {
+    const trigger = value[i]
+
+    // 校验触发器类型
+    if (!trigger.type) {
+      callback(new Error(`触发器 ${i + 1}: 触发器类型不能为空`))
+      return
+    }
+
+    // 校验设备触发器
+    if (isDeviceTrigger(trigger.type)) {
+      if (!trigger.productId) {
+        callback(new Error(`触发器 ${i + 1}: 产品不能为空`))
+        return
+      }
+      if (!trigger.deviceId) {
+        callback(new Error(`触发器 ${i + 1}: 设备不能为空`))
+        return
+      }
+      if (!trigger.identifier) {
+        callback(new Error(`触发器 ${i + 1}: 物模型标识符不能为空`))
+        return
+      }
+      if (!trigger.operator) {
+        callback(new Error(`触发器 ${i + 1}: 操作符不能为空`))
+        return
+      }
+      if (trigger.value === undefined || trigger.value === null || trigger.value === '') {
+        callback(new Error(`触发器 ${i + 1}: 参数值不能为空`))
+        return
+      }
+    }
+
+    // 校验定时触发器
+    if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
+      if (!trigger.cronExpression) {
+        callback(new Error(`触发器 ${i + 1}: CRON表达式不能为空`))
+        return
+      }
+    }
+  }
+
+  callback()
+}
+
+const validateActions = (rule: any, value: any, callback: any) => {
+  if (!value || !Array.isArray(value) || value.length === 0) {
+    callback(new Error('至少需要一个执行器'))
+    return
+  }
+
+  for (let i = 0; i < value.length; i++) {
+    const action = value[i]
+
+    // 校验执行器类型
+    if (!action.type) {
+      callback(new Error(`执行器 ${i + 1}: 执行器类型不能为空`))
+      return
+    }
+
+    // 校验设备控制执行器
+    if (
+      action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
+      action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
+    ) {
+      if (!action.productId) {
+        callback(new Error(`执行器 ${i + 1}: 产品不能为空`))
+        return
+      }
+      if (!action.deviceId) {
+        callback(new Error(`执行器 ${i + 1}: 设备不能为空`))
+        return
+      }
+      if (!action.params || Object.keys(action.params).length === 0) {
+        callback(new Error(`执行器 ${i + 1}: 参数配置不能为空`))
+        return
+      }
+    }
+
+    // 校验告警执行器
+    if (
+      action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER ||
+      action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER
+    ) {
+      if (!action.alertConfigId) {
+        callback(new Error(`执行器 ${i + 1}: 告警配置不能为空`))
+        return
+      }
+    }
+  }
+
+  callback()
+}
+
 const formRules = reactive({
   name: [
     { required: true, message: '场景名称不能为空', trigger: 'blur' },
@@ -191,36 +296,16 @@ const formRules = reactive({
   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' }
-  ]
+  triggers: [{ required: true, validator: validateTriggers, trigger: 'change' }],
+  actions: [{ required: true, validator: validateActions, trigger: 'change' }]
 })
 
 const submitLoading = ref(false)
 
-// 验证状态
-const triggerValidation = ref({ valid: true, message: '' })
-const actionValidation = ref({ valid: true, message: '' })
-
 // 计算属性
 const isEdit = ref(false)
 const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则'))
 
-// TODO @puhui999:方法的注释风格统一;
-// 事件处理
-const handleTriggerValidate = (result: { valid: boolean; message: string }) => {
-  triggerValidation.value = result
-}
-
-const handleActionValidate = (result: { valid: boolean; message: string }) => {
-  actionValidation.value = result
-}
-
 /** 提交表单 */
 const handleSubmit = async () => {
   // 校验表单
@@ -228,16 +313,6 @@ const handleSubmit = async () => {
   const valid = await formRef.value.validate()
   if (!valid) return
 
-  // 验证触发器和执行器
-  if (!triggerValidation.value.valid) {
-    ElMessage.error(triggerValidation.value.message)
-    return
-  }
-  if (!actionValidation.value.valid) {
-    ElMessage.error(actionValidation.value.message)
-    return
-  }
-
   // 提交请求
   submitLoading.value = true
   try {

+ 22 - 65
src/views/iot/rule/scene/form/configs/AlertConfig.vue

@@ -20,8 +20,12 @@
         >
           <div class="flex items-center justify-between w-full py-4px">
             <div class="flex-1">
-              <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">{{ config.name }}</div>
-              <div class="text-12px text-[var(--el-text-color-secondary)]">{{ config.description }}</div>
+              <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">{{
+                config.name
+              }}</div>
+              <div class="text-12px text-[var(--el-text-color-secondary)]">{{
+                config.description
+              }}</div>
             </div>
             <el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
               {{ config.enabled ? '启用' : '禁用' }}
@@ -32,10 +36,15 @@
     </el-form-item>
 
     <!-- 告警配置详情 -->
-    <div v-if="selectedConfig" class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
+    <div
+      v-if="selectedConfig"
+      class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"
+    >
       <div class="flex items-center gap-8px mb-12px">
         <Icon icon="ep:bell" class="text-[var(--el-color-warning)] text-16px" />
-        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">{{ selectedConfig.name }}</span>
+        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">{{
+          selectedConfig.name
+        }}</span>
         <el-tag :type="selectedConfig.enabled ? 'success' : 'danger'" size="small">
           {{ selectedConfig.enabled ? '启用' : '禁用' }}
         </el-tag>
@@ -43,28 +52,24 @@
       <div class="space-y-8px">
         <div class="flex items-start gap-8px">
           <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">描述:</span>
-          <span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedConfig.description }}</span>
+          <span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
+            selectedConfig.description
+          }}</span>
         </div>
         <div class="flex items-start gap-8px">
           <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">通知方式:</span>
-          <span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ getNotifyTypeName(selectedConfig.notifyType) }}</span>
+          <span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
+            getNotifyTypeName(selectedConfig.notifyType)
+          }}</span>
         </div>
         <div v-if="selectedConfig.receivers" class="flex items-start gap-8px">
           <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px">接收人:</span>
-          <span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{ selectedConfig.receivers.join(', ') }}</span>
+          <span class="text-12px text-[var(--el-text-color-primary)] flex-1">{{
+            selectedConfig.receivers.join(', ')
+          }}</span>
         </div>
       </div>
     </div>
-
-    <!-- 验证结果 -->
-    <div v-if="validationMessage" class="mt-16px">
-      <el-alert
-        :title="validationMessage"
-        :type="isValid ? 'success' : 'error'"
-        :closable="false"
-        show-icon
-      />
-    </div>
   </div>
 </template>
 
@@ -80,7 +85,6 @@ interface Props {
 
 interface Emits {
   (e: 'update:modelValue', value?: number): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
 }
 
 const props = defineProps<Props>()
@@ -91,8 +95,6 @@ const localValue = useVModel(props, 'modelValue', emit)
 // 状态
 const loading = ref(false)
 const alertConfigs = ref<any[]>([])
-const validationMessage = ref('')
-const isValid = ref(true)
 
 // 计算属性
 const selectedConfig = computed(() => {
@@ -110,40 +112,6 @@ const getNotifyTypeName = (type: number) => {
   return typeMap[type] || '未知'
 }
 
-// 事件处理
-const handleChange = () => {
-  updateValidationResult()
-}
-
-const updateValidationResult = () => {
-  if (!localValue.value) {
-    isValid.value = false
-    validationMessage.value = '请选择告警配置'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  const config = selectedConfig.value
-  if (!config) {
-    isValid.value = false
-    validationMessage.value = '告警配置不存在'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  if (!config.enabled) {
-    isValid.value = false
-    validationMessage.value = '选择的告警配置已禁用'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  // 验证通过
-  isValid.value = true
-  validationMessage.value = '告警配置验证通过'
-  emit('validate', { valid: true, message: validationMessage.value })
-}
-
 // API 调用
 const getAlertConfigs = async () => {
   loading.value = true
@@ -184,20 +152,9 @@ const getAlertConfigs = async () => {
   }
 }
 
-// 监听值变化
-watch(
-  () => localValue.value,
-  () => {
-    updateValidationResult()
-  }
-)
-
 // 初始化
 onMounted(() => {
   getAlertConfigs()
-  if (localValue.value) {
-    updateValidationResult()
-  }
 })
 </script>
 

+ 1 - 41
src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue

@@ -39,16 +39,6 @@
         </el-alert>
       </div>
     </div>
-
-    <!-- 验证结果 -->
-    <div v-if="validationMessage" class="mt-16px">
-      <el-alert
-        :title="validationMessage"
-        :type="isValid ? 'success' : 'error'"
-        :closable="false"
-        show-icon
-      />
-    </div>
   </div>
 </template>
 
@@ -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()
 })
 
 // 监听参数变化

+ 0 - 74
src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue

@@ -6,7 +6,6 @@
       <MainConditionConfig
         v-model="trigger"
         :trigger-type="trigger.type"
-        @validate="handleMainConditionValidate"
         @trigger-type-change="handleTriggerTypeChange"
       />
     </div>
@@ -17,7 +16,6 @@
       <ConditionGroupContainerConfig
         v-model="trigger.conditionGroups"
         :trigger-type="trigger.type"
-        @validate="handleConditionGroupValidate"
         @remove="removeConditionGroup"
       />
     </div>
@@ -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()
-  }
-)
 </script>

+ 0 - 66
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)"
             />
           </div>
         </div>
@@ -100,16 +98,6 @@
           最多可添加 {{ maxActions }} 个执行器
         </span>
       </div>
-
-      <!-- 验证结果 -->
-      <div v-if="validationMessage" class="validation-result">
-        <el-alert
-          :title="validationMessage"
-          :type="isValid ? 'success' : 'error'"
-          :closable="false"
-          show-icon
-        />
-      </div>
     </div>
   </el-card>
 </template>
@@ -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<Props>()
@@ -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()
-  }
-)
 </script>