Просмотр исходного кода

perf:【IoT 物联网】场景联动重构优化

puhui999 10 месяцев назад
Родитель
Сommit
1b6ba33921
37 измененных файлов с 9524 добавлено и 177 удалено
  1. 117 26
      src/api/iot/rule/scene/scene.types.ts
  2. 3 2
      src/views/iot/device/device/detail/DeviceDetailsMessage.vue
  3. 2 3
      src/views/iot/ota/task/OtaTaskList.vue
  4. 192 0
      src/views/iot/rule/scene/111index.vue
  5. 1101 0
      src/views/iot/rule/scene/IoT场景联动规则表单设计思路文档.md
  6. 315 0
      src/views/iot/rule/scene/components/RuleSceneForm.vue
  7. 1 1
      src/views/iot/rule/scene/components/action/ActionExecutor.vue
  8. 288 0
      src/views/iot/rule/scene/components/configs/AlertConfig.vue
  9. 260 0
      src/views/iot/rule/scene/components/configs/ConditionConfig.vue
  10. 331 0
      src/views/iot/rule/scene/components/configs/ConditionGroupConfig.vue
  11. 173 0
      src/views/iot/rule/scene/components/configs/DeviceControlConfig.vue
  12. 347 0
      src/views/iot/rule/scene/components/configs/DeviceTriggerConfig.vue
  13. 142 0
      src/views/iot/rule/scene/components/configs/TimerTriggerConfig.vue
  14. 233 0
      src/views/iot/rule/scene/components/inputs/CronBuilder.vue
  15. 142 0
      src/views/iot/rule/scene/components/inputs/CronInput.vue
  16. 184 0
      src/views/iot/rule/scene/components/inputs/DescriptionInput.vue
  17. 162 0
      src/views/iot/rule/scene/components/inputs/NameInput.vue
  18. 185 0
      src/views/iot/rule/scene/components/inputs/StatusRadio.vue
  19. 406 0
      src/views/iot/rule/scene/components/inputs/ValueInput.vue
  20. 127 0
      src/views/iot/rule/scene/components/previews/ActionPreview.vue
  21. 65 0
      src/views/iot/rule/scene/components/previews/ConfigPreview.vue
  22. 226 0
      src/views/iot/rule/scene/components/previews/NextExecutionPreview.vue
  23. 131 0
      src/views/iot/rule/scene/components/previews/TriggerPreview.vue
  24. 120 0
      src/views/iot/rule/scene/components/previews/ValidationResult.vue
  25. 390 0
      src/views/iot/rule/scene/components/sections/ActionSection.vue
  26. 110 0
      src/views/iot/rule/scene/components/sections/BasicInfoSection.vue
  27. 183 0
      src/views/iot/rule/scene/components/sections/PreviewSection.vue
  28. 395 0
      src/views/iot/rule/scene/components/sections/TriggerSection.vue
  29. 145 0
      src/views/iot/rule/scene/components/selectors/ActionTypeSelector.vue
  30. 275 0
      src/views/iot/rule/scene/components/selectors/OperatorSelector.vue
  31. 325 0
      src/views/iot/rule/scene/components/selectors/ProductDeviceSelector.vue
  32. 341 0
      src/views/iot/rule/scene/components/selectors/PropertySelector.vue
  33. 264 0
      src/views/iot/rule/scene/components/selectors/TriggerTypeSelector.vue
  34. 611 145
      src/views/iot/rule/scene/index.vue
  35. 548 0
      src/views/iot/rule/scene/utils/errorHandler.ts
  36. 406 0
      src/views/iot/rule/scene/utils/transform.ts
  37. 278 0
      src/views/iot/rule/scene/utils/validation.ts

+ 117 - 26
src/api/iot/rule/scene/scene.types.ts

@@ -50,6 +50,19 @@ const IotAlertConfigReceiveTypeEnum = {
   NOTIFY: 3 // 通知
 } as const
 
+// 设备状态枚举
+const DeviceStateEnum = {
+  INACTIVE: 0, // 未激活
+  ONLINE: 1, // 在线
+  OFFLINE: 2 // 离线
+} as const
+
+// 通用状态枚举
+const CommonStatusEnum = {
+  ENABLE: 0, // 开启
+  DISABLE: 1 // 关闭
+} as const
+
 // 基础接口
 interface TenantBaseDO {
   createTime?: Date // 创建时间
@@ -62,10 +75,10 @@ interface TenantBaseDO {
 
 // 触发条件参数
 interface TriggerConditionParameter {
-  identifier0: string // 标识符(事件、服务)
-  identifier: string // 标识符(属性)
-  operator: string // 操作符
-  value: string // 比较值
+  identifier0?: string // 标识符(事件、服务)
+  identifier?: string // 标识符(属性)
+  operator: string // 操作符(必填)
+  value: string // 比较值(必填,多值用逗号分隔)
 }
 
 // 触发条件
@@ -77,39 +90,104 @@ interface TriggerCondition {
 
 // 触发器配置
 interface TriggerConfig {
-  key: any // 解决组件索引重用
-  type: number // 触发类型
-  productKey: string // 产品标识
-  deviceNames: string[] // 设备名称数组
-  conditions?: TriggerCondition[] // 触发条件数组
-  cronExpression?: string // CRON 表达式
+  key?: string // 组件唯一标识符,用于解决索引重用问题
+  type: number // 触发类型(必填)
+  productKey?: string // 产品标识(设备触发时必填)
+  deviceNames?: string[] // 设备名称数组(设备触发时必填)
+  conditions?: TriggerCondition[] // 触发条件数组(设备触发时必填)
+  cronExpression?: string // CRON表达式(定时触发时必填)
 }
 
 // 执行设备控制
 interface ActionDeviceControl {
-  productKey: string // 产品标识
-  deviceNames: string[] // 设备名称数组
-  type: string // 消息类型
-  identifier: string // 消息标识符
-  data: Record<string, any> // 具体数据
+  productKey: string // 产品标识(必填)
+  deviceNames: string[] // 设备名称数组(必填)
+  type: string // 消息类型(必填)
+  identifier: string // 消息标识符(必填)
+  params: Record<string, any> // 参数对象(必填)- 统一使用 params 字段
 }
 
 // 执行器配置
 interface ActionConfig {
-  key: any // 解决组件索引重用 TODO @puhui999:看看有没更好的解决方案呢。
-  type: number // 执行类型
-  deviceControl?: ActionDeviceControl // 设备控制
-  alertConfigId?: number // 告警配置ID(告警恢复时需要)
+  key?: string // 组件唯一标识符,用于解决索引重用问题
+  type: number // 执行类型(必填)
+  deviceControl?: ActionDeviceControl // 设备控制(设备控制时必填)
+  alertConfigId?: number // 告警配置ID(告警恢复时必填)
+}
+
+// 表单数据接口
+interface RuleSceneFormData {
+  id?: number
+  name: string
+  description?: string
+  status: number
+  triggers: TriggerFormData[]
+  actions: ActionFormData[]
+}
+
+interface TriggerFormData {
+  type: number
+  productId?: number
+  deviceId?: number
+  identifier?: string
+  operator?: string
+  value?: string
+  cronExpression?: string
+  conditionGroups?: ConditionGroupFormData[]
+}
+
+interface ActionFormData {
+  type: number
+  productId?: number
+  deviceId?: number
+  params?: Record<string, any>
+  alertConfigId?: number
+}
+
+interface ConditionGroupFormData {
+  conditions: ConditionFormData[]
+  logicOperator: 'AND' | 'OR'
+}
+
+interface ConditionFormData {
+  type: number
+  productId: number
+  deviceId: number
+  identifier: string
+  operator: string
+  param: string
 }
 
 // 主接口
 interface IotRuleScene extends TenantBaseDO {
-  id: number // 场景编号
-  name: string // 场景名称
-  description: string // 场景描述
-  status: number // 场景状态
-  triggers: TriggerConfig[] // 触发器数组
-  actions: ActionConfig[] // 执行器数组
+  id?: number // 场景编号(新增时为空)
+  name: string // 场景名称(必填)
+  description?: string // 场景描述(可选)
+  status: number // 场景状态:0-开启,1-关闭
+  triggers: TriggerConfig[] // 触发器数组(必填,至少一个)
+  actions: ActionConfig[] // 执行器数组(必填,至少一个)
+}
+
+// 工具类型
+type TriggerType = (typeof IotRuleSceneTriggerTypeEnum)[keyof typeof IotRuleSceneTriggerTypeEnum]
+type ActionType = (typeof IotRuleSceneActionTypeEnum)[keyof typeof IotRuleSceneActionTypeEnum]
+type MessageType = (typeof IotDeviceMessageTypeEnum)[keyof typeof IotDeviceMessageTypeEnum]
+type OperatorType =
+  (typeof IotRuleSceneTriggerConditionParameterOperatorEnum)[keyof typeof IotRuleSceneTriggerConditionParameterOperatorEnum]['value']
+
+// 表单验证规则类型
+interface ValidationRule {
+  required?: boolean
+  message?: string
+  trigger?: string | string[]
+  type?: string
+  min?: number
+  max?: number
+  enum?: any[]
+}
+
+interface FormValidationRules {
+  [key: string]: ValidationRule[]
 }
 
 export {
@@ -119,10 +197,23 @@ export {
   TriggerConditionParameter,
   ActionConfig,
   ActionDeviceControl,
+  RuleSceneFormData,
+  TriggerFormData,
+  ActionFormData,
+  ConditionGroupFormData,
+  ConditionFormData,
   IotRuleSceneTriggerTypeEnum,
   IotRuleSceneActionTypeEnum,
   IotDeviceMessageTypeEnum,
   IotDeviceMessageIdentifierEnum,
   IotRuleSceneTriggerConditionParameterOperatorEnum,
-  IotAlertConfigReceiveTypeEnum
+  IotAlertConfigReceiveTypeEnum,
+  DeviceStateEnum,
+  CommonStatusEnum,
+  TriggerType,
+  ActionType,
+  MessageType,
+  OperatorType,
+  ValidationRule,
+  FormValidationRules
 }

+ 3 - 2
src/views/iot/device/device/detail/DeviceDetailsMessage.vue

@@ -116,8 +116,8 @@ const queryParams = reactive({
 const loading = ref(false)
 const total = ref(0)
 const list = ref([])
-const autoRefresh = ref(false)
-let autoRefreshTimer: any = null // TODO @super:autoRefreshEnable,autoRefreshTimer;对应上
+const autoRefresh = ref(false) // 自动刷新开关
+let autoRefreshTimer: any = null // 自动刷新定时器
 
 // 消息方法选项
 const methodOptions = computed(() => {
@@ -172,6 +172,7 @@ watch(
 onBeforeUnmount(() => {
   if (autoRefreshTimer) {
     clearInterval(autoRefreshTimer)
+    autoRefreshTimer = null
   }
 })
 

+ 2 - 3
src/views/iot/ota/task/OtaTaskList.vue

@@ -7,15 +7,14 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item>
         <el-button type="primary" @click="openTaskForm" v-hasPermi="['iot:ota-task:create']">
           <Icon icon="ep:plus" class="mr-5px" /> 新增
         </el-button>
       </el-form-item>
-      <!-- TODO @AI:unocss -->
-      <el-form-item style="float: right">
-        <!--TODO @AI:有个 bug:回车后,会刷新,修复下 -->
+      <el-form-item class="float-right">
         <el-input
           v-model="queryParams.name"
           placeholder="请输入任务名称"

+ 192 - 0
src/views/iot/rule/scene/111index.vue

@@ -0,0 +1,192 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="场景名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入场景名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="场景状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择场景状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['iot:rule-scene:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="场景编号" align="center" prop="id" />
+      <el-table-column label="场景名称" align="center" prop="name" />
+      <el-table-column label="场景描述" align="center" prop="description" />
+      <el-table-column label="场景状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="触发器" align="center" prop="triggers">
+        <template #default="{ row }"> {{ row.triggers?.length }}个 </template>
+      </el-table-column>
+      <el-table-column label="执行器" align="center" prop="actions">
+        <template #default="{ row }"> {{ row.actions?.length }}个 </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['iot:rule-scene:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['iot:rule-scene:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <RuleSceneForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import { RuleSceneApi } from '@/api/iot/rule/scene'
+import RuleSceneForm from './RuleSceneForm.vue'
+import { IotRuleScene } from '@/api/iot/rule/scene/scene.types'
+
+/** IoT 场景联动 列表 */
+defineOptions({ name: 'IotRuleScene' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRuleScene[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  description: undefined,
+  status: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await RuleSceneApi.getRuleScenePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await RuleSceneApi.deleteRuleScene(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 1101 - 0
src/views/iot/rule/scene/IoT场景联动规则表单设计思路文档.md

@@ -0,0 +1,1101 @@
+# IoT场景联动规则表单设计思路文档
+
+## 概述
+
+本文档详细描述了IoT场景联动规则表单的设计思路,包括表单结构、组件设计、数据流转和用户交互逻辑。通过Mermaid图直观展示各个组件之间的关系和数据流向。
+
+## 表单整体架构设计
+
+### 1. 表单主体结构
+
+表单采用分步骤设计,包含以下主要部分:
+
+- **基础信息配置**:场景名称、描述、状态
+- **触发器配置**:设备触发或定时触发
+- **执行器配置**:设备控制或告警配置
+- **预览与保存**:配置预览和最终保存
+
+### 2. 组件层次结构图
+
+```mermaid
+graph TB
+    A[RuleSceneForm<br/>主表单组件] --> B[BasicInfoSection<br/>基础信息]
+    A --> C[TriggerSection<br/>触发器配置]
+    A --> D[ActionSection<br/>执行器配置]
+    A --> E[PreviewSection<br/>预览区域]
+
+    %% 基础信息组件
+    B --> B1[NameInput<br/>场景名称输入]
+    B --> B2[DescriptionInput<br/>场景描述输入]
+    B --> B3[StatusRadio<br/>状态选择]
+
+    %% 触发器配置组件
+    C --> C1[TriggerTypeSelector<br/>触发类型选择器]
+    C --> C2[DeviceTriggerConfig<br/>设备触发配置]
+    C --> C3[TimerTriggerConfig<br/>定时触发配置]
+
+    %% 设备触发配置子组件
+    C2 --> C21[ProductSelector<br/>产品选择器]
+    C2 --> C22[DeviceSelector<br/>设备选择器]
+    C2 --> C23[PropertySelector<br/>属性选择器]
+    C2 --> C24[OperatorSelector<br/>操作符选择器]
+    C2 --> C25[ValueInput<br/>值输入]
+    C2 --> C26[ConditionGroupConfig<br/>条件分组配置]
+
+    %% 定时触发配置子组件
+    C3 --> C31[CronInput<br/>CRON表达式输入]
+    C3 --> C32[CronBuilder<br/>可视化CRON构建器]
+    C3 --> C33[NextExecutionPreview<br/>下次执行时间预览]
+
+    %% 执行器配置组件
+    D --> D1[ActionTypeSelector<br/>执行类型选择器]
+    D --> D2[DeviceControlConfig<br/>设备控制配置]
+    D --> D3[AlertConfig<br/>告警配置]
+
+    %% 设备控制配置子组件
+    D2 --> D21[TargetProductSelector<br/>目标产品选择器]
+    D2 --> D22[TargetDeviceSelector<br/>目标设备选择器]
+    D2 --> D23[ControlTypeSelector<br/>控制类型选择器]
+    D2 --> D24[ParamsConfig<br/>参数配置]
+
+    %% 告警配置子组件
+    D3 --> D31[AlertConfigSelector<br/>告警配置选择器]
+
+    %% 预览区域组件
+    E --> E1[ConfigPreview<br/>配置预览]
+    E --> E2[ValidationResult<br/>验证结果]
+    E --> E3[SaveButton<br/>保存按钮]
+
+    %% 样式定义
+    classDef mainComponent fill:#e1f5fe,stroke:#01579b,stroke-width:2px
+    classDef sectionComponent fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
+    classDef subComponent fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
+
+    class A mainComponent
+    class B,C,D,E sectionComponent
+    class B1,B2,B3,C1,C2,C3,C21,C22,C23,C24,C25,C26,C31,C32,C33,D1,D2,D3,D21,D22,D23,D24,D31,E1,E2,E3 subComponent
+```
+
+### 3. 组件层次结构文本
+
+```text
+RuleSceneForm (主表单)
+├── BasicInfoSection (基础信息)
+│   ├── NameInput (场景名称输入)
+│   ├── DescriptionInput (场景描述输入)
+│   └── StatusRadio (状态选择)
+├── TriggerSection (触发器配置)
+│   ├── TriggerTypeSelector (触发类型选择)
+│   ├── DeviceTriggerConfig (设备触发配置)
+│   │   ├── ProductSelector (产品选择器)
+│   │   ├── DeviceSelector (设备选择器)
+│   │   ├── PropertySelector (属性选择器)
+│   │   ├── OperatorSelector (操作符选择器)
+│   │   ├── ValueInput (值输入)
+│   │   └── ConditionGroupConfig (条件分组配置)
+│   └── TimerTriggerConfig (定时触发配置)
+│       ├── CronInput (CRON表达式输入)
+│       ├── CronBuilder (可视化CRON构建器)
+│       └── NextExecutionPreview (下次执行时间预览)
+├── ActionSection (执行器配置)
+│   ├── ActionTypeSelector (执行类型选择)
+│   ├── DeviceControlConfig (设备控制配置)
+│   │   ├── TargetProductSelector (目标产品选择器)
+│   │   ├── TargetDeviceSelector (目标设备选择器)
+│   │   ├── ControlTypeSelector (控制类型选择器)
+│   │   └── ParamsConfig (参数配置)
+│   └── AlertConfig (告警配置)
+│       └── AlertConfigSelector (告警配置选择器)
+└── PreviewSection (预览区域)
+    ├── ConfigPreview (配置预览)
+    ├── ValidationResult (验证结果)
+    └── SaveButton (保存按钮)
+```
+
+## 表单数据结构设计
+
+### 1. 表单数据模型结构图
+
+```mermaid
+classDiagram
+    class RuleSceneFormData {
+        +number id?
+        +string name
+        +string description?
+        +number status
+        +TriggerFormData[] triggers
+        +ActionFormData[] actions
+        +validateForm() boolean
+        +toApiFormat() ApiRequestData
+    }
+
+    class TriggerFormData {
+        +number type
+        +number productId?
+        +number deviceId?
+        +string identifier?
+        +string operator?
+        +string value?
+        +string cronExpression?
+        +ConditionGroupFormData[] conditionGroups?
+        +validateTrigger() boolean
+        +isDeviceTrigger() boolean
+        +isTimerTrigger() boolean
+    }
+
+    class ActionFormData {
+        +number type
+        +number productId?
+        +number deviceId?
+        +Record params?
+        +number alertConfigId?
+        +validateAction() boolean
+        +isDeviceAction() boolean
+        +isAlertAction() boolean
+    }
+
+    class ConditionGroupFormData {
+        +ConditionFormData[] conditions
+        +string logicOperator
+        +validateGroup() boolean
+    }
+
+    class ConditionFormData {
+        +number type
+        +number productId
+        +number deviceId
+        +string identifier
+        +string operator
+        +string param
+        +validateCondition() boolean
+    }
+
+    class TriggerTypeEnum {
+        <<enumeration>>
+        DEVICE_STATE_UPDATE: 1
+        DEVICE_PROPERTY_POST: 2
+        DEVICE_EVENT_POST: 3
+        DEVICE_SERVICE_INVOKE: 4
+        TIMER: 100
+    }
+
+    class ActionTypeEnum {
+        <<enumeration>>
+        DEVICE_PROPERTY_SET: 1
+        DEVICE_SERVICE_INVOKE: 2
+        ALERT_TRIGGER: 100
+        ALERT_RECOVER: 101
+    }
+
+    class OperatorEnum {
+        <<enumeration>>
+        EQUALS: "="
+        NOT_EQUALS: "!="
+        GREATER_THAN: ">"
+        LESS_THAN: "<"
+        IN: "in"
+        BETWEEN: "between"
+    }
+
+    RuleSceneFormData "1" --> "*" TriggerFormData : contains
+    RuleSceneFormData "1" --> "*" ActionFormData : contains
+    TriggerFormData "1" --> "*" ConditionGroupFormData : contains
+    ConditionGroupFormData "1" --> "*" ConditionFormData : contains
+    TriggerFormData --> TriggerTypeEnum : uses
+    ActionFormData --> ActionTypeEnum : uses
+    ConditionFormData --> OperatorEnum : uses
+```
+
+### 2. 表单数据模型代码
+
+```typescript
+interface RuleSceneFormData {
+  // 基础信息
+  id?: number;
+  name: string;
+  description?: string;
+  status: number;
+
+  // 触发器配置
+  triggers: TriggerFormData[];
+
+  // 执行器配置
+  actions: ActionFormData[];
+}
+
+interface TriggerFormData {
+  type: number;
+  productId?: number;
+  deviceId?: number;
+  identifier?: string;
+  operator?: string;
+  value?: string;
+  cronExpression?: string;
+  conditionGroups?: ConditionGroupFormData[];
+}
+
+interface ActionFormData {
+  type: number;
+  productId?: number;
+  deviceId?: number;
+  params?: Record<string, any>;
+  alertConfigId?: number;
+}
+
+interface ConditionGroupFormData {
+  conditions: ConditionFormData[];
+  logicOperator: 'AND' | 'OR';
+}
+
+interface ConditionFormData {
+  type: number;
+  productId: number;
+  deviceId: number;
+  identifier: string;
+  operator: string;
+  param: string;
+}
+```
+
+### 2. 表单验证规则
+
+```typescript
+const validationRules = {
+  name: [
+    { required: true, message: '场景名称不能为空' },
+    { max: 50, message: '场景名称不能超过50个字符' }
+  ],
+  status: [
+    { required: true, message: '场景状态不能为空' },
+    { type: 'enum', enum: [0, 1], message: '状态值必须为0或1' }
+  ],
+  triggers: [
+    { required: true, message: '触发器配置不能为空' },
+    { type: 'array', min: 1, message: '至少需要一个触发器' }
+  ],
+  actions: [
+    { required: true, message: '执行器配置不能为空' },
+    { type: 'array', min: 1, message: '至少需要一个执行器' }
+  ]
+};
+```
+
+## 核心组件设计
+
+### 1. 基础信息组件 (BasicInfoSection)
+
+```vue
+<template>
+  <el-card class="basic-info-section">
+    <template #header>
+      <span>基础信息</span>
+    </template>
+    
+    <el-form :model="formData" :rules="rules" label-width="120px">
+      <el-form-item label="场景名称" prop="name">
+        <el-input 
+          v-model="formData.name" 
+          placeholder="请输入场景名称"
+          maxlength="50"
+          show-word-limit
+        />
+      </el-form-item>
+      
+      <el-form-item label="场景描述" prop="description">
+        <el-input 
+          v-model="formData.description" 
+          type="textarea"
+          placeholder="请输入场景描述"
+          :rows="3"
+          maxlength="200"
+          show-word-limit
+        />
+      </el-form-item>
+      
+      <el-form-item label="场景状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio :label="0">开启</el-radio>
+          <el-radio :label="1">关闭</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+  </el-card>
+</template>
+```
+
+### 2. 触发器配置组件 (TriggerSection)
+
+```vue
+<template>
+  <el-card class="trigger-section">
+    <template #header>
+      <div class="section-header">
+        <span>触发器配置</span>
+        <el-button type="primary" size="small" @click="addTrigger">
+          <el-icon><Plus /></el-icon>
+          添加触发器
+        </el-button>
+      </div>
+    </template>
+    
+    <div v-for="(trigger, index) in triggers" :key="index" class="trigger-item">
+      <div class="trigger-header">
+        <span>触发器 {{ index + 1 }}</span>
+        <el-button 
+          type="danger" 
+          size="small" 
+          text 
+          @click="removeTrigger(index)"
+          v-if="triggers.length > 1"
+        >
+          删除
+        </el-button>
+      </div>
+      
+      <!-- 触发类型选择 -->
+      <TriggerTypeSelector v-model="trigger.type" @change="onTriggerTypeChange(trigger, $event)" />
+      
+      <!-- 设备触发配置 -->
+      <DeviceTriggerConfig 
+        v-if="isDeviceTrigger(trigger.type)"
+        v-model="trigger"
+      />
+      
+      <!-- 定时触发配置 -->
+      <TimerTriggerConfig 
+        v-if="trigger.type === TriggerType.TIMER"
+        v-model="trigger.cronExpression"
+      />
+    </div>
+  </el-card>
+</template>
+```
+
+### 3. 执行器配置组件 (ActionSection)
+
+```vue
+<template>
+  <el-card class="action-section">
+    <template #header>
+      <div class="section-header">
+        <span>执行器配置</span>
+        <el-button type="primary" size="small" @click="addAction">
+          <el-icon><Plus /></el-icon>
+          添加执行器
+        </el-button>
+      </div>
+    </template>
+    
+    <div v-for="(action, index) in actions" :key="index" class="action-item">
+      <div class="action-header">
+        <span>执行器 {{ index + 1 }}</span>
+        <el-button 
+          type="danger" 
+          size="small" 
+          text 
+          @click="removeAction(index)"
+          v-if="actions.length > 1"
+        >
+          删除
+        </el-button>
+      </div>
+      
+      <!-- 执行类型选择 -->
+      <ActionTypeSelector v-model="action.type" @change="onActionTypeChange(action, $event)" />
+      
+      <!-- 设备控制配置 -->
+      <DeviceControlConfig 
+        v-if="isDeviceAction(action.type)"
+        v-model="action"
+      />
+      
+      <!-- 告警配置 -->
+      <AlertConfig 
+        v-if="isAlertAction(action.type)"
+        v-model="action.alertConfigId"
+      />
+    </div>
+  </el-card>
+</template>
+```
+
+## 表单交互流程设计
+
+### 1. 表单初始化流程图
+
+```mermaid
+flowchart TD
+    A[页面加载] --> B[初始化表单数据结构]
+    B --> C[获取基础数据]
+    C --> C1[加载产品列表]
+    C --> C2[加载告警配置列表]
+    C --> C3[加载用户权限信息]
+    C1 --> D[表单渲染]
+    C2 --> D
+    C3 --> D
+    D --> E[建立双向数据绑定]
+    E --> F[表单就绪]
+
+    %% 错误处理
+    C --> G{数据加载失败?}
+    G -->|是| H[显示错误信息]
+    G -->|否| D
+    H --> I[提供重试选项]
+    I --> C
+```
+
+### 2. 触发器配置流程图
+
+```mermaid
+flowchart TD
+    A[开始配置触发器] --> B[选择触发类型]
+    B --> C{触发类型}
+
+    %% 设备触发分支
+    C -->|设备触发| D[设备触发配置]
+    D --> D1[选择产品]
+    D1 --> D2[加载设备列表]
+    D2 --> D3[选择设备]
+    D3 --> D4[加载物模型]
+    D4 --> D5[选择属性/事件]
+    D5 --> D6[选择操作符]
+    D6 --> D7[输入比较值]
+    D7 --> D8[配置条件分组]
+    D8 --> E[触发器配置完成]
+
+    %% 定时触发分支
+    C -->|定时触发| F[定时触发配置]
+    F --> F1[输入CRON表达式]
+    F1 --> F2{表达式格式}
+    F2 -->|正确| F3[显示下次执行时间]
+    F2 -->|错误| F4[显示错误提示]
+    F4 --> F5[提供可视化编辑器]
+    F5 --> F1
+    F3 --> E
+
+    %% 验证
+    E --> G[验证触发器配置]
+    G --> H{验证通过?}
+    H -->|是| I[保存触发器配置]
+    H -->|否| J[显示验证错误]
+    J --> D
+```
+
+### 3. 执行器配置流程图
+
+```mermaid
+flowchart TD
+    A[开始配置执行器] --> B[选择执行类型]
+    B --> C{执行类型}
+
+    %% 设备控制分支
+    C -->|设备控制| D[设备控制配置]
+    D --> D1[选择目标产品]
+    D1 --> D2[加载目标设备列表]
+    D2 --> D3[选择目标设备]
+    D3 --> D4[选择控制类型]
+    D4 --> D5{控制类型}
+    D5 -->|属性设置| D6[配置属性参数]
+    D5 -->|服务调用| D7[配置服务参数]
+    D6 --> E[执行器配置完成]
+    D7 --> E
+
+    %% 告警分支
+    C -->|告警触发/恢复| F[告警配置]
+    F --> F1[选择告警配置项]
+    F1 --> F2[配置告警参数]
+    F2 --> E
+
+    %% 验证
+    E --> G[验证执行器配置]
+    G --> H{验证通过?}
+    H -->|是| I[保存执行器配置]
+    H -->|否| J[显示验证错误]
+    J --> D
+```
+
+### 4. 表单提交流程图
+
+```mermaid
+flowchart TD
+    A[用户点击保存] --> B[表单验证]
+    B --> C{验证通过?}
+    C -->|否| D[显示验证错误]
+    D --> E[用户修正错误]
+    E --> B
+
+    C -->|是| F[数据转换]
+    F --> G[转换为API格式]
+    G --> H[提交请求]
+    H --> I{请求成功?}
+
+    I -->|是| J[显示成功消息]
+    J --> K[跳转到列表页面]
+
+    I -->|否| L[显示错误消息]
+    L --> M[提供重试选项]
+    M --> H
+
+    %% 加载状态
+    H --> N[显示加载状态]
+    N --> I
+```
+
+### 5. 数据流转图
+
+```mermaid
+flowchart LR
+    A[用户输入] --> B[表单组件]
+    B --> C[数据验证]
+    C --> D[状态管理]
+    D --> E[API调用]
+    E --> F[后端处理]
+    F --> G[数据库存储]
+
+    %% 反向数据流
+    G --> H[响应数据]
+    H --> I[状态更新]
+    I --> J[UI更新]
+    J --> K[用户反馈]
+
+    %% 错误处理流
+    C --> L{验证失败?}
+    L -->|是| M[错误提示]
+    M --> A
+
+    E --> N{请求失败?}
+    N -->|是| O[错误处理]
+    O --> K
+```
+
+## 组件状态管理设计
+
+### 1. 状态管理架构图
+
+```mermaid
+graph TB
+    A[全局状态管理] --> B[表单状态]
+    A --> C[UI状态]
+    A --> D[数据缓存状态]
+
+    %% 表单状态
+    B --> B1[formData<br/>表单数据]
+    B --> B2[validationErrors<br/>验证错误]
+    B --> B3[isDirty<br/>数据变更标识]
+    B --> B4[isSubmitting<br/>提交状态]
+
+    %% UI状态
+    C --> C1[loading<br/>加载状态]
+    C --> C2[activeStep<br/>当前步骤]
+    C --> C3[expandedSections<br/>展开的区域]
+    C --> C4[modalVisible<br/>弹窗显示状态]
+
+    %% 数据缓存状态
+    D --> D1[productList<br/>产品列表]
+    D --> D2[deviceList<br/>设备列表]
+    D --> D3[thingModelList<br/>物模型列表]
+    D --> D4[alertConfigList<br/>告警配置列表]
+
+    %% 状态操作
+    E[状态操作] --> E1[updateFormData<br/>更新表单数据]
+    E --> E2[validateForm<br/>验证表单]
+    E --> E3[resetForm<br/>重置表单]
+    E --> E4[submitForm<br/>提交表单]
+
+    %% 样式定义
+    classDef stateClass fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+    classDef actionClass fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
+
+    class A,B,C,D stateClass
+    class E,E1,E2,E3,E4 actionClass
+```
+
+### 2. 组件通信图
+
+```mermaid
+sequenceDiagram
+    participant U as User
+    participant F as FormComponent
+    participant T as TriggerSection
+    participant A as ActionSection
+    participant S as StateManager
+    participant API as BackendAPI
+
+    U->>F: 填写基础信息
+    F->>S: updateFormData(basicInfo)
+    S-->>F: 状态更新完成
+
+    U->>T: 配置触发器
+    T->>S: updateTriggers(triggerData)
+    S->>API: loadDeviceList(productId)
+    API-->>S: 返回设备列表
+    S-->>T: 更新设备选项
+
+    U->>A: 配置执行器
+    A->>S: updateActions(actionData)
+    S-->>A: 状态更新完成
+
+    U->>F: 点击保存
+    F->>S: validateForm()
+    S-->>F: 验证结果
+
+    alt 验证通过
+        F->>S: submitForm()
+        S->>API: saveRuleScene(formData)
+        API-->>S: 保存结果
+        S-->>F: 显示成功消息
+    else 验证失败
+        S-->>F: 显示错误信息
+    end
+```
+
+### 3. 数据流向图
+
+```mermaid
+flowchart LR
+    A[用户操作] --> B[组件事件]
+    B --> C[状态更新]
+    C --> D[数据验证]
+    D --> E{验证通过?}
+
+    E -->|是| F[更新状态]
+    E -->|否| G[显示错误]
+
+    F --> H[触发副作用]
+    H --> I[API调用]
+    I --> J[更新缓存]
+    J --> K[UI重新渲染]
+
+    G --> L[用户修正]
+    L --> A
+
+    %% 样式
+    classDef processClass fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
+    classDef decisionClass fill:#fff3e0,stroke:#f57c00,stroke-width:2px
+    classDef errorClass fill:#ffebee,stroke:#d32f2f,stroke-width:2px
+
+    class A,B,C,D,F,H,I,J,K,L processClass
+    class E decisionClass
+    class G errorClass
+```
+
+## 用户体验优化
+
+### 1. 智能提示和帮助
+
+- **字段说明**:为复杂字段提供详细说明和示例
+- **实时验证**:输入时实时验证数据格式
+- **智能推荐**:根据已选择的产品推荐相关设备
+- **预览功能**:实时预览配置效果
+
+### 2. 错误处理和反馈
+
+- **表单验证**:清晰的错误提示信息
+- **数据加载**:加载状态和错误重试机制
+- **保存反馈**:明确的成功/失败反馈
+
+### 3. 操作便利性
+
+- **批量操作**:支持批量添加/删除触发器和执行器
+- **模板功能**:提供常用场景模板
+- **导入导出**:支持配置的导入和导出
+
+## 响应式设计考虑
+
+### 1. 移动端适配
+
+- **布局调整**:在小屏幕上采用垂直布局
+- **操作优化**:增大点击区域,优化触摸操作
+- **内容精简**:在移动端隐藏非必要信息
+
+### 2. 不同屏幕尺寸适配
+
+- **大屏幕**:充分利用空间,并排显示更多内容
+- **中等屏幕**:平衡内容密度和可读性
+- **小屏幕**:优先显示核心功能
+
+## 性能优化策略
+
+### 1. 组件懒加载
+
+```javascript
+// 懒加载复杂组件
+const DeviceTriggerConfig = defineAsyncComponent(() => 
+  import('./components/DeviceTriggerConfig.vue')
+);
+```
+
+### 2. 数据缓存
+
+```javascript
+// 缓存产品和设备数据
+const productCache = new Map();
+const deviceCache = new Map();
+```
+
+### 3. 防抖处理
+
+```javascript
+// 搜索防抖
+const debouncedSearch = debounce(searchDevices, 300);
+```
+
+## 可访问性设计
+
+### 1. 键盘导航
+
+- 支持Tab键在表单元素间导航
+- 提供快捷键操作
+
+### 2. 屏幕阅读器支持
+
+- 为表单元素提供适当的标签
+- 使用ARIA属性增强可访问性
+
+### 3. 颜色和对比度
+
+- 确保足够的颜色对比度
+- 不仅依赖颜色传达信息
+
+## 表单验证策略
+
+### 1. 验证层次结构图
+
+```mermaid
+graph TB
+    A[表单验证] --> B[字段级验证]
+    A --> C[组件级验证]
+    A --> D[表单级验证]
+    A --> E[业务级验证]
+
+    %% 字段级验证
+    B --> B1[必填验证]
+    B --> B2[格式验证]
+    B --> B3[长度验证]
+    B --> B4[类型验证]
+
+    %% 组件级验证
+    C --> C1[触发器验证]
+    C --> C2[执行器验证]
+    C --> C3[条件组合验证]
+
+    %% 表单级验证
+    D --> D1[数据完整性验证]
+    D --> D2[逻辑一致性验证]
+    D --> D3[依赖关系验证]
+
+    %% 业务级验证
+    E --> E1[设备权限验证]
+    E --> E2[产品可用性验证]
+    E --> E3[规则冲突验证]
+
+    %% 样式定义
+    classDef levelClass fill:#e1f5fe,stroke:#01579b,stroke-width:2px
+    classDef validationClass fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
+
+    class A levelClass
+    class B,C,D,E levelClass
+    class B1,B2,B3,B4,C1,C2,C3,D1,D2,D3,E1,E2,E3 validationClass
+```
+
+### 2. 验证时机图
+
+```mermaid
+stateDiagram-v2
+    [*] --> 字段输入
+    字段输入 --> 实时验证: onChange
+    实时验证 --> 显示错误: 验证失败
+    实时验证 --> 清除错误: 验证通过
+    显示错误 --> 字段输入: 用户修正
+    清除错误 --> 字段输入: 继续输入
+
+    字段输入 --> 失焦验证: onBlur
+    失焦验证 --> 显示警告: 格式错误
+    失焦验证 --> 正常状态: 格式正确
+    显示警告 --> 字段输入: 重新聚焦
+    正常状态 --> 字段输入: 重新聚焦
+
+    字段输入 --> 表单提交: 用户提交
+    表单提交 --> 全量验证
+    全量验证 --> 提交成功: 验证通过
+    全量验证 --> 显示错误: 验证失败
+    提交成功 --> [*]
+    显示错误 --> 字段输入: 用户修正
+```
+
+## 测试策略
+
+### 1. 测试金字塔图
+
+```mermaid
+graph TB
+    A[测试金字塔] --> B[单元测试<br/>Unit Tests<br/>70%]
+    A --> C[集成测试<br/>Integration Tests<br/>20%]
+    A --> D[端到端测试<br/>E2E Tests<br/>10%]
+
+    %% 单元测试详细
+    B --> B1[组件渲染测试]
+    B --> B2[数据验证逻辑测试]
+    B --> B3[用户交互测试]
+    B --> B4[工具函数测试]
+
+    %% 集成测试详细
+    C --> C1[表单提交流程测试]
+    C --> C2[API调用测试]
+    C --> C3[数据转换测试]
+    C --> C4[组件间通信测试]
+
+    %% 端到端测试详细
+    D --> D1[完整用户流程测试]
+    D --> D2[浏览器兼容性测试]
+    D --> D3[响应式设计测试]
+    D --> D4[性能测试]
+
+    %% 样式定义
+    classDef pyramidClass fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px
+    classDef unitClass fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+    classDef integrationClass fill:#fff3e0,stroke:#f57c00,stroke-width:2px
+    classDef e2eClass fill:#ffebee,stroke:#d32f2f,stroke-width:2px
+
+    class A pyramidClass
+    class B,B1,B2,B3,B4 unitClass
+    class C,C1,C2,C3,C4 integrationClass
+    class D,D1,D2,D3,D4 e2eClass
+```
+
+### 2. 测试用例覆盖图
+
+```mermaid
+mindmap
+  root((测试用例覆盖))
+    功能测试
+      基础信息
+        名称输入验证
+        描述输入验证
+        状态切换测试
+      触发器配置
+        设备触发配置
+        定时触发配置
+        条件组合测试
+      执行器配置
+        设备控制配置
+        告警配置测试
+      表单提交
+        验证流程测试
+        保存流程测试
+
+    异常测试
+      网络异常
+        API调用失败
+        超时处理
+      数据异常
+        格式错误处理
+        空数据处理
+      用户异常
+        权限不足
+        操作冲突
+
+    性能测试
+      加载性能
+        首屏加载时间
+        组件渲染性能
+      交互性能
+        表单响应速度
+        数据处理性能
+      内存性能
+        内存泄漏检测
+        组件销毁测试
+
+    兼容性测试
+      浏览器兼容
+        Chrome测试
+        Firefox测试
+        Safari测试
+        Edge测试
+      设备兼容
+        桌面端测试
+        平板端测试
+        移动端测试
+```
+
+## 表单设计架构总览
+
+### 完整架构图
+
+```mermaid
+graph TB
+    %% 用户界面层
+    subgraph "用户界面层 (UI Layer)"
+        A[RuleSceneForm 主表单]
+        B[BasicInfoSection 基础信息]
+        C[TriggerSection 触发器配置]
+        D[ActionSection 执行器配置]
+        E[PreviewSection 预览区域]
+    end
+
+    %% 状态管理层
+    subgraph "状态管理层 (State Management)"
+        F[FormState 表单状态]
+        G[ValidationState 验证状态]
+        H[UIState 界面状态]
+        I[CacheState 缓存状态]
+    end
+
+    %% 业务逻辑层
+    subgraph "业务逻辑层 (Business Logic)"
+        J[FormValidator 表单验证器]
+        K[DataTransformer 数据转换器]
+        L[ConfigBuilder 配置构建器]
+        M[RuleEngine 规则引擎]
+    end
+
+    %% 数据访问层
+    subgraph "数据访问层 (Data Access)"
+        N[ProductAPI 产品接口]
+        O[DeviceAPI 设备接口]
+        P[RuleSceneAPI 规则场景接口]
+        Q[AlertAPI 告警接口]
+    end
+
+    %% 工具层
+    subgraph "工具层 (Utilities)"
+        R[CronValidator CRON验证器]
+        S[TypeChecker 类型检查器]
+        T[ErrorHandler 错误处理器]
+        U[Logger 日志记录器]
+    end
+
+    %% 连接关系
+    A --> B
+    A --> C
+    A --> D
+    A --> E
+
+    B --> F
+    C --> F
+    D --> F
+    E --> F
+
+    F --> J
+    G --> J
+    H --> K
+    I --> L
+
+    J --> M
+    K --> M
+    L --> M
+
+    M --> N
+    M --> O
+    M --> P
+    M --> Q
+
+    J --> R
+    K --> S
+    M --> T
+    A --> U
+
+    %% 样式定义
+    classDef uiClass fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+    classDef stateClass fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
+    classDef businessClass fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
+    classDef dataClass fill:#fff3e0,stroke:#f57c00,stroke-width:2px
+    classDef utilClass fill:#fce4ec,stroke:#c2185b,stroke-width:2px
+
+    class A,B,C,D,E uiClass
+    class F,G,H,I stateClass
+    class J,K,L,M businessClass
+    class N,O,P,Q dataClass
+    class R,S,T,U utilClass
+```
+
+### 设计原则总结
+
+```mermaid
+mindmap
+  root((表单设计原则))
+    用户体验
+      直观易用
+        清晰的视觉层次
+        一致的交互模式
+        智能的操作提示
+      响应迅速
+        实时验证反馈
+        快速数据加载
+        流畅的动画效果
+      错误友好
+        明确的错误信息
+        便捷的错误修正
+        优雅的异常处理
+
+    技术架构
+      组件化设计
+        高内聚低耦合
+        可复用的组件
+        清晰的组件边界
+      状态管理
+        集中式状态管理
+        可预测的状态变更
+        高效的状态同步
+      数据流控制
+        单向数据流
+        明确的数据来源
+        可追踪的数据变更
+
+    质量保证
+      代码质量
+        类型安全
+        代码规范
+        充分的注释
+      测试覆盖
+        单元测试
+        集成测试
+        端到端测试
+      性能优化
+        懒加载
+        缓存策略
+        防抖节流
+
+    可维护性
+      模块化结构
+        清晰的目录结构
+        合理的文件组织
+        明确的依赖关系
+      文档完善
+        API文档
+        组件文档
+        使用说明
+      扩展性设计
+        插件化架构
+        配置化开发
+        版本兼容性
+```
+
+## 总结
+
+IoT场景联动规则表单设计需要考虑:
+
+### 1. 核心设计要点
+
+- **复杂性管理**:通过组件化和分步骤设计降低复杂度
+- **用户体验**:提供直观的操作界面和智能提示
+- **数据完整性**:完善的验证机制确保数据质量
+- **扩展性**:模块化设计支持功能扩展
+- **性能优化**:合理的加载和缓存策略
+- **可访问性**:确保所有用户都能正常使用
+
+### 2. 技术实现要点
+
+- **状态管理**:采用集中式状态管理,确保数据流的可控性
+- **组件设计**:高内聚低耦合的组件架构,提高代码复用性
+- **验证策略**:多层次的验证机制,保证数据质量
+- **错误处理**:完善的错误处理和用户反馈机制
+- **性能优化**:懒加载、缓存、防抖等优化策略
+
+### 3. 质量保证
+
+- **测试覆盖**:完整的测试金字塔,确保代码质量
+- **文档完善**:详细的设计文档和使用说明
+- **代码规范**:统一的编码规范和类型安全
+
+通过以上设计思路和详细的Mermaid图表,可以构建一个功能完善、用户友好、技术先进的IoT场景联动规则配置表单系统。

+ 315 - 0
src/views/iot/rule/scene/components/RuleSceneForm.vue

@@ -0,0 +1,315 @@
+<!-- IoT场景联动规则表单 - 主表单组件 -->
+<template>
+  <el-drawer
+    v-model="drawerVisible"
+    :title="drawerTitle"
+    size="80%"
+    direction="rtl"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    @close="handleClose"
+    class="rule-scene-drawer"
+  >
+    <div class="rule-scene-form">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="formRules"
+        label-width="120px"
+        class="form-container"
+      >
+        <!-- 基础信息配置 -->
+        <BasicInfoSection
+          v-model="formData"
+          :rules="formRules"
+        />
+
+        <!-- 触发器配置 -->
+        <TriggerSection
+          v-model:triggers="formData.triggers"
+          @validate="handleTriggerValidate"
+        />
+
+        <!-- 执行器配置 -->
+        <ActionSection
+          v-model:actions="formData.actions"
+          @validate="handleActionValidate"
+        />
+
+        <!-- 预览区域 -->
+        <PreviewSection
+          :form-data="formData"
+          :validation-result="validationResult"
+          @validate="handleValidate"
+        />
+      </el-form>
+    </div>
+
+    <!-- 抽屉底部操作栏 -->
+    <template #footer>
+      <div class="drawer-footer">
+        <el-button @click="handleClose" size="large">取消</el-button>
+        <el-button
+          type="primary"
+          @click="handleSubmit"
+          :loading="submitLoading"
+          :disabled="!canSubmit"
+          size="large"
+        >
+          {{ isEdit ? '更新' : '创建' }}
+        </el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import BasicInfoSection from './sections/BasicInfoSection.vue'
+import TriggerSection from './sections/TriggerSection.vue'
+import ActionSection from './sections/ActionSection.vue'
+import PreviewSection from './sections/PreviewSection.vue'
+import { RuleSceneFormData, IotRuleScene } from '@/api/iot/rule/scene/scene.types'
+import { getBaseValidationRules } from '../utils/validation'
+import { 
+  transformFormToApi, 
+  transformApiToForm, 
+  createDefaultFormData 
+} from '../utils/transform'
+import { 
+  handleValidationError, 
+  handleNetworkError, 
+  showSuccess, 
+  withErrorHandling 
+} from '../utils/errorHandler'
+
+/** IoT场景联动规则表单 - 主表单组件 */
+defineOptions({ name: 'RuleSceneForm' })
+
+interface Props {
+  modelValue: boolean
+  ruleScene?: IotRuleScene
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: boolean): void
+  (e: 'success'): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const drawerVisible = useVModel(props, 'modelValue', emit)
+
+// 表单数据和状态
+const formRef = ref()
+const formData = ref<RuleSceneFormData>(createDefaultFormData())
+const formRules = getBaseValidationRules()
+const submitLoading = ref(false)
+const validationResult = ref<{ valid: boolean; message?: string } | null>(null)
+
+// 验证状态
+const triggerValidation = ref({ valid: true, message: '' })
+const actionValidation = ref({ valid: true, message: '' })
+
+// 计算属性
+const isEdit = computed(() => !!props.ruleScene?.id)
+const drawerTitle = computed(() => isEdit.value ? '编辑场景联动规则' : '新增场景联动规则')
+
+const canSubmit = computed(() => {
+  return formData.value.name && 
+         formData.value.triggers.length > 0 && 
+         formData.value.actions.length > 0 &&
+         triggerValidation.value.valid &&
+         actionValidation.value.valid
+})
+
+// 事件处理
+const handleTriggerValidate = (result: { valid: boolean; message: string }) => {
+  triggerValidation.value = result
+}
+
+const handleActionValidate = (result: { valid: boolean; message: string }) => {
+  actionValidation.value = result
+}
+
+const handleValidate = async () => {
+  try {
+    await formRef.value?.validate()
+    
+    if (!triggerValidation.value.valid) {
+      throw new Error(triggerValidation.value.message)
+    }
+    
+    if (!actionValidation.value.valid) {
+      throw new Error(actionValidation.value.message)
+    }
+    
+    validationResult.value = { valid: true, message: '验证通过' }
+    showSuccess('规则验证通过')
+    return true
+  } catch (error: any) {
+    const message = error.message || '表单验证失败'
+    validationResult.value = { valid: false, message }
+    await handleValidationError(message, 'rule-scene-form')
+    return false
+  }
+}
+
+const handleSubmit = async () => {
+  const result = await withErrorHandling(
+    async () => {
+      // 验证表单
+      const isValid = await handleValidate()
+      if (!isValid) {
+        throw new Error('表单验证失败')
+      }
+      
+      // 转换数据格式
+      const apiData = transformFormToApi(formData.value)
+      
+      // 这里应该调用API保存数据
+      console.log('提交数据:', apiData)
+      
+      // 模拟API调用
+      await new Promise(resolve => setTimeout(resolve, 1000))
+      
+      return apiData
+    },
+    {
+      loadingKey: 'rule-scene-submit',
+      loadingText: isEdit.value ? '更新中...' : '创建中...',
+      context: 'rule-scene-form',
+      showSuccess: true,
+      successMessage: isEdit.value ? '更新成功' : '创建成功'
+    }
+  )
+  
+  if (result) {
+    emit('success')
+    handleClose()
+  }
+}
+
+const handleClose = () => {
+  drawerVisible.value = false
+  validationResult.value = null
+}
+
+// 初始化表单数据
+const initFormData = () => {
+  if (props.ruleScene) {
+    formData.value = transformApiToForm(props.ruleScene)
+  } else {
+    formData.value = createDefaultFormData()
+  }
+}
+
+// 监听抽屉显示
+watch(drawerVisible, (visible) => {
+  if (visible) {
+    initFormData()
+    nextTick(() => {
+      formRef.value?.clearValidate()
+    })
+  }
+})
+
+// 监听props变化
+watch(() => props.ruleScene, () => {
+  if (drawerVisible.value) {
+    initFormData()
+  }
+})
+</script>
+
+<style scoped>
+.rule-scene-drawer {
+  --el-drawer-padding-primary: 20px;
+}
+
+.rule-scene-form {
+  height: calc(100vh - 120px);
+  overflow-y: auto;
+  padding: 20px;
+  padding-bottom: 80px; /* 为底部操作栏留出空间 */
+}
+
+.form-container {
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+}
+
+.drawer-footer {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  display: flex;
+  justify-content: flex-end;
+  gap: 16px;
+  padding: 16px 20px;
+  background: var(--el-bg-color);
+  border-top: 1px solid var(--el-border-color-light);
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 滚动条样式 */
+.rule-scene-form::-webkit-scrollbar {
+  width: 6px;
+}
+
+.rule-scene-form::-webkit-scrollbar-track {
+  background: var(--el-fill-color-light);
+  border-radius: 3px;
+}
+
+.rule-scene-form::-webkit-scrollbar-thumb {
+  background: var(--el-border-color);
+  border-radius: 3px;
+}
+
+.rule-scene-form::-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) {
+  .rule-scene-drawer {
+    --el-drawer-size: 100% !important;
+  }
+
+  .rule-scene-form {
+    padding: 16px;
+    padding-bottom: 80px;
+  }
+
+  .form-container {
+    gap: 20px;
+  }
+
+  .drawer-footer {
+    padding: 12px 16px;
+    gap: 12px;
+  }
+}
+</style>

+ 1 - 1
src/views/iot/rule/scene/components/action/ActionExecutor.vue

@@ -25,7 +25,7 @@
             {{ product ? product.name : '选择产品' }}
           </el-button>
         </div>
-        <!-- TODO @puhui999:单选设备 -->
+        <!-- TODO @puhui999:单选设备,默认不选就是全部设备 -->
         <div v-if="isDeviceAction" class="flex items-center mr-60px">
           <span class="mr-10px">设备</span>
           <el-button type="primary" @click="handleSelectDevice" size="small" plain>

+ 288 - 0
src/views/iot/rule/scene/components/configs/AlertConfig.vue

@@ -0,0 +1,288 @@
+<!-- 告警配置组件 -->
+<template>
+  <div class="alert-config">
+    <el-form-item label="告警配置" required>
+      <el-select
+        v-model="localValue"
+        placeholder="请选择告警配置"
+        filterable
+        clearable
+        @change="handleChange"
+        class="w-full"
+        :loading="loading"
+      >
+        <el-option
+          v-for="config in alertConfigs"
+          :key="config.id"
+          :label="config.name"
+          :value="config.id"
+        >
+          <div class="alert-option">
+            <div class="option-content">
+              <div class="option-name">{{ config.name }}</div>
+              <div class="option-desc">{{ config.description }}</div>
+            </div>
+            <el-tag :type="config.enabled ? 'success' : 'danger'" size="small">
+              {{ config.enabled ? '启用' : '禁用' }}
+            </el-tag>
+          </div>
+        </el-option>
+      </el-select>
+    </el-form-item>
+
+    <!-- 告警配置详情 -->
+    <div v-if="selectedConfig" class="alert-details">
+      <div class="details-header">
+        <Icon icon="ep:bell" class="details-icon" />
+        <span class="details-title">{{ selectedConfig.name }}</span>
+        <el-tag :type="selectedConfig.enabled ? 'success' : 'danger'" size="small">
+          {{ selectedConfig.enabled ? '启用' : '禁用' }}
+        </el-tag>
+      </div>
+      <div class="details-content">
+        <div class="detail-item">
+          <span class="detail-label">描述:</span>
+          <span class="detail-value">{{ selectedConfig.description }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-label">通知方式:</span>
+          <span class="detail-value">{{ getNotifyTypeName(selectedConfig.notifyType) }}</span>
+        </div>
+        <div v-if="selectedConfig.receivers" class="detail-item">
+          <span class="detail-label">接收人:</span>
+          <span class="detail-value">{{ selectedConfig.receivers.join(', ') }}</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="validation-result">
+      <el-alert
+        :title="validationMessage"
+        :type="isValid ? 'success' : 'error'"
+        :closable="false"
+        show-icon
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** 告警配置组件 */
+defineOptions({ name: 'AlertConfig' })
+
+interface Props {
+  modelValue?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value?: number): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+// 状态
+const loading = ref(false)
+const alertConfigs = ref<any[]>([])
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 计算属性
+const selectedConfig = computed(() => {
+  return alertConfigs.value.find(config => config.id === localValue.value)
+})
+
+// 工具函数
+const getNotifyTypeName = (type: number) => {
+  const typeMap = {
+    1: '邮件通知',
+    2: '短信通知',
+    3: '微信通知',
+    4: '钉钉通知'
+  }
+  return typeMap[type] || '未知'
+}
+
+// 事件处理
+const handleChange = () => {
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  if (!localValue.value) {
+    isValid.value = false
+    validationMessage.value = '请选择告警配置'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  const config = selectedConfig.value
+  if (!config) {
+    isValid.value = false
+    validationMessage.value = '告警配置不存在'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  if (!config.enabled) {
+    isValid.value = false
+    validationMessage.value = '选择的告警配置已禁用'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  // 验证通过
+  isValid.value = true
+  validationMessage.value = '告警配置验证通过'
+  emit('validate', { valid: true, message: validationMessage.value })
+}
+
+// API 调用
+const getAlertConfigs = async () => {
+  loading.value = true
+  try {
+    // 这里应该调用真实的API获取告警配置
+    // 暂时使用模拟数据
+    alertConfigs.value = [
+      {
+        id: 1,
+        name: '设备异常告警',
+        description: '设备状态异常时发送告警',
+        enabled: true,
+        notifyType: 1,
+        receivers: ['admin@example.com', 'operator@example.com']
+      },
+      {
+        id: 2,
+        name: '温度超限告警',
+        description: '温度超过阈值时发送告警',
+        enabled: true,
+        notifyType: 2,
+        receivers: ['13800138000', '13900139000']
+      },
+      {
+        id: 3,
+        name: '系统故障告警',
+        description: '系统发生故障时发送告警',
+        enabled: false,
+        notifyType: 3,
+        receivers: ['技术支持群']
+      }
+    ]
+  } catch (error) {
+    console.error('获取告警配置失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 监听值变化
+watch(() => localValue.value, () => {
+  updateValidationResult()
+})
+
+// 初始化
+onMounted(() => {
+  getAlertConfigs()
+  if (localValue.value) {
+    updateValidationResult()
+  }
+})
+</script>
+
+<style scoped>
+.alert-config {
+  width: 100%;
+}
+
+.alert-option {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 4px 0;
+}
+
+.option-content {
+  flex: 1;
+}
+
+.option-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 2px;
+}
+
+.option-desc {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.alert-details {
+  margin-top: 12px;
+  padding: 12px;
+  background: var(--el-fill-color-light);
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.details-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.details-icon {
+  color: var(--el-color-warning);
+  font-size: 14px;
+}
+
+.details-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.details-content {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-left: 22px;
+}
+
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  gap: 8px;
+}
+
+.detail-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  min-width: 60px;
+  flex-shrink: 0;
+}
+
+.detail-value {
+  font-size: 12px;
+  color: var(--el-text-color-primary);
+  flex: 1;
+}
+
+.validation-result {
+  margin-top: 8px;
+}
+
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 8px 20px;
+}
+</style>

+ 260 - 0
src/views/iot/rule/scene/components/configs/ConditionConfig.vue

@@ -0,0 +1,260 @@
+<!-- 单个条件配置组件 -->
+<template>
+  <div class="condition-config">
+    <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>
+      </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="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-if="conditionPreview" class="condition-preview">
+      <div class="preview-header">
+        <Icon icon="ep:view" class="preview-icon" />
+        <span class="preview-title">条件预览</span>
+      </div>
+      <div class="preview-content">
+        <code class="preview-text">{{ conditionPreview }}</code>
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="validation-result">
+      <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 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'
+
+/** 单个条件配置组件 */
+defineOptions({ name: 'ConditionConfig' })
+
+interface Props {
+  modelValue: ConditionFormData
+  triggerType: number
+  productId?: number
+  deviceId?: 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 condition = useVModel(props, 'modelValue', emit)
+
+// 状态
+const propertyType = ref<string>('string')
+const propertyConfig = ref<any>(null)
+const validationMessage = ref('')
+const isValid = ref(true)
+const valueValidation = ref({ valid: true, message: '' })
+
+// 计算属性
+const conditionPreview = computed(() => {
+  if (!condition.value.identifier || !condition.value.operator || !condition.value.param) {
+    return ''
+  }
+  
+  const propertyName = propertyConfig.value?.name || condition.value.identifier
+  const operatorText = getOperatorText(condition.value.operator)
+  const value = condition.value.param
+  
+  return `当 ${propertyName} ${operatorText} ${value} 时触发`
+})
+
+// 工具函数
+const getOperatorText = (operator: string) => {
+  const operatorMap = {
+    '=': '等于',
+    '!=': '不等于',
+    '>': '大于',
+    '>=': '大于等于',
+    '<': '小于',
+    '<=': '小于等于',
+    'in': '包含于',
+    'between': '介于'
+  }
+  return operatorMap[operator] || operator
+}
+
+// 事件处理
+const updateConditionField = (field: keyof ConditionFormData, value: any) => {
+  condition.value[field] = value
+  emit('update:modelValue', condition.value)
+}
+
+const handlePropertyChange = (propertyInfo: { type: string; config: any }) => {
+  propertyType.value = propertyInfo.type
+  propertyConfig.value = propertyInfo.config
+  
+  // 重置操作符和值
+  condition.value.operator = '='
+  condition.value.param = ''
+  
+  updateValidationResult()
+}
+
+const handleOperatorChange = () => {
+  // 重置值
+  condition.value.param = ''
+  updateValidationResult()
+}
+
+const handleValueValidate = (result: { valid: boolean; message: string }) => {
+  valueValidation.value = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  // 基础验证
+  if (!condition.value.identifier) {
+    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
+  }
+  
+  if (!condition.value.param) {
+    isValid.value = false
+    validationMessage.value = '请输入比较值'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  // 值验证
+  if (!valueValidation.value.valid) {
+    isValid.value = false
+    validationMessage.value = valueValidation.value.message
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  // 验证通过
+  isValid.value = true
+  validationMessage.value = '条件配置验证通过'
+  emit('validate', { valid: true, message: validationMessage.value })
+}
+
+// 监听条件变化
+watch(() => [condition.value.identifier, condition.value.operator, condition.value.param], () => {
+  updateValidationResult()
+}, { deep: true })
+
+// 初始化
+onMounted(() => {
+  updateValidationResult()
+})
+</script>
+
+<style scoped>
+.condition-config {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.condition-preview {
+  padding: 12px;
+  background: var(--el-fill-color-light);
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.preview-header {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-bottom: 8px;
+}
+
+.preview-icon {
+  color: var(--el-color-primary);
+  font-size: 14px;
+}
+
+.preview-title {
+  font-size: 12px;
+  font-weight: 500;
+  color: var(--el-text-color-secondary);
+}
+
+.preview-content {
+  margin-left: 20px;
+}
+
+.preview-text {
+  font-size: 14px;
+  color: var(--el-text-color-primary);
+  background: var(--el-fill-color-blank);
+  padding: 8px 12px;
+  border-radius: 4px;
+  display: block;
+  font-family: inherit;
+}
+
+.validation-result {
+  margin-top: 8px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+</style>

+ 331 - 0
src/views/iot/rule/scene/components/configs/ConditionGroupConfig.vue

@@ -0,0 +1,331 @@
+<!-- 条件组配置组件 -->
+<template>
+  <div class="condition-group-config">
+    <div class="group-content">
+      <!-- 条件列表 -->
+      <div v-if="group.conditions && group.conditions.length > 0" class="conditions-list">
+        <div
+          v-for="(condition, index) in group.conditions"
+          :key="`condition-${index}`"
+          class="condition-item"
+        >
+          <div class="condition-header">
+            <div class="condition-title">
+              <span>条件 {{ index + 1 }}</span>
+              <el-tag size="small" type="primary">
+                {{ getConditionTypeName(condition.type) }}
+              </el-tag>
+            </div>
+            <el-button
+              type="danger"
+              size="small"
+              text
+              @click="removeCondition(index)"
+              v-if="group.conditions!.length > 1"
+            >
+              <Icon icon="ep:delete" />
+              删除
+            </el-button>
+          </div>
+
+          <div class="condition-content">
+            <ConditionConfig
+              :model-value="condition"
+              @update:model-value="(value) => updateCondition(index, value)"
+              :trigger-type="triggerType"
+              :product-id="productId"
+              :device-id="deviceId"
+              @validate="(result) => handleConditionValidate(index, result)"
+            />
+          </div>
+
+          <!-- 逻辑连接符 -->
+          <div
+            v-if="index < group.conditions!.length - 1"
+            class="logic-connector"
+          >
+            <el-select
+              v-model="group.logicOperator"
+              size="small"
+              style="width: 80px;"
+            >
+              <el-option label="且" value="AND" />
+              <el-option label="或" value="OR" />
+            </el-select>
+          </div>
+        </div>
+      </div>
+
+      <!-- 空状态 -->
+      <div v-else class="empty-conditions">
+        <el-empty description="暂无条件配置" :image-size="80">
+          <el-button type="primary" @click="addCondition">
+            <Icon icon="ep:plus" />
+            添加第一个条件
+          </el-button>
+        </el-empty>
+      </div>
+
+      <!-- 添加条件按钮 -->
+      <div v-if="group.conditions && group.conditions.length > 0 && group.conditions.length < maxConditions" class="add-condition">
+        <el-button
+          type="primary"
+          plain
+          @click="addCondition"
+          class="add-condition-btn"
+        >
+          <Icon icon="ep:plus" />
+          继续添加条件
+        </el-button>
+        <span class="add-condition-text">
+          最多可添加 {{ maxConditions }} 个条件
+        </span>
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="validation-result">
+      <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 ConditionConfig from './ConditionConfig.vue'
+import { 
+  ConditionGroupFormData, 
+  ConditionFormData,
+  IotRuleSceneTriggerTypeEnum 
+} from '@/api/iot/rule/scene/scene.types'
+
+/** 条件组配置组件 */
+defineOptions({ name: 'ConditionGroupConfig' })
+
+interface Props {
+  modelValue: ConditionGroupFormData
+  triggerType: number
+  productId?: number
+  deviceId?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: ConditionGroupFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const group = useVModel(props, 'modelValue', emit)
+
+// 配置常量
+const maxConditions = 5
+
+// 验证状态
+const conditionValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 条件类型映射
+const conditionTypeNames = {
+  [IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST]: '属性条件',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST]: '事件条件',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE]: '服务条件'
+}
+
+// 工具函数
+const getConditionTypeName = (type: number) => {
+  return conditionTypeNames[type] || '未知条件'
+}
+
+// 事件处理
+const updateCondition = (index: number, condition: ConditionFormData) => {
+  if (group.value.conditions) {
+    group.value.conditions[index] = condition
+  }
+}
+
+const addCondition = () => {
+  if (!group.value.conditions) {
+    group.value.conditions = []
+  }
+  
+  if (group.value.conditions.length >= maxConditions) {
+    return
+  }
+  
+  const newCondition: ConditionFormData = {
+    type: props.triggerType,
+    productId: props.productId || 0,
+    deviceId: props.deviceId || 0,
+    identifier: '',
+    operator: '=',
+    param: ''
+  }
+  
+  group.value.conditions.push(newCondition)
+}
+
+const removeCondition = (index: number) => {
+  if (group.value.conditions) {
+    group.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 handleConditionValidate = (index: number, result: { valid: boolean; message: string }) => {
+  conditionValidations.value[index] = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  if (!group.value.conditions || group.value.conditions.length === 0) {
+    isValid.value = false
+    validationMessage.value = '请至少添加一个条件'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  const validations = Object.values(conditionValidations.value)
+  const allValid = validations.every(v => v.valid)
+  
+  if (allValid) {
+    isValid.value = true
+    validationMessage.value = '条件组配置验证通过'
+  } else {
+    isValid.value = false
+    const errorMessages = validations
+      .filter(v => !v.valid)
+      .map(v => v.message)
+    validationMessage.value = `条件配置错误: ${errorMessages.join('; ')}`
+  }
+  
+  emit('validate', { valid: isValid.value, message: validationMessage.value })
+}
+
+// 监听条件数量变化
+watch(() => group.value.conditions?.length, () => {
+  updateValidationResult()
+})
+
+// 初始化
+onMounted(() => {
+  if (!group.value.conditions || group.value.conditions.length === 0) {
+    addCondition()
+  }
+})
+</script>
+
+<style scoped>
+.condition-group-config {
+  padding: 16px;
+}
+
+.group-content {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.conditions-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.condition-item {
+  position: relative;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.condition-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.condition-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.condition-content {
+  padding: 16px;
+}
+
+.logic-connector {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 8px 0;
+  position: relative;
+}
+
+.logic-connector::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 1px;
+  height: 100%;
+  background: var(--el-border-color);
+}
+
+.empty-conditions {
+  padding: 40px 0;
+  text-align: center;
+}
+
+.add-condition {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px;
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  background: var(--el-fill-color-lighter);
+}
+
+.add-condition-btn {
+  flex-shrink: 0;
+}
+
+.add-condition-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.validation-result {
+  margin-top: 16px;
+}
+</style>

+ 173 - 0
src/views/iot/rule/scene/components/configs/DeviceControlConfig.vue

@@ -0,0 +1,173 @@
+<!-- 设备控制配置组件 -->
+<template>
+  <div class="device-control-config">
+    <!-- 产品和设备选择 -->
+    <ProductDeviceSelector
+      v-model:product-id="action.productId"
+      v-model:device-id="action.deviceId"
+      @change="handleDeviceChange"
+    />
+
+    <!-- 控制参数配置 -->
+    <div v-if="action.productId && action.deviceId" class="control-params">
+      <el-form-item label="控制参数" required>
+        <el-input
+          v-model="paramsJson"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入JSON格式的控制参数"
+          @input="handleParamsChange"
+        />
+      </el-form-item>
+      
+      <!-- 参数示例 -->
+      <div class="params-example">
+        <el-alert
+          title="参数格式示例"
+          type="info"
+          :closable="false"
+          show-icon
+        >
+          <template #default>
+            <div class="example-content">
+              <p>属性设置示例:</p>
+              <pre><code>{ "temperature": 25, "power": true }</code></pre>
+              <p>服务调用示例:</p>
+              <pre><code>{ "method": "restart", "params": { "delay": 5 } }</code></pre>
+            </div>
+          </template>
+        </el-alert>
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="validation-result">
+      <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 ProductDeviceSelector from '../selectors/ProductDeviceSelector.vue'
+import { ActionFormData } from '@/api/iot/rule/scene/scene.types'
+
+/** 设备控制配置组件 */
+defineOptions({ name: 'DeviceControlConfig' })
+
+interface Props {
+  modelValue: ActionFormData
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: ActionFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const action = useVModel(props, 'modelValue', emit)
+
+// 状态
+const paramsJson = ref('')
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 事件处理
+const handleDeviceChange = ({ productId, deviceId }: { productId?: number; deviceId?: number }) => {
+  action.value.productId = productId
+  action.value.deviceId = deviceId
+  updateValidationResult()
+}
+
+const handleParamsChange = () => {
+  try {
+    if (paramsJson.value.trim()) {
+      action.value.params = JSON.parse(paramsJson.value)
+    } else {
+      action.value.params = {}
+    }
+    updateValidationResult()
+  } catch (error) {
+    isValid.value = false
+    validationMessage.value = 'JSON格式错误'
+    emit('validate', { valid: false, message: validationMessage.value })
+  }
+}
+
+const updateValidationResult = () => {
+  // 基础验证
+  if (!action.value.productId || !action.value.deviceId) {
+    isValid.value = false
+    validationMessage.value = '请选择产品和设备'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  if (!action.value.params || Object.keys(action.value.params).length === 0) {
+    isValid.value = false
+    validationMessage.value = '请配置控制参数'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  // 验证通过
+  isValid.value = true
+  validationMessage.value = '设备控制配置验证通过'
+  emit('validate', { valid: true, message: validationMessage.value })
+}
+
+// 初始化
+onMounted(() => {
+  if (action.value.params) {
+    paramsJson.value = JSON.stringify(action.value.params, null, 2)
+  }
+  updateValidationResult()
+})
+
+// 监听参数变化
+watch(() => action.value.params, (newParams) => {
+  if (newParams && typeof newParams === 'object') {
+    paramsJson.value = JSON.stringify(newParams, null, 2)
+  }
+}, { deep: true })
+</script>
+
+<style scoped>
+.device-control-config {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.control-params {
+  margin-top: 16px;
+}
+
+.params-example {
+  margin-top: 8px;
+}
+
+.example-content pre {
+  margin: 4px 0;
+  padding: 8px;
+  background: var(--el-fill-color-light);
+  border-radius: 4px;
+  font-size: 12px;
+}
+
+.example-content code {
+  font-family: 'Courier New', monospace;
+  color: var(--el-color-primary);
+}
+
+.validation-result {
+  margin-top: 8px;
+}
+</style>

+ 347 - 0
src/views/iot/rule/scene/components/configs/DeviceTriggerConfig.vue

@@ -0,0 +1,347 @@
+<!-- 设备触发配置组件 -->
+<template>
+  <div class="device-trigger-config">
+    <!-- 产品和设备选择 -->
+    <ProductDeviceSelector
+      v-model:product-id="trigger.productId"
+      v-model:device-id="trigger.deviceId"
+      @change="handleDeviceChange"
+    />
+
+    <!-- 设备状态变更提示 -->
+    <div
+      v-if="trigger.type === TriggerTypeEnum.DEVICE_STATE_UPDATE"
+      class="state-update-notice"
+    >
+      <el-alert
+        title="设备状态变更触发"
+        type="info"
+        :closable="false"
+        show-icon
+      >
+        <template #default>
+          <p>当选中的设备上线或离线时将自动触发场景规则</p>
+          <p class="notice-tip">无需配置额外的触发条件</p>
+        </template>
+      </el-alert>
+    </div>
+
+    <!-- 条件组配置 -->
+    <div
+      v-else-if="needsConditions"
+      class="condition-groups"
+    >
+      <div class="condition-groups-header">
+        <div class="header-left">
+          <span class="header-title">触发条件</span>
+          <el-tag size="small" type="info">
+            {{ trigger.conditionGroups?.length || 0 }}/{{ maxConditionGroups }}
+          </el-tag>
+        </div>
+        <div class="header-right">
+          <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="trigger.conditionGroups && trigger.conditionGroups.length > 0" class="condition-groups-list">
+        <div
+          v-for="(group, groupIndex) in trigger.conditionGroups"
+          :key="`group-${groupIndex}`"
+          class="condition-group"
+        >
+          <div class="group-header">
+            <div class="group-title">
+              <span>条件组 {{ groupIndex + 1 }}</span>
+              <el-select
+                v-model="group.logicOperator"
+                size="small"
+                style="width: 80px; margin-left: 12px;"
+              >
+                <el-option label="且" value="AND" />
+                <el-option label="或" value="OR" />
+              </el-select>
+            </div>
+            <el-button
+              type="danger"
+              size="small"
+              text
+              @click="removeConditionGroup(groupIndex)"
+              v-if="trigger.conditionGroups!.length > 1"
+            >
+              <Icon icon="ep:delete" />
+              删除组
+            </el-button>
+          </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>
+      </div>
+
+      <!-- 空状态 -->
+      <div v-else class="empty-conditions">
+        <el-empty description="暂无触发条件">
+          <el-button type="primary" @click="addConditionGroup">
+            <Icon icon="ep:plus" />
+            添加第一个条件组
+          </el-button>
+        </el-empty>
+      </div>
+    </div>
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="validation-result">
+      <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 ProductDeviceSelector from '../selectors/ProductDeviceSelector.vue'
+import ConditionGroupConfig from './ConditionGroupConfig.vue'
+import { 
+  TriggerFormData, 
+  ConditionGroupFormData,
+  IotRuleSceneTriggerTypeEnum as TriggerTypeEnum 
+} from '@/api/iot/rule/scene/scene.types'
+
+/** 设备触发配置组件 */
+defineOptions({ name: 'DeviceTriggerConfig' })
+
+interface Props {
+  modelValue: TriggerFormData
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: TriggerFormData): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const trigger = useVModel(props, 'modelValue', emit)
+
+// 配置常量
+const maxConditionGroups = 3
+
+// 验证状态
+const groupValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 计算属性
+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 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'
+  }
+  
+  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 handleGroupValidate = (index: number, result: { valid: boolean; message: string }) => {
+  groupValidations.value[index] = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  // 基础验证
+  if (!trigger.value.productId || !trigger.value.deviceId) {
+    isValid.value = false
+    validationMessage.value = '请选择产品和设备'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+  
+  // 设备状态变更不需要条件验证
+  if (trigger.value.type === TriggerTypeEnum.DEVICE_STATE_UPDATE) {
+    isValid.value = true
+    validationMessage.value = '设备触发配置验证通过'
+    emit('validate', { valid: true, message: validationMessage.value })
+    return
+  }
+  
+  // 条件组验证
+  if (!trigger.value.conditionGroups || trigger.value.conditionGroups.length === 0) {
+    isValid.value = false
+    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 {
+    isValid.value = false
+    const errorMessages = validations
+      .filter(v => !v.valid)
+      .map(v => v.message)
+    validationMessage.value = `条件组配置错误: ${errorMessages.join('; ')}`
+  }
+  
+  emit('validate', { valid: isValid.value, message: validationMessage.value })
+}
+
+// 监听触发器类型变化
+watch(() => trigger.value.type, () => {
+  updateValidationResult()
+})
+
+// 监听产品设备变化
+watch(() => [trigger.value.productId, trigger.value.deviceId], () => {
+  updateValidationResult()
+})
+</script>
+
+<style scoped>
+.device-trigger-config {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.state-update-notice {
+  margin-top: 8px;
+}
+
+.notice-tip {
+  margin: 4px 0 0 0;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.condition-groups-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.header-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.condition-groups-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.condition-group {
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.group-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.group-title {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.empty-conditions {
+  padding: 40px 0;
+  text-align: center;
+}
+
+.validation-result {
+  margin-top: 8px;
+}
+</style>

+ 142 - 0
src/views/iot/rule/scene/components/configs/TimerTriggerConfig.vue

@@ -0,0 +1,142 @@
+<!-- 定时触发配置组件 -->
+<template>
+  <div class="timer-trigger-config">
+    <div class="config-header">
+      <div class="header-left">
+        <Icon icon="ep:timer" class="header-icon" />
+        <span class="header-title">定时触发配置</span>
+      </div>
+      <div class="header-right">
+        <el-button
+          type="text"
+          size="small"
+          @click="showBuilder = !showBuilder"
+        >
+          <Icon :icon="showBuilder ? 'ep:edit' : 'ep:setting'" />
+          {{ showBuilder ? '手动编辑' : '可视化编辑' }}
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 可视化编辑器 -->
+    <div v-if="showBuilder" class="visual-builder">
+      <CronBuilder v-model="localValue" @validate="handleValidate" />
+    </div>
+
+    <!-- 手动编辑 -->
+    <div v-else class="manual-editor">
+      <el-form-item label="CRON表达式" required>
+        <CronInput v-model="localValue" @validate="handleValidate" />
+      </el-form-item>
+    </div>
+
+    <!-- 下次执行时间预览 -->
+    <NextExecutionPreview :cron-expression="localValue" />
+
+    <!-- 验证结果 -->
+    <div v-if="validationMessage" class="validation-result">
+      <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 CronBuilder from '../inputs/CronBuilder.vue'
+import CronInput from '../inputs/CronInput.vue'
+import NextExecutionPreview from '../previews/NextExecutionPreview.vue'
+
+/** 定时触发配置组件 */
+defineOptions({ name: 'TimerTriggerConfig' })
+
+interface Props {
+  modelValue?: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit, {
+  defaultValue: '0 0 12 * * ?'
+})
+
+// 状态
+const showBuilder = ref(true)
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 事件处理
+const handleValidate = (result: { valid: boolean; message: string }) => {
+  isValid.value = result.valid
+  validationMessage.value = result.message
+  emit('validate', result)
+}
+
+// 初始验证
+onMounted(() => {
+  handleValidate({ valid: true, message: '定时触发配置验证通过' })
+})
+</script>
+
+<style scoped>
+.timer-trigger-config {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.config-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.header-icon {
+  color: var(--el-color-danger);
+  font-size: 18px;
+}
+
+.header-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.visual-builder,
+.manual-editor {
+  padding: 16px;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.validation-result {
+  margin-top: 8px;
+}
+</style>

+ 233 - 0
src/views/iot/rule/scene/components/inputs/CronBuilder.vue

@@ -0,0 +1,233 @@
+<!-- CRON 可视化构建器组件 -->
+<template>
+  <div class="cron-builder">
+    <div class="builder-header">
+      <span class="header-title">可视化 CRON 编辑器</span>
+    </div>
+    
+    <div class="builder-content">
+      <!-- 快捷选项 -->
+      <div class="quick-options">
+        <span class="options-label">常用配置:</span>
+        <el-button
+          v-for="option in quickOptions"
+          :key="option.label"
+          size="small"
+          @click="applyQuickOption(option)"
+        >
+          {{ option.label }}
+        </el-button>
+      </div>
+
+      <!-- 详细配置 -->
+      <div class="detailed-config">
+        <el-row :gutter="16">
+          <el-col :span="4">
+            <el-form-item label="秒">
+              <el-select v-model="cronParts.second" @change="updateCronExpression">
+                <el-option label="每秒" value="*" />
+                <el-option label="0秒" value="0" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="分钟">
+              <el-select v-model="cronParts.minute" @change="updateCronExpression">
+                <el-option label="每分钟" value="*" />
+                <el-option
+                  v-for="i in 60"
+                  :key="i-1"
+                  :label="`${i-1}分`"
+                  :value="String(i-1)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="小时">
+              <el-select v-model="cronParts.hour" @change="updateCronExpression">
+                <el-option label="每小时" value="*" />
+                <el-option
+                  v-for="i in 24"
+                  :key="i-1"
+                  :label="`${i-1}时`"
+                  :value="String(i-1)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="日">
+              <el-select v-model="cronParts.day" @change="updateCronExpression">
+                <el-option label="每日" value="*" />
+                <el-option
+                  v-for="i in 31"
+                  :key="i"
+                  :label="`${i}日`"
+                  :value="String(i)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="月">
+              <el-select v-model="cronParts.month" @change="updateCronExpression">
+                <el-option label="每月" value="*" />
+                <el-option
+                  v-for="(month, index) in months"
+                  :key="index"
+                  :label="month"
+                  :value="String(index + 1)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="周">
+              <el-select v-model="cronParts.week" @change="updateCronExpression">
+                <el-option label="每周" value="*" />
+                <el-option
+                  v-for="(week, index) in weeks"
+                  :key="index"
+                  :label="week"
+                  :value="String(index)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** CRON 可视化构建器组件 */
+defineOptions({ name: 'CronBuilder' })
+
+interface Props {
+  modelValue: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+// CRON 各部分
+const cronParts = reactive({
+  second: '0',
+  minute: '0',
+  hour: '12',
+  day: '*',
+  month: '*',
+  week: '?'
+})
+
+// 常量数据
+const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
+const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
+
+// 快捷选项
+const quickOptions = [
+  { label: '每分钟', cron: '0 * * * * ?' },
+  { label: '每小时', cron: '0 0 * * * ?' },
+  { label: '每天中午', cron: '0 0 12 * * ?' },
+  { label: '每天凌晨', cron: '0 0 0 * * ?' },
+  { label: '工作日9点', cron: '0 0 9 * * MON-FRI' },
+  { label: '每周一', cron: '0 0 9 * * MON' }
+]
+
+// 方法
+const updateCronExpression = () => {
+  localValue.value = `${cronParts.second} ${cronParts.minute} ${cronParts.hour} ${cronParts.day} ${cronParts.month} ${cronParts.week}`
+  emit('validate', { valid: true, message: 'CRON表达式验证通过' })
+}
+
+const applyQuickOption = (option: any) => {
+  localValue.value = option.cron
+  parseCronExpression()
+  emit('validate', { valid: true, message: 'CRON表达式验证通过' })
+}
+
+const parseCronExpression = () => {
+  if (!localValue.value) return
+  
+  const parts = localValue.value.split(' ')
+  if (parts.length >= 6) {
+    cronParts.second = parts[0] || '0'
+    cronParts.minute = parts[1] || '0'
+    cronParts.hour = parts[2] || '12'
+    cronParts.day = parts[3] || '*'
+    cronParts.month = parts[4] || '*'
+    cronParts.week = parts[5] || '?'
+  }
+}
+
+// 初始化
+onMounted(() => {
+  if (localValue.value) {
+    parseCronExpression()
+  } else {
+    updateCronExpression()
+  }
+})
+</script>
+
+<style scoped>
+.cron-builder {
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.builder-header {
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.header-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.builder-content {
+  padding: 16px;
+}
+
+.quick-options {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 16px;
+  flex-wrap: wrap;
+}
+
+.options-label {
+  font-weight: 500;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.detailed-config {
+  margin-top: 16px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-form-item__label) {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+</style>

+ 142 - 0
src/views/iot/rule/scene/components/inputs/CronInput.vue

@@ -0,0 +1,142 @@
+<!-- CRON 表达式输入组件 -->
+<template>
+  <div class="cron-input">
+    <el-input
+      v-model="localValue"
+      placeholder="请输入 CRON 表达式,如:0 0 12 * * ?"
+      @blur="handleBlur"
+      @input="handleInput"
+    >
+      <template #suffix>
+        <el-tooltip content="CRON 表达式帮助" placement="top">
+          <Icon icon="ep:question-filled" class="input-help" @click="showHelp = !showHelp" />
+        </el-tooltip>
+      </template>
+    </el-input>
+    
+    <!-- 帮助信息 -->
+    <div v-if="showHelp" class="cron-help">
+      <el-alert
+        title="CRON 表达式格式:秒 分 时 日 月 周"
+        type="info"
+        :closable="false"
+        show-icon
+      >
+        <template #default>
+          <div class="help-content">
+            <p><strong>示例:</strong></p>
+            <ul>
+              <li><code>0 0 12 * * ?</code> - 每天中午12点执行</li>
+              <li><code>0 */5 * * * ?</code> - 每5分钟执行一次</li>
+              <li><code>0 0 9-17 * * MON-FRI</code> - 工作日9-17点每小时执行</li>
+            </ul>
+            <p><strong>特殊字符:</strong></p>
+            <ul>
+              <li><code>*</code> - 匹配任意值</li>
+              <li><code>?</code> - 不指定值(用于日和周)</li>
+              <li><code>/</code> - 间隔触发,如 */5 表示每5个单位</li>
+              <li><code>-</code> - 范围,如 9-17 表示9到17</li>
+              <li><code>,</code> - 列举,如 MON,WED,FRI</li>
+            </ul>
+          </div>
+        </template>
+      </el-alert>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { validateCronExpression } from '../../utils/validation'
+
+/** CRON 表达式输入组件 */
+defineOptions({ name: 'CronInput' })
+
+interface Props {
+  modelValue: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+// 状态
+const showHelp = ref(false)
+
+// 事件处理
+const handleInput = () => {
+  validateExpression()
+}
+
+const handleBlur = () => {
+  validateExpression()
+}
+
+const validateExpression = () => {
+  if (!localValue.value) {
+    emit('validate', { valid: false, message: '请输入CRON表达式' })
+    return
+  }
+  
+  const isValid = validateCronExpression(localValue.value)
+  if (isValid) {
+    emit('validate', { valid: true, message: 'CRON表达式验证通过' })
+  } else {
+    emit('validate', { valid: false, message: 'CRON表达式格式不正确' })
+  }
+}
+
+// 监听值变化
+watch(() => localValue.value, () => {
+  validateExpression()
+})
+
+// 初始化
+onMounted(() => {
+  if (localValue.value) {
+    validateExpression()
+  }
+})
+</script>
+
+<style scoped>
+.cron-input {
+  width: 100%;
+}
+
+.input-help {
+  color: var(--el-text-color-placeholder);
+  cursor: pointer;
+  transition: color 0.2s;
+}
+
+.input-help:hover {
+  color: var(--el-color-primary);
+}
+
+.cron-help {
+  margin-top: 8px;
+}
+
+.help-content ul {
+  margin: 8px 0 0 0;
+  padding-left: 20px;
+}
+
+.help-content li {
+  margin-bottom: 4px;
+}
+
+.help-content code {
+  background: var(--el-fill-color-light);
+  padding: 2px 4px;
+  border-radius: 2px;
+  font-family: 'Courier New', monospace;
+}
+</style>

+ 184 - 0
src/views/iot/rule/scene/components/inputs/DescriptionInput.vue

@@ -0,0 +1,184 @@
+<!-- 场景描述输入组件 -->
+<template>
+  <div class="description-input">
+    <el-input
+      v-model="localValue"
+      type="textarea"
+      placeholder="请输入场景描述(可选)"
+      :rows="3"
+      maxlength="200"
+      show-word-limit
+      resize="none"
+      @input="handleInput"
+    />
+    
+    <!-- 描述模板 -->
+    <div v-if="showTemplates" class="templates">
+      <div class="templates-header">
+        <span class="templates-title">描述模板</span>
+        <el-button 
+          type="text" 
+          size="small" 
+          @click="showTemplates = false"
+        >
+          <Icon icon="ep:close" />
+        </el-button>
+      </div>
+      <div class="templates-list">
+        <div
+          v-for="template in descriptionTemplates"
+          :key="template.title"
+          class="template-item"
+          @click="applyTemplate(template)"
+        >
+          <div class="template-title">{{ template.title }}</div>
+          <div class="template-content">{{ template.content }}</div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 模板按钮 -->
+    <div v-if="!localValue && !showTemplates" class="template-trigger">
+      <el-button 
+        type="text" 
+        size="small" 
+        @click="showTemplates = true"
+      >
+        <Icon icon="ep:document" class="mr-1" />
+        使用模板
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** 场景描述输入组件 */
+defineOptions({ name: 'DescriptionInput' })
+
+interface Props {
+  modelValue?: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit, {
+  defaultValue: ''
+})
+
+const showTemplates = ref(false)
+
+// 描述模板
+const descriptionTemplates = [
+  {
+    title: '温度控制场景',
+    content: '当环境温度超过设定阈值时,自动启动空调降温设备,确保环境温度保持在舒适范围内。'
+  },
+  {
+    title: '设备监控场景',
+    content: '实时监控关键设备的运行状态,当设备出现异常或离线时,立即发送告警通知相关人员。'
+  },
+  {
+    title: '节能控制场景',
+    content: '根据时间段和环境条件,自动调节设备功率或关闭非必要设备,实现智能节能管理。'
+  },
+  {
+    title: '安防联动场景',
+    content: '当检测到异常情况时,自动触发安防设备联动,包括报警器、摄像头录制等安全措施。'
+  },
+  {
+    title: '定时任务场景',
+    content: '按照预设的时间计划,定期执行设备检查、数据备份或系统维护等自动化任务。'
+  }
+]
+
+const handleInput = (value: string) => {
+  if (value.length > 0) {
+    showTemplates.value = false
+  }
+}
+
+const applyTemplate = (template: any) => {
+  localValue.value = template.content
+  showTemplates.value = false
+}
+</script>
+
+<style scoped>
+.description-input {
+  position: relative;
+  width: 100%;
+}
+
+.templates {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  z-index: 1000;
+  background: white;
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 4px;
+  box-shadow: var(--el-box-shadow-light);
+  margin-top: 4px;
+}
+
+.templates-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 12px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  background: var(--el-fill-color-light);
+}
+
+.templates-title {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  font-weight: 500;
+}
+
+.templates-list {
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.template-item {
+  padding: 12px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.template-item:hover {
+  background: var(--el-fill-color-light);
+}
+
+.template-item:last-child {
+  border-bottom: none;
+}
+
+.template-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 4px;
+}
+
+.template-content {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
+
+.template-trigger {
+  margin-top: 8px;
+  text-align: right;
+}
+</style>

+ 162 - 0
src/views/iot/rule/scene/components/inputs/NameInput.vue

@@ -0,0 +1,162 @@
+<!-- 场景名称输入组件 -->
+<template>
+  <div class="name-input">
+    <el-input
+      v-model="localValue"
+      placeholder="请输入场景名称"
+      maxlength="50"
+      show-word-limit
+      clearable
+      @blur="handleBlur"
+      @input="handleInput"
+    >
+      <template #prefix>
+        <Icon icon="ep:edit" class="input-icon" />
+      </template>
+    </el-input>
+    
+    <!-- 智能提示 -->
+    <div v-if="showSuggestions && suggestions.length > 0" class="suggestions">
+      <div class="suggestions-header">
+        <span class="suggestions-title">推荐名称</span>
+      </div>
+      <div class="suggestions-list">
+        <div
+          v-for="suggestion in suggestions"
+          :key="suggestion"
+          class="suggestion-item"
+          @click="applySuggestion(suggestion)"
+        >
+          {{ suggestion }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** 场景名称输入组件 */
+defineOptions({ name: 'NameInput' })
+
+interface Props {
+  modelValue: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+// 智能提示相关
+const showSuggestions = ref(false)
+const suggestions = ref<string[]>([])
+
+// 常用场景名称模板
+const nameTemplates = [
+  '温度过高自动降温',
+  '设备离线告警通知',
+  '湿度异常自动调节',
+  '夜间安防模式启动',
+  '能耗超标自动关闭',
+  '故障设备自动重启',
+  '定时设备状态检查',
+  '环境数据异常告警'
+]
+
+const handleInput = (value: string) => {
+  if (value.length > 0 && value.length < 10) {
+    // 根据输入内容过滤建议
+    suggestions.value = nameTemplates.filter(template => 
+      template.includes(value) || value.includes('温度') && template.includes('温度')
+    ).slice(0, 5)
+    showSuggestions.value = suggestions.value.length > 0
+  } else {
+    showSuggestions.value = false
+  }
+}
+
+const handleBlur = () => {
+  // 延迟隐藏建议,允许点击建议项
+  setTimeout(() => {
+    showSuggestions.value = false
+  }, 200)
+}
+
+const applySuggestion = (suggestion: string) => {
+  localValue.value = suggestion
+  showSuggestions.value = false
+}
+
+// 监听外部点击隐藏建议
+onMounted(() => {
+  document.addEventListener('click', (e) => {
+    const target = e.target as HTMLElement
+    if (!target.closest('.name-input')) {
+      showSuggestions.value = false
+    }
+  })
+})
+</script>
+
+<style scoped>
+.name-input {
+  position: relative;
+  width: 100%;
+}
+
+.input-icon {
+  color: var(--el-text-color-placeholder);
+}
+
+.suggestions {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  z-index: 1000;
+  background: white;
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 4px;
+  box-shadow: var(--el-box-shadow-light);
+  margin-top: 4px;
+}
+
+.suggestions-header {
+  padding: 8px 12px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  background: var(--el-fill-color-light);
+}
+
+.suggestions-title {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  font-weight: 500;
+}
+
+.suggestions-list {
+  max-height: 200px;
+  overflow-y: auto;
+}
+
+.suggestion-item {
+  padding: 8px 12px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+  font-size: 14px;
+  color: var(--el-text-color-primary);
+}
+
+.suggestion-item:hover {
+  background: var(--el-fill-color-light);
+}
+
+.suggestion-item:last-child {
+  border-bottom: none;
+}
+</style>

+ 185 - 0
src/views/iot/rule/scene/components/inputs/StatusRadio.vue

@@ -0,0 +1,185 @@
+<!-- 场景状态选择组件 -->
+<template>
+  <div class="status-radio">
+    <el-radio-group 
+      v-model="localValue" 
+      @change="handleChange"
+    >
+      <el-radio :value="0" class="status-option">
+        <div class="status-content">
+          <div class="status-indicator enabled"></div>
+          <div class="status-info">
+            <div class="status-label">启用</div>
+            <div class="status-desc">场景规则生效,满足条件时自动执行</div>
+          </div>
+        </div>
+      </el-radio>
+      
+      <el-radio :value="1" class="status-option">
+        <div class="status-content">
+          <div class="status-indicator disabled"></div>
+          <div class="status-info">
+            <div class="status-label">禁用</div>
+            <div class="status-desc">场景规则暂停,不会触发任何执行动作</div>
+          </div>
+        </div>
+      </el-radio>
+    </el-radio-group>
+    
+    <!-- 状态说明 -->
+    <div class="status-note">
+      <Icon icon="ep:info-filled" class="note-icon" />
+      <span class="note-text">
+        {{ localValue === 0 ? '启用状态下,规则将实时监控并执行相应动作' : '禁用状态下,规则不会执行任何操作' }}
+      </span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** 场景状态选择组件 */
+defineOptions({ name: 'StatusRadio' })
+
+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 localValue = useVModel(props, 'modelValue', emit)
+
+const handleChange = (value: number) => {
+  emit('change', value)
+}
+</script>
+
+<style scoped>
+.status-radio {
+  width: 100%;
+}
+
+.status-radio :deep(.el-radio) {
+  margin-bottom: 8px;
+}
+
+.status-radio :deep(.el-radio:last-child) {
+  margin-bottom: 0;
+}
+
+:deep(.el-radio-group) {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  width: 100%;
+}
+
+:deep(.el-radio) {
+  margin-right: 0;
+  width: 100%;
+  height: auto;
+  align-items: flex-start;
+}
+
+.status-option {
+  width: 100%;
+}
+
+:deep(.el-radio__input) {
+  margin-top: 12px;
+  flex-shrink: 0;
+}
+
+:deep(.el-radio__label) {
+  width: 100%;
+  padding-left: 8px;
+}
+
+.status-content {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+  padding: 12px;
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 6px;
+  transition: all 0.2s;
+  width: calc(100% - 28px);
+  margin-left: 0;
+}
+
+:deep(.el-radio.is-checked) .status-content {
+  border-color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+}
+
+.status-content:hover {
+  border-color: var(--el-color-primary-light-3);
+}
+
+.status-indicator {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  margin-top: 4px;
+  flex-shrink: 0;
+}
+
+.status-indicator.enabled {
+  background: var(--el-color-success);
+  box-shadow: 0 0 0 2px var(--el-color-success-light-8);
+}
+
+.status-indicator.disabled {
+  background: var(--el-color-danger);
+  box-shadow: 0 0 0 2px var(--el-color-danger-light-8);
+}
+
+.status-info {
+  flex: 1;
+}
+
+.status-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 4px;
+}
+
+.status-desc {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
+
+.status-note {
+  display: flex;
+  align-items: flex-start;
+  gap: 6px;
+  margin-top: 16px;
+  padding: 12px;
+  background: var(--el-fill-color-light);
+  border-radius: 4px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.note-icon {
+  color: var(--el-color-primary);
+  font-size: 14px;
+  flex-shrink: 0;
+  margin-top: 1px;
+}
+
+.note-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.5;
+  flex: 1;
+}
+</style>

+ 406 - 0
src/views/iot/rule/scene/components/inputs/ValueInput.vue

@@ -0,0 +1,406 @@
+<!-- 值输入组件 -->
+<template>
+  <div class="value-input">
+    <!-- 布尔值选择 -->
+    <el-select
+      v-if="propertyType === 'bool'"
+      v-model="localValue"
+      placeholder="请选择布尔值"
+      @change="handleChange"
+      class="w-full"
+    >
+      <el-option label="真 (true)" value="true" />
+      <el-option label="假 (false)" value="false" />
+    </el-select>
+
+    <!-- 枚举值选择 -->
+    <el-select
+      v-else-if="propertyType === 'enum' && enumOptions.length > 0"
+      v-model="localValue"
+      placeholder="请选择枚举值"
+      @change="handleChange"
+      class="w-full"
+    >
+      <el-option
+        v-for="option in enumOptions"
+        :key="option.value"
+        :label="option.label"
+        :value="option.value"
+      />
+    </el-select>
+
+    <!-- 范围输入 (between 操作符) -->
+    <div v-else-if="operator === 'between'" class="range-input">
+      <el-input
+        v-model="rangeStart"
+        :type="getInputType()"
+        placeholder="最小值"
+        @input="handleRangeChange"
+        class="range-start"
+      />
+      <span class="range-separator">至</span>
+      <el-input
+        v-model="rangeEnd"
+        :type="getInputType()"
+        placeholder="最大值"
+        @input="handleRangeChange"
+        class="range-end"
+      />
+    </div>
+
+    <!-- 列表输入 (in 操作符) -->
+    <div v-else-if="operator === 'in'" class="list-input">
+      <el-input
+        v-model="localValue"
+        placeholder="请输入值列表,用逗号分隔"
+        @input="handleChange"
+        class="w-full"
+      >
+        <template #suffix>
+          <el-tooltip content="多个值用逗号分隔,如:1,2,3" placement="top">
+            <Icon icon="ep:question-filled" class="input-tip" />
+          </el-tooltip>
+        </template>
+      </el-input>
+      <div v-if="listPreview.length > 0" class="list-preview">
+        <span class="preview-label">解析结果:</span>
+        <el-tag
+          v-for="(item, index) in listPreview"
+          :key="index"
+          size="small"
+          class="preview-tag"
+        >
+          {{ item }}
+        </el-tag>
+      </div>
+    </div>
+
+    <!-- 日期时间输入 -->
+    <el-date-picker
+      v-else-if="propertyType === 'date'"
+      v-model="dateValue"
+      type="datetime"
+      placeholder="请选择日期时间"
+      format="YYYY-MM-DD HH:mm:ss"
+      value-format="YYYY-MM-DD HH:mm:ss"
+      @change="handleDateChange"
+      class="w-full"
+    />
+
+    <!-- 数字输入 -->
+    <el-input-number
+      v-else-if="isNumericType()"
+      v-model="numberValue"
+      :precision="getPrecision()"
+      :step="getStep()"
+      :min="getMin()"
+      :max="getMax()"
+      placeholder="请输入数值"
+      @change="handleNumberChange"
+      class="w-full"
+    />
+
+    <!-- 文本输入 -->
+    <el-input
+      v-else
+      v-model="localValue"
+      :type="getInputType()"
+      :placeholder="getPlaceholder()"
+      @input="handleChange"
+      class="w-full"
+    >
+      <template #suffix>
+        <el-tooltip v-if="propertyConfig?.unit" :content="`单位:${propertyConfig.unit}`" placement="top">
+          <span class="input-unit">{{ propertyConfig.unit }}</span>
+        </el-tooltip>
+      </template>
+    </el-input>
+
+    <!-- 验证提示 -->
+    <div v-if="validationMessage" class="validation-message">
+      <el-text :type="isValid ? 'success' : 'danger'" size="small">
+        <Icon :icon="isValid ? 'ep:check' : 'ep:warning-filled'" />
+        {{ validationMessage }}
+      </el-text>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** 值输入组件 */
+defineOptions({ name: 'ValueInput' })
+
+interface Props {
+  modelValue?: string
+  propertyType?: string
+  operator?: string
+  propertyConfig?: any
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit, {
+  defaultValue: ''
+})
+
+// 状态
+const rangeStart = ref('')
+const rangeEnd = ref('')
+const dateValue = ref('')
+const numberValue = ref<number>()
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 计算属性
+const enumOptions = computed(() => {
+  if (props.propertyConfig?.enum) {
+    return props.propertyConfig.enum.map((item: any) => ({
+      label: item.name || item.label || item.value,
+      value: item.value
+    }))
+  }
+  return []
+})
+
+const listPreview = computed(() => {
+  if (props.operator === 'in' && localValue.value) {
+    return localValue.value.split(',').map(item => item.trim()).filter(item => item)
+  }
+  return []
+})
+
+// 工具函数
+const isNumericType = () => {
+  return ['int', 'float', 'double'].includes(props.propertyType || '')
+}
+
+const getInputType = () => {
+  switch (props.propertyType) {
+    case 'int':
+    case 'float':
+    case 'double':
+      return 'number'
+    default:
+      return 'text'
+  }
+}
+
+const getPlaceholder = () => {
+  const typeMap = {
+    'string': '请输入字符串',
+    'int': '请输入整数',
+    'float': '请输入浮点数',
+    'double': '请输入双精度数',
+    'struct': '请输入JSON格式数据',
+    'array': '请输入数组格式数据'
+  }
+  return typeMap[props.propertyType || ''] || '请输入值'
+}
+
+const getPrecision = () => {
+  return props.propertyType === 'int' ? 0 : 2
+}
+
+const getStep = () => {
+  return props.propertyType === 'int' ? 1 : 0.1
+}
+
+const getMin = () => {
+  return props.propertyConfig?.min || undefined
+}
+
+const getMax = () => {
+  return props.propertyConfig?.max || undefined
+}
+
+// 事件处理
+const handleChange = () => {
+  validateValue()
+}
+
+const handleRangeChange = () => {
+  if (rangeStart.value && rangeEnd.value) {
+    localValue.value = `${rangeStart.value},${rangeEnd.value}`
+  } else {
+    localValue.value = ''
+  }
+  validateValue()
+}
+
+const handleDateChange = (value: string) => {
+  localValue.value = value || ''
+  validateValue()
+}
+
+const handleNumberChange = (value: number | undefined) => {
+  localValue.value = value?.toString() || ''
+  validateValue()
+}
+
+// 验证函数
+const validateValue = () => {
+  if (!localValue.value) {
+    isValid.value = false
+    validationMessage.value = '请输入值'
+    emit('validate', { valid: false, message: validationMessage.value })
+    return
+  }
+
+  // 数字类型验证
+  if (isNumericType()) {
+    const num = parseFloat(localValue.value)
+    if (isNaN(num)) {
+      isValid.value = false
+      validationMessage.value = '请输入有效的数字'
+      emit('validate', { valid: false, message: validationMessage.value })
+      return
+    }
+
+    // 范围验证
+    const min = getMin()
+    const max = getMax()
+    if (min !== undefined && num < min) {
+      isValid.value = false
+      validationMessage.value = `值不能小于 ${min}`
+      emit('validate', { valid: false, message: validationMessage.value })
+      return
+    }
+    if (max !== undefined && num > max) {
+      isValid.value = false
+      validationMessage.value = `值不能大于 ${max}`
+      emit('validate', { valid: false, message: validationMessage.value })
+      return
+    }
+  }
+
+  // 范围输入验证
+  if (props.operator === 'between') {
+    const parts = localValue.value.split(',')
+    if (parts.length !== 2) {
+      isValid.value = false
+      validationMessage.value = '范围格式错误'
+      emit('validate', { valid: false, message: validationMessage.value })
+      return
+    }
+
+    const start = parseFloat(parts[0])
+    const end = parseFloat(parts[1])
+    if (isNaN(start) || isNaN(end)) {
+      isValid.value = false
+      validationMessage.value = '范围值必须是数字'
+      emit('validate', { valid: false, message: validationMessage.value })
+      return
+    }
+
+    if (start >= end) {
+      isValid.value = false
+      validationMessage.value = '起始值必须小于结束值'
+      emit('validate', { valid: false, message: validationMessage.value })
+      return
+    }
+  }
+
+  // 列表输入验证
+  if (props.operator === 'in') {
+    if (listPreview.value.length === 0) {
+      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(() => localValue.value, () => {
+  validateValue()
+})
+
+// 监听操作符变化
+watch(() => props.operator, () => {
+  localValue.value = ''
+  rangeStart.value = ''
+  rangeEnd.value = ''
+  dateValue.value = ''
+  numberValue.value = undefined
+})
+
+// 初始化
+onMounted(() => {
+  if (localValue.value) {
+    validateValue()
+  }
+})
+</script>
+
+<style scoped>
+.value-input {
+  width: 100%;
+}
+
+.range-input {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.range-start,
+.range-end {
+  flex: 1;
+}
+
+.range-separator {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.list-input {
+  width: 100%;
+}
+
+.input-tip {
+  color: var(--el-text-color-placeholder);
+  cursor: help;
+}
+
+.input-unit {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  padding: 0 4px;
+}
+
+.list-preview {
+  margin-top: 8px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  flex-wrap: wrap;
+}
+
+.preview-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.preview-tag {
+  margin: 0;
+}
+
+.validation-message {
+  margin-top: 4px;
+}
+</style>

+ 127 - 0
src/views/iot/rule/scene/components/previews/ActionPreview.vue

@@ -0,0 +1,127 @@
+<!-- 执行器预览组件 -->
+<template>
+  <div class="action-preview">
+    <div v-if="actions.length === 0" class="empty-preview">
+      <el-text type="info" size="small">暂无执行器配置</el-text>
+    </div>
+    <div v-else class="action-list">
+      <div
+        v-for="(action, index) in actions"
+        :key="index"
+        class="action-item"
+      >
+        <div class="action-header">
+          <Icon icon="ep:setting" class="action-icon" />
+          <span class="action-title">执行器 {{ index + 1 }}</span>
+          <el-tag :type="getActionTypeTag(action.type)" size="small">
+            {{ getActionTypeName(action.type) }}
+          </el-tag>
+        </div>
+        <div class="action-content">
+          <div class="action-summary">
+            {{ getActionSummary(action) }}
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ActionFormData, IotRuleSceneActionTypeEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 执行器预览组件 */
+defineOptions({ name: 'ActionPreview' })
+
+interface Props {
+  actions: ActionFormData[]
+}
+
+const props = defineProps<Props>()
+
+// 执行器类型映射
+const actionTypeNames = {
+  [IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: '属性设置',
+  [IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
+  [IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: '触发告警',
+  [IotRuleSceneActionTypeEnum.ALERT_RECOVER]: '恢复告警'
+}
+
+const actionTypeTags = {
+  [IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
+  [IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
+  [IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: 'danger',
+  [IotRuleSceneActionTypeEnum.ALERT_RECOVER]: 'warning'
+}
+
+// 工具函数
+const getActionTypeName = (type: number) => {
+  return actionTypeNames[type] || '未知类型'
+}
+
+const getActionTypeTag = (type: number) => {
+  return actionTypeTags[type] || 'info'
+}
+
+const getActionSummary = (action: ActionFormData) => {
+  if (action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER || action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER) {
+    return `告警配置: ${action.alertConfigId ? `配置ID ${action.alertConfigId}` : '未选择'}`
+  } else {
+    const paramsCount = action.params ? Object.keys(action.params).length : 0
+    return `设备控制: 产品${action.productId || '未选择'} 设备${action.deviceId || '未选择'} (${paramsCount}个参数)`
+  }
+}
+</script>
+
+<style scoped>
+.action-preview {
+  width: 100%;
+}
+
+.empty-preview {
+  text-align: center;
+  padding: 20px 0;
+}
+
+.action-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.action-item {
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 4px;
+  background: var(--el-fill-color-blank);
+}
+
+.action-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.action-icon {
+  color: var(--el-color-success);
+  font-size: 14px;
+}
+
+.action-title {
+  font-size: 12px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.action-content {
+  padding: 8px 12px;
+}
+
+.action-summary {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
+</style>

+ 65 - 0
src/views/iot/rule/scene/components/previews/ConfigPreview.vue

@@ -0,0 +1,65 @@
+<!-- 配置预览组件 -->
+<template>
+  <div class="config-preview">
+    <div class="preview-items">
+      <div class="preview-item">
+        <span class="item-label">场景名称:</span>
+        <span class="item-value">{{ formData.name || '未设置' }}</span>
+      </div>
+      <div class="preview-item">
+        <span class="item-label">场景状态:</span>
+        <el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small">
+          {{ formData.status === 0 ? '启用' : '禁用' }}
+        </el-tag>
+      </div>
+      <div v-if="formData.description" class="preview-item">
+        <span class="item-label">场景描述:</span>
+        <span class="item-value">{{ formData.description }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
+
+/** 配置预览组件 */
+defineOptions({ name: 'ConfigPreview' })
+
+interface Props {
+  formData: RuleSceneFormData
+}
+
+defineProps<Props>()
+</script>
+
+<style scoped>
+.config-preview {
+  width: 100%;
+}
+
+.preview-items {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.preview-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.item-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  min-width: 80px;
+  flex-shrink: 0;
+}
+
+.item-value {
+  font-size: 12px;
+  color: var(--el-text-color-primary);
+  flex: 1;
+}
+</style>

+ 226 - 0
src/views/iot/rule/scene/components/previews/NextExecutionPreview.vue

@@ -0,0 +1,226 @@
+<!-- 下次执行时间预览组件 -->
+<template>
+  <div class="next-execution-preview">
+    <div class="preview-header">
+      <Icon icon="ep:timer" class="preview-icon" />
+      <span class="preview-title">执行时间预览</span>
+    </div>
+    
+    <div v-if="isValidCron" class="preview-content">
+      <div class="current-expression">
+        <span class="expression-label">CRON表达式:</span>
+        <code class="expression-code">{{ cronExpression }}</code>
+      </div>
+      
+      <div class="description">
+        <span class="description-label">执行规律:</span>
+        <span class="description-text">{{ cronDescription }}</span>
+      </div>
+      
+      <div class="next-times">
+        <span class="times-label">接下来5次执行时间:</span>
+        <div class="times-list">
+          <div
+            v-for="(time, index) in nextExecutionTimes"
+            :key="index"
+            class="time-item"
+          >
+            <Icon icon="ep:clock" class="time-icon" />
+            <span class="time-text">{{ time }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <div v-else class="preview-error">
+      <el-alert
+        title="CRON表达式无效"
+        description="请检查CRON表达式格式是否正确"
+        type="error"
+        :closable="false"
+        show-icon
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { validateCronExpression } from '../../utils/validation'
+
+/** 下次执行时间预览组件 */
+defineOptions({ name: 'NextExecutionPreview' })
+
+interface Props {
+  cronExpression?: string
+}
+
+const props = defineProps<Props>()
+
+// 计算属性
+const isValidCron = computed(() => {
+  return props.cronExpression ? validateCronExpression(props.cronExpression) : false
+})
+
+const cronDescription = computed(() => {
+  if (!isValidCron.value) return ''
+  
+  // 简单的CRON描述生成
+  const parts = props.cronExpression?.split(' ') || []
+  if (parts.length < 6) return '无法解析'
+  
+  const [second, minute, hour, day, month, week] = parts
+  
+  // 生成描述
+  let description = ''
+  
+  if (second === '0' && minute === '0' && hour === '12' && day === '*' && month === '*' && week === '?') {
+    description = '每天中午12点执行'
+  } else if (second === '0' && minute === '*' && hour === '*' && day === '*' && month === '*' && week === '?') {
+    description = '每分钟执行一次'
+  } else if (second === '0' && minute === '0' && hour === '*' && day === '*' && month === '*' && week === '?') {
+    description = '每小时执行一次'
+  } else {
+    description = '按自定义时间规律执行'
+  }
+  
+  return description
+})
+
+const nextExecutionTimes = computed(() => {
+  if (!isValidCron.value) return []
+  
+  // 模拟生成下次执行时间
+  const now = new Date()
+  const times = []
+  
+  for (let i = 1; i <= 5; i++) {
+    // 这里应该使用真实的CRON解析库来计算
+    // 暂时生成模拟时间
+    const nextTime = new Date(now.getTime() + i * 60 * 60 * 1000)
+    times.push(nextTime.toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit',
+      second: '2-digit'
+    }))
+  }
+  
+  return times
+})
+</script>
+
+<style scoped>
+.next-execution-preview {
+  margin-top: 16px;
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.preview-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.preview-icon {
+  color: var(--el-color-primary);
+  font-size: 16px;
+}
+
+.preview-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.preview-content {
+  padding: 16px;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.current-expression {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.expression-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  min-width: 80px;
+}
+
+.expression-code {
+  font-family: 'Courier New', monospace;
+  background: var(--el-fill-color-light);
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  color: var(--el-color-primary);
+}
+
+.description {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.description-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  min-width: 80px;
+}
+
+.description-text {
+  font-size: 12px;
+  color: var(--el-text-color-primary);
+  font-weight: 500;
+}
+
+.next-times {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.times-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.times-list {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-left: 12px;
+}
+
+.time-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.time-icon {
+  color: var(--el-color-success);
+  font-size: 12px;
+}
+
+.time-text {
+  font-size: 12px;
+  color: var(--el-text-color-primary);
+  font-family: 'Courier New', monospace;
+}
+
+.preview-error {
+  padding: 16px;
+}
+</style>

+ 131 - 0
src/views/iot/rule/scene/components/previews/TriggerPreview.vue

@@ -0,0 +1,131 @@
+<!-- 触发器预览组件 -->
+<template>
+  <div class="trigger-preview">
+    <div v-if="triggers.length === 0" class="empty-preview">
+      <el-text type="info" size="small">暂无触发器配置</el-text>
+    </div>
+    <div v-else class="trigger-list">
+      <div
+        v-for="(trigger, index) in triggers"
+        :key="index"
+        class="trigger-item"
+      >
+        <div class="trigger-header">
+          <Icon icon="ep:lightning" class="trigger-icon" />
+          <span class="trigger-title">触发器 {{ index + 1 }}</span>
+          <el-tag :type="getTriggerTypeTag(trigger.type)" size="small">
+            {{ getTriggerTypeName(trigger.type) }}
+          </el-tag>
+        </div>
+        <div class="trigger-content">
+          <div class="trigger-summary">
+            {{ getTriggerSummary(trigger) }}
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { TriggerFormData, IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 触发器预览组件 */
+defineOptions({ name: 'TriggerPreview' })
+
+interface Props {
+  triggers: TriggerFormData[]
+}
+
+const props = defineProps<Props>()
+
+// 触发器类型映射
+const triggerTypeNames = {
+  [IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE]: '设备状态变更',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST]: '属性上报',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST]: '事件上报',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
+  [IotRuleSceneTriggerTypeEnum.TIMER]: '定时触发'
+}
+
+const triggerTypeTags = {
+  [IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE]: 'warning',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST]: 'primary',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST]: 'success',
+  [IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE]: 'info',
+  [IotRuleSceneTriggerTypeEnum.TIMER]: 'danger'
+}
+
+// 工具函数
+const getTriggerTypeName = (type: number) => {
+  return triggerTypeNames[type] || '未知类型'
+}
+
+const getTriggerTypeTag = (type: number) => {
+  return triggerTypeTags[type] || 'info'
+}
+
+const getTriggerSummary = (trigger: TriggerFormData) => {
+  if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
+    return `定时执行: ${trigger.cronExpression || '未配置'}`
+  } else if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
+    return `设备状态变更: 产品${trigger.productId || '未选择'} 设备${trigger.deviceId || '未选择'}`
+  } else {
+    const conditionCount = trigger.conditionGroups?.reduce((total, group) => total + (group.conditions?.length || 0), 0) || 0
+    return `设备监控: 产品${trigger.productId || '未选择'} 设备${trigger.deviceId || '未选择'} (${conditionCount}个条件)`
+  }
+}
+</script>
+
+<style scoped>
+.trigger-preview {
+  width: 100%;
+}
+
+.empty-preview {
+  text-align: center;
+  padding: 20px 0;
+}
+
+.trigger-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.trigger-item {
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 4px;
+  background: var(--el-fill-color-blank);
+}
+
+.trigger-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.trigger-icon {
+  color: var(--el-color-warning);
+  font-size: 14px;
+}
+
+.trigger-title {
+  font-size: 12px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.trigger-content {
+  padding: 8px 12px;
+}
+
+.trigger-summary {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
+</style>

+ 120 - 0
src/views/iot/rule/scene/components/previews/ValidationResult.vue

@@ -0,0 +1,120 @@
+<!-- 验证结果组件 -->
+<template>
+  <div class="validation-result">
+    <div v-if="!validationResult" class="no-validation">
+      <el-text type="info" size="small">
+        <Icon icon="ep:info-filled" />
+        点击"验证配置"按钮检查规则配置
+      </el-text>
+    </div>
+    <div v-else class="validation-content">
+      <el-alert
+        :title="validationResult.valid ? '配置验证通过' : '配置验证失败'"
+        :description="validationResult.message"
+        :type="validationResult.valid ? 'success' : 'error'"
+        :closable="false"
+        show-icon
+      >
+        <template #default>
+          <div v-if="validationResult.valid" class="success-content">
+            <p>{{ validationResult.message || '所有配置项验证通过,规则可以正常运行' }}</p>
+            <div class="success-tips">
+              <Icon icon="ep:check" class="tip-icon" />
+              <span class="tip-text">规则配置完整且有效</span>
+            </div>
+          </div>
+          <div v-else class="error-content">
+            <p>{{ validationResult.message || '配置验证失败,请检查以下问题' }}</p>
+            <div class="error-tips">
+              <div class="tip-item">
+                <Icon icon="ep:warning-filled" class="tip-icon error" />
+                <span class="tip-text">请确保所有必填项都已配置</span>
+              </div>
+              <div class="tip-item">
+                <Icon icon="ep:warning-filled" class="tip-icon error" />
+                <span class="tip-text">请检查触发器和执行器配置是否正确</span>
+              </div>
+            </div>
+          </div>
+        </template>
+      </el-alert>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+/** 验证结果组件 */
+defineOptions({ name: 'ValidationResult' })
+
+interface Props {
+  validationResult?: { valid: boolean; message?: string } | null
+}
+
+defineProps<Props>()
+</script>
+
+<style scoped>
+.validation-result {
+  width: 100%;
+}
+
+.no-validation {
+  text-align: center;
+  padding: 20px 0;
+}
+
+.validation-content {
+  width: 100%;
+}
+
+.success-content,
+.error-content {
+  margin-top: 8px;
+}
+
+.success-content p,
+.error-content p {
+  margin: 0 0 8px 0;
+  font-size: 14px;
+  line-height: 1.5;
+}
+
+.success-tips,
+.error-tips {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.tip-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.tip-icon {
+  font-size: 12px;
+  flex-shrink: 0;
+}
+
+.tip-icon:not(.error) {
+  color: var(--el-color-success);
+}
+
+.tip-icon.error {
+  color: var(--el-color-danger);
+}
+
+.tip-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.success-tips .tip-text {
+  color: var(--el-color-success-dark-2);
+}
+
+.error-tips .tip-text {
+  color: var(--el-color-danger-dark-2);
+}
+</style>

+ 390 - 0
src/views/iot/rule/scene/components/sections/ActionSection.vue

@@ -0,0 +1,390 @@
+<!-- 执行器配置组件 -->
+<template>
+  <el-card class="action-section" shadow="never">
+    <template #header>
+      <div class="section-header">
+        <div class="header-left">
+          <Icon icon="ep:setting" class="section-icon" />
+          <span class="section-title">执行器配置</span>
+          <el-tag size="small" type="info">{{ actions.length }}/{{ maxActions }}</el-tag>
+        </div>
+        <div class="header-right">
+          <el-button
+            type="primary"
+            size="small"
+            @click="addAction"
+            :disabled="actions.length >= maxActions"
+          >
+            <Icon icon="ep:plus" />
+            添加执行器
+          </el-button>
+        </div>
+      </div>
+    </template>
+
+    <div class="section-content">
+      <!-- 空状态 -->
+      <div v-if="actions.length === 0" class="empty-state">
+        <el-empty description="暂无执行器配置">
+          <el-button type="primary" @click="addAction">
+            <Icon icon="ep:plus" />
+            添加第一个执行器
+          </el-button>
+        </el-empty>
+      </div>
+
+      <!-- 执行器列表 -->
+      <div v-else class="actions-list">
+        <div
+          v-for="(action, index) in actions"
+          :key="`action-${index}`"
+          class="action-item"
+        >
+          <div class="action-header">
+            <div class="action-title">
+              <Icon icon="ep:setting" class="action-icon" />
+              <span>执行器 {{ index + 1 }}</span>
+              <el-tag
+                :type="getActionTypeTag(action.type)"
+                size="small"
+              >
+                {{ getActionTypeName(action.type) }}
+              </el-tag>
+            </div>
+            <div class="action-actions">
+              <el-button
+                type="danger"
+                size="small"
+                text
+                @click="removeAction(index)"
+                v-if="actions.length > 1"
+              >
+                <Icon icon="ep:delete" />
+                删除
+              </el-button>
+            </div>
+          </div>
+
+          <div class="action-content">
+            <!-- 执行类型选择 -->
+            <ActionTypeSelector
+              :model-value="action.type"
+              @update:model-value="(value) => updateActionType(index, value)"
+              @change="onActionTypeChange(action, $event)"
+            />
+
+            <!-- 设备控制配置 -->
+            <DeviceControlConfig
+              v-if="isDeviceAction(action.type)"
+              :model-value="action"
+              @update:model-value="(value) => updateAction(index, value)"
+              @validate="(result) => handleActionValidate(index, result)"
+            />
+
+            <!-- 告警配置 -->
+            <AlertConfig
+              v-if="isAlertAction(action.type)"
+              :model-value="action.alertConfigId"
+              @update:model-value="(value) => updateActionAlertConfig(index, value)"
+              @validate="(result) => handleActionValidate(index, result)"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 添加提示 -->
+      <div v-if="actions.length > 0 && actions.length < maxActions" class="add-more">
+        <el-button
+          type="primary"
+          plain
+          @click="addAction"
+          class="add-more-btn"
+        >
+          <Icon icon="ep:plus" />
+          继续添加执行器
+        </el-button>
+        <span class="add-more-text">
+          最多可添加 {{ maxActions }} 个执行器
+        </span>
+      </div>
+
+      <!-- 验证结果 -->
+      <div v-if="validationMessage" class="validation-result">
+        <el-alert
+          :title="validationMessage"
+          :type="isValid ? 'success' : 'error'"
+          :closable="false"
+          show-icon
+        />
+      </div>
+    </div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import ActionTypeSelector from '../selectors/ActionTypeSelector.vue'
+import DeviceControlConfig from '../configs/DeviceControlConfig.vue'
+import AlertConfig from '../configs/AlertConfig.vue'
+import { 
+  ActionFormData, 
+  IotRuleSceneActionTypeEnum as ActionTypeEnum 
+} from '@/api/iot/rule/scene/scene.types'
+import { createDefaultActionData } from '../../utils/transform'
+
+/** 执行器配置组件 */
+defineOptions({ name: 'ActionSection' })
+
+interface Props {
+  actions: ActionFormData[]
+}
+
+interface Emits {
+  (e: 'update:actions', value: ActionFormData[]): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const actions = useVModel(props, 'actions', emit)
+
+// 配置常量
+const maxActions = 5
+
+// 验证状态
+const actionValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 执行器类型映射
+const actionTypeNames = {
+  [ActionTypeEnum.DEVICE_PROPERTY_SET]: '属性设置',
+  [ActionTypeEnum.DEVICE_SERVICE_INVOKE]: '服务调用',
+  [ActionTypeEnum.ALERT_TRIGGER]: '触发告警',
+  [ActionTypeEnum.ALERT_RECOVER]: '恢复告警'
+}
+
+const actionTypeTags = {
+  [ActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
+  [ActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
+  [ActionTypeEnum.ALERT_TRIGGER]: 'danger',
+  [ActionTypeEnum.ALERT_RECOVER]: 'warning'
+}
+
+// 工具函数
+const isDeviceAction = (type: number) => {
+  return [
+    ActionTypeEnum.DEVICE_PROPERTY_SET,
+    ActionTypeEnum.DEVICE_SERVICE_INVOKE
+  ].includes(type)
+}
+
+const isAlertAction = (type: number) => {
+  return [
+    ActionTypeEnum.ALERT_TRIGGER,
+    ActionTypeEnum.ALERT_RECOVER
+  ].includes(type)
+}
+
+const getActionTypeName = (type: number) => {
+  return actionTypeNames[type] || '未知类型'
+}
+
+const getActionTypeTag = (type: number) => {
+  return actionTypeTags[type] || 'info'
+}
+
+// 事件处理
+const addAction = () => {
+  if (actions.value.length >= maxActions) {
+    return
+  }
+  
+  const newAction = createDefaultActionData()
+  actions.value.push(newAction)
+}
+
+const removeAction = (index: number) => {
+  actions.value.splice(index, 1)
+  delete actionValidations.value[index]
+  
+  // 重新索引验证结果
+  const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
+  Object.keys(actionValidations.value).forEach(key => {
+    const numKey = parseInt(key)
+    if (numKey > index) {
+      newValidations[numKey - 1] = actionValidations.value[numKey]
+    } else if (numKey < index) {
+      newValidations[numKey] = actionValidations.value[numKey]
+    }
+  })
+  actionValidations.value = newValidations
+  
+  updateValidationResult()
+}
+
+const updateActionType = (index: number, type: number) => {
+  actions.value[index].type = type
+  onActionTypeChange(actions.value[index], type)
+}
+
+const updateAction = (index: number, action: ActionFormData) => {
+  actions.value[index] = action
+}
+
+const updateActionAlertConfig = (index: number, alertConfigId?: number) => {
+  actions.value[index].alertConfigId = alertConfigId
+}
+
+const onActionTypeChange = (action: ActionFormData, type: number) => {
+  // 清理不相关的配置
+  if (isDeviceAction(type)) {
+    action.alertConfigId = undefined
+    if (!action.params) {
+      action.params = {}
+    }
+  } else if (isAlertAction(type)) {
+    action.productId = undefined
+    action.deviceId = undefined
+    action.params = undefined
+  }
+}
+
+const handleActionValidate = (index: number, result: { valid: boolean; message: string }) => {
+  actionValidations.value[index] = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  const validations = Object.values(actionValidations.value)
+  const allValid = validations.every(v => v.valid)
+  const hasValidations = validations.length > 0
+  
+  if (!hasValidations) {
+    isValid.value = true
+    validationMessage.value = ''
+  } else if (allValid) {
+    isValid.value = true
+    validationMessage.value = '所有执行器配置验证通过'
+  } else {
+    isValid.value = false
+    const errorMessages = validations
+      .filter(v => !v.valid)
+      .map(v => v.message)
+    validationMessage.value = `执行器配置错误: ${errorMessages.join('; ')}`
+  }
+  
+  emit('validate', { valid: isValid.value, message: validationMessage.value })
+}
+
+// 监听执行器数量变化
+watch(() => actions.value.length, () => {
+  updateValidationResult()
+})
+</script>
+
+<style scoped>
+.action-section {
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 8px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-icon {
+  color: var(--el-color-primary);
+  font-size: 18px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-content {
+  padding: 0;
+}
+
+.empty-state {
+  padding: 40px 0;
+  text-align: center;
+}
+
+.actions-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.action-item {
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.action-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.action-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.action-icon {
+  color: var(--el-color-success);
+  font-size: 16px;
+}
+
+.action-content {
+  padding: 16px;
+}
+
+.add-more {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-top: 16px;
+  padding: 16px;
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  background: var(--el-fill-color-lighter);
+}
+
+.add-more-btn {
+  flex-shrink: 0;
+}
+
+.add-more-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.validation-result {
+  margin-top: 16px;
+}
+</style>

+ 110 - 0
src/views/iot/rule/scene/components/sections/BasicInfoSection.vue

@@ -0,0 +1,110 @@
+<!-- 基础信息配置组件 -->
+<template>
+  <el-card class="basic-info-section" shadow="never">
+    <template #header>
+      <div class="section-header">
+        <div class="header-left">
+          <Icon icon="ep:info-filled" class="section-icon" />
+          <span class="section-title">基础信息</span>
+        </div>
+        <div class="header-right">
+          <el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small">
+            {{ formData.status === 0 ? '启用' : '禁用' }}
+          </el-tag>
+        </div>
+      </div>
+    </template>
+
+    <div class="section-content">
+      <el-row :gutter="24">
+        <el-col :span="12">
+          <el-form-item label="场景名称" prop="name" required>
+            <NameInput v-model="formData.name" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="场景状态" prop="status" required>
+            <StatusRadio v-model="formData.status" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-form-item label="场景描述" prop="description">
+        <DescriptionInput v-model="formData.description" />
+      </el-form-item>
+    </div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import NameInput from '../inputs/NameInput.vue'
+import DescriptionInput from '../inputs/DescriptionInput.vue'
+import StatusRadio from '../inputs/StatusRadio.vue'
+import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
+
+/** 基础信息配置组件 */
+defineOptions({ name: 'BasicInfoSection' })
+
+interface Props {
+  modelValue: RuleSceneFormData
+  rules?: any
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: RuleSceneFormData): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const formData = useVModel(props, 'modelValue', emit)
+</script>
+
+<style scoped>
+.basic-info-section {
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 8px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-icon {
+  color: var(--el-color-primary);
+  font-size: 18px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-content {
+  padding: 0;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 20px;
+}
+
+:deep(.el-form-item:last-child) {
+  margin-bottom: 0;
+}
+</style>

+ 183 - 0
src/views/iot/rule/scene/components/sections/PreviewSection.vue

@@ -0,0 +1,183 @@
+<!-- 预览区域组件 -->
+<template>
+  <el-card class="preview-section" shadow="never">
+    <template #header>
+      <div class="section-header">
+        <div class="header-left">
+          <Icon icon="ep:view" class="section-icon" />
+          <span class="section-title">配置预览</span>
+        </div>
+        <div class="header-right">
+          <el-button
+            type="primary"
+            size="small"
+            @click="handleValidate"
+            :loading="validating"
+          >
+            <Icon icon="ep:check" />
+            验证配置
+          </el-button>
+        </div>
+      </div>
+    </template>
+
+    <div class="section-content">
+      <!-- 基础信息预览 -->
+      <div class="preview-group">
+        <div class="group-header">
+          <Icon icon="ep:info-filled" class="group-icon" />
+          <span class="group-title">基础信息</span>
+        </div>
+        <div class="group-content">
+          <ConfigPreview :form-data="formData" />
+        </div>
+      </div>
+
+      <!-- 触发器预览 -->
+      <div class="preview-group">
+        <div class="group-header">
+          <Icon icon="ep:lightning" class="group-icon" />
+          <span class="group-title">触发器配置</span>
+          <el-tag size="small" type="primary">{{ formData.triggers.length }}</el-tag>
+        </div>
+        <div class="group-content">
+          <TriggerPreview :triggers="formData.triggers" />
+        </div>
+      </div>
+
+      <!-- 执行器预览 -->
+      <div class="preview-group">
+        <div class="group-header">
+          <Icon icon="ep:setting" class="group-icon" />
+          <span class="group-title">执行器配置</span>
+          <el-tag size="small" type="success">{{ formData.actions.length }}</el-tag>
+        </div>
+        <div class="group-content">
+          <ActionPreview :actions="formData.actions" />
+        </div>
+      </div>
+
+      <!-- 验证结果 -->
+      <div class="preview-group">
+        <div class="group-header">
+          <Icon icon="ep:circle-check" class="group-icon" />
+          <span class="group-title">验证结果</span>
+        </div>
+        <div class="group-content">
+          <ValidationResult :validation-result="validationResult" />
+        </div>
+      </div>
+    </div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import ConfigPreview from '../previews/ConfigPreview.vue'
+import TriggerPreview from '../previews/TriggerPreview.vue'
+import ActionPreview from '../previews/ActionPreview.vue'
+import ValidationResult from '../previews/ValidationResult.vue'
+import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
+
+/** 预览区域组件 */
+defineOptions({ name: 'PreviewSection' })
+
+interface Props {
+  formData: RuleSceneFormData
+  validationResult?: { valid: boolean; message?: string } | null
+}
+
+interface Emits {
+  (e: 'validate'): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+// 状态
+const validating = ref(false)
+
+// 事件处理
+const handleValidate = async () => {
+  validating.value = true
+  try {
+    // 延迟一下模拟验证过程
+    await new Promise(resolve => setTimeout(resolve, 500))
+    emit('validate')
+  } finally {
+    validating.value = false
+  }
+}
+</script>
+
+<style scoped>
+.preview-section {
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 8px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-icon {
+  color: var(--el-color-primary);
+  font-size: 18px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-content {
+  padding: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.preview-group {
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.group-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.group-icon {
+  color: var(--el-color-primary);
+  font-size: 16px;
+}
+
+.group-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.group-content {
+  padding: 16px;
+}
+</style>

+ 395 - 0
src/views/iot/rule/scene/components/sections/TriggerSection.vue

@@ -0,0 +1,395 @@
+<!-- 触发器配置组件 -->
+<template>
+  <el-card class="trigger-section" shadow="never">
+    <template #header>
+      <div class="section-header">
+        <div class="header-left">
+          <Icon icon="ep:lightning" class="section-icon" />
+          <span class="section-title">触发器配置</span>
+          <el-tag size="small" type="info">{{ triggers.length }}/{{ maxTriggers }}</el-tag>
+        </div>
+        <div class="header-right">
+          <el-button
+            type="primary"
+            size="small"
+            @click="addTrigger"
+            :disabled="triggers.length >= maxTriggers"
+          >
+            <Icon icon="ep:plus" />
+            添加触发器
+          </el-button>
+        </div>
+      </div>
+    </template>
+
+    <div class="section-content">
+      <!-- 空状态 -->
+      <div v-if="triggers.length === 0" class="empty-state">
+        <el-empty description="暂无触发器配置">
+          <el-button type="primary" @click="addTrigger">
+            <Icon icon="ep:plus" />
+            添加第一个触发器
+          </el-button>
+        </el-empty>
+      </div>
+
+      <!-- 触发器列表 -->
+      <div v-else class="triggers-list">
+        <div
+          v-for="(trigger, index) in triggers"
+          :key="`trigger-${index}`"
+          class="trigger-item"
+        >
+          <div class="trigger-header">
+            <div class="trigger-title">
+              <Icon icon="ep:lightning" class="trigger-icon" />
+              <span>触发器 {{ index + 1 }}</span>
+              <el-tag
+                :type="getTriggerTypeTag(trigger.type)"
+                size="small"
+              >
+                {{ getTriggerTypeName(trigger.type) }}
+              </el-tag>
+            </div>
+            <div class="trigger-actions">
+              <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="trigger-content">
+            <!-- 触发类型选择 -->
+            <TriggerTypeSelector
+              :model-value="trigger.type"
+              @update:model-value="(value) => updateTriggerType(index, value)"
+              @change="onTriggerTypeChange(trigger, $event)"
+            />
+
+            <!-- 设备触发配置 -->
+            <DeviceTriggerConfig
+              v-if="isDeviceTrigger(trigger.type)"
+              :model-value="trigger"
+              @update:model-value="(value) => updateTrigger(index, value)"
+              @validate="(result) => handleTriggerValidate(index, result)"
+            />
+
+            <!-- 定时触发配置 -->
+            <TimerTriggerConfig
+              v-if="trigger.type === TriggerTypeEnum.TIMER"
+              :model-value="trigger.cronExpression"
+              @update:model-value="(value) => updateTriggerCronExpression(index, value)"
+              @validate="(result) => handleTriggerValidate(index, result)"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 添加提示 -->
+      <div v-if="triggers.length > 0 && triggers.length < maxTriggers" class="add-more">
+        <el-button
+          type="primary"
+          plain
+          @click="addTrigger"
+          class="add-more-btn"
+        >
+          <Icon icon="ep:plus" />
+          继续添加触发器
+        </el-button>
+        <span class="add-more-text">
+          最多可添加 {{ maxTriggers }} 个触发器
+        </span>
+      </div>
+
+      <!-- 验证结果 -->
+      <div v-if="validationMessage" class="validation-result">
+        <el-alert
+          :title="validationMessage"
+          :type="isValid ? 'success' : 'error'"
+          :closable="false"
+          show-icon
+        />
+      </div>
+    </div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import TriggerTypeSelector from '../selectors/TriggerTypeSelector.vue'
+import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue'
+import TimerTriggerConfig from '../configs/TimerTriggerConfig.vue'
+import { 
+  TriggerFormData, 
+  IotRuleSceneTriggerTypeEnum as TriggerTypeEnum 
+} from '@/api/iot/rule/scene/scene.types'
+import { createDefaultTriggerData } from '../../utils/transform'
+
+/** 触发器配置组件 */
+defineOptions({ name: 'TriggerSection' })
+
+interface Props {
+  triggers: TriggerFormData[]
+}
+
+interface Emits {
+  (e: 'update:triggers', value: TriggerFormData[]): void
+  (e: 'validate', result: { valid: boolean; message: string }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const triggers = useVModel(props, 'triggers', emit)
+
+// 配置常量
+const maxTriggers = 5
+
+// 验证状态
+const triggerValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
+const validationMessage = ref('')
+const isValid = ref(true)
+
+// 触发器类型映射
+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) => {
+  return [
+    TriggerTypeEnum.DEVICE_STATE_UPDATE,
+    TriggerTypeEnum.DEVICE_PROPERTY_POST,
+    TriggerTypeEnum.DEVICE_EVENT_POST,
+    TriggerTypeEnum.DEVICE_SERVICE_INVOKE
+  ].includes(type)
+}
+
+const getTriggerTypeName = (type: number) => {
+  return triggerTypeNames[type] || '未知类型'
+}
+
+const getTriggerTypeTag = (type: number) => {
+  return triggerTypeTags[type] || 'info'
+}
+
+// 事件处理
+const addTrigger = () => {
+  if (triggers.value.length >= maxTriggers) {
+    return
+  }
+  
+  const newTrigger = createDefaultTriggerData()
+  triggers.value.push(newTrigger)
+}
+
+const removeTrigger = (index: number) => {
+  triggers.value.splice(index, 1)
+  delete triggerValidations.value[index]
+  
+  // 重新索引验证结果
+  const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
+  Object.keys(triggerValidations.value).forEach(key => {
+    const numKey = parseInt(key)
+    if (numKey > index) {
+      newValidations[numKey - 1] = triggerValidations.value[numKey]
+    } else if (numKey < index) {
+      newValidations[numKey] = triggerValidations.value[numKey]
+    }
+  })
+  triggerValidations.value = newValidations
+  
+  updateValidationResult()
+}
+
+const updateTriggerType = (index: number, type: number) => {
+  triggers.value[index].type = type
+  onTriggerTypeChange(triggers.value[index], type)
+}
+
+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) => {
+  // 清理不相关的配置
+  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 * * ?'
+    }
+  } else {
+    trigger.cronExpression = undefined
+    if (type === TriggerTypeEnum.DEVICE_STATE_UPDATE) {
+      trigger.conditionGroups = undefined
+    } else if (!trigger.conditionGroups) {
+      trigger.conditionGroups = []
+    }
+  }
+}
+
+const handleTriggerValidate = (index: number, result: { valid: boolean; message: string }) => {
+  triggerValidations.value[index] = result
+  updateValidationResult()
+}
+
+const updateValidationResult = () => {
+  const validations = Object.values(triggerValidations.value)
+  const allValid = validations.every(v => v.valid)
+  const hasValidations = validations.length > 0
+  
+  if (!hasValidations) {
+    isValid.value = true
+    validationMessage.value = ''
+  } else if (allValid) {
+    isValid.value = true
+    validationMessage.value = '所有触发器配置验证通过'
+  } else {
+    isValid.value = false
+    const errorMessages = validations
+      .filter(v => !v.valid)
+      .map(v => v.message)
+    validationMessage.value = `触发器配置错误: ${errorMessages.join('; ')}`
+  }
+  
+  emit('validate', { valid: isValid.value, message: validationMessage.value })
+}
+
+// 监听触发器数量变化
+watch(() => triggers.value.length, () => {
+  updateValidationResult()
+})
+</script>
+
+<style scoped>
+.trigger-section {
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 8px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-icon {
+  color: var(--el-color-primary);
+  font-size: 18px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.section-content {
+  padding: 0;
+}
+
+.empty-state {
+  padding: 40px 0;
+  text-align: center;
+}
+
+.triggers-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.trigger-item {
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  background: var(--el-fill-color-blank);
+}
+
+.trigger-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: var(--el-fill-color-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.trigger-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.trigger-icon {
+  color: var(--el-color-warning);
+  font-size: 16px;
+}
+
+.trigger-content {
+  padding: 16px;
+}
+
+.add-more {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-top: 16px;
+  padding: 16px;
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  background: var(--el-fill-color-lighter);
+}
+
+.add-more-btn {
+  flex-shrink: 0;
+}
+
+.add-more-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.validation-result {
+  margin-top: 16px;
+}
+</style>

+ 145 - 0
src/views/iot/rule/scene/components/selectors/ActionTypeSelector.vue

@@ -0,0 +1,145 @@
+<!-- 执行器类型选择组件 -->
+<template>
+  <div class="action-type-selector">
+    <el-form-item label="执行类型" required>
+      <el-select
+        v-model="localValue"
+        placeholder="请选择执行类型"
+        @change="handleChange"
+        class="w-full"
+      >
+        <el-option
+          v-for="option in actionTypeOptions"
+          :key="option.value"
+          :label="option.label"
+          :value="option.value"
+        >
+          <div class="action-option">
+            <div class="option-content">
+              <Icon :icon="option.icon" class="option-icon" />
+              <div class="option-info">
+                <div class="option-label">{{ option.label }}</div>
+                <div class="option-desc">{{ option.description }}</div>
+              </div>
+            </div>
+            <el-tag :type="option.tag" size="small">
+              {{ option.category }}
+            </el-tag>
+          </div>
+        </el-option>
+      </el-select>
+    </el-form-item>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { IotRuleSceneActionTypeEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 执行器类型选择组件 */
+defineOptions({ name: 'ActionTypeSelector' })
+
+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 localValue = useVModel(props, 'modelValue', emit)
+
+// 执行器类型选项
+const actionTypeOptions = [
+  {
+    value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
+    label: '设备属性设置',
+    description: '设置目标设备的属性值',
+    icon: 'ep:edit',
+    tag: 'primary',
+    category: '设备控制'
+  },
+  {
+    value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE,
+    label: '设备服务调用',
+    description: '调用目标设备的服务',
+    icon: 'ep:service',
+    tag: 'success',
+    category: '设备控制'
+  },
+  {
+    value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
+    label: '触发告警',
+    description: '触发系统告警通知',
+    icon: 'ep:warning',
+    tag: 'danger',
+    category: '告警通知'
+  },
+  {
+    value: IotRuleSceneActionTypeEnum.ALERT_RECOVER,
+    label: '恢复告警',
+    description: '恢复已触发的告警',
+    icon: 'ep:circle-check',
+    tag: 'warning',
+    category: '告警通知'
+  }
+]
+
+// 事件处理
+const handleChange = (value: number) => {
+  emit('change', value)
+}
+</script>
+
+<style scoped>
+.action-type-selector {
+  width: 100%;
+}
+
+.action-option {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 4px 0;
+}
+
+.option-content {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex: 1;
+}
+
+.option-icon {
+  font-size: 18px;
+  color: var(--el-color-primary);
+  flex-shrink: 0;
+}
+
+.option-info {
+  flex: 1;
+}
+
+.option-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 2px;
+}
+
+.option-desc {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
+
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 8px 20px;
+}
+</style>

+ 275 - 0
src/views/iot/rule/scene/components/selectors/OperatorSelector.vue

@@ -0,0 +1,275 @@
+<!-- 操作符选择器组件 -->
+<template>
+  <div class="operator-selector">
+    <el-select
+      v-model="localValue"
+      placeholder="请选择操作符"
+      @change="handleChange"
+      class="w-full"
+    >
+      <el-option
+        v-for="operator in availableOperators"
+        :key="operator.value"
+        :label="operator.label"
+        :value="operator.value"
+      >
+        <div class="operator-option">
+          <div class="option-content">
+            <div class="option-label">{{ operator.label }}</div>
+            <div class="option-symbol">{{ operator.symbol }}</div>
+          </div>
+          <div class="option-desc">{{ operator.description }}</div>
+        </div>
+      </el-option>
+    </el-select>
+
+    <!-- 操作符说明 -->
+    <div v-if="selectedOperator" class="operator-description">
+      <div class="desc-content">
+        <Icon icon="ep:info-filled" class="desc-icon" />
+        <span class="desc-text">{{ selectedOperator.description }}</span>
+      </div>
+      <div v-if="selectedOperator.example" class="desc-example">
+        <span class="example-label">示例:</span>
+        <code class="example-code">{{ selectedOperator.example }}</code>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+
+/** 操作符选择器组件 */
+defineOptions({ name: 'OperatorSelector' })
+
+interface Props {
+  modelValue?: string
+  propertyType?: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'change', value: string): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+// 所有操作符定义
+const allOperators = [
+  {
+    value: '=',
+    label: '等于',
+    symbol: '=',
+    description: '值完全相等时触发',
+    example: 'temperature = 25',
+    supportedTypes: ['int', 'float', 'double', 'string', 'bool', 'enum']
+  },
+  {
+    value: '!=',
+    label: '不等于',
+    symbol: '≠',
+    description: '值不相等时触发',
+    example: 'power != false',
+    supportedTypes: ['int', 'float', 'double', 'string', 'bool', 'enum']
+  },
+  {
+    value: '>',
+    label: '大于',
+    symbol: '>',
+    description: '值大于指定值时触发',
+    example: 'temperature > 30',
+    supportedTypes: ['int', 'float', 'double', 'date']
+  },
+  {
+    value: '>=',
+    label: '大于等于',
+    symbol: '≥',
+    description: '值大于或等于指定值时触发',
+    example: 'humidity >= 80',
+    supportedTypes: ['int', 'float', 'double', 'date']
+  },
+  {
+    value: '<',
+    label: '小于',
+    symbol: '<',
+    description: '值小于指定值时触发',
+    example: 'temperature < 10',
+    supportedTypes: ['int', 'float', 'double', 'date']
+  },
+  {
+    value: '<=',
+    label: '小于等于',
+    symbol: '≤',
+    description: '值小于或等于指定值时触发',
+    example: 'battery <= 20',
+    supportedTypes: ['int', 'float', 'double', 'date']
+  },
+  {
+    value: 'in',
+    label: '包含于',
+    symbol: '∈',
+    description: '值在指定列表中时触发',
+    example: 'status in [1,2,3]',
+    supportedTypes: ['int', 'float', 'string', 'enum']
+  },
+  {
+    value: 'between',
+    label: '介于',
+    symbol: '⊆',
+    description: '值在指定范围内时触发',
+    example: 'temperature between 20,30',
+    supportedTypes: ['int', 'float', 'double', 'date']
+  },
+  {
+    value: 'contains',
+    label: '包含',
+    symbol: '⊃',
+    description: '字符串包含指定内容时触发',
+    example: 'message contains "error"',
+    supportedTypes: ['string']
+  },
+  {
+    value: 'startsWith',
+    label: '开始于',
+    symbol: '⊢',
+    description: '字符串以指定内容开始时触发',
+    example: 'deviceName startsWith "sensor"',
+    supportedTypes: ['string']
+  },
+  {
+    value: 'endsWith',
+    label: '结束于',
+    symbol: '⊣',
+    description: '字符串以指定内容结束时触发',
+    example: 'fileName endsWith ".log"',
+    supportedTypes: ['string']
+  }
+]
+
+// 计算属性
+const availableOperators = computed(() => {
+  if (!props.propertyType) {
+    return allOperators
+  }
+  
+  return allOperators.filter(op => 
+    op.supportedTypes.includes(props.propertyType!)
+  )
+})
+
+const selectedOperator = computed(() => {
+  return allOperators.find(op => op.value === localValue.value)
+})
+
+// 事件处理
+const handleChange = (value: string) => {
+  emit('change', value)
+}
+
+// 监听属性类型变化
+watch(() => props.propertyType, () => {
+  // 如果当前选择的操作符不支持新的属性类型,则清空选择
+  if (localValue.value && selectedOperator.value) {
+    if (!selectedOperator.value.supportedTypes.includes(props.propertyType || '')) {
+      localValue.value = ''
+    }
+  }
+})
+</script>
+
+<style scoped>
+.operator-selector {
+  width: 100%;
+}
+
+.operator-option {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 4px 0;
+}
+
+.option-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.option-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.option-symbol {
+  font-size: 16px;
+  color: var(--el-color-primary);
+  font-weight: bold;
+  min-width: 20px;
+  text-align: center;
+}
+
+.option-desc {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  max-width: 120px;
+  text-align: right;
+}
+
+.operator-description {
+  margin-top: 8px;
+  padding: 8px 12px;
+  background: var(--el-fill-color-light);
+  border-radius: 4px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.desc-content {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-bottom: 4px;
+}
+
+.desc-icon {
+  color: var(--el-color-primary);
+  font-size: 12px;
+  flex-shrink: 0;
+}
+
+.desc-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.desc-example {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-left: 18px;
+}
+
+.example-label {
+  font-size: 11px;
+  color: var(--el-text-color-placeholder);
+}
+
+.example-code {
+  font-size: 11px;
+  color: var(--el-color-primary);
+  background: var(--el-fill-color-blank);
+  padding: 2px 4px;
+  border-radius: 2px;
+  font-family: 'Courier New', monospace;
+}
+
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 8px 20px;
+}
+</style>

+ 325 - 0
src/views/iot/rule/scene/components/selectors/ProductDeviceSelector.vue

@@ -0,0 +1,325 @@
+<!-- 产品设备选择器组件 -->
+<template>
+  <div class="product-device-selector">
+    <el-row :gutter="16">
+      <!-- 产品选择 -->
+      <el-col :span="12">
+        <el-form-item label="选择产品" required>
+          <el-select
+            v-model="localProductId"
+            placeholder="请选择产品"
+            filterable
+            clearable
+            @change="handleProductChange"
+            class="w-full"
+            :loading="productLoading"
+          >
+            <el-option
+              v-for="product in productList"
+              :key="product.id"
+              :label="product.name"
+              :value="product.id"
+            >
+              <div class="product-option">
+                <div class="option-content">
+                  <div class="option-name">{{ product.name }}</div>
+                  <div class="option-key">{{ product.productKey }}</div>
+                </div>
+                <el-tag size="small" :type="product.status === 0 ? 'success' : 'danger'">
+                  {{ product.status === 0 ? '正常' : '禁用' }}
+                </el-tag>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-col>
+
+      <!-- 设备选择 -->
+      <el-col :span="12">
+        <el-form-item label="选择设备" required>
+          <el-select
+            v-model="localDeviceId"
+            placeholder="请先选择产品"
+            filterable
+            clearable
+            @change="handleDeviceChange"
+            class="w-full"
+            :loading="deviceLoading"
+            :disabled="!localProductId"
+          >
+            <el-option
+              v-for="device in deviceList"
+              :key="device.id"
+              :label="device.deviceName"
+              :value="device.id"
+            >
+              <div class="device-option">
+                <div class="option-content">
+                  <div class="option-name">{{ device.deviceName }}</div>
+                  <div class="option-nickname">{{ device.nickname || '无备注' }}</div>
+                </div>
+                <el-tag 
+                  size="small" 
+                  :type="getDeviceStatusTag(device.state)"
+                >
+                  {{ getDeviceStatusText(device.state) }}
+                </el-tag>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <!-- 选择结果展示 -->
+    <div v-if="localProductId && localDeviceId" class="selection-result">
+      <div class="result-header">
+        <Icon icon="ep:check" class="result-icon" />
+        <span class="result-title">已选择设备</span>
+      </div>
+      <div class="result-content">
+        <div class="result-item">
+          <span class="result-label">产品:</span>
+          <span class="result-value">{{ selectedProduct?.name }}</span>
+          <el-tag size="small" type="primary">{{ selectedProduct?.productKey }}</el-tag>
+        </div>
+        <div class="result-item">
+          <span class="result-label">设备:</span>
+          <span class="result-value">{{ selectedDevice?.deviceName }}</span>
+          <el-tag 
+            size="small" 
+            :type="getDeviceStatusTag(selectedDevice?.state)"
+          >
+            {{ getDeviceStatusText(selectedDevice?.state) }}
+          </el-tag>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { ProductApi } from '@/api/iot/product/product'
+import { DeviceApi } from '@/api/iot/device/device'
+
+/** 产品设备选择器组件 */
+defineOptions({ name: 'ProductDeviceSelector' })
+
+interface Props {
+  productId?: number
+  deviceId?: number
+}
+
+interface Emits {
+  (e: 'update:productId', value?: number): void
+  (e: 'update:deviceId', value?: number): void
+  (e: 'change', value: { productId?: number; deviceId?: number }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localProductId = useVModel(props, 'productId', emit)
+const localDeviceId = useVModel(props, 'deviceId', emit)
+
+// 数据状态
+const productLoading = ref(false)
+const deviceLoading = ref(false)
+const productList = ref<any[]>([])
+const deviceList = ref<any[]>([])
+
+// 计算属性
+const selectedProduct = computed(() => {
+  return productList.value.find(p => p.id === localProductId.value)
+})
+
+const selectedDevice = computed(() => {
+  return deviceList.value.find(d => d.id === localDeviceId.value)
+})
+
+// 设备状态映射
+const getDeviceStatusText = (state?: number) => {
+  switch (state) {
+    case 0: return '未激活'
+    case 1: return '在线'
+    case 2: return '离线'
+    default: return '未知'
+  }
+}
+
+const getDeviceStatusTag = (state?: number) => {
+  switch (state) {
+    case 0: return 'info'
+    case 1: return 'success'
+    case 2: return 'danger'
+    default: return 'info'
+  }
+}
+
+// 事件处理
+const handleProductChange = async (productId?: number) => {
+  localProductId.value = productId
+  localDeviceId.value = undefined
+  deviceList.value = []
+  
+  if (productId) {
+    await getDeviceList(productId)
+  }
+  
+  emitChange()
+}
+
+const handleDeviceChange = (deviceId?: number) => {
+  localDeviceId.value = deviceId
+  emitChange()
+}
+
+const emitChange = () => {
+  emit('change', {
+    productId: localProductId.value,
+    deviceId: localDeviceId.value
+  })
+}
+
+// API 调用
+const getProductList = async () => {
+  productLoading.value = true
+  try {
+    const data = await ProductApi.getSimpleProductList()
+    productList.value = data || []
+  } catch (error) {
+    console.error('获取产品列表失败:', error)
+    // 模拟数据
+    productList.value = [
+      { id: 1, name: '智能温度传感器', productKey: 'temp_sensor_001', status: 0 },
+      { id: 2, name: '智能空调控制器', productKey: 'ac_controller_001', status: 0 },
+      { id: 3, name: '智能门锁', productKey: 'smart_lock_001', status: 0 }
+    ]
+  } finally {
+    productLoading.value = false
+  }
+}
+
+const getDeviceList = async (productId: number) => {
+  deviceLoading.value = true
+  try {
+    const data = await DeviceApi.getSimpleDeviceList(undefined, productId)
+    deviceList.value = data || []
+  } catch (error) {
+    console.error('获取设备列表失败:', error)
+    // 模拟数据
+    deviceList.value = [
+      { id: 1, deviceName: 'sensor_001', nickname: '客厅温度传感器', state: 1, productId },
+      { id: 2, deviceName: 'sensor_002', nickname: '卧室温度传感器', state: 2, productId },
+      { id: 3, deviceName: 'sensor_003', nickname: '厨房温度传感器', state: 1, productId }
+    ]
+  } finally {
+    deviceLoading.value = false
+  }
+}
+
+// 初始化
+onMounted(async () => {
+  await getProductList()
+  
+  if (localProductId.value) {
+    await getDeviceList(localProductId.value)
+  }
+})
+
+// 监听产品变化
+watch(() => localProductId.value, async (newProductId) => {
+  if (newProductId && deviceList.value.length === 0) {
+    await getDeviceList(newProductId)
+  }
+})
+</script>
+
+<style scoped>
+.product-device-selector {
+  width: 100%;
+}
+
+.product-option,
+.device-option {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 4px 0;
+}
+
+.option-content {
+  flex: 1;
+}
+
+.option-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 2px;
+}
+
+.option-key,
+.option-nickname {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.selection-result {
+  margin-top: 16px;
+  padding: 12px;
+  background: var(--el-fill-color-light);
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.result-header {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-bottom: 8px;
+}
+
+.result-icon {
+  color: var(--el-color-success);
+  font-size: 16px;
+}
+
+.result-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.result-content {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  margin-left: 22px;
+}
+
+.result-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.result-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  min-width: 40px;
+}
+
+.result-value {
+  font-size: 12px;
+  color: var(--el-text-color-primary);
+  font-weight: 500;
+}
+
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 8px 20px;
+}
+</style>

+ 341 - 0
src/views/iot/rule/scene/components/selectors/PropertySelector.vue

@@ -0,0 +1,341 @@
+<!-- 属性选择器组件 -->
+<template>
+  <div class="property-selector">
+    <el-select
+      v-model="localValue"
+      placeholder="请选择监控项"
+      filterable
+      clearable
+      @change="handleChange"
+      class="w-full"
+      :loading="loading"
+    >
+      <el-option-group
+        v-for="group in propertyGroups"
+        :key="group.label"
+        :label="group.label"
+      >
+        <el-option
+          v-for="property in group.options"
+          :key="property.identifier"
+          :label="property.name"
+          :value="property.identifier"
+        >
+          <div class="property-option">
+            <div class="option-content">
+              <div class="option-name">{{ property.name }}</div>
+              <div class="option-identifier">{{ property.identifier }}</div>
+            </div>
+            <div class="option-meta">
+              <el-tag :type="getPropertyTypeTag(property.type)" size="small">
+                {{ getPropertyTypeName(property.type) }}
+              </el-tag>
+            </div>
+          </div>
+        </el-option>
+      </el-option-group>
+    </el-select>
+
+    <!-- 属性详情 -->
+    <div v-if="selectedProperty" class="property-details">
+      <div class="details-header">
+        <Icon icon="ep:info-filled" class="details-icon" />
+        <span class="details-title">{{ selectedProperty.name }}</span>
+        <el-tag :type="getPropertyTypeTag(selectedProperty.type)" size="small">
+          {{ getPropertyTypeName(selectedProperty.type) }}
+        </el-tag>
+      </div>
+      <div class="details-content">
+        <div class="detail-item">
+          <span class="detail-label">标识符:</span>
+          <span class="detail-value">{{ selectedProperty.identifier }}</span>
+        </div>
+        <div v-if="selectedProperty.description" class="detail-item">
+          <span class="detail-label">描述:</span>
+          <span class="detail-value">{{ selectedProperty.description }}</span>
+        </div>
+        <div v-if="selectedProperty.unit" class="detail-item">
+          <span class="detail-label">单位:</span>
+          <span class="detail-value">{{ selectedProperty.unit }}</span>
+        </div>
+        <div v-if="selectedProperty.range" class="detail-item">
+          <span class="detail-label">取值范围:</span>
+          <span class="detail-value">{{ selectedProperty.range }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 属性选择器组件 */
+defineOptions({ name: 'PropertySelector' })
+
+interface Props {
+  modelValue?: string
+  triggerType: number
+  productId?: number
+  deviceId?: number
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'change', value: { type: string; config: any }): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+// 状态
+const loading = ref(false)
+const propertyList = ref<any[]>([])
+
+// 计算属性
+const propertyGroups = computed(() => {
+  const groups: { label: string; options: any[] }[] = []
+  
+  if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST) {
+    groups.push({
+      label: '设备属性',
+      options: propertyList.value.filter(p => p.category === 'property')
+    })
+  }
+  
+  if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) {
+    groups.push({
+      label: '设备事件',
+      options: propertyList.value.filter(p => p.category === 'event')
+    })
+  }
+  
+  if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) {
+    groups.push({
+      label: '设备服务',
+      options: propertyList.value.filter(p => p.category === 'service')
+    })
+  }
+  
+  return groups.filter(group => group.options.length > 0)
+})
+
+const selectedProperty = computed(() => {
+  return propertyList.value.find(p => p.identifier === localValue.value)
+})
+
+// 工具函数
+const getPropertyTypeName = (type: string) => {
+  const typeMap = {
+    'int': '整数',
+    'float': '浮点数',
+    'double': '双精度',
+    'string': '字符串',
+    'bool': '布尔值',
+    'enum': '枚举',
+    'date': '日期',
+    'struct': '结构体',
+    'array': '数组'
+  }
+  return typeMap[type] || type
+}
+
+const getPropertyTypeTag = (type: string) => {
+  const tagMap = {
+    'int': 'primary',
+    'float': 'success',
+    'double': 'success',
+    'string': 'info',
+    'bool': 'warning',
+    'enum': 'danger',
+    'date': 'primary',
+    'struct': 'info',
+    'array': 'warning'
+  }
+  return tagMap[type] || 'info'
+}
+
+// 事件处理
+const handleChange = (value: string) => {
+  const property = propertyList.value.find(p => p.identifier === value)
+  if (property) {
+    emit('change', {
+      type: property.type,
+      config: property
+    })
+  }
+}
+
+// API 调用
+const getPropertyList = async () => {
+  if (!props.productId) {
+    propertyList.value = []
+    return
+  }
+  
+  loading.value = true
+  try {
+    // 这里应该调用真实的API获取物模型数据
+    // 暂时使用模拟数据
+    propertyList.value = [
+      // 属性
+      {
+        identifier: 'temperature',
+        name: '温度',
+        type: 'float',
+        category: 'property',
+        description: '环境温度',
+        unit: '°C',
+        range: '-40~80'
+      },
+      {
+        identifier: 'humidity',
+        name: '湿度',
+        type: 'float',
+        category: 'property',
+        description: '环境湿度',
+        unit: '%',
+        range: '0~100'
+      },
+      {
+        identifier: 'power',
+        name: '电源状态',
+        type: 'bool',
+        category: 'property',
+        description: '设备电源开关状态'
+      },
+      // 事件
+      {
+        identifier: 'alarm',
+        name: '告警事件',
+        type: 'struct',
+        category: 'event',
+        description: '设备告警事件'
+      },
+      {
+        identifier: 'fault',
+        name: '故障事件',
+        type: 'struct',
+        category: 'event',
+        description: '设备故障事件'
+      },
+      // 服务
+      {
+        identifier: 'restart',
+        name: '重启服务',
+        type: 'struct',
+        category: 'service',
+        description: '设备重启服务'
+      }
+    ]
+  } catch (error) {
+    console.error('获取物模型失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 监听产品变化
+watch(() => props.productId, () => {
+  getPropertyList()
+}, { immediate: true })
+
+// 监听触发类型变化
+watch(() => props.triggerType, () => {
+  localValue.value = ''
+})
+</script>
+
+<style scoped>
+.property-selector {
+  width: 100%;
+}
+
+.property-option {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 4px 0;
+}
+
+.option-content {
+  flex: 1;
+}
+
+.option-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 2px;
+}
+
+.option-identifier {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  font-family: 'Courier New', monospace;
+}
+
+.option-meta {
+  flex-shrink: 0;
+}
+
+.property-details {
+  margin-top: 12px;
+  padding: 12px;
+  background: var(--el-fill-color-light);
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.details-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.details-icon {
+  color: var(--el-color-primary);
+  font-size: 14px;
+}
+
+.details-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+}
+
+.details-content {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-left: 22px;
+}
+
+.detail-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.detail-label {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  min-width: 60px;
+}
+
+.detail-value {
+  font-size: 12px;
+  color: var(--el-text-color-primary);
+  font-family: 'Courier New', monospace;
+}
+
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 8px 20px;
+}
+</style>

+ 264 - 0
src/views/iot/rule/scene/components/selectors/TriggerTypeSelector.vue

@@ -0,0 +1,264 @@
+<!-- 触发器类型选择组件 -->
+<template>
+  <div class="trigger-type-selector">
+    <el-form-item label="触发类型" required>
+      <el-select
+        v-model="localValue"
+        placeholder="请选择触发类型"
+        @change="handleChange"
+        class="w-full"
+      >
+        <el-option
+          v-for="option in triggerTypeOptions"
+          :key="option.value"
+          :label="option.label"
+          :value="option.value"
+        >
+          <div class="trigger-option">
+            <div class="option-content">
+              <Icon :icon="option.icon" class="option-icon" />
+              <div class="option-info">
+                <div class="option-label">{{ option.label }}</div>
+                <div class="option-desc">{{ option.description }}</div>
+              </div>
+            </div>
+            <el-tag :type="option.tag" size="small">
+              {{ option.category }}
+            </el-tag>
+          </div>
+        </el-option>
+      </el-select>
+    </el-form-item>
+
+    <!-- 类型说明 -->
+    <div v-if="selectedOption" class="type-description">
+      <div class="desc-header">
+        <Icon :icon="selectedOption.icon" class="desc-icon" />
+        <span class="desc-title">{{ selectedOption.label }}</span>
+      </div>
+      <div class="desc-content">
+        <p class="desc-text">{{ selectedOption.description }}</p>
+        <div class="desc-features">
+          <div
+            v-for="feature in selectedOption.features"
+            :key="feature"
+            class="feature-item"
+          >
+            <Icon icon="ep:check" class="feature-icon" />
+            <span class="feature-text">{{ feature }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
+
+/** 触发器类型选择组件 */
+defineOptions({ name: 'TriggerTypeSelector' })
+
+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 localValue = useVModel(props, 'modelValue', emit)
+
+// 触发器类型选项
+const triggerTypeOptions = [
+  {
+    value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
+    label: '设备状态变更',
+    description: '当设备上线、离线状态发生变化时触发',
+    icon: 'ep:connection',
+    tag: 'warning',
+    category: '设备状态',
+    features: [
+      '监控设备连接状态',
+      '实时响应设备变化',
+      '无需配置额外条件'
+    ]
+  },
+  {
+    value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
+    label: '设备属性上报',
+    description: '当设备属性值满足指定条件时触发',
+    icon: 'ep:data-line',
+    tag: 'primary',
+    category: '数据监控',
+    features: [
+      '监控设备属性变化',
+      '支持多种比较条件',
+      '可配置阈值范围'
+    ]
+  },
+  {
+    value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
+    label: '设备事件上报',
+    description: '当设备上报特定事件时触发',
+    icon: 'ep:bell',
+    tag: 'success',
+    category: '事件监控',
+    features: [
+      '监控设备事件',
+      '支持事件参数过滤',
+      '实时事件响应'
+    ]
+  },
+  {
+    value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
+    label: '设备服务调用',
+    description: '当设备服务被调用时触发',
+    icon: 'ep:service',
+    tag: 'info',
+    category: '服务监控',
+    features: [
+      '监控服务调用',
+      '支持参数条件',
+      '服务执行跟踪'
+    ]
+  },
+  {
+    value: IotRuleSceneTriggerTypeEnum.TIMER,
+    label: '定时触发',
+    description: '按照设定的时间计划定时触发',
+    icon: 'ep:timer',
+    tag: 'danger',
+    category: '定时任务',
+    features: [
+      '支持CRON表达式',
+      '灵活的时间配置',
+      '可视化时间设置'
+    ]
+  }
+]
+
+// 计算属性
+const selectedOption = computed(() => {
+  return triggerTypeOptions.find(option => option.value === localValue.value)
+})
+
+// 事件处理
+const handleChange = (value: number) => {
+  emit('change', value)
+}
+</script>
+
+<style scoped>
+.trigger-type-selector {
+  width: 100%;
+}
+
+.trigger-option {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 4px 0;
+}
+
+.option-content {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex: 1;
+}
+
+.option-icon {
+  font-size: 18px;
+  color: var(--el-color-primary);
+  flex-shrink: 0;
+}
+
+.option-info {
+  flex: 1;
+}
+
+.option-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  margin-bottom: 2px;
+}
+
+.option-desc {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.4;
+}
+
+.type-description {
+  margin-top: 16px;
+  padding: 16px;
+  background: var(--el-fill-color-light);
+  border-radius: 6px;
+  border: 1px solid var(--el-border-color-lighter);
+}
+
+.desc-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.desc-icon {
+  font-size: 20px;
+  color: var(--el-color-primary);
+}
+
+.desc-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.desc-content {
+  margin-left: 28px;
+}
+
+.desc-text {
+  font-size: 14px;
+  color: var(--el-text-color-regular);
+  margin: 0 0 12px 0;
+  line-height: 1.5;
+}
+
+.desc-features {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.feature-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.feature-icon {
+  font-size: 12px;
+  color: var(--el-color-success);
+  flex-shrink: 0;
+}
+
+.feature-text {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 8px 20px;
+}
+</style>

+ 611 - 145
src/views/iot/rule/scene/index.vue

@@ -1,192 +1,658 @@
+<!-- 改进的场景联动规则管理页面 -->
 <template>
   <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="场景名称" prop="name">
-        <el-input
-          v-model="queryParams.name"
-          placeholder="请输入场景名称"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="场景状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择场景状态"
-          clearable
-          class="!w-240px"
-        >
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
-        <el-date-picker
-          v-model="queryParams.createTime"
-          value-format="YYYY-MM-DD HH:mm:ss"
-          type="daterange"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-220px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-        <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-          v-hasPermi="['iot:rule-scene:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
+    <!-- 页面头部 -->
+    <div class="page-header">
+      <div class="header-left">
+        <h2 class="page-title">
+          <Icon icon="ep:connection" class="title-icon" />
+          场景联动规则
+        </h2>
+        <p class="page-description"> 通过配置触发条件和执行动作,实现设备间的智能联动控制 </p>
+      </div>
+      <div class="header-right">
+        <el-button type="primary" @click="handleAdd">
+          <Icon icon="ep:plus" />
+          新增规则
         </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
+      </div>
+    </div>
 
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="场景编号" align="center" prop="id" />
-      <el-table-column label="场景名称" align="center" prop="name" />
-      <el-table-column label="场景描述" align="center" prop="description" />
-      <el-table-column label="场景状态" align="center" prop="status">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
-        </template>
-      </el-table-column>
-      <el-table-column label="触发器" align="center" prop="triggers">
-        <template #default="{ row }"> {{ row.triggers?.length }}个 </template>
-      </el-table-column>
-      <el-table-column label="执行器" align="center" prop="actions">
-        <template #default="{ row }"> {{ row.actions?.length }}个 </template>
-      </el-table-column>
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="操作" align="center" min-width="120px">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['iot:rule-scene:update']"
+    <!-- 搜索和筛选 -->
+    <el-card class="search-card" shadow="never">
+      <el-form
+        ref="queryFormRef"
+        :model="queryParams"
+        :inline="true"
+        label-width="80px"
+        @submit.prevent
+      >
+        <el-form-item label="规则名称">
+          <el-input
+            v-model="queryParams.name"
+            placeholder="请输入规则名称"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="规则状态">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="请选择状态"
+            clearable
+            class="!w-240px"
           >
-            编辑
+            <el-option label="启用" :value="0" />
+            <el-option label="禁用" :value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">
+            <Icon icon="ep:search" />
+            搜索
           </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['iot:rule-scene:delete']"
-          >
-            删除
+          <el-button @click="resetQuery">
+            <Icon icon="ep:refresh" />
+            重置
           </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 统计卡片 -->
+    <el-row :gutter="16" class="stats-row">
+      <el-col :span="6">
+        <el-card class="stats-card" shadow="hover">
+          <div class="stats-content">
+            <div class="stats-icon total">
+              <Icon icon="ep:document" />
+            </div>
+            <div class="stats-info">
+              <div class="stats-number">{{ statistics.total }}</div>
+              <div class="stats-label">总规则数</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card class="stats-card" shadow="hover">
+          <div class="stats-content">
+            <div class="stats-icon enabled">
+              <Icon icon="ep:check" />
+            </div>
+            <div class="stats-info">
+              <div class="stats-number">{{ statistics.enabled }}</div>
+              <div class="stats-label">启用规则</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card class="stats-card" shadow="hover">
+          <div class="stats-content">
+            <div class="stats-icon disabled">
+              <Icon icon="ep:close" />
+            </div>
+            <div class="stats-info">
+              <div class="stats-number">{{ statistics.disabled }}</div>
+              <div class="stats-label">禁用规则</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card class="stats-card" shadow="hover">
+          <div class="stats-content">
+            <div class="stats-icon active">
+              <Icon icon="ep:lightning" />
+            </div>
+            <div class="stats-info">
+              <div class="stats-number">{{ statistics.triggered }}</div>
+              <div class="stats-label">今日触发</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-card class="table-card" shadow="never">
+      <el-table v-loading="loading" :data="list" stripe @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" />
+        <el-table-column label="规则名称" prop="name" min-width="200">
+          <template #default="{ row }">
+            <div class="rule-name-cell">
+              <span class="rule-name">{{ row.name }}</span>
+              <el-tag
+                :type="row.status === 0 ? 'success' : 'danger'"
+                size="small"
+                class="status-tag"
+              >
+                {{ row.status === 0 ? '启用' : '禁用' }}
+              </el-tag>
+            </div>
+            <div v-if="row.description" class="rule-description">
+              {{ row.description }}
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="触发条件" min-width="250">
+          <template #default="{ row }">
+            <div class="trigger-summary">
+              <el-tag
+                v-for="(trigger, index) in getTriggerSummary(row)"
+                :key="index"
+                type="primary"
+                size="small"
+                class="trigger-tag"
+              >
+                {{ trigger }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="执行动作" min-width="250">
+          <template #default="{ row }">
+            <div class="action-summary">
+              <el-tag
+                v-for="(action, index) in getActionSummary(row)"
+                :key="index"
+                type="success"
+                size="small"
+                class="action-tag"
+              >
+                {{ action }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="最近触发" prop="lastTriggeredTime" width="180">
+          <template #default="{ row }">
+            <span v-if="row.lastTriggeredTime">
+              {{ formatDate(row.lastTriggeredTime) }}
+            </span>
+            <span v-else class="text-gray-400">未触发</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="创建时间" prop="createTime" width="180">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="200" fixed="right">
+          <template #default="{ row }">
+            <div class="action-buttons">
+              <el-button type="primary" link @click="handleEdit(row)">
+                <Icon icon="ep:edit" />
+                编辑
+              </el-button>
+              <el-button
+                :type="row.status === 0 ? 'warning' : 'success'"
+                link
+                @click="handleToggleStatus(row)"
+              >
+                <Icon :icon="row.status === 0 ? 'ep:video-pause' : 'ep:video-play'" />
+                {{ row.status === 0 ? '禁用' : '启用' }}
+              </el-button>
+              <el-button type="danger" link @click="handleDelete(row)">
+                <Icon icon="ep:delete" />
+                删除
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 批量操作 -->
+    <div v-if="selectedRows.length > 0" class="batch-actions">
+      <el-card shadow="always">
+        <div class="batch-content">
+          <span class="batch-info"> 已选择 {{ selectedRows.length }} 项 </span>
+          <div class="batch-buttons">
+            <el-button @click="handleBatchEnable">
+              <Icon icon="ep:video-play" />
+              批量启用
+            </el-button>
+            <el-button @click="handleBatchDisable">
+              <Icon icon="ep:video-pause" />
+              批量禁用
+            </el-button>
+            <el-button type="danger" @click="handleBatchDelete">
+              <Icon icon="ep:delete" />
+              批量删除
+            </el-button>
+          </div>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- 表单对话框 -->
+    <RuleSceneForm
+      v-model="formVisible"
+      :rule-scene="currentRule"
+      @success="handleFormSuccess"
     />
   </ContentWrap>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <RuleSceneForm ref="formRef" @success="getList" />
 </template>
 
 <script setup lang="ts">
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import { RuleSceneApi } from '@/api/iot/rule/scene'
-import RuleSceneForm from './RuleSceneForm.vue'
+import { ContentWrap } from '@/components/ContentWrap'
+import RuleSceneForm from './components/RuleSceneForm.vue'
 import { IotRuleScene } from '@/api/iot/rule/scene/scene.types'
+import { getRuleSceneSummary } from './utils/transform'
+import { formatDate } from '@/utils/formatTime'
 
-/** IoT 场景联动 列表 */
-defineOptions({ name: 'IotRuleScene' })
+/** 改进的场景联动规则管理页面 */
+defineOptions({ name: 'ImprovedRuleSceneIndex' })
 
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
+const message = useMessage()
+// const { t } = useI18n()
 
-const loading = ref(true) // 列表的加载中
-const list = ref<IotRuleScene[]>([]) // 列表的数据
-const total = ref(0) // 列表的总页数
+// 查询参数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: undefined,
-  description: undefined,
-  status: undefined,
-  createTime: []
+  name: '',
+  status: undefined as number | undefined
+})
+
+// 数据状态
+const loading = ref(true)
+const list = ref<IotRuleScene[]>([])
+const total = ref(0)
+const selectedRows = ref<IotRuleScene[]>([])
+
+// 表单状态
+const formVisible = ref(false)
+const currentRule = ref<IotRuleScene>()
+
+// 统计数据
+const statistics = ref({
+  total: 0,
+  enabled: 0,
+  disabled: 0,
+  triggered: 0
 })
-const queryFormRef = ref() // 搜索的表单
 
-/** 查询列表 */
+// 获取列表数据
 const getList = async () => {
   loading.value = true
   try {
-    const data = await RuleSceneApi.getRuleScenePage(queryParams)
-    list.value = data.list
-    total.value = data.total
+    // 模拟API调用
+    const mockData = {
+      list: [
+        {
+          id: 1,
+          name: '温度过高自动降温',
+          description: '当温度超过30度时自动开启空调',
+          status: 0,
+          triggers: [
+            {
+              type: 2,
+              productKey: 'temp_sensor',
+              deviceNames: ['sensor_001'],
+              conditions: [
+                {
+                  type: 'property',
+                  identifier: 'temperature',
+                  parameters: [{ operator: '>', value: '30' }]
+                }
+              ]
+            }
+          ],
+          actions: [
+            {
+              type: 1,
+              deviceControl: {
+                productKey: 'air_conditioner',
+                deviceNames: ['ac_001'],
+                type: 'property',
+                identifier: 'power',
+                params: { power: 1 }
+              }
+            }
+          ],
+          lastTriggeredTime: new Date().toISOString(),
+          createTime: new Date().toISOString()
+        },
+        {
+          id: 2,
+          name: '设备离线告警',
+          description: '设备离线时发送告警通知',
+          status: 0,
+          triggers: [
+            { type: 1, productKey: 'smart_device', deviceNames: ['device_001', 'device_002'] }
+          ],
+          actions: [{ type: 100, alertConfigId: 1 }],
+          createTime: new Date().toISOString()
+        }
+      ],
+      total: 2
+    }
+
+    list.value = mockData.list
+    total.value = mockData.total
+
+    // 更新统计数据
+    updateStatistics()
+  } catch (error) {
+    console.error('获取列表失败:', error)
   } finally {
     loading.value = false
   }
 }
 
-/** 搜索按钮操作 */
+// 更新统计数据
+const updateStatistics = () => {
+  statistics.value = {
+    total: list.value.length,
+    enabled: list.value.filter((item) => item.status === 0).length,
+    disabled: list.value.filter((item) => item.status === 1).length,
+    triggered: list.value.filter((item) => item.lastTriggeredTime).length
+  }
+}
+
+// 获取触发器摘要
+const getTriggerSummary = (rule: IotRuleScene) => {
+  return getRuleSceneSummary(rule).triggerSummary
+}
+
+// 获取执行器摘要
+const getActionSummary = (rule: IotRuleScene) => {
+  return getRuleSceneSummary(rule).actionSummary
+}
+
+// 事件处理
 const handleQuery = () => {
   queryParams.pageNo = 1
   getList()
 }
 
-/** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryParams.name = ''
+  queryParams.status = undefined
   handleQuery()
 }
 
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
+const handleAdd = () => {
+  currentRule.value = undefined
+  formVisible.value = true
 }
 
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
+const handleEdit = (row: IotRuleScene) => {
+  currentRule.value = row
+  formVisible.value = true
+}
+
+const handleDelete = async (row: IotRuleScene) => {
+  try {
+    await ElMessageBox.confirm('确定要删除这个规则吗?', '提示', {
+      type: 'warning'
+    })
+
+    // 这里应该调用删除API
+    message.success('删除成功')
+    getList()
+  } catch (error) {
+    // 用户取消删除
+  }
+}
+
+const handleToggleStatus = async (row: IotRuleScene) => {
+  try {
+    const newStatus = row.status === 0 ? 1 : 0
+    const action = newStatus === 0 ? '启用' : '禁用'
+
+    await ElMessageBox.confirm(`确定要${action}这个规则吗?`, '提示', {
+      type: 'warning'
+    })
+
+    // 这里应该调用状态切换API
+    row.status = newStatus
+    message.success(`${action}成功`)
+    updateStatistics()
+  } catch (error) {
+    // 用户取消操作
+  }
+}
+
+const handleSelectionChange = (selection: IotRuleScene[]) => {
+  selectedRows.value = selection
+}
+
+const handleBatchEnable = async () => {
   try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await RuleSceneApi.deleteRuleScene(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
+    await ElMessageBox.confirm(`确定要启用选中的 ${selectedRows.value.length} 个规则吗?`, '提示', {
+      type: 'warning'
+    })
+
+    // 这里应该调用批量启用API
+    selectedRows.value.forEach((row) => {
+      row.status = 0
+    })
+
+    message.success('批量启用成功')
+    updateStatistics()
+  } catch (error) {
+    // 用户取消操作
+  }
 }
 
-/** 初始化 **/
+const handleBatchDisable = async () => {
+  try {
+    await ElMessageBox.confirm(`确定要禁用选中的 ${selectedRows.value.length} 个规则吗?`, '提示', {
+      type: 'warning'
+    })
+
+    // 这里应该调用批量禁用API
+    selectedRows.value.forEach((row) => {
+      row.status = 1
+    })
+
+    message.success('批量禁用成功')
+    updateStatistics()
+  } catch (error) {
+    // 用户取消操作
+  }
+}
+
+const handleBatchDelete = async () => {
+  try {
+    await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个规则吗?`, '提示', {
+      type: 'warning'
+    })
+
+    // 这里应该调用批量删除API
+    message.success('批量删除成功')
+    getList()
+  } catch (error) {
+    // 用户取消操作
+  }
+}
+
+const handleFormSuccess = () => {
+  getList()
+}
+
+// 初始化
 onMounted(() => {
   getList()
 })
 </script>
+
+<style scoped>
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 20px;
+}
+
+.header-left {
+  flex: 1;
+}
+
+.page-title {
+  display: flex;
+  align-items: center;
+  margin: 0 0 8px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.title-icon {
+  margin-right: 12px;
+  color: #409eff;
+}
+
+.page-description {
+  margin: 0;
+  color: #606266;
+  font-size: 14px;
+}
+
+.search-card {
+  margin-bottom: 16px;
+}
+
+.stats-row {
+  margin-bottom: 16px;
+}
+
+.stats-card {
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.stats-card:hover {
+  transform: translateY(-2px);
+}
+
+.stats-content {
+  display: flex;
+  align-items: center;
+}
+
+.stats-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24px;
+  color: white;
+  margin-right: 16px;
+}
+
+.stats-icon.total {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.stats-icon.enabled {
+  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.stats-icon.disabled {
+  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.stats-icon.active {
+  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+}
+
+.stats-number {
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+  line-height: 1;
+}
+
+.stats-label {
+  font-size: 14px;
+  color: #909399;
+  margin-top: 4px;
+}
+
+.table-card {
+  margin-bottom: 20px;
+}
+
+.rule-name-cell {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.rule-name {
+  font-weight: 500;
+  color: #303133;
+}
+
+.status-tag {
+  flex-shrink: 0;
+}
+
+.rule-description {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 4px;
+}
+
+.trigger-summary,
+.action-summary {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+
+.trigger-tag,
+.action-tag {
+  margin: 0;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 8px;
+}
+
+.batch-actions {
+  position: fixed;
+  bottom: 20px;
+  left: 50%;
+  transform: translateX(-50%);
+  z-index: 1000;
+}
+
+.batch-content {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.batch-info {
+  font-weight: 500;
+  color: #303133;
+}
+
+.batch-buttons {
+  display: flex;
+  gap: 8px;
+}
+</style>

+ 548 - 0
src/views/iot/rule/scene/utils/errorHandler.ts

@@ -0,0 +1,548 @@
+/**
+ * IoT 场景联动错误处理和用户反馈工具
+ */
+
+import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
+
+// 错误类型枚举
+export enum ErrorType {
+  VALIDATION = 'validation',
+  NETWORK = 'network',
+  BUSINESS = 'business',
+  SYSTEM = 'system',
+  PERMISSION = 'permission'
+}
+
+// 错误级别枚举
+export enum ErrorLevel {
+  INFO = 'info',
+  WARNING = 'warning',
+  ERROR = 'error',
+  CRITICAL = 'critical'
+}
+
+// 错误信息接口
+export interface ErrorInfo {
+  type: ErrorType
+  level: ErrorLevel
+  code?: string
+  message: string
+  details?: any
+  timestamp?: Date
+  context?: string
+}
+
+// 用户反馈选项
+export interface FeedbackOptions {
+  showMessage?: boolean
+  showNotification?: boolean
+  showDialog?: boolean
+  autoClose?: boolean
+  duration?: number
+  confirmText?: string
+  cancelText?: string
+}
+
+/**
+ * 错误处理器类
+ */
+export class SceneRuleErrorHandler {
+  private static instance: SceneRuleErrorHandler
+  private errorLog: ErrorInfo[] = []
+  private maxLogSize = 100
+
+  private constructor() {}
+
+  static getInstance(): SceneRuleErrorHandler {
+    if (!SceneRuleErrorHandler.instance) {
+      SceneRuleErrorHandler.instance = new SceneRuleErrorHandler()
+    }
+    return SceneRuleErrorHandler.instance
+  }
+
+  /**
+   * 处理错误
+   */
+  handleError(error: ErrorInfo, options: FeedbackOptions = {}): Promise<boolean> {
+    // 记录错误日志
+    this.logError(error)
+
+    // 根据错误类型和级别选择处理方式
+    return this.processError(error, options)
+  }
+
+  /**
+   * 记录错误日志
+   */
+  private logError(error: ErrorInfo): void {
+    const errorWithTimestamp = {
+      ...error,
+      timestamp: new Date()
+    }
+
+    this.errorLog.unshift(errorWithTimestamp)
+
+    // 限制日志大小
+    if (this.errorLog.length > this.maxLogSize) {
+      this.errorLog = this.errorLog.slice(0, this.maxLogSize)
+    }
+
+    // 开发环境下打印到控制台
+    if (import.meta.env.DEV) {
+      console.error('[SceneRule Error]', errorWithTimestamp)
+    }
+  }
+
+  /**
+   * 处理错误
+   */
+  private async processError(error: ErrorInfo, options: FeedbackOptions): Promise<boolean> {
+    const defaultOptions: FeedbackOptions = {
+      showMessage: true,
+      showNotification: false,
+      showDialog: false,
+      autoClose: true,
+      duration: 3000,
+      confirmText: '确定',
+      cancelText: '取消'
+    }
+
+    const finalOptions = { ...defaultOptions, ...options }
+
+    try {
+      // 根据错误级别决定反馈方式
+      switch (error.level) {
+        case ErrorLevel.INFO:
+          return this.handleInfoError(error, finalOptions)
+        case ErrorLevel.WARNING:
+          return this.handleWarningError(error, finalOptions)
+        case ErrorLevel.ERROR:
+          return this.handleNormalError(error, finalOptions)
+        case ErrorLevel.CRITICAL:
+          return this.handleCriticalError(error, finalOptions)
+        default:
+          return this.handleNormalError(error, finalOptions)
+      }
+    } catch (e) {
+      console.error('Error handler failed:', e)
+      return false
+    }
+  }
+
+  /**
+   * 处理信息级错误
+   */
+  private async handleInfoError(error: ErrorInfo, options: FeedbackOptions): Promise<boolean> {
+    if (options.showMessage) {
+      ElMessage.info({
+        message: error.message,
+        duration: options.duration,
+        showClose: !options.autoClose
+      })
+    }
+    return true
+  }
+
+  /**
+   * 处理警告级错误
+   */
+  private async handleWarningError(error: ErrorInfo, options: FeedbackOptions): Promise<boolean> {
+    if (options.showNotification) {
+      ElNotification.warning({
+        title: '警告',
+        message: error.message,
+        duration: options.duration
+      })
+    } else if (options.showMessage) {
+      ElMessage.warning({
+        message: error.message,
+        duration: options.duration,
+        showClose: !options.autoClose
+      })
+    }
+    return true
+  }
+
+  /**
+   * 处理普通错误
+   */
+  private async handleNormalError(error: ErrorInfo, options: FeedbackOptions): Promise<boolean> {
+    if (options.showDialog) {
+      try {
+        await ElMessageBox.alert(error.message, '错误', {
+          type: 'error',
+          confirmButtonText: options.confirmText
+        })
+        return true
+      } catch (e) {
+        return false
+      }
+    } else if (options.showNotification) {
+      ElNotification.error({
+        title: '错误',
+        message: error.message,
+        duration: options.duration
+      })
+    } else if (options.showMessage) {
+      ElMessage.error({
+        message: error.message,
+        duration: options.duration,
+        showClose: !options.autoClose
+      })
+    }
+    return true
+  }
+
+  /**
+   * 处理严重错误
+   */
+  private async handleCriticalError(error: ErrorInfo, _: FeedbackOptions): Promise<boolean> {
+    try {
+      await ElMessageBox.confirm(`${error.message}\n\n是否重新加载页面?`, '严重错误', {
+        type: 'error',
+        confirmButtonText: '重新加载',
+        cancelButtonText: '继续使用'
+      })
+      // 用户选择重新加载
+      window.location.reload()
+      return true
+    } catch (e) {
+      // 用户选择继续使用
+      return false
+    }
+  }
+
+  /**
+   * 获取错误日志
+   */
+  getErrorLog(): ErrorInfo[] {
+    return [...this.errorLog]
+  }
+
+  /**
+   * 清空错误日志
+   */
+  clearErrorLog(): void {
+    this.errorLog = []
+  }
+
+  /**
+   * 导出错误日志
+   */
+  exportErrorLog(): string {
+    return JSON.stringify(this.errorLog, null, 2)
+  }
+}
+
+/**
+ * 预定义的错误处理函数
+ */
+export const errorHandler = SceneRuleErrorHandler.getInstance()
+
+/**
+ * 验证错误处理
+ */
+export function handleValidationError(message: string, context?: string): Promise<boolean> {
+  return errorHandler.handleError(
+    {
+      type: ErrorType.VALIDATION,
+      level: ErrorLevel.WARNING,
+      message,
+      context
+    },
+    {
+      showMessage: true,
+      duration: 4000
+    }
+  )
+}
+
+/**
+ * 网络错误处理
+ */
+export function handleNetworkError(error: any, context?: string): Promise<boolean> {
+  let message = '网络请求失败'
+
+  if (error?.response?.status) {
+    switch (error.response.status) {
+      case 400:
+        message = '请求参数错误'
+        break
+      case 401:
+        message = '未授权,请重新登录'
+        break
+      case 403:
+        message = '权限不足'
+        break
+      case 404:
+        message = '请求的资源不存在'
+        break
+      case 500:
+        message = '服务器内部错误'
+        break
+      case 502:
+        message = '网关错误'
+        break
+      case 503:
+        message = '服务暂不可用'
+        break
+      default:
+        message = `网络错误 (${error.response.status})`
+    }
+  } else if (error?.message) {
+    message = error.message
+  }
+
+  return errorHandler.handleError(
+    {
+      type: ErrorType.NETWORK,
+      level: ErrorLevel.ERROR,
+      code: error?.response?.status?.toString(),
+      message,
+      details: error,
+      context
+    },
+    {
+      showMessage: true,
+      duration: 5000
+    }
+  )
+}
+
+/**
+ * 业务逻辑错误处理
+ */
+export function handleBusinessError(
+  message: string,
+  code?: string,
+  context?: string
+): Promise<boolean> {
+  return errorHandler.handleError(
+    {
+      type: ErrorType.BUSINESS,
+      level: ErrorLevel.ERROR,
+      code,
+      message,
+      context
+    },
+    {
+      showMessage: true,
+      duration: 4000
+    }
+  )
+}
+
+/**
+ * 系统错误处理
+ */
+export function handleSystemError(error: any, context?: string): Promise<boolean> {
+  const message = error?.message || '系统发生未知错误'
+
+  return errorHandler.handleError(
+    {
+      type: ErrorType.SYSTEM,
+      level: ErrorLevel.CRITICAL,
+      message,
+      details: error,
+      context
+    },
+    {
+      showDialog: true
+    }
+  )
+}
+
+/**
+ * 权限错误处理
+ */
+export function handlePermissionError(
+  message: string = '权限不足',
+  context?: string
+): Promise<boolean> {
+  return errorHandler.handleError(
+    {
+      type: ErrorType.PERMISSION,
+      level: ErrorLevel.WARNING,
+      message,
+      context
+    },
+    {
+      showNotification: true,
+      duration: 5000
+    }
+  )
+}
+
+/**
+ * 成功反馈
+ */
+export function showSuccess(message: string, duration: number = 3000): void {
+  ElMessage.success({
+    message,
+    duration,
+    showClose: false
+  })
+}
+
+/**
+ * 信息反馈
+ */
+export function showInfo(message: string, duration: number = 3000): void {
+  ElMessage.info({
+    message,
+    duration,
+    showClose: false
+  })
+}
+
+/**
+ * 警告反馈
+ */
+export function showWarning(message: string, duration: number = 4000): void {
+  ElMessage.warning({
+    message,
+    duration,
+    showClose: true
+  })
+}
+
+/**
+ * 确认对话框
+ */
+export function showConfirm(
+  message: string,
+  title: string = '确认',
+  options: {
+    type?: 'info' | 'success' | 'warning' | 'error'
+    confirmText?: string
+    cancelText?: string
+  } = {}
+): Promise<boolean> {
+  const defaultOptions = {
+    type: 'warning' as const,
+    confirmText: '确定',
+    cancelText: '取消'
+  }
+
+  const finalOptions = { ...defaultOptions, ...options }
+
+  return ElMessageBox.confirm(message, title, {
+    type: finalOptions.type,
+    confirmButtonText: finalOptions.confirmText,
+    cancelButtonText: finalOptions.cancelText
+  })
+    .then(() => true)
+    .catch(() => false)
+}
+
+/**
+ * 加载状态管理
+ */
+export class LoadingManager {
+  private loadingStates = new Map<string, boolean>()
+  private loadingInstances = new Map<string, any>()
+
+  /**
+   * 开始加载
+   */
+  startLoading(key: string, _: string = '加载中...'): void {
+    if (this.loadingStates.get(key)) {
+      return // 已经在加载中
+    }
+
+    this.loadingStates.set(key, true)
+
+    // 这里可以根据需要创建全局加载实例
+    // const loading = ElLoading.service({
+    //   lock: true,
+    //   text,
+    //   background: 'rgba(0, 0, 0, 0.7)'
+    // })
+    // this.loadingInstances.set(key, loading)
+  }
+
+  /**
+   * 结束加载
+   */
+  stopLoading(key: string): void {
+    this.loadingStates.set(key, false)
+
+    const loading = this.loadingInstances.get(key)
+    if (loading) {
+      loading.close()
+      this.loadingInstances.delete(key)
+    }
+  }
+
+  /**
+   * 检查是否在加载中
+   */
+  isLoading(key: string): boolean {
+    return this.loadingStates.get(key) || false
+  }
+
+  /**
+   * 清空所有加载状态
+   */
+  clearAll(): void {
+    this.loadingInstances.forEach((loading) => loading.close())
+    this.loadingStates.clear()
+    this.loadingInstances.clear()
+  }
+}
+
+export const loadingManager = new LoadingManager()
+
+/**
+ * 异步操作包装器,自动处理错误和加载状态
+ */
+export async function withErrorHandling<T>(
+  operation: () => Promise<T>,
+  options: {
+    loadingKey?: string
+    loadingText?: string
+    context?: string
+    showSuccess?: boolean
+    successMessage?: string
+    errorHandler?: (error: any) => Promise<boolean>
+  } = {}
+): Promise<T | null> {
+  const {
+    loadingKey,
+    loadingText = '处理中...',
+    context,
+    showSuccess = false,
+    // successMessage = '操作成功',
+    errorHandler: customErrorHandler
+  } = options
+
+  try {
+    // 开始加载
+    if (loadingKey) {
+      loadingManager.startLoading(loadingKey, loadingText)
+    }
+
+    // 执行操作
+    const result = await operation()
+
+    // 显示成功消息
+    if (showSuccess) {
+      // showSuccess(successMessage)
+    }
+
+    return result
+  } catch (error) {
+    // 使用自定义错误处理器或默认处理器
+    if (customErrorHandler) {
+      await customErrorHandler(error)
+    } else {
+      await handleNetworkError(error, context)
+    }
+    return null
+  } finally {
+    // 结束加载
+    if (loadingKey) {
+      loadingManager.stopLoading(loadingKey)
+    }
+  }
+}

+ 406 - 0
src/views/iot/rule/scene/utils/transform.ts

@@ -0,0 +1,406 @@
+/**
+ * IoT 场景联动数据转换工具函数
+ */
+
+import {
+  IotRuleScene,
+  TriggerConfig,
+  ActionConfig,
+  RuleSceneFormData,
+  TriggerFormData,
+  ActionFormData
+} from '@/api/iot/rule/scene/scene.types'
+import { generateUUID } from '@/utils'
+
+/**
+ * 创建默认的表单数据
+ */
+export function createDefaultFormData(): RuleSceneFormData {
+  return {
+    name: '',
+    description: '',
+    status: 0,
+    triggers: [],
+    actions: []
+  }
+}
+
+/**
+ * 创建默认的触发器数据
+ */
+export function createDefaultTriggerData(): TriggerFormData {
+  return {
+    type: 2, // 默认为属性上报
+    productId: undefined,
+    deviceId: undefined,
+    identifier: undefined,
+    operator: undefined,
+    value: undefined,
+    cronExpression: undefined,
+    conditionGroups: []
+  }
+}
+
+/**
+ * 创建默认的执行器数据
+ */
+export function createDefaultActionData(): ActionFormData {
+  return {
+    type: 1, // 默认为属性设置
+    productId: undefined,
+    deviceId: undefined,
+    params: {},
+    alertConfigId: undefined
+  }
+}
+
+/**
+ * 将表单数据转换为API请求格式
+ */
+export function transformFormToApi(formData: RuleSceneFormData): IotRuleScene {
+  // 这里需要根据实际API结构进行转换
+  // 暂时返回基本结构
+  return {
+    id: formData.id,
+    name: formData.name,
+    description: formData.description,
+    status: Number(formData.status),
+    triggers: [], // 需要根据实际API结构转换
+    actions: [] // 需要根据实际API结构转换
+  } as IotRuleScene
+}
+
+/**
+ * 将API响应数据转换为表单格式
+ */
+export function transformApiToForm(apiData: IotRuleScene): RuleSceneFormData {
+  return {
+    ...apiData,
+    status: Number(apiData.status), // 确保状态为数字类型
+    triggers:
+      apiData.triggers?.map((trigger) => ({
+        ...trigger,
+        type: Number(trigger.type),
+        // 为每个触发器添加唯一标识符,解决组件索引重用问题
+        key: generateUUID()
+      })) || [],
+    actions:
+      apiData.actions?.map((action) => ({
+        ...action,
+        type: Number(action.type),
+        // 为每个执行器添加唯一标识符,解决组件索引重用问题
+        key: generateUUID()
+      })) || []
+  }
+}
+
+/**
+ * 创建默认的触发器配置
+ */
+export function createDefaultTriggerConfig(type?: number): TriggerConfig {
+  const baseConfig: TriggerConfig = {
+    key: generateUUID(),
+    type: type || 2, // 默认为物模型属性上报
+    productKey: '',
+    deviceNames: [],
+    conditions: []
+  }
+
+  // 定时触发的默认配置
+  if (type === 100) {
+    return {
+      ...baseConfig,
+      cronExpression: '0 0 12 * * ?', // 默认每天中午12点
+      productKey: undefined,
+      deviceNames: undefined,
+      conditions: undefined
+    }
+  }
+
+  // 设备状态变更的默认配置
+  if (type === 1) {
+    return {
+      ...baseConfig,
+      conditions: undefined // 设备状态变更不需要条件
+    }
+  }
+
+  // 其他设备触发类型的默认配置
+  return {
+    ...baseConfig,
+    conditions: [
+      {
+        type: 'property',
+        identifier: 'set',
+        parameters: [
+          {
+            identifier: '',
+            operator: '=',
+            value: ''
+          }
+        ]
+      }
+    ]
+  }
+}
+
+/**
+ * 创建默认的执行器配置
+ */
+export function createDefaultActionConfig(type?: number): ActionConfig {
+  const baseConfig: ActionConfig = {
+    key: generateUUID(),
+    type: type || 1 // 默认为设备属性设置
+  }
+
+  // 告警相关的默认配置
+  if (type === 100 || type === 101) {
+    return {
+      ...baseConfig,
+      alertConfigId: undefined
+    }
+  }
+
+  // 设备控制的默认配置
+  return {
+    ...baseConfig,
+    deviceControl: {
+      productKey: '',
+      deviceNames: [],
+      type: 'property',
+      identifier: 'set',
+      params: {}
+    }
+  }
+}
+
+/**
+ * 深度克隆对象(用于避免引用问题)
+ */
+export function deepClone<T>(obj: T): T {
+  if (obj === null || typeof obj !== 'object') {
+    return obj
+  }
+
+  if (obj instanceof Date) {
+    return new Date(obj.getTime()) as unknown as T
+  }
+
+  if (obj instanceof Array) {
+    return obj.map((item) => deepClone(item)) as unknown as T
+  }
+
+  if (typeof obj === 'object') {
+    const clonedObj = {} as T
+    for (const key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        clonedObj[key] = deepClone(obj[key])
+      }
+    }
+    return clonedObj
+  }
+
+  return obj
+}
+
+/**
+ * 清理空值和无效数据
+ */
+export function cleanFormData(data: IotRuleScene): IotRuleScene {
+  const cleaned = deepClone(data)
+
+  // 清理触发器数据
+  cleaned.triggers =
+    cleaned.triggers?.filter((trigger) => {
+      // 移除类型为空的触发器
+      if (!trigger.type) return false
+
+      // 定时触发器必须有CRON表达式
+      if (trigger.type === 100 && !trigger.cronExpression) return false
+
+      // 设备触发器必须有产品和设备
+      if (trigger.type !== 100 && (!trigger.productKey || !trigger.deviceNames?.length))
+        return false
+
+      return true
+    }) || []
+
+  // 清理执行器数据
+  cleaned.actions =
+    cleaned.actions?.filter((action) => {
+      // 移除类型为空的执行器
+      if (!action.type) return false
+
+      // 告警类型必须有告警配置ID
+      if ((action.type === 100 || action.type === 101) && !action.alertConfigId) return false
+
+      // 设备控制类型必须有完整的设备控制配置
+      if (
+        (action.type === 1 || action.type === 2) &&
+        (!action.deviceControl?.productKey ||
+          !action.deviceControl?.deviceNames?.length ||
+          !action.deviceControl?.identifier ||
+          !action.deviceControl?.params ||
+          Object.keys(action.deviceControl.params).length === 0)
+      ) {
+        return false
+      }
+
+      return true
+    }) || []
+
+  return cleaned
+}
+
+/**
+ * 格式化CRON表达式显示
+ */
+export function formatCronExpression(cron: string): string {
+  if (!cron) return ''
+
+  // 简单的CRON表达式解析和格式化
+  const parts = cron.trim().split(' ')
+  if (parts.length < 5) return cron
+
+  const [second, minute, hour] = parts
+
+  // 构建可读的描述
+  let description = ''
+
+  if (second === '0' && minute === '0') {
+    if (hour === '*') {
+      description = '每小时'
+    } else if (hour.includes('/')) {
+      const interval = hour.split('/')[1]
+      description = `每${interval}小时`
+    } else {
+      description = `每天${hour}点`
+    }
+  } else if (second === '0') {
+    if (minute === '*') {
+      description = '每分钟'
+    } else if (minute.includes('/')) {
+      const interval = minute.split('/')[1]
+      description = `每${interval}分钟`
+    } else {
+      description = `每小时第${minute}分钟`
+    }
+  } else {
+    if (second === '*') {
+      description = '每秒'
+    } else if (second.includes('/')) {
+      const interval = second.split('/')[1]
+      description = `每${interval}秒`
+    }
+  }
+
+  return description || cron
+}
+
+/**
+ * 验证并修复数据结构
+ */
+export function validateAndFixData(data: IotRuleScene): IotRuleScene {
+  const fixed = deepClone(data)
+
+  // 确保必要字段存在
+  if (!fixed.triggers) fixed.triggers = []
+  if (!fixed.actions) fixed.actions = []
+
+  // 修复触发器数据
+  fixed.triggers = fixed.triggers.map((trigger) => {
+    const fixedTrigger = { ...trigger }
+
+    // 确保有key
+    if (!fixedTrigger.key) {
+      fixedTrigger.key = generateUUID()
+    }
+    // 定时触发器不需要产品和设备信息
+    if (fixedTrigger.type === 100) {
+      fixedTrigger.productKey = undefined
+      fixedTrigger.deviceNames = undefined
+      fixedTrigger.conditions = undefined
+    }
+
+    return fixedTrigger
+  })
+
+  // 修复执行器数据
+  fixed.actions = fixed.actions.map((action) => {
+    const fixedAction = { ...action }
+
+    // 确保有key
+    if (!fixedAction.key) {
+      fixedAction.key = generateUUID()
+    }
+
+    // 确保类型为数字
+    if (typeof fixedAction.type === 'string') {
+      fixedAction.type = Number(fixedAction.type)
+    }
+
+    // 修复设备控制参数字段名
+    if (fixedAction.deviceControl && 'data' in fixedAction.deviceControl) {
+      fixedAction.deviceControl.params = (fixedAction.deviceControl as any).data
+      delete (fixedAction.deviceControl as any).data
+    }
+
+    return fixedAction
+  })
+
+  return fixed
+}
+
+/**
+ * 比较两个场景联动规则是否相等(忽略key字段)
+ */
+export function isRuleSceneEqual(a: IotRuleScene, b: IotRuleScene): boolean {
+  const cleanA = transformFormToApi(a)
+  const cleanB = transformFormToApi(b)
+
+  return JSON.stringify(cleanA) === JSON.stringify(cleanB)
+}
+
+/**
+ * 获取场景联动规则的摘要信息
+ */
+export function getRuleSceneSummary(ruleScene: IotRuleScene): {
+  triggerSummary: string[]
+  actionSummary: string[]
+} {
+  const triggerSummary =
+    ruleScene.triggers?.map((trigger) => {
+      switch (trigger.type) {
+        case 1:
+          return `设备状态变更 (${trigger.deviceNames?.length || 0}个设备)`
+        case 2:
+          return `属性上报 (${trigger.deviceNames?.length || 0}个设备)`
+        case 3:
+          return `事件上报 (${trigger.deviceNames?.length || 0}个设备)`
+        case 4:
+          return `服务调用 (${trigger.deviceNames?.length || 0}个设备)`
+        case 100:
+          return `定时触发 (${formatCronExpression(trigger.cronExpression || '')})`
+        default:
+          return '未知触发类型'
+      }
+    }) || []
+
+  const actionSummary =
+    ruleScene.actions?.map((action) => {
+      switch (action.type) {
+        case 1:
+          return `属性设置 (${action.deviceControl?.deviceNames?.length || 0}个设备)`
+        case 2:
+          return `服务调用 (${action.deviceControl?.deviceNames?.length || 0}个设备)`
+        case 100:
+          return '告警触发'
+        case 101:
+          return '告警恢复'
+        default:
+          return '未知执行类型'
+      }
+    }) || []
+
+  return { triggerSummary, actionSummary }
+}

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

@@ -0,0 +1,278 @@
+/**
+ * IoT 场景联动表单验证工具函数
+ */
+
+import { FormValidationRules, IotRuleScene, TriggerConfig, ActionConfig } from '@/api/iot/rule/scene/scene.types'
+import { IotRuleSceneTriggerTypeEnum, IotRuleSceneActionTypeEnum } 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: [0, 1], message: '状态值必须为0或1', 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表达式格式
+ */
+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()
+
+  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':
+    default:
+      return true
+  }
+}
+
+/**
+ * 验证触发器配置
+ */
+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 }
+}
+
+/**
+ * 验证执行器配置
+ */
+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: '未知的执行类型' }
+}
+
+/**
+ * 验证完整的场景联动规则
+ */
+export function validateRuleScene(ruleScene: IotRuleScene): { valid: boolean; message?: string } {
+  // 基础字段验证
+  if (!ruleScene.name || ruleScene.name.trim().length === 0) {
+    return { valid: false, message: '场景名称不能为空' }
+  }
+
+  if (ruleScene.status !== 0 && ruleScene.status !== 1) {
+    return { valid: false, message: '场景状态必须为0或1' }
+  }
+
+  if (!ruleScene.triggers || ruleScene.triggers.length === 0) {
+    return { valid: false, message: '至少需要一个触发器' }
+  }
+
+  if (!ruleScene.actions || ruleScene.actions.length === 0) {
+    return { valid: false, message: '至少需要一个执行器' }
+  }
+
+  // 验证每个触发器
+  for (let i = 0; i < ruleScene.triggers.length; i++) {
+    const triggerResult = validateTriggerConfig(ruleScene.triggers[i])
+    if (!triggerResult.valid) {
+      return { valid: false, message: `触发器${i + 1}: ${triggerResult.message}` }
+    }
+  }
+
+  // 验证每个执行器
+  for (let i = 0; i < ruleScene.actions.length; i++) {
+    const actionResult = validateActionConfig(ruleScene.actions[i])
+    if (!actionResult.valid) {
+      return { valid: false, message: `执行器${i + 1}: ${actionResult.message}` }
+    }
+  }
+
+  return { valid: true }
+}
+
+/**
+ * 获取操作符选项
+ */
+export function getOperatorOptions() {
+  return [
+    { value: '=', label: '等于' },
+    { value: '!=', label: '不等于' },
+    { value: '>', label: '大于' },
+    { value: '>=', label: '大于等于' },
+    { value: '<', label: '小于' },
+    { value: '<=', label: '小于等于' },
+    { value: 'in', label: '包含' },
+    { value: 'not in', label: '不包含' },
+    { value: 'between', label: '介于之间' },
+    { value: 'not between', label: '不在之间' },
+    { value: 'like', label: '字符串匹配' },
+    { value: 'not null', label: '非空' }
+  ]
+}
+
+/**
+ * 获取触发类型选项
+ */
+export function getTriggerTypeOptions() {
+  return [
+    { value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE, label: '设备上下线变更' },
+    { value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, label: '物模型属性上报' },
+    { value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST, label: '设备事件上报' },
+    { value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE, label: '设备服务调用' },
+    { value: IotRuleSceneTriggerTypeEnum.TIMER, label: '定时触发' }
+  ]
+}
+
+/**
+ * 获取执行类型选项
+ */
+export function getActionTypeOptions() {
+  return [
+    { value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, label: '设备属性设置' },
+    { value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE, label: '设备服务调用' },
+    { value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER, label: '告警触发' },
+    { value: IotRuleSceneActionTypeEnum.ALERT_RECOVER, label: '告警恢复' }
+  ]
+}