388 lines
14 KiB
Vue
388 lines
14 KiB
Vue
<!--
|
||
* 文件说明:识别节点参数面板,配置识别条件和告警规则。
|
||
* 作用范围:仅服务于流程设计器模块。
|
||
-->
|
||
<template>
|
||
<el-form :inline="true" :model="RecognizeNodeData" :rules="rules" ref="dynamicFormRef" class="http-form"
|
||
label-position="top" label-width="auto">
|
||
<el-collapse v-model="activeNames">
|
||
<el-collapse-item name="1" title="识别配置" icon-position="left">
|
||
<div v-for="(property, index) in RecognizeNodeData.nodeParams" class="form__container" :key="index">
|
||
<el-row>
|
||
<el-form-item :label="index === 0 ? '参数名' : ''" :prop="`nodeParams.${index}.name`"
|
||
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]">
|
||
<el-input :disabled="property?.disabled || false" class="param-name" v-model="property.name"
|
||
placeholder="请输入" clearable />
|
||
</el-form-item>
|
||
<el-form-item :label="index === 0 ? '参数值' : ''" :prop="`nodeParams.${index}.type`">
|
||
<el-select v-model="property.type" class="param-type"
|
||
@change="handleTypeChange(index, RecognizeNodeData, property.type)">
|
||
<el-option label="引用" value="quote" />
|
||
<el-option label="输入" value="input" />
|
||
</el-select>
|
||
<el-form-item v-if="property.type === 'input'" :rules="{ required: true, message: '请输入参数值', trigger: 'blur' }"
|
||
:prop="`nodeParams.${index}.input`">
|
||
<el-input-number class="param-value" v-if="property.componentType === 'number'"
|
||
v-model="property.input" :min="0" :max="property.max || Infinity"
|
||
:controls="property?.controls || true" :step-strictly="true"
|
||
:step="property.step || 1" placeholder="请输入" clearable />
|
||
<el-select v-model="property.input" class="param-value"
|
||
v-else-if="property.componentType === 'select'">
|
||
<el-option v-for="item in property.selectOptions" :key="item.value"
|
||
:label="item.label" :value="item.value" />
|
||
</el-select>
|
||
<el-input class="param-value" v-else v-model="property.input" placeholder="请输入"
|
||
clearable />
|
||
</el-form-item>
|
||
<el-form-item v-if="property.type === 'quote'"
|
||
:rules="[{ required: true, message: '请选择参数值', trigger: 'blur' }]"
|
||
:prop="`nodeParams.${index}.quote`">
|
||
<el-cascader :ref="(el) => { if (el) cascaderRefs[index] = el }"
|
||
v-model="property.quote" :checkStrictly="true" :options="quoteOptions"
|
||
placeholder="请选择"
|
||
@visible-change="(visible) => visibleChange(visible, index, property.quote)"
|
||
@change="(value) => cascaderChange(value, index, RecognizeNodeData, 'nodeParams')" />
|
||
</el-form-item>
|
||
</el-form-item>
|
||
</el-row>
|
||
</div>
|
||
</el-collapse-item>
|
||
<el-collapse-item name="2" title="告警级别" icon-position="left">
|
||
<template #title>
|
||
告警级别
|
||
<el-button :circle="true" size="small" @click="addAlarmRule" class="addFormItem">
|
||
<el-icon :size="14"><Plus /></el-icon>
|
||
</el-button>
|
||
</template>
|
||
<div class="form__container alarm-rules">
|
||
<el-row v-for="(rule, index) in RecognizeNodeData.alarmRules" :key="index">
|
||
<el-form-item label="告警级别" :prop="`alarmRules.${index}.level`"
|
||
:rules="[{ required: true, message: '请输入告警级别', trigger: 'change' }]">
|
||
<el-input-number v-model="rule.level" :min="1" :max="3" :step="1"
|
||
:step-strictly="true" />
|
||
</el-form-item>
|
||
<el-form-item label="比较符" :prop="`alarmRules.${index}.operator`"
|
||
:rules="[{ required: true, message: '请选择比较符', trigger: 'change' }]">
|
||
<el-select v-model="rule.operator" placeholder="请选择">
|
||
<el-option v-for="operator in alarmOperators" :key="operator.value"
|
||
:label="operator.label" :value="operator.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="阈值" :prop="`alarmRules.${index}.threshold`"
|
||
:rules="[{ required: true, message: '请输入阈值', trigger: 'change' }]">
|
||
<el-input-number v-model="rule.threshold" :controls="false" />
|
||
</el-form-item>
|
||
<el-form-item label="告警信息" :prop="`alarmRules.${index}.message`"
|
||
:rules="[{ required: true, message: '请输入告警信息', trigger: 'blur' }]">
|
||
<el-input v-model="rule.message" placeholder="请输入告警信息" clearable />
|
||
</el-form-item>
|
||
<el-button type="danger" link class="deleteBtn" @click="deleteAlarmRule(index)">
|
||
<el-icon><Minus /></el-icon>
|
||
</el-button>
|
||
</el-row>
|
||
<el-empty v-if="!RecognizeNodeData.alarmRules.length" description="暂无告警规则" :image-size="60" />
|
||
</div>
|
||
</el-collapse-item>
|
||
<el-collapse-item name="3" icon-position="left">
|
||
<template #title>
|
||
输出参数
|
||
<el-tooltip class="box-item" effect="dark" content="这里定义的输出变量可以被后续节点引用。" placement="top">
|
||
<el-icon class="header-icon" style="margin-left: 6px">
|
||
<info-filled />
|
||
</el-icon>
|
||
</el-tooltip>
|
||
<el-button :circle="true" size="small" @click="(e) => addRecognizeItem(e)" class="addFormItem">
|
||
<el-icon :size="14">
|
||
<Plus />
|
||
</el-icon>
|
||
</el-button>
|
||
</template>
|
||
<div class="form__container">
|
||
<el-form :inline="true" :model="RecognizeNodeData" label-position="top" label-width="auto"
|
||
:rules="outputRules" ref="outputFormRef">
|
||
<FormItemRecursive formType="output" :current-list="RecognizeNodeData.outputParams"
|
||
prop-path="outputParams" :depth="0" :is-first-level="true" :endDepth="2" :parent-path="[]"
|
||
@delete-item="(path) => deleteRecognizeItem(path, 'outputParams')" />
|
||
</el-form>
|
||
</div>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</el-form>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, watch, nextTick } from 'vue'
|
||
import { Plus, Minus } from '@element-plus/icons-vue'
|
||
import FormItemRecursive from '../FormItemRecursive.vue'
|
||
import { transformRecognizeNodeData } from '@/utils/flow'
|
||
import { useQuote } from './useQuote.js'
|
||
|
||
const props = defineProps({
|
||
data: Object
|
||
})
|
||
|
||
const emit = defineEmits(['save-success', 'save-error'])
|
||
|
||
const formData = reactive({
|
||
nodeParams: [],
|
||
outputParams: []
|
||
})
|
||
|
||
const rules = reactive({})
|
||
const outputRules = reactive({})
|
||
const dynamicFormRef = ref()
|
||
const outputFormRef = ref()
|
||
const activeNames = ref(['1', '2'])
|
||
|
||
// 使用共享的 quote 逻辑
|
||
const { quoteOptions, cascaderRefs, handleTypeChange, cascaderChange, visibleChange } = useQuote(props.data.id)
|
||
|
||
// 添加表单项
|
||
/**
|
||
* 新增 addFormItem 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
* @param {*} e 触发操作的浏览器事件
|
||
* @param {*} type 目标类型
|
||
*/
|
||
const addFormItem = (e, type) => {
|
||
e.stopPropagation()
|
||
const obj = {
|
||
name: '',
|
||
type: type === 'nodeParams' ? 'input' : 'string',
|
||
required: false,
|
||
children: [],
|
||
input: ''
|
||
}
|
||
if (!formData[type]) formData[type] = []
|
||
formData[type].push(obj)
|
||
}
|
||
|
||
// 商道智能体
|
||
const RecognizeNodeData = ref({})
|
||
|
||
const alarmOperators = [
|
||
{ label: '大于(GT)', value: 'GT' },
|
||
{ label: '大于等于(GTE)', value: 'GTE' },
|
||
{ label: '小于(LT)', value: 'LT' },
|
||
{ label: '小于等于(LTE)', value: 'LTE' },
|
||
{ label: '等于(EQ)', value: 'EQ' },
|
||
{ label: '不等于(NE)', value: 'NE' }
|
||
]
|
||
|
||
/**
|
||
* 新增 addAlarmRule 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
* @param {*} e 触发操作的浏览器事件
|
||
*/
|
||
const addAlarmRule = (e) => {
|
||
e.stopPropagation()
|
||
RecognizeNodeData.value.alarmRules.push({
|
||
level: 1,
|
||
operator: 'GTE',
|
||
threshold: 0,
|
||
message: ''
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除 deleteAlarmRule 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
* @param {*} index 目标项索引
|
||
*/
|
||
const deleteAlarmRule = (index) => {
|
||
RecognizeNodeData.value.alarmRules.splice(index, 1)
|
||
}
|
||
|
||
/**
|
||
* 新增 addRecognizeItem 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
* @param {*} e 触发操作的浏览器事件
|
||
*/
|
||
const addRecognizeItem = (e) => {
|
||
e.stopPropagation();
|
||
RecognizeNodeData.value.outputParams.push({ name: "", type: "string", input: "" });
|
||
}
|
||
|
||
/**
|
||
* 删除 deleteRecognizeItem 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
* @param {*} fullPath 字段完整路径
|
||
* @param {*} propPath 字段校验路径
|
||
*/
|
||
const deleteRecognizeItem = (fullPath, propPath) => {
|
||
// 从顶层数据开始查找
|
||
let currentLevel = RecognizeNodeData.value[propPath];
|
||
|
||
// 遍历路径(除最后一个索引,因为最后一个是要删除的项)
|
||
for (let i = 0; i < fullPath.length - 1; i++) {
|
||
const index = fullPath[i];
|
||
// 进入下一层级
|
||
currentLevel = currentLevel[index].children;
|
||
}
|
||
|
||
// 最后一个索引是当前层级要删除的项
|
||
const lastIndex = fullPath[fullPath.length - 1];
|
||
currentLevel.splice(lastIndex, 1);
|
||
}
|
||
|
||
// 初始化数据
|
||
/**
|
||
* 初始化 initData 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
*/
|
||
const initData = () => {
|
||
const nodeParams = JSON.parse(JSON.stringify(props.data.properties.nodeParams || []))
|
||
const outputParams = JSON.parse(JSON.stringify(props.data.properties.outputParams || []))
|
||
const transformedData = transformRecognizeNodeData(nodeParams);
|
||
RecognizeNodeData.value = {
|
||
...transformedData,
|
||
outputParams
|
||
};
|
||
}
|
||
|
||
watch(
|
||
() => props.data,
|
||
() => {
|
||
initData()
|
||
},
|
||
{ immediate: true, deep: true }
|
||
)
|
||
|
||
// 保存验证
|
||
/**
|
||
* 校验 validateAndSave 对应的数据或交互;作用范围仅限当前组件或模块。
|
||
*/
|
||
const validateAndSave = async () => {
|
||
let inputValid = true
|
||
let outputValid = true
|
||
if (dynamicFormRef.value) {
|
||
await dynamicFormRef.value.validate((valid) => { if (!valid) inputValid = false })
|
||
}
|
||
if (outputFormRef.value) {
|
||
await outputFormRef.value.validate((valid) => { if (!valid) outputValid = false })
|
||
}
|
||
|
||
const nodeParams = []
|
||
nodeParams.push(...RecognizeNodeData.value.nodeParams)
|
||
nodeParams.push({
|
||
name: "alarmRules",
|
||
type: "input",
|
||
input: RecognizeNodeData.value.alarmRules.map(({ level, operator, threshold, message }) => ({
|
||
level,
|
||
operator,
|
||
threshold,
|
||
message
|
||
})),
|
||
disabled: true
|
||
})
|
||
lf.setProperties(props.data.id, {
|
||
...props.data.properties,
|
||
nodeParams,
|
||
outputParams: RecognizeNodeData.value.outputParams
|
||
});
|
||
|
||
|
||
if (inputValid && outputValid) {
|
||
emit('save-success')
|
||
} else {
|
||
emit('save-error')
|
||
}
|
||
}
|
||
|
||
defineExpose({ validateAndSave })
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.http-form {
|
||
width: 100%;
|
||
|
||
.el-collapse {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.form__container {
|
||
margin: 12px 0;
|
||
|
||
:deep(.el-row) {
|
||
align-items: end;
|
||
|
||
.el-form-item {
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.param-name {
|
||
width: 160px;
|
||
}
|
||
|
||
.param-type {
|
||
width: 80px;
|
||
}
|
||
|
||
.param-value {
|
||
width: 160px;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
.sub-properties {
|
||
margin-left: 10px;
|
||
|
||
.zw {
|
||
margin-left: 15px;
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: -4px;
|
||
width: 10px;
|
||
border-left: 1px solid gray;
|
||
border-bottom: 1px solid gray;
|
||
border-bottom-left-radius: 4px;
|
||
background-color: transparent;
|
||
height: 24px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.gSon-properties {
|
||
margin-left: 10px;
|
||
|
||
.zw {
|
||
margin-left: 15px;
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: -4px;
|
||
width: 10px;
|
||
border-left: 1px solid gray;
|
||
border-bottom: 1px solid gray;
|
||
border-bottom-left-radius: 4px;
|
||
background-color: transparent;
|
||
height: 24px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.deleteBtn {
|
||
margin-bottom: 22px;
|
||
cursor: pointer;
|
||
}
|
||
}
|
||
|
||
.alarm-rules {
|
||
:deep(.el-row) {
|
||
flex-wrap: nowrap;
|
||
|
||
.el-form-item {
|
||
flex: 1;
|
||
}
|
||
|
||
.el-select,
|
||
.el-input,
|
||
.el-input-number {
|
||
width: 100%;
|
||
}
|
||
}
|
||
}
|
||
</style>
|