Browse Source

perf:【IoT 物联网】场景联动触发器优化

puhui999 10 months ago
parent
commit
d7b4db9b4e

+ 45 - 8
src/api/iot/rule/scene/scene.types.ts

@@ -46,6 +46,24 @@ const IotRuleSceneTriggerConditionParameterOperatorEnum = {
   NOT_NULL: { name: '非空', value: 'not null' } // 非空
 } as const
 
+// 条件类型枚举
+const IotRuleSceneTriggerConditionTypeEnum = {
+  DEVICE_STATUS: 1, // 设备状态
+  DEVICE_PROPERTY: 2, // 设备属性
+  CURRENT_TIME: 3 // 当前时间
+} as const
+
+// 时间运算符枚举
+const IotRuleSceneTriggerTimeOperatorEnum = {
+  BEFORE_TIME: { name: '在时间之前', value: 'before_time' }, // 在时间之前
+  AFTER_TIME: { name: '在时间之后', value: 'after_time' }, // 在时间之后
+  BETWEEN_TIME: { name: '在时间之间', value: 'between_time' }, // 在时间之间
+  AT_TIME: { name: '在指定时间', value: 'at_time' }, // 在指定时间
+  BEFORE_TODAY: { name: '在今日之前', value: 'before_today' }, // 在今日之前
+  AFTER_TODAY: { name: '在今日之后', value: 'after_today' }, // 在今日之后
+  TODAY: { name: '在今日之间', value: 'today' } // 在今日之间
+} as const
+
 // TODO @puhui999:下面 IotAlertConfigReceiveTypeEnum、DeviceStateEnum 没用到,貌似可以删除下?
 const IotAlertConfigReceiveTypeEnum = {
   SMS: 1, // 短信
@@ -126,7 +144,7 @@ interface RuleSceneFormData {
   name: string
   description?: string
   status: number
-  triggers: TriggerFormData[]
+  trigger: TriggerFormData // 改为单个触发器
   actions: ActionFormData[]
 }
 
@@ -138,7 +156,9 @@ interface TriggerFormData {
   operator?: string
   value?: string
   cronExpression?: string
-  conditionGroups?: ConditionGroupFormData[]
+  // 新的条件结构
+  mainCondition?: ConditionFormData // 主条件(必须满足)
+  conditionGroup?: ConditionGroupContainerFormData // 条件组容器(可选,与主条件为且关系)
 }
 
 interface ActionFormData {
@@ -149,6 +169,17 @@ interface ActionFormData {
   alertConfigId?: number
 }
 
+// 条件组容器(包含多个子条件组,子条件组间为或关系)
+interface ConditionGroupContainerFormData {
+  subGroups: SubConditionGroupFormData[] // 子条件组数组,子条件组间为或关系
+}
+
+// 子条件组(内部条件为且关系)
+interface SubConditionGroupFormData {
+  conditions: ConditionFormData[] // 条件数组,条件间为且关系
+}
+
+// 保留原有接口用于兼容性
 interface ConditionGroupFormData {
   conditions: ConditionFormData[]
   // 注意:条件组内部的条件固定为"且"关系,条件组之间固定为"或"关系
@@ -157,12 +188,14 @@ interface ConditionGroupFormData {
 }
 
 interface ConditionFormData {
-  type: number
-  productId: number
-  deviceId: number
-  identifier: string
-  operator: string
-  param: string
+  type: number // 条件类型:1-设备状态,2-设备属性,3-当前时间
+  productId?: number // 产品ID(设备状态和设备属性时必填)
+  deviceId?: number // 设备ID(设备状态和设备属性时必填)
+  identifier?: string // 标识符(设备属性时必填)
+  operator: string // 操作符
+  param: string // 参数值
+  timeValue?: string // 时间值(当前时间条件时使用)
+  timeValue2?: string // 第二个时间值(时间范围条件时使用)
 }
 
 // 主接口
@@ -210,12 +243,16 @@ export {
   TriggerFormData,
   ActionFormData,
   ConditionGroupFormData,
+  ConditionGroupContainerFormData,
+  SubConditionGroupFormData,
   ConditionFormData,
   IotRuleSceneTriggerTypeEnum,
   IotRuleSceneActionTypeEnum,
   IotDeviceMessageTypeEnum,
   IotDeviceMessageIdentifierEnum,
   IotRuleSceneTriggerConditionParameterOperatorEnum,
+  IotRuleSceneTriggerConditionTypeEnum,
+  IotRuleSceneTriggerTimeOperatorEnum,
   IotAlertConfigReceiveTypeEnum,
   DeviceStateEnum,
   CommonStatusEnum,

+ 43 - 26
src/views/iot/rule/scene/form/RuleSceneForm.vue

@@ -22,7 +22,7 @@
         <BasicInfoSection v-model="formData" :rules="formRules" />
 
         <!-- 触发器配置 -->
-        <TriggerSection v-model:triggers="formData.triggers" @validate="handleTriggerValidate" />
+        <TriggerSection v-model:trigger="formData.trigger" @validate="handleTriggerValidate" />
 
         <!-- 执行器配置 -->
         <ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
@@ -45,6 +45,7 @@ import {
   RuleSceneFormData,
   IotRuleScene,
   IotRuleSceneActionTypeEnum,
+  IotRuleSceneTriggerTypeEnum,
   CommonStatusEnum
 } from '@/api/iot/rule/scene/scene.types'
 import { getBaseValidationRules } from '../utils/validation'
@@ -77,7 +78,17 @@ const createDefaultFormData = (): RuleSceneFormData => {
     name: '',
     description: '',
     status: CommonStatusEnum.ENABLE, // 默认启用状态
-    triggers: [],
+    trigger: {
+      type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
+      productId: undefined,
+      deviceId: undefined,
+      identifier: undefined,
+      operator: undefined,
+      value: undefined,
+      cronExpression: undefined,
+      mainCondition: undefined,
+      conditionGroup: undefined
+    },
     actions: []
   }
 }
@@ -91,23 +102,19 @@ const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => {
     name: formData.name,
     description: formData.description,
     status: Number(formData.status),
-    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:
-          trigger.conditionGroups?.map((group) => ({
-            type: 'property',
-            identifier: trigger.identifier || '',
-            parameters: group.conditions.map((condition) => ({
-              identifier: condition.identifier,
-              operator: condition.operator,
-              value: condition.param
-            }))
-          })) || []
-      })) || [],
+    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: [] // TODO: 实现新的条件转换逻辑
+      }
+    ],
     actions:
       formData.actions?.map((action) => ({
         type: action.type,
@@ -131,16 +138,26 @@ const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => {
  * 将 API 响应数据转换为表单格式
  */
 const transformApiToForm = (apiData: IotRuleScene): RuleSceneFormData => {
+  const firstTrigger = apiData.triggers?.[0]
   return {
     ...apiData,
     status: Number(apiData.status), // 确保状态为数字类型
-    triggers:
-      apiData.triggers?.map((trigger) => ({
-        ...trigger,
-        type: Number(trigger.type),
-        // 为每个触发器添加唯一标识符,解决组件索引重用问题
-        key: generateUUID()
-      })) || [],
+    trigger: firstTrigger
+      ? {
+          ...firstTrigger,
+          type: Number(firstTrigger.type)
+        }
+      : {
+          type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
+          productId: undefined,
+          deviceId: undefined,
+          identifier: undefined,
+          operator: undefined,
+          value: undefined,
+          cronExpression: undefined,
+          mainCondition: undefined,
+          conditionGroup: undefined
+        },
     actions:
       apiData.actions?.map((action) => ({
         ...action,

+ 163 - 47
src/views/iot/rule/scene/form/configs/ConditionConfig.vue

@@ -1,60 +1,121 @@
 <!-- 单个条件配置组件 -->
-<!-- TODO @puhui999:这里需要在对下阿里云 IoT,不太对;它是条件类型;然后选择产品、设备;接着选条件类型对应的比较; -->
 <template>
   <div class="flex flex-col gap-16px">
+    <!-- 条件类型选择 -->
     <el-row :gutter="16">
-      <!-- 属性/事件/服务选择 -->
       <el-col :span="8">
-        <el-form-item label="监控项" required>
-          <PropertySelector
-            :model-value="condition.identifier"
-            @update:model-value="(value) => updateConditionField('identifier', value)"
-            :trigger-type="triggerType"
-            :product-id="productId"
-            :device-id="deviceId"
-            @change="handlePropertyChange"
+        <el-form-item label="条件类型" required>
+          <ConditionTypeSelector
+            :model-value="condition.type"
+            @update:model-value="(value) => updateConditionField('type', value)"
+            @change="handleConditionTypeChange"
           />
         </el-form-item>
       </el-col>
+    </el-row>
 
-      <!-- 操作符选择 -->
-      <el-col :span="6">
-        <el-form-item label="操作符" required>
-          <OperatorSelector
-            :model-value="condition.operator"
-            @update:model-value="(value) => updateConditionField('operator', value)"
-            :property-type="propertyType"
-            @change="handleOperatorChange"
-          />
-        </el-form-item>
-      </el-col>
+    <!-- 设备状态条件配置 -->
+    <DeviceStatusConditionConfig
+      v-if="condition.type === ConditionTypeEnum.DEVICE_STATUS"
+      :model-value="condition"
+      @update:model-value="updateCondition"
+      @validate="handleValidate"
+    />
 
-      <!-- 值输入 -->
-      <el-col :span="10">
-        <el-form-item label="比较值" required>
-          <ValueInput
-            :model-value="condition.param"
-            @update:model-value="(value) => updateConditionField('param', value)"
-            :property-type="propertyType"
-            :operator="condition.operator"
-            :property-config="propertyConfig"
-            @validate="handleValueValidate"
-          />
-        </el-form-item>
-      </el-col>
-    </el-row>
+    <!-- 设备属性条件配置 -->
+    <div v-else-if="condition.type === ConditionTypeEnum.DEVICE_PROPERTY" class="space-y-16px">
+      <!-- 产品设备选择 -->
+      <el-row :gutter="16">
+        <el-col :span="12">
+          <el-form-item label="产品" required>
+            <ProductSelector
+              :model-value="condition.productId"
+              @update:model-value="(value) => updateConditionField('productId', value)"
+              @change="handleProductChange"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="设备" required>
+            <DeviceSelector
+              :model-value="condition.deviceId"
+              @update:model-value="(value) => updateConditionField('deviceId', value)"
+              :product-id="condition.productId"
+              @change="handleDeviceChange"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
 
-    <!-- 条件预览 -->
-    <div v-if="conditionPreview" class="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-8px">
-        <Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" />
-        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span>
-      </div>
-      <div class="pl-24px">
-        <code class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono">{{ conditionPreview }}</code>
+      <!-- 属性配置 -->
+      <el-row :gutter="16">
+        <!-- 属性/事件/服务选择 -->
+        <el-col :span="6">
+          <el-form-item label="监控项" required>
+            <PropertySelector
+              :model-value="condition.identifier"
+              @update:model-value="(value) => updateConditionField('identifier', value)"
+              :trigger-type="triggerType"
+              :product-id="condition.productId"
+              :device-id="condition.deviceId"
+              @change="handlePropertyChange"
+            />
+          </el-form-item>
+        </el-col>
+
+        <!-- 操作符选择 -->
+        <el-col :span="6">
+          <el-form-item label="操作符" required>
+            <OperatorSelector
+              :model-value="condition.operator"
+              @update:model-value="(value) => updateConditionField('operator', value)"
+              :property-type="propertyType"
+              @change="handleOperatorChange"
+            />
+          </el-form-item>
+        </el-col>
+
+        <!-- 值输入 -->
+        <el-col :span="12">
+          <el-form-item label="比较值" required>
+            <ValueInput
+              :model-value="condition.param"
+              @update:model-value="(value) => updateConditionField('param', value)"
+              :property-type="propertyType"
+              :operator="condition.operator"
+              :property-config="propertyConfig"
+              @validate="handleValueValidate"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 条件预览 -->
+      <div
+        v-if="conditionPreview"
+        class="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-8px">
+          <Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" />
+          <span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span>
+        </div>
+        <div class="pl-24px">
+          <code
+            class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono"
+            >{{ conditionPreview }}</code
+          >
+        </div>
       </div>
     </div>
 
+    <!-- 当前时间条件配置 -->
+    <CurrentTimeConditionConfig
+      v-else-if="condition.type === ConditionTypeEnum.CURRENT_TIME"
+      :model-value="condition"
+      @update:model-value="updateCondition"
+      @validate="handleValidate"
+    />
+
     <!-- 验证结果 -->
     <div v-if="validationMessage" class="mt-8px">
       <el-alert
@@ -69,10 +130,18 @@
 
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
+import ConditionTypeSelector from '../selectors/ConditionTypeSelector.vue'
+import DeviceStatusConditionConfig from './DeviceStatusConditionConfig.vue'
+import CurrentTimeConditionConfig from './CurrentTimeConditionConfig.vue'
+import ProductSelector from '../selectors/ProductSelector.vue'
+import DeviceSelector from '../selectors/DeviceSelector.vue'
 import PropertySelector from '../selectors/PropertySelector.vue'
 import OperatorSelector from '../selectors/OperatorSelector.vue'
 import ValueInput from '../inputs/ValueInput.vue'
-import { ConditionFormData } from '@/api/iot/rule/scene/scene.types'
+import {
+  ConditionFormData,
+  IotRuleSceneTriggerConditionTypeEnum
+} from '@/api/iot/rule/scene/scene.types'
 
 /** 单个条件配置组件 */
 defineOptions({ name: 'ConditionConfig' })
@@ -80,8 +149,6 @@ defineOptions({ name: 'ConditionConfig' })
 interface Props {
   modelValue: ConditionFormData
   triggerType: number
-  productId?: number
-  deviceId?: number
 }
 
 interface Emits {
@@ -94,6 +161,9 @@ const emit = defineEmits<Emits>()
 
 const condition = useVModel(props, 'modelValue', emit)
 
+// 常量定义
+const ConditionTypeEnum = IotRuleSceneTriggerConditionTypeEnum
+
 // 状态
 const propertyType = ref<string>('string')
 const propertyConfig = ref<any>(null)
@@ -131,10 +201,56 @@ const getOperatorText = (operator: string) => {
 
 // 事件处理
 const updateConditionField = (field: keyof ConditionFormData, value: any) => {
-  condition.value[field] = value
+  ;(condition.value as any)[field] = value
+  emit('update:modelValue', condition.value)
+}
+
+const updateCondition = (newCondition: ConditionFormData) => {
+  condition.value = newCondition
   emit('update:modelValue', condition.value)
 }
 
+const handleConditionTypeChange = (type: number) => {
+  // 清理不相关的字段
+  if (type === ConditionTypeEnum.DEVICE_STATUS) {
+    condition.value.identifier = undefined
+    condition.value.timeValue = undefined
+    condition.value.timeValue2 = undefined
+  } else if (type === ConditionTypeEnum.CURRENT_TIME) {
+    condition.value.identifier = undefined
+    condition.value.productId = undefined
+    condition.value.deviceId = undefined
+  } else if (type === ConditionTypeEnum.DEVICE_PROPERTY) {
+    condition.value.timeValue = undefined
+    condition.value.timeValue2 = undefined
+  }
+
+  // 重置操作符和参数
+  condition.value.operator = '='
+  condition.value.param = ''
+
+  updateValidationResult()
+}
+
+const handleValidate = (result: { valid: boolean; message: string }) => {
+  isValid.value = result.valid
+  validationMessage.value = result.message
+  emit('validate', result)
+}
+
+const handleProductChange = (productId: number) => {
+  // 产品变化时清空设备和属性
+  condition.value.deviceId = undefined
+  condition.value.identifier = ''
+  updateValidationResult()
+}
+
+const handleDeviceChange = (deviceId: number) => {
+  // 设备变化时清空属性
+  condition.value.identifier = ''
+  updateValidationResult()
+}
+
 const handlePropertyChange = (propertyInfo: { type: string; config: any }) => {
   propertyType.value = propertyInfo.type
   propertyConfig.value = propertyInfo.config

+ 4 - 3
src/views/iot/rule/scene/form/configs/ConditionGroupConfig.vue

@@ -128,6 +128,7 @@ interface Props {
   triggerType: number
   productId?: number
   deviceId?: number
+  maxConditions?: number
 }
 
 interface Emits {
@@ -141,7 +142,7 @@ const emit = defineEmits<Emits>()
 const group = useVModel(props, 'modelValue', emit)
 
 // 配置常量
-const maxConditions = 5
+const maxConditions = computed(() => props.maxConditions || 3)
 
 // 验证状态
 const conditionValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
@@ -172,12 +173,12 @@ const addCondition = () => {
     group.value.conditions = []
   }
 
-  if (group.value.conditions.length >= maxConditions) {
+  if (group.value.conditions.length >= maxConditions.value) {
     return
   }
 
   const newCondition: ConditionFormData = {
-    type: props.triggerType,
+    type: 2, // 默认为设备属性条件
     productId: props.productId || 0,
     deviceId: props.deviceId || 0,
     identifier: '',

+ 247 - 0
src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue

@@ -0,0 +1,247 @@
+<!-- 条件组容器配置组件 -->
+<template>
+  <div class="flex flex-col gap-16px">
+    <!-- 条件组容器头部 -->
+    <div
+      class="flex items-center justify-between p-16px bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-8px"
+    >
+      <div class="flex items-center gap-12px">
+        <div class="flex items-center gap-8px text-16px font-600 text-green-700">
+          <div
+            class="w-24px h-24px bg-green-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+          >
+            组
+          </div>
+          <span>附加条件组</span>
+        </div>
+        <el-tag size="small" type="success">与主条件为且关系</el-tag>
+        <el-tag size="small" type="info">
+          {{ modelValue.subGroups?.length || 0 }}个子条件组
+        </el-tag>
+      </div>
+      <div class="flex items-center gap-8px">
+        <el-button
+          type="primary"
+          size="small"
+          @click="addSubGroup"
+          :disabled="(modelValue.subGroups?.length || 0) >= maxSubGroups"
+        >
+          <Icon icon="ep:plus" />
+          添加子条件组
+        </el-button>
+        <el-button type="danger" size="small" text @click="removeContainer">
+          <Icon icon="ep:delete" />
+          删除条件组
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 子条件组列表 -->
+    <div v-if="modelValue.subGroups && modelValue.subGroups.length > 0" class="space-y-16px">
+      <!-- 逻辑关系说明 -->
+      <div v-if="modelValue.subGroups.length > 1" class="flex items-center justify-center">
+        <div
+          class="flex items-center gap-8px px-12px py-6px bg-orange-50 border border-orange-200 rounded-full text-12px text-orange-600"
+        >
+          <Icon icon="ep:info-filled" />
+          <span>子条件组之间为"或"关系,满足任意一组即可触发</span>
+        </div>
+      </div>
+
+      <div class="relative">
+        <div
+          v-for="(subGroup, subGroupIndex) in modelValue.subGroups"
+          :key="`sub-group-${subGroupIndex}`"
+          class="relative"
+        >
+          <!-- 子条件组容器 -->
+          <div
+            class="border-2 border-orange-200 rounded-8px bg-orange-50 shadow-sm hover:shadow-md transition-shadow"
+          >
+            <div
+              class="flex items-center justify-between p-16px bg-gradient-to-r from-orange-50 to-yellow-50 border-b border-orange-200 rounded-t-6px"
+            >
+              <div class="flex items-center gap-12px">
+                <div class="flex items-center gap-8px text-16px font-600 text-orange-700">
+                  <div
+                    class="w-24px h-24px bg-orange-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+                  >
+                    {{ subGroupIndex + 1 }}
+                  </div>
+                  <span>子条件组 {{ subGroupIndex + 1 }}</span>
+                </div>
+                <el-tag size="small" type="warning" class="font-500">组内条件为"且"关系</el-tag>
+                <el-tag size="small" type="info">
+                  {{ subGroup.conditions?.length || 0 }}个条件
+                </el-tag>
+              </div>
+              <el-button
+                type="danger"
+                size="small"
+                text
+                @click="removeSubGroup(subGroupIndex)"
+                class="hover:bg-red-50"
+              >
+                <Icon icon="ep:delete" />
+                删除组
+              </el-button>
+            </div>
+
+            <SubConditionGroupConfig
+              :model-value="subGroup"
+              @update:model-value="(value) => updateSubGroup(subGroupIndex, value)"
+              :trigger-type="triggerType"
+              :max-conditions="maxConditionsPerGroup"
+              @validate="(result) => handleSubGroupValidate(subGroupIndex, result)"
+            />
+          </div>
+
+          <!-- 子条件组间的"或"连接符 -->
+          <div
+            v-if="subGroupIndex < modelValue.subGroups!.length - 1"
+            class="flex items-center justify-center py-12px"
+          >
+            <div class="flex items-center gap-8px">
+              <!-- 连接线 -->
+              <div class="w-32px h-1px bg-orange-300"></div>
+              <!-- 或标签 -->
+              <div class="px-16px py-6px bg-orange-100 border-2 border-orange-300 rounded-full">
+                <span class="text-14px font-600 text-orange-600">或</span>
+              </div>
+              <!-- 连接线 -->
+              <div class="w-32px h-1px bg-orange-300"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div
+      v-else
+      class="p-24px border-2 border-dashed border-orange-200 rounded-8px text-center bg-orange-50"
+    >
+      <div class="flex flex-col items-center gap-12px">
+        <Icon icon="ep:plus" class="text-32px text-orange-400" />
+        <div class="text-orange-600">
+          <p class="text-14px font-500 mb-4px">暂无子条件组</p>
+          <p class="text-12px">点击上方"添加子条件组"按钮开始配置</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import SubConditionGroupConfig from './SubConditionGroupConfig.vue'
+import {
+  ConditionGroupContainerFormData,
+  SubConditionGroupFormData
+} from '@/api/iot/rule/scene/scene.types'
+
+/** 条件组容器配置组件 */
+defineOptions({ name: 'ConditionGroupContainerConfig' })
+
+interface Props {
+  modelValue: ConditionGroupContainerFormData
+  triggerType: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: ConditionGroupContainerFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+  (e: 'remove'): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const container = useVModel(props, 'modelValue', emit)
+
+// 配置常量
+const maxSubGroups = 3 // 最多3个子条件组
+const maxConditionsPerGroup = 3 // 每组最多3个条件
+
+// 验证状态
+const subGroupValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+
+// 事件处理
+const addSubGroup = () => {
+  if (!container.value.subGroups) {
+    container.value.subGroups = []
+  }
+
+  if (container.value.subGroups.length >= maxSubGroups) {
+    return
+  }
+
+  const newSubGroup: SubConditionGroupFormData = {
+    conditions: []
+  }
+
+  container.value.subGroups.push(newSubGroup)
+}
+
+const removeSubGroup = (index: number) => {
+  if (container.value.subGroups) {
+    container.value.subGroups.splice(index, 1)
+    delete subGroupValidations.value[index]
+
+    // 重新索引验证结果
+    const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
+    Object.keys(subGroupValidations.value).forEach((key) => {
+      const numKey = parseInt(key)
+      if (numKey > index) {
+        newValidations[numKey - 1] = subGroupValidations.value[numKey]
+      } else if (numKey < index) {
+        newValidations[numKey] = subGroupValidations.value[numKey]
+      }
+    })
+    subGroupValidations.value = newValidations
+
+    updateValidationResult()
+  }
+}
+
+const updateSubGroup = (index: number, subGroup: SubConditionGroupFormData) => {
+  if (container.value.subGroups) {
+    container.value.subGroups[index] = subGroup
+  }
+}
+
+const removeContainer = () => {
+  emit('remove')
+}
+
+const handleSubGroupValidate = (index: number, result: { valid: boolean; message: string }) => {
+  subGroupValidations.value[index] = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  if (!container.value.subGroups || container.value.subGroups.length === 0) {
+    emit('validate', { valid: true, message: '条件组容器为空,验证通过' })
+    return
+  }
+
+  const validations = Object.values(subGroupValidations.value)
+  const allValid = validations.every((v: any) => v.valid)
+
+  if (allValid) {
+    emit('validate', { valid: true, message: '条件组容器配置验证通过' })
+  } else {
+    const errorMessages = validations.filter((v: any) => !v.valid).map((v: any) => v.message)
+    emit('validate', { valid: false, message: `子条件组配置错误: ${errorMessages.join('; ')}` })
+  }
+}
+
+// 监听变化
+watch(
+  () => container.value.subGroups,
+  () => {
+    updateValidationResult()
+  },
+  { deep: true, immediate: true }
+)
+</script>

+ 287 - 0
src/views/iot/rule/scene/form/configs/CurrentTimeConditionConfig.vue

@@ -0,0 +1,287 @@
+<!-- 当前时间条件配置组件 -->
+<template>
+  <div class="flex flex-col gap-16px">
+    <div class="flex items-center gap-8px p-12px px-16px bg-orange-50 rounded-6px border border-orange-200">
+      <Icon icon="ep:timer" class="text-orange-500 text-18px" />
+      <span class="text-14px font-500 text-orange-700">当前时间条件配置</span>
+    </div>
+
+    <el-row :gutter="16">
+      <!-- 时间操作符选择 -->
+      <el-col :span="8">
+        <el-form-item label="时间条件" required>
+          <el-select
+            :model-value="condition.operator"
+            @update:model-value="(value) => updateConditionField('operator', value)"
+            placeholder="请选择时间条件"
+            class="w-full"
+          >
+            <el-option
+              v-for="option in timeOperatorOptions"
+              :key="option.value"
+              :label="option.label"
+              :value="option.value"
+            >
+              <div class="flex items-center justify-between w-full">
+                <div class="flex items-center gap-8px">
+                  <Icon :icon="option.icon" :class="option.iconClass" />
+                  <span>{{ option.label }}</span>
+                </div>
+                <el-tag :type="option.tag" size="small">{{ option.category }}</el-tag>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-col>
+
+      <!-- 时间值输入 -->
+      <el-col :span="8">
+        <el-form-item label="时间值" required>
+          <el-time-picker
+            v-if="needsTimeInput"
+            :model-value="condition.timeValue"
+            @update:model-value="(value) => updateConditionField('timeValue', value)"
+            placeholder="请选择时间"
+            format="HH:mm:ss"
+            value-format="HH:mm:ss"
+            class="w-full"
+          />
+          <el-date-picker
+            v-else-if="needsDateInput"
+            :model-value="condition.timeValue"
+            @update:model-value="(value) => updateConditionField('timeValue', value)"
+            type="datetime"
+            placeholder="请选择日期时间"
+            format="YYYY-MM-DD HH:mm:ss"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            class="w-full"
+          />
+          <div v-else class="text-[var(--el-text-color-placeholder)] text-14px">
+            无需设置时间值
+          </div>
+        </el-form-item>
+      </el-col>
+
+      <!-- 第二个时间值(范围条件) -->
+      <el-col :span="8" v-if="needsSecondTimeInput">
+        <el-form-item label="结束时间" required>
+          <el-time-picker
+            v-if="needsTimeInput"
+            :model-value="condition.timeValue2"
+            @update:model-value="(value) => updateConditionField('timeValue2', value)"
+            placeholder="请选择结束时间"
+            format="HH:mm:ss"
+            value-format="HH:mm:ss"
+            class="w-full"
+          />
+          <el-date-picker
+            v-else
+            :model-value="condition.timeValue2"
+            @update:model-value="(value) => updateConditionField('timeValue2', value)"
+            type="datetime"
+            placeholder="请选择结束日期时间"
+            format="YYYY-MM-DD HH:mm:ss"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            class="w-full"
+          />
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <!-- 条件预览 -->
+    <div v-if="conditionPreview" class="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-8px">
+        <Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" />
+        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span>
+      </div>
+      <div class="pl-24px">
+        <code class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono">{{ conditionPreview }}</code>
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="mt-8px">
+      <el-alert
+        :title="validationMessage"
+        :type="isValid ? 'success' : 'error'"
+        :closable="false"
+        show-icon
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { ConditionFormData, IotRuleSceneTriggerTimeOperatorEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 当前时间条件配置组件 */
+defineOptions({ name: 'CurrentTimeConditionConfig' })
+
+interface Props {
+  modelValue: ConditionFormData
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: ConditionFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const condition = useVModel(props, 'modelValue', emit)
+
+// 时间操作符选项
+const timeOperatorOptions = [
+  {
+    value: IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.value,
+    label: IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.name,
+    icon: 'ep:arrow-left',
+    iconClass: 'text-blue-500',
+    tag: 'primary',
+    category: '时间点'
+  },
+  {
+    value: IotRuleSceneTriggerTimeOperatorEnum.AFTER_TIME.value,
+    label: IotRuleSceneTriggerTimeOperatorEnum.AFTER_TIME.name,
+    icon: 'ep:arrow-right',
+    iconClass: 'text-green-500',
+    tag: 'success',
+    category: '时间点'
+  },
+  {
+    value: IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value,
+    label: IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.name,
+    icon: 'ep:sort',
+    iconClass: 'text-orange-500',
+    tag: 'warning',
+    category: '时间段'
+  },
+  {
+    value: IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.value,
+    label: IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.name,
+    icon: 'ep:position',
+    iconClass: 'text-purple-500',
+    tag: 'info',
+    category: '时间点'
+  },
+  {
+    value: IotRuleSceneTriggerTimeOperatorEnum.TODAY.value,
+    label: IotRuleSceneTriggerTimeOperatorEnum.TODAY.name,
+    icon: 'ep:calendar',
+    iconClass: 'text-red-500',
+    tag: 'danger',
+    category: '日期'
+  }
+]
+
+// 状态
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 计算属性
+const needsTimeInput = computed(() => {
+  const timeOnlyOperators = [
+    IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.value,
+    IotRuleSceneTriggerTimeOperatorEnum.AFTER_TIME.value,
+    IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value,
+    IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.value
+  ]
+  return timeOnlyOperators.includes(condition.value.operator)
+})
+
+const needsDateInput = computed(() => {
+  return false // 暂时不支持日期输入,只支持时间
+})
+
+const needsSecondTimeInput = computed(() => {
+  return condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
+})
+
+const conditionPreview = computed(() => {
+  if (!condition.value.operator) {
+    return ''
+  }
+  
+  const operatorOption = timeOperatorOptions.find(opt => opt.value === condition.value.operator)
+  const operatorLabel = operatorOption?.label || condition.value.operator
+  
+  if (condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
+    return `当前时间 ${operatorLabel}`
+  }
+  
+  if (!condition.value.timeValue) {
+    return `当前时间 ${operatorLabel} [未设置时间]`
+  }
+  
+  if (needsSecondTimeInput.value && condition.value.timeValue2) {
+    return `当前时间 ${operatorLabel} ${condition.value.timeValue} 和 ${condition.value.timeValue2}`
+  }
+  
+  return `当前时间 ${operatorLabel} ${condition.value.timeValue}`
+})
+
+// 事件处理
+const updateConditionField = (field: keyof ConditionFormData, value: any) => {
+  condition.value[field] = value
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  if (!condition.value.operator) {
+    isValid.value = false
+    validationMessage.value = '请选择时间条件'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+
+  // 今日条件不需要时间值
+  if (condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
+    isValid.value = true
+    validationMessage.value = '当前时间条件配置验证通过'
+    emit('validate', { valid: true, message: validationMessage.value })
+    return
+  }
+
+  if (needsTimeInput.value && !condition.value.timeValue) {
+    isValid.value = false
+    validationMessage.value = '请设置时间值'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+
+  if (needsSecondTimeInput.value && !condition.value.timeValue2) {
+    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 })
+}
+
+// 监听变化
+watch(
+  () => [condition.value.operator, condition.value.timeValue, condition.value.timeValue2],
+  () => {
+    updateValidationResult()
+  },
+  { immediate: true }
+)
+
+// 监听操作符变化,清理不相关的时间值
+watch(
+  () => condition.value.operator,
+  (newOperator) => {
+    if (newOperator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
+      condition.value.timeValue = undefined
+      condition.value.timeValue2 = undefined
+    } else if (!needsSecondTimeInput.value) {
+      condition.value.timeValue2 = undefined
+    }
+  }
+)
+</script>

+ 258 - 0
src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue

@@ -0,0 +1,258 @@
+<!-- 设备状态条件配置组件 -->
+<template>
+  <div class="flex flex-col gap-16px">
+    <div
+      class="flex items-center gap-8px p-12px px-16px bg-blue-50 rounded-6px border border-blue-200"
+    >
+      <Icon icon="ep:connection" class="text-blue-500 text-18px" />
+      <span class="text-14px font-500 text-blue-700">设备状态条件配置</span>
+    </div>
+
+    <!-- 产品设备选择 -->
+    <el-row :gutter="16">
+      <el-col :span="12">
+        <el-form-item label="产品" required>
+          <ProductSelector
+            :model-value="condition.productId"
+            @update:model-value="(value) => updateConditionField('productId', value)"
+            @change="handleProductChange"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="设备" required>
+          <DeviceSelector
+            :model-value="condition.deviceId"
+            @update:model-value="(value) => updateConditionField('deviceId', value)"
+            :product-id="condition.productId"
+            @change="handleDeviceChange"
+          />
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <!-- 状态和操作符选择 -->
+    <el-row :gutter="16">
+      <!-- 状态选择 -->
+      <el-col :span="12">
+        <el-form-item label="设备状态" required>
+          <el-select
+            :model-value="condition.param"
+            @update:model-value="(value) => updateConditionField('param', value)"
+            placeholder="请选择设备状态"
+            class="w-full"
+          >
+            <el-option
+              v-for="option in deviceStatusOptions"
+              :key="option.value"
+              :label="option.label"
+              :value="option.value"
+            >
+              <div class="flex items-center gap-8px">
+                <Icon :icon="option.icon" :class="option.iconClass" />
+                <span>{{ option.label }}</span>
+                <el-tag :type="option.tag" size="small">{{ option.description }}</el-tag>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-col>
+
+      <!-- 操作符选择 -->
+      <el-col :span="12">
+        <el-form-item label="操作符" required>
+          <el-select
+            :model-value="condition.operator"
+            @update:model-value="(value) => updateConditionField('operator', value)"
+            placeholder="请选择操作符"
+            class="w-full"
+          >
+            <el-option
+              v-for="option in statusOperatorOptions"
+              :key="option.value"
+              :label="option.label"
+              :value="option.value"
+            >
+              <div class="flex items-center justify-between w-full">
+                <span>{{ option.label }}</span>
+                <span class="text-12px text-[var(--el-text-color-secondary)]">{{
+                  option.description
+                }}</span>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <!-- 条件预览 -->
+    <div
+      v-if="conditionPreview"
+      class="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-8px">
+        <Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" />
+        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span>
+      </div>
+      <div class="pl-24px">
+        <code
+          class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono"
+          >{{ conditionPreview }}</code
+        >
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="mt-8px">
+      <el-alert
+        :title="validationMessage"
+        :type="isValid ? 'success' : 'error'"
+        :closable="false"
+        show-icon
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import ProductSelector from '../selectors/ProductSelector.vue'
+import DeviceSelector from '../selectors/DeviceSelector.vue'
+import { ConditionFormData } from '@/api/iot/rule/scene/scene.types'
+
+/** 设备状态条件配置组件 */
+defineOptions({ name: 'DeviceStatusConditionConfig' })
+
+interface Props {
+  modelValue: ConditionFormData
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: ConditionFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const condition = useVModel(props, 'modelValue', emit)
+
+// 设备状态选项
+const deviceStatusOptions = [
+  {
+    value: 'online',
+    label: '在线',
+    description: '设备已连接',
+    icon: 'ep:circle-check',
+    iconClass: 'text-green-500',
+    tag: 'success'
+  },
+  {
+    value: 'offline',
+    label: '离线',
+    description: '设备已断开',
+    icon: 'ep:circle-close',
+    iconClass: 'text-red-500',
+    tag: 'danger'
+  }
+]
+
+// 状态操作符选项
+const statusOperatorOptions = [
+  {
+    value: '=',
+    label: '等于',
+    description: '状态完全匹配时触发'
+  },
+  {
+    value: '!=',
+    label: '不等于',
+    description: '状态不匹配时触发'
+  }
+]
+
+// 状态
+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
+  updateValidationResult()
+}
+
+const handleProductChange = (productId: number) => {
+  // 产品变化时清空设备
+  condition.value.deviceId = undefined
+  updateValidationResult()
+}
+
+const handleDeviceChange = (deviceId: number) => {
+  // 设备变化时可以进行其他处理
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  if (!condition.value.productId) {
+    isValid.value = false
+    validationMessage.value = '请选择产品'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+
+  if (!condition.value.deviceId) {
+    isValid.value = false
+    validationMessage.value = '请选择设备'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+
+  if (!condition.value.param) {
+    isValid.value = false
+    validationMessage.value = '请选择设备状态'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+
+  if (!condition.value.operator) {
+    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 })
+}
+
+// 监听变化
+watch(
+  () => [
+    condition.value.productId,
+    condition.value.deviceId,
+    condition.value.param,
+    condition.value.operator
+  ],
+  () => {
+    updateValidationResult()
+  },
+  { immediate: true }
+)
+</script>

+ 75 - 166
src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue

@@ -8,118 +8,56 @@
       @change="handleDeviceChange"
     />
 
-    <!-- 条件组配置 -->
-    <div v-if="needsConditions" class="space-y-12px">
-      <div class="flex items-center justify-between mb-12px">
-        <div class="flex items-center gap-8px">
-          <span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发条件</span>
-          <el-tag size="small" type="info">
-            {{ trigger.conditionGroups?.length || 0 }}个条件组
-          </el-tag>
-          <el-tooltip
-            content="条件组之间为'或'关系,满足任意一组即可触发;每个条件组内的条件为'且'关系,需要全部满足"
-            placement="top"
-          >
-            <Icon icon="ep:question-filled" class="text-[var(--el-color-info)] cursor-help" />
-          </el-tooltip>
-        </div>
-        <div class="flex items-center gap-8px">
-          <el-button
-            type="primary"
-            size="small"
-            @click="addConditionGroup"
-            :disabled="(trigger.conditionGroups?.length || 0) >= maxConditionGroups"
-          >
-            <Icon icon="ep:plus" />
-            添加条件组
-          </el-button>
-        </div>
-      </div>
-
-      <!-- 条件组列表 -->
+    <!-- 主条件配置 -->
+    <div v-if="needsConditions" class="space-y-16px">
       <div
-        v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0"
-        class="space-y-16px"
+        class="flex items-center gap-8px p-12px px-16px bg-blue-50 rounded-6px border border-blue-200"
       >
-        <!-- 逻辑关系说明 -->
-        <div v-if="trigger.conditionGroups.length > 1" class="flex items-center justify-center">
-          <div
-            class="flex items-center gap-8px px-12px py-6px bg-blue-50 border border-blue-200 rounded-full text-12px text-blue-600"
-          >
-            <Icon icon="ep:info-filled" />
-            <span>条件组之间为"或"关系,满足任意一组即可触发</span>
-          </div>
-        </div>
-
-        <div class="relative">
-          <div
-            v-for="(group, groupIndex) in trigger.conditionGroups"
-            :key="`group-${groupIndex}`"
-            class="relative"
-          >
-            <!-- 条件组容器 -->
-            <div
-              class="border-2 border-[var(--el-border-color-lighter)] rounded-8px bg-[var(--el-fill-color-blank)] shadow-sm hover:shadow-md transition-shadow"
-            >
-              <div
-                class="flex items-center justify-between p-16px bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-[var(--el-border-color-lighter)] rounded-t-6px"
-              >
-                <div class="flex items-center gap-12px">
-                  <div
-                    class="flex items-center gap-8px text-16px font-600 text-[var(--el-text-color-primary)]"
-                  >
-                    <div
-                      class="w-24px h-24px bg-blue-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
-                    >
-                      {{ groupIndex + 1 }}
-                    </div>
-                    <span>条件组</span>
-                  </div>
-                  <el-tag size="small" type="success" class="font-500"> 组内条件为"且"关系 </el-tag>
-                </div>
-                <el-button
-                  type="danger"
-                  size="small"
-                  text
-                  @click="removeConditionGroup(groupIndex)"
-                  v-if="trigger.conditionGroups!.length > 1"
-                  class="hover:bg-red-50"
-                >
-                  <Icon icon="ep:delete" />
-                  删除组
-                </el-button>
-              </div>
+        <Icon icon="ep:star-filled" class="text-blue-500 text-18px" />
+        <span class="text-14px font-600 text-blue-700">主条件配置</span>
+        <el-tag size="small" type="primary">必须满足</el-tag>
+      </div>
 
-              <ConditionGroupConfig
-                :model-value="group"
-                @update:model-value="(value) => updateConditionGroup(groupIndex, value)"
-                :trigger-type="trigger.type"
-                :product-id="trigger.productId"
-                :device-id="trigger.deviceId"
-                @validate="(result) => handleGroupValidate(groupIndex, result)"
-              />
-            </div>
+      <MainConditionConfig
+        v-model="trigger.mainCondition"
+        :trigger-type="trigger.type"
+        @validate="handleMainConditionValidate"
+      />
+    </div>
 
-            <!-- 条件组间的"或"连接符 -->
-            <div
-              v-if="groupIndex < trigger.conditionGroups!.length - 1"
-              class="flex items-center justify-center py-12px"
-            >
-              <div class="flex items-center gap-8px">
-                <!-- 连接线 -->
-                <div class="w-32px h-1px bg-orange-300"></div>
-                <!-- 或标签 -->
-                <div class="px-16px py-6px bg-orange-100 border-2 border-orange-300 rounded-full">
-                  <span class="text-14px font-600 text-orange-600">或</span>
-                </div>
-                <!-- 连接线 -->
-                <div class="w-32px h-1px bg-orange-300"></div>
-              </div>
-            </div>
-          </div>
+    <!-- 条件组配置 -->
+    <div v-if="needsConditions && trigger.mainCondition" class="space-y-16px">
+      <div class="flex items-center justify-between">
+        <div
+          class="flex items-center gap-8px p-12px px-16px bg-green-50 rounded-6px border border-green-200"
+        >
+          <Icon icon="ep:connection" class="text-green-500 text-18px" />
+          <span class="text-14px font-600 text-green-700">附加条件组</span>
+          <el-tag size="small" type="success">与主条件为且关系</el-tag>
+          <el-tag size="small" type="info">
+            {{ trigger.conditionGroup?.subGroups?.length || 0 }}个子条件组
+          </el-tag>
         </div>
+        <el-button
+          type="primary"
+          size="small"
+          @click="addConditionGroup"
+          v-if="!trigger.conditionGroup"
+        >
+          <Icon icon="ep:plus" />
+          添加条件组
+        </el-button>
       </div>
 
+      <!-- 条件组配置 -->
+      <ConditionGroupContainerConfig
+        v-if="trigger.conditionGroup"
+        v-model="trigger.conditionGroup"
+        :trigger-type="trigger.type"
+        @validate="handleConditionGroupValidate"
+        @remove="removeConditionGroup"
+      />
+
       <!-- 空状态 -->
       <div v-else class="py-40px text-center">
         <el-empty description="暂无触发条件">
@@ -140,10 +78,10 @@
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
 import ProductDeviceSelector from '../selectors/ProductDeviceSelector.vue'
-import ConditionGroupConfig from './ConditionGroupConfig.vue'
+import MainConditionConfig from './MainConditionConfig.vue'
+import ConditionGroupContainerConfig from './ConditionGroupContainerConfig.vue'
 import {
   TriggerFormData,
-  ConditionGroupFormData,
   IotRuleSceneTriggerTypeEnum as TriggerTypeEnum
 } from '@/api/iot/rule/scene/scene.types'
 
@@ -164,11 +102,11 @@ const emit = defineEmits<Emits>()
 
 const trigger = useVModel(props, 'modelValue', emit)
 
-// 配置常量
-const maxConditionGroups = 3
-
 // 验证状态
-const groupValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+const mainConditionValidation = ref<{ valid: boolean; message: string }>({
+  valid: true,
+  message: ''
+})
 const validationMessage = ref('')
 const isValid = ref(true)
 
@@ -177,60 +115,33 @@ const needsConditions = computed(() => {
   return trigger.value.type !== TriggerTypeEnum.DEVICE_STATE_UPDATE
 })
 
-// 事件处理
-const updateConditionGroup = (index: number, group: ConditionGroupFormData) => {
-  if (trigger.value.conditionGroups) {
-    trigger.value.conditionGroups[index] = group
+// 新的事件处理函数
+const handleMainConditionValidate = (result: { valid: boolean; message: string }) => {
+  mainConditionValidation.value = result
+  updateValidationResult()
+}
+
+const addConditionGroup = () => {
+  if (!trigger.value.conditionGroup) {
+    trigger.value.conditionGroup = {
+      subGroups: []
+    }
   }
 }
 
+// 事件处理
 const handleDeviceChange = ({ productId, deviceId }: { productId?: number; deviceId?: number }) => {
   trigger.value.productId = productId
   trigger.value.deviceId = deviceId
   updateValidationResult()
 }
 
-const addConditionGroup = () => {
-  if (!trigger.value.conditionGroups) {
-    trigger.value.conditionGroups = []
-  }
-
-  if (trigger.value.conditionGroups.length >= maxConditionGroups) {
-    return
-  }
-
-  const newGroup: ConditionGroupFormData = {
-    conditions: [],
-    logicOperator: 'AND' // 固定为AND,因为条件组内部条件间为"且"关系
-  }
-
-  trigger.value.conditionGroups.push(newGroup)
-}
-
-const removeConditionGroup = (index: number) => {
-  if (trigger.value.conditionGroups) {
-    trigger.value.conditionGroups.splice(index, 1)
-    delete groupValidations.value[index]
-
-    // 重新索引验证结果
-    const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
-    Object.keys(groupValidations.value).forEach((key) => {
-      const numKey = parseInt(key)
-      if (numKey > index) {
-        newValidations[numKey - 1] = groupValidations.value[numKey]
-      } else if (numKey < index) {
-        newValidations[numKey] = groupValidations.value[numKey]
-      }
-    })
-    groupValidations.value = newValidations
-
-    updateValidationResult()
-  }
+const handleConditionGroupValidate = (result: { valid: boolean; message: string }) => {
+  updateValidationResult()
 }
 
-const handleGroupValidate = (index: number, result: { valid: boolean; message: string }) => {
-  groupValidations.value[index] = result
-  updateValidationResult()
+const removeConditionGroup = () => {
+  trigger.value.conditionGroup = undefined
 }
 
 const updateValidationResult = () => {
@@ -250,26 +161,24 @@ const updateValidationResult = () => {
     return
   }
 
-  // 条件验证
-  if (!trigger.value.conditionGroups || trigger.value.conditionGroups.length === 0) {
+  // 条件验证
+  if (!trigger.value.mainCondition) {
     isValid.value = false
-    validationMessage.value = '请至少添加一个触发条件组'
+    validationMessage.value = '请配置主条件'
     emit('validate', { valid: false, message: validationMessage.value })
     return
   }
 
-  const validations = Object.values(groupValidations.value)
-  const allValid = validations.every((v) => v.valid)
-
-  if (allValid) {
-    isValid.value = true
-    validationMessage.value = '设备触发配置验证通过'
-  } else {
+  // 主条件详细验证
+  if (!mainConditionValidation.value.valid) {
     isValid.value = false
-    const errorMessages = validations.filter((v) => !v.valid).map((v) => v.message)
-    validationMessage.value = `条件组配置错误: ${errorMessages.join('; ')}`
+    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 })
 }
 

+ 114 - 0
src/views/iot/rule/scene/form/configs/MainConditionConfig.vue

@@ -0,0 +1,114 @@
+<!-- 主条件配置组件 -->
+<template>
+  <div class="flex flex-col gap-16px">
+    <!-- 条件配置提示 -->
+    <div
+      v-if="!modelValue"
+      class="p-16px border-2 border-dashed border-[var(--el-border-color)] rounded-8px text-center"
+    >
+      <div class="flex flex-col items-center gap-12px">
+        <Icon icon="ep:plus" class="text-32px text-[var(--el-text-color-placeholder)]" />
+        <div class="text-[var(--el-text-color-secondary)]">
+          <p class="text-14px font-500 mb-4px">请配置主条件</p>
+          <p class="text-12px">主条件是触发器的核心条件,必须满足才能触发场景</p>
+        </div>
+        <el-button type="primary" @click="addMainCondition">
+          <Icon icon="ep:plus" />
+          添加主条件
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 主条件配置 -->
+    <div
+      v-else
+      class="border border-[var(--el-border-color-lighter)] rounded-8px bg-[var(--el-fill-color-blank)] shadow-sm"
+    >
+      <div
+        class="flex items-center justify-between p-16px bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-[var(--el-border-color-lighter)] rounded-t-6px"
+      >
+        <div class="flex items-center gap-12px">
+          <div
+            class="flex items-center gap-8px text-16px font-600 text-[var(--el-text-color-primary)]"
+          >
+            <div
+              class="w-24px h-24px bg-blue-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+            >
+              主
+            </div>
+            <span>主条件</span>
+          </div>
+          <el-tag size="small" type="primary" class="font-500">必须满足</el-tag>
+        </div>
+        <el-button
+          type="danger"
+          size="small"
+          text
+          @click="removeMainCondition"
+          class="hover:bg-red-50"
+        >
+          <Icon icon="ep:delete" />
+          删除
+        </el-button>
+      </div>
+
+      <div class="p-16px">
+        <ConditionConfig
+          :model-value="modelValue"
+          @update:model-value="updateCondition"
+          :trigger-type="triggerType"
+          @validate="handleValidate"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ConditionConfig from './ConditionConfig.vue'
+import {
+  ConditionFormData,
+  IotRuleSceneTriggerConditionTypeEnum
+} from '@/api/iot/rule/scene/scene.types'
+
+/** 主条件配置组件 */
+defineOptions({ name: 'MainConditionConfig' })
+
+interface Props {
+  modelValue?: ConditionFormData
+  triggerType: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value?: ConditionFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+// 事件处理
+const addMainCondition = () => {
+  const newCondition: ConditionFormData = {
+    type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // 默认为设备属性
+    productId: undefined,
+    deviceId: undefined,
+    identifier: '',
+    operator: '=',
+    param: ''
+  }
+  emit('update:modelValue', newCondition)
+}
+
+const removeMainCondition = () => {
+  emit('update:modelValue', undefined)
+}
+
+const updateCondition = (condition: ConditionFormData) => {
+  emit('update:modelValue', condition)
+}
+
+const handleValidate = (result: { valid: boolean; message: string }) => {
+  emit('validate', result)
+}
+</script>

+ 220 - 0
src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue

@@ -0,0 +1,220 @@
+<!-- 子条件组配置组件 -->
+<template>
+  <div class="p-16px">
+    <!-- 空状态 -->
+    <div
+      v-if="!subGroup.conditions || subGroup.conditions.length === 0"
+      class="text-center py-24px"
+    >
+      <div class="flex flex-col items-center gap-12px">
+        <Icon icon="ep:plus" class="text-32px text-[var(--el-text-color-placeholder)]" />
+        <div class="text-[var(--el-text-color-secondary)]">
+          <p class="text-14px font-500 mb-4px">暂无条件</p>
+          <p class="text-12px">点击下方按钮添加第一个条件</p>
+        </div>
+        <el-button type="primary" @click="addCondition">
+          <Icon icon="ep:plus" />
+          添加条件
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 条件列表 -->
+    <div v-else class="space-y-16px">
+      <div
+        v-for="(condition, conditionIndex) in subGroup.conditions"
+        :key="`condition-${conditionIndex}`"
+        class="relative"
+      >
+        <!-- 条件配置 -->
+        <div
+          class="border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)] shadow-sm"
+        >
+          <div
+            class="flex items-center justify-between p-12px bg-[var(--el-fill-color-light)] border-b border-[var(--el-border-color-lighter)] rounded-t-4px"
+          >
+            <div class="flex items-center gap-8px">
+              <div
+                class="w-20px h-20px bg-blue-500 text-white rounded-full flex items-center justify-center text-10px font-bold"
+              >
+                {{ conditionIndex + 1 }}
+              </div>
+              <span class="text-12px font-500 text-[var(--el-text-color-primary)]"
+                >条件 {{ conditionIndex + 1 }}</span
+              >
+            </div>
+            <el-button
+              type="danger"
+              size="small"
+              text
+              @click="removeCondition(conditionIndex)"
+              v-if="subGroup.conditions!.length > 1"
+              class="hover:bg-red-50"
+            >
+              <Icon icon="ep:delete" />
+            </el-button>
+          </div>
+
+          <div class="p-12px">
+            <ConditionConfig
+              :model-value="condition"
+              @update:model-value="(value) => updateCondition(conditionIndex, value)"
+              :trigger-type="triggerType"
+              @validate="(result) => handleConditionValidate(conditionIndex, result)"
+            />
+          </div>
+        </div>
+
+        <!-- 条件间的"且"连接符 -->
+        <div
+          v-if="conditionIndex < subGroup.conditions!.length - 1"
+          class="flex items-center justify-center py-8px"
+        >
+          <div class="flex items-center gap-8px">
+            <!-- 连接线 -->
+            <div class="w-24px h-1px bg-green-300"></div>
+            <!-- 且标签 -->
+            <div class="px-12px py-4px bg-green-100 border border-green-300 rounded-full">
+              <span class="text-12px font-600 text-green-600">且</span>
+            </div>
+            <!-- 连接线 -->
+            <div class="w-24px h-1px bg-green-300"></div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 添加条件按钮 -->
+      <div
+        v-if="
+          subGroup.conditions &&
+          subGroup.conditions.length > 0 &&
+          subGroup.conditions.length < maxConditions
+        "
+        class="text-center py-16px"
+      >
+        <el-button type="primary" plain @click="addCondition">
+          <Icon icon="ep:plus" />
+          继续添加条件
+        </el-button>
+        <span class="block mt-8px text-12px text-[var(--el-text-color-secondary)]">
+          最多可添加 {{ maxConditions }} 个条件
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import ConditionConfig from './ConditionConfig.vue'
+import {
+  SubConditionGroupFormData,
+  ConditionFormData,
+  IotRuleSceneTriggerConditionTypeEnum
+} from '@/api/iot/rule/scene/scene.types'
+
+/** 子条件组配置组件 */
+defineOptions({ name: 'SubConditionGroupConfig' })
+
+interface Props {
+  modelValue: SubConditionGroupFormData
+  triggerType: number
+  maxConditions?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: SubConditionGroupFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const subGroup = useVModel(props, 'modelValue', emit)
+
+// 配置常量
+const maxConditions = computed(() => props.maxConditions || 3)
+
+// 验证状态
+const conditionValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+
+// 事件处理
+const addCondition = () => {
+  if (!subGroup.value.conditions) {
+    subGroup.value.conditions = []
+  }
+
+  if (subGroup.value.conditions.length >= maxConditions.value) {
+    return
+  }
+
+  const newCondition: ConditionFormData = {
+    type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // 默认为设备属性
+    productId: undefined,
+    deviceId: undefined,
+    identifier: '',
+    operator: '=',
+    param: ''
+  }
+
+  subGroup.value.conditions.push(newCondition)
+}
+
+const removeCondition = (index: number) => {
+  if (subGroup.value.conditions) {
+    subGroup.value.conditions.splice(index, 1)
+    delete conditionValidations.value[index]
+
+    // 重新索引验证结果
+    const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
+    Object.keys(conditionValidations.value).forEach((key) => {
+      const numKey = parseInt(key)
+      if (numKey > index) {
+        newValidations[numKey - 1] = conditionValidations.value[numKey]
+      } else if (numKey < index) {
+        newValidations[numKey] = conditionValidations.value[numKey]
+      }
+    })
+    conditionValidations.value = newValidations
+
+    updateValidationResult()
+  }
+}
+
+const updateCondition = (index: number, condition: ConditionFormData) => {
+  if (subGroup.value.conditions) {
+    subGroup.value.conditions[index] = condition
+  }
+}
+
+const handleConditionValidate = (index: number, result: { valid: boolean; message: string }) => {
+  conditionValidations.value[index] = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  if (!subGroup.value.conditions || subGroup.value.conditions.length === 0) {
+    emit('validate', { valid: false, message: '子条件组至少需要一个条件' })
+    return
+  }
+
+  const validations = Object.values(conditionValidations.value)
+  const allValid = validations.every((v: any) => v.valid)
+
+  if (allValid) {
+    emit('validate', { valid: true, message: '子条件组配置验证通过' })
+  } else {
+    const errorMessages = validations.filter((v: any) => !v.valid).map((v: any) => v.message)
+    emit('validate', { valid: false, message: `条件配置错误: ${errorMessages.join('; ')}` })
+  }
+}
+
+// 监听变化
+watch(
+  () => subGroup.value.conditions,
+  () => {
+    updateValidationResult()
+  },
+  { deep: true, immediate: true }
+)
+</script>

+ 68 - 162
src/views/iot/rule/scene/form/sections/TriggerSection.vue

@@ -1,92 +1,46 @@
-<!-- 触发器配置组件 -->
+<!-- 场景触发器配置组件 -->
 <template>
   <el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
     <template #header>
-      <div class="flex items-center justify-between">
-        <div class="flex items-center gap-8px">
-          <Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" />
-          <span class="text-16px font-600 text-[var(--el-text-color-primary)]">触发器配置</span>
-          <el-tag size="small" type="info">{{ triggers.length }}个触发器</el-tag>
-        </div>
-        <div class="flex items-center gap-8px">
-          <el-button
-            type="primary"
-            size="small"
-            @click="addTrigger"
-          >
-            <Icon icon="ep:plus" />
-            添加触发器
-          </el-button>
-        </div>
+      <div class="flex items-center gap-8px">
+        <Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" />
+        <span class="text-16px font-600 text-[var(--el-text-color-primary)]">触发器配置</span>
+        <el-tag size="small" type="info">场景触发器</el-tag>
       </div>
     </template>
 
-    <div class="p-0">
-      <!-- 空状态 -->
-      <div v-if="triggers.length === 0">
-        <el-empty description="暂无触发器配置,请点击右上角添加触发器按钮开始配置" />
-      </div>
-
-      <!-- 触发器列表 -->
-      <div v-else class="space-y-16px">
-        <div v-for="(trigger, index) in triggers" :key="`trigger-${index}`" class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
-          <div class="flex items-center justify-between mb-16px">
-            <div class="flex items-center gap-8px">
-              <Icon icon="ep:lightning" class="text-[var(--el-color-warning)] text-16px" />
-              <span>触发器 {{ index + 1 }}</span>
-              <el-tag :type="getTriggerTypeTag(trigger.type)" size="small">
-                {{ getTriggerTypeName(trigger.type) }}
-              </el-tag>
-            </div>
-            <div>
-              <el-button
-                type="danger"
-                size="small"
-                text
-                @click="removeTrigger(index)"
-                v-if="triggers.length > 1"
-              >
-                <Icon icon="ep:delete" />
-                删除
-              </el-button>
-            </div>
-          </div>
-
-          <div class="space-y-16px">
-            <!-- 触发类型选择 -->
-            <el-form-item label="触发类型" required>
-              <el-select
-                :model-value="trigger.type"
-                @update:model-value="(value) => updateTriggerType(index, value)"
-                @change="onTriggerTypeChange(trigger, $event)"
-                placeholder="请选择触发类型"
-                class="w-full"
-              >
-                <el-option
-                  v-for="option in triggerTypeOptions"
-                  :key="option.value"
-                  :label="option.label"
-                  :value="option.value"
-                />
-              </el-select>
-            </el-form-item>
-
-            <!-- 设备触发配置 -->
-            <DeviceTriggerConfig
-              v-if="isDeviceTrigger(trigger.type)"
-              :model-value="trigger"
-              @update:model-value="(value) => updateTrigger(index, value)"
-            />
-
-            <!-- 定时触发配置 -->
-            <TimerTriggerConfig
-              v-if="trigger.type === TriggerTypeEnum.TIMER"
-              :model-value="trigger.cronExpression"
-              @update:model-value="(value) => updateTriggerCronExpression(index, value)"
-            />
-          </div>
-        </div>
-      </div>
+    <div class="p-16px space-y-16px">
+      <!-- 触发事件类型选择 -->
+      <el-form-item label="触发事件类型" required>
+        <el-select
+          :model-value="trigger.type"
+          @update:model-value="(value) => updateTriggerType(value)"
+          @change="onTriggerTypeChange"
+          placeholder="请选择触发事件类型"
+          class="w-full"
+        >
+          <el-option
+            v-for="option in triggerTypeOptions"
+            :key="option.value"
+            :label="option.label"
+            :value="option.value"
+          />
+        </el-select>
+      </el-form-item>
+
+      <!-- 设备触发配置 -->
+      <DeviceTriggerConfig
+        v-if="isDeviceTrigger(trigger.type)"
+        :model-value="trigger"
+        @update:model-value="updateTrigger"
+      />
+
+      <!-- 定时触发配置 -->
+      <TimerTriggerConfig
+        v-if="trigger.type === TriggerTypeEnum.TIMER"
+        :model-value="trigger.cronExpression"
+        @update:model-value="updateTriggerCronExpression"
+      />
     </div>
   </el-card>
 </template>
@@ -104,37 +58,17 @@ import {
 defineOptions({ name: 'TriggerSection' })
 
 interface Props {
-  triggers: TriggerFormData[]
+  trigger: TriggerFormData
 }
 
 interface Emits {
-  (e: 'update:triggers', value: TriggerFormData[]): void
+  (e: 'update:trigger', value: TriggerFormData): void
 }
 
 const props = defineProps<Props>()
 const emit = defineEmits<Emits>()
 
-const triggers = useVModel(props, 'triggers', emit)
-
-/**
- * 创建默认的触发器数据
- */
-const createDefaultTriggerData = (): TriggerFormData => {
-  return {
-    type: TriggerTypeEnum.DEVICE_PROPERTY_POST, // 默认为设备属性上报
-    productId: undefined,
-    deviceId: undefined,
-    identifier: undefined,
-    operator: undefined,
-    value: undefined,
-    cronExpression: undefined,
-    conditionGroups: []
-  }
-}
-
-
-
-
+const trigger = useVModel(props, 'trigger', emit)
 
 // 触发器类型选项
 const triggerTypeOptions = [
@@ -160,23 +94,6 @@ const triggerTypeOptions = [
   }
 ]
 
-// 触发器类型映射
-const triggerTypeNames = {
-  [TriggerTypeEnum.DEVICE_STATE_UPDATE]: '设备状态变更',
-  [TriggerTypeEnum.DEVICE_PROPERTY_POST]: '属性上报',
-  [TriggerTypeEnum.DEVICE_EVENT_POST]: '事件上报',
-  [TriggerTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
-  [TriggerTypeEnum.TIMER]: '定时触发'
-}
-
-const triggerTypeTags = {
-  [TriggerTypeEnum.DEVICE_STATE_UPDATE]: 'warning',
-  [TriggerTypeEnum.DEVICE_PROPERTY_POST]: 'primary',
-  [TriggerTypeEnum.DEVICE_EVENT_POST]: 'success',
-  [TriggerTypeEnum.DEVICE_SERVICE_INVOKE]: 'info',
-  [TriggerTypeEnum.TIMER]: 'danger'
-}
-
 // 工具函数
 const isDeviceTrigger = (type: number) => {
   const deviceTriggerTypes = [
@@ -188,58 +105,47 @@ const isDeviceTrigger = (type: number) => {
   return deviceTriggerTypes.includes(type)
 }
 
-const getTriggerTypeName = (type: number) => {
-  return triggerTypeNames[type] || '未知类型'
-}
-
-const getTriggerTypeTag = (type: number) => {
-  return triggerTypeTags[type] || 'info'
-}
-
 // 事件处理
-const addTrigger = () => {
-  const newTrigger = createDefaultTriggerData()
-  triggers.value.push(newTrigger)
+const updateTriggerType = (type: number) => {
+  trigger.value.type = type
+  onTriggerTypeChange(type)
 }
 
-const removeTrigger = (index: number) => {
-  triggers.value.splice(index, 1)
+const updateTrigger = (newTrigger: TriggerFormData) => {
+  trigger.value = newTrigger
 }
 
-const updateTriggerType = (index: number, type: number) => {
-  triggers.value[index].type = type
-  onTriggerTypeChange(triggers.value[index], type)
+const updateTriggerCronExpression = (cronExpression?: string) => {
+  trigger.value.cronExpression = cronExpression
 }
 
-const updateTrigger = (index: number, trigger: TriggerFormData) => {
-  triggers.value[index] = trigger
-}
-
-const updateTriggerCronExpression = (index: number, cronExpression?: string) => {
-  triggers.value[index].cronExpression = cronExpression
-}
-
-const onTriggerTypeChange = (trigger: TriggerFormData, type: number) => {
+const onTriggerTypeChange = (type: number) => {
   // 清理不相关的配置
   if (type === TriggerTypeEnum.TIMER) {
-    trigger.productId = undefined
-    trigger.deviceId = undefined
-    trigger.identifier = undefined
-    trigger.operator = undefined
-    trigger.value = undefined
-    trigger.conditionGroups = undefined
-    if (!trigger.cronExpression) {
-      trigger.cronExpression = '0 0 12 * * ?'
+    trigger.value.productId = undefined
+    trigger.value.deviceId = undefined
+    trigger.value.identifier = undefined
+    trigger.value.operator = undefined
+    trigger.value.value = undefined
+    trigger.value.mainCondition = undefined
+    trigger.value.conditionGroup = undefined
+    if (!trigger.value.cronExpression) {
+      trigger.value.cronExpression = '0 0 12 * * ?'
     }
   } else {
-    trigger.cronExpression = undefined
+    trigger.value.cronExpression = undefined
     if (type === TriggerTypeEnum.DEVICE_STATE_UPDATE) {
-      trigger.conditionGroups = undefined
-    } else if (!trigger.conditionGroups) {
-      trigger.conditionGroups = []
+      trigger.value.mainCondition = undefined
+      trigger.value.conditionGroup = undefined
+    } else {
+      // 设备属性、事件、服务触发需要条件配置
+      if (!trigger.value.mainCondition) {
+        trigger.value.mainCondition = undefined // 等待用户配置
+      }
+      if (!trigger.value.conditionGroup) {
+        trigger.value.conditionGroup = undefined // 可选的条件组
+      }
     }
   }
 }
 </script>
-
-

+ 80 - 0
src/views/iot/rule/scene/form/selectors/ConditionTypeSelector.vue

@@ -0,0 +1,80 @@
+<!-- 条件类型选择器组件 -->
+<template>
+  <el-select
+    :model-value="modelValue"
+    @update:model-value="handleChange"
+    placeholder="请选择条件类型"
+    class="w-full"
+  >
+    <el-option
+      v-for="option in conditionTypeOptions"
+      :key="option.value"
+      :label="option.label"
+      :value="option.value"
+    >
+      <div class="flex items-center justify-between w-full">
+        <div class="flex items-center gap-8px">
+          <Icon :icon="option.icon" :class="option.iconClass" />
+          <span>{{ option.label }}</span>
+        </div>
+        <el-tag :type="option.tag" size="small">{{ option.category }}</el-tag>
+      </div>
+    </el-option>
+  </el-select>
+</template>
+
+<script setup lang="ts">
+import { IotRuleSceneTriggerConditionTypeEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 条件类型选择器组件 */
+defineOptions({ name: 'ConditionTypeSelector' })
+
+interface Props {
+  modelValue?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: number): void
+  (e: 'change', value: number): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+// 条件类型选项
+const conditionTypeOptions = [
+  {
+    value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS,
+    label: '设备状态',
+    description: '监控设备的在线/离线状态变化',
+    icon: 'ep:connection',
+    iconClass: 'text-blue-500',
+    tag: 'primary',
+    category: '设备'
+  },
+  {
+    value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY,
+    label: '设备属性',
+    description: '监控设备属性值的变化',
+    icon: 'ep:data-analysis',
+    iconClass: 'text-green-500',
+    tag: 'success',
+    category: '属性'
+  },
+  {
+    value: IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME,
+    label: '当前时间',
+    description: '基于当前时间的条件判断',
+    icon: 'ep:timer',
+    iconClass: 'text-orange-500',
+    tag: 'warning',
+    category: '时间'
+  }
+]
+
+// 事件处理
+const handleChange = (value: number) => {
+  emit('update:modelValue', value)
+  emit('change', value)
+}
+</script>

+ 127 - 0
src/views/iot/rule/scene/form/selectors/DeviceSelector.vue

@@ -0,0 +1,127 @@
+<!-- 设备选择器组件 -->
+<template>
+  <el-select
+    :model-value="modelValue"
+    @update:model-value="handleChange"
+    placeholder="请选择设备"
+    filterable
+    clearable
+    class="w-full"
+    :loading="deviceLoading"
+    :disabled="!productId"
+  >
+    <el-option
+      v-for="device in deviceList"
+      :key="device.id"
+      :label="device.deviceName"
+      :value="device.id"
+    >
+      <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">{{
+            device.deviceName
+          }}</div>
+          <div class="text-12px text-[var(--el-text-color-secondary)]">{{ device.deviceKey }}</div>
+        </div>
+        <div class="flex items-center gap-4px">
+          <el-tag size="small" :type="getStatusType(device.status)">
+            {{ getStatusText(device.status) }}
+          </el-tag>
+          <el-tag size="small" :type="device.activeTime ? 'success' : 'info'">
+            {{ device.activeTime ? '已激活' : '未激活' }}
+          </el-tag>
+        </div>
+      </div>
+    </el-option>
+  </el-select>
+</template>
+
+<script setup lang="ts">
+import { DeviceApi } from '@/api/iot/device/device'
+
+/** 设备选择器组件 */
+defineOptions({ name: 'DeviceSelector' })
+
+interface Props {
+  modelValue?: number
+  productId?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value?: number): void
+  (e: 'change', value?: number): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+// 状态
+const deviceLoading = ref(false)
+const deviceList = ref<any[]>([])
+
+// 事件处理
+const handleChange = (value?: number) => {
+  emit('update:modelValue', value)
+  emit('change', value)
+}
+
+// 获取设备列表
+const getDeviceList = async () => {
+  if (!props.productId) {
+    deviceList.value = []
+    return
+  }
+
+  try {
+    deviceLoading.value = true
+    const res = await DeviceApi.getDeviceListByProductId(props.productId)
+    deviceList.value = res || []
+  } catch (error) {
+    console.error('获取设备列表失败:', error)
+    deviceList.value = []
+  } finally {
+    deviceLoading.value = false
+  }
+}
+
+// 设备状态映射
+const getStatusType = (status: number) => {
+  switch (status) {
+    case 0:
+      return 'success' // 正常
+    case 1:
+      return 'danger' // 禁用
+    default:
+      return 'info'
+  }
+}
+
+const getStatusText = (status: number) => {
+  switch (status) {
+    case 0:
+      return '正常'
+    case 1:
+      return '禁用'
+    default:
+      return '未知'
+  }
+}
+
+// 监听产品变化
+watch(
+  () => props.productId,
+  (newProductId) => {
+    if (newProductId) {
+      getDeviceList()
+    } else {
+      deviceList.value = []
+      // 清空当前选择的设备
+      if (props.modelValue) {
+        emit('update:modelValue', undefined)
+        emit('change', undefined)
+      }
+    }
+  },
+  { immediate: true }
+)
+</script>

+ 81 - 0
src/views/iot/rule/scene/form/selectors/ProductSelector.vue

@@ -0,0 +1,81 @@
+<!-- 产品选择器组件 -->
+<template>
+  <el-select
+    :model-value="modelValue"
+    @update:model-value="handleChange"
+    placeholder="请选择产品"
+    filterable
+    clearable
+    class="w-full"
+    :loading="productLoading"
+  >
+    <el-option
+      v-for="product in productList"
+      :key="product.id"
+      :label="product.name"
+      :value="product.id"
+    >
+      <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">{{
+            product.name
+          }}</div>
+          <div class="text-12px text-[var(--el-text-color-secondary)]">{{
+            product.productKey
+          }}</div>
+        </div>
+        <el-tag size="small" :type="product.status === 0 ? 'success' : 'danger'">
+          {{ product.status === 0 ? '正常' : '禁用' }}
+        </el-tag>
+      </div>
+    </el-option>
+  </el-select>
+</template>
+
+<script setup lang="ts">
+import { ProductApi } from '@/api/iot/product/product'
+
+/** 产品选择器组件 */
+defineOptions({ name: 'ProductSelector' })
+
+interface Props {
+  modelValue?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value?: number): void
+  (e: 'change', value?: number): void
+}
+
+defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+// 状态
+const productLoading = ref(false)
+const productList = ref<any[]>([])
+
+// 事件处理
+const handleChange = (value?: number) => {
+  emit('update:modelValue', value)
+  emit('change', value)
+}
+
+// 获取产品列表
+const getProductList = async () => {
+  try {
+    productLoading.value = true
+    const res = await ProductApi.getSimpleProductList()
+    productList.value = res || []
+  } catch (error) {
+    console.error('获取产品列表失败:', error)
+    productList.value = []
+  } finally {
+    productLoading.value = false
+  }
+}
+
+// 组件挂载时获取产品列表
+onMounted(() => {
+  getProductList()
+})
+</script>

+ 0 - 38
src/views/iot/rule/scene/form/selectors/types.ts

@@ -130,41 +130,3 @@ export interface PropertySelectorItem {
   event?: ThingModelEvent
   service?: ThingModelService
 }
-
-/** 数据类型枚举 */
-export enum DataTypeEnum {
-  INT = 'int',
-  FLOAT = 'float',
-  DOUBLE = 'double',
-  ENUM = 'enum',
-  BOOL = 'bool',
-  TEXT = 'text',
-  DATE = 'date',
-  STRUCT = 'struct',
-  ARRAY = 'array'
-}
-
-/** 访问模式枚举 */
-export enum AccessModeEnum {
-  READ = 'r',
-  READ_write = 'rw'
-}
-
-/** 事件类型枚举 */
-export enum EventTypeEnum {
-  INFO = 'info',
-  ALERT = 'alert',
-  ERROR = 'error'
-}
-
-/** 调用类型枚举 */
-export enum CallTypeEnum {
-  ASYNC = 'async',
-  SYNC = 'sync'
-}
-
-/** 参数方向枚举 */
-export enum ParamDirectionEnum {
-  INPUT = 'input',
-  OUTPUT = 'output'
-}