Procházet zdrojové kódy

!812 perf: 【IoT 物联网】优化场景联动 review 提到的逻辑
Merge pull request !812 from puhui999/feature/iot

芋道源码 před 5 měsíci
rodič
revize
96a9154e52

+ 7 - 7
src/api/iot/rule/scene/index.ts

@@ -46,28 +46,28 @@ export interface Action {
 export const RuleSceneApi = {
   // 查询场景联动分页
   getRuleScenePage: async (params: any) => {
-    return await request.get({ url: `/iot/rule-scene/page`, params })
+    return await request.get({ url: `/iot/scene-rule/page`, params })
   },
 
   // 查询场景联动详情
   getRuleScene: async (id: number) => {
-    return await request.get({ url: `/iot/rule-scene/get?id=` + id })
+    return await request.get({ url: `/iot/scene-rule/get?id=` + id })
   },
 
   // 新增场景联动
   createRuleScene: async (data: IotSceneRule) => {
-    return await request.post({ url: `/iot/rule-scene/create`, data })
+    return await request.post({ url: `/iot/scene-rule/create`, data })
   },
 
   // 修改场景联动
   updateRuleScene: async (data: IotSceneRule) => {
-    return await request.put({ url: `/iot/rule-scene/update`, data })
+    return await request.put({ url: `/iot/scene-rule/update`, data })
   },
 
   // 修改场景联动
   updateRuleSceneStatus: async (id: number, status: number) => {
     return await request.put({
-      url: `/iot/rule-scene/update-status`,
+      url: `/iot/scene-rule/update-status`,
       data: {
         id,
         status
@@ -77,11 +77,11 @@ export const RuleSceneApi = {
 
   // 删除场景联动
   deleteRuleScene: async (id: number) => {
-    return await request.delete({ url: `/iot/rule-scene/delete?id=` + id })
+    return await request.delete({ url: `/iot/scene-rule/delete?id=` + id })
   },
 
   // 获取场景联动简单列表
   getSimpleRuleSceneList: async () => {
-    return await request.get({ url: `/iot/rule-scene/simple-list` })
+    return await request.get({ url: `/iot/scene-rule/simple-list` })
   }
 }

+ 1 - 1
src/views/iot/device/device/DeviceForm.vue

@@ -98,7 +98,7 @@
                 :isWrite="true"
                 :clickMap="true"
                 :center="formData.location"
-                @locateChange="handleLocationChange"
+                @locate-change="handleLocationChange"
                 ref="mapRef"
                 class="h-full w-full"
               />

+ 27 - 4
src/views/iot/rule/scene/form/configs/ConditionConfig.vue

@@ -63,7 +63,7 @@
               class="w-full"
             >
               <el-option
-                v-for="option in getStatusOperatorOptions()"
+                v-for="option in statusOperatorOptions"
                 :key="option.value"
                 :label="option.label"
                 :value="option.value"
@@ -82,7 +82,7 @@
               class="w-full"
             >
               <el-option
-                v-for="option in getDeviceStatusOptions()"
+                v-for="option in deviceStatusOptions"
                 :key="option.value"
                 :label="option.label"
                 :value="option.value"
@@ -163,8 +163,7 @@ import {
   IotRuleSceneTriggerConditionTypeEnum,
   IotRuleSceneTriggerConditionParameterOperatorEnum,
   getConditionTypeOptions,
-  getDeviceStatusOptions,
-  getStatusOperatorOptions
+  IoTDeviceStatusEnum
 } from '@/views/iot/utils/constants'
 
 /** 单个条件配置组件 */
@@ -179,6 +178,30 @@ const emit = defineEmits<{
   (e: 'update:modelValue', value: TriggerCondition): void
 }>()
 
+/** 获取设备状态选项 */
+const deviceStatusOptions = [
+  {
+    value: IoTDeviceStatusEnum.ONLINE.value,
+    label: IoTDeviceStatusEnum.ONLINE.label
+  },
+  {
+    value: IoTDeviceStatusEnum.OFFLINE.value,
+    label: IoTDeviceStatusEnum.OFFLINE.label
+  }
+]
+
+/** 获取状态操作符选项 */
+const statusOperatorOptions = [
+  {
+    value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value,
+    label: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
+  },
+  {
+    value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
+    label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name
+  }
+]
+
 const condition = useVModel(props, 'modelValue', emit)
 
 const propertyType = ref<string>('string') // 属性类型

+ 14 - 11
src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue

@@ -65,7 +65,6 @@
               :model-value="condition.operator"
               @update:model-value="(value) => updateConditionField('operator', value)"
               :property-type="propertyType"
-              @change="handleOperatorChange"
             />
           </el-form-item>
         </el-col>
@@ -188,8 +187,8 @@ import {
   IotRuleSceneTriggerTypeEnum,
   triggerTypeOptions,
   getTriggerTypeLabel,
-  deviceStatusChangeOptions,
-  IotRuleSceneTriggerConditionParameterOperatorEnum
+  IotRuleSceneTriggerConditionParameterOperatorEnum,
+  IoTDeviceStatusEnum
 } from '@/views/iot/utils/constants'
 import { useVModel } from '@vueuse/core'
 
@@ -206,6 +205,18 @@ const emit = defineEmits<{
   (e: 'trigger-type-change', value: number): void
 }>()
 
+/** 获取设备状态变更选项(用于触发器配置) */
+const deviceStatusChangeOptions = [
+  {
+    label: IoTDeviceStatusEnum.ONLINE.label,
+    value: IoTDeviceStatusEnum.ONLINE.value
+  },
+  {
+    label: IoTDeviceStatusEnum.OFFLINE.label,
+    value: IoTDeviceStatusEnum.OFFLINE.value
+  }
+]
+
 const condition = useVModel(props, 'modelValue', emit)
 const propertyType = ref('') // 属性类型
 const propertyConfig = ref<any>(null) // 属性配置
@@ -326,12 +337,4 @@ const handlePropertyChange = (propertyInfo: any) => {
     }
   }
 }
-
-// TODO @puhui999:这个貌似没用上
-/**
- * 处理操作符变化事件
- */
-const handleOperatorChange = () => {
-  // 操作符变化处理
-}
 </script>

+ 118 - 140
src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue

@@ -1,148 +1,139 @@
 <!-- JSON参数输入组件 - 通用版本 -->
 <template>
-  <div class="w-full min-w-0">
-    <!-- 参数配置 -->
-    <div v-if="hasConfig" class="space-y-12px">
-      <!-- JSON 输入框 -->
-      <div class="relative">
-        <el-input
-          v-model="paramsJson"
-          type="textarea"
-          :rows="4"
-          :placeholder="placeholder"
-          @input="handleParamsChange"
-          :class="{ 'is-error': jsonError }"
-        />
-        <!-- 查看详细示例弹出层 -->
-        <div class="absolute top-8px right-8px">
-          <el-popover
-            placement="left-start"
-            :width="450"
-            trigger="click"
-            :show-arrow="true"
-            :offset="8"
-            popper-class="json-params-detail-popover"
-          >
-            <template #reference>
-              <el-button
-                type="info"
-                :icon="InfoFilled"
-                circle
-                size="small"
-                :title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
-              />
-            </template>
-
-            <!-- 弹出层内容 -->
-            <div class="json-params-detail-content">
-              <div class="flex items-center gap-8px mb-16px">
-                <Icon :icon="titleIcon" class="text-[var(--el-color-primary)] text-18px" />
-                <span class="text-16px font-600 text-[var(--el-text-color-primary)]">
-                  {{ title }}
-                </span>
-              </div>
+  <!-- 参数配置 -->
+  <div class="w-full space-y-12px">
+    <!-- JSON 输入框 -->
+    <div class="relative">
+      <el-input
+        v-model="paramsJson"
+        type="textarea"
+        :rows="4"
+        :placeholder="placeholder"
+        @input="handleParamsChange"
+        :class="{ 'is-error': jsonError }"
+      />
+      <!-- 查看详细示例弹出层 -->
+      <div class="absolute top-8px right-8px">
+        <el-popover
+          placement="left-start"
+          :width="450"
+          trigger="click"
+          :show-arrow="true"
+          :offset="8"
+          popper-class="json-params-detail-popover"
+        >
+          <template #reference>
+            <el-button
+              type="info"
+              :icon="InfoFilled"
+              circle
+              size="small"
+              :title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
+            />
+          </template>
+
+          <!-- 弹出层内容 -->
+          <div class="json-params-detail-content">
+            <div class="flex items-center gap-8px mb-16px">
+              <Icon :icon="titleIcon" class="text-[var(--el-color-primary)] text-18px" />
+              <span class="text-16px font-600 text-[var(--el-text-color-primary)]">
+                {{ title }}
+              </span>
+            </div>
 
-              <div class="space-y-16px">
-                <!-- 参数列表 -->
-                <div v-if="paramsList.length > 0">
-                  <div class="flex items-center gap-8px mb-8px">
-                    <Icon :icon="paramsIcon" class="text-[var(--el-color-primary)] text-14px" />
-                    <span class="text-14px font-500 text-[var(--el-text-color-primary)]">
-                      {{ paramsLabel }}
-                    </span>
-                  </div>
-                  <div class="ml-22px space-y-8px">
-                    <div
-                      v-for="param in paramsList"
-                      :key="param.identifier"
-                      class="flex items-center justify-between p-8px bg-[var(--el-fill-color-lighter)] rounded-4px"
-                    >
-                      <div class="flex-1">
-                        <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)]">
-                          {{ param.identifier }}
-                        </div>
-                      </div>
-                      <div class="flex items-center gap-8px">
-                        <el-tag :type="getParamTypeTag(param.dataType)" size="small">
-                          {{ getParamTypeName(param.dataType) }}
+            <div class="space-y-16px">
+              <!-- 参数列表 -->
+              <div v-if="paramsList.length > 0">
+                <div class="flex items-center gap-8px mb-8px">
+                  <Icon :icon="paramsIcon" class="text-[var(--el-color-primary)] text-14px" />
+                  <span class="text-14px font-500 text-[var(--el-text-color-primary)]">
+                    {{ paramsLabel }}
+                  </span>
+                </div>
+                <div class="ml-22px space-y-8px">
+                  <div
+                    v-for="param in paramsList"
+                    :key="param.identifier"
+                    class="flex items-center justify-between p-8px bg-[var(--el-fill-color-lighter)] rounded-4px"
+                  >
+                    <div class="flex-1">
+                      <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>
-                        <span class="text-11px text-[var(--el-text-color-secondary)]">
-                          {{ getExampleValue(param) }}
-                        </span>
                       </div>
+                      <div class="text-11px text-[var(--el-text-color-secondary)]">
+                        {{ param.identifier }}
+                      </div>
+                    </div>
+                    <div class="flex items-center gap-8px">
+                      <el-tag :type="getParamTypeTag(param.dataType)" size="small">
+                        {{ getParamTypeName(param.dataType) }}
+                      </el-tag>
+                      <span class="text-11px text-[var(--el-text-color-secondary)]">
+                        {{ getExampleValue(param) }}
+                      </span>
                     </div>
                   </div>
+                </div>
 
-                  <div class="mt-12px ml-22px">
-                    <div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
-                      {{ 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)]"
-                    >
+                <div class="mt-12px ml-22px">
+                  <div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
+                    {{ 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)]"
+                  >
                       <code>{{ generateExampleJson() }}</code>
                     </pre>
-                  </div>
                 </div>
+              </div>
 
-                <!-- 无参数提示 -->
-                <div v-else>
-                  <div class="text-center py-16px">
-                    <p class="text-14px text-[var(--el-text-color-secondary)]">{{
-                      emptyMessage
-                    }}</p>
-                  </div>
+              <!-- 无参数提示 -->
+              <div v-else>
+                <div class="text-center py-16px">
+                  <p class="text-14px text-[var(--el-text-color-secondary)]">{{ emptyMessage }}</p>
                 </div>
               </div>
             </div>
-          </el-popover>
-        </div>
+          </div>
+        </el-popover>
       </div>
+    </div>
 
-      <!-- 验证状态和错误提示 -->
-      <div class="flex items-center justify-between">
-        <div class="flex items-center gap-8px">
-          <Icon
-            :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"
-          />
-          <span
-            :class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
-            class="text-12px"
-          >
-            {{ 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)]">{{
-            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">{{
-            JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON
-          }}</el-button>
-        </div>
+    <!-- 验证状态和错误提示 -->
+    <div class="flex items-center justify-between">
+      <div class="flex items-center gap-8px">
+        <Icon
+          :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"
+        />
+        <span
+          :class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
+          class="text-12px"
+        >
+          {{ jsonError || JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_CORRECT }}
+        </span>
       </div>
-    </div>
 
-    <!-- 无配置提示 -->
-    <div v-else class="text-center py-20px">
-      <p class="text-14px text-[var(--el-text-color-secondary)]">{{ noConfigMessage }}</p>
+      <!-- 快速填充按钮 -->
+      <div v-if="paramsList.length > 0" class="flex items-center gap-8px">
+        <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">{{
+          JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON
+        }}</el-button>
+      </div>
     </div>
   </div>
 </template>
@@ -162,7 +153,7 @@ import {
 /** JSON参数输入组件 - 通用版本 */
 defineOptions({ name: 'JsonParamsInput' })
 
-export interface JsonParamsConfig {
+interface JsonParamsConfig {
   // 服务配置
   service?: {
     name: string
@@ -207,19 +198,6 @@ const localValue = useVModel(props, 'modelValue', emit, {
 const paramsJson = ref('') // JSON参数字符串
 const jsonError = ref('') // JSON验证错误信息
 
-// 计算属性:是否有配置
-const hasConfig = computed(() => {
-  // TODO @puhui999: 后续统一处理
-  console.log(props.config)
-  // return !!(
-  //   props.config?.service ||
-  //   props.config?.event ||
-  //   props.config?.properties ||
-  //   props.config?.custom
-  // )
-  return true
-})
-
 // 计算属性:参数列表
 const paramsList = computed(() => {
   switch (props.type) {

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

@@ -1,5 +1,4 @@
 <!-- 执行器配置组件 -->
-<!-- TODO @puhui999:每个执行器的 UI 风格,应该和【触发器配置】那,有点像 -->
 <template>
   <el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
     <template #header>
@@ -7,15 +6,10 @@
         <div class="flex items-center gap-8px">
           <Icon icon="ep:setting" class="text-[var(--el-color-primary)] text-18px" />
           <span class="text-16px font-600 text-[var(--el-text-color-primary)]">执行器配置</span>
-          <el-tag size="small" type="info">{{ actions.length }}/{{ maxActions }}</el-tag>
+          <el-tag size="small" type="info">{{ actions.length }} 个执行器</el-tag>
         </div>
         <div class="flex items-center gap-8px">
-          <el-button
-            type="primary"
-            size="small"
-            @click="addAction"
-            :disabled="actions.length >= maxActions"
-          >
+          <el-button type="primary" size="small" @click="addAction">
             <Icon icon="ep:plus" />
             添加执行器
           </el-button>
@@ -35,27 +29,37 @@
       </div>
 
       <!-- 执行器列表 -->
-      <div v-else class="space-y-16px">
+      <div v-else class="space-y-24px">
         <div
           v-for="(action, index) in actions"
           :key="`action-${index}`"
-          class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
+          class="border-2 border-blue-200 rounded-8px bg-blue-50 shadow-sm hover:shadow-md transition-shadow"
         >
-          <div class="flex items-center justify-between mb-16px">
-            <div class="flex items-center gap-8px">
-              <Icon icon="ep:setting" class="text-[var(--el-color-success)] text-16px" />
-              <span>执行器 {{ index + 1 }}</span>
-              <el-tag :type="getActionTypeTag(action.type)" size="small">
+          <!-- 执行器头部 - 蓝色主题 -->
+          <div
+            class="flex items-center justify-between p-16px bg-gradient-to-r from-blue-50 to-sky-50 border-b border-blue-200 rounded-t-6px"
+          >
+            <div class="flex items-center gap-12px">
+              <div class="flex items-center gap-8px text-16px font-600 text-blue-700">
+                <div
+                  class="w-24px h-24px bg-blue-500 text-white rounded-full flex items-center justify-center text-12px font-bold"
+                >
+                  {{ index + 1 }}
+                </div>
+                <span>执行器 {{ index + 1 }}</span>
+              </div>
+              <el-tag :type="getActionTypeTag(action.type)" size="small" class="font-500">
                 {{ getActionTypeLabel(action.type) }}
               </el-tag>
             </div>
-            <div>
+            <div class="flex items-center gap-8px">
               <el-button
+                v-if="actions.length > 1"
                 type="danger"
                 size="small"
                 text
                 @click="removeAction(index)"
-                v-if="actions.length > 1"
+                class="hover:bg-red-50"
               >
                 <Icon icon="ep:delete" />
                 删除
@@ -63,7 +67,8 @@
             </div>
           </div>
 
-          <div class="space-y-16px">
+          <!-- 执行器内容区域 -->
+          <div class="p-16px space-y-16px">
             <!-- 执行类型选择 -->
             <div class="w-full">
               <el-form-item label="执行类型" required>
@@ -93,14 +98,14 @@
 
             <!-- 告警配置 - 只有恢复告警时才显示 -->
             <AlertConfig
-              v-if="action.type === ActionTypeEnum.ALERT_RECOVER"
+              v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER"
               :model-value="action.alertConfigId"
               @update:model-value="(value) => updateActionAlertConfig(index, value)"
             />
 
             <!-- 触发告警提示 - 触发告警时显示 -->
             <div
-              v-if="action.type === ActionTypeEnum.ALERT_TRIGGER"
+              v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER"
               class="border border-[var(--el-border-color-light)] rounded-6px p-16px bg-[var(--el-fill-color-blank)]"
             >
               <div class="flex items-center gap-8px mb-8px">
@@ -117,14 +122,11 @@
       </div>
 
       <!-- 添加提示 -->
-      <div v-if="actions.length > 0 && actions.length < maxActions" class="text-center py-16px">
+      <div v-if="actions.length > 0" class="text-center py-16px">
         <el-button type="primary" plain @click="addAction">
           <Icon icon="ep:plus" />
           继续添加执行器
         </el-button>
-        <span class="block mt-8px text-12px text-[var(--el-text-color-secondary)]">
-          最多可添加 {{ maxActions }} 个执行器
-        </span>
       </div>
     </div>
   </el-card>
@@ -136,13 +138,9 @@ import DeviceControlConfig from '../configs/DeviceControlConfig.vue'
 import AlertConfig from '../configs/AlertConfig.vue'
 import type { Action } from '@/api/iot/rule/scene'
 import {
-  IotRuleSceneActionTypeEnum as ActionTypeEnum,
-  isDeviceAction,
-  isAlertAction,
   getActionTypeLabel,
   getActionTypeOptions,
-  getActionTypeTag,
-  SCENE_RULE_CONFIG
+  IotRuleSceneActionTypeEnum
 } from '@/views/iot/utils/constants'
 
 /** 执行器配置组件 */
@@ -158,7 +156,34 @@ const emit = defineEmits<{
 
 const actions = useVModel(props, 'actions', emit)
 
-const maxActions = SCENE_RULE_CONFIG.MAX_ACTIONS // 最大执行器数量
+/** 获取执行器标签类型(用于 el-tag 的 type 属性) */
+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'
+}
+
+/** 判断是否为设备执行器类型 */
+const isDeviceAction = (type: number): boolean => {
+  const deviceActionTypes = [
+    IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
+    IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
+  ] as number[]
+  return deviceActionTypes.includes(type)
+}
+
+/** 判断是否为告警执行器类型 */
+const isAlertAction = (type: number): boolean => {
+  const alertActionTypes = [
+    IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
+    IotRuleSceneActionTypeEnum.ALERT_RECOVER
+  ] as number[]
+  return alertActionTypes.includes(type)
+}
 
 /**
  * 创建默认的执行器数据
@@ -166,7 +191,7 @@ const maxActions = SCENE_RULE_CONFIG.MAX_ACTIONS // 最大执行器数量
  */
 const createDefaultActionData = (): Action => {
   return {
-    type: ActionTypeEnum.DEVICE_PROPERTY_SET, // 默认为设备属性设置
+    type: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, // 默认为设备属性设置
     productId: undefined,
     deviceId: undefined,
     identifier: undefined, // 物模型标识符(服务调用时使用)
@@ -179,10 +204,6 @@ const createDefaultActionData = (): Action => {
  * 添加执行器
  */
 const addAction = () => {
-  if (actions.value.length >= maxActions) {
-    return
-  }
-
   const newAction = createDefaultActionData()
   actions.value.push(newAction)
 }

+ 11 - 4
src/views/iot/rule/scene/form/sections/TriggerSection.vue

@@ -67,7 +67,7 @@
 
             <!-- 定时触发配置 -->
             <div
-              v-else-if="triggerItem.type === TriggerTypeEnum.TIMER"
+              v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER"
               class="flex flex-col gap-16px"
             >
               <div
@@ -119,8 +119,7 @@ import { Crontab } from '@/components/Crontab'
 import type { Trigger } from '@/api/iot/rule/scene'
 import {
   getTriggerTypeLabel,
-  getTriggerTagType,
-  IotRuleSceneTriggerTypeEnum as TriggerTypeEnum,
+  IotRuleSceneTriggerTypeEnum,
   isDeviceTrigger
 } from '@/views/iot/utils/constants'
 
@@ -137,10 +136,18 @@ const emit = defineEmits<{
 
 const triggers = useVModel(props, 'triggers', emit)
 
+/** 获取触发器标签类型(用于 el-tag 的 type 属性) */
+const getTriggerTagType = (type: number): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
+  if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
+    return 'warning'
+  }
+  return isDeviceTrigger(type) ? 'success' : 'info'
+}
+
 /** 添加触发器 */
 const addTrigger = () => {
   const newTrigger: Trigger = {
-    type: TriggerTypeEnum.DEVICE_STATE_UPDATE,
+    type: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
     productId: undefined,
     deviceId: undefined,
     identifier: undefined,

+ 3 - 12
src/views/iot/rule/scene/form/selectors/DeviceSelector.vue

@@ -24,12 +24,7 @@
           <div class="text-12px text-[var(--el-text-color-secondary)]">{{ device.deviceKey }}</div>
         </div>
         <div class="flex items-center gap-4px" v-if="device.id > 0">
-          <el-tag size="small" :type="getDeviceEnableStatusTagType(device.status)">
-            {{ getDeviceEnableStatusText(device.status) }}
-          </el-tag>
-          <el-tag size="small" :type="getDeviceActiveStatus(device.activeTime).tagType">
-            {{ getDeviceActiveStatus(device.activeTime).text }}
-          </el-tag>
+          <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
         </div>
       </div>
     </el-option>
@@ -38,12 +33,8 @@
 
 <script setup lang="ts">
 import { DeviceApi } from '@/api/iot/device/device'
-import {
-  getDeviceEnableStatusText,
-  getDeviceEnableStatusTagType,
-  getDeviceActiveStatus,
-  DEVICE_SELECTOR_OPTIONS
-} from '@/views/iot/utils/constants'
+import { DEVICE_SELECTOR_OPTIONS } from '@/views/iot/utils/constants'
+import { DICT_TYPE } from '@/utils/dict'
 
 /** 设备选择器组件 */
 defineOptions({ name: 'DeviceSelector' })

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

@@ -222,7 +222,9 @@ const availableOperators = computed(() => {
   if (!props.propertyType) {
     return allOperators
   }
-  return allOperators.filter((op) => op.supportedTypes.includes(props.propertyType!))
+  return allOperators.filter((op) =>
+    (op.supportedTypes as any[]).includes(props.propertyType || '')
+  )
 })
 
 // 计算属性:当前选中的操作符
@@ -243,10 +245,12 @@ watch(
   () => props.propertyType,
   () => {
     // 如果当前选择的操作符不支持新的属性类型,则清空选择
-    if (localValue.value && selectedOperator.value) {
-      if (!selectedOperator.value.supportedTypes.includes(props.propertyType || '')) {
-        localValue.value = ''
-      }
+    if (
+      localValue.value &&
+      selectedOperator.value &&
+      !(selectedOperator.value.supportedTypes as any[]).includes(props.propertyType || '')
+    ) {
+      localValue.value = ''
     }
   }
 )

+ 51 - 53
src/views/iot/rule/scene/form/selectors/PropertySelector.vue

@@ -262,7 +262,6 @@ const handleChange = (value: string) => {
   }
 }
 
-// TODO @puhui999:这里没用到哈;
 /**
  * 获取物模型TSL数据
  */
@@ -297,62 +296,62 @@ const parseThingModelData = () => {
   const tsl = thingModelTSL.value
   const properties: PropertySelectorItem[] = []
 
-  // TODO @puhui999:if return,减少下括号层级;
-  if (tsl) {
-    // 解析属性
-    if (tsl.properties && Array.isArray(tsl.properties)) {
-      tsl.properties.forEach((prop) => {
-        properties.push({
-          identifier: prop.identifier,
-          name: prop.name,
-          description: prop.description,
-          dataType: prop.dataType,
-          type: IoTThingModelTypeEnum.PROPERTY,
-          accessMode: prop.accessMode,
-          required: prop.required,
-          unit: getPropertyUnit(prop),
-          range: getPropertyRange(prop),
-          property: prop
-        })
+  if (!tsl) {
+    propertyList.value = properties
+    return
+  }
+  // 解析属性
+  if (tsl.properties && Array.isArray(tsl.properties)) {
+    tsl.properties.forEach((prop) => {
+      properties.push({
+        identifier: prop.identifier,
+        name: prop.name,
+        description: prop.description,
+        dataType: prop.dataType,
+        type: IoTThingModelTypeEnum.PROPERTY,
+        accessMode: prop.accessMode,
+        required: prop.required,
+        unit: getPropertyUnit(prop),
+        range: getPropertyRange(prop),
+        property: prop
       })
-    }
+    })
+  }
 
-    // 解析事件
-    if (tsl.events && Array.isArray(tsl.events)) {
-      tsl.events.forEach((event) => {
-        properties.push({
-          identifier: event.identifier,
-          name: event.name,
-          description: event.description,
-          dataType: 'struct',
-          type: IoTThingModelTypeEnum.EVENT,
-          eventType: event.type,
-          required: event.required,
-          outputParams: event.outputParams,
-          event: event
-        })
+  // 解析事件
+  if (tsl.events && Array.isArray(tsl.events)) {
+    tsl.events.forEach((event) => {
+      properties.push({
+        identifier: event.identifier,
+        name: event.name,
+        description: event.description,
+        dataType: 'struct',
+        type: IoTThingModelTypeEnum.EVENT,
+        eventType: event.type,
+        required: event.required,
+        outputParams: event.outputParams,
+        event: event
       })
-    }
+    })
+  }
 
-    // 解析服务
-    if (tsl.services && Array.isArray(tsl.services)) {
-      tsl.services.forEach((service) => {
-        properties.push({
-          identifier: service.identifier,
-          name: service.name,
-          description: service.description,
-          dataType: 'struct',
-          type: IoTThingModelTypeEnum.SERVICE,
-          callType: service.callType,
-          required: service.required,
-          inputParams: service.inputParams,
-          outputParams: service.outputParams,
-          service: service
-        })
+  // 解析服务
+  if (tsl.services && Array.isArray(tsl.services)) {
+    tsl.services.forEach((service) => {
+      properties.push({
+        identifier: service.identifier,
+        name: service.name,
+        description: service.description,
+        dataType: 'struct',
+        type: IoTThingModelTypeEnum.SERVICE,
+        callType: service.callType,
+        required: service.required,
+        inputParams: service.inputParams,
+        outputParams: service.outputParams,
+        service: service
       })
-    }
+    })
   }
-
   propertyList.value = properties
 }
 
@@ -396,7 +395,7 @@ const getPropertyRange = (property: any) => {
   return undefined
 }
 
-/** 监听产品变化 *
+/** 监听产品变化 */
 watch(
   () => props.productId,
   () => {
@@ -410,7 +409,6 @@ watch(
   () => props.triggerType,
   () => {
     localValue.value = ''
-    // el-popover 会自动关闭,无需手动处理
   }
 )
 </script>

+ 14 - 147
src/views/iot/utils/constants.ts

@@ -55,13 +55,6 @@ export const IotDeviceMessageMethodEnum = {
   }
 }
 
-// IoT 产品物模型类型枚举类
-export const IotThingModelTypeEnum = {
-  PROPERTY: 1, // 属性
-  SERVICE: 2, // 服务
-  EVENT: 3 // 事件
-}
-
 // IoT 产品物模型服务调用方式枚举
 export const IoTThingModelServiceCallTypeEnum = {
   ASYNC: {
@@ -331,60 +324,12 @@ export const getActionTypeOptions = () => [
   }
 ]
 
-/** 判断是否为设备执行器类型 */
-export const isDeviceAction = (type: number): boolean => {
-  const deviceActionTypes = [
-    IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
-    IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
-  ] as number[]
-  return deviceActionTypes.includes(type)
-}
-
-/** 判断是否为告警执行器类型 */
-export const isAlertAction = (type: number): boolean => {
-  const alertActionTypes = [
-    IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
-    IotRuleSceneActionTypeEnum.ALERT_RECOVER
-  ] as number[]
-  return alertActionTypes.includes(type)
-}
-
 /** 获取执行器类型标签 */
 export const getActionTypeLabel = (type: number): string => {
   const option = getActionTypeOptions().find((opt) => opt.value === type)
   return option?.label || '未知类型'
 }
 
-/** 获取执行器标签类型(用于 el-tag 的 type 属性) */
-// TODO @puhui999:这种跟界面相关的,可以拿到对应组件里;
-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'
-}
-
-// TODO @puhui999:建议不设置最大值哈。
-/** 场景联动规则配置常量 */
-export const SCENE_RULE_CONFIG = {
-  MAX_ACTIONS: 5, // 最大执行器数量
-  MAX_TRIGGERS: 10, // 最大触发器数量
-  MAX_CONDITIONS: 20 // 最大条件数量
-} as const
-
-// TODO @puhui999:下面这个要去掉么?
-/** IoT 设备消息类型枚举 */
-export const IotDeviceMessageTypeEnum = {
-  PROPERTY: 'property', // 属性
-  SERVICE: 'service', // 服务
-  EVENT: 'event' // 事件
-} as const
-
 /** IoT 场景联动触发条件参数操作符枚举 */
 export const IotRuleSceneTriggerConditionParameterOperatorEnum = {
   EQUALS: { name: '等于', value: '=' }, // 等于
@@ -424,42 +369,41 @@ export const getConditionTypeOptions = () => [
   }
 ]
 
-/** 设备状态枚举 */
+/** 设备状态枚举 - 统一的设备状态管理 */
 export const IoTDeviceStatusEnum = {
+  // 在线状态
   ONLINE: {
     label: '在线',
-    value: 'online'
+    value: 'online',
+    tagType: 'success'
   },
   OFFLINE: {
     label: '离线',
-    value: 'offline'
-  }
-} as const
-
-/** 设备启用状态枚举 */
-// TODO @puhui999:这个是不是和 IoTDeviceStatusEnum 合并下;额外增加一个 value2
-export const IoTDeviceEnableStatusEnum = {
+    value: 'offline',
+    tagType: 'danger'
+  },
+  // 启用状态
   ENABLED: {
     label: '正常',
     value: 0,
+    value2: 'enabled',
     tagType: 'success'
   },
   DISABLED: {
     label: '禁用',
     value: 1,
+    value2: 'disabled',
     tagType: 'danger'
-  }
-} as const
-
-/** 设备激活状态枚举 */
-// TODO @puhui999:这个是不是搞到界面里。label 就是 IoTDeviceStatusEnum,然后 tag 界面里处理;;或者也可以在想想,= = 主要设备状态有 3 个枚举,嘿嘿~
-export const IoTDeviceActiveStatusEnum = {
+  },
+  // 激活状态
   ACTIVATED: {
     label: '已激活',
+    value2: 'activated',
     tagType: 'success'
   },
   NOT_ACTIVATED: {
     label: '未激活',
+    value2: 'not_activated',
     tagType: 'info'
   }
 } as const
@@ -472,72 +416,6 @@ export const DEVICE_SELECTOR_OPTIONS = {
   }
 } 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 => {
-  // TODO @puhui999:设备有 3 个状态,上线、离线,未激活;
-  const statusItem = Object.values(IoTDeviceEnableStatusEnum).find((item) => item.value === status)
-  return statusItem?.label || '未知'
-}
-
-/** 获取设备启用状态标签类型 */
-// TODO @puhui999:这个是不是可以直接在界面里处理;或者也可以在想想,= = 主要设备状态有 3 个枚举,嘿嘿~
-export const getDeviceEnableStatusTagType = (
-  status: number
-): 'primary' | 'success' | 'info' | 'warning' | 'danger' => {
-  const statusItem = Object.values(IoTDeviceEnableStatusEnum).find((item) => item.value === status)
-  return statusItem?.tagType || 'info'
-}
-
-/** 获取设备激活状态文本和标签类型 */
-// TODO @puhui999:这个是不是可以直接在界面里处理;或者也可以在想想,= = 主要设备状态有 3 个枚举,嘿嘿~
-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' }, // 在时间之前
@@ -555,17 +433,6 @@ export const getTriggerTypeLabel = (type: number): string => {
   return option?.label || '未知类型'
 }
 
-// TODO @puhui999:这种跟界面相关的,可以拿到对应组件里;
-/** 获取触发器标签类型(用于 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 参数输入组件类型枚举 */