yichael 4 miesięcy temu
rodzic
commit
93ca256966
39 zmienionych plików z 2191 dodań i 324 usunięć
  1. 65 8
      src/pages/blueprint/blueprint-core.js
  2. 135 46
      src/pages/blueprint/canvas/canvas.js
  3. 61 0
      src/pages/blueprint/node-renderer/adb/adb-node.css
  4. 46 0
      src/pages/blueprint/node-renderer/adb/adb-node.js
  5. 66 0
      src/pages/blueprint/node-renderer/adb/adb-node.jsx
  6. 45 0
      src/pages/blueprint/node-renderer/begin/begin-node.css
  7. 52 0
      src/pages/blueprint/node-renderer/begin/begin-node.js
  8. 32 0
      src/pages/blueprint/node-renderer/begin/begin-node.jsx
  9. 61 0
      src/pages/blueprint/node-renderer/delay/delay-node.css
  10. 24 0
      src/pages/blueprint/node-renderer/delay/delay-node.js
  11. 35 0
      src/pages/blueprint/node-renderer/delay/delay-node.jsx
  12. 85 0
      src/pages/blueprint/node-renderer/echo/echo-node.css
  13. 68 0
      src/pages/blueprint/node-renderer/echo/echo-node.js
  14. 22 0
      src/pages/blueprint/node-renderer/echo/echo-node.jsx
  15. 86 0
      src/pages/blueprint/node-renderer/func/func-node.css
  16. 60 0
      src/pages/blueprint/node-renderer/func/func-node.js
  17. 22 0
      src/pages/blueprint/node-renderer/func/func-node.jsx
  18. 53 0
      src/pages/blueprint/node-renderer/get/get-node.css
  19. 49 0
      src/pages/blueprint/node-renderer/get/get-node.js
  20. 31 0
      src/pages/blueprint/node-renderer/get/get-node.jsx
  21. 78 0
      src/pages/blueprint/node-renderer/if/if-node.css
  22. 39 0
      src/pages/blueprint/node-renderer/if/if-node.js
  23. 24 0
      src/pages/blueprint/node-renderer/if/if-node.jsx
  24. 55 0
      src/pages/blueprint/node-renderer/index.js
  25. 129 10
      src/pages/blueprint/node-renderer/node-renderer.css
  26. 30 4
      src/pages/blueprint/node-renderer/node-renderer.js
  27. 209 207
      src/pages/blueprint/node-renderer/node-renderer.jsx
  28. 65 0
      src/pages/blueprint/node-renderer/schedule/schedule-node.css
  29. 23 0
      src/pages/blueprint/node-renderer/schedule/schedule-node.js
  30. 33 0
      src/pages/blueprint/node-renderer/schedule/schedule-node.jsx
  31. 84 0
      src/pages/blueprint/node-renderer/set/set-node.css
  32. 58 0
      src/pages/blueprint/node-renderer/set/set-node.js
  33. 23 0
      src/pages/blueprint/node-renderer/set/set-node.jsx
  34. 74 0
      src/pages/blueprint/node-renderer/while/while-node.css
  35. 23 0
      src/pages/blueprint/node-renderer/while/while-node.js
  36. 23 0
      src/pages/blueprint/node-renderer/while/while-node.jsx
  37. 23 9
      src/pages/blueprint/utils/node-operations.js
  38. 91 8
      src/pages/blueprint/utils/workflow-converter.js
  39. 9 32
      static/processing/测试/bp.json

+ 65 - 8
src/pages/blueprint/blueprint-core.js

@@ -123,11 +123,32 @@ export function useBlueprint(workflowName = null) {
       console.log('创建的变量节点数量:', variableNodes.length);
       
       // 根据 inVars 中的变量引用创建从变量节点到流程节点的连接
-      const variableConnections = createVariableConnections(blueprint.nodes, variableNodes, processingData);
-      console.log('创建的变量连接数量:', variableConnections.length);
+      const variableConnResult = createVariableConnections(blueprint.nodes, variableNodes, processingData, blueprint.connections || []);
       
-      // 合并连接
-      blueprint.connections = [...(blueprint.connections || []), ...variableConnections];
+      // 处理返回值(可能是数组或对象)
+      let variableDataConnections = [];
+      let variableExecConnections = [];
+      let modifiedConnectionIds = [];
+      
+      if (Array.isArray(variableConnResult)) {
+        // 向后兼容:如果返回数组,说明是旧格式(只有数据连接)
+        variableDataConnections = variableConnResult;
+      } else {
+        // 新格式:包含数据连接、执行连接和需要修改的连接
+        variableDataConnections = variableConnResult.dataConnections || [];
+        variableExecConnections = variableConnResult.executionConnections || [];
+        modifiedConnectionIds = variableConnResult.modifiedConnections || [];
+      }
+      
+      console.log('创建的变量数据连接数量:', variableDataConnections.length);
+      console.log('创建的变量执行连接数量:', variableExecConnections.length);
+      console.log('需要修改的执行连接数量:', modifiedConnectionIds.length);
+      
+      // 删除需要修改的旧连接(这些连接将被新的连接替代)
+      let finalConnections = (blueprint.connections || []).filter(conn => !modifiedConnectionIds.includes(conn.id));
+      
+      // 合并所有新的变量连接(数据连接 + 执行连接)
+      blueprint.connections = [...finalConnections, ...variableDataConnections, ...variableExecConnections];
       
       console.log('blueprint.nodes 是否存在:', !!blueprint.nodes);
       console.log('blueprint.nodes 长度:', blueprint.nodes?.length);
@@ -228,13 +249,32 @@ export function useBlueprint(workflowName = null) {
         setNodes(finalNodes);
         
         // 优先使用保存的连线信息,否则使用从工作流解析的连线
+        let connectionsToUse = [];
         if (savedConnections && savedConnections.length > 0) {
           console.log('使用保存的连线信息,数量:', savedConnections.length);
-          setConnections(savedConnections);
+          connectionsToUse = savedConnections;
         } else {
           console.log('使用解析的连线信息,数量:', blueprint.connections?.length || 0);
-          setConnections(blueprint.connections || []);
+          connectionsToUse = blueprint.connections || [];
         }
+        
+        // 迁移旧的 SET 节点连接端口 ID(从 input_0 到 input_value)
+        connectionsToUse = connectionsToUse.map(conn => {
+          const targetNode = finalNodes.find(n => n.id === conn.target);
+          if (targetNode && targetNode.type === 'variable' && targetNode.varMode === 'set') {
+            // 如果目标是 SET 节点,并且使用的是旧端口 ID
+            if (conn.targetPort === 'input_0') {
+              console.log('迁移 SET 节点连接端口 ID:', conn.id, 'input_0 -> input_value');
+              return {
+                ...conn,
+                targetPort: 'input_value'
+              };
+            }
+          }
+          return conn;
+        });
+        
+        setConnections(connectionsToUse);
         setIsDirty(false); // 加载完成后,标记为未修改
       } else {
         console.log('blueprint.nodes 为空或长度为 0,设置空数组');
@@ -802,7 +842,7 @@ export function useBlueprint(workflowName = null) {
       setNodes(finalNodes);
       setIsDirty(true);
       
-      // 保存位置信息
+      // 保存位置信息和连接信息
       if (workflowName && window.electronAPI && window.electronAPI.saveBlueprintJson) {
         try {
           const nodePositions = {};
@@ -812,7 +852,24 @@ export function useBlueprint(workflowName = null) {
               y: node.y
             };
           });
-          await window.electronAPI.saveBlueprintJson(workflowName, { nodePositions });
+          
+          // 保存连线信息
+          const connectionData = connections.map(conn => ({
+            id: conn.id,
+            source: conn.source,
+            target: conn.target,
+            sourcePort: conn.sourcePort,
+            targetPort: conn.targetPort,
+            sourceX: conn.sourceX,
+            sourceY: conn.sourceY,
+            targetX: conn.targetX,
+            targetY: conn.targetY
+          }));
+          
+          await window.electronAPI.saveBlueprintJson(workflowName, { 
+            nodePositions,
+            connections: connectionData
+          });
           setLayoutProgress(100);
           setLayoutMessage('完成');
         } catch (error) {

+ 135 - 46
src/pages/blueprint/canvas/canvas.js

@@ -23,6 +23,10 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
   const [draggedNodeId, setDraggedNodeId] = useState(null);
   const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
   
+  // 使用 ref 来存储拖拽状态,避免闭包陷阱
+  const draggedNodeIdRef = useRef(null);
+  const dragOffsetRef = useRef({ x: 0, y: 0 });
+  
   // 初始化画布控制器
   useEffect(() => {
     if (canvasRef.current) {
@@ -57,6 +61,7 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
   
   /**
    * 从 DOM 获取端口的精确坐标并缓存
+   * 坐标是相对于画布原点的绝对坐标(不考虑transform,因为SVG会自动应用transform)
    * @param {string} nodeId - 节点ID
    * @param {Object} node - 节点对象
    * @returns {Object|null} 该节点所有端口的坐标 { portId: { x, y } }
@@ -70,6 +75,8 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
     const portPositions = {};
     const allPorts = [...(node.inputs || []), ...(node.outputs || [])];
     
+    const isSetNode = node.type === 'variable' && node.varMode === 'set';
+    
     allPorts.forEach(port => {
       // 查找端口元素
       const isInput = (node.inputs || []).some(p => p.id === port.id);
@@ -101,13 +108,29 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
         
         if (targetElement) {
           const targetRect = targetElement.getBoundingClientRect();
-          const canvasRect = canvasRef.current.getBoundingClientRect();
+          const nodeRect = nodeElement.getBoundingClientRect();
+          
+          // 计算端口相对于节点左上角的偏移(像素值)
+          const offsetX = (targetRect.left + targetRect.width / 2 - nodeRect.left) / transform.scale;
+          const offsetY = (targetRect.top + targetRect.height / 2 - nodeRect.top) / transform.scale;
           
-          // 计算相对于画布的坐标(考虑当前 transform)
-          const x = (targetRect.left + targetRect.width / 2 - canvasRect.left - transform.translateX) / transform.scale;
-          const y = (targetRect.top + targetRect.height / 2 - canvasRect.top - transform.translateY) / transform.scale;
+          // 端口的画布绝对坐标 = 节点坐标 + 端口偏移
+          portPositions[port.id] = { 
+            x: node.x + offsetX, 
+            y: node.y + offsetY 
+          };
+        } else if (isSetNode) {
+          // SET节点:如果找不到点或箭头元素,使用端口元素本身
+          const portElRect = portEl.getBoundingClientRect();
+          const nodeRect = nodeElement.getBoundingClientRect();
           
-          portPositions[port.id] = { x, y };
+          const offsetX = (portElRect.left + portElRect.width / 2 - nodeRect.left) / transform.scale;
+          const offsetY = (portElRect.top + portElRect.height / 2 - nodeRect.top) / transform.scale;
+          
+          portPositions[port.id] = { 
+            x: node.x + offsetX, 
+            y: node.y + offsetY 
+          };
         }
       }
     });
@@ -159,11 +182,40 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
   const getPortPositionFallback = (node, port) => {
     const isInput = (node.inputs || []).some(p => p.id === port.id);
     const isVariable = node.type === 'variable';
+    const isSetNode = isVariable && node.varMode === 'set';
     
     // 获取节点尺寸
-    const nodeDims = nodeWidthCacheRef.current.get(node.id) || { width: 180, height: isVariable ? 40 : 140 };
+    const nodeDims = nodeWidthCacheRef.current.get(node.id) || { 
+      width: 180, 
+      height: isSetNode ? 100 : (isVariable ? 40 : 140)
+    };
     
-    if (isVariable) {
+    // SET 变量节点的特殊处理
+    if (isSetNode) {
+      const headerHeight = 32;
+      const executionHeight = 32;
+      const dataHeight = 36;
+      const isExecutionPort = port.type === 'execution';
+      
+      if (isExecutionPort) {
+        // 执行端口在执行箭头区域的中间
+        const portCenterY = node.y + headerHeight + executionHeight / 2;
+        if (isInput) {
+          return { x: node.x + 8, y: portCenterY };
+        } else {
+          return { x: node.x + nodeDims.width - 8, y: portCenterY };
+        }
+      } else {
+        // 数据端口在数据区域的中间偏左
+        const portCenterY = node.y + headerHeight + executionHeight + dataHeight / 2;
+        if (isInput) {
+          return { x: node.x + 8, y: portCenterY };
+        } else {
+          return { x: node.x + nodeDims.width - 8, y: portCenterY };
+        }
+      }
+    } else if (isVariable) {
+      // GET 变量节点的处理(原有逻辑)
       const portY = node.y + nodeDims.height / 2;
       if (isInput) {
         return { x: node.x, y: portY };
@@ -214,34 +266,50 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
     if (e.button !== 0) return;
     
     e.stopPropagation();
-    const rect = canvasRef.current.getBoundingClientRect();
     const portElement = e.target.closest('.Blueprint-node-port');
     if (!portElement) return;
     
-    // 精确计算端口中心点(考虑画布变换)
-    const portRect = portElement.getBoundingClientRect();
-    // 端口中心在屏幕坐标
-    const screenPortX = portRect.left + portRect.width / 2;
-    const screenPortY = portRect.top + portRect.height / 2;
-    // 转换为画布坐标(考虑缩放和平移)
-    const portX = (screenPortX - rect.left - transform.translateX) / transform.scale;
-    const portY = (screenPortY - rect.top - transform.translateY) / transform.scale;
-    
-    // 从节点数据中获取端口类型信息
+    // 获取节点
     const node = nodes?.find(n => n.id === nodeId);
-    let portType = 'execution';
-    let paramType = 'string';
+    if (!node) return;
     
-    if (node) {
-      const port = [...(node.outputs || []), ...(node.inputs || [])].find(p => p.id === portId);
-      if (port) {
-        portType = port.type || 'execution';
-        paramType = port.paramType || 'string';
+    // 获取端口对象
+    const port = [...(node.outputs || []), ...(node.inputs || [])].find(p => p.id === portId);
+    if (!port) return;
+    
+    // 使用缓存或从DOM获取端口坐标(画布坐标系)
+    let portPos = getPortPosition(node, port);
+    
+    // 如果缓存获取失败,从DOM实时计算
+    if (!portPos) {
+      const nodeElement = canvasRef.current.querySelector(`[data-node-id="${nodeId}"]`);
+      if (nodeElement) {
+        const nodeRect = nodeElement.getBoundingClientRect();
+        const portRect = portElement.getBoundingClientRect();
+        
+        // 计算端口相对于节点的偏移
+        const offsetX = (portRect.left + portRect.width / 2 - nodeRect.left) / transform.scale;
+        const offsetY = (portRect.top + portRect.height / 2 - nodeRect.top) / transform.scale;
+        
+        // 端口的画布绝对坐标
+        portPos = {
+          x: node.x + offsetX,
+          y: node.y + offsetY
+        };
       }
     }
     
-    setConnectingStart({ nodeId, portId, x: portX, y: portY, portType, paramType });
-    setConnectingEnd({ x: portX, y: portY });
+    if (!portPos) return;
+    
+    setConnectingStart({ 
+      nodeId, 
+      portId, 
+      x: portPos.x, 
+      y: portPos.y, 
+      portType: port.type || 'execution',
+      paramType: port.paramType || 'string'
+    });
+    setConnectingEnd({ x: portPos.x, y: portPos.y });
     onConnectionStart?.(nodeId, portId);
   };
   
@@ -310,11 +378,16 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
     const canvasX = (e.clientX - rect.left - transform.translateX) / transform.scale;
     const canvasY = (e.clientY - rect.top - transform.translateY) / transform.scale;
     
-    setDraggedNodeId(nodeId);
-    setDragOffset({
+    const offset = {
       x: canvasX - node.x,
       y: canvasY - node.y
-    });
+    };
+    
+    // 同时更新 state 和 ref,确保所有闭包都能访问到最新值
+    setDraggedNodeId(nodeId);
+    setDragOffset(offset);
+    draggedNodeIdRef.current = nodeId;
+    dragOffsetRef.current = offset;
     
     // 将 cursor 应用到 document.body,确保在边界外也保持 grabbing
     document.body.style.cursor = 'grabbing';
@@ -336,21 +409,28 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
    * 处理鼠标移动(连线预览、节点拖拽)
    */
   const handleMouseMove = (e, onNodeMove) => {
+    const rect = canvasRef.current.getBoundingClientRect();
+    
     // 如果正在连线,更新连线预览的终点位置
     if (connectingStart) {
-      const rect = canvasRef.current.getBoundingClientRect();
-      // 转换为画布坐标(考虑缩放和平移)
-      const x = (e.clientX - rect.left - transform.translateX) / transform.scale;
-      const y = (e.clientY - rect.top - transform.translateY) / transform.scale;
+      // 计算鼠标在画布坐标系中的位置
+      // 屏幕坐标 -> 画布容器相对坐标 -> 画布坐标(去除transform影响)
+      const containerX = e.clientX - rect.left;
+      const containerY = e.clientY - rect.top;
+      const x = (containerX - transform.translateX) / transform.scale;
+      const y = (containerY - transform.translateY) / transform.scale;
       setConnectingEnd({ x, y });
     }
     
-    if (draggedNodeId) {
-      const rect = canvasRef.current.getBoundingClientRect();
-      const canvasX = (e.clientX - rect.left - transform.translateX) / transform.scale - dragOffset.x;
-      const canvasY = (e.clientY - rect.top - transform.translateY) / transform.scale - dragOffset.y;
+    // 使用 ref 来检查拖拽状态,避免闭包问题
+    if (draggedNodeIdRef.current) {
+      // 计算节点在画布坐标系中的新位置
+      const containerX = e.clientX - rect.left;
+      const containerY = e.clientY - rect.top;
+      const canvasX = (containerX - transform.translateX) / transform.scale - dragOffsetRef.current.x;
+      const canvasY = (containerY - transform.translateY) / transform.scale - dragOffsetRef.current.y;
       
-      onNodeMove?.(draggedNodeId, canvasX, canvasY);
+      onNodeMove?.(draggedNodeIdRef.current, canvasX, canvasY);
     }
   };
   
@@ -358,12 +438,17 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
    * 处理鼠标释放
    */
   const handleMouseUp = (e) => {
-    if (draggedNodeId) {
+    // 使用 ref 来检查拖拽状态,避免闭包问题
+    if (draggedNodeIdRef.current) {
       // 恢复 document.body 的 cursor 样式
       document.body.style.cursor = '';
       document.body.style.userSelect = '';
+      
+      // 同时清除 state 和 ref
       setDraggedNodeId(null);
       setDragOffset({ x: 0, y: 0 });
+      draggedNodeIdRef.current = null;
+      dragOffsetRef.current = { x: 0, y: 0 };
     }
     
     // 如果正在连线但没有连接到端口,取消连线
@@ -599,7 +684,7 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
   };
   
   /**
-   * 从 DOM 获取端口的精确坐标
+   * 从 DOM 获取端口的精确坐标(画布坐标系)
    * @param {string} nodeId - 节点ID
    * @param {string} portId - 端口ID
    * @param {boolean} isInput - 是否是输入端口
@@ -646,13 +731,17 @@ export function useCanvasLogic(connections, externalControllerRef, nodes = []) {
       
       if (targetElement) {
         const targetRect = targetElement.getBoundingClientRect();
-        const canvasRect = canvasRef.current.getBoundingClientRect();
+        const nodeRect = nodeElement.getBoundingClientRect();
         
-        // 计算相对于画布的坐标(考虑当前 transform)
-        const x = (targetRect.left + targetRect.width / 2 - canvasRect.left - transform.translateX) / transform.scale;
-        const y = (targetRect.top + targetRect.height / 2 - canvasRect.top - transform.translateY) / transform.scale;
+        // 计算端口相对于节点的偏移
+        const offsetX = (targetRect.left + targetRect.width / 2 - nodeRect.left) / transform.scale;
+        const offsetY = (targetRect.top + targetRect.height / 2 - nodeRect.top) / transform.scale;
         
-        return { x, y };
+        // 端口的画布绝对坐标 = 节点坐标 + 端口偏移
+        return { 
+          x: node.x + offsetX, 
+          y: node.y + offsetY 
+        };
       }
     }
     

+ 61 - 0
src/pages/blueprint/node-renderer/adb/adb-node.css

@@ -0,0 +1,61 @@
+/* ADB 操作节点样式 */
+.Blueprint-adb-node {
+  position: absolute;
+  min-width: 180px;
+  width: fit-content;
+  background: #2d2d30;
+  border: 2px solid #3ddc84;
+  border-radius: 4px;
+  box-shadow: 0 0 8px rgba(61, 220, 132, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  user-select: none;
+  cursor: move;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-adb-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(61, 220, 132, 0.4);
+}
+
+.Blueprint-adb-node-header {
+  background: linear-gradient(135deg, #3ddc84 0%, #2bb673 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 0.5px;
+}
+
+.Blueprint-adb-node-execution {
+  height: 40px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}
+
+.Blueprint-adb-node-execution .Blueprint-node-port {
+  position: relative;
+}
+
+.Blueprint-adb-node-params {
+  padding: 12px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  position: relative;
+  min-height: 20px;
+}
+
+.Blueprint-adb-node-params .Blueprint-node-port {
+  position: relative;
+}

+ 46 - 0
src/pages/blueprint/node-renderer/adb/adb-node.js

@@ -0,0 +1,46 @@
+/**
+ * ADB 操作节点逻辑
+ */
+
+/**
+ * 创建 ADB 节点数据
+ */
+export function createAdbNodeData(adbType, config = {}) {
+  return {
+    id: config.id || `node_adb_${adbType}_${Date.now()}`,
+    type: adbType,
+    label: config.label || adbType,
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      ...(config.inputs || [])
+    ],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' },
+      ...(config.outputs || [])
+    ],
+    data: config.data || {}
+  };
+}
+
+/**
+ * 验证 ADB 节点
+ */
+export function validateAdbNode(node) {
+  const errors = [];
+  
+  const execInput = node.inputs?.find(p => p.type === 'execution');
+  const execOutput = node.outputs?.find(p => p.type === 'execution');
+  
+  if (!execInput) {
+    errors.push('ADB节点必须有执行输入端口');
+  }
+  
+  if (!execOutput) {
+    errors.push('ADB节点必须有执行输出端口');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}

+ 66 - 0
src/pages/blueprint/node-renderer/adb/adb-node.jsx

@@ -0,0 +1,66 @@
+/**
+ * ADB 操作节点组件
+ */
+
+import { BaseNode, renderStandardPort } from '../base-node.jsx';
+import './adb-node.css';
+
+export function AdbNode(props) {
+  const { node, connections } = props;
+  
+  const renderHeader = () => (
+    <div className="Blueprint-adb-node-header">
+      {node.label || 'ADB'}
+    </div>
+  );
+  
+  const renderExecutionPorts = (handlers) => {
+    const executionInputs = node.inputs?.filter(input => input.type === 'execution') || [];
+    const executionOutputs = node.outputs?.filter(output => output.type === 'execution') || [];
+    
+    return (
+      <div className="Blueprint-adb-node-execution">
+        {executionInputs.map((input) => 
+          renderStandardPort(input, node.id, true, connections, handlers)
+        )}
+        {executionOutputs.map((output) => 
+          renderStandardPort(output, node.id, false, connections, handlers)
+        )}
+      </div>
+    );
+  };
+  
+  const renderParamsPorts = (handlers) => {
+    const dataInputs = node.inputs?.filter(input => input.type !== 'execution') || [];
+    const dataOutputs = node.outputs?.filter(output => output.type !== 'execution') || [];
+    
+    return (
+      <div className="Blueprint-adb-node-params">
+        {dataInputs.map((input) => {
+          const portElement = renderStandardPort(input, node.id, true, connections, handlers);
+          return <div key={input.id}>{portElement}</div>;
+        })}
+        {dataOutputs.map((output) => {
+          const portElement = renderStandardPort(output, node.id, false, connections, handlers);
+          return <div key={output.id}>{portElement}</div>;
+        })}
+      </div>
+    );
+  };
+  
+  const renderPorts = (node, connections, handlers) => (
+    <>
+      {renderExecutionPorts(handlers)}
+      {renderParamsPorts(handlers)}
+    </>
+  );
+  
+  return (
+    <BaseNode
+      {...props}
+      className="Blueprint-adb-node"
+      renderHeader={renderHeader}
+      renderPorts={renderPorts}
+    />
+  );
+}

+ 45 - 0
src/pages/blueprint/node-renderer/begin/begin-node.css

@@ -0,0 +1,45 @@
+/* Begin 节点样式 */
+.Blueprint-begin-node {
+  /* 公共逻辑已在 node-renderer.css 中定义 */
+  /* position, width, overflow, pointer-events, user-select, cursor, z-index */
+  
+  /* 节点特有样式 */
+  border-color: #4caf50 !important;
+  box-shadow: 0 0 8px rgba(76, 175, 80, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3) !important;
+}
+
+.Blueprint-begin-node.selected {
+  border-color: #ffa500 !important;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(76, 175, 80, 0.4) !important;
+}
+
+.Blueprint-begin-node-header {
+  /* pointer-events 和 user-select 已在公共样式中定义 */
+  background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 14px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 1px;
+}
+
+.Blueprint-begin-node-body {
+  /* pointer-events 已在公共样式中定义 */
+  padding: 12px 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+}
+
+.Blueprint-begin-node-icon {
+  font-size: 20px;
+  color: #4caf50;
+}
+
+.Blueprint-begin-node-text {
+  font-size: 12px;
+  color: #999;
+}

+ 52 - 0
src/pages/blueprint/node-renderer/begin/begin-node.js

@@ -0,0 +1,52 @@
+/**
+ * Begin 节点逻辑
+ * 工作流起始节点的特定逻辑
+ */
+
+/**
+ * 创建 Begin 节点数据
+ */
+export function createBeginNodeData() {
+  return {
+    id: 'node_begin',
+    type: 'begin',
+    label: 'Begin',
+    inputs: [],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' }
+    ],
+    data: {}
+  };
+}
+
+/**
+ * 验证 Begin 节点
+ * @param {Object} node - 节点数据
+ * @returns {Object} 验证结果 { valid: boolean, errors: string[] }
+ */
+export function validateBeginNode(node) {
+  const errors = [];
+  
+  if (!node.outputs || node.outputs.length === 0) {
+    errors.push('Begin节点必须有输出端口');
+  }
+  
+  if (node.inputs && node.inputs.length > 0) {
+    errors.push('Begin节点不应有输入端口');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}
+
+/**
+ * Begin 节点转换为工作流数据
+ * @param {Object} node - 节点数据
+ * @returns {Object} 工作流数据
+ */
+export function beginNodeToWorkflow(node) {
+  // Begin节点在工作流中通常作为起点,不需要特殊数据
+  return null;
+}

+ 32 - 0
src/pages/blueprint/node-renderer/begin/begin-node.jsx

@@ -0,0 +1,32 @@
+/**
+ * Begin 节点组件
+ * 工作流的起始节点
+ * 
+ * 特点:
+ * - 只有1个输出执行端口
+ * - 没有输入端口
+ * - 绿色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './begin-node.css';
+
+export function BeginNode(props) {
+  // 自定义主体内容
+  const renderCustomBody = (node) => (
+    <div className="Blueprint-begin-node-body">
+      <div className="Blueprint-begin-node-icon">▶</div>
+      <span className="Blueprint-begin-node-text">工作流起点</span>
+    </div>
+  );
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-begin-node"
+      headerText="Begin"
+      renderCustomBody={renderCustomBody}
+      layoutType="standard"
+    />
+  );
+}

+ 61 - 0
src/pages/blueprint/node-renderer/delay/delay-node.css

@@ -0,0 +1,61 @@
+/* Delay 延迟节点样式 */
+.Blueprint-delay-node {
+  position: absolute;
+  min-width: 160px;
+  width: fit-content;
+  background: #2d2d30;
+  border: 2px solid #00bcd4;
+  border-radius: 4px;
+  box-shadow: 0 0 8px rgba(0, 188, 212, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  user-select: none;
+  cursor: move;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-delay-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(0, 188, 212, 0.4);
+}
+
+.Blueprint-delay-node-header {
+  background: linear-gradient(135deg, #00bcd4 0%, #0097a7 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 1px;
+}
+
+.Blueprint-delay-node-body {
+  padding: 8px 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+}
+
+.Blueprint-delay-node-icon {
+  font-size: 16px;
+}
+
+.Blueprint-delay-node-text {
+  font-size: 12px;
+  color: #00bcd4;
+  font-weight: 500;
+}
+
+.Blueprint-delay-node-execution {
+  height: 40px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}

+ 24 - 0
src/pages/blueprint/node-renderer/delay/delay-node.js

@@ -0,0 +1,24 @@
+/**
+ * Delay 延迟节点逻辑
+ */
+
+/**
+ * 创建 Delay 节点数据
+ */
+export function createDelayNodeData(config = {}) {
+  return {
+    id: config.id || `node_delay_${Date.now()}`,
+    type: 'delay',
+    label: 'DELAY',
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' }
+    ],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' }
+    ],
+    data: {
+      duration: config.duration || 1000,
+      ...config.data
+    }
+  };
+}

+ 35 - 0
src/pages/blueprint/node-renderer/delay/delay-node.jsx

@@ -0,0 +1,35 @@
+/**
+ * Delay 延迟节点组件
+ * 
+ * 特点:
+ * - 1个输入执行端口 + 1个输出执行端口
+ * - 显示延迟时间
+ * - 青色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './delay-node.css';
+
+export function DelayNode(props) {
+  const { node } = props;
+  
+  // 自定义主体内容
+  const renderCustomBody = (node) => (
+    <div className="Blueprint-delay-node-body">
+      <span className="Blueprint-delay-node-icon">⏱️</span>
+      <span className="Blueprint-delay-node-text">
+        {node.data?.duration || '0'}ms
+      </span>
+    </div>
+  );
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-delay-node"
+      headerText="DELAY"
+      renderCustomBody={renderCustomBody}
+      layoutType="standard"
+    />
+  );
+}

+ 85 - 0
src/pages/blueprint/node-renderer/echo/echo-node.css

@@ -0,0 +1,85 @@
+/* Echo 输出节点样式 */
+.Blueprint-echo-node {
+  /* 公共逻辑已在 node-renderer.css 中定义 */
+  /* position, width, overflow, pointer-events, user-select, cursor, z-index */
+  
+  /* 节点特有样式 */
+  min-width: 180px;
+  background: #2d2d30;
+  border: 2px solid #4caf50;
+  border-radius: 4px;
+  box-shadow: 0 0 8px rgba(76, 175, 80, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-echo-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(76, 175, 80, 0.4);
+}
+
+.Blueprint-echo-node-header {
+  /* pointer-events 和 user-select 已在公共样式中定义 */
+  background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 0.5px;
+}
+
+.Blueprint-echo-node-execution {
+  /* pointer-events 已在公共样式中定义 */
+  height: 40px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}
+
+.Blueprint-echo-node-execution .Blueprint-node-port {
+  /* pointer-events 已在公共样式中定义 */
+  position: relative;
+}
+
+.Blueprint-echo-node-execution .Blueprint-node-port.input.port-execution {
+  left: auto;
+  top: auto;
+}
+
+.Blueprint-echo-node-execution .Blueprint-node-port.output.port-execution {
+  right: auto;
+  top: auto;
+  margin-left: auto;
+}
+
+.Blueprint-echo-node-params {
+  /* pointer-events 已在公共样式中定义 */
+  padding: 12px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  position: relative;
+  min-height: 20px;
+}
+
+.Blueprint-echo-node-params .Blueprint-node-port {
+  /* pointer-events 已在公共样式中定义 */
+  position: relative;
+}
+
+.Blueprint-echo-node-params .Blueprint-node-port.input:not(.port-execution) {
+  left: auto;
+}
+
+.Blueprint-echo-node-params .Blueprint-node-port.output:not(.port-execution) {
+  right: auto;
+  align-self: flex-end;
+}

+ 68 - 0
src/pages/blueprint/node-renderer/echo/echo-node.js

@@ -0,0 +1,68 @@
+/**
+ * Echo 节点逻辑
+ * 用于打印/输出信息
+ */
+
+/**
+ * 创建 Echo 节点数据
+ * @param {Object} config - 节点配置
+ */
+export function createEchoNodeData(config = {}) {
+  return {
+    id: config.id || `node_echo_${Date.now()}`,
+    type: 'echo',
+    label: config.label || 'Echo',
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      { id: 'input_message', label: '消息', type: 'data', paramType: 'string', paramName: 'message' }
+    ],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' }
+    ],
+    data: {
+      message: config.message || '',
+      ...config.data
+    }
+  };
+}
+
+/**
+ * 验证 Echo 节点
+ * @param {Object} node - 节点数据
+ * @returns {Object} 验证结果 { valid: boolean, errors: string[] }
+ */
+export function validateEchoNode(node) {
+  const errors = [];
+  
+  if (!node.inputs || node.inputs.length === 0) {
+    errors.push('Echo节点必须有输入端口');
+  }
+  
+  if (!node.outputs || node.outputs.length === 0) {
+    errors.push('Echo节点必须有输出端口');
+  }
+  
+  const messageInput = node.inputs?.find(p => p.paramName === 'message');
+  if (!messageInput) {
+    errors.push('Echo节点必须有消息输入参数');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}
+
+/**
+ * Echo 节点转换为工作流数据
+ * @param {Object} node - 节点数据
+ * @returns {Object} 工作流数据
+ */
+export function echoNodeToWorkflow(node) {
+  return {
+    type: 'echo',
+    params: {
+      message: node.data?.message || ''
+    }
+  };
+}

+ 22 - 0
src/pages/blueprint/node-renderer/echo/echo-node.jsx

@@ -0,0 +1,22 @@
+/**
+ * Echo 输出节点组件
+ * 用于打印/输出信息到控制台或日志
+ * 
+ * 特点:
+ * - 1个输入执行端口 + 1个输出执行端口
+ * - 1个输入数据端口(message)
+ * - 绿色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './echo-node.css';
+
+export function EchoNode(props) {
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-echo-node"
+      layoutType="standard"
+    />
+  );
+}

+ 86 - 0
src/pages/blueprint/node-renderer/func/func-node.css

@@ -0,0 +1,86 @@
+/* Func 功能节点样式 */
+.Blueprint-func-node {
+  /* 公共逻辑已在 node-renderer.css 中定义 */
+  /* position, width, overflow, pointer-events, user-select, cursor, z-index */
+  
+  /* 节点特有样式 */
+  min-width: 180px;
+  background: #2d2d30;
+  border: 2px solid #007acc;
+  border-radius: 4px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-func-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3);
+}
+
+.Blueprint-func-node-header {
+  /* pointer-events 和 user-select 已在公共样式中定义 */
+  background: #007acc;
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 500;
+  border-radius: 2px 2px 0 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.Blueprint-func-node-execution {
+  /* pointer-events 已在公共样式中定义 */
+  height: 40px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}
+
+.Blueprint-func-node-execution .Blueprint-node-port {
+  /* pointer-events 已在公共样式中定义 */
+  position: relative;
+}
+
+.Blueprint-func-node-execution .Blueprint-node-port.input.port-execution {
+  left: auto;
+  top: auto;
+}
+
+.Blueprint-func-node-execution .Blueprint-node-port.output.port-execution {
+  right: auto;
+  top: auto;
+  margin-left: auto;
+}
+
+.Blueprint-func-node-params {
+  /* pointer-events 已在公共样式中定义 */
+  padding: 12px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  position: relative;
+  min-height: 20px;
+}
+
+.Blueprint-func-node-params .Blueprint-node-port {
+  /* pointer-events 已在公共样式中定义 */
+  position: relative;
+}
+
+.Blueprint-func-node-params .Blueprint-node-port.input:not(.port-execution) {
+  left: auto;
+}
+
+.Blueprint-func-node-params .Blueprint-node-port.output:not(.port-execution) {
+  right: auto;
+  align-self: flex-end;
+}

+ 60 - 0
src/pages/blueprint/node-renderer/func/func-node.js

@@ -0,0 +1,60 @@
+/**
+ * Func 功能节点逻辑
+ */
+
+/**
+ * 创建功能节点数据
+ * @param {string} funcType - 功能类型
+ * @param {Object} config - 节点配置
+ */
+export function createFuncNodeData(funcType, config = {}) {
+  const { id, label, inputs = [], outputs = [], data = {} } = config;
+  
+  return {
+    id: id || `node_${funcType}_${Date.now()}`,
+    type: funcType,
+    label: label || funcType,
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      ...inputs
+    ],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' },
+      ...outputs
+    ],
+    data
+  };
+}
+
+/**
+ * 验证功能节点
+ */
+export function validateFuncNode(node) {
+  const errors = [];
+  
+  const execInput = node.inputs?.find(p => p.type === 'execution');
+  const execOutput = node.outputs?.find(p => p.type === 'execution');
+  
+  if (!execInput) {
+    errors.push('功能节点必须有执行输入端口');
+  }
+  
+  if (!execOutput) {
+    errors.push('功能节点必须有执行输出端口');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}
+
+/**
+ * 功能节点转换为工作流数据
+ */
+export function funcNodeToWorkflow(node) {
+  return {
+    type: node.type,
+    data: node.data || {}
+  };
+}

+ 22 - 0
src/pages/blueprint/node-renderer/func/func-node.jsx

@@ -0,0 +1,22 @@
+/**
+ * Func 功能节点组件
+ * 用于执行各种功能操作
+ * 
+ * 特点:
+ * - 通常有1个输入执行端口 + 1个输出执行端口
+ * - 可以有多个输入/输出数据端口(根据功能而定)
+ * - 蓝色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './func-node.css';
+
+export function FuncNode(props) {
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-func-node"
+      layoutType="standard"
+    />
+  );
+}

+ 53 - 0
src/pages/blueprint/node-renderer/get/get-node.css

@@ -0,0 +1,53 @@
+/* Get 变量节点样式 */
+.Blueprint-get-node {
+  /* 公共逻辑已在 node-renderer.css 中定义 */
+  /* position, width, overflow, pointer-events, user-select, cursor, z-index */
+  
+  /* 节点特有样式 */
+  min-width: 120px;
+  height: 40px;
+  background: #2d2d30;
+  border: 2px solid #2196f3;
+  border-radius: 20px;
+  box-shadow: 0 0 8px rgba(33, 150, 243, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  padding: 0;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 隐藏暗色区域(背景、边框、阴影) */
+.Blueprint-get-node.hide-dark-area {
+  background: transparent;
+  border-color: transparent;
+  box-shadow: none;
+}
+
+.Blueprint-get-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(33, 150, 243, 0.4);
+}
+
+.Blueprint-get-node-label {
+  /* pointer-events 和 user-select 已在公共样式中定义 */
+  color: #fff;
+  font-size: 13px;
+  font-weight: 500;
+  text-align: center;
+  padding: 0 16px;
+  white-space: nowrap;
+  visibility: hidden; /* 占位但不可见 */
+}
+
+.Blueprint-get-node .Blueprint-variable-node-body {
+  /* pointer-events 已在公共样式中定义 */
+  width: 100%;
+  height: 100%;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 端口的 pointer-events 已在公共样式中定义 */

+ 49 - 0
src/pages/blueprint/node-renderer/get/get-node.js

@@ -0,0 +1,49 @@
+/**
+ * Get 变量节点逻辑
+ */
+
+/**
+ * 配置:是否显示暗色区域(节点主体背景)
+ * @type {boolean}
+ */
+export const SHOW_DARK_AREA = false;
+
+/**
+ * 创建 Get 节点数据
+ * @param {string} varName - 变量名
+ * @param {string} varType - 变量类型
+ */
+export function createGetNodeData(varName, varType = 'string') {
+  return {
+    id: `var_get_${varName}`,
+    type: 'variable',
+    varMode: 'get',
+    varName,
+    label: varName,
+    inputs: [],
+    outputs: [
+      { id: 'output_0', label: varName, type: 'data', paramType: varType }
+    ],
+    data: {}
+  };
+}
+
+/**
+ * 验证 Get 节点
+ */
+export function validateGetNode(node) {
+  const errors = [];
+  
+  if (!node.varName) {
+    errors.push('Get节点必须指定变量名');
+  }
+  
+  if (!node.outputs || node.outputs.length === 0) {
+    errors.push('Get节点必须有输出端口');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}

+ 31 - 0
src/pages/blueprint/node-renderer/get/get-node.jsx

@@ -0,0 +1,31 @@
+/**
+ * Get 变量节点组件
+ * 用于获取变量值
+ * 
+ * 特点:
+ * - 只有1个输出数据端口
+ * - 没有输入端口
+ * - 没有执行端口
+ * - 圆角矩形,蓝色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import { SHOW_DARK_AREA } from './get-node.js';
+import './get-node.css';
+
+export function GetNode(props) {
+  const { node } = props;
+  
+  // 根据配置决定暗色区域的样式
+  const darkAreaClass = SHOW_DARK_AREA ? '' : 'hide-dark-area';
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className={`Blueprint-get-node ${darkAreaClass}`}
+      headerText={node.label || node.varName || 'Variable'}
+      showHeader={false}
+      layoutType="variable"
+    />
+  );
+}

+ 78 - 0
src/pages/blueprint/node-renderer/if/if-node.css

@@ -0,0 +1,78 @@
+/* If 条件节点样式 */
+.Blueprint-if-node {
+  position: absolute;
+  min-width: 200px;
+  width: fit-content;
+  background: #2d2d30;
+  border: 2px solid #ff9800;
+  border-radius: 4px;
+  box-shadow: 0 0 8px rgba(255, 152, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  user-select: none;
+  cursor: move;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-if-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(255, 152, 0, 0.4);
+}
+
+.Blueprint-if-node-header {
+  background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 1px;
+}
+
+.Blueprint-if-node-execution {
+  height: auto;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: flex-start;
+  position: relative;
+  overflow: visible;
+  padding: 8px;
+}
+
+.Blueprint-if-node-branches {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  margin-left: auto;
+}
+
+.Blueprint-if-node-branch {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 8px;
+}
+
+.Blueprint-if-node-branch-label {
+  font-size: 11px;
+  color: #999;
+  min-width: 40px;
+  text-align: right;
+}
+
+.Blueprint-if-node-params {
+  padding: 8px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  position: relative;
+  min-height: 20px;
+}
+
+.Blueprint-if-node-params .Blueprint-node-port {
+  position: relative;
+}

+ 39 - 0
src/pages/blueprint/node-renderer/if/if-node.js

@@ -0,0 +1,39 @@
+/**
+ * If 条件节点逻辑
+ */
+
+/**
+ * 创建 If 节点数据
+ */
+export function createIfNodeData(config = {}) {
+  return {
+    id: config.id || `node_if_${Date.now()}`,
+    type: 'if',
+    label: 'IF',
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      { id: 'input_condition', label: '条件', type: 'data', paramType: 'string' }
+    ],
+    outputs: [
+      { id: 'output_true', label: 'True', type: 'execution' },
+      { id: 'output_false', label: 'False', type: 'execution' }
+    ],
+    data: config.data || {}
+  };
+}
+
+/**
+ * 验证 If 节点
+ */
+export function validateIfNode(node) {
+  const errors = [];
+  
+  if (node.outputs?.length !== 2) {
+    errors.push('If节点必须有两个输出端口(True/False)');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}

+ 24 - 0
src/pages/blueprint/node-renderer/if/if-node.jsx

@@ -0,0 +1,24 @@
+/**
+ * If 条件节点组件
+ * 用于条件判断分支
+ * 
+ * 特点:
+ * - 1个输入执行端口
+ * - 2个输出执行端口(True、False)
+ * - 1个条件数据输入端口
+ * - 黄色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './if-node.css';
+
+export function IfNode(props) {
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-if-node"
+      headerText="IF"
+      layoutType="standard"
+    />
+  );
+}

+ 55 - 0
src/pages/blueprint/node-renderer/index.js

@@ -0,0 +1,55 @@
+/**
+ * Node Renderer 统一导出
+ * 提供所有节点类型的组件和工具函数
+ */
+
+// 导出基础节点渲染器
+export { NodeRenderer } from './node-renderer.jsx';
+
+// 导出各类节点组件
+export { BeginNode } from './begin/begin-node.jsx';
+export { SetNode } from './set/set-node.jsx';
+export { GetNode } from './get/get-node.jsx';
+export { FuncNode } from './func/func-node.jsx';
+export { EchoNode } from './echo/echo-node.jsx';
+
+// 导出节点逻辑函数
+export * from './begin/begin-node.js';
+export * from './set/set-node.js';
+export * from './get/get-node.js';
+export * from './func/func-node.js';
+export * from './echo/echo-node.js';
+
+// 导出公共工具函数
+export * from './node-renderer.js';
+
+/**
+ * 根据节点类型获取对应的渲染组件
+ * @param {Object} node - 节点数据
+ * @returns {Function} 节点组件
+ */
+export function getNodeComponent(node) {
+  if (!node) return null;
+  
+  // Begin 节点
+  if (node.type === 'begin') {
+    return BeginNode;
+  }
+  
+  // 变量节点
+  if (node.type === 'variable') {
+    if (node.varMode === 'set') {
+      return SetNode;
+    } else {
+      return GetNode;
+    }
+  }
+  
+  // Echo 节点
+  if (node.type === 'echo') {
+    return EchoNode;
+  }
+  
+  // 其他功能节点
+  return FuncNode;
+}

+ 129 - 10
src/pages/blueprint/node-renderer/node-renderer.css

@@ -1,15 +1,58 @@
+/* ========== 公共节点样式(所有节点都会继承) ========== */
+/* 所有节点的基础交互和事件处理 */
+.Blueprint-node,
+.Blueprint-variable-node,
+.Blueprint-variable-set-node,
+.Blueprint-begin-node,
+.Blueprint-set-node,
+.Blueprint-get-node,
+.Blueprint-echo-node,
+.Blueprint-func-node,
+.Blueprint-schedule-node,
+.Blueprint-while-node,
+.Blueprint-if-node,
+.Blueprint-delay-node {
+  /* 基础定位和尺寸 */
+  position: absolute;
+  width: fit-content;
+  overflow: visible;
+  
+  /* 事件处理(公共逻辑) */
+  pointer-events: auto; /* 节点本身可以接收事件 */
+  user-select: none; /* 防止文本被选中 */
+  cursor: move; /* 鼠标悬停显示可拖动 */
+  z-index: 10; /* 确保节点在连线之上 */
+}
+
+/* 所有节点的子元素(header、body、execution等)默认不拦截事件 */
+.Blueprint-node > *:not(.Blueprint-node-port),
+.Blueprint-variable-node > *:not(.Blueprint-node-port),
+.Blueprint-variable-set-node > *:not(.Blueprint-node-port),
+.Blueprint-begin-node > *:not(.Blueprint-node-port),
+.Blueprint-set-node > *:not(.Blueprint-node-port),
+.Blueprint-get-node > *:not(.Blueprint-node-port),
+.Blueprint-echo-node > *:not(.Blueprint-node-port),
+.Blueprint-func-node > *:not(.Blueprint-node-port),
+.Blueprint-schedule-node > *:not(.Blueprint-node-port),
+.Blueprint-while-node > *:not(.Blueprint-node-port),
+.Blueprint-if-node > *:not(.Blueprint-node-port),
+.Blueprint-delay-node > *:not(.Blueprint-node-port) {
+  pointer-events: none; /* 让事件传递到父节点 */
+  user-select: none; /* 防止文本被选中 */
+}
+
+/* 所有端口可以接收事件(用于连线) */
+.Blueprint-node-port {
+  pointer-events: auto !important; /* 端口可以接收事件 */
+}
+
 /* 流程节点样式 */
 .Blueprint-node {
-  position: absolute; /* 节点在画布上的位置需要绝对定位 */
   min-width: 180px;
-  width: fit-content; /* 宽度自适应内部内容 */
   background: #2d2d30;
   border: 2px solid #007acc;
   border-radius: 4px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
-  user-select: none;
-  cursor: move;
-  overflow: visible; /* 确保端口箭头不被裁剪 */
   display: flex;
   flex-direction: column;
   align-items: stretch;
@@ -22,17 +65,12 @@
 
 /* 变量节点样式(圆角矩形,蓝色发光轮廓,扁平设计) */
 .Blueprint-variable-node {
-  position: absolute; /* 节点在画布上的位置需要绝对定位 */
   min-width: 140px;
-  width: fit-content; /* 宽度自适应内部内容 */
   height: 40px; /* 固定高度,更扁平 */
   background: #2d2d30;
   border: 2px solid #2196f3;
   border-radius: 12px; /* 圆角矩形 */
   box-shadow: 0 0 8px rgba(33, 150, 243, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3); /* 蓝色发光效果 */
-  user-select: none;
-  cursor: move;
-  overflow: visible;
   padding: 0 12px; /* 左右padding让内容有间距 */
   display: flex;
   flex-direction: row;
@@ -45,6 +83,85 @@
   box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(33, 150, 243, 0.4);
 }
 
+/* SET 变量节点样式(类似流程节点,但更紧凑) */
+.Blueprint-variable-set-node {
+  min-width: 160px;
+  background: #2d2d30;
+  border: 2px solid #2196f3;
+  border-radius: 8px;
+  box-shadow: 0 0 8px rgba(33, 150, 243, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-variable-set-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(33, 150, 243, 0.4);
+}
+
+/* SET 节点标题 */
+.Blueprint-variable-set-header {
+  background: linear-gradient(135deg, #424242 0%, #303030 100%);
+  color: #fff;
+  padding: 8px 16px;
+  font-size: 14px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 6px 6px 0 0;
+  letter-spacing: 1px;
+}
+
+/* SET 节点执行箭头区域 */
+.Blueprint-variable-set-execution {
+  height: 32px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}
+
+.Blueprint-variable-set-execution .Blueprint-node-port {
+  position: relative;
+}
+
+.Blueprint-variable-set-execution .Blueprint-node-port.input.port-execution {
+  left: auto;
+  top: auto;
+}
+
+.Blueprint-variable-set-execution .Blueprint-node-port.output.port-execution {
+  right: auto;
+  top: auto;
+  margin-left: auto;
+}
+
+/* SET 节点数据端口区域 */
+.Blueprint-variable-set-data {
+  padding: 8px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  position: relative;
+  min-height: 28px;
+}
+
+.Blueprint-variable-set-data .Blueprint-node-port {
+  position: relative;
+  left: auto;
+  right: auto;
+}
+
+.Blueprint-variable-set-data .Blueprint-node-port.input {
+  flex-direction: row;
+  align-items: center;
+  gap: 6px;
+}
+
 .Blueprint-variable-node-label {
   color: #fff;
   font-size: 13px;
@@ -193,6 +310,7 @@
   border-style: solid;
   display: flex;
   flex-shrink: 0;
+  pointer-events: none; /* 箭头不拦截事件,让父元素(端口)处理 */
 }
 
 /* 执行输入端口箭头(在左侧,指向右,进入节点) */
@@ -232,6 +350,7 @@
   display: flex;
   align-items: center;
   justify-content: center;
+  pointer-events: none; /* 圆点不拦截事件,让父元素(端口)处理 */
 }
 
 /* 实心端口(已连接) */

+ 30 - 4
src/pages/blueprint/node-renderer/node-renderer.js

@@ -8,11 +8,23 @@
  * @returns {number} 节点高度(像素)
  */
 export function calculateNodeHeight(node) {
-  if (!node || node.type === 'variable') {
-    // 变量节点固定高度
+  if (!node) {
+    return 40;
+  }
+  
+  // GET 变量节点固定高度
+  if (node.type === 'variable' && node.varMode !== 'set') {
     return 40;
   }
   
+  // SET 变量节点:标题 + 执行箭头区域 + 数据端口区域
+  if (node.type === 'variable' && node.varMode === 'set') {
+    const headerHeight = 32; // 标题区域
+    const executionHeight = 32; // 执行箭头区域
+    const dataHeight = 36; // 数据端口区域
+    return headerHeight + executionHeight + dataHeight;
+  }
+  
   const headerHeight = 40; // 区域1:标题区域高度
   const executionHeight = 40; // 区域2:执行箭头区域(固定高度)
   const paramsPadding = 12 * 2; // 区域3:参数区域上下padding
@@ -244,7 +256,16 @@ export function calculatePortPosition(index, portType = 'data', executionPortCou
  * 获取节点类名
  */
 export function getNodeClassName(isSelected, node) {
-  const baseClass = node?.type === 'variable' ? 'Blueprint-variable-node' : 'Blueprint-node';
+  let baseClass = 'Blueprint-node';
+  
+  if (node?.type === 'variable') {
+    if (node.varMode === 'set') {
+      baseClass = 'Blueprint-variable-set-node';
+    } else {
+      baseClass = 'Blueprint-variable-node';
+    }
+  }
+  
   return `${baseClass} ${isSelected ? 'selected' : ''}`;
 }
 
@@ -370,7 +391,11 @@ export function separatePorts(ports) {
  */
 export function handleNodeMouseDown(e, onNodeMouseDown, nodeId) {
   const portElement = e.target.closest('.Blueprint-node-port');
-  if (portElement) return;
+  if (portElement) {
+    // 如果点击的是端口,也要阻止事件冒泡到画布
+    e.stopPropagation();
+    return;
+  }
   // 阻止事件冒泡,防止触发画布平移
   e.stopPropagation();
   e.preventDefault();
@@ -397,6 +422,7 @@ export function handleNodeDoubleClick(e, onNodeDoubleClick, nodeId) {
  */
 export function handlePortMouseDown(e, onPortMouseDown, nodeId, portId) {
   e.stopPropagation();
+  e.preventDefault();
   onPortMouseDown?.(e, nodeId, portId);
 }
 

+ 209 - 207
src/pages/blueprint/node-renderer/node-renderer.jsx

@@ -1,18 +1,21 @@
 /**
- * 节点渲染组件(方形节点、输入/输出端口)
+ * 节点渲染器(父类/基类组件)
+ * 所有节点类型都使用此组件,通过配置来实现不同的节点类型
+ * 
+ * 核心设计思想:
+ * - 所有公共逻辑(事件处理、端口渲染、样式计算)都在此实现
+ * - 子类只需要提供配置信息(className、标题文本、布局类型)
+ * - 节点的差异主要体现在:端口数量(通过node.inputs/outputs定义)、节点尺寸(通过CSS控制)、样式主题(通过className控制)
  */
 
 import './node-renderer.css';
 import { 
   getNodeStyle, 
-  calculatePortPosition, 
   getNodeClassName, 
   getPortTypeClass, 
-  getPortStyle,
   isPortConnected,
   getPortDefaultValue,
   formatOutputLabel,
-  separatePorts,
   handleNodeMouseDown,
   handleNodeDoubleClick,
   handlePortMouseDown,
@@ -21,231 +24,230 @@ import {
   validateNodeCoordinates
 } from './node-renderer.js';
 
-export function NodeRenderer({ node, isSelected, connections = [], onPortMouseDown, onPortMouseUp, onNodeMouseDown, onNodeDoubleClick, onPortValueChange }) {
+/**
+ * 节点渲染器组件
+ * @param {Object} props - 组件属性
+ * @param {Object} props.node - 节点数据(包含inputs/outputs定义端口数量)
+ * @param {boolean} props.isSelected - 是否选中
+ * @param {Array} props.connections - 连接数组
+ * @param {string} props.className - 额外的CSS类名(用于子类样式)
+ * @param {string} props.headerText - 标题文本(如果不提供,使用node.label)
+ * @param {boolean} props.showHeader - 是否显示标题(GET节点不显示)
+ * @param {Function} props.renderCustomBody - 自定义主体渲染(可选,用于特殊节点如Begin)
+ * @param {string} props.layoutType - 布局类型:'standard'(标准), 'variable'(GET变量), 'set-variable'(SET变量)
+ */
+export function NodeRenderer({ 
+  node, 
+  isSelected, 
+  connections = [], 
+  onPortMouseDown, 
+  onPortMouseUp, 
+  onNodeMouseDown, 
+  onNodeDoubleClick,
+  onPortValueChange,
+  className = '',
+  headerText,
+  showHeader = true,
+  renderCustomBody,
+  layoutType = 'standard'
+}) {
   if (!node) return null;
   
   validateNodeCoordinates(node);
   
   const nodeStyle = getNodeStyle(node);
   const nodeClassName = getNodeClassName(isSelected, node);
+  const finalClassName = className ? `${nodeClassName} ${className}` : nodeClassName;
+  
+  // 分离端口类型(通过node.inputs/outputs获取端口数量和类型)
+  const executionInputs = node.inputs?.filter(p => p.type === 'execution') || [];
+  const executionOutputs = node.outputs?.filter(p => p.type === 'execution') || [];
+  const dataInputs = node.inputs?.filter(p => p.type !== 'execution') || [];
+  const dataOutputs = node.outputs?.filter(p => p.type !== 'execution') || [];
   
   return (
     <div
-      className={nodeClassName}
+      className={finalClassName}
       style={nodeStyle}
       data-node-id={node.id}
       onMouseDown={(e) => handleNodeMouseDown(e, onNodeMouseDown, node.id)}
       onDoubleClick={(e) => handleNodeDoubleClick(e, onNodeDoubleClick, node.id)}
     >
-      {node.type === 'variable' ? (
-        <div className="Blueprint-variable-node-label">
-          {node.varMode === 'set' ? `Set ${node.varName || node.label}` : (node.label || node.varName || node.type)}
+      {/* 标题栏(GET节点不显示) */}
+      {showHeader && renderHeader(headerText || node.label || node.type, className)}
+      
+      {/* 自定义主体内容(可选,用于特殊节点) */}
+      {renderCustomBody && renderCustomBody(node)}
+      
+      {/* 根据布局类型渲染端口 */}
+      {layoutType === 'variable' ? (
+        // GET变量节点布局:只有输出数据端口,端口在右侧中间
+        // 变量名直接显示在节点中央
+        <div className="Blueprint-variable-node-body">
+          <span className="Blueprint-get-node-label">{headerText || node.label || node.varName || 'Variable'}</span>
+          {dataOutputs.map(port => (
+            <div key={port.id} style={{ position: 'absolute', right: '-8px', top: '50%', transform: 'translateY(-50%)' }}>
+              {renderPortWithoutLabel(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp })}
+            </div>
+          ))}
         </div>
-      ) : (
-        <div className="Blueprint-node-header">
-          {node.label || node.type}
-        </div>
-      )}
-      {node.type === 'variable' ? (
-        /* 变量节点:根据 varMode 决定端口位置 */
-        /* Get 节点:只有右侧输出端口 */
-        /* Set 节点:只有左侧输入端口 */
+      ) : layoutType === 'set-variable' ? (
+        // SET变量节点布局:有执行端口区域和数据端口区域
         <>
-          <div className="Blueprint-variable-node-body">
-            {/* Set 节点的输入端口(左侧) */}
-            {node.varMode === 'set' && node.inputs && node.inputs.length > 0 && node.inputs.map((input, index) => {
-              const portTypeClass = getPortTypeClass(input);
-              const isConnected = isPortConnected(node.id, input.id, connections);
-              return (
-                <div
-                  key={input.id}
-                  className={`Blueprint-node-port input ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                  style={{ left: '-8px', top: '50%', transform: 'translateY(-50%)' }}
-                  data-port-type={input.type}
-                  data-param-type={input.paramType || ''}
-                  onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, input.id)}
-                  onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, input.id)}
-                  title={input.label || input.id}
-                >
-                  <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
-                </div>
-              );
-            })}
-            {/* Get 节点的输出端口(右侧) */}
-            {node.varMode === 'get' && node.outputs && node.outputs.length > 0 && node.outputs.map((output, index) => {
-              const portTypeClass = getPortTypeClass(output);
-              const isConnected = isPortConnected(node.id, output.id, connections);
-              return (
-                <div
-                  key={output.id}
-                  className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                  style={{ right: '-8px', top: '50%', transform: 'translateY(-50%)' }}
-                  data-port-type={output.type}
-                  data-param-type={output.paramType || ''}
-                  onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
-                  onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
-                  title={output.label || output.id}
-                >
-                  <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
-                </div>
-              );
-            })}
-            {/* 兼容旧的变量节点(没有 varMode 的情况,默认为 get) */}
-            {!node.varMode && node.outputs && node.outputs.length > 0 && node.outputs.map((output, index) => {
-              const portTypeClass = getPortTypeClass(output);
-              const isConnected = isPortConnected(node.id, output.id, connections);
-              return (
-                <div
-                  key={output.id}
-                  className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                  style={{ right: '-8px', top: '50%', transform: 'translateY(-50%)' }}
-                  data-port-type={output.type}
-                  data-param-type={output.paramType || ''}
-                  onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
-                  onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
-                  title={output.label || output.id}
-                >
-                  <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
-                </div>
-              );
-            })}
+          {/* 执行端口区域 */}
+          <div className="Blueprint-variable-set-execution">
+            {executionInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
+            {executionOutputs.map(port => renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp }))}
+          </div>
+          {/* 数据端口区域 */}
+          <div className="Blueprint-variable-set-data">
+            {dataInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
           </div>
         </>
       ) : (
-        /* 流程节点:分为三个区域 */
+        // 标准流程节点布局:执行端口区域 + 数据端口区域
         <>
-          {/* 区域2:执行箭头区域(固定高度) */}
-          <div className="Blueprint-node-execution">
-            {/* 输入执行端口 */}
-            {node.inputs && (() => {
-              const executionInputs = node.inputs.filter(input => input.type === 'execution');
-              return executionInputs.map((input, index) => {
-                const portTypeClass = getPortTypeClass(input);
-                const isConnected = isPortConnected(node.id, input.id, connections);
-                
-                return (
-                  <div
-                    key={input.id}
-                    className={`Blueprint-node-port input ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                    data-port-type={input.type}
-                    data-param-type={input.paramType || ''}
-                    onMouseDown={(e) => {
-                      e.stopPropagation();
-                      onPortMouseDown?.(e, node.id, input.id);
-                    }}
-                    onMouseUp={(e) => {
-                      e.stopPropagation();
-                      onPortMouseUp?.(e, node.id, input.id);
-                    }}
-                    title={input.label || input.id}
-                  >
-                    <div className="Blueprint-node-port-arrow execution-input"></div>
-                  </div>
-                );
-              });
-            })()}
-            {/* 输出执行端口 */}
-            {node.outputs && (() => {
-              const executionOutputs = node.outputs.filter(output => output.type === 'execution');
-              return executionOutputs.map((output, index) => {
-                const portTypeClass = getPortTypeClass(output);
-                const isConnected = isPortConnected(node.id, output.id, connections);
-                
-                return (
-                  <div
-                    key={output.id}
-                    className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                    data-port-type={output.type}
-                    data-param-type={output.paramType || ''}
-                    onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
-                    onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
-                    title={output.label || output.id}
-                  >
-                    <div className="Blueprint-node-port-arrow execution-output"></div>
-                  </div>
-                );
-              });
-            })()}
-          </div>
-          
-          {/* 区域3:参数区域 */}
-          <div className="Blueprint-node-params">
-            {/* 输入数据端口 */}
-            {node.inputs && (() => {
-              const dataInputs = node.inputs.filter(input => input.type !== 'execution');
-              
-              return dataInputs.map((input, dataIndex) => {
-                const portTypeClass = getPortTypeClass(input);
-                const isConnected = isPortConnected(node.id, input.id, connections);
-                const isDataPort = input.type === 'data';
-                const defaultValue = isDataPort ? getPortDefaultValue(input, node) : '';
-                
-                return (
-                  <div
-                    key={input.id}
-                    className={`Blueprint-node-port input ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                    data-port-type={input.type}
-                    data-param-type={input.paramType || ''}
-                    onMouseDown={(e) => {
-                      if (e.target.tagName === 'INPUT') {
-                        e.stopPropagation();
-                        return;
-                      }
-                      e.stopPropagation();
-                      onPortMouseDown?.(e, node.id, input.id);
-                    }}
-                    onMouseUp={(e) => {
-                      e.stopPropagation();
-                      onPortMouseUp?.(e, node.id, input.id);
-                    }}
-                    title={input.label || input.id}
-                  >
-                    <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
-                    <span className="Blueprint-node-port-label">{input.label}</span>
-                    {isDataPort && !isConnected && (
-                      <input
-                        type="text"
-                        className="Blueprint-node-port-input"
-                        value={defaultValue}
-                        placeholder={input.paramType === 'int' ? '0' : '""'}
-                        onChange={(e) => handleInputChange(e, onPortValueChange, node.id, input.id, input.paramName)}
-                        onClick={(e) => e.stopPropagation()}
-                        onMouseDown={(e) => e.stopPropagation()}
-                      />
-                    )}
-                  </div>
-                );
-              });
-            })()}
-            
-            {/* 输出数据端口 */}
-            {node.outputs && (() => {
-              const dataOutputs = node.outputs.filter(output => output.type !== 'execution');
-              
-              return dataOutputs.map((output, dataIndex) => {
-                const portTypeClass = getPortTypeClass(output);
-                const isConnected = isPortConnected(node.id, output.id, connections);
-                const displayLabel = formatOutputLabel(output.label);
-                
-                return (
-                  <div
-                    key={output.id}
-                    className={`Blueprint-node-port output ${portTypeClass} ${isConnected ? 'connected' : 'disconnected'}`}
-                    data-port-type={output.type}
-                    data-param-type={output.paramType || ''}
-                    onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, node.id, output.id)}
-                    onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, node.id, output.id)}
-                    title={output.label || output.id}
-                  >
-                    <div className={`Blueprint-node-port-dot ${isConnected ? 'solid' : 'hollow'}`}></div>
-                    {displayLabel && <span className="Blueprint-node-port-label">{displayLabel}</span>}
-                  </div>
-                );
-              });
-            })()}
-          </div>
+          {/* 执行端口区域 */}
+          {(executionInputs.length > 0 || executionOutputs.length > 0) && (
+            <div className="Blueprint-node-execution">
+              {executionInputs.map(port => renderPort(port, node.id, true, connections, { onPortMouseDown, onPortMouseUp }))}
+              {executionOutputs.map(port => renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp }))}
+            </div>
+          )}
+          {/* 数据端口区域 */}
+          {(dataInputs.length > 0 || dataOutputs.length > 0) && (
+            <div className="Blueprint-node-params">
+              {dataInputs.map(port => (
+                <div key={port.id}>{renderDataPort(port, node, true, connections, { onPortMouseDown, onPortMouseUp, onPortValueChange })}</div>
+              ))}
+              {dataOutputs.map(port => (
+                <div key={port.id}>{renderPort(port, node.id, false, connections, { onPortMouseDown, onPortMouseUp })}</div>
+              ))}
+            </div>
+          )}
+        </>
+      )}
+    </div>
+  );
+}
+
+/**
+ * 渲染标题(公共方法)
+ */
+function renderHeader(text, nodeClassName) {
+  // 根据节点类型使用不同的标题样式类
+  const headerClass = nodeClassName.includes('begin') ? 'Blueprint-begin-node-header' :
+                      nodeClassName.includes('set-node') ? 'Blueprint-set-node-header' :
+                      nodeClassName.includes('get-node') ? 'Blueprint-get-node-label' :
+                      nodeClassName.includes('echo') ? 'Blueprint-echo-node-header' :
+                      nodeClassName.includes('schedule') ? 'Blueprint-schedule-node-header' :
+                      nodeClassName.includes('while') ? 'Blueprint-while-node-header' :
+                      nodeClassName.includes('if') ? 'Blueprint-if-node-header' :
+                      nodeClassName.includes('delay') ? 'Blueprint-delay-node-header' :
+                      'Blueprint-node-header';
+  
+  return (
+    <div className={headerClass}>
+      {text}
+    </div>
+  );
+}
+
+/**
+ * 渲染单个端口(公共方法)
+ */
+function renderPort(port, nodeId, isInput, connections, handlers) {
+  const { onPortMouseDown, onPortMouseUp } = handlers;
+  const portTypeClass = getPortTypeClass(port);
+  const connected = isPortConnected(nodeId, port.id, connections);
+  
+  return (
+    <div
+      key={port.id}
+      className={`Blueprint-node-port ${isInput ? 'input' : 'output'} ${portTypeClass} ${connected ? 'connected' : 'disconnected'}`}
+      data-port-type={port.type}
+      data-param-type={port.paramType || ''}
+      onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, nodeId, port.id)}
+      onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, nodeId, port.id)}
+      title={port.label || port.id}
+    >
+      {port.type === 'execution' ? (
+        <div className={`Blueprint-node-port-arrow ${isInput ? 'execution-input' : 'execution-output'}`}></div>
+      ) : (
+        <>
+          <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
+          {port.label && <span className="Blueprint-node-port-label">{formatOutputLabel(port.label)}</span>}
         </>
       )}
     </div>
   );
 }
+
+/**
+ * 渲染单个端口(不显示标签,用于GET变量节点)
+ */
+function renderPortWithoutLabel(port, nodeId, isInput, connections, handlers) {
+  const { onPortMouseDown, onPortMouseUp } = handlers;
+  const portTypeClass = getPortTypeClass(port);
+  const connected = isPortConnected(nodeId, port.id, connections);
+  
+  return (
+    <div
+      key={port.id}
+      className={`Blueprint-node-port ${isInput ? 'input' : 'output'} ${portTypeClass} ${connected ? 'connected' : 'disconnected'}`}
+      data-port-type={port.type}
+      data-param-type={port.paramType || ''}
+      onMouseDown={(e) => handlePortMouseDown(e, onPortMouseDown, nodeId, port.id)}
+      onMouseUp={(e) => handlePortMouseUp(e, onPortMouseUp, nodeId, port.id)}
+      title={port.label || port.id}
+    >
+      <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
+    </div>
+  );
+}
+
+/**
+ * 渲染数据输入端口(带输入框)
+ */
+function renderDataPort(port, node, isInput, connections, handlers) {
+  const { onPortMouseDown, onPortMouseUp, onPortValueChange } = handlers;
+  const portTypeClass = getPortTypeClass(port);
+  const connected = isPortConnected(node.id, port.id, connections);
+  const defaultValue = getPortDefaultValue(port, node);
+  
+  return (
+    <div
+      key={port.id}
+      className={`Blueprint-node-port input ${portTypeClass} ${connected ? 'connected' : 'disconnected'}`}
+      data-port-type={port.type}
+      data-param-type={port.paramType || ''}
+      onMouseDown={(e) => {
+        if (e.target.tagName === 'INPUT') {
+          e.stopPropagation();
+          return;
+        }
+        e.stopPropagation();
+        onPortMouseDown?.(e, node.id, port.id);
+      }}
+      onMouseUp={(e) => {
+        e.stopPropagation();
+        onPortMouseUp?.(e, node.id, port.id);
+      }}
+      title={port.label || port.id}
+    >
+      <div className={`Blueprint-node-port-dot ${connected ? 'solid' : 'hollow'}`}></div>
+      <span className="Blueprint-node-port-label">{port.label}</span>
+      {!connected && (
+        <input
+          type="text"
+          className="Blueprint-node-port-input"
+          value={defaultValue}
+          placeholder={port.paramType === 'int' ? '0' : '""'}
+          onChange={(e) => handleInputChange(e, onPortValueChange, node.id, port.id, port.paramName)}
+          onClick={(e) => e.stopPropagation()}
+          onMouseDown={(e) => e.stopPropagation()}
+        />
+      )}
+    </div>
+  );
+}

+ 65 - 0
src/pages/blueprint/node-renderer/schedule/schedule-node.css

@@ -0,0 +1,65 @@
+/* Schedule 定时节点样式 */
+.Blueprint-schedule-node {
+  position: absolute;
+  min-width: 180px;
+  width: fit-content;
+  background: #2d2d30;
+  border: 2px solid #ff5722;
+  border-radius: 4px;
+  box-shadow: 0 0 8px rgba(255, 87, 34, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  user-select: none;
+  cursor: move;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-schedule-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(255, 87, 34, 0.4);
+}
+
+.Blueprint-schedule-node-header {
+  background: linear-gradient(135deg, #ff5722 0%, #e64a19 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 1px;
+}
+
+.Blueprint-schedule-node-body {
+  padding: 12px 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.Blueprint-schedule-node-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.Blueprint-schedule-node-icon {
+  font-size: 18px;
+}
+
+.Blueprint-schedule-node-text {
+  font-size: 12px;
+  color: #999;
+}
+
+.Blueprint-schedule-node-execution {
+  height: 40px;
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}

+ 23 - 0
src/pages/blueprint/node-renderer/schedule/schedule-node.js

@@ -0,0 +1,23 @@
+/**
+ * Schedule 定时节点逻辑
+ */
+
+/**
+ * 创建 Schedule 节点数据
+ */
+export function createScheduleNodeData(config = {}) {
+  return {
+    id: config.id || `node_schedule_${Date.now()}`,
+    type: 'schedule',
+    label: 'SCHEDULE',
+    inputs: [],
+    outputs: [
+      { id: 'output_0', label: '', type: 'execution' }
+    ],
+    data: {
+      interval: config.interval || 1000,
+      count: config.count || 1,
+      ...config.data
+    }
+  };
+}

+ 33 - 0
src/pages/blueprint/node-renderer/schedule/schedule-node.jsx

@@ -0,0 +1,33 @@
+/**
+ * Schedule 定时节点组件
+ * 
+ * 特点:
+ * - 只有1个输出执行端口
+ * - 没有输入端口
+ * - 橙色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './schedule-node.css';
+
+export function ScheduleNode(props) {
+  // 自定义主体内容
+  const renderCustomBody = (node) => (
+    <div className="Blueprint-schedule-node-body">
+      <div className="Blueprint-schedule-node-info">
+        <span className="Blueprint-schedule-node-icon">⏰</span>
+        <span className="Blueprint-schedule-node-text">定时执行</span>
+      </div>
+    </div>
+  );
+  
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-schedule-node"
+      headerText="SCHEDULE"
+      renderCustomBody={renderCustomBody}
+      layoutType="standard"
+    />
+  );
+}

+ 84 - 0
src/pages/blueprint/node-renderer/set/set-node.css

@@ -0,0 +1,84 @@
+/* Set 变量节点样式 */
+.Blueprint-set-node {
+  /* 公共逻辑已在 node-renderer.css 中定义 */
+  /* position, width, overflow, pointer-events, user-select, cursor, z-index */
+  
+  /* 节点特有样式 */
+  min-width: 160px;
+  background: #2d2d30;
+  border: 2px solid #2196f3;
+  border-radius: 8px;
+  box-shadow: 0 0 8px rgba(33, 150, 243, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-set-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(33, 150, 243, 0.4);
+}
+
+.Blueprint-set-node-header {
+  /* pointer-events 和 user-select 已在公共样式中定义 */
+  background: linear-gradient(135deg, #424242 0%, #303030 100%);
+  color: #fff;
+  padding: 8px 16px;
+  font-size: 14px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 6px 6px 0 0;
+  letter-spacing: 1px;
+}
+
+.Blueprint-set-node-execution {
+  /* pointer-events 已在公共样式中定义 */
+  height: 32px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  overflow: visible;
+  padding: 0 8px;
+}
+
+.Blueprint-set-node-execution .Blueprint-node-port {
+  /* pointer-events 已在公共样式中定义 */
+  position: relative;
+}
+
+.Blueprint-set-node-execution .Blueprint-node-port.input.port-execution {
+  left: auto;
+  top: auto;
+}
+
+.Blueprint-set-node-execution .Blueprint-node-port.output.port-execution {
+  right: auto;
+  top: auto;
+  margin-left: auto;
+}
+
+.Blueprint-set-node-data {
+  /* pointer-events 已在公共样式中定义 */
+  padding: 8px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  position: relative;
+  min-height: 28px;
+}
+
+.Blueprint-set-node-data .Blueprint-node-port {
+  /* pointer-events 已在公共样式中定义 */
+  position: relative;
+  left: auto;
+  right: auto;
+}
+
+.Blueprint-set-node-data .Blueprint-node-port.input {
+  flex-direction: row;
+  align-items: center;
+  gap: 6px;
+}

+ 58 - 0
src/pages/blueprint/node-renderer/set/set-node.js

@@ -0,0 +1,58 @@
+/**
+ * Set 变量节点逻辑
+ */
+
+/**
+ * 创建 Set 节点数据
+ * @param {string} varName - 变量名
+ * @param {string} varType - 变量类型
+ */
+export function createSetNodeData(varName, varType = 'string') {
+  return {
+    id: `var_set_${varName}`,
+    type: 'variable',
+    varMode: 'set',
+    varName,
+    label: varName,
+    inputs: [
+      { id: 'input_exec', label: '', type: 'execution' },
+      { id: 'input_value', label: varName, type: 'data', paramType: varType }
+    ],
+    outputs: [
+      { id: 'output_exec', label: '', type: 'execution' }
+    ],
+    data: {}
+  };
+}
+
+/**
+ * 验证 Set 节点
+ */
+export function validateSetNode(node) {
+  const errors = [];
+  
+  if (!node.varName) {
+    errors.push('Set节点必须指定变量名');
+  }
+  
+  const execInput = node.inputs?.find(p => p.type === 'execution');
+  const execOutput = node.outputs?.find(p => p.type === 'execution');
+  const dataInput = node.inputs?.find(p => p.type === 'data');
+  
+  if (!execInput) {
+    errors.push('Set节点必须有执行输入端口');
+  }
+  
+  if (!execOutput) {
+    errors.push('Set节点必须有执行输出端口');
+  }
+  
+  if (!dataInput) {
+    errors.push('Set节点必须有数据输入端口');
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors
+  };
+}

+ 23 - 0
src/pages/blueprint/node-renderer/set/set-node.jsx

@@ -0,0 +1,23 @@
+/**
+ * Set 变量节点组件
+ * 用于设置变量值
+ * 
+ * 特点:
+ * - 1个输入执行端口 + 1个输出执行端口
+ * - 1个输入数据端口(接收赋值)
+ * - 蓝色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './set-node.css';
+
+export function SetNode(props) {
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-set-node"
+      headerText="SET"
+      layoutType="set-variable"
+    />
+  );
+}

+ 74 - 0
src/pages/blueprint/node-renderer/while/while-node.css

@@ -0,0 +1,74 @@
+/* While 循环节点样式 */
+.Blueprint-while-node {
+  position: absolute;
+  min-width: 180px;
+  width: fit-content;
+  background: #2d2d30;
+  border: 2px solid #9c27b0;
+  border-radius: 4px;
+  box-shadow: 0 0 8px rgba(156, 39, 176, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
+  user-select: none;
+  cursor: move;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+}
+
+.Blueprint-while-node.selected {
+  border-color: #ffa500;
+  box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.3), 0 0 8px rgba(156, 39, 176, 0.4);
+}
+
+.Blueprint-while-node-header {
+  background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
+  color: #fff;
+  padding: 12px 16px;
+  font-size: 13px;
+  font-weight: 600;
+  text-align: center;
+  border-radius: 2px 2px 0 0;
+  letter-spacing: 1px;
+}
+
+.Blueprint-while-node-execution {
+  height: auto;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: flex-start;
+  position: relative;
+  overflow: visible;
+  padding: 8px;
+}
+
+.Blueprint-while-node-outputs {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  margin-left: auto;
+}
+
+.Blueprint-while-node-output {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 6px;
+}
+
+.Blueprint-while-node-output-label {
+  font-size: 11px;
+  color: #999;
+  min-width: 50px;
+  text-align: right;
+}
+
+.Blueprint-while-node-params {
+  padding: 8px 16px;
+  overflow: visible;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  position: relative;
+  min-height: 20px;
+}

+ 23 - 0
src/pages/blueprint/node-renderer/while/while-node.js

@@ -0,0 +1,23 @@
+/**
+ * While 循环节点逻辑
+ */
+
+/**
+ * 创建 While 节点数据
+ */
+export function createWhileNodeData(config = {}) {
+  return {
+    id: config.id || `node_while_${Date.now()}`,
+    type: 'while',
+    label: 'WHILE',
+    inputs: [
+      { id: 'input_0', label: '', type: 'execution' },
+      { id: 'input_condition', label: '条件', type: 'data', paramType: 'string' }
+    ],
+    outputs: [
+      { id: 'output_loop', label: 'Loop Body', type: 'execution' },
+      { id: 'output_completed', label: 'Completed', type: 'execution' }
+    ],
+    data: config.data || {}
+  };
+}

+ 23 - 0
src/pages/blueprint/node-renderer/while/while-node.jsx

@@ -0,0 +1,23 @@
+/**
+ * While 循环节点组件
+ * 
+ * 特点:
+ * - 1个输入执行端口
+ * - 2个输出执行端口(Loop Body、Completed)
+ * - 1个条件数据输入端口
+ * - 紫色主题
+ */
+
+import { NodeRenderer } from '../node-renderer.jsx';
+import './while-node.css';
+
+export function WhileNode(props) {
+  return (
+    <NodeRenderer
+      {...props}
+      className="Blueprint-while-node"
+      headerText="WHILE"
+      layoutType="standard"
+    />
+  );
+}

+ 23 - 9
src/pages/blueprint/utils/node-operations.js

@@ -281,7 +281,7 @@ function getNodeLabelFromType(nodeType, data = {}) {
  * @param {*} varValue - 变量值
  * @param {number} x - X 坐标
  * @param {number} y - Y 坐标
- * @param {string} mode - 模式:'get'(只有输出端口)或 'set'(有输入端口)
+ * @param {string} mode - 模式:'get'(只有输出端口)或 'set'(有执行端口和数据输入端口)
  * @param {string} customId - 自定义节点 ID(可选,用于确保 ID 一致性)
  * @returns {Object} 变量节点对象
  */
@@ -294,7 +294,7 @@ export function createVariableNode(varName, varValue, x, y, mode = 'get', custom
   let outputs = [];
   
   if (mode === 'get') {
-    // Get 节点:只有右侧输出端口
+    // Get 节点:只有右侧输出数据端口
     outputs = [{
       id: 'output_0',
       label: varName,
@@ -302,13 +302,27 @@ export function createVariableNode(varName, varValue, x, y, mode = 'get', custom
       paramType: varType
     }];
   } else if (mode === 'set') {
-    // Set 节点:只有左侧输入端口
-    inputs = [{
-      id: 'input_0',
-      label: '',
-      type: 'data',
-      paramType: varType
-    }];
+    // Set 节点:有执行输入/输出端口 + 左侧数据输入端口
+    inputs = [
+      {
+        id: 'input_exec',
+        label: '',
+        type: 'execution'
+      },
+      {
+        id: 'input_value',
+        label: varName,
+        type: 'data',
+        paramType: varType
+      }
+    ];
+    outputs = [
+      {
+        id: 'output_exec',
+        label: '',
+        type: 'execution'
+      }
+    ];
   }
   
   return {

+ 91 - 8
src/pages/blueprint/utils/workflow-converter.js

@@ -244,10 +244,13 @@ export function analyzeVariableReferences(processNodes) {
  * @param {Array} processNodes - 流程节点数组
  * @param {Array} variableNodes - 变量节点数组(包含 Get 和 Set 节点)
  * @param {Object} workflow - 工作流对象(包含 variables)
- * @returns {Array} 连接数组
+ * @param {Array} existingConnections - 现有的执行连接数组(可选,用于修改执行流程)
+ * @returns {Object} {dataConnections: [], executionConnections: [], modifiedConnections: []} - 数据连接、新执行连接和需要修改的连接
  */
-export function createVariableConnections(processNodes, variableNodes, workflow) {
-  const connections = [];
+export function createVariableConnections(processNodes, variableNodes, workflow, existingConnections = []) {
+  const dataConnections = [];
+  const executionConnections = [];
+  const modifiedConnections = [];
   
   // 创建变量节点映射(按变量名和模式分类)
   // getNodes: Map<varName, varNode> - Get 节点(用于输入)
@@ -276,6 +279,22 @@ export function createVariableConnections(processNodes, variableNodes, workflow)
     return null;
   };
   
+  // 构建执行连接映射:source -> [targets]
+  const executionConnMap = new Map();
+  existingConnections.forEach(conn => {
+    const sourcePort = processNodes.find(n => n.id === conn.source)?.outputs?.find(p => p.id === conn.sourcePort);
+    const targetPort = processNodes.find(n => n.id === conn.target)?.inputs?.find(p => p.id === conn.targetPort);
+    if (sourcePort?.type === 'execution' && targetPort?.type === 'execution') {
+      if (!executionConnMap.has(conn.source)) {
+        executionConnMap.set(conn.source, []);
+      }
+      executionConnMap.get(conn.source).push({
+        target: conn.target,
+        connectionId: conn.id
+      });
+    }
+  });
+  
   // 遍历所有流程节点
   processNodes.forEach(processNode => {
     if (processNode.type === 'begin' || !processNode.data) {
@@ -300,7 +319,7 @@ export function createVariableConnections(processNodes, variableNodes, workflow)
         const getNode = getNodes.get(varName);
         if (getNode && getNode.outputs && getNode.outputs.length > 0) {
           const varOutputPort = getNode.outputs[0];
-          connections.push({
+          dataConnections.push({
             id: `conn_var_in_${processNode.id}_${inputPort.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
             source: getNode.id,
             target: processNode.id,
@@ -325,23 +344,87 @@ export function createVariableConnections(processNodes, variableNodes, workflow)
         const varName = extractVarName(value);
         if (!varName) return;
         
-        // 从流程节点的输出端口连接到 Set 节点
+        // 从流程节点的数据输出端口连接到 Set 节点的数据输入端口
         const setNode = setNodes.get(varName);
         if (setNode && setNode.inputs && setNode.inputs.length > 0) {
-          const varInputPort = setNode.inputs[0];
-          connections.push({
+          // 找到数据输入端口(不是执行端口)
+          const varInputPort = setNode.inputs.find(input => input.type === 'data');
+          if (!varInputPort) {
+            console.warn('SET 节点找不到数据输入端口:', {
+              varName,
+              setNodeId: setNode.id,
+              setNodeInputs: setNode.inputs.map(p => ({ id: p.id, type: p.type }))
+            });
+            return; // 在 forEach 中使用 return 跳过当前迭代
+          }
+          
+          console.log('创建 SET 节点数据连接:', {
+            source: processNode.id,
+            sourcePort: outputPort.id,
+            target: setNode.id,
+            targetPort: varInputPort.id,
+            varName
+          });
+          
+          dataConnections.push({
             id: `conn_var_out_${processNode.id}_${outputPort.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
             source: processNode.id,
             target: setNode.id,
             sourcePort: outputPort.id,
             targetPort: varInputPort.id
           });
+          
+          // 创建执行连接:流程节点执行输出 → SET 节点执行输入
+          const execOutputPort = processNode.outputs?.find(p => p.type === 'execution');
+          const execInputPort = setNode.inputs?.find(p => p.type === 'execution');
+          if (execOutputPort && execInputPort) {
+            // 检查是否已有从该流程节点到其他节点的执行连接
+            const nextNodes = executionConnMap.get(processNode.id) || [];
+            
+            // 创建从流程节点到 SET 节点的执行连接
+            executionConnections.push({
+              id: `conn_exec_to_set_${processNode.id}_${setNode.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+              source: processNode.id,
+              target: setNode.id,
+              sourcePort: execOutputPort.id,
+              targetPort: execInputPort.id
+            });
+            
+            // 创建从 SET 节点到下一个节点的执行连接
+            const setExecOutputPort = setNode.outputs?.find(p => p.type === 'execution');
+            if (setExecOutputPort && nextNodes.length > 0) {
+              nextNodes.forEach(nextNode => {
+                const nextExecInputPort = processNodes.find(n => n.id === nextNode.target)?.inputs?.find(p => p.type === 'execution');
+                if (nextExecInputPort) {
+                  executionConnections.push({
+                    id: `conn_exec_from_set_${setNode.id}_${nextNode.target}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+                    source: setNode.id,
+                    target: nextNode.target,
+                    sourcePort: setExecOutputPort.id,
+                    targetPort: nextExecInputPort.id
+                  });
+                  // 标记需要删除的原连接
+                  modifiedConnections.push(nextNode.connectionId);
+                }
+              });
+            }
+          }
         }
       });
     }
   });
   
-  return connections;
+  // 为了向后兼容,如果 existingConnections 为空,直接返回数据连接数组
+  if (existingConnections.length === 0) {
+    return dataConnections;
+  }
+  
+  // 返回所有连接(需要调用者处理修改的连接)
+  return {
+    dataConnections,
+    executionConnections,
+    modifiedConnections
+  };
 }
 
 /**

+ 9 - 32
static/processing/测试/bp.json

@@ -1,43 +1,20 @@
 {
 	"nodePositions": {
 		"node_begin": {
-			"x": 150,
-			"y": 100
+			"x": -120,
+			"y": 180
 		},
 		"node_0": {
-			"x": 150,
-			"y": 300
+			"x": 200,
+			"y": 180
 		},
 		"var_get_mini": {
-			"x": 150,
-			"y": 500
+			"x": -80,
+			"y": 360
 		},
 		"var_set_swipeDirection": {
-			"x": 600,
-			"y": 500
+			"x": 580,
+			"y": 320
 		}
-	},
-	"connections": [
-		{
-			"id": "conn_begin",
-			"source": "node_begin",
-			"target": "node_0",
-			"sourcePort": "output_0",
-			"targetPort": "input_0"
-		},
-		{
-			"id": "conn_var_in_node_0_input_param_min_1768726400654_j8er0pxs0",
-			"source": "var_get_mini",
-			"target": "node_0",
-			"sourcePort": "output_0",
-			"targetPort": "input_param_min"
-		},
-		{
-			"id": "conn_var_out_node_0_output_1_1768726400654_uzge68fg8",
-			"source": "node_0",
-			"target": "var_set_swipeDirection",
-			"sourcePort": "output_1",
-			"targetPort": "input_0"
-		}
-	]
+	}
 }