440 lines
14 KiB
Vue
440 lines
14 KiB
Vue
|
|
<template>
|
||
|
|
<div class="node__container" ref="switchRef" @mouseleave="setNodeProperties">
|
||
|
|
<NodeState :state="nodeOperatingStatus">
|
||
|
|
<template #input>
|
||
|
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||
|
|
</template>
|
||
|
|
<template #output>
|
||
|
|
<JsonViewer :value="outputJsonData" copyable boxed sort theme="light" />
|
||
|
|
</template>
|
||
|
|
</NodeState>
|
||
|
|
<NodeTitle :icon="switchSvg" :nodeId="props.model.id" :nodeProperties="props.properties" :nodeName="props.properties?.name || '分支'" nodeDesc="连接多个下游分支,根据设定的条件按照顺序查找的方式来匹配运行的分支,如果匹配到某条件则只运行该条件对应的分支,否则继续匹配下一条件直至结束" />
|
||
|
|
<div class="condition_title_container">
|
||
|
|
<div class="left">
|
||
|
|
<div class="space"></div>
|
||
|
|
<div class="title">所有条件</div>
|
||
|
|
</div>
|
||
|
|
<div class="right">
|
||
|
|
<el-button :disabled="flowStore.disableForm" @click="addFormItem" class="addFormItem">
|
||
|
|
<el-icon :size="20" ><Plus /></el-icon>
|
||
|
|
</el-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<el-form :inline="true" :model="formData" :rules="rules" ref="dynamicForm" label-position="top" label-width="auto" :disabled="flowStore.disableForm">
|
||
|
|
<div class="condition" v-for="(params, index) in formData.nodeParams" :id="params.id">
|
||
|
|
<div class="title__container">
|
||
|
|
<div class="left">
|
||
|
|
<div class="space"></div>
|
||
|
|
<div class="title">{{ index ===0 ? 'If' : 'Else If' }}</div>
|
||
|
|
</div>
|
||
|
|
<div class="right">
|
||
|
|
<el-button :disabled="flowStore.disableForm" circle size="small" @click="deleteFormItem(params.id, index)">
|
||
|
|
<el-icon :size="16" ><Minus /></el-icon>
|
||
|
|
</el-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="withOr">
|
||
|
|
<div class="title">条件</div>
|
||
|
|
<div>
|
||
|
|
<el-select v-model="params.withOr" placeholder="Select" style="width: 240px">
|
||
|
|
<el-option key="and" label="AND" value="and" />
|
||
|
|
<el-option key="or" label="OR" value="or" />
|
||
|
|
</el-select>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="form__container">
|
||
|
|
<el-row v-for="(property, pIndex) in params.list">
|
||
|
|
<!-- :prop="`nodeParams.${index}.list.${pIndex}.name`" :rules="[{ required: true, message: '请输入变量名', trigger: 'blur' }]" -->
|
||
|
|
<el-form-item :label="pIndex === 0 ? '引用变量' : ''" :prop="`nodeParams.${index}.list.${pIndex}.nameType`">
|
||
|
|
<!-- <el-input v-model="property.name" placeholder="请输入" clearable /> -->
|
||
|
|
<el-select v-model="property.nameType" @change="handleTypeChange(index, pIndex)">
|
||
|
|
<el-option label="引用" value="quote" />
|
||
|
|
<el-option label="输入" value="input" />
|
||
|
|
</el-select>
|
||
|
|
<el-form-item v-if="property.nameType === 'input'" :rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]" :prop="`nodeParams.${index}.list.${pIndex}.name`">
|
||
|
|
<el-input v-model="property.name" placeholder="请输入" clearable />
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item v-if="property.nameType === 'quote'" :rules="[{ required: true, message: '请选择参数值', trigger: 'blur' }]" :prop="`nodeParams.${index}.list.${pIndex}.nameQuote`">
|
||
|
|
<el-cascader v-model="property.nameQuote" :options="quoteOptions" placeholder="请选择" @visible-change="visibleChange" />
|
||
|
|
</el-form-item>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item :label="pIndex === 0 ? '选择条件' : ''" :prop="`nodeParams.${index}.list.${pIndex}.condition`" :rules="[{ required: true, message: '请输入变量名', trigger: 'blur' }]">
|
||
|
|
<el-select v-model="property.condition">
|
||
|
|
<el-option label="等于" value="equal" />
|
||
|
|
<el-option label="不等于" value="notEqualTo" />
|
||
|
|
<el-option label="为空" value="null" />
|
||
|
|
<el-option label="不为空" value="notNull" />
|
||
|
|
<el-option label="包含" value="include" />
|
||
|
|
<el-option label="不包含" value="notInclude" />
|
||
|
|
<el-option label="大于" value="greaterThan" />
|
||
|
|
<el-option label="大于等于" value="greaterThanOrEqual" />
|
||
|
|
<el-option label="小于" value="lessThan" />
|
||
|
|
<el-option label="小于等于" value="lessThanOrEqual" />
|
||
|
|
</el-select>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item :label="pIndex === 0 ? '比较值' : ''" :prop="`nodeParams.${index}.list.${pIndex}.type`" >
|
||
|
|
<el-select v-model="property.type" @change="handleTypeChange(index, pIndex)">
|
||
|
|
<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}.list.${pIndex}.input`">
|
||
|
|
<el-input 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}.list.${pIndex}.quote`">
|
||
|
|
<el-cascader v-model="property.quote" :options="quoteOptions" placeholder="请选择" @visible-change="visibleChange" />
|
||
|
|
</el-form-item>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item :label="pIndex === 0 ? '操作' : ''">
|
||
|
|
<el-button :disabled="flowStore.disableForm" circle size="small" @click="addListItem(index)">
|
||
|
|
<el-icon :size="12" ><Plus /></el-icon>
|
||
|
|
</el-button>
|
||
|
|
<el-button :disabled="flowStore.disableForm" circle size="small" @click="removeListItem(index, pIndex)">
|
||
|
|
<el-icon :size="12" ><Minus /></el-icon>
|
||
|
|
</el-button>
|
||
|
|
</el-form-item>
|
||
|
|
|
||
|
|
</el-row>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</el-form>
|
||
|
|
</div>
|
||
|
|
<div class="else" :id="''.concat(props.model.id, '_else')">
|
||
|
|
<div class="space"></div>
|
||
|
|
<div class="title">Else</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
<script setup>
|
||
|
|
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||
|
|
import NodeTitle from '../../components/NodeTitle.vue'
|
||
|
|
import NodeState from "../../components/NodeState.vue";
|
||
|
|
import switchSvg from '../../icon/switch.svg'
|
||
|
|
import { v4 as randomUUID } from 'uuid'
|
||
|
|
import { useFlowStore } from '@/store/modules/flow'
|
||
|
|
import { Plus, Minus } from '@element-plus/icons-vue'
|
||
|
|
import { getInput, addNewEdge, removeSwitchEdge } from '@/utils/flow'
|
||
|
|
import { emitter } from "@/utils/eventBus";
|
||
|
|
|
||
|
|
const props = defineProps({
|
||
|
|
model: Object,
|
||
|
|
graphModel: Object,
|
||
|
|
properties: Object,
|
||
|
|
nowTime: Number,
|
||
|
|
});
|
||
|
|
|
||
|
|
const emits = defineEmits(['addAnchor', 'removeAnchor', 'bindRef', 'changeAnchor'])
|
||
|
|
|
||
|
|
const switchRef = ref()
|
||
|
|
const dynamicForm = ref()
|
||
|
|
const flowStore = useFlowStore()
|
||
|
|
|
||
|
|
const formData = reactive({
|
||
|
|
nodeParams: props.properties?.conditions || []
|
||
|
|
})
|
||
|
|
|
||
|
|
const addFormItem = () => {
|
||
|
|
const rect = switchRef.value.getBoundingClientRect();
|
||
|
|
addCondition(rect.height - 60)
|
||
|
|
}
|
||
|
|
|
||
|
|
const deleteFormItem = (id, index) => {
|
||
|
|
if (index === 0) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
formData.nodeParams.splice(index, 1)
|
||
|
|
emits('removeAnchor', id)
|
||
|
|
removeSwitchEdge(props.model.id, id)
|
||
|
|
nextTick(() => {
|
||
|
|
changeFormItemHeight()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const quoteOptions = ref([])
|
||
|
|
const handleTypeChange = (pIndex, index) => {
|
||
|
|
if (formData.nodeParams[pIndex].list[index].type === 'input') {
|
||
|
|
formData.nodeParams[pIndex].list[index].quote = ""
|
||
|
|
} else {
|
||
|
|
formData.nodeParams[pIndex].list[index].input = ""
|
||
|
|
const option = getInput(props.model.id, false)
|
||
|
|
if (option) {
|
||
|
|
quoteOptions.value = option
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const addCondition = (height) => {
|
||
|
|
const id = randomUUID().replace(/-/g, '')
|
||
|
|
formData.nodeParams.push({
|
||
|
|
id,
|
||
|
|
withOr: 'and',
|
||
|
|
list: [{
|
||
|
|
name: '',
|
||
|
|
nameQuote: '',
|
||
|
|
nameType: 'input',
|
||
|
|
type: 'input',
|
||
|
|
input: '',
|
||
|
|
quote: ''
|
||
|
|
}]
|
||
|
|
})
|
||
|
|
emits('addAnchor', height, id)
|
||
|
|
|
||
|
|
setTimeout(() => {
|
||
|
|
addNewEdge(props.model.id)
|
||
|
|
}, 50)
|
||
|
|
|
||
|
|
nextTick(() => {
|
||
|
|
changeFormItemHeight()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const addListItem = (pIndex, cIndex) => {
|
||
|
|
formData.nodeParams[pIndex].list.push({
|
||
|
|
name: '',
|
||
|
|
nameQuote: '',
|
||
|
|
nameType: 'input',
|
||
|
|
type: 'input',
|
||
|
|
input: '',
|
||
|
|
quote: ''
|
||
|
|
})
|
||
|
|
nextTick(() => {
|
||
|
|
changeFormItemHeight()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const removeListItem = (pIndex, cIndex) => {
|
||
|
|
if (cIndex === 0) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
formData.nodeParams[pIndex].list.splice(cIndex, 1)
|
||
|
|
nextTick(() => {
|
||
|
|
changeFormItemHeight()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const changeFormItemHeight = () => {
|
||
|
|
formData.nodeParams.forEach(item => {
|
||
|
|
const node = document.getElementById(item.id)
|
||
|
|
emits('changeAnchor', node.offsetTop, item.id)
|
||
|
|
});
|
||
|
|
|
||
|
|
const node = document.getElementById("".concat(props.model.id, "_else"))
|
||
|
|
emits('changeAnchor', node.offsetTop, "".concat(props.model.id, "_else"))
|
||
|
|
}
|
||
|
|
|
||
|
|
const setNodeProperties = async () => {
|
||
|
|
try {
|
||
|
|
const valid = await dynamicForm.value.validate()
|
||
|
|
if (valid) {
|
||
|
|
const data = toRaw(formData)
|
||
|
|
const properties = lf.getProperties(props.model.id)
|
||
|
|
lf.setProperties(props.model.id, {
|
||
|
|
...properties,
|
||
|
|
...data
|
||
|
|
})
|
||
|
|
emits('contentChange')
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
dynamicForm.value.clearValidate()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const visibleChange = (value) => {
|
||
|
|
if (value) {
|
||
|
|
const option = getInput(props.model.id)
|
||
|
|
if (option) {
|
||
|
|
quoteOptions.value = option
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const nodeOperatingStatus = ref("NORMAL");
|
||
|
|
const inputJsonData = ref({});
|
||
|
|
const outputJsonData = ref({});
|
||
|
|
|
||
|
|
watch(
|
||
|
|
() => props.properties,
|
||
|
|
() => {
|
||
|
|
if (props.properties.nodeParams && props.properties.nodeParams.length > 0) {
|
||
|
|
formData.nodeParams = props.properties.nodeParams
|
||
|
|
} else {
|
||
|
|
if (formData.nodeParams.length === 0) {
|
||
|
|
addCondition(100)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
immediate: true,
|
||
|
|
deep: true
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
emits('addAnchor', 280, "".concat(props.model.id, "_else"))
|
||
|
|
|
||
|
|
emits('bindRef', dynamicForm.value)
|
||
|
|
|
||
|
|
emitter.on("changeNodeState", (data) => {
|
||
|
|
if (data.nodeId === props.model.id) {
|
||
|
|
nodeOperatingStatus.value = data.status;
|
||
|
|
let inputData = {};
|
||
|
|
let output = {};
|
||
|
|
try {
|
||
|
|
inputData = JSON.parse(data.paramsIn) || {};
|
||
|
|
output = JSON.parse(data.paramsOut) || {};
|
||
|
|
} catch (error) {
|
||
|
|
inputData = data.paramsIn || {};
|
||
|
|
output = data.paramsOut || {};
|
||
|
|
}
|
||
|
|
inputJsonData.value = inputData;
|
||
|
|
outputJsonData.value = output;
|
||
|
|
|
||
|
|
emits("contentChange");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
emitter.on("contentChange", (data) => {
|
||
|
|
if (data.id === props.model.id) {
|
||
|
|
nodeOperatingStatus.value = "NORMAL";
|
||
|
|
inputJsonData.value = {};
|
||
|
|
outputJsonData.value = {};
|
||
|
|
|
||
|
|
emits("contentChange");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
emitter.off("changeNodeState");
|
||
|
|
emitter.off("contentChange");
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
</script>
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.node__container {
|
||
|
|
width: 720px;
|
||
|
|
height: auto;
|
||
|
|
background: #fff;
|
||
|
|
padding: 12px;
|
||
|
|
cursor: default;
|
||
|
|
border-radius: 12px;
|
||
|
|
border: 2px solid white;
|
||
|
|
box-shadow: 0 5px 15px 0#00000008;
|
||
|
|
position: relative;
|
||
|
|
|
||
|
|
.condition_title_container {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
|
||
|
|
.left {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
|
||
|
|
.space {
|
||
|
|
width: 4px;
|
||
|
|
height: 14px;
|
||
|
|
background: rgb(22, 100, 255);
|
||
|
|
border-radius: 0px 4px 4px 0px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.title {
|
||
|
|
margin-left: 8px;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
.el-button {
|
||
|
|
border: none;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.condition {
|
||
|
|
width: 100%;
|
||
|
|
height: auto;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
background-color: #fafbfc;
|
||
|
|
padding: 8px 16px;
|
||
|
|
border-radius: 8px;
|
||
|
|
box-sizing: border-box;
|
||
|
|
|
||
|
|
.title__container {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
|
||
|
|
.left {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
|
||
|
|
.space {
|
||
|
|
width: 4px;
|
||
|
|
height: 14px;
|
||
|
|
background: rgb(22, 100, 255);
|
||
|
|
border-radius: 0px 4px 4px 0px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.title {
|
||
|
|
margin-left: 8px;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.withOr {
|
||
|
|
margin-bottom: 12px;
|
||
|
|
|
||
|
|
.title {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #737a87;
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.form__container) {
|
||
|
|
|
||
|
|
.el-form-item {
|
||
|
|
margin-right: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.el-input {
|
||
|
|
width: 92px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.el-select {
|
||
|
|
width: 92px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.el-cascader {
|
||
|
|
width: 92px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.else {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
background-color: #fafbfc;
|
||
|
|
padding: 8px 16px;
|
||
|
|
|
||
|
|
.space {
|
||
|
|
width: 4px;
|
||
|
|
height: 14px;
|
||
|
|
background: rgb(22, 100, 255);
|
||
|
|
border-radius: 0px 4px 4px 0px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.title {
|
||
|
|
margin-left: 8px;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 500;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|