Explorar o código

!808 perf: 【IoT 物联网】场景联动优化
Merge pull request !808 from puhui999/feature/iot

芋道源码 hai 9 meses
pai
achega
c83a2dff93
Modificáronse 33 ficheiros con 2211 adicións e 2465 borrados
  1. 0 12
      src/api/iot/device/device/index.ts
  2. 42 1
      src/api/iot/rule/scene/index.ts
  3. 0 202
      src/api/iot/rule/scene/scene.types.ts
  4. 103 2
      src/api/iot/thingmodel/index.ts
  5. 491 0
      src/utils/cron.ts
  6. 0 1
      src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue
  7. 37 20
      src/views/iot/rule/scene/form/RuleSceneForm.vue
  8. 135 144
      src/views/iot/rule/scene/form/configs/ConditionConfig.vue
  9. 0 233
      src/views/iot/rule/scene/form/configs/ConditionGroupContainerConfig.vue
  10. 64 65
      src/views/iot/rule/scene/form/configs/CurrentTimeConditionConfig.vue
  11. 54 36
      src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue
  12. 0 207
      src/views/iot/rule/scene/form/configs/DeviceStatusConditionConfig.vue
  13. 213 39
      src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue
  14. 0 66
      src/views/iot/rule/scene/form/configs/MainConditionConfig.vue
  15. 95 158
      src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue
  16. 19 61
      src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue
  17. 0 39
      src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue
  18. 172 159
      src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue
  19. 66 129
      src/views/iot/rule/scene/form/inputs/ValueInput.vue
  20. 56 35
      src/views/iot/rule/scene/form/sections/ActionSection.vue
  21. 3 2
      src/views/iot/rule/scene/form/sections/BasicInfoSection.vue
  22. 59 31
      src/views/iot/rule/scene/form/sections/TriggerSection.vue
  23. 0 113
      src/views/iot/rule/scene/form/selectors/ActionTypeSelector.vue
  24. 0 77
      src/views/iot/rule/scene/form/selectors/ConditionTypeSelector.vue
  25. 20 33
      src/views/iot/rule/scene/form/selectors/DeviceSelector.vue
  26. 84 15
      src/views/iot/rule/scene/form/selectors/OperatorSelector.vue
  27. 0 317
      src/views/iot/rule/scene/form/selectors/ProductDeviceSelector.vue
  28. 9 5
      src/views/iot/rule/scene/form/selectors/ProductSelector.vue
  29. 80 114
      src/views/iot/rule/scene/form/selectors/PropertySelector.vue
  30. 94 122
      src/views/iot/rule/scene/index.vue
  31. 2 2
      src/views/iot/thingmodel/dataSpecs/ThingModelEnumDataSpecs.vue
  32. 2 2
      src/views/iot/thingmodel/dataSpecs/ThingModelNumberDataSpecs.vue
  33. 311 23
      src/views/iot/utils/constants.ts

+ 0 - 12
src/api/iot/device/device/index.ts

@@ -148,18 +148,6 @@ export const DeviceApi = {
     return await request.get({ url: `/iot/device/get-auth-info`, params: { id } })
   },
 
-  // 根据 ProductKey 和 DeviceNames 获取设备列表
-  // TODO @puhui999:有没可能搞成基于 id 的查询哈?
-  getDevicesByProductKeyAndNames: async (productKey: string, deviceNames: string[]) => {
-    return await request.get({
-      url: `/iot/device/list-by-product-key-and-names`,
-      params: {
-        productKey,
-        deviceNames: deviceNames.join(',')
-      }
-    })
-  },
-
   // 查询设备消息分页
   getDeviceMessagePage: async (params: any) => {
     return await request.get({ url: `/iot/device/message/page`, params })

+ 42 - 1
src/api/iot/rule/scene/index.ts

@@ -1,5 +1,46 @@
 import request from '@/config/axios'
-import { IotSceneRule } from './scene.types'
+
+// 场景联动
+export interface IotSceneRule {
+  id?: number // 场景编号
+  name: string // 场景名称
+  description?: string // 场景描述
+  status: number // 场景状态:0-开启,1-关闭
+  triggers: Trigger[] // 触发器数组
+  actions: Action[] // 执行器数组
+}
+
+// 触发器结构
+export interface Trigger {
+  type: number // 触发类型
+  productId?: number // 产品编号
+  deviceId?: number // 设备编号
+  identifier?: string // 物模型标识符
+  operator?: string // 操作符
+  value?: string // 参数值
+  cronExpression?: string // CRON 表达式
+  conditionGroups?: TriggerCondition[][] // 条件组(二维数组)
+}
+
+// 触发条件结构
+export interface TriggerCondition {
+  type: number // 条件类型:1-设备状态,2-设备属性,3-当前时间
+  productId?: number // 产品编号
+  deviceId?: number // 设备编号
+  identifier?: string // 标识符
+  operator: string // 操作符
+  param: string // 参数
+}
+
+// 执行器结构
+export interface Action {
+  type: number // 执行类型
+  productId?: number // 产品编号
+  deviceId?: number // 设备编号
+  identifier?: string // 物模型标识符(服务调用时使用)
+  params?: string // 请求参数
+  alertConfigId?: number // 告警配置编号
+}
 
 // IoT 场景联动 API
 export const RuleSceneApi = {

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

@@ -1,202 +0,0 @@
-/**
- * IoT 场景联动接口定义
- */
-
-// ========== IoT物模型TSL数据类型定义 ==========
-
-// TODO @puhui999:看看有些是不是在别的模块已经定义了。物模型的
-
-/** 物模型TSL响应数据结构 */
-export interface IotThingModelTSLRespVO {
-  productId: number
-  productKey: string
-  properties: ThingModelProperty[]
-  events: ThingModelEvent[]
-  services: ThingModelService[]
-}
-
-/** 物模型属性 */
-export interface ThingModelProperty {
-  identifier: string
-  name: string
-  accessMode: string
-  required?: boolean
-  dataType: string
-  description?: string
-  dataSpecs?: ThingModelDataSpecs
-  dataSpecsList?: ThingModelDataSpecs[]
-}
-
-/** 物模型事件 */
-export interface ThingModelEvent {
-  identifier: string
-  name: string
-  required?: boolean
-  type: string
-  description?: string
-  outputParams?: ThingModelParam[]
-  method?: string
-}
-
-/** 物模型服务 */
-export interface ThingModelService {
-  identifier: string
-  name: string
-  required?: boolean
-  callType: string
-  description?: string
-  inputParams?: ThingModelParam[]
-  outputParams?: ThingModelParam[]
-  method?: string
-}
-
-/** 物模型参数 */
-export interface ThingModelParam {
-  identifier: string
-  name: string
-  direction: string
-  paraOrder?: number
-  dataType: string
-  dataSpecs?: ThingModelDataSpecs
-  dataSpecsList?: ThingModelDataSpecs[]
-}
-
-/** 数值型数据规范 */
-export interface ThingModelNumericDataSpec {
-  dataType: 'int' | 'float' | 'double'
-  max: string
-  min: string
-  step: string
-  precise?: string
-  defaultValue?: string
-  unit?: string
-  unitName?: string
-}
-
-/** 布尔/枚举型数据规范 */
-export interface ThingModelBoolOrEnumDataSpecs {
-  dataType: 'bool' | 'enum'
-  name: string
-  value: number
-}
-
-/** 文本/时间型数据规范 */
-export interface ThingModelDateOrTextDataSpecs {
-  dataType: 'text' | 'date'
-  length?: number
-  defaultValue?: string
-}
-
-/** 数组型数据规范 */
-export interface ThingModelArrayDataSpecs {
-  dataType: 'array'
-  size: number
-  childDataType: string
-  dataSpecsList?: ThingModelDataSpecs[]
-}
-
-/** 结构体型数据规范 */
-export interface ThingModelStructDataSpecs {
-  dataType: 'struct'
-  identifier: string
-  name: string
-  accessMode: string
-  required?: boolean
-  childDataType: string
-  dataSpecs?: ThingModelDataSpecs
-  dataSpecsList?: ThingModelDataSpecs[]
-}
-
-/** 数据规范联合类型 */
-export type ThingModelDataSpecs =
-  | ThingModelNumericDataSpec
-  | ThingModelBoolOrEnumDataSpecs
-  | ThingModelDateOrTextDataSpecs
-  | ThingModelArrayDataSpecs
-  | ThingModelStructDataSpecs
-
-/** 属性选择器内部使用的统一数据结构 */
-export interface PropertySelectorItem {
-  identifier: string
-  name: string
-  description?: string
-  dataType: string
-  type: number // IoTThingModelTypeEnum
-  accessMode?: string
-  required?: boolean
-  unit?: string
-  range?: string
-  eventType?: string
-  callType?: string
-  inputParams?: ThingModelParam[]
-  outputParams?: ThingModelParam[]
-  property?: ThingModelProperty
-  event?: ThingModelEvent
-  service?: ThingModelService
-}
-
-// ========== 场景联动规则相关接口定义 ==========
-
-// 后端 DO 接口 - 匹配后端数据结构
-interface IotSceneRule {
-  id?: number // 场景编号
-  name: string // 场景名称
-  description?: string // 场景描述
-  status: number // 场景状态:0-开启,1-关闭
-  triggers: Trigger[] // 触发器数组
-  actions: Action[] // 执行器数组
-}
-
-// 触发器 DO 结构
-interface Trigger {
-  type: number // 触发类型
-  productId?: number // 产品编号
-  deviceId?: number // 设备编号
-  identifier?: string // 物模型标识符
-  operator?: string // 操作符
-  value?: string // 参数值
-  cronExpression?: string // CRON 表达式
-  conditionGroups?: TriggerCondition[][] // 条件组(二维数组)
-}
-
-// 触发条件 DO 结构
-interface TriggerCondition {
-  type: number // 条件类型:1-设备状态,2-设备属性,3-当前时间
-  productId?: number // 产品编号
-  deviceId?: number // 设备编号
-  identifier?: string // 标识符
-  operator: string // 操作符
-  param: string // 参数
-}
-
-// 执行器 DO 结构
-interface Action {
-  type: number // 执行类型
-  productId?: number // 产品编号
-  deviceId?: number // 设备编号
-  identifier?: string // 物模型标识符(服务调用时使用)
-  params?: string // 请求参数
-  alertConfigId?: number // 告警配置编号
-}
-
-// 表单验证规则类型
-interface ValidationRule {
-  required?: boolean
-  message?: string
-  trigger?: string | string[]
-  type?: string
-  min?: number
-  max?: number
-  enum?: any[]
-}
-
-interface FormValidationRules {
-  [key: string]: ValidationRule[]
-}
-
-// 表单数据类型别名
-export type TriggerFormData = Trigger
-
-// TODO @puhui999:这个文件,目标最终没有哈,和别的模块一致;
-
-export { IotSceneRule, Trigger, TriggerCondition, Action, ValidationRule, FormValidationRules }

+ 103 - 2
src/api/iot/thingmodel/index.ts

@@ -40,7 +40,7 @@ export interface ThingModelService {
 }
 
 /** dataSpecs 数值型数据结构 */
-export interface DataSpecsNumberDataVO {
+export interface DataSpecsNumberData {
   dataType: 'int' | 'float' | 'double' // 数据类型,取值为 INT、FLOAT 或 DOUBLE
   max: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型
   min: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型
@@ -52,13 +52,114 @@ export interface DataSpecsNumberDataVO {
 }
 
 /** dataSpecs 枚举型数据结构 */
-export interface DataSpecsEnumOrBoolDataVO {
+export interface DataSpecsEnumOrBoolData {
   dataType: 'enum' | 'bool'
   defaultValue?: string // 默认值,可选
   name: string // 枚举项的名称
   value: number | undefined // 枚举值
 }
 
+/** 物模型TSL响应数据结构 */
+export interface IotThingModelTSLResp {
+  productId: number
+  productKey: string
+  properties: ThingModelProperty[]
+  events: ThingModelEvent[]
+  services: ThingModelService[]
+}
+
+/** 物模型属性 */
+export interface ThingModelProperty {
+  identifier: string
+  name: string
+  accessMode: string
+  required?: boolean
+  dataType: string
+  description?: string
+  dataSpecs?: ThingModelProperty
+  dataSpecsList?: ThingModelProperty[]
+}
+
+/** 物模型事件 */
+export interface ThingModelEvent {
+  identifier: string
+  name: string
+  required?: boolean
+  type: string
+  description?: string
+  outputParams?: ThingModelParam[]
+  method?: string
+}
+
+/** 物模型服务 */
+export interface ThingModelService {
+  identifier: string
+  name: string
+  required?: boolean
+  callType: string
+  description?: string
+  inputParams?: ThingModelParam[]
+  outputParams?: ThingModelParam[]
+  method?: string
+}
+
+/** 物模型参数 */
+export interface ThingModelParam {
+  identifier: string
+  name: string
+  direction: string
+  paraOrder?: number
+  dataType: string
+  dataSpecs?: ThingModelProperty
+  dataSpecsList?: ThingModelProperty[]
+}
+
+/** 数值型数据规范 */
+export interface ThingModelNumericDataSpec {
+  dataType: 'int' | 'float' | 'double'
+  max: string
+  min: string
+  step: string
+  precise?: string
+  defaultValue?: string
+  unit?: string
+  unitName?: string
+}
+
+/** 布尔/枚举型数据规范 */
+export interface ThingModelBoolOrEnumDataSpecs {
+  dataType: 'bool' | 'enum'
+  name: string
+  value: number
+}
+
+/** 文本/时间型数据规范 */
+export interface ThingModelDateOrTextDataSpecs {
+  dataType: 'text' | 'date'
+  length?: number
+  defaultValue?: string
+}
+
+/** 数组型数据规范 */
+export interface ThingModelArrayDataSpecs {
+  dataType: 'array'
+  size: number
+  childDataType: string
+  dataSpecsList?: ThingModelProperty[]
+}
+
+/** 结构体型数据规范 */
+export interface ThingModelStructDataSpecs {
+  dataType: 'struct'
+  identifier: string
+  name: string
+  accessMode: string
+  required?: boolean
+  childDataType: string
+  dataSpecs?: ThingModelProperty
+  dataSpecsList?: ThingModelProperty[]
+}
+
 // IoT 产品物模型 API
 export const ThingModelApi = {
   // 查询产品物模型分页

+ 491 - 0
src/utils/cron.ts

@@ -0,0 +1,491 @@
+/**
+ * CRON 表达式工具类
+ * 提供 CRON 表达式的解析、格式化、验证等功能
+ */
+
+/** CRON 字段类型枚举 */
+export enum CronFieldType {
+  SECOND = 'second',
+  MINUTE = 'minute',
+  HOUR = 'hour',
+  DAY = 'day',
+  MONTH = 'month',
+  WEEK = 'week',
+  YEAR = 'year'
+}
+
+/** CRON 字段配置 */
+export interface CronFieldConfig {
+  key: CronFieldType
+  label: string
+  min: number
+  max: number
+  names?: Record<string, number> // 名称映射,如月份名称
+}
+
+/** CRON 字段配置常量 */
+export const CRON_FIELD_CONFIGS: Record<CronFieldType, CronFieldConfig> = {
+  [CronFieldType.SECOND]: { key: CronFieldType.SECOND, label: '秒', min: 0, max: 59 },
+  [CronFieldType.MINUTE]: { key: CronFieldType.MINUTE, label: '分', min: 0, max: 59 },
+  [CronFieldType.HOUR]: { key: CronFieldType.HOUR, label: '时', min: 0, max: 23 },
+  [CronFieldType.DAY]: { key: CronFieldType.DAY, label: '日', min: 1, max: 31 },
+  [CronFieldType.MONTH]: {
+    key: CronFieldType.MONTH,
+    label: '月',
+    min: 1,
+    max: 12,
+    names: {
+      JAN: 1,
+      FEB: 2,
+      MAR: 3,
+      APR: 4,
+      MAY: 5,
+      JUN: 6,
+      JUL: 7,
+      AUG: 8,
+      SEP: 9,
+      OCT: 10,
+      NOV: 11,
+      DEC: 12
+    }
+  },
+  [CronFieldType.WEEK]: {
+    key: CronFieldType.WEEK,
+    label: '周',
+    min: 0,
+    max: 7,
+    names: {
+      SUN: 0,
+      MON: 1,
+      TUE: 2,
+      WED: 3,
+      THU: 4,
+      FRI: 5,
+      SAT: 6
+    }
+  },
+  [CronFieldType.YEAR]: { key: CronFieldType.YEAR, label: '年', min: 1970, max: 2099 }
+}
+
+/** 解析后的 CRON 字段 */
+export interface ParsedCronField {
+  type: 'any' | 'specific' | 'range' | 'step' | 'list' | 'last' | 'weekday' | 'nth'
+  values: number[]
+  original: string
+  description: string
+}
+
+/** 解析后的 CRON 表达式 */
+export interface ParsedCronExpression {
+  second: ParsedCronField
+  minute: ParsedCronField
+  hour: ParsedCronField
+  day: ParsedCronField
+  month: ParsedCronField
+  week: ParsedCronField
+  year?: ParsedCronField
+  isValid: boolean
+  description: string
+  nextExecutionTime?: Date
+}
+
+/** 常用 CRON 表达式预设 */
+export const CRON_PRESETS = {
+  EVERY_SECOND: '* * * * * ?',
+  EVERY_MINUTE: '0 * * * * ?',
+  EVERY_HOUR: '0 0 * * * ?',
+  EVERY_DAY: '0 0 0 * * ?',
+  EVERY_WEEK: '0 0 0 ? * 1',
+  EVERY_MONTH: '0 0 0 1 * ?',
+  EVERY_YEAR: '0 0 0 1 1 ?',
+  WORKDAY_9AM: '0 0 9 ? * 2-6', // 工作日上午9点
+  WORKDAY_6PM: '0 0 18 ? * 2-6', // 工作日下午6点
+  WEEKEND_10AM: '0 0 10 ? * 1,7' // 周末上午10点
+} as const
+
+/** CRON 表达式工具类 */
+export class CronUtils {
+  /**
+   * 验证 CRON 表达式格式
+   */
+  static validate(cronExpression: string): boolean {
+    if (!cronExpression || typeof cronExpression !== 'string') {
+      return false
+    }
+
+    const parts = cronExpression.trim().split(/\s+/)
+
+    // 支持 5-7 个字段的 CRON 表达式
+    if (parts.length < 5 || parts.length > 7) {
+      return false
+    }
+
+    // 基本格式验证
+    const cronRegex = /^[0-9*\/\-,?LW#]+$/
+    return parts.every((part) => cronRegex.test(part))
+  }
+
+  /**
+   * 解析单个 CRON 字段
+   */
+  static parseField(
+    fieldValue: string,
+    fieldType: CronFieldType,
+    config: CronFieldConfig
+  ): ParsedCronField {
+    const field: ParsedCronField = {
+      type: 'any',
+      values: [],
+      original: fieldValue,
+      description: ''
+    }
+
+    // 处理特殊字符
+    if (fieldValue === '*' || fieldValue === '?') {
+      field.type = 'any'
+      field.description = `每${config.label}`
+      return field
+    }
+
+    // 处理最后一天 (L)
+    if (fieldValue === 'L' && fieldType === CronFieldType.DAY) {
+      field.type = 'last'
+      field.description = '每月最后一天'
+      return field
+    }
+
+    // 处理范围 (-)
+    if (fieldValue.includes('-')) {
+      const [start, end] = fieldValue.split('-').map(Number)
+      if (!isNaN(start) && !isNaN(end) && start >= config.min && end <= config.max) {
+        field.type = 'range'
+        field.values = Array.from({ length: end - start + 1 }, (_, i) => start + i)
+        field.description = `${config.label} ${start}-${end}`
+      }
+      return field
+    }
+
+    // 处理步长 (/)
+    if (fieldValue.includes('/')) {
+      const [base, step] = fieldValue.split('/')
+      const stepNum = Number(step)
+      if (!isNaN(stepNum) && stepNum > 0) {
+        field.type = 'step'
+        if (base === '*') {
+          field.description = `每${stepNum}${config.label}`
+        } else {
+          const startNum = Number(base)
+          field.description = `从${startNum}开始每${stepNum}${config.label}`
+        }
+      }
+      return field
+    }
+
+    // 处理列表 (,)
+    if (fieldValue.includes(',')) {
+      const values = fieldValue
+        .split(',')
+        .map(Number)
+        .filter((n) => !isNaN(n))
+      if (values.length > 0) {
+        field.type = 'list'
+        field.values = values
+        field.description = `${config.label} ${values.join(',')}`
+      }
+      return field
+    }
+
+    // 处理具体数值
+    const numValue = Number(fieldValue)
+    if (!isNaN(numValue) && numValue >= config.min && numValue <= config.max) {
+      field.type = 'specific'
+      field.values = [numValue]
+      field.description = `${config.label} ${numValue}`
+    }
+
+    return field
+  }
+
+  /**
+   * 解析完整的 CRON 表达式
+   */
+  static parse(cronExpression: string): ParsedCronExpression {
+    const result: ParsedCronExpression = {
+      second: { type: 'any', values: [], original: '*', description: '每秒' },
+      minute: { type: 'any', values: [], original: '*', description: '每分' },
+      hour: { type: 'any', values: [], original: '*', description: '每时' },
+      day: { type: 'any', values: [], original: '*', description: '每日' },
+      month: { type: 'any', values: [], original: '*', description: '每月' },
+      week: { type: 'any', values: [], original: '?', description: '任意周' },
+      isValid: false,
+      description: ''
+    }
+
+    if (!this.validate(cronExpression)) {
+      result.description = '无效的 CRON 表达式'
+      return result
+    }
+
+    const parts = cronExpression.trim().split(/\s+/)
+    const fieldTypes = [
+      CronFieldType.SECOND,
+      CronFieldType.MINUTE,
+      CronFieldType.HOUR,
+      CronFieldType.DAY,
+      CronFieldType.MONTH,
+      CronFieldType.WEEK
+    ]
+
+    // 如果只有5个字段,则第一个字段是分钟
+    const startIndex = parts.length === 5 ? 1 : 0
+
+    for (let i = 0; i < parts.length; i++) {
+      const fieldType = fieldTypes[i + startIndex]
+      if (fieldType && CRON_FIELD_CONFIGS[fieldType]) {
+        const config = CRON_FIELD_CONFIGS[fieldType]
+        result[fieldType] = this.parseField(parts[i], fieldType, config)
+      }
+    }
+
+    // 处理年份字段(如果存在)
+    if (parts.length === 7) {
+      const yearConfig = CRON_FIELD_CONFIGS[CronFieldType.YEAR]
+      result.year = this.parseField(parts[6], CronFieldType.YEAR, yearConfig)
+    }
+
+    result.isValid = true
+    result.description = this.generateDescription(result)
+
+    return result
+  }
+
+  /**
+   * 生成 CRON 表达式的可读描述
+   */
+  static generateDescription(parsed: ParsedCronExpression): string {
+    const parts: string[] = []
+
+    // 构建时间部分描述
+    if (parsed.hour.type === 'specific' && parsed.minute.type === 'specific') {
+      const hour = parsed.hour.values[0]
+      const minute = parsed.minute.values[0]
+      parts.push(`${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`)
+    } else if (parsed.hour.type === 'specific') {
+      parts.push(`每天${parsed.hour.values[0]}点`)
+    } else if (parsed.minute.type === 'specific' && parsed.minute.values[0] === 0) {
+      if (parsed.hour.type === 'any') {
+        parts.push('每小时整点')
+      }
+    } else if (parsed.minute.type === 'step') {
+      const step = parsed.minute.original.split('/')[1]
+      parts.push(`每${step}分钟`)
+    } else if (parsed.hour.type === 'step') {
+      const step = parsed.hour.original.split('/')[1]
+      parts.push(`每${step}小时`)
+    }
+
+    // 构建日期部分描述
+    if (parsed.day.type === 'specific') {
+      parts.push(`每月${parsed.day.values[0]}日`)
+    } else if (parsed.week.type === 'specific') {
+      const weekNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
+      const weekDay = parsed.week.values[0]
+      if (weekDay >= 0 && weekDay <= 6) {
+        parts.push(`每${weekNames[weekDay]}`)
+      }
+    } else if (parsed.week.type === 'range') {
+      parts.push('工作日')
+    }
+
+    // 构建月份部分描述
+    if (parsed.month.type === 'specific') {
+      parts.push(`${parsed.month.values[0]}月`)
+    }
+
+    return parts.length > 0 ? parts.join(' ') : '自定义时间规则'
+  }
+
+  /**
+   * 格式化 CRON 表达式为可读文本
+   */
+  static format(cronExpression: string): string {
+    if (!cronExpression) return ''
+
+    const parsed = this.parse(cronExpression)
+    return parsed.isValid ? parsed.description : cronExpression
+  }
+
+  /**
+   * 获取预设的 CRON 表达式列表
+   */
+  static getPresets() {
+    return Object.entries(CRON_PRESETS).map(([key, value]) => ({
+      label: this.format(value),
+      value,
+      key
+    }))
+  }
+
+  /**
+   * 计算 CRON 表达式的下次执行时间
+   */
+  static getNextExecutionTime(cronExpression: string, fromDate?: Date): Date | null {
+    const parsed = this.parse(cronExpression)
+    if (!parsed.isValid) {
+      return null
+    }
+
+    const now = fromDate || new Date()
+    // eslint-disable-next-line prefer-const
+    let nextTime = new Date(now.getTime() + 1000) // 从下一秒开始
+
+    // 简化版本:处理常见的 CRON 表达式模式
+    // 对于复杂的 CRON 表达式,建议使用专门的库如 node-cron 或 cron-parser
+
+    // 处理每分钟执行
+    if (parsed.second.type === 'specific' && parsed.minute.type === 'any') {
+      const targetSecond = parsed.second.values[0]
+      nextTime.setSeconds(targetSecond, 0)
+      if (nextTime <= now) {
+        nextTime.setMinutes(nextTime.getMinutes() + 1)
+      }
+      return nextTime
+    }
+
+    // 处理每小时执行
+    if (
+      parsed.second.type === 'specific' &&
+      parsed.minute.type === 'specific' &&
+      parsed.hour.type === 'any'
+    ) {
+      const targetSecond = parsed.second.values[0]
+      const targetMinute = parsed.minute.values[0]
+      nextTime.setMinutes(targetMinute, targetSecond, 0)
+      if (nextTime <= now) {
+        nextTime.setHours(nextTime.getHours() + 1)
+      }
+      return nextTime
+    }
+
+    // 处理每天执行
+    if (
+      parsed.second.type === 'specific' &&
+      parsed.minute.type === 'specific' &&
+      parsed.hour.type === 'specific'
+    ) {
+      const targetSecond = parsed.second.values[0]
+      const targetMinute = parsed.minute.values[0]
+      const targetHour = parsed.hour.values[0]
+
+      nextTime.setHours(targetHour, targetMinute, targetSecond, 0)
+      if (nextTime <= now) {
+        nextTime.setDate(nextTime.getDate() + 1)
+      }
+      return nextTime
+    }
+
+    // 处理步长执行
+    if (parsed.minute.type === 'step') {
+      const step = parseInt(parsed.minute.original.split('/')[1])
+      const currentMinute = nextTime.getMinutes()
+      const nextMinute = Math.ceil(currentMinute / step) * step
+
+      if (nextMinute >= 60) {
+        nextTime.setHours(nextTime.getHours() + 1, 0, 0, 0)
+      } else {
+        nextTime.setMinutes(nextMinute, 0, 0)
+      }
+      return nextTime
+    }
+
+    // 对于其他复杂情况,返回一个估算时间
+    return new Date(now.getTime() + 60000) // 1分钟后
+  }
+
+  /**
+   * 获取 CRON 表达式的执行频率描述
+   */
+  static getFrequencyDescription(cronExpression: string): string {
+    const parsed = this.parse(cronExpression)
+    if (!parsed.isValid) {
+      return '无效表达式'
+    }
+
+    // 计算大概的执行频率
+    if (parsed.second.type === 'any' && parsed.minute.type === 'any') {
+      return '每秒执行'
+    }
+
+    if (parsed.minute.type === 'any' && parsed.hour.type === 'any') {
+      return '每分钟执行'
+    }
+
+    if (parsed.hour.type === 'any' && parsed.day.type === 'any') {
+      return '每小时执行'
+    }
+
+    if (parsed.day.type === 'any' && parsed.month.type === 'any') {
+      return '每天执行'
+    }
+
+    if (parsed.month.type === 'any') {
+      return '每月执行'
+    }
+
+    return '按计划执行'
+  }
+
+  /**
+   * 检查 CRON 表达式是否会在指定时间执行
+   */
+  static willExecuteAt(cronExpression: string, targetDate: Date): boolean {
+    const parsed = this.parse(cronExpression)
+    if (!parsed.isValid) {
+      return false
+    }
+
+    // 检查各个字段是否匹配
+    const second = targetDate.getSeconds()
+    const minute = targetDate.getMinutes()
+    const hour = targetDate.getHours()
+    const day = targetDate.getDate()
+    const month = targetDate.getMonth() + 1
+    const weekDay = targetDate.getDay()
+
+    return (
+      this.fieldMatches(parsed.second, second) &&
+      this.fieldMatches(parsed.minute, minute) &&
+      this.fieldMatches(parsed.hour, hour) &&
+      this.fieldMatches(parsed.day, day) &&
+      this.fieldMatches(parsed.month, month) &&
+      (parsed.week.type === 'any' || this.fieldMatches(parsed.week, weekDay))
+    )
+  }
+
+  /**
+   * 检查字段值是否匹配
+   */
+  private static fieldMatches(field: ParsedCronField, value: number): boolean {
+    if (field.type === 'any') {
+      return true
+    }
+
+    if (field.type === 'specific' || field.type === 'list') {
+      return field.values.includes(value)
+    }
+
+    if (field.type === 'range') {
+      return value >= field.values[0] && value <= field.values[field.values.length - 1]
+    }
+
+    if (field.type === 'step') {
+      const [base, step] = field.original.split('/').map(Number)
+      if (base === 0 || field.original.startsWith('*')) {
+        return value % step === 0
+      }
+      return value >= base && (value - base) % step === 0
+    }
+
+    return false
+  }
+}

+ 0 - 1
src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue

@@ -58,7 +58,6 @@ const updateModelValue = () => {
   emit('update:modelValue', result)
 }
 
-// TODO @puhui999:有告警的地方,尽量用 cursor 处理下
 /** 监听项目变化 */
 watch(items, updateModelValue, { deep: true })
 watch(

+ 37 - 20
src/views/iot/rule/scene/form/RuleSceneForm.vue

@@ -36,7 +36,7 @@ import { useVModel } from '@vueuse/core'
 import BasicInfoSection from './sections/BasicInfoSection.vue'
 import TriggerSection from './sections/TriggerSection.vue'
 import ActionSection from './sections/ActionSection.vue'
-import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
+import { IotSceneRule } from '@/api/iot/rule/scene'
 import { RuleSceneApi } from '@/api/iot/rule/scene'
 import {
   IotRuleSceneTriggerTypeEnum,
@@ -63,9 +63,12 @@ const emit = defineEmits<{
   (e: 'success'): void
 }>()
 
-const drawerVisible = useVModel(props, 'modelValue', emit) // 是否可见
+const drawerVisible = useVModel(props, 'modelValue', emit) // 抽屉显示状态
 
-/** 创建默认的表单数据 */
+/**
+ * 创建默认的表单数据
+ * @returns 默认表单数据对象
+ */
 const createDefaultFormData = (): IotSceneRule => {
   return {
     name: '',
@@ -87,10 +90,15 @@ const createDefaultFormData = (): IotSceneRule => {
   }
 }
 
-// 表单数据和状态
-const formRef = ref()
-const formData = ref<IotSceneRule>(createDefaultFormData())
-// 自定义校验器
+const formRef = ref() // 表单引用
+const formData = ref<IotSceneRule>(createDefaultFormData()) // 表单数据
+
+/**
+ * 触发器校验器
+ * @param _rule 校验规则(未使用)
+ * @param value 校验值
+ * @param callback 回调函数
+ */
 const validateTriggers = (_rule: any, value: any, callback: any) => {
   if (!value || !Array.isArray(value) || value.length === 0) {
     callback(new Error('至少需要一个触发器'))
@@ -142,6 +150,12 @@ const validateTriggers = (_rule: any, value: any, callback: any) => {
   callback()
 }
 
+/**
+ * 执行器校验器
+ * @param _rule 校验规则(未使用)
+ * @param value 校验值
+ * @param callback 回调函数
+ */
 const validateActions = (_rule: any, value: any, callback: any) => {
   if (!value || !Array.isArray(value) || value.length === 0) {
     callback(new Error('至少需要一个执行器'))
@@ -201,6 +215,7 @@ const validateActions = (_rule: any, value: any, callback: any) => {
 }
 
 const formRules = reactive({
+  // 表单校验规则
   name: [
     { required: true, message: '场景名称不能为空', trigger: 'blur' },
     { type: 'string', min: 1, max: 50, message: '场景名称长度应在1-50个字符之间', trigger: 'blur' }
@@ -221,13 +236,15 @@ const formRules = reactive({
   actions: [{ required: true, validator: validateActions, trigger: 'change' }]
 })
 
-const submitLoading = ref(false)
+const submitLoading = ref(false) // 提交加载状态
+const isEdit = ref(false) // 是否为编辑模式
 
-// 计算属性
-const isEdit = ref(false)
+// 计算属性:抽屉标题
 const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则'))
 
-/** 提交表单 */
+/**
+ * 提交表单
+ */
 const handleSubmit = async () => {
   // 校验表单
   if (!formRef.value) return
@@ -237,10 +254,6 @@ const handleSubmit = async () => {
   // 提交请求
   submitLoading.value = true
   try {
-    // 数据结构已对齐,直接使用表单数据
-    console.log('提交数据:', formData.value)
-
-    // 调用API保存数据
     if (isEdit.value) {
       // 更新场景联动规则
       await RuleSceneApi.updateRuleScene(formData.value)
@@ -262,11 +275,16 @@ const handleSubmit = async () => {
   }
 }
 
+/**
+ * 处理抽屉关闭事件
+ */
 const handleClose = () => {
   drawerVisible.value = false
 }
 
-/** 初始化表单数据 */
+/**
+ * 初始化表单数据
+ */
 const initFormData = () => {
   if (props.ruleScene) {
     // 编辑模式:数据结构已对齐,直接使用后端数据
@@ -299,13 +317,12 @@ const initFormData = () => {
 }
 
 // 监听抽屉显示
-watch(drawerVisible, (visible) => {
+watch(drawerVisible, async (visible) => {
   if (visible) {
     initFormData()
     // 重置表单验证状态
-    nextTick(() => {
-      formRef.value?.clearValidate()
-    })
+    await nextTick()
+    formRef.value?.clearValidate()
   }
 })
 

+ 135 - 144
src/views/iot/rule/scene/form/configs/ConditionConfig.vue

@@ -5,54 +5,104 @@
     <el-row :gutter="16">
       <el-col :span="8">
         <el-form-item label="条件类型" required>
-          <ConditionTypeSelector
+          <el-select
             :model-value="condition.type"
             @update:model-value="(value) => updateConditionField('type', value)"
             @change="handleConditionTypeChange"
+            placeholder="请选择条件类型"
+            class="w-full"
+          >
+            <el-option
+              v-for="option in getConditionTypeOptions()"
+              :key="option.value"
+              :label="option.label"
+              :value="option.value"
+            />
+          </el-select>
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <!-- 产品设备选择 - 设备相关条件的公共部分 -->
+    <el-row v-if="isDeviceCondition" :gutter="16">
+      <el-col :span="12">
+        <el-form-item label="产品" required>
+          <ProductSelector
+            :model-value="condition.productId"
+            @update:model-value="(value) => updateConditionField('productId', value)"
+            @change="handleProductChange"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="设备" required>
+          <DeviceSelector
+            :model-value="condition.deviceId"
+            @update:model-value="(value) => updateConditionField('deviceId', value)"
+            :product-id="condition.productId"
+            @change="handleDeviceChange"
           />
         </el-form-item>
       </el-col>
     </el-row>
 
     <!-- 设备状态条件配置 -->
-    <DeviceStatusConditionConfig
-      v-if="condition.type === ConditionTypeEnum.DEVICE_STATUS"
-      :model-value="condition"
-      @update:model-value="updateCondition"
-      @validate="handleValidate"
-    />
-
-    <!-- 设备属性条件配置 -->
-    <div v-else-if="condition.type === ConditionTypeEnum.DEVICE_PROPERTY" class="space-y-16px">
-      <!-- 产品设备选择 -->
+    <div
+      v-if="condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS"
+      class="flex flex-col gap-16px"
+    >
+      <!-- 状态和操作符选择 -->
       <el-row :gutter="16">
+        <!-- 操作符选择 -->
         <el-col :span="12">
-          <el-form-item label="产品" required>
-            <ProductSelector
-              :model-value="condition.productId"
-              @update:model-value="(value) => updateConditionField('productId', value)"
-              @change="handleProductChange"
-            />
+          <el-form-item label="操作符" required>
+            <el-select
+              :model-value="condition.operator"
+              @update:model-value="(value) => updateConditionField('operator', value)"
+              placeholder="请选择操作符"
+              class="w-full"
+            >
+              <el-option
+                v-for="option in getStatusOperatorOptions()"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+              />
+            </el-select>
           </el-form-item>
         </el-col>
+
+        <!-- 状态选择 -->
         <el-col :span="12">
-          <el-form-item label="设备" required>
-            <DeviceSelector
-              :model-value="condition.deviceId"
-              @update:model-value="(value) => updateConditionField('deviceId', value)"
-              :product-id="condition.productId"
-              @change="handleDeviceChange"
-            />
+          <el-form-item label="设备状态" required>
+            <el-select
+              :model-value="condition.param"
+              @update:model-value="(value) => updateConditionField('param', value)"
+              placeholder="请选择设备状态"
+              class="w-full"
+            >
+              <el-option
+                v-for="option in getDeviceStatusOptions()"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+              />
+            </el-select>
           </el-form-item>
         </el-col>
       </el-row>
+    </div>
 
+    <!-- 设备属性条件配置 -->
+    <div
+      v-else-if="condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY"
+      class="space-y-16px"
+    >
       <!-- 属性配置 -->
       <el-row :gutter="16">
         <!-- 属性/事件/服务选择 -->
         <el-col :span="6">
           <el-form-item label="监控项" required>
-            <!-- TODO @puhui999:是不是不展示“整数”、“小数”这个类型,一行,只展示属性名 + 标识,更简洁一点;然后标识是 tag;因为已经有个 ? tip 了 -->
             <PropertySelector
               :model-value="condition.identifier"
               @update:model-value="(value) => updateConditionField('identifier', value)"
@@ -77,7 +127,6 @@
         </el-col>
 
         <!-- 值输入 -->
-        <!-- TODO @puhui999:框子大小占满哈。 -->
         <el-col :span="12">
           <el-form-item label="比较值" required>
             <ValueInput
@@ -86,7 +135,6 @@
               :property-type="propertyType"
               :operator="condition.operator"
               :property-config="propertyConfig"
-              @validate="handleValueValidate"
             />
           </el-form-item>
         </el-col>
@@ -95,38 +143,28 @@
 
     <!-- 当前时间条件配置 -->
     <CurrentTimeConditionConfig
-      v-else-if="condition.type === ConditionTypeEnum.CURRENT_TIME"
+      v-else-if="condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME"
       :model-value="condition"
       @update:model-value="updateCondition"
-      @validate="handleValidate"
     />
-
-    <!-- 验证结果 -->
-    <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 ConditionTypeSelector from '../selectors/ConditionTypeSelector.vue'
-import DeviceStatusConditionConfig from './DeviceStatusConditionConfig.vue'
 import CurrentTimeConditionConfig from './CurrentTimeConditionConfig.vue'
 import ProductSelector from '../selectors/ProductSelector.vue'
 import DeviceSelector from '../selectors/DeviceSelector.vue'
 import PropertySelector from '../selectors/PropertySelector.vue'
 import OperatorSelector from '../selectors/OperatorSelector.vue'
 import ValueInput from '../inputs/ValueInput.vue'
-import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
+import type { TriggerCondition } from '@/api/iot/rule/scene'
 import {
   IotRuleSceneTriggerConditionTypeEnum,
-  IotRuleSceneTriggerConditionParameterOperatorEnum
+  IotRuleSceneTriggerConditionParameterOperatorEnum,
+  getConditionTypeOptions,
+  getDeviceStatusOptions,
+  getStatusOperatorOptions
 } from '@/views/iot/utils/constants'
 
 /** 单个条件配置组件 */
@@ -139,83 +177,92 @@ const props = defineProps<{
 
 const emit = defineEmits<{
   (e: 'update:modelValue', value: TriggerCondition): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
 }>()
 
 const condition = useVModel(props, 'modelValue', emit)
 
-// 常量定义
-const ConditionTypeEnum = IotRuleSceneTriggerConditionTypeEnum
+const propertyType = ref<string>('string') // 属性类型
+const propertyConfig = ref<any>(null) // 属性配置
 
-// 状态
-const propertyType = ref<string>('string')
-const propertyConfig = ref<any>(null)
-const validationMessage = ref('')
-const isValid = ref(true)
-const valueValidation = ref({ valid: true, message: '' })
+// 计算属性:判断是否为设备相关条件
+const isDeviceCondition = computed(() => {
+  return (
+    condition.value.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS ||
+    condition.value.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
+  )
+})
 
-// 事件处理
-const updateConditionField = (field: keyof TriggerCondition, value: any) => {
+/**
+ * 更新条件字段
+ * @param field 字段名
+ * @param value 字段值
+ */
+const updateConditionField = (field: any, value: any) => {
   ;(condition.value as any)[field] = value
   emit('update:modelValue', condition.value)
 }
 
+/**
+ * 更新整个条件对象
+ * @param newCondition 新的条件对象
+ */
 const updateCondition = (newCondition: TriggerCondition) => {
   condition.value = newCondition
   emit('update:modelValue', condition.value)
 }
 
+/**
+ * 处理条件类型变化事件
+ * @param type 条件类型
+ */
 const handleConditionTypeChange = (type: number) => {
-  // 清理不相关的字段
-  if (type === ConditionTypeEnum.DEVICE_STATUS) {
-    condition.value.identifier = undefined
-    // 清理时间相关字段(如果存在)
-    if ('timeValue' in condition.value) {
-      delete (condition.value as any).timeValue
-    }
-    if ('timeValue2' in condition.value) {
-      delete (condition.value as any).timeValue2
-    }
-  } else if (type === ConditionTypeEnum.CURRENT_TIME) {
+  // 根据条件类型清理字段
+  const isCurrentTime = type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
+  const isDeviceStatus = type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
+
+  // 清理标识符字段(时间条件和设备状态条件都不需要)
+  if (isCurrentTime || isDeviceStatus) {
     condition.value.identifier = undefined
+  }
+
+  // 清理设备相关字段(仅时间条件需要)
+  if (isCurrentTime) {
     condition.value.productId = undefined
     condition.value.deviceId = undefined
-  } else if (type === ConditionTypeEnum.DEVICE_PROPERTY) {
-    // 清理时间相关字段(如果存在)
-    if ('timeValue' in condition.value) {
-      delete (condition.value as any).timeValue
-    }
-    if ('timeValue2' in condition.value) {
-      delete (condition.value as any).timeValue2
-    }
   }
 
-  // 重置操作符和参数,使用枚举中的默认值
-  condition.value.operator = IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value
-  condition.value.param = ''
+  // 设置默认操作符
+  condition.value.operator = isCurrentTime
+    ? 'at_time'
+    : IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value
 
-  updateValidationResult()
-}
-
-const handleValidate = (result: { valid: boolean; message: string }) => {
-  isValid.value = result.valid
-  validationMessage.value = result.message
-  emit('validate', result)
+  // 清空参数值
+  condition.value.param = ''
 }
 
+/**
+ * 处理产品变化事件
+ * @param _ 产品ID(未使用)
+ */
 const handleProductChange = (_: number) => {
   // 产品变化时清空设备和属性
   condition.value.deviceId = undefined
   condition.value.identifier = ''
-  updateValidationResult()
 }
 
+/**
+ * 处理设备变化事件
+ * @param _ 设备ID(未使用)
+ */
 const handleDeviceChange = (_: number) => {
   // 设备变化时清空属性
   condition.value.identifier = ''
-  updateValidationResult()
 }
 
+/**
+ * 处理属性变化事件
+ * @param propertyInfo 属性信息对象
+ */
 const handlePropertyChange = (propertyInfo: { type: string; config: any }) => {
   propertyType.value = propertyInfo.type
   propertyConfig.value = propertyInfo.config
@@ -223,71 +270,15 @@ const handlePropertyChange = (propertyInfo: { type: string; config: any }) => {
   // 重置操作符和值
   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>

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

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

+ 64 - 65
src/views/iot/rule/scene/form/configs/CurrentTimeConditionConfig.vue

@@ -29,7 +29,7 @@
                   <Icon :icon="option.icon" :class="option.iconClass" />
                   <span>{{ option.label }}</span>
                 </div>
-                <el-tag :type="option.tag" size="small">{{ option.category }}</el-tag>
+                <el-tag :type="option.tag as any" size="small">{{ option.category }}</el-tag>
               </div>
             </el-option>
           </el-select>
@@ -41,8 +41,8 @@
         <el-form-item label="时间值" required>
           <el-time-picker
             v-if="needsTimeInput"
-            :model-value="condition.timeValue"
-            @update:model-value="(value) => updateConditionField('timeValue', value)"
+            :model-value="timeValue"
+            @update:model-value="handleTimeValueChange"
             placeholder="请选择时间"
             format="HH:mm:ss"
             value-format="HH:mm:ss"
@@ -50,8 +50,8 @@
           />
           <el-date-picker
             v-else-if="needsDateInput"
-            :model-value="condition.timeValue"
-            @update:model-value="(value) => updateConditionField('timeValue', value)"
+            :model-value="timeValue"
+            @update:model-value="handleTimeValueChange"
             type="datetime"
             placeholder="请选择日期时间"
             format="YYYY-MM-DD HH:mm:ss"
@@ -69,8 +69,8 @@
         <el-form-item label="结束时间" required>
           <el-time-picker
             v-if="needsTimeInput"
-            :model-value="condition.timeValue2"
-            @update:model-value="(value) => updateConditionField('timeValue2', value)"
+            :model-value="timeValue2"
+            @update:model-value="handleTimeValue2Change"
             placeholder="请选择结束时间"
             format="HH:mm:ss"
             value-format="HH:mm:ss"
@@ -78,8 +78,8 @@
           />
           <el-date-picker
             v-else
-            :model-value="condition.timeValue2"
-            @update:model-value="(value) => updateConditionField('timeValue2', value)"
+            :model-value="timeValue2"
+            @update:model-value="handleTimeValue2Change"
             type="datetime"
             placeholder="请选择结束日期时间"
             format="YYYY-MM-DD HH:mm:ss"
@@ -94,18 +94,18 @@
 
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
-import { ConditionFormData, IotRuleSceneTriggerTimeOperatorEnum } from '@/views/iot/utils/constants'
+import { IotRuleSceneTriggerTimeOperatorEnum } from '@/views/iot/utils/constants'
+import type { TriggerCondition } from '@/api/iot/rule/scene'
 
 /** 当前时间条件配置组件 */
 defineOptions({ name: 'CurrentTimeConditionConfig' })
 
 const props = defineProps<{
-  modelValue: ConditionFormData
+  modelValue: TriggerCondition
 }>()
 
 const emit = defineEmits<{
-  (e: 'update:modelValue', value: ConditionFormData): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
+  (e: 'update:modelValue', value: TriggerCondition): void
 }>()
 
 const condition = useVModel(props, 'modelValue', emit)
@@ -154,11 +154,7 @@ const timeOperatorOptions = [
   }
 ]
 
-// 状态
-const validationMessage = ref('')
-const isValid = ref(true)
-
-// 计算属性
+// 计算属性:是否需要时间输入
 const needsTimeInput = computed(() => {
   const timeOnlyOperators = [
     IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.value,
@@ -166,76 +162,79 @@ const needsTimeInput = computed(() => {
     IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value,
     IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.value
   ]
-  return timeOnlyOperators.includes(condition.value.operator)
+  return timeOnlyOperators.includes(condition.value.operator as any)
 })
 
+// 计算属性:是否需要日期输入
 const needsDateInput = computed(() => {
   return false // 暂时不支持日期输入,只支持时间
 })
 
+// 计算属性:是否需要第二个时间输入
 const needsSecondTimeInput = computed(() => {
   return condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
 })
 
-// 事件处理
-const updateConditionField = (field: keyof ConditionFormData, value: any) => {
-  condition.value[field] = value
-  updateValidationResult()
-}
-
-const updateValidationResult = () => {
-  if (!condition.value.operator) {
-    isValid.value = false
-    validationMessage.value = '请选择时间条件'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
+// 计算属性:从 param 中解析时间值
+const timeValue = computed(() => {
+  if (!condition.value.param) return ''
+  const params = condition.value.param.split(',')
+  return params[0] || ''
+})
 
-  // 今日条件不需要时间值
-  if (condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
-    isValid.value = true
-    validationMessage.value = '当前时间条件配置验证通过'
-    emit('validate', { valid: true, message: validationMessage.value })
-    return
-  }
+// 计算属性:从 param 中解析第二个时间值
+const timeValue2 = computed(() => {
+  if (!condition.value.param) return ''
+  const params = condition.value.param.split(',')
+  return params[1] || ''
+})
 
-  if (needsTimeInput.value && !condition.value.timeValue) {
-    isValid.value = false
-    validationMessage.value = '请设置时间值'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
+/**
+ * 更新条件字段
+ * @param field 字段名
+ * @param value 字段值
+ */
+const updateConditionField = (field: any, value: any) => {
+  condition.value[field] = value
+}
 
-  if (needsSecondTimeInput.value && !condition.value.timeValue2) {
-    isValid.value = false
-    validationMessage.value = '请设置结束时间'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
+/**
+ * 处理第一个时间值变化
+ * @param value 时间值
+ */
+const handleTimeValueChange = (value: string) => {
+  const currentParams = condition.value.param ? condition.value.param.split(',') : []
+  currentParams[0] = value || ''
+
+  // 如果是范围条件,保留第二个值;否则只保留第一个值
+  if (needsSecondTimeInput.value) {
+    condition.value.param = currentParams.slice(0, 2).join(',')
+  } else {
+    condition.value.param = currentParams[0]
   }
-
-  isValid.value = true
-  validationMessage.value = '当前时间条件配置验证通过'
-  emit('validate', { valid: true, message: validationMessage.value })
 }
 
-// 监听变化
-watch(
-  () => [condition.value.operator, condition.value.timeValue, condition.value.timeValue2],
-  () => {
-    updateValidationResult()
-  },
-  { immediate: true }
-)
+/**
+ * 处理第二个时间值变化
+ * @param value 时间值
+ */
+const handleTimeValue2Change = (value: string) => {
+  const currentParams = condition.value.param ? condition.value.param.split(',') : ['']
+  currentParams[1] = value || ''
+  condition.value.param = currentParams.slice(0, 2).join(',')
+}
 
 // 监听操作符变化,清理不相关的时间值
 watch(
   () => condition.value.operator,
   (newOperator) => {
     if (newOperator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
-      condition.value.timeValue = undefined
-      condition.value.timeValue2 = undefined
+      // 今日条件不需要时间参数
+      condition.value.param = ''
     } else if (!needsSecondTimeInput.value) {
-      condition.value.timeValue2 = undefined
+      // 非范围条件只保留第一个时间值
+      const currentParams = condition.value.param ? condition.value.param.split(',') : []
+      condition.value.param = currentParams[0] || ''
     }
   }
 )

+ 54 - 36
src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue

@@ -50,13 +50,11 @@
       <!-- 服务参数配置 -->
       <div v-if="action.identifier" class="space-y-16px">
         <el-form-item label="服务参数" required>
-          <!-- TODO@puhui999:这里有个 idea 告警 -->
           <JsonParamsInput
             v-model="paramsValue"
             type="service"
-            :config="{ service: selectedService }"
-            placeholder="请输入JSON格式的服务参数"
-            @validate="handleParamsValidate"
+            :config="{ service: selectedService } as any"
+            placeholder="请输入 JSON 格式的服务参数"
           />
         </el-form-item>
       </div>
@@ -70,8 +68,7 @@
           v-model="paramsValue"
           type="property"
           :config="{ properties: thingModelProperties }"
-          placeholder="请输入JSON格式的控制参数"
-          @validate="handleParamsValidate"
+          placeholder="请输入 JSON 格式的控制参数"
         />
       </el-form-item>
     </div>
@@ -83,10 +80,12 @@ import { useVModel } from '@vueuse/core'
 import ProductSelector from '../selectors/ProductSelector.vue'
 import DeviceSelector from '../selectors/DeviceSelector.vue'
 import JsonParamsInput from '../inputs/JsonParamsInput.vue'
-import { Action, ThingModelProperty, ThingModelService } from '@/api/iot/rule/scene/scene.types'
+import type { Action } from '@/api/iot/rule/scene'
+import type { ThingModelProperty, ThingModelService } from '@/api/iot/thingmodel'
 import {
   IotRuleSceneActionTypeEnum,
-  IoTThingModelAccessModeEnum
+  IoTThingModelAccessModeEnum,
+  IoTDataSpecsDataTypeEnum
 } from '@/views/iot/utils/constants'
 import { ThingModelApi } from '@/api/iot/thingmodel'
 
@@ -103,7 +102,6 @@ const emit = defineEmits<{
 
 const action = useVModel(props, 'modelValue', emit)
 
-// 状态变量
 const thingModelProperties = ref<ThingModelProperty[]>([]) // 物模型属性列表
 const loadingThingModel = ref(false) // 物模型加载状态
 const selectedService = ref<ThingModelService | null>(null) // 选中的服务对象
@@ -126,20 +124,13 @@ const paramsValue = computed({
   }
 })
 
-// 参数验证处理
-// TODO @puhui999:这个还需要哇?
-const handleParamsValidate = (result: { valid: boolean; message: string }) => {
-  // 可以在这里处理验证结果,比如显示错误信息
-  console.log('参数验证结果:', result)
-}
-
+// 计算属性:是否为属性设置类型
 const isPropertySetAction = computed(() => {
-  // 是否为属性设置类型
   return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET
 })
 
+// 计算属性:是否为服务调用类型
 const isServiceInvokeAction = computed(() => {
-  // 是否为服务调用类型
   return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
 })
 
@@ -167,7 +158,10 @@ const handleProductChange = (productId?: number) => {
   }
 }
 
-/** 处理设备变化事件 */
+/**
+ * 处理设备变化事件
+ * @param deviceId 设备ID
+ */
 const handleDeviceChange = (deviceId?: number) => {
   // 当设备变化时,清空参数配置
   if (action.value.deviceId !== deviceId) {
@@ -175,7 +169,10 @@ const handleDeviceChange = (deviceId?: number) => {
   }
 }
 
-/** 处理服务变化事件 */
+/**
+ * 处理服务变化事件
+ * @param serviceIdentifier 服务标识符
+ */
 const handleServiceChange = (serviceIdentifier?: string) => {
   // 根据服务标识符找到对应的服务对象
   const service = serviceList.value.find((s) => s.identifier === serviceIdentifier) || null
@@ -196,10 +193,13 @@ const handleServiceChange = (serviceIdentifier?: string) => {
 }
 
 /**
- * 获取物模型 TSL 数据
+ * 获取物模型TSL数据
+ * @param productId 产品ID
+ * @returns 物模型TSL数据
  */
 const getThingModelTSL = async (productId: number) => {
   if (!productId) return null
+
   try {
     return await ThingModelApi.getThingModelTSLByProductId(productId)
   } catch (error) {
@@ -208,7 +208,10 @@ const getThingModelTSL = async (productId: number) => {
   }
 }
 
-/** 加载物模型属性(可写属性)*/
+/**
+ * 加载物模型属性(可写属性)
+ * @param productId 产品ID
+ */
 const loadThingModelProperties = async (productId: number) => {
   if (!productId) {
     thingModelProperties.value = []
@@ -239,12 +242,16 @@ const loadThingModelProperties = async (productId: number) => {
   }
 }
 
-/** 加载服务列表 */
+/**
+ * 加载服务列表
+ * @param productId 产品ID
+ */
 const loadServiceList = async (productId: number) => {
   if (!productId) {
     serviceList.value = []
     return
   }
+
   try {
     loadingServices.value = true
     const tslData = await getThingModelTSL(productId)
@@ -263,7 +270,11 @@ const loadServiceList = async (productId: number) => {
   }
 }
 
-/** 从 TSL 加载服务信息(用于编辑模式回显)*/
+/**
+ * 从TSL加载服务信息(用于编辑模式回显)
+ * @param productId 产品ID
+ * @param serviceIdentifier 服务标识符
+ */
 const loadServiceFromTSL = async (productId: number, serviceIdentifier: string) => {
   // 先加载服务列表
   await loadServiceList(productId)
@@ -275,19 +286,23 @@ const loadServiceFromTSL = async (productId: number, serviceIdentifier: string)
   }
 }
 
-/** 根据参数类型获取默认值 */
+/**
+ * 根据参数类型获取默认值
+ * @param param 参数对象
+ * @returns 默认值
+ */
 const getDefaultValueForParam = (param: any) => {
   switch (param.dataType) {
-    case 'int':
+    case IoTDataSpecsDataTypeEnum.INT:
       return 0
-    case 'float':
-    case 'double':
+    case IoTDataSpecsDataTypeEnum.FLOAT:
+    case IoTDataSpecsDataTypeEnum.DOUBLE:
       return 0.0
-    case 'bool':
+    case IoTDataSpecsDataTypeEnum.BOOL:
       return false
-    case 'text':
+    case IoTDataSpecsDataTypeEnum.TEXT:
       return ''
-    case 'enum':
+    case IoTDataSpecsDataTypeEnum.ENUM:
       // 如果有枚举值,使用第一个
       if (param.dataSpecs?.dataSpecsList && param.dataSpecs.dataSpecsList.length > 0) {
         return param.dataSpecs.dataSpecsList[0].value
@@ -298,10 +313,11 @@ const getDefaultValueForParam = (param: any) => {
   }
 }
 
-// 防止重复初始化的标志
-const isInitialized = ref(false)
+const isInitialized = ref(false) // 防止重复初始化的标志
 
-/** 初始化组件数据 */
+/**
+ * 初始化组件数据
+ */
 const initializeComponent = async () => {
   if (isInitialized.value) return
 
@@ -322,12 +338,14 @@ const initializeComponent = async () => {
   isInitialized.value = true
 }
 
-/** 组件初始化 */
+/**
+ * 组件初始化
+ */
 onMounted(() => {
   initializeComponent()
 })
 
-// 监听关键字段的变化,避免深度监听导致的性能问题
+// 监听关键字段的变化,避免深度监听导致的性能问题
 watch(
   () => [action.value.productId, action.value.type, action.value.identifier],
   async ([newProductId, , newIdentifier], [oldProductId, , oldIdentifier]) => {

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

@@ -1,207 +0,0 @@
-<!-- 设备状态条件配置组件 -->
-<template>
-  <div class="flex flex-col gap-16px">
-    <!-- 产品设备选择 -->
-    <el-row :gutter="16">
-      <el-col :span="12">
-        <el-form-item label="产品" required>
-          <ProductSelector
-            :model-value="condition.productId"
-            @update:model-value="(value) => updateConditionField('productId', value)"
-            @change="handleProductChange"
-          />
-        </el-form-item>
-      </el-col>
-      <el-col :span="12">
-        <el-form-item label="设备" required>
-          <DeviceSelector
-            :model-value="condition.deviceId"
-            @update:model-value="(value) => updateConditionField('deviceId', value)"
-            :product-id="condition.productId"
-            @change="handleDeviceChange"
-          />
-        </el-form-item>
-      </el-col>
-    </el-row>
-
-    <!-- 状态和操作符选择 -->
-    <el-row :gutter="16">
-      <!-- 操作符选择 -->
-      <el-col :span="12">
-        <el-form-item label="操作符" required>
-          <el-select
-            :model-value="condition.operator"
-            @update:model-value="(value) => updateConditionField('operator', value)"
-            placeholder="请选择操作符"
-            class="w-full"
-          >
-            <el-option
-              v-for="option in statusOperatorOptions"
-              :key="option.value"
-              :label="option.label"
-              :value="option.value"
-            >
-              <div class="flex items-center justify-between w-full">
-                <span>{{ option.label }}</span>
-                <span class="text-12px text-[var(--el-text-color-secondary)]">
-                  {{ option.description }}
-                </span>
-              </div>
-            </el-option>
-          </el-select>
-        </el-form-item>
-      </el-col>
-
-      <!-- 状态选择 -->
-      <el-col :span="12">
-        <el-form-item label="设备状态" required>
-          <el-select
-            :model-value="condition.param"
-            @update:model-value="(value) => updateConditionField('param', value)"
-            placeholder="请选择设备状态"
-            class="w-full"
-          >
-            <el-option
-              v-for="option in deviceStatusOptions"
-              :key="option.value"
-              :label="option.label"
-              :value="option.value"
-            >
-              <div class="flex items-center gap-8px">
-                <Icon :icon="option.icon" :class="option.iconClass" />
-                <span>{{ option.label }}</span>
-                <el-tag :type="option.tag" size="small">{{ option.description }}</el-tag>
-              </div>
-            </el-option>
-          </el-select>
-        </el-form-item>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { useVModel } from '@vueuse/core'
-import ProductSelector from '../selectors/ProductSelector.vue'
-import DeviceSelector from '../selectors/DeviceSelector.vue'
-import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
-
-/** 设备状态条件配置组件 */
-defineOptions({ name: 'DeviceStatusConditionConfig' })
-
-const props = defineProps<{
-  modelValue: TriggerCondition
-}>()
-
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: TriggerCondition): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
-}>()
-
-const condition = useVModel(props, 'modelValue', emit)
-
-// 设备状态选项
-// TODO @puhui999:这个要不直接字段,简洁一点;
-const deviceStatusOptions = [
-  {
-    value: 'online',
-    label: '在线',
-    description: '设备已连接',
-    icon: 'ep:circle-check',
-    iconClass: 'text-green-500',
-    tag: 'success'
-  },
-  {
-    value: 'offline',
-    label: '离线',
-    description: '设备已断开',
-    icon: 'ep:circle-close',
-    iconClass: 'text-red-500',
-    tag: 'danger'
-  }
-]
-
-// 状态操作符选项
-// TODO @puhui999:value、label 看看能不能复用枚举值;
-const statusOperatorOptions = [
-  {
-    value: '=',
-    label: '等于',
-    description: '状态完全匹配时触发'
-  },
-  {
-    value: '!=',
-    label: '不等于',
-    description: '状态不匹配时触发'
-  }
-]
-
-// 状态
-const validationMessage = ref('')
-const isValid = ref(true)
-
-// 事件处理
-const updateConditionField = (field: any, value: any) => {
-  condition.value[field] = value
-  updateValidationResult()
-}
-
-const handleProductChange = (_: number) => {
-  // 产品变化时清空设备
-  condition.value.deviceId = undefined
-  updateValidationResult()
-}
-
-const handleDeviceChange = (_: number) => {
-  // 设备变化时可以进行其他处理
-  updateValidationResult()
-}
-
-const updateValidationResult = () => {
-  if (!condition.value.productId) {
-    isValid.value = false
-    validationMessage.value = '请选择产品'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  if (!condition.value.deviceId) {
-    isValid.value = false
-    validationMessage.value = '请选择设备'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  if (!condition.value.param) {
-    isValid.value = false
-    validationMessage.value = '请选择设备状态'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  if (!condition.value.operator) {
-    isValid.value = false
-    validationMessage.value = '请选择操作符'
-    emit('validate', { valid: false, message: validationMessage.value })
-    return
-  }
-
-  isValid.value = true
-  validationMessage.value = '设备状态条件配置验证通过'
-  emit('validate', { valid: true, message: validationMessage.value })
-}
-
-// 监听变化
-watch(
-  () => [
-    condition.value.productId,
-    condition.value.deviceId,
-    condition.value.param,
-    condition.value.operator
-  ],
-  () => {
-    updateValidationResult()
-  },
-  { immediate: true }
-)
-</script>

+ 213 - 39
src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue

@@ -3,21 +3,162 @@
   <div class="flex flex-col gap-16px">
     <!-- 主条件配置 - 默认直接展示 -->
     <div class="space-y-16px">
-      <MainConditionConfig
-        v-model="trigger"
-        :trigger-type="trigger.type"
-        @trigger-type-change="handleTriggerTypeChange"
-      />
+      <!-- 主条件配置 -->
+      <div class="flex flex-col gap-16px">
+        <!-- 主条件配置 -->
+        <div class="space-y-16px">
+          <!-- 主条件头部 - 与附加条件组保持一致的绿色风格 -->
+          <div
+            class="flex items-center justify-between p-16px bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-8px"
+          >
+            <div class="flex items-center gap-12px">
+              <div class="flex items-center gap-8px text-16px font-600 text-green-700">
+                <div
+                  class="w-24px h-24px bg-green-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+                >
+                  主
+                </div>
+                <span>主条件</span>
+              </div>
+              <el-tag size="small" type="success">必须满足</el-tag>
+            </div>
+          </div>
+
+          <!-- 主条件内容配置 -->
+          <MainConditionInnerConfig
+            :model-value="trigger"
+            @update:model-value="updateCondition"
+            :trigger-type="trigger.type"
+            @trigger-type-change="handleTriggerTypeChange"
+          />
+        </div>
+      </div>
     </div>
 
     <!-- 条件组配置 -->
     <div class="space-y-16px">
       <!-- 条件组配置 -->
-      <ConditionGroupContainerConfig
-        v-model="trigger.conditionGroups"
-        :trigger-type="trigger.type"
-        @remove="removeConditionGroup"
-      />
+      <div class="flex flex-col gap-16px">
+        <!-- 条件组容器头部 -->
+        <div
+          class="flex items-center justify-between p-16px bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-8px"
+        >
+          <div class="flex items-center gap-12px">
+            <div class="flex items-center gap-8px text-16px font-600 text-green-700">
+              <div
+                class="w-24px h-24px bg-green-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+              >
+                组
+              </div>
+              <span>附加条件组</span>
+            </div>
+            <el-tag size="small" type="success">与"主条件"为且关系</el-tag>
+            <el-tag size="small" type="info">
+              {{ trigger.conditionGroups?.length || 0 }} 个子条件组
+            </el-tag>
+          </div>
+          <div class="flex items-center gap-8px">
+            <el-button
+              type="primary"
+              size="small"
+              @click="addSubGroup"
+              :disabled="(trigger.conditionGroups?.length || 0) >= maxSubGroups"
+            >
+              <Icon icon="ep:plus" />
+              添加子条件组
+            </el-button>
+            <el-button type="danger" size="small" text @click="removeConditionGroup">
+              <Icon icon="ep:delete" />
+              删除条件组
+            </el-button>
+          </div>
+        </div>
+
+        <!-- 子条件组列表 -->
+        <div
+          v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0"
+          class="space-y-16px"
+        >
+          <!-- 逻辑关系说明 -->
+          <div class="relative">
+            <div
+              v-for="(subGroup, subGroupIndex) in trigger.conditionGroups"
+              :key="`sub-group-${subGroupIndex}`"
+              class="relative"
+            >
+              <!-- 子条件组容器 -->
+              <div
+                class="border-2 border-orange-200 rounded-8px bg-orange-50 shadow-sm hover:shadow-md transition-shadow"
+              >
+                <div
+                  class="flex items-center justify-between p-16px bg-gradient-to-r from-orange-50 to-yellow-50 border-b border-orange-200 rounded-t-6px"
+                >
+                  <div class="flex items-center gap-12px">
+                    <div class="flex items-center gap-8px text-16px font-600 text-orange-700">
+                      <div
+                        class="w-24px h-24px bg-orange-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+                      >
+                        {{ subGroupIndex + 1 }}
+                      </div>
+                      <span>子条件组 {{ subGroupIndex + 1 }}</span>
+                    </div>
+                    <el-tag size="small" type="warning" class="font-500">组内条件为"且"关系</el-tag>
+                    <el-tag size="small" type="info"> {{ subGroup?.length || 0 }}个条件 </el-tag>
+                  </div>
+                  <el-button
+                    type="danger"
+                    size="small"
+                    text
+                    @click="removeSubGroup(subGroupIndex)"
+                    class="hover:bg-red-50"
+                  >
+                    <Icon icon="ep:delete" />
+                    删除组
+                  </el-button>
+                </div>
+
+                <SubConditionGroupConfig
+                  :model-value="subGroup"
+                  @update:model-value="(value) => updateSubGroup(subGroupIndex, value)"
+                  :trigger-type="trigger.type"
+                  :max-conditions="maxConditionsPerGroup"
+                />
+              </div>
+
+              <!-- 子条件组间的"或"连接符 -->
+              <div
+                v-if="subGroupIndex < trigger.conditionGroups!.length - 1"
+                class="flex items-center justify-center py-12px"
+              >
+                <div class="flex items-center gap-8px">
+                  <!-- 连接线 -->
+                  <div class="w-32px h-1px bg-orange-300"></div>
+                  <!-- 或标签 -->
+                  <div class="px-16px py-6px bg-orange-100 border-2 border-orange-300 rounded-full">
+                    <span class="text-14px font-600 text-orange-600">或</span>
+                  </div>
+                  <!-- 连接线 -->
+                  <div class="w-32px h-1px bg-orange-300"></div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 空状态 -->
+        <div
+          v-else
+          class="p-24px border-2 border-dashed border-orange-200 rounded-8px text-center bg-orange-50"
+        >
+          <div class="flex flex-col items-center gap-12px">
+            <Icon icon="ep:plus" class="text-32px text-orange-400" />
+            <div class="text-orange-600">
+              <p class="text-14px font-500 mb-4px">暂无子条件组</p>
+              <p class="text-12px">点击上方"添加子条件组"按钮开始配置</p>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
   </div>
 </template>
@@ -25,56 +166,89 @@
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
 
-import MainConditionConfig from './MainConditionConfig.vue'
-import ConditionGroupContainerConfig from './ConditionGroupContainerConfig.vue'
-import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
-import { IotRuleSceneTriggerTypeEnum as TriggerTypeEnum } from '@/views/iot/utils/constants'
+import MainConditionInnerConfig from './MainConditionInnerConfig.vue'
+import SubConditionGroupConfig from './SubConditionGroupConfig.vue'
+import type { Trigger } from '@/api/iot/rule/scene'
 
 /** 设备触发配置组件 */
 defineOptions({ name: 'DeviceTriggerConfig' })
 
 const props = defineProps<{
-  modelValue: TriggerFormData
+  modelValue: Trigger
   index: number
 }>()
 
 const emit = defineEmits<{
-  (e: 'update:modelValue', value: TriggerFormData): void
-  (e: 'validate', value: { valid: boolean; message: string }): void
+  (e: 'update:modelValue', value: Trigger): void
   (e: 'trigger-type-change', type: number): void
 }>()
 
 const trigger = useVModel(props, 'modelValue', emit)
 
-// 初始化主条件
-const initMainCondition = () => {
-  // TODO @puhui999: 等到编辑回显时联调
-  // if (!trigger.value.mainCondition) {
-  //   trigger.value = {
-  //     type: trigger.value.type, // 使用触发事件类型作为条件类型
-  //     productId: undefined,
-  //     deviceId: undefined,
-  //     identifier: '',
-  //     operator: '=',
-  //     param: ''
-  //   }
-  // }
-}
+const maxSubGroups = 3 // 最多 3 个子条件组
+const maxConditionsPerGroup = 3 // 每组最多 3 个条件
 
-// 监听触发器类型变化,自动初始化主条件
-watch(
-  () => trigger.value.type,
-  () => {
-    initMainCondition()
-  },
-  { immediate: true }
-)
+/**
+ * 更新条件
+ * @param condition 条件对象
+ */
+const updateCondition = (condition: Trigger) => {
+  trigger.value = condition
+}
 
+/**
+ * 处理触发器类型变化事件
+ * @param type 触发器类型
+ */
 const handleTriggerTypeChange = (type: number) => {
   trigger.value.type = type
   emit('trigger-type-change', type)
 }
 
+/**
+ * 添加子条件组
+ */
+const addSubGroup = async () => {
+  if (!trigger.value.conditionGroups) {
+    trigger.value.conditionGroups = []
+  }
+
+  // 检查是否达到最大子组数量限制
+  if (trigger.value.conditionGroups?.length >= maxSubGroups) {
+    return
+  }
+
+  // 使用 nextTick 确保响应式更新完成后再添加新的子组
+  await nextTick()
+  if (trigger.value.conditionGroups) {
+    trigger.value.conditionGroups.push([])
+  }
+}
+
+/**
+ * 移除子条件组
+ * @param index 子条件组索引
+ */
+const removeSubGroup = (index: number) => {
+  if (trigger.value.conditionGroups) {
+    trigger.value.conditionGroups.splice(index, 1)
+  }
+}
+
+/**
+ * 更新子条件组
+ * @param index 子条件组索引
+ * @param subGroup 子条件组数据
+ */
+const updateSubGroup = (index: number, subGroup: any) => {
+  if (trigger.value.conditionGroups) {
+    trigger.value.conditionGroups[index] = subGroup
+  }
+}
+
+/**
+ * 移除整个条件组
+ */
 const removeConditionGroup = () => {
   trigger.value.conditionGroups = undefined
 }

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

@@ -1,66 +0,0 @@
-<!-- 主条件配置组件 -->
-<template>
-  <div class="flex flex-col gap-16px">
-    <!-- 主条件配置 -->
-    <!-- TODO @puhui999:和“主条件”,是不是和“附加条件组”弄成一个风格,都是占一行;有个绿条; -->
-    <div class="space-y-16px">
-      <!-- 主条件头部 - 与附加条件组保持一致的绿色风格 -->
-      <div
-        class="flex items-center justify-between p-16px bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-8px"
-      >
-        <div class="flex items-center gap-12px">
-          <div class="flex items-center gap-8px text-16px font-600 text-green-700">
-            <div
-              class="w-24px h-24px bg-green-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
-            >
-              主
-            </div>
-            <span>主条件</span>
-          </div>
-          <el-tag size="small" type="success">必须满足</el-tag>
-        </div>
-      </div>
-
-      <!-- 主条件内容配置 -->
-      <MainConditionInnerConfig
-        :model-value="modelValue"
-        @update:model-value="updateCondition"
-        :trigger-type="triggerType"
-        @validate="handleValidate"
-        @trigger-type-change="handleTriggerTypeChange"
-      />
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import MainConditionInnerConfig from './MainConditionInnerConfig.vue'
-import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
-import { IotRuleSceneTriggerConditionTypeEnum } from '@/views/iot/utils/constants'
-/** 主条件配置组件 */
-defineOptions({ name: 'MainConditionConfig' })
-
-const props = defineProps<{
-  modelValue: TriggerFormData
-  triggerType: number
-}>()
-
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: TriggerFormData): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
-  (e: 'trigger-type-change', type: number): void
-}>()
-
-// 事件处理
-const updateCondition = (condition: TriggerFormData) => {
-  emit('update:modelValue', condition)
-}
-
-const handleValidate = (result: { valid: boolean; message: string }) => {
-  emit('validate', result)
-}
-
-const handleTriggerTypeChange = (type: number) => {
-  emit('trigger-type-change', type)
-}
-</script>

+ 95 - 158
src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="space-y-16px">
     <!-- 触发事件类型选择 -->
-    <!-- TODO @puhui999:事件上报时,应该也是 json? -->
     <el-form-item label="触发事件类型" required>
       <el-select
         :model-value="triggerType"
@@ -60,13 +59,7 @@
         </el-col>
 
         <!-- 操作符选择 - 服务调用和事件上报不需要操作符 -->
-        <el-col
-          v-if="
-            triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE &&
-            triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
-          "
-          :span="6"
-        >
+        <el-col v-if="needsOperatorSelector" :span="6">
           <el-form-item label="操作符" required>
             <OperatorSelector
               :model-value="condition.operator"
@@ -78,32 +71,15 @@
         </el-col>
 
         <!-- 值输入 -->
-        <!-- TODO @puhui999:这种用 include 更简洁 -->
-        <el-col
-          :span="
-            triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE ||
-            triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
-              ? 18
-              : 12
-          "
-        >
-          <el-form-item
-            :label="
-              triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
-                ? '服务参数'
-                : '比较值'
-            "
-            required
-          >
+        <el-col :span="isWideValueColumn ? 18 : 12">
+          <el-form-item :label="valueInputLabel" required>
             <!-- 服务调用参数配置 -->
-            <!-- TODO @puhui999:中英文之间,有个空格哈? -->
             <JsonParamsInput
               v-if="triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE"
               v-model="condition.value"
               type="service"
               :config="serviceConfig"
-              placeholder="请输入JSON格式的服务参数"
-              @validate="handleValueValidate"
+              placeholder="请输入 JSON 格式的服务参数"
             />
             <!-- 事件上报参数配置 -->
             <JsonParamsInput
@@ -111,8 +87,7 @@
               v-model="condition.value"
               type="event"
               :config="eventConfig"
-              placeholder="请输入JSON格式的事件参数"
-              @validate="handleValueValidate"
+              placeholder="请输入 JSON 格式的事件参数"
             />
             <!-- 普通值输入 -->
             <ValueInput
@@ -122,7 +97,6 @@
               :property-type="propertyType"
               :operator="condition.operator"
               :property-config="propertyConfig"
-              @validate="handleValueValidate"
             />
           </el-form-item>
         </el-col>
@@ -153,10 +127,8 @@
           </el-form-item>
         </el-col>
       </el-row>
-
-      <!-- TODO @puhui999:这个是不是跟阿里云,还是一致一点哈? -->
       <el-row :gutter="16">
-        <el-col :span="12">
+        <el-col :span="6">
           <el-form-item label="操作符" required>
             <el-select
               :model-value="condition.operator"
@@ -164,8 +136,27 @@
               placeholder="请选择操作符"
               class="w-full"
             >
-              <el-option label="变为在线" value="online" />
-              <el-option label="变为离线" value="offline" />
+              <el-option
+                :label="IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name"
+                :value="IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="参数" required>
+            <el-select
+              :model-value="condition.value"
+              @update:model-value="(value) => updateConditionField('value', value)"
+              placeholder="请选择操作符"
+              class="w-full"
+            >
+              <el-option
+                v-for="option in deviceStatusChangeOptions"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+              />
             </el-select>
           </el-form-item>
         </el-col>
@@ -175,7 +166,7 @@
     <!-- 其他触发类型的提示 -->
     <div v-else class="text-center py-20px">
       <p class="text-14px text-[var(--el-text-color-secondary)] mb-4px">
-        当前触发事件类型:{{ getTriggerTypeText(triggerType) }}
+        当前触发事件类型:{{ getTriggerTypeLabel(triggerType) }}
       </p>
       <p class="text-12px text-[var(--el-text-color-placeholder)]">
         此触发类型暂不需要配置额外条件
@@ -192,33 +183,34 @@ import OperatorSelector from '../selectors/OperatorSelector.vue'
 import ValueInput from '../inputs/ValueInput.vue'
 import JsonParamsInput from '../inputs/JsonParamsInput.vue'
 
-import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
-import { IotRuleSceneTriggerTypeEnum, getTriggerTypeOptions } from '@/views/iot/utils/constants'
+import type { Trigger } from '@/api/iot/rule/scene'
+import {
+  IotRuleSceneTriggerTypeEnum,
+  triggerTypeOptions,
+  getTriggerTypeLabel,
+  deviceStatusChangeOptions,
+  IotRuleSceneTriggerConditionParameterOperatorEnum
+} from '@/views/iot/utils/constants'
 import { useVModel } from '@vueuse/core'
 
 /** 主条件内部配置组件 */
 defineOptions({ name: 'MainConditionInnerConfig' })
 
 const props = defineProps<{
-  modelValue: TriggerFormData
+  modelValue: Trigger
   triggerType: number
 }>()
 
 const emit = defineEmits<{
-  (e: 'update:modelValue', value: TriggerFormData): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
+  (e: 'update:modelValue', value: Trigger): void
   (e: 'trigger-type-change', value: number): void
 }>()
 
-// 响应式数据
 const condition = useVModel(props, 'modelValue', emit)
-// TODO @puhui999:是不是 validationMessage 非空,就是不通过哈;
-const isValid = ref(true)
-const validationMessage = ref('')
-const propertyType = ref('')
-const propertyConfig = ref<any>(null)
+const propertyType = ref('') // 属性类型
+const propertyConfig = ref<any>(null) // 属性配置
 
-// 计算属性
+// 计算属性:是否为设备属性触发器
 const isDevicePropertyTrigger = computed(() => {
   return (
     props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST ||
@@ -227,11 +219,37 @@ const isDevicePropertyTrigger = computed(() => {
   )
 })
 
+// 计算属性:是否为设备状态触发器
 const isDeviceStatusTrigger = computed(() => {
   return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE
 })
 
-// 服务配置 - 用于 JsonParamsInput
+// 计算属性:是否需要操作符选择(服务调用和事件上报不需要操作符)
+const needsOperatorSelector = computed(() => {
+  const noOperatorTriggerTypes = [
+    IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
+    IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
+  ] as number[]
+  return !noOperatorTriggerTypes.includes(props.triggerType)
+})
+
+// 计算属性:是否需要宽列布局(服务调用和事件上报不需要操作符列,所以值输入列更宽)
+const isWideValueColumn = computed(() => {
+  const wideColumnTriggerTypes = [
+    IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
+    IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
+  ] as number[]
+  return wideColumnTriggerTypes.includes(props.triggerType)
+})
+
+// 计算属性:值输入字段的标签文本
+const valueInputLabel = computed(() => {
+  return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
+    ? '服务参数'
+    : '比较值'
+})
+
+// 计算属性:服务配置 - 用于 JsonParamsInput
 const serviceConfig = computed(() => {
   if (
     propertyConfig.value &&
@@ -247,7 +265,7 @@ const serviceConfig = computed(() => {
   return undefined
 })
 
-// 事件配置 - 用于 JsonParamsInput
+// 计算属性:事件配置 - 用于 JsonParamsInput
 const eventConfig = computed(() => {
   if (propertyConfig.value && props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) {
     return {
@@ -260,49 +278,44 @@ const eventConfig = computed(() => {
   return undefined
 })
 
-// 获取触发类型文本
-// TODO @puhui999:是不是有枚举可以服用哈;
-const getTriggerTypeText = (type: number) => {
-  switch (type) {
-    case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
-      return '设备属性上报'
-    case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
-      return '设备事件上报'
-    case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
-      return '设备服务调用'
-    case IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE:
-      return '设备状态变化'
-    default:
-      return '未知类型'
-  }
-}
-
-// 触发器类型选项
-const triggerTypeOptions = getTriggerTypeOptions()
-
-// 事件处理
-const updateConditionField = (field: keyof TriggerFormData, value: any) => {
-  ;(condition.value as any)[field] = value
-  updateValidationResult()
+/**
+ * 更新条件字段
+ * @param field 字段名
+ * @param value 字段值
+ */
+const updateConditionField = (field: any, value: any) => {
+  condition.value[field] = value
 }
 
+/**
+ * 处理触发器类型变化事件
+ * @param type 触发器类型
+ */
 const handleTriggerTypeChange = (type: number) => {
   emit('trigger-type-change', type)
 }
 
+/**
+ * 处理产品变化事件
+ */
 const handleProductChange = () => {
   // 产品变化时清空设备和属性
   condition.value.deviceId = undefined
   condition.value.identifier = ''
-  updateValidationResult()
 }
 
+/**
+ * 处理设备变化事件
+ */
 const handleDeviceChange = () => {
   // 设备变化时清空属性
   condition.value.identifier = ''
-  updateValidationResult()
 }
 
+/**
+ * 处理属性变化事件
+ * @param propertyInfo 属性信息对象
+ */
 const handlePropertyChange = (propertyInfo: any) => {
   if (propertyInfo) {
     propertyType.value = propertyInfo.type
@@ -313,91 +326,15 @@ const handlePropertyChange = (propertyInfo: any) => {
       props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
       props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
     ) {
-      condition.value.operator = '='
+      condition.value.operator = IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value
     }
   }
-  updateValidationResult()
 }
 
+/**
+ * 处理操作符变化事件
+ */
 const handleOperatorChange = () => {
-  updateValidationResult()
-}
-
-// 处理参数验证结果
-const handleValueValidate = (result: { valid: boolean; message: string }) => {
-  isValid.value = result.valid
-  validationMessage.value = result.message
-  emit('validate', result)
-  updateValidationResult()
-}
-
-// 验证逻辑
-// TODO @puhui999:这个校验,是不是用更原生的 validator 哈。项目风格更统一点。
-const updateValidationResult = () => {
-  if (isDevicePropertyTrigger.value) {
-    // 设备属性触发验证
-    if (!condition.value.productId) {
-      isValid.value = false
-      validationMessage.value = '请选择产品'
-      emit('validate', { valid: false, message: validationMessage.value })
-      return
-    }
-
-    if (!condition.value.deviceId) {
-      isValid.value = false
-      validationMessage.value = '请选择设备'
-      emit('validate', { valid: false, message: validationMessage.value })
-      return
-    }
-
-    if (!condition.value.identifier) {
-      isValid.value = false
-      validationMessage.value = '请选择监控项'
-      emit('validate', { valid: false, message: validationMessage.value })
-      return
-    }
-
-    // 服务调用和事件上报不需要操作符
-    if (
-      props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE &&
-      props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST &&
-      !condition.value.operator
-    ) {
-      isValid.value = false
-      validationMessage.value = '请选择操作符'
-      emit('validate', { valid: false, message: validationMessage.value })
-      return
-    }
-
-    if (!condition.value.value) {
-      isValid.value = false
-      validationMessage.value = '请输入比较值'
-      emit('validate', { valid: false, message: validationMessage.value })
-      return
-    }
-  }
-
-  isValid.value = true
-  validationMessage.value = '主条件配置验证通过'
-  emit('validate', { valid: true, message: validationMessage.value })
+  // 操作符变化处理
 }
-
-// 监听变化
-watch(
-  () => [
-    condition.value.productId,
-    condition.value.deviceId,
-    condition.value.identifier,
-    // 服务调用和事件上报不需要监听操作符
-    props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE &&
-    props.triggerType !== IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
-      ? condition.value.operator
-      : null,
-    condition.value.value
-  ],
-  () => {
-    updateValidationResult()
-  },
-  { immediate: true }
-)
 </script>

+ 19 - 61
src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue

@@ -56,7 +56,6 @@
               :model-value="condition"
               @update:model-value="(value) => updateCondition(conditionIndex, value)"
               :trigger-type="triggerType"
-              @validate="(result) => handleConditionValidate(conditionIndex, result)"
             />
           </div>
         </div>
@@ -83,7 +82,7 @@
 import { nextTick } from 'vue'
 import { useVModel } from '@vueuse/core'
 import ConditionConfig from './ConditionConfig.vue'
-import { TriggerCondition } from '@/api/iot/rule/scene/scene.types'
+import type { TriggerCondition } from '@/api/iot/rule/scene'
 import {
   IotRuleSceneTriggerConditionTypeEnum,
   IotRuleSceneTriggerConditionParameterOperatorEnum
@@ -100,19 +99,16 @@ const props = defineProps<{
 
 const emit = defineEmits<{
   (e: 'update:modelValue', value: TriggerCondition[]): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
 }>()
 
 const subGroup = useVModel(props, 'modelValue', emit)
 
-// 配置常量
-const maxConditions = computed(() => props.maxConditions || 3)
+const maxConditions = computed(() => props.maxConditions || 3) // 最大条件数量
 
-// 验证状态
-const conditionValidations = ref<{ [key: number]: { valid: boolean; message: string } }>({})
-
-// 事件处理
-const addCondition = () => {
+/**
+ * 添加条件
+ */
+const addCondition = async () => {
   // 确保 subGroup.value 是一个数组
   if (!subGroup.value) {
     subGroup.value = []
@@ -133,68 +129,30 @@ const addCondition = () => {
   }
 
   // 使用 nextTick 确保响应式更新完成后再添加新条件
-  nextTick(() => {
-    if (subGroup.value) {
-      subGroup.value.push(newCondition)
-    }
-  })
+  await nextTick()
+  if (subGroup.value) {
+    subGroup.value.push(newCondition)
+  }
 }
 
+/**
+ * 移除条件
+ * @param index 条件索引
+ */
 const removeCondition = (index: number) => {
   if (subGroup.value) {
     subGroup.value.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()
   }
 }
 
+/**
+ * 更新条件
+ * @param index 条件索引
+ * @param condition 条件对象
+ */
 const updateCondition = (index: number, condition: TriggerCondition) => {
   if (subGroup.value) {
     subGroup.value[index] = condition
   }
 }
-
-const handleConditionValidate = (index: number, result: { valid: boolean; message: string }) => {
-  conditionValidations.value[index] = result
-  updateValidationResult()
-}
-
-const updateValidationResult = () => {
-  if (!subGroup.value || subGroup.value.length === 0) {
-    emit('validate', { valid: false, message: '子条件组至少需要一个条件' })
-    return
-  }
-
-  const validations = Object.values(conditionValidations.value)
-  const allValid = validations.every((v: any) => v.valid)
-
-  if (allValid) {
-    emit('validate', { valid: true, message: '子条件组配置验证通过' })
-  } else {
-    const errorMessages = validations.filter((v: any) => !v.valid).map((v: any) => v.message)
-    emit('validate', { valid: false, message: `条件配置错误: ${errorMessages.join('; ')}` })
-  }
-}
-
-// 监听变化
-watch(
-  () => subGroup.value,
-  () => {
-    updateValidationResult()
-  },
-  { deep: true, immediate: true }
-)
 </script>

+ 0 - 39
src/views/iot/rule/scene/form/configs/TimerTriggerConfig.vue

@@ -1,39 +0,0 @@
-<template>
-  <div class="flex flex-col gap-16px">
-    <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>
-
-    <!-- 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>
-        <Crontab v-model="localValue" />
-      </el-form-item>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { useVModel } from '@vueuse/core'
-import { Crontab } from '@/components/Crontab'
-
-/** 定时触发配置组件 */
-defineOptions({ name: 'TimerTriggerConfig' })
-
-const props = defineProps<{
-  modelValue?: string
-}>()
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: string): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
-}>()
-
-const localValue = useVModel(props, 'modelValue', emit, {
-  defaultValue: '0 0 12 * * ?'
-})
-</script>

+ 172 - 159
src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue

@@ -24,7 +24,13 @@
             popper-class="json-params-detail-popover"
           >
             <template #reference>
-              <el-button type="info" :icon="InfoFilled" circle size="small" title="查看参数示例" />
+              <el-button
+                type="info"
+                :icon="InfoFilled"
+                circle
+                size="small"
+                :title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
+              />
             </template>
 
             <!-- 弹出层内容 -->
@@ -55,7 +61,7 @@
                         <div class="text-12px font-500 text-[var(--el-text-color-primary)]">
                           {{ param.name }}
                           <el-tag v-if="param.required" size="small" type="danger" class="ml-4px">
-                            必填
+                            {{ JSON_PARAMS_INPUT_CONSTANTS.REQUIRED_TAG }}
                           </el-tag>
                         </div>
                         <div class="text-11px text-[var(--el-text-color-secondary)]">
@@ -75,7 +81,7 @@
 
                   <div class="mt-12px ml-22px">
                     <div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
-                      完整 JSON 格式:
+                      {{ JSON_PARAMS_INPUT_CONSTANTS.COMPLETE_JSON_FORMAT }}
                     </div>
                     <pre
                       class="p-12px bg-[var(--el-fill-color-light)] rounded-4px text-11px text-[var(--el-text-color-primary)] overflow-x-auto border-l-3px border-[var(--el-color-primary)]"
@@ -103,7 +109,11 @@
       <div class="flex items-center justify-between">
         <div class="flex items-center gap-8px">
           <Icon
-            :icon="jsonError ? 'ep:warning' : 'ep:circle-check'"
+            :icon="
+              jsonError
+                ? JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.ERROR
+                : JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.SUCCESS
+            "
             :class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
             class="text-14px"
           />
@@ -111,17 +121,21 @@
             :class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
             class="text-12px"
           >
-            {{ jsonError || 'JSON 格式正确' }}
+            {{ jsonError || JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_CORRECT }}
           </span>
         </div>
 
         <!-- 快速填充按钮 -->
         <div v-if="paramsList.length > 0" class="flex items-center gap-8px">
-          <span class="text-12px text-[var(--el-text-color-secondary)]">快速填充:</span>
+          <span class="text-12px text-[var(--el-text-color-secondary)]">{{
+            JSON_PARAMS_INPUT_CONSTANTS.QUICK_FILL_LABEL
+          }}</span>
           <el-button size="small" type="primary" plain @click="fillExampleJson">
-            示例数据
+            {{ JSON_PARAMS_INPUT_CONSTANTS.EXAMPLE_DATA_BUTTON }}
           </el-button>
-          <el-button size="small" type="danger" plain @click="clearParams"> 清空</el-button>
+          <el-button size="small" type="danger" plain @click="clearParams">{{
+            JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON
+          }}</el-button>
         </div>
       </div>
     </div>
@@ -136,6 +150,14 @@
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
 import { InfoFilled } from '@element-plus/icons-vue'
+import {
+  IoTDataSpecsDataTypeEnum,
+  JSON_PARAMS_INPUT_CONSTANTS,
+  JSON_PARAMS_INPUT_ICONS,
+  JSON_PARAMS_EXAMPLE_VALUES,
+  JsonParamsInputTypeEnum,
+  type JsonParamsInputType
+} from '@/views/iot/utils/constants'
 
 /** JSON参数输入组件 - 通用版本 */
 defineOptions({ name: 'JsonParamsInput' })
@@ -163,18 +185,17 @@ export interface JsonParamsConfig {
 interface Props {
   modelValue?: string
   config?: JsonParamsConfig
-  type?: 'service' | 'event' | 'property' | 'custom'
+  type?: JsonParamsInputType
   placeholder?: string
 }
 
 interface Emits {
   (e: 'update:modelValue', value: string): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
 }
 
 const props = withDefaults(defineProps<Props>(), {
-  type: 'service',
-  placeholder: '请输入JSON格式的参数'
+  type: JsonParamsInputTypeEnum.SERVICE,
+  placeholder: JSON_PARAMS_INPUT_CONSTANTS.PLACEHOLDER
 })
 
 const emit = defineEmits<Emits>()
@@ -183,11 +204,10 @@ const localValue = useVModel(props, 'modelValue', emit, {
   defaultValue: ''
 })
 
-// 状态
-const paramsJson = ref('')
-const jsonError = ref('')
+const paramsJson = ref('') // JSON参数字符串
+const jsonError = ref('') // JSON验证错误信息
 
-// 计算属性
+// 计算属性:是否有配置
 const hasConfig = computed(() => {
   // TODO @puhui999: 后续统一处理
   console.log(props.config)
@@ -200,112 +220,121 @@ const hasConfig = computed(() => {
   return true
 })
 
+// 计算属性:参数列表
 const paramsList = computed(() => {
   switch (props.type) {
-    case 'service':
+    case JsonParamsInputTypeEnum.SERVICE:
       return props.config?.service?.inputParams || []
-    case 'event':
+    case JsonParamsInputTypeEnum.EVENT:
       return props.config?.event?.outputParams || []
-    case 'property':
+    case JsonParamsInputTypeEnum.PROPERTY:
       return props.config?.properties || []
-    case 'custom':
+    case JsonParamsInputTypeEnum.CUSTOM:
       return props.config?.custom?.params || []
     default:
       return []
   }
 })
 
+// 计算属性:标题
 const title = computed(() => {
   switch (props.type) {
-    case 'service':
-      return `${props.config?.service?.name || '服务'} - 输入参数示例`
-    case 'event':
-      return `${props.config?.event?.name || '事件'} - 输出参数示例`
-    case 'property':
-      return '属性设置 - 参数示例'
-    case 'custom':
-      return `${props.config?.custom?.name || '自定义'} - 参数示例`
+    case JsonParamsInputTypeEnum.SERVICE:
+      return JSON_PARAMS_INPUT_CONSTANTS.TITLES.SERVICE(props.config?.service?.name)
+    case JsonParamsInputTypeEnum.EVENT:
+      return JSON_PARAMS_INPUT_CONSTANTS.TITLES.EVENT(props.config?.event?.name)
+    case JsonParamsInputTypeEnum.PROPERTY:
+      return JSON_PARAMS_INPUT_CONSTANTS.TITLES.PROPERTY
+    case JsonParamsInputTypeEnum.CUSTOM:
+      return JSON_PARAMS_INPUT_CONSTANTS.TITLES.CUSTOM(props.config?.custom?.name)
     default:
-      return '参数示例'
+      return JSON_PARAMS_INPUT_CONSTANTS.TITLES.DEFAULT
   }
 })
 
+// 计算属性:标题图标
 const titleIcon = computed(() => {
   switch (props.type) {
-    case 'service':
-      return 'ep:service'
-    case 'event':
-      return 'ep:bell'
-    case 'property':
-      return 'ep:edit'
-    case 'custom':
-      return 'ep:document'
+    case JsonParamsInputTypeEnum.SERVICE:
+      return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.SERVICE
+    case JsonParamsInputTypeEnum.EVENT:
+      return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.EVENT
+    case JsonParamsInputTypeEnum.PROPERTY:
+      return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.PROPERTY
+    case JsonParamsInputTypeEnum.CUSTOM:
+      return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.CUSTOM
     default:
-      return 'ep:document'
+      return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.DEFAULT
   }
 })
 
+// 计算属性:参数图标
 const paramsIcon = computed(() => {
   switch (props.type) {
-    case 'service':
-      return 'ep:edit'
-    case 'event':
-      return 'ep:upload'
-    case 'property':
-      return 'ep:setting'
-    case 'custom':
-      return 'ep:list'
+    case JsonParamsInputTypeEnum.SERVICE:
+      return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.SERVICE
+    case JsonParamsInputTypeEnum.EVENT:
+      return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.EVENT
+    case JsonParamsInputTypeEnum.PROPERTY:
+      return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.PROPERTY
+    case JsonParamsInputTypeEnum.CUSTOM:
+      return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.CUSTOM
     default:
-      return 'ep:edit'
+      return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.DEFAULT
   }
 })
 
+// 计算属性:参数标签
 const paramsLabel = computed(() => {
   switch (props.type) {
-    case 'service':
-      return '输入参数'
-    case 'event':
-      return '输出参数'
-    case 'property':
-      return '属性参数'
-    case 'custom':
-      return '参数列表'
+    case JsonParamsInputTypeEnum.SERVICE:
+      return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.SERVICE
+    case JsonParamsInputTypeEnum.EVENT:
+      return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.EVENT
+    case JsonParamsInputTypeEnum.PROPERTY:
+      return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.PROPERTY
+    case JsonParamsInputTypeEnum.CUSTOM:
+      return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.CUSTOM
     default:
-      return '参数'
+      return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.DEFAULT
   }
 })
 
+// 计算属性:空状态消息
 const emptyMessage = computed(() => {
   switch (props.type) {
-    case 'service':
-      return '此服务无需输入参数'
-    case 'event':
-      return '此事件无输出参数'
-    case 'property':
-      return '无可设置的属性'
-    case 'custom':
-      return '无参数配置'
+    case JsonParamsInputTypeEnum.SERVICE:
+      return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.SERVICE
+    case JsonParamsInputTypeEnum.EVENT:
+      return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.EVENT
+    case JsonParamsInputTypeEnum.PROPERTY:
+      return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.PROPERTY
+    case JsonParamsInputTypeEnum.CUSTOM:
+      return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.CUSTOM
     default:
-      return '无参数'
+      return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.DEFAULT
   }
 })
 
+// 计算属性:无配置消息
 const noConfigMessage = computed(() => {
   switch (props.type) {
-    case 'service':
-      return '请先选择服务'
-    case 'event':
-      return '请先选择事件'
-    case 'property':
-      return '请先选择产品'
-    case 'custom':
-      return '请先进行配置'
+    case JsonParamsInputTypeEnum.SERVICE:
+      return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.SERVICE
+    case JsonParamsInputTypeEnum.EVENT:
+      return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.EVENT
+    case JsonParamsInputTypeEnum.PROPERTY:
+      return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.PROPERTY
+    case JsonParamsInputTypeEnum.CUSTOM:
+      return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.CUSTOM
     default:
-      return '请先进行配置'
+      return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.DEFAULT
   }
 })
 
-// 事件处理
+/**
+ * 处理参数变化事件
+ */
 const handleParamsChange = () => {
   try {
     jsonError.value = '' // 清除之前的错误
@@ -316,16 +345,14 @@ const handleParamsChange = () => {
 
       // 额外的参数验证
       if (typeof parsed !== 'object' || parsed === null) {
-        jsonError.value = '参数必须是一个有效的 JSON 对象'
-        emit('validate', { valid: false, message: jsonError.value })
+        jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.PARAMS_MUST_BE_OBJECT
         return
       }
 
       // 验证必填参数
       for (const param of paramsList.value) {
         if (param.required && (!parsed[param.identifier] || parsed[param.identifier] === '')) {
-          jsonError.value = `参数 ${param.name} 为必填项`
-          emit('validate', { valid: false, message: jsonError.value })
+          jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.PARAM_REQUIRED_ERROR(param.name)
           return
         }
       }
@@ -334,80 +361,87 @@ const handleParamsChange = () => {
     }
 
     // 验证通过
-    emit('validate', { valid: true, message: 'JSON格式正确' })
+    jsonError.value = ''
   } catch (error) {
-    jsonError.value = `JSON格式错误: ${error instanceof Error ? error.message : '未知错误'}`
-    emit('validate', { valid: false, message: jsonError.value })
+    jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_ERROR(
+      error instanceof Error ? error.message : JSON_PARAMS_INPUT_CONSTANTS.UNKNOWN_ERROR
+    )
   }
 }
 
-// 快速填充示例数据
+/**
+ * 快速填充示例数据
+ */
 const fillExampleJson = () => {
   paramsJson.value = generateExampleJson()
   handleParamsChange()
 }
 
-// 清空参数
+/**
+ * 清空参数
+ */
 const clearParams = () => {
   paramsJson.value = ''
   localValue.value = ''
   jsonError.value = ''
-  emit('validate', { valid: true, message: '' })
 }
 
-// 工具函数
+/**
+ * 获取参数类型名称
+ * @param dataType 数据类型
+ * @returns 类型名称
+ */
 const getParamTypeName = (dataType: string) => {
+  // 使用 constants.ts 中已有的 getDataTypeName 函数逻辑
   const typeMap = {
-    int: '整数',
-    float: '浮点数',
-    double: '双精度',
-    text: '字符串',
-    bool: '布尔值',
-    enum: '枚举',
-    date: '日期',
-    struct: '结构体',
-    array: '数组'
+    [IoTDataSpecsDataTypeEnum.INT]: '整数',
+    [IoTDataSpecsDataTypeEnum.FLOAT]: '浮点数',
+    [IoTDataSpecsDataTypeEnum.DOUBLE]: '双精度',
+    [IoTDataSpecsDataTypeEnum.TEXT]: '字符串',
+    [IoTDataSpecsDataTypeEnum.BOOL]: '布尔值',
+    [IoTDataSpecsDataTypeEnum.ENUM]: '枚举',
+    [IoTDataSpecsDataTypeEnum.DATE]: '日期',
+    [IoTDataSpecsDataTypeEnum.STRUCT]: '结构体',
+    [IoTDataSpecsDataTypeEnum.ARRAY]: '数组'
   }
   return typeMap[dataType] || dataType
 }
 
+/**
+ * 获取参数类型标签样式
+ * @param dataType 数据类型
+ * @returns 标签样式
+ */
 const getParamTypeTag = (dataType: string) => {
   const tagMap = {
-    int: 'primary',
-    float: 'success',
-    double: 'success',
-    text: 'info',
-    bool: 'warning',
-    enum: 'danger',
-    date: 'primary',
-    struct: 'info',
-    array: 'warning'
+    [IoTDataSpecsDataTypeEnum.INT]: 'primary',
+    [IoTDataSpecsDataTypeEnum.FLOAT]: 'success',
+    [IoTDataSpecsDataTypeEnum.DOUBLE]: 'success',
+    [IoTDataSpecsDataTypeEnum.TEXT]: 'info',
+    [IoTDataSpecsDataTypeEnum.BOOL]: 'warning',
+    [IoTDataSpecsDataTypeEnum.ENUM]: 'danger',
+    [IoTDataSpecsDataTypeEnum.DATE]: 'primary',
+    [IoTDataSpecsDataTypeEnum.STRUCT]: 'info',
+    [IoTDataSpecsDataTypeEnum.ARRAY]: 'warning'
   }
   return tagMap[dataType] || 'info'
 }
 
+/**
+ * 获取示例值
+ * @param param 参数对象
+ * @returns 示例值
+ */
 const getExampleValue = (param: any) => {
-  switch (param.dataType) {
-    case 'int':
-      return '25'
-    case 'float':
-    case 'double':
-      return '25.5'
-    case 'bool':
-      return 'false'
-    case 'text':
-      return '"auto"'
-    case 'enum':
-      return '"option1"'
-    case 'struct':
-      return '{}'
-    case 'array':
-      return '[]'
-    default:
-      return '""'
-  }
+  const exampleConfig =
+    JSON_PARAMS_EXAMPLE_VALUES[param.dataType] || JSON_PARAMS_EXAMPLE_VALUES.DEFAULT
+  return exampleConfig.display
 }
 
+/**
+ * 生成示例JSON
+ * @returns JSON字符串
+ */
 const generateExampleJson = () => {
   if (paramsList.value.length === 0) {
     return '{}'
@@ -415,36 +449,18 @@ const generateExampleJson = () => {
 
   const example = {}
   paramsList.value.forEach((param) => {
-    switch (param.dataType) {
-      case 'int':
-        example[param.identifier] = 25
-        break
-      case 'float':
-      case 'double':
-        example[param.identifier] = 25.5
-        break
-      case 'bool':
-        example[param.identifier] = false
-        break
-      case 'text':
-        example[param.identifier] = 'auto'
-        break
-      case 'struct':
-        example[param.identifier] = {}
-        break
-      case 'array':
-        example[param.identifier] = []
-        break
-      default:
-        example[param.identifier] = ''
-    }
+    const exampleConfig =
+      JSON_PARAMS_EXAMPLE_VALUES[param.dataType] || JSON_PARAMS_EXAMPLE_VALUES.DEFAULT
+    example[param.identifier] = exampleConfig.value
   })
 
   return JSON.stringify(example, null, 2)
 }
 
-// 处理数据回显的函数
-// TODO @puhui999:注释风格;
+/**
+ * 处理数据回显
+ * @param value 值字符串
+ */
 const handleDataDisplay = (value: string) => {
   if (!value || !value.trim()) {
     paramsJson.value = ''
@@ -467,25 +483,23 @@ const handleDataDisplay = (value: string) => {
 // 监听外部值变化(编辑模式数据回显)
 watch(
   () => localValue.value,
-  (newValue, oldValue) => {
+  async (newValue, oldValue) => {
     // 避免循环更新
     if (newValue === oldValue) return
 
     // 使用 nextTick 确保在下一个 tick 中处理数据
-    nextTick(() => {
-      handleDataDisplay(newValue || '')
-    })
+    await nextTick()
+    handleDataDisplay(newValue || '')
   },
   { immediate: true }
 )
 
 // 组件挂载后也尝试处理一次数据回显
-onMounted(() => {
-  nextTick(() => {
-    if (localValue.value) {
-      handleDataDisplay(localValue.value)
-    }
-  })
+onMounted(async () => {
+  await nextTick()
+  if (localValue.value) {
+    handleDataDisplay(localValue.value)
+  }
 })
 
 // 监听配置变化
@@ -505,7 +519,6 @@ watch(
 </script>
 
 <style scoped>
-/** TODO @puhui999:unocss,看看哪些可以搞掉哈。 */
 /* 弹出层内容样式 */
 .json-params-detail-content {
   padding: 4px 0;

+ 66 - 129
src/views/iot/rule/scene/form/inputs/ValueInput.vue

@@ -132,19 +132,12 @@
         </el-tooltip>
       </template>
     </el-input>
-
-    <!-- 验证提示 -->
-    <div v-if="validationMessage" class="mt-4px">
-      <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'
+import { IoTDataSpecsDataTypeEnum } from '@/views/iot/utils/constants'
 
 /** 值输入组件 */
 defineOptions({ name: 'ValueInput' })
@@ -158,7 +151,6 @@ interface Props {
 
 interface Emits {
   (e: 'update:modelValue', value: string): void
-  (e: 'validate', result: { valid: boolean; message: string }): void
 }
 
 const props = defineProps<Props>()
@@ -168,15 +160,12 @@ 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 rangeStart = ref('') // 范围开始值
+const rangeEnd = ref('') // 范围结束值
+const dateValue = ref('') // 日期值
+const numberValue = ref<number>() // 数字值
 
-// 计算属性
+// 计算属性:枚举选项
 const enumOptions = computed(() => {
   if (props.propertyConfig?.enum) {
     return props.propertyConfig.enum.map((item: any) => ({
@@ -187,6 +176,7 @@ const enumOptions = computed(() => {
   return []
 })
 
+// 计算属性:列表预览
 const listPreview = computed(() => {
   if (props.operator === 'in' && localValue.value) {
     return localValue.value
@@ -197,161 +187,115 @@ const listPreview = computed(() => {
   return []
 })
 
-// 工具函数
+/**
+ * 判断是否为数字类型
+ * @returns 是否为数字类型
+ */
 const isNumericType = () => {
-  return ['int', 'float', 'double'].includes(props.propertyType || '')
+  return [
+    IoTDataSpecsDataTypeEnum.INT,
+    IoTDataSpecsDataTypeEnum.FLOAT,
+    IoTDataSpecsDataTypeEnum.DOUBLE
+  ].includes((props.propertyType || '') as any)
 }
 
+/**
+ * 获取输入框类型
+ * @returns 输入框类型
+ */
 const getInputType = () => {
   switch (props.propertyType) {
-    case 'int':
-    case 'float':
-    case 'double':
+    case IoTDataSpecsDataTypeEnum.INT:
+    case IoTDataSpecsDataTypeEnum.FLOAT:
+    case IoTDataSpecsDataTypeEnum.DOUBLE:
       return 'number'
     default:
       return 'text'
   }
 }
 
+/**
+ * 获取占位符文本
+ * @returns 占位符文本
+ */
 const getPlaceholder = () => {
   const typeMap = {
-    string: '请输入字符串',
-    int: '请输入整数',
-    float: '请输入浮点数',
-    double: '请输入双精度数',
-    struct: '请输入JSON格式数据',
-    array: '请输入数组格式数据'
+    [IoTDataSpecsDataTypeEnum.TEXT]: '请输入字符串',
+    [IoTDataSpecsDataTypeEnum.INT]: '请输入整数',
+    [IoTDataSpecsDataTypeEnum.FLOAT]: '请输入浮点数',
+    [IoTDataSpecsDataTypeEnum.DOUBLE]: '请输入双精度数',
+    [IoTDataSpecsDataTypeEnum.STRUCT]: '请输入 JSON 格式数据',
+    [IoTDataSpecsDataTypeEnum.ARRAY]: '请输入数组格式数据'
   }
   return typeMap[props.propertyType || ''] || '请输入值'
 }
 
+/**
+ * 获取数字精度
+ * @returns 数字精度
+ */
 const getPrecision = () => {
-  return props.propertyType === 'int' ? 0 : 2
+  return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 0 : 2
 }
 
+/**
+ * 获取数字步长
+ * @returns 数字步长
+ */
 const getStep = () => {
-  return props.propertyType === 'int' ? 1 : 0.1
+  return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 1 : 0.1
 }
 
+/**
+ * 获取最小值
+ * @returns 最小值
+ */
 const getMin = () => {
   return props.propertyConfig?.min || undefined
 }
 
+/**
+ * 获取最大值
+ * @returns 最大值
+ */
 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()
 }
 
+/**
+ * 处理日期变化事件
+ * @param value 日期值
+ */
 const handleDateChange = (value: string) => {
   localValue.value = value || ''
-  validateValue()
 }
 
+/**
+ * 处理数字变化事件
+ * @param value 数字值
+ */
 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,
@@ -363,11 +307,4 @@ watch(
     numberValue.value = undefined
   }
 )
-
-// 初始化
-onMounted(() => {
-  if (localValue.value) {
-    validateValue()
-  }
-})
 </script>

+ 56 - 35
src/views/iot/rule/scene/form/sections/ActionSection.vue

@@ -1,5 +1,4 @@
 <!-- 执行器配置组件 -->
-<!-- todo @puhui999:参考“触发器配置”,简化下。 -->
 <template>
   <el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
     <template #header>
@@ -46,7 +45,7 @@
               <Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
               <span>执行器 {{ index + 1 }}</span>
               <el-tag :type="getActionTypeTag(action.type)" size="small">
-                {{ getActionTypeName(action.type) }}
+                {{ getActionTypeLabel(action.type) }}
               </el-tag>
             </div>
             <div>
@@ -65,11 +64,24 @@
 
           <div class="space-y-16px">
             <!-- 执行类型选择 -->
-            <ActionTypeSelector
-              :model-value="action.type"
-              @update:model-value="(value) => updateActionType(index, value)"
-              @change="onActionTypeChange(action, $event)"
-            />
+            <div class="w-full">
+              <el-form-item label="执行类型" required>
+                <el-select
+                  :model-value="action.type"
+                  @update:model-value="(value) => updateActionType(index, value)"
+                  @change="(value) => onActionTypeChange(action, value)"
+                  placeholder="请选择执行类型"
+                  class="w-full"
+                >
+                  <el-option
+                    v-for="option in getActionTypeOptions()"
+                    :key="option.value"
+                    :label="option.label"
+                    :value="option.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </div>
 
             <!-- 设备控制配置 -->
             <DeviceControlConfig
@@ -119,15 +131,17 @@
 
 <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 { Action } from '@/api/iot/rule/scene/scene.types'
+import type { Action } from '@/api/iot/rule/scene'
 import {
   IotRuleSceneActionTypeEnum as ActionTypeEnum,
   isDeviceAction,
   isAlertAction,
-  getActionTypeLabel
+  getActionTypeLabel,
+  getActionTypeOptions,
+  getActionTypeTag,
+  SCENE_RULE_CONFIG
 } from '@/views/iot/utils/constants'
 
 /** 执行器配置组件 */
@@ -143,8 +157,11 @@ const emit = defineEmits<{
 
 const actions = useVModel(props, 'actions', emit)
 
+const maxActions = SCENE_RULE_CONFIG.MAX_ACTIONS // 最大执行器数量
+
 /**
  * 创建默认的执行器数据
+ * @returns 默认执行器对象
  */
 const createDefaultActionData = (): Action => {
   return {
@@ -152,29 +169,14 @@ const createDefaultActionData = (): Action => {
     productId: undefined,
     deviceId: undefined,
     identifier: undefined, // 物模型标识符(服务调用时使用)
-    params: {},
+    params: undefined,
     alertConfigId: undefined
   }
 }
 
-const maxActions = 5 // 最大执行器数量
-
-// 工具函数
-const getActionTypeName = (type: number) => {
-  return getActionTypeLabel(type)
-}
-
-const getActionTypeTag = (type: number) => {
-  const actionTypeTags = {
-    [ActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
-    [ActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
-    [ActionTypeEnum.ALERT_TRIGGER]: 'danger',
-    [ActionTypeEnum.ALERT_RECOVER]: 'warning'
-  }
-  return actionTypeTags[type] || 'info'
-}
-
-/** 添加执行器 */
+/**
+ * 添加执行器
+ */
 const addAction = () => {
   if (actions.value.length >= maxActions) {
     return
@@ -184,35 +186,54 @@ const addAction = () => {
   actions.value.push(newAction)
 }
 
-/** 删除执行器 */
+/**
+ * 删除执行器
+ * @param index 执行器索引
+ */
 const removeAction = (index: number) => {
   actions.value.splice(index, 1)
 }
 
-/** 更新执行器类型 */
+/**
+ * 更新执行器类型
+ * @param index 执行器索引
+ * @param type 执行器类型
+ */
 const updateActionType = (index: number, type: number) => {
   actions.value[index].type = type
   onActionTypeChange(actions.value[index], type)
 }
 
-/** 更新执行器 */
+/**
+ * 更新执行器
+ * @param index 执行器索引
+ * @param action 执行器对象
+ */
 const updateAction = (index: number, action: Action) => {
   actions.value[index] = action
 }
 
-/** 更新告警配置 */
+/**
+ * 更新告警配置
+ * @param index 执行器索引
+ * @param alertConfigId 告警配置ID
+ */
 const updateActionAlertConfig = (index: number, alertConfigId?: number) => {
   actions.value[index].alertConfigId = alertConfigId
 }
 
-/** 监听执行器类型变化 */
+/**
+ * 监听执行器类型变化
+ * @param action 执行器对象
+ * @param type 执行器类型
+ */
 const onActionTypeChange = (action: Action, type: number) => {
   // 清理不相关的配置,确保数据结构干净
   if (isDeviceAction(type)) {
     // 设备控制类型:清理告警配置,确保设备参数存在
     action.alertConfigId = undefined
     if (!action.params) {
-      action.params = {}
+      action.params = ''
     }
     // 如果从其他类型切换到设备控制类型,清空identifier(让用户重新选择)
     if (action.identifier && type !== action.type) {

+ 3 - 2
src/views/iot/rule/scene/form/sections/BasicInfoSection.vue

@@ -58,7 +58,7 @@
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
+import type { IotSceneRule } from '@/api/iot/rule/scene'
 
 /** 基础信息配置组件 */
 defineOptions({ name: 'BasicInfoSection' })
@@ -67,11 +67,12 @@ const props = defineProps<{
   modelValue: IotSceneRule
   rules?: any
 }>()
+
 const emit = defineEmits<{
   (e: 'update:modelValue', value: IotSceneRule): void
 }>()
 
-const formData = useVModel(props, 'modelValue', emit)
+const formData = useVModel(props, 'modelValue', emit) // 表单数据
 </script>
 
 <style scoped>

+ 59 - 31
src/views/iot/rule/scene/form/sections/TriggerSection.vue

@@ -66,12 +66,31 @@
             />
 
             <!-- 定时触发配置 -->
-            <!-- TODO @puhui999:改成定时触发配置后,就改不回来了。 -->
-            <TimerTriggerConfig
+            <div
               v-else-if="triggerItem.type === TriggerTypeEnum.TIMER"
-              :model-value="triggerItem.cronExpression"
-              @update:model-value="(value) => updateTriggerCronConfig(index, value)"
-            />
+              class="flex flex-col gap-16px"
+            >
+              <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>
+
+              <!-- 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>
+                  <Crontab
+                    :model-value="triggerItem.cronExpression || '0 0 12 * * ?'"
+                    @update:model-value="(value) => updateTriggerCronConfig(index, value)"
+                  />
+                </el-form-item>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -96,12 +115,12 @@
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
 import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue'
-import TimerTriggerConfig from '../configs/TimerTriggerConfig.vue'
-import { TriggerFormData } from '@/api/iot/rule/scene/scene.types'
+import { Crontab } from '@/components/Crontab'
+import type { Trigger } from '@/api/iot/rule/scene'
 import {
-  getTriggerTypeOptions,
+  getTriggerTypeLabel,
+  getTriggerTagType,
   IotRuleSceneTriggerTypeEnum as TriggerTypeEnum,
-  IotRuleSceneTriggerTypeEnum,
   isDeviceTrigger
 } from '@/views/iot/utils/constants'
 
@@ -109,35 +128,20 @@ import {
 defineOptions({ name: 'TriggerSection' })
 
 const props = defineProps<{
-  triggers: TriggerFormData[]
+  triggers: Trigger[]
 }>()
 
 const emit = defineEmits<{
-  (e: 'update:triggers', value: TriggerFormData[]): void
+  (e: 'update:triggers', value: Trigger[]): void
 }>()
 
 const triggers = useVModel(props, 'triggers', emit)
 
-// 触发器类型选项(从 constants 中获取)
-const triggerTypeOptions = getTriggerTypeOptions()
-
-// 工具函数
-// TODO @puhui999:这里是不是重复了哈;
-const getTriggerTypeLabel = (type: number): string => {
-  const option = triggerTypeOptions.find((opt) => opt.value === type)
-  return option?.label || '未知类型'
-}
-
-const getTriggerTagType = (type: number): string => {
-  if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
-    return 'warning'
-  }
-  return isDeviceTrigger(type) ? 'success' : 'info'
-}
-
-// 事件处理函数
+/**
+ * 添加触发器
+ */
 const addTrigger = () => {
-  const newTrigger: TriggerFormData = {
+  const newTrigger: Trigger = {
     type: TriggerTypeEnum.DEVICE_STATE_UPDATE,
     productId: undefined,
     deviceId: undefined,
@@ -150,25 +154,49 @@ const addTrigger = () => {
   triggers.value.push(newTrigger)
 }
 
+/**
+ * 删除触发器
+ * @param index 触发器索引
+ */
 const removeTrigger = (index: number) => {
   if (triggers.value.length > 1) {
     triggers.value.splice(index, 1)
   }
 }
 
+/**
+ * 更新触发器类型
+ * @param index 触发器索引
+ * @param type 触发器类型
+ */
 const updateTriggerType = (index: number, type: number) => {
   triggers.value[index].type = type
   onTriggerTypeChange(index, type)
 }
 
-const updateTriggerDeviceConfig = (index: number, newTrigger: TriggerFormData) => {
+/**
+ * 更新触发器设备配置
+ * @param index 触发器索引
+ * @param newTrigger 新的触发器对象
+ */
+const updateTriggerDeviceConfig = (index: number, newTrigger: Trigger) => {
   triggers.value[index] = newTrigger
 }
 
+/**
+ * 更新触发器CRON配置
+ * @param index 触发器索引
+ * @param cronExpression CRON表达式
+ */
 const updateTriggerCronConfig = (index: number, cronExpression?: string) => {
   triggers.value[index].cronExpression = cronExpression
 }
 
+/**
+ * 处理触发器类型变化事件
+ * @param index 触发器索引
+ * @param _ 触发器类型(未使用)
+ */
 const onTriggerTypeChange = (index: number, _: number) => {
   const triggerItem = triggers.value[index]
   triggerItem.productId = undefined

+ 0 - 113
src/views/iot/rule/scene/form/selectors/ActionTypeSelector.vue

@@ -1,113 +0,0 @@
-<!-- 执行器类型选择组件 -->
-<template>
-  <div class="w-full">
-    <!-- TODO @puhui999:1)设备属性设置时,貌似没选属性;2)服务调用时,貌似也没的设置哈; -->
-    <!-- TODO @puhui999:执行器的样式风格,需要统一; -->
-    <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="flex items-center justify-between w-full py-4px">
-            <div class="flex items-center gap-12px flex-1">
-              <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>
-            <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 '@/views/iot/utils/constants'
-
-/** 执行器类型选择组件 */
-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)
-
-// 执行器类型选项
-// TODO @puhui999:我们是不是弱化 icon 和 tag;所有组件,让整体交互更简洁和一致;
-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>
-:deep(.el-select-dropdown__item) {
-  height: auto;
-  padding: 8px 20px;
-}
-</style>

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

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

+ 20 - 33
src/views/iot/rule/scene/form/selectors/DeviceSelector.vue

@@ -24,11 +24,11 @@
           <div class="text-12px text-[var(--el-text-color-secondary)]">{{ device.deviceKey }}</div>
         </div>
         <div class="flex items-center gap-4px">
-          <el-tag size="small" :type="getStatusType(device.status)">
-            {{ getStatusText(device.status) }}
+          <el-tag size="small" :type="getDeviceEnableStatusTagType(device.status)">
+            {{ getDeviceEnableStatusText(device.status) }}
           </el-tag>
-          <el-tag size="small" :type="device.activeTime ? 'success' : 'info'">
-            {{ device.activeTime ? '已激活' : '未激活' }}
+          <el-tag size="small" :type="getDeviceActiveStatus(device.activeTime).tagType">
+            {{ getDeviceActiveStatus(device.activeTime).text }}
           </el-tag>
         </div>
       </div>
@@ -38,6 +38,12 @@
 
 <script setup lang="ts">
 import { DeviceApi } from '@/api/iot/device/device'
+import {
+  getDeviceEnableStatusText,
+  getDeviceEnableStatusTagType,
+  getDeviceActiveStatus,
+  DEVICE_SELECTOR_OPTIONS
+} from '@/views/iot/utils/constants'
 
 /** 设备选择器组件 */
 defineOptions({ name: 'DeviceSelector' })
@@ -52,17 +58,21 @@ const emit = defineEmits<{
   (e: 'change', value?: number): void
 }>()
 
-// 状态
-const deviceLoading = ref(false)
-const deviceList = ref<any[]>([])
+const deviceLoading = ref(false) // 设备加载状态
+const deviceList = ref<any[]>([]) // 设备列表
 
-// 事件处理
+/**
+ * 处理选择变化事件
+ * @param value 选中的设备ID
+ */
 const handleChange = (value?: number) => {
   emit('update:modelValue', value)
   emit('change', value)
 }
 
-// 获取设备列表
+/**
+ * 获取设备列表
+ */
 const getDeviceList = async () => {
   if (!props.productId) {
     deviceList.value = []
@@ -77,34 +87,11 @@ const getDeviceList = async () => {
     console.error('获取设备列表失败:', error)
     deviceList.value = []
   } finally {
-    deviceList.value.push({ id: 0, deviceName: '全部设备' })
+    deviceList.value.push(DEVICE_SELECTOR_OPTIONS.ALL_DEVICES)
     deviceLoading.value = false
   }
 }
 
-// 设备状态映射
-const getStatusType = (status: number) => {
-  switch (status) {
-    case 0:
-      return 'success' // 正常
-    case 1:
-      return 'danger' // 禁用
-    default:
-      return 'info'
-  }
-}
-
-const getStatusText = (status: number) => {
-  switch (status) {
-    case 0:
-      return '正常'
-    case 1:
-      return '禁用'
-    default:
-      return '未知'
-  }
-}
-
 // 监听产品变化
 watch(
   () => props.productId,

+ 84 - 15
src/views/iot/rule/scene/form/selectors/OperatorSelector.vue

@@ -35,7 +35,10 @@
 
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
-import { IotRuleSceneTriggerConditionParameterOperatorEnum } from '@/views/iot/utils/constants'
+import {
+  IotRuleSceneTriggerConditionParameterOperatorEnum,
+  IoTDataSpecsDataTypeEnum
+} from '@/views/iot/utils/constants'
 
 /** 操作符选择器组件 */
 defineOptions({ name: 'OperatorSelector' })
@@ -60,7 +63,14 @@ const allOperators = [
     symbol: '=',
     description: '值完全相等时触发',
     example: 'temperature = 25',
-    supportedTypes: ['int', 'float', 'double', 'string', 'bool', 'enum']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.TEXT,
+      IoTDataSpecsDataTypeEnum.BOOL,
+      IoTDataSpecsDataTypeEnum.ENUM
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
@@ -68,7 +78,14 @@ const allOperators = [
     symbol: '≠',
     description: '值不相等时触发',
     example: 'power != false',
-    supportedTypes: ['int', 'float', 'double', 'string', 'bool', 'enum']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.TEXT,
+      IoTDataSpecsDataTypeEnum.BOOL,
+      IoTDataSpecsDataTypeEnum.ENUM
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN.value,
@@ -76,7 +93,12 @@ const allOperators = [
     symbol: '>',
     description: '值大于指定值时触发',
     example: 'temperature > 30',
-    supportedTypes: ['int', 'float', 'double', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS.value,
@@ -84,7 +106,12 @@ const allOperators = [
     symbol: '≥',
     description: '值大于或等于指定值时触发',
     example: 'humidity >= 80',
-    supportedTypes: ['int', 'float', 'double', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN.value,
@@ -92,7 +119,12 @@ const allOperators = [
     symbol: '<',
     description: '值小于指定值时触发',
     example: 'temperature < 10',
-    supportedTypes: ['int', 'float', 'double', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS.value,
@@ -100,7 +132,12 @@ const allOperators = [
     symbol: '≤',
     description: '值小于或等于指定值时触发',
     example: 'battery <= 20',
-    supportedTypes: ['int', 'float', 'double', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.IN.value,
@@ -108,7 +145,12 @@ const allOperators = [
     symbol: '∈',
     description: '值在指定列表中时触发',
     example: 'status in [1,2,3]',
-    supportedTypes: ['int', 'float', 'string', 'enum']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.TEXT,
+      IoTDataSpecsDataTypeEnum.ENUM
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN.value,
@@ -116,7 +158,12 @@ const allOperators = [
     symbol: '∉',
     description: '值不在指定列表中时触发',
     example: 'status not in [1,2,3]',
-    supportedTypes: ['int', 'float', 'string', 'enum']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.TEXT,
+      IoTDataSpecsDataTypeEnum.ENUM
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value,
@@ -124,7 +171,12 @@ const allOperators = [
     symbol: '⊆',
     description: '值在指定范围内时触发',
     example: 'temperature between 20,30',
-    supportedTypes: ['int', 'float', 'double', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN.value,
@@ -132,7 +184,12 @@ const allOperators = [
     symbol: '⊄',
     description: '值不在指定范围内时触发',
     example: 'temperature not between 20,30',
-    supportedTypes: ['int', 'float', 'double', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.LIKE.value,
@@ -140,7 +197,7 @@ const allOperators = [
     symbol: '≈',
     description: '字符串匹配指定模式时触发',
     example: 'message like "%error%"',
-    supportedTypes: ['string']
+    supportedTypes: [IoTDataSpecsDataTypeEnum.TEXT]
   },
   {
     value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value,
@@ -148,11 +205,19 @@ const allOperators = [
     symbol: '≠∅',
     description: '值非空时触发',
     example: 'data not null',
-    supportedTypes: ['int', 'float', 'double', 'string', 'bool', 'enum', 'date']
+    supportedTypes: [
+      IoTDataSpecsDataTypeEnum.INT,
+      IoTDataSpecsDataTypeEnum.FLOAT,
+      IoTDataSpecsDataTypeEnum.DOUBLE,
+      IoTDataSpecsDataTypeEnum.TEXT,
+      IoTDataSpecsDataTypeEnum.BOOL,
+      IoTDataSpecsDataTypeEnum.ENUM,
+      IoTDataSpecsDataTypeEnum.DATE
+    ]
   }
 ]
 
-// 计算属性
+// 计算属性:可用的操作符
 const availableOperators = computed(() => {
   if (!props.propertyType) {
     return allOperators
@@ -161,11 +226,15 @@ const availableOperators = computed(() => {
   return allOperators.filter((op) => op.supportedTypes.includes(props.propertyType!))
 })
 
+// 计算属性:当前选中的操作符
 const selectedOperator = computed(() => {
   return allOperators.find((op) => op.value === localValue.value)
 })
 
-// 事件处理
+/**
+ * 处理选择变化事件
+ * @param value 选中的操作符值
+ */
 const handleChange = (value: string) => {
   emit('change', value)
 }

+ 0 - 317
src/views/iot/rule/scene/form/selectors/ProductDeviceSelector.vue

@@ -1,317 +0,0 @@
-<!-- 产品设备选择器组件 -->
-<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="flex items-center justify-between w-full py-4px">
-                <div class="flex-1">
-                  <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">
-                    {{ product.name }}
-                  </div>
-                  <div class="text-12px text-[var(--el-text-color-secondary)]">
-                    {{ product.productKey }}
-                  </div>
-                </div>
-                <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
-              </div>
-            </el-option>
-          </el-select>
-        </el-form-item>
-      </el-col>
-
-      <!-- 设备选择模式 -->
-      <el-col :span="12">
-        <el-form-item label="设备选择模式" required>
-          <el-radio-group
-            v-model="deviceSelectionMode"
-            @change="handleDeviceSelectionModeChange"
-            :disabled="!localProductId"
-          >
-            <el-radio value="all">全部设备</el-radio>
-            <el-radio value="specific">选择设备</el-radio>
-          </el-radio-group>
-          <div
-            v-if="!localProductId"
-            class="text-12px text-[var(--el-text-color-placeholder)] mt-4px"
-          >
-            请先选择产品
-          </div>
-        </el-form-item>
-      </el-col>
-    </el-row>
-
-    <!-- 具体设备选择 -->
-    <el-row v-if="deviceSelectionMode === 'specific'" :gutter="16">
-      <el-col :span="24">
-        <el-form-item label="选择设备" required>
-          <el-select
-            v-model="localDeviceId"
-            :placeholder="localProductId ? '请选择设备' : '请先选择产品'"
-            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="flex items-center justify-between w-full py-4px">
-                <div class="flex-1">
-                  <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">
-                    {{ device.deviceName }}
-                  </div>
-                  <div class="text-12px text-[var(--el-text-color-secondary)]">
-                    {{ device.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 !== undefined"
-      class="mt-16px p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"
-    >
-      <div class="flex items-center gap-6px mb-8px">
-        <Icon icon="ep:check" class="text-[var(--el-color-success)] text-16px" />
-        <span class="text-14px font-500 text-[var(--el-text-color-primary)]">已选择设备</span>
-      </div>
-      <div class="flex flex-col gap-6px ml-22px">
-        <div class="flex items-center gap-8px">
-          <span class="text-12px text-[var(--el-text-color-secondary)] min-w-40px">产品:</span>
-          <span class="text-12px text-[var(--el-text-color-primary)] font-500">
-            {{ selectedProduct?.name }}
-          </span>
-          <el-tag size="small" type="primary">{{ selectedProduct?.productKey }}</el-tag>
-        </div>
-        <div class="flex items-center gap-8px">
-          <span class="text-12px text-[var(--el-text-color-secondary)] min-w-40px">设备:</span>
-          <span
-            v-if="deviceSelectionMode === 'all'"
-            class="text-12px text-[var(--el-text-color-primary)] font-500"
-            >全部设备</span
-          >
-          <span v-else class="text-12px text-[var(--el-text-color-primary)] font-500">
-            {{ selectedDevice?.deviceName }}
-          </span>
-          <el-tag v-if="deviceSelectionMode === 'all'" size="small" type="warning"> 全部</el-tag>
-          <el-tag v-else 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'
-import { DICT_TYPE } from '@/utils/dict'
-
-/** 产品设备选择器组件 */
-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 deviceSelectionMode = ref<'specific' | 'all'>('specific')
-
-// 数据状态
-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)
-})
-
-// TODO @puhui999:字典下;
-// 设备状态映射
-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'
-  }
-}
-
-// TODO @puhui999:注释风格哈
-// 事件处理
-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 handleDeviceSelectionModeChange = (mode: 'specific' | 'all') => {
-  deviceSelectionMode.value = mode
-  if (mode === 'all') {
-    // 全部设备时,设备 ID 设为 0
-    localDeviceId.value = 0
-  } else {
-    // 选择设备时,清空设备 ID
-    localDeviceId.value = undefined
-  }
-  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)
-    // 模拟数据
-    // TODO @puhui999:移除下,不太合理
-    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)
-    // 模拟数据
-    // TODO @puhui999:移除下,不太合理
-    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()
-
-  // 根据初始设备 ID 设置选择模式
-  if (localDeviceId.value === 0) {
-    deviceSelectionMode.value = 'all'
-  } else if (localDeviceId.value) {
-    deviceSelectionMode.value = 'specific'
-  }
-
-  if (localProductId.value) {
-    await getDeviceList(localProductId.value)
-  }
-})
-
-// 监听产品变化
-watch(
-  () => localProductId.value,
-  async (newProductId) => {
-    if (newProductId && deviceList.value.length === 0) {
-      await getDeviceList(newProductId)
-    }
-  }
-)
-// TODO @puhui999:是不是 unocss
-</script>
-
-<style scoped>
-:deep(.el-select-dropdown__item) {
-  height: auto;
-  padding: 8px 20px;
-}
-</style>

+ 9 - 5
src/views/iot/rule/scene/form/selectors/ProductSelector.vue

@@ -46,17 +46,21 @@ const emit = defineEmits<{
   (e: 'change', value?: number): void
 }>()
 
-// 状态
-const productLoading = ref(false)
-const productList = ref<any[]>([])
+const productLoading = ref(false) // 产品加载状态
+const productList = ref<any[]>([]) // 产品列表
 
-// 事件处理
+/**
+ * 处理选择变化事件
+ * @param value 选中的产品ID
+ */
 const handleChange = (value?: number) => {
   emit('update:modelValue', value)
   emit('change', value)
 }
 
-// 获取产品列表
+/**
+ * 获取产品列表
+ */
 const getProductList = async () => {
   try {
     productLoading.value = true

+ 80 - 114
src/views/iot/rule/scene/form/selectors/PropertySelector.vue

@@ -18,20 +18,17 @@
           :label="property.name"
           :value="property.identifier"
         >
-          <div class="flex items-center justify-between w-full py-4px">
-            <div class="flex-1">
-              <div class="text-14px font-500 text-[var(--el-text-color-primary)] mb-2px">
-                {{ property.name }}
-              </div>
-              <div class="text-12px text-[var(--el-text-color-secondary)]">
-                {{ property.identifier }}
-              </div>
-            </div>
-            <div class="flex-shrink-0">
-              <el-tag :type="getPropertyTypeTag(property.dataType)" size="small">
-                {{ getPropertyTypeName(property.dataType) }}
-              </el-tag>
-            </div>
+          <div class="flex items-center justify-between w-full py-2px">
+            <span class="text-14px font-500 text-[var(--el-text-color-primary)] flex-1 truncate">
+              {{ property.name }}
+            </span>
+            <el-tag
+              :type="getDataTypeTagType(property.dataType)"
+              size="small"
+              class="ml-8px flex-shrink-0"
+            >
+              {{ property.identifier }}
+            </el-tag>
           </div>
         </el-option>
       </el-option-group>
@@ -65,8 +62,8 @@
           <span class="text-14px font-500 text-[var(--el-text-color-primary)]">
             {{ selectedProperty.name }}
           </span>
-          <el-tag :type="getPropertyTypeTag(selectedProperty.dataType)" size="small">
-            {{ getPropertyTypeName(selectedProperty.dataType) }}
+          <el-tag :type="getDataTypeTagType(selectedProperty.dataType)" size="small">
+            {{ getDataTypeName(selectedProperty.dataType) }}
           </el-tag>
         </div>
 
@@ -119,7 +116,7 @@
               访问模式:
             </span>
             <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
-              {{ getAccessModeText(selectedProperty.accessMode) }}
+              {{ getAccessModeLabel(selectedProperty.accessMode) }}
             </span>
           </div>
 
@@ -133,7 +130,7 @@
               事件类型:
             </span>
             <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
-              {{ getEventTypeText(selectedProperty.eventType) }}
+              {{ getEventTypeLabel(selectedProperty.eventType) }}
             </span>
           </div>
 
@@ -147,7 +144,7 @@
               调用类型:
             </span>
             <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
-              {{ getCallTypeText(selectedProperty.callType) }}
+              {{ getThingModelServiceCallTypeLabel(selectedProperty.callType) }}
             </span>
           </div>
         </div>
@@ -159,13 +156,48 @@
 <script setup lang="ts">
 import { useVModel } from '@vueuse/core'
 import { InfoFilled } from '@element-plus/icons-vue'
-import { IotRuleSceneTriggerTypeEnum, IoTThingModelTypeEnum } from '@/views/iot/utils/constants'
+import {
+  IotRuleSceneTriggerTypeEnum,
+  IoTThingModelTypeEnum,
+  getAccessModeLabel,
+  getEventTypeLabel,
+  getThingModelServiceCallTypeLabel,
+  getDataTypeName,
+  getDataTypeTagType,
+  THING_MODEL_GROUP_LABELS
+} from '@/views/iot/utils/constants'
+import type {
+  IotThingModelTSLResp,
+  ThingModelEvent,
+  ThingModelParam,
+  ThingModelProperty,
+  ThingModelService
+} from '@/api/iot/thingmodel'
 import { ThingModelApi } from '@/api/iot/thingmodel'
-import type { IotThingModelTSLRespVO, PropertySelectorItem } from '@/api/iot/rule/scene/scene.types'
 
 /** 属性选择器组件 */
 defineOptions({ name: 'PropertySelector' })
 
+/** 属性选择器内部使用的统一数据结构 */
+interface PropertySelectorItem {
+  identifier: string
+  name: string
+  description?: string
+  dataType: string
+  type: number // IoTThingModelTypeEnum
+  accessMode?: string
+  required?: boolean
+  unit?: string
+  range?: string
+  eventType?: string
+  callType?: string
+  inputParams?: ThingModelParam[]
+  outputParams?: ThingModelParam[]
+  property?: ThingModelProperty
+  event?: ThingModelEvent
+  service?: ThingModelService
+}
+
 const props = defineProps<{
   modelValue?: string
   triggerType: number
@@ -180,32 +212,31 @@ const emit = defineEmits<{
 
 const localValue = useVModel(props, 'modelValue', emit)
 
-// 状态
-const loading = ref(false)
-const propertyList = ref<PropertySelectorItem[]>([])
-const thingModelTSL = ref<IotThingModelTSLRespVO | null>(null)
+const loading = ref(false) // 加载状态
+const propertyList = ref<PropertySelectorItem[]>([]) // 属性列表
+const thingModelTSL = ref<IotThingModelTSLResp | null>(null) // 物模型TSL数据
 
-// 计算属性
+// 计算属性:属性分组
 const propertyGroups = computed(() => {
   const groups: { label: string; options: any[] }[] = []
 
   if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST) {
     groups.push({
-      label: '设备属性',
+      label: THING_MODEL_GROUP_LABELS.PROPERTY,
       options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.PROPERTY)
     })
   }
 
   if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) {
     groups.push({
-      label: '设备事件',
+      label: THING_MODEL_GROUP_LABELS.EVENT,
       options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.EVENT)
     })
   }
 
   if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) {
     groups.push({
-      label: '设备服务',
+      label: THING_MODEL_GROUP_LABELS.SERVICE,
       options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.SERVICE)
     })
   }
@@ -213,71 +244,15 @@ const propertyGroups = computed(() => {
   return groups.filter((group) => group.options.length > 0)
 })
 
+// 计算属性:当前选中的属性
 const selectedProperty = computed(() => {
   return propertyList.value.find((p) => p.identifier === localValue.value)
 })
 
-// 工具函数
-const getPropertyTypeName = (dataType: string) => {
-  const typeMap = {
-    int: '整数',
-    float: '浮点数',
-    double: '双精度',
-    text: '字符串',
-    bool: '布尔值',
-    enum: '枚举',
-    date: '日期',
-    struct: '结构体',
-    array: '数组'
-  }
-  return typeMap[dataType] || dataType
-}
-
-const getPropertyTypeTag = (dataType: string) => {
-  const tagMap = {
-    int: 'primary',
-    float: 'success',
-    double: 'success',
-    text: 'info',
-    bool: 'warning',
-    enum: 'danger',
-    date: 'primary',
-    struct: 'info',
-    array: 'warning'
-  }
-  return tagMap[dataType] || 'info'
-}
-
-// 工具函数 - 获取访问模式文本
-const getAccessModeText = (accessMode: string) => {
-  const modeMap = {
-    r: '只读',
-    w: '只写',
-    rw: '读写'
-  }
-  return modeMap[accessMode] || accessMode
-}
-
-// 工具函数 - 获取事件类型文本
-const getEventTypeText = (eventType: string) => {
-  const typeMap = {
-    info: '信息',
-    alert: '告警',
-    error: '故障'
-  }
-  return typeMap[eventType] || eventType
-}
-
-// 工具函数 - 获取调用类型文本
-const getCallTypeText = (callType: string) => {
-  const typeMap = {
-    sync: '同步',
-    async: '异步'
-  }
-  return typeMap[callType] || callType
-}
-
-// 事件处理
+/**
+ * 处理选择变化事件
+ * @param value 选中的属性标识符
+ */
 const handleChange = (value: string) => {
   const property = propertyList.value.find((p) => p.identifier === value)
   if (property) {
@@ -306,37 +281,20 @@ const getThingModelTSL = async () => {
       thingModelTSL.value = tslData
       parseThingModelData()
     } else {
-      // 如果TSL获取失败,尝试获取物模型列表
-      await getThingModelList()
+      console.error('获取物模型TSL失败: 返回数据为空')
+      propertyList.value = []
     }
   } catch (error) {
     console.error('获取物模型TSL失败:', error)
-    // 如果TSL获取失败,尝试获取物模型列表
-    await getThingModelList()
+    propertyList.value = []
   } finally {
     loading.value = false
   }
 }
 
 /**
- * 获取物模型列表(备用方案)
+ * 解析物模型TSL数据
  */
-const getThingModelList = async () => {
-  if (!props.productId) {
-    propertyList.value = []
-    return
-  }
-
-  try {
-    const data = await ThingModelApi.getThingModelList({ productId: props.productId })
-    propertyList.value = data || []
-  } catch (error) {
-    console.error('获取物模型列表失败:', error)
-    propertyList.value = []
-  }
-}
-
-// 解析物模型TSL数据
 const parseThingModelData = () => {
   const tsl = thingModelTSL.value
   const properties: PropertySelectorItem[] = []
@@ -399,7 +357,11 @@ const parseThingModelData = () => {
   propertyList.value = properties
 }
 
-// 获取属性单位
+/**
+ * 获取属性单位
+ * @param property 属性对象
+ * @returns 属性单位
+ */
 const getPropertyUnit = (property: any) => {
   if (!property) return undefined
 
@@ -411,7 +373,11 @@ const getPropertyUnit = (property: any) => {
   return undefined
 }
 
-// 获取属性范围描述
+/**
+ * 获取属性范围描述
+ * @param property 属性对象
+ * @returns 属性范围描述
+ */
 const getPropertyRange = (property: any) => {
   if (!property) return undefined
 
@@ -454,7 +420,7 @@ watch(
 /* 下拉选项样式 */
 :deep(.el-select-dropdown__item) {
   height: auto;
-  padding: 8px 20px;
+  padding: 6px 20px;
 }
 
 /* 弹出层内容样式 */

+ 94 - 122
src/views/iot/rule/scene/index.vue

@@ -136,13 +136,13 @@
             <div
               class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#43e97b] to-[#38f9d7]"
             >
-              <Icon icon="ep:lightning" />
+              <Icon icon="ep:timer" />
             </div>
             <div>
               <div class="text-24px font-600 text-[#303133] leading-none">{{
-                statistics.triggered
+                statistics.timerRules
               }}</div>
-              <div class="text-14px text-[#909399] mt-4px">今日触发</div>
+              <div class="text-14px text-[#909399] mt-4px">定时规则</div>
             </div>
           </div>
         </el-card>
@@ -165,12 +165,27 @@
           </template>
         </el-table-column>
         <!-- 触发条件列 -->
-        <el-table-column label="触发条件" min-width="250">
+        <el-table-column label="触发条件" min-width="280">
           <template #default="{ row }">
-            <div class="flex flex-wrap gap-4px">
-              <el-tag type="primary" size="small" class="m-0">
-                {{ getTriggerSummary(row) }}
-              </el-tag>
+            <div class="space-y-4px">
+              <div class="flex flex-wrap gap-4px">
+                <el-tag type="primary" size="small" class="m-0">
+                  {{ getTriggerSummary(row) }}
+                </el-tag>
+              </div>
+              <!-- 显示定时触发器的额外信息 -->
+              <div v-if="hasTimerTrigger(row)" class="mt-4px">
+                <el-tooltip :content="getCronExpression(row)" placement="top">
+                  <el-tag size="small" type="info" class="mr-4px">
+                    <Icon icon="ep:timer" class="mr-2px" />
+                    {{ getCronFrequency(row) }}
+                  </el-tag>
+                </el-tooltip>
+                <div v-if="getNextExecutionTime(row)" class="text-12px text-[#909399] mt-2px">
+                  <Icon icon="ep:clock" class="mr-2px" />
+                  下次执行: {{ formatDate(getNextExecutionTime(row)!) }}
+                </div>
+              </div>
             </div>
           </template>
         </el-table-column>
@@ -210,7 +225,14 @@
                 @click="handleToggleStatus(row)"
               >
                 <Icon :icon="row.status === 0 ? 'ep:video-pause' : 'ep:video-play'" />
-                {{ getDictLabel(DICT_TYPE.COMMON_STATUS, row.status === 0 ? 1 : 0) }}
+                {{
+                  getDictLabel(
+                    DICT_TYPE.COMMON_STATUS,
+                    row.status === CommonStatusEnum.ENABLE
+                      ? CommonStatusEnum.DISABLE
+                      : CommonStatusEnum.ENABLE
+                  )
+                }}
               </el-button>
               <el-button type="danger" class="!mr-10px" link @click="handleDelete(row.id)">
                 <Icon icon="ep:delete" />
@@ -236,18 +258,18 @@
 </template>
 
 <script setup lang="ts">
-import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
+import { DICT_TYPE, getDictLabel, getIntDictOptions } from '@/utils/dict'
 import { ContentWrap } from '@/components/ContentWrap'
 import RuleSceneForm from './form/RuleSceneForm.vue'
-import { IotSceneRule } from '@/api/iot/rule/scene/scene.types'
-import { RuleSceneApi } from '@/api/iot/rule/scene'
+import { IotSceneRule, RuleSceneApi } from '@/api/iot/rule/scene'
 import {
-  IotRuleSceneTriggerTypeEnum,
-  IotRuleSceneActionTypeEnum,
+  getActionTypeLabel,
   getTriggerTypeLabel,
-  getActionTypeLabel
+  IotRuleSceneTriggerTypeEnum
 } from '@/views/iot/utils/constants'
 import { formatDate } from '@/utils/formatTime'
+import { CommonStatusEnum } from '@/utils/constants'
+import { CronUtils } from '@/utils/cron'
 
 /** 场景联动规则管理页面 */
 defineOptions({ name: 'IoTSceneRule' })
@@ -260,7 +282,7 @@ const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   name: '',
-  status: undefined as number | undefined
+  status: undefined
 })
 
 const loading = ref(true) // 列表的加载中
@@ -278,85 +300,28 @@ const statistics = ref({
   total: 0,
   enabled: 0,
   disabled: 0,
-  triggered: 0
+  triggered: 0,
+  timerRules: 0 // 定时规则数量
 })
 
-/** 格式化 CRON 表达式显示 */
-/** 注:后续可考虑将此功能移至 CRON 组件内部 */
-// TODO @puhui999:优化这个format
-const 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
-}
-
 /** 获取规则摘要信息 */
 const getRuleSceneSummary = (rule: IotSceneRule) => {
   const triggerSummary =
     rule.triggers?.map((trigger: any) => {
       // 构建基础描述
-      let description = ''
-
+      let description = getTriggerTypeLabel(trigger.type)
       switch (trigger.type) {
         case IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE:
-          description = '设备状态变更'
           break
         case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
-          description = '属性上报'
-          if (trigger.identifier) {
-            description += ` (${trigger.identifier})`
-          }
-          break
         case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
-          description = '事件上报'
-          if (trigger.identifier) {
-            description += ` (${trigger.identifier})`
-          }
-          break
         case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
-          description = '服务调用'
           if (trigger.identifier) {
             description += ` (${trigger.identifier})`
           }
           break
         case IotRuleSceneTriggerTypeEnum.TIMER:
-          description = `定时触发 (${formatCronExpression(trigger.cronExpression || '')})`
+          description = `${getTriggerTypeLabel(trigger.type)} (${CronUtils.format(trigger.cronExpression || '')})`
           break
         default:
           description = getTriggerTypeLabel(trigger.type)
@@ -375,24 +340,7 @@ const getRuleSceneSummary = (rule: IotSceneRule) => {
   const actionSummary =
     rule.actions?.map((action: any) => {
       // 构建基础描述
-      let description = ''
-
-      switch (action.type) {
-        case IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET:
-          description = '设备属性设置'
-          break
-        case IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE:
-          description = '设备服务调用'
-          break
-        case IotRuleSceneActionTypeEnum.ALERT_TRIGGER:
-          description = '发送告警通知'
-          break
-        case IotRuleSceneActionTypeEnum.ALERT_RECOVER:
-          description = '发送恢复通知'
-          break
-        default:
-          description = getActionTypeLabel(action.type)
-      }
+      let description = getActionTypeLabel(action.type)
 
       // 添加设备信息(如果有)
       if (action.deviceId) {
@@ -419,26 +367,12 @@ const getRuleSceneSummary = (rule: IotSceneRule) => {
 const getList = async () => {
   loading.value = true
   try {
-    // TODO @puhui999:这里的注释优化下;
-    // 调用真实API获取数据
     const data = await RuleSceneApi.getRuleScenePage(queryParams)
     list.value = data.list
     total.value = data.total
-
-    // 更新统计数据
-    updateStatistics()
-  } catch (error) {
-    console.error('获取列表失败:', error)
-    // TODO @puhui999:这里的处理,是不是和其他模块一致哈;
-    ElMessage.error('获取列表失败')
-
-    // 清空列表数据
-    list.value = []
-    total.value = 0
-
+  } finally {
     // 更新统计数据
     updateStatistics()
-  } finally {
     loading.value = false
   }
 }
@@ -447,10 +381,12 @@ const getList = async () => {
 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,
+    enabled: list.value.filter((item) => item.status === CommonStatusEnum.ENABLE).length,
+    disabled: list.value.filter((item) => item.status === CommonStatusEnum.DISABLE).length,
     // 已触发的规则数量 (暂时使用启用状态的规则数量)
-    triggered: list.value.filter((item) => item.status === 0).length
+    triggered: list.value.filter((item) => item.status === CommonStatusEnum.ENABLE).length,
+    // 定时规则数量
+    timerRules: list.value.filter((item) => hasTimerTrigger(item)).length
   }
 }
 
@@ -464,6 +400,43 @@ const getActionSummary = (rule: IotSceneRule) => {
   return getRuleSceneSummary(rule).actionSummary
 }
 
+/** 检查规则是否包含定时触发器 */
+const hasTimerTrigger = (rule: IotSceneRule): boolean => {
+  return (
+    rule.triggers?.some((trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) || false
+  )
+}
+
+/** 获取 CRON 表达式的执行频率描述 */
+const getCronFrequency = (rule: IotSceneRule): string => {
+  const timerTrigger = rule.triggers?.find(
+    (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
+  )
+  if (timerTrigger?.cronExpression) {
+    return CronUtils.getFrequencyDescription(timerTrigger.cronExpression)
+  }
+  return ''
+}
+
+/** 获取下次执行时间 */
+const getNextExecutionTime = (rule: IotSceneRule): Date | null => {
+  const timerTrigger = rule.triggers?.find(
+    (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
+  )
+  if (timerTrigger?.cronExpression) {
+    return CronUtils.getNextExecutionTime(timerTrigger.cronExpression)
+  }
+  return null
+}
+
+/** 获取 CRON 表达式原始值 */
+const getCronExpression = (rule: IotSceneRule): string => {
+  const timerTrigger = rule.triggers?.find(
+    (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
+  )
+  return timerTrigger?.cronExpression || ''
+}
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
@@ -499,28 +472,27 @@ const handleDelete = async (id: number) => {
     message.success(t('common.delSuccess'))
     // 刷新列表
     await getList()
-  } catch (error) {
-    console.error('删除失败:', error)
-    ElMessage.error('删除失败')
-  }
+  } catch (error) {}
 }
 
 /** 修改状态 */
 const handleToggleStatus = async (row: IotSceneRule) => {
   try {
     // 修改状态的二次确认
-    // TODO @puhui999:status 枚举;
-    const text = row.status === 0 ? '禁用' : '启用'
+    const text = row.status === CommonStatusEnum.ENABLE ? '禁用' : '启用'
     await message.confirm('确认要' + text + '"' + row.name + '"吗?')
     // 发起修改状态
-    await RuleSceneApi.updateRuleSceneStatus(row.id!, row.status === 0 ? 1 : 0)
+    await RuleSceneApi.updateRuleSceneStatus(
+      row.id!,
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+    )
     message.success(text + '成功')
     // 刷新
     await getList()
-    updateStatistics()
   } catch {
     // 取消后,进行恢复按钮
-    row.status = row.status === 0 ? 1 : 0
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
   }
 }
 

+ 2 - 2
src/views/iot/thingmodel/dataSpecs/ThingModelEnumDataSpecs.vue

@@ -46,14 +46,14 @@
 import { useVModel } from '@vueuse/core'
 import { isEmpty } from '@/utils/is'
 import { IoTDataSpecsDataTypeEnum } from '@/views/iot/utils/constants'
-import { DataSpecsEnumOrBoolDataVO } from '@/api/iot/thingmodel'
+import { DataSpecsEnumOrBoolData } from '@/api/iot/thingmodel'
 
 /** 枚举型的 dataSpecs 配置组件 */
 defineOptions({ name: 'ThingModelEnumDataSpecs' })
 
 const props = defineProps<{ modelValue: any }>()
 const emits = defineEmits(['update:modelValue'])
-const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<DataSpecsEnumOrBoolDataVO[]>
+const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<DataSpecsEnumOrBoolData[]>
 const message = useMessage()
 
 /** 添加枚举项 */

+ 2 - 2
src/views/iot/thingmodel/dataSpecs/ThingModelNumberDataSpecs.vue

@@ -60,14 +60,14 @@
 <script lang="ts" setup>
 import { useVModel } from '@vueuse/core'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
-import { DataSpecsNumberDataVO } from '@/api/iot/thingmodel'
+import { DataSpecsNumberData } from '@/api/iot/thingmodel'
 
 /** 数值型的 dataSpecs 配置组件 */
 defineOptions({ name: 'ThingModelNumberDataSpecs' })
 
 const props = defineProps<{ modelValue: any }>()
 const emits = defineEmits(['update:modelValue'])
-const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<DataSpecsNumberDataVO>
+const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<DataSpecsNumberData>
 
 /** 单位发生变化时触发 */
 const unitChange = (UnitSpecs: string) => {

+ 311 - 23
src/views/iot/utils/constants.ts

@@ -158,6 +158,47 @@ export const getDataTypeOptionsLabel = (value: string) => {
   return dataType && `${dataType.value}(${dataType.label})`
 }
 
+/** 获取数据类型显示名称(用于属性选择器) */
+export const getDataTypeName = (dataType: string): string => {
+  const typeMap = {
+    [IoTDataSpecsDataTypeEnum.INT]: '整数',
+    [IoTDataSpecsDataTypeEnum.FLOAT]: '浮点数',
+    [IoTDataSpecsDataTypeEnum.DOUBLE]: '双精度',
+    [IoTDataSpecsDataTypeEnum.TEXT]: '字符串',
+    [IoTDataSpecsDataTypeEnum.BOOL]: '布尔值',
+    [IoTDataSpecsDataTypeEnum.ENUM]: '枚举',
+    [IoTDataSpecsDataTypeEnum.DATE]: '日期',
+    [IoTDataSpecsDataTypeEnum.STRUCT]: '结构体',
+    [IoTDataSpecsDataTypeEnum.ARRAY]: '数组'
+  }
+  return typeMap[dataType] || dataType
+}
+
+/** 获取数据类型标签类型(用于 el-tag 的 type 属性) */
+export const getDataTypeTagType = (
+  dataType: string
+): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
+  const tagMap = {
+    [IoTDataSpecsDataTypeEnum.INT]: 'primary',
+    [IoTDataSpecsDataTypeEnum.FLOAT]: 'success',
+    [IoTDataSpecsDataTypeEnum.DOUBLE]: 'success',
+    [IoTDataSpecsDataTypeEnum.TEXT]: 'info',
+    [IoTDataSpecsDataTypeEnum.BOOL]: 'warning',
+    [IoTDataSpecsDataTypeEnum.ENUM]: 'danger',
+    [IoTDataSpecsDataTypeEnum.DATE]: 'primary',
+    [IoTDataSpecsDataTypeEnum.STRUCT]: 'info',
+    [IoTDataSpecsDataTypeEnum.ARRAY]: 'warning'
+  } as const
+  return tagMap[dataType] || 'info'
+}
+
+/** 物模型组标签常量 */
+export const THING_MODEL_GROUP_LABELS = {
+  PROPERTY: '设备属性',
+  EVENT: '设备事件',
+  SERVICE: '设备服务'
+} as const
+
 // IoT OTA 任务设备范围枚举
 export const IoTOtaTaskDeviceScopeEnum = {
   ALL: {
@@ -226,7 +267,7 @@ export const IotRuleSceneTriggerTypeEnum = {
 } as const
 
 /** 触发器类型选项配置 */
-export const getTriggerTypeOptions = () => [
+export const triggerTypeOptions = [
   {
     value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
     label: '设备状态变更'
@@ -274,35 +315,19 @@ export const IotRuleSceneActionTypeEnum = {
 export const getActionTypeOptions = () => [
   {
     value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
-    label: '设备属性设置',
-    description: '设置目标设备的属性值',
-    icon: 'ep:edit',
-    tag: 'primary',
-    category: '设备控制'
+    label: '设备属性设置'
   },
   {
     value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE,
-    label: '设备服务调用',
-    description: '调用目标设备的服务',
-    icon: 'ep:service',
-    tag: 'success',
-    category: '设备控制'
+    label: '设备服务调用'
   },
   {
     value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
-    label: '触发告警',
-    description: '触发系统告警通知',
-    icon: 'ep:warning',
-    tag: 'danger',
-    category: '告警通知'
+    label: '触发告警'
   },
   {
     value: IotRuleSceneActionTypeEnum.ALERT_RECOVER,
-    label: '恢复告警',
-    description: '恢复已触发的告警',
-    icon: 'ep:circle-check',
-    tag: 'warning',
-    category: '告警通知'
+    label: '恢复告警'
   }
 ]
 
@@ -330,6 +355,26 @@ export const getActionTypeLabel = (type: number): string => {
   return option?.label || '未知类型'
 }
 
+/** 获取执行器标签类型(用于 el-tag 的 type 属性) */
+export const getActionTypeTag = (
+  type: number
+): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
+  const actionTypeTags = {
+    [IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: 'primary',
+    [IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
+    [IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: 'danger',
+    [IotRuleSceneActionTypeEnum.ALERT_RECOVER]: 'warning'
+  } as const
+  return actionTypeTags[type] || 'info'
+}
+
+/** 场景联动规则配置常量 */
+export const SCENE_RULE_CONFIG = {
+  MAX_ACTIONS: 5, // 最大执行器数量
+  MAX_TRIGGERS: 10, // 最大触发器数量
+  MAX_CONDITIONS: 20 // 最大条件数量
+} as const
+
 /** IoT 设备消息类型枚举 */
 export const IotDeviceMessageTypeEnum = {
   PROPERTY: 'property', // 属性
@@ -360,6 +405,131 @@ export const IotRuleSceneTriggerConditionTypeEnum = {
   CURRENT_TIME: 3 // 当前时间
 } as const
 
+/** 获取条件类型选项 */
+export const getConditionTypeOptions = () => [
+  {
+    value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS,
+    label: '设备状态'
+  },
+  {
+    value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY,
+    label: '设备属性'
+  },
+  {
+    value: IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME,
+    label: '当前时间'
+  }
+]
+
+/** 设备状态枚举 */
+export const IoTDeviceStatusEnum = {
+  ONLINE: {
+    label: '在线',
+    value: 'online'
+  },
+  OFFLINE: {
+    label: '离线',
+    value: 'offline'
+  }
+} as const
+
+/** 设备启用状态枚举 */
+export const IoTDeviceEnableStatusEnum = {
+  ENABLED: {
+    label: '正常',
+    value: 0,
+    tagType: 'success'
+  },
+  DISABLED: {
+    label: '禁用',
+    value: 1,
+    tagType: 'danger'
+  }
+} as const
+
+/** 设备激活状态枚举 */
+export const IoTDeviceActiveStatusEnum = {
+  ACTIVATED: {
+    label: '已激活',
+    tagType: 'success'
+  },
+  NOT_ACTIVATED: {
+    label: '未激活',
+    tagType: 'info'
+  }
+} as const
+
+/** 设备选择器特殊选项 */
+export const DEVICE_SELECTOR_OPTIONS = {
+  ALL_DEVICES: {
+    id: 0,
+    deviceName: '全部设备'
+  }
+} as const
+
+/** 获取设备状态选项 */
+export const getDeviceStatusOptions = () => [
+  {
+    value: IoTDeviceStatusEnum.ONLINE.value,
+    label: IoTDeviceStatusEnum.ONLINE.label
+  },
+  {
+    value: IoTDeviceStatusEnum.OFFLINE.value,
+    label: IoTDeviceStatusEnum.OFFLINE.label
+  }
+]
+
+/** 获取状态操作符选项 */
+export const getStatusOperatorOptions = () => [
+  {
+    value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value,
+    label: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
+  },
+  {
+    value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
+    label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name
+  }
+]
+
+/** 获取设备状态变更选项(用于触发器配置) */
+export const deviceStatusChangeOptions = [
+  {
+    label: IoTDeviceStatusEnum.ONLINE.label,
+    value: IoTDeviceStatusEnum.ONLINE.value
+  },
+  {
+    label: IoTDeviceStatusEnum.OFFLINE.label,
+    value: IoTDeviceStatusEnum.OFFLINE.value
+  }
+]
+
+/** 获取设备启用状态文本 */
+export const getDeviceEnableStatusText = (status: number): string => {
+  const statusItem = Object.values(IoTDeviceEnableStatusEnum).find((item) => item.value === status)
+  return statusItem?.label || '未知'
+}
+
+/** 获取设备启用状态标签类型 */
+export const getDeviceEnableStatusTagType = (
+  status: number
+): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
+  const statusItem = Object.values(IoTDeviceEnableStatusEnum).find((item) => item.value === status)
+  return statusItem?.tagType || 'info'
+}
+
+/** 获取设备激活状态文本和标签类型 */
+export const getDeviceActiveStatus = (activeTime?: string | null) => {
+  const isActivated = !!activeTime
+  return {
+    text: isActivated
+      ? IoTDeviceActiveStatusEnum.ACTIVATED.label
+      : IoTDeviceActiveStatusEnum.NOT_ACTIVATED.label,
+    tagType: isActivated
+      ? IoTDeviceActiveStatusEnum.ACTIVATED.tagType
+      : IoTDeviceActiveStatusEnum.NOT_ACTIVATED.tagType
+  }
+}
+
 /** IoT 场景联动触发时间操作符枚举 */
 export const IotRuleSceneTriggerTimeOperatorEnum = {
   BEFORE_TIME: { name: '在时间之前', value: 'before_time' }, // 在时间之前
@@ -375,7 +545,125 @@ export const IotRuleSceneTriggerTimeOperatorEnum = {
 
 /** 获取触发器类型标签 */
 export const getTriggerTypeLabel = (type: number): string => {
-  const options = getTriggerTypeOptions()
-  const option = options.find((item) => item.value === type)
+  const option = triggerTypeOptions.find((item) => item.value === type)
   return option?.label || '未知类型'
 }
+
+/** 获取触发器标签类型(用于 el-tag 的 type 属性) */
+export const getTriggerTagType = (
+  type: number
+): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
+  if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
+    return 'warning'
+  }
+  return isDeviceTrigger(type) ? 'success' : 'info'
+}
+
+// ========== JSON参数输入组件相关常量 ==========
+
+/** JSON参数输入组件类型枚举 */
+export const JsonParamsInputTypeEnum = {
+  SERVICE: 'service',
+  EVENT: 'event',
+  PROPERTY: 'property',
+  CUSTOM: 'custom'
+} as const
+
+/** JSON参数输入组件类型 */
+export type JsonParamsInputType =
+  (typeof JsonParamsInputTypeEnum)[keyof typeof JsonParamsInputTypeEnum]
+
+/** JSON参数输入组件文本常量 */
+export const JSON_PARAMS_INPUT_CONSTANTS = {
+  // 基础文本
+  PLACEHOLDER: '请输入JSON格式的参数',
+  JSON_FORMAT_CORRECT: 'JSON 格式正确',
+  QUICK_FILL_LABEL: '快速填充:',
+  EXAMPLE_DATA_BUTTON: '示例数据',
+  CLEAR_BUTTON: '清空',
+  VIEW_EXAMPLE_TITLE: '查看参数示例',
+  COMPLETE_JSON_FORMAT: '完整 JSON 格式:',
+  REQUIRED_TAG: '必填',
+
+  // 错误信息
+  PARAMS_MUST_BE_OBJECT: '参数必须是一个有效的 JSON 对象',
+  PARAM_REQUIRED_ERROR: (paramName: string) => `参数 ${paramName} 为必填项`,
+  JSON_FORMAT_ERROR: (error: string) => `JSON格式错误: ${error}`,
+  UNKNOWN_ERROR: '未知错误',
+
+  // 类型相关标题
+  TITLES: {
+    SERVICE: (name?: string) => `${name || '服务'} - 输入参数示例`,
+    EVENT: (name?: string) => `${name || '事件'} - 输出参数示例`,
+    PROPERTY: '属性设置 - 参数示例',
+    CUSTOM: (name?: string) => `${name || '自定义'} - 参数示例`,
+    DEFAULT: '参数示例'
+  },
+
+  // 参数标签
+  PARAMS_LABELS: {
+    SERVICE: '输入参数',
+    EVENT: '输出参数',
+    PROPERTY: '属性参数',
+    CUSTOM: '参数列表',
+    DEFAULT: '参数'
+  },
+
+  // 空状态消息
+  EMPTY_MESSAGES: {
+    SERVICE: '此服务无需输入参数',
+    EVENT: '此事件无输出参数',
+    PROPERTY: '无可设置的属性',
+    CUSTOM: '无参数配置',
+    DEFAULT: '无参数'
+  },
+
+  // 无配置消息
+  NO_CONFIG_MESSAGES: {
+    SERVICE: '请先选择服务',
+    EVENT: '请先选择事件',
+    PROPERTY: '请先选择产品',
+    CUSTOM: '请先进行配置',
+    DEFAULT: '请先进行配置'
+  }
+} as const
+
+/** JSON参数输入组件图标常量 */
+export const JSON_PARAMS_INPUT_ICONS = {
+  // 标题图标
+  TITLE_ICONS: {
+    SERVICE: 'ep:service',
+    EVENT: 'ep:bell',
+    PROPERTY: 'ep:edit',
+    CUSTOM: 'ep:document',
+    DEFAULT: 'ep:document'
+  },
+
+  // 参数图标
+  PARAMS_ICONS: {
+    SERVICE: 'ep:edit',
+    EVENT: 'ep:upload',
+    PROPERTY: 'ep:setting',
+    CUSTOM: 'ep:list',
+    DEFAULT: 'ep:edit'
+  },
+
+  // 状态图标
+  STATUS_ICONS: {
+    ERROR: 'ep:warning',
+    SUCCESS: 'ep:circle-check'
+  }
+} as const
+
+/** JSON参数输入组件示例值常量 */
+export const JSON_PARAMS_EXAMPLE_VALUES = {
+  [IoTDataSpecsDataTypeEnum.INT]: { display: '25', value: 25 },
+  [IoTDataSpecsDataTypeEnum.FLOAT]: { display: '25.5', value: 25.5 },
+  [IoTDataSpecsDataTypeEnum.DOUBLE]: { display: '25.5', value: 25.5 },
+  [IoTDataSpecsDataTypeEnum.BOOL]: { display: 'false', value: false },
+  [IoTDataSpecsDataTypeEnum.TEXT]: { display: '"auto"', value: 'auto' },
+  [IoTDataSpecsDataTypeEnum.ENUM]: { display: '"option1"', value: 'option1' },
+  [IoTDataSpecsDataTypeEnum.STRUCT]: { display: '{}', value: {} },
+  [IoTDataSpecsDataTypeEnum.ARRAY]: { display: '[]', value: [] },
+  DEFAULT: { display: '""', value: '' }
+} as const