瀏覽代碼

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

puhui999 6 月之前
父節點
當前提交
274ecb5dca

+ 1 - 1
.vscode/settings.json

@@ -88,7 +88,7 @@
   },
   "editor.formatOnSave": true,
   "[vue]": {
-    "editor.defaultFormatter": "octref.vetur"
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "i18n-ally.localesPaths": ["src/locales"],
   "i18n-ally.keystyle": "nested",

+ 7 - 6
src/api/iot/rule/scene/scene.types.ts

@@ -173,12 +173,13 @@ interface IotRuleScene extends TenantBaseDO {
   actions: ActionConfig[] // 执行器数组(必填,至少一个)
 }
 
-// 工具类型
-// TODO @puhui999:这些在瞅瞅~
-type TriggerType = (typeof IotRuleSceneTriggerTypeEnum)[keyof typeof IotRuleSceneTriggerTypeEnum]
-type ActionType = (typeof IotRuleSceneActionTypeEnum)[keyof typeof IotRuleSceneActionTypeEnum]
-type MessageType = (typeof IotDeviceMessageTypeEnum)[keyof typeof IotDeviceMessageTypeEnum]
-type OperatorType =
+// 工具类型 - 从枚举中提取类型
+export type TriggerType =
+  (typeof IotRuleSceneTriggerTypeEnum)[keyof typeof IotRuleSceneTriggerTypeEnum]
+export type ActionType =
+  (typeof IotRuleSceneActionTypeEnum)[keyof typeof IotRuleSceneActionTypeEnum]
+export type MessageType = (typeof IotDeviceMessageTypeEnum)[keyof typeof IotDeviceMessageTypeEnum]
+export type OperatorType =
   (typeof IotRuleSceneTriggerConditionParameterOperatorEnum)[keyof typeof IotRuleSceneTriggerConditionParameterOperatorEnum]['value']
 
 // 表单验证规则类型

File diff suppressed because it is too large
+ 0 - 1102
src/views/iot/rule/scene/IoT场景联动规则表单设计思路文档.md


+ 0 - 469
src/views/iot/rule/scene/IotThingModelTSLRespVO数据结构文档.md

@@ -1,469 +0,0 @@
-// TODO @puhui999:这些后续需要删除哈
-# IotThingModelTSLRespVO 数据结构文档
-
-## 概述
-
-`IotThingModelTSLRespVO` 是IoT产品物模型TSL(Thing Specification Language)的响应数据结构,用于返回完整的产品物模型定义,包括属性、事件和服务的详细信息。TSL是阿里云IoT平台定义的一套物模型描述规范。
-
-## 主体数据结构
-
-### IotThingModelTSLRespVO
-
-```typescript
-interface IotThingModelTSLRespVO {
-  productId: number;                    // 产品编号(必填)
-  productKey: string;                   // 产品标识(必填)
-  properties: ThingModelProperty[];     // 属性列表(必填)
-  events: ThingModelEvent[];           // 事件列表(必填)
-  services: ThingModelService[];       // 服务列表(必填)
-}
-```
-
-**字段说明:**
-- `productId`: 产品编号,唯一标识一个IoT产品
-- `productKey`: 产品标识符,用于设备连接和识别
-- `properties`: 设备属性列表,描述设备的状态信息
-- `events`: 设备事件列表,描述设备主动上报的事件
-- `services`: 设备服务列表,描述可以调用的设备功能
-
-## 属性数据结构 (ThingModelProperty)
-
-### 基本结构
-
-```typescript
-interface ThingModelProperty {
-  identifier: string;                   // 属性标识符(必填)
-  name: string;                        // 属性名称(必填)
-  accessMode: string;                  // 访问模式(必填)
-  required?: boolean;                  // 是否必选
-  dataType: string;                    // 数据类型(必填)
-  dataSpecs?: ThingModelDataSpecs;     // 数据规范(非列表型)
-  dataSpecsList?: ThingModelDataSpecs[]; // 数据规范(列表型)
-}
-```
-
-### 字段详细说明
-
-#### identifier(属性标识符)
-- **类型**: `string`
-- **必填**: 是
-- **格式**: 正则表达式 `^[a-zA-Z][a-zA-Z0-9_]{0,31}$`
-- **说明**: 只能由字母、数字和下划线组成,必须以字母开头,长度不超过32个字符
-- **示例**: `"temperature"`, `"humidity"`, `"power_status"`
-
-#### name(属性名称)
-- **类型**: `string`
-- **必填**: 是
-- **说明**: 属性的显示名称,用于界面展示
-- **示例**: `"温度"`, `"湿度"`, `"电源状态"`
-
-#### accessMode(访问模式)
-- **类型**: `string`
-- **必填**: 是
-- **枚举值**:
-  - `"r"`: 只读,设备只能上报,平台不能下发
-  - `"rw"`: 读写,设备可以上报,平台也可以下发
-- **示例**: `"r"`, `"rw"`
-
-#### dataType(数据类型)
-- **类型**: `string`
-- **必填**: 是
-- **枚举值**:
-  - `"int"`: 整数型
-  - `"float"`: 单精度浮点型
-  - `"double"`: 双精度浮点型
-  - `"enum"`: 枚举型
-  - `"bool"`: 布尔型
-  - `"text"`: 文本型
-  - `"date"`: 时间型
-  - `"struct"`: 结构体型
-  - `"array"`: 数组型
-
-## 事件数据结构 (ThingModelEvent)
-
-### 基本结构
-
-```typescript
-interface ThingModelEvent {
-  identifier: string;                   // 事件标识符(必填)
-  name: string;                        // 事件名称(必填)
-  required?: boolean;                  // 是否必选
-  type: string;                        // 事件类型(必填)
-  outputParams?: ThingModelParam[];    // 输出参数
-  method?: string;                     // 执行方法
-}
-```
-
-### 字段详细说明
-
-#### type(事件类型)
-- **类型**: `string`
-- **必填**: 是
-- **枚举值**:
-  - `"info"`: 信息事件
-  - `"alert"`: 告警事件
-  - `"error"`: 故障事件
-
-#### outputParams(输出参数)
-- **类型**: `ThingModelParam[]`
-- **必填**: 否
-- **说明**: 事件触发时返回的参数信息
-
-## 服务数据结构 (ThingModelService)
-
-### 基本结构
-
-```typescript
-interface ThingModelService {
-  identifier: string;                   // 服务标识符(必填)
-  name: string;                        // 服务名称(必填)
-  required?: boolean;                  // 是否必选
-  callType: string;                    // 调用类型(必填)
-  inputParams?: ThingModelParam[];     // 输入参数
-  outputParams?: ThingModelParam[];    // 输出参数
-  method?: string;                     // 执行方法
-}
-```
-
-### 字段详细说明
-
-#### callType(调用类型)
-- **类型**: `string`
-- **必填**: 是
-- **枚举值**:
-  - `"async"`: 异步调用
-  - `"sync"`: 同步调用
-
-## 参数数据结构 (ThingModelParam)
-
-### 基本结构
-
-```typescript
-interface ThingModelParam {
-  identifier: string;                   // 参数标识符(必填)
-  name: string;                        // 参数名称(必填)
-  direction: string;                   // 参数方向(必填)
-  paraOrder?: number;                  // 参数序号
-  dataType: string;                    // 数据类型(必填)
-  dataSpecs?: ThingModelDataSpecs;     // 数据规范(非列表型)
-  dataSpecsList?: ThingModelDataSpecs[]; // 数据规范(列表型)
-}
-```
-
-### 字段详细说明
-
-#### direction(参数方向)
-- **类型**: `string`
-- **必填**: 是
-- **枚举值**:
-  - `"input"`: 输入参数
-  - `"output"`: 输出参数
-
-## 数据规范结构 (ThingModelDataSpecs)
-
-数据规范是一个抽象基类,根据不同的数据类型有不同的具体实现:
-
-### 1. 数值型数据规范 (ThingModelNumericDataSpec)
-
-适用于 `int`、`float`、`double` 类型:
-
-```typescript
-interface ThingModelNumericDataSpec {
-  dataType: "int" | "float" | "double";
-  max: string;                         // 最大值(必填)
-  min: string;                         // 最小值(必填)
-  step: string;                        // 步长(必填)
-  precise?: string;                    // 精度(float/double可选)
-  defaultValue?: string;               // 默认值
-  unit?: string;                       // 单位符号
-  unitName?: string;                   // 单位名称
-}
-```
-
-### 2. 布尔/枚举型数据规范 (ThingModelBoolOrEnumDataSpecs)
-
-适用于 `bool`、`enum` 类型:
-
-```typescript
-interface ThingModelBoolOrEnumDataSpecs {
-  dataType: "bool" | "enum";
-  name: string;                        // 枚举项名称(必填)
-  value: number;                       // 枚举值(必填)
-}
-```
-
-### 3. 文本/时间型数据规范 (ThingModelDateOrTextDataSpecs)
-
-适用于 `text`、`date` 类型:
-
-```typescript
-interface ThingModelDateOrTextDataSpecs {
-  dataType: "text" | "date";
-  length?: number;                     // 数据长度(text类型需要,最大2048)
-  defaultValue?: string;               // 默认值
-}
-```
-
-### 4. 数组型数据规范 (ThingModelArrayDataSpecs)
-
-适用于 `array` 类型:
-
-```typescript
-interface ThingModelArrayDataSpecs {
-  dataType: "array";
-  size: number;                        // 数组元素个数(必填)
-  childDataType: string;               // 数组元素数据类型(必填)
-  dataSpecsList?: ThingModelDataSpecs[]; // 子元素数据规范(struct类型时)
-}
-```
-
-**childDataType 枚举值**:
-- `"struct"`: 结构体
-- `"int"`: 整数
-- `"float"`: 单精度浮点
-- `"double"`: 双精度浮点
-- `"text"`: 文本
-
-### 5. 结构体型数据规范 (ThingModelStructDataSpecs)
-
-适用于 `struct` 类型:
-
-```typescript
-interface ThingModelStructDataSpecs {
-  dataType: "struct";
-  identifier: string;                  // 属性标识符(必填)
-  name: string;                        // 属性名称(必填)
-  accessMode: string;                  // 操作类型(必填)
-  required?: boolean;                  // 是否必选
-  childDataType: string;               // 子数据类型(必填)
-  dataSpecs?: ThingModelDataSpecs;     // 数据规范(非列表型)
-  dataSpecsList?: ThingModelDataSpecs[]; // 数据规范(列表型)
-}
-```
-
-**childDataType 枚举值**:
-- `"int"`: 整数
-- `"float"`: 单精度浮点
-- `"double"`: 双精度浮点
-- `"text"`: 文本
-- `"date"`: 时间
-- `"enum"`: 枚举
-- `"bool"`: 布尔
-
-## 数据类型映射关系
-
-### dataSpecs vs dataSpecsList
-
-- **dataSpecs**: 用于非列表型数据类型(`int`、`float`、`double`、`text`、`date`、`array`)
-- **dataSpecsList**: 用于列表型数据类型(`enum`、`bool`、`struct`)
-
-### JSON多态序列化
-
-数据规范使用Jackson的`@JsonTypeInfo`和`@JsonSubTypes`注解实现多态序列化:
-
-```json
-{
-  "dataType": "int",
-  "max": "100",
-  "min": "0",
-  "step": "1",
-  "unit": "°C",
-  "unitName": "摄氏度"
-}
-```
-
-## 完整示例
-
-### 温度传感器物模型示例
-
-```json
-{
-  "productId": 1024,
-  "productKey": "temperature_sensor",
-  "properties": [
-    {
-      "identifier": "temperature",
-      "name": "温度",
-      "accessMode": "r",
-      "required": true,
-      "dataType": "float",
-      "dataSpecs": {
-        "dataType": "float",
-        "max": "100.0",
-        "min": "-40.0",
-        "step": "0.1",
-        "precise": "1",
-        "unit": "°C",
-        "unitName": "摄氏度"
-      }
-    },
-    {
-      "identifier": "power_switch",
-      "name": "电源开关",
-      "accessMode": "rw",
-      "required": false,
-      "dataType": "bool",
-      "dataSpecsList": [
-        {
-          "dataType": "bool",
-          "name": "关闭",
-          "value": 0
-        },
-        {
-          "dataType": "bool",
-          "name": "开启",
-          "value": 1
-        }
-      ]
-    }
-  ],
-  "events": [
-    {
-      "identifier": "high_temperature_alert",
-      "name": "高温告警",
-      "required": false,
-      "type": "alert",
-      "outputParams": [
-        {
-          "identifier": "current_temp",
-          "name": "当前温度",
-          "direction": "output",
-          "dataType": "float",
-          "dataSpecs": {
-            "dataType": "float",
-            "max": "100.0",
-            "min": "-40.0",
-            "step": "0.1"
-          }
-        }
-      ]
-    }
-  ],
-  "services": [
-    {
-      "identifier": "reset_device",
-      "name": "重置设备",
-      "required": false,
-      "callType": "async",
-      "inputParams": [
-        {
-          "identifier": "reset_type",
-          "name": "重置类型",
-          "direction": "input",
-          "dataType": "enum",
-          "dataSpecsList": [
-            {
-              "dataType": "enum",
-              "name": "软重置",
-              "value": 1
-            },
-            {
-              "dataType": "enum",
-              "name": "硬重置",
-              "value": 2
-            }
-          ]
-        }
-      ],
-      "outputParams": [
-        {
-          "identifier": "result",
-          "name": "执行结果",
-          "direction": "output",
-          "dataType": "bool",
-          "dataSpecsList": [
-            {
-              "dataType": "bool",
-              "name": "失败",
-              "value": 0
-            },
-            {
-              "dataType": "bool",
-              "name": "成功",
-              "value": 1
-            }
-          ]
-        }
-      ]
-    }
-  ]
-}
-```
-
-## 前端使用建议
-
-### 1. TypeScript类型定义
-
-建议在前端项目中定义完整的TypeScript接口,确保类型安全:
-
-```typescript
-// 定义完整的类型接口
-export interface IotThingModelTSLRespVO {
-  productId: number;
-  productKey: string;
-  properties: ThingModelProperty[];
-  events: ThingModelEvent[];
-  services: ThingModelService[];
-}
-
-// 使用联合类型处理数据规范的多态性
-export type ThingModelDataSpecs = 
-  | ThingModelNumericDataSpec
-  | ThingModelBoolOrEnumDataSpecs
-  | ThingModelDateOrTextDataSpecs
-  | ThingModelArrayDataSpecs
-  | ThingModelStructDataSpecs;
-```
-
-### 2. 数据验证
-
-```typescript
-// 验证数据类型和数据规范的一致性
-function validateDataSpecs(dataType: string, dataSpecs: any): boolean {
-  switch (dataType) {
-    case 'int':
-    case 'float':
-    case 'double':
-      return dataSpecs.dataType === dataType && 
-             dataSpecs.max !== undefined && 
-             dataSpecs.min !== undefined;
-    case 'bool':
-    case 'enum':
-      return Array.isArray(dataSpecs) && 
-             dataSpecs.every(spec => spec.name && spec.value !== undefined);
-    // ... 其他类型验证
-    default:
-      return false;
-  }
-}
-```
-
-### 3. 数据转换工具
-
-```typescript
-// 将后端数据转换为前端展示格式
-function formatPropertyValue(property: ThingModelProperty, value: any): string {
-  if (property.dataType === 'enum' || property.dataType === 'bool') {
-    const spec = property.dataSpecsList?.find(s => s.value === value);
-    return spec?.name || String(value);
-  }
-  
-  if (property.dataType === 'float' || property.dataType === 'double') {
-    const unit = property.dataSpecs?.unit || '';
-    return `${value}${unit}`;
-  }
-  
-  return String(value);
-}
-```
-
-## 注意事项
-
-1. **数据规范选择**: 根据`dataType`选择使用`dataSpecs`还是`dataSpecsList`
-2. **标识符唯一性**: 在同一产品下,所有功能的`identifier`必须唯一
-3. **数据类型一致性**: 参数的`dataType`必须与其`dataSpecs`的`dataType`保持一致
-4. **枚举值处理**: 布尔型和枚举型数据使用`dataSpecsList`数组存储可选值
-5. **嵌套结构**: 结构体和数组类型可能包含嵌套的数据规范定义
-6. **版本兼容**: 物模型结构可能随版本演进,前端需要做好兼容性处理
-
-这个数据结构为IoT设备的完整功能描述提供了标准化的格式,支持复杂的数据类型和嵌套结构,能够满足各种IoT设备的建模需求。

+ 6 - 24
src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue

@@ -8,25 +8,13 @@
       @change="handleDeviceChange"
     />
 
-    <!-- TODO @puhui999:这里有点冗余,建议去掉 -->
-    <!-- 设备状态变更提示 -->
-    <div v-if="trigger.type === TriggerTypeEnum.DEVICE_STATE_UPDATE" class="mt-8px">
-      <el-alert title="设备状态变更触发" type="info" :closable="false" show-icon>
-        <template #default>
-          <p class="m-0">当选中的设备上线或离线时将自动触发场景规则</p>
-          <p class="m-0 mt-4px text-12px text-[var(--el-text-color-secondary)]">无需配置额外的触发条件</p>
-        </template>
-      </el-alert>
-    </div>
-
     <!-- 条件组配置 -->
-    <div v-else-if="needsConditions" class="space-y-12px">
+    <div v-if="needsConditions" class="space-y-12px">
       <div class="flex items-center justify-between mb-12px">
         <div class="flex items-center gap-8px">
           <span class="text-14px font-500 text-[var(--el-text-color-primary)]">触发条件</span>
-          <!-- TODO @puhui999:去掉数量限制 -->
           <el-tag size="small" type="info">
-            {{ trigger.conditionGroups?.length || 0 }}/{{ maxConditionGroups }}
+            {{ trigger.conditionGroups?.length || 0 }}个条件组
           </el-tag>
         </div>
         <div class="flex items-center gap-8px">
@@ -52,18 +40,13 @@
           :key="`group-${groupIndex}`"
           class="border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
         >
-          <div class="flex items-center justify-between p-12px px-16px bg-[var(--el-fill-color-light)] border-b border-[var(--el-border-color-lighter)]">
+          <div
+            class="flex items-center justify-between p-12px px-16px bg-[var(--el-fill-color-light)] border-b border-[var(--el-border-color-lighter)]"
+          >
             <div class="flex items-center text-14px font-500 text-[var(--el-text-color-primary)]">
               <span>条件组 {{ groupIndex + 1 }}</span>
               <!-- TODO @puhui999:不用“且、或”哈。条件组之间,就是或;条件之间就是且 -->
-              <el-select
-                v-model="group.logicOperator"
-                size="small"
-                class="w-80px ml-12px"
-              >
-                <el-option label="且" value="AND" />
-                <el-option label="或" value="OR" />
-              </el-select>
+              <el-tag size="small" type="info" class="ml-8px">条件间为"且"关系</el-tag>
             </div>
             <el-button
               type="danger"
@@ -262,5 +245,4 @@ watch(
     updateValidationResult()
   }
 )
-// TODO @puhui999:unocss - 已完成转换
 </script>

+ 8 - 56
src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue

@@ -1,52 +1,23 @@
 <!-- 定时触发配置组件 -->
 <template>
   <div class="flex flex-col gap-16px">
-    <div class="flex items-center justify-between p-12px px-16px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
-      <div class="flex items-center gap-8px">
-        <Icon icon="ep:timer" class="text-[var(--el-color-danger)] text-18px" />
-        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">定时触发配置</span>
-      </div>
-      <div class="flex items-center gap-8px">
-        <el-button type="text" size="small" @click="showBuilder = !showBuilder">
-          <Icon :icon="showBuilder ? 'ep:edit' : 'ep:setting'" />
-          {{ showBuilder ? '手动编辑' : '可视化编辑' }}
-        </el-button>
-      </div>
+    <div class="flex items-center gap-8px p-12px px-16px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
+      <Icon icon="ep:timer" class="text-[var(--el-color-danger)] text-18px" />
+      <span class="text-14px font-500 text-[var(--el-text-color-primary)]">定时触发配置</span>
     </div>
 
-    <!-- 可视化编辑器 -->
-    <!-- TODO @puhui999:是不是复用现有的 cron 组件;不然有点重复哈;维护比较复杂 -->
-    <div v-if="showBuilder" class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
-      <CronBuilder v-model="localValue" @validate="handleValidate" />
-    </div>
-
-    <!-- 手动编辑 -->
-    <div v-else class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
+    <!-- CRON表达式配置 -->
+    <div class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]">
       <el-form-item label="CRON表达式" required>
-        <CronInput v-model="localValue" @validate="handleValidate" />
+        <Crontab v-model="localValue" />
       </el-form-item>
     </div>
-
-    <!-- 下次执行时间预览 -->
-    <NextExecutionPreview :cron-expression="localValue" />
-
-    <!-- 验证结果 -->
-    <div v-if="validationMessage" class="mt-8px">
-      <el-alert
-        :title="validationMessage"
-        :type="isValid ? 'success' : 'error'"
-        :closable="false"
-        show-icon
-      />
-    </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
-import CronBuilder from '../inputs/CronBuilder.vue'
-import CronInput from '../inputs/CronInput.vue'
-import NextExecutionPreview from '../previews/NextExecutionPreview.vue'
+import { Crontab } from '@/components/Crontab'
 
 /** 定时触发配置组件 */
 defineOptions({ name: 'TimerTriggerConfig' })
@@ -66,23 +37,4 @@ 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>
-
-
+</script>

+ 0 - 242
src/views/iot/rule/scene/form/inputs/CronBuilder.vue

@@ -1,242 +0,0 @@
-<!-- CRON 可视化构建器组件 -->
-<!-- TODO @puhui999:看看能不能复用全局的 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>

+ 0 - 141
src/views/iot/rule/scene/form/inputs/CronInput.vue

@@ -1,141 +0,0 @@
-<!-- CRON 表达式输入组件 -->
-<!-- TODO @puhui999:看看能不能复用全局的 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>

+ 0 - 178
src/views/iot/rule/scene/form/inputs/DescriptionInput.vue

@@ -1,178 +0,0 @@
-<!-- 场景描述输入组件 -->
-<template>
-  <div class="relative w-full">
-    <el-input
-      ref="inputRef"
-      v-model="localValue"
-      type="textarea"
-      placeholder="请输入场景描述(可选)"
-      :rows="3"
-      maxlength="200"
-      show-word-limit
-      resize="none"
-      @input="handleInput"
-    />
-
-    <!-- 描述模板 -->
-    <teleport to="body">
-      <div v-if="showTemplates" ref="templateDropdownRef" class="fixed z-1000 bg-white border border-[var(--el-border-color-light)] rounded-6px shadow-[var(--el-box-shadow)] min-w-300px max-w-400px" :style="dropdownStyle">
-        <div class="flex items-center justify-between p-12px border-b border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-light)]">
-          <span class="text-14px font-500 text-[var(--el-text-color-primary)]">描述模板</span>
-          <el-button type="text" size="small" @click="showTemplates = false">
-            <Icon icon="ep:close" />
-          </el-button>
-        </div>
-        <div class="max-h-300px overflow-y-auto">
-          <div
-            v-for="template in descriptionTemplates"
-            :key="template.title"
-            class="p-12px border-b border-[var(--el-border-color-lighter)] cursor-pointer transition-colors duration-200 hover:bg-[var(--el-fill-color-light)] last:border-b-0"
-            @click="applyTemplate(template)"
-          >
-            <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-4px">{{ template.title }}</div>
-            <div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">{{ template.content }}</div>
-          </div>
-        </div>
-      </div>
-    </teleport>
-
-    <!-- TODO @puhui999:不用模版哈,简单点。。。 -->
-    <!-- 模板按钮 -->
-    <div v-if="!localValue && !showTemplates" class="absolute top-2px right-2px">
-      <el-button type="text" size="small" @click="toggleTemplates">
-        <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 templateDropdownRef = ref()
-const inputRef = ref()
-const dropdownStyle = ref({})
-
-// 描述模板
-const descriptionTemplates = [
-  {
-    title: '温度控制场景',
-    content: '当环境温度超过设定阈值时,自动启动空调降温设备,确保环境温度保持在舒适范围内。'
-  },
-  {
-    title: '设备监控场景',
-    content: '实时监控关键设备的运行状态,当设备出现异常或离线时,立即发送告警通知相关人员。'
-  },
-  {
-    title: '节能控制场景',
-    content: '根据时间段和环境条件,自动调节设备功率或关闭非必要设备,实现智能节能管理。'
-  },
-  {
-    title: '安防联动场景',
-    content: '当检测到异常情况时,自动触发安防设备联动,包括报警器、摄像头录制等安全措施。'
-  },
-  {
-    title: '定时任务场景',
-    content: '按照预设的时间计划,定期执行设备检查、数据备份或系统维护等自动化任务。'
-  }
-]
-
-// 计算下拉框位置
-const calculateDropdownPosition = () => {
-  if (!inputRef.value) return
-
-  const inputElement = inputRef.value.$el || inputRef.value
-  const rect = inputElement.getBoundingClientRect()
-  const viewportHeight = window.innerHeight
-  const dropdownHeight = 300 // 预估下拉框高度
-
-  let top = rect.bottom + 4
-  let left = rect.left
-
-  // 如果下方空间不够,显示在上方
-  if (top + dropdownHeight > viewportHeight) {
-    top = rect.top - dropdownHeight - 4
-  }
-
-  // 确保不超出左右边界
-  const maxLeft = window.innerWidth - 400 // 下拉框最大宽度
-  if (left > maxLeft) {
-    left = maxLeft
-  }
-  if (left < 10) {
-    left = 10
-  }
-
-  dropdownStyle.value = {
-    top: `${top}px`,
-    left: `${left}px`
-  }
-}
-
-const handleInput = (value: string) => {
-  if (value.length > 0) {
-    showTemplates.value = false
-  }
-}
-
-const applyTemplate = (template: any) => {
-  localValue.value = template.content
-  showTemplates.value = false
-}
-
-const toggleTemplates = () => {
-  showTemplates.value = !showTemplates.value
-  if (showTemplates.value) {
-    nextTick(() => {
-      calculateDropdownPosition()
-    })
-  }
-}
-
-// 点击外部关闭下拉框
-const handleClickOutside = (event: Event) => {
-  if (
-    templateDropdownRef.value &&
-    !templateDropdownRef.value.contains(event.target as Node) &&
-    inputRef.value &&
-    !inputRef.value.$el.contains(event.target as Node)
-  ) {
-    showTemplates.value = false
-  }
-}
-
-// 监听窗口大小变化和点击事件
-onMounted(() => {
-  window.addEventListener('resize', calculateDropdownPosition)
-  window.addEventListener('scroll', calculateDropdownPosition)
-  document.addEventListener('click', handleClickOutside)
-})
-
-onUnmounted(() => {
-  window.removeEventListener('resize', calculateDropdownPosition)
-  window.removeEventListener('scroll', calculateDropdownPosition)
-  document.removeEventListener('click', handleClickOutside)
-})
-</script>
-
-

+ 0 - 111
src/views/iot/rule/scene/form/inputs/NameInput.vue

@@ -1,111 +0,0 @@
-<!-- 场景名称输入组件 -->
-<template>
-  <div class="relative w-full">
-    <el-input
-      v-model="localValue"
-      placeholder="请输入场景名称"
-      maxlength="50"
-      show-word-limit
-      clearable
-      @blur="handleBlur"
-      @input="handleInput"
-    >
-      <template #prefix>
-        <Icon icon="ep:edit" class="text-[var(--el-text-color-placeholder)]" />
-      </template>
-    </el-input>
-
-    <!-- 智能提示 -->
-    <!-- TODO @puhui999:暂时不用考虑智能推荐哈。用途不大 -->
-    <div v-if="showSuggestions && suggestions.length > 0" class="absolute top-full left-0 right-0 z-1000 bg-white border border-[var(--el-border-color-light)] rounded-4px shadow-[var(--el-box-shadow-light)] mt-4px">
-      <div class="p-8px px-12px border-b border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-light)]">
-        <span class="text-12px text-[var(--el-text-color-secondary)] font-500">推荐名称</span>
-      </div>
-      <div class="max-h-200px overflow-y-auto">
-        <div
-          v-for="suggestion in suggestions"
-          :key="suggestion"
-          class="p-8px px-12px cursor-pointer transition-colors duration-200 text-14px text-[var(--el-text-color-primary)] hover:bg-[var(--el-fill-color-light)] last:border-b-0"
-          @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>
-
-

+ 0 - 158
src/views/iot/rule/scene/form/inputs/StatusRadio.vue

@@ -1,158 +0,0 @@
-<!-- 场景状态选择组件 -->
-<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>
-</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: row;
-  gap: 16px;
-  width: 100%;
-  align-items: flex-start;
-}
-
-:deep(.el-radio) {
-  margin-right: 0;
-  width: auto;
-  flex: 1;
-  height: auto;
-  align-items: flex-start;
-}
-
-.status-option {
-  width: auto;
-  flex: 1;
-}
-
-: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: 100%;
-  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;
-}
-
-
-</style>

+ 27 - 11
src/views/iot/rule/scene/form/sections/BasicInfoSection.vue

@@ -16,23 +16,42 @@
     </template>
 
     <div class="p-0">
-      <el-row :gutter="24">
-        <!-- TODO @puhui999:NameInput、StatusRadio、DescriptionInput 是不是直接写在当前界面哈。有点散; -->
+      <el-row :gutter="24" class="mb-24px">
         <el-col :span="12">
           <el-form-item label="场景名称" prop="name" required>
-            <NameInput v-model="formData.name" />
+            <el-input
+              v-model="formData.name"
+              placeholder="请输入场景名称"
+              maxlength="50"
+              show-word-limit
+              clearable
+            />
           </el-form-item>
         </el-col>
-        <!-- TODO @puhui999:每个一行会好点? -->
         <el-col :span="12">
           <el-form-item label="场景状态" prop="status" required>
-            <StatusRadio v-model="formData.status" />
+            <el-radio-group v-model="formData.status">
+              <el-radio
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.value"
+              >
+                {{ dict.label }}
+              </el-radio>
+            </el-radio-group>
           </el-form-item>
         </el-col>
       </el-row>
-
       <el-form-item label="场景描述" prop="description">
-        <DescriptionInput v-model="formData.description" />
+        <el-input
+          v-model="formData.description"
+          type="textarea"
+          placeholder="请输入场景描述(可选)"
+          :rows="3"
+          maxlength="200"
+          show-word-limit
+          resize="none"
+        />
       </el-form-item>
     </div>
   </el-card>
@@ -40,9 +59,7 @@
 
 <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 { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
 
 /** 基础信息配置组件 */
@@ -61,7 +78,6 @@ const props = defineProps<Props>()
 const emit = defineEmits<Emits>()
 
 const formData = useVModel(props, 'modelValue', emit)
-// TODO @puhui999:看看能不能 unocss
 </script>
 
 <style scoped>

+ 47 - 99
src/views/iot/rule/scene/form/sections/TriggerSection.vue

@@ -6,15 +6,13 @@
         <div class="flex items-center gap-8px">
           <Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" />
           <span class="text-16px font-600 text-[var(--el-text-color-primary)]">触发器配置</span>
-          <!-- TODO @puhui999:是不是去掉 maxTriggers;计数 -->
-          <el-tag size="small" type="info">{{ triggers.length }}/{{ maxTriggers }}</el-tag>
+          <el-tag size="small" type="info">{{ triggers.length }}个触发器</el-tag>
         </div>
         <div class="flex items-center gap-8px">
           <el-button
             type="primary"
             size="small"
             @click="addTrigger"
-            :disabled="triggers.length >= maxTriggers"
           >
             <Icon icon="ep:plus" />
             添加触发器
@@ -26,13 +24,7 @@
     <div class="p-0">
       <!-- 空状态 -->
       <div v-if="triggers.length === 0">
-        <el-empty description="暂无触发器配置">
-          <!-- TODO @puhui999:这个要不要去掉哈;入口统一点 -->
-          <el-button type="primary" @click="addTrigger">
-            <Icon icon="ep:plus" />
-            添加第一个触发器
-          </el-button>
-        </el-empty>
+        <el-empty description="暂无触发器配置,请点击右上角添加触发器按钮开始配置" />
       </div>
 
       <!-- 触发器列表 -->
@@ -62,18 +54,28 @@
 
           <div class="space-y-16px">
             <!-- 触发类型选择 -->
-            <TriggerTypeSelector
-              :model-value="trigger.type"
-              @update:model-value="(value) => updateTriggerType(index, value)"
-              @change="onTriggerTypeChange(trigger, $event)"
-            />
+            <el-form-item label="触发类型" required>
+              <el-select
+                :model-value="trigger.type"
+                @update:model-value="(value) => updateTriggerType(index, value)"
+                @change="onTriggerTypeChange(trigger, $event)"
+                placeholder="请选择触发类型"
+                class="w-full"
+              >
+                <el-option
+                  v-for="option in triggerTypeOptions"
+                  :key="option.value"
+                  :label="option.label"
+                  :value="option.value"
+                />
+              </el-select>
+            </el-form-item>
 
             <!-- 设备触发配置 -->
             <DeviceTriggerConfig
               v-if="isDeviceTrigger(trigger.type)"
               :model-value="trigger"
               @update:model-value="(value) => updateTrigger(index, value)"
-              @validate="(result) => handleTriggerValidate(index, result)"
             />
 
             <!-- 定时触发配置 -->
@@ -81,38 +83,16 @@
               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>
-
-      <!-- 添加提示 -->
-      <!-- TODO @puhui999:这个要不要去掉哈;入口统一点 -->
-      <div v-if="triggers.length > 0 && triggers.length < maxTriggers" class="text-center py-16px">
-        <el-button type="primary" plain @click="addTrigger">
-          <Icon icon="ep:plus" />
-          继续添加触发器
-        </el-button>
-        <span class="block mt-8px text-12px text-[var(--el-text-color-secondary)]"> 最多可添加 {{ 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 {
@@ -129,7 +109,6 @@ interface Props {
 
 interface Emits {
   (e: 'update:triggers', value: TriggerFormData[]): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
 }
 
 const props = defineProps<Props>()
@@ -153,13 +132,33 @@ const createDefaultTriggerData = (): TriggerFormData => {
   }
 }
 
-// 配置常量
-const maxTriggers = 5
 
-// 验证状态
-const triggerValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
-const validationMessage = ref('')
-const isValid = ref(true)
+
+
+
+// 触发器类型选项
+const triggerTypeOptions = [
+  {
+    value: TriggerTypeEnum.DEVICE_STATE_UPDATE,
+    label: '设备状态变更'
+  },
+  {
+    value: TriggerTypeEnum.DEVICE_PROPERTY_POST,
+    label: '设备属性上报'
+  },
+  {
+    value: TriggerTypeEnum.DEVICE_EVENT_POST,
+    label: '设备事件上报'
+  },
+  {
+    value: TriggerTypeEnum.DEVICE_SERVICE_INVOKE,
+    label: '设备服务调用'
+  },
+  {
+    value: TriggerTypeEnum.TIMER,
+    label: '定时触发'
+  }
+]
 
 // 触发器类型映射
 const triggerTypeNames = {
@@ -180,12 +179,13 @@ const triggerTypeTags = {
 
 // 工具函数
 const isDeviceTrigger = (type: number) => {
-  return [
+  const deviceTriggerTypes = [
     TriggerTypeEnum.DEVICE_STATE_UPDATE,
     TriggerTypeEnum.DEVICE_PROPERTY_POST,
     TriggerTypeEnum.DEVICE_EVENT_POST,
     TriggerTypeEnum.DEVICE_SERVICE_INVOKE
-  ].includes(type)
+  ] as number[]
+  return deviceTriggerTypes.includes(type)
 }
 
 const getTriggerTypeName = (type: number) => {
@@ -198,31 +198,12 @@ const getTriggerTypeTag = (type: number) => {
 
 // 事件处理
 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) => {
@@ -259,39 +240,6 @@ const onTriggerTypeChange = (trigger: TriggerFormData, type: number) => {
     }
   }
 }
-
-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>
 
 

+ 0 - 142
src/views/iot/rule/scene/form/selectors/TriggerTypeSelector.vue

@@ -1,142 +0,0 @@
-<!-- 触发器类型选择组件 -->
-<template>
-  <div class="w-full">
-    <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="flex items-center justify-between w-full py-4px">
-            <div class="flex items-center gap-12px flex-1">
-              <!-- TODO @puhui999:貌似没对齐? -->
-              <Icon :icon="option.icon" class="text-18px text-[var(--el-color-primary)] flex-shrink-0" />
-              <div class="flex-1">
-                <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">{{ option.label }}</div>
-                <div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">{{ option.description }}</div>
-              </div>
-            </div>
-            <!-- TODO @puhui999:这个要不去掉? -->
-            <el-tag :type="option.tag" size="small">
-              {{ option.category }}
-            </el-tag>
-          </div>
-        </el-option>
-      </el-select>
-    </el-form-item>
-
-    <!-- 类型说明 -->
-    <!-- TODO @puhui999:这个去掉。感觉没啥内容哈; -->
-    <div v-if="selectedOption" class="mt-16px p-16px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]">
-      <div class="flex items-center gap-8px mb-12px">
-        <Icon :icon="selectedOption.icon" class="text-20px text-[var(--el-color-primary)]" />
-        <span class="text-16px font-600 text-[var(--el-text-color-primary)]">{{ selectedOption.label }}</span>
-      </div>
-      <div class="ml-28px">
-        <p class="text-14px text-[var(--el-text-color-regular)] m-0 mb-12px leading-relaxed">{{ selectedOption.description }}</p>
-        <div class="flex flex-col gap-6px">
-          <div v-for="feature in selectedOption.features" :key="feature" class="flex items-center gap-6px">
-            <Icon icon="ep:check" class="text-12px text-[var(--el-color-success)] flex-shrink-0" />
-            <span class="text-12px text-[var(--el-text-color-secondary)]">{{ 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>
-/** TODO @puhui999:unocss 哈 - 已完成转换 */
-:deep(.el-select-dropdown__item) {
-  height: auto;
-  padding: 8px 20px;
-}
-</style>