|
|
@@ -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>
|