Vue2高级实战:打造企业级可视化拖拽编排系统

2025-07-24 0 712

Vue2高级实战:打造企业级可视化拖拽编排系统

一、架构设计

基于Vue2+SVG+自定义指令的编排引擎,支持200+节点实时渲染和智能连线

二、核心实现

1. 拖拽节点系统

// drag-node.js
export default {
  directives: {
    dragNode: {
      bind(el, binding, vnode) {
        let isDragging = false;
        let offsetX, offsetY;
        
        el.addEventListener('mousedown', (e) => {
          isDragging = true;
          const rect = el.getBoundingClientRect();
          offsetX = e.clientX - rect.left;
          offsetY = e.clientY - rect.top;
          
          document.addEventListener('mousemove', move);
          document.addEventListener('mouseup', up);
        });

        const move = (e) => {
          if (!isDragging) return;
          const x = e.clientX - offsetX;
          const y = e.clientY - offsetY;
          vnode.context.$set(binding.value.node, 'x', x);
          vnode.context.$set(binding.value.node, 'y', y);
        };

        const up = () => {
          isDragging = false;
          document.removeEventListener('mousemove', move);
          document.removeEventListener('mouseup', up);
        };
      }
    }
  },

  props: {
    nodes: {
      type: Array,
      required: true
    }
  },

  methods: {
    addNode(type) {
      this.nodes.push({
        id: `node_${Date.now()}`,
        type,
        x: 100,
        y: 100,
        outputs: []
      });
    }
  }
};

2. 智能连线引擎

// connection-engine.js
export default {
  data() {
    return {
      connections: [],
      tempLine: null
    };
  },

  methods: {
    startConnect(node, port) {
      this.tempLine = {
        from: { nodeId: node.id, portId: port.id },
        to: null
      };
    },

    endConnect(node, port) {
      if (!this.tempLine) return;
      
      this.tempLine.to = { nodeId: node.id, portId: port.id };
      this.connections.push(this.tempLine);
      this.tempLine = null;
    },

    renderConnection(conn) {
      const fromNode = this.nodes.find(n => n.id === conn.from.nodeId);
      const toNode = this.nodes.find(n => n.id === conn.to.nodeId);
      
      if (!fromNode || !toNode) return null;

      return {
        x1: fromNode.x + 200,
        y1: fromNode.y + 40,
        x2: toNode.x,
        y2: toNode.y + 40,
        path: this.calcBezierPath(
          fromNode.x + 200, fromNode.y + 40,
          toNode.x, toNode.y + 40
        )
      };
    },

    calcBezierPath(x1, y1, x2, y2) {
      const cp1x = x1 + (x2 - x1) / 2;
      const cp2x = x1 + (x2 - x1) / 2;
      return `M${x1},${y1} C${cp1x},${y1} ${cp2x},${y2} ${x2},${y2}`;
    }
  }
};

三、高级特性

1. 节点模板系统

// node-templates.js
export default {
  data() {
    return {
      nodeTypes: [
        {
          name: '开始节点',
          type: 'start',
          outputs: [{ id: 'output', name: '输出' }],
          template: `
            
            开始
          `
        },
        {
          name: 'API调用',
          type: 'api',
          inputs: [{ id: 'input', name: '输入' }],
          outputs: [{ id: 'output', name: '输出' }],
          template: `
            
            API调用
          `
        }
      ]
    };
  },

  methods: {
    renderNode(node) {
      const template = this.nodeTypes.find(t => t.type === node.type)?.template;
      return template || '';
    }
  }
};

2. 实时预览系统

// preview-engine.js
export default {
  computed: {
    compiledFlow() {
      const steps = [];
      let currentNode = this.nodes.find(n => n.type === 'start');
      
      while (currentNode) {
        steps.push({
          type: currentNode.type,
          config: currentNode.config
        });
        
        const connection = this.connections.find(
          conn => conn.from.nodeId === currentNode.id
        );
        
        if (!connection) break;
        
        currentNode = this.nodes.find(
          n => n.id === connection.to.nodeId
        );
      }
      
      return steps;
    }
  },

  methods: {
    executeFlow() {
      const flow = this.compiledFlow;
      // 执行流程逻辑...
      console.log('执行流程:', flow);
    }
  }
};

四、完整案例

<template>
  <div class="flow-designer">
    <svg width="100%" height="600" @click="tempLine = null">
      <!-- 渲染连接线 -->
      <path v-for="(conn, i) in connections" 
            :key="i"
            :d="renderConnection(conn).path"
            stroke="#666" 
            fill="transparent" />
            
      <!-- 渲染节点 -->
      <g v-for="node in nodes" 
         :key="node.id"
         :transform="`translate(${node.x},${node.y})`"
         v-drag-node="{ node }">
        <svg v-html="renderNode(node)" />
      </g>
    </svg>
    
    <button @click="executeFlow">执行流程</button>
  </div>
</template>

<script>
import mixins from './mixins';
export default {
  mixins: [
    mixins.dragNode,
    mixins.connectionEngine,
    mixins.nodeTemplates,
    mixins.previewEngine
  ],
  
  data() {
    return {
      nodes: [],
      connections: []
    };
  }
};
</script>
Vue2高级实战:打造企业级可视化拖拽编排系统
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 vue2 Vue2高级实战:打造企业级可视化拖拽编排系统 https://www.taomawang.com/web/vue2/623.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务