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>