feat: 处理循环体

This commit is contained in:
zhanghao 2026-04-15 15:30:29 +08:00
parent 783e54bab0
commit 5c7134f691
5 changed files with 150 additions and 130 deletions

View File

@ -109,7 +109,7 @@ export const recursiveFilter = (data, id, type = ['loop', 'customGroup'], result
data.forEach((item) => {
if (type.includes(item.type) && item.children && item.children.includes(id)) {
result.push(item)
recursiveFilter(data, item.id, result)
recursiveFilter(data, item.id, type, result)
}
});
return result

View File

@ -3,11 +3,30 @@
v-model="props.drawer"
custom-class="custom-drawer-header"
class="no-header-margin"
:title="props.data.properties?.name || '参数配置'"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
>
<template #header>
<div class="drawer-header">
<div>{{ props.data.properties?.name || '参数配置' }}</div>
<div>
<el-popconfirm
width="220"
confirm-button-text="确认删除"
cancel-button-text="误点击,不删除"
:icon="InfoFilled"
icon-color="#626AEF"
title="是否删除此节点?"
@confirm="deleteNode"
>
<template #reference>
<el-button type="primary" size="small">删除</el-button>
</template>
</el-popconfirm>
</div>
</div>
</template>
<div class="input__container" v-if="props.data.type === 'start'">
<el-collapse v-model="activeNames">
<el-collapse-item name="1" icon-position="left">
@ -787,7 +806,6 @@
</template>
<div class="form__container">
<el-select v-model="httpNodeData.body.type">
<el-option label="None" value="none" />
<el-option label="JSON" value="json" />
<el-option label="form-data" value="form-data" />
</el-select>
@ -818,7 +836,7 @@
<el-select
v-model="property.type"
class="param-type"
@change="handleTypeChange(index)"
@change="handleTypeChange(index, 'httpBody')"
>
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
@ -887,7 +905,7 @@
:options="quoteOptions"
placeholder="请选择"
@visible-change="
(visible) => visibleChange(visible, index, property.quote)
(visible) => visibleChange(visible, index, property.quote, 'httpBody')
"
@change="(value) => cascaderChange(value, index, 'httpBody')"
/>
@ -931,6 +949,11 @@ const props = defineProps({
const emits = defineEmits(['close'])
const deleteNode = () => {
lf.deleteNode(props.data.id);
emits('close')
}
const formData = reactive({
inputParams: [],
nodeParams: [],
@ -955,28 +978,25 @@ const confirm = async () => {
ElMessage.error('JSON中存在错误请修正后再保存');
return;
}
} else {
}
const nodeParams = []
nodeParams.push({ name: "config", type: "input", input: "", children: httpNodeData.value.config, disabled: true, required: true })
console.log('httpNodeData.value:', httpNodeData.value)
if (httpNodeData.value?.headers?.length > 0) {
nodeParams.push({ name: "headers", type: "input", input: "", children: httpNodeData.value.headers, disabled: true, required: true })
}
if (httpNodeData.value?.params?.length > 0) {
nodeParams.push({ name: "params", type: "input", input: "", children: httpNodeData.value.params, disabled: true, required: true })
}
if (httpNodeData.value.body.type !== 'none') {
const bodyObj = {
name: "body", type: "input", input: "", children: [
{ name: 'bodyType', type: "input", input: "json", disabled: true },
{ name: 'json', type: "input", input: editorInstance.getValue() || '' },
{ name: 'bodyType', type: "input", input: httpNodeData.value.body.type, disabled: true },
{ name: 'json', type: "input", input: httpNodeData.value.body.type === 'json' ? editorInstance.getValue() : '' },
{ name: 'formData', type: "input", input: "", children: httpNodeData.value.body.formData }
]
}
nodeParams.push(bodyObj)
}
lf.setProperties(props.data.id, {
...props.data.properties,
nodeParams
@ -1078,16 +1098,34 @@ const handleDelete = (index) => {
};
const quoteOptions = ref([]);
const handleTypeChange = (index) => {
const handleTypeChange = (index, type = 'default') => {
if (type === 'default') {
if (formData.nodeParams[index].type === "input") {
formData.nodeParams[index].quote = "";
} else {
formData.nodeParams[index].input = "";
const option = getInput(props.model.id);
const option = getInput(props.data.id);
if (option) {
quoteOptions.value = option;
}
}
} else {
const bodyItem = formData.nodeParams.find(item => item.name === 'body');
if (bodyItem) {
const formDataItem = bodyItem.children.find(child => child.name === 'formData');
if (formDataItem) {
if (formDataItem.children[index].type === "input") {
formDataItem.children[index].quote = "";
} else {
formDataItem.children[index].input = "";
const option = getInput(props.data.id);
if (option) {
quoteOptions.value = option;
}
}
}
}
}
};
const cascaderRefs = ref([]);
@ -1097,12 +1135,12 @@ const cascaderChange = (value, index, type = 'default') => {
formData.nodeParams[index].quote = value;
formData.nodeParams[index].quoteType = selectedOptions[0].data.type;
} else {
formData.nodeParams.find(item => item.name === 'body').children.find(child => child.name === 'formData').children[index].quote = value;
formData.nodeParams.find(item => item.name === 'body').children.find(child => child.name === 'formData').children[index].quoteType = selectedOptions[0].data.type;
httpNodeData.value.body.formData[index].quote = value;
httpNodeData.value.body.formData[index].quoteType = selectedOptions[0].data.type;
}
};
const visibleChange = (value, index, quote) => {
const visibleChange = (value, index, quote = '', type = 'default') => {
if (value) {
const option = getInput(props.data.id);
if (option) {
@ -1113,15 +1151,21 @@ const visibleChange = (value, index, quote) => {
// DOMoptions
setTimeout(() => {
//
if (currentValue.length) {
if (currentValue.length > 0) {
if (type === 'default') {
formData.nodeParams[index].quote = [];
//
setTimeout(() => {
formData.nodeParams[index].quote = currentValue;
}, 0);
} else {
httpNodeData.value.body.formData[index].quote = [];
//
setTimeout(() => {
httpNodeData.value.body.formData[index].quote = currentValue;
}, 0);
}
}
//
// cascaderRefs.value[index].updatePopper();
}, 0);
}
}
@ -1207,45 +1251,7 @@ watch(
}
);
const httpMethodOptions = () => {
return [{
value: 'POST',
label: 'POST'
}, {
value: 'GET',
label: 'GET'
}, {
value: 'PUT',
label: 'PUT'
}, {
value: 'DELETE',
label: 'DELETE'
}, {
value: 'PATCH',
label: 'PATCH'
}]
}
const httpNodeData = ref({
// config: [
// { name: "url", type: "input", input: "", disabled: true, required: true },
// { name: "method", type: "input", input: "POST", componentType: 'select', selectOptions: httpMethodOptions(), disabled: true },
// { name: "timeout", type: "input", input: "10000", componentType: 'number', disabled: true }
// ],
// headers: [
// { name: "", type: "input", input: ""}
// ],
// params: [
// { name: "", type: "input", input: ""}
// ],
// body: {
// type: 'json',
// json: '{}',
// formData: [
// { name: "", type: "input", input: ""}
// ]
// }
});
const httpNodeData = ref({});
const addHttpNodeFormItem = (e, type) => {
e.stopPropagation();
@ -1268,9 +1274,6 @@ const handleHttpNodeDelete = (type, index) => {
} else {
httpNodeData.value[type].splice(index, 1);
}
// nextTick(() => {
// dynamicForm.value?.clearValidate()
// });
};
watch(
@ -1300,15 +1303,14 @@ watch(
} else if (props.data.type === 'http') {
const nodeParams = JSON.parse(JSON.stringify(props.data.properties.nodeParams || []))
const transformedData = transformHttpNodeData(nodeParams);
console.log('transformedData', transformedData);
httpNodeData.value = transformedData;
if (transformedData.body.type === 'json') {
console.log('httpNodeData', httpNodeData.value);
if (httpNodeData.value.body.type === 'json') {
setTimeout(() => {
if (editorInstance) {
setValue(transformedData.body.json || '{}')
setValue(httpNodeData.value.body.json || '{}')
}
}, 100)
}
}
},
@ -1323,6 +1325,12 @@ onUnmounted(() => {
})
</script>
<style lang="scss" scoped>
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.addFormItem {
margin-left: 12px;
}

View File

@ -168,7 +168,7 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from "vue";
import LogicFlow, { action } from "@logicflow/core";
import LogicFlow from "@logicflow/core";
import "@logicflow/core/es/index.css";
import "@logicflow/core/lib/style/index.css";
import "@logicflow/extension/lib/style/index.css";
@ -220,7 +220,6 @@ const drop = (e) => {
!["selectArea", "branch", "stopLoop", "subStart", "subEnd"].includes(newNode.type)
) {
showParamsDrawer.value = true;
console.log("newNode", newNode);
paramsDrawerData.value = newNode;
}
};
@ -272,22 +271,7 @@ const testRun = async () => {
return;
}
let allValid = true;
// for (const node of nodes) {
// const nodeModel = lf.getNodeModelById(node.id);
// if (nodeModel && nodeModel.validateForm) {
// const result = await nodeModel.validateForm();
// if (!result.valid) {
// allValid = false;
// }
// }
// }
if (allValid) {
isOpen.value = true;
} else {
ElMessage.error("部分节点验证失败,请查看详情");
}
};
//
@ -623,6 +607,8 @@ const initFlow = () => {
lf.on("node:mousemove", ({ data, e }) => {
const { nodes } = lf.getGraphData();
const arr = recursiveFilter(nodes, data.id, "loop");
// const arr = findRelationNodesById(nodes, data.id);
console.log('arr', arr)
arr.forEach((item) => {
const node = lf.getNodeModelById(item.id);
node.updateSize();
@ -782,6 +768,12 @@ onMounted(() => {
initFlow();
document.addEventListener("singleNodeExecution", singleNodeExecution);
registerBeforeUnload();
emitter.on("openParamsDrawer", (node) => {
showParamsDrawer.value = true;
paramsDrawerData.value = node;
});
});
const operationEdge = (arr) => {
@ -792,6 +784,8 @@ const operationEdge = (arr) => {
onUnmounted(() => {
emitter.off("changeNodeState");
emitter.off("openParamsDrawer");
window.removeEventListener("beforeunload", handleBeforeUnload);
document.removeEventListener("singleNodeExecution", singleNodeExecution);
});

View File

@ -20,49 +20,57 @@ class loopBodyModel extends GroupNodeModel {
this.componentInstance = instance;
}
// 验证表单
// async validateForm() {
// if (this.componentInstance && this.componentInstance.validate) {
// try {
// const result = await this.componentInstance.validate();
// if (result) {
// return { valid: true, message: "验证通过" };
// } else {
// return { valid: false, message: "验证失败" };
// }
// } catch {
// return { valid: false, message: "验证失败" };
// }
// }
// return { valid: true, message: "无验证方法" };
// }
// 递归获取所有有效子节点(包含多层嵌套 children
getAllValidChildren = (nodeIds, graphModel) => {
const validNodes = [];
// 遍历当前层节点 ID
Array.from(nodeIds).forEach((id) => {
const node = graphModel.getNodeModelById(id);
if (node) {
// 当前节点有效,加入结果
validNodes.push(node);
// 如果节点还有 children递归获取下级节点
if (node.children && node.children.size > 0) {
const childNodes = this.getAllValidChildren(node.children, graphModel);
validNodes.push(...childNodes);
}
} else {
// 无效 ID从集合中删除
nodeIds.delete(id);
}
});
return validNodes;
};
// 计算子节点的包围盒(包含所有子节点的最小矩形区域)
getChildrenBBox() {
const children = []
Array.from(this.children).forEach((id) => {
const node = this.graphModel.getNodeModelById(id)
if (node) {
children.push(this.graphModel.getNodeModelById(id))
} else {
this.children.delete(id)
}
});
const children = this.getAllValidChildren(this.children, this.graphModel)
console.log('children', children)
// Array.from(this.children).forEach((id) => {
// const node = this.graphModel.getNodeModelById(id)
// if (node) {
// children.push(this.graphModel.getNodeModelById(id))
// } else {
// this.children.delete(id)
// }
// });
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
nextTick(() => {
children.forEach((node) => {
let width = node.width === 0 ? 320 : node.width
let height = node.height === 0 ? 200 : node.height
let _width = node.type === 'loop' ? 500 : 320
let _height = node.type === 'loop' ? 300 : 200
let width = node.width === 0 ? _width : node.width
let height = node.height === 0 ? _height : node.height
minX = Math.min(minX, node.x - width / 2);
maxX = Math.max(maxX, node.x + width / 2);
minY = Math.min(minY, node.y - height / 2);
maxY = Math.max(maxY, node.y + height / 2);
});
this.width = maxX - minX + 50; // 增加内边距
this.height = maxY - minY + 50;
this.width = maxX - minX + 180; // 增加内边距
this.height = maxY - minY + 300;
this.x = (minX + maxX) / 2;
this.y = (minY + maxY) / 2 - 120;
this.y = (minY + maxY) / 2;
const properties = lf.getProperties(this.id)
this.setProperties({
...properties,

View File

@ -20,10 +20,11 @@
</div>
</template>
<script setup>
import { onMounted } from "vue";
import NodeTitle from "../../components/NodeTitle.vue";
import loop from "../../icon/loop.svg";
import { addNewEdge } from "@/utils/flow";
import { emitter } from "@/utils/eventBus";
import { onUnmounted } from "vue";
const props = defineProps({
model: Object,
@ -56,6 +57,11 @@ const handleDrop = (e) => {
...other,
},
});
if (
!["selectArea", "branch", "stopLoop", "subStart", "subEnd"].includes(node.type)
) {
emitter.emit("openParamsDrawer", node);
}
emits("addToGroup", node.id);
setTimeout(() => {
@ -72,6 +78,10 @@ const setNodeName = (name) => {
emits("contentChange");
}
onUnmounted(() => {
emitter.off("openParamsDrawer");
})
</script>
<style lang="scss" scoped>
.group__container {