Browse Source

perf:【IoT 物联网】场景联动主表单优化去除多余样式

puhui999 10 tháng trước cách đây
mục cha
commit
c740da02b9

+ 0 - 4
src/api/iot/rule/scene/scene.types.ts

@@ -256,10 +256,6 @@ export {
   IotAlertConfigReceiveTypeEnum,
   DeviceStateEnum,
   CommonStatusEnum,
-  TriggerType,
-  ActionType,
-  MessageType,
-  OperatorType,
   ValidationRule,
   FormValidationRules
 }

+ 174 - 116
src/views/iot/rule/scene/form/RuleSceneForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <!-- TODO @puhui999:这个抽屉的高度太高了?! -->
+  <!-- 场景联动规则表单抽屉 - 优化高度和布局 -->
   <el-drawer
     v-model="drawerVisible"
     :title="drawerTitle"
@@ -8,29 +8,28 @@
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     @close="handleClose"
-    class="[--el-drawer-padding-primary:20px]"
   >
-    <div class="h-[calc(100vh-120px)] overflow-y-auto p-20px pb-80px">
-      <el-form
-        ref="formRef"
-        :model="formData"
-        :rules="formRules"
-        label-width="120px"
-        class="flex flex-col gap-24px"
-      >
-        <!-- 基础信息配置 -->
-        <BasicInfoSection v-model="formData" :rules="formRules" />
-
-        <!-- 触发器配置 -->
-        <TriggerSection v-model:trigger="formData.trigger" @validate="handleTriggerValidate" />
-
-        <!-- 执行器配置 -->
-        <ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
-      </el-form>
-    </div>
+    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
+      <!-- 基础信息配置 -->
+      <BasicInfoSection v-model="formData" :rules="formRules" />
+
+      <!-- 触发器配置 -->
+      <TriggerSection v-model:trigger="formData.trigger" @validate="handleTriggerValidate" />
+
+      <!-- 执行器配置 -->
+      <ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
+    </el-form>
     <template #footer>
-      <el-button :disabled="submitLoading" type="primary" @click="handleSubmit">确 定</el-button>
-      <el-button @click="handleClose">取 消</el-button>
+      <div class="drawer-footer">
+        <el-button :disabled="submitLoading" type="primary" @click="handleSubmit">
+          <Icon icon="ep:check" />
+          确 定
+        </el-button>
+        <el-button @click="handleClose">
+          <Icon icon="ep:close" />
+          取 消
+        </el-button>
+      </div>
     </template>
   </el-drawer>
 </template>
@@ -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<Props>()
-const emit = defineEmits<Emits>()
+/** 组件事件定义 */
+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<RuleSceneFormData>(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(
   }
 )
 </script>
-
-<!-- TODO @puhui999:看看下面样式,哪些是必要添加的 -->
-<style scoped>
-/* 滚动条样式 */
-.h-\[calc\(100vh-120px\)\]::-webkit-scrollbar {
-  width: 6px;
-}
-
-.h-\[calc\(100vh-120px\)\]::-webkit-scrollbar-track {
-  background: var(--el-fill-color-light);
-  border-radius: 3px;
-}
-
-.h-\[calc\(100vh-120px\)\]::-webkit-scrollbar-thumb {
-  background: var(--el-border-color);
-  border-radius: 3px;
-}
-
-.h-\[calc\(100vh-120px\)\]::-webkit-scrollbar-thumb:hover {
-  background: var(--el-border-color-dark);
-}
-
-/* 抽屉内容区域优化 */
-:deep(.el-drawer__body) {
-  padding: 0;
-  position: relative;
-}
-
-:deep(.el-drawer__header) {
-  padding: 20px 20px 16px 20px;
-  border-bottom: 1px solid var(--el-border-color-light);
-  margin-bottom: 0;
-}
-
-:deep(.el-drawer__title) {
-  font-size: 18px;
-  font-weight: 600;
-  color: var(--el-text-color-primary);
-}
-
-/* 响应式设计 */
-@media (max-width: 768px) {
-  .el-drawer {
-    --el-drawer-size: 100% !important;
-  }
-
-  .h-\[calc\(100vh-120px\)\] {
-    padding: 16px;
-    padding-bottom: 80px;
-  }
-
-  .flex.flex-col.gap-24px {
-    gap: 20px;
-  }
-
-  .absolute.bottom-0 {
-    padding: 12px 16px;
-    gap: 12px;
-  }
-}
-</style>

+ 0 - 188
src/views/iot/rule/scene/utils/validation.ts

@@ -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: '未知的执行类型' }
-}