CMVR-IOT-UI/src/views/flow/nodes/function/switch.vue

578 lines
16 KiB
Vue
Raw Normal View History

2025-11-17 14:54:32 +08:00
<template>
<div class="node__container" ref="switchRef" @mouseleave="setNodeProperties">
2026-04-14 17:22:08 +08:00
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
2025-11-17 14:54:32 +08:00
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
<template #output>
2026-03-19 13:36:14 +08:00
<div v-if="nodeOperatingStatus != 'FAILED'">
<JsonViewer :value="outputJsonData" copyable boxed sort theme="light" />
</div>
<div v-else>{{ errorInfoData }}</div>
2025-11-17 14:54:32 +08:00
</template>
</NodeState>
<NodeTitle
:icon="switchSvg"
:nodeId="props.model.id"
:nodeProperties="props.properties"
:nodeName="props.properties?.name || '分支'"
nodeDesc="连接多个下游分支,根据设定的条件按照顺序查找的方式来匹配运行的分支,如果匹配到某条件则只运行该条件对应的分支,否则继续匹配下一条件直至结束"
@setNodeName="setNodeName"
2025-11-17 14:54:32 +08:00
/>
<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 setNodeName = (name) => {
const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, {
...properties,
name
});
emits("contentChange");
}
2025-11-17 14:54:32 +08:00
const visibleChange = (value) => {
if (value) {
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
}
};
const nodeOperatingStatus = ref("NORMAL");
2026-04-14 17:22:08 +08:00
const runtimes = ref(0);
2025-11-17 14:54:32 +08:00
const inputJsonData = ref({});
const outputJsonData = ref({});
2026-03-19 13:36:14 +08:00
const errorInfoData = ref('');
2025-11-17 14:54:32 +08:00
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;
2026-04-14 17:22:08 +08:00
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
2025-11-17 14:54:32 +08:00
let inputData = {};
let output = {};
2026-03-19 13:36:14 +08:00
errorInfoData.value = data?.message || ''
2025-11-17 14:54:32 +08:00
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>