learshaw 4 месяцев назад
Родитель
Сommit
d9b58ac07c

+ 0 - 63
ems-ui-cloud/src/api/mgr/OpEnergyStrategy.js

@@ -1,63 +0,0 @@
-import request from '@/utils/request'
-
-
-//获取能源策略参数
-export function getStrategyParam(code) {
-  return request({
-    url: '/ems/energyStrategy/param?strategyCode='+code,
-    method: 'get',
-  })
-}
-
-// 获取能源策略参数选项
-export function getStrategyParamOption(query) {
-  return request({
-    url: '/ems/energyStrategy/param/option',
-    method: 'get',
-    params: query
-  })
-}
-
-// 修改能源策略
-export function updateOpEnergyStrategy(data) {
-  return request({
-    url: '/ems/energyStrategy/param',
-    method: 'put',
-    data: data
-  })
-}
-
-//查询策略步骤
-export function getStrategyStep(code) {
-  return request({
-    url: '/ems/energyStrategy/step?strategyCode='+code,
-    method: 'get',
-  })
-}
-
-//新增策略步骤
-
-export function addStrategyStep(data) {
-  return request({
-    url: '/ems/energyStrategy/step',
-    method: 'post',
-    data: data
-  })
-}
-
-// 修改策略步骤
-export function updateStrategyStep(data) {
-  return request({
-    url: '/ems/energyStrategy/step',
-    method: 'put',
-    data: data
-  })
-}
-
-//删除策略步骤
-export function deleteStrategyStep(code) {
-  return request({
-    url: '/ems/energyStrategy/step?strategyCode='+code,
-    method: 'delete',
-  })
-}

+ 50 - 0
ems-ui-cloud/src/api/mgr/energyStrategy.js

@@ -316,3 +316,53 @@ export function deleteStrategyTemplate(templateCode) {
     method: 'delete'
   })
 }
+
+// ===========================
+// 轮询监控管理(新增)
+// ===========================
+
+/**
+ * 获取轮询监控状态
+ * 返回当前注册的轮询任务数量、策略列表及配置详情
+ */
+export function getPollingStatus() {
+  return request({
+    url: '/ems/energyStrategy/polling/status',
+    method: 'get'
+  })
+}
+
+/**
+ * 手动触发一次轮询检查
+ * 用于测试轮询配置是否正确
+ * @param {string} strategyCode 策略编码
+ */
+export function triggerPollingCheck(strategyCode) {
+  return request({
+    url: '/ems/energyStrategy/polling/trigger/' + strategyCode,
+    method: 'post'
+  })
+}
+
+/**
+ * 刷新策略的轮询配置
+ * 当策略配置变更后,重新注册轮询任务
+ * @param {string} strategyCode 策略编码
+ */
+export function refreshPollingStrategy(strategyCode) {
+  return request({
+    url: '/ems/energyStrategy/polling/refresh/' + strategyCode,
+    method: 'post'
+  })
+}
+
+/**
+ * 检查策略是否有轮询任务
+ * @param {string} strategyCode 策略编码
+ */
+export function checkPollingTask(strategyCode) {
+  return request({
+    url: '/ems/energyStrategy/polling/check/' + strategyCode,
+    method: 'get'
+  })
+}

+ 557 - 498
ems-ui-cloud/src/views/mgr/strategy/components/StepConfig.vue

@@ -1,36 +1,29 @@
 <template>
-  <el-dialog
-    :title="dialogTitle"
-    :visible.sync="visible"
-    width="900px"
-    @close="handleClose"
-    :close-on-click-modal="false"
-  >
-    <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+  <div class="step-config-panel">
+    <el-form ref="form" :model="form" :rules="rules" label-width="90px" size="small">
       <!-- 步骤名称 -->
       <el-form-item label="步骤名称" prop="stepName">
-        <el-input v-model="form.stepName" placeholder="请输入步骤名称" />
+        <el-input v-model="form.stepName" placeholder="请输入步骤名称" @input="emitChange" />
       </el-form-item>
 
       <!-- 步骤类型 -->
-      <el-form-item label="步骤类型" prop="stepType">
-        <el-select v-model="form.stepType" placeholder="请选择步骤类型" @change="handleStepTypeChange">
-          <el-option label="能力调用" value="ABILITY" />
-          <el-option label="延时等待" value="DELAY" />
-          <el-option label="条件判断" value="CONDITION" />
-          <el-option label="循环执行" value="LOOP" />
-          <el-option label="属性查询" value="ATTR_QUERY" />
-        </el-select>
+      <el-form-item label="步骤类型">
+        <el-tag :type="getStepTypeTag(form.stepType)">
+          <i :class="getStepTypeIcon(form.stepType)"></i>
+          {{ getStepTypeName(form.stepType) }}
+        </el-tag>
       </el-form-item>
 
       <!-- ==================== 能力调用配置 ==================== -->
       <template v-if="form.stepType === 'ABILITY'">
-        <!-- 目标设备 -->
+        <el-divider content-position="left">目标配置</el-divider>
+
         <el-form-item label="目标设备" prop="targetObjCode">
           <el-select
             v-model="form.targetObjCode"
             placeholder="请选择目标设备"
             filterable
+            style="width: 100%"
             @change="handleDeviceChange"
           >
             <el-option
@@ -38,20 +31,16 @@
               :key="device.deviceCode"
               :label="device.deviceName"
               :value="device.deviceCode"
-            >
-              <span>{{ device.deviceName }}</span>
-              <span style="float: right; color: #8492a6; font-size: 13px">
-                {{ device.deviceCode }}
-              </span>
-            </el-option>
+            />
           </el-select>
         </el-form-item>
 
-        <!-- 设备能力 -->
         <el-form-item label="设备能力" prop="abilityKey">
           <el-select
             v-model="form.abilityKey"
             placeholder="请选择设备能力"
+            style="width: 100%"
+            :loading="loadingAbility"
             @change="handleAbilityChange"
           >
             <el-option
@@ -59,274 +48,222 @@
               :key="ability.abilityKey"
               :label="ability.abilityName"
               :value="ability.abilityKey"
-            >
-              <span>{{ ability.abilityName }}</span>
-              <span style="float: right; color: #8492a6; font-size: 13px">
-                {{ ability.abilityDesc }}
-              </span>
-            </el-option>
+            />
           </el-select>
         </el-form-item>
 
-        <!-- 参数来源 -->
-        <el-form-item label="参数来源" prop="paramSource">
-          <el-radio-group v-model="form.paramSource" @change="handleParamSourceChange">
-            <el-radio label="STATIC">静态参数</el-radio>
-            <el-radio label="CONTEXT">上下文参数</el-radio>
-            <el-radio label="ATTR">设备属性</el-radio>
+        <el-divider content-position="left">参数配置</el-divider>
+
+        <el-form-item label="参数来源">
+          <el-radio-group v-model="form.paramSource" size="mini" @change="handleParamSourceChange">
+            <el-radio-button label="STATIC">静态</el-radio-button>
+            <el-radio-button label="CONTEXT">上下文</el-radio-button>
           </el-radio-group>
         </el-form-item>
 
-        <!-- 静态参数配置 -->
-        <el-form-item
-          v-if="form.paramSource === 'STATIC' && currentAbility"
-          label="参数值"
-        >
-          <!-- 场景1: paramDefinition 为 null - 无参数 -->
-          <div v-if="!currentAbility.paramDefinition">
-            <el-tag type="info">该能力无需参数</el-tag>
+        <el-form-item v-if="form.paramSource === 'STATIC'" label="参数值">
+          <div v-if="!currentAbility || !currentAbility.paramDefinition">
+            <el-tag type="info" size="small">该能力无需参数</el-tag>
           </div>
-
-          <!-- 场景2: Options 类型 - 枚举值 -->
           <div v-else-if="getParamType(currentAbility.paramDefinition) === 'Options'">
-            <el-select
-              v-model="form.abilityParam"
-              placeholder="请选择参数"
-            >
+            <el-select v-model="form.abilityParam" placeholder="请选择" style="width: 100%" @change="emitChange">
               <el-option
                 v-for="option in parseOptions(currentAbility.paramDefinition)"
                 :key="option.value"
                 :label="option.key"
-                :value="option.value"
+                :value="String(option.value)"
               />
             </el-select>
-            <div style="margin-top: 8px; color: #909399; font-size: 12px;">
-              当前选择: {{ getSelectedOptionLabel() }}
-            </div>
           </div>
-
-          <!-- 场景3: Slider 类型 - 滑块 -->
-          <div v-else-if="getParamType(currentAbility.paramDefinition) === 'Slider'">
-            <el-slider
-              v-model="sliderValue"
-              :min="getSliderMin(currentAbility.paramDefinition)"
-              :max="getSliderMax(currentAbility.paramDefinition)"
-              :format-tooltip="formatSliderTooltip"
-              @change="handleSliderChange"
-            />
-            <div style="margin-top: 8px;">
-              <el-input-number
-                v-model="sliderValue"
-                :min="getSliderMin(currentAbility.paramDefinition)"
-                :max="getSliderMax(currentAbility.paramDefinition)"
-                @change="handleSliderChange"
-              />
-              <span style="margin-left: 10px; color: #909399;">
-                {{ getSliderUnit(currentAbility.paramDefinition) }}
-              </span>
-            </div>
+          <div v-else>
+            <el-input v-model="form.abilityParam" placeholder="请输入参数值" @input="emitChange" />
           </div>
+        </el-form-item>
+      </template>
 
-          <!-- 场景4: Input 类型 - 文本输入 -->
-          <div v-else-if="getParamType(currentAbility.paramDefinition) === 'Input'">
-            <el-input
-              v-model="form.abilityParam"
-              placeholder="请输入参数值"
-            >
-              <template #append v-if="getInputUnit(currentAbility.paramDefinition)">
-                {{ getInputUnit(currentAbility.paramDefinition) }}
-              </template>
-            </el-input>
-          </div>
+      <!-- ==================== 属性查询配置 ==================== -->
+      <template v-if="form.stepType === 'ATTR_QUERY'">
+        <el-divider content-position="left">查询配置</el-divider>
 
-          <!-- 未知类型 - 兜底 -->
-          <div v-else>
-            <el-input
-              v-model="form.abilityParam"
-              placeholder="请输入参数值"
+        <el-form-item label="目标设备" prop="targetObjCode">
+          <el-select
+            v-model="form.targetObjCode"
+            placeholder="请选择设备"
+            filterable
+            style="width: 100%"
+            @change="handleQueryDeviceChange"
+          >
+            <el-option
+              v-for="device in deviceList"
+              :key="device.deviceCode"
+              :label="device.deviceName"
+              :value="device.deviceCode"
             />
-          </div>
+          </el-select>
         </el-form-item>
 
-        <!-- 上下文参数配置 -->
-        <el-form-item v-if="form.paramSource === 'CONTEXT'" label="参数映射">
-          <el-input
-            v-model="form.paramMapping"
-            type="textarea"
-            :rows="3"
-            placeholder='示例: {"value": "context.switchState"}'
-          />
-          <div style="margin-top: 8px; color: #909399; font-size: 12px;">
-            <i class="el-icon-info"></i>
-            从执行上下文中动态获取参数值
+        <el-form-item v-if="loadingQueryAttr">
+          <div style="color: #909399; font-size: 12px;">
+            <i class="el-icon-loading"></i> 正在加载属性列表...
           </div>
         </el-form-item>
 
-        <!-- 设备属性配置 -->
-        <el-form-item v-if="form.paramSource === 'ATTR'" label="属性映射">
-          <el-input
-            v-model="form.paramMapping"
-            type="textarea"
-            :rows="3"
-            placeholder='示例: {"value": "D-B-QS-10000001.Switch"}'
-          />
-          <div style="margin-top: 8px; color: #909399; font-size: 12px;">
-            <i class="el-icon-info"></i>
-            从设备属性中动态获取参数值
+        <el-form-item label="查询属性" prop="abilityKey" v-if="form.targetObjCode && !loadingQueryAttr">
+          <el-select
+            v-model="form.abilityKey"
+            placeholder="请选择要查询的属性"
+            filterable
+            style="width: 100%"
+            @change="handleQueryAttrChange"
+          >
+            <el-option-group
+              v-for="group in queryAttrGroups"
+              :key="group.name"
+              :label="group.label"
+            >
+              <el-option
+                v-for="attr in group.attrs"
+                :key="attr.attrKey"
+                :label="attr.attrName"
+                :value="attr.attrKey"
+              >
+                <span>{{ attr.attrName }}</span>
+                <span style="float: right; color: #909399; font-size: 12px">{{ attr.attrKey }}</span>
+              </el-option>
+            </el-option-group>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item v-if="selectedQueryAttr">
+          <div class="attr-info-card">
+            <div class="attr-info-row">
+              <span class="attr-info-label">属性类型:</span>
+              <el-tag :type="getAttrTypeTag(selectedQueryAttr.attrValueType)" size="mini">
+                {{ getAttrTypeName(selectedQueryAttr.attrValueType) }}
+              </el-tag>
+            </div>
+            <div class="attr-info-row" v-if="selectedQueryAttr.attrUnit">
+              <span class="attr-info-label">单位:</span>
+              <span>{{ selectedQueryAttr.attrUnit }}</span>
+            </div>
+            <div class="attr-info-row">
+              <span class="attr-info-label">结果变量:</span>
+              <el-tag type="success" size="small">current_{{ form.abilityKey }}</el-tag>
+            </div>
           </div>
         </el-form-item>
+
+        <el-alert type="info" :closable="false" style="margin-top: 12px;">
+          <template #title>
+            查询结果将保存到上下文变量 <code>current_{{ form.abilityKey || '[属性键]' }}</code>
+          </template>
+        </el-alert>
       </template>
 
       <!-- ==================== 延时等待配置 ==================== -->
-      <el-form-item v-if="form.stepType === 'DELAY'" label="延时时长" prop="delaySeconds">
-        <el-input-number
-          v-model="form.delaySeconds"
-          :min="1"
-          :max="3600"
-          placeholder="秒"
-        />
-        <span style="margin-left: 10px;">秒</span>
-      </el-form-item>
-
-      <!-- ==================== 条件判断配置 ==================== -->
-      <el-form-item v-if="form.stepType === 'CONDITION'" label="条件表达式">
-        <el-input
-          v-model="form.conditionExpr"
-          type="textarea"
-          :rows="4"
-          placeholder='简单格式: {"left":"Switch","op":"==","right":"1"}
-复杂格式: {"logic":"AND","conditions":[{"left":"Switch","op":"==","right":"1"}]}'
-        />
-        <div style="margin-top: 8px; color: #909399; font-size: 12px;">
-          <i class="el-icon-info"></i>
-          支持简单条件和复杂逻辑组合(AND/OR)
+      <template v-if="form.stepType === 'DELAY'">
+        <el-divider content-position="left">延时配置</el-divider>
+        <el-form-item label="延时时长" prop="delaySeconds">
+          <el-input-number v-model="form.delaySeconds" :min="1" :max="3600" style="width: 150px" @change="emitChange" />
+          <span style="margin-left: 8px;">秒</span>
+        </el-form-item>
+        <div class="delay-shortcuts">
+          <span class="shortcuts-label">快捷:</span>
+          <el-tag @click="setDelay(1)" class="shortcut-tag">1秒</el-tag>
+          <el-tag @click="setDelay(2)" class="shortcut-tag">2秒</el-tag>
+          <el-tag @click="setDelay(5)" class="shortcut-tag">5秒</el-tag>
+          <el-tag @click="setDelay(10)" class="shortcut-tag">10秒</el-tag>
+          <el-tag @click="setDelay(30)" class="shortcut-tag">30秒</el-tag>
         </div>
-      </el-form-item>
+      </template>
 
       <!-- ==================== 循环执行配置 ==================== -->
       <template v-if="form.stepType === 'LOOP'">
-        <el-card shadow="never" style="margin-bottom: 20px; background: #f5f7fa;">
-          <div slot="header" class="clearfix">
-            <span style="font-weight: 600;">循环配置</span>
-          </div>
+        <el-divider content-position="left">循环配置</el-divider>
 
-          <!-- 最大循环次数 -->
-          <el-form-item label="最大次数" label-width="120px">
-            <el-input-number
-              v-model="form.loopMaxCount"
-              :min="0"
-              :max="1000"
-              placeholder="0表示无限循环"
-            />
-            <span style="margin-left: 10px; color: #909399;">
-              0表示无限循环(需配置跳出条件)
-            </span>
+        <el-form-item label="最大次数">
+          <el-input-number v-model="form.loopMaxCount" :min="0" :max="1000" style="width: 150px" @change="emitChange" />
+          <div class="form-tip">0表示无限循环</div>
+        </el-form-item>
+
+        <el-form-item label="循环间隔">
+          <el-input-number v-model="form.loopInterval" :min="100" :max="60000" :step="100" style="width: 150px" @change="emitChange" />
+          <span style="margin-left: 8px;">毫秒</span>
+        </el-form-item>
+
+        <el-divider content-position="left">跳出条件</el-divider>
+
+        <el-form-item label="条件类型">
+          <el-radio-group v-model="loopBreakType" size="mini" @change="switchBreakType">
+            <el-radio-button label="ATTR">属性监测</el-radio-button>
+            <el-radio-button label="EXPR">表达式</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+
+        <template v-if="loopBreakType === 'ATTR'">
+          <el-form-item label="监测设备">
+            <el-select v-model="breakConfig.deviceCode" placeholder="请选择设备" filterable style="width: 100%" @change="handleBreakDeviceChange">
+              <el-option v-for="device in deviceList" :key="device.deviceCode" :label="device.deviceName" :value="device.deviceCode" />
+            </el-select>
           </el-form-item>
 
-          <!-- 循环间隔 -->
-          <el-form-item label="循环间隔" label-width="120px">
-            <el-input-number
-              v-model="form.loopInterval"
-              :min="100"
-              :max="60000"
-              :step="100"
-            />
-            <span style="margin-left: 10px;">毫秒</span>
+          <el-form-item label="监测属性" v-if="breakConfig.deviceCode && !loadingBreakAttr">
+            <el-select v-model="breakConfig.attrKey" placeholder="请选择属性" style="width: 100%" @change="handleBreakAttrChange">
+              <el-option-group v-for="group in breakAttrGroups" :key="group.name" :label="group.label">
+                <el-option v-for="attr in group.attrs" :key="attr.attrKey" :label="attr.attrName" :value="attr.attrKey" />
+              </el-option-group>
+            </el-select>
           </el-form-item>
 
-          <!-- 跳出条件 -->
-          <el-form-item label="跳出条件" label-width="120px">
-            <el-input
-              v-model="form.loopCondition"
-              type="textarea"
-              :rows="3"
-              placeholder='示例: {"left":"current_Switch","op":"==","right":"1"}'
-            />
-            <div style="margin-top: 8px; color: #909399; font-size: 12px;">
+          <el-form-item label="跳出条件" v-if="breakConfig.attrKey">
+            <el-row :gutter="8">
+              <el-col :span="8">
+                <el-select v-model="breakConfig.operator" style="width: 100%" @change="updateLoopCondition">
+                  <el-option label="==" value="==" />
+                  <el-option label="!=" value="!=" />
+                  <el-option label=">" value=">" />
+                  <el-option label=">=" value=">=" />
+                  <el-option label="<" value="<" />
+                  <el-option label="<=" value="<=" />
+                </el-select>
+              </el-col>
+              <el-col :span="16">
+                <el-select v-if="breakAttrEnums.length > 0" v-model="breakConfig.value" placeholder="请选择值" style="width: 100%" @change="updateLoopCondition">
+                  <el-option v-for="enumItem in breakAttrEnums" :key="enumItem.attrValue" :label="enumItem.attrValueName" :value="enumItem.attrValue" />
+                </el-select>
+                <el-input v-else v-model="breakConfig.value" placeholder="请输入比较值" @input="updateLoopCondition" />
+              </el-col>
+            </el-row>
+          </el-form-item>
+
+          <el-form-item v-if="breakConfig.deviceCode && breakConfig.attrKey && breakConfig.value">
+            <div class="condition-preview">
               <i class="el-icon-info"></i>
-              满足条件时跳出循环,可引用上下文变量(如 current_Switch)
+              当 {{ getDeviceName(breakConfig.deviceCode) }}.{{ breakConfig.attrKey }} {{ breakConfig.operator }} {{ breakConfig.value }} 时跳出
             </div>
           </el-form-item>
-        </el-card>
-
-        <!-- 子步骤配置提示 -->
-        <el-alert
-          title="子步骤配置"
-          type="info"
-          :closable="false"
-          style="margin-bottom: 20px;"
-        >
-          循环步骤需要配置子步骤,子步骤将在每次循环中依次执行。
-          保存当前步骤后,请在编排器中为此循环步骤添加子步骤。
-        </el-alert>
-      </template>
-
-      <!-- ==================== 属性查询配置 ==================== -->
-      <template v-if="form.stepType === 'ATTR_QUERY'">
-        <!-- 目标设备 -->
-        <el-form-item label="目标设备" prop="targetObjCode">
-          <el-select
-            v-model="form.targetObjCode"
-            placeholder="请选择设备"
-            filterable
-            @change="handleDeviceChange"
-          >
-            <el-option
-              v-for="device in deviceList"
-              :key="device.deviceCode"
-              :label="device.deviceName"
-              :value="device.deviceCode"
-            >
-              <span>{{ device.deviceName }}</span>
-              <span style="float: right; color: #8492a6; font-size: 13px">
-                {{ device.deviceCode }}
-              </span>
-            </el-option>
-          </el-select>
-        </el-form-item>
+        </template>
 
-        <!-- 属性键 -->
-        <el-form-item label="属性键" prop="abilityKey">
-          <el-input
-            v-model="form.abilityKey"
-            placeholder="例如: Switch"
-          />
-          <div style="margin-top: 8px; color: #909399; font-size: 12px;">
-            <i class="el-icon-info"></i>
-            查询结果将保存到上下文: current_[属性键名]
-          </div>
-        </el-form-item>
+        <template v-if="loopBreakType === 'EXPR'">
+          <el-form-item label="跳出条件">
+            <el-input v-model="form.loopCondition" type="textarea" :rows="3" placeholder='{"left":"current_Switch","op":"==","right":"1"}' @input="emitChange" />
+          </el-form-item>
+        </template>
       </template>
 
       <!-- ==================== 通用配置 ==================== -->
-      <!-- 执行条件 -->
-      <el-form-item label="执行条件" v-if="form.stepType !== 'LOOP'">
-        <el-input
-          v-model="form.conditionExpr"
-          type="textarea"
-          :rows="2"
-          placeholder='留空表示无条件执行,示例: {"left":"status","op":"==","right":"ready"}'
-        />
-        <div style="margin-top: 8px; color: #909399; font-size: 12px;">
-          <i class="el-icon-question"></i>
-          此步骤执行前需满足的条件(可选)
-        </div>
+      <el-divider content-position="left">执行配置</el-divider>
+
+      <el-form-item label="失败继续">
+        <el-switch v-model="continueOnFailSwitch" @change="handleContinueOnFailChange" />
+        <span style="margin-left: 8px; color: #909399; font-size: 12px;">开启后即使失败也继续</span>
       </el-form-item>
 
-      <!-- 失败后继续 -->
-      <el-form-item label="失败后继续">
-        <el-switch v-model="continueOnFailSwitch" />
-        <span style="margin-left: 10px; color: #909399;">
-          开启后,即使此步骤失败也会继续执行后续步骤
-        </span>
+      <el-form-item label="是否启用">
+        <el-switch v-model="enableSwitch" @change="handleEnableChange" />
       </el-form-item>
     </el-form>
-
-    <div slot="footer">
-      <el-button @click="visible = false">取消</el-button>
-      <el-button type="primary" @click="handleSubmit" :loading="submitting">确定</el-button>
-    </div>
-  </el-dialog>
+  </div>
 </template>
 
 <script>
@@ -334,359 +271,481 @@ import { getModelByCode } from '@/api/basecfg/objModel'
 
 export default {
   name: 'StepConfig',
+
   props: {
-    deviceList: {
-      type: Array,
-      default: () => []
-    }
+    step: { type: Object, default: () => ({}) },
+    deviceList: { type: Array, default: () => [] },
+    contextVariables: { type: Array, default: () => [] }
   },
+
   data() {
     return {
-      visible: false,
-      dialogTitle: '配置步骤',
-      submitting: false,
       form: {
-        id: null,
-        stepCode: '',
         stepName: '',
         stepType: 'ABILITY',
-        stepIndex: 1,
         targetObjCode: '',
         targetObjType: 2,
         targetModelCode: '',
         abilityKey: '',
         abilityParam: '',
         paramSource: 'STATIC',
-        paramMapping: '',
         delaySeconds: 10,
-        conditionExpr: '',
         continueOnFail: 0,
-        // 循环配置
-        loopMaxCount: 10,
+        enable: 1,
+        loopMaxCount: 100,
         loopInterval: 1000,
-        loopCondition: '',
-        parentStepCode: null
+        loopCondition: ''
       },
       rules: {
-        stepName: [
-          { required: true, message: '请输入步骤名称', trigger: 'blur' }
-        ],
-        stepType: [
-          { required: true, message: '请选择步骤类型', trigger: 'change' }
-        ],
-        targetObjCode: [
-          { required: true, message: '请选择目标设备', trigger: 'change' }
-        ],
-        abilityKey: [
-          { required: true, message: '请选择设备能力或属性键', trigger: 'blur' }
-        ]
+        stepName: [{ required: true, message: '请输入步骤名称', trigger: 'blur' }]
       },
+
+      // 能力调用相关
       abilityList: [],
       currentAbility: null,
-      sliderValue: 50,
-      continueOnFailSwitch: false
+      loadingAbility: false,
+
+      // 属性查询相关
+      queryAttrList: [],
+      loadingQueryAttr: false,
+      lastQueryModelCode: '',
+
+      // 开关
+      continueOnFailSwitch: false,
+      enableSwitch: true,
+      isUpdatingFromProp: false,
+
+      // 循环跳出条件
+      loopBreakType: 'ATTR',
+      breakConfig: { deviceCode: '', modelCode: '', attrKey: '', operator: '==', value: '' },
+      breakAttrList: [],
+      breakAttrEnums: [],
+      loadingBreakAttr: false,
+      lastBreakModelCode: ''
     }
   },
+
+  computed: {
+    queryAttrGroups() {
+      const groups = {}
+      this.queryAttrList.forEach(attr => {
+        const groupName = attr.attrGroup || 'Default'
+        if (!groups[groupName]) groups[groupName] = []
+        groups[groupName].push(attr)
+      })
+      return Object.entries(groups).map(([name, attrs]) => ({
+        name,
+        label: this.getGroupLabel(name),
+        attrs
+      }))
+    },
+
+    selectedQueryAttr() {
+      return this.queryAttrList.find(a => a.attrKey === this.form.abilityKey)
+    },
+
+    breakAttrGroups() {
+      const groups = {}
+      this.breakAttrList.forEach(attr => {
+        const groupName = attr.attrGroup || 'Default'
+        if (!groups[groupName]) groups[groupName] = []
+        groups[groupName].push(attr)
+      })
+      return Object.entries(groups).map(([name, attrs]) => ({
+        name,
+        label: this.getGroupLabel(name),
+        attrs
+      }))
+    },
+
+    selectedBreakAttr() {
+      return this.breakAttrList.find(a => a.attrKey === this.breakConfig.attrKey)
+    }
+  },
+
   watch: {
-    continueOnFailSwitch(val) {
-      this.form.continueOnFail = val ? 1 : 0
+    step: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (val && Object.keys(val).length > 0) {
+          this.initFormFromStep(val)
+        }
+      }
     }
   },
+
   methods: {
-    /**
-     * 打开对话框
-     */
-    open(step, parentStepCode) {
-      this.visible = true
-      this.resetForm()
-
-      if (step) {
-        this.dialogTitle = '编辑步骤'
-        this.form = { ...step }
-        this.continueOnFailSwitch = step.continueOnFail === 1
-
-        // 加载能力列表
-        if (step.targetModelCode && step.stepType === 'ABILITY') {
-          this.loadAbilities(step.targetModelCode)
-        }
-      } else {
-        this.dialogTitle = '新增步骤'
-        if (parentStepCode) {
-          this.form.parentStepCode = parentStepCode
-        }
+    initFormFromStep(step) {
+      this.isUpdatingFromProp = true
+
+      this.form = {
+        ...this.form,
+        ...JSON.parse(JSON.stringify(step)),
+        paramSource: step.paramSource || 'STATIC',
+        delaySeconds: step.delaySeconds || 10,
+        loopMaxCount: step.loopMaxCount || 100,
+        loopInterval: step.loopInterval || 1000,
+        enable: step.enable !== undefined ? step.enable : 1,
+        continueOnFail: step.continueOnFail || 0
+      }
+
+      this.continueOnFailSwitch = this.form.continueOnFail === 1
+      this.enableSwitch = this.form.enable === 1
+
+      // 循环步骤解析跳出条件
+      if (step.stepType === 'LOOP' && step.loopCondition) {
+        this.parseLoopCondition(step.loopCondition)
+      }
+
+      // 能力调用加载能力列表
+      if (step.stepType === 'ABILITY' && step.targetModelCode) {
+        this.loadAbilities(step.targetModelCode)
       }
+
+      // 属性查询加载属性列表
+      if (step.stepType === 'ATTR_QUERY' && step.targetModelCode) {
+        this.loadQueryAttrList(step.targetModelCode)
+      }
+
+      this.$nextTick(() => { this.isUpdatingFromProp = false })
     },
 
-    /**
-     * 重置表单
-     */
-    resetForm() {
-      this.form = {
-        id: null,
-        stepCode: '',
-        stepName: '',
-        stepType: 'ABILITY',
-        stepIndex: 1,
-        targetObjCode: '',
-        targetObjType: 2,
-        targetModelCode: '',
-        abilityKey: '',
-        abilityParam: '',
-        paramSource: 'STATIC',
-        paramMapping: '',
-        delaySeconds: 10,
-        conditionExpr: '',
-        continueOnFail: 0,
-        loopMaxCount: 10,
-        loopInterval: 1000,
-        loopCondition: '',
-        parentStepCode: null
+    parseLoopCondition(loopCondition) {
+      try {
+        const condition = JSON.parse(loopCondition)
+        if (condition.breakType === 'ATTR' && condition.deviceCode) {
+          this.loopBreakType = 'ATTR'
+          this.breakConfig = {
+            deviceCode: condition.deviceCode,
+            modelCode: condition.modelCode || '',
+            attrKey: condition.attrKey || '',
+            operator: condition.operator || '==',
+            value: condition.value || ''
+          }
+          if (condition.deviceCode) {
+            const device = this.deviceList.find(d => d.deviceCode === condition.deviceCode)
+            if (device) {
+              const modelCode = device.deviceModel || device.modelCode
+              if (modelCode) this.loadBreakAttrList(modelCode)
+            }
+          }
+        } else {
+          this.loopBreakType = 'EXPR'
+        }
+      } catch {
+        this.loopBreakType = 'EXPR'
       }
-      this.abilityList = []
-      this.currentAbility = null
-      this.sliderValue = 50
-      this.continueOnFailSwitch = false
     },
 
-    /**
-     * 设备改变事件
-     */
+    // ========== 能力调用相关 ==========
     handleDeviceChange(deviceCode) {
       const device = this.deviceList.find(d => d.deviceCode === deviceCode)
       if (device) {
         this.form.targetModelCode = device.deviceModel || device.modelCode
         this.form.targetObjType = 2
-
-        // 加载能力列表(仅能力调用类型需要)
-        if (this.form.stepType === 'ABILITY') {
-          this.loadAbilities(this.form.targetModelCode)
-        }
+        if (this.form.targetModelCode) this.loadAbilities(this.form.targetModelCode)
       }
-
-      // 重置能力和参数
       this.form.abilityKey = ''
       this.form.abilityParam = ''
       this.currentAbility = null
+      this.emitChange()
     },
 
-    /**
-     * 加载设备能力列表
-     */
     async loadAbilities(modelCode) {
+      if (!modelCode) return
+      this.loadingAbility = true
       try {
         const res = await getModelByCode(modelCode)
-        this.abilityList = res.data?.abilityList?.filter(a => a.hiddenFlag === 1) || []
+        this.abilityList = (res.data?.abilityList || []).filter(a => a.hiddenFlag === 1)
+        if (this.form.abilityKey) {
+          this.currentAbility = this.abilityList.find(a => a.abilityKey === this.form.abilityKey)
+        }
       } catch (error) {
         console.error('加载能力失败:', error)
-        this.$message.error('加载能力列表失败')
+      } finally {
+        this.loadingAbility = false
       }
     },
 
-    /**
-     * 能力改变事件
-     */
     handleAbilityChange(abilityKey) {
       this.currentAbility = this.abilityList.find(a => a.abilityKey === abilityKey)
-
-      // 重置参数
       this.form.abilityParam = ''
-      this.form.paramMapping = ''
-
-      // 如果是 Slider 类型,设置默认值
-      if (this.currentAbility && this.getParamType(this.currentAbility.paramDefinition) === 'Slider') {
-        const min = this.getSliderMin(this.currentAbility.paramDefinition)
-        const max = this.getSliderMax(this.currentAbility.paramDefinition)
-        this.sliderValue = Math.floor((min + max) / 2)
-        this.form.abilityParam = String(this.sliderValue)
-      }
+      this.emitChange()
     },
 
-    /**
-     * 参数来源改变事件
-     */
     handleParamSourceChange() {
       this.form.abilityParam = ''
-      this.form.paramMapping = ''
+      this.emitChange()
     },
 
-    /**
-     * Slider 值改变事件
-     */
-    handleSliderChange(value) {
-      this.form.abilityParam = String(value)
-    },
+    // ========== 属性查询相关 ==========
+    async handleQueryDeviceChange(deviceCode) {
+      const device = this.deviceList.find(d => d.deviceCode === deviceCode)
+      if (!device) return
 
-    /**
-     * 步骤类型改变事件
-     */
-    handleStepTypeChange(newType) {
-      // 切换类型时重置相关字段
-      if (newType === 'ABILITY') {
-        // 能力调用
-        this.form.targetObjCode = ''
-        this.form.abilityKey = ''
-        this.form.abilityParam = ''
-        this.form.paramSource = 'STATIC'
-      } else if (newType === 'DELAY') {
-        // 延时等待
-        this.form.delaySeconds = 10
-      } else if (newType === 'CONDITION') {
-        // 条件判断
-        this.form.conditionExpr = ''
-      } else if (newType === 'LOOP') {
-        // 循环执行
-        this.form.loopMaxCount = 10
-        this.form.loopInterval = 1000
-        this.form.loopCondition = ''
-      } else if (newType === 'ATTR_QUERY') {
-        // 属性查询
-        this.form.targetObjCode = ''
-        this.form.abilityKey = '' // 用于存储属性键
+      const modelCode = device.deviceModel || device.modelCode
+      this.form.targetModelCode = modelCode || ''
+      this.form.abilityKey = ''
+      this.queryAttrList = []
+      this.lastQueryModelCode = ''
+
+      if (modelCode) {
+        await this.loadQueryAttrList(modelCode)
+      } else {
+        this.$message.warning('该设备未配置物模型')
       }
+      this.emitChange()
     },
 
-    /**
-     * 获取参数类型
-     */
-    getParamType(paramDefinition) {
-      if (!paramDefinition) return null
+    async loadQueryAttrList(modelCode) {
+      if (!modelCode || modelCode === this.lastQueryModelCode) return
+      if (this.loadingQueryAttr) return
+
+      this.loadingQueryAttr = true
+      this.lastQueryModelCode = modelCode
+
       try {
-        const def = JSON.parse(paramDefinition)
-        return def.type
-      } catch (e) {
-        return null
+        const res = await getModelByCode(modelCode)
+        this.queryAttrList = res.data?.attrList || []
+      } catch (error) {
+        console.error('加载属性列表失败:', error)
+        this.queryAttrList = []
+        this.lastQueryModelCode = ''
+      } finally {
+        this.loadingQueryAttr = false
       }
     },
 
-    /**
-     * 解析 Options 列表
-     */
-    parseOptions(paramDefinition) {
-      try {
-        const def = JSON.parse(paramDefinition)
-        if (def.type === 'Options' && def.list) {
-          return def.list
-        }
-      } catch (e) {
-        console.error('解析Options失败:', e)
+    handleQueryAttrChange(attrKey) {
+      // 更新步骤名称
+      const attr = this.queryAttrList.find(a => a.attrKey === attrKey)
+      if (attr && !this.form.stepName.includes('查询')) {
+        this.form.stepName = `查询${attr.attrName}`
       }
-      return []
+      this.emitChange()
     },
 
-    /**
-     * 获取选中的选项标签
-     */
-    getSelectedOptionLabel() {
-      if (!this.currentAbility || !this.form.abilityParam) return ''
-      const options = this.parseOptions(this.currentAbility.paramDefinition)
-      const option = options.find(opt => opt.value === this.form.abilityParam)
-      return option ? option.key : this.form.abilityParam
+    // ========== 循环跳出条件相关 ==========
+    switchBreakType(type) {
+      this.loopBreakType = type
+      if (type === 'ATTR') {
+        this.form.loopCondition = ''
+        this.breakConfig = { deviceCode: '', modelCode: '', attrKey: '', operator: '==', value: '' }
+      }
+      this.emitChange()
     },
 
-    /**
-     * 获取 Slider 最小值
-     */
-    getSliderMin(paramDefinition) {
+    async handleBreakDeviceChange(deviceCode) {
+      const device = this.deviceList.find(d => d.deviceCode === deviceCode)
+      if (!device) return
+
+      const modelCode = device.deviceModel || device.modelCode
+      this.breakConfig.modelCode = modelCode || ''
+      this.breakConfig.attrKey = ''
+      this.breakConfig.value = ''
+      this.breakAttrEnums = []
+      this.breakAttrList = []
+      this.lastBreakModelCode = ''
+
+      if (modelCode) await this.loadBreakAttrList(modelCode)
+      this.updateLoopCondition()
+    },
+
+    async loadBreakAttrList(modelCode) {
+      if (!modelCode || modelCode === this.lastBreakModelCode) return
+      if (this.loadingBreakAttr) return
+
+      this.loadingBreakAttr = true
+      this.lastBreakModelCode = modelCode
+
       try {
-        const def = JSON.parse(paramDefinition)
-        return def.min || 0
-      } catch (e) {
-        return 0
+        const res = await getModelByCode(modelCode)
+        this.breakAttrList = res.data?.attrList || []
+        if (this.breakConfig.attrKey) this.loadBreakAttrEnums()
+      } catch (error) {
+        console.error('加载属性列表失败:', error)
+        this.breakAttrList = []
+        this.lastBreakModelCode = ''
+      } finally {
+        this.loadingBreakAttr = false
       }
     },
 
-    /**
-     * 获取 Slider 最大值
-     */
-    getSliderMax(paramDefinition) {
-      try {
-        const def = JSON.parse(paramDefinition)
-        return def.max || 100
-      } catch (e) {
-        return 100
+    handleBreakAttrChange() {
+      this.breakConfig.value = ''
+      this.loadBreakAttrEnums()
+      this.updateLoopCondition()
+    },
+
+    loadBreakAttrEnums() {
+      const attr = this.selectedBreakAttr
+      if (attr && attr.attrValueType === 'Enum' && attr.valueEnums) {
+        this.breakAttrEnums = attr.valueEnums
+      } else {
+        this.breakAttrEnums = []
       }
     },
 
-    /**
-     * 获取 Slider 单位
-     */
-    getSliderUnit(paramDefinition) {
-      try {
-        const def = JSON.parse(paramDefinition)
-        return def.unit || ''
-      } catch (e) {
-        return ''
+    updateLoopCondition() {
+      if (this.loopBreakType === 'ATTR') {
+        const condition = {
+          breakType: 'ATTR',
+          deviceCode: this.breakConfig.deviceCode,
+          modelCode: this.breakConfig.modelCode,
+          attrKey: this.breakConfig.attrKey,
+          operator: this.breakConfig.operator,
+          value: this.breakConfig.value,
+          left: `current_${this.breakConfig.attrKey}`,
+          op: this.breakConfig.operator,
+          right: this.breakConfig.value
+        }
+        this.form.loopCondition = JSON.stringify(condition)
       }
+      this.emitChange()
+    },
+
+    // ========== 通用方法 ==========
+    setDelay(seconds) {
+      this.form.delaySeconds = seconds
+      this.emitChange()
+    },
+
+    handleContinueOnFailChange(val) {
+      this.form.continueOnFail = val ? 1 : 0
+      this.emitChange()
+    },
+
+    handleEnableChange(val) {
+      this.form.enable = val ? 1 : 0
+      this.emitChange()
+    },
+
+    emitChange() {
+      if (this.isUpdatingFromProp) return
+      this.$emit('change', { ...this.step, ...this.form })
+    },
+
+    getDeviceName(deviceCode) {
+      const device = this.deviceList.find(d => d.deviceCode === deviceCode)
+      return device ? device.deviceName : deviceCode
+    },
+
+    getGroupLabel(groupName) {
+      const labelMap = { 'Base': '基础信息', 'State': '状态信息', 'Protocol': '协议信息', 'Measure': '测量数据', 'Default': '其他' }
+      return labelMap[groupName] || groupName
+    },
+
+    getAttrTypeName(type) {
+      const typeMap = { 'Value': '数值', 'String': '字符串', 'Enum': '枚举', 'Boolean': '布尔' }
+      return typeMap[type] || type
+    },
+
+    getAttrTypeTag(type) {
+      const tagMap = { 'Value': '', 'String': 'info', 'Enum': 'warning', 'Boolean': 'success' }
+      return tagMap[type] || ''
     },
 
-    /**
-     * 格式化 Slider 提示
-     */
-    formatSliderTooltip(value) {
-      const unit = this.getSliderUnit(this.currentAbility?.paramDefinition)
-      return unit ? `${value}${unit}` : String(value)
+    getParamType(paramDefinition) {
+      if (!paramDefinition) return null
+      try { return JSON.parse(paramDefinition).type } catch { return null }
     },
 
-    /**
-     * 获取 Input 单位
-     */
-    getInputUnit(paramDefinition) {
+    parseOptions(paramDefinition) {
       try {
         const def = JSON.parse(paramDefinition)
-        return def.unit || ''
-      } catch (e) {
-        return ''
-      }
+        return def.type === 'Options' && def.list ? def.list : []
+      } catch { return [] }
     },
 
-    /**
-     * 提交表单
-     */
-    handleSubmit() {
-      this.$refs.form.validate((valid) => {
-        if (valid) {
-          // 特殊验证
-          if (this.form.stepType === 'LOOP') {
-            if (this.form.loopMaxCount === 0 && !this.form.loopCondition) {
-              this.$message.warning('无限循环必须配置跳出条件')
-              return
-            }
-          }
-
-          this.submitting = true
-          this.$emit('submit', this.form)
+    getStepTypeName(type) {
+      const map = { 'ABILITY': '能力调用', 'DELAY': '延时等待', 'CONDITION': '条件判断', 'LOOP': '循环执行', 'ATTR_QUERY': '属性查询', 'PARALLEL': '并行执行' }
+      return map[type] || type
+    },
 
-          // 延迟关闭,等待外部处理
-          setTimeout(() => {
-            this.submitting = false
-            this.visible = false
-          }, 500)
-        }
-      })
+    getStepTypeIcon(type) {
+      const map = { 'ABILITY': 'el-icon-s-operation', 'DELAY': 'el-icon-time', 'CONDITION': 'el-icon-question', 'LOOP': 'el-icon-refresh', 'ATTR_QUERY': 'el-icon-search', 'PARALLEL': 'el-icon-sort' }
+      return map[type] || 'el-icon-tickets'
     },
 
-    /**
-     * 关闭对话框
-     */
-    handleClose() {
-      this.$refs.form.resetFields()
+    getStepTypeTag(type) {
+      const map = { 'ABILITY': '', 'DELAY': 'success', 'CONDITION': 'warning', 'LOOP': 'danger', 'ATTR_QUERY': '', 'PARALLEL': 'info' }
+      return map[type] || ''
     }
   }
 }
 </script>
 
-<style scoped>
-.el-select {
-  width: 100%;
-}
+<style lang="scss" scoped>
+.step-config-panel {
+  .form-tip {
+    font-size: 12px;
+    color: #909399;
+    margin-top: 4px;
+  }
 
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: "";
-}
+  .el-divider {
+    margin: 16px 0 12px;
+  }
 
-.clearfix:after {
-  clear: both;
+  .attr-info-card {
+    background: #f5f7fa;
+    border-radius: 6px;
+    padding: 12px;
+
+    .attr-info-row {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+      &:last-child { margin-bottom: 0; }
+
+      .attr-info-label {
+        color: #909399;
+        font-size: 12px;
+        width: 70px;
+      }
+    }
+  }
+
+  .delay-shortcuts {
+    margin-top: 8px;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 8px;
+
+    .shortcuts-label {
+      font-size: 12px;
+      color: #909399;
+    }
+
+    .shortcut-tag {
+      cursor: pointer;
+      transition: all 0.2s;
+      &:hover {
+        background: #ecf5ff;
+        color: #409eff;
+      }
+    }
+  }
+
+  .condition-preview {
+    padding: 10px 12px;
+    background: linear-gradient(135deg, #f5f7fa 0%, #e8f4f8 100%);
+    border-radius: 6px;
+    border-left: 3px solid #409eff;
+    font-size: 13px;
+    color: #606266;
+
+    i {
+      color: #409eff;
+      margin-right: 6px;
+    }
+  }
+
+  .break-type-switch {
+    display: flex;
+    gap: 8px;
+  }
 }
 </style>

+ 587 - 314
ems-ui-cloud/src/views/mgr/strategy/components/TriggerConfig.vue

@@ -2,7 +2,7 @@
   <div class="trigger-config">
     <el-form :model="form" label-width="90px" size="small">
       <el-form-item label="触发器名称">
-        <el-input v-model="form.triggerName" placeholder="请输入触发器名称" />
+        <el-input v-model="form.triggerName" placeholder="请输入触发器名称" @input="emitChange" />
       </el-form-item>
 
       <el-form-item label="触发类型">
@@ -10,37 +10,28 @@
           <el-option label="事件触发" value="EVENT">
             <i class="el-icon-lightning" style="color: #f56c6c; margin-right: 8px"></i> 事件触发
           </el-option>
-          <el-option label="属性变化" value="ATTR">
-            <i class="el-icon-data-line" style="color: #e6a23c; margin-right: 8px"></i> 属性变化
+          <el-option label="状态切换" value="STATE_CHANGE">
+            <i class="el-icon-switch-button" style="color: #67c23a; margin-right: 8px"></i> 状态切换
           </el-option>
           <el-option label="定时触发" value="TIME">
             <i class="el-icon-time" style="color: #409eff; margin-right: 8px"></i> 定时触发
           </el-option>
-          <el-option label="条件触发" value="CONDITION">
-            <i class="el-icon-set-up" style="color: #67c23a; margin-right: 8px"></i> 条件触发
-          </el-option>
         </el-select>
       </el-form-item>
 
-      <!-- 事件触发配置 -->
+      <!-- ==================== 事件触发配置 ==================== -->
       <template v-if="form.triggerType === 'EVENT'">
-        <el-divider content-position="left">事件源配置</el-divider>
+        <el-divider content-position="left">
+          <i class="el-icon-lightning"></i> 事件源配置
+        </el-divider>
 
-        <el-form-item label="对象类型">
-          <el-radio-group v-model="form.sourceObjType" @change="handleObjTypeChange">
-            <el-radio :label="2">设备</el-radio>
-            <el-radio :label="3">系统</el-radio>
-          </el-radio-group>
-        </el-form-item>
-
-        <!-- 设备选择 -->
-        <el-form-item label="选择设备" v-if="form.sourceObjType === 2">
+        <el-form-item label="选择设备">
           <el-select
             v-model="form.sourceObjCode"
             filterable
             placeholder="搜索选择设备"
             style="width: 100%"
-            @change="handleObjChange"
+            @change="handleDeviceSelect"
           >
             <el-option
               v-for="device in deviceList"
@@ -54,40 +45,19 @@
           </el-select>
         </el-form-item>
 
-        <!-- 系统选择 -->
-        <el-form-item label="选择系统" v-else-if="form.sourceObjType === 3">
-          <el-select
-            v-model="form.sourceObjCode"
-            filterable
-            placeholder="搜索选择系统"
-            style="width: 100%"
-            @change="handleObjChange"
-          >
-            <el-option
-              v-for="system in systemList"
-              :key="system.systemCode"
-              :label="system.systemName"
-              :value="system.systemCode"
-            >
-              <span>{{ system.systemName }}</span>
-              <span style="color: #909399; font-size: 12px; margin-left: 8px">{{ system.systemCode }}</span>
-            </el-option>
-          </el-select>
-        </el-form-item>
-
-        <el-form-item label="模型代码">
-          <el-input v-model="form.sourceModelCode" placeholder="自动关联" disabled />
+        <el-form-item v-if="loadingModel">
+          <div style="color: #909399; font-size: 12px;">
+            <i class="el-icon-loading"></i> 正在加载物模型...
+          </div>
         </el-form-item>
 
-        <!-- 事件选择 - 从物模型加载 -->
-        <el-form-item label="监听事件">
+        <el-form-item label="监听事件" v-if="form.sourceObjCode && !loadingModel">
           <el-select
             v-model="form.eventKey"
             filterable
             placeholder="请选择事件"
             style="width: 100%"
-            :loading="loadingModel"
-            :disabled="!form.sourceObjCode"
+            @change="emitChange"
           >
             <el-option
               v-for="event in eventList"
@@ -104,76 +74,98 @@
               </div>
             </el-option>
           </el-select>
-          <div class="form-tip" v-if="selectedEvent">
-            {{ selectedEvent.eventDesc || '暂无描述' }}
+          <div class="form-tip" v-if="eventList.length === 0 && form.sourceObjCode">
+            ⚠️ 该设备暂无事件定义
           </div>
-          <div class="form-tip" v-else-if="!loadingModel && eventList.length === 0 && form.sourceObjCode">
-            ⚠️ 该对象暂无事件定义
+        </el-form-item>
+
+        <el-form-item v-if="form.sourceObjCode && form.eventKey">
+          <div class="condition-preview success">
+            <i class="el-icon-info"></i>
+            当设备 <strong>{{ getDeviceName(form.sourceObjCode) }}</strong>
+            触发 <strong>{{ getEventName(form.eventKey) }}</strong> 事件时执行策略
           </div>
         </el-form-item>
       </template>
 
-      <!-- 属性变化配置 -->
-      <template v-if="form.triggerType === 'ATTR'">
-        <el-divider content-position="left">属性监控配置</el-divider>
-
-        <el-form-item label="对象类型">
-          <el-radio-group v-model="form.sourceObjType" @change="handleObjTypeChange">
-            <el-radio :label="2">设备</el-radio>
-            <el-radio :label="3">系统</el-radio>
+      <!-- ==================== 状态切换触发配置 ==================== -->
+      <template v-if="form.triggerType === 'STATE_CHANGE'">
+        <el-divider content-position="left">
+          <i class="el-icon-switch-button"></i> 监控模式
+        </el-divider>
+
+        <el-form-item label="监控模式">
+          <el-radio-group v-model="stateChangeMode" @change="handleModeChange">
+            <el-radio-button label="PASSIVE">
+              <i class="el-icon-upload2"></i> 主动上报
+            </el-radio-button>
+            <el-radio-button label="POLLING">
+              <i class="el-icon-refresh"></i> 轮询监控
+            </el-radio-button>
           </el-radio-group>
         </el-form-item>
 
-        <!-- 设备选择 -->
-        <el-form-item label="选择设备" v-if="form.sourceObjType === 2">
+        <el-alert
+          v-if="stateChangeMode === 'PASSIVE'"
+          type="info"
+          :closable="false"
+          show-icon
+          style="margin-bottom: 16px;"
+        >
+          <template slot="title">
+            <strong>主动上报模式</strong>:适用于设备会主动推送状态变化的场景(如4G/WiFi直连设备)
+          </template>
+        </el-alert>
+
+        <el-alert
+          v-if="stateChangeMode === 'POLLING'"
+          type="warning"
+          :closable="false"
+          show-icon
+          style="margin-bottom: 16px;"
+        >
+          <template slot="title">
+            <strong>轮询监控模式</strong>:适用于需要主动查询的设备(如485设备)
+          </template>
+        </el-alert>
+
+        <el-divider content-position="left">
+          <i class="el-icon-aim"></i> 设备与属性
+        </el-divider>
+
+        <el-form-item label="监控设备">
           <el-select
             v-model="form.sourceObjCode"
             filterable
-            placeholder="搜索选择设备"
+            placeholder="请选择监控设备"
             style="width: 100%"
-            @change="handleObjChange"
+            @change="handleDeviceSelect"
           >
             <el-option
               v-for="device in deviceList"
               :key="device.deviceCode"
               :label="device.deviceName"
               :value="device.deviceCode"
-            />
-          </el-select>
-        </el-form-item>
-
-        <!-- 系统选择 -->
-        <el-form-item label="选择系统" v-else-if="form.sourceObjType === 3">
-          <el-select
-            v-model="form.sourceObjCode"
-            filterable
-            placeholder="搜索选择系统"
-            style="width: 100%"
-            @change="handleObjChange"
-          >
-            <el-option
-              v-for="system in systemList"
-              :key="system.systemCode"
-              :label="system.systemName"
-              :value="system.systemCode"
-            />
+            >
+              <span>{{ device.deviceName }}</span>
+              <span style="color: #909399; font-size: 12px; margin-left: 8px">{{ device.deviceCode }}</span>
+            </el-option>
           </el-select>
         </el-form-item>
 
-        <el-form-item label="模型代码">
-          <el-input v-model="form.sourceModelCode" placeholder="自动关联" disabled />
+        <el-form-item v-if="loadingModel">
+          <div style="color: #909399; font-size: 12px;">
+            <i class="el-icon-loading"></i> 正在加载物模型...
+          </div>
         </el-form-item>
 
-        <!-- 属性选择 - 从物模型加载 -->
-        <el-form-item label="监控属性">
+        <el-form-item label="监控属性" v-if="form.sourceObjCode && !loadingModel">
           <el-select
             v-model="form.attrKey"
             filterable
-            placeholder="请选择属性"
+            placeholder="请选择监控属性"
             style="width: 100%"
-            :loading="loadingModel"
-            :disabled="!form.sourceObjCode"
-            @change="handleAttrChange"
+            @change="handleAttrSelect"
           >
             <el-option-group
               v-for="group in attrGroups"
@@ -199,37 +191,193 @@
               </el-option>
             </el-option-group>
           </el-select>
-          <div class="form-tip" v-if="!loadingModel && attrList.length === 0 && form.sourceObjCode">
-            ⚠️ 该对象暂无属性定义
+          <div class="form-tip" v-if="attrList.length === 0 && form.sourceObjCode">
+            ⚠️ 该设备暂无属性定义
           </div>
         </el-form-item>
 
-        <!-- 属性类型提示 -->
-        <el-form-item label="属性类型" v-if="selectedAttr">
-          <el-tag :type="getAttrTypeTag(selectedAttr.attrValueType)">
-            {{ getAttrTypeName(selectedAttr.attrValueType) }}
-          </el-tag>
-          <span style="margin-left: 8px; color: #909399; font-size: 12px">
-            {{ selectedAttr.attrUnit || '无单位' }}
-          </span>
+        <el-form-item v-if="form.attrKey && selectedAttr">
+          <div class="attr-type-info">
+            <el-tag :type="getAttrTypeTag(selectedAttr.attrValueType)" size="small">
+              {{ getAttrTypeName(selectedAttr.attrValueType) }}
+            </el-tag>
+            <span v-if="selectedAttr.attrUnit" style="margin-left: 8px; color: #606266;">
+              单位: {{ selectedAttr.attrUnit }}
+            </span>
+          </div>
         </el-form-item>
 
-        <!-- 触发条件 -->
-        <el-form-item label="触发条件" v-if="form.attrKey">
-          <condition-builder
-            v-model="form.conditionExpr"
-            :attr-key="form.attrKey"
-            :attr-info="selectedAttr"
-          />
+        <!-- 轮询监控专属配置 -->
+        <template v-if="stateChangeMode === 'POLLING'">
+          <el-divider content-position="left">
+            <i class="el-icon-setting"></i> 轮询参数
+          </el-divider>
+
+          <el-form-item label="轮询间隔">
+            <el-input-number
+              v-model="pollingConfig.interval"
+              :min="1000"
+              :max="60000"
+              :step="500"
+              style="width: 150px"
+              @change="updateCondition"
+            />
+            <span class="unit-label">毫秒(建议≥2000ms)</span>
+          </el-form-item>
+
+          <el-form-item label="初始延迟">
+            <el-input-number
+              v-model="pollingConfig.initialDelay"
+              :min="0"
+              :max="10000"
+              :step="500"
+              style="width: 150px"
+              @change="updateCondition"
+            />
+            <span class="unit-label">毫秒</span>
+          </el-form-item>
+
+          <el-divider content-position="left">
+            <i class="el-icon-s-promotion"></i> 主动查询配置
+          </el-divider>
+
+          <el-form-item label="主动查询">
+            <el-switch
+              v-model="pollingConfig.activeQuery"
+              active-text="开启"
+              inactive-text="关闭"
+              @change="updateCondition"
+            />
+            <el-tooltip content="485设备需要主动下发查询指令才能获取最新状态" placement="right">
+              <i class="el-icon-question" style="margin-left: 8px; color: #909399;"></i>
+            </el-tooltip>
+          </el-form-item>
+
+          <template v-if="pollingConfig.activeQuery">
+            <el-form-item label="查询能力">
+              <el-select
+                v-model="pollingConfig.queryAbilityKey"
+                placeholder="请选择用于查询状态的能力"
+                style="width: 100%"
+                @change="updateCondition"
+              >
+                <el-option
+                  v-for="ability in abilityList"
+                  :key="ability.abilityKey"
+                  :label="ability.abilityName"
+                  :value="ability.abilityKey"
+                >
+                  <span>{{ ability.abilityName }}</span>
+                  <span style="float: right; color: #909399; font-size: 12px">{{ ability.abilityKey }}</span>
+                </el-option>
+              </el-select>
+              <div class="form-tip">
+                选择物模型中定义的能力,用于主动查询设备状态(如 syncState)
+              </div>
+            </el-form-item>
+
+            <el-form-item label="等待时间">
+              <el-input-number
+                v-model="pollingConfig.queryWaitTime"
+                :min="100"
+                :max="5000"
+                :step="100"
+                style="width: 150px"
+                @change="updateCondition"
+              />
+              <span class="unit-label">毫秒(设备响应等待时间)</span>
+            </el-form-item>
+          </template>
+        </template>
+
+        <!-- 触发条件配置(两种模式共用) -->
+        <el-divider content-position="left">
+          <i class="el-icon-s-check"></i> 触发条件
+        </el-divider>
+
+        <el-form-item label="触发条件">
+          <el-row :gutter="8">
+            <el-col :span="8">
+              <el-select v-model="conditionOperator" style="width: 100%" @change="updateCondition">
+                <el-option label="等于 (==)" value="==" />
+                <el-option label="不等于 (!=)" value="!=" />
+                <el-option label="大于 (>)" value=">" />
+                <el-option label="大于等于 (>=)" value=">=" />
+                <el-option label="小于 (<)" value="<" />
+                <el-option label="小于等于 (<=)" value="<=" />
+              </el-select>
+            </el-col>
+            <el-col :span="16">
+              <el-select
+                v-if="selectedAttr && selectedAttr.attrValueType === 'Enum' && attrEnums.length > 0"
+                v-model="conditionValue"
+                placeholder="请选择值"
+                style="width: 100%"
+                @change="updateCondition"
+              >
+                <el-option
+                  v-for="enumItem in attrEnums"
+                  :key="enumItem.attrValue"
+                  :label="enumItem.attrValueName"
+                  :value="enumItem.attrValue"
+                >
+                  <span>{{ enumItem.attrValueName }}</span>
+                  <span style="float: right; color: #909399; font-size: 12px">{{ enumItem.attrValue }}</span>
+                </el-option>
+              </el-select>
+              <el-input
+                v-else
+                v-model="conditionValue"
+                placeholder="请输入比较值"
+                @input="updateCondition"
+              >
+                <template slot="prepend">{{ form.attrKey || '属性' }}</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <!-- 轮询模式下的触发方式 -->
+        <el-form-item label="触发方式" v-if="stateChangeMode === 'POLLING'">
+          <el-radio-group v-model="pollingConfig.triggerMode" @change="updateCondition">
+            <el-radio label="ON_FIRST_MATCH">
+              <span>首次满足</span>
+              <el-tooltip content="条件首次满足时触发(边沿触发),避免重复执行" placement="top">
+                <i class="el-icon-question" style="margin-left: 4px; color: #909399;"></i>
+              </el-tooltip>
+            </el-radio>
+            <el-radio label="ON_CHANGE">
+              <span>值变化</span>
+              <el-tooltip content="值变化且条件满足时触发" placement="top">
+                <i class="el-icon-question" style="margin-left: 4px; color: #909399;"></i>
+              </el-tooltip>
+            </el-radio>
+            <el-radio label="ALWAYS">
+              <span>总是触发</span>
+              <el-tooltip content="只要条件满足就触发(谨慎使用)" placement="top">
+                <i class="el-icon-warning" style="margin-left: 4px; color: #E6A23C;"></i>
+              </el-tooltip>
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <!-- 条件预览 -->
+        <el-form-item label="配置预览">
+          <div class="condition-preview" :class="conditionPreviewClass">
+            <i class="el-icon-info"></i>
+            {{ conditionPreviewText }}
+          </div>
         </el-form-item>
       </template>
 
-      <!-- 定时触发配置 -->
+      <!-- ==================== 定时触发配置 ==================== -->
       <template v-if="form.triggerType === 'TIME'">
-        <el-divider content-position="left">定时配置</el-divider>
+        <el-divider content-position="left">
+          <i class="el-icon-time"></i> 定时配置
+        </el-divider>
 
         <el-form-item label="CRON表达式">
-          <el-input v-model="cronExpression" placeholder="0 0 8 * * ?">
+          <el-input v-model="cronExpression" placeholder="0 0 8 * * ?" @input="updateCronCondition">
             <el-button slot="append" @click="showCronHelper">
               <i class="el-icon-question"></i>
             </el-button>
@@ -243,65 +391,54 @@
               :key="shortcut.value"
               @click="setCron(shortcut.value)"
               class="cron-tag"
+              :effect="cronExpression === shortcut.value ? 'dark' : 'plain'"
             >
               {{ shortcut.label }}
             </el-tag>
           </div>
         </el-form-item>
-      </template>
 
-      <!-- 条件触发配置 -->
-      <template v-if="form.triggerType === 'CONDITION'">
-        <el-divider content-position="left">条件配置</el-divider>
-
-        <el-form-item label="条件表达式">
-          <condition-builder
-            v-model="form.conditionExpr"
-            :variables="availableVariables"
-            :device-list="deviceList"
-            :show-attr-select="true"
-          />
+        <el-form-item label="表达式说明" v-if="cronExpression">
+          <div class="cron-desc">{{ cronDescription }}</div>
         </el-form-item>
       </template>
 
-      <el-divider content-position="left">高级配置</el-divider>
+      <!-- ==================== 高级配置 ==================== -->
+      <el-divider content-position="left">
+        <i class="el-icon-setting"></i> 高级配置
+      </el-divider>
 
       <el-form-item label="优先级">
-        <el-slider v-model="form.priority" :min="0" :max="100" show-input />
+        <el-slider v-model="form.priority" :min="0" :max="100" show-input @change="emitChange" />
       </el-form-item>
 
       <el-form-item label="是否启用">
-        <el-switch v-model="form.enable" :active-value="1" :inactive-value="0" />
+        <el-switch v-model="form.enable" :active-value="1" :inactive-value="0" @change="emitChange" />
       </el-form-item>
     </el-form>
   </div>
 </template>
 
 <script>
-import { getModelByCode } from '@/api/basecfg/objModel';
-import { listSubsystemAll } from '@/api/adapter/subsystem';
-import ConditionBuilder from './ConditionBuilder.vue';
+import { getModelByCode } from '@/api/basecfg/objModel'
 
 export default {
   name: 'TriggerConfig',
-  components: { ConditionBuilder },
 
   props: {
-    trigger: {
-      type: Object,
-      default: () => ({})
-    },
-    deviceList: {
-      type: Array,
-      default: () => []
-    }
+    trigger: { type: Object, default: () => ({}) },
+    deviceList: { type: Array, default: () => [] },
+    // 新增:策略代码
+    strategyCode: { type: String, default: '' }
   },
 
   data() {
     return {
       form: {
+        id: null,           // 数据库ID,用于更新
+        strategyCode: '',   // 策略代码,必需字段
         triggerName: '',
-        triggerType: 'EVENT',
+        triggerType: 'STATE_CHANGE',
         sourceObjType: 2,
         sourceObjCode: '',
         sourceModelCode: '',
@@ -312,52 +449,88 @@ export default {
         enable: 1
       },
 
-      systemList: [], // 系统列表
-      eventList: [],  // 物模型事件列表
-      attrList: [],   // 物模型属性列表
+      stateChangeMode: 'POLLING',
+      eventList: [],
+      attrList: [],
+      abilityList: [],
+      attrEnums: [],
       loadingModel: false,
-
+      conditionOperator: '==',
+      conditionValue: '',
       cronExpression: '',
       cronShortcuts: [
         { label: '每天8点', value: '0 0 8 * * ?' },
         { label: '每天0点', value: '0 0 0 * * ?' },
         { label: '每小时', value: '0 0 * * * ?' },
         { label: '每30分钟', value: '0 */30 * * * ?' },
-        { label: '工作日8点', value: '0 0 8 ? * MON-FRI' },
-        { label: '每月1号', value: '0 0 0 1 * ?' }
+        { label: '每5分钟', value: '0 */5 * * * ?' },
+        { label: '工作日8点', value: '0 0 8 ? * MON-FRI' }
       ],
-
-      availableVariables: []
-    };
+      pollingConfig: {
+        interval: 2000,
+        initialDelay: 1000,
+        activeQuery: true,
+        queryAbilityKey: 'syncState',
+        queryAbilityParam: '',
+        queryWaitTime: 500,
+        triggerMode: 'ON_FIRST_MATCH'
+      },
+      lastLoadedModelCode: ''
+    }
   },
 
   computed: {
-    // 选中的事件
-    selectedEvent() {
-      return this.eventList.find(e => e.eventKey === this.form.eventKey);
-    },
-
-    // 选中的属性
     selectedAttr() {
-      return this.attrList.find(a => a.attrKey === this.form.attrKey);
+      return this.attrList.find(a => a.attrKey === this.form.attrKey)
     },
-
-    // 属性分组
     attrGroups() {
-      const groups = {};
+      const groups = {}
       this.attrList.forEach(attr => {
-        const groupName = attr.attrGroup || 'Default';
-        if (!groups[groupName]) {
-          groups[groupName] = [];
-        }
-        groups[groupName].push(attr);
-      });
-
+        const groupName = attr.attrGroup || 'Default'
+        if (!groups[groupName]) groups[groupName] = []
+        groups[groupName].push(attr)
+      })
       return Object.entries(groups).map(([name, attrs]) => ({
         name,
         label: this.getGroupLabel(name),
         attrs
-      }));
+      }))
+    },
+    cronDescription() {
+      const expr = this.cronExpression
+      if (!expr) return ''
+      if (expr === '0 0 8 * * ?') return '每天早上8点执行'
+      if (expr === '0 0 0 * * ?') return '每天凌晨0点执行'
+      if (expr === '0 0 * * * ?') return '每小时整点执行'
+      if (expr === '0 */30 * * * ?') return '每30分钟执行一次'
+      if (expr === '0 */5 * * * ?') return '每5分钟执行一次'
+      if (expr === '0 0 8 ? * MON-FRI') return '工作日早上8点执行'
+      const parts = expr.split(' ')
+      if (parts.length >= 6) {
+        return `秒:${parts[0]} 分:${parts[1]} 时:${parts[2]} 日:${parts[3]} 月:${parts[4]} 周:${parts[5]}`
+      }
+      return '表达式格式不正确'
+    },
+    conditionPreviewText() {
+      const deviceName = this.getDeviceName(this.form.sourceObjCode) || '[未选择设备]'
+      const attrName = this.selectedAttr ? this.selectedAttr.attrName : (this.form.attrKey || '[未选择属性]')
+      const op = this.conditionOperator || '=='
+      const value = this.getConditionValueDisplay()
+      if (this.stateChangeMode === 'PASSIVE') {
+        return `当 ${deviceName} 的 ${attrName} ${this.getOperatorText(op)} ${value} 时执行策略(等待设备主动上报)`
+      } else {
+        const interval = this.pollingConfig.interval / 1000
+        const queryDesc = this.pollingConfig.activeQuery
+          ? `,调用 ${this.pollingConfig.queryAbilityKey} 查询`
+          : ''
+        const modeDesc = this.getTriggerModeText(this.pollingConfig.triggerMode)
+        return `每${interval}秒检查 ${deviceName} 的 ${attrName}${queryDesc},当 ${this.getOperatorText(op)} ${value} 时${modeDesc}`
+      }
+    },
+    conditionPreviewClass() {
+      if (!this.form.sourceObjCode || !this.form.attrKey) return 'warning'
+      if (!this.conditionValue) return 'warning'
+      return 'success'
     }
   },
 
@@ -365,146 +538,213 @@ export default {
     trigger: {
       immediate: true,
       handler(val) {
-        this.form = {
-          triggerName: '',
-          triggerType: 'EVENT',
-          sourceObjType: 2,
-          sourceObjCode: '',
-          sourceModelCode: '',
-          eventKey: '',
-          attrKey: '',
-          conditionExpr: '',
-          priority: 50,
-          enable: 1,
-          ...val
-        };
-
-        // 如果已有对象代码,加载物模型
-        if (val.sourceObjCode && val.sourceModelCode) {
-          this.loadObjectModel(val.sourceModelCode);
+        if (val && Object.keys(val).length > 0) {
+          this.initFormFromTrigger(val)
         }
       }
     },
-    form: {
-      deep: true,
+    // 监听 strategyCode 变化
+    strategyCode: {
+      immediate: true,
       handler(val) {
-        this.$emit('change', val);
+        if (val && !this.form.strategyCode) {
+          this.form.strategyCode = val
+        }
       }
     }
   },
 
-  created() {
-    this.loadSystems();
-  },
-
   methods: {
-    // 加载系统列表
-    async loadSystems() {
-      try {
-        const response = await listSubsystemAll();
-        this.systemList = response.data || [];
-      } catch (error) {
-        console.error('加载系统列表失败', error);
-        this.systemList = [];
+    initFormFromTrigger(trigger) {
+      // 确保 strategyCode 存在
+      const strategyCode = trigger.strategyCode || this.strategyCode
+
+      this.form = {
+        id: trigger.id || null,  // 保留数据库ID
+        strategyCode: strategyCode,  // 确保 strategyCode 存在
+        triggerName: trigger.triggerName || '',
+        triggerType: this.mapTriggerType(trigger.triggerType),
+        sourceObjType: trigger.sourceObjType || 2,
+        sourceObjCode: trigger.sourceObjCode || '',
+        sourceModelCode: trigger.sourceModelCode || '',
+        eventKey: trigger.eventKey || '',
+        attrKey: trigger.attrKey || '',
+        conditionExpr: trigger.conditionExpr || '',
+        priority: trigger.priority || 50,
+        enable: trigger.enable !== undefined ? trigger.enable : 1
       }
-    },
 
-    // 触发类型改变
-    handleTypeChange() {
-      this.form.eventKey = null;
-      this.form.attrKey = null;
-      this.form.conditionExpr = null;
-      this.eventList = [];
-      this.attrList = [];
-    },
+      if (trigger.conditionExpr) {
+        this.parseConditionExpr(trigger.conditionExpr)
+      }
 
-    // 对象类型改变
-    handleObjTypeChange() {
-      this.form.sourceObjCode = null;
-      this.form.sourceModelCode = null;
-      this.form.eventKey = null;
-      this.form.attrKey = null;
-      this.eventList = [];
-      this.attrList = [];
+      if (trigger.sourceModelCode && trigger.sourceModelCode !== this.lastLoadedModelCode) {
+        this.loadObjectModel(trigger.sourceModelCode)
+      }
     },
 
-    // 对象改变(设备或系统)
-    async handleObjChange(objCode) {
-      let modelCode = null;
+    mapTriggerType(oldType) {
+      if (oldType === 'ATTR' || oldType === 'POLLING') {
+        return 'STATE_CHANGE'
+      }
+      return oldType || 'STATE_CHANGE'
+    },
 
-      if (this.form.sourceObjType === 2) {
-        // 设备 - 使用 deviceModel
-        const device = this.deviceList.find(d => d.deviceCode === objCode);
-        if (device) {
-          modelCode = device.modelCode || device.deviceModel;
+    parseConditionExpr(expr) {
+      try {
+        const condition = JSON.parse(expr)
+        if (condition.cron) {
+          this.cronExpression = condition.cron
+        } else {
+          this.conditionOperator = condition.op || '=='
+          this.conditionValue = condition.right || ''
         }
-      } else if (this.form.sourceObjType === 3) {
-        // 系统
-        const system = this.systemList.find(s => s.systemCode === objCode);
-        if (system) {
-          modelCode = system.modelCode;
+        if (condition.polling && condition.polling.enabled) {
+          this.stateChangeMode = 'POLLING'
+          this.pollingConfig = {
+            interval: condition.polling.interval || 2000,
+            initialDelay: condition.polling.initialDelay || 1000,
+            activeQuery: condition.polling.activeQuery !== false,
+            queryAbilityKey: condition.polling.queryAbilityKey ||
+              (condition.polling.queryAbility && condition.polling.queryAbility.abilityKey) ||
+              'syncState',
+            queryAbilityParam: condition.polling.queryAbilityParam ||
+              (condition.polling.queryAbility && condition.polling.queryAbility.abilityParam) ||
+              '',
+            queryWaitTime: condition.polling.queryWaitTime || 500,
+            triggerMode: condition.polling.triggerMode || 'ON_FIRST_MATCH'
+          }
+        } else {
+          this.stateChangeMode = 'PASSIVE'
         }
+      } catch (e) {
+        console.warn('解析条件表达式失败', e)
       }
+    },
+
+    handleTypeChange() {
+      this.form.eventKey = ''
+      this.form.attrKey = ''
+      this.form.conditionExpr = ''
+      this.conditionOperator = '=='
+      this.conditionValue = ''
+      this.cronExpression = ''
+      this.eventList = []
+      this.attrList = []
+      this.abilityList = []
+      this.attrEnums = []
+      this.emitChange()
+    },
 
-      this.form.sourceModelCode = modelCode;
+    handleModeChange(mode) {
+      this.stateChangeMode = mode
+      this.updateCondition()
+    },
 
-      if (modelCode) {
-        await this.loadObjectModel(modelCode);
-      } else {
-        this.$message.warning('该对象未配置物模型');
-        this.eventList = [];
-        this.attrList = [];
+    handleDeviceSelect(deviceCode) {
+      const device = this.deviceList.find(d => d.deviceCode === deviceCode)
+      if (!device) return
+      const modelCode = device.deviceModel || device.modelCode
+      this.form.sourceModelCode = modelCode || ''
+      this.form.sourceObjType = 2
+      this.form.eventKey = ''
+      this.form.attrKey = ''
+      this.eventList = []
+      this.attrList = []
+      this.abilityList = []
+      this.attrEnums = []
+      if (modelCode && modelCode !== this.lastLoadedModelCode) {
+        this.loadObjectModel(modelCode)
+      } else if (!modelCode) {
+        this.$message.warning('该设备未配置物模型')
       }
+      this.emitChange()
     },
 
-    // 加载物模型(核心方法)
     async loadObjectModel(modelCode) {
-      if (!modelCode) return;
-
-      this.loadingModel = true;
+      if (!modelCode) return
+      if (modelCode === this.lastLoadedModelCode) return
+      if (this.loadingModel) return
+      this.loadingModel = true
+      this.lastLoadedModelCode = modelCode
       try {
-        const response = await getModelByCode(modelCode);
-        const modelData = response.data;
-
+        const response = await getModelByCode(modelCode)
+        const modelData = response.data
         if (!modelData) {
-          throw new Error('物模型数据为空');
+          this.$message.warning('物模型数据为空')
+          return
+        }
+        this.attrList = modelData.attrList || []
+        this.eventList = modelData.eventList || []
+        this.abilityList = modelData.abilityList || []
+        if (this.abilityList.length > 0) {
+          const syncAbility = this.abilityList.find(a => a.abilityKey === 'syncState')
+          if (syncAbility && !this.pollingConfig.queryAbilityKey) {
+            this.pollingConfig.queryAbilityKey = 'syncState'
+          }
         }
-
-        // 加载属性列表
-        this.attrList = modelData.attrList || [];
-
-        // 加载事件列表
-        this.eventList = modelData.eventList || [];
-
-        console.log('物模型加载成功:', {
-          modelCode,
-          attrCount: this.attrList.length,
-          eventCount: this.eventList.length
-        });
-
       } catch (error) {
-        console.error('加载物模型失败', error);
-        this.$message.error('加载物模型失败: ' + (error.message || '未知错误'));
-        this.attrList = [];
-        this.eventList = [];
+        console.error('加载物模型失败', error)
+        this.$message.error('加载物模型失败')
+        this.attrList = []
+        this.eventList = []
+        this.abilityList = []
+        this.lastLoadedModelCode = ''
       } finally {
-        this.loadingModel = false;
+        this.loadingModel = false
       }
     },
 
-    // 属性改变
-    handleAttrChange(attrKey) {
-      const attr = this.attrList.find(a => a.attrKey === attrKey);
-      console.log('选中属性:', attr);
+    handleAttrSelect(attrKey) {
+      const attr = this.attrList.find(a => a.attrKey === attrKey)
+      this.conditionValue = ''
+      this.conditionOperator = '=='
+      if (attr && attr.attrValueType === 'Enum' && attr.valueEnums) {
+        this.attrEnums = attr.valueEnums
+      } else {
+        this.attrEnums = []
+      }
+      this.updateCondition()
+    },
+
+    updateCondition() {
+      if (this.form.triggerType === 'STATE_CHANGE') {
+        const condition = {
+          left: this.form.attrKey,
+          op: this.conditionOperator,
+          right: this.conditionValue
+        }
+        if (this.stateChangeMode === 'POLLING') {
+          condition.polling = {
+            enabled: true,
+            interval: this.pollingConfig.interval,
+            initialDelay: this.pollingConfig.initialDelay,
+            activeQuery: this.pollingConfig.activeQuery,
+            queryWaitTime: this.pollingConfig.queryWaitTime,
+            triggerMode: this.pollingConfig.triggerMode
+          }
+          if (this.pollingConfig.activeQuery) {
+            condition.polling.queryAbility = {
+              abilityKey: this.pollingConfig.queryAbilityKey,
+              abilityParam: this.pollingConfig.queryAbilityParam
+            }
+          }
+        }
+        this.form.conditionExpr = JSON.stringify(condition)
+      }
+      this.emitChange()
+    },
+
+    updateCronCondition() {
+      this.form.conditionExpr = JSON.stringify({ cron: this.cronExpression })
+      this.emitChange()
     },
 
-    // 设置CRON表达式
     setCron(value) {
-      this.cronExpression = value;
-      this.form.conditionExpr = JSON.stringify({ cron: value });
+      this.cronExpression = value
+      this.updateCronCondition()
     },
 
-    // 显示CRON帮助
     showCronHelper() {
       this.$alert(`
         <div style="line-height: 1.8">
@@ -515,73 +755,106 @@ export default {
           <p>• 0 0 9-18 * * ? - 每天9-18点整点执行</p>
           <p>• 0 0 8 ? * MON-FRI - 工作日8点执行</p>
         </div>
-      `, 'CRON表达式帮助', {
-        dangerouslyUseHTMLString: true
-      });
+      `, 'CRON表达式帮助', { dangerouslyUseHTMLString: true })
     },
 
-    // 辅助方法
-    getGroupLabel(groupName) {
-      const labelMap = {
-        'Base': '基础信息',
-        'State': '状态信息',
-        'Protocol': '协议信息',
-        'Measure': '测量数据',
-        'Control': '控制参数',
-        'Default': '其他'
-      };
-      return labelMap[groupName] || groupName;
+    emitChange() {
+      // 构建完整的表单数据,确保包含 strategyCode
+      const formData = {
+        ...this.form,
+        strategyCode: this.form.strategyCode || this.strategyCode  // 双重保障
+      }
+
+      // 转换回后端需要的 triggerType
+      if (formData.triggerType === 'STATE_CHANGE') {
+        formData.triggerType = this.stateChangeMode === 'POLLING' ? 'POLLING' : 'ATTR'
+      }
+
+      this.$emit('change', formData)
     },
 
+    getDeviceName(deviceCode) {
+      const device = this.deviceList.find(d => d.deviceCode === deviceCode)
+      return device ? device.deviceName : deviceCode
+    },
+    getEventName(eventKey) {
+      const event = this.eventList.find(e => e.eventKey === eventKey)
+      return event ? event.eventName : eventKey
+    },
+    getConditionValueDisplay() {
+      if (!this.conditionValue) return '[未设置]'
+      if (this.attrEnums.length > 0) {
+        const enumItem = this.attrEnums.find(e => e.attrValue === this.conditionValue)
+        if (enumItem) return enumItem.attrValueName
+      }
+      return this.conditionValue
+    },
+    getOperatorText(op) {
+      const map = { '==': '等于', '!=': '不等于', '>': '大于', '>=': '大于等于', '<': '小于', '<=': '小于等于' }
+      return map[op] || op
+    },
+    getTriggerModeText(mode) {
+      const map = { 'ON_FIRST_MATCH': '触发(首次满足)', 'ON_CHANGE': '触发(值变化时)', 'ALWAYS': '触发(每次检查)' }
+      return map[mode] || '触发'
+    },
+    getGroupLabel(groupName) {
+      const labelMap = { 'Base': '基础信息', 'State': '状态信息', 'Protocol': '协议信息', 'Measure': '测量数据', 'Control': '控制参数', 'Default': '其他' }
+      return labelMap[groupName] || groupName
+    },
     getAttrTypeName(type) {
-      const typeMap = {
-        'Value': '数值',
-        'String': '字符串',
-        'Enum': '枚举',
-        'Boolean': '布尔',
-        'WebPic': '图片',
-        'Object': '对象'
-      };
-      return typeMap[type] || type;
+      const typeMap = { 'Value': '数值', 'String': '字符串', 'Enum': '枚举', 'Boolean': '布尔' }
+      return typeMap[type] || type
     },
-
     getAttrTypeTag(type) {
-      const tagMap = {
-        'Value': '',
-        'String': 'info',
-        'Enum': 'warning',
-        'Boolean': 'success'
-      };
-      return tagMap[type] || '';
+      const tagMap = { 'Value': '', 'String': 'info', 'Enum': 'warning', 'Boolean': 'success' }
+      return tagMap[type] || ''
     }
   }
-};
+}
 </script>
 
 <style lang="scss" scoped>
 .trigger-config {
+  .el-divider { margin: 16px 0 12px; }
+  .form-tip { font-size: 12px; color: #909399; margin-top: 4px; }
+  .unit-label { margin-left: 8px; color: #909399; font-size: 12px; }
+  .attr-type-info {
+    padding: 8px 12px;
+    background: #f5f7fa;
+    border-radius: 6px;
+    display: flex;
+    align-items: center;
+  }
   .cron-shortcuts {
     display: flex;
     flex-wrap: wrap;
     gap: 8px;
-
-    .cron-tag {
-      cursor: pointer;
-      transition: all 0.2s;
-
-      &:hover {
-        background: #ecf5ff;
-        color: #409eff;
-        transform: scale(1.05);
-      }
-    }
+    .cron-tag { cursor: pointer; transition: all 0.2s; &:hover { transform: scale(1.05); } }
   }
-
-  .form-tip {
+  .cron-desc {
     font-size: 12px;
-    color: #909399;
-    margin-top: 4px;
-    line-height: 1.5;
+    color: #67c23a;
+    padding: 8px 12px;
+    background: #f0f9eb;
+    border-radius: 4px;
+  }
+  .condition-preview {
+    padding: 12px 14px;
+    border-radius: 6px;
+    font-size: 13px;
+    line-height: 1.6;
+    i { margin-right: 6px; }
+    strong { color: #409eff; }
+    &.success {
+      background: linear-gradient(135deg, #f0f9eb 0%, #e8f8e0 100%);
+      border-left: 3px solid #67c23a;
+      color: #606266;
+    }
+    &.warning {
+      background: linear-gradient(135deg, #fef0e5 0%, #fdf6ec 100%);
+      border-left: 3px solid #e6a23c;
+      color: #909399;
+    }
   }
 }
 </style>

Разница между файлами не показана из-за своего большого размера
+ 572 - 425
ems-ui-cloud/src/views/mgr/strategy/editor.vue


+ 77 - 6
ems-ui-cloud/src/views/mgr/strategy/index.vue

@@ -303,7 +303,16 @@
             </div>
 
             <!-- 状态指示器 -->
-            <div :class="['card-status-indicator', getStatusClass(strategy)]"></div>
+            <div :class="['card-status-indicator', getStatusClass(strategy)]">
+              <el-tooltip v-if="strategy.strategyState === 1" placement="left">
+                <template #content>
+                  <div>策略已启用</div>
+                  <div v-if="strategy.triggerType === 2">定时执行: {{ strategy.execRule }}</div>
+                  <div v-if="strategy.triggerType === 4">监听设备属性变化</div>
+                </template>
+                <span class="status-dot"></span>
+              </el-tooltip>
+            </div>
           </div>
 
           <!-- 空状态 -->
@@ -954,17 +963,79 @@ export default {
       }).catch(() => {});
     },
 
-    // 状态切换
+    /**
+     * 状态切换(启用/停用)
+     * 添加确认提示和状态说明
+     */
     handleStateChange(strategy) {
-      const originalState = strategy.strategyState === 1 ? 0 : 1;
-      changeStrategyState(strategy.strategyCode, strategy.strategyState).then(() => {
-        this.$message.success(strategy.strategyState === 1 ? '启用成功' : '停用成功');
+      const newState = strategy.strategyState;
+      const action = newState === 1 ? '启用' : '停用';
+      const originalState = newState === 1 ? 0 : 1;
+
+      // 构建确认提示信息
+      let confirmMsg = `确认${action}策略【${strategy.strategyName}】吗?`;
+
+      if (newState === 1) {
+        // 启用策略时的额外提示
+        const triggerTypeMap = {
+          1: '事件触发 - 监听设备事件自动执行',
+          2: '定时触发 - 按CRON表达式定时执行',
+          3: '手动触发 - 需要手动点击执行按钮',
+          4: '条件触发 - 监听设备属性变化自动执行'
+        };
+        confirmMsg += `\n\n触发类型:${triggerTypeMap[strategy.triggerType] || '未知'}`;
+
+        if (strategy.triggerType === 2 && strategy.execRule) {
+          confirmMsg += `\nCRON表达式:${strategy.execRule}`;
+        }
+
+        if (strategy.triggerType === 4) {
+          confirmMsg += '\n\n启用后,当监控的设备属性满足条件时将自动执行策略。';
+        }
+      }
+
+      this.$confirm(confirmMsg, '提示', {
+        type: 'warning',
+        distinguishCancelAndClose: true
+      }).then(() => {
+        // 调用后端API
+        changeStrategyState(strategy.strategyCode, newState).then(() => {
+          if (newState === 1) {
+            this.$message.success(`策略已启用,${this.getTriggerTypeHint(strategy.triggerType)}`);
+          } else {
+            this.$message.success('策略已停用');
+          }
+          // 刷新列表获取最新状态
+          this.getList();
+        }).catch((error) => {
+          // 恢复原状态
+          strategy.strategyState = originalState;
+          this.$message.error('操作失败: ' + (error.msg || error.message || '未知错误'));
+        });
       }).catch(() => {
+        // 用户取消,恢复原状态
         strategy.strategyState = originalState;
-        this.$message.error('操作失败');
       });
     },
 
+    /**
+     * 获取触发类型提示信息
+     */
+    getTriggerTypeHint(triggerType) {
+      switch (triggerType) {
+        case 1:
+          return '系统将监听设备事件并自动执行';
+        case 2:
+          return '系统将按定时规则自动执行';
+        case 3:
+          return '请点击执行按钮手动触发';
+        case 4:
+          return '系统将监控设备属性变化并自动执行';
+        default:
+          return '';
+      }
+    },
+
     // 查看日志
     handleViewLog(strategy) {
       this.currentLogStrategy = strategy;

+ 161 - 44
ems-ui-cloud/src/views/mgr/strategy/log.vue

@@ -61,13 +61,13 @@
 
         <div class="filter-section">
           <div class="filter-title">触发类型</div>
-          <el-radio-group v-model="queryParams.triggerType" @change="getList">
-            <el-radio label="">全部</el-radio>
-            <el-radio label="EVENT">事件</el-radio>
-            <el-radio label="TIME">定时</el-radio>
-            <el-radio label="MANUAL">手动</el-radio>
-            <el-radio label="CONDITION">条件</el-radio>
-            <el-radio label="ATTR">属性</el-radio>
+          <el-radio-group v-model="queryParams.triggerType" @change="getList" size="mini">
+            <el-radio-button label="">全部</el-radio-button>
+            <el-radio-button label="MANUAL">手动</el-radio-button>
+            <el-radio-button label="TIME">定时</el-radio-button>
+            <el-radio-button label="POLLING">轮询</el-radio-button>
+            <el-radio-button label="ATTR">属性</el-radio-button>
+            <el-radio-button label="EVENT">事件</el-radio-button>
           </el-radio-group>
         </div>
 
@@ -97,17 +97,17 @@
       <!-- 右侧日志列表 -->
       <div class="log-panel">
         <el-table :data="logList" v-loading="loading" border stripe>
-          <el-table-column label="执行ID" prop="execId" width="200" show-overflow-tooltip>
+          <el-table-column label="执行ID" prop="execId" width="120" show-overflow-tooltip>
             <template slot-scope="{ row }">
               <el-button type="text" @click="viewLogDetail(row)">
                 {{ row.execId.slice(0, 8) }}...
               </el-button>
             </template>
           </el-table-column>
-          <el-table-column label="策略" min-width="150">
+          <el-table-column label="策略" min-width="180">
             <template slot-scope="{ row }">
               <div class="strategy-info">
-                <span class="strategy-name">{{ row.strategyName || row.strategyCode }}</span>
+                <span class="strategy-name">{{ getStrategyName(row.strategyCode) }}</span>
                 <span class="strategy-code">{{ row.strategyCode }}</span>
               </div>
             </template>
@@ -115,11 +115,19 @@
           <el-table-column label="触发类型" width="100" align="center">
             <template slot-scope="{ row }">
               <el-tag size="small" :type="getTriggerTagType(row.triggerType)">
+                <i :class="getTriggerIcon(row.triggerType)"></i>
                 {{ getTriggerTypeName(row.triggerType) }}
               </el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="触发源" prop="triggerSource" width="120" show-overflow-tooltip />
+          <el-table-column label="触发源" width="160" show-overflow-tooltip>
+            <template slot-scope="{ row }">
+              <span class="trigger-source">
+                <i :class="getTriggerSourceIcon(row.triggerSource)"></i>
+                {{ formatTriggerSource(row.triggerSource) }}
+              </span>
+            </template>
+          </el-table-column>
           <el-table-column label="执行状态" width="100" align="center">
             <template slot-scope="{ row }">
               <el-tag :type="getExecStatusType(row.execStatus)" size="small">
@@ -133,19 +141,22 @@
               {{ parseTime(row.startTime) }}
             </template>
           </el-table-column>
-          <el-table-column label="耗时" width="100" align="center">
+          <el-table-column label="耗时" width="90" align="center">
             <template slot-scope="{ row }">
               <span :class="getDurationClass(row.duration)">
                 {{ formatDuration(row.duration) }}
               </span>
             </template>
           </el-table-column>
-          <el-table-column label="执行人" prop="execBy" width="100" align="center">
+          <el-table-column label="执行人" width="120" align="center">
             <template slot-scope="{ row }">
-              {{ row.execBy || '-' }}
+              <span class="exec-by">
+                <i :class="getExecByIcon(row.execBy)"></i>
+                {{ formatExecBy(row.execBy) }}
+              </span>
             </template>
           </el-table-column>
-          <el-table-column label="操作" width="120" align="center" fixed="right">
+          <el-table-column label="操作" width="80" align="center" fixed="right">
             <template slot-scope="{ row }">
               <el-button size="mini" type="text" @click="viewLogDetail(row)">
                 <i class="el-icon-view"></i> 详情
@@ -182,20 +193,44 @@
         <div class="detail-section">
           <h3 class="section-title">基本信息</h3>
           <el-descriptions :column="2" border size="small">
-            <el-descriptions-item label="执行ID">{{ currentLog.execId }}</el-descriptions-item>
+            <el-descriptions-item label="执行ID">
+              <el-tooltip :content="currentLog.execId" placement="top">
+                <span>{{ currentLog.execId }}</span>
+              </el-tooltip>
+            </el-descriptions-item>
             <el-descriptions-item label="策略代码">{{ currentLog.strategyCode }}</el-descriptions-item>
-            <el-descriptions-item label="策略名称">{{ currentLog.strategyName || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="触发类型">{{ getTriggerTypeName(currentLog.triggerType) }}</el-descriptions-item>
-            <el-descriptions-item label="触发源">{{ currentLog.triggerSource || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="策略名称">{{ getStrategyName(currentLog.strategyCode) }}</el-descriptions-item>
+            <el-descriptions-item label="触发类型">
+              <el-tag :type="getTriggerTagType(currentLog.triggerType)" size="small">
+                <i :class="getTriggerIcon(currentLog.triggerType)"></i>
+                {{ getTriggerTypeName(currentLog.triggerType) }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="触发源">
+              <span class="trigger-source-detail">
+                <i :class="getTriggerSourceIcon(currentLog.triggerSource)"></i>
+                {{ formatTriggerSource(currentLog.triggerSource) }}
+              </span>
+            </el-descriptions-item>
             <el-descriptions-item label="执行状态">
               <el-tag :type="getExecStatusType(currentLog.execStatus)" size="small">
+                <i :class="getExecStatusIcon(currentLog.execStatus)"></i>
                 {{ getExecStatusText(currentLog.execStatus) }}
               </el-tag>
             </el-descriptions-item>
-            <el-descriptions-item label="执行人">{{ currentLog.execBy || '系统' }}</el-descriptions-item>
+            <el-descriptions-item label="执行人">
+              <span class="exec-by-detail">
+                <i :class="getExecByIcon(currentLog.execBy)"></i>
+                {{ formatExecBy(currentLog.execBy) }}
+              </span>
+            </el-descriptions-item>
             <el-descriptions-item label="开始时间">{{ parseTime(currentLog.startTime) }}</el-descriptions-item>
             <el-descriptions-item label="结束时间">{{ parseTime(currentLog.endTime) || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="执行耗时">{{ formatDuration(currentLog.duration) }}</el-descriptions-item>
+            <el-descriptions-item label="执行耗时">
+              <span :class="getDurationClass(currentLog.duration)">
+                {{ formatDuration(currentLog.duration) }}
+              </span>
+            </el-descriptions-item>
           </el-descriptions>
         </div>
 
@@ -254,15 +289,13 @@
           </el-timeline>
           <el-empty v-else description="暂无步骤执行记录" />
         </div>
-
-        <!-- ✅ 已删除"上下文数据"区块 -->
       </div>
     </el-drawer>
   </div>
 </template>
 
 <script>
-import { listEnergyStrategy, getExecLogList, getExecLog, getStepExecLog } from '@/api/mgr/energyStrategy';
+import { listEnergyStrategy, getExecLogList, getStepExecLog } from '@/api/mgr/energyStrategy';
 
 export default {
   name: 'StrategyLog',
@@ -273,6 +306,7 @@ export default {
       logList: [],
       total: 0,
       strategyList: [],
+      strategyMap: {},  // 策略代码 -> 策略名称映射
       dateRange: [],
 
       queryParams: {
@@ -312,24 +346,32 @@ export default {
   },
 
   methods: {
-    // 加载策略列表(用于筛选)
+    // 加载策略列表(用于筛选和名称显示
     async loadStrategies() {
       try {
         const response = await listEnergyStrategy({ pageSize: 1000 });
         this.strategyList = response.rows || [];
+        // 构建映射表
+        this.strategyMap = {};
+        this.strategyList.forEach(s => {
+          this.strategyMap[s.strategyCode] = s.strategyName;
+        });
       } catch (error) {
         console.error('加载策略列表失败', error);
-        this.$message.error('加载策略列表失败');
       }
     },
 
+    // 获取策略名称
+    getStrategyName(strategyCode) {
+      return this.strategyMap[strategyCode] || strategyCode;
+    },
+
     // 获取日志列表
     async getList() {
       this.loading = true;
       try {
         const params = { ...this.queryParams };
 
-        // 处理日期范围
         if (this.dateRange && this.dateRange.length === 2) {
           params.startTimeBegin = this.dateRange[0] + ' 00:00:00';
           params.startTimeEnd = this.dateRange[1] + ' 23:59:59';
@@ -338,7 +380,6 @@ export default {
           params.startTimeEnd = '';
         }
 
-        // 清理空值参数
         Object.keys(params).forEach(key => {
           if (params[key] === '' || params[key] === null) {
             delete params[key];
@@ -348,12 +389,9 @@ export default {
         const response = await getExecLogList(params);
         this.logList = response.rows || [];
         this.total = response.total || 0;
-
-        // 计算统计数据
         this.calculateStats();
       } catch (error) {
         console.error('获取日志列表失败', error);
-        this.$message.error('获取日志列表失败');
         this.logList = [];
         this.total = 0;
       } finally {
@@ -387,45 +425,123 @@ export default {
       this.getList();
     },
 
-    // 查看日志详情
     async viewLogDetail(log) {
       this.currentLog = log;
       this.detailDrawerVisible = true;
       this.stepLogs = [];
 
-      // 加载步骤执行日志
       try {
         const response = await getStepExecLog(log.execId);
         this.stepLogs = (response.data || []).sort((a, b) => a.stepIndex - b.stepIndex);
       } catch (error) {
         console.error('加载步骤日志失败', error);
-        this.$message.warning('步骤日志加载失败');
       }
     },
 
-    // 辅助方法
+    // ==================== 触发类型相关 ====================
     getTriggerTypeName(type) {
       const map = {
-        'EVENT': '事件',
-        'TIME': '定时',
         'MANUAL': '手动',
-        'CONDITION': '条件',
-        'ATTR': '属性'  // ✅ 新增属性触发类型
+        'TIME': '定时',
+        'EVENT': '事件',
+        'ATTR': '属性',
+        'POLLING': '轮询',
+        'CONDITION': '条件'
       };
-      return map[type] || type || '-';
+      return map[type] || type || '未知';
     },
 
     getTriggerTagType(type) {
       const map = {
-        'EVENT': 'danger',
-        'TIME': '',
         'MANUAL': 'info',
-        'CONDITION': 'success',
-        'ATTR': 'warning'  // ✅ 新增属性触发类型
+        'TIME': '',
+        'EVENT': 'danger',
+        'ATTR': 'warning',
+        'POLLING': 'success',
+        'CONDITION': ''
       };
       return map[type] || '';
     },
 
+    getTriggerIcon(type) {
+      const map = {
+        'MANUAL': 'el-icon-user',
+        'TIME': 'el-icon-time',
+        'EVENT': 'el-icon-lightning',
+        'ATTR': 'el-icon-connection',
+        'POLLING': 'el-icon-refresh',
+        'CONDITION': 'el-icon-set-up'
+      };
+      return map[type] || 'el-icon-question';
+    },
+
+    // ==================== 触发源相关 ====================
+    formatTriggerSource(source) {
+      if (!source) return '-';
+
+      // 处理 CRON 表达式
+      if (source.startsWith('CRON:')) {
+        return source.replace('CRON:', '定时: ');
+      }
+
+      // 处理设备属性格式 (deviceCode.attrKey)
+      if (source.includes('.')) {
+        const parts = source.split('.');
+        if (parts.length === 2) {
+          return `${parts[0]} / ${parts[1]}`;
+        }
+      }
+
+      // 处理系统触发源
+      const sourceMap = {
+        'USER': '用户操作',
+        'USER_MANUAL': '用户手动',
+        'SCHEDULER': '定时调度',
+        'EVENT_LISTENER': '事件监听',
+        'ATTR_CHANGE': '属性变化',
+        'POLLING_MONITOR': '轮询监控'
+      };
+
+      return sourceMap[source] || source;
+    },
+
+    getTriggerSourceIcon(source) {
+      if (!source) return 'el-icon-question';
+
+      if (source.startsWith('CRON:')) return 'el-icon-alarm-clock';
+      if (source.includes('.')) return 'el-icon-cpu';
+      if (source.includes('USER')) return 'el-icon-user';
+      if (source.includes('SCHEDULER')) return 'el-icon-time';
+      if (source.includes('POLLING')) return 'el-icon-refresh';
+
+      return 'el-icon-s-operation';
+    },
+
+    // ==================== 执行人相关 ====================
+    formatExecBy(execBy) {
+      if (!execBy) return '-';
+
+      const execByMap = {
+        'SYSTEM': '系统',
+        'SYSTEM_SCHEDULER': '定时任务',
+        'SYSTEM_POLLING': '轮询监控',
+        'SYSTEM_ATTR_CHANGE': '属性变化',
+        'SYSTEM_EVENT': '事件触发',
+        'UNKNOWN_USER': '未知用户',
+        'ANONYMOUS': '匿名用户'
+      };
+
+      return execByMap[execBy] || execBy;
+    },
+
+    getExecByIcon(execBy) {
+      if (!execBy) return 'el-icon-question';
+
+      if (execBy.startsWith('SYSTEM')) return 'el-icon-monitor';
+      return 'el-icon-user';
+    },
+
+    // ==================== 执行状态相关 ====================
     getExecStatusType(status) {
       const map = { 0: 'info', 1: 'success', 2: 'danger', 3: 'warning' };
       return map[status] || '';
@@ -451,6 +567,7 @@ export default {
       return map[status] || 'el-icon-more';
     },
 
+    // ==================== 工具方法 ====================
     getDurationClass(duration) {
       if (!duration) return '';
       if (duration > 10000) return 'duration-slow';

Некоторые файлы не были показаны из-за большого количества измененных файлов