From 75f64e8d9fc47726c781ef033c3f6e45a6b5b297 Mon Sep 17 00:00:00 2001
From: zhanghao <774378400@qq.com>
Date: Tue, 14 Apr 2026 11:35:42 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20http=E3=80=81loop=E3=80=81sleep?=
=?UTF-8?q?=E8=8A=82=E7=82=B9=E9=87=8D=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/views/flow/components/ParamsDrawer.vue | 626 +++++++++++++++++++--
src/views/flow/config.js | 22 +-
src/views/flow/nodes/function/httpNode.js | 14 +-
src/views/flow/nodes/function/httpNode.vue | 505 +----------------
src/views/flow/nodes/function/loop.js | 38 +-
src/views/flow/nodes/function/loop.vue | 238 +-------
src/views/flow/nodes/function/sleep.vue | 356 +-----------
7 files changed, 665 insertions(+), 1134 deletions(-)
diff --git a/src/views/flow/components/ParamsDrawer.vue b/src/views/flow/components/ParamsDrawer.vue
index f482552..9fa8067 100644
--- a/src/views/flow/components/ParamsDrawer.vue
+++ b/src/views/flow/components/ParamsDrawer.vue
@@ -43,7 +43,7 @@
-
+
@@ -395,6 +395,494 @@
+
+
+
+
+
+ 请求配置
+
+
+
+
+
+
+
+
+ Header配置
+ addHttpNodeFormItem(e, 'headers')"
+ class="addFormItem"
+ >
+
+
+
+
+
+
+
+ Param配置
+ addHttpNodeFormItem(e, 'params')"
+ class="addFormItem"
+ >
+
+
+
+
+
+
+
+ Body配置
+ addHttpNodeFormItem(e, 'body')"
+ class="addFormItem"
+ >
+
+
+
+
+
+
+
+
+
关闭
确定
@@ -430,18 +918,43 @@ const codeRef = ref()
let editorInstance
const handleClose = (done) => {
- const filteredObj = {
- inputParams: filterEmptyName(formData.inputParams),
- nodeParams: filterEmptyName(formData.nodeParams),
- outputParams: filterEmptyName(formData.outputParams),
- };
-
- lf.setProperties(props.data.id, {
- ...props.data.properties,
- ...filteredObj
- });
- emitter.emit("setProperties", { id: props.data.id});
+ if (props.data.type === 'http') {
+ const nodeParams = []
+ nodeParams.push({ name: "config", type: "input", input: "", children: httpNodeData.value.config, disabled: true, required: true })
+ if (httpNodeData.value.headers.length) {
+ nodeParams.push({ name: "headers", type: "input", input: "", children: httpNodeData.value.headers, disabled: true, required: true })
+ }
+ if (httpNodeData.value.params.length) {
+ 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: 'formData', type: "input", input: "", children: httpNodeData.value.body.formData }
+ ]
+ }
+ nodeParams.push(bodyObj)
+ }
+ lf.setProperties(props.data.id, {
+ ...props.data.properties,
+ nodeParams
+ });
+ } else {
+ const filteredObj = {
+ inputParams: filterEmptyName(formData.inputParams),
+ nodeParams: filterEmptyName(formData.nodeParams),
+ outputParams: filterEmptyName(formData.outputParams),
+ };
+
+ lf.setProperties(props.data.id, {
+ ...props.data.properties,
+ ...filteredObj
+ });
+ }
+ emitter.emit("setProperties", { id: props.data.id});
emits('close')
}
@@ -525,7 +1038,7 @@ const visibleChange = (value, index, quote) => {
}
};
-const activeNames = ref(['1', '2'])
+const activeNames = ref(['1', '2', '3', '4'])
function hasErrors() {
const model = editorInstance.getModel();
@@ -537,7 +1050,7 @@ function hasErrors() {
return markers.some(marker => marker.severity === monaco.MarkerSeverity.Error);
}
-const initEditor = () => {
+const initEditor = (type) => {
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true, // 允许非 ts 扩展名(.js)
@@ -554,19 +1067,9 @@ const initEditor = () => {
noSyntaxValidation: false, // 开启语法检查
});
-
editorInstance = monaco.editor.create(codeRef.value, {
- value: [`
- // 方法定义不能修改
- function handler(params) {
- // 返回值是一个可序列化成 json 的 dict 或 object
- const result ={
- type: 2,
- message: params.input
- }
- return result
- }`].join('\n'),
- language: 'javascript',
+ value: ['{}'].join('\n'),
+ language: type === 'code' ? 'javascript' : 'json',
fontSize: 14,
lineNumbers: 'on',
roundedSelection: true,
@@ -604,9 +1107,9 @@ const disposeEditor = () => {
watch(
() => props.drawer,
(newVal) => {
- if (newVal && props.data.type === 'code') {
+ if (newVal && ['code', 'http'].includes(props.data.type)) {
nextTick(() => {
- initEditor();
+ initEditor(props.data.type);
});
} else {
disposeEditor();
@@ -614,12 +1117,62 @@ 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: ""}
+ ]
+ },
+ timeout: 10000
+});
+
+const addHttpNodeFormItem = (e, type) => {
+ e.stopPropagation();
+ if (type === 'body') {
+ httpNodeData.value.body.formData.push({ name: "", type: "input", input: ""});
+ } else {
+ httpNodeData.value[type].push({ name: "", type: "input", input: ""});
+ }
+};
+
watch(
() => props.data,
() => {
if (props.data.type === "start") {
formData.inputParams = props.data.properties?.inputParams || [];
- } else if (['currentLoop', 'serviceNode'].includes(props.data.type)) {
+ } else if (['currentLoop', 'serviceNode', 'sleep', 'loop'].includes(props.data.type)) {
formData.nodeParams = props.data.properties.nodeParams;
formData.outputParams = props.data.properties.outputParams;
} else if (props.data.type === "code") {
@@ -627,12 +1180,14 @@ watch(
formData.outputParams = props.data.properties.outputParams;
const codeParams = props.data.properties.nodeParams.find(item => item.name === 'code')
if (codeParams) {
- nextTick(() => {
+ setTimeout(() => {
if (editorInstance) {
setValue(codeParams.input)
}
- })
+ }, 100)
}
+ } else if (props.data.type === 'http') {
+
}
},
{
@@ -641,7 +1196,6 @@ watch(
}
);
-
onUnmounted(() => {
disposeEditor()
})
@@ -651,6 +1205,14 @@ onUnmounted(() => {
margin-left: 12px;
}
+.http-form {
+ width: 100%;
+
+ .el-collapse {
+ width: 100%;
+ }
+}
+
.form__container {
margin: 12px 0;
diff --git a/src/views/flow/config.js b/src/views/flow/config.js
index 80d1593..3b26a6c 100644
--- a/src/views/flow/config.js
+++ b/src/views/flow/config.js
@@ -171,6 +171,7 @@ export const collapseList = [
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
+ outputParams: [],
outputType: 'json'
},
{
@@ -196,6 +197,7 @@ export const collapseList = [
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
+ outputParams: [],
outputType: 'json'
},
],
@@ -213,6 +215,7 @@ export const collapseList = [
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
+ outputParams: [],
outputType: 'json'
},
{
@@ -239,6 +242,9 @@ export const collapseList = [
type: "loop",
action: 'START_LOOP',
desc: "实现对列表对象循环执行一系列任务",
+ nodeParams: [{ name: "loopNum", type: "input", input: null, quote: "" }],
+ outputParams: [],
+ outputType: 'json'
},
{
icon: stopLoopSvg,
@@ -275,8 +281,9 @@ export const collapseList = [
desc: "用于睡眠整个流程,表示延迟多少毫秒",
action: 'sleep',
nodeParams: [
- { name: "delayMs", type: "input", componentType: 'number', input: "", disabled: true }
+ { name: "delayMs", type: "input", componentType: 'number', input: 0, disabled: true }
],
+ outputParams: [],
outputType: 'json'
},
{
@@ -303,7 +310,18 @@ export const collapseList = [
action: 'CODE',
canAddFormItem: true,
nodeParams: [
- { name: "input", type: "input", input: "", required: true }
+ { name: "input", type: "input", input: "", required: true },
+ { name: "code", type: "input", input: `
+ // 方法定义不能修改
+ function handler(params) {
+ // 返回值是一个可序列化成 json 的 dict 或 object
+ const result ={
+ type: 2,
+ message: params.input
+ }
+ return result
+ }
+ `, required: true }
],
outputType: 'json',
outputParams: [{ name: 'result', type: 'object', children: [
diff --git a/src/views/flow/nodes/function/httpNode.js b/src/views/flow/nodes/function/httpNode.js
index c642f73..870db47 100644
--- a/src/views/flow/nodes/function/httpNode.js
+++ b/src/views/flow/nodes/function/httpNode.js
@@ -145,14 +145,14 @@ class HttpNodeHtmlModel extends HtmlNodeModel {
}
// 验证表单
- async validateForm() {
- const result = this.componentInstance.validateForm()
- return result
- }
+ // async validateForm() {
+ // const result = this.componentInstance.validateForm()
+ // return result
+ // }
- setCustomProperties() {
- this.componentInstance.setNodeProperties()
- }
+ // setCustomProperties() {
+ // this.componentInstance.setNodeProperties()
+ // }
/**
* 设置节点属性
diff --git a/src/views/flow/nodes/function/httpNode.vue b/src/views/flow/nodes/function/httpNode.vue
index 9235cd6..a6af5e9 100644
--- a/src/views/flow/nodes/function/httpNode.vue
+++ b/src/views/flow/nodes/function/httpNode.vue
@@ -44,188 +44,16 @@
@zoom="zoom"
@setNodeName="setNodeName"
/>
-
-
@@ -467,151 +124,5 @@ defineExpose({
.node__container {
width: 100%;
height: auto;
-
- .input__container {
- width: 100%;
- background-color: #fafbfc;
- padding: 0 16px;
- border-radius: 8px;
- box-sizing: border-box;
-
- .title {
- height: 32px;
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- .left {
- display: flex;
- align-items: center;
-
- .tag {
- width: 3px;
- height: 16px;
- background: #1664ff;
- border-radius: 0 4px 4px 0;
- margin-right: 12px;
- }
-
- .text {
- font-size: 16px;
- font-weight: 700;
- color: #0c0d0e;
- }
- }
-
- .right {
- .addFormItem {
- border: none;
- background: transparent;
- cursor: pointer;
- }
- }
- }
-
- .form__container {
- margin: 12px 0;
-
- .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;
- }
- }
- }
- }
- }
-
- .output__container {
- width: 100%;
- background-color: #fafbfc;
- padding: 0 16px;
- border-radius: 8px;
- box-sizing: border-box;
-
- .title {
- height: 32px;
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- .left {
- display: flex;
- align-items: center;
-
- .tag {
- width: 3px;
- height: 16px;
- background: #1664ff;
- border-radius: 0 4px 4px 0;
- margin-right: 12px;
- }
-
- .text {
- font-size: 16px;
- font-weight: 700;
- color: #0c0d0e;
- }
- }
-
- .right {
- .addFormItem {
- border: none;
- background: transparent;
- cursor: pointer;
- }
- }
- }
-
- .form__container {
- margin: 12px 0;
- }
- }
-
- :deep(.el-row) {
- align-items: end;
-
- .el-input {
- --el-input-width: 148px;
- }
-
- .el-select {
- --el-select-width: 148px;
- }
-
- .el-cascader {
- --el-form-inline-content-width: 148px;
- }
- }
}
diff --git a/src/views/flow/nodes/function/loop.js b/src/views/flow/nodes/function/loop.js
index 7f443bf..e9c069c 100644
--- a/src/views/flow/nodes/function/loop.js
+++ b/src/views/flow/nodes/function/loop.js
@@ -21,21 +21,21 @@ class loopBodyModel extends GroupNodeModel {
}
// 验证表单
- 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: "无验证方法" };
- }
+ // 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: "无验证方法" };
+ // }
// 计算子节点的包围盒(包含所有子节点的最小矩形区域)
getChildrenBBox() {
@@ -59,8 +59,8 @@ class loopBodyModel extends GroupNodeModel {
minY = Math.min(minY, node.y - height / 2);
maxY = Math.max(maxY, node.y + height / 2);
});
- this.width = maxX - minX + 300; // 增加内边距
- this.height = maxY - minY + 260;
+ this.width = maxX - minX + 50; // 增加内边距
+ this.height = maxY - minY + 50;
this.x = (minX + maxX) / 2;
this.y = (minY + maxY) / 2 - 120;
const properties = lf.getProperties(this.id)
@@ -101,14 +101,14 @@ class loopBodyModel extends GroupNodeModel {
return [
{
x: x + width / 2 ,
- y: y - 25,
+ y: y + height / 2,
name: "right",
id: "".concat(this.id, "_1"),
properties: { connectionType: "source" },
},
{
x: x - width / 2,
- y: y - 25,
+ y: y + height / 2,
name: "left",
id: "".concat(this.id, "_3"),
properties: { connectionType: "target" },
diff --git a/src/views/flow/nodes/function/loop.vue b/src/views/flow/nodes/function/loop.vue
index 410677d..ef84fc3 100644
--- a/src/views/flow/nodes/function/loop.vue
+++ b/src/views/flow/nodes/function/loop.vue
@@ -9,122 +9,21 @@
:showZoom="false"
@setNodeName="setNodeName"
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- visibleChange(visible, index, property.quote)
- "
- @change="(value) => cascaderChange(value, index)"
- />
-
-
-
-
-
-
-
- 循环体
-
-
-
+ 循环体
+
+
+