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

【功能新增】IoT: 规则场景监听器相关组件

puhui999 1 год назад
Родитель
Сommit
14ffb6483f

+ 46 - 53
src/api/iot/rule/scene/scene.types.ts

@@ -2,36 +2,36 @@
  * 场景规则触发器配置
  */
 export interface IotRuleSceneTriggerConfig {
-  /** 
+  /**
    * 触发类型
    * - 1: 设备触发
    * - 2: 定时触发
    */
-  type: number;
+  type: number
   /** 产品标识 */
-  productKey?: string;
+  productKey?: string
   /** 设备名称数组 */
-  deviceNames?: string[];
+  deviceNames?: string[]
   /** 触发条件数组。条件之间是"或"的关系 */
-  conditions?: IotRuleSceneTriggerCondition[];
+  conditions?: IotRuleSceneTriggerCondition[]
   /** CRON 表达式。当 type = 2 时必填 */
-  cronExpression?: string;
+  cronExpression?: string
 }
 
 /**
  * 触发条件
  */
 export interface IotRuleSceneTriggerCondition {
-  /** 
+  /**
    * 消息类型
    * - property: 属性上报
    * - event: 事件上报
    */
-  type: string;
+  type: string
   /** 消息标识符 */
-  identifier: string;
+  identifier: string
   /** 参数数组。参数之间是"或"的关系 */
-  parameters: IotRuleSceneTriggerConditionParameter[];
+  parameters: IotRuleSceneTriggerConditionParameter[]
 }
 
 /**
@@ -39,39 +39,32 @@ export interface IotRuleSceneTriggerCondition {
  */
 export interface IotRuleSceneTriggerConditionParameter {
   /** 标识符(属性、事件、服务) */
-  identifier: string;
+  identifier: string
   /**
    * 操作符
-   * - eq: 等于
-   * - gt: 大于
-   * - gte: 大于等于
-   * - lt: 小于
-   * - lte: 小于等于
-   * - between: 范围
-   * - in: 在列表中
    */
-  operator: string;
-  /** 
+  operator: string
+  /**
    * 比较值
    * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"
    */
-  value: string;
+  value: string
 }
 
 /**
  * 执行器配置
  */
 export interface IotRuleSceneActionConfig {
-  /** 
+  /**
    * 执行类型
    * - 1: 设备控制
    * - 2: 数据桥接
    */
-  type: number;
+  type: number
   /** 设备控制配置。当 type = 1 时必填 */
-  deviceControl?: IotRuleSceneActionDeviceControl;
+  deviceControl?: IotRuleSceneActionDeviceControl
   /** 数据桥接编号。当 type = 2 时必填 */
-  dataBridgeId?: number;
+  dataBridgeId?: number
 }
 
 /**
@@ -79,23 +72,23 @@ export interface IotRuleSceneActionConfig {
  */
 export interface IotRuleSceneActionDeviceControl {
   /** 产品标识 */
-  productKey: string;
+  productKey: string
   /** 设备名称数组 */
-  deviceNames: string[];
-  /** 
+  deviceNames: string[]
+  /**
    * 消息类型
    * - property: 属性
    * - service: 服务
    */
-  type: string;
-  /** 
+  type: string
+  /**
    * 消息标识符
    * - property_set: 属性设置
    * - service_invoke: 服务调用
    */
-  identifier: string;
+  identifier: string
   /** 具体数据 */
-  data: Record<string, any>;
+  data: Record<string, any>
 }
 
 /**
@@ -103,17 +96,17 @@ export interface IotRuleSceneActionDeviceControl {
  */
 export interface IotRuleSceneSaveReqVO {
   /** 场景规则编号 */
-  id?: number;
+  id?: number
   /** 场景规则名称 */
-  name: string;
+  name: string
   /** 场景规则状态(0=禁用 1=启用) */
-  status: number;
+  status: number
   /** 触发器配置 */
-  triggerConfig: IotRuleSceneTriggerConfig;
+  triggerConfig: IotRuleSceneTriggerConfig
   /** 执行动作配置数组 */
-  actionConfigs: IotRuleSceneActionConfig[];
+  actionConfigs: IotRuleSceneActionConfig[]
   /** 备注 */
-  remark?: string;
+  remark?: string
 }
 
 /**
@@ -121,19 +114,19 @@ export interface IotRuleSceneSaveReqVO {
  */
 export interface IotRuleSceneRespVO {
   /** 场景规则编号 */
-  id: number;
+  id: number
   /** 场景规则名称 */
-  name: string;
+  name: string
   /** 场景规则状态(0=禁用 1=启用) */
-  status: number;
+  status: number
   /** 触发器配置 */
-  triggerConfig: IotRuleSceneTriggerConfig;
+  triggerConfig: IotRuleSceneTriggerConfig
   /** 执行动作配置数组 */
-  actionConfigs: IotRuleSceneActionConfig[];
+  actionConfigs: IotRuleSceneActionConfig[]
   /** 备注 */
-  remark?: string;
+  remark?: string
   /** 创建时间 */
-  createTime: Date;
+  createTime: Date
 }
 
 /**
@@ -141,9 +134,9 @@ export interface IotRuleSceneRespVO {
  */
 export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
   /** 触发次数 */
-  triggerCount: number;
+  triggerCount: number
   /** 最后触发时间 */
-  lastTriggerTime?: Date;
+  lastTriggerTime?: Date
 }
 
 /**
@@ -151,15 +144,15 @@ export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
  */
 export interface IotRuleScenePageReqVO {
   /** 场景规则名称 */
-  name?: string;
+  name?: string
   /** 场景规则状态(0=禁用 1=启用) */
-  status?: number;
+  status?: number
   /** 创建时间 */
-  createTime?: [Date, Date];
+  createTime?: [Date, Date]
   /** 页码 */
-  pageNo?: number;
+  pageNo?: number
   /** 每页条数 */
-  pageSize?: number;
+  pageSize?: number
 }
 
 /**
@@ -226,4 +219,4 @@ export enum IotRuleSceneTriggerConditionParameterOperatorEnum {
   BETWEEN = 'between',
   /** 在列表中 */
   IN = 'in'
-} 
+}

+ 3 - 1
src/utils/dict.ts

@@ -245,5 +245,7 @@ export enum DICT_TYPE {
   IOT_PLUGIN_STATUS = 'iot_plugin_status', // IOT 插件状态
   IOT_PLUGIN_TYPE = 'iot_plugin_type', // IOT 插件类型
   IOT_DATA_BRIDGE_DIRECTION_ENUM = 'iot_data_bridge_direction_enum', // 桥梁方向
-  IOT_DATA_BRIDGE_TYPE_ENUM = 'iot_data_bridge_type_enum' // 桥梁类型
+  IOT_DATA_BRIDGE_TYPE_ENUM = 'iot_data_bridge_type_enum', // 桥梁类型
+  IOT_DEVICE_MESSAGE_TYPE_ENUM = 'iot_device_message_type_enum', // IoT 设备消息类型枚举
+  IOT_RULE_SCENE_TRIGGER_TYPE_ENUM = 'iot_rule_scene_trigger_type_enum' // IoT 场景流转的触发类型枚举
 }

+ 37 - 24
src/views/iot/rule/scene/IoTRuleSceneForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="70%">
     <el-form
       ref="formRef"
       :model="formData"
@@ -7,29 +7,41 @@
       label-width="100px"
       v-loading="formLoading"
     >
-      <el-form-item label="场景名称" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入场景名称" />
-      </el-form-item>
-      <el-form-item label="场景描述" prop="description">
-        <el-input v-model="formData.description" type="textarea" placeholder="请输入场景描述" />
-      </el-form-item>
-      <el-form-item label="场景状态" prop="status">
-        <el-radio-group v-model="formData.status">
-          <el-radio
-            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-            :key="dict.value"
-            :label="dict.value"
-          >
-            {{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="触发器数组" prop="triggers">
-        <el-input v-model="formData.triggers" placeholder="请输入触发器数组" />
-      </el-form-item>
-      <el-form-item label="执行器数组" prop="actions">
-        <el-input v-model="formData.actions" placeholder="请输入执行器数组" />
-      </el-form-item>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="场景名称" prop="name">
+            <el-input v-model="formData.name" placeholder="请输入场景名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="场景状态" prop="status">
+            <el-radio-group v-model="formData.status">
+              <el-radio
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.value"
+              >
+                {{ dict.label }}
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="场景描述" prop="description">
+            <el-input v-model="formData.description" type="textarea" placeholder="请输入场景描述" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-divider content-position="left">触发器配置</el-divider>
+          <device-listener v-model="formData.triggers" />
+        </el-col>
+        <el-col :span="24">
+          <el-divider content-position="left">执行动作配置</el-divider>
+          <el-form-item label="执行器数组" prop="actionConfigs">
+            <el-input v-model="formData.actions" placeholder="请输入执行器数组" />
+          </el-form-item>
+        </el-col>
+      </el-row>
     </el-form>
     <template #footer>
       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
@@ -40,6 +52,7 @@
 <script setup lang="ts">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
+import DeviceListener from './components/DeviceListener.vue'
 
 /** IoT 规则场景(场景联动) 表单 */
 defineOptions({ name: 'RuleSceneForm' })

+ 241 - 0
src/views/iot/rule/scene/components/ConditionSelector.vue

@@ -0,0 +1,241 @@
+<template>
+  <div class="condition-selector">
+    <el-select 
+      v-model="selectedOperator" 
+      :placeholder="placeholder || '请选择操作符'"
+      class="condition-select"
+      @change="handleOperatorChange"
+    >
+      <el-option 
+        v-for="item in operatorOptions" 
+        :key="item.value" 
+        :label="item.label" 
+        :value="item.value"
+      >
+        <div class="operator-option">
+          <span>{{ item.label }}</span>
+          <span class="operator-symbol">{{ item.symbol }}</span>
+        </div>
+      </el-option>
+    </el-select>
+
+    <!-- 普通输入值 -->
+    <template v-if="!isRangeOperator && !isListOperator">
+      <el-input 
+        v-model="inputValue" 
+        :placeholder="valuePlaceholder || '请输入值'"
+        class="value-input"
+        @change="handleValueChange"
+      />
+    </template>
+
+    <!-- 范围值输入 -->
+    <template v-if="isRangeOperator">
+      <div class="range-input">
+        <el-input 
+          v-model="rangeValue.min" 
+          placeholder="最小值"
+          class="range-input-item"
+          @change="handleRangeValueChange"
+        />
+        <span class="range-separator">至</span>
+        <el-input 
+          v-model="rangeValue.max" 
+          placeholder="最大值"
+          class="range-input-item"
+          @change="handleRangeValueChange"
+        />
+      </div>
+    </template>
+
+    <!-- 列表值输入 -->
+    <template v-if="isListOperator">
+      <el-select
+        v-model="listValue"
+        :placeholder="valuePlaceholder || '请选择值'"
+        multiple
+        filterable
+        allow-create
+        class="list-select"
+        @change="handleListValueChange"
+      >
+        <el-option
+          v-for="item in listOptions"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
+      </el-select>
+    </template>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch, defineEmits, defineProps } from 'vue'
+import { IotRuleSceneTriggerConditionParameterOperatorEnum } from '@/api/iot/rule/scene/scene.types'
+
+interface ConditionValue {
+  operator: IotRuleSceneTriggerConditionParameterOperatorEnum
+  value: string
+}
+
+// 定义组件属性
+const props = defineProps({
+  modelValue: {
+    type: Object as () => ConditionValue,
+    required: true
+  },
+  placeholder: {
+    type: String,
+    default: ''
+  },
+  valuePlaceholder: {
+    type: String,
+    default: ''
+  },
+  listOptions: {
+    type: Array as () => { label: string, value: string | number }[],
+    default: () => []
+  }
+})
+
+// 定义事件
+const emit = defineEmits(['update:modelValue'])
+
+// 操作符选项
+const operatorOptions = [
+  { label: '等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQ, symbol: '=' },
+  { label: '不等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.NE, symbol: '≠' },
+  { label: '大于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.GT, symbol: '>' },
+  { label: '小于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.LT, symbol: '<' },
+  { label: '大于等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.GTE, symbol: '≥' },
+  { label: '小于等于', value: IotRuleSceneTriggerConditionParameterOperatorEnum.LTE, symbol: '≤' },
+  { label: '在...之间', value: IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN, symbol: '↔' },
+  { label: '不在...之间', value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN, symbol: '⟷' },
+  { label: '在列表中', value: IotRuleSceneTriggerConditionParameterOperatorEnum.IN, symbol: '∈' },
+  { label: '不在列表中', value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN, symbol: '∉' }
+]
+
+// 当前选中的操作符
+const selectedOperator = ref(props.modelValue.operator || '')
+
+// 输入值
+const inputValue = ref(props.modelValue.value || '')
+
+// 范围值
+const rangeValue = ref({
+  min: '',
+  max: ''
+})
+
+// 列表值
+const listValue = ref([])
+
+// 计算属性:是否为范围操作符
+const isRangeOperator = computed(() => {
+  return ['between', 'notBetween'].includes(selectedOperator.value)
+})
+
+// 计算属性:是否为列表操作符
+const isListOperator = computed(() => {
+  return ['in', 'notIn'].includes(selectedOperator.value)
+})
+
+// 处理操作符变更
+const handleOperatorChange = () => {
+  updateModelValue()
+}
+
+// 处理值变更
+const handleValueChange = () => {
+  updateModelValue()
+}
+
+// 处理范围值变更
+const handleRangeValueChange = () => {
+  updateModelValue()
+}
+
+// 处理列表值变更
+const handleListValueChange = () => {
+  updateModelValue()
+}
+
+// 更新组件值
+const updateModelValue = () => {
+  const value = isRangeOperator.value
+    ? `${rangeValue.value.min},${rangeValue.value.max}`
+    : isListOperator.value
+    ? listValue.value.join(',')
+    : inputValue.value
+
+  emit('update:modelValue', {
+    operator: selectedOperator.value,
+    value
+  })
+}
+
+// 监听 modelValue 变化
+watch(() => props.modelValue, (newVal) => {
+  if (newVal) {
+    selectedOperator.value = newVal.operator || ''
+    
+    if (isRangeOperator.value && newVal.value) {
+      const [min, max] = newVal.value.split(',')
+      rangeValue.value = { min, max }
+    } else if (isListOperator.value && newVal.value) {
+      listValue.value = newVal.value.split(',')
+    } else {
+      inputValue.value = newVal.value || ''
+    }
+  }
+}, { deep: true })
+</script>
+
+<style scoped>
+.condition-selector {
+  display: flex;
+  gap: 8px;
+  align-items: flex-start;
+  width: 100%;
+}
+
+.condition-select {
+  width: 120px;
+}
+
+.value-input {
+  flex: 1;
+}
+
+.operator-option {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.operator-symbol {
+  font-size: 14px;
+  color: var(--el-text-color-secondary);
+}
+
+.range-input {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  flex: 1;
+}
+
+.range-input-item {
+  flex: 1;
+}
+
+.range-separator {
+  padding: 0 4px;
+  color: var(--el-text-color-regular);
+}
+
+.list-select {
+  flex: 1;
+}
+</style> 

+ 87 - 0
src/views/iot/rule/scene/components/DeviceListener.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="device-listener m-10px">
+    <div class="device-listener-header h-50px flex items-center px-10px">
+      <div class="flex items-center mr-60px">
+        <span class="mr-10px">触发条件</span>
+        <el-select v-model="triggerType" class="!w-240px" clearable placeholder="请选择触发条件">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.IOT_RULE_SCENE_TRIGGER_TYPE_ENUM)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </div>
+      <div class="flex items-center mr-60px">
+        <span class="mr-10px">产品</span>
+        <el-button type="primary">选择产品</el-button>
+      </div>
+      <div class="flex items-center mr-60px">
+        <span class="mr-10px">设备</span>
+        <el-button type="primary">选择设备</el-button>
+      </div>
+    </div>
+    <div class="device-listener-condition flex p-10px">
+      <div class="flex flex-col items-center justify-center mr-10px h-a">
+        <el-select v-model="messageType" class="!w-160px" clearable placeholder="">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </div>
+      <div class="flex items-center flex-wrap">
+        <DeviceListenerCondition
+          v-for="i in 2"
+          :key="i"
+          v-model="conditionParameter"
+          class="mb-10px last:mb-0"
+        />
+      </div>
+      <div class="flex flex-col items-center justify-center mr-10px h-a">
+        <!-- 添加规则 -->
+        <el-button type="primary" circle :icon="Plus" size="small" />
+      </div>
+    </div>
+
+    <!-- 新增条件按钮 -->
+    <div class="mt-4">
+      <el-button type="primary"> 新增监听器 </el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { Plus } from '@element-plus/icons-vue'
+import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
+import { ref } from 'vue'
+import DeviceListenerCondition from './DeviceListenerCondition.vue'
+
+/** 场景联动之监听器组件 */
+defineOptions({ name: 'DeviceListener' })
+
+defineProps<{
+  modelValue: any[]
+}>()
+
+const emit = defineEmits(['update:modelValue'])
+
+// 添加响应式变量
+const triggerType = ref()
+const messageType = ref('property')
+const conditionParameter = ref({})
+</script>
+
+<style lang="scss" scoped>
+.device-listener {
+  .device-listener-header {
+    background-color: #eff3f7;
+  }
+
+  .device-listener-condition {
+    background-color: #dbe5f6;
+  }
+}
+</style>

+ 50 - 0
src/views/iot/rule/scene/components/DeviceListenerCondition.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="device-listener-condition">
+    <el-select
+      v-model="conditionParameter.identifier"
+      class="!w-240px mr-10px"
+      clearable
+      placeholder="请选择物模型"
+    >
+      <el-option
+        v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"
+        :key="dict.value"
+        :label="dict.label"
+        :value="dict.value"
+      />
+    </el-select>
+    <el-select
+      v-model="conditionParameter.operator"
+      class="!w-240px mr-10px"
+      clearable
+      placeholder="请选择条件"
+    >
+      <el-option
+        v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"
+        :key="dict.value"
+        :label="dict.label"
+        :value="dict.value"
+      />
+    </el-select>
+    <el-input v-model="conditionParameter.value" class="!w-240px mr-10px" placeholder="请输入值">
+      <template #append> 单位 </template>
+    </el-input>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { IotRuleSceneTriggerConditionParameter } from '@/api/iot/rule/scene/scene.types'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { useVModel } from '@vueuse/core'
+
+defineOptions({ name: 'DeviceListenerCondition' })
+const props = defineProps<{ modelValue: any }>()
+const emits = defineEmits(['update:modelValue'])
+const conditionParameter = useVModel(
+  props,
+  'modelValue',
+  emits
+) as Ref<IotRuleSceneTriggerConditionParameter>
+</script>
+
+<style scoped lang="scss"></style>

+ 379 - 0
src/views/iot/rule/scene/components/TriggerConditions.vue

@@ -0,0 +1,379 @@
+<template>
+  <div class="trigger-conditions">
+    <div class="conditions-header mb-3">
+      <el-button type="primary" @click="addCondition" :disabled="!productKey">
+        <Icon icon="ep:plus" class="mr-5px" /> 添加条件
+      </el-button>
+      <div class="conditions-tips" v-if="modelValue && modelValue.length > 0">
+        注:多个条件之间为"或"关系
+      </div>
+    </div>
+
+    <el-empty v-if="!modelValue || modelValue.length === 0" description="暂无触发条件" />
+
+    <div class="conditions-list" v-else>
+      <div v-for="(condition, index) in modelValue" :key="index" class="condition-item mb-3">
+        <el-card class="box-card">
+          <template #header>
+            <div class="card-header">
+              <span>条件 {{ index + 1 }}</span>
+              <el-button type="danger" link @click="removeCondition(index)"> 删除 </el-button>
+            </div>
+          </template>
+          <div class="condition-content">
+            <el-form label-width="100px" :model="condition">
+              <el-form-item label="消息类型">
+                <el-select
+                  v-model="condition.type"
+                  placeholder="请选择消息类型"
+                  @change="handleMessageTypeChange(index)"
+                >
+                  <el-option label="属性上报" :value="IotDeviceMessageTypeEnum.PROPERTY" />
+                  <el-option label="事件上报" :value="IotDeviceMessageTypeEnum.EVENT" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="消息标识符">
+                <el-select
+                  v-model="condition.identifier"
+                  placeholder="请选择消息标识符"
+                  filterable
+                  :loading="thingModelLoading"
+                  @change="handleIdentifierChange(index)"
+                >
+                  <el-option
+                    v-for="item in getThingModelOptions(condition.type)"
+                    :key="item.identifier"
+                    :label="item.name"
+                    :value="item.identifier"
+                  >
+                    <div class="thing-model-option">
+                      <span>{{ item.name }}</span>
+                      <span class="thing-model-identifier">{{ item.identifier }}</span>
+                    </div>
+                    <div class="thing-model-desc" v-if="item.description">{{
+                      item.description
+                    }}</div>
+                  </el-option>
+                </el-select>
+              </el-form-item>
+
+              <div class="parameters-area mt-3 mb-2">
+                <div class="parameters-header">
+                  <div>参数列表(多个参数之间为"或"关系)</div>
+                  <el-button type="primary" link @click="addParameter(index)"> 添加参数 </el-button>
+                </div>
+
+                <el-empty
+                  v-if="!condition.parameters || condition.parameters.length === 0"
+                  description="暂无参数"
+                />
+
+                <div class="parameters-list mt-2" v-else>
+                  <div
+                    v-for="(param, pIndex) in condition.parameters"
+                    :key="pIndex"
+                    class="parameter-item mb-2"
+                  >
+                    <el-card shadow="hover">
+                      <div class="parameter-item-header">
+                        <span>参数 {{ pIndex + 1 }}</span>
+                        <el-button type="danger" link @click="removeParameter(index, pIndex)">
+                          删除
+                        </el-button>
+                      </div>
+
+                      <el-form label-width="90px" :model="param" class="mt-2">
+                        <el-form-item label="标识符">
+                          <el-select
+                            v-model="param.identifier"
+                            placeholder="请选择参数标识符"
+                            filterable
+                          >
+                            <el-option
+                              v-for="item in getParameterOptions(condition)"
+                              :key="item.identifier"
+                              :label="item.name"
+                              :value="item.identifier"
+                            >
+                              <div class="thing-model-option">
+                                <span>{{ item.name }}</span>
+                                <span class="thing-model-identifier">{{ item.identifier }}</span>
+                              </div>
+                              <div class="thing-model-desc" v-if="item.description">{{
+                                item.description
+                              }}</div>
+                            </el-option>
+                          </el-select>
+                        </el-form-item>
+                        <el-form-item label="条件">
+                          <condition-selector
+                            v-model="param.condition"
+                            :placeholder="'请选择条件'"
+                            :value-placeholder="'请输入比较值'"
+                          />
+                        </el-form-item>
+                      </el-form>
+                    </el-card>
+                  </div>
+                </div>
+              </div>
+            </el-form>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { defineEmits, defineProps, onMounted, ref, watch } from 'vue'
+import {
+  IotDeviceMessageIdentifierEnum,
+  IotDeviceMessageTypeEnum,
+  IotRuleSceneTriggerCondition,
+  IotRuleSceneTriggerConditionParameter
+} from '@/api/iot/rule/scene/scene.types'
+import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
+import ConditionSelector from './ConditionSelector.vue'
+
+const props = defineProps({
+  modelValue: {
+    type: Array as () => IotRuleSceneTriggerCondition[],
+    required: true
+  },
+  productKey: {
+    type: String,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+// 物模型数据
+const thingModelList = ref<ThingModelData[]>([])
+const thingModelLoading = ref(false)
+
+// 加载物模型数据
+const loadThingModelData = async () => {
+  if (!props.productKey) return
+
+  try {
+    thingModelLoading.value = true
+    const result = await ThingModelApi.getThingModelListByProductId({
+      productKey: props.productKey
+    })
+    thingModelList.value = result || []
+  } catch (error) {
+    console.error('获取物模型数据失败', error)
+  } finally {
+    thingModelLoading.value = false
+  }
+}
+
+// 获取物模型选项
+const getThingModelOptions = (type: string) => {
+  if (!thingModelList.value) return []
+
+  return thingModelList.value.filter((item) => {
+    if (type === IotDeviceMessageTypeEnum.PROPERTY) {
+      return item.property
+    } else if (type === IotDeviceMessageTypeEnum.EVENT) {
+      return item.event
+    }
+    return false
+  })
+}
+
+// 获取参数选项
+const getParameterOptions = (condition: IotRuleSceneTriggerCondition) => {
+  if (!condition || !condition.identifier) return []
+
+  const model = thingModelList.value?.find((item) => item.identifier === condition.identifier)
+  if (!model) return []
+
+  if (condition.type === IotDeviceMessageTypeEnum.PROPERTY) {
+    return [model] // 属性本身就是参数
+  } else if (condition.type === IotDeviceMessageTypeEnum.EVENT) {
+    // TODO: 获取事件的输出参数列表
+    return []
+  }
+  return []
+}
+
+// 添加条件
+const addCondition = () => {
+  const newCondition: IotRuleSceneTriggerCondition = {
+    type: IotDeviceMessageTypeEnum.PROPERTY,
+    identifier: IotDeviceMessageIdentifierEnum.PROPERTY_REPORT,
+    parameters: []
+  }
+
+  const newValue = [...(props.modelValue || []), newCondition]
+  emit('update:modelValue', newValue)
+}
+
+// 移除条件
+const removeCondition = (index: number) => {
+  const newValue = [...props.modelValue]
+  newValue.splice(index, 1)
+  emit('update:modelValue', newValue)
+}
+
+// 消息类型变更
+const handleMessageTypeChange = (index: number) => {
+  const newValue = [...props.modelValue]
+  // 更新标识符
+  if (newValue[index].type === IotDeviceMessageTypeEnum.PROPERTY) {
+    newValue[index].identifier = IotDeviceMessageIdentifierEnum.PROPERTY_REPORT
+  } else if (newValue[index].type === IotDeviceMessageTypeEnum.EVENT) {
+    newValue[index].identifier = IotDeviceMessageIdentifierEnum.EVENT_REPORT
+  }
+  // 清空参数
+  newValue[index].parameters = []
+  emit('update:modelValue', newValue)
+}
+
+// 标识符变更
+const handleIdentifierChange = (index: number) => {
+  const newValue = [...props.modelValue]
+  // 清空参数
+  newValue[index].parameters = []
+  emit('update:modelValue', newValue)
+}
+
+// 添加参数
+const addParameter = (conditionIndex: number) => {
+  const newValue = [...props.modelValue]
+  if (!newValue[conditionIndex].parameters) {
+    newValue[conditionIndex].parameters = []
+  }
+
+  const newParameter: IotRuleSceneTriggerConditionParameter = {
+    identifier: '',
+    condition: {
+      operator: 'eq',
+      value: ''
+    }
+  }
+
+  newValue[conditionIndex].parameters.push(newParameter)
+  emit('update:modelValue', newValue)
+}
+
+// 移除参数
+const removeParameter = (conditionIndex: number, paramIndex: number) => {
+  const newValue = [...props.modelValue]
+  newValue[conditionIndex].parameters.splice(paramIndex, 1)
+  emit('update:modelValue', newValue)
+}
+
+// 监听 productKey 变化
+watch(
+  () => props.productKey,
+  (newVal) => {
+    if (!newVal) {
+      // 清空条件
+      if (props.modelValue?.length > 0) {
+        emit('update:modelValue', [])
+      }
+      // 清空物模型数据
+      thingModelList.value = []
+    } else {
+      // 加载物模型数据
+      loadThingModelData()
+    }
+  }
+)
+
+// 初始化
+onMounted(() => {
+  if (props.productKey) {
+    loadThingModelData()
+  }
+})
+</script>
+
+<style scoped>
+.trigger-conditions {
+  width: 100%;
+}
+
+.conditions-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.conditions-tips {
+  font-size: 12px;
+  color: #999;
+}
+
+.condition-item {
+  border-radius: 4px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.parameters-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+  font-weight: bold;
+}
+
+.parameter-item-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.value-tips {
+  margin-top: 5px;
+  font-size: 12px;
+  color: #999;
+}
+
+.thing-model-option {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.thing-model-identifier {
+  font-size: 12px;
+  color: #999;
+}
+
+.thing-model-desc {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #666;
+}
+
+.mb-3 {
+  margin-bottom: 12px;
+}
+
+.mb-2 {
+  margin-bottom: 8px;
+}
+
+.mt-3 {
+  margin-top: 12px;
+}
+
+.mt-2 {
+  margin-top: 8px;
+}
+
+.mr-5px {
+  margin-right: 5px;
+}
+</style>